Skip to content

Enhancement: Set up Storybook for component documentation and development - #25

Merged
ViewFromTheBox merged 4 commits into
release/1.0from
enhancement/11-setup-storybook
Mar 30, 2026
Merged

ViewFromTheBox merged 4 commits into
release/1.0from
enhancement/11-setup-storybook

Conversation

@ViewFromTheBox

@ViewFromTheBox ViewFromTheBox commented Mar 30, 2026 •

Copy link
Copy Markdown
Member

Description

Set up Storybook 10 with React + Vite for component development and documentation, covering all 6 component categories with 27 story files.

Closes: #11

Type of Change

  • Bug fix (fixes an issue)
  • New feature (adds new functionality)
  • Enhancement (improves existing functionality)
  • Refactoring (code improvement, no behavior change)
  • Documentation update
  • Performance improvement
  • Security fix
  • Breaking change (breaks backward compatibility)

Related Issue

Issue: #11

Motivation and Context

Developers need a visual development environment and interactive documentation for the 80+ React components. Storybook provides isolated component development, auto-generated docs from TypeScript types, DaisyUI theme switching, and accessibility checks in one tool.

Changes Made

  • Configured Storybook 10 with React + Vite framework at monorepo root
  • Integrated Tailwind CSS v4 + daisyUI via @tailwindcss/vite plugin
  • Added DaisyUI theme switcher in toolbar (32 themes) via global decorator
  • Installed addons: docs (auto-generated prop tables), a11y (accessibility checks), chromatic
  • Created 27 story files across all component categories:
    • Form (6): Button, Input, Select, Checkbox, Toggle, Textarea
    • Layout (6): Card, Modal, Tabs, Accordion, Drawer, Dropdown
    • Navigation (4): Breadcrumbs, Steps, Pagination, Menu
    • Data Display (5): Badge, Avatar, Progress, Stat, Table
    • Feedback (4): Alert, Loading, Skeleton, EmptyState
    • Utility (2): Tooltip, Clipboard
  • Each story includes default/playground story with autodocs, variant stories (colors, sizes, states), and composition examples
  • Configured @artisanpack-ui/tokens alias for monorepo resolution
  • Added eslint-plugin-storybook for story linting
  • Updated package.json with Storybook dependencies

How Has This Been Tested?

Testing Environment:

  • Operating System: macOS
  • Node.js: v23.7.0

Tests Performed:

  1. npm run build-storybook completes successfully
  2. npm run storybook starts dev server, all stories render correctly
  3. DaisyUI theme switcher changes component themes in real-time
  4. Addon-docs generates prop tables from TypeScript interfaces
  5. Addon-a11y panel runs accessibility checks on each story

Accessibility Tests Run

  • Keyboard navigation tested
  • Screen reader tested
  • Color contrast verified
  • ARIA labels checked

Details: Storybook addon-a11y runs axe-core accessibility checks on all stories. Components inherit accessibility from the main component implementations.

Tests Added

  • Unit tests added/updated
  • Integration tests added/updated
  • All tests passing

Test details: Storybook build serves as a compilation/rendering test for all 27 story files. No unit tests added as stories are documentation/development tooling. Note: existing vitest tests have a pre-existing @vitejs/plugin-react@6 / vite@7 version conflict unrelated to this change.

Documentation

  • Inline code documentation added
  • README updated
  • Wiki updated
  • API documentation updated

Documentation details: All stories use tags: ['autodocs'] for automatic documentation generation from TypeScript prop interfaces.

Pre-Submission Checklist

  • Followed contributing guidelines
  • Checked for other open PRs for same update
  • Code passes all tests
  • Code has been linted
  • Accessibility tests completed
  • Code follows project style guide
  • Self-review completed
  • Comments added for complex code
  • No new warnings generated

Summary by CodeRabbit

  • New Features

    • Extensive Storybook documentation and interactive examples added across many components (Data, Form, Layout, Feedback, Navigation, Utility), showcasing variants, sizes, colors, and interactive states.
  • Documentation

    • Global theme toolbar (default: light) with live theme switching and improved automatic control matching for colors and dates.
  • Chores

    • Storybook, Tailwind CSS and DaisyUI integrated for consistent component previews and styling in the playground.

…ment

Configure Storybook 10 with React + Vite, Tailwind CSS v4 + daisyUI
integration, and 27 story files covering all component categories (form,
layout, navigation, data display, feedback, utility). Includes DaisyUI
theme switcher in toolbar, addon-docs for auto-generated prop tables,
and addon-a11y for accessibility checks.

Closes #11
@coderabbitai

coderabbitai Bot commented Mar 30, 2026 •

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b66d4b2e-b786-4921-9577-5aa26de25716

📥 Commits

Reviewing files that changed from the base of the PR and between 724fccd and f9c0d0b.

📒 Files selected for processing (1)
  • packages/react/src/components/layout/Card/Card.stories.tsx

📝 Walkthrough

Walkthrough

Adds Storybook 10 (React + Vite) configuration with Tailwind/DaisyUI integration, ESLint Storybook rules, devDependencies, a Vite alias for @artisanpack-ui/tokens, and ~30+ new component story files across data, feedback, form, layout, navigation, and utility categories.

Changes

Cohort / File(s) Summary
Storybook Core
\.storybook/main.ts, \.storybook/preview.ts, \.storybook/storybook.css
New StorybookConfig export: stories glob, addons (docs, a11y, chromatic), framework set to @storybook/react-vite; viteFinal appends @tailwindcss/vite plugin and adds Vite resolve alias mapping @artisanpack-ui/tokens → ../packages/tokens/src; preview adds DaisyUI theme toolbar, global decorator (sets data-theme), control matchers; CSS imports Tailwind + DaisyUI plugin.
Build & Linting
eslint.config.js, package.json
ESLint flat config extended to include eslint-plugin-storybook recommended rules; devDependencies added for Storybook, Storybook addons, Chromatic, Tailwind, DaisyUI, and @tailwindcss/vite.
Data component stories
packages/react/src/components/data/...
Avatar/Avatar.stories.tsx, Badge/..., Progress/..., Stat/..., Table/...
New stories demonstrating variants (colors, sizes), compositions (groups), examples with images/rings, and interactive Table demos (sorting, selection).
Feedback component stories
packages/react/src/components/feedback/...
Alert/..., EmptyState/..., Loading/..., Skeleton/...
Stories for alerts, empty states, loaders, skeletons with variants (icons, dismissible), and composed skeleton layouts.
Form component stories
packages/react/src/components/form/...
Button/..., Checkbox/..., Input/..., Select/..., Textarea/..., Toggle/...
Stories covering color/size controls, states (disabled/loading/checked), icon/badge/tooltip usages, hints/errors, and composition examples.
Layout component stories
packages/react/src/components/layout/...
Accordion/..., Card/..., Drawer/..., Dropdown/..., Modal/..., Tabs/...
Interactive stories managing open/close state, layout variants (bordered/glass/compact), bottom-sheet modal, joined accordions, and card composition examples.
Navigation component stories
packages/react/src/components/navigation/...
Breadcrumbs/..., Menu/..., Pagination/..., Steps/...
Stories demonstrating breadcrumb truncation, menu variants (nested/horizontal/compact), pagination interactivity, and stepper variants/sizes.
Utility component stories
packages/react/src/components/utility/...
Clipboard/..., Tooltip/...
Clipboard and Tooltip stories showing color/size/position variants, success labels, and always-open demos.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(13,110,253,0.5)
    participant Dev as Developer
  end
  rect rgba(16,185,129,0.5)
    participant SB as Storybook
  end
  rect rgba(234,88,12,0.5)
    participant Vite as Vite
  end
  rect rgba(120,53,138,0.5)
    participant Tokens as LocalTokens
  end

  Dev->>SB: run storybook
  SB->>Vite: initialize with framework `@storybook/react-vite`
  Vite->>Vite: apply plugins (existing + `@tailwindcss/vite`)
  Vite->>Tokens: resolve alias `@artisanpack-ui/tokens` -> ../packages/tokens/src
  Vite->>SB: serve built stories (CSS + decorators)
  SB->>Dev: render UI with DaisyUI theme toolbar
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code to tune the view,
Stories lined in rows — buttons, cards, and hue,
Tailwind stitched the scenes, Daisy themes to try,
Components prance and pose beneath my eye,
A cheerful rabbit cheers — hop, preview, woohoo!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Enhancement: Set up Storybook for component documentation and development' clearly and concisely summarizes the main change—setting up Storybook for the component library.
Linked Issues check ✅ Passed The PR comprehensively implements all coding objectives from issue #11: Storybook 10 configured with React+Vite, 27 story files added across six component categories, DaisyUI theme switching integrated, Controls and Docs addons enabled, a11y addon installed, chromatic integration configured, and ESLint plugin added.
Out of Scope Changes check ✅ Passed All changes are directly aligned with Storybook setup objectives: configuration files (.storybook/), story files for components, dependency additions, and ESLint plugin integration. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch enhancement/11-setup-storybook

Comment @coderabbitai help to get the list of available commands and usage tips.

@ViewFromTheBox ViewFromTheBox moved this from Backlog to In Review in ArtisanPack UI Overview Mar 30, 2026
@ViewFromTheBox ViewFromTheBox linked an issue Mar 30, 2026 that may be closed by this pull request
@ViewFromTheBox
ViewFromTheBox marked this pull request as ready for review March 30, 2026 01:07

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
package.json (1)

20-38: ⚠️ Potential issue | 🟠 Major

Update Node engines constraint to match Storybook and Tailwind toolchain requirements.

Lines 20–38 introduce Storybook 10.3.x and @tailwindcss/vite, which require Node.js 20.19+ or 22.12+ (Storybook's minimum is 20.19.0, not 20.0.0). Add an engines.node constraint to prevent incompatible Node versions across developer and CI environments.

Proposed diff
 {
   "name": "@artisanpack-ui/monorepo",
   "private": true,
   "type": "module",
+  "engines": {
+    "node": ">=20.19.0"
+  },
   "workspaces": [
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` around lines 20 - 38, Add a top-level "engines" field to
package.json to require a compatible Node version for Storybook 10.3.x and
`@tailwindcss/vite`; specifically add "engines": { "node": ">=20.19.0" } (so CI
and dev environments running older Node 20.0.0 are blocked), placing it at the
root of package.json alongside "devDependencies".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react/src/components/data/Avatar/Avatar.stories.tsx`:
- Line 32: The story currently uses a remote image URL
('https://placehold.co/96x96') which makes tests flaky; replace that remote
dependency by importing a local asset or using an inline data URI and assign it
to the story's image arg (the property image in Avatar.stories.tsx). Update the
story to import the local file (or embed the base64 data URI) and use that
imported identifier as the image value so visual tests/docs are deterministic;
if using a local file, ensure the asset is added to Storybook/static or the
bundler asset pipeline.

In `@packages/react/src/components/feedback/Alert/Alert.stories.tsx`:
- Line 68: In the WithIcon story (Alert.stories.tsx) the icon is decorative and
should be hidden from assistive tech; update the JSX for the icon prop
(currently "icon: <span>ℹ️</span>") to include aria-hidden="true" on the span so
screen readers skip it (e.g., change the span in the WithIcon story to <span
aria-hidden="true">ℹ️</span>).

In `@packages/react/src/components/form/Input/Input.stories.tsx`:
- Around line 29-34: The WithError Story uses a controlled prop (value) without
an onChange handler, causing React to treat the input as read-only and emit
warnings; update the WithError story's args to use defaultValue instead of value
(or alternatively add an onChange noop) so the Input component is uncontrolled
and editable in the error-state example—locate the WithError export in
Input.stories.tsx and replace args.value with args.defaultValue (or add a simple
onChange handler) accordingly.

In `@packages/react/src/components/form/Textarea/Textarea.stories.tsx`:
- Around line 28-33: The WithError Story object currently sets a controlled prop
"value: ''" which triggers React's controlled-component warning; update the
WithError story to replace the "value" prop with "defaultValue" (e.g., remove
value and add defaultValue: '') so the Textarea story remains uncontrolled for
this static example, and do not add an onChange handler.

In `@packages/react/src/components/layout/Card/Card.stories.tsx`:
- Around line 35-39: Replace the external src on the <img> elements in
Card.stories.tsx with a self-contained placeholder: either import a local static
asset (e.g., import placeholder from './placeholder.png' and set
src={placeholder}) or inline a data URI/SVG string as the src, so the story's
<img src=...> no longer depends on network-hosted images and won't be flaky in
Storybook/Chromatic.
- Around line 97-101: The menu button in the WithMenu Story (args.menu) is
icon-only and lacks an accessible name; update the menu element used in the
WithMenu story (the menu arg) to include an accessible label such as aria-label
or aria-labelledby (e.g., aria-label="Open menu" or aria-label="More options")
so screen readers announce a meaningful control name; ensure the change is made
on the menu JSX provided in the WithMenu args so the story renders an accessible
button.

In `@packages/react/src/components/layout/Drawer/Drawer.stories.tsx`:
- Around line 23-27: In the Drawer.stories.tsx menu example (the <ul
className="menu bg-base-200 min-h-full w-60 p-4"> block), replace the href="#"
anchor usages with non-navigating demo actions: either use realistic demo routes
like "/home", "/about", "/contact" or convert the anchors to buttons (or add an
onClick handler that calls e.preventDefault()) so clicking items doesn't trigger
in-canvas navigation; update the three menu items (Home, About, Contact)
accordingly.
- Around line 15-62: Add an args-driven Storybook story (e.g., export const
Playground) that exposes the Drawer props open, end, and persistent for
controls; implement it similarly to Default/EndSide but use Storybook's useArgs
to read/update args so the Drawer is controlled by args (initial args: { open:
false, end: false, persistent: false }), wire the Drawer prop values to
args.open/args.end/args.persistent, and handle onClose/onClick to call setArgs
(from useArgs) to toggle open so controls reflect interactions.

In `@packages/react/src/components/layout/Modal/Modal.stories.tsx`:
- Around line 15-94: Add a new args-driven story named Playground that exposes
open, title, subtitle, persistent, and bottom as configurable args; define
default args (e.g., open: false, title: "Modal Title", subtitle: "", persistent:
false, bottom: false) and implement render as a function receiving (args, {
updateArgs }) that uses local state for open (const [open, setOpen] =
useState(args.open)), syncs local open to controls via updateArgs({ open }) when
it changes, and passes the rest of args into the Modal component (Modal
open={open} onClose={() => setOpen(false)} title={args.title}
subtitle={args.subtitle} persistent={args.persistent} bottom={args.bottom}) so
Storybook Controls can drive the modal; add a Button in the render to open the
modal by calling setOpen(true).

In
`@packages/react/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx`:
- Around line 15-40: Replace href="#" placeholders inside the story args (the
items arrays used by the Breadcrumbs stories such as WithIcons and Truncated)
with non-navigating handlers or realistic routes: remove href="#" and either
provide a real route (e.g., "/home") or add an onClick handler like onClick: (e)
=> e.preventDefault() so clicks do not trigger hash navigation in the Storybook
iframe; update each item object in the items arrays for WithIcons and Truncated
(and any other story items arrays) accordingly.

In `@packages/react/src/components/navigation/Menu/Menu.stories.tsx`:
- Line 35: Create a single shared decorator constant (e.g., WIDTH_DECORATOR)
that returns the wrapper div with className "w-56" and replace the duplicated
inline decorators arrays in Menu.stories.tsx (the decorators: [(Story) => <div
className="w-56"><Story /></div>] occurrences) with that constant so all stories
reuse the same decorator; update each story's decorators reference to use the
shared constant to avoid repetition and make future width changes a single edit.

In `@packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx`:
- Around line 14-17: The Storybook control for the Tooltip color prop is missing
the 'neutral' DaisyColor option; update the control options array for the
`color` control in Tooltip.stories.tsx (the `color: { control: 'select',
options: [...] }` block) to include 'neutral' so Storybook exposes all valid
DaisyColor values for `Tooltip.color`.

---

Outside diff comments:
In `@package.json`:
- Around line 20-38: Add a top-level "engines" field to package.json to require
a compatible Node version for Storybook 10.3.x and `@tailwindcss/vite`;
specifically add "engines": { "node": ">=20.19.0" } (so CI and dev environments
running older Node 20.0.0 are blocked), placing it at the root of package.json
alongside "devDependencies".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 17a788d4-e45e-42c0-b39b-8de4759ea86d

📥 Commits

Reviewing files that changed from the base of the PR and between 5d90334 and 83a772c.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (32)
  • .storybook/main.ts
  • .storybook/preview.ts
  • .storybook/storybook.css
  • eslint.config.js
  • package.json
  • packages/react/src/components/data/Avatar/Avatar.stories.tsx
  • packages/react/src/components/data/Badge/Badge.stories.tsx
  • packages/react/src/components/data/Progress/Progress.stories.tsx
  • packages/react/src/components/data/Stat/Stat.stories.tsx
  • packages/react/src/components/data/Table/Table.stories.tsx
  • packages/react/src/components/feedback/Alert/Alert.stories.tsx
  • packages/react/src/components/feedback/EmptyState/EmptyState.stories.tsx
  • packages/react/src/components/feedback/Loading/Loading.stories.tsx
  • packages/react/src/components/feedback/Skeleton/Skeleton.stories.tsx
  • packages/react/src/components/form/Button/Button.stories.tsx
  • packages/react/src/components/form/Checkbox/Checkbox.stories.tsx
  • packages/react/src/components/form/Input/Input.stories.tsx
  • packages/react/src/components/form/Select/Select.stories.tsx
  • packages/react/src/components/form/Textarea/Textarea.stories.tsx
  • packages/react/src/components/form/Toggle/Toggle.stories.tsx
  • packages/react/src/components/layout/Accordion/Accordion.stories.tsx
  • packages/react/src/components/layout/Card/Card.stories.tsx
  • packages/react/src/components/layout/Drawer/Drawer.stories.tsx
  • packages/react/src/components/layout/Dropdown/Dropdown.stories.tsx
  • packages/react/src/components/layout/Modal/Modal.stories.tsx
  • packages/react/src/components/layout/Tabs/Tabs.stories.tsx
  • packages/react/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx
  • packages/react/src/components/navigation/Menu/Menu.stories.tsx
  • packages/react/src/components/navigation/Pagination/Pagination.stories.tsx
  • packages/react/src/components/navigation/Steps/Steps.stories.tsx
  • packages/react/src/components/utility/Clipboard/Clipboard.stories.tsx
  • packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx

Comment thread packages/react/src/components/data/Avatar/Avatar.stories.tsx Outdated
Comment thread packages/react/src/components/feedback/Alert/Alert.stories.tsx Outdated
Comment thread packages/react/src/components/form/Input/Input.stories.tsx
Comment thread packages/react/src/components/form/Textarea/Textarea.stories.tsx
Comment thread packages/react/src/components/layout/Card/Card.stories.tsx
Comment thread packages/react/src/components/layout/Drawer/Drawer.stories.tsx
Comment on lines +15 to +94
export const Default: Story = {
render: () => {
const [open, setOpen] = useState(false);
return (
<>
<Button label="Open Modal" color="primary" onClick={() => setOpen(true)} />
<Modal open={open} onClose={() => setOpen(false)} title="Modal Title">
<p>This is the modal body content.</p>
</Modal>
</>
);
},
};

export const WithActions: Story = {
render: () => {
const [open, setOpen] = useState(false);
return (
<>
<Button label="Open Modal" color="primary" onClick={() => setOpen(true)} />
<Modal
open={open}
onClose={() => setOpen(false)}
title="Confirm Delete"
subtitle="This action cannot be undone."
actions={
<>
<Button label="Cancel" color="ghost" onClick={() => setOpen(false)} />
<Button label="Delete" color="error" onClick={() => setOpen(false)} />
</>
}
>
<p>Are you sure you want to delete this item?</p>
</Modal>
</>
);
},
};

export const Persistent: Story = {
render: () => {
const [open, setOpen] = useState(false);
return (
<>
<Button label="Open Persistent Modal" color="warning" onClick={() => setOpen(true)} />
<Modal
open={open}
onClose={() => setOpen(false)}
title="Required Action"
persistent
actions={
<Button label="I Understand" color="primary" onClick={() => setOpen(false)} />
}
>
<p>You must acknowledge this before continuing. Clicking outside will not close it.</p>
</Modal>
</>
);
},
};

export const Bottom: Story = {
render: () => {
const [open, setOpen] = useState(false);
return (
<>
<Button label="Open Bottom Sheet" color="accent" onClick={() => setOpen(true)} />
<Modal
open={open}
onClose={() => setOpen(false)}
title="Bottom Sheet"
bottom
actions={<Button label="Close" color="ghost" onClick={() => setOpen(false)} />}
>
<p>This modal slides up from the bottom on mobile.</p>
</Modal>
</>
);
},
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

❓ Verification inconclusive

Script executed:

find . -name "Modal.stories.tsx" -type f

Repository: ArtisanPack-UI/react


Repository: ArtisanPack-UI/react
Exit code: 0

stdout:

./packages/react/src/components/layout/Modal/Modal.stories.tsx

Script executed:

cat -n ./packages/react/src/components/layout/Modal/Modal.stories.tsx

Repository: ArtisanPack-UI/react


Repository: ArtisanPack-UI/react
Exit code: 0

stdout:

     1	import { useState } from 'react';
     2	import type { Meta, StoryObj } from '@storybook/react-vite';
     3	import { Modal } from './Modal';
     4	import { Button } from '../../form/Button/Button';
     5	
     6	const meta: Meta<typeof Modal> = {
     7	  title: 'Layout/Modal',
     8	  component: Modal,
     9	  tags: ['autodocs'],
    10	};
    11	
    12	export default meta;
    13	type Story = StoryObj<typeof Modal>;
    14	
    15	export const Default: Story = {
    16	  render: () => {
    17	    const [open, setOpen] = useState(false);
    18	    return (
    19	      <>
    20	        <Button label="Open Modal" color="primary" onClick={() => setOpen(true)} />
    21	        <Modal open={open} onClose={() => setOpen(false)} title="Modal Title">
    22	          <p>This is the modal body content.</p>
    23	        </Modal>
    24	      </>
    25	    );
    26	  },
    27	};
    28	
    29	export const WithActions: Story = {
    30	  render: () => {
    31	    const [open, setOpen] = useState(false);
    32	    return (
    33	      <>
    34	        <Button label="Open Modal" color="primary" onClick={() => setOpen(true)} />
    35	        <Modal
    36	          open={open}
    37	          onClose={() => setOpen(false)}
    38	          title="Confirm Delete"
    39	          subtitle="This action cannot be undone."
    40	          actions={
    41	            <>
    42	              <Button label="Cancel" color="ghost" onClick={() => setOpen(false)} />
    43	              <Button label="Delete" color="error" onClick={() => setOpen(false)} />
    44	            </>
    45	          }
    46	        >
    47	          <p>Are you sure you want to delete this item?</p>
    48	        </Modal>
    49	      </>
    50	    );
    51	  },
    52	};
    53	
    54	export const Persistent: Story = {
    55	  render: () => {
    56	    const [open, setOpen] = useState(false);
    57	    return (
    58	      <>
    59	        <Button label="Open Persistent Modal" color="warning" onClick={() => setOpen(true)} />
    60	        <Modal
    61	          open={open}
    62	          onClose={() => setOpen(false)}
    63	          title="Required Action"
    64	          persistent
    65	          actions={
    66	            <Button label="I Understand" color="primary" onClick={() => setOpen(false)} />
    67	          }
    68	        >
    69	          <p>You must acknowledge this before continuing. Clicking outside will not close it.</p>
    70	        </Modal>
    71	      </>
    72	    );
    73	  },
    74	};
    75	
    76	export const Bottom: Story = {
    77	  render: () => {
    78	    const [open, setOpen] = useState(false);
    79	    return (
    80	      <>
    81	        <Button label="Open Bottom Sheet" color="accent" onClick={() => setOpen(true)} />
    82	        <Modal
    83	          open={open}
    84	          onClose={() => setOpen(false)}
    85	          title="Bottom Sheet"
    86	          bottom
    87	          actions={<Button label="Close" color="ghost" onClick={() => setOpen(false)} />}
    88	        >
    89	          <p>This modal slides up from the bottom on mobile.</p>
    90	        </Modal>
    91	      </>
    92	    );
    93	  },
    94	};

Script executed:

find . -name "Modal.tsx" -type f | grep -E "components/layout/Modal/Modal\.tsx$"

Repository: ArtisanPack-UI/react


Repository: ArtisanPack-UI/react
Exit code: 0

stdout:

./packages/react/src/components/layout/Modal/Modal.tsx

Script executed:

cat -n ./packages/react/src/components/layout/Modal/Modal.tsx

Repository: ArtisanPack-UI/react


Repository: ArtisanPack-UI/react
Exit code: 0

stdout:

     1	import {
     2	  forwardRef,
     3	  useEffect,
     4	  useRef,
     5	  useCallback,
     6	  useId,
     7	  type HTMLAttributes,
     8	  type ReactNode,
     9	  type MouseEvent,
    10	} from 'react';
    11	import { cn } from '@artisanpack-ui/tokens';
    12	
    13	export interface ModalProps extends Omit<HTMLAttributes<HTMLDialogElement>, 'title'> {
    14	  /** Whether the modal is open */
    15	  open: boolean;
    16	  /** Callback to close the modal */
    17	  onClose: () => void;
    18	  /** Modal title */
    19	  title?: ReactNode;
    20	  /** Modal subtitle */
    21	  subtitle?: string;
    22	  /** Actions slot (buttons in footer) */
    23	  actions?: ReactNode;
    24	  /** Prevent closing via escape key or backdrop click */
    25	  persistent?: boolean;
    26	  /** Glass morphism effect */
    27	  glass?: boolean;
    28	  /** Position modal at bottom (mobile sheet style) */
    29	  bottom?: boolean;
    30	}
    31	
    32	/**
    33	 * Dialog overlay with focus trapping, keyboard dismiss, and backdrop click.
    34	 */
    35	export const Modal = forwardRef<HTMLDialogElement, ModalProps>(
    36	  (
    37	    {
    38	      open,
    39	      onClose,
    40	      title,
    41	      subtitle,
    42	      actions,
    43	      persistent = false,
    44	      glass = false,
    45	      bottom = false,
    46	      className,
    47	      children,
    48	      ...rest
    49	    },
    50	    ref,
    51	  ) => {
    52	    const dialogRef = useRef<HTMLDialogElement | null>(null);
    53	    const previousActiveElement = useRef<Element | null>(null);
    54	    const titleId = useId();
    55	
    56	    const setRefs = useCallback(
    57	      (node: HTMLDialogElement | null) => {
    58	        dialogRef.current = node;
    59	        if (typeof ref === 'function') {
    60	          ref(node);
    61	        } else if (ref) {
    62	          (ref as React.MutableRefObject<HTMLDialogElement | null>).current = node;
    63	        }
    64	      },
    65	      [ref],
    66	    );
    67	
    68	    useEffect(() => {
    69	      const dialog = dialogRef.current;
    70	      if (!dialog) return;
    71	
    72	      if (open) {
    73	        previousActiveElement.current = document.activeElement;
    74	        if (!dialog.open) {
    75	          dialog.showModal();
    76	        }
    77	      } else {
    78	        if (dialog.open) {
    79	          dialog.close();
    80	        }
    81	        if (previousActiveElement.current instanceof HTMLElement) {
    82	          previousActiveElement.current.focus();
    83	        }
    84	      }
    85	    }, [open]);
    86	
    87	    useEffect(() => {
    88	      const dialog = dialogRef.current;
    89	      if (!dialog) return;
    90	
    91	      const handleCancel = (e: Event) => {
    92	        e.preventDefault();
    93	        if (!persistent) {
    94	          onClose();
    95	        }
    96	      };
    97	
    98	      dialog.addEventListener('cancel', handleCancel);
    99	      return () => dialog.removeEventListener('cancel', handleCancel);
   100	    }, [persistent, onClose]);
   101	
   102	    const handleBackdropClick = (e: MouseEvent<HTMLDialogElement>) => {
   103	      if (persistent) return;
   104	      if (e.target === dialogRef.current) {
   105	        onClose();
   106	      }
   107	    };
   108	
   109	    return (
   110	      <dialog
   111	        ref={setRefs}
   112	        className={cn(
   113	          'modal',
   114	          bottom && 'modal-bottom',
   115	          className,
   116	        )}
   117	        onClick={handleBackdropClick}
   118	        aria-modal="true"
   119	        aria-labelledby={title ? titleId : undefined}
   120	        {...rest}
   121	      >
   122	        <div className={cn('modal-box', glass && 'glass')}>
   123	          {(title || subtitle) && (
   124	            <div className="mb-4">
   125	              {title && (
   126	                typeof title === 'string' ? (
   127	                  <h3 id={titleId} className="text-lg font-bold">{title}</h3>
   128	                ) : (
   129	                  <div id={titleId}>{title}</div>
   130	                )
   131	              )}
   132	              {subtitle && (
   133	                <p className="text-base-content/60 text-sm">{subtitle}</p>
   134	              )}
   135	            </div>
   136	          )}
   137	          {!persistent && (
   138	            <button
   139	              type="button"
   140	              className="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
   141	              onClick={onClose}
   142	              aria-label="Close"
   143	            >
   144	              &#x2715;
   145	            </button>
   146	          )}
   147	          <div>{children}</div>
   148	          {actions && (
   149	            <div className="modal-action">{actions}</div>
   150	          )}
   151	        </div>
   152	      </dialog>
   153	    );
   154	  },
   155	);
   156	
   157	Modal.displayName = 'Modal';

Add an args-driven Modal playground story to expose interactive controls.

Current stories demonstrate specific use cases but don't provide interactive prop controls. Add one args-based story with configurable props (open, title, subtitle, persistent, bottom) to enable controls-first exploration in Storybook's Controls panel.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/components/layout/Modal/Modal.stories.tsx` around lines 15
- 94, Add a new args-driven story named Playground that exposes open, title,
subtitle, persistent, and bottom as configurable args; define default args
(e.g., open: false, title: "Modal Title", subtitle: "", persistent: false,
bottom: false) and implement render as a function receiving (args, { updateArgs
}) that uses local state for open (const [open, setOpen] = useState(args.open)),
syncs local open to controls via updateArgs({ open }) when it changes, and
passes the rest of args into the Modal component (Modal open={open} onClose={()
=> setOpen(false)} title={args.title} subtitle={args.subtitle}
persistent={args.persistent} bottom={args.bottom}) so Storybook Controls can
drive the modal; add a Button in the render to open the modal by calling
setOpen(true).

Comment thread packages/react/src/components/navigation/Menu/Menu.stories.tsx Outdated
Comment thread packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx
- Replace external placeholder images with inline SVG data URIs
- Fix controlled value warnings (use defaultValue instead of value)
- Add aria-hidden to decorative emoji icons
- Add aria-label to icon-only menu button
- Use realistic demo routes instead of href="#" in Breadcrumbs/Drawer
- Add preventDefault to Drawer demo links
- Extract shared width decorator in Menu stories
- Add neutral to Tooltip/Avatar color control options

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (1)
packages/react/src/components/layout/Drawer/Drawer.stories.tsx (1)

17-64: ⚠️ Potential issue | 🟠 Major

Add an args-driven playground story for controls parity.

Both stories are fully local-state driven, so open, end, and persistent are not controllable from the Storybook Controls panel. This leaves the Drawer docs/demo incomplete for interactive prop testing.

Proposed patch
 import { useState } from 'react';
 import type { Meta, StoryObj } from '@storybook/react-vite';
+import { useArgs } from '@storybook/preview-api';
 import { Drawer } from './Drawer';
 import { Button } from '../../form/Button/Button';
@@
 export default meta;
 type Story = StoryObj<typeof Drawer>;
@@
 const handleLinkClick = (e: React.MouseEvent) => e.preventDefault();
+
+export const Playground: Story = {
+  args: {
+    open: false,
+    end: false,
+    persistent: false,
+  },
+  render: (args) => {
+    const [, setArgs] = useArgs();
+    return (
+      <Drawer
+        {...args}
+        open={!!args.open}
+        end={!!args.end}
+        persistent={!!args.persistent}
+        onClose={() => setArgs({ open: false })}
+        side={
+          <ul className="menu bg-base-200 min-h-full w-60 p-4">
+            <li><a href="/home" onClick={handleLinkClick}>Home</a></li>
+            <li><a href="/about" onClick={handleLinkClick}>About</a></li>
+            <li><a href="/contact" onClick={handleLinkClick}>Contact</a></li>
+          </ul>
+        }
+      >
+        <div className="p-4">
+          <Button
+            label="Toggle Drawer"
+            color="primary"
+            onClick={() => setArgs({ open: !args.open })}
+          />
+          <p className="mt-4">Use controls to test open/end/persistent behavior.</p>
+        </div>
+      </Drawer>
+    );
+  },
+};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/components/layout/Drawer/Drawer.stories.tsx` around lines
17 - 64, Add an args-driven "Playground" story so Storybook Controls can
manipulate open, end, and persistent: create a new export Playground: Story that
defines args { open: false, end: false, persistent: false } and in its render
use the Storybook useArgs hook to read/update args (e.g., const [{ open, end,
persistent }, updateArgs] = useArgs()); pass those values into <Drawer
open={open} end={end} persistent={persistent} /> and make onClose call
updateArgs({ open: false }); for the open toggle buttons call updateArgs({ open:
true }); and reuse the same side markup (or replace handleLinkClick with a local
noop) so the story is interactive via Controls and mirrors the existing
Default/EndSide props.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react/src/components/data/Avatar/Avatar.stories.tsx`:
- Around line 78-88: The Colors story for the Avatar component omits the
"neutral" variant even though argTypes.color includes it; update the Colors
Story (export const Colors) to render an additional <Avatar> with placeholder
"N" and color="neutral" alongside the existing variants so the showcased color
matrix matches argTypes.color and the Avatar component's available options.

In `@packages/react/src/components/form/Input/Input.stories.tsx`:
- Around line 37-43: The decorative search emoji in the Story object WithIcons
(args.icon) should be marked aria-hidden so screen readers ignore it; update the
icon value in the WithIcons story to render the emoji with aria-hidden="true"
(e.g., change the span used for the icon in WithIcons to include aria-hidden) so
the visual emoji is skipped by assistive tech while the label "Search" remains
accessible.

In `@packages/react/src/components/layout/Card/Card.stories.tsx`:
- Around line 37-40: The SVG data-URI construction is duplicated in
Card.stories.tsx (used at the image placeholder in multiple stories); extract it
into a single helper (e.g., a constant SVG_PLACEHOLDER_DATA_URI or a small
function buildSvgPlaceholder(width, height, text)) and replace the inline
concatenation in the stories with that helper; update every use (the instances
around the current encodeURIComponent blocks) so the stories import/consume the
new helper, keeping the same output format and parameters for width/height/text
if you choose a function.
- Line 118: The story currently sets the Card component's link prop to '#' which
causes hash-jump behavior; update the story args for the Card story (the object
that includes link: '#') to use a realistic demo route like '/pricing/plan-a' or
remove the link and provide a non-navigating onClick handler that calls
event.preventDefault() for interactive previews; locate the "link" prop in
Card.stories.tsx (the story args object for the Card story) and replace '#' with
the chosen stable path or switch to a prevent-default click handler to avoid
navigation during Storybook interactions.

In `@packages/react/src/components/navigation/Menu/Menu.stories.tsx`:
- Around line 56-58: The emoji spans in the menu items array in Menu.stories.tsx
(the objects with keys 'home', 'settings', 'profile') are decorative and need
aria-hidden set; update the <span> elements used as icons (the spans containing
"🏠", "⚙️", "👤") to include aria-hidden="true" so screen readers ignore the
decorative emoji while keeping the visible label text intact.

In `@packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx`:
- Around line 51-67: The Colors story for Tooltip only shows a subset of the
available colors; update the exported Colors story (symbol: Colors) to render
Tooltip/Button examples for the remaining exposed color options (neutral,
warning, error, info) so docs match controls. Modify the render function inside
Colors to include additional <Tooltip tip="..." color="..."><Button label="..."
color="..." /></Tooltip> entries for neutral, warning, error, and info (or
iterate over a shared color list constant if one exists) ensuring each color
string matches the component prop names.
- Around line 29-30: Duplicate inline decorator used in the stories (the
decorators: [(Story) => <div className="p-16 flex justify-center"><Story
/></div>] usage) should be extracted into a shared constant (e.g.,
centeredDecorator) and reused by both story definitions; replace the inline
lambda passed to the decorators array with the shared centeredDecorator constant
in each story so layout changes are centralized and duplication removed.

---

Duplicate comments:
In `@packages/react/src/components/layout/Drawer/Drawer.stories.tsx`:
- Around line 17-64: Add an args-driven "Playground" story so Storybook Controls
can manipulate open, end, and persistent: create a new export Playground: Story
that defines args { open: false, end: false, persistent: false } and in its
render use the Storybook useArgs hook to read/update args (e.g., const [{ open,
end, persistent }, updateArgs] = useArgs()); pass those values into <Drawer
open={open} end={end} persistent={persistent} /> and make onClose call
updateArgs({ open: false }); for the open toggle buttons call updateArgs({ open:
true }); and reuse the same side markup (or replace handleLinkClick with a local
noop) so the story is interactive via Controls and mirrors the existing
Default/EndSide props.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9e0771b8-1a51-45dd-a000-d22d01f160ba

📥 Commits

Reviewing files that changed from the base of the PR and between 83a772c and 2635e2f.

📒 Files selected for processing (9)
  • packages/react/src/components/data/Avatar/Avatar.stories.tsx
  • packages/react/src/components/feedback/Alert/Alert.stories.tsx
  • packages/react/src/components/form/Input/Input.stories.tsx
  • packages/react/src/components/form/Textarea/Textarea.stories.tsx
  • packages/react/src/components/layout/Card/Card.stories.tsx
  • packages/react/src/components/layout/Drawer/Drawer.stories.tsx
  • packages/react/src/components/navigation/Breadcrumbs/Breadcrumbs.stories.tsx
  • packages/react/src/components/navigation/Menu/Menu.stories.tsx
  • packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx

Comment thread packages/react/src/components/data/Avatar/Avatar.stories.tsx
Comment thread packages/react/src/components/form/Input/Input.stories.tsx
Comment thread packages/react/src/components/layout/Card/Card.stories.tsx Outdated
Comment thread packages/react/src/components/layout/Card/Card.stories.tsx Outdated
Comment thread packages/react/src/components/navigation/Menu/Menu.stories.tsx Outdated
Comment thread packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx Outdated
Comment thread packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx
- Add neutral Avatar to Colors story to match argTypes
- Add aria-hidden to decorative emoji icons in Input/Menu stories
- Extract svgPlaceholder helper to deduplicate Card data URIs
- Replace href="#" with realistic route in Card AsLink story
- Add all color variants to Tooltip Colors story
- Extract shared centeredDecorator in Tooltip stories

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react/src/components/layout/Card/Card.stories.tsx`:
- Around line 138-143: The Card story uses the glass prop on the Card component
(Card title="Plan C" ... glass) but lacks a colored backdrop so the glass
morphism won't be visible; either wrap this Card in a gradient/colored container
(e.g., a decorator or a surrounding div with the same gradient used by the Glass
story) to make the effect visible, or remove the glass prop from this
composition example if the glass look is not intended. Locate the Card instance
in Card.stories.tsx and apply the chosen fix (add gradient wrapper/decorator or
remove glass).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8c787a92-1e09-400e-9ecd-21ebf6255fe0

📥 Commits

Reviewing files that changed from the base of the PR and between 2635e2f and 724fccd.

📒 Files selected for processing (5)
  • packages/react/src/components/data/Avatar/Avatar.stories.tsx
  • packages/react/src/components/form/Input/Input.stories.tsx
  • packages/react/src/components/layout/Card/Card.stories.tsx
  • packages/react/src/components/navigation/Menu/Menu.stories.tsx
  • packages/react/src/components/utility/Tooltip/Tooltip.stories.tsx

Comment thread packages/react/src/components/layout/Card/Card.stories.tsx Outdated
The glass morphism effect is invisible without a colored backdrop.
Replaced with bordered to keep the three cards visually distinct.
The dedicated Glass story already demonstrates the effect properly.
@ViewFromTheBox
ViewFromTheBox merged commit e8b6903 into release/1.0 Mar 30, 2026
1 check passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in ArtisanPack UI Overview Mar 30, 2026
@ViewFromTheBox
ViewFromTheBox deleted the enhancement/11-setup-storybook branch March 30, 2026 01:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

Set up Storybook for component documentation and development

1 participant