@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.
- package/INSTALLATION.md +215 -0
- package/README.md +2 -1
- package/dist/command-palette-BuYcxPCc.cjs +5 -0
- package/dist/command-palette-dEJ9aEk4.js +694 -0
- package/dist/command.cjs.js +1 -1
- package/dist/command.es.js +1 -1
- package/dist/components/custom/enhanced-badge.d.ts +1 -1
- package/dist/components/custom/enhanced-button.d.ts +6 -1
- package/dist/components/custom/enhanced-button.d.ts.map +1 -1
- package/dist/components/custom/enhanced-checkbox.d.ts +11 -0
- package/dist/components/custom/enhanced-checkbox.d.ts.map +1 -1
- package/dist/components/custom/enhanced-radio-group.d.ts +13 -4
- package/dist/components/custom/enhanced-radio-group.d.ts.map +1 -1
- package/dist/components/custom/enhanced-sonner.d.ts +5 -6
- package/dist/components/custom/enhanced-sonner.d.ts.map +1 -1
- package/dist/components/custom/enhanced-tabs.d.ts.map +1 -1
- package/dist/components/error-boundary.d.ts +20 -0
- package/dist/components/error-boundary.d.ts.map +1 -0
- package/dist/components/index.d.ts +102 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/checkbox.d.ts +4 -1
- package/dist/components/ui/checkbox.d.ts.map +1 -1
- package/dist/components/ui/input-group.d.ts +1 -1
- package/dist/components/ui/input-group.d.ts.map +1 -1
- package/dist/components/ui/radio-group.d.ts +3 -1
- package/dist/components/ui/radio-group.d.ts.map +1 -1
- package/dist/components/ui/sidebar.d.ts.map +1 -1
- package/dist/debug-panel-AjzBdMMz.js +9198 -0
- package/dist/debug-panel-NaOmD68t.cjs +171 -0
- package/dist/debug.cjs.js +1 -0
- package/dist/debug.es.js +7 -0
- package/dist/drawer-Cqq0Ozb2.cjs +1 -0
- package/dist/{drawer-CU4lkcz7.js → drawer-pUXPg3lF.js} +2 -2
- package/dist/drawer.cjs.js +1 -1
- package/dist/drawer.es.js +1 -1
- package/dist/entries/debug.d.ts +14 -0
- package/dist/entries/debug.d.ts.map +1 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -1
- package/dist/hooks/use-scroll-spy.d.ts.map +1 -1
- package/dist/index-CI756mSv.cjs +41 -0
- package/dist/index-CgfzsUO6.js +1069 -0
- package/dist/index.d.ts +2 -98
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/nqui.cjs.js +42 -212
- package/dist/nqui.es.js +8589 -17780
- package/dist/sonner-BtzU00r3.js +248 -0
- package/dist/sonner-Dfk26eds.cjs +54 -0
- package/dist/sonner.cjs.js +1 -1
- package/dist/sonner.es.js +1 -1
- package/dist/styles.css +3 -0
- package/docs/components/README.md +99 -1
- package/docs/components/nqui-card.md +7 -0
- package/docs/components/nqui-checkbox.md +23 -1
- package/docs/components/nqui-radio-group.md +45 -2
- package/docs/components/nqui-tabs.md +11 -1
- package/docs/nqui-skills/SKILL.md +95 -0
- package/docs/nqui-skills/design-system.md +130 -0
- package/docs/nqui-skills/rules/composition.md +183 -0
- package/docs/nqui-skills/rules/forms.md +190 -0
- package/docs/nqui-skills/rules/icons.md +158 -0
- package/docs/nqui-skills/rules/styling.md +192 -0
- package/package.json +23 -10
- package/scripts/cli.js +1 -0
- package/scripts/download-skills.js +91 -0
- package/scripts/examples/nextjs-layout-sidebar.tsx +100 -0
- package/scripts/examples/nextjs-page-sidebar.tsx +81 -0
- package/scripts/examples/vite-app.tsx +135 -0
- package/scripts/examples/vite-main.tsx +17 -0
- package/scripts/examples.js +92 -6
- package/scripts/generate-docs.js +169 -0
- package/scripts/init-css.js +34 -14
- package/scripts/init-cursor.js +8 -0
- package/scripts/post-install.js +41 -9
- package/scripts/resolve-target-dir.js +20 -1
- package/scripts/wizard.js +12 -7
- package/dist/command-palette-UHk8zZOg.cjs +0 -45
- package/dist/command-palette-d-TrdBsM.js +0 -1778
- package/dist/drawer-BcIxWRN8.cjs +0 -1
- package/dist/sonner-Co6YpYVs.js +0 -546
- 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
|
+
```
|