From a7f65c401ac5ab8893e56459f5f9d254ebd1c70d Mon Sep 17 00:00:00 2001 From: Juanra GM Date: Sat, 21 May 2022 17:19:06 +0200 Subject: [PATCH] refactor: separate style properties --- .changeset/healthy-snakes-join.md | 6 + packages/material/src/Grid/Grid.tsx | 10 +- packages/material/src/Grow/Grow.tsx | 8 +- packages/material/src/Stack/Stack.tsx | 4 +- .../material/src/styles/createTypography.tsx | 30 +-- .../material/src/transitions/transition.ts | 4 +- packages/material/src/transitions/utils.ts | 4 +- packages/system/src/Box/Box.tsx | 40 ++-- packages/system/src/breakpoints.ts | 13 +- .../system/src/createStylePropsFactory.ts | 182 ---------------- packages/system/src/createStyleStoreEffect.ts | 20 -- packages/system/src/createStyled.tsx | 78 ++++--- packages/system/src/createSxClass.ts | 3 + packages/system/src/createSxMemo.ts | 15 -- packages/system/src/createSxPropsFactory.ts | 91 -------- packages/system/src/createSystemProps.ts | 204 ++++++++++++++++++ packages/system/src/index.tsx | 4 +- packages/system/src/resolveStyleProps.ts | 19 -- packages/system/src/resolveStyledProps.ts | 17 ++ packages/system/src/resolveSxProps.ts | 28 +++ packages/system/src/styleProps.ts | 20 ++ packages/system/src/stylePropsFactory.ts | 12 -- packages/system/src/styledProps.ts | 15 ++ packages/system/src/sxProps.ts | 30 +-- packages/system/src/sxPropsFactory.ts | 12 -- packages/system/src/systemProps.ts | 24 +++ 26 files changed, 430 insertions(+), 463 deletions(-) create mode 100644 .changeset/healthy-snakes-join.md delete mode 100644 packages/system/src/createStylePropsFactory.ts delete mode 100644 packages/system/src/createStyleStoreEffect.ts delete mode 100644 packages/system/src/createSxMemo.ts delete mode 100644 packages/system/src/createSxPropsFactory.ts create mode 100644 packages/system/src/createSystemProps.ts delete mode 100644 packages/system/src/resolveStyleProps.ts create mode 100644 packages/system/src/resolveStyledProps.ts create mode 100644 packages/system/src/resolveSxProps.ts create mode 100644 packages/system/src/styleProps.ts delete mode 100644 packages/system/src/stylePropsFactory.ts create mode 100644 packages/system/src/styledProps.ts delete mode 100644 packages/system/src/sxPropsFactory.ts create mode 100644 packages/system/src/systemProps.ts diff --git a/.changeset/healthy-snakes-join.md b/.changeset/healthy-snakes-join.md new file mode 100644 index 000000000..c8cd7db57 --- /dev/null +++ b/.changeset/healthy-snakes-join.md @@ -0,0 +1,6 @@ +--- +"@suid/material": minor +"@suid/system": minor +--- + +Global refactoring for separating style properties into `StyleProps`, `StyledProps`, `SxProps` and `SystemProps` diff --git a/packages/material/src/Grid/Grid.tsx b/packages/material/src/Grid/Grid.tsx index 38aa4f1bd..9b5166a70 100644 --- a/packages/material/src/Grid/Grid.tsx +++ b/packages/material/src/Grid/Grid.tsx @@ -19,7 +19,7 @@ import { handleBreakpoints, resolveBreakpointValues, } from "@suid/system"; -import { SxPropsObject } from "@suid/system/sxProps"; +import StyledProps from "@suid/system/styledProps"; import { InPropsOf } from "@suid/types"; import clsx from "clsx"; import { JSXElement, useContext } from "solid-js"; @@ -97,7 +97,7 @@ export function generateGrid(input: { const { theme, ownerState } = input; let size: number | boolean | "auto"; - return theme.breakpoints.keys.reduce( + return theme.breakpoints.keys.reduce( (globalStyles, breakpoint) => { // Use side effect over immutability for better performance. let styles = {}; @@ -195,7 +195,7 @@ export function generateDirection(input: { }); return handleBreakpoints({ theme }, directionValues, (propValue) => { - let output: SxPropsObject = { + let output: StyledProps = { flexDirection: propValue, }; @@ -235,7 +235,7 @@ export function generateRowGap(input: { [`& > .${gridClasses.item}`]: { paddingTop: getOffset(themeSpacing), }, - } as SxPropsObject; + } as StyledProps; } return {}; @@ -268,7 +268,7 @@ export function generateColumnGap(input: { [`& > .${gridClasses.item}`]: { paddingLeft: getOffset(themeSpacing), }, - } as SxPropsObject; + } as StyledProps; } return {}; diff --git a/packages/material/src/Grow/Grow.tsx b/packages/material/src/Grow/Grow.tsx index f41110835..ede468604 100644 --- a/packages/material/src/Grow/Grow.tsx +++ b/packages/material/src/Grow/Grow.tsx @@ -3,7 +3,7 @@ import useTheme from "../styles/useTheme"; import { reflow, getTransitionProps } from "../transitions/utils"; import Transition, { TransitionStatus } from "@suid/base/Transition"; import createComponentFactory from "@suid/base/createComponentFactory"; -import { NativeStyleProps } from "@suid/system/sxProps"; +import StyleProps from "@suid/system/styleProps"; import { children, onCleanup } from "solid-js"; const $ = createComponentFactory()({ @@ -20,7 +20,7 @@ function getScale(value: number) { return `scale(${value}, ${value ** 2})`; } -const styles: { [name in TransitionStatus]?: NativeStyleProps } = { +const styles: { [name in TransitionStatus]?: StyleProps } = { entering: { opacity: 1, transform: getScale(1), @@ -153,13 +153,13 @@ const Grow = $.component(function Grow({ props, otherProps }) { element.style.removeProperty("visibility"); } - const style: NativeStyleProps = { + const style: StyleProps = { ...(styles[state] || {}), ...(otherProps.style || {}), }; for (const name in style) { - const value = style[name as keyof NativeStyleProps] as any; + const value = style[name as keyof StyleProps] as any; if (value === undefined) { element.style.removeProperty(name); } else { diff --git a/packages/material/src/Stack/Stack.tsx b/packages/material/src/Stack/Stack.tsx index ff4a3a7c9..e18ae4df2 100644 --- a/packages/material/src/Stack/Stack.tsx +++ b/packages/material/src/Stack/Stack.tsx @@ -9,7 +9,7 @@ import { import { Breakpoint } from "@suid/system/createTheme/createBreakpoints"; import mergeSxObjects from "@suid/system/mergeSxObjects"; import { createUnarySpacing } from "@suid/system/spacing"; -import { SxPropsObject } from "@suid/system/sxProps"; +import StyledProps from "@suid/system/styledProps"; import { JSXElement, Show } from "solid-js"; const $ = createComponentFactory()({ @@ -105,7 +105,7 @@ const StackRoot = styled("div", { breakpoint ? directionValues[breakpoint] : ownerState.direction )}` as any]: transformer(propValue), }, - } as SxPropsObject; + } as StyledProps; }; styles = mergeSxObjects( styles, diff --git a/packages/material/src/styles/createTypography.tsx b/packages/material/src/styles/createTypography.tsx index 278a5da68..b1e3b657b 100644 --- a/packages/material/src/styles/createTypography.tsx +++ b/packages/material/src/styles/createTypography.tsx @@ -1,4 +1,4 @@ -import { NativeStyleProps, CSSProps } from "@suid/system/sxProps"; +import StyledProps from "@suid/system/styledProps"; import { DeepPartial } from "@suid/types"; import merge from "@suid/utils/merge"; @@ -7,19 +7,19 @@ export type ThemeTypographyType = TypographyOptions & { }; export type TypographyVariants = { - h1: CSSProps; - h2: CSSProps; - h3: CSSProps; - h4: CSSProps; - h5: CSSProps; - h6: CSSProps; - subtitle1: CSSProps; - subtitle2: CSSProps; - body1: CSSProps; - body2: CSSProps; - button: CSSProps; - caption: CSSProps; - overline: CSSProps; + h1: StyledProps; + h2: StyledProps; + h3: StyledProps; + h4: StyledProps; + h5: StyledProps; + h6: StyledProps; + subtitle1: StyledProps; + subtitle2: StyledProps; + body1: StyledProps; + body2: StyledProps; + button: StyledProps; + caption: StyledProps; + overline: StyledProps; }; export type Variant = keyof TypographyVariants; @@ -86,7 +86,7 @@ export function makeVariant( lineHeight: `${lineHeight}`, letterSpacing: `${round(letterSpacing / size)}em`, ...(casing ? { textTransform: "uppercase" } : {}), - } as NativeStyleProps; + } as StyledProps; } export function createTypography( diff --git a/packages/material/src/transitions/transition.ts b/packages/material/src/transitions/transition.ts index 32df9db8b..569136eca 100644 --- a/packages/material/src/transitions/transition.ts +++ b/packages/material/src/transitions/transition.ts @@ -2,7 +2,7 @@ import { TransitionProps as _TransitionProps, TransitionActions, } from "@suid/base/Transition"; -import { NativeStyleProps } from "@suid/system/sxProps"; +import StyleProps from "@suid/system/styleProps"; export type TransitionHandlerKeys = | "onEnter" @@ -32,5 +32,5 @@ export type TransitionKeys = export interface TransitionProps extends TransitionActions, Partial> { - style?: NativeStyleProps; + style?: StyleProps; } diff --git a/packages/material/src/transitions/utils.ts b/packages/material/src/transitions/utils.ts index f6d569cb9..d8abb2c3d 100644 --- a/packages/material/src/transitions/utils.ts +++ b/packages/material/src/transitions/utils.ts @@ -1,4 +1,4 @@ -import { NativeStyleProps } from "@suid/system/sxProps"; +import StyleProps from "@suid/system/styleProps"; export const reflow = (node: Element) => node.scrollTop; @@ -6,7 +6,7 @@ interface ComponentProps { easing: string | { enter?: string; exit?: string } | undefined; style: | Pick< - NativeStyleProps, + StyleProps, "transitionDuration" | "transitionTimingFunction" | "transitionDelay" > | undefined; diff --git a/packages/system/src/Box/Box.tsx b/packages/system/src/Box/Box.tsx index 5242cfe8b..36be6139b 100644 --- a/packages/system/src/Box/Box.tsx +++ b/packages/system/src/Box/Box.tsx @@ -1,10 +1,8 @@ import { BoxSelfProps } from "."; import Dynamic from "../Dynamic/Dynamic"; -import createSxClass from "../createSxClass"; +import createSxClass, { resolvedPropKey } from "../createSxClass"; import defineComponent from "../defineComponent"; -import resolveStyleProps from "../resolveStyleProps"; -import { SxPropsObject } from "../sxProps"; -import sxPropsFactory from "../sxPropsFactory"; +import resolveSxProps from "../resolveSxProps"; import useTheme from "../useTheme"; import { BoxTypeMap } from "./BoxProps"; import { mergeProps, splitProps } from "solid-js"; @@ -18,10 +16,13 @@ export const Box = defineComponent(function Box(inProps) { }, inProps ); + const [props, otherProps] = splitProps(allProps, boxSelfProps); - const theme = useTheme(); + + const useInTheme = () => inProps.theme || useTheme(); const forwardSx = () => !!inProps.component && typeof inProps.component !== "string"; + const dynamicProps = mergeProps(otherProps, { get sx() { return forwardSx() ? inProps.sx : undefined; @@ -29,24 +30,19 @@ export const Box = defineComponent(function Box(inProps) { }); const sxClass = createSxClass(() => { - if (!props.sx || forwardSx()) return []; - const sxArray = Array.isArray(props.sx) ? props.sx : [props.sx]; - const result = sxArray.map( - (object: SxPropsObject & { resolved?: boolean }) => { - if (object.resolved) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { resolved, ...restObject } = object; - return restObject; - } else { - return resolveStyleProps( - object, - props.theme || theme, - sxPropsFactory - ); - } - } + const theme = useInTheme(); + const haveStyles = !!props.sx; + if (!haveStyles || forwardSx()) return []; + const objects = Array.isArray(props.sx) + ? props.sx + : props.sx + ? [props.sx] + : []; + return objects.map((object) => + (object as never)[resolvedPropKey] + ? object + : resolveSxProps(object, theme) ); - return result; }); const className = () => { diff --git a/packages/system/src/breakpoints.ts b/packages/system/src/breakpoints.ts index 0d9c8408f..f42c1d28b 100644 --- a/packages/system/src/breakpoints.ts +++ b/packages/system/src/breakpoints.ts @@ -1,6 +1,6 @@ import { Theme } from "./createTheme"; import { Breakpoint } from "./createTheme/createBreakpoints"; -import { CSSProps, SxPropsObject } from "./sxProps"; +import StyledProps from "./styledProps"; export type BreakpointValueType = { [K in T]?: number | boolean | "auto"; @@ -32,7 +32,7 @@ export function handleBreakpoints( theme: Theme; }, propValue: any[], - styleFromPropValue: (value: any, breakpoint?: Breakpoint) => SxPropsObject + styleFromPropValue: (value: any, breakpoint?: Breakpoint) => StyledProps ) { const theme = props.theme || ({} as Theme); @@ -43,7 +43,7 @@ export function handleBreakpoints( ...acc, ...themeBreakpoints.up( themeBreakpoints.keys[index], - styleFromPropValue(propValue[index]) as SxPropsObject + styleFromPropValue(propValue[index]) ), }; return acc; @@ -53,7 +53,7 @@ export function handleBreakpoints( if (typeof propValue === "object") { const themeBreakpoints = theme.breakpoints; const keys = Object.keys(propValue) as Breakpoint[]; - return keys.reduce((acc, breakpoint) => { + return keys.reduce((acc, breakpoint) => { // key is breakpoint if ( Object.keys(themeBreakpoints.values || values).indexOf(breakpoint) !== @@ -63,10 +63,7 @@ export function handleBreakpoints( ...acc, ...themeBreakpoints.up( breakpoint, - styleFromPropValue( - propValue[breakpoint], - breakpoint - ) as SxPropsObject + styleFromPropValue(propValue[breakpoint], breakpoint) ), }; } else { diff --git a/packages/system/src/createStylePropsFactory.ts b/packages/system/src/createStylePropsFactory.ts deleted file mode 100644 index 44a4abab0..000000000 --- a/packages/system/src/createStylePropsFactory.ts +++ /dev/null @@ -1,182 +0,0 @@ -import { Theme } from "./createTheme"; - -const dirMap = { - t: ["Top"], - r: ["Right"], - b: ["Bottom"], - l: ["Left"], - x: ["Left", "Right"], - y: ["Top", "Bottom"], -}; - -type StylePropsConfig = ReturnType; - -export type StyleProps = { - [K in keyof StylePropsConfig]?: Parameters[0]; -}; - -export type PropsOptions = { - onValue?: (name: string, value: any, theme: Theme) => any; -}; - -export type Dir = keyof typeof dirMap; - -function numberProp( - name: string, - suffix: string[], - options: PropsOptions = {}, - valueSuffix?: string -) { - const names = suffix.length ? suffix.map((v) => `${name}${v}`) : [name]; - return (value: string | number, theme: Theme) => { - return names.reduce((result, name) => { - value = options.onValue ? options.onValue(name, value, theme) : value; - if (typeof value === "number") - value = valueSuffix ? `${value}${valueSuffix}` : value; - result[name] = value; - return result; - }, {} as Record); - }; -} - -function pxProp(name: string, suffix: string[], options: PropsOptions = {}) { - return numberProp(name, suffix, options, "px"); -} - -function prop(name: string, options: PropsOptions = {}) { - return (value: V, theme: Theme) => { - return { - [name]: options.onValue ? options.onValue(name, value, theme) : value, - } as Record; - }; -} - -export function createStylePropsFactory( - options: { - position?: PropsOptions; - palette?: PropsOptions; - sizing?: PropsOptions; - border?: PropsOptions; - spacing?: PropsOptions; - typography?: PropsOptions; - } = {} -) { - return { - ...createStylePositionProps(options.position), - ...createStylePaletteProps(options.palette), - ...createStyleSizingProps(options.sizing), - ...createStyleBorderProps(options.border), - ...createStyleSpacingProps(options.spacing), - ...createStyleTypographyProps(options.typography), - }; -} - -export function createStylePositionProps(options: PropsOptions = {}) { - return { - zIndex: numberProp("zIndex", [], options), - top: pxProp("top", [], options), - right: pxProp("right", [], options), - bottom: pxProp("bottom", [], options), - left: pxProp("left", [], options), - }; -} - -export function createStylePaletteProps(options: PropsOptions = {}) { - return { - color: prop("color", options), - bgcolor: prop("backgroundColor", options), - backgroundColor: prop("backgroundColor", options), - }; -} - -export function createStyleSizingProps(options: PropsOptions = {}) { - return { - width: pxProp("width", [], options), - maxWidth: pxProp("maxWidth", [], options), - minWidth: pxProp("minWidth", [], options), - height: pxProp("height", [], options), - maxHeight: pxProp("maxHeight", [], options), - minHeight: pxProp("minHeight", [], options), - boxSizing: pxProp("boxSizing", [], options), - }; -} - -export function createStyleBorderProps(options: PropsOptions = {}) { - const b = "border"; - return { - border: pxProp(b, [], options), - borderTop: pxProp(b, dirMap["t"], options), - borderRight: pxProp(b, dirMap["r"], options), - borderBottom: pxProp(b, dirMap["b"], options), - borderLeft: pxProp(b, dirMap["l"], options), - borderColor: prop("borderColor", options), - borderTopColor: prop("borderTopColor", options), - borderRightColor: prop("borderRightColor", options), - borderBottomColor: prop("borderBottomColor", options), - borderLeftColor: prop("borderLeftColor", options), - borderRadius: pxProp(b, ["Radius"], options), - }; -} - -export function createStyleTypographyProps(options: PropsOptions = {}) { - return { - typography: prop("typography", options), - fontFamily: prop("fontFamily", options), - fontSize: pxProp("fontSize", [], options), - fontStyle: prop("fontStyle", options), - fontWeight: prop("fontWeight", options), - letterSpacing: pxProp("letterSpacing", [], options), - lineHeight: prop("lineHeight", options), - textAlign: prop("textAlign", options), - textTransform: prop("textTransform", options), - }; -} - -export function createStyleSpacingProps(options: PropsOptions = {}) { - const m = "margin"; - const p = "padding"; - return { - m: pxProp(m, [], options), - mt: pxProp(m, dirMap["t"], options), - mr: pxProp(m, dirMap["r"], options), - mb: pxProp(m, dirMap["b"], options), - ml: pxProp(m, dirMap["l"], options), - mx: pxProp(m, dirMap["x"], options), - my: pxProp(m, dirMap["y"], options), - margin: pxProp(m, [], options), - marginTop: pxProp(m, dirMap["t"], options), - marginRight: pxProp(m, dirMap["r"], options), - marginBottom: pxProp(m, dirMap["b"], options), - marginLeft: pxProp(m, dirMap["l"], options), - marginX: pxProp(m, dirMap["x"], options), - marginY: pxProp(m, dirMap["y"], options), - marginInline: pxProp(m, ["Inline", "InlineStart"], options), - marginInlineStart: pxProp(m, ["InlineStart"], options), - marginInlineEnd: pxProp(m, ["InlineEnd"], options), - marginBlock: pxProp(m, ["BlockStart", "BlockEnd"], options), - marginBlockStart: pxProp(m, ["BlockStart"], options), - marginBlockEnd: pxProp(m, ["BlockEnd"], options), - p: pxProp(p, [], options), - pt: pxProp(p, dirMap["t"], options), - pr: pxProp(p, dirMap["r"], options), - pb: pxProp(p, dirMap["b"], options), - pl: pxProp(p, dirMap["l"], options), - px: pxProp(p, dirMap["x"], options), - py: pxProp(p, dirMap["y"], options), - padding: pxProp(p, [], options), - paddingTop: pxProp(p, dirMap["t"], options), - paddingRight: pxProp(p, dirMap["r"], options), - paddingBottom: pxProp(p, dirMap["b"], options), - paddingLeft: pxProp(p, dirMap["l"], options), - paddingX: pxProp(p, dirMap["x"], options), - paddingY: pxProp(p, dirMap["y"], options), - paddingInline: pxProp(p, ["Inline", "InlineStart"], options), - paddingInlineStart: pxProp(p, ["InlineStart"], options), - paddingInlineEnd: pxProp(p, ["InlineEnd"], options), - paddingBlock: pxProp(p, ["BlockStart", "BlockEnd"], options), - paddingBlockStart: pxProp(p, ["BlockStart"], options), - paddingBlockEnd: pxProp(p, ["BlockEnd"], options), - }; -} - -export default createStylePropsFactory; diff --git a/packages/system/src/createStyleStoreEffect.ts b/packages/system/src/createStyleStoreEffect.ts deleted file mode 100644 index cec93e65a..000000000 --- a/packages/system/src/createStyleStoreEffect.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { applyClassList } from "./createClassListEffect"; -import { createEffect } from "solid-js"; - -function createStyleStoreEffect( - element: { ref: HTMLElement }, - style: Record -) { - createEffect | undefined>((prev) => { - return applyClassList( - element.ref, - Object.values(style ?? {}).reduce((result, name) => { - result[name] = true; - return result; - }, {} as Record), - prev - ); - }, undefined); -} - -export default createStyleStoreEffect; diff --git a/packages/system/src/createStyled.tsx b/packages/system/src/createStyled.tsx index e7d370c71..b70af9547 100644 --- a/packages/system/src/createStyled.tsx +++ b/packages/system/src/createStyled.tsx @@ -1,9 +1,8 @@ -import { SxProps } from "."; import Box, { BoxTypeMap } from "./Box"; -import createSxMemo from "./createSxMemo"; import type { Theme } from "./createTheme/createTheme"; -import resolveStyleProps from "./resolveStyleProps"; -import { SxPropsObject } from "./sxProps"; +import resolveStyledProps from "./resolveStyledProps"; +import { StyledProps } from "./styledProps"; +import { SxProps } from "./sxProps"; import useTheme from "./useTheme"; import { ElementType, @@ -12,7 +11,7 @@ import { PropsOf, } from "@suid/types"; import clsx from "clsx"; -import { createMemo, Accessor } from "solid-js"; +import { createMemo } from "solid-js"; import { ComponentProps as _ComponentProps, JSX, @@ -20,7 +19,7 @@ import { Show, } from "solid-js"; -export interface StyledProps { +export interface ComponentProps { ownerState: O; theme: T; sx?: SxProps; @@ -28,9 +27,9 @@ export interface StyledProps { } type Style, P, O> = - | ((props: StyledProps & { props: P }) => false | SxPropsObject) + | ((props: ComponentProps & { props: P }) => false | StyledProps) | false - | SxPropsObject; + | StyledProps; type StyledOptions = { name?: N; @@ -43,13 +42,43 @@ type StyledOptions = { ) => (string | false)[]; }; -export const skipProps: (keyof StyledProps)[] = [ +export const skipProps: (keyof ComponentProps)[] = [ "ownerState", "theme", "sx", "as", ]; +function resolveStyles, P, O>( + theme: T, + className: string, + styles: Style[], + inProps: ComponentProps +) { + return createMemo(() => + styles.reduce((result, style) => { + let styledProps: StyledProps | false | undefined; + if (typeof style === "function") { + styledProps = style({ + ownerState: inProps.ownerState, + theme, + sx: inProps.sx, + as: inProps.as, + props: inProps as never as P, + }); + } else if (style) { + styledProps = style; + } + if (styledProps) + result.push({ + ["name" as never]: className, + ...resolveStyledProps(styledProps), + }); + return result; + }, [] as StyledProps[]) + ); +} + function createStyled< T extends Theme, CM extends Record = {} @@ -86,35 +115,22 @@ function createStyled< O >[] ) { - return function (inProps: _ComponentProps & StyledProps) { + return function (inProps: _ComponentProps & ComponentProps) { const theme = config?.onUseTheme ? config.onUseTheme() : (useTheme() as T); + const [, otherProps] = splitProps( inProps, options.skipProps ?? skipProps ); - const inStyles = createSxMemo( - className ?? "css", - () => - styles - .map((v) => { - let object: SxPropsObject | false | undefined; - if (typeof v === "function") { - object = v({ - ownerState: inProps["ownerState"], - theme, - sx: inProps.sx, - as: inProps.as, - props: inProps as any, - }); - } else if (v) { - object = v; - } - if (object) return resolveStyleProps(object, theme); - }) - .filter((v) => !!v) as SxPropsObject[] - ) as Accessor; + + const inStyles = resolveStyles( + theme, + className || "css", + styles, + inProps + ); const inSx = createMemo(() => !options.skipSx && inProps.sx diff --git a/packages/system/src/createSxClass.ts b/packages/system/src/createSxClass.ts index b71051be9..5baf4b4f0 100644 --- a/packages/system/src/createSxClass.ts +++ b/packages/system/src/createSxClass.ts @@ -9,6 +9,8 @@ import { } from "@suid/css/style-element"; import { createRenderEffect, createSignal, onCleanup } from "solid-js"; +export const resolvedPropKey = "__resolved"; + function createSxClass(value: () => SxProps | undefined) { const [name, setName] = createSignal(""); let styleElement: HTMLStyleElement | undefined; @@ -30,6 +32,7 @@ function createSxClass(value: () => SxProps | undefined) { }, {}); delete css.name; + delete css[resolvedPropKey]; result = createStyle({ name: "css", diff --git a/packages/system/src/createSxMemo.ts b/packages/system/src/createSxMemo.ts deleted file mode 100644 index 6d57d1c09..000000000 --- a/packages/system/src/createSxMemo.ts +++ /dev/null @@ -1,15 +0,0 @@ -import SxProps from "./sxProps"; -import { createMemo } from "solid-js"; - -function createSxMemo(name: string, props: () => SxProps) { - return createMemo(() => { - const value = props(); - if (Array.isArray(value)) - return value.map((style) => - typeof style === "string" ? style : { name, ...style } - ); - return { name, ...value }; - }); -} - -export default createSxMemo; diff --git a/packages/system/src/createSxPropsFactory.ts b/packages/system/src/createSxPropsFactory.ts deleted file mode 100644 index 01a5ee635..000000000 --- a/packages/system/src/createSxPropsFactory.ts +++ /dev/null @@ -1,91 +0,0 @@ -import createStylePropsFactory from "./createStylePropsFactory"; -import { Theme } from "./createTheme"; -import getThemeValue from "./getThemeValue"; - -export function resolveThemeValue( - theme: Theme, - key: keyof Theme, - value: unknown -) { - if (typeof value !== "string") return value; - const names = value.split("."); - let ref = theme[key]; - for (let i = 0; i < names.length; i++) { - ref = ref?.[names[i]]; - if (!ref) break; - } - return ref ?? value; -} - -function createSxPropsFactory( - options: { - spacing?: { - themeSpacing?: boolean; - }; - border?: { - themeBorderRadius?: boolean; - }; - } = {} -) { - return createStylePropsFactory({ - border: { - onValue: (name, value, theme) => { - if (name === "borderRadius") { - if (typeof value === "number") { - if (options.border?.themeBorderRadius) { - return `${theme.shape.borderRadius * value}px`; - } else { - return `${value}px`; - } - } - } - if (name.includes("Color")) - return resolveThemeValue(theme, "palette", value); - if (typeof value === "number") value = `${value}px solid`; - return value; - }, - }, - palette: { - onValue: (name, value, theme) => { - return resolveThemeValue(theme, "palette", value); - }, - }, - position: { - onValue: (name, value, theme) => { - if (name === "zIndex") { - value = theme.zIndex?.[name] ?? value; - } - return value; - }, - }, - sizing: { - onValue: (name, value, theme) => { - if (name === "maxWidth") { - value = theme.breakpoints.values[name as "xs"] ?? value; - } - if (typeof value === "number" && value > 0 && value <= 1) { - return `${value * 100}%`; - } else { - return value; - } - }, - }, - spacing: { - onValue: (name, value, theme) => { - return options.spacing?.themeSpacing ? theme.spacing(value) : value; - }, - }, - typography: { - onValue: (name, value, theme) => { - if (name === "typography") { - return getThemeValue(theme, "typography", value); - } else if (typeof value === "string") { - return resolveThemeValue(theme, "typography", value); - } - return value; - }, - }, - }); -} - -export default createSxPropsFactory; diff --git a/packages/system/src/createSystemProps.ts b/packages/system/src/createSystemProps.ts new file mode 100644 index 000000000..3b5b72a56 --- /dev/null +++ b/packages/system/src/createSystemProps.ts @@ -0,0 +1,204 @@ +import { Theme } from "./createTheme"; +import getThemeValue from "./getThemeValue"; +import { StyledPropsBase } from "./styledProps"; + +const dirMap = { + x: ["Left", "Right"], + y: ["Top", "Bottom"], +}; + +type OnValue = (name: string, value: any, theme: Theme) => any; + +type PropReturn = ( + value: Exclude, + theme: Theme +) => Record; + +function asPx(value: unknown) { + return typeof value === "number" ? `${value}px` : value; +} + +function customProp(name: string, onValue: OnValue): PropReturn { + return (value, theme) => onValue(name, value, theme); +} + +function prop( + name: N, + onValue?: OnValue +): PropReturn { + return onValue + ? (value, theme) => ({ + [name]: onValue(name, value, theme), + }) + : (value) => ({ [name]: value }); +} + +function pxProp(name: N) { + return prop(name, (name, value) => asPx(value)); +} + +function mProp(name: string, suffix: string[], onValue?: OnValue) { + const names = suffix.map((v) => `${name}${v}`); + return onValue + ? (value: V, theme: Theme) => + names.reduce((result, name) => { + result[name] = onValue(name, value, theme); + return result; + }, {} as Record) + : (value: V) => + names.reduce((result, name) => { + result[name] = value; + return result; + }, {} as Record); +} + +function createSystemProps() { + return { + ...createSystemPositionProps(), + ...createSystemPaletteProps(), + ...createSystemSizingProps(), + ...createSystemBorderProps(), + ...createSystemSpacingProps(), + ...createSystemTypographyProps(), + }; +} + +export function createSystemPositionProps() { + return { + position: prop("position"), + zIndex: prop( + "zIndex", + (name, value, theme) => theme.zIndex?.[name] ?? value + ), + top: pxProp("top"), + right: pxProp("right"), + bottom: pxProp("bottom"), + left: pxProp("left"), + }; +} + +export function createSystemPaletteProps() { + const paletteValue: OnValue = (name, value, theme) => + getThemeValue(theme, "palette", value); + return { + color: prop("color", paletteValue), + bgcolor: prop("backgroundColor", paletteValue), + backgroundColor: prop("backgroundColor", paletteValue), + }; +} + +export function createSystemSizingProps() { + const onValue: OnValue = (name, value, theme) => { + if (name === "maxWidth") { + value = theme.breakpoints.values[name as "xs"] ?? value; + } + if (typeof value === "number") { + value = value > 0 && value <= 1 ? `${value * 100}%` : `${value}px`; + } + return value; + }; + return { + width: prop("width", onValue), + maxWidth: prop("maxWidth", onValue), + minWidth: prop("minWidth", onValue), + height: prop("height", onValue), + maxHeight: prop("maxHeight", onValue), + minHeight: prop("minHeight", onValue), + boxSizing: prop("boxSizing", onValue), + }; +} + +export function createSystemBorderProps() { + const borderValue: OnValue = (name, value) => + typeof value === "number" ? `${value}px solid` : value; + const paletteValue: OnValue = (name, value, theme) => + getThemeValue(theme, "palette", value); + return { + border: prop("border", borderValue), + borderTop: prop("borderTop", borderValue), + borderRight: prop("borderRight", borderValue), + borderBottom: prop("borderBottom", borderValue), + borderLeft: prop("borderLeft", borderValue), + borderColor: prop("borderColor", paletteValue), + borderTopColor: prop("borderTopColor", paletteValue), + borderRightColor: prop("borderRightColor", paletteValue), + borderBottomColor: prop("borderBottomColor", paletteValue), + borderLeftColor: prop("borderLeftColor", paletteValue), + borderRadius: prop("borderRadius", (name, value, theme) => + typeof value === "number" + ? `${theme.shape.borderRadius * value}px` + : value + ), + }; +} + +export function createSystemTypographyProps() { + const typographyValue: OnValue = (name, value, theme) => + getThemeValue(theme, "typography", value); + + return { + typography: customProp("typography", (name, value, theme) => + getThemeValue(theme, "typography", value) + ), + fontFamily: prop("fontFamily", typographyValue), + fontSize: prop("fontSize", (name, value, theme) => + asPx(typographyValue(name, value, theme)) + ), + fontStyle: prop("fontStyle", typographyValue), + fontWeight: prop("fontWeight", typographyValue), + letterSpacing: pxProp("letterSpacing"), + lineHeight: prop("lineHeight"), + textAlign: prop("textAlign"), + textTransform: prop("textTransform"), + }; +} + +export function createSystemSpacingProps() { + const spacing: OnValue = (name, value, theme) => theme.spacing(value); + const m = "margin"; + const p = "padding"; + return { + m: prop(m, spacing), + mt: prop("marginTop", spacing), + mr: prop("marginRight", spacing), + mb: prop("marginBottom", spacing), + ml: prop("marginLeft", spacing), + mx: mProp(m, dirMap["x"], spacing), + my: mProp(m, dirMap["y"], spacing), + margin: prop(m, spacing), + marginTop: prop("marginTop", spacing), + marginRight: prop("marginRight", spacing), + marginBottom: prop("marginBottom", spacing), + marginLeft: prop("marginLeft", spacing), + marginX: mProp(m, dirMap["x"], spacing), + marginY: mProp(m, dirMap["y"], spacing), + marginInline: mProp(m, ["Inline", "InlineStart"], spacing), + marginInlineStart: prop("marginInlineStart", spacing), + marginInlineEnd: prop("marginInlineEnd", spacing), + marginBlock: mProp(m, ["BlockStart", "BlockEnd"], spacing), + marginBlockStart: prop("marginBlockStart", spacing), + marginBlockEnd: prop("marginBlockEnd", spacing), + p: prop(p, spacing), + pt: prop("paddingTop", spacing), + pr: prop("paddingRight", spacing), + pb: prop("paddingBottom", spacing), + pl: prop("paddingLeft", spacing), + px: mProp(p, dirMap["x"], spacing), + py: mProp(p, dirMap["y"], spacing), + padding: prop(p, spacing), + paddingTop: prop("paddingTop", spacing), + paddingRight: prop("paddingRight", spacing), + paddingBottom: prop("paddingBottom", spacing), + paddingLeft: prop("paddingLeft", spacing), + paddingX: mProp(p, dirMap["x"], spacing), + paddingY: mProp(p, dirMap["y"], spacing), + paddingInline: mProp(p, ["Inline", "InlineStart"], spacing), + paddingInlineStart: prop("paddingInlineStart", spacing), + paddingInlineEnd: prop("paddingInlineEnd", spacing), + paddingBlock: mProp(p, ["BlockStart", "BlockEnd"], spacing), + paddingBlockStart: prop("paddingBlockStart", spacing), + paddingBlockEnd: prop("paddingBlockEnd", spacing), + }; +} + +export default createSystemProps; diff --git a/packages/system/src/index.tsx b/packages/system/src/index.tsx index 3e58dea83..729100c2d 100644 --- a/packages/system/src/index.tsx +++ b/packages/system/src/index.tsx @@ -1,6 +1,6 @@ export * from "./colorManipulator"; export * from "./styleFunctionSx"; export * from "./breakpoints"; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export type SystemProps = {}; + export type { SxProps } from "./sxProps"; +export type { SystemProps } from "./systemProps"; diff --git a/packages/system/src/resolveStyleProps.ts b/packages/system/src/resolveStyleProps.ts deleted file mode 100644 index d9ace2487..000000000 --- a/packages/system/src/resolveStyleProps.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Theme } from "./createTheme"; -import stylePropsFactory from "./stylePropsFactory"; -import { SxPropsObject } from "./sxProps"; -import resolve from "@suid/css/resolve"; - -function resolveStyleProps( - v: SxPropsObject, - theme: Theme, - propsFactory: Record< - string, - (value: any, theme: Theme) => any - > = stylePropsFactory -): SxPropsObject { - return resolve(v, (name, value) => { - if (name in propsFactory) return propsFactory[name](value, theme); - }); -} - -export default resolveStyleProps; diff --git a/packages/system/src/resolveStyledProps.ts b/packages/system/src/resolveStyledProps.ts new file mode 100644 index 000000000..ebd28203a --- /dev/null +++ b/packages/system/src/resolveStyledProps.ts @@ -0,0 +1,17 @@ +import { resolvedPropKey } from "./createSxClass"; +import { StyledProps } from "./styledProps"; +import resolve from "@suid/css/resolve"; + +export const unitLess = new Set([]); + +export function resolveStyledPropsValue(name: string, value: unknown) { + if (typeof value === "number") { + return { [name]: unitLess.has(name) ? value.toString() : `${value}px` }; + } +} + +function resolveStyledProps(v: StyledProps): StyledProps { + return resolve(v, resolveStyledPropsValue, { [resolvedPropKey]: true }); +} + +export default resolveStyledProps; diff --git a/packages/system/src/resolveSxProps.ts b/packages/system/src/resolveSxProps.ts new file mode 100644 index 000000000..631c4657c --- /dev/null +++ b/packages/system/src/resolveSxProps.ts @@ -0,0 +1,28 @@ +import { resolvedPropKey } from "./createSxClass"; +import { Theme } from "./createTheme"; +import { resolveStyledPropsValue } from "./resolveStyledProps"; +import { SxPropsObject } from "./sxProps"; +import systemProps from "./systemProps"; +import resolve from "@suid/css/resolve"; + +export function resolveSystemPropsValue( + name: string, + value: any, + theme: Theme +) { + return systemProps[name as keyof typeof systemProps](value, theme); +} + +export function reslveSxPropsValue(name: string, value: unknown, theme: Theme) { + return name in systemProps + ? resolveSystemPropsValue(name, value, theme) + : resolveStyledPropsValue(name, value); +} + +function resolveSxProps(v: SxPropsObject, theme: Theme): SxPropsObject { + return resolve(v, (name, value) => reslveSxPropsValue(name, value, theme), { + [resolvedPropKey]: true, + }); +} + +export default resolveSxProps; diff --git a/packages/system/src/styleProps.ts b/packages/system/src/styleProps.ts new file mode 100644 index 000000000..dcfbe4013 --- /dev/null +++ b/packages/system/src/styleProps.ts @@ -0,0 +1,20 @@ +import { + StandardProperties, + SvgProperties, + VendorLonghandProperties, +} from "csstype"; + +export type StyleProps = StandardProperties & + SvgProperties & + VendorLonghandProperties; + +export type StyleCascade = + | { + [K in keyof T]?: T[K]; + } + | { + [K: string]: StyleCascade; + }; + +export {}; +export default StyleProps; diff --git a/packages/system/src/stylePropsFactory.ts b/packages/system/src/stylePropsFactory.ts deleted file mode 100644 index a1a635d08..000000000 --- a/packages/system/src/stylePropsFactory.ts +++ /dev/null @@ -1,12 +0,0 @@ -import createSxPropsFactory from "./createSxPropsFactory"; - -const stylePropsFactory = createSxPropsFactory({ - spacing: { - themeSpacing: false, - }, - border: { - themeBorderRadius: false, - }, -}); - -export default stylePropsFactory; diff --git a/packages/system/src/styledProps.ts b/packages/system/src/styledProps.ts new file mode 100644 index 000000000..716d41739 --- /dev/null +++ b/packages/system/src/styledProps.ts @@ -0,0 +1,15 @@ +import { StyleCascade } from "./styleProps"; +import { + StandardProperties, + SvgProperties, + VendorLonghandProperties, +} from "csstype"; + +export type StyledPropsBase = StandardProperties & + SvgProperties & + VendorLonghandProperties; + +export type StyledProps = StyleCascade; + +export {}; +export default StyledProps; diff --git a/packages/system/src/sxProps.ts b/packages/system/src/sxProps.ts index 44476f7c2..918ed84ba 100644 --- a/packages/system/src/sxProps.ts +++ b/packages/system/src/sxProps.ts @@ -1,24 +1,16 @@ -import { StyleProps } from "./createStylePropsFactory"; import { Theme } from "./createTheme"; -import { - StandardProperties, - SvgProperties, - VendorLonghandProperties, -} from "csstype"; +import { StyleCascade } from "./styleProps"; +import { StyledPropsBase } from "./styledProps"; +import { SystemProps } from "./systemProps"; -export type NativeStyleProps = StandardProperties & - SvgProperties & - VendorLonghandProperties; -export type CSSProps = Omit & StyleProps; -export type SxPropsObject = - | { - [K in keyof CSSProps]?: CSSProps[K]; - } - | { - [K: string]: SxPropsObject; - }; +export type SxPropsBase = Omit< + StyledPropsBase, + keyof SystemProps +> & + StyledPropsBase & + SystemProps; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -export type SxProps = SxPropsObject[] | SxPropsObject; +export type SxPropsObject = StyleCascade>; +export type SxProps = SxPropsObject[] | SxPropsObject; export default SxProps; diff --git a/packages/system/src/sxPropsFactory.ts b/packages/system/src/sxPropsFactory.ts deleted file mode 100644 index ec06eff1b..000000000 --- a/packages/system/src/sxPropsFactory.ts +++ /dev/null @@ -1,12 +0,0 @@ -import createSxPropsFactory from "./createSxPropsFactory"; - -const sxPropsFactory = createSxPropsFactory({ - spacing: { - themeSpacing: true, - }, - border: { - themeBorderRadius: true, - }, -}); - -export default sxPropsFactory; diff --git a/packages/system/src/systemProps.ts b/packages/system/src/systemProps.ts new file mode 100644 index 000000000..8bfcf73b8 --- /dev/null +++ b/packages/system/src/systemProps.ts @@ -0,0 +1,24 @@ +import createSystemProps from "./createSystemProps"; +import { Theme } from "./createTheme"; +import { StyledPropsBase } from "./styledProps"; + +const systemProps = createSystemProps(); + +export type SystemPropName = keyof typeof systemProps; +export const systemPropNames = Object.keys(systemProps) as SystemPropName[]; + +type SystemStyledPropName = Extract; +export type SystemExtraPropName = Exclude; + +export type SystemExtraPropsBase = Partial<{ + [K in SystemExtraPropName]: Parameters[0]; +}>; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export type SystemProps = Pick< + StyledPropsBase, + SystemStyledPropName +> & + SystemExtraPropsBase; + +export default systemProps;