1import { css, Global } from '@emotion/react'; 2import { SerializedStyles } from '@emotion/serialize'; 3import { breakpoints, spacing, theme } from '@expo/styleguide'; 4import React, { PropsWithChildren, ReactNode } from 'react'; 5 6import { Header } from '~/ui/components/Header'; 7import { LayoutScroll } from '~/ui/components/Layout'; 8 9type LayoutProps = PropsWithChildren<{ 10 /** 11 * The content within the top bar that spans the columns. 12 */ 13 header?: ReactNode; 14 /** 15 * The content within the left column. 16 */ 17 navigation?: ReactNode; 18 /** 19 * The content within the right column. 20 */ 21 sidebar?: ReactNode; 22 /** 23 * Custom CSS for the whole layout. 24 */ 25 cssLayout?: SerializedStyles; 26 /** 27 * Custom CSS for the main content wrapper. 28 */ 29 cssContent?: SerializedStyles; 30}>; 31 32export const Layout = ({ 33 header = <Header />, 34 navigation, 35 sidebar, 36 children, 37 cssLayout = undefined, 38 cssContent = undefined, 39}: LayoutProps) => ( 40 <> 41 <Global 42 styles={css({ 43 // Ensure correct background for Overscroll 44 '[data-expo-theme="dark"] body': { 45 backgroundColor: theme.background.screen, 46 }, 47 })} 48 /> 49 <header css={headerStyle}>{header}</header> 50 <main css={[layoutStyle, cssLayout]}> 51 {navigation && <nav css={navigationStyle}>{navigation}</nav>} 52 <LayoutScroll> 53 <article css={[innerContentStyle, cssContent]}>{children}</article> 54 </LayoutScroll> 55 {sidebar && <aside css={asideStyle}>{sidebar}</aside>} 56 </main> 57 </> 58); 59 60const HEADER_HEIGHT = 60; 61 62const layoutStyle = css({ 63 display: 'flex', 64 alignItems: 'stretch', 65 maxHeight: `calc(100vh - ${HEADER_HEIGHT}px)`, 66 marginTop: HEADER_HEIGHT, 67 backgroundColor: theme.background.default, 68 '[data-expo-theme="dark"] &': { 69 backgroundColor: theme.background.screen, 70 }, 71 [`@media screen and (max-width: ${breakpoints.medium}px)`]: { 72 // Ditch inner scroll on mobile, which results in weird bugs 73 maxHeight: 'none', 74 }, 75}); 76 77const headerStyle = css({ 78 position: 'fixed', 79 top: 0, 80 width: '100%', 81 height: HEADER_HEIGHT, 82 zIndex: 100, 83}); 84 85const navigationStyle = css({ 86 flexBasis: 256, 87 [`@media screen and (max-width: ${breakpoints.medium}px)`]: { 88 display: 'none', 89 }, 90}); 91 92const innerContentStyle = css({ 93 margin: '0 auto', 94 minHeight: `calc(100vh - ${HEADER_HEIGHT}px)`, 95 maxWidth: breakpoints.large, 96 padding: `${spacing[8]}px ${spacing[4]}px`, 97}); 98 99const asideStyle = css({ 100 flexBasis: 288, 101 [`@media screen and (max-width: ${breakpoints.medium}px)`]: { 102 display: 'none', 103 }, 104}); 105