@nqlib/nqui 0.4.2 → 0.4.4
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 +104 -152
- 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-dEJ9aEk4.js → command-palette-DCtLpM3Q.js} +1 -1
- package/dist/{command-palette-BuYcxPCc.cjs → command-palette-MHc03bBf.cjs} +1 -1
- 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/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/index.d.ts +14 -13
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/ui/badge.d.ts +15 -4
- package/dist/components/ui/badge.d.ts.map +1 -1
- package/dist/components/ui/button.d.ts +37 -3
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/checkbox.d.ts +12 -1
- 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/pagination.d.ts +3 -2
- package/dist/components/ui/pagination.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-AjzBdMMz.js → debug-panel-CG-vAN0L.js} +3593 -3775
- package/dist/debug-panel-DHBfAc1V.cjs +75 -0
- package/dist/debug.cjs.js +1 -1
- package/dist/debug.es.js +1 -1
- package/dist/{drawer-pUXPg3lF.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/sonner.d.ts +1 -2
- package/dist/entries/sonner.d.ts.map +1 -1
- 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 +21 -47
- package/dist/nqui.es.js +2236 -2381
- 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 +183 -1
- package/docs/components/README.md +8 -7
- package/docs/components/nqui-badge.md +1 -0
- package/docs/components/nqui-button.md +3 -1
- package/docs/components/nqui-card.md +1 -0
- package/docs/components/nqui-carousel.md +6 -0
- package/docs/components/nqui-checkbox.md +15 -0
- 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-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-toaster.md +5 -1
- package/docs/nqui-skills/SKILL.md +8 -1
- package/docs/nqui-skills/design-system.md +16 -3
- package/package.json +1 -1
- package/scripts/build-styles.js +16 -0
- package/scripts/init-debug-css.js +4 -2
- package/scripts/verify-build.js +1 -1
- 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/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 -39
- package/dist/components/custom/enhanced-button.d.ts.map +0 -1
- package/dist/components/custom/enhanced-checkbox.d.ts +0 -39
- 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 -15
- package/dist/components/custom/enhanced-sonner.d.ts.map +0 -1
- package/dist/debug-panel-NaOmD68t.cjs +0 -171
- package/dist/drawer-Cqq0Ozb2.cjs +0 -1
- package/dist/sonner-BtzU00r3.js +0 -248
- package/dist/sonner-Dfk26eds.cjs +0 -54
|
@@ -1,73 +1,94 @@
|
|
|
1
1
|
# nqui Combobox
|
|
2
2
|
|
|
3
|
-
> **Searchable** select
|
|
3
|
+
> **Searchable** select (Base UI). User types to filter options. Single or multiple selection.
|
|
4
4
|
|
|
5
5
|
## When to Use
|
|
6
6
|
|
|
7
|
-
- **Selection:** Single or multiple
|
|
8
|
-
- **Key feature:**
|
|
9
|
-
- **Options:** Many
|
|
7
|
+
- **Selection:** Single (default) or multiple (`multiple` on root)
|
|
8
|
+
- **Key feature:** Filter list by typing in the input
|
|
9
|
+
- **Options:** Many rows; use **`items`** on `Combobox` + render prop on `ComboboxList` for built-in filtering
|
|
10
10
|
|
|
11
|
-
**Choose Combobox when:**
|
|
11
|
+
**Choose Combobox when:** Users need search. Use **Select** when a plain dropdown is enough.
|
|
12
12
|
|
|
13
13
|
## Import
|
|
14
14
|
|
|
15
15
|
```tsx
|
|
16
16
|
import {
|
|
17
|
-
Combobox,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
Combobox,
|
|
18
|
+
ComboboxInput,
|
|
19
|
+
ComboboxContent,
|
|
20
|
+
ComboboxList,
|
|
21
|
+
ComboboxItem,
|
|
22
|
+
ComboboxEmpty,
|
|
23
|
+
ComboboxGroup,
|
|
24
|
+
ComboboxLabel,
|
|
25
|
+
ComboboxSeparator,
|
|
26
|
+
ComboboxCollection,
|
|
27
|
+
ComboboxChips,
|
|
28
|
+
ComboboxChip,
|
|
29
|
+
ComboboxChipsInput,
|
|
30
|
+
ComboboxTrigger,
|
|
31
|
+
ComboboxValue,
|
|
32
|
+
ComboboxClear,
|
|
33
|
+
useComboboxAnchor,
|
|
21
34
|
} from "@nqlib/nqui"
|
|
22
35
|
```
|
|
23
36
|
|
|
24
|
-
##
|
|
37
|
+
## Single selection + type-to-filter (recommended)
|
|
38
|
+
|
|
39
|
+
Pass **`items`** to `Combobox` and use a **render function** as the only child of `ComboboxList`. Place **`ComboboxEmpty`** next to `ComboboxList` (both under `ComboboxContent`), not inside `ComboboxList` alongside the function.
|
|
25
40
|
|
|
26
41
|
```tsx
|
|
27
|
-
|
|
42
|
+
const fruits = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
|
|
43
|
+
|
|
44
|
+
<Combobox items={fruits}>
|
|
28
45
|
<ComboboxInput placeholder="Search..." />
|
|
29
46
|
<ComboboxContent>
|
|
47
|
+
<ComboboxEmpty>No results found.</ComboboxEmpty>
|
|
30
48
|
<ComboboxList>
|
|
31
|
-
{
|
|
32
|
-
<ComboboxItem key={item
|
|
33
|
-
|
|
49
|
+
{(item) => (
|
|
50
|
+
<ComboboxItem key={item} value={item}>
|
|
51
|
+
{item}
|
|
52
|
+
</ComboboxItem>
|
|
53
|
+
)}
|
|
34
54
|
</ComboboxList>
|
|
35
|
-
<ComboboxEmpty>No results</ComboboxEmpty>
|
|
36
55
|
</ComboboxContent>
|
|
37
56
|
</Combobox>
|
|
38
57
|
```
|
|
39
58
|
|
|
40
|
-
##
|
|
59
|
+
## Static list (no `items` filter)
|
|
41
60
|
|
|
42
|
-
|
|
43
|
-
<Combobox showClear ... />
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Multi-select chips
|
|
61
|
+
You can still render `ComboboxItem` children manually when you control filtering yourself.
|
|
47
62
|
|
|
48
63
|
```tsx
|
|
49
|
-
<Combobox
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
64
|
+
<Combobox>
|
|
65
|
+
<ComboboxInput placeholder="Search..." />
|
|
66
|
+
<ComboboxContent>
|
|
67
|
+
<ComboboxList>
|
|
68
|
+
<ComboboxItem value="a">A</ComboboxItem>
|
|
69
|
+
<ComboboxItem value="b">B</ComboboxItem>
|
|
70
|
+
</ComboboxList>
|
|
71
|
+
</ComboboxContent>
|
|
54
72
|
</Combobox>
|
|
55
73
|
```
|
|
56
74
|
|
|
57
|
-
##
|
|
75
|
+
## Clear button
|
|
58
76
|
|
|
59
77
|
```tsx
|
|
60
|
-
<
|
|
61
|
-
<ComboboxGroup>
|
|
62
|
-
<ComboboxLabel>Group A</ComboboxLabel>
|
|
63
|
-
<ComboboxItem value="a">A</ComboboxItem>
|
|
64
|
-
</ComboboxGroup>
|
|
65
|
-
<ComboboxSeparator />
|
|
66
|
-
...
|
|
67
|
-
</ComboboxCollection>
|
|
78
|
+
<ComboboxInput showClear placeholder="Search..." />
|
|
68
79
|
```
|
|
69
80
|
|
|
81
|
+
## Multiple selection
|
|
82
|
+
|
|
83
|
+
Use **`multiple`** on `Combobox` (see [Base UI Combobox](https://base-ui.com/react/components/combobox)). Combine with **`ComboboxChips`**, **`ComboboxChip`**, **`ComboboxChipsInput`** as needed for chip UI.
|
|
84
|
+
|
|
85
|
+
## Implementation
|
|
86
|
+
|
|
87
|
+
- **Source:** `packages/nqui/src/components/ui/combobox.tsx`
|
|
88
|
+
- **Public API:** exported from `@nqlib/nqui` (same as `CoreCombobox*` aliases in the barrel if you need the unprefixed base re-exports)
|
|
89
|
+
- **Styling:** Input group uses injected CSS once per page (`nqui-combobox-styles-v1`) for trigger depth/shadow; component is `"use client"`.
|
|
90
|
+
|
|
70
91
|
## Notes
|
|
71
92
|
|
|
72
|
-
-
|
|
73
|
-
-
|
|
93
|
+
- **`useComboboxAnchor`:** ref for anchoring `ComboboxContent` when using chips / custom layout.
|
|
94
|
+
- **Dropdown items:** spacing/hover treatment aligns with **Select** (`SelectItem`-style density).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nqui Drawer
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **Vaul** drawer. **DrawerContent** keeps an **inset rounded card** (`before:bg-card`, `before:inset-2`, `before:rounded-xl`) so the panel does not read as a full-bleed slab—surface follows **`card`** tokens in both themes.
|
|
4
4
|
|
|
5
5
|
## Import
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nqui ScrollArea
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Default export is **EnhancedScrollArea** (fade mask, orientation). Underlying primitive: **`CoreScrollArea`** / `ScrollBar` from **`ui/scroll-area`**. Core scrollbar uses a **thinner** track/thumb (drawer-like).
|
|
4
4
|
|
|
5
5
|
## Import
|
|
6
6
|
|
|
@@ -48,6 +48,6 @@ import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem, SelectGr
|
|
|
48
48
|
|
|
49
49
|
## Notes
|
|
50
50
|
|
|
51
|
-
-
|
|
51
|
+
- **Content:** FrostedGlass + popover surface; **items** use relaxed row spacing (hover `accent`, margins) for dropdown parity with **Combobox** list items.
|
|
52
52
|
- Use `SelectScrollUpButton` / `SelectScrollDownButton` for long lists.
|
|
53
|
-
-
|
|
53
|
+
- **`CoreSelect*`** for the same primitives without the enhanced trigger chrome (re-exported from the same `ui/select` module).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nqui Sheet
|
|
2
2
|
|
|
3
|
-
> Side panel.
|
|
3
|
+
> Side panel (Radix **Dialog** pattern). **SheetContent** uses an **inset card** panel: transparent outer shell + `before:bg-card` block inset with **rounded corners** (matches drawer-style “card floating in the viewport”). **No edge borders** on the sheet shell (divider lines removed in favor of the card shape).
|
|
4
4
|
|
|
5
5
|
## Import
|
|
6
6
|
|
|
@@ -14,3 +14,16 @@ import { Slider } from "@nqlib/nqui"
|
|
|
14
14
|
<Slider value={[50]} onValueChange={([v]) => setVal(v)} />
|
|
15
15
|
<Slider value={[20, 80]} onValueChange={setRange} />
|
|
16
16
|
```
|
|
17
|
+
|
|
18
|
+
## Sizes
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
<Slider size="sm" defaultValue={[40]} />
|
|
22
|
+
<Slider size="default" defaultValue={[40]} />
|
|
23
|
+
<Slider size="lg" defaultValue={[40]} />
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Notes
|
|
27
|
+
|
|
28
|
+
- **Thumb:** white, rounded-full, subtle shadow (aligned with **Switch** thumb language).
|
|
29
|
+
- **`size`:** `sm` | `default` | `lg` (control scale heights).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nqui Spinner
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Dual-arc loader (primary **`linearGradient`** strokes, rotating shell). Keyframes live in **`packages/nqui/src/index.css`** (`.nqui-spinner-*`).
|
|
4
4
|
|
|
5
5
|
## Import
|
|
6
6
|
|
|
@@ -14,3 +14,8 @@ import { Spinner } from "@nqlib/nqui"
|
|
|
14
14
|
<Spinner />
|
|
15
15
|
<Spinner className="size-6" />
|
|
16
16
|
```
|
|
17
|
+
|
|
18
|
+
## Notes
|
|
19
|
+
|
|
20
|
+
- Root is a `div` with `role="status"` and `aria-label="Loading"`.
|
|
21
|
+
- Strokes use `var(--primary)` and `color-mix(in oklch, var(--primary) …, white)` so light/dark themes stay on-brand.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nqui Switch
|
|
2
2
|
|
|
3
|
-
> Toggle switch.
|
|
3
|
+
> Toggle switch. **Radix** primitive with **size** variants and **capsule thumb** (white, shadow). Sizes align with control scale: `sm` / `default` / `lg`.
|
|
4
4
|
|
|
5
5
|
## Import
|
|
6
6
|
|
|
@@ -13,3 +13,25 @@ import { Switch } from "@nqlib/nqui"
|
|
|
13
13
|
```tsx
|
|
14
14
|
<Switch checked={checked} onCheckedChange={setChecked} />
|
|
15
15
|
```
|
|
16
|
+
|
|
17
|
+
## Sizes
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
<Switch size="sm" />
|
|
21
|
+
<Switch size="default" />
|
|
22
|
+
<Switch size="lg" />
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Hit area
|
|
26
|
+
|
|
27
|
+
The switch root has no `::before` styling, so you can add [hit-area utilities](https://bazza.dev/craft/2026/hit-area) on the same element:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
<Switch className="hit-area-4" id="notifications" />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Use sparingly in dense toolbars so expanded targets do not overlap.
|
|
34
|
+
|
|
35
|
+
## Notes
|
|
36
|
+
|
|
37
|
+
- Thumb travel and track sizes are fixed per `size`; overriding height on the root without matching thumb classes may look off.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nqui Toaster
|
|
2
2
|
|
|
3
|
-
> Toast notifications (sonner).
|
|
3
|
+
> Toast notifications via **Sonner**. Default toast is **pill-shaped**; default **normal** toast uses **inverted** surface (dark fill in light theme, light fill in dark theme) using `--foreground` / `--background`. Implemented in **`packages/nqui/src/components/ui/sonner.tsx`** (`Toaster`). Compatibility aliases: `EnhancedSonner`, `CoreToaster` → same component.
|
|
4
4
|
|
|
5
5
|
## Import
|
|
6
6
|
|
|
@@ -42,3 +42,7 @@ toast.promise(fetchData(), {
|
|
|
42
42
|
toast.success("Msg", { duration: 5000 })
|
|
43
43
|
// Toaster: position, richColors, etc.
|
|
44
44
|
```
|
|
45
|
+
|
|
46
|
+
## Notes
|
|
47
|
+
|
|
48
|
+
- **Subpath import:** `@nqlib/nqui/sonner` re-exports the same `Toaster`.
|
|
@@ -40,6 +40,8 @@ When designing app UI (toolbars, headers, inline controls):
|
|
|
40
40
|
|
|
41
41
|
## Design System Conventions
|
|
42
42
|
|
|
43
|
+
See **`design-system.md`** in this folder for sizing, grouped controls (including pill **ButtonGroup** / **ToggleGroup** shells), and file paths under `packages/nqui/src/components/ui/`.
|
|
44
|
+
|
|
43
45
|
### Control Sizing
|
|
44
46
|
- sm = h-6
|
|
45
47
|
- default = h-7
|
|
@@ -57,10 +59,15 @@ Always use CSS variables from elevation.css:
|
|
|
57
59
|
- `--z-tooltip` (70)
|
|
58
60
|
|
|
59
61
|
### Component Naming
|
|
60
|
-
-
|
|
62
|
+
- Default exports are the enhanced/polished variants; use **Core\*** for plain primitives
|
|
63
|
+
- Implementations are consolidated under **`ui/`** (not separate `custom/enhanced-*` per component for Button, Badge, Checkbox, Select, Combobox, Sonner)
|
|
61
64
|
- File names: kebab-case
|
|
62
65
|
- Component names: PascalCase
|
|
63
66
|
|
|
67
|
+
### Hit area (optional)
|
|
68
|
+
|
|
69
|
+
Library CSS includes [Bazza hit-area](https://bazza.dev/craft/2026/hit-area) utilities. For **Checkbox** / **Switch** in padded tables or lists, pass **`className="hit-area-6"`** (or `hit-area-4`, axis variants) on the **component root**, not on a wrapper-only parent. Opt-in only; use **`hit-area-debug`** while tuning. Details: `packages/nqui/docs/components/nqui-checkbox.md`, `nqui-switch.md`; examples: `ComponentShowcase` Checkbox + Switch sections.
|
|
70
|
+
|
|
64
71
|
## Key Dependencies
|
|
65
72
|
|
|
66
73
|
Required peer dependencies:
|
|
@@ -77,14 +77,15 @@ Font: `--font-sans` (Inter Variable). Leading: `leading-normal` or `text-xs/rela
|
|
|
77
77
|
1. **Use the scale** – If the component has a `size` prop, map `sm`→h-6, `default`→h-7, `lg`→h-8.
|
|
78
78
|
2. **Match padding** – Text controls: px-2–3, py-1.5. Icon-only: p-0 with explicit size.
|
|
79
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)`.
|
|
80
|
+
4. **Border radius** – Use `rounded-md` (default) or `rounded-[min(var(--radius-md),8px)]` for sm; **Button** uses full pill (`rounded-full`). For nested layouts, use `calc(outer - offset)`.
|
|
81
81
|
|
|
82
82
|
## Grouped Controls (ButtonGroup, ToggleGroup)
|
|
83
83
|
|
|
84
|
-
- **Shared border** – Container: `rounded-
|
|
84
|
+
- **Shared border** – Container: `rounded-full border border-input overflow-hidden` (pill outer edge)
|
|
85
85
|
- **Child borders** – Items: `border-0` (container provides the border)
|
|
86
86
|
- **Dividers** – ToggleGroup: item borders (`border-foreground/20`) between items. Or `ToggleGroupSeparator` when `separator={false}`.
|
|
87
87
|
- **Corners** – First item: rounded-l (or rounded-t vertical), last: rounded-r (or rounded-b)
|
|
88
|
+
- **Several groups on one horizontal row** – `ButtonGroup`’s container is **`inline-flex`**, so multiple sibling groups in normal flow behave like **inline boxes** and align to **baseline**. Mixed content (labels vs icons vs `ButtonGroupText`) then produces inconsistent vertical alignment. Prefer an explicit row container: **`flex flex-wrap items-center gap-*`**, or **grid** columns—so you choose **cross-axis alignment** (`items-center`, `items-end`, etc.) instead of inheriting baseline alignment from inline layout.
|
|
88
89
|
|
|
89
90
|
## Toggle & ToggleGroup Visual Treatment (Visibility on Any Background)
|
|
90
91
|
|
|
@@ -108,12 +109,24 @@ Never use flat `bg-muted` only for selected state; always add gradient + shadow
|
|
|
108
109
|
| Chart/settings panel | Label + inline controls, `rounded-lg border bg-muted/30 p-3` | Y-axis: Linear/Log; Size: S/M/L |
|
|
109
110
|
| Standalone | Inline with related UI, not floating alone | Pin, Mute, single Toggle |
|
|
110
111
|
|
|
112
|
+
## Bounded content (default UX)
|
|
113
|
+
|
|
114
|
+
- **Rule:** Interactive surfaces should not **visually bleed** outside their box: prefer **ellipsis** (`truncate`), **line-clamp**, **controlled scroll**, or **wrapping** (`break-words` / `whitespace-normal`) instead of letting text or icons paint past padding in narrow layouts.
|
|
115
|
+
- **Not universal:** Portals (dropdowns, popovers), **tooltips**, and **drag overlays** intentionally escape bounds. **Data tables** may use horizontal scroll. Choose per component.
|
|
116
|
+
- **Flex gotcha:** `inline-flex` + icon + `whitespace-nowrap` + raw **string** children keeps an implicit **min-width: min-content**; parents need **`min-w-0`** (and often **`max-w-full`**) on the control and a truncating inner wrapper for long labels.
|
|
117
|
+
- **Built-in helpers in nqui:** Shared `wrapInlineLabelTextNodes` + `min-w-0 max-w-full` on **Button**, **Toggle**, **TabsTrigger**, **Badge**, **ComboboxChip**; **SelectTrigger** uses **`min-w-0 max-w-full`** and **`min-w-0`** on the value slot with **`line-clamp-1`**. **Carousel** prev/next sit **inside** the carousel box so they don’t spill out of **Card** (override via `className` if you want outside arrows).
|
|
118
|
+
- **Card:** **Card** uses **`min-w-0`** on shell + header/content/footer for flex/grid safety but keeps **`overflow-visible`** so overlays aren’t clipped; don’t rely on **`overflow-hidden`** on Card as a global “catch-all” unless you accept clipping dropdowns/focus.
|
|
119
|
+
|
|
111
120
|
## Customization
|
|
112
121
|
|
|
113
122
|
- End users override via `className` or `size` when supported.
|
|
114
123
|
- Do NOT hardcode heights in consumer code when the component supports `size`.
|
|
115
124
|
- Prefer semantic sizes over pixel values in component defaults.
|
|
116
125
|
|
|
126
|
+
## Hit area utilities
|
|
127
|
+
|
|
128
|
+
**Hit area** expands the pointer target; it does **not** replace control **size** (sm / default / lg). Use **opt-in** on **Checkbox** / **Switch** for padded rows or cells—not as a global default. Avoid large overlapping hit areas in dense toolbars. See **`SKILL.md`** (Hit area) in this folder and `packages/nqui/docs/components/nqui-checkbox.md` / `nqui-switch.md`.
|
|
129
|
+
|
|
117
130
|
## Files to Check for Consistency
|
|
118
131
|
|
|
119
132
|
- `packages/nqui/src/components/ui/button.tsx`
|
|
@@ -121,7 +134,7 @@ Never use flat `bg-muted` only for selected state; always add gradient + shadow
|
|
|
121
134
|
- `packages/nqui/src/components/ui/toggle-group.tsx`
|
|
122
135
|
- `packages/nqui/src/components/ui/input.tsx`
|
|
123
136
|
- `packages/nqui/src/components/ui/select.tsx`
|
|
124
|
-
- `packages/nqui/src/components/
|
|
137
|
+
- `packages/nqui/src/components/ui/combobox.tsx`
|
|
125
138
|
|
|
126
139
|
## Anti-Patterns
|
|
127
140
|
|
package/package.json
CHANGED
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
|
|
@@ -114,8 +114,10 @@ function main() {
|
|
|
114
114
|
console.log(' 1. Import the CSS in your app entry point:');
|
|
115
115
|
console.log(` import './${finalTargetPath.replace(process.cwd() + '/', '')}'`);
|
|
116
116
|
console.log(' 2. Use DebugPanel in your app:');
|
|
117
|
-
console.log(' import { DebugPanel } from "nqui"');
|
|
118
|
-
console.log('
|
|
117
|
+
console.log(' import { DebugPanel } from "@nqlib/nqui"');
|
|
118
|
+
console.log(' // Or tree-shake debug out of production: import { DebugPanel } from "@nqlib/nqui/debug"');
|
|
119
|
+
console.log(' 3. Add <DebugPanel /> to your root layout (panel is inactive until the user opens it).');
|
|
120
|
+
console.log(' Wrapping in process.env.NODE_ENV or import.meta.env.DEV is optional.');
|
|
119
121
|
console.log('\n✨ Done!');
|
|
120
122
|
}
|
|
121
123
|
|
package/scripts/verify-build.js
CHANGED
|
@@ -284,7 +284,7 @@ function main() {
|
|
|
284
284
|
{ name: '@source inline() directives', pattern: /@source\s+inline\(/g },
|
|
285
285
|
{ name: ':root block', pattern: /:root\s*\{/ },
|
|
286
286
|
{ name: '.dark block', pattern: /\.dark\s*\{/ },
|
|
287
|
-
|
|
287
|
+
// Viewport lock (html, body, #root) was intentionally removed from base styles (see CHANGELOG 0.3.3); use AppLayout for opt-in lock.
|
|
288
288
|
];
|
|
289
289
|
|
|
290
290
|
criticalPatterns.forEach(({ name, pattern }) => {
|
package/dist/button-CYFTFDKe.cjs
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"use strict";const u=require("react/jsx-runtime"),d=require("react"),l=require("@radix-ui/react-slot"),f=require("class-variance-authority"),g=require("./utils-IjLH3w2e.cjs");function b(e){const r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const t in e)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,n.get?n:{enumerable:!0,get:()=>e[t]})}}return r.default=e,Object.freeze(r)}const v=b(d),o=f.cva("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-destructive-foreground hover:bg-destructive/90",outline:"border border-input bg-background hover:bg-accent hover:text-accent-foreground",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-7 min-w-7 px-3",sm:"h-6 min-w-6 rounded-[min(var(--radius-md),8px)] px-2 text-xs",lg:"h-8 min-w-8 px-4",icon:"h-7 w-7 p-0"}},defaultVariants:{variant:"default",size:"default"}}),i=v.forwardRef(({className:e,variant:r,size:t,asChild:n=!1,...s},a)=>{const c=n?l.Slot:"button";return u.jsx(c,{className:g.cn(o({variant:r,size:t,className:e})),ref:a,...s})});i.displayName="Button";exports.Button=i;exports.buttonVariants=o;
|
package/dist/button-nJvDl3w8.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { jsx as s } from "react/jsx-runtime";
|
|
2
|
-
import * as a from "react";
|
|
3
|
-
import { Slot as d } from "@radix-ui/react-slot";
|
|
4
|
-
import { cva as c } from "class-variance-authority";
|
|
5
|
-
import { c as u } from "./utils-B6yFEsav.js";
|
|
6
|
-
const f = c(
|
|
7
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
8
|
-
{
|
|
9
|
-
variants: {
|
|
10
|
-
variant: {
|
|
11
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
-
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
13
|
-
outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
|
14
|
-
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
15
|
-
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
16
|
-
link: "text-primary underline-offset-4 hover:underline"
|
|
17
|
-
},
|
|
18
|
-
size: {
|
|
19
|
-
default: "h-7 min-w-7 px-3",
|
|
20
|
-
sm: "h-6 min-w-6 rounded-[min(var(--radius-md),8px)] px-2 text-xs",
|
|
21
|
-
lg: "h-8 min-w-8 px-4",
|
|
22
|
-
icon: "h-7 w-7 p-0"
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
defaultVariants: {
|
|
26
|
-
variant: "default",
|
|
27
|
-
size: "default"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
), m = a.forwardRef(
|
|
31
|
-
({ className: e, variant: r, size: t, asChild: o = !1, ...n }, i) => /* @__PURE__ */ s(
|
|
32
|
-
o ? d : "button",
|
|
33
|
-
{
|
|
34
|
-
className: u(f({ variant: r, size: t, className: e })),
|
|
35
|
-
ref: i,
|
|
36
|
-
...n
|
|
37
|
-
}
|
|
38
|
-
)
|
|
39
|
-
);
|
|
40
|
-
m.displayName = "Button";
|
|
41
|
-
export {
|
|
42
|
-
m as B,
|
|
43
|
-
f as b
|
|
44
|
-
};
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { jsx as a, jsxs as p } from "react/jsx-runtime";
|
|
2
|
-
import * as l from "react";
|
|
3
|
-
import z from "embla-carousel-react";
|
|
4
|
-
import { K as C } from "./keyboard-pkY42Y3a.js";
|
|
5
|
-
import { c as d } from "./utils-B6yFEsav.js";
|
|
6
|
-
import { B as v } from "./button-nJvDl3w8.js";
|
|
7
|
-
import { HugeiconsIcon as N } from "@hugeicons/react";
|
|
8
|
-
import { ArrowLeft01Icon as I, ArrowRight01Icon as R } from "@hugeicons/core-free-icons";
|
|
9
|
-
const b = l.createContext(null);
|
|
10
|
-
function m() {
|
|
11
|
-
const e = l.useContext(b);
|
|
12
|
-
if (!e)
|
|
13
|
-
throw new Error("useCarousel must be used within a <Carousel />");
|
|
14
|
-
return e;
|
|
15
|
-
}
|
|
16
|
-
function W({
|
|
17
|
-
orientation: e = "horizontal",
|
|
18
|
-
opts: t,
|
|
19
|
-
setApi: r,
|
|
20
|
-
plugins: s,
|
|
21
|
-
className: c,
|
|
22
|
-
children: i,
|
|
23
|
-
...u
|
|
24
|
-
}) {
|
|
25
|
-
const [w, o] = z(
|
|
26
|
-
{
|
|
27
|
-
...t,
|
|
28
|
-
axis: e === "horizontal" ? "x" : "y"
|
|
29
|
-
},
|
|
30
|
-
s
|
|
31
|
-
), [k, y] = l.useState(!1), [S, P] = l.useState(!1), f = l.useCallback((n) => {
|
|
32
|
-
n && (y(n.canScrollPrev()), P(n.canScrollNext()));
|
|
33
|
-
}, []), h = l.useCallback(() => {
|
|
34
|
-
o?.scrollPrev();
|
|
35
|
-
}, [o]), x = l.useCallback(() => {
|
|
36
|
-
o?.scrollNext();
|
|
37
|
-
}, [o]), g = l.useCallback(
|
|
38
|
-
(n) => {
|
|
39
|
-
n.key === C.ArrowLeft ? (n.preventDefault(), h()) : n.key === C.ArrowRight && (n.preventDefault(), x());
|
|
40
|
-
},
|
|
41
|
-
[h, x]
|
|
42
|
-
);
|
|
43
|
-
return l.useEffect(() => {
|
|
44
|
-
!o || !r || r(o);
|
|
45
|
-
}, [o, r]), l.useEffect(() => {
|
|
46
|
-
if (o)
|
|
47
|
-
return f(o), o.on("reInit", f), o.on("select", f), () => {
|
|
48
|
-
o?.off("select", f);
|
|
49
|
-
};
|
|
50
|
-
}, [o, f]), /* @__PURE__ */ a(
|
|
51
|
-
b.Provider,
|
|
52
|
-
{
|
|
53
|
-
value: {
|
|
54
|
-
carouselRef: w,
|
|
55
|
-
api: o,
|
|
56
|
-
opts: t,
|
|
57
|
-
orientation: e || (t?.axis === "y" ? "vertical" : "horizontal"),
|
|
58
|
-
scrollPrev: h,
|
|
59
|
-
scrollNext: x,
|
|
60
|
-
canScrollPrev: k,
|
|
61
|
-
canScrollNext: S
|
|
62
|
-
},
|
|
63
|
-
children: /* @__PURE__ */ a(
|
|
64
|
-
"div",
|
|
65
|
-
{
|
|
66
|
-
onKeyDownCapture: g,
|
|
67
|
-
className: d("relative", c),
|
|
68
|
-
role: "region",
|
|
69
|
-
"aria-roledescription": "carousel",
|
|
70
|
-
"data-slot": "carousel",
|
|
71
|
-
...u,
|
|
72
|
-
children: i
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
function H({ className: e, ...t }) {
|
|
79
|
-
const { carouselRef: r, orientation: s } = m();
|
|
80
|
-
return /* @__PURE__ */ a(
|
|
81
|
-
"div",
|
|
82
|
-
{
|
|
83
|
-
ref: r,
|
|
84
|
-
className: "overflow-hidden",
|
|
85
|
-
"data-slot": "carousel-content",
|
|
86
|
-
children: /* @__PURE__ */ a(
|
|
87
|
-
"div",
|
|
88
|
-
{
|
|
89
|
-
className: d(
|
|
90
|
-
"flex",
|
|
91
|
-
s === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
|
92
|
-
e
|
|
93
|
-
),
|
|
94
|
-
...t
|
|
95
|
-
}
|
|
96
|
-
)
|
|
97
|
-
}
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
function q({ className: e, ...t }) {
|
|
101
|
-
const { orientation: r } = m();
|
|
102
|
-
return /* @__PURE__ */ a(
|
|
103
|
-
"div",
|
|
104
|
-
{
|
|
105
|
-
role: "group",
|
|
106
|
-
"aria-roledescription": "slide",
|
|
107
|
-
"data-slot": "carousel-item",
|
|
108
|
-
className: d(
|
|
109
|
-
"min-w-0 shrink-0 grow-0 basis-full",
|
|
110
|
-
r === "horizontal" ? "pl-4" : "pt-4",
|
|
111
|
-
e
|
|
112
|
-
),
|
|
113
|
-
...t
|
|
114
|
-
}
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
function F({
|
|
118
|
-
className: e,
|
|
119
|
-
variant: t = "outline",
|
|
120
|
-
size: r = "icon",
|
|
121
|
-
...s
|
|
122
|
-
}) {
|
|
123
|
-
const { orientation: c, scrollPrev: i, canScrollPrev: u } = m();
|
|
124
|
-
return /* @__PURE__ */ p(
|
|
125
|
-
v,
|
|
126
|
-
{
|
|
127
|
-
"data-slot": "carousel-previous",
|
|
128
|
-
variant: t,
|
|
129
|
-
size: r,
|
|
130
|
-
className: d(
|
|
131
|
-
"rounded-full absolute touch-manipulation",
|
|
132
|
-
c === "horizontal" ? "top-1/2 -left-12 -translate-y-1/2" : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
133
|
-
e
|
|
134
|
-
),
|
|
135
|
-
disabled: !u,
|
|
136
|
-
onClick: i,
|
|
137
|
-
...s,
|
|
138
|
-
children: [
|
|
139
|
-
/* @__PURE__ */ a(N, { icon: I, strokeWidth: 2 }),
|
|
140
|
-
/* @__PURE__ */ a("span", { className: "sr-only", children: "Previous slide" })
|
|
141
|
-
]
|
|
142
|
-
}
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
function G({
|
|
146
|
-
className: e,
|
|
147
|
-
variant: t = "outline",
|
|
148
|
-
size: r = "icon",
|
|
149
|
-
...s
|
|
150
|
-
}) {
|
|
151
|
-
const { orientation: c, scrollNext: i, canScrollNext: u } = m();
|
|
152
|
-
return /* @__PURE__ */ p(
|
|
153
|
-
v,
|
|
154
|
-
{
|
|
155
|
-
"data-slot": "carousel-next",
|
|
156
|
-
variant: t,
|
|
157
|
-
size: r,
|
|
158
|
-
className: d(
|
|
159
|
-
"rounded-full absolute touch-manipulation",
|
|
160
|
-
c === "horizontal" ? "top-1/2 -right-12 -translate-y-1/2" : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
|
161
|
-
e
|
|
162
|
-
),
|
|
163
|
-
disabled: !u,
|
|
164
|
-
onClick: i,
|
|
165
|
-
...s,
|
|
166
|
-
children: [
|
|
167
|
-
/* @__PURE__ */ a(N, { icon: R, strokeWidth: 2 }),
|
|
168
|
-
/* @__PURE__ */ a("span", { className: "sr-only", children: "Next slide" })
|
|
169
|
-
]
|
|
170
|
-
}
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
export {
|
|
174
|
-
W as C,
|
|
175
|
-
H as a,
|
|
176
|
-
q as b,
|
|
177
|
-
F as c,
|
|
178
|
-
G as d
|
|
179
|
-
};
|