Skip to content

Commit

Permalink
feat(theme): Add fluid tokens for spacing and typo (#88)
Browse files Browse the repository at this point in the history
  • Loading branch information
tchock authored Jan 18, 2025
1 parent aed4c84 commit 38dc9fa
Show file tree
Hide file tree
Showing 82 changed files with 2,197 additions and 1,218 deletions.
2 changes: 1 addition & 1 deletion .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,400;0,500;1,400;1,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,400;0,500;0,700;1,400;1,500&display=swap" rel="stylesheet">
20 changes: 19 additions & 1 deletion src/Box/Box.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { layout, LayoutProps } from './interpolations/layout';
import { svg, SvgProps } from './interpolations/svg';
import { position, PositionProps } from './interpolations/position';
import { flexItem, FlexItemProps } from './interpolations/flex';
import { shape, ShapeProps } from './interpolations/shape';
import { splitProps } from '../utils/splitProps';

export interface BoxCssProps {
css?: CssFunctionReturn;
Expand All @@ -19,10 +21,26 @@ export type BoxProps = SpacingProps &
FlexItemProps &
PositionProps &
SvgProps &
ShapeProps &
BoxCssProps;

export const boxInterpolateFn = (props) =>
[margin, padding, color, layout, svg, position, flexItem].map((fn) => fn(props));
[margin, padding, color, shape, layout, svg, position, flexItem].map((fn) => fn(props));

export const boxPropNames = [
...margin.propNames,
...padding.propNames,
...color.propNames,
...shape.propNames,
...layout.propNames,
...svg.propNames,
...position.propNames,
...flexItem.propNames,
];

export const useBoxProps = (props: Record<string, any>) => {
return splitProps(props, boxPropNames);
};

export const Box = styled.div<BoxProps>`
${baseStyle}
Expand Down
4 changes: 2 additions & 2 deletions src/Box/interpolations/layout.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type * as CSS from 'csstype';
import { pixelTransform, ResponsiveValue, system } from '../system';
import { PabloTheme } from '../../theme/types';
import { PabloThemeFull } from '../../theme/types';
import { isNumber } from '../../utils/isNumber';

interface LayoutProps {
Expand All @@ -14,7 +14,7 @@ interface LayoutProps {
display?: ResponsiveValue<CSS.Property.Display>;
}

const widthTransform = (value: number | string, theme: PabloTheme) =>
const widthTransform = (value: number | string, theme: PabloThemeFull) =>
isNumber(value) && value <= 1 ? `${value * 100}%` : pixelTransform(value, theme);

const layout = system([
Expand Down
8 changes: 4 additions & 4 deletions src/Box/interpolations/position.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test('position', () => {

test('top', () => {
expect(position({ ...props, top: 2 })).toEqual({
top: '1rem',
top: expect.stringMatching(/clamp\(1rem, .*?, 1\.5rem\)/),
});
expect(position({ ...props, top: '100%' })).toEqual({
top: '100%',
Expand All @@ -32,7 +32,7 @@ test('top', () => {

test('right', () => {
expect(position({ ...props, right: 2 })).toEqual({
right: '1rem',
right: expect.stringMatching(/clamp\(1rem, .*?, 1\.5rem\)/),
});
expect(position({ ...props, right: '100%' })).toEqual({
right: '100%',
Expand All @@ -41,7 +41,7 @@ test('right', () => {

test('bottom', () => {
expect(position({ ...props, bottom: 2 })).toEqual({
bottom: '1rem',
bottom: expect.stringMatching(/clamp\(1rem, .*?, 1\.5rem\)/),
});
expect(position({ ...props, bottom: '100%' })).toEqual({
bottom: '100%',
Expand All @@ -50,7 +50,7 @@ test('bottom', () => {

test('left', () => {
expect(position({ ...props, left: 2 })).toEqual({
left: '1rem',
left: expect.stringMatching(/clamp\(1rem, .*?, 1\.5rem\)/),
});
expect(position({ ...props, left: '100%' })).toEqual({
left: '100%',
Expand Down
31 changes: 31 additions & 0 deletions src/Box/interpolations/shape.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { ResponsiveValue, system } from '../system';
import { BorderRadiusSizes } from '../../theme/shape';
import { PabloTheme } from '../../theme/types';
import { calculateFluidClamp, ensureFluidTuple } from '../../styleHelpers/fluidClamp';

type BorderRadiusInput = BorderRadiusSizes | string | number;

export interface ShapeProps {
borderRadius?: ResponsiveValue<BorderRadiusInput>;
br?: ResponsiveValue<BorderRadiusInput>;
}

const borderRadiusTransform = (value: BorderRadiusInput, theme: PabloTheme) => {
const borderRadius = theme.shape.borderRadius[value as BorderRadiusSizes] || value;
if (typeof borderRadius === 'string') {
return borderRadius;
}
const [minSize, maxSize] = ensureFluidTuple(borderRadius);
return calculateFluidClamp(minSize, maxSize, theme.fluid.minScreen, theme.fluid.maxScreen);
};

const shape = system([
{
properties: ['borderRadius'],
fromProps: ['borderRadius', 'br'],
transform: borderRadiusTransform,
as: 'type',
},
]);

export { shape, borderRadiusTransform };
149 changes: 78 additions & 71 deletions src/Box/interpolations/spacing.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { defaultTheme } from '../../theme';
import { PabloThemeableProps } from '../../theme/types';
import { margin, microMargin, microPadding, padding } from './spacing';
import { matchClamp, matchMultipleClamp } from '../../../testUtils/matchClamp';

let props: PabloThemeableProps = {
theme: defaultTheme,
Expand All @@ -13,14 +14,14 @@ beforeEach(() => {
});

describe.each([
['macro margin', margin, 'margin', 0.5, 'm'],
['macro padding', padding, 'padding', 0.5, 'p'],
['micro margin', microMargin, 'margin', 0.25, 'mm'],
['micro padding', microPadding, 'padding', 0.25, 'mp'],
])('%s system', (name, systemFn: any, property, base, shorthand) => {
['macro margin', margin, 'margin', 0.5, 0.75, 'm'],
['macro padding', padding, 'padding', 0.5, 0.75, 'p'],
['micro margin', microMargin, 'margin', 0.25, 0.5, 'mm'],
['micro padding', microPadding, 'padding', 0.25, 0.5, 'mp'],
])('%s system', (name, systemFn: any, property, lowerBase, upperBase, shorthand) => {
test(`${name} system`, () => {
expect(systemFn({ [shorthand]: 10, ...props })).toEqual({
[property]: `${10 * base}rem`,
[property]: matchClamp(10, lowerBase, upperBase),
});
});

Expand All @@ -34,19 +35,19 @@ describe.each([
...props,
})
).toEqual({
[`${property}Top`]: `${1 * base}rem`,
[`${property}Right`]: `${2 * base}rem`,
[`${property}Bottom`]: `${3 * base}rem`,
[`${property}Left`]: `${4 * base}rem`,
[`${property}Top`]: matchClamp(1, lowerBase, upperBase),
[`${property}Right`]: matchClamp(2, lowerBase, upperBase),
[`${property}Bottom`]: matchClamp(3, lowerBase, upperBase),
[`${property}Left`]: matchClamp(4, lowerBase, upperBase),
});
});

test(`${name} system with x and y props`, () => {
expect(systemFn({ [`${shorthand}x`]: 10, [`${shorthand}y`]: 20, ...props })).toEqual({
[`${property}Left`]: `${10 * base}rem`,
[`${property}Right`]: `${10 * base}rem`,
[`${property}Top`]: `${20 * base}rem`,
[`${property}Bottom`]: `${20 * base}rem`,
[`${property}Left`]: matchClamp(10, lowerBase, upperBase),
[`${property}Right`]: matchClamp(10, lowerBase, upperBase),
[`${property}Top`]: matchClamp(20, lowerBase, upperBase),
[`${property}Bottom`]: matchClamp(20, lowerBase, upperBase),
});
});

Expand All @@ -65,99 +66,105 @@ describe.each([

namedSpacings.forEach(([name, multiplier]) => {
expect(systemFn({ [`${shorthand}`]: name, ...props })).toEqual({
[property]: `${multiplier * base}rem`,
[property]: matchClamp(multiplier, lowerBase, upperBase),
});
expect(systemFn({ [`${shorthand}t`]: name, ...props })).toEqual({
[`${property}Top`]: `${multiplier * base}rem`,
[`${property}Top`]: matchClamp(multiplier, lowerBase, upperBase),
});
expect(systemFn({ [`${shorthand}r`]: name, ...props })).toEqual({
[`${property}Right`]: `${multiplier * base}rem`,
[`${property}Right`]: matchClamp(multiplier, lowerBase, upperBase),
});
expect(systemFn({ [`${shorthand}b`]: name, ...props })).toEqual({
[`${property}Bottom`]: `${multiplier * base}rem`,
[`${property}Bottom`]: matchClamp(multiplier, lowerBase, upperBase),
});
expect(systemFn({ [`${shorthand}l`]: name, ...props })).toEqual({
[`${property}Left`]: `${multiplier * base}rem`,
[`${property}Left`]: matchClamp(multiplier, lowerBase, upperBase),
});
expect(systemFn({ [`${shorthand}x`]: name, ...props })).toEqual({
[`${property}Left`]: `${multiplier * base}rem`,
[`${property}Right`]: `${multiplier * base}rem`,
[`${property}Left`]: matchClamp(multiplier, lowerBase, upperBase),
[`${property}Right`]: matchClamp(multiplier, lowerBase, upperBase),
});
expect(systemFn({ [`${shorthand}y`]: name, ...props })).toEqual({
[`${property}Top`]: `${multiplier * base}rem`,
[`${property}Bottom`]: `${multiplier * base}rem`,
[`${property}Top`]: matchClamp(multiplier, lowerBase, upperBase),
[`${property}Bottom`]: matchClamp(multiplier, lowerBase, upperBase),
});
});
});

test(`styled interpolation functions for ${name}`, () => {
expect(systemFn.all(10)(props)).toEqual({
[property]: `${10 * base}rem`,
[property]: matchClamp(10, lowerBase, upperBase),
});
expect(systemFn.top(1)(props)).toEqual({
[`${property}Top`]: `${1 * base}rem`,
[`${property}Top`]: matchClamp(1, lowerBase, upperBase),
});
expect(systemFn.x(10)(props)).toEqual({
[`${property}Left`]: `${10 * base}rem`,
[`${property}Right`]: `${10 * base}rem`,
[`${property}Left`]: matchClamp(10, lowerBase, upperBase),
[`${property}Right`]: matchClamp(10, lowerBase, upperBase),
});
expect(systemFn.y(10)(props)).toEqual({
[`${property}Top`]: `${10 * base}rem`,
[`${property}Bottom`]: `${10 * base}rem`,
[`${property}Top`]: matchClamp(10, lowerBase, upperBase),
[`${property}Bottom`]: matchClamp(10, lowerBase, upperBase),
});
});
});

test.each([
['macro', 0.5, padding],
['micro', 0.25, microPadding],
])('%s gap spacing', (_, base, systemFn: typeof padding) => {
['macro', 0.5, 0.75, padding],
['micro', 0.25, 0.5, microPadding],
])('%s gap spacing', (_, lowerBase, upperBase, systemFn: typeof padding) => {
expect(systemFn({ gap: 10, ...props })).toEqual({
gap: `${10 * base}rem ${10 * base}rem`,
gap: matchMultipleClamp([10, 10], lowerBase, upperBase),
});
expect(systemFn({ gap: [[10, 1]], ...props })).toEqual({
gap: `${10 * base}rem ${1 * base}rem`,
gap: matchMultipleClamp([10, 1], lowerBase, upperBase),
});

expect(systemFn.gap(10)(props)).toEqual({
gap: `${10 * base}rem ${10 * base}rem`,
gap: matchMultipleClamp([10, 10], lowerBase, upperBase),
});
});

test('getSpacing', () => {
expect(margin.get('xxxs')(props)).toEqual('0.125rem');
expect(margin.get('xxs')(props)).toEqual('0.25rem');
expect(margin.get('xs')(props)).toEqual('0.375rem');
expect(margin.get('sm')(props)).toEqual('0.5rem');
expect(margin.get('md')(props)).toEqual('0.75rem');
expect(margin.get('lg')(props)).toEqual('1rem');
expect(margin.get('xl')(props)).toEqual('1.5rem');
expect(margin.get('xxl')(props)).toEqual('2rem');
expect(margin.get('xxxl')(props)).toEqual('3rem');
expect(padding.get('xxxs')(props)).toEqual('0.125rem');
expect(padding.get('xxs')(props)).toEqual('0.25rem');
expect(padding.get('xs')(props)).toEqual('0.375rem');
expect(padding.get('sm')(props)).toEqual('0.5rem');
expect(padding.get('md')(props)).toEqual('0.75rem');
expect(padding.get('lg')(props)).toEqual('1rem');
expect(padding.get('xl')(props)).toEqual('1.5rem');
expect(padding.get('xxl')(props)).toEqual('2rem');
expect(padding.get('xxxl')(props)).toEqual('3rem');
expect(microMargin.get('xxxs')(props)).toEqual('0.0625rem');
expect(microMargin.get('xxs')(props)).toEqual('0.125rem');
expect(microMargin.get('xs')(props)).toEqual('0.1875rem');
expect(microMargin.get('sm')(props)).toEqual('0.25rem');
expect(microMargin.get('md')(props)).toEqual('0.375rem');
expect(microMargin.get('lg')(props)).toEqual('0.5rem');
expect(microMargin.get('xl')(props)).toEqual('0.75rem');
expect(microMargin.get('xxl')(props)).toEqual('1rem');
expect(microMargin.get('xxxl')(props)).toEqual('1.5rem');
expect(microPadding.get('xxxs')(props)).toEqual('0.0625rem');
expect(microPadding.get('xxs')(props)).toEqual('0.125rem');
expect(microPadding.get('xs')(props)).toEqual('0.1875rem');
expect(microPadding.get('sm')(props)).toEqual('0.25rem');
expect(microPadding.get('md')(props)).toEqual('0.375rem');
expect(microPadding.get('lg')(props)).toEqual('0.5rem');
expect(microPadding.get('xl')(props)).toEqual('0.75rem');
expect(microPadding.get('xxl')(props)).toEqual('1rem');
expect(microPadding.get('xxxl')(props)).toEqual('1.5rem');
const macroUpperBase = 0.75;
const macroLowerBase = 0.5;
expect(margin.get('xxxs')(props)).toEqual(matchClamp(0.25, macroLowerBase, macroUpperBase));
expect(margin.get('xxs')(props)).toEqual(matchClamp(0.5, macroLowerBase, macroUpperBase));
expect(margin.get('xs')(props)).toEqual(matchClamp(0.75, macroLowerBase, macroUpperBase));
expect(margin.get('sm')(props)).toEqual(matchClamp(1, macroLowerBase, macroUpperBase));
expect(margin.get('md')(props)).toEqual(matchClamp(1.5, macroLowerBase, macroUpperBase));
expect(margin.get('lg')(props)).toEqual(matchClamp(2, macroLowerBase, macroUpperBase));
expect(margin.get('xl')(props)).toEqual(matchClamp(3, macroLowerBase, macroUpperBase));
expect(margin.get('xxl')(props)).toEqual(matchClamp(4, macroLowerBase, macroUpperBase));
expect(margin.get('xxxl')(props)).toEqual(matchClamp(6, macroLowerBase, macroUpperBase));
expect(padding.get('xxxs')(props)).toEqual(matchClamp(0.25, macroLowerBase, macroUpperBase));
expect(padding.get('xxs')(props)).toEqual(matchClamp(0.5, macroLowerBase, macroUpperBase));
expect(padding.get('xs')(props)).toEqual(matchClamp(0.75, macroLowerBase, macroUpperBase));
expect(padding.get('sm')(props)).toEqual(matchClamp(1, macroLowerBase, macroUpperBase));
expect(padding.get('md')(props)).toEqual(matchClamp(1.5, macroLowerBase, macroUpperBase));
expect(padding.get('lg')(props)).toEqual(matchClamp(2, macroLowerBase, macroUpperBase));
expect(padding.get('xl')(props)).toEqual(matchClamp(3, macroLowerBase, macroUpperBase));
expect(padding.get('xxl')(props)).toEqual(matchClamp(4, macroLowerBase, macroUpperBase));
expect(padding.get('xxxl')(props)).toEqual(matchClamp(6, macroLowerBase, macroUpperBase));

const microUpperBase = 0.5;
const microLowerBase = 0.25;
expect(microMargin.get('xxxs')(props)).toEqual(matchClamp(0.25, microLowerBase, microUpperBase));
expect(microMargin.get('xxs')(props)).toEqual(matchClamp(0.5, microLowerBase, microUpperBase));
expect(microMargin.get('xs')(props)).toEqual(matchClamp(0.75, microLowerBase, microUpperBase));
expect(microMargin.get('sm')(props)).toEqual(matchClamp(1, microLowerBase, microUpperBase));
expect(microMargin.get('md')(props)).toEqual(matchClamp(1.5, microLowerBase, microUpperBase));
expect(microMargin.get('lg')(props)).toEqual(matchClamp(2, microLowerBase, microUpperBase));
expect(microMargin.get('xl')(props)).toEqual(matchClamp(3, microLowerBase, microUpperBase));
expect(microMargin.get('xxl')(props)).toEqual(matchClamp(4, microLowerBase, microUpperBase));
expect(microMargin.get('xxxl')(props)).toEqual(matchClamp(6, microLowerBase, microUpperBase));
expect(microPadding.get('xxxs')(props)).toEqual(matchClamp(0.25, microLowerBase, microUpperBase));
expect(microPadding.get('xxs')(props)).toEqual(matchClamp(0.5, microLowerBase, microUpperBase));
expect(microPadding.get('xs')(props)).toEqual(matchClamp(0.75, microLowerBase, microUpperBase));
expect(microPadding.get('sm')(props)).toEqual(matchClamp(1, microLowerBase, microUpperBase));
expect(microPadding.get('md')(props)).toEqual(matchClamp(1.5, microLowerBase, microUpperBase));
expect(microPadding.get('lg')(props)).toEqual(matchClamp(2, microLowerBase, microUpperBase));
expect(microPadding.get('xl')(props)).toEqual(matchClamp(3, microLowerBase, microUpperBase));
expect(microPadding.get('xxl')(props)).toEqual(matchClamp(4, microLowerBase, microUpperBase));
expect(microPadding.get('xxxl')(props)).toEqual(matchClamp(6, microLowerBase, microUpperBase));
});
4 changes: 2 additions & 2 deletions src/Box/interpolations/spacing.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SpacingNames } from '../../theme/spacing';
import { PabloTheme } from '../../theme/types';
import { PabloThemeFull } from '../../theme/types';
import {
InterpolationReturn,
InterpolationTransformFn,
Expand All @@ -11,7 +11,7 @@ import {

type SpacingTransformFn = InterpolationTransformFn<number | string, InterpolationReturn>;

const getGapSpacing = (transformFn: SpacingTransformFn) => (value: any, theme: PabloTheme) => {
const getGapSpacing = (transformFn: SpacingTransformFn) => (value: any, theme: PabloThemeFull) => {
if (Array.isArray(value)) {
return value.map((val) => transformFn(val, theme)).join(' ');
}
Expand Down
Loading

0 comments on commit 38dc9fa

Please sign in to comment.