@nqlib/nqui 0.4.3 → 0.4.5
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 +234 -0
- package/README.md +109 -151
- package/dist/button-CJHdCq9I.js +155 -0
- package/dist/button-R304rhsj.cjs +1 -0
- package/dist/calendar.cjs.js +1 -1
- package/dist/calendar.es.js +1 -1
- package/dist/carousel-D1FMVglR.cjs +1 -0
- package/dist/carousel-U7RZhYZj.js +179 -0
- package/dist/carousel.cjs.js +1 -1
- package/dist/carousel.es.js +1 -1
- package/dist/command-palette-DCtLpM3Q.js +694 -0
- package/dist/command-palette-MHc03bBf.cjs +5 -0
- package/dist/command.cjs.js +1 -1
- package/dist/command.es.js +1 -1
- package/dist/components/custom/color-picker.d.ts +1 -1
- package/dist/components/custom/color-picker.d.ts.map +1 -1
- package/dist/components/custom/color-slider.d.ts +4 -10
- package/dist/components/custom/color-slider.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-tabs.d.ts.map +1 -1
- package/dist/components/debug/debug-features.d.ts +29 -0
- package/dist/components/debug/debug-features.d.ts.map +1 -0
- package/dist/components/debug/debug-panel.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 +103 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/ui/badge.d.ts +16 -5
- package/dist/components/ui/badge.d.ts.map +1 -1
- package/dist/components/ui/button.d.ts +38 -4
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/checkbox.d.ts +16 -2
- package/dist/components/ui/checkbox.d.ts.map +1 -1
- package/dist/components/ui/combobox.d.ts +2 -1
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/frosted-glass.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/pagination.d.ts +3 -2
- package/dist/components/ui/pagination.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/select.d.ts +6 -1
- package/dist/components/ui/select.d.ts.map +1 -1
- package/dist/components/ui/sidebar.d.ts +1 -1
- package/dist/components/ui/sidebar.d.ts.map +1 -1
- package/dist/components/ui/slider.d.ts +10 -2
- package/dist/components/ui/slider.d.ts.map +1 -1
- package/dist/components/ui/sonner.d.ts +18 -2
- package/dist/components/ui/sonner.d.ts.map +1 -1
- package/dist/components/ui/spinner.d.ts +2 -1
- package/dist/components/ui/spinner.d.ts.map +1 -1
- package/dist/components/ui/switch.d.ts +15 -2
- package/dist/components/ui/switch.d.ts.map +1 -1
- package/dist/components/ui/tabs.d.ts +1 -1
- package/dist/components/ui/tabs.d.ts.map +1 -1
- package/dist/components/ui/toggle.d.ts +1 -1
- package/dist/components/ui/toggle.d.ts.map +1 -1
- package/dist/debug-panel-CNKk-No5.cjs +75 -0
- package/dist/debug-panel-pg39-6xw.js +9011 -0
- package/dist/debug.cjs.js +1 -0
- package/dist/debug.es.js +7 -0
- package/dist/{drawer-CU4lkcz7.js → drawer-DO26uhym.js} +31 -31
- package/dist/drawer-DVarEy65.cjs +1 -0
- package/dist/drawer.cjs.js +1 -1
- package/dist/drawer.es.js +1 -1
- package/dist/{enhanced-calendar-BENbxw7_.js → enhanced-calendar-BGlsSYJd.js} +1 -1
- package/dist/{enhanced-calendar-5PA8CeF7.cjs → enhanced-calendar-C7EQIr6i.cjs} +1 -1
- package/dist/entries/debug.d.ts +14 -0
- package/dist/entries/debug.d.ts.map +1 -0
- package/dist/entries/sonner.d.ts +1 -2
- package/dist/entries/sonner.d.ts.map +1 -1
- 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/lib/wrap-inline-label-text.d.ts +7 -0
- package/dist/lib/wrap-inline-label-text.d.ts.map +1 -0
- package/dist/nqui.cjs.js +49 -245
- package/dist/nqui.es.js +7402 -16735
- package/dist/sonner-CpmECDBk.js +179 -0
- package/dist/sonner-nE9hIalJ.cjs +48 -0
- package/dist/sonner.cjs.js +1 -1
- package/dist/sonner.es.js +3 -2
- package/dist/styles.css +237 -10
- package/docs/components/README.md +109 -10
- package/docs/components/nqui-badge.md +1 -0
- package/docs/components/nqui-button.md +3 -1
- package/docs/components/nqui-card.md +8 -0
- package/docs/components/nqui-carousel.md +6 -0
- package/docs/components/nqui-checkbox.md +38 -1
- package/docs/components/nqui-color-slider.md +5 -3
- package/docs/components/nqui-combobox.md +58 -37
- package/docs/components/nqui-drawer.md +1 -1
- package/docs/components/nqui-frosted-glass.md +83 -5
- package/docs/components/nqui-radio-group.md +47 -2
- package/docs/components/nqui-scroll-area.md +1 -1
- package/docs/components/nqui-select.md +2 -2
- package/docs/components/nqui-sheet.md +1 -1
- package/docs/components/nqui-slider.md +13 -0
- package/docs/components/nqui-spinner.md +6 -1
- package/docs/components/nqui-switch.md +23 -1
- package/docs/components/nqui-tabs.md +11 -1
- package/docs/components/nqui-toaster.md +5 -1
- package/docs/internal-notes/PUBLISHING.md +46 -4
- package/docs/nqui-skills/SKILL.md +106 -0
- package/docs/nqui-skills/design-system.md +143 -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 -12
- package/scripts/build-styles.js +16 -0
- 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/init-debug-css.js +4 -2
- package/scripts/post-install.js +41 -9
- package/scripts/publish-npmjs.js +17 -3
- package/scripts/resolve-target-dir.js +20 -1
- package/scripts/setup-helper.js +13 -1
- package/scripts/verify-build.js +1 -1
- package/scripts/wizard.js +12 -7
- package/dist/button-CYFTFDKe.cjs +0 -1
- package/dist/button-nJvDl3w8.js +0 -44
- package/dist/carousel-DEyyJi49.js +0 -179
- package/dist/carousel-Dhhz8m5V.cjs +0 -1
- package/dist/command-palette-UHk8zZOg.cjs +0 -45
- package/dist/command-palette-d-TrdBsM.js +0 -1778
- package/dist/components/custom/enhanced-badge.d.ts +0 -33
- package/dist/components/custom/enhanced-badge.d.ts.map +0 -1
- package/dist/components/custom/enhanced-button.d.ts +0 -34
- package/dist/components/custom/enhanced-button.d.ts.map +0 -1
- package/dist/components/custom/enhanced-checkbox.d.ts +0 -28
- package/dist/components/custom/enhanced-checkbox.d.ts.map +0 -1
- package/dist/components/custom/enhanced-combobox.d.ts +0 -35
- package/dist/components/custom/enhanced-combobox.d.ts.map +0 -1
- package/dist/components/custom/enhanced-select.d.ts +0 -30
- package/dist/components/custom/enhanced-select.d.ts.map +0 -1
- package/dist/components/custom/enhanced-sonner.d.ts +0 -16
- package/dist/components/custom/enhanced-sonner.d.ts.map +0 -1
- package/dist/drawer-BcIxWRN8.cjs +0 -1
- package/dist/sonner-Co6YpYVs.js +0 -546
- package/dist/sonner-DbQhVp8m.cjs +0 -330
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Styling & Customization
|
|
2
|
+
|
|
3
|
+
nqui uses Tailwind CSS v4 with semantic CSS variables. See customization docs for theming and CSS variables.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- Semantic colors
|
|
8
|
+
- Built-in variants first
|
|
9
|
+
- className for layout only
|
|
10
|
+
- No space-x-* / space-y-*
|
|
11
|
+
- Prefer size-* over w-* h-* when equal
|
|
12
|
+
- Prefer truncate shorthand
|
|
13
|
+
- No manual dark: color overrides
|
|
14
|
+
- Use cn() for conditional classes
|
|
15
|
+
- No manual z-index on overlay components (use elevation.css)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Semantic colors
|
|
20
|
+
|
|
21
|
+
**Incorrect:**
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<div className="bg-blue-500 text-white">
|
|
25
|
+
<p className="text-gray-600">Secondary text</p>
|
|
26
|
+
</div>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Correct:**
|
|
30
|
+
|
|
31
|
+
```tsx
|
|
32
|
+
<div className="bg-primary text-primary-foreground">
|
|
33
|
+
<p className="text-muted-foreground">Secondary text</p>
|
|
34
|
+
</div>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## No raw color values for status/state indicators
|
|
40
|
+
|
|
41
|
+
For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
|
|
42
|
+
|
|
43
|
+
**Incorrect:**
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<span className="text-emerald-600">+20.1%</span>
|
|
47
|
+
<span className="text-green-500">Active</span>
|
|
48
|
+
<span className="text-red-600">-3.2%</span>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Correct:**
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Badge variant="secondary">+20.1%</Badge>
|
|
55
|
+
<Badge>Active</Badge>
|
|
56
|
+
<span className="text-destructive">-3.2%</span>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Built-in variants first
|
|
62
|
+
|
|
63
|
+
**Incorrect:**
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<Button className="border border-input bg-transparent hover:bg-accent">
|
|
67
|
+
Click me
|
|
68
|
+
</Button>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Correct:**
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<Button variant="outline">Click me</Button>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## className for layout only
|
|
80
|
+
|
|
81
|
+
Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
|
|
82
|
+
|
|
83
|
+
**Incorrect:**
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Card className="bg-blue-100 text-blue-900 font-bold">
|
|
87
|
+
<CardContent>Dashboard</CardContent>
|
|
88
|
+
</Card>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Correct:**
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
<Card className="max-w-md mx-auto">
|
|
95
|
+
<CardContent>Dashboard</CardContent>
|
|
96
|
+
</Card>
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
To customize a component's appearance, prefer these approaches in order:
|
|
100
|
+
1. **Built-in variants** — `variant="outline"`, `variant="destructive"`, etc.
|
|
101
|
+
2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.
|
|
102
|
+
3. **CSS variables** — define custom colors in the global CSS file.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## No space-x-* / space-y-*
|
|
107
|
+
|
|
108
|
+
Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<div className="flex flex-col gap-4">
|
|
112
|
+
<Input />
|
|
113
|
+
<Input />
|
|
114
|
+
<Button>Submit</Button>
|
|
115
|
+
</div>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Prefer size-* over w-* h-* when equal
|
|
121
|
+
|
|
122
|
+
`size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Prefer truncate shorthand
|
|
127
|
+
|
|
128
|
+
`truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## No manual dark: color overrides
|
|
133
|
+
|
|
134
|
+
Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Use cn() for conditional classes
|
|
139
|
+
|
|
140
|
+
Use the `cn()` utility from nqui for conditional or merged class names. Don't write manual ternaries in className strings.
|
|
141
|
+
|
|
142
|
+
**Incorrect:**
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
<div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Correct:**
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { cn } from "@/lib/utils"
|
|
152
|
+
|
|
153
|
+
<div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## No manual z-index on overlay components (use elevation.css)
|
|
159
|
+
|
|
160
|
+
`Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Use the centralized z-index system from `elevation.css`.
|
|
161
|
+
|
|
162
|
+
**Incorrect:**
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
<div className="z-50">
|
|
166
|
+
<Dialog>...</Dialog>
|
|
167
|
+
</div>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Correct:**
|
|
171
|
+
|
|
172
|
+
```tsx
|
|
173
|
+
import { cn } from "@/lib/utils"
|
|
174
|
+
|
|
175
|
+
<div className={cn("z-[var(--z-modal)]")}>
|
|
176
|
+
<Dialog>...</Dialog>
|
|
177
|
+
</div>
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Reference: [elevation.css](../src/styles/elevation.css)
|
|
181
|
+
|
|
182
|
+
| Variable | Value | Use Case |
|
|
183
|
+
|----------|-------|----------|
|
|
184
|
+
| `--z-base` | 0 | Base layer |
|
|
185
|
+
| `--z-content` | 10 | Standard content |
|
|
186
|
+
| `--z-sticky-content` | 15 | Sticky content within containers |
|
|
187
|
+
| `--z-sticky-page` | 20 | Page-level sticky elements |
|
|
188
|
+
| `--z-floating` | 30 | Floating panels, sidebars |
|
|
189
|
+
| `--z-modal-backdrop` | 40 | Modal backdrops |
|
|
190
|
+
| `--z-modal` | 50 | Modal content |
|
|
191
|
+
| `--z-popover` | 60 | Dropdowns, select menus |
|
|
192
|
+
| `--z-tooltip` | 70 | Tooltips |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nqlib/nqui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
4
4
|
"description": "A React component library with enhanced UI components and developer tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/nqui.cjs.js",
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"require": "./dist/styles.css",
|
|
48
48
|
"default": "./dist/styles.css"
|
|
49
49
|
},
|
|
50
|
+
"./debug": {
|
|
51
|
+
"types": "./dist/entries/debug.d.ts",
|
|
52
|
+
"import": "./dist/debug.es.js",
|
|
53
|
+
"require": "./dist/debug.cjs.js"
|
|
54
|
+
},
|
|
50
55
|
"./debug.css": "./dist/nqui.css"
|
|
51
56
|
},
|
|
52
57
|
"files": [
|
|
@@ -65,7 +70,7 @@
|
|
|
65
70
|
},
|
|
66
71
|
"repository": {
|
|
67
72
|
"type": "git",
|
|
68
|
-
"url": "https://github.com/nqlib/nqui.git"
|
|
73
|
+
"url": "git+https://github.com/nqlib/nqui.git"
|
|
69
74
|
},
|
|
70
75
|
"keywords": [
|
|
71
76
|
"ui",
|
|
@@ -103,18 +108,21 @@
|
|
|
103
108
|
"build-storybook": "storybook build",
|
|
104
109
|
"test-storybook": "test-storybook",
|
|
105
110
|
"test-storybook:ci": "test-storybook --ci",
|
|
106
|
-
"test-storybook:coverage": "test-storybook --coverage"
|
|
111
|
+
"test-storybook:coverage": "test-storybook --coverage",
|
|
112
|
+
"test": "vitest run",
|
|
113
|
+
"test:watch": "vitest",
|
|
114
|
+
"nqui:init": "npx @nqlib/nqui install-peers && npx @nqlib/nqui init-cursor && npx @nqlib/nqui init-skills && npx @nqlib/nqui init-css --sidebar --force"
|
|
107
115
|
},
|
|
108
116
|
"bin": {
|
|
109
|
-
"nqui": "
|
|
110
|
-
"nqui-init-css": "
|
|
111
|
-
"nqui-init-cursor": "
|
|
112
|
-
"nqui-
|
|
113
|
-
"nqui-
|
|
114
|
-
"nqui-
|
|
117
|
+
"nqui": "scripts/cli.js",
|
|
118
|
+
"nqui-init-css": "scripts/init-css.js",
|
|
119
|
+
"nqui-init-cursor": "scripts/init-cursor.js",
|
|
120
|
+
"nqui-init-skills": "scripts/download-skills.js",
|
|
121
|
+
"nqui-install-peers": "scripts/install-peers.js",
|
|
122
|
+
"nqui-init-debug": "scripts/init-debug-css.js",
|
|
123
|
+
"nqui-setup": "scripts/post-install.js"
|
|
115
124
|
},
|
|
116
125
|
"dependencies": {
|
|
117
|
-
"@nqlib/nqcode": "workspace:*",
|
|
118
126
|
"@codesandbox/sandpack-react": "^2.20.0",
|
|
119
127
|
"@dnd-kit/core": "^6.3.1",
|
|
120
128
|
"@dnd-kit/modifiers": "^9.0.0",
|
|
@@ -123,7 +131,6 @@
|
|
|
123
131
|
"@fontsource-variable/inter": "^5.2.8",
|
|
124
132
|
"@hugeicons/core-free-icons": "^3.1.1",
|
|
125
133
|
"@hugeicons/react": "^1.1.4",
|
|
126
|
-
"@nqlib/nqappbuilder": "workspace:*",
|
|
127
134
|
"@radix-ui/react-avatar": "^1.1.11",
|
|
128
135
|
"@radix-ui/react-checkbox": "^1.3.3",
|
|
129
136
|
"@radix-ui/react-collapsible": "^1.1.12",
|
|
@@ -240,6 +247,8 @@
|
|
|
240
247
|
"@storybook/react-vite": "^10.1.11",
|
|
241
248
|
"@storybook/test-runner": "^0.24.2",
|
|
242
249
|
"@storybook/testing-library": "^0.2.2",
|
|
250
|
+
"@testing-library/jest-dom": "^6.6.3",
|
|
251
|
+
"@testing-library/react": "^16.1.0",
|
|
243
252
|
"@types/lodash.throttle": "^4.1.9",
|
|
244
253
|
"@types/node": "^24.10.1",
|
|
245
254
|
"@types/react": "^19.0.0",
|
|
@@ -255,6 +264,8 @@
|
|
|
255
264
|
"storybook": "^10.1.11",
|
|
256
265
|
"typescript": "~5.9.3",
|
|
257
266
|
"typescript-eslint": "^8.46.4",
|
|
258
|
-
"vite": "^7.2.4"
|
|
267
|
+
"vite": "^7.2.4",
|
|
268
|
+
"vitest": "^3.1.4",
|
|
269
|
+
"jsdom": "^26.1.0"
|
|
259
270
|
}
|
|
260
271
|
}
|
package/scripts/build-styles.js
CHANGED
|
@@ -19,6 +19,7 @@ const projectRoot = resolve(__dirname, '..');
|
|
|
19
19
|
const indexCssPath = join(projectRoot, 'src', 'index.css');
|
|
20
20
|
const colorsCssPath = join(projectRoot, 'src', 'styles', 'colors.css');
|
|
21
21
|
const elevationCssPath = join(projectRoot, 'src', 'styles', 'elevation.css');
|
|
22
|
+
const hitAreaCssPath = join(projectRoot, 'src', 'styles', 'hit-area.css');
|
|
22
23
|
const outputPath = join(projectRoot, 'dist', 'styles.css');
|
|
23
24
|
|
|
24
25
|
function extractStandaloneCSS() {
|
|
@@ -30,6 +31,11 @@ function extractStandaloneCSS() {
|
|
|
30
31
|
throw new Error(`Colors CSS file not found: ${colorsCssPath}`);
|
|
31
32
|
}
|
|
32
33
|
|
|
34
|
+
let hitAreaCss = '';
|
|
35
|
+
if (existsSync(hitAreaCssPath)) {
|
|
36
|
+
hitAreaCss = readFileSync(hitAreaCssPath, 'utf-8').trimEnd();
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
let indexCss = readFileSync(indexCssPath, 'utf-8');
|
|
34
40
|
let colorsCss = readFileSync(colorsCssPath, 'utf-8');
|
|
35
41
|
let elevationCss = '';
|
|
@@ -110,14 +116,23 @@ function extractStandaloneCSS() {
|
|
|
110
116
|
.replace(/@import\s+["']@fontsource-variable\/inter["'];?\s*\n/g, '')
|
|
111
117
|
.replace(/@import\s+["']\.\/styles\/colors\.css["'];?\s*\n/g, '')
|
|
112
118
|
.replace(/@import\s+["']\.\/styles\/elevation\.css["'];?\s*\n/g, '')
|
|
119
|
+
.replace(/@import\s+["']\.\/styles\/hit-area\.css["'];?\s*\n/g, '')
|
|
113
120
|
.replace(/\/\*\s*Import enhanced color system\s*\*\//g, '')
|
|
114
121
|
.replace(/\/\*\s*Import elevation system\s*\*\//g, '')
|
|
122
|
+
.replace(/\/\*\s*Hit-area utilities \(expanded pointer targets\)\s*\*\/\s*\n/g, '')
|
|
115
123
|
// Remove @source inline() directives (already extracted above) - must match multiline
|
|
116
124
|
.replace(/\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*@source\s+inline\([\s\S]*?\)\s*;/g, '')
|
|
117
125
|
// Remove other @source directives (non-inline ones)
|
|
118
126
|
.replace(/@source\s+(?!inline\()[^;]+;?\s*\n/g, '')
|
|
119
127
|
.replace(/@custom-variant\s+[^;]+;?\s*\n/g, '');
|
|
120
128
|
|
|
129
|
+
if (hitAreaCss) {
|
|
130
|
+
indexCss = indexCss.replace(
|
|
131
|
+
/@theme inline\s*\{/,
|
|
132
|
+
`/* Hit-area — https://bazza.dev/craft/2026/hit-area */\n${hitAreaCss}\n\n@theme inline {`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
121
136
|
// Extract :root and .dark blocks from index.css
|
|
122
137
|
const indexRootMatch = indexCss.match(/:root\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
|
|
123
138
|
const indexDarkMatch = indexCss.match(/\.dark\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/s);
|
|
@@ -232,6 +247,7 @@ function extractStandaloneCSS() {
|
|
|
232
247
|
* - Light and dark mode support
|
|
233
248
|
* - Base layer styles
|
|
234
249
|
* - Utility animations
|
|
250
|
+
* - Hit-area @utility blocks (inlined from src/styles/hit-area.css)
|
|
235
251
|
* - @source inline() directives for zero-config Tailwind utility generation
|
|
236
252
|
*
|
|
237
253
|
* Generated by: npm run build:lib
|
package/scripts/cli.js
CHANGED
|
@@ -20,6 +20,7 @@ const subcommand = process.argv[2];
|
|
|
20
20
|
|
|
21
21
|
const routes = {
|
|
22
22
|
'init-cursor': './init-cursor.js',
|
|
23
|
+
'init-skills': './download-skills.js',
|
|
23
24
|
'install-peers': './install-peers.js',
|
|
24
25
|
'init-debug': './init-debug-css.js',
|
|
25
26
|
'init-debug-css': './init-debug-css.js',
|