Skip to content

Commit

Permalink
[react] Create Stack component
Browse files Browse the repository at this point in the history
This uses atomic classes to cover all the scenarios of rendering a Stack
  • Loading branch information
brijeshb42 committed Jun 3, 2024
1 parent 3c1de03 commit 18a6f92
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 12 deletions.
10 changes: 10 additions & 0 deletions packages/pigment-css-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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": {
Expand Down
20 changes: 20 additions & 0 deletions packages/pigment-css-react/src/Stack.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as CSS from 'csstype';

import { Breakpoint } from './base';
import { PolymorphicComponent } from './Box';

type CssProperty<T> = T | Array<T> | Partial<Record<Breakpoint, T>>;

type StackBaseProps = {
display?: CssProperty<'flex' | 'inline-flex'>;
spacing?: CssProperty<number | string>;
direction?: CssProperty<CSS.StandardLonghandProperties['flexDirection']>;
justifyContent?: CssProperty<CSS.StandardLonghandProperties['justifyContent']>;
alignItems?: CssProperty<CSS.StandardLonghandProperties['alignItems']>;
divider?: React.ReactNode;
className?: string;
};

declare const Stack: PolymorphicComponent<StackBaseProps>;

export default Stack;
50 changes: 50 additions & 0 deletions packages/pigment-css-react/src/Stack.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<Component
ref={ref}
className={clsx(stackClasses.className, className)}
style={{ ...style, ...stackClasses.style }}
{...rest}
>
{children}
</Component>
);
});

Stack.displayName = 'Stack';

export default Stack;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type * as CSS from 'csstype';
import { OverridableStringUnion } from '@mui/types';

export type CSSProperties = CSS.PropertiesFallback<number | string>;

Expand Down Expand Up @@ -64,3 +65,10 @@ export interface PolymorphicComponent<SxProp, BaseProps extends BaseDefaultProps
props: PolymorphicComponentProps<SxProp, BaseProps, AsTarget>,
): JSX.Element;
}

export interface BreakpointOverrides {}

export type Breakpoint = OverridableStringUnion<
'xs' | 'sm' | 'md' | 'lg' | 'xl',
BreakpointOverrides
>;
43 changes: 43 additions & 0 deletions packages/pigment-css-react/src/baseAtomics.js
Original file line number Diff line number Diff line change
@@ -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,
}));
65 changes: 59 additions & 6 deletions packages/pigment-css-react/src/generateAtomics.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,69 @@ export function generateAtomics() {
* @property {Object.<string, Object.<string, Object.<string, string>>>} styles
* @property {Object.<string, string[]>} 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) => {
Expand All @@ -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) {
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions packages/pigment-css-react/src/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
24 changes: 21 additions & 3 deletions packages/pigment-css-react/src/utils/convertAtomicsToCss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,32 @@ export type Atomics = {
[key: string]: string[];
};
shorthands: Record<string, string[]>;
unitless: string[];
multiplier?: number;
};

export type RuntimeConfig = {
conditions: string[];
styles: Record<string, Record<string, Record<string, string>>>;
shorthands: Atomics['shorthands'];
defaultCondition: string;
unitless: string[];
multiplier?: number;
};

function getClassName(...items: string[]) {
return cssesc(items.filter(Boolean).join('_'));
}

export function convertAtomicsToCss(
{ conditions = {}, defaultCondition, properties, shorthands = {} }: Atomics,
{
conditions = {},
defaultCondition,
properties,
shorthands = {},
unitless = [],
multiplier = undefined,
}: Atomics,
mainClassName: string,
isGlobal = false,
debug = false,
Expand All @@ -30,6 +42,9 @@ export function convertAtomicsToCss(
styles: {},
shorthands,
conditions: Object.keys(conditions),
defaultCondition,
unitless,
multiplier,
};
let count = 1;
function getCount() {
Expand All @@ -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(
Expand All @@ -60,15 +78,15 @@ export function convertAtomicsToCss(
classes.push({
className,
css: {
[cssPropertyName]: propertyValue,
[cssPropertyName]: propValue,
},
});
} else {
classes.push({
className,
css: {
[mediaQueryStr]: {
[cssPropertyName]: propertyValue,
[cssPropertyName]: propValue,
},
},
});
Expand Down
13 changes: 13 additions & 0 deletions packages/pigment-css-react/tests/Stack.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as React from 'react';
import Stack from '../src/Stack';

<Stack
display={{
xs: 'flex',
xl: 'inline-flex',
}}
direction="column"
/>;

// @ts-expect-error
<Stack display={{ ml: 'block' }} divider={<h1>Hello</h1>} />;
3 changes: 2 additions & 1 deletion packages/pigment-css-react/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion packages/pigment-css-react/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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([
{
Expand Down
4 changes: 3 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 18a6f92

Please sign in to comment.