Skip to content

Commit 1769770

Browse files
committed
feat: implement MainButton
1 parent 56aae86 commit 1769770

5 files changed

Lines changed: 132 additions & 1 deletion

File tree

packages/mini-app-react/src/context.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1-
import type { BackButton, Haptic, SettingsButton, Theme } from '@grom.js/mini-app'
1+
import type {
2+
BackButton,
3+
Haptic,
4+
MainButton,
5+
SettingsButton,
6+
Theme,
7+
} from '@grom.js/mini-app'
28
import type { Context } from 'react'
39
import { createContext } from 'react'
410

511
export interface MiniApp {
612
backButton: BackButton.BackButton
713
settingsButton: SettingsButton.SettingsButton
14+
mainButton: MainButton.MainButton
815
theme: Theme.Theme
916
haptic: Haptic.Haptic
1017
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { useEffect } from 'react'
2+
import { useMiniApp } from './useMiniApp'
3+
4+
export interface UseMainButtonOptions {
5+
text: string
6+
visible: boolean
7+
loading?: boolean
8+
active?: boolean
9+
shining?: boolean
10+
bgColor?: string | null
11+
textColor?: string | null
12+
onClick?: () => void
13+
}
14+
15+
export function useMainButton({
16+
text,
17+
visible,
18+
loading,
19+
active,
20+
shining,
21+
bgColor,
22+
textColor,
23+
onClick,
24+
}: UseMainButtonOptions): void {
25+
const { mainButton } = useMiniApp()
26+
useEffect(() => {
27+
mainButton.stateStore.setState(prev => ({
28+
text,
29+
visible,
30+
loading: loading ?? prev.loading,
31+
active: active ?? prev.active,
32+
shining: shining ?? prev.shining,
33+
bgColor: bgColor === undefined ? prev.bgColor : bgColor,
34+
textColor: textColor === undefined ? prev.textColor : textColor,
35+
}))
36+
}, [mainButton, text, visible, loading, active, shining, bgColor, textColor])
37+
useEffect(() => {
38+
if (onClick) {
39+
return mainButton.onClick(onClick)
40+
}
41+
}, [onClick])
42+
}

packages/mini-app/src/Events.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import type { ThemeParams } from './LaunchParams.ts'
66
* @see https://core.telegram.org/api/bots/webapps#incoming-events-client-to-mini-app
77
*/
88
export interface IncomingEventMap {
9+
main_button_pressed: void
10+
911
back_button_pressed: void
1012

1113
settings_button_pressed: void
@@ -33,6 +35,16 @@ export interface OutgoingEventMap {
3335

3436
web_app_ready: void
3537

38+
web_app_setup_main_button: {
39+
is_visible?: boolean
40+
is_active?: boolean
41+
text: string
42+
color?: string
43+
text_color?: string
44+
is_progress_visible?: boolean
45+
has_shine_effect?: boolean
46+
}
47+
3648
web_app_setup_back_button: {
3749
is_visible: boolean
3850
}

packages/mini-app/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * as InitParams from './InitParams.ts'
44
export * as LaunchParams from './LaunchParams.ts'
55
export * as BackButton from './modules/BackButton.ts'
66
export * as Haptic from './modules/Haptic.ts'
7+
export * as MainButton from './modules/MainButton.ts'
78
export * as SettingsButton from './modules/SettingsButton.ts'
89
export * as Theme from './modules/Theme.ts'
910
export * as SessionStorage from './SessionStorage.ts'
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { Bridge } from '../Bridge'
2+
import type { UnsubscribeFn } from '../internal/EventBus'
3+
import type { StoredState } from '../SessionStorage'
4+
import type { Theme } from './Theme'
5+
import { Effect, Store } from '@tanstack/store'
6+
7+
export interface MainButton {
8+
stateStore: Store<State>
9+
onClick: (listener: () => void) => UnsubscribeFn
10+
offClick: (listener: any) => void
11+
}
12+
13+
export interface State {
14+
text: string
15+
visible: boolean
16+
loading: boolean
17+
active: boolean
18+
shining: boolean
19+
bgColor: string | null
20+
textColor: string | null
21+
}
22+
23+
const INITIAL_STATE: State = {
24+
text: 'Continue',
25+
visible: false,
26+
active: true,
27+
shining: false,
28+
loading: false,
29+
bgColor: null,
30+
textColor: null,
31+
}
32+
33+
export const init = (options: {
34+
bridge: Bridge
35+
theme: Theme
36+
storedState: StoredState<State>
37+
}): MainButton => {
38+
const { bridge, theme, storedState } = options
39+
const stateStore = new Store<State>(storedState.load() ?? INITIAL_STATE)
40+
stateStore.subscribe(({ currentVal }) => {
41+
storedState.save(currentVal)
42+
})
43+
const effect = new Effect({
44+
fn: () => {
45+
const state = stateStore.state
46+
const palette = theme.paletteStore.state
47+
bridge.emit('web_app_setup_main_button', {
48+
text: state.text,
49+
is_visible: state.visible,
50+
is_active: state.active,
51+
is_progress_visible: state.loading,
52+
has_shine_effect: state.shining,
53+
color: state.bgColor ?? palette.button_color ?? '#2481cc',
54+
text_color: state.textColor ?? palette.button_text_color ?? '#ffffff',
55+
})
56+
},
57+
deps: [stateStore, theme.paletteStore],
58+
})
59+
effect.mount()
60+
return {
61+
stateStore,
62+
onClick: (listener) => {
63+
return bridge.on('main_button_pressed', listener)
64+
},
65+
offClick: (listener) => {
66+
bridge.off('main_button_pressed', listener)
67+
},
68+
}
69+
}

0 commit comments

Comments
 (0)