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