@fpkit/acss 1.0.0-beta.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +32 -0
  2. package/docs/README.md +325 -0
  3. package/docs/guides/accessibility.md +764 -0
  4. package/docs/guides/architecture.md +705 -0
  5. package/docs/guides/composition.md +688 -0
  6. package/docs/guides/css-variables.md +522 -0
  7. package/docs/guides/storybook.md +828 -0
  8. package/docs/guides/testing.md +817 -0
  9. package/docs/testing/focus-indicator-testing.md +437 -0
  10. package/libs/components/buttons/button.css +1 -1
  11. package/libs/components/buttons/button.css.map +1 -1
  12. package/libs/components/buttons/button.min.css +2 -2
  13. package/libs/components/icons/icon.d.cts +32 -32
  14. package/libs/components/icons/icon.d.ts +32 -32
  15. package/libs/components/list/list.css +1 -1
  16. package/libs/components/list/list.min.css +1 -1
  17. package/libs/index.css +1 -1
  18. package/libs/index.css.map +1 -1
  19. package/package.json +4 -3
  20. package/src/components/README.mdx +1 -1
  21. package/src/components/buttons/button.scss +5 -0
  22. package/src/components/buttons/button.stories.tsx +8 -5
  23. package/src/components/cards/card.stories.tsx +1 -1
  24. package/src/components/details/details.stories.tsx +1 -1
  25. package/src/components/form/form.stories.tsx +1 -1
  26. package/src/components/form/input.stories.tsx +1 -1
  27. package/src/components/form/select.stories.tsx +1 -1
  28. package/src/components/heading/README.mdx +292 -0
  29. package/src/components/icons/icon.stories.tsx +1 -1
  30. package/src/components/list/list.scss +1 -1
  31. package/src/components/nav/nav.stories.tsx +1 -1
  32. package/src/components/ui.stories.tsx +53 -19
  33. package/src/docs/accessibility.mdx +484 -0
  34. package/src/docs/composition.mdx +549 -0
  35. package/src/docs/css-variables.mdx +380 -0
  36. package/src/docs/fpkit-developer.mdx +545 -0
  37. package/src/introduction.mdx +356 -0
  38. package/src/styles/buttons/button.css +4 -0
  39. package/src/styles/buttons/button.css.map +1 -1
  40. package/src/styles/index.css +9 -3
  41. package/src/styles/index.css.map +1 -1
  42. package/src/styles/list/list.css +1 -1
  43. package/src/styles/utilities/_disabled.scss +5 -4
@@ -0,0 +1,828 @@
1
+ # Storybook Guide
2
+
3
+ ## Overview
4
+
5
+ This guide shows how to document custom components and compositions built with @fpkit/acss using Storybook. Whether you're building an internal component library or documenting your application components, Storybook provides an excellent development and documentation environment.
6
+
7
+ ---
8
+
9
+ ## Setup
10
+
11
+ ### Installation
12
+
13
+ ```bash
14
+ npm install -D @storybook/react @storybook/react-vite @storybook/test storybook
15
+ ```
16
+
17
+ ### Initialize Storybook
18
+
19
+ ```bash
20
+ npx storybook init
21
+ ```
22
+
23
+ ### Configuration
24
+
25
+ ```javascript
26
+ // .storybook/main.ts
27
+ import type { StorybookConfig } from '@storybook/react-vite'
28
+
29
+ const config: StorybookConfig = {
30
+ stories: ['../src/**/*.stories.@(js|jsx|ts|tsx|mdx)'],
31
+ addons: [
32
+ '@storybook/addon-links',
33
+ '@storybook/addon-essentials',
34
+ '@storybook/addon-interactions',
35
+ ],
36
+ framework: {
37
+ name: '@storybook/react-vite',
38
+ options: {},
39
+ },
40
+ }
41
+
42
+ export default config
43
+ ```
44
+
45
+ ### Import fpkit Styles
46
+
47
+ ```javascript
48
+ // .storybook/preview.ts
49
+ import '@fpkit/acss/libs/index.css'
50
+
51
+ export default {
52
+ parameters: {
53
+ actions: { argTypesRegex: '^on.*' },
54
+ controls: {
55
+ matchers: {
56
+ color: /(background|color)$/i,
57
+ date: /Date$/,
58
+ },
59
+ },
60
+ },
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Basic Story Structure
67
+
68
+ ### Creating Your First Story
69
+
70
+ ```typescript
71
+ // components/CustomButton.stories.tsx
72
+ import type { Meta, StoryObj } from '@storybook/react'
73
+ import { Button } from '@fpkit/acss'
74
+
75
+ // Your custom composed component
76
+ const CustomButton = ({ loading, children, ...props }) => (
77
+ <Button {...props} disabled={loading}>
78
+ {loading ? 'Loading...' : children}
79
+ </Button>
80
+ )
81
+
82
+ const meta = {
83
+ title: 'Components/CustomButton',
84
+ component: CustomButton,
85
+ tags: ['autodocs'],
86
+ args: {
87
+ children: 'Click me',
88
+ loading: false,
89
+ },
90
+ argTypes: {
91
+ loading: {
92
+ control: 'boolean',
93
+ description: 'Shows loading state',
94
+ },
95
+ onClick: {
96
+ action: 'clicked',
97
+ },
98
+ },
99
+ } satisfies Meta<typeof CustomButton>
100
+
101
+ export default meta
102
+ type Story = StoryObj<typeof meta>
103
+
104
+ export const Default: Story = {}
105
+
106
+ export const Loading: Story = {
107
+ args: {
108
+ loading: true,
109
+ },
110
+ }
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Story Patterns
116
+
117
+ ### Default Story
118
+
119
+ The simplest story uses the default args from `meta`:
120
+
121
+ ```typescript
122
+ export const Default: Story = {}
123
+ ```
124
+
125
+ ### Story with Custom Args
126
+
127
+ Override specific args for different variants:
128
+
129
+ ```typescript
130
+ export const Primary: Story = {
131
+ args: {
132
+ variant: 'primary',
133
+ children: 'Primary Action',
134
+ },
135
+ }
136
+
137
+ export const Secondary: Story = {
138
+ args: {
139
+ variant: 'secondary',
140
+ children: 'Secondary Action',
141
+ },
142
+ }
143
+
144
+ export const Disabled: Story = {
145
+ args: {
146
+ disabled: true,
147
+ children: 'Disabled Button',
148
+ },
149
+ }
150
+ ```
151
+
152
+ ### Story with Custom Render
153
+
154
+ For complex compositions:
155
+
156
+ ```typescript
157
+ import { Button, Badge } from '@fpkit/acss'
158
+
159
+ export const WithBadge: Story = {
160
+ render: (args) => (
161
+ <Button {...args}>
162
+ <span>{args.children}</span>
163
+ <Badge>New</Badge>
164
+ </Button>
165
+ ),
166
+ args: {
167
+ children: 'Featured',
168
+ },
169
+ }
170
+
171
+ export const MultipleButtons: Story = {
172
+ render: () => (
173
+ <div style={{ display: 'flex', gap: '1rem' }}>
174
+ <Button variant="primary">Save</Button>
175
+ <Button variant="secondary">Cancel</Button>
176
+ <Button variant="tertiary">Reset</Button>
177
+ </div>
178
+ ),
179
+ }
180
+ ```
181
+
182
+ ---
183
+
184
+ ## Documenting Composed Components
185
+
186
+ ### Card Composition Example
187
+
188
+ ```typescript
189
+ // components/ActionCard.stories.tsx
190
+ import type { Meta, StoryObj } from '@storybook/react'
191
+ import { Card, Button } from '@fpkit/acss'
192
+
193
+ const ActionCard = ({ title, children, actions }) => (
194
+ <Card>
195
+ <Card.Header>
196
+ <Card.Title>{title}</Card.Title>
197
+ </Card.Header>
198
+ <Card.Content>{children}</Card.Content>
199
+ <Card.Footer>
200
+ {actions.map((action, i) => (
201
+ <Button key={i} {...action} />
202
+ ))}
203
+ </Card.Footer>
204
+ </Card>
205
+ )
206
+
207
+ const meta = {
208
+ title: 'Compositions/ActionCard',
209
+ component: ActionCard,
210
+ tags: ['autodocs'],
211
+ parameters: {
212
+ docs: {
213
+ description: {
214
+ component: `
215
+ ActionCard is a composition of fpkit Card, Button, and sub-components.
216
+ It provides a consistent layout for cards with title, content, and action buttons.
217
+
218
+ **Composed from:**
219
+ - Card (fpkit)
220
+ - Card.Header (fpkit)
221
+ - Card.Title (fpkit)
222
+ - Card.Content (fpkit)
223
+ - Card.Footer (fpkit)
224
+ - Button (fpkit)
225
+ `,
226
+ },
227
+ },
228
+ },
229
+ } satisfies Meta<typeof ActionCard>
230
+
231
+ export default meta
232
+ type Story = StoryObj<typeof meta>
233
+
234
+ export const Default: Story = {
235
+ args: {
236
+ title: 'Confirm Action',
237
+ children: 'Are you sure you want to proceed with this action?',
238
+ actions: [
239
+ { children: 'Cancel', variant: 'secondary' },
240
+ { children: 'Confirm', variant: 'primary' },
241
+ ],
242
+ },
243
+ }
244
+
245
+ export const SingleAction: Story = {
246
+ args: {
247
+ title: 'Notification',
248
+ children: 'Your changes have been saved successfully.',
249
+ actions: [
250
+ { children: 'OK', variant: 'primary' },
251
+ ],
252
+ },
253
+ }
254
+ ```
255
+
256
+ ---
257
+
258
+ ## CSS Variable Customization Stories
259
+
260
+ Show how users can customize your components:
261
+
262
+ ```typescript
263
+ export const CustomStyling: Story = {
264
+ render: () => (
265
+ <div
266
+ style={{
267
+ '--btn-primary-bg': '#7c3aed',
268
+ '--btn-primary-color': 'white',
269
+ '--btn-radius': '2rem',
270
+ '--btn-padding-inline': '3rem',
271
+ } as React.CSSProperties}
272
+ >
273
+ <Button variant="primary">Custom Styled Button</Button>
274
+ </div>
275
+ ),
276
+ parameters: {
277
+ docs: {
278
+ description: {
279
+ story: `
280
+ Customize appearance using CSS variables:
281
+ - \`--btn-primary-bg\`: Background color
282
+ - \`--btn-primary-color\`: Text color
283
+ - \`--btn-radius\`: Border radius
284
+ - \`--btn-padding-inline\`: Horizontal padding
285
+
286
+ See the [CSS Variables Guide](/docs/guides/css-variables.md) for all available variables.
287
+ `,
288
+ },
289
+ },
290
+ },
291
+ }
292
+
293
+ export const ThemeExample: Story = {
294
+ render: () => (
295
+ <>
296
+ <div
297
+ className="light-theme"
298
+ style={{
299
+ '--btn-bg': 'white',
300
+ '--btn-color': '#333',
301
+ padding: '2rem',
302
+ background: '#f9f9f9',
303
+ } as React.CSSProperties}
304
+ >
305
+ <Button>Light Theme Button</Button>
306
+ </div>
307
+
308
+ <div
309
+ className="dark-theme"
310
+ style={{
311
+ '--btn-bg': '#2d2d2d',
312
+ '--btn-color': '#f0f0f0',
313
+ padding: '2rem',
314
+ background: '#1a1a1a',
315
+ marginTop: '1rem',
316
+ } as React.CSSProperties}
317
+ >
318
+ <Button>Dark Theme Button</Button>
319
+ </div>
320
+ </>
321
+ ),
322
+ parameters: {
323
+ docs: {
324
+ description: {
325
+ story: 'Theme-based customization using scoped CSS variables.',
326
+ },
327
+ },
328
+ },
329
+ }
330
+ ```
331
+
332
+ ---
333
+
334
+ ## Interactive Testing with Play Functions
335
+
336
+ Play functions enable automated interaction testing in Storybook:
337
+
338
+ ```typescript
339
+ import { within, userEvent, expect } from '@storybook/test'
340
+
341
+ export const InteractiveTest: Story = {
342
+ play: async ({ canvasElement, step }) => {
343
+ const canvas = within(canvasElement)
344
+ const button = canvas.getByRole('button')
345
+
346
+ await step('Button is rendered', async () => {
347
+ expect(button).toBeInTheDocument()
348
+ expect(button).toHaveTextContent('Click me')
349
+ })
350
+
351
+ await step('Button responds to click', async () => {
352
+ await userEvent.click(button)
353
+ // Check for expected behavior
354
+ })
355
+
356
+ await step('Button is keyboard accessible', async () => {
357
+ await userEvent.tab()
358
+ expect(button).toHaveFocus()
359
+ })
360
+ },
361
+ }
362
+ ```
363
+
364
+ ### Form Interaction Example
365
+
366
+ ```typescript
367
+ export const FormInteraction: Story = {
368
+ render: () => {
369
+ const [value, setValue] = useState('')
370
+ const [submitted, setSubmitted] = useState(false)
371
+
372
+ return (
373
+ <div>
374
+ <Input
375
+ value={value}
376
+ onChange={(e) => setValue(e.target.value)}
377
+ placeholder="Enter email..."
378
+ />
379
+ <Button
380
+ onClick={() => setSubmitted(true)}
381
+ disabled={!value}
382
+ >
383
+ Submit
384
+ </Button>
385
+ {submitted && <Alert variant="success">Form submitted!</Alert>}
386
+ </div>
387
+ )
388
+ },
389
+ play: async ({ canvasElement, step }) => {
390
+ const canvas = within(canvasElement)
391
+ const input = canvas.getByPlaceholderText('Enter email...')
392
+ const button = canvas.getByRole('button')
393
+
394
+ await step('User types into input', async () => {
395
+ await userEvent.type(input, 'test@example.com')
396
+ expect(input).toHaveValue('test@example.com')
397
+ })
398
+
399
+ await step('Submit button becomes enabled', async () => {
400
+ expect(button).not.toHaveAttribute('aria-disabled', 'true')
401
+ })
402
+
403
+ await step('User submits form', async () => {
404
+ await userEvent.click(button)
405
+ expect(canvas.getByRole('alert')).toHaveTextContent('Form submitted!')
406
+ })
407
+ },
408
+ }
409
+ ```
410
+
411
+ ---
412
+
413
+ ## ArgTypes Configuration
414
+
415
+ Control how props appear in Storybook controls:
416
+
417
+ ```typescript
418
+ const meta = {
419
+ component: CustomButton,
420
+ argTypes: {
421
+ variant: {
422
+ control: 'select',
423
+ options: ['primary', 'secondary', 'tertiary'],
424
+ description: 'Visual style variant',
425
+ table: {
426
+ defaultValue: { summary: 'primary' },
427
+ type: { summary: 'string' },
428
+ },
429
+ },
430
+ size: {
431
+ control: 'radio',
432
+ options: ['small', 'medium', 'large'],
433
+ description: 'Button size',
434
+ },
435
+ disabled: {
436
+ control: 'boolean',
437
+ description: 'Disables the button',
438
+ },
439
+ loading: {
440
+ control: 'boolean',
441
+ description: 'Shows loading state',
442
+ },
443
+ onClick: {
444
+ action: 'clicked',
445
+ description: 'Click event handler',
446
+ },
447
+ // Hide internal props
448
+ ref: { table: { disable: true } },
449
+ as: { table: { disable: true } },
450
+ },
451
+ } satisfies Meta<typeof CustomButton>
452
+ ```
453
+
454
+ ---
455
+
456
+ ## Documentation Parameters
457
+
458
+ ### Component Description
459
+
460
+ ```typescript
461
+ const meta = {
462
+ title: 'Components/StatusButton',
463
+ component: StatusButton,
464
+ parameters: {
465
+ docs: {
466
+ description: {
467
+ component: `
468
+ # StatusButton
469
+
470
+ A button component with an integrated status badge.
471
+
472
+ ## Features
473
+ - Built on fpkit Button
474
+ - Includes Badge for status indication
475
+ - Fully accessible
476
+ - Customizable via CSS variables
477
+
478
+ ## Usage
479
+ \`\`\`tsx
480
+ <StatusButton status="active" onClick={handleClick}>
481
+ Server Status
482
+ </StatusButton>
483
+ \`\`\`
484
+
485
+ ## Composed Components
486
+ - **Button** - Base interactive element
487
+ - **Badge** - Status indicator
488
+
489
+ See [Composition Guide](/docs/guides/composition.md) for composition patterns.
490
+ `,
491
+ },
492
+ },
493
+ },
494
+ } satisfies Meta<typeof StatusButton>
495
+ ```
496
+
497
+ ### Story Description
498
+
499
+ ```typescript
500
+ export const Primary: Story = {
501
+ args: {
502
+ variant: 'primary',
503
+ },
504
+ parameters: {
505
+ docs: {
506
+ description: {
507
+ story: 'Primary variant for high-emphasis actions.',
508
+ },
509
+ },
510
+ },
511
+ }
512
+ ```
513
+
514
+ ---
515
+
516
+ ## Organizing Stories
517
+
518
+ ### Title Hierarchy
519
+
520
+ ```typescript
521
+ // Group by category
522
+ title: 'Components/Buttons/CustomButton'
523
+ title: 'Components/Forms/SearchInput'
524
+ title: 'Compositions/Cards/ActionCard'
525
+
526
+ // Or by feature
527
+ title: 'Features/Authentication/LoginForm'
528
+ title: 'Features/Dashboard/StatsCard'
529
+
530
+ // Or by page
531
+ title: 'Pages/Home/HeroSection'
532
+ title: 'Pages/Settings/ProfileCard'
533
+ ```
534
+
535
+ ### Tags
536
+
537
+ ```typescript
538
+ tags: ['autodocs'] // Auto-generate documentation
539
+ tags: ['stable'] // Production-ready
540
+ tags: ['beta'] // Testing phase
541
+ tags: ['composition'] // fpkit composition
542
+ ```
543
+
544
+ ---
545
+
546
+ ## Accessibility Testing in Storybook
547
+
548
+ ### Basic Accessibility Test
549
+
550
+ ```typescript
551
+ export const AccessibilityTest: Story = {
552
+ play: async ({ canvasElement, step }) => {
553
+ const canvas = within(canvasElement)
554
+ const button = canvas.getByRole('button')
555
+
556
+ await step('Has accessible name', async () => {
557
+ expect(button).toHaveAccessibleName()
558
+ })
559
+
560
+ await step('Is keyboard navigable', async () => {
561
+ await userEvent.tab()
562
+ expect(button).toHaveFocus()
563
+ })
564
+
565
+ await step('Activates with Enter key', async () => {
566
+ await userEvent.keyboard('{Enter}')
567
+ })
568
+
569
+ await step('Activates with Space key', async () => {
570
+ button.focus()
571
+ await userEvent.keyboard(' ')
572
+ })
573
+ },
574
+ }
575
+ ```
576
+
577
+ ### ARIA Attributes Test
578
+
579
+ ```typescript
580
+ export const AriaAttributesTest: Story = {
581
+ args: {
582
+ 'aria-label': 'Close dialog',
583
+ 'aria-describedby': 'hint',
584
+ },
585
+ play: async ({ canvasElement, step }) => {
586
+ const canvas = within(canvasElement)
587
+ const button = canvas.getByRole('button')
588
+
589
+ await step('Has ARIA label', async () => {
590
+ expect(button).toHaveAttribute('aria-label', 'Close dialog')
591
+ })
592
+
593
+ await step('Has ARIA description', async () => {
594
+ expect(button).toHaveAttribute('aria-describedby', 'hint')
595
+ })
596
+ },
597
+ }
598
+ ```
599
+
600
+ ---
601
+
602
+ ## State Comparison Stories
603
+
604
+ Show different states side-by-side:
605
+
606
+ ```typescript
607
+ export const AllStates: Story = {
608
+ render: () => (
609
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
610
+ <div>
611
+ <h3>Default</h3>
612
+ <Button>Default Button</Button>
613
+ </div>
614
+
615
+ <div>
616
+ <h3>Hover</h3>
617
+ <Button className="hover">Hover State</Button>
618
+ </div>
619
+
620
+ <div>
621
+ <h3>Focus</h3>
622
+ <Button className="focus">Focus State</Button>
623
+ </div>
624
+
625
+ <div>
626
+ <h3>Disabled</h3>
627
+ <Button disabled>Disabled Button</Button>
628
+ </div>
629
+
630
+ <div>
631
+ <h3>Loading</h3>
632
+ <Button disabled>Loading...</Button>
633
+ </div>
634
+ </div>
635
+ ),
636
+ parameters: {
637
+ docs: {
638
+ description: {
639
+ story: 'Comparison of all button states',
640
+ },
641
+ },
642
+ },
643
+ }
644
+ ```
645
+
646
+ ---
647
+
648
+ ## Best Practices
649
+
650
+ ### ✅ Do
651
+
652
+ - **Document compositions** - Show which fpkit components you're using
653
+ - **Include play functions** - Test interactions automatically
654
+ - **Show CSS customization** - Demonstrate variable usage
655
+ - **Test accessibility** - Keyboard navigation, ARIA attributes
656
+ - **Use descriptive titles** - Clear hierarchy and organization
657
+ - **Add component descriptions** - Explain purpose and usage
658
+ - **Show state variants** - Default, hover, disabled, loading, error
659
+ - **Document props** - Use argTypes for prop documentation
660
+ - **Include usage examples** - Code snippets in descriptions
661
+
662
+ ### ❌ Don't
663
+
664
+ - **Don't duplicate fpkit stories** - Focus on your custom logic
665
+ - **Don't overcomplicate** - Keep stories simple and focused
666
+ - **Don't skip accessibility** - Always test keyboard and screen readers
667
+ - **Don't forget CSS imports** - Import fpkit styles in preview
668
+ - **Don't use vague titles** - Be specific about what's being demonstrated
669
+
670
+ ---
671
+
672
+ ## Running Storybook
673
+
674
+ ```bash
675
+ # Start Storybook dev server
676
+ npm run storybook
677
+
678
+ # Build Storybook static site
679
+ npm run build-storybook
680
+
681
+ # Serve built Storybook
682
+ npx http-server storybook-static
683
+ ```
684
+
685
+ ---
686
+
687
+ ## Example: Complete Story File
688
+
689
+ ```typescript
690
+ import type { Meta, StoryObj } from '@storybook/react'
691
+ import { within, userEvent, expect, fn } from '@storybook/test'
692
+ import { Button, Badge } from '@fpkit/acss'
693
+
694
+ // Your composed component
695
+ const NotificationButton = ({ count, onClick }) => (
696
+ <Button onClick={onClick} aria-label={`Notifications (${count} unread)`}>
697
+ <span aria-hidden="true">🔔</span>
698
+ {count > 0 && (
699
+ <Badge aria-hidden="true">{count}</Badge>
700
+ )}
701
+ </Button>
702
+ )
703
+
704
+ const meta = {
705
+ title: 'Compositions/NotificationButton',
706
+ component: NotificationButton,
707
+ tags: ['autodocs', 'composition'],
708
+ args: {
709
+ count: 3,
710
+ onClick: fn(),
711
+ },
712
+ argTypes: {
713
+ count: {
714
+ control: 'number',
715
+ description: 'Number of unread notifications',
716
+ },
717
+ onClick: {
718
+ action: 'clicked',
719
+ description: 'Click handler',
720
+ },
721
+ },
722
+ parameters: {
723
+ docs: {
724
+ description: {
725
+ component: `
726
+ # NotificationButton
727
+
728
+ A button displaying notification count with a badge.
729
+
730
+ **Composed from:**
731
+ - Button (fpkit)
732
+ - Badge (fpkit)
733
+
734
+ **Accessibility:**
735
+ - Uses \`aria-label\` to announce count to screen readers
736
+ - Visual elements hidden from screen readers with \`aria-hidden\`
737
+ `,
738
+ },
739
+ },
740
+ },
741
+ } satisfies Meta<typeof NotificationButton>
742
+
743
+ export default meta
744
+ type Story = StoryObj<typeof meta>
745
+
746
+ export const Default: Story = {}
747
+
748
+ export const NoNotifications: Story = {
749
+ args: {
750
+ count: 0,
751
+ },
752
+ }
753
+
754
+ export const ManyNotifications: Story = {
755
+ args: {
756
+ count: 99,
757
+ },
758
+ }
759
+
760
+ export const CustomStyling: Story = {
761
+ render: (args) => (
762
+ <div
763
+ style={{
764
+ '--btn-padding-inline': '1.5rem',
765
+ '--badge-bg': '#ef4444',
766
+ } as React.CSSProperties}
767
+ >
768
+ <NotificationButton {...args} />
769
+ </div>
770
+ ),
771
+ args: {
772
+ count: 5,
773
+ },
774
+ }
775
+
776
+ export const InteractiveTest: Story = {
777
+ args: {
778
+ count: 3,
779
+ },
780
+ play: async ({ canvasElement, step, args }) => {
781
+ const canvas = within(canvasElement)
782
+ const button = canvas.getByRole('button')
783
+
784
+ await step('Has accessible label with count', async () => {
785
+ expect(button).toHaveAttribute('aria-label', 'Notifications (3 unread)')
786
+ })
787
+
788
+ await step('Shows badge with count', async () => {
789
+ expect(canvas.getByText('3')).toBeInTheDocument()
790
+ })
791
+
792
+ await step('Calls onClick when clicked', async () => {
793
+ await userEvent.click(button)
794
+ expect(args.onClick).toHaveBeenCalledTimes(1)
795
+ })
796
+
797
+ await step('Is keyboard accessible', async () => {
798
+ await userEvent.tab()
799
+ expect(button).toHaveFocus()
800
+
801
+ await userEvent.keyboard('{Enter}')
802
+ expect(args.onClick).toHaveBeenCalledTimes(2)
803
+ })
804
+ },
805
+ }
806
+ ```
807
+
808
+ ---
809
+
810
+ ## Additional Resources
811
+
812
+ - **[Storybook Documentation](https://storybook.js.org/docs)** - Official Storybook docs
813
+ - **[Testing with Storybook](https://storybook.js.org/docs/writing-tests)** - Play functions and interaction testing
814
+ - **[Storybook Addons](https://storybook.js.org/addons)** - Extend functionality
815
+ - **[Component Story Format](https://storybook.js.org/docs/api/csf)** - CSF 3.0 specification
816
+
817
+ ---
818
+
819
+ ## Related Guides
820
+
821
+ - **[Composition Guide](./composition.md)** - Component composition patterns
822
+ - **[Testing Guide](./testing.md)** - Testing composed components
823
+ - **[Accessibility Guide](./accessibility.md)** - Accessibility testing patterns
824
+ - **[CSS Variables Guide](./css-variables.md)** - Styling customization
825
+
826
+ ---
827
+
828
+ **Remember**: Storybook is for documenting **your components and compositions**. fpkit components already have stories in the fpkit Storybook - focus on showcasing how you use and compose them in your application.