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