Skip to content

Commit 6bf055c

Browse files
chore: design system colour tokens, primitives & Storybook (#6883)
1 parent b5f81bc commit 6bf055c

52 files changed

Lines changed: 6424 additions & 156 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Frontend Chromatic
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
paths:
7+
- frontend/**
8+
- .github/workflows/frontend-chromatic.yml
9+
10+
permissions:
11+
contents: read
12+
13+
jobs:
14+
chromatic:
15+
name: Chromatic
16+
runs-on: ubuntu-latest
17+
if: github.event.pull_request.draft == false
18+
19+
defaults:
20+
run:
21+
working-directory: frontend
22+
23+
steps:
24+
- uses: actions/checkout@v5
25+
with:
26+
fetch-depth: 0
27+
28+
- name: Setup Node.js
29+
uses: actions/setup-node@v4
30+
with:
31+
node-version-file: frontend/.nvmrc
32+
cache: npm
33+
cache-dependency-path: frontend/package-lock.json
34+
35+
- name: Install dependencies
36+
run: npm ci
37+
38+
- name: Publish to Chromatic
39+
uses: chromaui/action@v11
40+
with:
41+
workingDir: frontend
42+
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
43+
exitZeroOnChanges: true
44+
exitOnceUploaded: true
45+
onlyChanged: true

frontend/.claude/context/ui-patterns.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# UI Patterns & Best Practices
22

3+
## Storybook (Optional)
4+
5+
When working on complex or unfamiliar components, you can query Storybook MCP (`list-all-documentation`, then `get-documentation`) to discover existing components, their props, and visual examples. This is optional — for simple changes, grepping the codebase is fine.
6+
37
## Table Components
48

59
### Pattern: Reusable Table Components

frontend/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
'plugin:@typescript-eslint/recommended',
1111
'plugin:prettier/recommended',
1212
'plugin:@dword-design/import-alias/recommended',
13+
'plugin:storybook/recommended',
1314
],
1415
'globals': {
1516
'$': true,

frontend/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,6 @@ common/project.js
3333
# Playwright
3434
e2e/playwright-report/
3535
e2e/test-results/
36+
37+
*storybook.log
38+
storybook-static
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// =============================================================================
2+
// Storybook Docs Theme Overrides
3+
// =============================================================================
4+
// The Storybook docs renderer uses its own styling that doesn't respond to
5+
// our theme toggle. These overrides ensure documentation pages are readable
6+
// in both light and dark mode.
7+
// =============================================================================
8+
9+
// Light mode — fix invisible inline code
10+
.sbdocs code {
11+
color: #1a2634;
12+
}
13+
14+
.dark {
15+
// Docs page background
16+
.sbdocs-wrapper {
17+
background-color: var(--color-surface-default, #101628);
18+
color: var(--color-text-default, #ffffff);
19+
}
20+
21+
.sbdocs-content {
22+
color: var(--color-text-default, #ffffff);
23+
}
24+
25+
// Headings
26+
.sbdocs h1,
27+
.sbdocs h2,
28+
.sbdocs h3,
29+
.sbdocs h4,
30+
.sbdocs h5 {
31+
color: var(--color-text-default, #ffffff);
32+
}
33+
34+
// Body text
35+
.sbdocs p,
36+
.sbdocs li,
37+
.sbdocs td,
38+
.sbdocs th {
39+
color: var(--color-text-default, #ffffff);
40+
}
41+
42+
// Code — inline and blocks
43+
.sbdocs code {
44+
background-color: var(--color-surface-emphasis, #202839);
45+
color: var(--color-text-default, #ffffff);
46+
}
47+
48+
.sbdocs pre {
49+
background-color: var(--color-surface-emphasis, #202839);
50+
color: var(--color-text-default, #ffffff);
51+
}
52+
53+
// Storybook Source/code block container and all its children
54+
.docblock-source,
55+
.docblock-source > * {
56+
background-color: var(--color-surface-emphasis, #202839) !important;
57+
color: var(--color-text-default, #ffffff) !important;
58+
}
59+
60+
// Copy button inside code blocks
61+
.docblock-source button {
62+
background-color: var(--color-surface-muted, #161d30) !important;
63+
color: var(--color-text-default, #ffffff) !important;
64+
border-color: var(--color-border-default, rgba(255, 255, 255, 0.16)) !important;
65+
}
66+
67+
// Syntax highlighting tokens
68+
.docblock-source .token {
69+
color: var(--color-text-default, #ffffff) !important;
70+
}
71+
72+
// Horizontal rules
73+
.sbdocs hr {
74+
border-color: var(--color-border-default, rgba(255, 255, 255, 0.16));
75+
}
76+
77+
// Canvas (story preview) background
78+
.docs-story {
79+
background-color: var(--color-surface-default, #101628);
80+
}
81+
82+
// Table borders
83+
.sbdocs table {
84+
border-color: var(--color-border-default, rgba(255, 255, 255, 0.16));
85+
}
86+
87+
.sbdocs th,
88+
.sbdocs td {
89+
border-color: var(--color-border-default, rgba(255, 255, 255, 0.16));
90+
}
91+
92+
// Table row backgrounds (override Storybook's alternating white rows)
93+
.sbdocs tr {
94+
background-color: var(--color-surface-default, #101628);
95+
}
96+
97+
.sbdocs tr:nth-child(even) {
98+
background-color: var(--color-surface-subtle, #15192b);
99+
}
100+
101+
.sbdocs thead tr {
102+
background-color: var(--color-surface-muted, #161d30);
103+
}
104+
}

frontend/.storybook/main.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const path = require('path')
2+
const webpack = require('webpack')
3+
4+
/** @type { import('storybook').StorybookConfig } */
5+
const config = {
6+
stories: [
7+
'../documentation/**/*.mdx',
8+
'../documentation/**/*.stories.@(js|jsx|ts|tsx)',
9+
],
10+
staticDirs: ['../web'],
11+
addons: [
12+
'@storybook/addon-webpack5-compiler-swc',
13+
'@storybook/addon-docs',
14+
'@storybook/addon-a11y',
15+
],
16+
framework: {
17+
name: '@storybook/react-webpack5',
18+
options: {},
19+
},
20+
swc: () => ({
21+
jsc: {
22+
transform: {
23+
react: {
24+
runtime: 'automatic',
25+
},
26+
},
27+
parser: {
28+
syntax: 'typescript',
29+
tsx: true,
30+
},
31+
},
32+
}),
33+
webpackFinal: async (config) => {
34+
config.resolve = config.resolve || {}
35+
config.resolve.alias = {
36+
...config.resolve.alias,
37+
common: path.resolve(__dirname, '../common'),
38+
components: path.resolve(__dirname, '../web/components'),
39+
project: path.resolve(__dirname, '../web/project'),
40+
}
41+
42+
config.module = config.module || {}
43+
config.module.rules = config.module.rules || []
44+
config.module.rules.push({
45+
test: /\.scss$/,
46+
use: [
47+
'style-loader',
48+
{ loader: 'css-loader', options: { importLoaders: 1 } },
49+
{
50+
loader: 'sass-loader',
51+
options: {
52+
sassOptions: {
53+
silenceDeprecations: ['slash-div'],
54+
},
55+
},
56+
},
57+
],
58+
})
59+
60+
config.plugins = config.plugins || []
61+
config.plugins.push(
62+
new webpack.DefinePlugin({
63+
E2E: false,
64+
}),
65+
)
66+
67+
return config
68+
},
69+
}
70+
module.exports = config
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<style>
2+
.sidebar-header img {
3+
max-width: 28px;
4+
max-height: 28px;
5+
}
6+
</style>

frontend/.storybook/manager.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { addons } from 'storybook/manager-api'
2+
import { create } from 'storybook/theming'
3+
4+
// Primitive palette — mirrors _primitives.scss
5+
// Storybook manager runs outside the app, so CSS vars aren't available.
6+
const slate = {
7+
0: '#ffffff',
8+
50: '#fafafb',
9+
100: '#eff1f4',
10+
200: '#e0e3e9',
11+
300: '#9da4ae',
12+
500: '#656d7b',
13+
600: '#1a2634',
14+
850: '#161d30',
15+
900: '#15192b',
16+
}
17+
const purple = { 600: '#6837fc' }
18+
19+
const shared = {
20+
brandTitle: 'Flagsmith',
21+
brandUrl: 'https://flagsmith.com',
22+
brandImage: '/static/images/nav-logo.png',
23+
brandTarget: '_blank',
24+
fontBase: '"Inter", sans-serif',
25+
colorPrimary: purple[600],
26+
colorSecondary: purple[600],
27+
}
28+
29+
const dark = create({
30+
base: 'dark',
31+
...shared,
32+
appBg: slate[900],
33+
appContentBg: slate[850],
34+
appBorderColor: 'rgba(255, 255, 255, 0.1)',
35+
barBg: slate[900],
36+
barTextColor: slate[300],
37+
barSelectedColor: purple[600],
38+
textColor: slate[200],
39+
textMutedColor: slate[300],
40+
textInverseColor: slate[900],
41+
})
42+
43+
const light = create({
44+
base: 'light',
45+
...shared,
46+
appBg: slate[50],
47+
appContentBg: slate[0],
48+
appBorderColor: slate[100],
49+
barBg: slate[0],
50+
barTextColor: slate[500],
51+
barSelectedColor: purple[600],
52+
textColor: slate[600],
53+
textMutedColor: slate[300],
54+
textInverseColor: slate[0],
55+
})
56+
57+
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
58+
59+
addons.setConfig({
60+
theme: prefersDark ? dark : light,
61+
})

frontend/.storybook/modes.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/** @type { Record<string, import('chromatic').Mode> } */
2+
export const allModes = {
3+
light: {
4+
theme: 'light',
5+
},
6+
dark: {
7+
theme: 'dark',
8+
},
9+
}

frontend/.storybook/preview.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import '../web/styles/styles.scss'
2+
import './docs-theme.scss'
3+
import { allModes } from './modes'
4+
5+
/** @type { import('storybook').Preview } */
6+
const preview = {
7+
globalTypes: {
8+
theme: {
9+
description: 'Dark mode toggle',
10+
toolbar: {
11+
title: 'Theme',
12+
icon: 'moon',
13+
items: [
14+
{ value: 'light', title: 'Light', icon: 'sun' },
15+
{ value: 'dark', title: 'Dark', icon: 'moon' },
16+
],
17+
dynamicTitle: true,
18+
},
19+
},
20+
},
21+
initialGlobals: {
22+
theme: window.matchMedia('(prefers-color-scheme: dark)').matches
23+
? 'dark'
24+
: 'light',
25+
},
26+
decorators: [
27+
(Story, context) => {
28+
const theme = context.globals.theme || 'light'
29+
const isDark = theme === 'dark'
30+
31+
document.documentElement.setAttribute(
32+
'data-bs-theme',
33+
isDark ? 'dark' : 'light',
34+
)
35+
document.body.classList.toggle('dark', isDark)
36+
37+
return Story()
38+
},
39+
],
40+
parameters: {
41+
controls: {
42+
matchers: {
43+
color: /(background|color)$/i,
44+
date: /Date$/i,
45+
},
46+
},
47+
backgrounds: { disable: true },
48+
chromatic: {
49+
modes: {
50+
light: allModes.light,
51+
dark: allModes.dark,
52+
},
53+
},
54+
},
55+
}
56+
57+
export default preview

0 commit comments

Comments
 (0)