xref: /expo/docs/ui/components/Layout/Layout.tsx (revision af644ed4)
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