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

View File

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

View File

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

View File

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

View File

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