diff --git a/packages/pigment-css-react/package.json b/packages/pigment-css-react/package.json index 4c5b47ed..17babafd 100644 --- a/packages/pigment-css-react/package.json +++ b/packages/pigment-css-react/package.json @@ -55,6 +55,7 @@ }, "devDependencies": { "@babel/plugin-syntax-jsx": "^7.24.1", + "@mui/types": "7.2.14", "@types/babel__core": "^7.20.5", "@types/babel__helper-module-imports": "^7.18.3", "@types/babel__helper-plugin-utils": "^7.10.3", @@ -148,6 +149,15 @@ }, "require": "./build/RtlProvider.js", "default": "./build/RtlProvider.js" + }, + "./Stack": { + "types": "./build/Stack.d.ts", + "import": { + "types": "./build/Stack.d.mts", + "default": "./build/Stack.mjs" + }, + "require": "./build/Stack.js", + "default": "./build/Stack.js" } }, "nx": { diff --git a/packages/pigment-css-react/src/Stack.d.ts b/packages/pigment-css-react/src/Stack.d.ts new file mode 100644 index 00000000..4b185fa4 --- /dev/null +++ b/packages/pigment-css-react/src/Stack.d.ts @@ -0,0 +1,20 @@ +import * as CSS from 'csstype'; + +import { Breakpoint } from './base'; +import { PolymorphicComponent } from './Box'; + +type CssProperty = T | Array | Partial>; + +type StackBaseProps = { + display?: CssProperty<'flex' | 'inline-flex'>; + spacing?: CssProperty; + direction?: CssProperty; + justifyContent?: CssProperty; + alignItems?: CssProperty; + divider?: React.ReactNode; + className?: string; +}; + +declare const Stack: PolymorphicComponent; + +export default Stack; diff --git a/packages/pigment-css-react/src/Stack.jsx b/packages/pigment-css-react/src/Stack.jsx new file mode 100644 index 00000000..f9f57fdb --- /dev/null +++ b/packages/pigment-css-react/src/Stack.jsx @@ -0,0 +1,50 @@ +import clsx from 'clsx'; +import * as React from 'react'; + +import { stackAtomics } from './baseAtomics'; + +const Stack = React.forwardRef(function Stack( + { + children, + spacing = 0, + style, + className, + display = 'flex', + component = 'div', + direction = 'column', + alignItems, + justifyContent, + ...rest + }, + ref, +) { + const stackAtomicsObj = { + display, + direction, + }; + if (spacing) { + stackAtomicsObj.spacing = spacing; + } + if (alignItems) { + stackAtomicsObj.alignItems = alignItems; + } + if (justifyContent) { + stackAtomicsObj.justifyContent = justifyContent; + } + const stackClasses = stackAtomics(stackAtomicsObj); + const Component = component; + return ( + + {children} + + ); +}); + +Stack.displayName = 'Stack'; + +export default Stack; diff --git a/packages/pigment-css-react/src/base.d.ts b/packages/pigment-css-react/src/base.ts similarity index 90% rename from packages/pigment-css-react/src/base.d.ts rename to packages/pigment-css-react/src/base.ts index c524bd4a..1451eef2 100644 --- a/packages/pigment-css-react/src/base.d.ts +++ b/packages/pigment-css-react/src/base.ts @@ -1,4 +1,5 @@ import type * as CSS from 'csstype'; +import { OverridableStringUnion } from '@mui/types'; export type CSSProperties = CSS.PropertiesFallback; @@ -64,3 +65,10 @@ export interface PolymorphicComponent, ): JSX.Element; } + +export interface BreakpointOverrides {} + +export type Breakpoint = OverridableStringUnion< + 'xs' | 'sm' | 'md' | 'lg' | 'xl', + BreakpointOverrides +>; diff --git a/packages/pigment-css-react/src/baseAtomics.js b/packages/pigment-css-react/src/baseAtomics.js new file mode 100644 index 00000000..24277f10 --- /dev/null +++ b/packages/pigment-css-react/src/baseAtomics.js @@ -0,0 +1,43 @@ +import { generateAtomics } from './generateAtomics'; + +export const stackAtomics = generateAtomics(({ theme }) => ({ + conditions: Object.keys(theme.breakpoints.values).reduce((acc, breakpoint) => { + acc[breakpoint] = `@media (min-width: ${theme.breakpoints.values[breakpoint]}${ + theme.breakpoints.unit ?? 'px' + })`; + return acc; + }, {}), + defaultCondition: theme.defaultBreakpoint ?? 'xs', + properties: { + display: ['flex', 'inline-flex'], + flexDirection: ['column', 'column-reverse', 'row', 'row-reverse'], + justifyContent: [ + 'end', + 'start', + 'flex-end', + 'flex-start', + 'center', + 'space-between', + 'space-around', + 'space-evenly', + ], + alignItems: [ + 'center', + 'end', + 'flex-end', + 'flex-start', + 'self-end', + 'self-start', + 'start', + 'baseline', + 'normal', + 'stretch', + ], + gap: ['--Stack-gap'], + }, + shorthands: { + direction: ['flexDirection'], + spacing: ['gap'], + }, + multiplier: 8, +})); diff --git a/packages/pigment-css-react/src/generateAtomics.js b/packages/pigment-css-react/src/generateAtomics.js index 442e1f67..dabbb6d8 100644 --- a/packages/pigment-css-react/src/generateAtomics.js +++ b/packages/pigment-css-react/src/generateAtomics.js @@ -11,18 +11,69 @@ export function generateAtomics() { * @property {Object.>>} styles * @property {Object.} shorthands * @property {string[]} conditions + * @property {string} defaultCondition + * @property {string[]} unitless + * @property {string} multiplier */ /** * @param {RuntimeConfig} runtimeConfig */ -export function atomics({ styles, shorthands, conditions }) { - function addStyles(cssProperty, values, classes) { +export function atomics({ + styles, + defaultCondition, + shorthands, + conditions, + unitless, + multiplier = 1, +}) { + function addStyles(cssProperty, values, classes, inlineStyle) { const styleClasses = styles[cssProperty]; if (!styleClasses) { return; } - if (typeof values === 'string') { + // dynamic values + if (!(values in styleClasses)) { + const keys = Object.keys(styleClasses); + if (keys.length !== 1) { + return; + } + const key = keys[0]; + if (typeof values === 'string' || typeof values === 'number') { + const value = typeof values === 'number' ? values * multiplier : values; + classes.push(styleClasses[key].$$default); + inlineStyle[`${key}_${defaultCondition}`] = unitless.includes(cssProperty) + ? value + : `${value}px`; + } else if (Array.isArray(values)) { + values.forEach((itemValue, index) => { + const breakpoint = conditions[index]; + classes.push(styleClasses[key][breakpoint]); + const value = typeof itemValue === 'number' ? itemValue * multiplier : itemValue; + inlineStyle[`${key}_${breakpoint}`] = unitless.includes(cssProperty) + ? value + : `${value}px`; + }); + } else { + Object.keys(values).forEach((condition) => { + const propertyClasses = styleClasses[key]; + if (!propertyClasses) { + return; + } + console.log(propertyClasses); + classes.push(propertyClasses[condition]); + const value = + typeof values[condition] === 'number' + ? values[condition] * multiplier + : values[condition]; + inlineStyle[`${key}_${condition}`] = unitless.includes(cssProperty) + ? value + : `${value}px`; + }); + } + return; + } + if (typeof values === 'string' || typeof values === 'number') { classes.push(styleClasses[values].$$default); } else if (Array.isArray(values)) { values.forEach((value, index) => { @@ -41,7 +92,8 @@ export function atomics({ styles, shorthands, conditions }) { function generateClass(props) { const classes = []; - const runtimeStyles = { ...props }; + const inlineStyle = {}; + const runtimeStyles = props; Object.keys(runtimeStyles).forEach((cssProperty) => { const values = runtimeStyles[cssProperty]; if (cssProperty in shorthands) { @@ -50,14 +102,15 @@ export function atomics({ styles, shorthands, conditions }) { return; } configShorthands.forEach((shorthand) => { - addStyles(shorthand, values, classes); + addStyles(shorthand, values, classes, inlineStyle); }); } else { - addStyles(cssProperty, values, classes); + addStyles(cssProperty, values, classes, inlineStyle); } }); return { className: cx(Array.from(new Set(classes))), + style: inlineStyle, }; } return generateClass; diff --git a/packages/pigment-css-react/src/index.ts b/packages/pigment-css-react/src/index.ts index 2b27d96f..819aa904 100644 --- a/packages/pigment-css-react/src/index.ts +++ b/packages/pigment-css-react/src/index.ts @@ -1,3 +1,4 @@ +export * from './base'; export { default as styled, type StyledComponent } from './styled'; export { default as sx } from './sx'; export { default as keyframes } from './keyframes'; diff --git a/packages/pigment-css-react/src/utils/convertAtomicsToCss.ts b/packages/pigment-css-react/src/utils/convertAtomicsToCss.ts index 1afdf7c6..d1ca46fe 100644 --- a/packages/pigment-css-react/src/utils/convertAtomicsToCss.ts +++ b/packages/pigment-css-react/src/utils/convertAtomicsToCss.ts @@ -7,12 +7,17 @@ export type Atomics = { [key: string]: string[]; }; shorthands: Record; + unitless: string[]; + multiplier?: number; }; export type RuntimeConfig = { conditions: string[]; styles: Record>>; shorthands: Atomics['shorthands']; + defaultCondition: string; + unitless: string[]; + multiplier?: number; }; function getClassName(...items: string[]) { @@ -20,7 +25,14 @@ function getClassName(...items: string[]) { } export function convertAtomicsToCss( - { conditions = {}, defaultCondition, properties, shorthands = {} }: Atomics, + { + conditions = {}, + defaultCondition, + properties, + shorthands = {}, + unitless = [], + multiplier = undefined, + }: Atomics, mainClassName: string, isGlobal = false, debug = false, @@ -30,6 +42,9 @@ export function convertAtomicsToCss( styles: {}, shorthands, conditions: Object.keys(conditions), + defaultCondition, + unitless, + multiplier, }; let count = 1; function getCount() { @@ -46,6 +61,9 @@ export function convertAtomicsToCss( Object.entries(conditions).forEach(([conditionName, mediaQueryStr]) => { Object.entries(properties).forEach(([cssPropertyName, propertyValues]) => { propertyValues.forEach((propertyValue) => { + const propValue = propertyValue.startsWith('--') + ? cssesc(`var(${propertyValue}_${conditionName})`) + : propertyValue; const className = isGlobal || debug ? getClassName( @@ -60,7 +78,7 @@ export function convertAtomicsToCss( classes.push({ className, css: { - [cssPropertyName]: propertyValue, + [cssPropertyName]: propValue, }, }); } else { @@ -68,7 +86,7 @@ export function convertAtomicsToCss( className, css: { [mediaQueryStr]: { - [cssPropertyName]: propertyValue, + [cssPropertyName]: propValue, }, }, }); diff --git a/packages/pigment-css-react/tests/Stack.spec.tsx b/packages/pigment-css-react/tests/Stack.spec.tsx new file mode 100644 index 00000000..92929c27 --- /dev/null +++ b/packages/pigment-css-react/tests/Stack.spec.tsx @@ -0,0 +1,13 @@ +import * as React from 'react'; +import Stack from '../src/Stack'; + +; + +// @ts-expect-error +Hello} />; diff --git a/packages/pigment-css-react/tsconfig.json b/packages/pigment-css-react/tsconfig.json index 8ac34324..3e0efa80 100644 --- a/packages/pigment-css-react/tsconfig.json +++ b/packages/pigment-css-react/tsconfig.json @@ -7,7 +7,8 @@ "composite": true, "noEmit": false, "resolveJsonModule": true, - "types": ["node", "mocha"] + "types": ["node", "mocha"], + "jsx": "react-jsx" }, "include": ["src/**/*.tsx", "src/**/*.js", "src/**/*.ts"], "exclude": ["./tsup.config.ts", "src/**/*.d.ts"] diff --git a/packages/pigment-css-react/tsup.config.ts b/packages/pigment-css-react/tsup.config.ts index 3c238b65..8d940a3d 100644 --- a/packages/pigment-css-react/tsup.config.ts +++ b/packages/pigment-css-react/tsup.config.ts @@ -18,7 +18,7 @@ const baseConfig: Options = { external, }; -const BASE_FILES = ['index.ts', 'theme.ts', 'Box.jsx', 'RtlProvider.tsx']; +const BASE_FILES = ['index.ts', 'theme.ts', 'Box.jsx', 'RtlProvider.tsx', 'Stack.jsx']; export default defineConfig([ { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 76cacd84..34036798 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -482,6 +482,9 @@ importers: '@babel/plugin-syntax-jsx': specifier: ^7.24.1 version: 7.24.1(@babel/core@7.24.4) + '@mui/types': + specifier: 7.2.14 + version: 7.2.14(@types/react@18.2.75) '@types/babel__core': specifier: ^7.20.5 version: 7.20.5 @@ -3325,7 +3328,6 @@ packages: optional: true dependencies: '@types/react': 18.2.75 - dev: false /@mui/utils@6.0.0-alpha.8(@types/react@18.2.75)(react@18.2.0): resolution: {integrity: sha512-X5lg0bh8B6uYt/0HXV+t82HXLTOVFEKcIBmIbJ5El1h9ykXaRTenr8mORxt5UC5w9DHFhkRoI8XiM5qyDuSJVw==}