From 82d60c71052327ab7081a6e0de0ee78d2422fa53 Mon Sep 17 00:00:00 2001 From: Brijesh Bittu Date: Fri, 31 May 2024 20:52:18 +0530 Subject: [PATCH 01/16] [react] Create Stack component This uses atomic classes to cover all the scenarios of rendering a Stack --- packages/pigment-css-react/package.json | 10 +++ packages/pigment-css-react/src/Stack.d.ts | 20 ++++++ packages/pigment-css-react/src/Stack.jsx | 50 ++++++++++++++ .../src/{base.d.ts => base.ts} | 8 +++ packages/pigment-css-react/src/baseAtomics.js | 43 ++++++++++++ .../pigment-css-react/src/generateAtomics.js | 65 +++++++++++++++++-- packages/pigment-css-react/src/index.ts | 1 + .../src/utils/convertAtomicsToCss.ts | 24 ++++++- .../pigment-css-react/tests/Stack.spec.tsx | 13 ++++ packages/pigment-css-react/tsconfig.json | 3 +- packages/pigment-css-react/tsup.config.ts | 2 +- pnpm-lock.yaml | 4 +- 12 files changed, 231 insertions(+), 12 deletions(-) create mode 100644 packages/pigment-css-react/src/Stack.d.ts create mode 100644 packages/pigment-css-react/src/Stack.jsx rename packages/pigment-css-react/src/{base.d.ts => base.ts} (90%) create mode 100644 packages/pigment-css-react/src/baseAtomics.js create mode 100644 packages/pigment-css-react/tests/Stack.spec.tsx diff --git a/packages/pigment-css-react/package.json b/packages/pigment-css-react/package.json index 30ec6fe9..c0c2d053 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", @@ -151,6 +152,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 d576aa38..149fac4b 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 b40aecd6..57dfbe01 100644 --- a/packages/pigment-css-react/tsup.config.ts +++ b/packages/pigment-css-react/tsup.config.ts @@ -20,7 +20,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 d63e6ef6..7a3ba641 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.9(@types/react@18.2.75)(react@18.2.0): resolution: {integrity: sha512-fie2VEiVzpHCbcUIwgWx5lQT+SYjL3uHDCfutXzr7hTbnqDu9KSVEBPgADitFqZOGCIcBXfs4aE4LIn1FmmKjg==} From c4bbc466fc758793cb275c50479659576e047a94 Mon Sep 17 00:00:00 2001 From: Brijesh Bittu Date: Fri, 31 May 2024 20:55:31 +0530 Subject: [PATCH 02/16] Add Stack demo to both the apps --- apps/pigment-css-next-app/next.config.js | 1 - .../pigment-css-next-app/src/app/box/page.tsx | 58 ------ apps/pigment-css-next-app/src/app/layout.tsx | 1 - .../src/app/stack/page.tsx | 164 +++++++++++++++++ .../src/components/Box.jsx | 68 ------- apps/pigment-css-vite-app/src/pages/index.tsx | 170 +++++++++++++++++- apps/pigment-css-vite-app/vite.config.ts | 2 +- packages/pigment-css-react/src/Stack.jsx | 28 ++- packages/pigment-css-react/src/baseAtomics.js | 2 +- 9 files changed, 356 insertions(+), 138 deletions(-) delete mode 100644 apps/pigment-css-next-app/src/app/box/page.tsx create mode 100644 apps/pigment-css-next-app/src/app/stack/page.tsx delete mode 100644 apps/pigment-css-next-app/src/components/Box.jsx diff --git a/apps/pigment-css-next-app/next.config.js b/apps/pigment-css-next-app/next.config.js index 4dcc9476..f7f86335 100644 --- a/apps/pigment-css-next-app/next.config.js +++ b/apps/pigment-css-next-app/next.config.js @@ -103,7 +103,6 @@ const pigmentOptions = { theme, transformLibraries: ['local-ui-lib', '@mui/material'], sourceMap: true, - displayName: true, }; /** @type {import('next').NextConfig} */ diff --git a/apps/pigment-css-next-app/src/app/box/page.tsx b/apps/pigment-css-next-app/src/app/box/page.tsx deleted file mode 100644 index 877b36e5..00000000 --- a/apps/pigment-css-next-app/src/app/box/page.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable material-ui/no-empty-box */ - -import { styled } from '@pigment-css/react'; -import * as React from 'react'; -import { Box as MuiBox } from '../../components/Box'; - -const Box = styled(MuiBox)(({ theme }) => ({ - border: `1px dashed ${(theme.vars ?? theme).palette.primary.main}`, - padding: 10, -})); -const Paragraph = styled.p({ - margin: 0, - marginBottom: 5, -}); - -export default function DemoBox() { - return ( -
- {[...Array(500)].map((_, i) => ( - - Flex with column for "sm" breakpoint - - - 1 - - 2 - 3 - - Row Reverse - - 1 - 2 - 3 - - Column - - 1 - 2 - 3 - - Column Reverse - - 1 - 2 - 3 - - - ))} -
- ); -} diff --git a/apps/pigment-css-next-app/src/app/layout.tsx b/apps/pigment-css-next-app/src/app/layout.tsx index 77126c9b..d4bc3c7b 100644 --- a/apps/pigment-css-next-app/src/app/layout.tsx +++ b/apps/pigment-css-next-app/src/app/layout.tsx @@ -23,7 +23,6 @@ export default function RootLayout(props: { children: React.ReactNode }) { className={`${inter.className} ${css` background-color: ${({ theme: t }) => t.vars.palette.background.default}; color: ${({ theme: t }) => t.vars.palette.text.primary}; - background-image: url('@/assets/mui.svg'); background-repeat: no-repeat; background-position: 1rem 1rem; `}`} diff --git a/apps/pigment-css-next-app/src/app/stack/page.tsx b/apps/pigment-css-next-app/src/app/stack/page.tsx new file mode 100644 index 00000000..14d566ca --- /dev/null +++ b/apps/pigment-css-next-app/src/app/stack/page.tsx @@ -0,0 +1,164 @@ +'use client'; +import * as React from 'react'; +import Stack from '@pigment-css/react/Stack'; +import { styled, css } from '@pigment-css/react'; + +type StackProps = React.ComponentProps; + +const labelClass = css({ + padding: '0px 10px', +}); + +const Card = styled.div` + transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; + border-radius: 4px; + box-shadow: + rgba(0, 0, 0, 0.2) 0px 2px 1px -1px, + rgba(0, 0, 0, 0.14) 0px 1px 1px 0px, + rgba(0, 0, 0, 0.12) 0px 1px 3px 0px; + background-image: linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05)); + padding: 8px 16px; + font-family: Roboto, Helvetica, Arial, sans-serif; + font-weight: 400; + font-size: 0.875rem; + line-height: 1.43; + letter-spacing: 0.01071em; + background-color: rgb(26, 32, 39); +`; + +export default function InteractiveStack() { + const [direction, setDirection] = React.useState('column'); + const [justifyContent, setJustifyContent] = + React.useState('center'); + const [alignItems, setAlignItems] = React.useState('center'); + const [spacing, setSpacing] = React.useState(2); + + const handleChange = React.useCallback((ev: React.ChangeEvent) => { + switch (ev.target.name) { + case 'direction': { + setDirection(ev.target.value as StackProps['direction']); + break; + } + case 'justify-content': { + setJustifyContent(ev.target.value as StackProps['justifyContent']); + break; + } + case 'align-items': { + setAlignItems(ev.target.value as StackProps['alignItems']); + break; + } + case 'spacing': { + setSpacing(parseFloat(ev.target.value)); + break; + } + default: + break; + } + }, []); + + const jsx = ` + +`; + + return ( + + +
+ Direction + {['row', 'row-reverse', 'column', 'column-reverse'].map((item) => ( + + ))} +
+
+ justifyContent + {[ + 'flex-start', + 'center', + 'flex-end', + 'space-between', + 'space-around', + 'space-evenly', + ].map((item) => ( + + ))} +
+
+ +
+ alignItems + {[ + 'flex-start', + 'center', + 'flex-end', + 'stretch', + 'baseline', + 'space-between', + 'space-around', + 'space-evenly', + ].map((item) => ( + + ))} +
+
+ Spacing + {[0, 0.5, 1, 2, 3, 4, 8, 12].map((item) => ( + + ))} +
+
+ + {[0, 1, 2].map((value) => ( + {`Item ${value + 1}`} + ))} + +