app: make the Home and auth pages reflow on mobile

This commit is contained in:
dstrukt 2026-08-02 23:00:43 -06:00
parent 64d0227452
commit 1c463439db
7 changed files with 69 additions and 12 deletions

View file

@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="%PUBLIC_URL%/icons/favicon-32x32.png" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" sizes="48x48" href="%PUBLIC_URL%/icons/icon-48x48.png" />

View file

@ -81,10 +81,12 @@
font-display: swap;
}
// the minimum width is not applied globally, since the Home and auth pages reflow
// down to mobile widths. the pages which are laid out for desktop widths apply it
// themselves via the Layout component
html,
body,
#root {
background-color: #252f4a;
width: 100%;
min-width: 1024px;
}

View file

@ -12,6 +12,17 @@ describe('AppView', () => {
store = rootStore.appView;
});
it('should only treat the Home page as responsive', () => {
store.goToHome();
expect(store.responsive).toBe(true);
store.goToPool();
expect(store.responsive).toBe(false);
store.goToLoop();
expect(store.responsive).toBe(false);
store.goToSettings();
expect(store.responsive).toBe(false);
});
it('should add an alert', async () => {
expect(store.alerts.size).toBe(0);
store.notify('test message', 'test title');

View file

@ -1,15 +1,25 @@
import React from 'react';
import styled from '@emotion/styled';
import { usePrefixedTranslation } from 'hooks';
import QRCodeImg from 'qrcode.react';
import { QRCodeSVG } from 'qrcode.react';
import { Paragraph } from 'components/base';
import Modal from 'components/common/Modal';
const Styled = {
QRWrap: styled.div`
display: inline-block;
max-width: 100%;
padding: 8px 8px 0;
background-color: ${props => props.theme.colors.white};
/* the code is rendered as an svg so that it can scale down to fit inside of the
modal on narrow screens, instead of being clipped by it */
> svg {
display: block;
width: 500px;
max-width: 100%;
height: auto;
}
`,
};
@ -26,7 +36,7 @@ const QRCodeModal: React.FC<Props> = ({ url, visible, onClose }) => {
<Modal title={l('title')} visible={visible} onClose={onClose}>
<Paragraph space={32}>{l('desc')}</Paragraph>
<QRWrap>
<QRCodeImg value={url} size={500} />
<QRCodeSVG value={url} size={500} />
</QRWrap>
</Modal>
);

View file

@ -3,24 +3,42 @@ import { observer } from 'mobx-react-lite';
import styled from '@emotion/styled';
import { usePrefixedTranslation } from 'hooks';
import { useStore } from 'store';
import { BoltOutlined, Button, Display, Paragraph, QRCode } from 'components/base';
import { AUTO_COLLAPSE_MAX_WIDTH } from 'store/stores/settingsStore';
import { BoltOutlined, Button, QRCode } from 'components/base';
import { Display, Paragraph } from 'components/common/v2/Text';
import PurpleButton from 'components/connect/PurpleButton';
import QRCodeModal from 'components/connect/QRCodeModal';
import YoutubeModal from './YoutubeModal';
const Styled = {
Wrapper: styled.div`
padding: 72px 0;
/* while the sidebar is collapsed its toggle floats over the top of the page and
the layout reserves space for it, so only a small amount of padding is added on
top of that. the full padding is restored once the sidebar is displayed
alongside the content again */
padding: 16px 0 40px;
@media (min-width: ${AUTO_COLLAPSE_MAX_WIDTH + 1}px) {
padding: 72px 0;
}
`,
/* the connect buttons wrap onto multiple lines when the page is too narrow to fit
them all on one line */
Buttons: styled.div`
display: flex;
flex-wrap: wrap;
gap: 16px 24px;
`,
PurpleButton: styled(PurpleButton)`
font-size: ${props => props.theme.sizes.s};
line-height: 24px;
padding: 8px 16px;
margin-right: 24px;
`,
/* a text button which sits inline at the end of the description paragraph */
LearnMoreButton: styled(Button)`
font-family: ${props => props.theme.fonts.open.semiBold};
/* inherit the metrics of the paragraph, which change on smaller screens */
font-size: inherit;
line-height: inherit;
vertical-align: baseline;
padding: 0;
@ -41,19 +59,19 @@ const HomePage: React.FC = () => {
const closeQRModal = useCallback(() => setQrUrl(''), []);
const toggleVideoModal = useCallback(() => setShowVideo(v => !v), []);
const { Wrapper, PurpleButton, LearnMoreButton } = Styled;
const { Wrapper, Buttons, PurpleButton, LearnMoreButton } = Styled;
return (
<Wrapper>
<Display semiBold space={16}>
{l('pageTitle')}
</Display>
<Paragraph space={32}>
<Paragraph space={24} desktopSpace={32}>
{l('connectDesc')}
<LearnMoreButton ghost borderless compact onClick={toggleVideoModal}>
{l('learnMore')}
</LearnMoreButton>
</Paragraph>
<Paragraph space={40}>
<Buttons>
<PurpleButton onClick={sessionStore.connectToTerminalWeb}>
<BoltOutlined />
{l('connectTerminalBtn')}
@ -62,7 +80,7 @@ const HomePage: React.FC = () => {
<QRCode />
{l('connectQrBtn')}
</PurpleButton>
</Paragraph>
</Buttons>
<QRCodeModal url={qrUrl} visible={!!qrUrl} onClose={closeQRModal} />
<YoutubeModal
videoId="5kH1ByxjkTM"

View file

@ -58,6 +58,9 @@ const GlobalStyles = (theme: Theme) => `
}
`;
/** the width that the pages which do not reflow are laid out for */
const DESKTOP_MIN_WIDTH = 1024;
/** the space the sidebar toggle occupies at the top of the page, including its offset */
const HAMBURGER_HEIGHT = 80;
@ -65,12 +68,15 @@ const HAMBURGER_HEIGHT = 80;
const SIDEBAR_WIDTH = 285;
const Styled = {
Container: styled.div<{ fullWidth: boolean }>`
Container: styled.div<{ fullWidth: boolean; responsive: boolean }>`
position: relative;
height: 100%;
max-width: ${props => (props.fullWidth ? '100%' : '1440px')};
width: ${props => (props.fullWidth ? '100%' : '100%')};
margin: 0 auto;
/* pages which do not reflow scroll horizontally rather than being squeezed */
${props => !props.responsive && `min-width: ${DESKTOP_MIN_WIDTH}px;`}
`,
Hamburger: styled.span<CollapsedProps>`
display: inline-block;
@ -163,7 +169,7 @@ export const Layout: React.FC = ({ children }) => {
<Aside collapsed={!settingsStore.sidebarVisible}>
<Sidebar />
</Aside>
<Container fullWidth={appView.fullWidth}>
<Container fullWidth={appView.fullWidth} responsive={appView.responsive}>
<Content collapsed={!settingsStore.sidebarVisible} fullWidth={appView.fullWidth}>
<Fluid className="container-fluid">{children}</Fluid>
</Content>

View file

@ -32,6 +32,15 @@ export default class AppView {
return this._store.router.location.pathname === `${PUBLIC_URL}/pool`;
}
/**
* indicates if the current page reflows down to mobile widths. the remaining pages
* contain tables and multi column sections which are laid out for desktop widths, so
* they keep a minimum width and scroll horizontally instead of being squeezed
*/
get responsive() {
return this._store.router.location.pathname === `${PUBLIC_URL}/home`;
}
/** navigate to the specified route */
goTo(route: string) {
const path = `${PUBLIC_URL}${route}`;