Template for starting apps, powered by Next.js
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 

533 lignes
12 KiB

  1. import * as React from 'react';
  2. import * as T from '@tesseract-design/react-common';
  3. import styled, { createGlobalStyle } from 'styled-components';
  4. import TopBar from '../../widgets/TopBar';
  5. import {UrlObject} from 'url';
  6. const DisableScrolling = createGlobalStyle({
  7. 'body': {
  8. overflow: 'hidden',
  9. '@media (min-width: 1080px)': {
  10. overflow: 'auto',
  11. },
  12. },
  13. })
  14. const Wrapper = styled('div')({
  15. '--width-base': '360px',
  16. '--size-menu': '4rem',
  17. '--height-topbar': '4rem',
  18. })
  19. const Main = styled('main')({
  20. boxSizing: 'border-box',
  21. paddingBottom: 'var(--size-menu, 4rem)',
  22. '@media (min-width: 1080px)': {
  23. paddingLeft: 'calc(50% - var(--width-base, 360px) * 0.5)',
  24. paddingBottom: 0,
  25. },
  26. })
  27. const LeftSidebar = styled('div')({
  28. boxSizing: 'border-box',
  29. backgroundColor: 'var(--color-bg, white)',
  30. overflow: 'hidden',
  31. display: 'contents',
  32. left: 'calc(var(--width-base, 360px) * -1)',
  33. '@media (prefers-color-scheme: dark)': {
  34. backgroundColor: 'var(--color-bg, black)',
  35. },
  36. '@media (min-width: 1080px)': {
  37. position: 'fixed',
  38. top: 0,
  39. left: 0,
  40. width: 'calc(50% - var(--width-base, 360px) * 0.5)',
  41. height: '100%',
  42. display: 'block',
  43. },
  44. })
  45. const SidebarMain = styled('div')({
  46. scrollbarWidth: 'none',
  47. backgroundColor: 'var(--color-bg, white)',
  48. boxSizing: 'border-box',
  49. position: 'fixed',
  50. top: 0,
  51. right: '100%',
  52. width: '100%',
  53. height: '100%',
  54. overflow: 'auto',
  55. paddingTop: 'inherit',
  56. paddingBottom: 'var(--size-menu, 4rem)',
  57. '@media (prefers-color-scheme: dark)': {
  58. backgroundColor: 'var(--color-bg, black)',
  59. },
  60. '::-webkit-scrollbar': {
  61. display: 'none',
  62. },
  63. '@media (min-width: 1080px)': {
  64. position: 'absolute',
  65. right: 0,
  66. width: 'calc(var(--width-base, 360px) - var(--size-menu, 4rem))',
  67. marginLeft: 'auto',
  68. paddingBottom: 0,
  69. },
  70. })
  71. const OpenSidebarMain = styled(SidebarMain)({
  72. right: 0,
  73. })
  74. const SidebarMenu = styled('div')({
  75. boxSizing: 'border-box',
  76. scrollbarWidth: 'none',
  77. '::-webkit-scrollbar': {
  78. display: 'none',
  79. },
  80. position: 'fixed',
  81. bottom: 0,
  82. left: 0,
  83. width: '100%',
  84. height: 'var(--size-menu, 4rem)',
  85. zIndex: 1,
  86. backgroundColor: 'var(--color-bg, white)',
  87. '@media (prefers-color-scheme: dark)': {
  88. backgroundColor: 'var(--color-bg, black)',
  89. },
  90. '@media (min-width: 1080px)': {
  91. top: 0,
  92. marginLeft: 'auto',
  93. position: 'absolute',
  94. height: '100%',
  95. paddingTop: 'inherit',
  96. overflow: 'auto',
  97. zIndex: 'auto',
  98. },
  99. })
  100. const SidebarMenuSize = styled('div')({
  101. display: 'flex',
  102. width: '100%',
  103. height: '100%',
  104. maxWidth: 'calc(var(--width-base, 360px) * 2)',
  105. margin: '0 auto',
  106. '@media (min-width: 1080px)': {
  107. maxWidth: 'none',
  108. marginRight: 0,
  109. flexDirection: 'column',
  110. justifyContent: 'space-between',
  111. alignItems: 'flex-end',
  112. },
  113. })
  114. const SidebarMenuGroup = styled('div')({
  115. display: 'contents',
  116. '@media (min-width: 1080px)': {
  117. width: '100%',
  118. display: 'block',
  119. },
  120. })
  121. const MoreItems = styled('div')({
  122. position: 'fixed',
  123. top: 0,
  124. left: '-100%',
  125. width: '100%',
  126. height: '100%',
  127. paddingTop: 'var(--height-topbar, 4rem)',
  128. paddingBottom: 'var(--size-menu, 4rem)',
  129. backgroundColor: 'var(--color-bg, white)',
  130. zIndex: -1,
  131. boxSizing: 'border-box',
  132. '@media (prefers-color-scheme: dark)': {
  133. backgroundColor: 'var(--color-bg, black)',
  134. },
  135. '@media (min-width: 1080px)': {
  136. display: 'contents',
  137. },
  138. })
  139. const OpenMoreItems = styled(MoreItems)({
  140. left: 0,
  141. })
  142. const MoreItemsScroll = styled('div')({
  143. width: '100%',
  144. height: '100%',
  145. overflow: 'auto',
  146. '@media (min-width: 1080px)': {
  147. display: 'contents',
  148. },
  149. })
  150. const MorePrimarySidebarMenuGroup = styled(SidebarMenuGroup)({
  151. '@media (min-width: 1080px)': {
  152. flex: 'auto',
  153. },
  154. })
  155. const MoreSecondarySidebarMenuGroup = styled(SidebarMenuGroup)({
  156. '@media (min-width: 1080px)': {
  157. order: 4,
  158. },
  159. })
  160. const SidebarMenuItem = styled('span')({
  161. width: 0,
  162. flex: 'auto',
  163. height: 'var(--size-menu, 4rem)',
  164. '> *': {
  165. height: '100%',
  166. },
  167. '@media (min-width: 1080px)': {
  168. width: 'auto !important',
  169. flex: '0 1 auto',
  170. '> *': {
  171. height: 'auto',
  172. }
  173. },
  174. })
  175. const MoreSidebarMenuItem = styled('span')({
  176. display: 'block',
  177. height: 'var(--size-menu, 4rem)',
  178. '> *': {
  179. height: '100%',
  180. },
  181. '@media (min-width: 1080px)': {
  182. width: 'auto !important',
  183. flex: '0 1 auto',
  184. },
  185. })
  186. const MoreToggleSidebarMenuItem = styled(SidebarMenuItem)({
  187. '@media (min-width: 1080px)': {
  188. display: 'none',
  189. },
  190. })
  191. const SidebarMenuItemIcon = styled('span')({
  192. display: 'block',
  193. '@media (min-width: 1080px)': {
  194. width: 'var(--size-menu, 4rem)',
  195. height: 'var(--size-menu, 4rem)',
  196. display: 'grid',
  197. placeContent: 'center',
  198. },
  199. })
  200. const MoreSidebarMenuItemIcon = styled('span')({
  201. marginRight: '1rem',
  202. display: 'block',
  203. '@media (min-width: 1080px)': {
  204. width: 'var(--size-menu, 4rem)',
  205. height: 'var(--size-menu, 4rem)',
  206. display: 'grid',
  207. placeContent: 'center',
  208. marginRight: 0,
  209. },
  210. })
  211. const SidebarMenuContainer = styled('span')({
  212. boxSizing: 'border-box',
  213. display: 'grid',
  214. placeContent: 'center',
  215. width: '100%',
  216. textAlign: 'center',
  217. '@media (min-width: 1080px)': {
  218. display: 'flex',
  219. justifyContent: 'flex-start',
  220. alignItems: 'center',
  221. width: 'var(--width-base, 360px)',
  222. marginLeft: 'auto',
  223. paddingRight: '1rem',
  224. textAlign: 'left',
  225. boxSizing: 'border-box',
  226. },
  227. })
  228. const MoreSidebarMenuContainer = styled('div')({
  229. display: 'flex',
  230. justifyContent: 'flex-start',
  231. alignItems: 'center',
  232. width: 'calc(var(--width-base, 360px) * 2)',
  233. margin: '0 auto',
  234. padding: '0 1rem',
  235. textAlign: 'left',
  236. boxSizing: 'border-box',
  237. '@media (min-width: 1080px)': {
  238. marginRight: 0,
  239. width: 'var(--width-base, 360px)',
  240. paddingLeft: 0,
  241. },
  242. })
  243. export const Container = styled('div')({
  244. padding: '0 1rem',
  245. boxSizing: 'border-box',
  246. width: '100%',
  247. maxWidth: 'calc(var(--width-base, 360px) * 2)',
  248. marginRight: 'auto',
  249. marginLeft: 'auto',
  250. '@media (min-width: 1080px)': {
  251. marginLeft: 0,
  252. },
  253. })
  254. export const SidebarMainContainer = styled('div')({
  255. padding: '0 1rem',
  256. boxSizing: 'border-box',
  257. width: '100%',
  258. maxWidth: 'calc(var(--width-base, 360px) * 2)',
  259. margin: '0 auto',
  260. '@media (min-width: 1080px)': {
  261. maxWidth: 'none',
  262. },
  263. })
  264. type MenuItem = {
  265. id: string,
  266. label: string,
  267. url: UrlObject,
  268. secondary?: boolean,
  269. icon: React.ReactChild,
  270. }
  271. type Props = {
  272. query?: string,
  273. onSearch?: React.FormEventHandler,
  274. searchLabel?: string,
  275. searchName?: string,
  276. searchHint?: string,
  277. brand?: React.ReactNode,
  278. linkComponent?: React.ElementType,
  279. sidebarMain?: React.ReactChild,
  280. sidebarMenuItems?: MenuItem[],
  281. sidebarMainOpen?: boolean,
  282. menuLink?: UrlObject,
  283. userLink?: UrlObject,
  284. menuLinkLabel?: string,
  285. userLinkLabel?: string,
  286. moreItemsOpen?: boolean,
  287. }
  288. const LeftSidebarWithMenuLayout: React.FC<Props> = ({
  289. query,
  290. children,
  291. sidebarMain,
  292. sidebarMenuItems,
  293. sidebarMainOpen,
  294. brand,
  295. onSearch,
  296. searchLabel,
  297. searchName,
  298. searchHint,
  299. linkComponent: LinkComponent = 'a',
  300. menuLinkLabel,
  301. menuLink,
  302. userLink,
  303. userLinkLabel,
  304. moreItemsOpen,
  305. }) => {
  306. const SidebarMainComponent = sidebarMainOpen ? OpenSidebarMain : SidebarMain
  307. const MoreItemsComponent = moreItemsOpen ? OpenMoreItems : MoreItems
  308. const primarySidebarMenuItems = sidebarMenuItems.filter(s => !s.secondary)
  309. const secondarySidebarMenuItems = sidebarMenuItems.filter(s => s.secondary)
  310. const visibleSecondarySidebarMenuItems = secondarySidebarMenuItems.slice(0, 1)
  311. const moreSecondarySidebarMenuItems = secondarySidebarMenuItems.slice(1)
  312. const visiblePrimarySidebarMenuItems = (
  313. visibleSecondarySidebarMenuItems.length === 1
  314. ? primarySidebarMenuItems.slice(0, 3)
  315. : primarySidebarMenuItems.slice(0, 4)
  316. )
  317. const morePrimarySidebarMenuItems = (
  318. visibleSecondarySidebarMenuItems.length === 1
  319. ? primarySidebarMenuItems.slice(3)
  320. : primarySidebarMenuItems.slice(4)
  321. )
  322. return (
  323. <>
  324. {
  325. (sidebarMainOpen || moreItemsOpen)
  326. && (
  327. <DisableScrolling />
  328. )
  329. }
  330. <Wrapper>
  331. <TopBar
  332. withMenu
  333. query={query}
  334. wide
  335. brand={brand}
  336. onSearch={onSearch}
  337. linkComponent={LinkComponent}
  338. searchHint={searchHint}
  339. searchLabel={searchLabel}
  340. searchName={searchName}
  341. menuLink={menuLink}
  342. menuLinkLabel={menuLinkLabel}
  343. userLink={userLink}
  344. userLinkLabel={userLinkLabel}
  345. />
  346. <LeftSidebar>
  347. <SidebarMenu>
  348. <SidebarMenuSize>
  349. <SidebarMenuGroup>
  350. {visiblePrimarySidebarMenuItems.map((item) => {
  351. return (
  352. <SidebarMenuItem
  353. key={item.id}
  354. >
  355. <LinkComponent
  356. href={item.url}
  357. style={{
  358. display: 'flex',
  359. alignItems: 'center',
  360. textDecoration: 'none',
  361. width: '100%',
  362. }}
  363. >
  364. <SidebarMenuContainer>
  365. <SidebarMenuItemIcon>
  366. {item.icon}
  367. </SidebarMenuItemIcon>
  368. {item.label}
  369. </SidebarMenuContainer>
  370. </LinkComponent>
  371. </SidebarMenuItem>
  372. )
  373. })}
  374. </SidebarMenuGroup>
  375. <MoreItemsComponent>
  376. <MoreItemsScroll>
  377. <MorePrimarySidebarMenuGroup>
  378. {morePrimarySidebarMenuItems.map((item) => {
  379. return (
  380. <MoreSidebarMenuItem
  381. key={item.id}
  382. >
  383. <LinkComponent
  384. href={item.url}
  385. style={{
  386. display: 'flex',
  387. alignItems: 'center',
  388. textDecoration: 'none',
  389. width: '100%',
  390. }}
  391. >
  392. <MoreSidebarMenuContainer>
  393. <MoreSidebarMenuItemIcon>
  394. {item.icon}
  395. </MoreSidebarMenuItemIcon>
  396. {item.label}
  397. </MoreSidebarMenuContainer>
  398. </LinkComponent>
  399. </MoreSidebarMenuItem>
  400. )
  401. })}
  402. </MorePrimarySidebarMenuGroup>
  403. <MoreSecondarySidebarMenuGroup>
  404. {moreSecondarySidebarMenuItems.map((item) => {
  405. return (
  406. <MoreSidebarMenuItem
  407. key={item.id}
  408. >
  409. <LinkComponent
  410. href={item.url}
  411. style={{
  412. display: 'flex',
  413. alignItems: 'center',
  414. textDecoration: 'none',
  415. width: '100%',
  416. }}
  417. >
  418. <MoreSidebarMenuContainer>
  419. <MoreSidebarMenuItemIcon>
  420. {item.icon}
  421. </MoreSidebarMenuItemIcon>
  422. {item.label}
  423. </MoreSidebarMenuContainer>
  424. </LinkComponent>
  425. </MoreSidebarMenuItem>
  426. )
  427. })}
  428. </MoreSecondarySidebarMenuGroup>
  429. </MoreItemsScroll>
  430. </MoreItemsComponent>
  431. <MoreToggleSidebarMenuItem>
  432. <SidebarMenuItem>
  433. <LinkComponent
  434. href={{
  435. query: {
  436. more: '',
  437. }
  438. }}
  439. style={{
  440. display: 'flex',
  441. alignItems: 'center',
  442. textDecoration: 'none',
  443. width: '100%',
  444. }}
  445. shallow
  446. >
  447. <SidebarMenuContainer>
  448. <SidebarMenuItemIcon>
  449. <T.Icon
  450. name="more-horizontal"
  451. label=""
  452. />
  453. </SidebarMenuItemIcon>
  454. More
  455. </SidebarMenuContainer>
  456. </LinkComponent>
  457. </SidebarMenuItem>
  458. </MoreToggleSidebarMenuItem>
  459. {
  460. visibleSecondarySidebarMenuItems.length > 0
  461. && (
  462. <SidebarMenuGroup>
  463. {visibleSecondarySidebarMenuItems.map((item, i) => (
  464. <SidebarMenuItem
  465. key={item.id}
  466. >
  467. <LinkComponent
  468. href={item.url}
  469. style={{
  470. display: 'flex',
  471. alignItems: 'center',
  472. textDecoration: 'none',
  473. width: '100%',
  474. }}
  475. >
  476. <SidebarMenuContainer>
  477. <SidebarMenuItemIcon>
  478. {item.icon}
  479. </SidebarMenuItemIcon>
  480. {item.label}
  481. </SidebarMenuContainer>
  482. </LinkComponent>
  483. </SidebarMenuItem>
  484. ))}
  485. </SidebarMenuGroup>
  486. )
  487. }
  488. </SidebarMenuSize>
  489. </SidebarMenu>
  490. <SidebarMainComponent>
  491. {sidebarMain}
  492. </SidebarMainComponent>
  493. </LeftSidebar>
  494. <Main>
  495. {children}
  496. </Main>
  497. </Wrapper>
  498. </>
  499. )
  500. }
  501. export default LeftSidebarWithMenuLayout