@nqlib/nqui 0.4.0 → 0.4.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.
Files changed (84) hide show
  1. package/INSTALLATION.md +215 -0
  2. package/README.md +2 -1
  3. package/dist/command-palette-BuYcxPCc.cjs +5 -0
  4. package/dist/command-palette-dEJ9aEk4.js +694 -0
  5. package/dist/command.cjs.js +1 -1
  6. package/dist/command.es.js +1 -1
  7. package/dist/components/custom/enhanced-badge.d.ts +1 -1
  8. package/dist/components/custom/enhanced-button.d.ts +6 -1
  9. package/dist/components/custom/enhanced-button.d.ts.map +1 -1
  10. package/dist/components/custom/enhanced-checkbox.d.ts +11 -0
  11. package/dist/components/custom/enhanced-checkbox.d.ts.map +1 -1
  12. package/dist/components/custom/enhanced-radio-group.d.ts +13 -4
  13. package/dist/components/custom/enhanced-radio-group.d.ts.map +1 -1
  14. package/dist/components/custom/enhanced-sonner.d.ts +5 -6
  15. package/dist/components/custom/enhanced-sonner.d.ts.map +1 -1
  16. package/dist/components/custom/enhanced-tabs.d.ts.map +1 -1
  17. package/dist/components/error-boundary.d.ts +20 -0
  18. package/dist/components/error-boundary.d.ts.map +1 -0
  19. package/dist/components/index.d.ts +102 -0
  20. package/dist/components/index.d.ts.map +1 -0
  21. package/dist/components/ui/badge.d.ts +1 -1
  22. package/dist/components/ui/button.d.ts +1 -1
  23. package/dist/components/ui/checkbox.d.ts +4 -1
  24. package/dist/components/ui/checkbox.d.ts.map +1 -1
  25. package/dist/components/ui/input-group.d.ts +1 -1
  26. package/dist/components/ui/input-group.d.ts.map +1 -1
  27. package/dist/components/ui/radio-group.d.ts +3 -1
  28. package/dist/components/ui/radio-group.d.ts.map +1 -1
  29. package/dist/components/ui/sidebar.d.ts.map +1 -1
  30. package/dist/debug-panel-AjzBdMMz.js +9198 -0
  31. package/dist/debug-panel-NaOmD68t.cjs +171 -0
  32. package/dist/debug.cjs.js +1 -0
  33. package/dist/debug.es.js +7 -0
  34. package/dist/drawer-Cqq0Ozb2.cjs +1 -0
  35. package/dist/{drawer-CU4lkcz7.js → drawer-pUXPg3lF.js} +2 -2
  36. package/dist/drawer.cjs.js +1 -1
  37. package/dist/drawer.es.js +1 -1
  38. package/dist/entries/debug.d.ts +14 -0
  39. package/dist/entries/debug.d.ts.map +1 -0
  40. package/dist/hooks/use-mobile.d.ts.map +1 -1
  41. package/dist/hooks/use-scroll-spy.d.ts.map +1 -1
  42. package/dist/index-CI756mSv.cjs +41 -0
  43. package/dist/index-CgfzsUO6.js +1069 -0
  44. package/dist/index.d.ts +2 -98
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/lib/index.d.ts +9 -0
  47. package/dist/lib/index.d.ts.map +1 -0
  48. package/dist/nqui.cjs.js +42 -212
  49. package/dist/nqui.es.js +8589 -17780
  50. package/dist/sonner-BtzU00r3.js +248 -0
  51. package/dist/sonner-Dfk26eds.cjs +54 -0
  52. package/dist/sonner.cjs.js +1 -1
  53. package/dist/sonner.es.js +1 -1
  54. package/dist/styles.css +3 -0
  55. package/docs/components/README.md +99 -1
  56. package/docs/components/nqui-card.md +7 -0
  57. package/docs/components/nqui-checkbox.md +23 -1
  58. package/docs/components/nqui-radio-group.md +45 -2
  59. package/docs/components/nqui-tabs.md +11 -1
  60. package/docs/nqui-skills/SKILL.md +95 -0
  61. package/docs/nqui-skills/design-system.md +130 -0
  62. package/docs/nqui-skills/rules/composition.md +183 -0
  63. package/docs/nqui-skills/rules/forms.md +190 -0
  64. package/docs/nqui-skills/rules/icons.md +158 -0
  65. package/docs/nqui-skills/rules/styling.md +192 -0
  66. package/package.json +23 -10
  67. package/scripts/cli.js +1 -0
  68. package/scripts/download-skills.js +91 -0
  69. package/scripts/examples/nextjs-layout-sidebar.tsx +100 -0
  70. package/scripts/examples/nextjs-page-sidebar.tsx +81 -0
  71. package/scripts/examples/vite-app.tsx +135 -0
  72. package/scripts/examples/vite-main.tsx +17 -0
  73. package/scripts/examples.js +92 -6
  74. package/scripts/generate-docs.js +169 -0
  75. package/scripts/init-css.js +34 -14
  76. package/scripts/init-cursor.js +8 -0
  77. package/scripts/post-install.js +41 -9
  78. package/scripts/resolve-target-dir.js +20 -1
  79. package/scripts/wizard.js +12 -7
  80. package/dist/command-palette-UHk8zZOg.cjs +0 -45
  81. package/dist/command-palette-d-TrdBsM.js +0 -1778
  82. package/dist/drawer-BcIxWRN8.cjs +0 -1
  83. package/dist/sonner-Co6YpYVs.js +0 -546
  84. package/dist/sonner-DbQhVp8m.cjs +0 -330
@@ -0,0 +1,130 @@
1
+ ---
2
+ name: nqui-design-system
3
+ description: Design system conventions for nqui component library. Use when creating or modifying nqui UI components to ensure sizing, spacing, and styling consistency.
4
+ ---
5
+
6
+ # nqui Design System
7
+
8
+ Guidelines for AI agents implementing or modifying nqui components. Follow these rules to maintain visual consistency.
9
+
10
+ ## Control Size Scale
11
+
12
+ All interactive controls (buttons, toggles, inputs, selects) use a unified scale:
13
+
14
+ | Size | Height | Min Width | Use Case |
15
+ |----------|--------|-----------|-----------------------------------|
16
+ | `sm` | h-6 (24px) | min-w-6 | Compact UIs, dense tables, toolbars |
17
+ | `default`| h-7 (28px) | min-w-7 | Standard forms, primary actions |
18
+ | `lg` | h-8 (32px) | min-w-8 | Emphasis, secondary actions |
19
+
20
+ ### Semantic Rule
21
+
22
+ `size="sm"` on Button MUST produce the same height as `size="sm"` on ToggleGroupItem, SegmentedControlItem, SelectTrigger, etc. Never introduce component-specific scales.
23
+
24
+ ## Layout Heights
25
+
26
+ | Element | Height | Use Case |
27
+ |---------|--------|----------|
28
+ | Header | h-12 (48px) | Page header, app bar, sticky nav |
29
+ | Sidebar header | h-12 (48px) | Sidebar section title bar |
30
+
31
+ Fits 4px/8px grid. Default button (h-7) in 48px header leaves 10px top/bottom; h-8 leaves 8px. Use px-4, gap-2 or gap-4 for horizontal spacing.
32
+
33
+ ## Component Size Mapping
34
+
35
+ | Component | sm | default | lg |
36
+ |-----------|-----|---------|-----|
37
+ | Button | h-6 min-w-6 px-2 text-xs | h-7 min-w-7 px-3 | h-8 min-w-8 px-4 |
38
+ | Button (icon) | — | h-7 w-7 p-0 | — |
39
+ | Toggle | h-6 min-w-6 px-1.5 | h-7 min-w-7 px-2 | h-8 min-w-8 px-2 |
40
+ | ToggleGroupItem | (uses Toggle) | | |
41
+ | SelectTrigger | h-6 | h-7 | — |
42
+ | Input | — | h-7 px-3 py-1.5 text-sm | — |
43
+ | InputGroup | — | h-7 | — |
44
+
45
+ ## Border Radius & Nested Radius
46
+
47
+ | Token | Formula | Use Case |
48
+ |-------|---------|----------|
49
+ | --radius-sm | calc(--radius - 4px) | Compact controls, kbd |
50
+ | --radius-md | calc(--radius - 2px) | Default (Button, Input, Card) |
51
+ | --radius-lg | var(--radius) | Large surfaces |
52
+ | --radius-xl | calc(--radius + 4px) | Modals, sheets |
53
+
54
+ **Nested radius formula:** `R_inner = R_outer - offset`. When a smaller element sits inside a larger one with padding, use concentric radius so corners align.
55
+
56
+ ```
57
+ /* Inner card with p-3 (12px) inset inside radius-lg card */
58
+ border-radius: calc(var(--radius-lg) - var(--spacing-3));
59
+ ```
60
+
61
+ - **Standalone** components (Button, Input) use radius tokens directly.
62
+ - **Nested** elements (Card inside Card, panel in modal) use `calc(outer - offset)`.
63
+ - Clamp to avoid negatives: `max(0px, calc(...))` when offset ≥ outer.
64
+
65
+ ## Typography
66
+
67
+ | Size | Class | Use Case |
68
+ |------|-------|----------|
69
+ | sm | `text-xs` or `text-[0.625rem]` | Compact controls, labels |
70
+ | default | `text-sm` | Body, inputs, buttons |
71
+ | base | `text-base` | Section titles, headings |
72
+
73
+ Font: `--font-sans` (Inter Variable). Leading: `leading-normal` or `text-xs/relaxed`.
74
+
75
+ ## When Adding a New Component
76
+
77
+ 1. **Use the scale** – If the component has a `size` prop, map `sm`→h-6, `default`→h-7, `lg`→h-8.
78
+ 2. **Match padding** – Text controls: px-2–3, py-1.5. Icon-only: p-0 with explicit size.
79
+ 3. **Text size** – sm: `text-xs` or `text-[0.625rem]`, default: `text-sm`, lg: `text-sm`.
80
+ 4. **Border radius** – Use `rounded-md` (default) or `rounded-[min(var(--radius-md),8px)]` for sm. For nested layouts, use `calc(outer - offset)`.
81
+
82
+ ## Grouped Controls (ButtonGroup, ToggleGroup)
83
+
84
+ - **Shared border** – Container: `rounded-md border border-input overflow-hidden`
85
+ - **Child borders** – Items: `border-0` (container provides the border)
86
+ - **Dividers** – ToggleGroup: item borders (`border-foreground/20`) between items. Or `ToggleGroupSeparator` when `separator={false}`.
87
+ - **Corners** – First item: rounded-l (or rounded-t vertical), last: rounded-r (or rounded-b)
88
+
89
+ ## Toggle & ToggleGroup Visual Treatment (Visibility on Any Background)
90
+
91
+ Toggles and ToggleGroup items must remain visible on card, sidebar, and varied backgrounds:
92
+
93
+ | State | Default/Outline | Segmented (single select) |
94
+ |-------|-----------------|---------------------------|
95
+ | **Off** | `border border-input/60` (or `border-input`), `shadow-sm`, `bg-background/50` (or transparent) | Transparent |
96
+ | **On** | `bg-secondary` + `nqui-button-gradient` + `nqui-button-shadow` (secondary-like layering) | `bg-primary` + gradient + shadow |
97
+ | **Active (pressed)** | `active:shadow-[inset_0_3px_5px_rgba(0,0,0,0.125)]` | Same |
98
+
99
+ Never use flat `bg-muted` only for selected state; always add gradient + shadow for depth and visibility.
100
+
101
+ ## Toolbar & In-Context Design
102
+
103
+ **Rule:** Show controls in realistic app context, not isolated. Reference: `packages/nqui/src/pages/ComponentShowcase.tsx` (Toggle & ToggleGroup section).
104
+
105
+ | Context | Layout | Example |
106
+ |---------|--------|---------|
107
+ | Document editor | Toolbar above content area, `bg-muted/30` container, `Separator` between control groups | Bold/Italic/Underline \| Align Left/Center/Right |
108
+ | Chart/settings panel | Label + inline controls, `rounded-lg border bg-muted/30 p-3` | Y-axis: Linear/Log; Size: S/M/L |
109
+ | Standalone | Inline with related UI, not floating alone | Pin, Mute, single Toggle |
110
+
111
+ ## Customization
112
+
113
+ - End users override via `className` or `size` when supported.
114
+ - Do NOT hardcode heights in consumer code when the component supports `size`.
115
+ - Prefer semantic sizes over pixel values in component defaults.
116
+
117
+ ## Files to Check for Consistency
118
+
119
+ - `packages/nqui/src/components/ui/button.tsx`
120
+ - `packages/nqui/src/components/ui/toggle.tsx`
121
+ - `packages/nqui/src/components/ui/toggle-group.tsx`
122
+ - `packages/nqui/src/components/ui/input.tsx`
123
+ - `packages/nqui/src/components/ui/select.tsx`
124
+ - `packages/nqui/src/components/custom/enhanced-button.tsx`
125
+
126
+ ## Anti-Patterns
127
+
128
+ - **Different heights for same size** – Button h-10 while Toggle h-7 = inconsistent.
129
+ - **Component-specific scales** – Do not add `xs` or `xl` without updating the design system doc.
130
+ - **Overriding in showcase** – If showcase needs `className="h-9"` to look right, the component default is wrong.
@@ -0,0 +1,183 @@
1
+ # Component Composition
2
+
3
+ Best practices for composing nqui components.
4
+
5
+ ## Contents
6
+
7
+ - Items always inside their Group component
8
+ - Callouts use Alert
9
+ - Empty states use Empty component
10
+ - Toast notifications use sonner
11
+ - Choosing between overlay components
12
+ - Dialog, Sheet, and Drawer always need a Title
13
+ - Card structure
14
+ - TabsTrigger must be inside TabsList
15
+ - Avatar always needs AvatarFallback
16
+ - Use Separator instead of raw hr or border divs
17
+ - Use Skeleton for loading placeholders
18
+ - Use Badge instead of custom styled spans
19
+
20
+ ---
21
+
22
+ ## Items always inside their Group component
23
+
24
+ Never render items directly inside the content container.
25
+
26
+ **Incorrect:**
27
+
28
+ ```tsx
29
+ <SelectContent>
30
+ <SelectItem value="apple">Apple</SelectItem>
31
+ <SelectItem value="banana">Banana</SelectItem>
32
+ </SelectContent>
33
+ ```
34
+
35
+ **Correct:**
36
+
37
+ ```tsx
38
+ <SelectContent>
39
+ <SelectGroup>
40
+ <SelectItem value="apple">Apple</SelectItem>
41
+ <SelectItem value="banana">Banana</SelectItem>
42
+ </SelectGroup>
43
+ </SelectContent>
44
+ ```
45
+
46
+ This applies to all group-based components:
47
+
48
+ | Item | Group |
49
+ |------|-------|
50
+ | `SelectItem`, `SelectLabel` | `SelectGroup` |
51
+ | `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
52
+ | `MenubarItem` | `MenubarGroup` |
53
+ | `ContextMenuItem` | `ContextMenuGroup` |
54
+ | `CommandItem` | `CommandGroup` |
55
+
56
+ ---
57
+
58
+ ## Callouts use Alert
59
+
60
+ ```tsx
61
+ <Alert>
62
+ <AlertTitle>Warning</AlertTitle>
63
+ <AlertDescription>Something needs attention.</AlertDescription>
64
+ </Alert>
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Empty states use Empty component
70
+
71
+ ```tsx
72
+ <Empty>
73
+ <EmptyHeader>
74
+ <EmptyMedia variant="icon"><FolderIcon /></EmptyMedia>
75
+ <EmptyTitle>No projects yet</EmptyTitle>
76
+ <EmptyDescription>Get started by creating a new project.</EmptyDescription>
77
+ </EmptyHeader>
78
+ <EmptyContent>
79
+ <Button>Create Project</Button>
80
+ </EmptyContent>
81
+ </Empty>
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Toast notifications use sonner
87
+
88
+ ```tsx
89
+ import { toast } from "sonner"
90
+
91
+ toast.success("Changes saved.")
92
+ toast.error("Something went wrong.")
93
+ toast("File deleted.", {
94
+ action: { label: "Undo", onClick: () => undoDelete() },
95
+ })
96
+ ```
97
+
98
+ ---
99
+
100
+ ## Choosing between overlay components
101
+
102
+ | Use case | Component |
103
+ |----------|-----------|
104
+ | Focused task that requires input | `Dialog` |
105
+ | Destructive action confirmation | `AlertDialog` |
106
+ | Side panel with details or filters | `Sheet` |
107
+ | Mobile-first bottom panel | `Drawer` |
108
+ | Quick info on hover | `HoverCard` |
109
+ | Small contextual content on click | `Popover` |
110
+
111
+ ---
112
+
113
+ ## Dialog, Sheet, and Drawer always need a Title
114
+
115
+ `DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden.
116
+
117
+ ```tsx
118
+ <DialogContent>
119
+ <DialogHeader>
120
+ <DialogTitle>Edit Profile</DialogTitle>
121
+ <DialogDescription>Update your profile.</DialogDescription>
122
+ </DialogHeader>
123
+ ...
124
+ </DialogContent>
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Card structure
130
+
131
+ Use full composition — don't dump everything into `CardContent`:
132
+
133
+ ```tsx
134
+ <Card>
135
+ <CardHeader>
136
+ <CardTitle>Team Members</CardTitle>
137
+ <CardDescription>Manage your team.</CardDescription>
138
+ </CardHeader>
139
+ <CardContent>...</CardContent>
140
+ <CardFooter>
141
+ <Button>Invite</Button>
142
+ </CardFooter>
143
+ </Card>
144
+ ```
145
+
146
+ ---
147
+
148
+ ## TabsTrigger must be inside TabsList
149
+
150
+ Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
151
+
152
+ ```tsx
153
+ <Tabs defaultValue="account">
154
+ <TabsList>
155
+ <TabsTrigger value="account">Account</TabsTrigger>
156
+ <TabsTrigger value="password">Password</TabsTrigger>
157
+ </TabsList>
158
+ <TabsContent value="account">...</TabsContent>
159
+ </Tabs>
160
+ ```
161
+
162
+ ---
163
+
164
+ ## Avatar always needs AvatarFallback
165
+
166
+ Always include `AvatarFallback` for when the image fails to load:
167
+
168
+ ```tsx
169
+ <Avatar>
170
+ <AvatarImage src="/avatar.png" alt="User" />
171
+ <AvatarFallback>JD</AvatarFallback>
172
+ </Avatar>
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Use existing components instead of custom markup
178
+
179
+ | Instead of | Use |
180
+ |------------|-----|
181
+ | `<hr>` or `<div className="border-t">` | `<Separator />` |
182
+ | `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
183
+ | `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
@@ -0,0 +1,190 @@
1
+ # Forms & Inputs
2
+
3
+ nqui provides a comprehensive form system using FieldGroup, Field, InputGroup, and ToggleGroup components.
4
+
5
+ ## Contents
6
+
7
+ - Forms use FieldGroup + Field
8
+ - InputGroup requires InputGroupInput/InputGroupTextarea
9
+ - Buttons inside inputs use InputGroup + InputGroupAddon
10
+ - Option sets (2–7 choices) use ToggleGroup
11
+ - FieldSet + FieldLegend for grouping related fields
12
+ - Field validation and disabled states
13
+
14
+ ---
15
+
16
+ ## Forms use FieldGroup + Field
17
+
18
+ Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
19
+
20
+ ```tsx
21
+ <FieldGroup>
22
+ <Field>
23
+ <FieldLabel htmlFor="email">Email</FieldLabel>
24
+ <Input id="email" type="email" />
25
+ </Field>
26
+ <Field>
27
+ <FieldLabel htmlFor="password">Password</FieldLabel>
28
+ <Input id="password" type="password" />
29
+ </Field>
30
+ </FieldGroup>
31
+ ```
32
+
33
+ Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
34
+
35
+ **Choosing form controls:**
36
+
37
+ - Simple text input → `Input`
38
+ - Dropdown with predefined options → `Select`
39
+ - Searchable dropdown → `Combobox`
40
+ - Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
41
+ - Single choice from few options → `RadioGroup`
42
+ - Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`
43
+ - Multi-line text → `Textarea`
44
+
45
+ ---
46
+
47
+ ## InputGroup requires InputGroupInput/InputGroupTextarea
48
+
49
+ Never use raw `Input` or `Textarea` inside an `InputGroup`.
50
+
51
+ **Incorrect:**
52
+
53
+ ```tsx
54
+ <InputGroup>
55
+ <Input placeholder="Search..." />
56
+ </InputGroup>
57
+ ```
58
+
59
+ **Correct:**
60
+
61
+ ```tsx
62
+ import { InputGroup, InputGroupInput } from "@nqlib/nqui"
63
+
64
+ <InputGroup>
65
+ <InputGroupInput placeholder="Search..." />
66
+ </InputGroup>
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Buttons inside inputs use InputGroup + InputGroupAddon
72
+
73
+ Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
74
+
75
+ **Incorrect:**
76
+
77
+ ```tsx
78
+ <div className="relative">
79
+ <Input placeholder="Search..." className="pr-10" />
80
+ <Button className="absolute right-0 top-0" size="icon">
81
+ <SearchIcon />
82
+ </Button>
83
+ </div>
84
+ ```
85
+
86
+ **Correct:**
87
+
88
+ ```tsx
89
+ import { InputGroup, InputGroupInput, InputGroupAddon, Button } from "@nqlib/nqui"
90
+
91
+ <InputGroup>
92
+ <InputGroupInput placeholder="Search..." />
93
+ <InputGroupAddon>
94
+ <Button size="icon">
95
+ <SearchIcon data-icon="inline-start" />
96
+ </Button>
97
+ </InputGroupAddon>
98
+ </InputGroup>
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Option sets (2–7 choices) use ToggleGroup
104
+
105
+ Don't manually loop `Button` components with active state.
106
+
107
+ **Incorrect:**
108
+
109
+ ```tsx
110
+ const [selected, setSelected] = useState("daily")
111
+
112
+ <div className="flex gap-2">
113
+ {["daily", "weekly", "monthly"].map((option) => (
114
+ <Button
115
+ key={option}
116
+ variant={selected === option ? "default" : "outline"}
117
+ onClick={() => setSelected(option)}
118
+ >
119
+ {option}
120
+ </Button>
121
+ ))}
122
+ </div>
123
+ ```
124
+
125
+ **Correct:**
126
+
127
+ ```tsx
128
+ import { ToggleGroup, ToggleGroupItem } from "@nqlib/nqui"
129
+
130
+ <ToggleGroup spacing={2}>
131
+ <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
132
+ <ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
133
+ <ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
134
+ </ToggleGroup>
135
+ ```
136
+
137
+ Combine with `Field` for labelled toggle groups:
138
+
139
+ ```tsx
140
+ <Field orientation="horizontal">
141
+ <FieldTitle id="theme-label">Theme</FieldTitle>
142
+ <ToggleGroup aria-labelledby="theme-label" spacing={2}>
143
+ <ToggleGroupItem value="light">Light</ToggleGroup<ToggleGroupItem valueItem>
144
+ ="dark">Dark</ToggleGroupItem>
145
+ <ToggleGroupItem value="system">System</ToggleGroupItem>
146
+ </ToggleGroup>
147
+ </Field>
148
+ ```
149
+
150
+ ---
151
+
152
+ ## FieldSet + FieldLegend for grouping related fields
153
+
154
+ Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
155
+
156
+ ```tsx
157
+ <FieldSet>
158
+ <FieldLegend variant="label">Preferences</FieldLegend>
159
+ <FieldDescription>Select all that apply.</FieldDescription>
160
+ <FieldGroup className="gap-3">
161
+ <Field orientation="horizontal">
162
+ <Checkbox id="dark" />
163
+ <FieldLabel htmlFor="dark" className="font-normal">Dark mode</FieldLabel>
164
+ </Field>
165
+ </FieldGroup>
166
+ </FieldSet>
167
+ ```
168
+
169
+ ---
170
+
171
+ ## Field validation and disabled states
172
+
173
+ Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
174
+
175
+ ```tsx
176
+ // Invalid.
177
+ <Field data-invalid>
178
+ <FieldLabel htmlFor="email">Email</FieldLabel>
179
+ <Input id="email" aria-invalid />
180
+ <FieldDescription>Invalid email address.</FieldDescription>
181
+ </Field>
182
+
183
+ // Disabled.
184
+ <Field data-disabled>
185
+ <FieldLabel htmlFor="email">Email</FieldLabel>
186
+ <Input id="email" disabled />
187
+ </Field>
188
+ ```
189
+
190
+ Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`.
@@ -0,0 +1,158 @@
1
+ # Icons
2
+
3
+ nqui uses Hugeicons as its icon library. This document covers how to use icons correctly in nqui components.
4
+
5
+ **Always use Hugeicons** for icons in nqui components. Import from `@hugeicons/react` or `@hugeicons/core-free-icons`.
6
+
7
+ ---
8
+
9
+ ## Icons in Button use data-icon attribute
10
+
11
+ Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
12
+
13
+ **Incorrect:**
14
+
15
+ ```tsx
16
+ <Button>
17
+ <SearchIcon className="mr-2 size-4" />
18
+ Search
19
+ </Button>
20
+ ```
21
+
22
+ **Correct:**
23
+
24
+ ```tsx
25
+ import { SearchIcon } from "@hugeicons/react"
26
+
27
+ <Button>
28
+ <SearchIcon data-icon="inline-start" />
29
+ Search
30
+ </Button>
31
+
32
+ <Button>
33
+ Next
34
+ <ArrowRightIcon data-icon="inline-end"/>
35
+ </Button>
36
+ ```
37
+
38
+ ---
39
+
40
+ ## No sizing classes on icons inside components
41
+
42
+ Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other nqui components. Unless the user explicitly asks for custom icon sizes.
43
+
44
+ **Incorrect:**
45
+
46
+ ```tsx
47
+ <Button>
48
+ <SearchIcon className="size-4" data-icon="inline-start" />
49
+ Search
50
+ </Button>
51
+
52
+ <DropdownMenuItem>
53
+ <SettingsIcon className="mr-2 size-4" />
54
+ Settings
55
+ </DropdownMenuItem>
56
+ ```
57
+
58
+ **Correct:**
59
+
60
+ ```tsx
61
+ <Button>
62
+ <SearchIcon data-icon="inline-start" />
63
+ Search
64
+ </Button>
65
+
66
+ <DropdownMenuItem>
67
+ <SettingsIcon />
68
+ Settings
69
+ </DropdownMenuItem>
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Importing Hugeicons
75
+
76
+ ### Using @hugeicons/react (recommended for React)
77
+
78
+ ```tsx
79
+ import { SearchIcon, SettingsIcon, ArrowRightIcon } from "@hugeicons/react"
80
+ ```
81
+
82
+ ### Using @hugeicons/core-free-icons
83
+
84
+ For tree-shaking individual icons:
85
+
86
+ ```tsx
87
+ import { SearchIcon } from "@hugeicons/core-free-icons"
88
+ ```
89
+
90
+ ---
91
+
92
+ ## Icon Usage Patterns
93
+
94
+ ### Icon with label
95
+
96
+ ```tsx
97
+ <Button>
98
+ <SettingsIcon data-icon="inline-start" />
99
+ Settings
100
+ </Button>
101
+ ```
102
+
103
+ ### Icon only button
104
+
105
+ ```tsx
106
+ <Button size="icon">
107
+ <SearchIcon />
108
+ </Button>
109
+ ```
110
+
111
+ ### Icon in menu items
112
+
113
+ ```tsx
114
+ <DropdownMenuItem>
115
+ <SettingsIcon />
116
+ Settings
117
+ </DropdownMenuItem>
118
+
119
+ <DropdownMenuItem>
120
+ <UserIcon />
121
+ Profile
122
+ </DropdownMenuItem>
123
+ ```
124
+
125
+ ### Icon in form controls
126
+
127
+ ```tsx
128
+ <InputGroup>
129
+ <InputGroupAddon>
130
+ <SearchIcon />
131
+ </InputGroupAddon>
132
+ <InputGroupInput placeholder="Search..." />
133
+ </InputGroup>
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Migrating from other icon libraries
139
+
140
+ If you have existing code using other icon libraries (e.g., lucide-react), replace imports:
141
+
142
+ ```tsx
143
+ // Before (lucide-react)
144
+ import { Search, Settings } from "lucide-react"
145
+
146
+ // After (Hugeicons)
147
+ import { SearchIcon, SettingsIcon } from "@hugeicons/react"
148
+ ```
149
+
150
+ Then add the `data-icon` attribute to icons used in buttons:
151
+
152
+ ```tsx
153
+ // Before
154
+ <Button><Search /> Search</Button>
155
+
156
+ // After
157
+ <Button><SearchIcon data-icon="inline-start" /> Search</Button>
158
+ ```