chore: catch up Storybook preview, coverage, and story hygiene

Stories were throwing without AuthModal/CreateFlow providers, autodocs was a no-op, and several DS/create-flow screens had no stories.
This commit is contained in:
adilallo
2026-08-17 19:25:46 -06:00
parent 1983a67ffd
commit db1581337c
44 changed files with 1215 additions and 932 deletions
+22 -16
View File
@@ -28,16 +28,17 @@ Do **not** colocate `*.stories.*` next to components. The Storybook config
# File naming # File naming
- `<ComponentName>.stories.js` — matches 69/70 existing files. - `<ComponentName>.stories.js` — default; keep `.js` unless the story
- Use `.tsx` only when the story genuinely needs types (rare; prefer JS to genuinely needs types.
match the codebase convention). - Use `.tsx` only when the story uses `Meta` / `StoryObj` (rare).
- Variants get a suffix: `Button.visual.stories.js`, - Variants get a suffix: `Button.visual.stories.js`,
`Footer.responsive.stories.js`. `Footer.responsive.stories.js`.
# Default export shape (CSF2) # Default export shape (CSF3)
```javascript ```javascript
import MyComponent from "../../app/components/<area>/MyComponent"; import MyComponent from "../../app/components/<area>/MyComponent";
import { CHIP_PALETTE_OPTIONS } from "../../lib/propNormalization";
export default { export default {
title: "Components/<SubFolder>/MyComponent", title: "Components/<SubFolder>/MyComponent",
@@ -51,31 +52,32 @@ export default {
}, },
}, },
argTypes: { argTypes: {
variant: { palette: {
control: { type: "select" }, control: { type: "select" },
options: ["filled", "outline"], options: [...CHIP_PALETTE_OPTIONS],
description: "The variant (Figma prop)", description: "The palette (Figma prop)",
}, },
onClick: { action: "clicked" }, onClick: { action: "clicked" },
}, },
}; };
export const Default = { args: { variant: "filled" } }; export const Default = { args: { palette: "default" } };
``` ```
## Title hierarchy ## Title hierarchy
- Design-system components → `Components/<SubFolder>/<Name>` (e.g. - Design-system components → `Components/<SubFolder>/<Name>` (e.g.
`Components/Controls/Checkbox`). `Components/Controls/Checkbox`).
- Pages → `Pages/<PageName>` (folder: `stories/pages/`). - Create-flow screens → `Pages/Create Flow/<Step>` (folder: `stories/pages/`).
- Create flow shared pieces → `Create Flow/<Name>`. - Create flow shared pieces → `Create Flow/<Name>`.
## `argTypes` ## `argTypes`
For every Figma enum prop (`variant`, `size`, `state`, `mode`, `palette`, For every Figma enum prop (`variant`, `size`, `state`, `mode`, `palette`,
…) expose a `select` control listing the **lowercase** option set, sourced …) expose a `select` control listing the option set, sourced from the matching
from the matching `*_OPTIONS` const in `lib/propNormalization.ts`. See `*_OPTIONS` const in `lib/propNormalization.ts`. See
`.cursor/rules/component-props.mdc`. `.cursor/rules/component-props.mdc`. Spread with `[...FOO_OPTIONS]` so the
control receives a mutable array.
# Rely on the global preview — don't re-wrap # Rely on the global preview — don't re-wrap
@@ -83,8 +85,12 @@ from the matching `*_OPTIONS` const in `lib/propNormalization.ts`. See
- `MessagesProvider` with `messages/en` → access copy via `useMessages()` - `MessagesProvider` with `messages/en` → access copy via `useMessages()`
inside stories exactly like app code. Never hard-code user-facing strings. inside stories exactly like app code. Never hard-code user-facing strings.
- `app/globals.css` + `.font-inter` wrapper → design tokens and fonts are - `AuthModalProvider` and `CreateFlowProvider` (same stack as
already present. `tests/utils/test-utils.tsx`) so `Top` and create-flow screens can mount.
- `app/globals.css` + Inter / Bricolage Grotesque / Space Grotesk CSS
variables + `.font-inter` wrapper.
- Dark canvas default and Figma breakpoints (`sm` 430, `md` 640, `lg` 1024,
`xl` 1440).
Do **not** add your own `MessagesProvider`, font wrapper, or token setup in a Do **not** add your own `MessagesProvider`, font wrapper, or token setup in a
story. If you need a new global, update `preview.js`. story. If you need a new global, update `preview.js`.
@@ -92,8 +98,8 @@ story. If you need a new global, update `preview.js`.
# Interaction tests (`play`) # Interaction tests (`play`)
Use `storybook/test` for interaction assertions — not `@testing-library/*` Use `storybook/test` for interaction assertions — not `@testing-library/*`
directly. This matches `Checkbox.stories.js` and stays compatible with the directly. This matches `Checkbox.stories.js`. Storybook is documentation;
Vitest portable-stories runner in `.storybook/vitest.setup.js`. Vitest component tests remain the source of truth.
```javascript ```javascript
import { within, userEvent, expect } from "storybook/test"; import { within, userEvent, expect } from "storybook/test";
+7 -1
View File
@@ -1,7 +1,13 @@
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Bricolage+Grotesque:wght@400;500;700;800&family=Space+Grotesk:wght@400;500;700&display=swap");
:root { :root {
--font-inter: --font-inter:
"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
"Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji"; "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji";
--font-bricolage-grotesque:
"Bricolage Grotesque", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
Roboto, "Helvetica Neue", Arial;
--font-space-grotesk:
"Space Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
Roboto, "Helvetica Neue", Arial;
} }
+1 -23
View File
@@ -4,32 +4,10 @@ module.exports = {
"../stories/**/*.mdx", "../stories/**/*.mdx",
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)", "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
], ],
addons: [ addons: ["@storybook/addon-docs", "@storybook/addon-a11y"],
// Removed @storybook/addon-essentials due to version mismatch with Storybook 10.x
// Using individual addons instead. Interaction helpers import from storybook/test
// (bundled with storybook@10); @storybook/addon-interactions was merged into SB 8 core.
"@storybook/addon-a11y",
],
framework: { framework: {
name: "@storybook/nextjs", name: "@storybook/nextjs",
options: {}, options: {},
}, },
staticDirs: ["../public"], staticDirs: ["../public"],
// Webpack configuration to resolve Next.js modules for Next.js 16 compatibility
async webpackFinal(config) {
// Ensure Next.js modules are resolved correctly
config.resolve = config.resolve || {};
config.resolve.alias = {
...(config.resolve.alias || {}),
};
// Ensure node_modules are resolved
config.resolve.modules = [
...(config.resolve.modules || []),
"node_modules",
];
return config;
},
}; };
+55 -3
View File
@@ -1,10 +1,43 @@
import "../app/globals.css"; import "../app/globals.css";
import "./fonts.css"; import "./fonts.css";
import { MINIMAL_VIEWPORTS } from "storybook/viewport";
import { AuthModalProvider } from "../app/contexts/AuthModalContext";
import { MessagesProvider } from "../app/contexts/MessagesContext"; import { MessagesProvider } from "../app/contexts/MessagesContext";
import { CreateFlowProvider } from "../app/(app)/create/context/CreateFlowContext";
import messages from "../messages/en/index"; import messages from "../messages/en/index";
/** Figma / Tailwind breakpoints from `app/tailwind.css`. */
const dsViewports = {
sm: {
name: "sm 430",
styles: { width: "430px", height: "932px" },
},
md: {
name: "md 640",
styles: { width: "640px", height: "1024px" },
},
lg: {
name: "lg 1024",
styles: { width: "1024px", height: "768px" },
},
xl: {
name: "xl 1440",
styles: { width: "1440px", height: "900px" },
},
// Aliases used by existing page stories
mobile1: {
name: "sm 430",
styles: { width: "430px", height: "932px" },
},
desktop: {
name: "xl 1440",
styles: { width: "1440px", height: "900px" },
},
};
/** @type { import('@storybook/react').Preview } */ /** @type { import('@storybook/react').Preview } */
const preview = { const preview = {
tags: ["autodocs"],
parameters: { parameters: {
controls: { controls: {
matchers: { matchers: {
@@ -12,13 +45,32 @@ const preview = {
date: /Date$/i, date: /Date$/i,
}, },
}, },
backgrounds: {
options: {
dark: { name: "Dark", value: "#000000" },
light: { name: "Light", value: "#ffffff" },
},
},
viewport: {
options: {
...MINIMAL_VIEWPORTS,
...dsViewports,
},
},
},
initialGlobals: {
backgrounds: { value: "dark" },
}, },
decorators: [ decorators: [
(Story) => ( (Story) => (
<MessagesProvider messages={messages}> <MessagesProvider messages={messages}>
<div className="font-inter"> <AuthModalProvider>
<Story /> <CreateFlowProvider>
</div> <div className="font-inter">
<Story />
</div>
</CreateFlowProvider>
</AuthModalProvider>
</MessagesProvider> </MessagesProvider>
), ),
], ],
-7
View File
@@ -1,7 +0,0 @@
import * as a11yAddonAnnotations from "@storybook/addon-a11y/preview";
import { setProjectAnnotations } from "@storybook/nextjs-vite";
import * as projectAnnotations from "./preview";
// This is an important step to apply the right configuration when testing your stories.
// More info at: https://storybook.js.org/docs/api/portable-stories/portable-stories-vitest#setprojectannotations
setProjectAnnotations([a11yAddonAnnotations, projectAnnotations]);
-1
View File
@@ -25,7 +25,6 @@
"@types/mdx", "@types/mdx",
"eslint-config-next", "eslint-config-next",
"typescript-eslint", "typescript-eslint",
"@storybook/nextjs-vite",
"@eslint/js", "@eslint/js",
"@next/eslint-plugin-next", "@next/eslint-plugin-next",
"eslint-plugin-react", "eslint-plugin-react",
+82 -1
View File
@@ -30,6 +30,7 @@
"@lhci/cli": "^0.15.1", "@lhci/cli": "^0.15.1",
"@playwright/test": "^1.55.0", "@playwright/test": "^1.55.0",
"@storybook/addon-a11y": "^10.2.0", "@storybook/addon-a11y": "^10.2.0",
"@storybook/addon-docs": "^10.2.0",
"@storybook/nextjs": "^10.2.0", "@storybook/nextjs": "^10.2.0",
"@storybook/react": "^10.2.0", "@storybook/react": "^10.2.0",
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
@@ -5911,6 +5912,35 @@
"storybook": "^10.4.1" "storybook": "^10.4.1"
} }
}, },
"node_modules/@storybook/addon-docs": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.4.1.tgz",
"integrity": "sha512-IYqUdjoZe4VO2LFZlKL/gwy7DsQSWCq6hX+zc1MBmZo04yycDASk1tte57n9pdlW3ajw9yYMF/+lVBi+xQjyvw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@mdx-js/react": "^3.0.0",
"@storybook/csf-plugin": "10.4.1",
"@storybook/icons": "^2.0.2",
"@storybook/react-dom-shim": "10.4.1",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"ts-dedent": "^2.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
"storybook": "^10.4.1"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@storybook/builder-webpack5": { "node_modules/@storybook/builder-webpack5": {
"version": "10.4.1", "version": "10.4.1",
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.4.1.tgz", "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.4.1.tgz",
@@ -6017,6 +6047,41 @@
"storybook": "^10.4.1" "storybook": "^10.4.1"
} }
}, },
"node_modules/@storybook/csf-plugin": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.4.1.tgz",
"integrity": "sha512-WdPepGBxDGOUDjYd8KxMtcf+us/2PAcnBczl77XtrnxxHNs0jWesxKkiJ9yiuGrge4BPhDeAj6rxjbBoaHxLBA==",
"dev": true,
"license": "MIT",
"dependencies": {
"unplugin": "^2.3.5"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/storybook"
},
"peerDependencies": {
"esbuild": "*",
"rollup": "*",
"storybook": "^10.4.1",
"vite": "*",
"webpack": "*"
},
"peerDependenciesMeta": {
"esbuild": {
"optional": true
},
"rollup": {
"optional": true
},
"vite": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/@storybook/global": { "node_modules/@storybook/global": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz", "resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz",
@@ -6192,7 +6257,7 @@
"webpack": ">= 4" "webpack": ">= 4"
} }
}, },
"node_modules/@storybook/react/node_modules/@storybook/react-dom-shim": { "node_modules/@storybook/react-dom-shim": {
"version": "10.4.1", "version": "10.4.1",
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.1.tgz", "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.1.tgz",
"integrity": "sha512-6QFqfDNH4DMrt7yHKRfpqRopsVUc/Az+sXIdJ39IetYnHUxL3nW4NVaPc6uy/8Qi8urzUyEXL/nn7cpSIP2aPQ==", "integrity": "sha512-6QFqfDNH4DMrt7yHKRfpqRopsVUc/Az+sXIdJ39IetYnHUxL3nW4NVaPc6uy/8Qi8urzUyEXL/nn7cpSIP2aPQ==",
@@ -23811,6 +23876,22 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/unplugin": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz",
"integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"acorn": "^8.15.0",
"picomatch": "^4.0.3",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
"node": ">=18.12.0"
}
},
"node_modules/unrs-resolver": { "node_modules/unrs-resolver": {
"version": "1.12.2", "version": "1.12.2",
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz",
+1 -2
View File
@@ -17,9 +17,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 9999", "lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 9999",
"postinstall": "npm rebuild lightningcss && prisma generate", "postinstall": "npm rebuild lightningcss && prisma generate",
"storybook": "storybook dev -p 6006", "storybook": "storybook dev -p 6006",
"storybook:github": "STORYBOOK_BASE_PATH=true storybook dev -p 6006",
"storybook:build": "storybook build", "storybook:build": "storybook build",
"storybook:build:github": "STORYBOOK_BASE_PATH=true storybook build",
"knip": "knip --include files,exports --exclude duplicates", "knip": "knip --include files,exports --exclude duplicates",
"test": "vitest run --coverage", "test": "vitest run --coverage",
"test:watch": "vitest", "test:watch": "vitest",
@@ -69,6 +67,7 @@
"@lhci/cli": "^0.15.1", "@lhci/cli": "^0.15.1",
"@playwright/test": "^1.55.0", "@playwright/test": "^1.55.0",
"@storybook/addon-a11y": "^10.2.0", "@storybook/addon-a11y": "^10.2.0",
"@storybook/addon-docs": "^10.2.0",
"@storybook/nextjs": "^10.2.0", "@storybook/nextjs": "^10.2.0",
"@storybook/react": "^10.2.0", "@storybook/react": "^10.2.0",
"@svgr/webpack": "^8.1.0", "@svgr/webpack": "^8.1.0",
+8 -3
View File
@@ -1,4 +1,9 @@
import Button from "../../app/components/buttons/Button"; import Button from "../../app/components/buttons/Button";
import {
BUTTON_PALETTE_OPTIONS,
BUTTON_TYPE_OPTIONS,
SIZE_OPTIONS,
} from "../../lib/propNormalization";
export default { export default {
title: "Components/Buttons/Button", title: "Components/Buttons/Button",
@@ -15,17 +20,17 @@ export default {
argTypes: { argTypes: {
buttonType: { buttonType: {
control: { type: "select" }, control: { type: "select" },
options: ["filled", "outline", "ghost", "danger"], options: [...BUTTON_TYPE_OPTIONS],
description: "The button type (Figma prop)", description: "The button type (Figma prop)",
}, },
palette: { palette: {
control: { type: "select" }, control: { type: "select" },
options: ["default", "inverse"], options: [...BUTTON_PALETTE_OPTIONS],
description: "The button palette (Figma prop)", description: "The button palette (Figma prop)",
}, },
size: { size: {
control: { type: "select" }, control: { type: "select" },
options: ["xsmall", "small", "medium", "large", "xlarge"], options: [...SIZE_OPTIONS],
description: "The size of the button", description: "The size of the button",
}, },
disabled: { disabled: {
+1 -1
View File
@@ -33,7 +33,7 @@ export const Default = {
export const InParagraph = { export const InParagraph = {
render: () => ( render: () => (
<p className="max-w-md font-inter text-[14px] leading-[20px] text-[color:var(--color-content-default-primary,#fff)]"> <p className="max-w-md text-small-paragraph text-[var(--color-content-default-primary)]">
Share a bit more detail so the group can weigh in. You can always{" "} Share a bit more detail so the group can weigh in. You can always{" "}
<InlineTextButton onClick={() => {}}>expand this later</InlineTextButton>{" "} <InlineTextButton onClick={() => {}}>expand this later</InlineTextButton>{" "}
if you need more room. if you need more room.
+4 -4
View File
@@ -37,10 +37,10 @@ export const Default = {
<Icon <Icon
name="number" name="number"
size={32} size={32}
className="text-[var(--color-content-default-brand-primary,#fefcc9)]" className="text-[var(--color-content-default-brand-primary)]"
/> />
</span> </span>
<span className="w-full text-center font-inter text-[14px] font-medium leading-[18px] text-[var(--color-content-default-brand-primary,#fefcc9)]"> <span className="w-full text-center text-small-paragraph font-medium text-[var(--color-content-default-brand-primary)]">
Number Number
</span> </span>
</Vertical> </Vertical>
@@ -54,10 +54,10 @@ export const Disabled = {
<Icon <Icon
name="number" name="number"
size={32} size={32}
className="text-[var(--color-content-default-brand-primary,#fefcc9)]" className="text-[var(--color-content-default-brand-primary)]"
/> />
</span> </span>
<span className="w-full text-center font-inter text-[14px] font-medium leading-[18px] text-[var(--color-content-default-brand-primary,#fefcc9)]"> <span className="w-full text-center text-small-paragraph font-medium text-[var(--color-content-default-brand-primary)]">
Number Number
</span> </span>
</Vertical> </Vertical>
+3 -7
View File
@@ -1,7 +1,5 @@
import React, { useState } from "react"; import React, { useState } from "react";
import AddCustomField from "../../app/components/controls/AddCustomField"; import AddCustomField from "../../app/components/controls/AddCustomField";
import { MessagesProvider } from "../../app/contexts/MessagesContext";
import messages from "../../messages/en/index";
/** Figma: Add Custom Field — node `20235:12994` (Community Rule System). */ /** Figma: Add Custom Field — node `20235:12994` (Community Rule System). */
export default { export default {
@@ -9,11 +7,9 @@ export default {
component: AddCustomField, component: AddCustomField,
decorators: [ decorators: [
(Story) => ( (Story) => (
<MessagesProvider messages={messages}> <div className="w-[min(100%,546px)] bg-[var(--color-surface-default-primary)] p-6">
<div className="w-[min(100%,546px)] bg-[var(--color-surface-default-primary)] p-6"> <Story />
<Story /> </div>
</div>
</MessagesProvider>
), ),
], ],
}; };
+8 -3
View File
@@ -1,4 +1,9 @@
import Chip from "../../app/components/controls/Chip"; import Chip from "../../app/components/controls/Chip";
import {
CHIP_PALETTE_OPTIONS,
CHIP_SIZE_OPTIONS,
CHIP_STATE_OPTIONS,
} from "../../lib/propNormalization";
export default { export default {
title: "Components/Controls/Chip", title: "Components/Controls/Chip",
@@ -13,17 +18,17 @@ export default {
}, },
state: { state: {
control: "select", control: "select",
options: ["unselected", "selected", "disabled", "custom"], options: [...CHIP_STATE_OPTIONS],
description: "Visual state of the chip", description: "Visual state of the chip",
}, },
palette: { palette: {
control: "select", control: "select",
options: ["default", "inverse"], options: [...CHIP_PALETTE_OPTIONS],
description: "Color palette of the chip", description: "Color palette of the chip",
}, },
size: { size: {
control: "select", control: "select",
options: ["s", "m"], options: [...CHIP_SIZE_OPTIONS],
description: "Size of the chip", description: "Size of the chip",
}, },
disabled: { disabled: {
+37 -34
View File
@@ -44,43 +44,46 @@ const Template = (args) => {
}; };
// Default story // Default story
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
label: "Default Select Input", label: "Default Select Input",
placeholder: "Choose an option", placeholder: "Choose an option",
state: "default", state: "default",
},
render: Template,
}; // States
export const Active = {
args: {
label: "Active State",
placeholder: "Choose an option",
state: "active",
},
render: Template,
}; };
export const Focus = {
// States args: {
export const Active = Template.bind({}); label: "Focus State",
Active.args = { placeholder: "Choose an option",
label: "Active State", state: "focus",
placeholder: "Choose an option", },
state: "active", render: Template,
}; };
export const Error = {
export const Focus = Template.bind({}); args: {
Focus.args = { label: "Error State",
label: "Focus State", placeholder: "Choose an option",
placeholder: "Choose an option", error: true,
state: "focus", },
render: Template,
}; };
export const Disabled = {
export const Error = Template.bind({}); args: {
Error.args = { label: "Disabled State",
label: "Error State", placeholder: "Choose an option",
placeholder: "Choose an option", disabled: true,
error: true, },
}; render: Template,
}; // Interactive example
export const Disabled = Template.bind({});
Disabled.args = {
label: "Disabled State",
placeholder: "Choose an option",
disabled: true,
};
// Interactive example
export const Interactive = (args) => { export const Interactive = (args) => {
const [value, setValue] = useState(""); const [value, setValue] = useState("");
+26 -22
View File
@@ -39,32 +39,36 @@ export default {
const Template = (args) => <Switch {...args} />; const Template = (args) => <Switch {...args} />;
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
propSwitch: false, propSwitch: false,
text: "Switch label", text: "Switch label",
},
render: Template,
}; };
export const Checked = {
export const Checked = Template.bind({}); args: {
Checked.args = { propSwitch: true,
propSwitch: true, text: "Switch label",
text: "Switch label", },
render: Template,
}; };
export const Focus = {
export const Focus = Template.bind({}); args: {
Focus.args = { propSwitch: false,
propSwitch: false, state: "focus",
state: "focus", text: "Switch label",
text: "Switch label", },
render: Template,
}; };
export const FocusChecked = {
export const FocusChecked = Template.bind({}); args: {
FocusChecked.args = { propSwitch: true,
propSwitch: true, state: "focus",
state: "focus", text: "Switch label",
text: "Switch label", },
render: Template,
}; };
export const States = () => ( export const States = () => (
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
+71 -54
View File
@@ -1,5 +1,11 @@
import React from "react"; import React from "react";
import TextArea from "../../app/components/controls/TextArea"; import TextArea from "../../app/components/controls/TextArea";
import {
INPUT_STATE_OPTIONS,
LABEL_VARIANT_OPTIONS,
SMALL_MEDIUM_LARGE_OPTIONS,
TEXT_AREA_APPEARANCE_OPTIONS,
} from "../../lib/propNormalization";
export default { export default {
title: "Components/Controls/TextArea", title: "Components/Controls/TextArea",
@@ -10,15 +16,19 @@ export default {
argTypes: { argTypes: {
size: { size: {
control: { type: "select" }, control: { type: "select" },
options: ["small", "medium", "large"], options: [...SMALL_MEDIUM_LARGE_OPTIONS],
}, },
labelVariant: { labelVariant: {
control: { type: "select" }, control: { type: "select" },
options: ["default", "horizontal"], options: [...LABEL_VARIANT_OPTIONS],
},
appearance: {
control: { type: "select" },
options: [...TEXT_AREA_APPEARANCE_OPTIONS],
}, },
state: { state: {
control: { type: "select" }, control: { type: "select" },
options: ["default", "active", "hover", "focus", "error"], options: [...INPUT_STATE_OPTIONS],
}, },
disabled: { disabled: {
control: { type: "boolean" }, control: { type: "boolean" },
@@ -31,64 +41,71 @@ export default {
const Template = (args) => <TextArea {...args} />; const Template = (args) => <TextArea {...args} />;
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
label: "Text Area", label: "Text Area",
placeholder: "Enter text...", placeholder: "Enter text...",
value: "", value: "",
},
render: Template,
}; };
export const WithValue = {
export const WithValue = Template.bind({}); args: {
WithValue.args = { label: "Text Area",
label: "Text Area", placeholder: "Enter text...",
placeholder: "Enter text...", value:
value: "This is some sample text content that demonstrates how the text area looks with content.",
"This is some sample text content that demonstrates how the text area looks with content.", },
render: Template,
}; };
export const Small = {
export const Small = Template.bind({}); args: {
Small.args = { size: "small",
size: "small", label: "Small Text Area",
label: "Small Text Area", placeholder: "Enter text...",
placeholder: "Enter text...", value: "",
value: "", },
render: Template,
}; };
export const Medium = {
export const Medium = Template.bind({}); args: {
Medium.args = { size: "medium",
size: "medium", label: "Medium Text Area",
label: "Medium Text Area", placeholder: "Enter text...",
placeholder: "Enter text...", value: "",
value: "", },
render: Template,
}; };
export const Large = {
export const Large = Template.bind({}); args: {
Large.args = { size: "large",
size: "large", label: "Large Text Area",
label: "Large Text Area", placeholder: "Enter text...",
placeholder: "Enter text...", value: "",
value: "", },
render: Template,
}; };
export const Embedded = {
export const Embedded = Template.bind({}); args: {
Embedded.args = { label: "Section content",
label: "Section content", placeholder: "Enter text...",
placeholder: "Enter text...", value:
value: "Embedded appearance used in create-flow modals: borderless, darker grey block.",
"Embedded appearance used in create-flow modals: borderless, darker grey block.", appearance: "embedded",
appearance: "embedded", size: "large",
size: "large", rows: 4,
rows: 4, },
render: Template,
}; };
export const HorizontalLabel = {
export const HorizontalLabel = Template.bind({}); args: {
HorizontalLabel.args = { labelVariant: "horizontal",
labelVariant: "horizontal", label: "Horizontal Label",
label: "Horizontal Label", placeholder: "Enter text...",
placeholder: "Enter text...", value: "",
value: "", },
render: Template,
}; };
export const AllSizes = () => ( export const AllSizes = () => (
<div className="space-y-6"> <div className="space-y-6">
<div className="space-y-4"> <div className="space-y-4">
+75 -66
View File
@@ -1,5 +1,9 @@
import React from "react"; import React from "react";
import TextInput from "../../app/components/controls/TextInput"; import TextInput from "../../app/components/controls/TextInput";
import {
INPUT_STATE_OPTIONS,
TEXT_INPUT_SIZE_OPTIONS,
} from "../../lib/propNormalization";
export default { export default {
title: "Components/Controls/TextInput", title: "Components/Controls/TextInput",
@@ -10,11 +14,11 @@ export default {
argTypes: { argTypes: {
inputSize: { inputSize: {
control: { type: "select" }, control: { type: "select" },
options: ["small", "medium", "Small", "Medium"], options: [...TEXT_INPUT_SIZE_OPTIONS],
}, },
state: { state: {
control: { type: "select" }, control: { type: "select" },
options: ["default", "active", "hover", "focus", "error", "disabled"], options: [...INPUT_STATE_OPTIONS],
}, },
disabled: { disabled: {
control: { type: "boolean" }, control: { type: "boolean" },
@@ -37,75 +41,80 @@ export default {
const Template = (args) => <TextInput {...args} />; const Template = (args) => <TextInput {...args} />;
// Default story // Default story
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
label: "Default Text Input", label: "Default Text Input",
placeholder: "Enter text...", placeholder: "Enter text...",
inputSize: "medium", inputSize: "medium",
state: "default", state: "default",
},
render: Template,
}; // Size variants
export const Small = {
args: {
label: "Small Text Input",
placeholder: "Small size",
inputSize: "small",
state: "default",
},
render: Template,
}; };
export const Medium = {
// Size variants args: {
export const Small = Template.bind({}); label: "Medium Text Input",
Small.args = { placeholder: "Medium size",
label: "Small Text Input", inputSize: "medium",
placeholder: "Small size", state: "default",
inputSize: "small", },
state: "default", render: Template,
}; // States
export const Active = {
args: {
label: "Active State",
placeholder: "Active input",
inputSize: "medium",
state: "active",
},
render: Template,
}; };
export const Hover = {
export const Medium = Template.bind({}); args: {
Medium.args = { label: "Hover State",
label: "Medium Text Input", placeholder: "Hover input",
placeholder: "Medium size", inputSize: "medium",
inputSize: "medium", state: "hover",
state: "default", },
render: Template,
}; };
export const Focus = {
// States args: {
export const Active = Template.bind({}); label: "Focus State",
Active.args = { placeholder: "Focused input",
label: "Active State", inputSize: "medium",
placeholder: "Active input", state: "focus",
inputSize: "medium", },
state: "active", render: Template,
}; };
export const Error = {
export const Hover = Template.bind({}); args: {
Hover.args = { label: "Error State",
label: "Hover State", placeholder: "Error input",
placeholder: "Hover input", inputSize: "medium",
inputSize: "medium", state: "default",
state: "hover", error: true,
},
render: Template,
}; };
export const Disabled = {
export const Focus = Template.bind({}); args: {
Focus.args = { label: "Disabled State",
label: "Focus State", placeholder: "Disabled input",
placeholder: "Focused input", inputSize: "medium",
inputSize: "medium", state: "default",
state: "focus", disabled: true,
}; },
render: Template,
export const Error = Template.bind({}); }; // Interactive example
Error.args = {
label: "Error State",
placeholder: "Error input",
inputSize: "medium",
state: "default",
error: true,
};
export const Disabled = Template.bind({});
Disabled.args = {
label: "Disabled State",
placeholder: "Disabled input",
inputSize: "medium",
state: "default",
disabled: true,
};
// Interactive example
export const Interactive = (args) => { export const Interactive = (args) => {
const [value, setValue] = React.useState(""); const [value, setValue] = React.useState("");
+16 -14
View File
@@ -44,22 +44,24 @@ export const States = () => (
</div> </div>
); );
export const WithText = Template.bind({}); export const WithText = {
WithText.args = { args: {
label: "Text Toggle", label: "Text Toggle",
checked: false, checked: false,
showText: true, showText: true,
text: "Toggle", text: "Toggle",
},
render: Template,
}; };
export const WithIcon = {
export const WithIcon = Template.bind({}); args: {
WithIcon.args = { label: "Icon Toggle",
label: "Icon Toggle", checked: false,
checked: false, showIcon: true,
showIcon: true, icon: "I",
icon: "I", },
render: Template,
}; };
export const Interactive = () => { export const Interactive = () => {
const [checked, setChecked] = React.useState(false); const [checked, setChecked] = React.useState(false);
const [state, setState] = React.useState("default"); const [state, setState] = React.useState("default");
+43 -34
View File
@@ -1,5 +1,9 @@
import React from "react"; import React from "react";
import ToggleGroup from "../../app/components/controls/ToggleGroup"; import ToggleGroup from "../../app/components/controls/ToggleGroup";
import {
STATE_OPTIONS,
TOGGLE_GROUP_POSITION_OPTIONS,
} from "../../lib/propNormalization";
export default { export default {
title: "Components/Controls/ToggleGroup", title: "Components/Controls/ToggleGroup",
@@ -10,11 +14,11 @@ export default {
argTypes: { argTypes: {
position: { position: {
control: { type: "select" }, control: { type: "select" },
options: ["left", "middle", "right"], options: [...TOGGLE_GROUP_POSITION_OPTIONS],
}, },
state: { state: {
control: { type: "select" }, control: { type: "select" },
options: ["default", "hover", "focus", "selected"], options: [...STATE_OPTIONS],
}, },
showText: { showText: {
control: { type: "boolean" }, control: { type: "boolean" },
@@ -24,27 +28,30 @@ export default {
const Template = (args) => <ToggleGroup {...args}>Toggle Item</ToggleGroup>; const Template = (args) => <ToggleGroup {...args}>Toggle Item</ToggleGroup>;
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
position: "left", position: "left",
state: "default", state: "default",
showText: true, showText: true,
},
render: Template,
}; };
export const Middle = {
export const Middle = Template.bind({}); args: {
Middle.args = { position: "middle",
position: "middle", state: "default",
state: "default", showText: true,
showText: true, },
render: Template,
}; };
export const Right = {
export const Right = Template.bind({}); args: {
Right.args = { position: "right",
position: "right", state: "default",
state: "default", showText: true,
showText: true, },
render: Template,
}; };
export const States = () => ( export const States = () => (
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
@@ -89,22 +96,24 @@ export const Positions = () => (
</div> </div>
); );
export const WithText = Template.bind({}); export const WithText = {
WithText.args = { args: {
position: "left", position: "left",
state: "default", state: "default",
showText: true, showText: true,
children: "Active Deals", children: "Active Deals",
},
render: Template,
}; };
export const WithoutText = {
export const WithoutText = Template.bind({}); args: {
WithoutText.args = { position: "left",
position: "left", state: "default",
state: "default", showText: false,
showText: false, children: "☰",
children: "☰", },
render: Template,
}; };
export const WithIcons = () => ( export const WithIcons = () => (
<div className="space-y-4"> <div className="space-y-4">
<div className="space-y-2"> <div className="space-y-2">
+69 -59
View File
@@ -34,87 +34,97 @@ export default {
const Template = (args) => <Upload {...args} />; const Template = (args) => <Upload {...args} />;
// Default story // Default story
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
label: "Upload", label: "Upload",
active: true, active: true,
showHelpIcon: true, showHelpIcon: true,
}; },
render: Template,
// Active state }; // Active state
export const Active = Template.bind({}); export const Active = {
Active.args = { args: {
label: "Upload", label: "Upload",
active: true, active: true,
showHelpIcon: true, showHelpIcon: true,
}; },
Active.parameters = { parameters: {
docs: { docs: {
description: { description: {
story: story:
"Upload component in active state with white button and black text.", "Upload component in active state with white button and black text.",
},
}, },
}, },
render: Template,
}; };
// Inactive state // Inactive state
export const Inactive = Template.bind({}); export const Inactive = {
Inactive.args = { args: {
label: "Upload", label: "Upload",
active: false, active: false,
showHelpIcon: true, showHelpIcon: true,
}; },
Inactive.parameters = { parameters: {
docs: { docs: {
description: { description: {
story: story:
"Upload component in inactive state with dark button and gray text.", "Upload component in inactive state with dark button and gray text.",
},
}, },
}, },
render: Template,
}; };
// Without help icon // Without help icon
export const WithoutHelpIcon = Template.bind({}); export const WithoutHelpIcon = {
WithoutHelpIcon.args = { args: {
label: "Upload", label: "Upload",
active: true, active: true,
showHelpIcon: false, showHelpIcon: false,
}; },
WithoutHelpIcon.parameters = { parameters: {
docs: { docs: {
description: { description: {
story: "Upload component without help icon.", story: "Upload component without help icon.",
},
}, },
}, },
render: Template,
}; };
// Without label // Without label
export const WithoutLabel = Template.bind({}); export const WithoutLabel = {
WithoutLabel.args = { args: {
active: true, active: true,
showHelpIcon: false, showHelpIcon: false,
}; },
WithoutLabel.parameters = { parameters: {
docs: { docs: {
description: { description: {
story: "Upload component without label.", story: "Upload component without label.",
},
}, },
}, },
render: Template,
}; };
// Custom label // Custom label
export const CustomLabel = Template.bind({}); export const CustomLabel = {
CustomLabel.args = { args: {
label: "Upload Files", label: "Upload Files",
active: true, active: true,
showHelpIcon: true, showHelpIcon: true,
}; },
CustomLabel.parameters = { parameters: {
docs: { docs: {
description: { description: {
story: "Upload component with custom label text.", story: "Upload component with custom label text.",
},
}, },
}, },
render: Template,
}; };
// All states comparison // All states comparison
@@ -1,16 +1,19 @@
import React from "react"; import React from "react";
import HeaderLockup from "../../app/components/type/HeaderLockup"; import HeaderLockup from "../../app/components/type/HeaderLockup";
import InfoMessageBox from "../../app/components/controls/InfoMessageBox"; import InfoMessageBox from "../../app/components/controls/InfoMessageBox";
import {
HEADER_LOCKUP_JUSTIFICATION_OPTIONS,
HEADER_LOCKUP_SIZE_OPTIONS,
} from "../../lib/propNormalization";
/** /**
* Compose pattern used by create-flow decision approaches rail: headline lockup * Compose pattern used by create-flow decision approaches rail: headline lockup
* plus bordered checklist no standalone wrapper component. * plus bordered checklist no standalone wrapper component.
*/ */
export default { export default {
title: "Components/Controls/Rail header (lockup + info box)", title: "Create Flow/Rail header (lockup + info box)",
parameters: { parameters: {
layout: "centered", layout: "centered",
backgrounds: { default: "dark" },
docs: { docs: {
description: { description: {
component: component:
@@ -18,14 +21,23 @@ export default {
}, },
}, },
}, },
argTypes: {
size: {
control: { type: "select" },
options: [...HEADER_LOCKUP_SIZE_OPTIONS],
},
justification: {
control: { type: "select" },
options: [...HEADER_LOCKUP_JUSTIFICATION_OPTIONS],
},
},
decorators: [ decorators: [
(Story) => ( (Story) => (
<div className="bg-black p-8 max-w-lg w-full"> <div className="w-full max-w-lg bg-[var(--color-surface-default-primary)] p-8">
<Story /> <Story />
</div> </div>
), ),
], ],
tags: ["autodocs"],
}; };
const messageItems = [ const messageItems = [
+73
View File
@@ -0,0 +1,73 @@
import ListItem from "../../app/components/layout/ListItem";
import { ICON_NAME_OPTIONS } from "../../app/components/asset/icon";
export default {
title: "Components/Layout/ListItem",
component: ListItem,
parameters: {
layout: "centered",
docs: {
description: {
component:
"Icon + label menuitem row used in Popover export menus and CreateFlowTopNav action menus. Distinct from List / ListEntry data rows.",
},
},
},
argTypes: {
label: { control: { type: "text" } },
leadingIcon: {
control: { type: "select" },
options: [...ICON_NAME_OPTIONS],
},
showDivider: { control: { type: "boolean" } },
variant: {
control: { type: "select" },
options: ["default", "destructive"],
},
onClick: { action: "clicked" },
},
decorators: [
(Story) => (
<div className="w-[240px] bg-[var(--color-surface-default-primary)]">
<Story />
</div>
),
],
};
export const Default = {
args: {
label: "PDF",
leadingIcon: "picture_as_pdf",
showDivider: true,
},
};
export const ExportMenu = {
render: (args) => (
<div>
<ListItem
{...args}
label="PDF"
leadingIcon="picture_as_pdf"
showDivider
/>
<ListItem {...args} label="CSV" leadingIcon="csv" showDivider />
<ListItem
{...args}
label="Markdown"
leadingIcon="markdown_copy"
showDivider={false}
/>
</div>
),
};
export const Destructive = {
args: {
label: "Log out",
leadingIcon: "log_out",
showDivider: false,
variant: "destructive",
},
};
+109 -96
View File
@@ -42,119 +42,132 @@ const Template = (args) => {
); );
}; };
export const ToastDefault = Template.bind({}); export const ToastDefault = {
ToastDefault.args = { args: {
title: "Short alert toast message goes here", title: "Short alert toast message goes here",
description: description:
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
status: "default", status: "default",
type: "toast", type: "toast",
},
render: Template,
}; };
export const ToastPositive = {
export const ToastPositive = Template.bind({}); args: {
ToastPositive.args = { title: "Short alert toast message goes here",
title: "Short alert toast message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "positive",
status: "positive", type: "toast",
type: "toast", },
render: Template,
}; };
export const ToastWarning = {
export const ToastWarning = Template.bind({}); args: {
ToastWarning.args = { title: "Short alert toast message goes here",
title: "Short alert toast message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "warning",
status: "warning", type: "toast",
type: "toast", },
render: Template,
}; };
export const ToastDanger = {
export const ToastDanger = Template.bind({}); args: {
ToastDanger.args = { title: "Short alert toast message goes here",
title: "Short alert toast message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "danger",
status: "danger", type: "toast",
type: "toast", },
render: Template,
}; };
export const Banner = {
export const Banner = Template.bind({}); args: {
Banner.args = { title: "Short alert banner message goes here",
title: "Short alert banner message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "default",
status: "default", type: "banner",
type: "banner", },
render: Template,
}; };
export const BannerPositive = {
export const BannerPositive = Template.bind({}); args: {
BannerPositive.args = { title: "Short alert banner message goes here",
title: "Short alert banner message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "positive",
status: "positive", type: "banner",
type: "banner", },
render: Template,
}; };
export const BannerWarning = {
export const BannerWarning = Template.bind({}); args: {
BannerWarning.args = { title: "Short alert banner message goes here",
title: "Short alert banner message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "warning",
status: "warning", type: "banner",
type: "banner", },
render: Template,
}; };
export const BannerDanger = {
export const BannerDanger = Template.bind({}); args: {
BannerDanger.args = { title: "Short alert banner message goes here",
title: "Short alert banner message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "danger",
status: "danger", type: "banner",
type: "banner", },
render: Template,
}; };
export const TitleOnly = {
export const TitleOnly = Template.bind({}); args: {
TitleOnly.args = { title: "Short alert banner message goes here",
title: "Short alert banner message goes here", status: "default",
status: "default", type: "toast",
type: "toast", },
render: Template,
}; };
export const ToastSmall = {
export const ToastSmall = Template.bind({}); args: {
ToastSmall.args = { title: "Short alert toast message goes here",
title: "Short alert toast message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "default",
status: "default", type: "toast",
type: "toast", size: "s",
size: "s", },
render: Template,
}; };
export const BannerSmall = {
export const BannerSmall = Template.bind({}); args: {
BannerSmall.args = { title: "Short alert banner message goes here",
title: "Short alert banner message goes here", description:
description: "Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.", status: "positive",
status: "positive", type: "banner",
type: "banner", size: "s",
size: "s", },
render: Template,
}; };
const NoDismissTemplate = (args) => ( const NoDismissTemplate = (args) => (
<div className="p-8 max-w-[600px]"> <div className="p-8 max-w-[600px]">
<Alert {...args} /> <Alert {...args} />
</div> </div>
); );
export const BannerNoDismiss = NoDismissTemplate.bind({}); export const BannerNoDismiss = {
BannerNoDismiss.args = { args: {
title: "Non-dismissible banner (no onClose)", title: "Non-dismissible banner (no onClose)",
description: "Used when the message clears via navigation or parent state only.", description:
status: "danger", "Used when the message clears via navigation or parent state only.",
type: "banner", status: "danger",
size: "s", type: "banner",
size: "s",
},
render: NoDismissTemplate,
}; };
export const AllStatuses = () => { export const AllStatuses = () => {
const [visible, setVisible] = useState({ const [visible, setVisible] = useState({
default: true, default: true,
+157 -147
View File
@@ -54,158 +54,168 @@ const Template = (args) => {
); );
}; };
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
isOpen: true, isOpen: true,
title: "What do you call your group's new policy?", title: "What do you call your group's new policy?",
description: "You can also combine or add new approaches to the list", description: "You can also combine or add new approaches to the list",
children: ( children: (
<div className="space-y-4"> <div className="space-y-4">
<TextInput label="Label" placeholder="Policy name" value="" /> <TextInput label="Label" placeholder="Policy name" value="" />
<p className="text-[12px] text-[var(--color-content-default-tertiary)]"> <p className="text-[12px] text-[var(--color-content-default-tertiary)]">
0/48 0/48
</p> </p>
</div> </div>
), ),
showBackButton: true, showBackButton: true,
showNextButton: true, showNextButton: true,
backButtonText: "Back", backButtonText: "Back",
nextButtonText: "Next", nextButtonText: "Next",
nextButtonDisabled: false, nextButtonDisabled: false,
},
render: Template,
}; };
export const WithStepper = {
export const WithStepper = Template.bind({}); args: {
WithStepper.args = { isOpen: true,
isOpen: true, title: "What do you call your group's new policy?",
title: "What do you call your group's new policy?", description: "You can also combine or add new approaches to the list",
description: "You can also combine or add new approaches to the list", children: (
children: ( <div className="space-y-4">
<div className="space-y-4"> <TextInput label="Label" placeholder="Policy name" value="" />
<TextInput label="Label" placeholder="Policy name" value="" /> <p className="text-[12px] text-[var(--color-content-default-tertiary)]">
<p className="text-[12px] text-[var(--color-content-default-tertiary)]"> 0/48
0/48 </p>
</p> </div>
</div> ),
), showBackButton: true,
showBackButton: true, showNextButton: true,
showNextButton: true, backButtonText: "Back",
backButtonText: "Back", nextButtonText: "Next",
nextButtonText: "Next", nextButtonDisabled: false,
nextButtonDisabled: false, currentStep: 1,
currentStep: 1, totalSteps: 3,
totalSteps: 3, },
render: Template,
}; };
export const Step2 = {
export const Step2 = Template.bind({}); args: {
Step2.args = { isOpen: true,
isOpen: true, title: "How should conflicts be resolved?",
title: "How should conflicts be resolved?", description: "You can also combine or add new approaches to the list",
description: "You can also combine or add new approaches to the list", children: (
children: ( <div className="space-y-4">
<div className="space-y-4"> <TextInput label="Label" placeholder="Enter text" value="" />
<TextInput label="Label" placeholder="Enter text" value="" /> </div>
</div> ),
), showBackButton: true,
showBackButton: true, showNextButton: true,
showNextButton: true, backButtonText: "Back",
backButtonText: "Back", nextButtonText: "Next",
nextButtonText: "Next", nextButtonDisabled: false,
nextButtonDisabled: false, currentStep: 2,
currentStep: 2, totalSteps: 3,
totalSteps: 3, },
render: Template,
}; };
export const Step3 = {
export const Step3 = Template.bind({}); args: {
Step3.args = { isOpen: true,
isOpen: true, title: "Final step",
title: "Final step", description: "Review your settings",
description: "Review your settings", children: (
children: ( <div className="space-y-4">
<div className="space-y-4"> <p className="text-[var(--color-content-default-primary)]">
<p className="text-[var(--color-content-default-primary)]"> Review your policy configuration
Review your policy configuration </p>
</p> </div>
</div> ),
), showBackButton: true,
showBackButton: true, showNextButton: true,
showNextButton: true, backButtonText: "Back",
backButtonText: "Back", nextButtonText: "Finish",
nextButtonText: "Finish", nextButtonDisabled: false,
nextButtonDisabled: false, currentStep: 3,
currentStep: 3, totalSteps: 3,
totalSteps: 3, },
render: Template,
}; };
export const WithCustomHeader = {
export const WithCustomHeader = Template.bind({}); args: {
WithCustomHeader.args = { isOpen: true,
isOpen: true, headerContent: <div className="text-lg font-semibold">Custom header</div>,
headerContent: <div className="text-lg font-semibold">Custom header</div>, children: (
children: ( <div className="space-y-4">
<div className="space-y-4"> <p className="text-[var(--color-content-default-primary)]">
<p className="text-[var(--color-content-default-primary)]"> When headerContent is provided, the default title and description are
When headerContent is provided, the default title and description are not shown.
not shown. </p>
</p> </div>
</div> ),
), showBackButton: false,
showBackButton: false, showNextButton: true,
showNextButton: true, nextButtonText: "Continue",
nextButtonText: "Continue", },
render: Template,
}; };
export const WithoutFooter = {
export const WithoutFooter = Template.bind({}); args: {
WithoutFooter.args = { isOpen: true,
isOpen: true, title: "Simple Create Dialog",
title: "Simple Create Dialog", description: "This create dialog has no footer buttons",
description: "This create dialog has no footer buttons", children: (
children: ( <div className="space-y-4">
<div className="space-y-4"> <p className="text-[var(--color-content-default-primary)]">
<p className="text-[var(--color-content-default-primary)]"> Modal content without footer
Modal content without footer </p>
</p> </div>
</div> ),
), showBackButton: false,
showBackButton: false, showNextButton: false,
showNextButton: false, },
render: Template,
}; };
export const LoginYellowBackdrop = {
export const LoginYellowBackdrop = Template.bind({}); args: {
LoginYellowBackdrop.args = { isOpen: true,
isOpen: true, title: "Horizontalism",
title: "Horizontalism", description:
description: "Edit or add to this description to describe what this value means to your community.", "Edit or add to this description to describe what this value means to your community.",
backdropVariant: "blurredYellow", backdropVariant: "blurredYellow",
children: ( children: (
<div className="space-y-4"> <div className="space-y-4">
<p className="text-[var(--color-content-default-primary)]"> <p className="text-[var(--color-content-default-primary)]">
Core value detail body (yellow blurred overlay like Login). Core value detail body (yellow blurred overlay like Login).
</p> </p>
</div> </div>
), ),
showBackButton: false, showBackButton: false,
showNextButton: true, showNextButton: true,
nextButtonText: "Add Value", nextButtonText: "Add Value",
nextButtonDisabled: false, nextButtonDisabled: false,
},
render: Template,
}; };
export const NextButtonDisabled = {
export const NextButtonDisabled = Template.bind({}); args: {
NextButtonDisabled.args = { isOpen: true,
isOpen: true, title: "What do you call your group's new policy?",
title: "What do you call your group's new policy?", description: "You can also combine or add new approaches to the list",
description: "You can also combine or add new approaches to the list", children: (
children: ( <div className="space-y-4">
<div className="space-y-4"> <TextInput label="Label" placeholder="Policy name" value="" />
<TextInput label="Label" placeholder="Policy name" value="" /> <p className="text-[12px] text-[var(--color-content-default-tertiary)]">
<p className="text-[12px] text-[var(--color-content-default-tertiary)]"> 0/48
0/48 </p>
</p> </div>
</div> ),
), showBackButton: true,
showBackButton: true, showNextButton: true,
showNextButton: true, backButtonText: "Back",
backButtonText: "Back", nextButtonText: "Next",
nextButtonText: "Next", nextButtonDisabled: true,
nextButtonDisabled: true, currentStep: 1,
currentStep: 1, totalSteps: 3,
totalSteps: 3, },
render: Template,
}; };
+15 -12
View File
@@ -1,6 +1,9 @@
import React, { Suspense, useEffect } from "react"; import React, { Suspense, useEffect } from "react";
import Login from "../../app/components/modals/Login"; import Login from "../../app/components/modals/Login";
import LoginForm from "../../app/components/modals/Login/LoginForm"; import LoginForm from "../../app/components/modals/Login/LoginForm";
import messages from "../../messages/en/index";
const backToHome = messages.pages.login.backToHome;
/** /**
* Storybook runs outside Next.js request context; successful "Send link" needs fetch mocked * Storybook runs outside Next.js request context; successful "Send link" needs fetch mocked
@@ -43,11 +46,11 @@ function FakeMarketingPageBehindOverlay({
return ( return (
<div className="relative min-h-[100dvh] overflow-hidden"> <div className="relative min-h-[100dvh] overflow-hidden">
<div <div
className="absolute inset-0 bg-gradient-to-br from-amber-100 via-white to-amber-50" className="absolute inset-0 bg-[var(--color-surface-inverse-brand-primary)]"
aria-hidden aria-hidden
/> />
<div className="relative z-0 px-8 py-16"> <div className="relative z-0 px-8 py-16">
<p className="font-inter max-w-md text-lg text-neutral-800"> <p className="max-w-md text-large-paragraph text-[var(--color-content-invert-primary)]">
Placeholder page content the login overlay portals above this and Placeholder page content the login overlay portals above this and
uses backdrop blur (`blurredYellow`). uses backdrop blur (`blurredYellow`).
</p> </p>
@@ -107,13 +110,13 @@ export const HeaderOverlayBlurred = {
belowCard={ belowCard={
<a <a
href="/" href="/"
className="font-inter font-normal text-[14px] leading-[20px] text-[var(--color-content-invert-tertiary,#2d2d2d)] text-center hover:opacity-90" className="text-center text-small-paragraph text-[var(--color-content-invert-tertiary)] hover:opacity-90"
> >
Back to home {backToHome}
</a> </a>
} }
> >
<Suspense fallback={<p className="font-inter p-6">Loading</p>}> <Suspense fallback={<p className="p-6 text-small-paragraph">Loading</p>}>
<LoginForm /> <LoginForm />
</Suspense> </Suspense>
</Login> </Login>
@@ -152,13 +155,13 @@ export const FullPageRouteSolid = {
belowCard={ belowCard={
<a <a
href="/" href="/"
className="font-inter font-normal text-[14px] leading-[20px] text-[var(--color-content-invert-tertiary,#2d2d2d)] text-center hover:opacity-90" className="text-center text-small-paragraph text-[var(--color-content-invert-tertiary)] hover:opacity-90"
> >
Back to home {backToHome}
</a> </a>
} }
> >
<Suspense fallback={<p className="font-inter p-6">Loading</p>}> <Suspense fallback={<p className="p-6 text-small-paragraph">Loading</p>}>
<LoginForm /> <LoginForm />
</Suspense> </Suspense>
</Login> </Login>
@@ -178,15 +181,15 @@ export const ModalChromeOnly = {
belowCard={ belowCard={
<a <a
href="/" href="/"
className="font-inter font-normal text-[14px] leading-[20px] text-[var(--color-content-invert-tertiary,#2d2d2d)] text-center hover:opacity-90" className="text-center text-small-paragraph text-[var(--color-content-invert-tertiary)] hover:opacity-90"
> >
Back to home {backToHome}
</a> </a>
} }
> >
<p <p
id="login-modal-heading" id="login-modal-heading"
className="font-inter px-2 py-4 text-[var(--color-content-default-primary)]" className="px-2 py-4 text-[var(--color-content-default-primary)]"
> >
Placeholder body use &quot;Header overlay&quot; or &quot;Full-page Placeholder body use &quot;Header overlay&quot; or &quot;Full-page
route&quot; for the real flow. route&quot; for the real flow.
@@ -215,7 +218,7 @@ export const FormOnly = {
], ],
render: () => ( render: () => (
<div className="mx-auto max-w-[560px] rounded-[20px] bg-[var(--color-surface-default-primary)] p-6 shadow-lg"> <div className="mx-auto max-w-[560px] rounded-[20px] bg-[var(--color-surface-default-primary)] p-6 shadow-lg">
<Suspense fallback={<p className="font-inter">Loading</p>}> <Suspense fallback={<p className="text-small-paragraph">Loading</p>}>
<LoginForm /> <LoginForm />
</Suspense> </Suspense>
</div> </div>
+28 -24
View File
@@ -1,41 +1,45 @@
import React, { useState } from "react"; import React, { useState } from "react";
import Share from "../../app/components/modals/Share"; import Share from "../../app/components/modals/Share";
import { MessagesProvider } from "../../app/contexts/MessagesContext";
import messages from "../../messages/en/index";
/** Figma: Modal / Share — node 22073-30884 (Community Rule System). */ /** Figma: Modal / Share — node 22073-30884 (Community Rule System). */
export default { export default {
title: "modals/Share", title: "Components/Modals/Share",
component: Share, component: Share,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Share modal for published rules (copy link and channel actions).",
},
},
},
}; };
function ShareStoryHost() { function ShareStoryHost() {
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
return ( return (
<MessagesProvider messages={messages}> <div className="min-h-[100dvh] bg-[var(--color-surface-inverse-brand-primary)] p-6">
<div className="min-h-[100dvh] bg-[var(--color-surface-inverse-brand-primary)] p-6"> <button
<button type="button"
type="button" className="rounded-md bg-[var(--color-surface-default-primary)] px-4 py-2 text-small-paragraph text-[var(--color-content-default-primary)]"
className="rounded-md bg-white px-4 py-2 text-sm text-black" onClick={() => setOpen(true)}
onClick={() => setOpen(true)} >
> Open Share
Open Share </button>
</button> <Share
<Share isOpen={open}
isOpen={open} onClose={() => setOpen(false)}
onClose={() => setOpen(false)} onCopyLink={() => {}}
onCopyLink={() => {}} onEmailShare={() => {}}
onEmailShare={() => {}} onSignalShare={() => {}}
onSignalShare={() => {}} onSlackShare={() => {}}
onSlackShare={() => {}} onDiscordShare={() => {}}
onDiscordShare={() => {}} />
/> </div>
</div>
</MessagesProvider>
); );
} }
export const Default = { export const Default = {
name: "Modal / Share",
render: () => <ShareStoryHost />, render: () => <ShareStoryHost />,
}; };
+33 -27
View File
@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import Tooltip from "../../app/components/modals/Tooltip"; import Tooltip from "../../app/components/modals/Tooltip";
import Button from "../../app/components/buttons/Button"; import Button from "../../app/components/buttons/Button";
import { TOOLTIP_POSITION_OPTIONS } from "../../lib/propNormalization";
export default { export default {
title: "Components/Modals/Tooltip", title: "Components/Modals/Tooltip",
@@ -8,7 +9,7 @@ export default {
argTypes: { argTypes: {
position: { position: {
control: { type: "select" }, control: { type: "select" },
options: ["top", "bottom"], options: [...TOOLTIP_POSITION_OPTIONS],
}, },
disabled: { disabled: {
control: { type: "boolean" }, control: { type: "boolean" },
@@ -29,37 +30,42 @@ const Template = (args) => (
</div> </div>
); );
export const Default = Template.bind({}); export const Default = {
Default.args = { args: {
text: "Tooltip text goes here", text: "Tooltip text goes here",
position: "top", position: "top",
disabled: false, disabled: false,
},
render: Template,
}; };
export const Top = {
export const Top = Template.bind({}); args: {
Top.args = { text: "Tooltip positioned at top",
text: "Tooltip positioned at top", position: "top",
position: "top", },
render: Template,
}; };
export const Bottom = {
export const Bottom = Template.bind({}); args: {
Bottom.args = { text: "Tooltip positioned at bottom",
text: "Tooltip positioned at bottom", position: "bottom",
position: "bottom", },
render: Template,
}; };
export const Disabled = {
export const Disabled = Template.bind({}); args: {
Disabled.args = { text: "This tooltip is disabled",
text: "This tooltip is disabled", disabled: true,
disabled: true, },
render: Template,
}; };
export const LongText = {
export const LongText = Template.bind({}); args: {
LongText.args = { text: "This is a longer tooltip text that demonstrates how the component handles multiple words and extended content",
text: "This is a longer tooltip text that demonstrates how the component handles multiple words and extended content", position: "top",
position: "top", },
render: Template,
}; };
export const WithIcon = () => ( export const WithIcon = () => (
<div className="p-16 flex items-center justify-center min-h-[200px]"> <div className="p-16 flex items-center justify-center min-h-[200px]">
<Tooltip text="Tooltip with icon button" position="top"> <Tooltip text="Tooltip with icon button" position="top">
+6 -2
View File
@@ -1,4 +1,8 @@
import MenuItem from "../../app/components/navigation/MenuItem"; import MenuItem from "../../app/components/navigation/MenuItem";
import {
MENU_ITEM_MODE_OPTIONS,
MENU_ITEM_SIZE_OPTIONS,
} from "../../lib/propNormalization";
export default { export default {
title: "Components/Navigation/MenuItem", title: "Components/Navigation/MenuItem",
@@ -15,12 +19,12 @@ export default {
argTypes: { argTypes: {
mode: { mode: {
control: { type: "select" }, control: { type: "select" },
options: ["default", "inverse"], options: [...MENU_ITEM_MODE_OPTIONS],
description: "The visual style mode of the menu item", description: "The visual style mode of the menu item",
}, },
size: { size: {
control: { type: "select" }, control: { type: "select" },
options: ["X Small", "Small", "Medium", "Large", "X Large"], options: [...MENU_ITEM_SIZE_OPTIONS],
description: "The size of the menu item", description: "The size of the menu item",
}, },
disabled: { disabled: {
@@ -0,0 +1,9 @@
import { CommunityStructureSelectScreen } from "../../app/(app)/create/screens/select/CommunityStructureSelectScreen";
export default {
title: "Pages/Create Flow/Community structure",
component: CommunityStructureSelectScreen,
parameters: { layout: "fullscreen" },
};
export const Default = {};
@@ -0,0 +1,17 @@
import { ConflictManagementScreen } from "../../app/(app)/create/screens/card/ConflictManagementScreen";
export default {
title: "Pages/Create Flow/Conflict management",
component: ConflictManagementScreen,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Conflict management step: card stack, customize modals, responsive layout.",
},
},
},
};
export const Default = {};
+9
View File
@@ -0,0 +1,9 @@
import { CoreValuesSelectScreen } from "../../app/(app)/create/screens/select/CoreValuesSelectScreen";
export default {
title: "Pages/Create Flow/Core values",
component: CoreValuesSelectScreen,
parameters: { layout: "fullscreen" },
};
export const Default = {};
+1 -1
View File
@@ -1,7 +1,7 @@
import { InformationalScreen } from "../../app/(app)/create/screens/informational/InformationalScreen"; import { InformationalScreen } from "../../app/(app)/create/screens/informational/InformationalScreen";
export default { export default {
title: "Pages/Create/Informational", title: "Pages/Create Flow/Informational",
component: InformationalScreen, component: InformationalScreen,
parameters: { layout: "fullscreen" }, parameters: { layout: "fullscreen" },
}; };
@@ -0,0 +1,17 @@
import { MembershipMethodsScreen } from "../../app/(app)/create/screens/card/MembershipMethodsScreen";
export default {
title: "Pages/Create Flow/Membership methods",
component: MembershipMethodsScreen,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Membership methods step: card stack, customize modals, responsive layout.",
},
},
},
};
export const Default = {};
@@ -0,0 +1,67 @@
import { useEffect, useRef } from "react";
import { PublishedStakeholdersManagePanel } from "../../app/(app)/create/screens/select/PublishedStakeholdersManagePanel";
function StakeholdersFetchMock({ children }) {
const origRef = useRef(undefined);
if (origRef.current === undefined) {
origRef.current = globalThis.fetch;
globalThis.fetch = async (input, init) => {
const url =
typeof input === "string"
? input
: input instanceof URL
? input.href
: input.url;
if (url.includes("/stakeholders")) {
if (init?.method && init.method !== "GET") {
return new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return new Response(JSON.stringify({ stakeholders: [] }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}
return origRef.current(input, init);
};
}
useEffect(() => {
return () => {
if (origRef.current) {
globalThis.fetch = origRef.current;
}
};
}, []);
return children;
}
export default {
title: "Pages/Create Flow/Published stakeholders",
component: PublishedStakeholdersManagePanel,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Manage invites on a published rule. Fetch is stubbed empty in this story.",
},
},
},
decorators: [
(Story) => (
<StakeholdersFetchMock>
<div className="min-h-screen bg-[var(--color-surface-default-primary)] p-8">
<Story />
</div>
</StakeholdersFetchMock>
),
],
};
export const Default = {
args: {
ruleId: "storybook-rule",
},
};
+1 -1
View File
@@ -1,7 +1,7 @@
import { CommunityReviewScreen } from "../../app/(app)/create/screens/review/CommunityReviewScreen"; import { CommunityReviewScreen } from "../../app/(app)/create/screens/review/CommunityReviewScreen";
export default { export default {
title: "Pages/Create/Review", title: "Pages/Create Flow/Review",
component: CommunityReviewScreen, component: CommunityReviewScreen,
parameters: { layout: "fullscreen" }, parameters: { layout: "fullscreen" },
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
import { CommunitySizeSelectScreen } from "../../app/(app)/create/screens/select/CommunitySizeSelectScreen"; import { CommunitySizeSelectScreen } from "../../app/(app)/create/screens/select/CommunitySizeSelectScreen";
export default { export default {
title: "Pages/Create/CommunitySize", title: "Pages/Create Flow/Community size",
component: CommunitySizeSelectScreen, component: CommunitySizeSelectScreen,
parameters: { layout: "fullscreen" }, parameters: { layout: "fullscreen" },
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
import { CreateFlowTextFieldScreen } from "../../app/(app)/create/screens/text/CreateFlowTextFieldScreen"; import { CreateFlowTextFieldScreen } from "../../app/(app)/create/screens/text/CreateFlowTextFieldScreen";
export default { export default {
title: "Pages/Create/CommunityName", title: "Pages/Create Flow/Community name",
component: CreateFlowTextFieldScreen, component: CreateFlowTextFieldScreen,
parameters: { layout: "fullscreen" }, parameters: { layout: "fullscreen" },
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
import { CommunityUploadScreen } from "../../app/(app)/create/screens/upload/CommunityUploadScreen"; import { CommunityUploadScreen } from "../../app/(app)/create/screens/upload/CommunityUploadScreen";
export default { export default {
title: "Pages/Create/CommunityUpload", title: "Pages/Create Flow/Community upload",
component: CommunityUploadScreen, component: CommunityUploadScreen,
parameters: { layout: "fullscreen" }, parameters: { layout: "fullscreen" },
}; };
+38
View File
@@ -0,0 +1,38 @@
import FaqAccordion from "../../app/components/sections/Accordion";
import about from "../../messages/en/pages/about.json";
export default {
title: "Components/Sections/Accordion",
component: FaqAccordion,
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"About-page FAQ wrapper over layout/Accordion. Sizes: s below lg, m at lg, l at xl.",
},
},
},
argTypes: {
title: { control: { type: "text" } },
size: {
control: { type: "select" },
options: ["s", "m", "l"],
},
lgSize: {
control: { type: "select" },
options: ["s", "m", "l"],
},
xlSize: {
control: { type: "select" },
options: ["s", "m", "l"],
},
},
};
export const Default = {
args: {
title: about.faq.title,
items: about.faq.items,
},
};
@@ -1,248 +0,0 @@
import HeroBanner from "../../app/components/sections/HeroBanner";
import ContentLockup from "../../app/components/type/ContentLockup";
import HeroDecor from "../../app/components/sections/HeroBanner/HeroDecor";
export default {
title: "Systems/HeroBanner System",
parameters: {
layout: "fullscreen",
docs: {
description: {
component:
"Complete HeroBanner system showcasing all nested components working together. This demonstrates the full responsive behavior and component integration.",
},
},
},
tags: ["autodocs"],
};
export const CompleteSystem = {
render: () => (
<div className="min-h-screen bg-gray-50">
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
),
parameters: {
docs: {
description: {
story:
"Complete HeroBanner system with all components integrated. Resize your browser to see responsive behavior across all breakpoints.",
},
},
},
};
export const ComponentBreakdown = {
render: () => (
<div className="space-y-12 p-8">
<div>
<h2 className="text-2xl font-bold mb-6">HeroBanner Components</h2>
<div className="space-y-8">
<div>
<h3 className="text-lg font-semibold mb-4">
1. ContentLockup Component
</h3>
<div className="bg-[var(--color-surface-default-brand-primary)] p-8 rounded-lg">
<ContentLockup
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-4">
2. HeroDecor Component
</h3>
<div className="bg-[var(--color-surface-default-brand-primary)] p-8 rounded-lg relative overflow-hidden h-64">
<HeroDecor className="w-full h-full" />
<div className="relative z-10 text-white mt-4">
<p>Decoration appears behind content</p>
</div>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-4">
3. Complete HeroBanner
</h3>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
</div>
</div>
),
parameters: {
docs: {
description: {
story:
"Breakdown of individual components that make up the HeroBanner system, showing how they work together.",
},
},
},
};
export const ResponsiveBreakpoints = {
render: () => (
<div className="space-y-8 p-8">
<h2 className="text-2xl font-bold">Responsive Breakpoints</h2>
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-2">XSmall (429px)</h3>
<div
className="border-2 border-gray-300 rounded-lg overflow-hidden"
style={{ width: "400px" }}
>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">Small (430px+)</h3>
<div
className="border-2 border-gray-300 rounded-lg overflow-hidden"
style={{ width: "600px" }}
>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">Medium (768px+)</h3>
<div
className="border-2 border-gray-300 rounded-lg overflow-hidden"
style={{ width: "900px" }}
>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">Large (1024px+)</h3>
<div
className="border-2 border-gray-300 rounded-lg overflow-hidden"
style={{ width: "1200px" }}
>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">XLarge (1440px+)</h3>
<div
className="border-2 border-gray-300 rounded-lg overflow-hidden"
style={{ width: "1600px" }}
>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
</div>
</div>
),
parameters: {
docs: {
description: {
story:
"HeroBanner system demonstrating responsive behavior at each breakpoint. Each container simulates a different screen size.",
},
},
},
};
export const ContentVariations = {
render: () => (
<div className="space-y-8 p-8">
<h2 className="text-2xl font-bold">Content Variations</h2>
<div className="space-y-6">
<div>
<h3 className="text-lg font-semibold mb-2">Standard Content</h3>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">Alternative Content</h3>
<HeroBanner
title="Build"
subtitle="better communities"
description="Create operating manuals that help your community thrive and make decisions together."
ctaText="Get started today"
ctaHref="/signup"
/>
</div>
<div>
<h3 className="text-lg font-semibold mb-2">Long Description</h3>
<HeroBanner
title="Collaborate"
subtitle="with clarity"
description="Help your community make important decisions in a way that reflects its unique values. Our platform provides the tools and frameworks needed to build successful, sustainable communities that can navigate complex challenges together."
ctaText="Learn how Community Rule works"
ctaHref="#"
/>
</div>
</div>
</div>
),
parameters: {
docs: {
description: {
story:
"HeroBanner system with different content variations to demonstrate flexibility and content handling.",
},
},
},
};
+2 -1
View File
@@ -1,4 +1,5 @@
import QuoteBlock from "../../app/components/sections/QuoteBlock"; import QuoteBlock from "../../app/components/sections/QuoteBlock";
import { QUOTE_BLOCK_VARIANT_OPTIONS } from "../../lib/propNormalization";
export default { export default {
title: "Components/Sections/QuoteBlock", title: "Components/Sections/QuoteBlock",
@@ -34,7 +35,7 @@ A responsive quote section component that displays inspirational governance quot
argTypes: { argTypes: {
variant: { variant: {
control: { type: "select" }, control: { type: "select" },
options: ["compact", "standard", "extended", "statement"], options: [...QUOTE_BLOCK_VARIANT_OPTIONS],
description: "Layout variant for different use cases", description: "Layout variant for different use cases",
}, },
quote: { quote: {
+6 -6
View File
@@ -1,23 +1,23 @@
import HeaderLockup from "../../app/components/type/HeaderLockup"; import HeaderLockup from "../../app/components/type/HeaderLockup";
import {
HEADER_LOCKUP_JUSTIFICATION_OPTIONS,
HEADER_LOCKUP_SIZE_OPTIONS,
} from "../../lib/propNormalization";
export default { export default {
title: "Components/Type/HeaderLockup", title: "Components/Type/HeaderLockup",
component: HeaderLockup, component: HeaderLockup,
parameters: { parameters: {
layout: "centered", layout: "centered",
backgrounds: {
default: "dark",
values: [{ name: "dark", value: "#000000" }],
},
}, },
argTypes: { argTypes: {
justification: { justification: {
control: { type: "select" }, control: { type: "select" },
options: ["left", "center", "Left", "Center"], options: [...HEADER_LOCKUP_JUSTIFICATION_OPTIONS],
}, },
size: { size: {
control: { type: "select" }, control: { type: "select" },
options: ["L", "M", "l", "m"], options: [...HEADER_LOCKUP_SIZE_OPTIONS],
}, },
}, },
}; };
+77
View File
@@ -0,0 +1,77 @@
import InputLabel from "../../app/components/type/InputLabel";
import {
INPUT_LABEL_PALETTE_OPTIONS,
INPUT_LABEL_SIZE_OPTIONS,
} from "../../lib/propNormalization";
export default {
title: "Components/Type/InputLabel",
component: InputLabel,
parameters: {
layout: "centered",
docs: {
description: {
component:
"Reusable form-input label with optional asterisk, help icon, and helper text. Figma Utility / InputLabel; canonical code under type/.",
},
},
},
argTypes: {
label: { control: { type: "text" } },
helpIcon: { control: { type: "boolean" } },
asterisk: { control: { type: "boolean" } },
helperText: { control: { type: "boolean" } },
size: {
control: { type: "select" },
options: [...INPUT_LABEL_SIZE_OPTIONS],
},
palette: {
control: { type: "select" },
options: [...INPUT_LABEL_PALETTE_OPTIONS],
},
},
};
export const Default = {
args: {
label: "Community name",
size: "s",
palette: "default",
},
};
export const Required = {
args: {
label: "Community name",
asterisk: true,
size: "s",
palette: "default",
},
};
export const WithHelp = {
args: {
label: "Applicable scope",
helpIcon: true,
size: "m",
palette: "default",
},
};
export const OptionalHelper = {
args: {
label: "Website",
helperText: true,
size: "s",
palette: "default",
},
};
export const Inverse = {
args: {
label: "Community name",
asterisk: true,
size: "m",
palette: "inverse",
},
};
+2 -5
View File
@@ -1,19 +1,16 @@
import NumberedList from "../../app/components/type/NumberedList"; import NumberedList from "../../app/components/type/NumberedList";
import { NUMBERED_LIST_SIZE_OPTIONS } from "../../lib/propNormalization";
export default { export default {
title: "Components/Type/NumberedList", title: "Components/Type/NumberedList",
component: NumberedList, component: NumberedList,
parameters: { parameters: {
layout: "centered", layout: "centered",
backgrounds: {
default: "dark",
values: [{ name: "dark", value: "#000000" }],
},
}, },
argTypes: { argTypes: {
size: { size: {
control: { type: "select" }, control: { type: "select" },
options: ["M", "S", "m", "s"], options: [...NUMBERED_LIST_SIZE_OPTIONS],
}, },
}, },
}; };