Merge pull request 'Catch up Storybook preview, coverage, and story hygiene' (#67) from adilallo/maintenance/StorybookCatchup into main
Reviewed-on: #67
This commit was merged in pull request #67.
This commit is contained in:
+22
-16
@@ -28,16 +28,17 @@ Do **not** colocate `*.stories.*` next to components. The Storybook config
|
||||
|
||||
# File naming
|
||||
|
||||
- `<ComponentName>.stories.js` — matches 69/70 existing files.
|
||||
- Use `.tsx` only when the story genuinely needs types (rare; prefer JS to
|
||||
match the codebase convention).
|
||||
- `<ComponentName>.stories.js` — default; keep `.js` unless the story
|
||||
genuinely needs types.
|
||||
- Use `.tsx` only when the story uses `Meta` / `StoryObj` (rare).
|
||||
- Variants get a suffix: `Button.visual.stories.js`,
|
||||
`Footer.responsive.stories.js`.
|
||||
|
||||
# Default export shape (CSF2)
|
||||
# Default export shape (CSF3)
|
||||
|
||||
```javascript
|
||||
import MyComponent from "../../app/components/<area>/MyComponent";
|
||||
import { CHIP_PALETTE_OPTIONS } from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/<SubFolder>/MyComponent",
|
||||
@@ -51,31 +52,32 @@ export default {
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
palette: {
|
||||
control: { type: "select" },
|
||||
options: ["filled", "outline"],
|
||||
description: "The variant (Figma prop)",
|
||||
options: [...CHIP_PALETTE_OPTIONS],
|
||||
description: "The palette (Figma prop)",
|
||||
},
|
||||
onClick: { action: "clicked" },
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = { args: { variant: "filled" } };
|
||||
export const Default = { args: { palette: "default" } };
|
||||
```
|
||||
|
||||
## Title hierarchy
|
||||
|
||||
- Design-system components → `Components/<SubFolder>/<Name>` (e.g.
|
||||
`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>`.
|
||||
|
||||
## `argTypes`
|
||||
|
||||
For every Figma enum prop (`variant`, `size`, `state`, `mode`, `palette`,
|
||||
…) expose a `select` control listing the **lowercase** option set, sourced
|
||||
from the matching `*_OPTIONS` const in `lib/propNormalization.ts`. See
|
||||
`.cursor/rules/component-props.mdc`.
|
||||
…) expose a `select` control listing the option set, sourced from the matching
|
||||
`*_OPTIONS` const in `lib/propNormalization.ts`. See
|
||||
`.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
|
||||
|
||||
@@ -83,8 +85,12 @@ from the matching `*_OPTIONS` const in `lib/propNormalization.ts`. See
|
||||
|
||||
- `MessagesProvider` with `messages/en` → access copy via `useMessages()`
|
||||
inside stories exactly like app code. Never hard-code user-facing strings.
|
||||
- `app/globals.css` + `.font-inter` wrapper → design tokens and fonts are
|
||||
already present.
|
||||
- `AuthModalProvider` and `CreateFlowProvider` (same stack as
|
||||
`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
|
||||
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`)
|
||||
|
||||
Use `storybook/test` for interaction assertions — not `@testing-library/*`
|
||||
directly. This matches `Checkbox.stories.js` and stays compatible with the
|
||||
Vitest portable-stories runner in `.storybook/vitest.setup.js`.
|
||||
directly. This matches `Checkbox.stories.js`. Storybook is documentation;
|
||||
Vitest component tests remain the source of truth.
|
||||
|
||||
```javascript
|
||||
import { within, userEvent, expect } from "storybook/test";
|
||||
|
||||
@@ -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 {
|
||||
--font-inter:
|
||||
"Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
|
||||
"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
@@ -4,32 +4,10 @@ module.exports = {
|
||||
"../stories/**/*.mdx",
|
||||
"../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
|
||||
],
|
||||
addons: [
|
||||
// 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",
|
||||
],
|
||||
addons: ["@storybook/addon-docs", "@storybook/addon-a11y"],
|
||||
framework: {
|
||||
name: "@storybook/nextjs",
|
||||
options: {},
|
||||
},
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,43 @@
|
||||
import "../app/globals.css";
|
||||
import "./fonts.css";
|
||||
import { MINIMAL_VIEWPORTS } from "storybook/viewport";
|
||||
import { AuthModalProvider } from "../app/contexts/AuthModalContext";
|
||||
import { MessagesProvider } from "../app/contexts/MessagesContext";
|
||||
import { CreateFlowProvider } from "../app/(app)/create/context/CreateFlowContext";
|
||||
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 } */
|
||||
const preview = {
|
||||
tags: ["autodocs"],
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
@@ -12,13 +45,32 @@ const preview = {
|
||||
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: [
|
||||
(Story) => (
|
||||
<MessagesProvider messages={messages}>
|
||||
<AuthModalProvider>
|
||||
<CreateFlowProvider>
|
||||
<div className="font-inter">
|
||||
<Story />
|
||||
</div>
|
||||
</CreateFlowProvider>
|
||||
</AuthModalProvider>
|
||||
</MessagesProvider>
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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]);
|
||||
@@ -25,7 +25,6 @@
|
||||
"@types/mdx",
|
||||
"eslint-config-next",
|
||||
"typescript-eslint",
|
||||
"@storybook/nextjs-vite",
|
||||
"@eslint/js",
|
||||
"@next/eslint-plugin-next",
|
||||
"eslint-plugin-react",
|
||||
|
||||
Generated
+82
-1
@@ -30,6 +30,7 @@
|
||||
"@lhci/cli": "^0.15.1",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@storybook/addon-a11y": "^10.2.0",
|
||||
"@storybook/addon-docs": "^10.2.0",
|
||||
"@storybook/nextjs": "^10.2.0",
|
||||
"@storybook/react": "^10.2.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
@@ -5911,6 +5912,35 @@
|
||||
"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": {
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-10.4.1.tgz",
|
||||
@@ -6017,6 +6047,41 @@
|
||||
"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": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/global/-/global-5.0.0.tgz",
|
||||
@@ -6192,7 +6257,7 @@
|
||||
"webpack": ">= 4"
|
||||
}
|
||||
},
|
||||
"node_modules/@storybook/react/node_modules/@storybook/react-dom-shim": {
|
||||
"node_modules/@storybook/react-dom-shim": {
|
||||
"version": "10.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.4.1.tgz",
|
||||
"integrity": "sha512-6QFqfDNH4DMrt7yHKRfpqRopsVUc/Az+sXIdJ39IetYnHUxL3nW4NVaPc6uy/8Qi8urzUyEXL/nn7cpSIP2aPQ==",
|
||||
@@ -23811,6 +23876,22 @@
|
||||
"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": {
|
||||
"version": "1.12.2",
|
||||
"resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz",
|
||||
|
||||
+1
-2
@@ -17,9 +17,7 @@
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 9999",
|
||||
"postinstall": "npm rebuild lightningcss && prisma generate",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"storybook:github": "STORYBOOK_BASE_PATH=true storybook dev -p 6006",
|
||||
"storybook:build": "storybook build",
|
||||
"storybook:build:github": "STORYBOOK_BASE_PATH=true storybook build",
|
||||
"knip": "knip --include files,exports --exclude duplicates",
|
||||
"test": "vitest run --coverage",
|
||||
"test:watch": "vitest",
|
||||
@@ -69,6 +67,7 @@
|
||||
"@lhci/cli": "^0.15.1",
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@storybook/addon-a11y": "^10.2.0",
|
||||
"@storybook/addon-docs": "^10.2.0",
|
||||
"@storybook/nextjs": "^10.2.0",
|
||||
"@storybook/react": "^10.2.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import Button from "../../app/components/buttons/Button";
|
||||
import {
|
||||
BUTTON_PALETTE_OPTIONS,
|
||||
BUTTON_TYPE_OPTIONS,
|
||||
SIZE_OPTIONS,
|
||||
} from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Buttons/Button",
|
||||
@@ -15,17 +20,17 @@ export default {
|
||||
argTypes: {
|
||||
buttonType: {
|
||||
control: { type: "select" },
|
||||
options: ["filled", "outline", "ghost", "danger"],
|
||||
options: [...BUTTON_TYPE_OPTIONS],
|
||||
description: "The button type (Figma prop)",
|
||||
},
|
||||
palette: {
|
||||
control: { type: "select" },
|
||||
options: ["default", "inverse"],
|
||||
options: [...BUTTON_PALETTE_OPTIONS],
|
||||
description: "The button palette (Figma prop)",
|
||||
},
|
||||
size: {
|
||||
control: { type: "select" },
|
||||
options: ["xsmall", "small", "medium", "large", "xlarge"],
|
||||
options: [...SIZE_OPTIONS],
|
||||
description: "The size of the button",
|
||||
},
|
||||
disabled: {
|
||||
|
||||
@@ -33,7 +33,7 @@ export const Default = {
|
||||
|
||||
export const InParagraph = {
|
||||
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{" "}
|
||||
<InlineTextButton onClick={() => {}}>expand this later</InlineTextButton>{" "}
|
||||
if you need more room.
|
||||
|
||||
@@ -37,10 +37,10 @@ export const Default = {
|
||||
<Icon
|
||||
name="number"
|
||||
size={32}
|
||||
className="text-[var(--color-content-default-brand-primary,#fefcc9)]"
|
||||
className="text-[var(--color-content-default-brand-primary)]"
|
||||
/>
|
||||
</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
|
||||
</span>
|
||||
</Vertical>
|
||||
@@ -54,10 +54,10 @@ export const Disabled = {
|
||||
<Icon
|
||||
name="number"
|
||||
size={32}
|
||||
className="text-[var(--color-content-default-brand-primary,#fefcc9)]"
|
||||
className="text-[var(--color-content-default-brand-primary)]"
|
||||
/>
|
||||
</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
|
||||
</span>
|
||||
</Vertical>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import React, { useState } from "react";
|
||||
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). */
|
||||
export default {
|
||||
@@ -9,11 +7,9 @@ export default {
|
||||
component: AddCustomField,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<MessagesProvider messages={messages}>
|
||||
<div className="w-[min(100%,546px)] bg-[var(--color-surface-default-primary)] p-6">
|
||||
<Story />
|
||||
</div>
|
||||
</MessagesProvider>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import Chip from "../../app/components/controls/Chip";
|
||||
import {
|
||||
CHIP_PALETTE_OPTIONS,
|
||||
CHIP_SIZE_OPTIONS,
|
||||
CHIP_STATE_OPTIONS,
|
||||
} from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Controls/Chip",
|
||||
@@ -13,17 +18,17 @@ export default {
|
||||
},
|
||||
state: {
|
||||
control: "select",
|
||||
options: ["unselected", "selected", "disabled", "custom"],
|
||||
options: [...CHIP_STATE_OPTIONS],
|
||||
description: "Visual state of the chip",
|
||||
},
|
||||
palette: {
|
||||
control: "select",
|
||||
options: ["default", "inverse"],
|
||||
options: [...CHIP_PALETTE_OPTIONS],
|
||||
description: "Color palette of the chip",
|
||||
},
|
||||
size: {
|
||||
control: "select",
|
||||
options: ["s", "m"],
|
||||
options: [...CHIP_SIZE_OPTIONS],
|
||||
description: "Size of the chip",
|
||||
},
|
||||
disabled: {
|
||||
|
||||
@@ -44,43 +44,46 @@ const Template = (args) => {
|
||||
};
|
||||
|
||||
// Default story
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
label: "Default Select Input",
|
||||
placeholder: "Choose an option",
|
||||
state: "default",
|
||||
};
|
||||
|
||||
// States
|
||||
export const Active = Template.bind({});
|
||||
Active.args = {
|
||||
},
|
||||
render: Template,
|
||||
}; // States
|
||||
export const Active = {
|
||||
args: {
|
||||
label: "Active State",
|
||||
placeholder: "Choose an option",
|
||||
state: "active",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Focus = Template.bind({});
|
||||
Focus.args = {
|
||||
export const Focus = {
|
||||
args: {
|
||||
label: "Focus State",
|
||||
placeholder: "Choose an option",
|
||||
state: "focus",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Error = Template.bind({});
|
||||
Error.args = {
|
||||
export const Error = {
|
||||
args: {
|
||||
label: "Error State",
|
||||
placeholder: "Choose an option",
|
||||
error: true,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
export const Disabled = {
|
||||
args: {
|
||||
label: "Disabled State",
|
||||
placeholder: "Choose an option",
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
// Interactive example
|
||||
},
|
||||
render: Template,
|
||||
}; // Interactive example
|
||||
export const Interactive = (args) => {
|
||||
const [value, setValue] = useState("");
|
||||
|
||||
|
||||
@@ -39,32 +39,36 @@ export default {
|
||||
|
||||
const Template = (args) => <Switch {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
propSwitch: false,
|
||||
text: "Switch label",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Checked = Template.bind({});
|
||||
Checked.args = {
|
||||
export const Checked = {
|
||||
args: {
|
||||
propSwitch: true,
|
||||
text: "Switch label",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Focus = Template.bind({});
|
||||
Focus.args = {
|
||||
export const Focus = {
|
||||
args: {
|
||||
propSwitch: false,
|
||||
state: "focus",
|
||||
text: "Switch label",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const FocusChecked = Template.bind({});
|
||||
FocusChecked.args = {
|
||||
export const FocusChecked = {
|
||||
args: {
|
||||
propSwitch: true,
|
||||
state: "focus",
|
||||
text: "Switch label",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const States = () => (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import React from "react";
|
||||
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 {
|
||||
title: "Components/Controls/TextArea",
|
||||
@@ -10,15 +16,19 @@ export default {
|
||||
argTypes: {
|
||||
size: {
|
||||
control: { type: "select" },
|
||||
options: ["small", "medium", "large"],
|
||||
options: [...SMALL_MEDIUM_LARGE_OPTIONS],
|
||||
},
|
||||
labelVariant: {
|
||||
control: { type: "select" },
|
||||
options: ["default", "horizontal"],
|
||||
options: [...LABEL_VARIANT_OPTIONS],
|
||||
},
|
||||
appearance: {
|
||||
control: { type: "select" },
|
||||
options: [...TEXT_AREA_APPEARANCE_OPTIONS],
|
||||
},
|
||||
state: {
|
||||
control: { type: "select" },
|
||||
options: ["default", "active", "hover", "focus", "error"],
|
||||
options: [...INPUT_STATE_OPTIONS],
|
||||
},
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
@@ -31,47 +41,52 @@ export default {
|
||||
|
||||
const Template = (args) => <TextArea {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
label: "Text Area",
|
||||
placeholder: "Enter text...",
|
||||
value: "",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithValue = Template.bind({});
|
||||
WithValue.args = {
|
||||
export const WithValue = {
|
||||
args: {
|
||||
label: "Text Area",
|
||||
placeholder: "Enter text...",
|
||||
value:
|
||||
"This is some sample text content that demonstrates how the text area looks with content.",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Small = Template.bind({});
|
||||
Small.args = {
|
||||
export const Small = {
|
||||
args: {
|
||||
size: "small",
|
||||
label: "Small Text Area",
|
||||
placeholder: "Enter text...",
|
||||
value: "",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Medium = Template.bind({});
|
||||
Medium.args = {
|
||||
export const Medium = {
|
||||
args: {
|
||||
size: "medium",
|
||||
label: "Medium Text Area",
|
||||
placeholder: "Enter text...",
|
||||
value: "",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Large = Template.bind({});
|
||||
Large.args = {
|
||||
export const Large = {
|
||||
args: {
|
||||
size: "large",
|
||||
label: "Large Text Area",
|
||||
placeholder: "Enter text...",
|
||||
value: "",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Embedded = Template.bind({});
|
||||
Embedded.args = {
|
||||
export const Embedded = {
|
||||
args: {
|
||||
label: "Section content",
|
||||
placeholder: "Enter text...",
|
||||
value:
|
||||
@@ -79,16 +94,18 @@ Embedded.args = {
|
||||
appearance: "embedded",
|
||||
size: "large",
|
||||
rows: 4,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const HorizontalLabel = Template.bind({});
|
||||
HorizontalLabel.args = {
|
||||
export const HorizontalLabel = {
|
||||
args: {
|
||||
labelVariant: "horizontal",
|
||||
label: "Horizontal Label",
|
||||
placeholder: "Enter text...",
|
||||
value: "",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const AllSizes = () => (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-4">
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from "react";
|
||||
import TextInput from "../../app/components/controls/TextInput";
|
||||
import {
|
||||
INPUT_STATE_OPTIONS,
|
||||
TEXT_INPUT_SIZE_OPTIONS,
|
||||
} from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Controls/TextInput",
|
||||
@@ -10,11 +14,11 @@ export default {
|
||||
argTypes: {
|
||||
inputSize: {
|
||||
control: { type: "select" },
|
||||
options: ["small", "medium", "Small", "Medium"],
|
||||
options: [...TEXT_INPUT_SIZE_OPTIONS],
|
||||
},
|
||||
state: {
|
||||
control: { type: "select" },
|
||||
options: ["default", "active", "hover", "focus", "error", "disabled"],
|
||||
options: [...INPUT_STATE_OPTIONS],
|
||||
},
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
@@ -37,75 +41,80 @@ export default {
|
||||
const Template = (args) => <TextInput {...args} />;
|
||||
|
||||
// Default story
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
label: "Default Text Input",
|
||||
placeholder: "Enter text...",
|
||||
inputSize: "medium",
|
||||
state: "default",
|
||||
};
|
||||
|
||||
// Size variants
|
||||
export const Small = Template.bind({});
|
||||
Small.args = {
|
||||
},
|
||||
render: Template,
|
||||
}; // Size variants
|
||||
export const Small = {
|
||||
args: {
|
||||
label: "Small Text Input",
|
||||
placeholder: "Small size",
|
||||
inputSize: "small",
|
||||
state: "default",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Medium = Template.bind({});
|
||||
Medium.args = {
|
||||
export const Medium = {
|
||||
args: {
|
||||
label: "Medium Text Input",
|
||||
placeholder: "Medium size",
|
||||
inputSize: "medium",
|
||||
state: "default",
|
||||
};
|
||||
|
||||
// States
|
||||
export const Active = Template.bind({});
|
||||
Active.args = {
|
||||
},
|
||||
render: Template,
|
||||
}; // States
|
||||
export const Active = {
|
||||
args: {
|
||||
label: "Active State",
|
||||
placeholder: "Active input",
|
||||
inputSize: "medium",
|
||||
state: "active",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Hover = Template.bind({});
|
||||
Hover.args = {
|
||||
export const Hover = {
|
||||
args: {
|
||||
label: "Hover State",
|
||||
placeholder: "Hover input",
|
||||
inputSize: "medium",
|
||||
state: "hover",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Focus = Template.bind({});
|
||||
Focus.args = {
|
||||
export const Focus = {
|
||||
args: {
|
||||
label: "Focus State",
|
||||
placeholder: "Focused input",
|
||||
inputSize: "medium",
|
||||
state: "focus",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Error = Template.bind({});
|
||||
Error.args = {
|
||||
export const Error = {
|
||||
args: {
|
||||
label: "Error State",
|
||||
placeholder: "Error input",
|
||||
inputSize: "medium",
|
||||
state: "default",
|
||||
error: true,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
export const Disabled = {
|
||||
args: {
|
||||
label: "Disabled State",
|
||||
placeholder: "Disabled input",
|
||||
inputSize: "medium",
|
||||
state: "default",
|
||||
disabled: true,
|
||||
};
|
||||
|
||||
// Interactive example
|
||||
},
|
||||
render: Template,
|
||||
}; // Interactive example
|
||||
export const Interactive = (args) => {
|
||||
const [value, setValue] = React.useState("");
|
||||
|
||||
|
||||
@@ -44,22 +44,24 @@ export const States = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export const WithText = Template.bind({});
|
||||
WithText.args = {
|
||||
export const WithText = {
|
||||
args: {
|
||||
label: "Text Toggle",
|
||||
checked: false,
|
||||
showText: true,
|
||||
text: "Toggle",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithIcon = Template.bind({});
|
||||
WithIcon.args = {
|
||||
export const WithIcon = {
|
||||
args: {
|
||||
label: "Icon Toggle",
|
||||
checked: false,
|
||||
showIcon: true,
|
||||
icon: "I",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Interactive = () => {
|
||||
const [checked, setChecked] = React.useState(false);
|
||||
const [state, setState] = React.useState("default");
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import React from "react";
|
||||
import ToggleGroup from "../../app/components/controls/ToggleGroup";
|
||||
import {
|
||||
STATE_OPTIONS,
|
||||
TOGGLE_GROUP_POSITION_OPTIONS,
|
||||
} from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Controls/ToggleGroup",
|
||||
@@ -10,11 +14,11 @@ export default {
|
||||
argTypes: {
|
||||
position: {
|
||||
control: { type: "select" },
|
||||
options: ["left", "middle", "right"],
|
||||
options: [...TOGGLE_GROUP_POSITION_OPTIONS],
|
||||
},
|
||||
state: {
|
||||
control: { type: "select" },
|
||||
options: ["default", "hover", "focus", "selected"],
|
||||
options: [...STATE_OPTIONS],
|
||||
},
|
||||
showText: {
|
||||
control: { type: "boolean" },
|
||||
@@ -24,27 +28,30 @@ export default {
|
||||
|
||||
const Template = (args) => <ToggleGroup {...args}>Toggle Item</ToggleGroup>;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
position: "left",
|
||||
state: "default",
|
||||
showText: true,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Middle = Template.bind({});
|
||||
Middle.args = {
|
||||
export const Middle = {
|
||||
args: {
|
||||
position: "middle",
|
||||
state: "default",
|
||||
showText: true,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Right = Template.bind({});
|
||||
Right.args = {
|
||||
export const Right = {
|
||||
args: {
|
||||
position: "right",
|
||||
state: "default",
|
||||
showText: true,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const States = () => (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
@@ -89,22 +96,24 @@ export const Positions = () => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export const WithText = Template.bind({});
|
||||
WithText.args = {
|
||||
export const WithText = {
|
||||
args: {
|
||||
position: "left",
|
||||
state: "default",
|
||||
showText: true,
|
||||
children: "Active Deals",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithoutText = Template.bind({});
|
||||
WithoutText.args = {
|
||||
export const WithoutText = {
|
||||
args: {
|
||||
position: "left",
|
||||
state: "default",
|
||||
showText: false,
|
||||
children: "☰",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithIcons = () => (
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
|
||||
@@ -34,87 +34,97 @@ export default {
|
||||
const Template = (args) => <Upload {...args} />;
|
||||
|
||||
// Default story
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
label: "Upload",
|
||||
active: true,
|
||||
showHelpIcon: true,
|
||||
};
|
||||
|
||||
// Active state
|
||||
export const Active = Template.bind({});
|
||||
Active.args = {
|
||||
},
|
||||
render: Template,
|
||||
}; // Active state
|
||||
export const Active = {
|
||||
args: {
|
||||
label: "Upload",
|
||||
active: true,
|
||||
showHelpIcon: true,
|
||||
};
|
||||
Active.parameters = {
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Upload component in active state with white button and black text.",
|
||||
},
|
||||
},
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
// Inactive state
|
||||
export const Inactive = Template.bind({});
|
||||
Inactive.args = {
|
||||
export const Inactive = {
|
||||
args: {
|
||||
label: "Upload",
|
||||
active: false,
|
||||
showHelpIcon: true,
|
||||
};
|
||||
Inactive.parameters = {
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
"Upload component in inactive state with dark button and gray text.",
|
||||
},
|
||||
},
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
// Without help icon
|
||||
export const WithoutHelpIcon = Template.bind({});
|
||||
WithoutHelpIcon.args = {
|
||||
export const WithoutHelpIcon = {
|
||||
args: {
|
||||
label: "Upload",
|
||||
active: true,
|
||||
showHelpIcon: false,
|
||||
};
|
||||
WithoutHelpIcon.parameters = {
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Upload component without help icon.",
|
||||
},
|
||||
},
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
// Without label
|
||||
export const WithoutLabel = Template.bind({});
|
||||
WithoutLabel.args = {
|
||||
export const WithoutLabel = {
|
||||
args: {
|
||||
active: true,
|
||||
showHelpIcon: false,
|
||||
};
|
||||
WithoutLabel.parameters = {
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Upload component without label.",
|
||||
},
|
||||
},
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
// Custom label
|
||||
export const CustomLabel = Template.bind({});
|
||||
CustomLabel.args = {
|
||||
export const CustomLabel = {
|
||||
args: {
|
||||
label: "Upload Files",
|
||||
active: true,
|
||||
showHelpIcon: true,
|
||||
};
|
||||
CustomLabel.parameters = {
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story: "Upload component with custom label text.",
|
||||
},
|
||||
},
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
// All states comparison
|
||||
|
||||
+16
-4
@@ -1,16 +1,19 @@
|
||||
import React from "react";
|
||||
import HeaderLockup from "../../app/components/type/HeaderLockup";
|
||||
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
|
||||
* plus bordered checklist — no standalone wrapper component.
|
||||
*/
|
||||
export default {
|
||||
title: "Components/Controls/Rail header (lockup + info box)",
|
||||
title: "Create Flow/Rail header (lockup + info box)",
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
backgrounds: { default: "dark" },
|
||||
docs: {
|
||||
description: {
|
||||
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: [
|
||||
(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 />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
tags: ["autodocs"],
|
||||
};
|
||||
|
||||
const messageItems = [
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -42,119 +42,132 @@ const Template = (args) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const ToastDefault = Template.bind({});
|
||||
ToastDefault.args = {
|
||||
export const ToastDefault = {
|
||||
args: {
|
||||
title: "Short alert toast message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "default",
|
||||
type: "toast",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const ToastPositive = Template.bind({});
|
||||
ToastPositive.args = {
|
||||
export const ToastPositive = {
|
||||
args: {
|
||||
title: "Short alert toast message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "positive",
|
||||
type: "toast",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const ToastWarning = Template.bind({});
|
||||
ToastWarning.args = {
|
||||
export const ToastWarning = {
|
||||
args: {
|
||||
title: "Short alert toast message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "warning",
|
||||
type: "toast",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const ToastDanger = Template.bind({});
|
||||
ToastDanger.args = {
|
||||
export const ToastDanger = {
|
||||
args: {
|
||||
title: "Short alert toast message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "danger",
|
||||
type: "toast",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Banner = Template.bind({});
|
||||
Banner.args = {
|
||||
export const Banner = {
|
||||
args: {
|
||||
title: "Short alert banner message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "default",
|
||||
type: "banner",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const BannerPositive = Template.bind({});
|
||||
BannerPositive.args = {
|
||||
export const BannerPositive = {
|
||||
args: {
|
||||
title: "Short alert banner message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "positive",
|
||||
type: "banner",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const BannerWarning = Template.bind({});
|
||||
BannerWarning.args = {
|
||||
export const BannerWarning = {
|
||||
args: {
|
||||
title: "Short alert banner message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "warning",
|
||||
type: "banner",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const BannerDanger = Template.bind({});
|
||||
BannerDanger.args = {
|
||||
export const BannerDanger = {
|
||||
args: {
|
||||
title: "Short alert banner message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "danger",
|
||||
type: "banner",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const TitleOnly = Template.bind({});
|
||||
TitleOnly.args = {
|
||||
export const TitleOnly = {
|
||||
args: {
|
||||
title: "Short alert banner message goes here",
|
||||
status: "default",
|
||||
type: "toast",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const ToastSmall = Template.bind({});
|
||||
ToastSmall.args = {
|
||||
export const ToastSmall = {
|
||||
args: {
|
||||
title: "Short alert toast message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "default",
|
||||
type: "toast",
|
||||
size: "s",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const BannerSmall = Template.bind({});
|
||||
BannerSmall.args = {
|
||||
export const BannerSmall = {
|
||||
args: {
|
||||
title: "Short alert banner message goes here",
|
||||
description:
|
||||
"Nascetur ipsum a nisi tempor cras nam neque volutpat. Aliquam id est faucibus nunc quis. Eleifend suspendisse.",
|
||||
status: "positive",
|
||||
type: "banner",
|
||||
size: "s",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
const NoDismissTemplate = (args) => (
|
||||
<div className="p-8 max-w-[600px]">
|
||||
<Alert {...args} />
|
||||
</div>
|
||||
);
|
||||
export const BannerNoDismiss = NoDismissTemplate.bind({});
|
||||
BannerNoDismiss.args = {
|
||||
export const BannerNoDismiss = {
|
||||
args: {
|
||||
title: "Non-dismissible banner (no onClose)",
|
||||
description: "Used when the message clears via navigation or parent state only.",
|
||||
description:
|
||||
"Used when the message clears via navigation or parent state only.",
|
||||
status: "danger",
|
||||
type: "banner",
|
||||
size: "s",
|
||||
},
|
||||
render: NoDismissTemplate,
|
||||
};
|
||||
|
||||
export const AllStatuses = () => {
|
||||
const [visible, setVisible] = useState({
|
||||
default: true,
|
||||
|
||||
@@ -54,8 +54,8 @@ const Template = (args) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
title: "What do you call your group's new policy?",
|
||||
description: "You can also combine or add new approaches to the list",
|
||||
@@ -72,10 +72,11 @@ Default.args = {
|
||||
backButtonText: "Back",
|
||||
nextButtonText: "Next",
|
||||
nextButtonDisabled: false,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithStepper = Template.bind({});
|
||||
WithStepper.args = {
|
||||
export const WithStepper = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
title: "What do you call your group's new policy?",
|
||||
description: "You can also combine or add new approaches to the list",
|
||||
@@ -94,10 +95,11 @@ WithStepper.args = {
|
||||
nextButtonDisabled: false,
|
||||
currentStep: 1,
|
||||
totalSteps: 3,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Step2 = Template.bind({});
|
||||
Step2.args = {
|
||||
export const Step2 = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
title: "How should conflicts be resolved?",
|
||||
description: "You can also combine or add new approaches to the list",
|
||||
@@ -113,10 +115,11 @@ Step2.args = {
|
||||
nextButtonDisabled: false,
|
||||
currentStep: 2,
|
||||
totalSteps: 3,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Step3 = Template.bind({});
|
||||
Step3.args = {
|
||||
export const Step3 = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
title: "Final step",
|
||||
description: "Review your settings",
|
||||
@@ -134,10 +137,11 @@ Step3.args = {
|
||||
nextButtonDisabled: false,
|
||||
currentStep: 3,
|
||||
totalSteps: 3,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithCustomHeader = Template.bind({});
|
||||
WithCustomHeader.args = {
|
||||
export const WithCustomHeader = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
headerContent: <div className="text-lg font-semibold">Custom header</div>,
|
||||
children: (
|
||||
@@ -151,10 +155,11 @@ WithCustomHeader.args = {
|
||||
showBackButton: false,
|
||||
showNextButton: true,
|
||||
nextButtonText: "Continue",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithoutFooter = Template.bind({});
|
||||
WithoutFooter.args = {
|
||||
export const WithoutFooter = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
title: "Simple Create Dialog",
|
||||
description: "This create dialog has no footer buttons",
|
||||
@@ -167,13 +172,15 @@ WithoutFooter.args = {
|
||||
),
|
||||
showBackButton: false,
|
||||
showNextButton: false,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const LoginYellowBackdrop = Template.bind({});
|
||||
LoginYellowBackdrop.args = {
|
||||
export const LoginYellowBackdrop = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
title: "Horizontalism",
|
||||
description: "Edit or add to this description to describe what this value means to your community.",
|
||||
description:
|
||||
"Edit or add to this description to describe what this value means to your community.",
|
||||
backdropVariant: "blurredYellow",
|
||||
children: (
|
||||
<div className="space-y-4">
|
||||
@@ -186,10 +193,11 @@ LoginYellowBackdrop.args = {
|
||||
showNextButton: true,
|
||||
nextButtonText: "Add Value",
|
||||
nextButtonDisabled: false,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const NextButtonDisabled = Template.bind({});
|
||||
NextButtonDisabled.args = {
|
||||
export const NextButtonDisabled = {
|
||||
args: {
|
||||
isOpen: true,
|
||||
title: "What do you call your group's new policy?",
|
||||
description: "You can also combine or add new approaches to the list",
|
||||
@@ -208,4 +216,6 @@ NextButtonDisabled.args = {
|
||||
nextButtonDisabled: true,
|
||||
currentStep: 1,
|
||||
totalSteps: 3,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import React, { Suspense, useEffect } from "react";
|
||||
import Login from "../../app/components/modals/Login";
|
||||
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
|
||||
@@ -43,11 +46,11 @@ function FakeMarketingPageBehindOverlay({
|
||||
return (
|
||||
<div className="relative min-h-[100dvh] overflow-hidden">
|
||||
<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
|
||||
/>
|
||||
<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
|
||||
uses backdrop blur (`blurredYellow`).
|
||||
</p>
|
||||
@@ -107,13 +110,13 @@ export const HeaderOverlayBlurred = {
|
||||
belowCard={
|
||||
<a
|
||||
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>
|
||||
}
|
||||
>
|
||||
<Suspense fallback={<p className="font-inter p-6">Loading…</p>}>
|
||||
<Suspense fallback={<p className="p-6 text-small-paragraph">Loading…</p>}>
|
||||
<LoginForm />
|
||||
</Suspense>
|
||||
</Login>
|
||||
@@ -152,13 +155,13 @@ export const FullPageRouteSolid = {
|
||||
belowCard={
|
||||
<a
|
||||
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>
|
||||
}
|
||||
>
|
||||
<Suspense fallback={<p className="font-inter p-6">Loading…</p>}>
|
||||
<Suspense fallback={<p className="p-6 text-small-paragraph">Loading…</p>}>
|
||||
<LoginForm />
|
||||
</Suspense>
|
||||
</Login>
|
||||
@@ -178,15 +181,15 @@ export const ModalChromeOnly = {
|
||||
belowCard={
|
||||
<a
|
||||
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>
|
||||
}
|
||||
>
|
||||
<p
|
||||
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 "Header overlay" or "Full-page
|
||||
route" for the real flow.
|
||||
@@ -215,7 +218,7 @@ export const FormOnly = {
|
||||
],
|
||||
render: () => (
|
||||
<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 />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@@ -1,22 +1,28 @@
|
||||
import React, { useState } from "react";
|
||||
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). */
|
||||
export default {
|
||||
title: "modals/Share",
|
||||
title: "Components/Modals/Share",
|
||||
component: Share,
|
||||
parameters: {
|
||||
layout: "fullscreen",
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
"Share modal for published rules (copy link and channel actions).",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function ShareStoryHost() {
|
||||
const [open, setOpen] = useState(true);
|
||||
return (
|
||||
<MessagesProvider messages={messages}>
|
||||
<div className="min-h-[100dvh] bg-[var(--color-surface-inverse-brand-primary)] p-6">
|
||||
<button
|
||||
type="button"
|
||||
className="rounded-md bg-white px-4 py-2 text-sm text-black"
|
||||
className="rounded-md bg-[var(--color-surface-default-primary)] px-4 py-2 text-small-paragraph text-[var(--color-content-default-primary)]"
|
||||
onClick={() => setOpen(true)}
|
||||
>
|
||||
Open Share
|
||||
@@ -31,11 +37,9 @@ function ShareStoryHost() {
|
||||
onDiscordShare={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</MessagesProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export const Default = {
|
||||
name: "Modal / Share",
|
||||
render: () => <ShareStoryHost />,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import Tooltip from "../../app/components/modals/Tooltip";
|
||||
import Button from "../../app/components/buttons/Button";
|
||||
import { TOOLTIP_POSITION_OPTIONS } from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Modals/Tooltip",
|
||||
@@ -8,7 +9,7 @@ export default {
|
||||
argTypes: {
|
||||
position: {
|
||||
control: { type: "select" },
|
||||
options: ["top", "bottom"],
|
||||
options: [...TOOLTIP_POSITION_OPTIONS],
|
||||
},
|
||||
disabled: {
|
||||
control: { type: "boolean" },
|
||||
@@ -29,37 +30,42 @@ const Template = (args) => (
|
||||
</div>
|
||||
);
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
export const Default = {
|
||||
args: {
|
||||
text: "Tooltip text goes here",
|
||||
position: "top",
|
||||
disabled: false,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Top = Template.bind({});
|
||||
Top.args = {
|
||||
export const Top = {
|
||||
args: {
|
||||
text: "Tooltip positioned at top",
|
||||
position: "top",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Bottom = Template.bind({});
|
||||
Bottom.args = {
|
||||
export const Bottom = {
|
||||
args: {
|
||||
text: "Tooltip positioned at bottom",
|
||||
position: "bottom",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Disabled = Template.bind({});
|
||||
Disabled.args = {
|
||||
export const Disabled = {
|
||||
args: {
|
||||
text: "This tooltip is disabled",
|
||||
disabled: true,
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const LongText = Template.bind({});
|
||||
LongText.args = {
|
||||
export const LongText = {
|
||||
args: {
|
||||
text: "This is a longer tooltip text that demonstrates how the component handles multiple words and extended content",
|
||||
position: "top",
|
||||
},
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const WithIcon = () => (
|
||||
<div className="p-16 flex items-center justify-center min-h-[200px]">
|
||||
<Tooltip text="Tooltip with icon button" position="top">
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import MenuItem from "../../app/components/navigation/MenuItem";
|
||||
import {
|
||||
MENU_ITEM_MODE_OPTIONS,
|
||||
MENU_ITEM_SIZE_OPTIONS,
|
||||
} from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Navigation/MenuItem",
|
||||
@@ -15,12 +19,12 @@ export default {
|
||||
argTypes: {
|
||||
mode: {
|
||||
control: { type: "select" },
|
||||
options: ["default", "inverse"],
|
||||
options: [...MENU_ITEM_MODE_OPTIONS],
|
||||
description: "The visual style mode of the menu item",
|
||||
},
|
||||
size: {
|
||||
control: { type: "select" },
|
||||
options: ["X Small", "Small", "Medium", "Large", "X Large"],
|
||||
options: [...MENU_ITEM_SIZE_OPTIONS],
|
||||
description: "The size of the menu item",
|
||||
},
|
||||
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 = {};
|
||||
@@ -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,7 +1,7 @@
|
||||
import { InformationalScreen } from "../../app/(app)/create/screens/informational/InformationalScreen";
|
||||
|
||||
export default {
|
||||
title: "Pages/Create/Informational",
|
||||
title: "Pages/Create Flow/Informational",
|
||||
component: InformationalScreen,
|
||||
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,7 +1,7 @@
|
||||
import { CommunityReviewScreen } from "../../app/(app)/create/screens/review/CommunityReviewScreen";
|
||||
|
||||
export default {
|
||||
title: "Pages/Create/Review",
|
||||
title: "Pages/Create Flow/Review",
|
||||
component: CommunityReviewScreen,
|
||||
parameters: { layout: "fullscreen" },
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CommunitySizeSelectScreen } from "../../app/(app)/create/screens/select/CommunitySizeSelectScreen";
|
||||
|
||||
export default {
|
||||
title: "Pages/Create/CommunitySize",
|
||||
title: "Pages/Create Flow/Community size",
|
||||
component: CommunitySizeSelectScreen,
|
||||
parameters: { layout: "fullscreen" },
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CreateFlowTextFieldScreen } from "../../app/(app)/create/screens/text/CreateFlowTextFieldScreen";
|
||||
|
||||
export default {
|
||||
title: "Pages/Create/CommunityName",
|
||||
title: "Pages/Create Flow/Community name",
|
||||
component: CreateFlowTextFieldScreen,
|
||||
parameters: { layout: "fullscreen" },
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { CommunityUploadScreen } from "../../app/(app)/create/screens/upload/CommunityUploadScreen";
|
||||
|
||||
export default {
|
||||
title: "Pages/Create/CommunityUpload",
|
||||
title: "Pages/Create Flow/Community upload",
|
||||
component: CommunityUploadScreen,
|
||||
parameters: { layout: "fullscreen" },
|
||||
};
|
||||
|
||||
@@ -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.",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import QuoteBlock from "../../app/components/sections/QuoteBlock";
|
||||
import { QUOTE_BLOCK_VARIANT_OPTIONS } from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Sections/QuoteBlock",
|
||||
@@ -34,7 +35,7 @@ A responsive quote section component that displays inspirational governance quot
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: { type: "select" },
|
||||
options: ["compact", "standard", "extended", "statement"],
|
||||
options: [...QUOTE_BLOCK_VARIANT_OPTIONS],
|
||||
description: "Layout variant for different use cases",
|
||||
},
|
||||
quote: {
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import HeaderLockup from "../../app/components/type/HeaderLockup";
|
||||
import {
|
||||
HEADER_LOCKUP_JUSTIFICATION_OPTIONS,
|
||||
HEADER_LOCKUP_SIZE_OPTIONS,
|
||||
} from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Type/HeaderLockup",
|
||||
component: HeaderLockup,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
backgrounds: {
|
||||
default: "dark",
|
||||
values: [{ name: "dark", value: "#000000" }],
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
justification: {
|
||||
control: { type: "select" },
|
||||
options: ["left", "center", "Left", "Center"],
|
||||
options: [...HEADER_LOCKUP_JUSTIFICATION_OPTIONS],
|
||||
},
|
||||
size: {
|
||||
control: { type: "select" },
|
||||
options: ["L", "M", "l", "m"],
|
||||
options: [...HEADER_LOCKUP_SIZE_OPTIONS],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -1,19 +1,16 @@
|
||||
import NumberedList from "../../app/components/type/NumberedList";
|
||||
import { NUMBERED_LIST_SIZE_OPTIONS } from "../../lib/propNormalization";
|
||||
|
||||
export default {
|
||||
title: "Components/Type/NumberedList",
|
||||
component: NumberedList,
|
||||
parameters: {
|
||||
layout: "centered",
|
||||
backgrounds: {
|
||||
default: "dark",
|
||||
values: [{ name: "dark", value: "#000000" }],
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
size: {
|
||||
control: { type: "select" },
|
||||
options: ["M", "S", "m", "s"],
|
||||
options: [...NUMBERED_LIST_SIZE_OPTIONS],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user