@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.
- package/README.md +32 -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/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/icons/icon.d.cts +32 -32
- package/libs/components/icons/icon.d.ts +32 -32
- package/libs/components/list/list.css +1 -1
- package/libs/components/list/list.min.css +1 -1
- package/libs/index.css +1 -1
- package/libs/index.css.map +1 -1
- package/package.json +4 -3
- package/src/components/README.mdx +1 -1
- package/src/components/buttons/button.scss +5 -0
- package/src/components/buttons/button.stories.tsx +8 -5
- 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/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 +545 -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
|
@@ -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.
|