Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3e98136
feat(tokens): add primitive colour palette and semantic token layer
talissoncosta Mar 10, 2026
8cc911c
feat(storybook): add Storybook 8 with colour palette and token stories
talissoncosta Mar 10, 2026
be8c991
ci: add Chromatic visual regression workflow for Storybook
talissoncosta Mar 10, 2026
a68af94
docs(storybook): add Introduction welcome page
talissoncosta Mar 10, 2026
e9cb9b6
chore(storybook): upgrade from 8.6 to 10.2
talissoncosta Mar 10, 2026
3e587b9
feat(storybook): add Flagsmith branding and dark theme to manager
talissoncosta Mar 10, 2026
6b98f2d
feat(tokens): add interactive surface and feedback hover/active tokens
talissoncosta Mar 10, 2026
d42d171
refactor(storybook): make token and palette stories fully dynamic
talissoncosta Mar 10, 2026
1f9517f
fix(storybook): fix code tag visibility and palette swatch layout
talissoncosta Mar 10, 2026
2ee13ec
fix(ci): add --legacy-peer-deps to frontend PR workflow
talissoncosta Mar 10, 2026
09b3b44
revert: keep Storybook on v8 until TypeScript is upgraded
talissoncosta Mar 10, 2026
464bd2e
refactor(storybook): extract inline styles into shared docs components
talissoncosta Mar 10, 2026
32ba93a
fix(ci): clean up Chromatic workflow and add useEffect comment
talissoncosta Mar 10, 2026
ecc9084
deps(storybook): upgrade from v8 to v10.3
talissoncosta Mar 24, 2026
226e919
refactor(storybook): migrate config and imports to v10 conventions
talissoncosta Mar 24, 2026
3351ab2
refactor(storybook): move stories from stories/ to documentation/
talissoncosta Mar 24, 2026
f454a60
feat(storybook): add shared Swatch component for colour documentation
talissoncosta Mar 26, 2026
1daa7ba
fix(storybook): fix CategoricalPalette CJS blocker and add colour names
talissoncosta Mar 26, 2026
43df8cf
feat(storybook): add 'How to create a semantic token' guide
talissoncosta Mar 26, 2026
7b32e00
feat(storybook): add Button component stories
talissoncosta Mar 26, 2026
1930977
fix(storybook): fix invisible text in light mode across doc pages
talissoncosta Mar 26, 2026
4182a3c
feat(banner): add Banner component with variant-driven icons and colours
talissoncosta Mar 26, 2026
216592a
refactor(banner): move to folder structure with dedicated scss
talissoncosta Mar 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/frontend-chromatic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Frontend Chromatic

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- frontend/**
- .github/workflows/frontend-chromatic.yml

permissions:
contents: read

jobs:
chromatic:
name: Chromatic
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false

defaults:
run:
working-directory: frontend

steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version-file: frontend/.nvmrc
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Publish to Chromatic
uses: chromaui/action@v11
with:
workingDir: frontend
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
exitZeroOnChanges: true
exitOnceUploaded: true
onlyChanged: true
1 change: 1 addition & 0 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'plugin:@dword-design/import-alias/recommended',
'plugin:storybook/recommended',
],
'globals': {
'$': true,
Expand Down
3 changes: 3 additions & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ common/project.js
# Playwright
e2e/playwright-report/
e2e/test-results/

*storybook.log
storybook-static
62 changes: 62 additions & 0 deletions frontend/.storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const path = require('path')

/** @type { import('storybook').StorybookConfig } */
const config = {
stories: [
'../documentation/**/*.mdx',
'../documentation/**/*.stories.@(js|jsx|ts|tsx)',
],
staticDirs: ['../web'],
addons: [
'@storybook/addon-webpack5-compiler-swc',
'@storybook/addon-docs',
'@storybook/addon-a11y',
],
framework: {
name: '@storybook/react-webpack5',
options: {},
},
swc: () => ({
jsc: {
transform: {
react: {
runtime: 'automatic',
},
},
parser: {
syntax: 'typescript',
tsx: true,
},
},
}),
webpackFinal: async (config) => {
config.resolve = config.resolve || {}
config.resolve.alias = {
...config.resolve.alias,
common: path.resolve(__dirname, '../common'),
components: path.resolve(__dirname, '../web/components'),
project: path.resolve(__dirname, '../web/project'),
}

config.module = config.module || {}
config.module.rules = config.module.rules || []
config.module.rules.push({
test: /\.scss$/,
use: [
'style-loader',
{ loader: 'css-loader', options: { importLoaders: 1 } },
{
loader: 'sass-loader',
options: {
sassOptions: {
silenceDeprecations: ['slash-div'],
},
},
},
],
})

return config
},
}
module.exports = config
6 changes: 6 additions & 0 deletions frontend/.storybook/manager-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<style>
.sidebar-header img {
max-width: 28px;
max-height: 28px;
}
</style>
44 changes: 44 additions & 0 deletions frontend/.storybook/manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { addons } from 'storybook/manager-api'
import { create } from 'storybook/theming'

// Primitive palette — mirrors _primitives.scss
// Storybook manager runs outside the app, so CSS vars aren't available.
const slate = {
200: '#e0e3e9',
300: '#9da4ae',
850: '#161d30',
900: '#15192b',
}
const purple = { 600: '#6837fc' }

addons.setConfig({
theme: create({
base: 'dark',
brandTitle: 'Flagsmith',
brandUrl: 'https://flagsmith.com',
brandImage: '/static/images/nav-logo.png',
brandTarget: '_blank',

// Sidebar
appBg: slate[900],
appContentBg: slate[850],
appBorderColor: 'rgba(255, 255, 255, 0.1)',

// Typography
fontBase: '"Inter", sans-serif',

// Toolbar
barBg: slate[900],
barTextColor: slate[300],
barSelectedColor: purple[600],

// Colours
colorPrimary: purple[600],
colorSecondary: purple[600],

// Text
textColor: slate[200],
textMutedColor: slate[300],
textInverseColor: slate[900],
}),
})
47 changes: 47 additions & 0 deletions frontend/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import '../web/styles/styles.scss'

/** @type { import('storybook').Preview } */
const preview = {
globalTypes: {
theme: {
description: 'Dark mode toggle',
toolbar: {
title: 'Theme',
icon: 'moon',
items: [
{ value: 'light', title: 'Light', icon: 'sun' },
{ value: 'dark', title: 'Dark', icon: 'moon' },
],
dynamicTitle: true,
},
},
},
initialGlobals: {
theme: 'light',
},
decorators: [
(Story, context) => {
const theme = context.globals.theme || 'light'
const isDark = theme === 'dark'

document.documentElement.setAttribute(
'data-bs-theme',
isDark ? 'dark' : 'light',
)
document.body.classList.toggle('dark', isDark)

return Story()
},
],
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
backgrounds: { disable: true },
},
}

export default preview
152 changes: 152 additions & 0 deletions frontend/documentation/CategoricalPalette.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React from 'react'
import type { Meta, StoryObj } from 'storybook'

import './docs.scss'
import DocPage from './components/DocPage'
import Swatch from './components/Swatch'

// ---------------------------------------------------------------------------
// Colour data — inlined to avoid importing Constants (which pulls in the
// full app dependency tree and breaks Storybook's ESM context).
// Source of truth: common/constants.ts
// ---------------------------------------------------------------------------

const TAG_COLOURS = [
{ hex: '#3d4db6', name: 'Indigo' },
{ hex: '#ea5a45', name: 'Coral' },
{ hex: '#c6b215', name: 'Gold' },
{ hex: '#60bd4e', name: 'Green' },
{ hex: '#fe5505', name: 'Orange' },
{ hex: '#1492f4', name: 'Blue' },
{ hex: '#14c0f4', name: 'Cyan' },
{ hex: '#c277e0', name: 'Lavender' },
{ hex: '#039587', name: 'Teal' },
{ hex: '#344562', name: 'Navy' },
{ hex: '#ffa500', name: 'Amber' },
{ hex: '#3cb371', name: 'Mint' },
{ hex: '#d3d3d3', name: 'Silver' },
{ hex: '#5D6D7E', name: 'Slate' },
{ hex: '#641E16', name: 'Maroon' },
{ hex: '#5B2C6F', name: 'Plum' },
{ hex: '#D35400', name: 'Burnt Orange' },
{ hex: '#F08080', name: 'Salmon' },
{ hex: '#AAC200', name: 'Lime' },
{ hex: '#DE3163', name: 'Cerise' },
]

const DEFAULT_TAG_COLOUR = '#dedede'

const PROJECT_COLOURS = [
'#906AF6',
'#FAE392',
'#42D0EB',
'#56CCAD',
'#FFBE71',
'#F57C78',
]

const FEATURE_HEALTH = {
healthyColor: '#60bd4e',
unhealthyColor: '#D35400',
}

const meta: Meta = {
parameters: { layout: 'padded' },
title: 'Design System/Categorical Palette',
}
export default meta

// ---------------------------------------------------------------------------
// Stories
// ---------------------------------------------------------------------------

export const TagColours: StoryObj = {
name: 'Tag colours',
render: () => (
<DocPage
title='Tag colours'
description={
<>
20 decorative colours users pick from when creating tags. Will be
defined in <code>_categorical.scss</code> as CSS custom properties (
<code>--color-tag-1</code> through <code>--color-tag-20</code>).
Currently in <code>constants.ts</code> pending migration. These are
NOT semantic tokens &mdash; they are categorical identifiers that need
to be visually distinct from each other.
</>
}
>
<div className='cat-grid'>
{TAG_COLOURS.map(({ hex, name }) => (
<Swatch key={hex} colour={hex} label={`${name}\n${hex}`} />
))}
</div>
<p className='cat-note'>
Default tag colour: <code>{DEFAULT_TAG_COLOUR}</code>
</p>
</DocPage>
),
}

export const ProjectColours: StoryObj = {
name: 'Project colours',
render: () => (
<DocPage
title='Project colours'
description={
<>
6 colours assigned by index for project avatar badges. Will be defined
in <code>_categorical.scss</code> as <code>--color-project-1</code>{' '}
through <code>--color-project-6</code>. Currently in{' '}
<code>constants.ts</code> pending migration. Decorative &mdash; not
tied to any UI role or theme.
</>
}
>
<div className='cat-grid'>
{PROJECT_COLOURS.map((colour: string, i: number) => (
<Swatch key={colour} colour={colour} label={`[${i}] ${colour}`} />
))}
</div>
</DocPage>
),
}

export const FeatureHealthColours: StoryObj = {
name: 'Feature health colours',
render: () => (
<DocPage
title='Feature health colours'
description={
<>
Status colours for feature health indicators. Currently hardcoded in{' '}
<code>common/constants.ts</code> as{' '}
<code>Constants.featureHealth</code>. These should migrate to semantic
feedback tokens: <code>var(--color-success-default)</code> and{' '}
<code>var(--color-warning-default)</code>.
</>
}
>
<div className='cat-health-row'>
<div className='cat-health-item'>
<Swatch colour={FEATURE_HEALTH.healthyColor} size={32} />
<div>
<strong>Healthy</strong>
<div className='cat-health-item__migration'>
Should use <code>var(--color-success-default)</code>
</div>
</div>
</div>
<div className='cat-health-item'>
<Swatch colour={FEATURE_HEALTH.unhealthyColor} size={32} />
<div>
<strong>Unhealthy</strong>
<div className='cat-health-item__migration'>
Should use <code>var(--color-warning-default)</code>
</div>
</div>
</div>
</div>
</DocPage>
),
}
Loading
Loading