xref: /expo/docs/ui/components/Header/Header.tsx (revision d4dd1f0b)
1import { css } from '@emotion/react';
2import { theme, Button } from '@expo/styleguide';
3import { breakpoints, spacing } from '@expo/styleguide-base';
4import { GithubIcon, Menu01Icon, Star01Icon } from '@expo/styleguide-icons';
5import type { ReactNode } from 'react';
6
7import { Logo } from './Logo';
8import { ThemeSelector } from './ThemeSelector';
9
10import { SidebarFooter, SidebarHead } from '~/ui/components/Sidebar';
11import { DEMI } from '~/ui/components/Text';
12
13type HeaderProps = {
14  sidebar: ReactNode;
15  sidebarActiveGroup: string;
16  isMobileMenuVisible: boolean;
17  setMobileMenuVisible: (isMobileMenuVisible: boolean) => void;
18};
19
20export const Header = ({
21  sidebar,
22  sidebarActiveGroup,
23  isMobileMenuVisible,
24  setMobileMenuVisible,
25}: HeaderProps) => {
26  const isArchive = sidebarActiveGroup === 'archive';
27  return (
28    <>
29      <nav css={[containerStyle, isMobileMenuVisible]}>
30        <div css={[columnStyle, leftColumnStyle]}>
31          <Logo subgroup={isArchive ? 'Archive' : undefined} />
32        </div>
33        <div css={[columnStyle, rightColumnStyle]}>
34          <Button
35            openInNewTab
36            theme="quaternary"
37            className="px-2 text-secondary"
38            href="https://blog.expo.dev">
39            Blog
40          </Button>
41          <Button
42            openInNewTab
43            theme="quaternary"
44            css={hideOnMobileStyle}
45            className="px-2 text-secondary"
46            leftSlot={<Star01Icon className="icon-sm" />}
47            href="https://github.com/expo/expo">
48            Star Us on GitHub
49          </Button>
50          <Button
51            openInNewTab
52            theme="quaternary"
53            href="https://github.com/expo/expo"
54            css={showOnMobileStyle}
55            aria-label="GitHub"
56            className="px-2">
57            <GithubIcon className="icon-lg" />
58          </Button>
59          <div css={hideOnMobileStyle}>
60            <ThemeSelector />
61          </div>
62          <div css={showOnMobileStyle}>
63            <Button
64              theme="quaternary"
65              css={[mobileButtonStyle, isMobileMenuVisible && mobileButtonActiveStyle]}
66              onClick={() => {
67                setMobileMenuVisible(!isMobileMenuVisible);
68              }}>
69              <Menu01Icon className="icon-sm" />
70            </Button>
71          </div>
72        </div>
73      </nav>
74      {isMobileMenuVisible && (
75        <nav css={[containerStyle, showOnMobileStyle]}>
76          <div css={[columnStyle, leftColumnStyle]}>
77            <DEMI>Theme</DEMI>
78          </div>
79          <div css={[columnStyle, rightColumnStyle]}>
80            <ThemeSelector />
81          </div>
82        </nav>
83      )}
84      {isMobileMenuVisible && (
85        <div css={mobileSidebarStyle}>
86          <SidebarHead sidebarActiveGroup={sidebarActiveGroup} />
87          {sidebar}
88          <SidebarFooter />
89        </div>
90      )}
91    </>
92  );
93};
94
95const containerStyle = css`
96  display: flex;
97  align-items: center;
98  justify-content: flex-end;
99  position: relative;
100  background-color: ${theme.background.default};
101  z-index: 2;
102  margin: 0 auto;
103  padding: 0 ${spacing[4]}px;
104  height: 60px;
105  box-sizing: border-box;
106  border-bottom: 1px solid ${theme.border.default};
107  gap: ${spacing[2.5]}px;
108`;
109
110const columnStyle = css`
111  flex-shrink: 0;
112  display: flex;
113  gap: ${spacing[8]}px;
114  align-items: center;
115  background-color: transparent;
116
117  @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) {
118    gap: ${spacing[4]}px;
119  }
120`;
121
122const leftColumnStyle = css`
123  flex-grow: 1;
124  align-items: center;
125
126  @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) {
127    flex-basis: auto;
128    width: auto;
129  }
130`;
131
132const rightColumnStyle = css`
133  justify-content: flex-end;
134  gap: ${spacing[4]}px;
135
136  @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) {
137    flex-basis: auto;
138    width: auto;
139  }
140`;
141
142const showOnMobileStyle = css`
143  display: none;
144
145  @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) {
146    display: flex;
147  }
148`;
149
150const hideOnMobileStyle = css`
151  @media screen and (max-width: ${(breakpoints.medium + breakpoints.large) / 2}px) {
152    display: none;
153  }
154`;
155
156const mobileButtonStyle = css`
157  padding: 0 ${spacing[3]}px;
158
159  &:hover {
160    background-color: ${theme.background.element};
161    box-shadow: none;
162  }
163`;
164
165const mobileButtonActiveStyle = css`
166  background-color: ${theme.background.subtle};
167`;
168
169const mobileSidebarStyle = css`
170  background-color: ${theme.background.subtle};
171  height: calc(100vh - (60px * 2));
172  overflow-y: auto;
173  overflow-x: hidden;
174`;
175