Layout scaffolding for Web apps.
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 

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