@maxsteinwender/sort-ui 1.1.0 → 1.1.1
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 +4 -0
- package/guidelines/Guidelines.md +294 -0
- package/guidelines/components.md +406 -0
- package/guidelines/icon-discovery.md +247 -0
- package/guidelines/setup.md +343 -0
- package/guidelines/styles.md +356 -0
- package/guidelines/tokens.md +354 -0
- package/package.json +3 -2
|
@@ -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.
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# Icon Discovery and Usage Guidelines
|
|
2
|
+
|
|
3
|
+
This document explains how to find and use icons in the SortUI Design System.
|
|
4
|
+
|
|
5
|
+
## Icon Package
|
|
6
|
+
|
|
7
|
+
**Bundled with:** `@maxsteinwender/sort-ui` (Remix Icons — no separate install)
|
|
8
|
+
**Import:** Icons are loaded automatically via the main CSS bundle
|
|
9
|
+
**Location in package:** `node_modules/@maxsteinwender/sort-ui/dist/styles.css` (font-icon classes)
|
|
10
|
+
|
|
11
|
+
**DO NOT install `lucide-react`, `heroicons`, `react-icons`, or any other icon package.** Remix Icons is the single source.
|
|
12
|
+
|
|
13
|
+
## Naming Convention
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
ri-{icon-name}-{variant}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Variants:**
|
|
20
|
+
- `-line` — outlined/stroke style (most common)
|
|
21
|
+
- `-fill` — filled/solid style
|
|
22
|
+
|
|
23
|
+
**Examples:**
|
|
24
|
+
- `ri-user-line`, `ri-user-fill`
|
|
25
|
+
- `ri-settings-line`, `ri-settings-fill`
|
|
26
|
+
- `ri-arrow-right-line`, `ri-arrow-right-fill`
|
|
27
|
+
- `ri-check-line`, `ri-check-fill`
|
|
28
|
+
- `ri-close-line`, `ri-close-fill`
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Icons are rendered as `<i>` elements with Remix Icon class names:
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
<i className="ri-user-line" />
|
|
36
|
+
<i className="ri-settings-fill" />
|
|
37
|
+
<i className="ri-arrow-right-line" />
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Color via icon tokens** (note: icon colors use `text-sui-icon-*`):
|
|
41
|
+
```tsx
|
|
42
|
+
<i className="ri-user-fill text-sui-icon-default" />
|
|
43
|
+
<i className="ri-error-warning-line text-sui-icon-destructive" />
|
|
44
|
+
<i className="ri-check-line text-sui-icon-success" />
|
|
45
|
+
<i className="ri-information-line text-sui-icon-informative" />
|
|
46
|
+
<i className="ri-alarm-warning-line text-sui-icon-warning" />
|
|
47
|
+
<i className="ri-user-line text-sui-icon-subtle" />
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Within SortUI components:**
|
|
51
|
+
```tsx
|
|
52
|
+
// Button with lead icon
|
|
53
|
+
<Button>
|
|
54
|
+
<i className="ri-search-line" />
|
|
55
|
+
Search
|
|
56
|
+
</Button>
|
|
57
|
+
|
|
58
|
+
// Icon-only button — MUST have aria-label
|
|
59
|
+
<Button iconOnly aria-label="Settings">
|
|
60
|
+
<i className="ri-settings-line" />
|
|
61
|
+
</Button>
|
|
62
|
+
|
|
63
|
+
// InputField with lead icon
|
|
64
|
+
<InputField
|
|
65
|
+
leadIcon={<i className="ri-mail-line" />}
|
|
66
|
+
placeholder="Email"
|
|
67
|
+
/>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Sizing
|
|
71
|
+
|
|
72
|
+
Most SortUI components handle icon sizing automatically. When direct size control is needed, use Tailwind text-size utilities — do NOT use `ri-xs` / `ri-sm` helpers:
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<i className="ri-user-line text-sm" />
|
|
76
|
+
<i className="ri-user-line text-base" />
|
|
77
|
+
<i className="ri-user-line text-lg" />
|
|
78
|
+
<i className="ri-user-line text-xl" />
|
|
79
|
+
<i className="ri-user-line text-2xl" />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## How to Find Icons
|
|
83
|
+
|
|
84
|
+
**CRITICAL:** Do NOT guess icon names. Icon names must be verified before use.
|
|
85
|
+
|
|
86
|
+
### Method 1: Search the bundled CSS
|
|
87
|
+
|
|
88
|
+
The bundled CSS contains every available icon class. Search `node_modules/@maxsteinwender/sort-ui/dist/styles.css` for keywords:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
grep -o "\.ri-[a-z0-9-]*user[a-z0-9-]*:before" node_modules/@maxsteinwender/sort-ui/dist/styles.css
|
|
92
|
+
grep -o "\.ri-[a-z0-9-]*calendar[a-z0-9-]*:before" node_modules/@maxsteinwender/sort-ui/dist/styles.css
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Example workflow:
|
|
96
|
+
1. Identify keyword (e.g., "calendar")
|
|
97
|
+
2. Search styles.css for `ri-calendar`
|
|
98
|
+
3. Find matching icons: `ri-calendar-line`, `ri-calendar-fill`, `ri-calendar-event-line`, `ri-calendar-2-line`, etc.
|
|
99
|
+
|
|
100
|
+
### Method 2: Official Remix Icon reference
|
|
101
|
+
|
|
102
|
+
The design system uses **Remix Icon v4.9.1**. All icons from Remix Icon 4.x are available.
|
|
103
|
+
|
|
104
|
+
Reference: https://remixicon.com/
|
|
105
|
+
|
|
106
|
+
**Common icon categories:**
|
|
107
|
+
- **System:** settings, more, menu, close, check, error, information, question, warning
|
|
108
|
+
- **Arrows:** arrow-up, arrow-down, arrow-left, arrow-right, chevron-up, chevron-down
|
|
109
|
+
- **Media:** play, pause, stop, volume, image, camera, video
|
|
110
|
+
- **User:** user, team, account, contacts, profile
|
|
111
|
+
- **Files:** file, folder, attachment, download, upload
|
|
112
|
+
- **Communication:** mail, message, chat, notification, phone
|
|
113
|
+
- **Design:** pencil, brush, palette, format, layout
|
|
114
|
+
- **Business:** briefcase, bank, wallet, price-tag, shopping-cart
|
|
115
|
+
- **Maps:** map, pin, location, navigation, compass
|
|
116
|
+
- **Device:** phone, tablet, computer, keyboard, mouse
|
|
117
|
+
- **Weather:** sun, moon, cloud, rain, snow
|
|
118
|
+
|
|
119
|
+
## Common Icon Patterns
|
|
120
|
+
|
|
121
|
+
### Navigation icons
|
|
122
|
+
```tsx
|
|
123
|
+
<i className="ri-arrow-left-line" />
|
|
124
|
+
<i className="ri-arrow-right-line" />
|
|
125
|
+
<i className="ri-arrow-up-line" />
|
|
126
|
+
<i className="ri-arrow-down-line" />
|
|
127
|
+
<i className="ri-chevron-left-line" />
|
|
128
|
+
<i className="ri-chevron-right-line" />
|
|
129
|
+
<i className="ri-menu-line" />
|
|
130
|
+
<i className="ri-close-line" />
|
|
131
|
+
<i className="ri-more-line" />
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Action icons
|
|
135
|
+
```tsx
|
|
136
|
+
<i className="ri-add-line" />
|
|
137
|
+
<i className="ri-subtract-line" />
|
|
138
|
+
<i className="ri-edit-line" />
|
|
139
|
+
<i className="ri-delete-bin-line" />
|
|
140
|
+
<i className="ri-save-line" />
|
|
141
|
+
<i className="ri-download-line" />
|
|
142
|
+
<i className="ri-upload-line" />
|
|
143
|
+
<i className="ri-share-line" />
|
|
144
|
+
<i className="ri-search-line" />
|
|
145
|
+
<i className="ri-filter-line" />
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Status icons
|
|
149
|
+
```tsx
|
|
150
|
+
<i className="ri-check-line text-sui-icon-success" />
|
|
151
|
+
<i className="ri-close-circle-line text-sui-icon-destructive" />
|
|
152
|
+
<i className="ri-error-warning-line text-sui-icon-warning" />
|
|
153
|
+
<i className="ri-information-line text-sui-icon-informative" />
|
|
154
|
+
<i className="ri-checkbox-circle-line text-sui-icon-success" />
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### User / Social icons
|
|
158
|
+
```tsx
|
|
159
|
+
<i className="ri-user-line" />
|
|
160
|
+
<i className="ri-user-fill" />
|
|
161
|
+
<i className="ri-team-line" />
|
|
162
|
+
<i className="ri-account-circle-line" />
|
|
163
|
+
<i className="ri-heart-line" />
|
|
164
|
+
<i className="ri-star-line" />
|
|
165
|
+
<i className="ri-thumb-up-line" />
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Communication icons
|
|
169
|
+
```tsx
|
|
170
|
+
<i className="ri-mail-line" />
|
|
171
|
+
<i className="ri-message-line" />
|
|
172
|
+
<i className="ri-chat-line" />
|
|
173
|
+
<i className="ri-notification-line" />
|
|
174
|
+
<i className="ri-phone-line" />
|
|
175
|
+
<i className="ri-send-plane-line" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### File / Document icons
|
|
179
|
+
```tsx
|
|
180
|
+
<i className="ri-file-line" />
|
|
181
|
+
<i className="ri-folder-line" />
|
|
182
|
+
<i className="ri-attachment-line" />
|
|
183
|
+
<i className="ri-file-text-line" />
|
|
184
|
+
<i className="ri-file-pdf-line" />
|
|
185
|
+
<i className="ri-image-line" />
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Settings / System icons
|
|
189
|
+
```tsx
|
|
190
|
+
<i className="ri-settings-line" />
|
|
191
|
+
<i className="ri-equalizer-line" />
|
|
192
|
+
<i className="ri-tools-line" />
|
|
193
|
+
<i className="ri-lock-line" />
|
|
194
|
+
<i className="ri-eye-line" />
|
|
195
|
+
<i className="ri-eye-off-line" />
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## DO / DON'T
|
|
199
|
+
|
|
200
|
+
**DO:**
|
|
201
|
+
```tsx
|
|
202
|
+
<i className="ri-user-line" />
|
|
203
|
+
<i className="ri-check-line" /> {/* outlined */}
|
|
204
|
+
<i className="ri-check-fill" /> {/* filled */}
|
|
205
|
+
|
|
206
|
+
// Icon-only button with aria-label
|
|
207
|
+
<Button iconOnly aria-label="Settings">
|
|
208
|
+
<i className="ri-settings-line" />
|
|
209
|
+
</Button>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**DON'T:**
|
|
213
|
+
```tsx
|
|
214
|
+
// ❌ Guessed icon names
|
|
215
|
+
<i className="ri-profile-line" /> {/* doesn't exist — use ri-user-line */}
|
|
216
|
+
|
|
217
|
+
// ❌ Installing alternate icon packages
|
|
218
|
+
import { User } from "lucide-react";
|
|
219
|
+
|
|
220
|
+
// ❌ Icon-only button without aria-label
|
|
221
|
+
<Button iconOnly>
|
|
222
|
+
<i className="ri-settings-line" />
|
|
223
|
+
</Button>
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Verifying Icon Existence
|
|
227
|
+
|
|
228
|
+
Before using an icon in generated code:
|
|
229
|
+
|
|
230
|
+
1. **Search the bundled CSS** for the icon class
|
|
231
|
+
2. **Check the pattern** matches `ri-{name}-line` or `ri-{name}-fill`
|
|
232
|
+
3. **If uncertain**, pick a similar known icon (e.g., `ri-user-line` instead of guessing `ri-profile-line`)
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
# Verify ri-calendar-event-line exists
|
|
236
|
+
grep "ri-calendar-event-line:before" node_modules/@maxsteinwender/sort-ui/dist/styles.css
|
|
237
|
+
# ✓ If output contains "content:" the icon is valid
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Reminders
|
|
241
|
+
|
|
242
|
+
- **Remix Icons are bundled** — no separate package install
|
|
243
|
+
- **Do NOT install `lucide-react`** or other icon packages
|
|
244
|
+
- **Always verify icon existence** before use
|
|
245
|
+
- **Use `-line` for outlined**, `-fill` for solid
|
|
246
|
+
- **Provide `aria-label`** for icon-only buttons
|
|
247
|
+
- **Icon color via `text-sui-icon-*` tokens** (never raw hex)
|