@maxsteinwender/sort-ui 1.1.0 → 1.1.2

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.
@@ -0,0 +1,294 @@
1
+ # SortUI Design System Guidelines
2
+
3
+ This is the main hub for all SortUI Design System (`@maxsteinwender/sort-ui`) guidelines. These files are authoritative — prefer them over any heuristics inferred from bundled source code.
4
+
5
+ ---
6
+
7
+ ## Reading order
8
+
9
+ Always read first:
10
+ - `Guidelines.md` — this file; the main hub and entry point
11
+ - `setup.md` — required project configuration, providers, and CSS imports
12
+ - `tokens.md` — foundational design tokens (color, typography, spacing, shadow, radius)
13
+
14
+ Read on-demand:
15
+ - `components.md` — read BEFORE using any design-system component
16
+ - `icon-discovery.md` — read BEFORE using any icons
17
+ - `styles.md` — read when building page layouts or applying custom spacing
18
+
19
+ ---
20
+
21
+ ## Critical rules (read before generating any code)
22
+
23
+ 1. **Tailwind version is v3** (`^3.4.1`). It is **NOT** Tailwind v4. Never emit `@theme` directives or v4-only syntax.
24
+ 2. **Package manager is npm.** Never suggest `pnpm` or `yarn` install commands — use `npm install`.
25
+ 3. **Selective `sui-` prefix.** Colors and spacing carry `sui-`; shadows and radius do NOT.
26
+ - ✅ `text-sui-text-default`, `bg-sui-bg-subtle`, `gap-sui-16`, `p-sui-24`
27
+ - ✅ `shadow-default`, `shadow-card`, `shadow-focus`, `shadow-modal-md`
28
+ - ✅ `rounded-md`, `rounded-lg`, `rounded-full`, `rounded-card-md`
29
+ - ❌ `shadow-sui-default`, `shadow-sui-card`, `shadow-sm`, `shadow-md`
30
+ - ❌ `rounded-sui-md`, `rounded-sui-lg`
31
+ 4. **Reuse existing SortUI components.** Never inline custom markup when a component exists — use `Badge`, `Avatar`, `ProgressBar`, `Button`, `InputField`, `Card`, etc.
32
+ 5. **Charts: `isAnimationActive={false}` on every Recharts series.** Recharts 2.x animations rely on removed React 19 APIs and render empty without this prop.
33
+ 6. **Icons: Remix Icons only.** Syntax `<i className="ri-{name}-{variant}" />`. Never install `lucide-react`, `heroicons`, or any other icon package.
34
+ 7. **All 4 themes.** Every generated UI must render correctly in `light`, `dark`, `theme-2`, and `theme-2-dark`.
35
+
36
+ ---
37
+
38
+ ## Companion guideline files
39
+
40
+ | File | Focus |
41
+ |---|---|
42
+ | `setup.md` | Installation, Tailwind v3 config, CSS imports, provider setup, all 4 theme classes |
43
+ | `tokens.md` | Design tokens — colors, typography, shadow, border, radius, spacing (correct class names) |
44
+ | `styles.md` | Spacing, layout primitives, responsive patterns (Tailwind v3 breakpoints) |
45
+ | `components.md` | Component imports, props, variants, and composition patterns |
46
+ | `icon-discovery.md` | Remix Icon naming, discovery, and verification |
47
+
48
+ ---
49
+
50
+ ## Before using an icon
51
+
52
+ 1. Check `icon-discovery.md` for verification workflow
53
+ 2. Do NOT guess icon names — verify the icon exists first
54
+ 3. If an icon doesn't exist, pick a different one and verify
55
+
56
+ ---
57
+
58
+ ## General Guidelines
59
+
60
+ ### Code Quality
61
+ - Use responsive layouts with Flexbox and Grid by default
62
+ - Avoid absolute positioning unless necessary
63
+ - Keep components focused and single-purpose
64
+ - Extract reusable logic into helper functions
65
+
66
+ ### Component Usage
67
+ - Always import components from the correct entry points:
68
+ - Main: `@maxsteinwender/sort-ui`
69
+ - Form (React Hook Form wrappers): `@maxsteinwender/sort-ui/form`
70
+ - Prefer composition over complex prop APIs
71
+ - **Reuse existing SortUI components instead of custom markup.**
72
+
73
+ ### Accessibility
74
+ - Provide `aria-label` for icon-only buttons
75
+ - Use semantic HTML elements
76
+ - Ensure proper heading hierarchy
77
+ - Add descriptive labels for form inputs
78
+
79
+ ---
80
+
81
+ ## Design System Guidelines
82
+
83
+ ### Colors & Theming
84
+ - Use semantic tokens (`text-sui-text-default`, `bg-sui-bg-subtle`) over raw hex values
85
+ - Tokens automatically adapt to theme changes
86
+ - Apply ONE theme class to `<html>` or `<body>`: `light`, `dark`, `theme-2`, `theme-2-dark`
87
+
88
+ ### Typography
89
+ - Default font: Geist Sans (bundled)
90
+ - Code font: Geist Mono (bundled)
91
+ - Display/quote font: Redaction (bundled)
92
+ - Use Tailwind size utilities: `text-xs` through `text-9xl`
93
+
94
+ ### Spacing
95
+ - Use SortUI spacing tokens: `gap-sui-16`, `p-sui-24`, `mx-sui-32`, `space-y-sui-16`
96
+ - Common patterns:
97
+ - Form field gap: `gap-sui-16`
98
+ - Card padding: `p-sui-24`
99
+ - Section gap: `gap-sui-32` to `gap-sui-48`
100
+
101
+ ### Shadow & Radius (NO `sui-` prefix)
102
+ - Shadows: `shadow-default`, `shadow-card`, `shadow-focus`, `shadow-modal-md`
103
+ - Radius: `rounded-md`, `rounded-lg`, `rounded-full`, `rounded-card-md`
104
+ - Never use `shadow-sm`, `shadow-md`, `shadow-lg`, or arbitrary `shadow-[…]` values
105
+
106
+ ### Responsive Design (Tailwind v3 breakpoints)
107
+ - Mobile-first approach
108
+ - Breakpoints: `sm` (640px), `md` (768px), `lg` (1024px), `xl` (1280px), `2xl` (1536px)
109
+ - Example: `p-sui-16 md:p-sui-24 lg:p-sui-32`
110
+
111
+ ---
112
+
113
+ ## Component Best Practices
114
+
115
+ ### Buttons
116
+ - `variant`: `default`, `ghost`, `destructive`, `secondary`, `outline`, `ghost-muted`
117
+ - `size`: `2xs`, `xs`, `sm`, `md`, `lg`
118
+ - Icon-only buttons **MUST** have `aria-label`
119
+ - Use `loading` prop for async actions (don't add manual spinners)
120
+
121
+ ### Forms
122
+ - Use `InputField` with `error` prop for validation feedback
123
+ - Group related fields with `gap-sui-16`
124
+ - Use `required` prop to show asterisk on labels
125
+ - Integrate with React Hook Form via `@maxsteinwender/sort-ui/form`
126
+
127
+ ### Cards
128
+ - Use semantic slots: `CardHeader`, `CardTitle`, `CardDescription`, `CardContent`, `CardFooter`
129
+ - Use `CardCover` with `flush` prop for edge-to-edge images
130
+ - `variant="default"` has border + shadow; `variant="flat"` removes them
131
+
132
+ ### Icons
133
+ - Always verify icon exists before using (see `icon-discovery.md`)
134
+ - Use `-line` for outlined, `-fill` for solid
135
+ - Syntax: `<i className="ri-{name}-line" />`
136
+ - Do NOT install additional icon packages
137
+
138
+ ### Charts (Recharts)
139
+ - Always set `isAnimationActive={false}` on every series (`Line`, `Bar`, `Area`, `Pie`, etc.)
140
+ - Wrap in `ChartContainer` from `@maxsteinwender/sort-ui` with a `ChartConfig`
141
+ - Use chart color tokens: `var(--chart-1)` through `var(--chart-5)`
142
+
143
+ ---
144
+
145
+ ## Common Patterns
146
+
147
+ ### Form with validation
148
+ ```tsx
149
+ import { useForm } from "react-hook-form";
150
+ import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@maxsteinwender/sort-ui/form";
151
+ import { InputField, Button } from "@maxsteinwender/sort-ui";
152
+
153
+ const form = useForm({ defaultValues: { email: "" } });
154
+
155
+ <Form {...form}>
156
+ <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-sui-16">
157
+ <FormField
158
+ control={form.control}
159
+ name="email"
160
+ render={({ field }) => (
161
+ <FormItem>
162
+ <FormLabel>Email</FormLabel>
163
+ <FormControl>
164
+ <InputField {...field} placeholder="you@example.com" />
165
+ </FormControl>
166
+ <FormMessage />
167
+ </FormItem>
168
+ )}
169
+ />
170
+ <Button type="submit">Submit</Button>
171
+ </form>
172
+ </Form>
173
+ ```
174
+
175
+ ### Modal dialog
176
+ ```tsx
177
+ <Dialog>
178
+ <DialogTrigger asChild>
179
+ <Button>Open</Button>
180
+ </DialogTrigger>
181
+ <DialogContent>
182
+ <DialogHeader>
183
+ <DialogTitle>Confirm</DialogTitle>
184
+ <DialogDescription>This action cannot be undone.</DialogDescription>
185
+ </DialogHeader>
186
+ <DialogFooter>
187
+ <DialogClose asChild><Button variant="ghost">Cancel</Button></DialogClose>
188
+ <Button>Confirm</Button>
189
+ </DialogFooter>
190
+ </DialogContent>
191
+ </Dialog>
192
+ ```
193
+
194
+ ### Data display with reused SortUI components
195
+ ```tsx
196
+ // ✅ DO — reuse Avatar + Badge
197
+ <div className="flex items-center gap-sui-12">
198
+ <Avatar
199
+ src="/user.jpg"
200
+ fallback="JD"
201
+ size="md"
202
+ bottomBadge
203
+ bottomBadgeVariant="online"
204
+ bottomBadgeLabel="Online"
205
+ />
206
+ <div>
207
+ <p className="font-semibold text-sui-text-default">John Doe</p>
208
+ <Badge color="green" size="sm">Active</Badge>
209
+ </div>
210
+ </div>
211
+
212
+ // ❌ DON'T — inline markup instead of reusing components
213
+ <div className="flex items-center gap-sui-12">
214
+ <img src="/user.jpg" className="w-10 h-10 rounded-full" />
215
+ <div>
216
+ <p className="font-semibold">John Doe</p>
217
+ <span className="inline-flex bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs">Active</span>
218
+ </div>
219
+ </div>
220
+ ```
221
+
222
+ ### Chart with required `isAnimationActive={false}`
223
+ ```tsx
224
+ import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@maxsteinwender/sort-ui";
225
+ import { LineChart, Line, Bar } from "recharts";
226
+
227
+ const config: ChartConfig = {
228
+ revenue: { label: "Revenue", color: "var(--chart-1)" },
229
+ };
230
+
231
+ <ChartContainer config={config}>
232
+ <LineChart data={data}>
233
+ <ChartTooltip content={<ChartTooltipContent />} />
234
+ <Line dataKey="revenue" stroke="var(--color-revenue)" isAnimationActive={false} />
235
+ <Bar dataKey="sales" fill="var(--chart-2)" isAnimationActive={false} />
236
+ </LineChart>
237
+ </ChartContainer>
238
+ ```
239
+
240
+ ---
241
+
242
+ ## DO / DON'T
243
+
244
+ **DO:**
245
+ - ✓ Use `npm install` (not `pnpm add`)
246
+ - ✓ Treat Tailwind as v3 (not v4)
247
+ - ✓ Use `shadow-default`, `shadow-card`, `shadow-focus`, `shadow-modal-md`
248
+ - ✓ Use `rounded-md`, `rounded-lg`, `rounded-full`, `rounded-card-md`
249
+ - ✓ Use `sui-` prefix for colors and spacing (`bg-sui-bg-default`, `gap-sui-16`)
250
+ - ✓ Reuse existing SortUI components (`Badge`, `Avatar`, `ProgressBar`, `Button`)
251
+ - ✓ Provide `aria-label` for icon-only buttons
252
+ - ✓ Set `isAnimationActive={false}` on all Recharts series
253
+ - ✓ Test all 4 themes (`light`, `dark`, `theme-2`, `theme-2-dark`)
254
+
255
+ **DON'T:**
256
+ - ✗ Use `pnpm add` or `yarn add`
257
+ - ✗ Assume Tailwind v4 or emit `@theme` directives
258
+ - ✗ Use `shadow-sui-*`, `shadow-sm`, `shadow-md`, `shadow-lg`
259
+ - ✗ Use `rounded-sui-*`
260
+ - ✗ Hard-code colors (`bg-[#fafafa]`) or spacing (`gap-[16px]`)
261
+ - ✗ Inline custom markup when a SortUI component exists
262
+ - ✗ Omit `isAnimationActive={false}` on Recharts series
263
+ - ✗ Guess icon names without verification
264
+ - ✗ Install `lucide-react` or other icon packages
265
+
266
+ ---
267
+
268
+ ## Quick Reference
269
+
270
+ | Need | Read |
271
+ |------|------|
272
+ | Setup / install / providers | `setup.md` |
273
+ | Color, typography, shadow, radius tokens | `tokens.md` |
274
+ | Spacing and layout | `styles.md` |
275
+ | Component API | `components.md` |
276
+ | Icon usage and discovery | `icon-discovery.md` |
277
+
278
+ ---
279
+
280
+ ## Common Mistakes to Avoid
281
+
282
+ | ❌ Wrong | ✅ Correct |
283
+ |---|---|
284
+ | `pnpm add @maxsteinwender/sort-ui` | `npm install @maxsteinwender/sort-ui` |
285
+ | "Tailwind v4" / `@theme` directive | Tailwind v3 / `tailwind.config.ts` preset |
286
+ | `shadow-sui-default`, `shadow-sui-card` | `shadow-default`, `shadow-card` |
287
+ | `shadow-md`, `shadow-lg` | `shadow-default` or `shadow-card` |
288
+ | `rounded-sui-md`, `rounded-sui-lg` | `rounded-md`, `rounded-lg` |
289
+ | `bg-[#fafafa]` | `bg-sui-bg-subtle` |
290
+ | `gap-[16px]` | `gap-sui-16` |
291
+ | `<i className="lucide-user" />` | `<i className="ri-user-line" />` |
292
+ | `<Line stroke="..." />` (Recharts) | `<Line stroke="..." isAnimationActive={false} />` |
293
+ | Inline `<span class="bg-green-100">Active</span>` | `<Badge color="green">Active</Badge>` |
294
+ | `<img src="/u.jpg" class="rounded-full" />` | `<Avatar src="/u.jpg" fallback="MS" size="md" />` |
@@ -0,0 +1,406 @@
1
+ # Component Usage Guidelines
2
+
3
+ Complete reference for components in `@maxsteinwender/sort-ui`.
4
+
5
+ ## Component Reuse (CRITICAL)
6
+
7
+ **Always reuse existing SortUI components — never inline custom markup when a component exists.**
8
+
9
+ This is the most common failure mode for generated UIs. Pattern:
10
+
11
+ - Status pill → `<Badge>` (not `<span className="bg-green-100 px-2 py-1 rounded-full">`)
12
+ - Progress indicator → `<ProgressBar>` / `<RadialProgressBar>` (not a custom `<div>`)
13
+ - User image → `<Avatar>` (not `<img className="rounded-full">`)
14
+ - Clickable action → `<Button>` / `<LinkButton>` / `<ControlButton>` (not `<div onClick>`)
15
+ - Form text input → `<InputField>` (not `<input>` with hand-rolled styling)
16
+ - Section header + body → `<Card>` with `CardHeader`/`CardContent`/`CardFooter`
17
+
18
+ ```tsx
19
+ // ✅ DO
20
+ <Badge color="green" shape="pill">Active</Badge>
21
+ <Avatar src="/u.jpg" fallback="MS" size="md" />
22
+ <ProgressBar value={75} label labelText="Upload" />
23
+
24
+ // ❌ DON'T
25
+ <span className="inline-flex bg-green-100 text-green-800 px-2 py-1 rounded-full text-xs">Active</span>
26
+ <img src="/u.jpg" className="w-10 h-10 rounded-full" />
27
+ <div className="h-2 bg-sui-bg-muted"><div style={{width:"75%"}} className="h-full bg-sui-state-primary" /></div>
28
+ ```
29
+
30
+ ---
31
+
32
+ ## Import Paths
33
+
34
+ ```tsx
35
+ // Main components
36
+ import { Button, InputField, Avatar, Badge, Card } from "@maxsteinwender/sort-ui";
37
+
38
+ // Form components (React Hook Form wrappers) — separate sub-entry
39
+ import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@maxsteinwender/sort-ui/form";
40
+
41
+ // Toast
42
+ import { Toaster } from "@maxsteinwender/sort-ui";
43
+ import { toast } from "sonner";
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Buttons & Actions
49
+
50
+ | Component | Variants | Key Props | Common Use |
51
+ |-----------|----------|-----------|------------|
52
+ | **Button** | `default`, `ghost`, `destructive`, `secondary`, `outline`, `ghost-muted` | `variant`, `size`, `shape`, `iconOnly`, `loading`, `asChild` | Primary actions |
53
+ | **ButtonGroup** | — | `size`, `aria-label` (required), `orientation` | Toolbar groups |
54
+ | **ButtonGroupItem** | — | `label`, `leadIcon`, `tailIcon`, `badge`, `iconOnly` | Items in ButtonGroup |
55
+ | **LinkButton** | `default`, `muted`, `informative` | `variant`, `size`, `leadIcon`, `tailIcon` | Styled links |
56
+ | **SocialButton** | `secondary`, `brand` | `brand` (required), `shape`, `iconOnly` | Social login |
57
+ | **ControlButton** | `default`, `inverted` | `size`, `shape`, `dismissLabel`, `asSpan` | Dismiss / small controls |
58
+ | **FilterButton** | — | `selected`, `filterValue`, `onClearFilter` | Filter toggles |
59
+
60
+ ```tsx
61
+ // ✅ iconOnly requires aria-label
62
+ <Button iconOnly aria-label="Settings"><i className="ri-settings-line" /></Button>
63
+
64
+ // ✅ loading prop replaces manual spinner
65
+ <Button loading>Saving…</Button>
66
+
67
+ // ✅ Size + variant
68
+ <Button size="lg" variant="destructive">Delete</Button>
69
+
70
+ // ✅ ButtonGroup
71
+ <ButtonGroup size="md" aria-label="Text formatting">
72
+ <ButtonGroupItem label="Bold" leadIcon="ri-bold" />
73
+ <ButtonGroupItem label="Italic" leadIcon="ri-italic" />
74
+ </ButtonGroup>
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Navigation
80
+
81
+ | Component | Variants | Key Props | Common Use |
82
+ |-----------|----------|-----------|------------|
83
+ | **SortBreadcrumb** | `slash`, `arrow`, `dot` | `items`, `variant` | Page navigation |
84
+ | **NavigationMenu** | — | — | Top-level nav with dropdowns |
85
+ | **Tabs** | — | `value`, `onValueChange`, `defaultValue` | Content switching |
86
+ | **Paginator** | `simple`, `numbered`, `dots` | `currentPage`, `totalPages`, `onPageChange` | Pagination |
87
+ | **HorizontalStepper**, **VerticalStepper** | — | `steps`, `currentStep` | Multi-step flows |
88
+
89
+ ```tsx
90
+ <SortBreadcrumb
91
+ variant="slash"
92
+ items={[
93
+ { label: "Home", href: "/" },
94
+ { label: "Projects", href: "/projects" },
95
+ { label: "Current Project" },
96
+ ]}
97
+ />
98
+
99
+ <Tabs value={activeTab} onValueChange={setActiveTab}>
100
+ <TabsList>
101
+ <TabsTrigger value="tab1">Tab 1</TabsTrigger>
102
+ </TabsList>
103
+ <TabsContent value="tab1">…</TabsContent>
104
+ </Tabs>
105
+ ```
106
+
107
+ ---
108
+
109
+ ## Forms & Inputs
110
+
111
+ | Component | Variants | Key Props |
112
+ |-----------|----------|-----------|
113
+ | **InputField** | `default`, `soft`, `shadow` | `label`, `error`, `leadIcon`, `tailIcon`, `prefix`, `suffix`, `leadAddon`, `tailAddon`, `tags`, `leadButton`, `tailButton` |
114
+ | **TextAreaField** | `default`, `soft`, `shadow` | `label`, `error`, `rows` |
115
+ | **Checkbox** | `default`, `shadow` | `checked`, `onCheckedChange`, `variant` |
116
+ | **CheckboxWithText** / **CheckboxCard** | — | `label`, `description`, `checkboxPosition` / `title`, `description`, `layout` |
117
+ | **RadioButton** / **WithText** / **Card** | — | `value`, `label`, `description`, `radioPosition`, `layout` |
118
+ | **Switch** / **SwitchWithText** | — | `checked`, `onCheckedChange`, `label`, `description` |
119
+ | **Select**, **SelectMenu** | — | `value`, `onValueChange`, `searchInput` |
120
+ | **DatePicker**, **DateRangePicker** | — | `date`, `onDateChange`, `dateRange`, `onDateRangeChange` |
121
+ | **FileUpload** | — | `files`, `onFilesAdded`, `accept`, `maxSize`, `multiple`, `autoPreview` |
122
+ | **Slider**, **SliderRange**, **DataRangeSlider** | — | `value`, `onValueChange`, `min`, `max`, `step` |
123
+
124
+ ```tsx
125
+ // InputField with error
126
+ <InputField
127
+ label="Email"
128
+ required
129
+ error={errors.email}
130
+ leadIcon={<i className="ri-mail-line" />}
131
+ />
132
+
133
+ // Addons / prefix / suffix
134
+ <InputField label="Website" leadAddon="https://" tailAddon=".com" />
135
+ <InputField label="Amount" prefix="$" suffix="USD" />
136
+
137
+ // Checkbox patterns
138
+ <CheckboxCard
139
+ title="Feature A"
140
+ description="Description"
141
+ selected={selected}
142
+ onSelectedChange={setSelected}
143
+ checkboxPosition="right"
144
+ />
145
+ ```
146
+
147
+ **FileUpload workflow (parent-owned state):**
148
+ ```tsx
149
+ const [files, setFiles] = useState<UploadFile[]>([]);
150
+
151
+ <FileUpload
152
+ files={files}
153
+ onFilesAdded={(newFiles) => {
154
+ const uploads = newFiles.map(file => ({
155
+ id: crypto.randomUUID(),
156
+ file,
157
+ state: "uploading" as const,
158
+ progress: 0,
159
+ }));
160
+ setFiles([...files, ...uploads]);
161
+ }}
162
+ onRemove={(id) => setFiles(files.filter(f => f.id !== id))}
163
+ accept="image/*"
164
+ maxSize={10 * 1024 * 1024}
165
+ multiple
166
+ autoPreview
167
+ />
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Data Display
173
+
174
+ | Component | Variants | Key Props |
175
+ |-----------|----------|-----------|
176
+ | **Avatar** | — | `src`, `fallback`, `size`, `shape`, `ring`, `topBadge`, `bottomBadge`, `color` |
177
+ | **AvatarGroup** | — | `items`, `size`, `stacking`, `aria-label` |
178
+ | **Badge** | `label`, `icon`, `image`, `dot` | `color`, `size`, `shape`, `variant`, `onClose` |
179
+ | **Card** | `default`, `flat` | `variant` |
180
+ | **Table** / **GridTable** | — | row/col composition |
181
+ | **Skeleton** | — | `className` |
182
+ | **EmptyState** | `default`, `icon`, `placeholder-icon` | `title`, `description`, `icon`, `action` |
183
+
184
+ ```tsx
185
+ // Avatar with badge
186
+ <Avatar
187
+ src="/user.jpg"
188
+ alt="User"
189
+ fallback="UN"
190
+ size="md"
191
+ bottomBadge
192
+ bottomBadgeVariant="online"
193
+ bottomBadgeLabel="Online"
194
+ />
195
+
196
+ // Avatar group
197
+ <AvatarGroup
198
+ items={[
199
+ { src: "/a.jpg", alt: "Alice", fallback: "A" },
200
+ { fallback: "B" },
201
+ ]}
202
+ size="md"
203
+ aria-label="2 team members"
204
+ />
205
+
206
+ // Badge variants
207
+ <Badge color="green" shape="pill">Active</Badge>
208
+ <Badge variant="icon" icon={<i className="ri-star-fill" />}>Featured</Badge>
209
+ <Badge color="blue" onClose={() => remove()}>Removable Tag</Badge>
210
+
211
+ // Card composition
212
+ <Card variant="default">
213
+ <CardCover flush aspectRatio="16/9">
214
+ <img src="/cover.jpg" alt="Cover" />
215
+ </CardCover>
216
+ <CardHeader>
217
+ <CardTitle>Title</CardTitle>
218
+ <CardDescription>Description</CardDescription>
219
+ </CardHeader>
220
+ <CardContent>{/* Main content */}</CardContent>
221
+ <CardFooter>{/* Actions */}</CardFooter>
222
+ </Card>
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Feedback
228
+
229
+ | Component | Variants | Key Props |
230
+ |-----------|----------|-----------|
231
+ | **Alert** | `default`, `destructive` | `variant` |
232
+ | **Toast** | — | via Sonner |
233
+ | **ProgressBar** | `linear`, `dashed` | `value`, `label`, `labelText`, `caption` |
234
+ | **RadialProgressBar** | `default`, `success`, `failed` | `value`, `size`, `shape`, `showValue` |
235
+ | **InlineTips** | `default`, `error`, `success`, `information`, `warning` | `variant`, `colorStyle`, `title` |
236
+
237
+ ```tsx
238
+ // Toast
239
+ import { toast } from "sonner";
240
+ toast.success("Saved");
241
+ toast.error("Failed");
242
+
243
+ // Progress
244
+ <ProgressBar value={75} label labelText="Upload progress" />
245
+ <RadialProgressBar value={80} size={120} showValue />
246
+ <RadialProgressBar variant="success" value={100} label="Done" shape="half" />
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Charts (Recharts — React 19 compat CRITICAL)
252
+
253
+ **Every Recharts series MUST set `isAnimationActive={false}`.** Recharts 2.x animations rely on removed React 19 APIs and render empty without this prop.
254
+
255
+ ```tsx
256
+ import { ChartContainer, ChartTooltip, ChartTooltipContent, type ChartConfig } from "@maxsteinwender/sort-ui";
257
+ import { LineChart, Line, BarChart, Bar, PieChart, Pie, AreaChart, Area } from "recharts";
258
+
259
+ const config: ChartConfig = {
260
+ revenue: { label: "Revenue", color: "var(--chart-1)" },
261
+ sales: { label: "Sales", color: "var(--chart-2)" },
262
+ };
263
+
264
+ // ✅ Line chart
265
+ <ChartContainer config={config}>
266
+ <LineChart data={data}>
267
+ <ChartTooltip content={<ChartTooltipContent />} />
268
+ <Line dataKey="revenue" stroke="var(--color-revenue)" isAnimationActive={false} />
269
+ </LineChart>
270
+ </ChartContainer>
271
+
272
+ // ✅ Bar chart
273
+ <ChartContainer config={config}>
274
+ <BarChart data={data}>
275
+ <ChartTooltip content={<ChartTooltipContent />} />
276
+ <Bar dataKey="sales" fill="var(--color-sales)" isAnimationActive={false} />
277
+ </BarChart>
278
+ </ChartContainer>
279
+
280
+ // ✅ Area + Pie
281
+ <Area dataKey="revenue" stroke="var(--color-revenue)" fill="var(--color-revenue)" isAnimationActive={false} />
282
+ <Pie data={pieData} dataKey="value" isAnimationActive={false} />
283
+
284
+ // ❌ WRONG — animation enabled (default) → empty chart in React 19
285
+ <Line dataKey="revenue" stroke="var(--color-revenue)" />
286
+ ```
287
+
288
+ Chart color tokens: `var(--chart-1)` through `var(--chart-5)`.
289
+
290
+ ---
291
+
292
+ ## Overlays
293
+
294
+ | Component | Type | Key Props |
295
+ |-----------|------|-----------|
296
+ | **Dialog** | Modal | composition: `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`, `DialogClose` |
297
+ | **AlertDialog** | Modal | confirmation dialogs |
298
+ | **Sheet** | Drawer | side panels |
299
+ | **Popover** | Floating | popovers |
300
+ | **Tooltip** | Floating | hover tooltips (requires `<TooltipProvider>`) |
301
+ | **DropdownMenu** | Floating | `search`, `searchValue`, `onSearchChange` |
302
+ | **CommandPalette** | Command | cmdk-powered palette |
303
+
304
+ ```tsx
305
+ <Dialog>
306
+ <DialogTrigger asChild>
307
+ <Button>Open</Button>
308
+ </DialogTrigger>
309
+ <DialogContent>
310
+ <DialogHeader>
311
+ <DialogTitle>Title</DialogTitle>
312
+ <DialogDescription>Description</DialogDescription>
313
+ </DialogHeader>
314
+ {/* Content */}
315
+ <DialogFooter>
316
+ <DialogClose asChild><Button variant="ghost">Cancel</Button></DialogClose>
317
+ <Button>Confirm</Button>
318
+ </DialogFooter>
319
+ </DialogContent>
320
+ </Dialog>
321
+
322
+ <DropdownMenu>
323
+ <DropdownMenuTrigger asChild>
324
+ <Button>Menu</Button>
325
+ </DropdownMenuTrigger>
326
+ <DropdownMenuContent search searchPlaceholder="Search…">
327
+ <DropdownMenuLabel>Actions</DropdownMenuLabel>
328
+ <DropdownMenuItem>Edit</DropdownMenuItem>
329
+ <DropdownMenuSeparator />
330
+ <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
331
+ </DropdownMenuContent>
332
+ </DropdownMenu>
333
+
334
+ <TooltipProvider>
335
+ <Tooltip>
336
+ <TooltipTrigger asChild>
337
+ <Button iconOnly aria-label="Help"><i className="ri-question-line" /></Button>
338
+ </TooltipTrigger>
339
+ <TooltipContent><p>Help text</p></TooltipContent>
340
+ </Tooltip>
341
+ </TooltipProvider>
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Form Integration (React Hook Form)
347
+
348
+ **IMPORTANT:** Form components use a separate import path.
349
+
350
+ ```tsx
351
+ import { useForm } from "react-hook-form";
352
+ import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from "@maxsteinwender/sort-ui/form";
353
+ import { InputField, Button } from "@maxsteinwender/sort-ui";
354
+
355
+ const form = useForm({ defaultValues: { email: "" } });
356
+
357
+ <Form {...form}>
358
+ <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-sui-16">
359
+ <FormField
360
+ control={form.control}
361
+ name="email"
362
+ render={({ field }) => (
363
+ <FormItem>
364
+ <FormLabel>Email</FormLabel>
365
+ <FormControl>
366
+ <InputField {...field} placeholder="you@example.com" />
367
+ </FormControl>
368
+ <FormDescription>We'll never share your email.</FormDescription>
369
+ <FormMessage />
370
+ </FormItem>
371
+ )}
372
+ />
373
+ <Button type="submit">Submit</Button>
374
+ </form>
375
+ </Form>
376
+ ```
377
+
378
+ ---
379
+
380
+ ## Composite Components
381
+
382
+ | Component | Purpose |
383
+ |-----------|---------|
384
+ | **SignInCard** | Pre-composed sign-in card |
385
+ | **ProductCard** | Pre-composed product card |
386
+ | **MarkdownEditor** | Lexical-based markdown editor |
387
+ | **CodeBlock** | Code display with syntax highlighting |
388
+ | **Userbar**, **SidebarMenuItem**, **SidebarCard** | Sidebar layout primitives |
389
+
390
+ ---
391
+
392
+ ## Icons
393
+
394
+ **IMPORTANT:** See `icon-discovery.md` for detailed icon guidelines.
395
+
396
+ SortUI uses **Remix Icons** (bundled). Use `<i className="ri-{name}-{variant}" />` syntax.
397
+
398
+ ```tsx
399
+ <Button><i className="ri-search-line" />Search</Button>
400
+ <i className="ri-user-fill" />
401
+
402
+ // Icon color via token (no sui- prefix variations — text-sui-icon-* only)
403
+ <i className="ri-user-fill text-sui-icon-subtle" />
404
+ ```
405
+
406
+ **DO NOT** install `lucide-react`, `heroicons`, `react-icons`, or any other icon package.