The CSS variables have been documented to help with guiding the user for configuration. The components have been adjusted to define variables internally. They will use internal defaults but will inherit their ascendant's CSS variables.master
@@ -2,6 +2,8 @@ | |||||
Layout scaffolding for Web apps. | Layout scaffolding for Web apps. | ||||
This library is made to avoid custom repetitive layout code. | |||||
## Usage | ## Usage | ||||
Just import: | Just import: | ||||
@@ -49,4 +51,21 @@ const Page: React.FC = ({ | |||||
export default Page | export default Page | ||||
``` | ``` | ||||
The available props per layout is included as a TypeScript declarations file. | |||||
The available props per layout are included as a TypeScript declarations file. | |||||
## Configuration | |||||
There are CSS variables that can be declared in the parent of the `*.Layout` components | |||||
(preferably `:root`) for customizing the metrics and colors of the layout: | |||||
### `--height-topbar` | |||||
Default value: `4rem` | |||||
Height of the top bar widget. | |||||
### `--size-menu` | |||||
Default value: `4rem` | |||||
Width or height of the menu, depending on the orientation it is rendered. |
@@ -1,5 +1,5 @@ | |||||
{ | { | ||||
"version": "0.1.0", | |||||
"version": "0.1.1", | |||||
"license": "MIT", | "license": "MIT", | ||||
"main": "dist/index.js", | "main": "dist/index.js", | ||||
"typings": "dist/index.d.ts", | "typings": "dist/index.d.ts", | ||||
@@ -0,0 +1,3 @@ | |||||
{ | |||||
"base-width": 360 | |||||
} |
@@ -1,10 +1,10 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import styled from 'styled-components'; | |||||
import TopBar from '../../widgets/TopBar'; | |||||
import styled, { createGlobalStyle } from 'styled-components' | |||||
import TopBar from '../../widgets/TopBar' | |||||
import {configVar, loadConfig} from '../../utilities/helpers' | |||||
const LayoutBase = styled('div')({ | |||||
'--width-base': 'var(--width-base, 360px)', | |||||
'--height-topbar': 'var(--height-topbar, 4rem)', | |||||
const Config = createGlobalStyle({ | |||||
...loadConfig(), | |||||
}) | }) | ||||
const ContentBase = styled('main')({ | const ContentBase = styled('main')({ | ||||
@@ -15,7 +15,7 @@ export const ContentContainer = styled('div')({ | |||||
padding: '0 1rem', | padding: '0 1rem', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
margin: '0 auto', | margin: '0 auto', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
width: '100%', | width: '100%', | ||||
}) | }) | ||||
@@ -32,7 +32,8 @@ export const Layout: React.FC<Props> = ({ | |||||
children, | children, | ||||
}) => { | }) => { | ||||
return ( | return ( | ||||
<LayoutBase> | |||||
<> | |||||
<Config /> | |||||
<TopBar | <TopBar | ||||
brand={brand} | brand={brand} | ||||
userLink={userLink} | userLink={userLink} | ||||
@@ -42,6 +43,6 @@ export const Layout: React.FC<Props> = ({ | |||||
<ContentBase> | <ContentBase> | ||||
{children} | {children} | ||||
</ContentBase> | </ContentBase> | ||||
</LayoutBase> | |||||
</> | |||||
) | ) | ||||
} | } |
@@ -1,26 +1,27 @@ | |||||
import * as React from 'react'; | |||||
import styled, {createGlobalStyle} from 'styled-components'; | |||||
import TopBar from '../../widgets/TopBar'; | |||||
import * as React from 'react' | |||||
import styled, {createGlobalStyle} from 'styled-components' | |||||
import TopBar from '../../widgets/TopBar' | |||||
import {applyBackgroundColor, minWidthFactor} from '../../utilities/mixins' | |||||
import {configVar, loadConfig} from '../../utilities/helpers' | |||||
const Config = createGlobalStyle({ | |||||
...loadConfig(), | |||||
}) | |||||
const DisableScrolling = createGlobalStyle({ | const DisableScrolling = createGlobalStyle({ | ||||
'body': { | 'body': { | ||||
overflow: 'hidden', | overflow: 'hidden', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
overflow: 'auto', | overflow: 'auto', | ||||
}, | |||||
}), | |||||
}, | }, | ||||
}) | }) | ||||
const LayoutBase = styled('div')({ | |||||
'--width-base': 'var(--width-base, 360px)', | |||||
'--height-topbar': 'var(--height-topbar, 4rem)', | |||||
}) | |||||
const ContentBase = styled('main')({ | const ContentBase = styled('main')({ | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
'@media (min-width: 1080px)': { | |||||
paddingLeft: 'calc(50% - var(--width-base, 360px) * 0.5)', | |||||
}, | |||||
...minWidthFactor(3)({ | |||||
paddingLeft: `calc(50% - ${configVar('base-width')} * 0.5)`, | |||||
}), | |||||
}) | }) | ||||
const SidebarOverflow = styled('div')({ | const SidebarOverflow = styled('div')({ | ||||
@@ -41,15 +42,12 @@ const SidebarBase = styled('div')({ | |||||
left: '-100%', | left: '-100%', | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
backgroundColor: 'var(--color-bg, white)', | |||||
overflow: 'hidden', | overflow: 'hidden', | ||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
'@media (min-width: 1080px)': { | |||||
width: 'calc(50% - var(--width-base, 360px) * 0.5)', | |||||
...applyBackgroundColor(), | |||||
...minWidthFactor(3)({ | |||||
width: `calc(50% - ${configVar('base-width')} * 0.5)`, | |||||
left: 0, | left: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const OpenSidebarBase = styled(SidebarBase)({ | const OpenSidebarBase = styled(SidebarBase)({ | ||||
@@ -60,11 +58,11 @@ export const SidebarContainer = styled('div')({ | |||||
margin: '0 auto', | margin: '0 auto', | ||||
padding: '0 1rem', | padding: '0 1rem', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
'@media (min-width: 1080px)': { | |||||
width: 'var(--width-base, 360px)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
...minWidthFactor(3)({ | |||||
width: `${configVar('base-width')}`, | |||||
marginRight: 0, | marginRight: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const ContentContainer = styled('div')({ | export const ContentContainer = styled('div')({ | ||||
@@ -72,12 +70,12 @@ export const ContentContainer = styled('div')({ | |||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
width: '100%', | width: '100%', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
marginRight: 'auto', | marginRight: 'auto', | ||||
marginLeft: 'auto', | marginLeft: 'auto', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
marginLeft: 0, | marginLeft: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
type Props = { | type Props = { | ||||
@@ -101,7 +99,8 @@ export const Layout: React.FC<Props> = ({ | |||||
const LeftSidebarComponent = sidebarMainOpen ? OpenSidebarBase : SidebarBase | const LeftSidebarComponent = sidebarMainOpen ? OpenSidebarBase : SidebarBase | ||||
return ( | return ( | ||||
<LayoutBase> | |||||
<> | |||||
<Config /> | |||||
{ | { | ||||
sidebarMainOpen | sidebarMainOpen | ||||
&& ( | && ( | ||||
@@ -124,6 +123,6 @@ export const Layout: React.FC<Props> = ({ | |||||
<ContentBase> | <ContentBase> | ||||
{children} | {children} | ||||
</ContentBase> | </ContentBase> | ||||
</LayoutBase> | |||||
</> | |||||
) | ) | ||||
} | } |
@@ -1,52 +1,48 @@ | |||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import styled, { createGlobalStyle } from 'styled-components'; | |||||
import TopBar from '../../widgets/TopBar'; | |||||
import styled, { createGlobalStyle } from 'styled-components' | |||||
import TopBar from '../../widgets/TopBar' | |||||
import {applyBackgroundColor, minWidthFactor} from '../../utilities/mixins' | |||||
import {configVar, loadConfig} from '../../utilities/helpers' | |||||
const Config = createGlobalStyle({ | |||||
...loadConfig(), | |||||
}) | |||||
const DisableScrolling = createGlobalStyle({ | const DisableScrolling = createGlobalStyle({ | ||||
'body': { | 'body': { | ||||
overflow: 'hidden', | overflow: 'hidden', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
overflow: 'auto', | overflow: 'auto', | ||||
}, | |||||
}), | |||||
}, | }, | ||||
}) | }) | ||||
const LayoutBase = styled('div')({ | |||||
'--width-base': 'var(--width-base, 360px)', | |||||
'--height-topbar': 'var(--height-topbar, 4rem)', | |||||
'--size-menu': 'var(--size-menu, 4rem)', | |||||
}) | |||||
const ContentBase = styled('main')({ | const ContentBase = styled('main')({ | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
paddingBottom: 'var(--size-menu, 4rem)', | paddingBottom: 'var(--size-menu, 4rem)', | ||||
'@media (min-width: 1080px)': { | |||||
paddingLeft: 'calc(50% - var(--width-base, 360px) * 0.5)', | |||||
...minWidthFactor(3)({ | |||||
paddingLeft: `calc(50% - ${configVar('base-width')} * 0.5)`, | |||||
paddingBottom: 0, | paddingBottom: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const SidebarBase = styled('div')({ | const SidebarBase = styled('div')({ | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
backgroundColor: 'var(--color-bg, white)', | |||||
overflow: 'hidden', | overflow: 'hidden', | ||||
display: 'contents', | display: 'contents', | ||||
left: 'calc(var(--width-base, 360px) * -1)', | |||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
'@media (min-width: 1080px)': { | |||||
left: `calc(${configVar('base-width')} * -1)`, | |||||
...applyBackgroundColor(), | |||||
...minWidthFactor(3)({ | |||||
position: 'fixed', | position: 'fixed', | ||||
top: 0, | top: 0, | ||||
left: 0, | left: 0, | ||||
width: 'calc(50% - var(--width-base, 360px) * 0.5)', | |||||
width: `calc(50% - ${configVar('base-width')} * 0.5)`, | |||||
height: '100%', | height: '100%', | ||||
display: 'block', | display: 'block', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const SidebarMain = styled('div')({ | const SidebarMain = styled('div')({ | ||||
backgroundColor: 'var(--color-bg, white)', | |||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
position: 'fixed', | position: 'fixed', | ||||
top: 0, | top: 0, | ||||
@@ -57,20 +53,18 @@ const SidebarMain = styled('div')({ | |||||
// overflow: 'overlay', | // overflow: 'overlay', | ||||
paddingTop: 'inherit', | paddingTop: 'inherit', | ||||
paddingBottom: 'var(--size-menu, 4rem)', | paddingBottom: 'var(--size-menu, 4rem)', | ||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
scrollbarWidth: 'none', | |||||
scrollbarWidth: 'none', | |||||
'::-webkit-scrollbar': { | '::-webkit-scrollbar': { | ||||
display: 'none', | display: 'none', | ||||
}, | }, | ||||
'@media (min-width: 1080px)': { | |||||
...applyBackgroundColor(), | |||||
...minWidthFactor(3)({ | |||||
position: 'absolute', | position: 'absolute', | ||||
right: 0, | right: 0, | ||||
width: 'calc(var(--width-base, 360px) - var(--size-menu, 4rem))', | |||||
width: `calc(${configVar('base-width')} - var(--size-menu, 4rem))`, | |||||
marginLeft: 'auto', | marginLeft: 'auto', | ||||
paddingBottom: 0, | paddingBottom: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const OpenSidebarMain = styled(SidebarMain)({ | const OpenSidebarMain = styled(SidebarMain)({ | ||||
@@ -91,11 +85,8 @@ const SidebarMenu = styled('div')({ | |||||
width: '100%', | width: '100%', | ||||
height: 'var(--size-menu, 4rem)', | height: 'var(--size-menu, 4rem)', | ||||
zIndex: 1, | zIndex: 1, | ||||
backgroundColor: 'var(--color-bg, white)', | |||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
'@media (min-width: 1080px)': { | |||||
...applyBackgroundColor(), | |||||
...minWidthFactor(3)({ | |||||
top: 0, | top: 0, | ||||
marginLeft: 'auto', | marginLeft: 'auto', | ||||
position: 'absolute', | position: 'absolute', | ||||
@@ -103,30 +94,30 @@ const SidebarMenu = styled('div')({ | |||||
paddingTop: 'inherit', | paddingTop: 'inherit', | ||||
overflow: 'auto', | overflow: 'auto', | ||||
zIndex: 'auto', | zIndex: 'auto', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const SidebarMenuSize = styled('div')({ | const SidebarMenuSize = styled('div')({ | ||||
display: 'flex', | display: 'flex', | ||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
margin: '0 auto', | margin: '0 auto', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
maxWidth: 'none', | maxWidth: 'none', | ||||
marginRight: 0, | marginRight: 0, | ||||
flexDirection: 'column', | flexDirection: 'column', | ||||
justifyContent: 'space-between', | justifyContent: 'space-between', | ||||
alignItems: 'flex-end', | alignItems: 'flex-end', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const SidebarMenuGroup = styled('div')({ | const SidebarMenuGroup = styled('div')({ | ||||
display: 'contents', | display: 'contents', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
width: '100%', | width: '100%', | ||||
display: 'block', | display: 'block', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const MoreItems = styled('div')({ | const MoreItems = styled('div')({ | ||||
@@ -137,15 +128,12 @@ const MoreItems = styled('div')({ | |||||
height: '100%', | height: '100%', | ||||
paddingTop: 'var(--height-topbar, 4rem)', | paddingTop: 'var(--height-topbar, 4rem)', | ||||
paddingBottom: 'var(--size-menu, 4rem)', | paddingBottom: 'var(--size-menu, 4rem)', | ||||
backgroundColor: 'var(--color-bg, white)', | |||||
zIndex: -1, | zIndex: -1, | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
'@media (min-width: 1080px)': { | |||||
...applyBackgroundColor(), | |||||
...minWidthFactor(3)({ | |||||
display: 'contents', | display: 'contents', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const OpenMoreItems = styled(MoreItems)({ | const OpenMoreItems = styled(MoreItems)({ | ||||
@@ -156,21 +144,21 @@ const MoreItemsScroll = styled('div')({ | |||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
overflow: 'auto', | overflow: 'auto', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
display: 'contents', | display: 'contents', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const MorePrimarySidebarMenuGroup = styled(SidebarMenuGroup)({ | const MorePrimarySidebarMenuGroup = styled(SidebarMenuGroup)({ | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
flex: 'auto', | flex: 'auto', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const MoreSecondarySidebarMenuGroup = styled(SidebarMenuGroup)({ | const MoreSecondarySidebarMenuGroup = styled(SidebarMenuGroup)({ | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
order: 4, | order: 4, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const SidebarMenuItem = styled('span')({ | const SidebarMenuItem = styled('span')({ | ||||
@@ -184,13 +172,13 @@ const SidebarMenuItem = styled('span')({ | |||||
textDecoration: 'none', | textDecoration: 'none', | ||||
width: '100%', | width: '100%', | ||||
}, | }, | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
width: 'auto !important', | width: 'auto !important', | ||||
flex: '0 1 auto', | flex: '0 1 auto', | ||||
'> *': { | '> *': { | ||||
height: 'auto', | height: 'auto', | ||||
} | |||||
}, | |||||
}, | |||||
}), | |||||
}) | }) | ||||
const MoreSidebarMenuItem = styled('span')({ | const MoreSidebarMenuItem = styled('span')({ | ||||
@@ -203,38 +191,38 @@ const MoreSidebarMenuItem = styled('span')({ | |||||
textDecoration: 'none', | textDecoration: 'none', | ||||
width: '100%', | width: '100%', | ||||
}, | }, | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
width: 'auto !important', | width: 'auto !important', | ||||
flex: '0 1 auto', | flex: '0 1 auto', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const MoreToggleSidebarMenuItem = styled(SidebarMenuItem)({ | const MoreToggleSidebarMenuItem = styled(SidebarMenuItem)({ | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
display: 'none', | display: 'none', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const SidebarMenuItemIcon = styled('span')({ | export const SidebarMenuItemIcon = styled('span')({ | ||||
display: 'block', | display: 'block', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
width: 'var(--size-menu, 4rem)', | width: 'var(--size-menu, 4rem)', | ||||
height: 'var(--size-menu, 4rem)', | height: 'var(--size-menu, 4rem)', | ||||
display: 'grid', | display: 'grid', | ||||
placeContent: 'center', | placeContent: 'center', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const MoreSidebarMenuItemIcon = styled('span')({ | export const MoreSidebarMenuItemIcon = styled('span')({ | ||||
marginRight: '1rem', | marginRight: '1rem', | ||||
display: 'block', | display: 'block', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
width: 'var(--size-menu, 4rem)', | width: 'var(--size-menu, 4rem)', | ||||
height: 'var(--size-menu, 4rem)', | height: 'var(--size-menu, 4rem)', | ||||
display: 'grid', | display: 'grid', | ||||
placeContent: 'center', | placeContent: 'center', | ||||
marginRight: 0, | marginRight: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const SidebarMenuContainer = styled('span')({ | export const SidebarMenuContainer = styled('span')({ | ||||
@@ -243,32 +231,32 @@ export const SidebarMenuContainer = styled('span')({ | |||||
placeContent: 'center', | placeContent: 'center', | ||||
width: '100%', | width: '100%', | ||||
textAlign: 'center', | textAlign: 'center', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
display: 'flex', | display: 'flex', | ||||
justifyContent: 'flex-start', | justifyContent: 'flex-start', | ||||
alignItems: 'center', | alignItems: 'center', | ||||
width: 'var(--width-base, 360px)', | |||||
width: `${configVar('base-width')}`, | |||||
marginLeft: 'auto', | marginLeft: 'auto', | ||||
paddingRight: '1rem', | paddingRight: '1rem', | ||||
textAlign: 'left', | textAlign: 'left', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const MoreSidebarMenuContainer = styled('div')({ | export const MoreSidebarMenuContainer = styled('div')({ | ||||
display: 'flex', | display: 'flex', | ||||
justifyContent: 'flex-start', | justifyContent: 'flex-start', | ||||
alignItems: 'center', | alignItems: 'center', | ||||
width: 'calc(var(--width-base, 360px) * 2)', | |||||
width: `calc(${configVar('base-width')} * 2)`, | |||||
margin: '0 auto', | margin: '0 auto', | ||||
padding: '0 1rem', | padding: '0 1rem', | ||||
textAlign: 'left', | textAlign: 'left', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
marginRight: 0, | marginRight: 0, | ||||
width: 'var(--width-base, 360px)', | |||||
width: `${configVar('base-width')}`, | |||||
paddingLeft: 0, | paddingLeft: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const ContentContainer = styled('div')({ | export const ContentContainer = styled('div')({ | ||||
@@ -276,23 +264,23 @@ export const ContentContainer = styled('div')({ | |||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
width: '100%', | width: '100%', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
marginRight: 'auto', | marginRight: 'auto', | ||||
marginLeft: 'auto', | marginLeft: 'auto', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
marginLeft: 0, | marginLeft: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const SidebarMainContainer = styled('div')({ | export const SidebarMainContainer = styled('div')({ | ||||
padding: '0 1rem', | padding: '0 1rem', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
width: '100%', | width: '100%', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
margin: '0 auto', | margin: '0 auto', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
maxWidth: 'none', | maxWidth: 'none', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
type BaseMenuItem = { | type BaseMenuItem = { | ||||
@@ -354,13 +342,14 @@ export const Layout: React.FC<Props> = ({ | |||||
return ( | return ( | ||||
<> | <> | ||||
<Config /> | |||||
{ | { | ||||
(sidebarMainOpen || moreItemsOpen) | (sidebarMainOpen || moreItemsOpen) | ||||
&& ( | && ( | ||||
<DisableScrolling /> | <DisableScrolling /> | ||||
) | ) | ||||
} | } | ||||
<LayoutBase> | |||||
<> | |||||
<TopBar | <TopBar | ||||
wide | wide | ||||
brand={brand} | brand={brand} | ||||
@@ -447,7 +436,7 @@ export const Layout: React.FC<Props> = ({ | |||||
<ContentBase> | <ContentBase> | ||||
{children} | {children} | ||||
</ContentBase> | </ContentBase> | ||||
</LayoutBase> | |||||
</> | |||||
</> | </> | ||||
) | ) | ||||
} | } |
@@ -1,22 +1,22 @@ | |||||
import * as React from 'react'; | |||||
import styled from 'styled-components'; | |||||
import TopBar from '../../widgets/TopBar'; | |||||
import * as React from 'react' | |||||
import styled, {createGlobalStyle} from 'styled-components'; | |||||
import TopBar from '../../widgets/TopBar' | |||||
import {applyBackgroundColor, minWidthFactor} from '../../utilities/mixins' | |||||
import {configVar, loadConfig} from '../../utilities/helpers' | |||||
const LayoutBase = styled('div')({ | |||||
'--width-base': 'var(--width-base, 360px)', | |||||
'--height-topbar': 'var(--height-topbar, 4rem)', | |||||
const Config = createGlobalStyle({ | |||||
...loadConfig(), | |||||
}) | }) | ||||
const ContentBase = styled('main')({ | const ContentBase = styled('main')({ | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
'@media (min-width: 1080px)': { | |||||
paddingRight: 'calc(50% - var(--width-base, 360px) * 0.5)', | |||||
}, | |||||
...minWidthFactor(3)({ | |||||
paddingRight: `calc(50% - ${configVar('base-width')} * 0.5)`, | |||||
}), | |||||
}) | }) | ||||
const SidebarBase = styled('div')({ | const SidebarBase = styled('div')({ | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
backgroundColor: 'var(--color-bg, white)', | |||||
// prevent collapse of margin | // prevent collapse of margin | ||||
'::after': { | '::after': { | ||||
content: "''", | content: "''", | ||||
@@ -25,39 +25,37 @@ const SidebarBase = styled('div')({ | |||||
marginTop: -1, | marginTop: -1, | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
}, | }, | ||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
'@media (min-width: 1080px)': { | |||||
...applyBackgroundColor(), | |||||
...minWidthFactor(3)({ | |||||
position: 'absolute', | position: 'absolute', | ||||
top: 0, | top: 0, | ||||
right: 0, | right: 0, | ||||
width: 'calc(50% - var(--width-base, 360px) * 0.5)', | |||||
width: `calc(50% - ${configVar('base-width')} * 0.5)`, | |||||
height: '100%', | height: '100%', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const SidebarContainer = styled('div')({ | export const SidebarContainer = styled('div')({ | ||||
padding: '0 1rem', | padding: '0 1rem', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
width: '100%', | width: '100%', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
margin: '0 auto', | margin: '0 auto', | ||||
'@media (min-width: 1080px)': { | |||||
width: 'var(--width-base, 360px)', | |||||
...minWidthFactor(3)({ | |||||
width: `${configVar('base-width')}`, | |||||
marginLeft: 0, | marginLeft: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
export const ContentContainer = styled('div')({ | export const ContentContainer = styled('div')({ | ||||
padding: '0 1rem', | padding: '0 1rem', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
width: '100%', | width: '100%', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
margin: '0 auto', | margin: '0 auto', | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
marginRight: 0, | marginRight: 0, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
type Props = { | type Props = { | ||||
@@ -75,7 +73,8 @@ export const Layout: React.FC<Props> = ({ | |||||
children, | children, | ||||
}) => { | }) => { | ||||
return ( | return ( | ||||
<LayoutBase> | |||||
<> | |||||
<Config /> | |||||
<TopBar | <TopBar | ||||
wide | wide | ||||
brand={brand} | brand={brand} | ||||
@@ -89,6 +88,6 @@ export const Layout: React.FC<Props> = ({ | |||||
<SidebarBase> | <SidebarBase> | ||||
{sidebarMain} | {sidebarMain} | ||||
</SidebarBase> | </SidebarBase> | ||||
</LayoutBase> | |||||
</> | |||||
) | ) | ||||
} | } |
@@ -0,0 +1,35 @@ | |||||
import config from '../config.json' | |||||
const mangledVarNames: Record<string, string> = {} | |||||
export const configVar = (varName: keyof typeof config) => { | |||||
let { [varName]: mangledVarName } = mangledVarNames | |||||
if (!mangledVarName) { | |||||
mangledVarNames[varName] = mangledVarName = `config-${varName}` | |||||
} | |||||
return `var(--${mangledVarName}, ${config[varName]})` | |||||
} | |||||
export const loadConfig = (): any => { | |||||
return ({ | |||||
':root': Object.entries(mangledVarNames).reduce( | |||||
(varNames, [key, cssVarName]) => { | |||||
switch (key as keyof typeof config) { | |||||
case 'base-width': | |||||
if (typeof config[key as keyof typeof config] === 'number') { | |||||
return ({ | |||||
...varNames, | |||||
[`--${cssVarName}`]: `${config[key as keyof typeof config]}px`, | |||||
}) | |||||
} | |||||
break | |||||
} | |||||
return ({ | |||||
...varNames, | |||||
[`--${cssVarName}`]: config[key as keyof typeof config], | |||||
}) | |||||
}, | |||||
{}, | |||||
), | |||||
}) | |||||
} |
@@ -0,0 +1,13 @@ | |||||
import {CSSObject} from 'styled-components' | |||||
import config from '../config.json' | |||||
export const minWidthFactor = (factor: number) => (styles: CSSObject) => ({ | |||||
[`@media (min-width: ${config['base-width'] * factor}px)`]: styles, | |||||
}) | |||||
export const applyBackgroundColor = () => ({ | |||||
backgroundColor: 'var(--color-bg, white)', | |||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
}) |
@@ -1,5 +1,7 @@ | |||||
import * as React from 'react' | import * as React from 'react' | ||||
import styled from 'styled-components' | import styled from 'styled-components' | ||||
import {applyBackgroundColor, minWidthFactor} from '../../utilities/mixins' | |||||
import {configVar} from '../../utilities/helpers' | |||||
const Base = styled('div')({ | const Base = styled('div')({ | ||||
position: 'fixed', | position: 'fixed', | ||||
@@ -7,29 +9,26 @@ const Base = styled('div')({ | |||||
left: 0, | left: 0, | ||||
width: '100%', | width: '100%', | ||||
height: 'var(--height-topbar, 4rem)', | height: 'var(--height-topbar, 4rem)', | ||||
backgroundColor: 'var(--color-bg, white)', | |||||
zIndex: 2, | zIndex: 2, | ||||
'@media (prefers-color-scheme: dark)': { | |||||
backgroundColor: 'var(--color-bg, black)', | |||||
}, | |||||
...applyBackgroundColor(), | |||||
'~ *': { | '~ *': { | ||||
paddingTop: 'var(--height-topbar, 4rem)', | paddingTop: 'var(--height-topbar, 4rem)', | ||||
}, | }, | ||||
'~ main ~ *': { | '~ main ~ *': { | ||||
paddingTop: 0, | paddingTop: 0, | ||||
}, | }, | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
'~ main ~ *': { | '~ main ~ *': { | ||||
paddingTop: 'var(--height-topbar, 4rem)', | paddingTop: 'var(--height-topbar, 4rem)', | ||||
}, | }, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const Container = styled('div')({ | const Container = styled('div')({ | ||||
padding: '0 1rem', | padding: '0 1rem', | ||||
boxSizing: 'border-box', | boxSizing: 'border-box', | ||||
margin: '0 auto', | margin: '0 auto', | ||||
maxWidth: 'calc(var(--width-base, 360px) * 2)', | |||||
maxWidth: `calc(${configVar('base-width')} * 2)`, | |||||
width: '100%', | width: '100%', | ||||
height: '100%', | height: '100%', | ||||
display: 'flex', | display: 'flex', | ||||
@@ -37,9 +36,9 @@ const Container = styled('div')({ | |||||
}) | }) | ||||
const WideContainer = styled(Container)({ | const WideContainer = styled(Container)({ | ||||
'@media (min-width: 1080px)': { | |||||
maxWidth: 'calc(var(--width-base, 360px) * 3)', | |||||
}, | |||||
...minWidthFactor(3)({ | |||||
maxWidth: `calc(${configVar('base-width')} * 3)`, | |||||
}), | |||||
}) | }) | ||||
const BrandContainer = styled('div')({ | const BrandContainer = styled('div')({ | ||||
@@ -60,9 +59,9 @@ const ActionContainer = styled('div')({ | |||||
justifyContent: 'flex-end', | justifyContent: 'flex-end', | ||||
height: '100%', | height: '100%', | ||||
whiteSpace: 'nowrap', | whiteSpace: 'nowrap', | ||||
'@media (min-width: 720px)': { | |||||
...minWidthFactor(2)({ | |||||
minWidth: '8rem', | minWidth: '8rem', | ||||
}, | |||||
}), | |||||
}) | }) | ||||
const LinkContainer = styled('div')({ | const LinkContainer = styled('div')({ | ||||
@@ -77,10 +76,10 @@ const LinkContainer = styled('div')({ | |||||
}) | }) | ||||
const MenuLinkContainer = styled(LinkContainer)({ | const MenuLinkContainer = styled(LinkContainer)({ | ||||
'@media (min-width: 1080px)': { | |||||
...minWidthFactor(3)({ | |||||
position: 'absolute', | position: 'absolute', | ||||
left: -999999, | left: -999999, | ||||
}, | |||||
}), | |||||
}) | }) | ||||
type Props = { | type Props = { | ||||
@@ -31,5 +31,6 @@ | |||||
"forceConsistentCasingInFileNames": true, | "forceConsistentCasingInFileNames": true, | ||||
// `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` | ||||
"noEmit": true, | "noEmit": true, | ||||
"resolveJsonModule": true | |||||
} | } | ||||
} | } |