diff options
Diffstat (limited to 'packages/taler-wallet-webextension/src/mui/Grid.tsx')
-rw-r--r-- | packages/taler-wallet-webextension/src/mui/Grid.tsx | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/packages/taler-wallet-webextension/src/mui/Grid.tsx b/packages/taler-wallet-webextension/src/mui/Grid.tsx new file mode 100644 index 000000000..2db439778 --- /dev/null +++ b/packages/taler-wallet-webextension/src/mui/Grid.tsx @@ -0,0 +1,347 @@ +/* + This file is part of GNU Taler + (C) 2022 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/> + */ +import { css } from "@linaria/core"; +import { h, JSX, VNode, ComponentChildren, createContext } from "preact"; +import { useContext } from "preact/hooks"; +// eslint-disable-next-line import/extensions +import { theme } from "./style.js"; + +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 extends JSX.HTMLAttributes<HTMLDivElement> { + columns?: number | Partial<ResponsiveSize>; + container?: boolean; + item?: boolean; + + 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; +} +theme.breakpoints.up; + +function getOffset(val: number | string): string | number { + 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); + } + } + ${theme.breakpoints.up("lg")} { + width: calc(100% + var(--space-col-lg)); + margin-left: calc(-1 * var(--space-col-lg)); + & > div { + padding-left: var(--space-col-lg); + } + } + ${theme.breakpoints.up("xl")} { + width: calc(100% + var(--space-col-xl)); + margin-left: calc(-1 * var(--space-col-xl)); + & > div { + padding-left: var(--space-col-xl); + } + } +`; +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); + } + } + ${theme.breakpoints.up("lg")} { + margin-top: calc(-1 * var(--space-row-lg)); + & > div { + padding-top: var(--space-row-lg); + } + } + ${theme.breakpoints.up("xl")} { + margin-top: calc(-1 * var(--space-row-xl)); + & > div { + padding-top: var(--space-row-xl); + } + } +`; + +const sizeVariantXS = css` + ${theme.breakpoints.up("xs")} { + flex-basis: var(--relation-col-vs-xs); + flex-grow: 0; + max-width: var(--relation-col-vs-xs); + } +`; +const sizeVariantSM = css` + ${theme.breakpoints.up("sm")} { + flex-basis: var(--relation-col-vs-sm); + flex-grow: 0; + max-width: var(--relation-col-vs-sm); + } +`; +const sizeVariantMD = css` + ${theme.breakpoints.up("md")} { + flex-basis: var(--relation-col-vs-md); + flex-grow: 0; + max-width: var(--relation-col-vs-md); + } +`; +const sizeVariantLG = css` + ${theme.breakpoints.up("lg")} { + flex-basis: var(--relation-col-vs-lg); + flex-grow: 0; + max-width: var(--relation-col-vs-lg); + } +`; +const sizeVariantXL = css` + ${theme.breakpoints.up("xl")} { + flex-basis: var(--relation-col-vs-xl); + flex-grow: 0; + max-width: var(--relation-col-vs-xl); + } +`; + +const sizeVariantExpand = css` + flex-basis: 0; + flex-grow: 1; + max-width: 100%; +`; + +const sizeVariantAuto = css` + flex-basis: auto; + flex-grow: 0; + flex-shrink: 0; + max-width: none; + width: auto; +`; + +const GridContext = createContext<Partial<ResponsiveSize>>(toResponsive(12)); + +function toResponsive( + v: number | Partial<ResponsiveSize>, +): Partial<ResponsiveSize> { + const p = typeof v === "number" ? { xs: v } : v; + const xs = p.xs; + 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, + onClick, + ...rest +}: 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); + + 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-xs": relation(columns, ssize, "xs"), + "--relation-col-vs-sm": relation(columns, ssize, "sm"), + "--relation-col-vs-md": relation(columns, ssize, "md"), + "--relation-col-vs-lg": relation(columns, ssize, "lg"), + "--relation-col-vs-xl": relation(columns, ssize, "xl"), + }; + + return ( + <GridContext.Provider value={columns}> + <div + class={[ + root, + container && containerStyle, + item && itemStyle, + zeroMinWidth && zeroMinWidthStyle, + xs && + (xs === "auto" + ? sizeVariantAuto + : xs === "true" + ? sizeVariantExpand + : sizeVariantXS), + sm && + (sm === "auto" + ? sizeVariantAuto + : sm === "true" + ? sizeVariantExpand + : sizeVariantSM), + md && + (md === "auto" + ? sizeVariantAuto + : md === "true" + ? sizeVariantExpand + : sizeVariantMD), + lg && + (lg === "auto" + ? sizeVariantAuto + : lg === "true" + ? sizeVariantExpand + : sizeVariantLG), + xl && + (xl === "auto" + ? sizeVariantAuto + : xl === "true" + ? sizeVariantExpand + : sizeVariantXL), + container && columnGapVariant, + container && rowGapVariant, + ].join(" ")} + style={{ + ...relationStyles, + ...spacingStyles, + justifyContent, + alignItems, + flexWrap: wrap, + cursor: onClick ? "pointer" : "inherit", + }} + onClick={onClick} + {...rest} + > + {children} + </div> + </GridContext.Provider> + ); +} +function relation( + cols: Partial<ResponsiveSize>, + values: Partial<ResponsiveSize>, + size: ResponsiveKeys, +): string { + const colsNum = typeof cols === "number" ? cols : cols[size] || 12; + return ( + String(Math.round(((values[size] || 1) / colsNum) * 10e7) / 10e5) + "%" + ); +} |