@fpkit/acss 1.0.0-beta.1 → 2.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 (103) hide show
  1. package/README.md +92 -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/{chunk-7XPFW7CB.js → chunk-43TK2ICH.js} +2 -2
  11. package/libs/chunk-5PJYLVFY.cjs +17 -0
  12. package/libs/chunk-5PJYLVFY.cjs.map +1 -0
  13. package/libs/chunk-E4OSROCA.cjs +17 -0
  14. package/libs/chunk-E4OSROCA.cjs.map +1 -0
  15. package/libs/chunk-KVKQLRJG.js +10 -0
  16. package/libs/chunk-KVKQLRJG.js.map +1 -0
  17. package/libs/{chunk-QVW6W76L.cjs → chunk-MGPWZRBX.cjs} +3 -3
  18. package/libs/chunk-NNTBIHSD.js +8 -0
  19. package/libs/chunk-NNTBIHSD.js.map +1 -0
  20. package/libs/{chunk-X3JCTEPD.js → chunk-QKHPHMG2.js} +2 -2
  21. package/libs/{chunk-T4T6GWYQ.cjs → chunk-R7NLLZU2.cjs} +3 -3
  22. package/libs/{chunk-X5LGFCWG.js → chunk-UJAQVHWC.js} +3 -3
  23. package/libs/{chunk-DKTHCQ5P.cjs → chunk-X5RKCLDC.cjs} +3 -3
  24. package/libs/components/breadcrumbs/breadcrumb.cjs +5 -5
  25. package/libs/components/breadcrumbs/breadcrumb.d.cts +1 -1
  26. package/libs/components/breadcrumbs/breadcrumb.d.ts +1 -1
  27. package/libs/components/breadcrumbs/breadcrumb.js +2 -2
  28. package/libs/components/button.cjs +3 -3
  29. package/libs/components/button.d.cts +1 -1
  30. package/libs/components/button.d.ts +1 -1
  31. package/libs/components/button.js +1 -1
  32. package/libs/components/buttons/button.css +1 -1
  33. package/libs/components/buttons/button.css.map +1 -1
  34. package/libs/components/buttons/button.min.css +2 -2
  35. package/libs/components/dialog/dialog.cjs +4 -4
  36. package/libs/components/dialog/dialog.js +2 -2
  37. package/libs/components/icons/icon.d.cts +32 -32
  38. package/libs/components/icons/icon.d.ts +32 -32
  39. package/libs/components/link/link.cjs +11 -3
  40. package/libs/components/link/link.d.cts +131 -3
  41. package/libs/components/link/link.d.ts +131 -3
  42. package/libs/components/link/link.js +1 -1
  43. package/libs/components/list/list.css +1 -1
  44. package/libs/components/list/list.min.css +1 -1
  45. package/libs/components/modal.cjs +3 -3
  46. package/libs/components/modal.js +2 -2
  47. package/libs/hooks.cjs +3 -3
  48. package/libs/hooks.d.cts +1 -1
  49. package/libs/hooks.d.ts +1 -1
  50. package/libs/hooks.js +2 -2
  51. package/libs/index.cjs +12 -12
  52. package/libs/index.css +1 -1
  53. package/libs/index.css.map +1 -1
  54. package/libs/index.d.cts +237 -2
  55. package/libs/index.d.ts +237 -2
  56. package/libs/index.js +5 -5
  57. package/package.json +4 -3
  58. package/src/components/README.mdx +1 -1
  59. package/src/components/breadcrumbs/breadcrumb.test.tsx +1 -2
  60. package/src/components/buttons/README.mdx +19 -9
  61. package/src/components/buttons/button.scss +5 -0
  62. package/src/components/buttons/button.stories.tsx +8 -5
  63. package/src/components/buttons/button.tsx +19 -15
  64. package/src/components/cards/card.stories.tsx +1 -1
  65. package/src/components/details/details.stories.tsx +1 -1
  66. package/src/components/form/form.stories.tsx +1 -1
  67. package/src/components/form/input.stories.tsx +1 -1
  68. package/src/components/form/select.stories.tsx +1 -1
  69. package/src/components/heading/README.mdx +292 -0
  70. package/src/components/icons/icon.stories.tsx +1 -1
  71. package/src/components/link/link.stories.tsx +205 -8
  72. package/src/components/link/link.test.tsx +1 -1
  73. package/src/components/link/link.tsx +22 -0
  74. package/src/components/link/link.types.ts +11 -3
  75. package/src/components/list/list.scss +1 -1
  76. package/src/components/nav/nav.stories.tsx +1 -1
  77. package/src/components/ui.stories.tsx +53 -19
  78. package/src/docs/accessibility.mdx +484 -0
  79. package/src/docs/composition.mdx +549 -0
  80. package/src/docs/css-variables.mdx +380 -0
  81. package/src/docs/fpkit-developer.mdx +623 -0
  82. package/src/introduction.mdx +356 -0
  83. package/src/styles/buttons/button.css +4 -0
  84. package/src/styles/buttons/button.css.map +1 -1
  85. package/src/styles/index.css +9 -3
  86. package/src/styles/index.css.map +1 -1
  87. package/src/styles/list/list.css +1 -1
  88. package/src/styles/utilities/_disabled.scss +5 -4
  89. package/libs/chunk-33PNJ4LO.cjs +0 -15
  90. package/libs/chunk-33PNJ4LO.cjs.map +0 -1
  91. package/libs/chunk-GT77BX4L.cjs +0 -17
  92. package/libs/chunk-GT77BX4L.cjs.map +0 -1
  93. package/libs/chunk-OVWLQYMK.js +0 -10
  94. package/libs/chunk-OVWLQYMK.js.map +0 -1
  95. package/libs/chunk-UEPAWMDF.js +0 -8
  96. package/libs/chunk-UEPAWMDF.js.map +0 -1
  97. package/libs/link-5192f411.d.ts +0 -323
  98. /package/libs/{chunk-7XPFW7CB.js.map → chunk-43TK2ICH.js.map} +0 -0
  99. /package/libs/{chunk-QVW6W76L.cjs.map → chunk-MGPWZRBX.cjs.map} +0 -0
  100. /package/libs/{chunk-X3JCTEPD.js.map → chunk-QKHPHMG2.js.map} +0 -0
  101. /package/libs/{chunk-T4T6GWYQ.cjs.map → chunk-R7NLLZU2.cjs.map} +0 -0
  102. /package/libs/{chunk-X5LGFCWG.js.map → chunk-UJAQVHWC.js.map} +0 -0
  103. /package/libs/{chunk-DKTHCQ5P.cjs.map → chunk-X5RKCLDC.cjs.map} +0 -0
@@ -0,0 +1,549 @@
1
+ import { Meta } from "@storybook/addon-docs/blocks";
2
+
3
+ <Meta title="Guides/Composition" />
4
+
5
+ # Component Composition Guide
6
+
7
+ Learn how to build custom components by composing existing @fpkit/acss
8
+ primitives.
9
+
10
+ > **📖 Full Guide:** For comprehensive documentation, see
11
+ > [docs/guides/composition.md](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/composition.md)
12
+
13
+ ---
14
+
15
+ ## Why Composition?
16
+
17
+ **Composition over duplication** ensures:
18
+
19
+ - ✅ **Consistency** - Reusing components ensures UI consistency
20
+ - ✅ **Maintainability** - Bug fixes propagate automatically
21
+ - ✅ **Reduced Code** - Less code to write and maintain
22
+ - ✅ **Tested Components** - Leverage existing test coverage
23
+ - ✅ **Accessibility** - Inherit WCAG-compliant patterns
24
+
25
+ ---
26
+
27
+ ## Decision Tree
28
+
29
+ ```
30
+ ┌─────────────────────────────────────┐
31
+ │ New Component Need: "ComponentName" │
32
+ └──────────────┬──────────────────────┘
33
+
34
+
35
+ ┌──────────────────────┐
36
+ │ Does fpkit have a │ YES → Use fpkit component
37
+ │ component that meets │ Customize with CSS variables
38
+ │ the need exactly? │
39
+ └──────┬───────────────┘
40
+ │ NO
41
+
42
+ ┌──────────────────────┐
43
+ │ Can it be built by │ YES → Compose existing components
44
+ │ combining 2+ fpkit │ Import and combine
45
+ │ components? │
46
+ └──────┬───────────────┘
47
+ │ NO
48
+
49
+ ┌──────────────────────┐
50
+ │ Can I extend an │ YES → Wrap fpkit component
51
+ │ fpkit component with │ Add custom logic/styling
52
+ │ additional features? │
53
+ └──────┬───────────────┘
54
+ │ NO
55
+
56
+ ┌──────────────────────┐
57
+ │ Create custom │
58
+ │ component from │
59
+ │ scratch using fpkit │
60
+ │ styling patterns │
61
+ └─────────────────────┘
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Common Patterns
67
+
68
+ ### Pattern 1: Container + Content
69
+
70
+ Wrap fpkit components with additional structure:
71
+
72
+ ```tsx
73
+ import { Button, Badge } from "@fpkit/acss";
74
+
75
+ export const StatusButton = ({ status, children, ...props }) => {
76
+ return (
77
+ <Button {...props}>
78
+ {children}
79
+ <Badge variant={status}>{status}</Badge>
80
+ </Button>
81
+ );
82
+ };
83
+
84
+ // Usage
85
+ <StatusButton status="active">Server Status</StatusButton>;
86
+ ```
87
+
88
+ ---
89
+
90
+ ### Pattern 2: Conditional Composition
91
+
92
+ Different combinations based on props:
93
+
94
+ ```tsx
95
+ import { Alert, Dialog } from '@fpkit/acss'
96
+
97
+ export const Notification = ({ inline, variant, children, ...props }) => {
98
+ if (inline) {
99
+ return <Alert variant={variant}>{children}</Alert>
100
+ }
101
+
102
+ return (
103
+ <Dialog {...props}>
104
+ <Alert variant={variant}>{children}</Alert>
105
+ </Dialog>
106
+ )
107
+ }
108
+
109
+ // Usage
110
+ <Notification inline variant="success">Saved!</Notification>
111
+ <Notification isOpen={showModal} variant="error">Error!</Notification>
112
+ ```
113
+
114
+ ---
115
+
116
+ ### Pattern 3: Enhanced Wrapper
117
+
118
+ Add behavior around fpkit components:
119
+
120
+ ```tsx
121
+ import { Button } from "@fpkit/acss";
122
+ import { useState } from "react";
123
+
124
+ export const LoadingButton = ({ loading, onClick, children, ...props }) => {
125
+ const [isLoading, setIsLoading] = useState(loading);
126
+
127
+ const handleClick = async (e) => {
128
+ setIsLoading(true);
129
+ try {
130
+ await onClick?.(e);
131
+ } finally {
132
+ setIsLoading(false);
133
+ }
134
+ };
135
+
136
+ return (
137
+ <Button
138
+ {...props}
139
+ disabled={isLoading || props.disabled}
140
+ onClick={handleClick}
141
+ >
142
+ {isLoading ? "Loading..." : children}
143
+ </Button>
144
+ );
145
+ };
146
+
147
+ // Usage
148
+ <LoadingButton onClick={async () => await saveData()}>Save</LoadingButton>;
149
+ ```
150
+
151
+ ---
152
+
153
+ ### Pattern 4: List of Components
154
+
155
+ Render multiple instances:
156
+
157
+ ```tsx
158
+ import { Tag } from "@fpkit/acss";
159
+
160
+ export const TagList = ({ tags, onRemove, ...props }) => {
161
+ return (
162
+ <div className="tag-list" {...props}>
163
+ {tags.map((tag) => (
164
+ <Tag key={tag.id} onClose={onRemove ? () => onRemove(tag) : undefined}>
165
+ {tag.label}
166
+ </Tag>
167
+ ))}
168
+ </div>
169
+ );
170
+ };
171
+
172
+ // Usage
173
+ <TagList
174
+ tags={[
175
+ { id: 1, label: "React" },
176
+ { id: 2, label: "TypeScript" },
177
+ ]}
178
+ onRemove={handleRemoveTag}
179
+ />;
180
+ ```
181
+
182
+ ---
183
+
184
+ ### Pattern 5: Compound Component
185
+
186
+ Multiple related components working together:
187
+
188
+ ```tsx
189
+ import { Card, Button } from "@fpkit/acss";
190
+
191
+ export const ActionCard = ({ title, children, actions, ...props }) => {
192
+ return (
193
+ <Card {...props}>
194
+ <Card.Header>
195
+ <Card.Title>{title}</Card.Title>
196
+ </Card.Header>
197
+ <Card.Content>{children}</Card.Content>
198
+ {actions && (
199
+ <Card.Footer>
200
+ {actions.map((action, i) => (
201
+ <Button key={i} {...action} />
202
+ ))}
203
+ </Card.Footer>
204
+ )}
205
+ </Card>
206
+ );
207
+ };
208
+
209
+ // Usage
210
+ <ActionCard
211
+ title="Confirm Action"
212
+ actions={[
213
+ { children: "Cancel", variant: "secondary", onClick: handleCancel },
214
+ { children: "Confirm", variant: "primary", onClick: handleConfirm },
215
+ ]}
216
+ >
217
+ Are you sure you want to proceed?
218
+ </ActionCard>;
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Real-World Examples
224
+
225
+ ### Example 1: Icon Button
226
+
227
+ ```tsx
228
+ import { Button } from "@fpkit/acss";
229
+
230
+ export const IconButton = ({
231
+ icon,
232
+ children,
233
+ iconPosition = "left",
234
+ ...props
235
+ }) => {
236
+ return (
237
+ <Button {...props}>
238
+ {iconPosition === "left" && <span className="icon">{icon}</span>}
239
+ {children}
240
+ {iconPosition === "right" && <span className="icon">{icon}</span>}
241
+ </Button>
242
+ );
243
+ };
244
+
245
+ // Usage
246
+ <IconButton icon="💾" variant="primary">
247
+ Save Changes
248
+ </IconButton>;
249
+ ```
250
+
251
+ ---
252
+
253
+ ### Example 2: Confirm Button
254
+
255
+ ```tsx
256
+ import { Button, Dialog } from "@fpkit/acss";
257
+ import { useState } from "react";
258
+
259
+ export const ConfirmButton = ({
260
+ confirmTitle = "Confirm Action",
261
+ confirmMessage = "Are you sure?",
262
+ onConfirm,
263
+ children,
264
+ ...props
265
+ }) => {
266
+ const [showConfirm, setShowConfirm] = useState(false);
267
+
268
+ const handleConfirm = () => {
269
+ setShowConfirm(false);
270
+ onConfirm?.();
271
+ };
272
+
273
+ return (
274
+ <>
275
+ <Button {...props} onClick={() => setShowConfirm(true)}>
276
+ {children}
277
+ </Button>
278
+
279
+ <Dialog isOpen={showConfirm} onClose={() => setShowConfirm(false)}>
280
+ <h2>{confirmTitle}</h2>
281
+ <p>{confirmMessage}</p>
282
+ <div className="dialog-actions">
283
+ <Button variant="secondary" onClick={() => setShowConfirm(false)}>
284
+ Cancel
285
+ </Button>
286
+ <Button variant="primary" onClick={handleConfirm}>
287
+ Confirm
288
+ </Button>
289
+ </div>
290
+ </Dialog>
291
+ </>
292
+ );
293
+ };
294
+
295
+ // Usage
296
+ <ConfirmButton
297
+ variant="danger"
298
+ confirmTitle="Delete Account"
299
+ confirmMessage="This action cannot be undone."
300
+ onConfirm={handleDeleteAccount}
301
+ >
302
+ Delete Account
303
+ </ConfirmButton>;
304
+ ```
305
+
306
+ ---
307
+
308
+ ### Example 3: Tag Input
309
+
310
+ ```tsx
311
+ import { Tag } from "@fpkit/acss";
312
+ import { useState } from "react";
313
+
314
+ export const TagInput = ({ value = [], onChange, placeholder, ...props }) => {
315
+ const [inputValue, setInputValue] = useState("");
316
+
317
+ const addTag = () => {
318
+ if (inputValue.trim() && !value.includes(inputValue.trim())) {
319
+ onChange?.([...value, inputValue.trim()]);
320
+ setInputValue("");
321
+ }
322
+ };
323
+
324
+ const removeTag = (tagToRemove) => {
325
+ onChange?.(value.filter((tag) => tag !== tagToRemove));
326
+ };
327
+
328
+ return (
329
+ <div className="tag-input" {...props}>
330
+ <div className="tag-list">
331
+ {value.map((tag) => (
332
+ <Tag key={tag} onClose={() => removeTag(tag)}>
333
+ {tag}
334
+ </Tag>
335
+ ))}
336
+ </div>
337
+ <input
338
+ type="text"
339
+ value={inputValue}
340
+ onChange={(e) => setInputValue(e.target.value)}
341
+ onKeyDown={(e) => {
342
+ if (e.key === "Enter") {
343
+ e.preventDefault();
344
+ addTag();
345
+ }
346
+ }}
347
+ placeholder={placeholder || "Add tag..."}
348
+ />
349
+ </div>
350
+ );
351
+ };
352
+
353
+ // Usage
354
+ <TagInput value={tags} onChange={setTags} placeholder="Add technology..." />;
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Anti-Patterns to Avoid
360
+
361
+ ### ❌ Over-Composition
362
+
363
+ Too many nested layers:
364
+
365
+ ```tsx
366
+ // ❌ Bad
367
+ <OuterWrapper>
368
+ <MiddleContainer>
369
+ <InnerBox>
370
+ <ContentWrapper>
371
+ <Button>Click</Button>
372
+ </ContentWrapper>
373
+ </InnerBox>
374
+ </MiddleContainer>
375
+ </OuterWrapper>
376
+
377
+ // ✅ Good
378
+ <Container>
379
+ <Button>Click</Button>
380
+ </Container>
381
+ ```
382
+
383
+ **Rule:** Keep composition depth ≤ 3 levels.
384
+
385
+ ---
386
+
387
+ ### ❌ Prop Drilling
388
+
389
+ Passing props through multiple layers:
390
+
391
+ ```tsx
392
+ // ❌ Bad
393
+ <Wrapper theme={theme} size={size}>
394
+ <Container theme={theme} size={size}>
395
+ <Button theme={theme} size={size} />
396
+ </Container>
397
+ </Wrapper>
398
+
399
+ // ✅ Good
400
+ const ThemeContext = createContext()
401
+
402
+ <ThemeProvider value={{ theme, size }}>
403
+ <Wrapper>
404
+ <Container>
405
+ <Button />
406
+ </Container>
407
+ </Wrapper>
408
+ </ThemeProvider>
409
+ ```
410
+
411
+ **Rule:** If passing >3 props through >2 levels, use context.
412
+
413
+ ---
414
+
415
+ ### ❌ Duplicating Instead of Composing
416
+
417
+ ```tsx
418
+ // ❌ Bad
419
+ export const Status = ({ variant, children }) => {
420
+ return <span className={`status status-${variant}`}>{children}</span>;
421
+ };
422
+
423
+ // ✅ Good
424
+ import { Badge } from "@fpkit/acss";
425
+ export const Status = Badge;
426
+ ```
427
+
428
+ **Rule:** If code looks similar to fpkit, reuse it.
429
+
430
+ ---
431
+
432
+ ### ❌ Composing Incompatible Components
433
+
434
+ ```tsx
435
+ // ❌ Bad - nested interactive elements (a11y violation)
436
+ <Link href="/page">
437
+ <Button>Click me</Button>
438
+ </Link>
439
+
440
+ // ✅ Good - use polymorphic 'as' prop
441
+ <Button as="a" href="/page">
442
+ Click me
443
+ </Button>
444
+ ```
445
+
446
+ **Rule:** Check component APIs for `as` prop support.
447
+
448
+ ---
449
+
450
+ ## Styling Composed Components
451
+
452
+ Customize with CSS variables:
453
+
454
+ ```tsx
455
+ import { Button, Badge } from "@fpkit/acss";
456
+
457
+ export const PriorityButton = ({ priority, children, ...props }) => {
458
+ return (
459
+ <Button
460
+ {...props}
461
+ style={{
462
+ "--btn-padding-inline": "2rem",
463
+ "--btn-gap": "0.75rem",
464
+ }}
465
+ >
466
+ {children}
467
+ <Badge
468
+ variant={priority === "high" ? "error" : "default"}
469
+ style={{
470
+ "--badge-fs": "0.75rem",
471
+ }}
472
+ >
473
+ {priority}
474
+ </Badge>
475
+ </Button>
476
+ );
477
+ };
478
+ ```
479
+
480
+ ---
481
+
482
+ ## TypeScript Support
483
+
484
+ Extend fpkit types:
485
+
486
+ ```tsx
487
+ import { Button, type ButtonProps } from "@fpkit/acss";
488
+
489
+ interface LoadingButtonProps extends ButtonProps {
490
+ loading?: boolean;
491
+ loadingText?: string;
492
+ }
493
+
494
+ export const LoadingButton = ({
495
+ loading,
496
+ loadingText = "Loading...",
497
+ children,
498
+ ...props
499
+ }: LoadingButtonProps) => {
500
+ return (
501
+ <Button {...props} disabled={loading || props.disabled}>
502
+ {loading ? loadingText : children}
503
+ </Button>
504
+ );
505
+ };
506
+ ```
507
+
508
+ ---
509
+
510
+ ## Best Practices
511
+
512
+ ### ✅ Do
513
+
514
+ - Start with fpkit components
515
+ - Preserve accessibility
516
+ - Use CSS variables for customization
517
+ - Document which fpkit components you're using
518
+ - Test integration
519
+ - Export cleanly
520
+
521
+ ### ❌ Don't
522
+
523
+ - Don't duplicate fpkit logic
524
+ - Don't break accessibility (nested interactive elements)
525
+ - Don't over-compose (≤3 levels)
526
+ - Don't prop drill (use context)
527
+ - Don't ignore polymorphism (`as` prop)
528
+
529
+ ---
530
+
531
+ ## Additional Resources
532
+
533
+ - **📖
534
+ [Full Composition Guide](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/composition.md)** -
535
+ Comprehensive patterns and examples
536
+ - **🎨
537
+ [CSS Variables Guide](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/css-variables.md)** -
538
+ Styling composed components
539
+ - **♿
540
+ [Accessibility Guide](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/accessibility.md)** -
541
+ Maintaining accessibility
542
+ - **🧪
543
+ [Testing Guide](https://github.com/shawn-sandy/acss/blob/main/packages/fpkit/docs/guides/testing.md)** -
544
+ Testing compositions
545
+
546
+ ---
547
+
548
+ **Remember:** Compose when it creates clearer, more maintainable code that
549
+ leverages tested, accessible primitives from @fpkit/acss.