Layout scaffolding for Web apps.
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.
 
 
 

466 lignes
9.8 KiB

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