diff --git a/app/public/index.html b/app/public/index.html index b2195b2f..abe03ff3 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -2,6 +2,7 @@ + diff --git a/app/src/App.scss b/app/src/App.scss index 9f501492..b25a1b6a 100644 --- a/app/src/App.scss +++ b/app/src/App.scss @@ -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; } diff --git a/app/src/__tests__/store/appView.spec.ts b/app/src/__tests__/store/appView.spec.ts index abeaac78..85144ee4 100644 --- a/app/src/__tests__/store/appView.spec.ts +++ b/app/src/__tests__/store/appView.spec.ts @@ -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'); diff --git a/app/src/components/connect/QRCodeModal.tsx b/app/src/components/connect/QRCodeModal.tsx index 51df4df8..154322ab 100644 --- a/app/src/components/connect/QRCodeModal.tsx +++ b/app/src/components/connect/QRCodeModal.tsx @@ -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 = ({ url, visible, onClose }) => { {l('desc')} - + ); diff --git a/app/src/components/home/HomePage.tsx b/app/src/components/home/HomePage.tsx index 0c1b1e88..b2023af7 100644 --- a/app/src/components/home/HomePage.tsx +++ b/app/src/components/home/HomePage.tsx @@ -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 ( {l('pageTitle')} - + {l('connectDesc')} {l('learnMore')} - + {l('connectTerminalBtn')} @@ -62,7 +80,7 @@ const HomePage: React.FC = () => { {l('connectQrBtn')} - + ` } `; +/** 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` display: inline-block; @@ -163,7 +169,7 @@ export const Layout: React.FC = ({ children }) => { - + {children} diff --git a/app/src/store/views/appView.ts b/app/src/store/views/appView.ts index b4985d4b..4c3042df 100644 --- a/app/src/store/views/appView.ts +++ b/app/src/store/views/appView.ts @@ -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}`;