Skip to content

Instantly share code, notes, and snippets.

@slavafomin
Last active May 1, 2023 12:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slavafomin/f22c8e2e9317685b95b7cd6ce846499b to your computer and use it in GitHub Desktop.
Save slavafomin/f22c8e2e9317685b95b7cd6ce846499b to your computer and use it in GitHub Desktop.
TWA Main Button Emulation
import { WebApp } from '@grammyjs/web-app';
export function isTwa(): boolean {
return (
Boolean(WebApp.initData) ||
(WebApp.platform !== 'unknown')
);
}
import { WebApp } from '@grammyjs/web-app';
import { BehaviorSubject, Observable } from 'rxjs';
import { isTwa } from '../../common/is-twa.js';
/**
* {@link https://core.telegram.org/bots/webapps#mainbutton | MainButton}
*/
export interface TwaMainButtonState {
text: string;
color: string;
textColor: string;
isActive: boolean;
isVisible: boolean;
isProgressVisible: boolean;
}
const defaultState: TwaMainButtonState = {
text: 'CONTINUE',
color: (WebApp.themeParams.button_color ?? '#5AC8FB'),
textColor: (WebApp.themeParams.button_text_color ?? '#FFF'),
isActive: true,
isVisible: false,
isProgressVisible: false,
};
export interface TwaMainButtonServiceOptions {
initialState?: Partial<TwaMainButtonState>;
}
/**
* Controls the state of the TWA main button.
*/
export class TwaMainButtonService {
#state$: BehaviorSubject<TwaMainButtonState>;
constructor(options?: TwaMainButtonServiceOptions) {
this.#state$ = new BehaviorSubject({
...defaultState,
...(options?.initialState ?? {})
});
}
updateButtonState(
stateUpdate: Partial<TwaMainButtonState>
): void {
const currentState = this.#state$.value;
const newState: TwaMainButtonState = {
...currentState,
...stateUpdate,
};
this.#state$.next(newState);
if (isTwa()) {
this.#updateNativeButtonState(
currentState,
newState
);
}
}
getState(): Observable<TwaMainButtonState> {
return this.#state$.asObservable();
}
getStateSnapshot(): TwaMainButtonState {
return this.#state$.value;
}
#updateNativeButtonState(
currentState: TwaMainButtonState,
newState: TwaMainButtonState,
): void {
if (
currentState.isProgressVisible !==
newState.isProgressVisible
) {
if (newState.isProgressVisible) {
WebApp.MainButton.showProgress();
} else {
WebApp.MainButton.hideProgress();
}
}
WebApp.MainButton.setParams({
text: newState.text,
color: newState.color,
text_color: newState.textColor,
is_active: newState.isActive,
is_visible: newState.isVisible,
});
}
}
.root {
display: block;
position: absolute;
bottom: 0;
width: 100%;
height: 70px;
border: 0;
padding: 0;
line-height: 70px;
font-size: 20px;
opacity: 0;
transition: opacity ease-in-out 1s;
cursor: pointer;
&:disabled {
cursor: not-allowed;
}
}
.isVisible {
opacity: 1;
}
.isProgressVisible {
}
import classes from './twa-main-button.scss';
import type { CSSProperties, FunctionComponent } from 'react';
import React from 'react';
import classNames from 'classnames';
import { useService } from '../../di/use-service.js';
import { TwaMainButtonService } from './twa-main-button-service.js';
import { useObservable } from '../../common/use-observable.js';
import { isTwa } from '../../common/is-twa.js';
export const TwaMainButton: FunctionComponent = (
() => {
const twaMainButtonService = useService(
TwaMainButtonService
);
const buttonState = useObservable(
twaMainButtonService.getState()
);
if (isTwa() || !buttonState) {
return null;
}
const className = classNames({
[classes.root!]: true,
[classes.isVisible!]: (
buttonState?.isVisible
),
[classes.isProgressVisible!]: (
buttonState?.isProgressVisible
),
});
const styles: CSSProperties = {
backgroundColor: buttonState?.color,
color: buttonState?.textColor,
};
return (
<>
<button
className={className}
disabled={!buttonState.isActive}
style={styles}
>
{ buttonState.text }
</button>
</>
);
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment