Web App: Fix button styles (#2478)

This commit is contained in:
Alexander Zinchuk 2023-02-03 03:22:13 +01:00
parent 172f03f474
commit 573583ce67
7 changed files with 185 additions and 145 deletions

View File

@ -0,0 +1,138 @@
.root {
:global {
.modal-header {
border-bottom: 1px solid var(--color-dividers);
padding: 0.5rem;
}
.modal-dialog {
height: 75%;
justify-content: center;
border: none;
box-shadow: none;
margin: 0;
overflow: hidden;
}
.modal-content {
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0;
border-bottom-right-radius: var(--border-radius-default);
border-bottom-left-radius: var(--border-radius-default);
}
@media (max-width: 600px) {
.modal-dialog {
background-color: var(--color-background);
border-radius: 0;
height: 100%;
max-width: 100% !important;
}
.modal-content {
max-height: none;
border-radius: 0;
}
}
}
}
.close-icon {
position: absolute;
transform: rotate(-45deg);
&,
&::before,
&::after {
width: 1.125rem;
height: 0.125rem;
border-radius: 0.125rem;
background-color: var(--color-text-secondary);
transition: transform var(--slide-transition);
}
&::before,
&::after {
position: absolute;
left: 0;
top: 0;
content: "";
}
&::before {
transform: rotate(90deg);
}
&.state-back {
transform: rotate(180deg);
&::before {
transform: rotate(45deg) scaleX(0.75) translate(0, -0.375rem);
}
&::after {
transform: rotate(-45deg) scaleX(0.75) translate(0, 0.375rem);
}
}
}
.loading-spinner {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition: opacity 0.2s ease-in-out;
}
.hide {
opacity: 0;
}
.frame {
width: 100%;
height: 100%;
border: 0;
z-index: 1;
&.with-button {
height: calc(100% - 3.5rem);
}
}
.main-button {
position: absolute;
bottom: 0;
border-radius: 0;
transform: translateY(100%);
transition: 0.25s ease-in-out transform;
&.visible {
transform: translateY(0);
}
&.hidden {
visibility: hidden;
}
}
.main-button-spinner {
position: absolute;
right: 1rem;
}
.web-app-popup {
:global(.modal-dialog) {
max-width: min(30rem, 100%);
}
&.without-title :global(.modal-content) {
padding-top: 0;
}
:global(.modal-content) {
padding-left: 2rem;
}
}

View File

@ -1,122 +0,0 @@
.WebAppModal {
.modal-header {
border-bottom: 1px solid var(--color-dividers);
padding: 0.5rem;
}
.modal-dialog {
height: 75%;
justify-content: center;
border: none;
box-shadow: none;
margin: 0;
overflow: hidden;
}
.modal-content {
display: flex;
flex-direction: column;
overflow: hidden;
padding: 0;
border-bottom-right-radius: var(--border-radius-default);
border-bottom-left-radius: var(--border-radius-default);
}
.web-app-frame {
width: 100%;
height: 100%;
border: 0;
&.with-button {
height: calc(100% - 56px);
}
}
.web-app-button {
position: absolute;
bottom: 0;
border-radius: 0;
transform: translateY(100%);
transition: 0.25s ease-in-out transform;
&.visible {
transform: translateY(0);
}
&.hidden {
visibility: hidden;
}
}
.Spinner {
position: absolute;
right: 1rem;
}
@media (max-width: 600px) {
.modal-dialog {
background-color: var(--color-background);
border-radius: 0;
height: 100%;
max-width: 100% !important;
}
.modal-content {
max-height: none;
border-radius: 0;
}
}
.animated-close-icon {
position: absolute;
transform: rotate(-45deg);
&,
&::before,
&::after {
width: 1.125rem;
height: 0.125rem;
border-radius: 0.125rem;
background-color: var(--color-text-secondary);
transition: transform var(--slide-transition);
}
&::before,
&::after {
position: absolute;
left: 0;
top: 0;
content: "";
}
&::before {
transform: rotate(90deg);
}
&.state-back {
transform: rotate(180deg);
&::before {
transform: rotate(45deg) scaleX(0.75) translate(0, -0.375rem);
}
&::after {
transform: rotate(-45deg) scaleX(0.75) translate(0, 0.375rem);
}
}
}
}
.web-app-popup {
.modal-dialog {
max-width: min(30rem, 100%);
}
&.without-title .modal-content {
padding-top: 0;
}
.modal-content {
padding-left: 2rem;
}
}

View File

@ -31,7 +31,7 @@ import MenuItem from '../ui/MenuItem';
import Spinner from '../ui/Spinner'; import Spinner from '../ui/Spinner';
import ConfirmDialog from '../ui/ConfirmDialog'; import ConfirmDialog from '../ui/ConfirmDialog';
import './WebAppModal.scss'; import styles from './WebAppModal.module.scss';
type WebAppButton = { type WebAppButton = {
isVisible: boolean; isVisible: boolean;
@ -101,6 +101,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
const [headerColor, setHeaderColor] = useState(extractCurrentThemeParams().bg_color); const [headerColor, setHeaderColor] = useState(extractCurrentThemeParams().bg_color);
const [confirmClose, setConfirmClose] = useState(false); const [confirmClose, setConfirmClose] = useState(false);
const [isCloseModalOpen, openCloseModal, closeCloseModal] = useFlag(false); const [isCloseModalOpen, openCloseModal, closeCloseModal] = useFlag(false);
const [isLoaded, markLoaded, markUnloaded] = useFlag(false);
const [popupParams, setPopupParams] = useState<PopupOptions | undefined>(); const [popupParams, setPopupParams] = useState<PopupOptions | undefined>();
const { isMobile } = useAppLayout(); const { isMobile } = useAppLayout();
const prevPopupParams = usePrevious(popupParams); const prevPopupParams = usePrevious(popupParams);
@ -204,7 +205,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
const { const {
reloadFrame, sendEvent, sendViewport, sendTheme, reloadFrame, sendEvent, sendViewport, sendTheme,
} = useWebAppFrame(frameRef, isOpen, isSimple, handleEvent); } = useWebAppFrame(frameRef, isOpen, isSimple, handleEvent, markLoaded);
const shouldShowMainButton = mainButton?.isVisible && mainButton.text.trim().length > 0; const shouldShowMainButton = mainButton?.isVisible && mainButton.text.trim().length > 0;
@ -315,8 +316,9 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
setConfirmClose(false); setConfirmClose(false);
closeCloseModal(); closeCloseModal();
setPopupParams(undefined); setPopupParams(undefined);
markUnloaded();
} }
}, [closeCloseModal, isOpen]); }, [closeCloseModal, isOpen, markUnloaded]);
const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => { const MoreMenuButton: FC<{ onTrigger: () => void; isOpen?: boolean }> = useMemo(() => {
return ({ onTrigger, isOpen: isMenuOpen }) => ( return ({ onTrigger, isOpen: isMenuOpen }) => (
@ -335,8 +337,8 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
}, [isMobile]); }, [isMobile]);
const backButtonClassName = buildClassName( const backButtonClassName = buildClassName(
'animated-close-icon', styles.closeIcon,
isBackButtonVisible && 'state-back', isBackButtonVisible && styles.stateBack,
); );
const header = useMemo(() => { const header = useMemo(() => {
@ -361,6 +363,11 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
<MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem> <MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem>
)} )}
<MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem> <MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem>
{attachBot?.hasSettings && (
<MenuItem icon="settings" onClick={handleSettingsButtonClick}>
{lang('Settings')}
</MenuItem>
)}
{bot?.isAttachBot && ( {bot?.isAttachBot && (
<MenuItem <MenuItem
icon={attachBot ? 'stop' : 'install'} icon={attachBot ? 'stop' : 'install'}
@ -370,11 +377,6 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
{lang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')} {lang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
</MenuItem> </MenuItem>
)} )}
{attachBot?.hasSettings && (
<MenuItem icon="settings" onClick={handleSettingsButtonClick}>
{lang('Settings')}
</MenuItem>
)}
</DropdownMenu> </DropdownMenu>
</div> </div>
); );
@ -425,17 +427,18 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
return ( return (
<Modal <Modal
className="WebAppModal" className={styles.root}
isOpen={isOpen} isOpen={isOpen}
onClose={handleClose} onClose={handleClose}
header={header} header={header}
hasCloseButton hasCloseButton
style={`background-color: ${backgroundColor}`} style={`background-color: ${backgroundColor}`}
> >
<Spinner className={buildClassName(styles.loadingSpinner, isLoaded && styles.hide)} />
{isOpen && ( {isOpen && (
<> <>
<iframe <iframe
className={buildClassName('web-app-frame', shouldDecreaseWebFrameSize && 'with-button')} className={buildClassName(styles.frame, shouldDecreaseWebFrameSize && styles.withButton)}
src={url} src={url}
title={`${bot?.firstName} Web App`} title={`${bot?.firstName} Web App`}
sandbox={SANDBOX_ATTRIBUTES} sandbox={SANDBOX_ATTRIBUTES}
@ -445,16 +448,16 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
/> />
<Button <Button
className={buildClassName( className={buildClassName(
'web-app-button', styles.mainButton,
shouldShowMainButton && 'visible', shouldShowMainButton && styles.visible,
shouldHideButton && 'hidden', shouldHideButton && styles.hidden,
)} )}
style={`background-color: ${mainButtonCurrentColor}; color: ${mainButtonCurrentTextColor}`} style={`background-color: ${mainButtonCurrentColor}; color: ${mainButtonCurrentTextColor}`}
disabled={!mainButtonCurrentIsActive} disabled={!mainButtonCurrentIsActive}
onClick={handleMainButtonClick} onClick={handleMainButtonClick}
> >
{mainButtonCurrentText} {mainButtonCurrentText}
{mainButton?.isProgressVisible && <Spinner color="white" />} {mainButton?.isProgressVisible && <Spinner className={styles.mainButtonSpinner} color="white" />}
</Button> </Button>
</> </>
)} )}
@ -475,16 +478,18 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
title={renderingPopupParams.title || NBSP} title={renderingPopupParams.title || NBSP}
onClose={handlePopupModalClose} onClose={handlePopupModalClose}
hasCloseButton hasCloseButton
className={buildClassName('web-app-popup', !renderingPopupParams.title?.trim().length && 'without-title')} className={
buildClassName(styles.webAppPopup, !renderingPopupParams.title?.trim().length && styles.withoutTitle)
}
> >
{renderingPopupParams.message} {renderingPopupParams.message}
<div className="dialog-buttons mt-2"> <div className="dialog-buttons mt-2">
{renderingPopupParams.buttons.map((button) => ( {renderingPopupParams.buttons.map((button) => (
<Button <Button
key={button.id || button.text || button.type} key={button.id || button.type}
className="confirm-dialog-button"
color={button.type === 'destructive' ? 'danger' : 'primary'} color={button.type === 'destructive' ? 'danger' : 'primary'}
isText isText
fluid
size="smaller" size="smaller"
// eslint-disable-next-line react/jsx-no-bind // eslint-disable-next-line react/jsx-no-bind
onClick={() => handlePopupClose(button.id)} onClick={() => handlePopupClose(button.id)}

View File

@ -169,11 +169,26 @@ const useWebAppFrame = (
isOpen: boolean, isOpen: boolean,
isSimpleView: boolean, isSimpleView: boolean,
onEvent: (event: WebAppInboundEvent) => void, onEvent: (event: WebAppInboundEvent) => void,
onLoad?: () => void,
) => { ) => {
const ignoreEventsRef = useRef<boolean>(false); const ignoreEventsRef = useRef<boolean>(false);
const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>(); const lastFrameSizeRef = useRef<{ width: number; height: number; isResizing?: boolean }>();
const windowSize = useWindowSize(); const windowSize = useWindowSize();
useEffect(() => {
if (!ref.current || !isOpen) return undefined;
const handleLoad = () => {
onLoad?.();
};
const frame = ref.current;
frame.addEventListener('load', handleLoad);
return () => {
frame.removeEventListener('load', handleLoad);
};
}, [onLoad, ref, isOpen]);
const reloadFrame = useCallback((url: string) => { const reloadFrame = useCallback((url: string) => {
if (!ref.current) return; if (!ref.current) return;
const frame = ref.current; const frame = ref.current;
@ -228,6 +243,10 @@ const useWebAppFrame = (
try { try {
const data = JSON.parse(event.data) as WebAppInboundEvent; const data = JSON.parse(event.data) as WebAppInboundEvent;
// Handle some app requests here to simplify hook usage // Handle some app requests here to simplify hook usage
if (data.eventType === 'web_app_ready') {
onLoad?.();
}
if (data.eventType === 'web_app_request_viewport') { if (data.eventType === 'web_app_request_viewport') {
sendViewport(windowSize.isResizing); sendViewport(windowSize.isResizing);
} }
@ -263,7 +282,7 @@ const useWebAppFrame = (
} catch (err) { } catch (err) {
// Ignore other messages // Ignore other messages
} }
}, [isSimpleView, onEvent, sendCustomStyle, sendEvent, sendTheme, sendViewport, windowSize.isResizing]); }, [isSimpleView, onEvent, sendCustomStyle, sendEvent, sendTheme, sendViewport, onLoad, windowSize.isResizing]);
useEffect(() => { useEffect(() => {
const { width, height, isResizing } = windowSize; const { width, height, isResizing } = windowSize;

View File

@ -185,7 +185,7 @@ const Button: FC<OwnProps> = ({
title={ariaLabel} title={ariaLabel}
tabIndex={tabIndex} tabIndex={tabIndex}
dir={isRtl ? 'rtl' : undefined} dir={isRtl ? 'rtl' : undefined}
style={buildStyle(backgroundImage && `background-image: url(${backgroundImage})`)} style={buildStyle(style, backgroundImage && `background-image: url(${backgroundImage})`)}
> >
{isLoading ? ( {isLoading ? (
<div> <div>

View File

@ -174,7 +174,7 @@
flex-wrap: wrap; flex-wrap: wrap;
.confirm-dialog-button + .confirm-dialog-button { .confirm-dialog-button + .confirm-dialog-button {
margin-left: 1rem; margin-right: 1rem;
} }
} }

View File

@ -16,7 +16,7 @@ const Spinner: FC<{
}) => { }) => {
return ( return (
<div className={buildClassName( <div className={buildClassName(
'Spinner', className, color, backgroundColor && 'with-background', `bg-${backgroundColor}`, 'Spinner', className, color, backgroundColor && 'with-background', backgroundColor && `bg-${backgroundColor}`,
)} )}
> >
<div /> <div />