Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,5 @@ vite.config.ts.timestamp*

# Build output
site/
storybook-static/

16 changes: 16 additions & 0 deletions .storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { StorybookConfig } from '@storybook/react-vite';

const config: StorybookConfig = {
stories: ['./stories/**/*.stories.@(ts|tsx)'],
addons: ['@storybook/addon-a11y', '@storybook/addon-themes'],
framework: {
name: '@storybook/react-vite',
options: {},
},
viteFinal: async config => {
// Ensure Tailwind CSS is processed
return config;
},
};

export default config;
10 changes: 10 additions & 0 deletions .storybook/manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { addons } from 'storybook/manager-api';
import { themes } from 'storybook/theming';

addons.setConfig({
theme: {
...themes.dark,
brandTitle: 'VariScout Components',
brandUrl: '/',
},
});
50 changes: 50 additions & 0 deletions .storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { Preview } from 'storybook/preview-api';
import '../packages/ui/src/styles/theme.css';
import '../packages/ui/src/styles/components.css';

const preview: Preview = {
globalTypes: {
theme: {
name: 'Theme',
description: 'Color theme',
defaultValue: 'dark',
toolbar: {
icon: 'circlehollow',
items: [
{ value: 'dark', icon: 'moon', title: 'Dark' },
{ value: 'light', icon: 'sun', title: 'Light' },
],
dynamicTitle: true,
},
},
chartMode: {
name: 'Chart Mode',
description: 'Chart color mode',
defaultValue: 'technical',
toolbar: {
icon: 'graphline',
items: [
{ value: 'technical', title: 'Technical' },
{ value: 'executive', title: 'Executive' },
],
dynamicTitle: true,
},
},
},
decorators: [
(Story, context) => {
document.documentElement.setAttribute('data-theme', context.globals.theme || 'dark');
document.documentElement.setAttribute(
'data-chart-mode',
context.globals.chartMode || 'technical'
);
return <Story />;
},
],
parameters: {
controls: { matchers: { color: /(background|color)$/i, date: /Date$/i } },
layout: 'centered',
},
};

export default preview;
72 changes: 72 additions & 0 deletions .storybook/stories/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# VariScout Storybook Story Conventions

## File Naming

- `ComponentName.stories.tsx`
- One story file per component

## Title Convention

Stories are organized by category:

| Category | Title prefix | Example |
| ---------------------- | ---------------- | -------------------------------- |
| Chart primitives | `Charts/` | `Charts/IChart` |
| UI Input components | `UI/Input/` | `UI/Input/ColumnMapping` |
| UI Data components | `UI/Data/` | `UI/Data/DataQualityBanner` |
| UI Analysis components | `UI/Analysis/` | `UI/Analysis/AnovaResults` |
| UI Chart wrappers | `UI/Charts/` | `UI/Charts/ChartCard` |
| UI Navigation | `UI/Navigation/` | `UI/Navigation/FilterBreadcrumb` |
| UI Findings | `UI/Findings/` | `UI/Findings/FindingCard` |
| UI Simulation | `UI/Simulation/` | `UI/Simulation/WhatIfSimulator` |
| UI Dashboard | `UI/Dashboard/` | `UI/Dashboard/DashboardGrid` |
| UI Utilities | `UI/Utilities/` | `UI/Utilities/HelpTooltip` |

## Story Structure

```tsx
import type { Meta, StoryObj } from '@storybook/react';
import { ComponentName } from '../path/to/component';

const meta = {
title: 'Category/ComponentName',
component: ComponentName,
tags: ['autodocs'],
} satisfies Meta<typeof ComponentName>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
/* default props */
},
};
```

## Auto-Generated Docs

Use `tags: ['autodocs']` on every story to enable automatic documentation pages.

## Sample Data

- Import sample datasets from `@variscout/data` (e.g., `SAMPLES`, `getSample`)
- Use `calculateStats` from `@variscout/core` for computed statistical data
- For simple demos, use inline mock data arrays

## Theme Integration

The Storybook toolbar provides:

- **Theme** toggle: Sets `data-theme` attribute (`dark` / `light`)
- **Chart Mode** toggle: Sets `data-chart-mode` attribute (`technical` / `executive`)

Components that read these attributes will respond automatically.

## Color Schemes

Many UI components accept a `colorScheme` prop. Use the exported `defaultScheme` for each component:

```tsx
import { AnovaResults, anovaDefaultColorScheme } from '@variscout/ui';
```
93 changes: 93 additions & 0 deletions .storybook/stories/charts/Boxplot.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { Meta, StoryObj } from '@storybook/react';
import { BoxplotBase } from '../../../packages/charts/src/index';
import type { BoxplotGroupData, SpecLimits } from '../../../packages/charts/src/index';

function makeGroup(key: string, center: number, spread: number): BoxplotGroupData {
const values: number[] = [];
for (let i = 0; i < 30; i++) {
values.push(center + (Math.random() - 0.5) * spread * 2);
}
values.sort((a, b) => a - b);
const q1 = values[7];
const median = values[15];
const q3 = values[22];
const iqr = q3 - q1;
return {
key,
values,
min: Math.max(values[0], q1 - 1.5 * iqr),
max: Math.min(values[29], q3 + 1.5 * iqr),
q1,
median,
mean: values.reduce((a, b) => a + b, 0) / values.length,
q3,
outliers: values.filter(v => v < q1 - 1.5 * iqr || v > q3 + 1.5 * iqr),
stdDev: spread * 0.6,
};
}

const mockData: BoxplotGroupData[] = [
makeGroup('Line A', 10.0, 0.5),
makeGroup('Line B', 10.3, 0.8),
makeGroup('Line C', 9.7, 0.4),
makeGroup('Line D', 10.1, 1.0),
];

const mockSpecs: SpecLimits = { usl: 11.0, lsl: 9.0, target: 10.0 };

const meta = {
title: 'Charts/Boxplot',
component: BoxplotBase,
tags: ['autodocs'],
parameters: { layout: 'padded' },
} satisfies Meta<typeof BoxplotBase>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
data: mockData,
specs: mockSpecs,
parentWidth: 800,
parentHeight: 500,
yAxisLabel: 'Weight (g)',
xAxisLabel: 'Production Line',
},
};

export const WithViolin: Story = {
args: {
...Default.args,
showViolin: true,
},
};

export const WithContributions: Story = {
args: {
...Default.args,
categoryContributions: new Map([
['Line A', 15],
['Line B', 45],
['Line C', 10],
['Line D', 30],
]),
showContributionLabels: true,
showContributionBars: true,
},
};

export const WithHighlights: Story = {
args: {
...Default.args,
highlightedCategories: { 'Line B': 'red', 'Line C': 'green' },
},
};

export const Compact: Story = {
args: {
...Default.args,
parentWidth: 400,
parentHeight: 300,
},
};
63 changes: 63 additions & 0 deletions .storybook/stories/charts/BoxplotStatsTable.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { Meta, StoryObj } from '@storybook/react';
import BoxplotStatsTable from '../../../packages/charts/src/BoxplotStatsTable';
import type { BoxplotGroupData } from '../../../packages/charts/src/index';

function makeGroup(key: string, mean: number, stdDev: number): BoxplotGroupData {
const values = Array.from({ length: 25 }, () => mean + (Math.random() - 0.5) * stdDev * 4);
values.sort((a, b) => a - b);
return {
key,
values,
min: values[0],
max: values[values.length - 1],
q1: values[6],
median: values[12],
mean,
q3: values[18],
outliers: [],
stdDev,
};
}

const mockData: BoxplotGroupData[] = [
makeGroup('Monday', 10.1, 0.3),
makeGroup('Tuesday', 10.3, 0.5),
makeGroup('Wednesday', 9.8, 0.2),
makeGroup('Thursday', 10.0, 0.9),
makeGroup('Friday', 10.2, 0.4),
];

const meta = {
title: 'Charts/BoxplotStatsTable',
component: BoxplotStatsTable,
tags: ['autodocs'],
} satisfies Meta<typeof BoxplotStatsTable>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
data: mockData,
},
};

export const WithContributions: Story = {
args: {
data: mockData,
categoryContributions: new Map<string | number, number>([
['Monday', 12],
['Tuesday', 38],
['Wednesday', 8],
['Thursday', 32],
['Friday', 10],
]),
},
};

export const Compact: Story = {
args: {
data: mockData,
compact: true,
},
};
51 changes: 51 additions & 0 deletions .storybook/stories/charts/CapabilityHistogram.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { Meta, StoryObj } from '@storybook/react';
import { CapabilityHistogramBase } from '../../../packages/charts/src/index';

const mockValues = Array.from({ length: 100 }, () => 10.0 + (Math.random() - 0.5) * 2);

const meta = {
title: 'Charts/CapabilityHistogram',
component: CapabilityHistogramBase,
tags: ['autodocs'],
parameters: { layout: 'padded' },
} satisfies Meta<typeof CapabilityHistogramBase>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Default: Story = {
args: {
data: mockValues,
specs: { usl: 11.5, lsl: 8.5, target: 10.0 },
mean: 10.0,
parentWidth: 600,
parentHeight: 400,
},
};

export const OffCenter: Story = {
args: {
data: mockValues.map(v => v + 0.8),
specs: { usl: 11.5, lsl: 8.5, target: 10.0 },
mean: 10.8,
parentWidth: 600,
parentHeight: 400,
},
};

export const NoSpecs: Story = {
args: {
data: mockValues,
specs: {},
mean: 10.0,
parentWidth: 600,
parentHeight: 400,
},
};

export const WithBranding: Story = {
args: {
...Default.args,
showBranding: true,
},
};
Loading