🎨 Storybook docs (#6770)

* start to a storybook

* release notes

* commit the release notes

* add test:web back

* adding some scripts to the main package for storybook

* styled it up a bit

* remove unneeded icon

* lint

* remove needless comment

* moving clarifying comment

* fast failing

* feedback

* removing unneeded config
This commit is contained in:
Michael Clark
2026-01-28 18:38:02 +00:00
committed by GitHub
parent ebf8e985ad
commit f71249f510
11 changed files with 1208 additions and 112 deletions

3
.gitignore vendored
View File

@@ -76,3 +76,6 @@ build/
# Lage cache
.lage/
*storybook.log
storybook-static

View File

@@ -33,6 +33,7 @@
"start:service-plugins": "yarn workspace plugins-service watch",
"start:browser-backend": "yarn workspace loot-core watch:browser",
"start:browser-frontend": "yarn workspace @actual-app/web start:browser",
"start:storybook": "yarn workspace @actual-app/components start:storybook",
"build:browser-backend": "yarn workspace loot-core build:browser",
"build:server": "yarn build:browser && yarn workspace @actual-app/sync-server build",
"build:browser": "./bin/package-browser",
@@ -40,6 +41,7 @@
"build:plugins-service": "yarn workspace plugins-service build",
"build:api": "yarn workspace @actual-app/api build",
"build:docs": "yarn workspace docs build",
"build:storybook": "yarn workspace @actual-app/components build:storybook",
"deploy:docs": "yarn workspace docs deploy",
"generate:i18n": "yarn workspace @actual-app/web generate:i18n",
"generate:release-notes": "ts-node ./bin/release-note-generator.ts",

View File

@@ -0,0 +1,3 @@
{
"jsPlugins": ["eslint-plugin-storybook"]
}

View File

@@ -0,0 +1,43 @@
import { dirname } from 'path';
import { fileURLToPath } from 'url';
import type { StorybookConfig } from '@storybook/react-vite';
import viteTsconfigPaths from 'vite-tsconfig-paths';
/**
* This function is used to resolve the absolute path of a package.
* It is needed in projects that use Yarn PnP or are set up within a monorepo.
*/
function getAbsolutePath(value: string) {
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
}
const config: StorybookConfig = {
stories: [
'../src/Introduction.mdx',
'../src/**/*.mdx',
'../src/**/*.stories.@(js|jsx|mjs|ts|tsx)',
],
addons: [
getAbsolutePath('@chromatic-com/storybook'),
getAbsolutePath('@storybook/addon-a11y'),
getAbsolutePath('@storybook/addon-docs'),
],
framework: getAbsolutePath('@storybook/react-vite'),
core: {
disableTelemetry: true,
},
async viteFinal(config) {
const { mergeConfig } = await import('vite');
return mergeConfig(config, {
// Telling Vite how to resolve path aliases
plugins: [viteTsconfigPaths({ root: '../..' })],
esbuild: {
// Needed to handle JSX in .ts/.tsx files
jsx: 'automatic',
},
});
},
};
export default config;

View File

@@ -0,0 +1,74 @@
import { addons } from 'storybook/manager-api';
import { create } from 'storybook/theming/create';
// Colors from the Actual Budget light theme palette
const purple500 = '#8719e0';
const purple400 = '#9a3de8';
const navy900 = '#102a43';
const navy700 = '#334e68';
const navy600 = '#486581';
const navy150 = '#d9e2ec';
const navy100 = '#e8ecf0';
const white = '#ffffff';
// Create a custom Storybook theme matching Actual Budget's light theme
const theme = create({
base: 'light',
brandTitle: 'Actual Budget',
brandUrl: 'https://actualbudget.org',
brandImage: 'https://actualbudget.org/img/actual.webp',
brandTarget: '_blank',
// UI colors
colorPrimary: purple500,
colorSecondary: purple400,
// App chrome
appBg: navy100,
appContentBg: white,
appPreviewBg: white,
appBorderColor: navy150,
appBorderRadius: 4,
// Fonts
fontBase:
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
fontCode: 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace',
// Text colors
textColor: navy900,
textInverseColor: white,
textMutedColor: navy600,
// Toolbar
barTextColor: navy700,
barHoverColor: purple500,
barSelectedColor: purple500,
barBg: white,
// Form colors
buttonBg: white,
buttonBorder: navy900,
booleanBg: navy150,
booleanSelectedBg: purple500,
inputBg: white,
inputBorder: navy900,
inputTextColor: navy900,
inputBorderRadius: 4,
});
addons.setConfig({
theme,
enableShortcuts: true,
isFullscreen: false,
isToolshown: true,
sidebar: {
collapsedRoots: [],
filters: {
patterns: item => {
// Hide stories that are marked as internal
return !item.tags?.includes('internal');
},
},
},
});

View File

@@ -0,0 +1,88 @@
import { type ReactNode } from 'react';
import type { Preview } from '@storybook/react-vite';
// Not ideal to import from desktop-client, but we need a source of truth for theme variables
import * as darkTheme from '../../desktop-client/src/style/themes/dark';
import * as developmentTheme from '../../desktop-client/src/style/themes/development';
import * as lightTheme from '../../desktop-client/src/style/themes/light';
import * as midnightTheme from '../../desktop-client/src/style/themes/midnight';
const THEMES = {
light: lightTheme,
dark: darkTheme,
midnight: midnightTheme,
development: developmentTheme,
} as const;
type ThemeName = keyof typeof THEMES;
const ThemedStory = ({
themeName,
children,
}: {
themeName?: ThemeName;
children?: ReactNode;
}) => {
if (!themeName || !THEMES[themeName]) {
throw new Error(`No theme specified`);
}
const css = Object.entries(THEMES[themeName])
.map(([key, value]) => `--color-${key}: ${value};`)
.join('\n');
return (
<div>
<style>{`:root {\n${css}}`}</style>
{children}
</div>
);
};
const preview: Preview = {
decorators: [
(Story, { globals }) => {
const themeName = globals.theme;
return (
<ThemedStory themeName={themeName}>
<Story />
</ThemedStory>
);
},
],
globalTypes: {
theme: {
name: 'Theme',
description: 'Global theme for components',
defaultValue: 'light',
toolbar: {
icon: 'circlehollow',
items: [
{ value: 'light', title: 'Light' },
{ value: 'dark', title: 'Dark' },
{ value: 'midnight', title: 'Midnight' },
{ value: 'development', title: 'Development' },
],
},
},
},
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
a11y: {
// 'todo' - show a11y violations in the test UI only
// 'error' - fail CI on a11y violations
// 'off' - skip a11y checks entirely
test: 'todo',
},
},
};
export default preview;

View File

@@ -37,7 +37,9 @@
"scripts": {
"generate:icons": "rm src/icons/*/*.tsx; cd src/icons && svgr --template template.ts --index-template index-template.ts --typescript --expand-props start -d . .",
"test": "npm-run-all -cp 'test:*'",
"test:web": "ENV=web vitest --run -c vitest.web.config.ts"
"test:web": "ENV=web vitest --run -c vitest.web.config.ts",
"start:storybook": "storybook dev -p 6006",
"build:storybook": "storybook build"
},
"dependencies": {
"@emotion/css": "^11.13.5",
@@ -45,10 +47,16 @@
"usehooks-ts": "^3.1.1"
},
"devDependencies": {
"@chromatic-com/storybook": "^5.0.0",
"@storybook/addon-a11y": "^10.2.0",
"@storybook/addon-docs": "^10.2.0",
"@storybook/react-vite": "^10.2.0",
"@svgr/cli": "^8.1.0",
"@types/react": "^19.2.5",
"eslint-plugin-storybook": "^10.2.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"storybook": "^10.2.0",
"vitest": "^4.0.16"
},
"peerDependencies": {

View File

@@ -0,0 +1,100 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { fn } from 'storybook/test';
import { Button } from './Button';
const meta = {
title: 'Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
onClick: { action: 'clicked' },
},
args: { onClick: fn() },
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary: Story = {
args: {
variant: 'primary',
bounce: false,
children: 'Button Text',
},
parameters: {
docs: {
description: {
story: `
Primary button variant uses the following theme CSS variables:
- \`--color-buttonPrimaryText\`
- \`--color-buttonPrimaryTextHover\`
- \`--color-buttonPrimaryBackground\`
- \`--color-buttonPrimaryBackgroundHover\`
- \`--color-buttonPrimaryBorder\`
- \`--color-buttonPrimaryShadow\`
- \`--color-buttonPrimaryDisabledText\`
- \`--color-buttonPrimaryDisabledBackground\`
- \`--color-buttonPrimaryDisabledBorder\`
`,
},
},
},
};
export const Normal: Story = {
args: {
variant: 'normal',
bounce: false,
children: 'Button Text',
},
parameters: {
docs: {
description: {
story: `
Normal button variant uses the following theme CSS variables:
- \`--color-buttonNormalText\`
- \`--color-buttonNormalTextHover\`
- \`--color-buttonNormalBackground\`
- \`--color-buttonNormalBackgroundHover\`
- \`--color-buttonNormalBorder\`
- \`--color-buttonNormalShadow\`
- \`--color-buttonNormalSelectedText\`
- \`--color-buttonNormalSelectedBackground\`
- \`--color-buttonNormalDisabledText\`
- \`--color-buttonNormalDisabledBackground\`
- \`--color-buttonNormalDisabledBorder\`
`,
},
},
},
};
export const Bare: Story = {
args: {
variant: 'bare',
bounce: false,
children: 'Button Text',
},
parameters: {
docs: {
description: {
story: `
Bare button variant uses the following theme CSS variables:
- \`--color-buttonBareText\`
- \`--color-buttonBareTextHover\`
- \`--color-buttonBareBackground\`
- \`--color-buttonBareBackgroundHover\`
- \`--color-buttonBareBackgroundActive\`
- \`--color-buttonBareDisabledText\`
- \`--color-buttonBareDisabledBackground\`
`,
},
},
},
};

View File

@@ -0,0 +1,27 @@
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Introduction" />
# Actual Budget Component Library
Welcome to the **Actual Budget Component Library**. Explore our UI components, see how they look across different themes, and learn how to use them in your code.
### What you can do here
- ✨ **Browse components** in the sidebar
- 🎨 **Switch themes** using the toolbar above
- 📚 **Read documentation** and see code examples
- 🔍 **Test variations** and component states
- ♿ **Check accessibility** compliance
### Getting Started
Select a component from the sidebar to explore its documentation, variants, and interactive controls.
---
### Useful Links
- [Actual Budget Website](https://actualbudget.org)
- [Documentation](https://actualbudget.org/docs)
- [GitHub Repository](https://github.com/actualbudget/actual)

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Add Storybook Documentation into the component library

964
yarn.lock

File diff suppressed because it is too large Load Diff