From 3bb46d970c62e260ba25ab622664322a45c10db5 Mon Sep 17 00:00:00 2001 From: georgewrmarshall Date: Thu, 10 Oct 2024 20:59:36 -0700 Subject: [PATCH] feat: adding initial spacing token --- docs/Spacing.mdx | 54 +++++++ docs/Spacing.stories.tsx | 71 +++++++++ .../SpacingSwatch/SpacingSwatch.stories.tsx | 27 ++++ .../SpacingSwatch/SpacingSwatch.tsx | 31 ++++ docs/components/SpacingSwatch/index.tsx | 1 + docs/components/index.tsx | 1 + src/css/spacing.css | 42 +++++ src/figma/spacing.json | 144 ++++++++++++++++++ src/js/spacing/spacing.test.ts | 89 +++++++++++ src/js/spacing/spacing.ts | 50 ++++++ 10 files changed, 510 insertions(+) create mode 100644 docs/Spacing.mdx create mode 100644 docs/Spacing.stories.tsx create mode 100644 docs/components/SpacingSwatch/SpacingSwatch.stories.tsx create mode 100644 docs/components/SpacingSwatch/SpacingSwatch.tsx create mode 100644 docs/components/SpacingSwatch/index.tsx create mode 100644 src/css/spacing.css create mode 100644 src/figma/spacing.json create mode 100644 src/js/spacing/spacing.test.ts create mode 100644 src/js/spacing/spacing.ts diff --git a/docs/Spacing.mdx b/docs/Spacing.mdx new file mode 100644 index 00000000..10d100f4 --- /dev/null +++ b/docs/Spacing.mdx @@ -0,0 +1,54 @@ +import { Meta, Story, Canvas } from '@storybook/addon-docs/blocks'; +import * as SpacingStories from './Spacing.stories'; + + + +# Spacing + +Spacing tokens are used to maintain consistent layout and whitespace throughout the application. They follow a scale that provides a range of sizes for various layout needs. It follows the [Tailwind CSS spacing](https://tailwindcss.com/docs/customizing-spacing). + + + + + +## Usage + +Spacing tokens can be used in both CSS and CSS-in-JS contexts. The table below shows the name, size in rem, pixels, and how to access each spacing token in JS and CSS. + +| Name | Size | Pixels | JS | CSS | +| ---- | -------- | ------ | -------------- | -------------------- | +| 0 | 0px | 0px | `spacing[0]` | `var(--spacing-0)` | +| px | 1px | 1px | `spacing.px` | `var(--spacing-px)` | +| 0.5 | 0.125rem | 2px | `spacing[0.5]` | `var(--spacing-0.5)` | +| 1 | 0.25rem | 4px | `spacing[1]` | `var(--spacing-1)` | +| 1.5 | 0.375rem | 6px | `spacing[1.5]` | `var(--spacing-1.5)` | +| 2 | 0.5rem | 8px | `spacing[2]` | `var(--spacing-2)` | +| 2.5 | 0.625rem | 10px | `spacing[2.5]` | `var(--spacing-2.5)` | +| 3 | 0.75rem | 12px | `spacing[3]` | `var(--spacing-3)` | +| 3.5 | 0.875rem | 14px | `spacing[3.5]` | `var(--spacing-3.5)` | +| 4 | 1rem | 16px | `spacing[4]` | `var(--spacing-4)` | +| 5 | 1.25rem | 20px | `spacing[5]` | `var(--spacing-5)` | +| 6 | 1.5rem | 24px | `spacing[6]` | `var(--spacing-6)` | +| 7 | 1.75rem | 28px | `spacing[7]` | `var(--spacing-7)` | +| 8 | 2rem | 32px | `spacing[8]` | `var(--spacing-8)` | +| 9 | 2.25rem | 36px | `spacing[9]` | `var(--spacing-9)` | +| 10 | 2.5rem | 40px | `spacing[10]` | `var(--spacing-10)` | +| 11 | 2.75rem | 44px | `spacing[11]` | `var(--spacing-11)` | +| 12 | 3rem | 48px | `spacing[12]` | `var(--spacing-12)` | +| 14 | 3.5rem | 56px | `spacing[14]` | `var(--spacing-14)` | +| 16 | 4rem | 64px | `spacing[16]` | `var(--spacing-16)` | +| 20 | 5rem | 80px | `spacing[20]` | `var(--spacing-20)` | +| 24 | 6rem | 96px | `spacing[24]` | `var(--spacing-24)` | +| 28 | 7rem | 112px | `spacing[28]` | `var(--spacing-28)` | +| 32 | 8rem | 128px | `spacing[32]` | `var(--spacing-32)` | +| 36 | 9rem | 144px | `spacing[36]` | `var(--spacing-36)` | +| 40 | 10rem | 160px | `spacing[40]` | `var(--spacing-40)` | +| 44 | 11rem | 176px | `spacing[44]` | `var(--spacing-44)` | +| 48 | 12rem | 192px | `spacing[48]` | `var(--spacing-48)` | +| 52 | 13rem | 208px | `spacing[52]` | `var(--spacing-52)` | +| 56 | 14rem | 224px | `spacing[56]` | `var(--spacing-56)` | +| 60 | 15rem | 240px | `spacing[60]` | `var(--spacing-60)` | +| 64 | 16rem | 256px | `spacing[64]` | `var(--spacing-64)` | +| 72 | 18rem | 288px | `spacing[72]` | `var(--spacing-72)` | +| 80 | 20rem | 320px | `spacing[80]` | `var(--spacing-80)` | +| 96 | 24rem | 384px | `spacing[96]` | `var(--spacing-96)` | diff --git a/docs/Spacing.stories.tsx b/docs/Spacing.stories.tsx new file mode 100644 index 00000000..2917abfa --- /dev/null +++ b/docs/Spacing.stories.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { SpacingSwatch, Text } from './components'; +import { spacing } from '../src/js/spacing/spacing'; +import README from './Spacing.mdx'; + +const meta: Meta = { + title: 'Tokens/Spacing', + parameters: { + docs: { + page: README, + }, + }, +}; + +export default meta; + +const spacingData = [ + { name: '0', size: '0px', pixels: '0px', value: spacing['0'] }, + { name: 'px', size: '1px', pixels: '1px', value: spacing.px }, + { name: '0.5', size: '0.125rem', pixels: '2px', value: spacing['0.5'] }, + { name: '1', size: '0.25rem', pixels: '4px', value: spacing['1'] }, + { name: '1.5', size: '0.375rem', pixels: '6px', value: spacing['1.5'] }, + { name: '2', size: '0.5rem', pixels: '8px', value: spacing['2'] }, + { name: '2.5', size: '0.625rem', pixels: '10px', value: spacing['2.5'] }, + { name: '3', size: '0.75rem', pixels: '12px', value: spacing['3'] }, + { name: '3.5', size: '0.875rem', pixels: '14px', value: spacing['3.5'] }, + { name: '4', size: '1rem', pixels: '16px', value: spacing['4'] }, + { name: '5', size: '1.25rem', pixels: '20px', value: spacing['5'] }, + { name: '6', size: '1.5rem', pixels: '24px', value: spacing['6'] }, + { name: '7', size: '1.75rem', pixels: '28px', value: spacing['7'] }, + { name: '8', size: '2rem', pixels: '32px', value: spacing['8'] }, + { name: '9', size: '2.25rem', pixels: '36px', value: spacing['9'] }, + { name: '10', size: '2.5rem', pixels: '40px', value: spacing['10'] }, + { name: '11', size: '2.75rem', pixels: '44px', value: spacing['11'] }, + { name: '12', size: '3rem', pixels: '48px', value: spacing['12'] }, + { name: '14', size: '3.5rem', pixels: '56px', value: spacing['14'] }, + { name: '16', size: '4rem', pixels: '64px', value: spacing['16'] }, + { name: '20', size: '5rem', pixels: '80px', value: spacing['20'] }, + { name: '24', size: '6rem', pixels: '96px', value: spacing['24'] }, + { name: '28', size: '7rem', pixels: '112px', value: spacing['28'] }, + { name: '32', size: '8rem', pixels: '128px', value: spacing['32'] }, + { name: '36', size: '9rem', pixels: '144px', value: spacing['36'] }, + { name: '40', size: '10rem', pixels: '160px', value: spacing['40'] }, + { name: '44', size: '11rem', pixels: '176px', value: spacing['44'] }, + { name: '48', size: '12rem', pixels: '192px', value: spacing['48'] }, + { name: '52', size: '13rem', pixels: '208px', value: spacing['52'] }, + { name: '56', size: '14rem', pixels: '224px', value: spacing['56'] }, + { name: '60', size: '15rem', pixels: '240px', value: spacing['60'] }, + { name: '64', size: '16rem', pixels: '256px', value: spacing['64'] }, + { name: '72', size: '18rem', pixels: '288px', value: spacing['72'] }, + { name: '80', size: '20rem', pixels: '320px', value: spacing['80'] }, + { name: '96', size: '24rem', pixels: '384px', value: spacing['96'] }, +]; + +export const Default: StoryObj = { + render: () => ( +
+ Name | Size | Pixels + {spacingData.map((item) => ( + + ))} +
+ ), +}; diff --git a/docs/components/SpacingSwatch/SpacingSwatch.stories.tsx b/docs/components/SpacingSwatch/SpacingSwatch.stories.tsx new file mode 100644 index 00000000..04f54ecc --- /dev/null +++ b/docs/components/SpacingSwatch/SpacingSwatch.stories.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import type { Meta, StoryObj } from '@storybook/react'; +import { SpacingSwatch } from './SpacingSwatch'; + +const meta: Meta = { + title: 'Documentation Components/SpacingSwatch', + component: SpacingSwatch, + argTypes: { + name: { control: 'text' }, + size: { control: 'text' }, + pixels: { control: 'text' }, + value: { control: 'text' }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + name: '4', + size: '1rem', + pixels: '16px', + value: '1rem', + }, +}; diff --git a/docs/components/SpacingSwatch/SpacingSwatch.tsx b/docs/components/SpacingSwatch/SpacingSwatch.tsx new file mode 100644 index 00000000..78f00b53 --- /dev/null +++ b/docs/components/SpacingSwatch/SpacingSwatch.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { Text } from '../Text'; + +interface SpacingSwatchProps { + name: string; + size: string; + pixels: string; + value: string; +} + +export const SpacingSwatch: React.FC = ({ + name, + size, + pixels, + value, +}) => ( +
+ + {name} |{' '} + {size} | {pixels} + +
+
+); diff --git a/docs/components/SpacingSwatch/index.tsx b/docs/components/SpacingSwatch/index.tsx new file mode 100644 index 00000000..60b4c942 --- /dev/null +++ b/docs/components/SpacingSwatch/index.tsx @@ -0,0 +1 @@ +export { SpacingSwatch } from './SpacingSwatch'; diff --git a/docs/components/index.tsx b/docs/components/index.tsx index 9868799e..bf055aa2 100644 --- a/docs/components/index.tsx +++ b/docs/components/index.tsx @@ -1,3 +1,4 @@ export { ColorSwatch } from './ColorSwatch'; export { ColorSwatchGroup } from './ColorSwatchGroup'; +export { SpacingSwatch } from './SpacingSwatch'; export { Text } from './Text'; diff --git a/src/css/spacing.css b/src/css/spacing.css new file mode 100644 index 00000000..d6db33a0 --- /dev/null +++ b/src/css/spacing.css @@ -0,0 +1,42 @@ +/* + * This spacing system follows Tailwind CSS default spacing scale. + * For more information, see: https://tailwindcss.com/docs/customizing-spacing + */ + +:root { + --spacing-0: 0px; + --spacing-px: 1px; + --spacing-0-5: 0.125rem; + --spacing-1: 0.25rem; + --spacing-1-5: 0.375rem; + --spacing-2: 0.5rem; + --spacing-2-5: 0.625rem; + --spacing-3: 0.75rem; + --spacing-3-5: 0.875rem; + --spacing-4: 1rem; + --spacing-5: 1.25rem; + --spacing-6: 1.5rem; + --spacing-7: 1.75rem; + --spacing-8: 2rem; + --spacing-9: 2.25rem; + --spacing-10: 2.5rem; + --spacing-11: 2.75rem; + --spacing-12: 3rem; + --spacing-14: 3.5rem; + --spacing-16: 4rem; + --spacing-20: 5rem; + --spacing-24: 6rem; + --spacing-28: 7rem; + --spacing-32: 8rem; + --spacing-36: 9rem; + --spacing-40: 10rem; + --spacing-44: 11rem; + --spacing-48: 12rem; + --spacing-52: 13rem; + --spacing-56: 14rem; + --spacing-60: 15rem; + --spacing-64: 16rem; + --spacing-72: 18rem; + --spacing-80: 20rem; + --spacing-96: 24rem; +} diff --git a/src/figma/spacing.json b/src/figma/spacing.json new file mode 100644 index 00000000..f19aca85 --- /dev/null +++ b/src/figma/spacing.json @@ -0,0 +1,144 @@ +{ + "spacing": { + "0": { + "value": "0px", + "type": "spacing" + }, + "px": { + "value": "1px", + "type": "spacing" + }, + "0.5": { + "value": "2px", + "type": "spacing" + }, + "1": { + "value": "4px", + "type": "spacing" + }, + "1.5": { + "value": "6px", + "type": "spacing" + }, + "2": { + "value": "8px", + "type": "spacing" + }, + "2.5": { + "value": "10px", + "type": "spacing" + }, + "3": { + "value": "12px", + "type": "spacing" + }, + "3.5": { + "value": "14px", + "type": "spacing" + }, + "4": { + "value": "16px", + "type": "spacing" + }, + "5": { + "value": "20px", + "type": "spacing" + }, + "6": { + "value": "24px", + "type": "spacing" + }, + "7": { + "value": "28px", + "type": "spacing" + }, + "8": { + "value": "32px", + "type": "spacing" + }, + "9": { + "value": "36px", + "type": "spacing" + }, + "10": { + "value": "40px", + "type": "spacing" + }, + "11": { + "value": "44px", + "type": "spacing" + }, + "12": { + "value": "48px", + "type": "spacing" + }, + "14": { + "value": "56px", + "type": "spacing" + }, + "16": { + "value": "64px", + "type": "spacing" + }, + "20": { + "value": "80px", + "type": "spacing" + }, + "24": { + "value": "96px", + "type": "spacing" + }, + "28": { + "value": "112px", + "type": "spacing" + }, + "32": { + "value": "128px", + "type": "spacing" + }, + "36": { + "value": "144px", + "type": "spacing" + }, + "40": { + "value": "160px", + "type": "spacing" + }, + "44": { + "value": "176px", + "type": "spacing" + }, + "48": { + "value": "192px", + "type": "spacing" + }, + "52": { + "value": "208px", + "type": "spacing" + }, + "56": { + "value": "224px", + "type": "spacing" + }, + "60": { + "value": "240px", + "type": "spacing" + }, + "64": { + "value": "256px", + "type": "spacing" + }, + "72": { + "value": "288px", + "type": "spacing" + }, + "80": { + "value": "320px", + "type": "spacing" + }, + "96": { + "value": "384px", + "type": "spacing" + } + } +} diff --git a/src/js/spacing/spacing.test.ts b/src/js/spacing/spacing.test.ts new file mode 100644 index 00000000..dacd7445 --- /dev/null +++ b/src/js/spacing/spacing.test.ts @@ -0,0 +1,89 @@ +import { spacing, getSpacing } from '../spacing/spacing'; + +describe('spacing tokens', () => { + it('spacing object contains all expected keys', () => { + const expectedKeys = [ + 'px', + '0', + '0.5', + '1', + '1.5', + '2', + '2.5', + '3', + '3.5', + '4', + '5', + '6', + '7', + '8', + '9', + '10', + '11', + '12', + '14', + '16', + '20', + '24', + '28', + '32', + '36', + '40', + '44', + '48', + '52', + '56', + '60', + '64', + '72', + '80', + '96', + ]; + + expectedKeys.forEach((key) => { + expect(spacing).toHaveProperty(key); + }); + }); + + it('spacing values are correct', () => { + expect(spacing['0']).toBe('0px'); + expect(spacing.px).toBe('1px'); + expect(spacing['0.5']).toBe('0.125rem'); + expect(spacing['1']).toBe('0.25rem'); + expect(spacing['1.5']).toBe('0.375rem'); + expect(spacing['2']).toBe('0.5rem'); + expect(spacing['4']).toBe('1rem'); + expect(spacing['16']).toBe('4rem'); + expect(spacing['96']).toBe('24rem'); + }); + + it('getSpacing function returns correct values', () => { + expect(getSpacing('0')).toBe('0px'); + expect(getSpacing('px')).toBe('1px'); + expect(getSpacing('0.5')).toBe('0.125rem'); + expect(getSpacing('1')).toBe('0.25rem'); + expect(getSpacing('4')).toBe('1rem'); + expect(getSpacing('96')).toBe('24rem'); + }); + + it('getSpacing function returns undefined for non-existent keys', () => { + expect(getSpacing('nonexistent')).toBeUndefined(); + expect(getSpacing('100')).toBeUndefined(); + }); + + it('spacing object has the correct number of entries', () => { + expect(Object.keys(spacing)).toHaveLength(35); + }); + + it('all spacing values are strings', () => { + Object.values(spacing).forEach((value) => { + expect(typeof value).toBe('string'); + }); + }); + + it('all spacing values end with either px or rem', () => { + Object.values(spacing).forEach((value) => { + expect(value.endsWith('px') || value.endsWith('rem')).toBe(true); + }); + }); +}); diff --git a/src/js/spacing/spacing.ts b/src/js/spacing/spacing.ts new file mode 100644 index 00000000..7071fe8d --- /dev/null +++ b/src/js/spacing/spacing.ts @@ -0,0 +1,50 @@ +/** + * This spacing system follows Tailwind CSS default spacing scale. + * For more information, see: https://tailwindcss.com/docs/customizing-spacing + */ +export const spacing: { [key: string]: string } = { + px: '1px', + 0: '0px', + 0.5: '0.125rem', + 1: '0.25rem', + 1.5: '0.375rem', + 2: '0.5rem', + 2.5: '0.625rem', + 3: '0.75rem', + 3.5: '0.875rem', + 4: '1rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 9: '2.25rem', + 10: '2.5rem', + 11: '2.75rem', + 12: '3rem', + 14: '3.5rem', + 16: '4rem', + 20: '5rem', + 24: '6rem', + 28: '7rem', + 32: '8rem', + 36: '9rem', + 40: '10rem', + 44: '11rem', + 48: '12rem', + 52: '13rem', + 56: '14rem', + 60: '15rem', + 64: '16rem', + 72: '18rem', + 80: '20rem', + 96: '24rem', +}; + +/** + * Get a spacing value by key. + * @param key - The spacing key. + * @returns The spacing value or undefined if not found. + */ +export function getSpacing(key: string): string | undefined { + return spacing[key]; +}