diff options
author | Sebastian <sebasjm@gmail.com> | 2022-03-10 23:13:10 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-03-10 23:14:10 -0300 |
commit | 2150f3d96b25772dd608e245cd3508f857478c5b (patch) | |
tree | 7f449ec4df0f90c46774d1934ebee2eabb52cec7 | |
parent | 60a50babd14de6efbe16d9b427af85acc9da76e9 (diff) |
grid implementation
10 files changed, 744 insertions, 28 deletions
diff --git a/packages/taler-wallet-webextension/src/components/Banner.stories.tsx b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx new file mode 100644 index 000000000..136302166 --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/Banner.stories.tsx @@ -0,0 +1,54 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { Banner } from "./Banner"; +import { Fragment, h } from "preact"; + +export default { + title: "mui/banner", + component: Banner, +}; + +function Wrapper({ children }: any) { + return ( + <div + style={{ + display: "flex", + backgroundColor: "lightgray", + padding: 10, + width: "100%", + // width: 400, + // height: 400, + justifyContent: "center", + }} + > + <div style={{ flexGrow: 1 }}>{children}</div> + </div> + ); +} + +export const BasicExample = () => ( + <Fragment> + <Wrapper> + <Banner /> + </Wrapper> + </Fragment> +); diff --git a/packages/taler-wallet-webextension/src/components/Banner.tsx b/packages/taler-wallet-webextension/src/components/Banner.tsx index 6ff7b1019..f6af81184 100644 --- a/packages/taler-wallet-webextension/src/components/Banner.tsx +++ b/packages/taler-wallet-webextension/src/components/Banner.tsx @@ -1,33 +1,58 @@ import { h, Fragment, VNode } from "preact"; import { Divider } from "../mui/Divider"; -import { Button } from "./styled/index.js"; +import { Button } from "../mui/Button"; import { Typography } from "../mui/Typography"; import { Avatar } from "../mui/Avatar"; import { Grid } from "../mui/Grid"; import { Paper } from "../mui/Paper"; +import { Icon } from "./styled"; +import settingsIcon from "../../static/img/settings_black_24dp.svg"; +// & > a > div.settings-icon { +// mask: url(${settingsIcon}) no-repeat center; +// background-color: white; +// width: 24px; +// height: 24px; +// margin-left: auto; +// margin-right: 8px; +// padding: 4px; +// } +// & > a.active { +// background-color: #f8faf7; +// color: #0042b2; +// font-weight: bold; +// } +// & > a.active > div.settings-icon { +// background-color: #0042b2; +// } -function SignalWifiOffIcon(): VNode { - return <Fragment />; +function SignalWifiOffIcon({ ...rest }: any): VNode { + return <Icon {...rest} />; } -function Banner({}: {}) { +export function Banner({}: {}) { return ( <Fragment> - <Paper elevation={0} /*className={classes.paper}*/> - <Grid container wrap="nowrap" spacing={16} alignItems="center"> - <Grid item> - <Avatar /*className={classes.avatar}*/> - <SignalWifiOffIcon /> + <Paper elevation={3} /*className={classes.paper}*/> + <Grid + container + // wrap="nowrap" + // spacing={10} + alignItems="center" + columns={3} + > + <Grid item xs={1}> + <Avatar> + <SignalWifiOffIcon style={{ backgroundColor: "red" }} /> </Avatar> </Grid> - <Grid item> + <Grid item xs={1}> <Typography> You have lost connection to the internet. This app is offline. </Typography> </Grid> </Grid> - <Grid container justify="flex-end" spacing={8}> - <Grid item> + <Grid container justifyContent="flex-end" spacing={8} columns={3}> + <Grid item xs={1}> <Button color="primary">Turn on wifi</Button> </Grid> </Grid> diff --git a/packages/taler-wallet-webextension/src/components/styled/index.tsx b/packages/taler-wallet-webextension/src/components/styled/index.tsx index a5ed64a83..80bfaa549 100644 --- a/packages/taler-wallet-webextension/src/components/styled/index.tsx +++ b/packages/taler-wallet-webextension/src/components/styled/index.tsx @@ -826,6 +826,16 @@ export const NavigationHeader = styled.div` } `; +export const Icon = styled.div` + mask: url(${settingsIcon}) no-repeat center; + background-color: gray; + width: 24px; + height: 24px; + margin-left: auto; + margin-right: 8px; + padding: 4px; +`; + const image = `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`; export const NiceSelect = styled.div` diff --git a/packages/taler-wallet-webextension/src/mui/Avatar.tsx b/packages/taler-wallet-webextension/src/mui/Avatar.tsx index 963984ab6..d5bd9d421 100644 --- a/packages/taler-wallet-webextension/src/mui/Avatar.tsx +++ b/packages/taler-wallet-webextension/src/mui/Avatar.tsx @@ -1,5 +1,53 @@ +import { css } from "@linaria/core"; import { h, Fragment, VNode, ComponentChildren } from "preact"; +import { theme } from "./style"; -export function Avatar({}: { children: ComponentChildren }): VNode { - return <Fragment />; +const root = css` + position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 40px; + height: 40px; + font-family: ${theme.typography.fontFamily}; + font-size: ${theme.typography.pxToRem(20)}; + line-height: 1; + overflow: hidden; + user-select: none; +`; + +const colorStyle = css` + color: ${theme.palette.background.default}; + background-color: ${theme.palette.mode === "light" + ? theme.palette.grey[400] + : theme.palette.grey[600]}; +`; + +const avatarImageStyle = css` + width: 100%; + height: 100%; + text-align: center; + object-fit: cover; + color: transparent; + text-indent: 10000; +`; + +interface Props { + variant?: "circular" | "rounded" | "square"; + children?: ComponentChildren; +} + +export function Avatar({ variant, children, ...rest }: Props): VNode { + const borderStyle = + variant === "square" + ? theme.shape.squareBorder + : variant === "rounded" + ? theme.shape.roundBorder + : theme.shape.circularBorder; + return ( + <div class={[root, borderStyle].join(" ")} {...rest}> + {children} + </div> + ); } diff --git a/packages/taler-wallet-webextension/src/mui/Button.tsx b/packages/taler-wallet-webextension/src/mui/Button.tsx index b197ca26a..f3272a57b 100644 --- a/packages/taler-wallet-webextension/src/mui/Button.tsx +++ b/packages/taler-wallet-webextension/src/mui/Button.tsx @@ -185,7 +185,7 @@ export function Button({ disabled={disabled} class={[ theme.typography.button, - theme.shape.borderRadius, + theme.shape.roundBorder, ripple, baseStyle, button, diff --git a/packages/taler-wallet-webextension/src/mui/Grid.stories.tsx b/packages/taler-wallet-webextension/src/mui/Grid.stories.tsx new file mode 100644 index 000000000..3c9361326 --- /dev/null +++ b/packages/taler-wallet-webextension/src/mui/Grid.stories.tsx @@ -0,0 +1,192 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** + * + * @author Sebastian Javier Marchano (sebasjm) + */ + +import { Grid } from "./Grid"; +import { Fragment, h } from "preact"; + +export default { + title: "mui/grid", + component: Grid, +}; + +function Item({ children }: any) { + return ( + <div + style={{ + padding: 10, + backgroundColor: "white", + textAlign: "center", + color: "back", + }} + > + {children} + </div> + ); +} + +function Wrapper({ children }: any) { + return ( + <div + style={{ + display: "flex", + backgroundColor: "lightgray", + padding: 10, + width: "100%", + // width: 400, + // height: 400, + justifyContent: "center", + }} + > + <div style={{ flexGrow: 1 }}>{children}</div> + </div> + ); +} + +export const BasicExample = () => ( + <Fragment> + <Wrapper> + <Grid container spacing={2}> + <Grid item xs={8}> + <Item>xs=8</Item> + </Grid> + <Grid item xs={4}> + <Item>xs=4</Item> + </Grid> + <Grid item xs={4}> + <Item>xs=4</Item> + </Grid> + <Grid item xs={8}> + <Item>xs=8</Item> + </Grid> + </Grid> + </Wrapper> + <Wrapper> + <Grid container spacing={2}> + <Grid item xs={6} md={8}> + <Item>xs=6 md=8</Item> + </Grid> + <Grid item xs={6} md={4}> + <Item>xs=6 md=4</Item> + </Grid> + <Grid item xs={6} md={4}> + <Item>xs=6 md=4</Item> + </Grid> + <Grid item xs={6} md={8}> + <Item>xs=6 md=8</Item> + </Grid> + </Grid> + </Wrapper> + </Fragment> +); + +export const Responsive12ColumnsSize = () => ( + <Fragment> + <Wrapper> + <p>Item size is responsive: xs=6 sm=4 md=2</p> + <Grid container spacing={1} columns={12}> + {Array.from(Array(6)).map((_, index) => ( + <Grid item xs={6} sm={4} md={2} key={index}> + <Item>item {index}</Item> + </Grid> + ))} + </Grid> + </Wrapper> + <Wrapper> + <p>Item size is fixed</p> + <Grid container spacing={1} columns={12}> + {Array.from(Array(6)).map((_, index) => ( + <Grid item xs={6} key={index}> + <Item>item {index}</Item> + </Grid> + ))} + </Grid> + </Wrapper> + </Fragment> +); + +export const Responsive12Spacing = () => ( + <Fragment> + <Wrapper> + <p>Item space is responsive: xs=1 sm=2 md=3</p> + <Grid container spacing={{ xs: 2, sm: 4, md: 6 }} columns={12}> + {Array.from(Array(6)).map((_, index) => ( + <Grid item xs={6} key={index}> + <Item>item {index}</Item> + </Grid> + ))} + </Grid> + </Wrapper> + <Wrapper> + <p>Item space is fixed</p> + <Grid container spacing={1} columns={12}> + {Array.from(Array(6)).map((_, index) => ( + <Grid item xs={6} key={index}> + <Item>item {index}</Item> + </Grid> + ))} + </Grid> + </Wrapper> + + <Wrapper> + <p>Item row space is responsive: xs=6 sm=4 md=1</p> + <Grid + container + rowSpacing={{ xs: 6, sm: 3, md: 1 }} + columnSpacing={1} + columns={12} + > + {Array.from(Array(6)).map((_, index) => ( + <Grid item xs={6} key={index}> + <Item>item {index}</Item> + </Grid> + ))} + </Grid> + </Wrapper> + <Wrapper> + <p>Item col space is responsive: xs=6 sm=3 md=1</p> + <Grid + container + columnSpacing={{ xs: 6, sm: 3, md: 1 }} + rowSpacing={1} + columns={12} + > + {Array.from(Array(6)).map((_, index) => ( + <Grid item xs={6} key={index}> + <Item>item {index}</Item> + </Grid> + ))} + </Grid> + </Wrapper> + </Fragment> +); + +export const Example = () => ( + <Wrapper> + <p>Item row space is responsive: xs=6 sm=4 md=1</p> + <Grid container rowSpacing={3} columnSpacing={1} columns={12}> + {Array.from(Array(6)).map((_, index) => ( + <Grid item xs={6} key={index}> + <Item>item {index}</Item> + </Grid> + ))} + </Grid> + </Wrapper> +); diff --git a/packages/taler-wallet-webextension/src/mui/Grid.tsx b/packages/taler-wallet-webextension/src/mui/Grid.tsx index 3974e3c2e..ccabed060 100644 --- a/packages/taler-wallet-webextension/src/mui/Grid.tsx +++ b/packages/taler-wallet-webextension/src/mui/Grid.tsx @@ -1,13 +1,241 @@ -import { h, Fragment, VNode, ComponentChildren } from "preact"; +import { css } from "@linaria/core"; +import { h, Fragment, VNode, ComponentChildren, createContext } from "preact"; +import { useContext } from "preact/hooks"; +import { theme } from "./style"; -export function Grid({}: { +type ResponsiveKeys = "xs" | "sm" | "md" | "lg" | "xl"; + +export type ResponsiveSize = { + xs: number; + sm: number; + md: number; + lg: number; + xl: number; +}; + +const root = css` + box-sizing: border-box; +`; +const containerStyle = css` + display: flex; + flex-wrap: wrap; + width: 100%; +`; +const itemStyle = css` + margin: 0; +`; +const zeroMinWidthStyle = css` + min-width: 0px; +`; + +type GridSizes = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12; +type SpacingSizes = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; + +export interface Props { + columns?: number | Partial<ResponsiveSize>; container?: boolean; - wrap?: string; item?: boolean; - spacing?: number; - alignItems?: string; - justify?: string; + + direction?: "column-reverse" | "column" | "row-reverse" | "row"; + + lg?: GridSizes | "auto" | "true"; + md?: GridSizes | "auto" | "true"; + sm?: GridSizes | "auto" | "true"; + xl?: GridSizes | "auto" | "true"; + xs?: GridSizes | "auto" | "true"; + + wrap?: "nowrap" | "wrap-reverse" | "wrap"; + spacing?: SpacingSizes | Partial<ResponsiveSize>; + columnSpacing?: SpacingSizes | Partial<ResponsiveSize>; + rowSpacing?: SpacingSizes | Partial<ResponsiveSize>; + + alignItems?: "flex-start" | "flex-end" | "center" | "stretch" | "baseline"; + justifyContent?: + | "flex-start" + | "flex-end" + | "center" + | "space-around" + | "space-between" + | "space-evenly"; + + zeroMinWidth?: boolean; children: ComponentChildren; -}): VNode { - return <Fragment />; +} +theme.breakpoints.up; + +function getOffset(val: number | string) { + if (typeof val === "number") `${val}px`; + return val; +} + +const columnGapVariant = css` + ${theme.breakpoints.up("xs")} { + width: calc(100% + var(--space-col-xs)); + margin-left: calc(-1 * var(--space-col-xs)); + & > div { + padding-left: var(--space-col-xs); + } + } + ${theme.breakpoints.up("sm")} { + width: calc(100% + var(--space-col-sm)); + margin-left: calc(-1 * var(--space-col-sm)); + & > div { + padding-left: var(--space-col-sm); + } + } + ${theme.breakpoints.up("md")} { + width: calc(100% + var(--space-col-md)); + margin-left: calc(-1 * var(--space-col-md)); + & > div { + padding-left: var(--space-col-md); + } + } +`; +const rowGapVariant = css` + ${theme.breakpoints.up("xs")} { + margin-top: calc(-1 * var(--space-row-xs)); + & > div { + padding-top: var(--space-row-xs); + } + } + ${theme.breakpoints.up("sm")} { + margin-top: calc(-1 * var(--space-row-sm)); + & > div { + padding-top: var(--space-row-sm); + } + } + ${theme.breakpoints.up("md")} { + margin-top: calc(-1 * var(--space-row-md)); + & > div { + padding-top: var(--space-row-md); + } + } +`; + +const sizeVariant = css` + ${theme.breakpoints.up("xs")} { + flex-basis: var(--relation-col-vs-xs); + flex-grow: 0; + max-width: var(--relation-col-vs-xs); + } + ${theme.breakpoints.up("sm")} { + flex-basis: var(--relation-col-vs-sm); + flex-grow: 0; + max-width: var(--relation-col-vs-sm); + } + ${theme.breakpoints.up("md")} { + flex-basis: var(--relation-col-vs-md); + flex-grow: 0; + max-width: var(--relation-col-vs-md); + } +`; + +const GridContext = createContext<ResponsiveSize>(toResponsive(12)); + +function toResponsive(v: number | Partial<ResponsiveSize>): ResponsiveSize { + const p = typeof v === "number" ? { xs: v } : v; + const xs = p.xs || 12; + const sm = p.sm || xs; + const md = p.md || sm; + const lg = p.lg || md; + const xl = p.xl || lg; + return { + xs, + sm, + md, + lg, + xl, + }; +} + +export function Grid({ + columns: cp, + container = false, + item = false, + direction = "row", + lg, + md, + sm, + xl, + xs, + wrap = "wrap", + spacing = 0, + columnSpacing: csp, + rowSpacing: rsp, + alignItems, + justifyContent, + zeroMinWidth = false, + children, +}: Props): VNode { + const cc = useContext(GridContext); + const columns = !cp ? cc : toResponsive(cp); + + const rowSpacing = rsp ? toResponsive(rsp) : toResponsive(spacing); + const columnSpacing = csp ? toResponsive(csp) : toResponsive(spacing); + + const ssize = toResponsive({ xs, md, lg, xl, sm } as any); + + if (container) { + console.log(rowSpacing); + console.log(columnSpacing); + } + const spacingStyles = !container + ? {} + : { + "--space-col-xs": getOffset(theme.spacing(columnSpacing.xs)), + "--space-col-sm": getOffset(theme.spacing(columnSpacing.sm)), + "--space-col-md": getOffset(theme.spacing(columnSpacing.md)), + "--space-col-lg": getOffset(theme.spacing(columnSpacing.lg)), + "--space-col-xl": getOffset(theme.spacing(columnSpacing.xl)), + + "--space-row-xs": getOffset(theme.spacing(rowSpacing.xs)), + "--space-row-sm": getOffset(theme.spacing(rowSpacing.sm)), + "--space-row-md": getOffset(theme.spacing(rowSpacing.md)), + "--space-row-lg": getOffset(theme.spacing(rowSpacing.lg)), + "--space-row-xl": getOffset(theme.spacing(rowSpacing.xl)), + }; + const relationStyles = !item + ? {} + : { + "--relation-col-vs-sm": relation(columns, ssize, "sm"), + "--relation-col-vs-lg": relation(columns, ssize, "lg"), + "--relation-col-vs-xs": relation(columns, ssize, "xs"), + "--relation-col-vs-xl": relation(columns, ssize, "xl"), + "--relation-col-vs-md": relation(columns, ssize, "md"), + }; + + return ( + <GridContext.Provider value={columns}> + <div + id={container ? "container" : "item"} + class={[ + root, + container && containerStyle, + item && itemStyle, + zeroMinWidth && zeroMinWidthStyle, + sizeVariant, + container && columnGapVariant, + container && rowGapVariant, + ].join(" ")} + style={{ + ...relationStyles, + ...spacingStyles, + justifyContent, + alignItems, + }} + > + {children} + </div> + </GridContext.Provider> + ); +} +function relation( + cols: ResponsiveSize, + values: ResponsiveSize, + size: ResponsiveKeys, +) { + const colsNum = typeof cols === "number" ? cols : cols[size] || 12; + return ( + String(Math.round(((values[size] || 1) / colsNum) * 10e7) / 10e5) + "%" + ); } diff --git a/packages/taler-wallet-webextension/src/mui/Paper.tsx b/packages/taler-wallet-webextension/src/mui/Paper.tsx index 52524380b..00eeda324 100644 --- a/packages/taler-wallet-webextension/src/mui/Paper.tsx +++ b/packages/taler-wallet-webextension/src/mui/Paper.tsx @@ -35,7 +35,7 @@ export function Paper({ <div class={[ baseStyle, - !square && theme.shape.borderRadius, + !square && theme.shape.roundBorder, borderVariant[variant], ].join(" ")} style={{ diff --git a/packages/taler-wallet-webextension/src/mui/Typography.tsx b/packages/taler-wallet-webextension/src/mui/Typography.tsx index 4fc614463..830f1005a 100644 --- a/packages/taler-wallet-webextension/src/mui/Typography.tsx +++ b/packages/taler-wallet-webextension/src/mui/Typography.tsx @@ -1,9 +1,92 @@ +import { css } from "@linaria/core"; import { h, Fragment, VNode, ComponentChildren } from "preact"; +type VariantEnum = + | "body1" + | "body2" + | "button" + | "caption" + | "h1" + | "h2" + | "h3" + | "h4" + | "h5" + | "h6" + | "inherit" + | "overline" + | "subtitle1" + | "subtitle2"; + interface Props { - children: ComponentChildren; + align?: "center" | "inherit" | "justify" | "left" | "right"; + gutterBottom?: boolean; + noWrap?: boolean; + paragraph?: boolean; + variant?: VariantEnum; + children?: ComponentChildren; } -export function Typography({ children }: Props): VNode { - return <p>{children}</p>; +const defaultVariantMapping = { + h1: "h1", + h2: "h2", + h3: "h3", + h4: "h4", + h5: "h5", + h6: "h6", + subtitle1: "h6", + subtitle2: "h6", + body1: "p", + body2: "p", + inherit: "p", +}; + +const root = css` + margin: 0; +`; + +const noWrapStyle = css` + overflow: "hidden"; + text-overflow: "ellipsis"; + white-space: "nowrap"; +`; +const gutterBottomStyle = css` + margin-bottom: 0.35em; +`; +const paragraphStyle = css` + margin-bottom: 16px; +`; + +export function Typography({ + align, + gutterBottom = false, + noWrap = false, + paragraph = false, + variant = "body1", + children, +}: Props): VNode { + const cmp = paragraph + ? "p" + : defaultVariantMapping[variant as "h1"] || "span"; + + const alignStyle = + align == "inherit" + ? {} + : { + textAlign: align, + }; + return h( + cmp, + { + class: [ + root, + noWrap && noWrapStyle, + gutterBottom && gutterBottomStyle, + paragraph && paragraphStyle, + ].join(" "), + style: { + ...alignStyle, + }, + }, + children, + ); } diff --git a/packages/taler-wallet-webextension/src/mui/style.tsx b/packages/taler-wallet-webextension/src/mui/style.tsx index 84b0538be..e2af05c49 100644 --- a/packages/taler-wallet-webextension/src/mui/style.tsx +++ b/packages/taler-wallet-webextension/src/mui/style.tsx @@ -22,6 +22,14 @@ export function pxToRem(size: number): string { return `${(size / htmlFontSize) * coef}rem`; } +export interface Spacing { + (): string; + (value: number): string; + (topBottom: number, rightLeft: number): string; + (top: number, rightLeft: number, bottom: number): string; + (top: number, right: number, bottom: number, left: number): string; +} + export const theme = createTheme(); export const ripple = css` @@ -117,11 +125,78 @@ function createTheme() { const shadows = createAllShadows(); const transitions = createTransitions({}); const breakpoints = createBreakpoints({}); + const spacing = createSpacing(); const shape = { - borderRadius: css` + roundBorder: css` border-radius: 4px; `, + squareBorder: css` + border-radius: 0px; + `, + circularBorder: css` + border-radius: 50%; + `, }; + + ///////////////////// + ///////////////////// SPACING + ///////////////////// + + function createUnaryUnit(theme: { spacing: number }, defaultValue: number) { + const themeSpacing = theme.spacing || defaultValue; + + if (typeof themeSpacing === "number") { + return (abs: number | string) => { + if (typeof abs === "string") { + return abs; + } + + return themeSpacing * abs; + }; + } + + if (Array.isArray(themeSpacing)) { + return (abs: number | string) => { + if (typeof abs === "string") { + return abs; + } + + return themeSpacing[abs]; + }; + } + + if (typeof themeSpacing === "function") { + return themeSpacing; + } + + return (a: string | number) => ""; + } + + function createUnarySpacing(theme: { spacing: number }) { + return createUnaryUnit(theme, 8); + } + + function createSpacing(spacingInput: number = 8): Spacing { + // Material Design layouts are visually balanced. Most measurements align to an 8dp grid, which aligns both spacing and the overall layout. + // Smaller components, such as icons, can align to a 4dp grid. + // https://material.io/design/layout/understanding-layout.html#usage + const transform = createUnarySpacing({ + spacing: spacingInput, + }); + + const spacing = (...argsInput: ReadonlyArray<number | string>): string => { + const args = argsInput.length === 0 ? [1] : argsInput; + + return args + .map((argument) => { + const output = transform(argument); + return typeof output === "number" ? `${output}px` : output; + }) + .join(" "); + }; + + return spacing; + } ///////////////////// ///////////////////// BREAKPOINTS ///////////////////// @@ -691,6 +766,7 @@ function createTheme() { shape, transitions, breakpoints, + spacing, pxToRem, }; } |