@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.
- package/README.md +92 -0
- package/docs/README.md +325 -0
- package/docs/guides/accessibility.md +764 -0
- package/docs/guides/architecture.md +705 -0
- package/docs/guides/composition.md +688 -0
- package/docs/guides/css-variables.md +522 -0
- package/docs/guides/storybook.md +828 -0
- package/docs/guides/testing.md +817 -0
- package/docs/testing/focus-indicator-testing.md +437 -0
- package/libs/{chunk-7XPFW7CB.js → chunk-43TK2ICH.js} +2 -2
- package/libs/chunk-5PJYLVFY.cjs +17 -0
- package/libs/chunk-5PJYLVFY.cjs.map +1 -0
- package/libs/chunk-E4OSROCA.cjs +17 -0
- package/libs/chunk-E4OSROCA.cjs.map +1 -0
- package/libs/chunk-KVKQLRJG.js +10 -0
- package/libs/chunk-KVKQLRJG.js.map +1 -0
- package/libs/{chunk-QVW6W76L.cjs → chunk-MGPWZRBX.cjs} +3 -3
- package/libs/chunk-NNTBIHSD.js +8 -0
- package/libs/chunk-NNTBIHSD.js.map +1 -0
- package/libs/{chunk-X3JCTEPD.js → chunk-QKHPHMG2.js} +2 -2
- package/libs/{chunk-T4T6GWYQ.cjs → chunk-R7NLLZU2.cjs} +3 -3
- package/libs/{chunk-X5LGFCWG.js → chunk-UJAQVHWC.js} +3 -3
- package/libs/{chunk-DKTHCQ5P.cjs → chunk-X5RKCLDC.cjs} +3 -3
- package/libs/components/breadcrumbs/breadcrumb.cjs +5 -5
- package/libs/components/breadcrumbs/breadcrumb.d.cts +1 -1
- package/libs/components/breadcrumbs/breadcrumb.d.ts +1 -1
- package/libs/components/breadcrumbs/breadcrumb.js +2 -2
- package/libs/components/button.cjs +3 -3
- package/libs/components/button.d.cts +1 -1
- package/libs/components/button.d.ts +1 -1
- package/libs/components/button.js +1 -1
- package/libs/components/buttons/button.css +1 -1
- package/libs/components/buttons/button.css.map +1 -1
- package/libs/components/buttons/button.min.css +2 -2
- package/libs/components/dialog/dialog.cjs +4 -4
- package/libs/components/dialog/dialog.js +2 -2
- package/libs/components/icons/icon.d.cts +32 -32
- package/libs/components/icons/icon.d.ts +32 -32
- package/libs/components/link/link.cjs +11 -3
- package/libs/components/link/link.d.cts +131 -3
- package/libs/components/link/link.d.ts +131 -3
- package/libs/components/link/link.js +1 -1
- package/libs/components/list/list.css +1 -1
- package/libs/components/list/list.min.css +1 -1
- package/libs/components/modal.cjs +3 -3
- package/libs/components/modal.js +2 -2
- package/libs/hooks.cjs +3 -3
- package/libs/hooks.d.cts +1 -1
- package/libs/hooks.d.ts +1 -1
- package/libs/hooks.js +2 -2
- package/libs/index.cjs +12 -12
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/libs/index.d.cts +237 -2
- package/libs/index.d.ts +237 -2
- package/libs/index.js +5 -5
- package/package.json +4 -3
- package/src/components/README.mdx +1 -1
- package/src/components/breadcrumbs/breadcrumb.test.tsx +1 -2
- package/src/components/buttons/README.mdx +19 -9
- package/src/components/buttons/button.scss +5 -0
- package/src/components/buttons/button.stories.tsx +8 -5
- package/src/components/buttons/button.tsx +19 -15
- package/src/components/cards/card.stories.tsx +1 -1
- package/src/components/details/details.stories.tsx +1 -1
- package/src/components/form/form.stories.tsx +1 -1
- package/src/components/form/input.stories.tsx +1 -1
- package/src/components/form/select.stories.tsx +1 -1
- package/src/components/heading/README.mdx +292 -0
- package/src/components/icons/icon.stories.tsx +1 -1
- package/src/components/link/link.stories.tsx +205 -8
- package/src/components/link/link.test.tsx +1 -1
- package/src/components/link/link.tsx +22 -0
- package/src/components/link/link.types.ts +11 -3
- package/src/components/list/list.scss +1 -1
- package/src/components/nav/nav.stories.tsx +1 -1
- package/src/components/ui.stories.tsx +53 -19
- package/src/docs/accessibility.mdx +484 -0
- package/src/docs/composition.mdx +549 -0
- package/src/docs/css-variables.mdx +380 -0
- package/src/docs/fpkit-developer.mdx +623 -0
- package/src/introduction.mdx +356 -0
- package/src/styles/buttons/button.css +4 -0
- package/src/styles/buttons/button.css.map +1 -1
- package/src/styles/index.css +9 -3
- package/src/styles/index.css.map +1 -1
- package/src/styles/list/list.css +1 -1
- package/src/styles/utilities/_disabled.scss +5 -4
- package/libs/chunk-33PNJ4LO.cjs +0 -15
- package/libs/chunk-33PNJ4LO.cjs.map +0 -1
- package/libs/chunk-GT77BX4L.cjs +0 -17
- package/libs/chunk-GT77BX4L.cjs.map +0 -1
- package/libs/chunk-OVWLQYMK.js +0 -10
- package/libs/chunk-OVWLQYMK.js.map +0 -1
- package/libs/chunk-UEPAWMDF.js +0 -8
- package/libs/chunk-UEPAWMDF.js.map +0 -1
- package/libs/link-5192f411.d.ts +0 -323
- /package/libs/{chunk-7XPFW7CB.js.map → chunk-43TK2ICH.js.map} +0 -0
- /package/libs/{chunk-QVW6W76L.cjs.map → chunk-MGPWZRBX.cjs.map} +0 -0
- /package/libs/{chunk-X3JCTEPD.js.map → chunk-QKHPHMG2.js.map} +0 -0
- /package/libs/{chunk-T4T6GWYQ.cjs.map → chunk-R7NLLZU2.cjs.map} +0 -0
- /package/libs/{chunk-X5LGFCWG.js.map → chunk-UJAQVHWC.js.map} +0 -0
- /package/libs/{chunk-DKTHCQ5P.cjs.map → chunk-X5RKCLDC.cjs.map} +0 -0
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
# Architecture Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
@fpkit/acss follows consistent architectural patterns that ensure maintainability, type safety, accessibility, and reusability. This guide explains these patterns to help you use and compose fpkit components effectively.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The UI Component Foundation
|
|
10
|
+
|
|
11
|
+
All fpkit components are built on a polymorphic `UI` base component that provides:
|
|
12
|
+
|
|
13
|
+
- **Polymorphic rendering** - Render as any HTML element via `as` prop
|
|
14
|
+
- **Type-safe prop spreading** - Full TypeScript support
|
|
15
|
+
- **Style merging** - Combine default and custom styles
|
|
16
|
+
- **Ref forwarding** - For focus management
|
|
17
|
+
- **Automatic ARIA** - Attribute forwarding
|
|
18
|
+
|
|
19
|
+
### Understanding Polymorphism
|
|
20
|
+
|
|
21
|
+
The `as` prop lets you change the rendered HTML element while preserving component behavior:
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { Button, Card } from '@fpkit/acss'
|
|
25
|
+
|
|
26
|
+
// Button as link
|
|
27
|
+
<Button as="a" href="/page">
|
|
28
|
+
Navigate
|
|
29
|
+
</Button>
|
|
30
|
+
// Renders: <a href="/page">Navigate</a>
|
|
31
|
+
|
|
32
|
+
// Card as section instead of article
|
|
33
|
+
<Card as="section">
|
|
34
|
+
Content
|
|
35
|
+
</Card>
|
|
36
|
+
// Renders: <section>Content</section>
|
|
37
|
+
|
|
38
|
+
// Badge as span instead of sup
|
|
39
|
+
<Badge as="span">
|
|
40
|
+
Label
|
|
41
|
+
</Badge>
|
|
42
|
+
// Renders: <span>Label</span>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Why this matters:**
|
|
46
|
+
- Semantic HTML flexibility
|
|
47
|
+
- Better accessibility (correct element for the job)
|
|
48
|
+
- SEO benefits (proper HTML structure)
|
|
49
|
+
- CSS targeting (style based on element type)
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Component Patterns
|
|
54
|
+
|
|
55
|
+
### Simple Components
|
|
56
|
+
|
|
57
|
+
Standalone components with no sub-components.
|
|
58
|
+
|
|
59
|
+
**Examples**: Badge, Button, Tag, Icon
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
import { Badge } from '@fpkit/acss'
|
|
63
|
+
|
|
64
|
+
// Simple component with variant
|
|
65
|
+
<Badge variant="rounded">New</Badge>
|
|
66
|
+
|
|
67
|
+
// Polymorphic - render as different element
|
|
68
|
+
<Badge as="span">Label</Badge>
|
|
69
|
+
|
|
70
|
+
// Custom styles via CSS variables
|
|
71
|
+
<Badge style={{ '--badge-bg': '#ff0000' }}>
|
|
72
|
+
Alert
|
|
73
|
+
</Badge>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Characteristics:**
|
|
77
|
+
- Single export
|
|
78
|
+
- Props extend base HTML element props
|
|
79
|
+
- Variants via `data-*` attributes
|
|
80
|
+
- Customizable via CSS variables
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### Compound Components
|
|
85
|
+
|
|
86
|
+
Complex components with multiple related sub-components.
|
|
87
|
+
|
|
88
|
+
**Examples**: Card, Dialog, Alert, Form
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { Card } from '@fpkit/acss'
|
|
92
|
+
|
|
93
|
+
// Using sub-components
|
|
94
|
+
<Card>
|
|
95
|
+
<Card.Header>
|
|
96
|
+
<Card.Title>Title</Card.Title>
|
|
97
|
+
</Card.Header>
|
|
98
|
+
<Card.Content>
|
|
99
|
+
Content goes here
|
|
100
|
+
</Card.Content>
|
|
101
|
+
<Card.Footer>
|
|
102
|
+
<Button>Action</Button>
|
|
103
|
+
</Card.Footer>
|
|
104
|
+
</Card>
|
|
105
|
+
|
|
106
|
+
// Or import individually
|
|
107
|
+
import { Card, CardHeader, CardTitle, CardContent, CardFooter } from '@fpkit/acss'
|
|
108
|
+
|
|
109
|
+
<Card>
|
|
110
|
+
<CardHeader>
|
|
111
|
+
<CardTitle>Title</CardTitle>
|
|
112
|
+
</CardHeader>
|
|
113
|
+
<CardContent>Content</CardContent>
|
|
114
|
+
<CardFooter>
|
|
115
|
+
<Button>Action</Button>
|
|
116
|
+
</CardFooter>
|
|
117
|
+
</Card>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Characteristics:**
|
|
121
|
+
- Main component + sub-components
|
|
122
|
+
- Available as `Component.SubComponent` or individual exports
|
|
123
|
+
- Each sub-component is independently customizable
|
|
124
|
+
- Flexible composition (use what you need)
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## TypeScript Support
|
|
129
|
+
|
|
130
|
+
### Component Props
|
|
131
|
+
|
|
132
|
+
All fpkit components are fully typed:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
import type { ButtonProps, CardProps } from '@fpkit/acss'
|
|
136
|
+
|
|
137
|
+
// Extend fpkit component props
|
|
138
|
+
interface CustomButtonProps extends ButtonProps {
|
|
139
|
+
loading?: boolean
|
|
140
|
+
loadingText?: string
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const CustomButton = ({
|
|
144
|
+
loading,
|
|
145
|
+
loadingText = 'Loading...',
|
|
146
|
+
children,
|
|
147
|
+
...props
|
|
148
|
+
}: CustomButtonProps) => {
|
|
149
|
+
return (
|
|
150
|
+
<Button {...props}>
|
|
151
|
+
{loading ? loadingText : children}
|
|
152
|
+
</Button>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Polymorphic Types
|
|
158
|
+
|
|
159
|
+
Types adapt based on the `as` prop:
|
|
160
|
+
|
|
161
|
+
```tsx
|
|
162
|
+
// Button as button - button props available
|
|
163
|
+
<Button type="submit" onClick={handleClick}>
|
|
164
|
+
Submit
|
|
165
|
+
</Button>
|
|
166
|
+
|
|
167
|
+
// Button as link - anchor props available
|
|
168
|
+
<Button as="a" href="/page" target="_blank">
|
|
169
|
+
Link
|
|
170
|
+
</Button>
|
|
171
|
+
|
|
172
|
+
// Button as div - div props available
|
|
173
|
+
<Button as="div" onKeyDown={handleKeyDown}>
|
|
174
|
+
Custom
|
|
175
|
+
</Button>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Generic Props Pattern
|
|
179
|
+
|
|
180
|
+
```tsx
|
|
181
|
+
import type { ComponentProps } from 'react'
|
|
182
|
+
import { Button } from '@fpkit/acss'
|
|
183
|
+
|
|
184
|
+
// Get button props type
|
|
185
|
+
type BaseButtonProps = ComponentProps<typeof Button>
|
|
186
|
+
|
|
187
|
+
// Extend with custom props
|
|
188
|
+
interface MyButtonProps extends BaseButtonProps {
|
|
189
|
+
icon?: string
|
|
190
|
+
badge?: number
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Composition Patterns
|
|
197
|
+
|
|
198
|
+
### Pattern 1: Container + Content
|
|
199
|
+
|
|
200
|
+
Wrap fpkit components with additional structure:
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
import { Button, Badge } from '@fpkit/acss'
|
|
204
|
+
|
|
205
|
+
export const StatusButton = ({ status, children, ...props }) => {
|
|
206
|
+
return (
|
|
207
|
+
<Button {...props}>
|
|
208
|
+
{children}
|
|
209
|
+
<Badge variant={status}>{status}</Badge>
|
|
210
|
+
</Button>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Usage
|
|
215
|
+
<StatusButton status="active">Server Status</StatusButton>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Pattern 2: Conditional Composition
|
|
219
|
+
|
|
220
|
+
Different combinations based on props:
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
import { Alert, Dialog } from '@fpkit/acss'
|
|
224
|
+
|
|
225
|
+
export const Notification = ({ inline, variant, children, ...props }) => {
|
|
226
|
+
if (inline) {
|
|
227
|
+
return <Alert variant={variant}>{children}</Alert>
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<Dialog {...props}>
|
|
232
|
+
<Alert variant={variant}>{children}</Alert>
|
|
233
|
+
</Dialog>
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Usage
|
|
238
|
+
<Notification inline variant="success">Saved!</Notification>
|
|
239
|
+
<Notification isOpen={showModal} variant="error">Error!</Notification>
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Pattern 3: Enhanced Wrapper
|
|
243
|
+
|
|
244
|
+
Add behavior around fpkit components:
|
|
245
|
+
|
|
246
|
+
```tsx
|
|
247
|
+
import { Button } from '@fpkit/acss'
|
|
248
|
+
import { useState } from 'react'
|
|
249
|
+
|
|
250
|
+
export const LoadingButton = ({ loading, onClick, children, ...props }) => {
|
|
251
|
+
const [isLoading, setIsLoading] = useState(loading)
|
|
252
|
+
|
|
253
|
+
const handleClick = async (e) => {
|
|
254
|
+
setIsLoading(true)
|
|
255
|
+
try {
|
|
256
|
+
await onClick?.(e)
|
|
257
|
+
} finally {
|
|
258
|
+
setIsLoading(false)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return (
|
|
263
|
+
<Button
|
|
264
|
+
{...props}
|
|
265
|
+
disabled={isLoading || props.disabled}
|
|
266
|
+
onClick={handleClick}
|
|
267
|
+
>
|
|
268
|
+
{isLoading ? 'Loading...' : children}
|
|
269
|
+
</Button>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Usage
|
|
274
|
+
<LoadingButton onClick={async () => await saveData()}>
|
|
275
|
+
Save
|
|
276
|
+
</LoadingButton>
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
See the [Composition Guide](./composition.md) for more patterns and examples.
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Styling Architecture
|
|
284
|
+
|
|
285
|
+
### Attribute-Based Variants
|
|
286
|
+
|
|
287
|
+
fpkit uses `data-*` attributes for variants:
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
// Component
|
|
291
|
+
<Button variant="primary" size="large">Click me</Button>
|
|
292
|
+
|
|
293
|
+
// Renders as
|
|
294
|
+
<button data-btn="primary large">Click me</button>
|
|
295
|
+
|
|
296
|
+
// Styled with SCSS
|
|
297
|
+
button[data-btn~="primary"] {
|
|
298
|
+
background: var(--btn-primary-bg);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
button[data-btn~="large"] {
|
|
302
|
+
font-size: var(--btn-size-lg);
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Benefits:**
|
|
307
|
+
- Multiple variants on same element
|
|
308
|
+
- Clean HTML output
|
|
309
|
+
- Type-safe variants in TypeScript
|
|
310
|
+
- Easy CSS targeting
|
|
311
|
+
|
|
312
|
+
### CSS Variable Integration
|
|
313
|
+
|
|
314
|
+
All styling uses CSS custom properties:
|
|
315
|
+
|
|
316
|
+
```tsx
|
|
317
|
+
// Global overrides
|
|
318
|
+
:root {
|
|
319
|
+
--btn-primary-bg: #0066cc;
|
|
320
|
+
--btn-padding-inline: 2rem;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Component-specific overrides
|
|
324
|
+
<Button
|
|
325
|
+
style={{
|
|
326
|
+
'--btn-bg': '#e63946',
|
|
327
|
+
'--btn-color': 'white',
|
|
328
|
+
}}
|
|
329
|
+
>
|
|
330
|
+
Custom
|
|
331
|
+
</Button>
|
|
332
|
+
|
|
333
|
+
// CSS class overrides
|
|
334
|
+
.hero-button {
|
|
335
|
+
--btn-padding-inline: 3rem;
|
|
336
|
+
--btn-fs: 1.25rem;
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
See the [CSS Variables Guide](./css-variables.md) for complete customization options.
|
|
341
|
+
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
## Props Patterns
|
|
345
|
+
|
|
346
|
+
### Common Props
|
|
347
|
+
|
|
348
|
+
All fpkit components accept these common props:
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
// className - additional CSS classes
|
|
352
|
+
<Button className="custom-button">Click</Button>
|
|
353
|
+
|
|
354
|
+
// style - inline styles
|
|
355
|
+
<Button style={{ marginTop: '1rem' }}>Click</Button>
|
|
356
|
+
|
|
357
|
+
// data-* - custom data attributes
|
|
358
|
+
<Button data-testid="submit-btn">Click</Button>
|
|
359
|
+
|
|
360
|
+
// aria-* - accessibility attributes
|
|
361
|
+
<Button aria-label="Close dialog">×</Button>
|
|
362
|
+
|
|
363
|
+
// ref - React ref
|
|
364
|
+
const ref = useRef()
|
|
365
|
+
<Button ref={ref}>Click</Button>
|
|
366
|
+
|
|
367
|
+
// as - polymorphic element
|
|
368
|
+
<Button as="a" href="/page">Link</Button>
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Variant Props
|
|
372
|
+
|
|
373
|
+
Components use semantic variant names:
|
|
374
|
+
|
|
375
|
+
```tsx
|
|
376
|
+
// Button variants
|
|
377
|
+
<Button variant="primary">Primary</Button>
|
|
378
|
+
<Button variant="secondary">Secondary</Button>
|
|
379
|
+
<Button variant="danger">Danger</Button>
|
|
380
|
+
|
|
381
|
+
// Alert variants
|
|
382
|
+
<Alert variant="error">Error message</Alert>
|
|
383
|
+
<Alert variant="success">Success message</Alert>
|
|
384
|
+
<Alert variant="warning">Warning message</Alert>
|
|
385
|
+
<Alert variant="info">Info message</Alert>
|
|
386
|
+
|
|
387
|
+
// Badge variants
|
|
388
|
+
<Badge variant="rounded">Rounded</Badge>
|
|
389
|
+
<Badge variant="pill">Pill</Badge>
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Size Props
|
|
393
|
+
|
|
394
|
+
When components support sizing:
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
<Button size="small">Small</Button>
|
|
398
|
+
<Button size="medium">Medium</Button>
|
|
399
|
+
<Button size="large">Large</Button>
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Boolean Props
|
|
403
|
+
|
|
404
|
+
State and behavior props:
|
|
405
|
+
|
|
406
|
+
```tsx
|
|
407
|
+
// Disabled state
|
|
408
|
+
<Button disabled>Disabled</Button>
|
|
409
|
+
|
|
410
|
+
// Interactive cards
|
|
411
|
+
<Card interactive onClick={handleClick}>Clickable</Card>
|
|
412
|
+
|
|
413
|
+
// Modal/dialog states
|
|
414
|
+
<Dialog isOpen={isOpen} onClose={handleClose}>Content</Dialog>
|
|
415
|
+
|
|
416
|
+
// Dismissable alerts
|
|
417
|
+
<Alert onClose={handleClose}>Dismissable</Alert>
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## Accessibility Architecture
|
|
423
|
+
|
|
424
|
+
### Semantic HTML
|
|
425
|
+
|
|
426
|
+
fpkit components render as appropriate semantic elements:
|
|
427
|
+
|
|
428
|
+
| Component | Default Element | Purpose |
|
|
429
|
+
|-----------|----------------|---------|
|
|
430
|
+
| Button | `<button>` | Interactive action |
|
|
431
|
+
| Card | `<article>` | Self-contained content |
|
|
432
|
+
| Alert | `<div role="alert">` | Important message |
|
|
433
|
+
| Dialog | `<dialog>` | Modal content |
|
|
434
|
+
| Nav | `<nav>` | Navigation menu |
|
|
435
|
+
|
|
436
|
+
### ARIA Attributes
|
|
437
|
+
|
|
438
|
+
Components include built-in ARIA:
|
|
439
|
+
|
|
440
|
+
```tsx
|
|
441
|
+
// Button disabled state
|
|
442
|
+
<Button disabled>
|
|
443
|
+
// Renders: <button aria-disabled="true">
|
|
444
|
+
|
|
445
|
+
// Alert role
|
|
446
|
+
<Alert variant="error">
|
|
447
|
+
// Renders: <div role="alert">
|
|
448
|
+
|
|
449
|
+
// Dialog modal
|
|
450
|
+
<Dialog isOpen>
|
|
451
|
+
// Renders: <dialog aria-modal="true">
|
|
452
|
+
|
|
453
|
+
// Expandable elements
|
|
454
|
+
<Accordion expanded>
|
|
455
|
+
// Renders: <div aria-expanded="true">
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Keyboard Navigation
|
|
459
|
+
|
|
460
|
+
All interactive components support:
|
|
461
|
+
- **Tab**: Focus navigation
|
|
462
|
+
- **Enter/Space**: Activation
|
|
463
|
+
- **Escape**: Close modals/dialogs
|
|
464
|
+
- **Arrow keys**: Navigate lists/menus
|
|
465
|
+
|
|
466
|
+
See the [Accessibility Guide](./accessibility.md) for complete patterns.
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## Component Lifecycle
|
|
471
|
+
|
|
472
|
+
### Initialization
|
|
473
|
+
|
|
474
|
+
```tsx
|
|
475
|
+
// Components accept default HTML element props
|
|
476
|
+
<Button onClick={handleClick}>Click</Button>
|
|
477
|
+
|
|
478
|
+
// Plus component-specific props
|
|
479
|
+
<Button variant="primary" disabled>Click</Button>
|
|
480
|
+
|
|
481
|
+
// Props spread to underlying element
|
|
482
|
+
<Button data-testid="btn" aria-label="Submit">
|
|
483
|
+
Submit
|
|
484
|
+
</Button>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Updates
|
|
488
|
+
|
|
489
|
+
```tsx
|
|
490
|
+
// Props update reactively
|
|
491
|
+
const [disabled, setDisabled] = useState(false)
|
|
492
|
+
|
|
493
|
+
<Button disabled={disabled}>
|
|
494
|
+
{disabled ? 'Disabled' : 'Enabled'}
|
|
495
|
+
</Button>
|
|
496
|
+
|
|
497
|
+
// CSS variables update in real-time
|
|
498
|
+
const [color, setColor] = useState('#0066cc')
|
|
499
|
+
|
|
500
|
+
<Button style={{ '--btn-bg': color }}>
|
|
501
|
+
Dynamic Color
|
|
502
|
+
</Button>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Cleanup
|
|
506
|
+
|
|
507
|
+
```tsx
|
|
508
|
+
// Refs are properly forwarded
|
|
509
|
+
const buttonRef = useRef<HTMLButtonElement>(null)
|
|
510
|
+
|
|
511
|
+
useEffect(() => {
|
|
512
|
+
// Access DOM element directly
|
|
513
|
+
buttonRef.current?.focus()
|
|
514
|
+
|
|
515
|
+
return () => {
|
|
516
|
+
// Cleanup if needed
|
|
517
|
+
}
|
|
518
|
+
}, [])
|
|
519
|
+
|
|
520
|
+
<Button ref={buttonRef}>Click</Button>
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Best Practices
|
|
526
|
+
|
|
527
|
+
### ✅ Do
|
|
528
|
+
|
|
529
|
+
- **Use semantic elements** - Leverage the `as` prop for correct HTML
|
|
530
|
+
- **Compose over create** - Combine fpkit components rather than building from scratch
|
|
531
|
+
- **Extend props properly** - Use TypeScript to extend component props
|
|
532
|
+
- **Customize with CSS variables** - Override styles without modifying components
|
|
533
|
+
- **Preserve accessibility** - Keep ARIA attributes and keyboard navigation
|
|
534
|
+
- **Forward refs** - When wrapping components, forward refs appropriately
|
|
535
|
+
|
|
536
|
+
```tsx
|
|
537
|
+
// ✅ Good - proper composition
|
|
538
|
+
import { forwardRef } from 'react'
|
|
539
|
+
import { Button, type ButtonProps } from '@fpkit/acss'
|
|
540
|
+
|
|
541
|
+
interface LoadingButtonProps extends ButtonProps {
|
|
542
|
+
loading?: boolean
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export const LoadingButton = forwardRef<HTMLButtonElement, LoadingButtonProps>(
|
|
546
|
+
({ loading, children, ...props }, ref) => {
|
|
547
|
+
return (
|
|
548
|
+
<Button ref={ref} {...props} disabled={loading || props.disabled}>
|
|
549
|
+
{loading ? 'Loading...' : children}
|
|
550
|
+
</Button>
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
)
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
### ❌ Don't
|
|
557
|
+
|
|
558
|
+
- **Don't duplicate components** - Reuse existing fpkit components
|
|
559
|
+
- **Don't break accessibility** - Maintain ARIA attributes and keyboard support
|
|
560
|
+
- **Don't hardcode styles** - Use CSS variables for customization
|
|
561
|
+
- **Don't ignore types** - Leverage TypeScript for type safety
|
|
562
|
+
- **Don't nest interactive elements** - Avoid `<button>` inside `<a>`
|
|
563
|
+
|
|
564
|
+
```tsx
|
|
565
|
+
// ❌ Bad - duplicating fpkit logic
|
|
566
|
+
export const MyBadge = ({ children }) => {
|
|
567
|
+
return <span className="my-badge">{children}</span>
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// ✅ Good - reuse fpkit component
|
|
571
|
+
import { Badge } from '@fpkit/acss'
|
|
572
|
+
export const MyBadge = Badge
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## Component API Patterns
|
|
578
|
+
|
|
579
|
+
### Children Prop
|
|
580
|
+
|
|
581
|
+
```tsx
|
|
582
|
+
// String children
|
|
583
|
+
<Button>Click me</Button>
|
|
584
|
+
|
|
585
|
+
// Element children
|
|
586
|
+
<Button>
|
|
587
|
+
<Icon name="save" />
|
|
588
|
+
Save
|
|
589
|
+
</Button>
|
|
590
|
+
|
|
591
|
+
// Render prop pattern
|
|
592
|
+
<Card>
|
|
593
|
+
{({ isHovered }) => (
|
|
594
|
+
<div>Content {isHovered ? 'hovered' : ''}</div>
|
|
595
|
+
)}
|
|
596
|
+
</Card>
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
### Event Handlers
|
|
600
|
+
|
|
601
|
+
```tsx
|
|
602
|
+
// Mouse events
|
|
603
|
+
<Button onClick={handleClick}>Click</Button>
|
|
604
|
+
<Button onMouseEnter={handleHover}>Hover</Button>
|
|
605
|
+
|
|
606
|
+
// Keyboard events
|
|
607
|
+
<Input onKeyDown={handleKeyPress} />
|
|
608
|
+
|
|
609
|
+
// Form events
|
|
610
|
+
<Input onChange={handleChange} />
|
|
611
|
+
<Form onSubmit={handleSubmit} />
|
|
612
|
+
|
|
613
|
+
// Custom events
|
|
614
|
+
<Dialog onClose={handleClose} />
|
|
615
|
+
<Alert onDismiss={handleDismiss} />
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Render Props
|
|
619
|
+
|
|
620
|
+
```tsx
|
|
621
|
+
// Custom rendering
|
|
622
|
+
<Select>
|
|
623
|
+
{(option) => (
|
|
624
|
+
<div>
|
|
625
|
+
<Icon name={option.icon} />
|
|
626
|
+
{option.label}
|
|
627
|
+
</div>
|
|
628
|
+
)}
|
|
629
|
+
</Select>
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
## Framework Integration
|
|
635
|
+
|
|
636
|
+
### React
|
|
637
|
+
|
|
638
|
+
fpkit is designed for React:
|
|
639
|
+
|
|
640
|
+
```tsx
|
|
641
|
+
import { Button, Card } from '@fpkit/acss'
|
|
642
|
+
|
|
643
|
+
export default function App() {
|
|
644
|
+
return (
|
|
645
|
+
<Card>
|
|
646
|
+
<Card.Title>Welcome</Card.Title>
|
|
647
|
+
<Card.Content>
|
|
648
|
+
<p>Content here</p>
|
|
649
|
+
<Button variant="primary">Action</Button>
|
|
650
|
+
</Card.Content>
|
|
651
|
+
</Card>
|
|
652
|
+
)
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Next.js
|
|
657
|
+
|
|
658
|
+
Works seamlessly with Next.js:
|
|
659
|
+
|
|
660
|
+
```tsx
|
|
661
|
+
import { Button } from '@fpkit/acss'
|
|
662
|
+
import Link from 'next/link'
|
|
663
|
+
|
|
664
|
+
// Button as Next.js Link
|
|
665
|
+
<Button as={Link} href="/page">
|
|
666
|
+
Navigate
|
|
667
|
+
</Button>
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
### TypeScript
|
|
671
|
+
|
|
672
|
+
Full type safety:
|
|
673
|
+
|
|
674
|
+
```tsx
|
|
675
|
+
import type { ButtonProps } from '@fpkit/acss'
|
|
676
|
+
|
|
677
|
+
// Type-safe custom component
|
|
678
|
+
const CustomButton = (props: ButtonProps) => {
|
|
679
|
+
return <Button {...props} />
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
## Additional Resources
|
|
686
|
+
|
|
687
|
+
- **[Composition Guide](./composition.md)** - Component composition patterns and strategies
|
|
688
|
+
- **[CSS Variables Guide](./css-variables.md)** - Styling and customization
|
|
689
|
+
- **[Accessibility Guide](./accessibility.md)** - WCAG compliance and ARIA patterns
|
|
690
|
+
- **[Testing Guide](./testing.md)** - Testing strategies for fpkit components
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
## Summary
|
|
695
|
+
|
|
696
|
+
@fpkit/acss architecture provides:
|
|
697
|
+
|
|
698
|
+
1. **Polymorphic Components** - Flexible HTML element rendering via `as` prop
|
|
699
|
+
2. **Compound Components** - Complex UIs with sub-components
|
|
700
|
+
3. **Composition-First** - Build custom components by combining primitives
|
|
701
|
+
4. **Type Safety** - Full TypeScript support with proper prop types
|
|
702
|
+
5. **CSS Variables** - Customizable styling without component modification
|
|
703
|
+
6. **Accessibility** - Built-in WCAG 2.1 AA compliance
|
|
704
|
+
|
|
705
|
+
Understanding these patterns helps you use fpkit effectively and build maintainable, accessible applications.
|