storybook
>
Install
mkdir -p .claude/skills/storybook-ghali21 && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/13670" && unzip -o skill.zip -d .claude/skills/storybook-ghali21 && rm skill.zipInstalls to .claude/skills/storybook-ghali21
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Storybook story generation standards for CleanMateX web-admin. Auto-invoked when generating or reviewing .stories.tsx files for Cmx Design System components (src/ui/) or feature components (src/features/). Covers story structure, RTL stories, a11y, mock patterns, and naming conventions. This file is the authoritative reference loaded by the storybook-generator agent before writing any story file.About this skill
Storybook Skill — CleanMateX
Scope
| This skill owns | Not this skill |
|---|---|
.stories.tsx file structure and content | Component logic or props design → /frontend |
| RTL story variants | Which RTL Tailwind classes to use → /frontend |
| Mock data and decorator patterns | i18n translation keys → /i18n |
| Story naming and file placement conventions | JSDoc on the component itself → /code-documentation |
| Storybook commands | Build/dev commands → /dev-commands |
Mandatory Rules
- Every component in
src/ui/must have a.stories.tsxfile — no exceptions. tags: ['autodocs']on everymeta— required for auto-generated docs page.- Always include an RTL story for any component that has visible text or directional layout.
parameters: { layout: 'centered' }for atomic components;'fullscreen'for layout/page components.- Never import from
@/components/uior@ui/compat— use@ui/primitives,@ui/feedback, etc. - No real API calls, auth, or DB access in stories — use static mock data and decorators.
- Story file lives alongside the component —
CmxButton.stories.tsxnext tocmx-button.tsx.
Story File Structure
import type { Meta, StoryObj } from '@storybook/nextjs'
import { fn } from 'storybook/test'
// Import component using @ui/* path alias
import { CmxButton } from '@ui/primitives'
// ─── Meta ─────────────────────────────────────────────────────────────────────
const meta = {
title: 'Primitives/CmxButton', // Category/ComponentName — see Naming below
component: CmxButton,
parameters: {
layout: 'centered', // 'centered' | 'fullscreen' | 'padded'
},
tags: ['autodocs'], // Required — generates API docs page
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'ghost', 'outline', 'destructive'],
description: 'Visual style variant',
},
size: {
control: 'select',
options: ['xs', 'sm', 'md', 'lg'],
},
loading: { control: 'boolean' },
disabled: { control: 'boolean' },
onClick: { action: 'clicked' }, // or fn() for interaction tests
},
args: {
// Default args shared by all stories — override per story
children: 'Button',
onClick: fn(),
},
} satisfies Meta<typeof CmxButton>
export default meta
type Story = StoryObj<typeof meta>
// ─── Stories ──────────────────────────────────────────────────────────────────
export const Primary: Story = {
args: { variant: 'primary', children: 'Save Order' },
}
export const Destructive: Story = {
args: { variant: 'destructive', children: 'Delete' },
}
export const Loading: Story = {
args: { loading: true, children: 'Saving…' },
}
export const Disabled: Story = {
args: { disabled: true, children: 'Unavailable' },
}
// RTL story — required for any component with text or directional layout
export const RTL: Story = {
args: { children: 'حفظ الطلب' },
parameters: { direction: 'rtl' }, // activates dir="rtl" via preview.ts decorator
}
Naming Conventions
title field — Category/ComponentName
| Source directory | Category prefix |
|---|---|
src/ui/primitives/ | Primitives/ |
src/ui/feedback/ | Feedback/ |
src/ui/forms/ | Forms/ |
src/ui/data-display/ | DataDisplay/ |
src/ui/navigation/ | Navigation/ |
src/ui/overlays/ | Overlays/ |
src/features/**/ | Features/FeatureName/ |
Export names (story variants)
Use PascalCase descriptive names — they appear as tabs in Storybook:
Default, Primary, Secondary, Ghost, Outline, Destructive
Loading, Disabled, WithIcon, WithError, WithHelpText
Small, Medium, Large
RTL ← always this exact name for Arabic layout stories
AllVariants ← when showing a grid of all variants in one story
Playground ← interactive story with all controls exposed
Per-Component Story Checklist
Primitives
CmxButton — stories: Primary, Secondary, Ghost, Outline, Destructive, Loading, Disabled, AllSizes, RTL
CmxInput — stories: Default, WithLabel, WithError, WithHelpText, WithLeftIcon, WithRightIcon, Disabled, RTL
CmxTextarea — stories: Default, WithLabel, WithError, Disabled, RTL
CmxSelect — stories: Default, WithLabel, WithError, Disabled, RTL
CmxCheckbox — stories: Default, Checked, Indeterminate, Disabled, WithLabel, RTL
CmxSwitch — stories: Default, Checked, Disabled, WithLabel, RTL
CmxSpinner — stories: Default, AllSizes
CmxCard — stories: Default, WithHeader, WithFooter, WithAll, RTL
Feedback
CmxStatusBadge — stories: AllVariants (grid), WithIcon, Pulse, AllSizes, RTL
CmxProgressBar — stories: Default, InProgress, Complete, RTL
CmxProgressIndicator — stories: Default, WithSteps, RTL
CmxSummaryMessage — stories: Success, Error, Warning, Info, RTL
CmxConfirmDialog — stories: Default, Open (use decorator to control open state), RTL
Forms
CmxForm — stories: Default (with CmxFormField children), WithValidation, RTL
CmxFormField — stories: Default, WithError, Required, RTL
CmxSelectDropdown — stories: Default, WithOptions, Disabled, RTL
CmxCheckboxGroup — stories: Default, WithPreselected, RTL
Data Display
CmxDataTable — stories: Default (with sample columns+data), Loading, Empty, WithPagination, RTL
CmxKpiStatCard — stories: Default, WithTrend, AllVariants, RTL
CmxEmptyState — stories: Default, WithAction, RTL
CmxProcessingStepTimeline — stories: Default, InProgress, Completed, RTL
Navigation
CmxTabsPanel — stories: Default, WithBadge, RTL
CmxProgressSteps — stories: Default, Step2Active, Completed, RTL
CmxLanguageSwitcher — stories: Default (no auth needed — static toggle)
Overlays
CmxDialog — stories: Default (closed), Open (use useState decorator), WithForm, RTL
RTL Stories — Pattern
The Storybook toolbar already has a Direction toggle (configured in .storybook/preview.ts). The RTL story sets it to 'rtl' by default so reviewers see Arabic layout immediately:
export const RTL: Story = {
name: 'RTL (Arabic)',
args: {
// Use real Arabic text — not Lorem Ipsum
label: 'رقم الطلب',
placeholder: 'أدخل رقم الطلب',
},
parameters: {
direction: 'rtl', // passed to preview.ts decorator → sets document.documentElement.dir
},
}
Real Arabic text to use in stories:
| Context | Arabic |
|---|---|
| Button save | حفظ |
| Button cancel | إلغاء |
| Button delete | حذف |
| Button submit | إرسال |
| Input label | رقم الطلب |
| Input placeholder | أدخل القيمة |
| Search placeholder | بحث... |
| Status: active | نشط |
| Status: processing | قيد المعالجة |
| Status: completed | مكتمل |
| Status: cancelled | ملغى |
| Table header | الاسم |
| Empty state title | لا توجد نتائج |
| Dialog title | تأكيد الحذف |
Mock Data Patterns
Static table data
// Mock data for CmxDataTable stories — no API call
const mockOrders = [
{ id: '1001', customer: 'Ahmed Al-Rashid', status: 'NEW', total: 45.00 },
{ id: '1002', customer: 'Sarah Johnson', status: 'IN_PROGRESS', total: 128.50 },
{ id: '1003', customer: 'Mohammed Ali', status: 'COMPLETED', total: 67.25 },
]
const mockColumns: ColumnDef<typeof mockOrders[0]>[] = [
{ accessorKey: 'id', header: 'Order #' },
{ accessorKey: 'customer', header: 'Customer' },
{ accessorKey: 'status', header: 'Status' },
{ accessorKey: 'total', header: 'Total' },
]
Controlled open state (Dialog, Confirm)
// Decorator to control open/close state for overlay components
export const Open: Story = {
decorators: [
(StoryComponent) => {
const [open, setOpen] = React.useState(true)
return <StoryComponent args={{ open, onOpenChange: setOpen }} />
},
],
}
Icon usage
import { CheckCircle, AlertTriangle, Info } from 'lucide-react'
// Pass Lucide icon components directly — they match LucideIcon type
args: { icon: CheckCircle, showIcon: true }
a11y Requirements
Every story must pass Storybook's @storybook/addon-a11y panel (no red violations). Common rules:
- Interactive elements must have accessible labels (
aria-labelor visible text) - Form inputs must be linked to labels (CmxInput handles this automatically via
id) - Color is not the only indicator of state (status badges use both color + text)
- Focus ring must be visible in keyboard nav (handled by Cmx theme tokens)
Stories that intentionally test accessibility edge cases:
export const A11yMinimal: Story = {
name: 'A11y: icon-only button',
args: { children: <SearchIcon />, 'aria-label': 'Search orders' },
}
Story Generation Workflow (for the agent)
When generating stories for a component, follow this sequence:
- Read the component file — extract props interface, variant values, default props
- Identify story set — use the per-component checklist above; add extras if component has unique behavior
- Write the
metablock — correcttitlecategory, fullargTypes, sharedargs - Write each story — one export per variant, use real-world content (not "foo", "test")
- Add RTL story — always, for any component with visible text
- Verify imports — use
@ui/*paths, never@/components/uior@ui/compat - Check
playfunctions — add for interactive stories (form submit, dialog open/close, etc.)
Commands
cd web-admin
npm run storybook # Dev server → localhost:6006 (hot reload)
npm run docs:storybook # Static build → docs/storybook/
npm run docs:generate # TypeDoc + Storybook static build together
File Placement
src/ui/primitives/
cmx-button.tsx
CmxButton.stories.tsx ← story lives next to component
src/ui/feedback/
cmx-status-badge.tsx
CmxStatusBadge.stories.tsx
src/features/orders/
OrderCard.tsx
Order
---
*Content truncated.*