@nqlib/nqui 0.4.8 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/{button-CJHdCq9I.js → button-BrroJ39H.js} +35 -34
  2. package/dist/button-CrLihxcE.cjs +1 -0
  3. package/dist/calendar.cjs.js +1 -1
  4. package/dist/calendar.es.js +1 -1
  5. package/dist/{carousel-U7RZhYZj.js → carousel-C976t5jo.js} +1 -1
  6. package/dist/{carousel-D1FMVglR.cjs → carousel-VldrniuT.cjs} +1 -1
  7. package/dist/carousel.cjs.js +1 -1
  8. package/dist/carousel.es.js +1 -1
  9. package/dist/{command-palette-D_SFxXyQ.js → command-palette-2V1VSlOM.js} +1 -1
  10. package/dist/{command-palette-DSQYbRiH.cjs → command-palette-4Sv4QjZ2.cjs} +1 -1
  11. package/dist/command.cjs.js +1 -1
  12. package/dist/command.es.js +1 -1
  13. package/dist/components/custom/enhanced-tabs.d.ts +1 -0
  14. package/dist/components/custom/enhanced-tabs.d.ts.map +1 -1
  15. package/dist/components/ui/button.d.ts +4 -0
  16. package/dist/components/ui/button.d.ts.map +1 -1
  17. package/dist/components/ui/card.d.ts +1 -0
  18. package/dist/components/ui/card.d.ts.map +1 -1
  19. package/dist/components/ui/sonner.d.ts.map +1 -1
  20. package/dist/components/ui/tabs.d.ts +6 -4
  21. package/dist/components/ui/tabs.d.ts.map +1 -1
  22. package/dist/{debug-panel-CS_wuYVH.js → debug-panel-BnZDhlaR.js} +1621 -1749
  23. package/dist/{debug-panel-nDTrIh9s.cjs → debug-panel-R2ZLkucO.cjs} +10 -10
  24. package/dist/debug.cjs.js +1 -1
  25. package/dist/debug.es.js +1 -1
  26. package/dist/{enhanced-calendar-C7EQIr6i.cjs → enhanced-calendar-BEIfybRx.cjs} +1 -1
  27. package/dist/{enhanced-calendar-BGlsSYJd.js → enhanced-calendar-Cj-4xhqf.js} +1 -1
  28. package/dist/hooks/use-resolved-theme.d.ts +2 -4
  29. package/dist/hooks/use-resolved-theme.d.ts.map +1 -1
  30. package/dist/nqui.cjs.js +3 -23
  31. package/dist/nqui.es.js +605 -627
  32. package/dist/{sonner-CpmECDBk.js → sonner-C1ndgVQY.js} +24 -24
  33. package/dist/sonner-CP8np0BP.cjs +48 -0
  34. package/dist/sonner.cjs.js +1 -1
  35. package/dist/sonner.es.js +1 -1
  36. package/dist/styles.css +79 -84
  37. package/docs/components/README.md +11 -2
  38. package/docs/components/nqui-scroll-area.md +74 -0
  39. package/docs/components/nqui-tooltip.md +17 -2
  40. package/docs/nqui-skills/COMPONENTS_INDEX.md +48 -0
  41. package/docs/nqui-skills/HUMAN_GUIDE.md +21 -0
  42. package/docs/nqui-skills/README.md +20 -0
  43. package/docs/nqui-skills/SKILL.md +36 -89
  44. package/docs/nqui-skills/nqui-bundle-size-best-practices/SKILL.md +78 -0
  45. package/docs/nqui-skills/nqui-components/SKILL.md +101 -0
  46. package/docs/nqui-skills/{design-system.md → nqui-design-system/SKILL.md} +53 -25
  47. package/docs/nqui-skills/nqui-install/SKILL.md +65 -0
  48. package/docs/nqui-skills/nqui-local-published-toggle/SKILL.md +132 -0
  49. package/docs/nqui-skills/nqui-local-published-toggle/scripts/toggle-nqui.js +203 -0
  50. package/docs/nqui-skills/nqui-shadcn/SKILL.md +164 -0
  51. package/docs/nqui-skills/{rules → nqui-shadcn/rules}/forms.md +3 -3
  52. package/docs/nqui-skills/{rules → nqui-shadcn/rules}/styling.md +1 -1
  53. package/package.json +1 -1
  54. package/scripts/download-skills.js +28 -8
  55. package/scripts/examples/nextjs-layout-sidebar.tsx +3 -2
  56. package/scripts/examples/nextjs-layout.tsx +3 -2
  57. package/scripts/examples/vite-main.tsx +7 -1
  58. package/scripts/init-cursor.js +4 -3
  59. package/scripts/skill-templates.js +16 -6
  60. package/dist/button-R304rhsj.cjs +0 -1
  61. package/dist/sonner-nE9hIalJ.cjs +0 -48
  62. /package/docs/nqui-skills/{rules → nqui-shadcn/rules}/composition.md +0 -0
  63. /package/docs/nqui-skills/{rules → nqui-shadcn/rules}/icons.md +0 -0
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: nqui-local-published-toggle
3
+ description: Enables switching between local (development) and published (npm) versions of @nqlib/nqui in consumer projects. Use when adding nqui to Vite, Next.js, or TypeScript projects; when user asks to "toggle nqui", "use local nqui", "npm link nqui", or "switch between local and published" nqui.
4
+ ---
5
+
6
+ # nqui Local/Published Toggle
7
+
8
+ Enables any consumer project (Vite, Next.js, TypeScript) to switch between local nqui development and the published npm package via `npm link` and env-driven scripts.
9
+
10
+ ## Environment Variables
11
+
12
+ | Variable | Purpose |
13
+ |----------|---------|
14
+ | `USE_LOCAL_NQUI=true` | Use local nqui via `npm link` |
15
+ | `USE_LOCAL_NQUI=false` | Use published `@nqlib/nqui` from registry |
16
+ | `NQUI_DIR` | Path to nqui **repo root** (default: sibling `../nqui` or customize in script) |
17
+ | `SKIP_BUILD=true` | Skip `npm run build:lib` when already linked (local only) |
18
+
19
+ ## Setup Steps
20
+
21
+ ### 1. Add the script
22
+
23
+ Copy [scripts/toggle-nqui.js](./scripts/toggle-nqui.js) into the consumer project's `scripts/` directory. Customize in the script:
24
+
25
+ - `root` – Resolved from `__dirname` (parent of `scripts/`)
26
+ - `nquiBaseDir` – Default `NQUI_DIR`; use `resolve(root, '..', 'nqui')` or absolute path
27
+ - `PUBLISHED_VERSION` – Semver for published mode (e.g. `^0.5.0`)
28
+
29
+ ### 2. Add package.json scripts
30
+
31
+ Replace `<framework-dev-command>` with the appropriate command:
32
+
33
+ ```json
34
+ {
35
+ "scripts": {
36
+ "dev": "node scripts/toggle-nqui.js && <framework-dev-command>",
37
+ "dev:local": "USE_LOCAL_NQUI=true node scripts/toggle-nqui.js && <framework-dev-command>",
38
+ "dev:local:fast": "USE_LOCAL_NQUI=true SKIP_BUILD=true node scripts/toggle-nqui.js && <framework-dev-command>",
39
+ "dev:published": "USE_LOCAL_NQUI=false node scripts/toggle-nqui.js && <framework-dev-command>",
40
+ "toggle-nqui": "node scripts/toggle-nqui.js",
41
+ "nqui:status": "node scripts/toggle-nqui.js --check"
42
+ }
43
+ }
44
+ ```
45
+
46
+ ### 3. Framework dev commands
47
+
48
+ | Framework | `<framework-dev-command>` |
49
+ |-----------|--------------------------|
50
+ | Vite | `vite` |
51
+ | Next.js | `next dev` or `next dev --webpack` (see Next.js note) |
52
+ | Create React App | `react-scripts start` |
53
+
54
+ ### 4. Ensure dependency
55
+
56
+ `@nqlib/nqui` must be in `dependencies` with semver:
57
+
58
+ ```json
59
+ {
60
+ "dependencies": {
61
+ "@nqlib/nqui": "^0.5.0"
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Framework-Specific Notes
67
+
68
+ ### Vite
69
+
70
+ Works out of the box. No special config.
71
+
72
+ ### Next.js
73
+
74
+ Use `next dev --webpack` when running with local nqui. Turbopack (Next.js 16+ default) has limited symlink support.
75
+
76
+ Options:
77
+
78
+ - Set `"dev": "next dev --webpack"` if you always want Webpack for dev
79
+ - Or add `"dev:webpack": "next dev --webpack"` and use it in `dev:local` / `dev:local:fast`:
80
+
81
+ ```json
82
+ "dev:local": "USE_LOCAL_NQUI=true node scripts/toggle-nqui.js && next dev --webpack",
83
+ "dev:local:fast": "USE_LOCAL_NQUI=true SKIP_BUILD=true node scripts/toggle-nqui.js && next dev --webpack",
84
+ "dev:published": "USE_LOCAL_NQUI=false node scripts/toggle-nqui.js && next dev"
85
+ ```
86
+
87
+ ### Monorepos
88
+
89
+ Each app that uses nqui needs its own `scripts/toggle-nqui.js` and package.json scripts. Root can proxy:
90
+
91
+ ```json
92
+ "dev:admin:local": "USE_LOCAL_NQUI=true npm --prefix client-saas run dev",
93
+ "dev:admin:local:fast": "USE_LOCAL_NQUI=true SKIP_BUILD=true npm --prefix client-saas run dev",
94
+ "dev:admin:published": "USE_LOCAL_NQUI=false npm --prefix client-saas run dev",
95
+ "nqui:status": "npm --prefix client-saas run nqui:status"
96
+ ```
97
+
98
+ ## How It Works
99
+
100
+ **Local mode** (`USE_LOCAL_NQUI=true`):
101
+
102
+ 1. Verify nqui exists at `NQUI_DIR/packages/nqui` (layout of the nqui **repository**)
103
+ 2. Run `npm run build:lib` in that package (unless `SKIP_BUILD=true` and already linked)
104
+ 3. Run `npm link` in the nqui package directory
105
+ 4. Run `npm unlink @nqlib/nqui` (if needed), then `npm link @nqlib/nqui` in consumer
106
+ 5. Update consumer `package.json` with `^${nqui.version}`
107
+
108
+ **Published mode** (`USE_LOCAL_NQUI=false`):
109
+
110
+ 1. Run `npm unlink @nqlib/nqui`
111
+ 2. Set `package.json` to `"@nqlib/nqui": "^x.y.z"`
112
+ 3. Run `npm install @nqlib/nqui@^x.y.z` (add `--legacy-peer-deps` if needed)
113
+
114
+ ## Status Check
115
+
116
+ ```bash
117
+ npm run nqui:status
118
+ # or
119
+ node scripts/toggle-nqui.js --check
120
+ ```
121
+
122
+ Shows: Source (LOCAL/PUBLISHED), Version, Symlink status, Location.
123
+
124
+ ## Troubleshooting
125
+
126
+ | Issue | Solution |
127
+ |-------|----------|
128
+ | Next.js 16+ Turbopack symlink issues | Use `next dev --webpack` for dev when using local nqui |
129
+ | pnpm | Script uses `npm link` for consistency; `npm link` works in pnpm projects |
130
+ | Version mismatch | Update `PUBLISHED_VERSION` in script to match latest @nqlib/nqui release |
131
+ | Peer dependency conflicts | Add `NPM_CONFIG_LEGACY_PEER_DEPS=true` to link env, or `--legacy-peer-deps` to install |
132
+ | nqui dir not found | Set `NQUI_DIR` to absolute path of nqui **repository** root |
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Toggle between local (npm link) and published @nqlib/nqui.
4
+ *
5
+ * CUSTOMIZE before copying to consumer project:
6
+ * - PROJECT_NAME: Used in log messages (e.g. "client-saas", "my-app")
7
+ * - PUBLISHED_VERSION: Semver for published mode (e.g. "^0.5.0")
8
+ * - USE_LEGACY_PEER_DEPS: true if consumer uses --legacy-peer-deps
9
+ * - NQUI_DIR: nqui repository root (monorepo with packages/nqui, or standalone nqui repo)
10
+ */
11
+ import { readFileSync, writeFileSync, existsSync, lstatSync, realpathSync } from "fs";
12
+ import { execSync } from "child_process";
13
+ import { fileURLToPath } from "url";
14
+ import { dirname, join, resolve } from "path";
15
+
16
+ const __filename = fileURLToPath(import.meta.url);
17
+ const __dirname = dirname(__filename);
18
+ /** Consumer project root when this file lives at `<project>/scripts/toggle-nqui.js` */
19
+ const root = resolve(__dirname, "..");
20
+ const packageJsonPath = join(root, "package.json");
21
+ const nodeModulesNqui = join(root, "node_modules", "@nqlib", "nqui");
22
+
23
+ // --- CUSTOMIZE ---
24
+ const PROJECT_NAME = "this project"; // e.g. "client-saas", "my-vite-app"
25
+ const PUBLISHED_VERSION = "^0.5.0"; // Match latest @nqlib/nqui release
26
+ const USE_LEGACY_PEER_DEPS = false; // Set true if project uses --legacy-peer-deps
27
+ // --- END CUSTOMIZE ---
28
+
29
+ const nquiBaseDir = process.env.NQUI_DIR || resolve(root, "..", "nqui");
30
+
31
+ /** Monorepo: <repo>/packages/nqui. Standalone nqui repo: <repo> is the package root. */
32
+ const monorepoNquiDir = join(nquiBaseDir, "packages", "nqui");
33
+ const nquiDir = existsSync(join(monorepoNquiDir, "package.json"))
34
+ ? monorepoNquiDir
35
+ : nquiBaseDir;
36
+
37
+ const useLocal = process.env.USE_LOCAL_NQUI === "true";
38
+ const skipBuild = process.env.SKIP_BUILD === "true";
39
+ const checkOnly = process.argv.includes("--check") || process.argv.includes("--status");
40
+
41
+ function isSymlink(path) {
42
+ try {
43
+ return lstatSync(path).isSymbolicLink();
44
+ } catch {
45
+ return false;
46
+ }
47
+ }
48
+
49
+ function checkStatus() {
50
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
51
+ const isLinked = existsSync(nodeModulesNqui) && isSymlink(nodeModulesNqui);
52
+
53
+ let version = "unknown";
54
+ let source = "unknown";
55
+
56
+ if (isLinked) {
57
+ try {
58
+ const resolvedPath = realpathSync(nodeModulesNqui);
59
+ if (resolvedPath.includes(nquiDir) || resolvedPath.includes("nqui")) {
60
+ source = "LOCAL (linked)";
61
+ try {
62
+ const nquiPackageJson = JSON.parse(readFileSync(join(nquiDir, "package.json"), "utf-8"));
63
+ version = nquiPackageJson.version;
64
+ } catch {
65
+ version = "unknown";
66
+ }
67
+ } else {
68
+ source = `LINKED (${resolvedPath})`;
69
+ try {
70
+ const linkedPackageJson = JSON.parse(readFileSync(join(resolvedPath, "package.json"), "utf-8"));
71
+ version = linkedPackageJson.version;
72
+ } catch {
73
+ version = "unknown";
74
+ }
75
+ }
76
+ } catch {
77
+ source = "LOCAL (linked)";
78
+ }
79
+ } else if (existsSync(nodeModulesNqui)) {
80
+ source = "PUBLISHED (npm package)";
81
+ try {
82
+ const installedPackageJson = JSON.parse(readFileSync(join(nodeModulesNqui, "package.json"), "utf-8"));
83
+ version = installedPackageJson.version;
84
+ } catch {
85
+ version = packageJson.dependencies["@nqlib/nqui"] || "unknown";
86
+ }
87
+ } else {
88
+ source = "NOT INSTALLED";
89
+ }
90
+
91
+ return { isLinked, version, source, packageVersion: packageJson.dependencies["@nqlib/nqui"] };
92
+ }
93
+
94
+ if (checkOnly) {
95
+ const status = checkStatus();
96
+ console.log("\nnqui Status:");
97
+ console.log(" Source:", status.source);
98
+ console.log(" Version:", status.version);
99
+ console.log(" Package.json:", status.packageVersion);
100
+ console.log(" Symlink:", status.isLinked ? "Yes" : "No");
101
+ console.log(" Location:", existsSync(nodeModulesNqui) ? nodeModulesNqui : "Not found", "\n");
102
+ process.exit(0);
103
+ }
104
+
105
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
106
+
107
+ console.log("\nToggling nqui source...");
108
+ console.log(" NQUI_DIR (repo root):", nquiBaseDir);
109
+ console.log(" nqui package:", nquiDir);
110
+ console.log(" USE_LOCAL_NQUI:", useLocal);
111
+ console.log(" SKIP_BUILD:", skipBuild, "\n");
112
+
113
+ if (useLocal) {
114
+ if (!existsSync(nquiDir)) {
115
+ console.error("Error: nqui directory not found at", nquiDir);
116
+ console.error(" Set NQUI_DIR to the nqui repository root (monorepo or standalone).");
117
+ process.exit(1);
118
+ }
119
+
120
+ const nquiPackageJsonPath = join(nquiDir, "package.json");
121
+ if (!existsSync(nquiPackageJsonPath)) {
122
+ console.error("Error: package.json not found in", nquiDir);
123
+ process.exit(1);
124
+ }
125
+
126
+ const currentStatus = checkStatus();
127
+ const alreadyLinked = currentStatus.isLinked && currentStatus.source.includes("LOCAL");
128
+
129
+ if (skipBuild && alreadyLinked) {
130
+ console.log("Skipping build (SKIP_BUILD=true and already linked)\n");
131
+ } else {
132
+ console.log("Building nqui library...");
133
+ try {
134
+ execSync("npm run build:lib", { cwd: nquiDir, stdio: "inherit" });
135
+ console.log("nqui library built successfully\n");
136
+ } catch (error) {
137
+ console.error("Failed to build nqui library");
138
+ process.exit(1);
139
+ }
140
+ }
141
+
142
+ console.log("Linking nqui library globally...");
143
+ try {
144
+ execSync("npm link", { cwd: nquiDir, stdio: "inherit" });
145
+ console.log("nqui linked globally\n");
146
+ } catch (error) {
147
+ console.error("Failed to link nqui globally");
148
+ process.exit(1);
149
+ }
150
+
151
+ const nquiPackageJson = JSON.parse(readFileSync(nquiPackageJsonPath, "utf-8"));
152
+ packageJson.dependencies["@nqlib/nqui"] = `^${nquiPackageJson.version}`;
153
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
154
+ console.log("Updated package.json (version: ^" + nquiPackageJson.version + ")");
155
+
156
+ console.log("Linking @nqlib/nqui in " + PROJECT_NAME + "...");
157
+ try {
158
+ try {
159
+ execSync("npm unlink @nqlib/nqui", { cwd: root, stdio: "pipe" });
160
+ } catch {}
161
+
162
+ const linkEnv = USE_LEGACY_PEER_DEPS ? { ...process.env, NPM_CONFIG_LEGACY_PEER_DEPS: "true" } : process.env;
163
+ execSync("npm link @nqlib/nqui", { cwd: root, stdio: "inherit", env: linkEnv });
164
+ console.log("Linked @nqlib/nqui in " + PROJECT_NAME + "\n");
165
+ } catch (error) {
166
+ console.error("Failed to link @nqlib/nqui");
167
+ process.exit(1);
168
+ }
169
+
170
+ const finalStatus = checkStatus();
171
+ console.log("Successfully switched to LOCAL nqui");
172
+ console.log(" Using:", nquiDir);
173
+ console.log(" Version:", finalStatus.version);
174
+ console.log(" Symlink:", finalStatus.isLinked ? "Active" : "Not active", "\n");
175
+ } else {
176
+ console.log("Unlinking local nqui...");
177
+ try {
178
+ execSync("npm unlink @nqlib/nqui", { cwd: root, stdio: "pipe" });
179
+ console.log("Unlinked local nqui\n");
180
+ } catch {
181
+ console.log("No local link to remove\n");
182
+ }
183
+
184
+ packageJson.dependencies["@nqlib/nqui"] = PUBLISHED_VERSION;
185
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
186
+ console.log("Updated package.json to published version (" + PUBLISHED_VERSION + ")");
187
+
188
+ console.log("Installing published @nqlib/nqui...");
189
+ const installCmd = USE_LEGACY_PEER_DEPS
190
+ ? `npm install @nqlib/nqui@${PUBLISHED_VERSION} --legacy-peer-deps`
191
+ : `npm install @nqlib/nqui@${PUBLISHED_VERSION}`;
192
+ try {
193
+ execSync(installCmd, { cwd: root, stdio: "inherit" });
194
+ console.log("Installed published @nqlib/nqui\n");
195
+ } catch (error) {
196
+ console.error("Failed to install published version. Run npm install manually.");
197
+ }
198
+
199
+ const finalStatus = checkStatus();
200
+ console.log("Successfully switched to PUBLISHED nqui");
201
+ console.log(" Version:", finalStatus.version);
202
+ console.log(" Symlink:", finalStatus.isLinked ? "Still linked (run npm install)" : "Removed", "\n");
203
+ }
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: nqui-shadcn
3
+ description: Manages nqui components and projects — adding, searching, fixing, debugging, styling, and composing UI. nqui is a shadcn-inspired component library with enhanced components. Applies when working with @nqlib/nqui, component imports, or designing app UI with nqui components.
4
+ user-invocable: false
5
+ ---
6
+
7
+ # nqui
8
+
9
+ A shadcn-inspired React component library built on Radix UI primitives with enhanced components. Components are imported directly from `@nqlib/nqui`.
10
+
11
+ > **IMPORTANT:** Use the path alias `@/` when importing from nqui in projects that support it (e.g., Next.js with path aliases configured). Otherwise use `@nqlib/nqui`.
12
+
13
+ ## Principles
14
+
15
+ 1. **Use existing components first.** Check the component index before writing custom UI.
16
+ 2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
17
+ 3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
18
+ 4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
19
+
20
+ ## Core vs Enhanced
21
+
22
+ nqui provides two versions of each component:
23
+
24
+ - **Default (Enhanced)**: Animated, styled, feature-rich — use this by default
25
+ - **Core***: Plain Radix-like, minimal styling — only when you explicitly need the base version
26
+
27
+ ```tsx
28
+ // Default = Enhanced (animated, styled)
29
+ import { Button, Checkbox, Badge } from "@nqlib/nqui"
30
+
31
+ // Only use Core* when you explicitly want plain/base version
32
+ import { CoreButton, CoreCheckbox, CoreBadge } from "@nqlib/nqui"
33
+ ```
34
+
35
+ ## Critical Rules
36
+
37
+ These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
38
+
39
+ ### Styling & Tailwind → [styling.md](./rules/styling.md)
40
+
41
+ - **`className` for layout, not styling.** Never override component colors or typography.
42
+ - **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
43
+ - **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
44
+ - **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
45
+ - **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
46
+ - **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
47
+ - **No manual `z-index` on overlay components.** Use elevation.css variables — see [elevation.css](../../../src/styles/elevation.css).
48
+
49
+ ### Forms & Inputs → [forms.md](./rules/forms.md)
50
+
51
+ - **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
52
+ - **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
53
+ - **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
54
+ - **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
55
+ - **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
56
+ - **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
57
+
58
+ ### Component Structure → [composition.md](./rules/composition.md)
59
+
60
+ - **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
61
+ - **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
62
+ - **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
63
+ - **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
64
+ - **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
65
+
66
+ ### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
67
+
68
+ - **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
69
+ - **Callouts use `Alert`.** Don't build custom styled divs.
70
+ - **Empty states use `Empty`.** Don't build custom empty state markup.
71
+ - **Toast via `sonner`.** Use `toast()` from `sonner`.
72
+ - **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
73
+ - **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
74
+ - **Use `Badge`** instead of custom styled spans.
75
+
76
+ ### Icons → [icons.md](./rules/icons.md)
77
+
78
+ - **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
79
+ - **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.
80
+ - **Use Hugeicons.** Import from `@hugeicons/react` or `@hugeicons/core-free-icons`.
81
+
82
+ ## Key Patterns
83
+
84
+ ```tsx
85
+ // Form layout: FieldGroup + Field, not div + Label.
86
+ <FieldGroup>
87
+ <Field>
88
+ <FieldLabel htmlFor="email">Email</FieldLabel>
89
+ <Input id="email" />
90
+ </Field>
91
+ </FieldGroup>
92
+
93
+ // Validation: data-invalid on Field, aria-invalid on the control.
94
+ <Field data-invalid>
95
+ <FieldLabel>Email</FieldLabel>
96
+ <Input aria-invalid />
97
+ <FieldDescription>Invalid email.</FieldDescription>
98
+ </Field>
99
+
100
+ // Icons in buttons: data-icon, no sizing classes.
101
+ <Button>
102
+ <SearchIcon data-icon="inline-start" />
103
+ Search
104
+ </Button>
105
+
106
+ // Spacing: gap-*, not space-y-*.
107
+ <div className="flex flex-col gap-4"> // correct
108
+ <div className="space-y-4"> // wrong
109
+
110
+ // Equal dimensions: size-*, not w-* h-*.
111
+ <Avatar className="size-10"> // correct
112
+ <Avatar className="w-10 h-10"> // wrong
113
+
114
+ // Status colors: Badge variants or semantic tokens, not raw colors.
115
+ <Badge variant="secondary">+20.1%</Badge> // correct
116
+ <span className="text-emerald-600">+20.1%</span> // wrong
117
+ ```
118
+
119
+ ## Component Selection
120
+
121
+ | Need | Use |
122
+ | -------------------------- | --------------------------------------------------------------------------------------------------- |
123
+ | Button/action | `Button` with appropriate variant |
124
+ | Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `Slider` |
125
+ | Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |
126
+ | Data display | `Table`, `Card`, `Badge`, `Avatar` |
127
+ | Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
128
+ | Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
129
+ | Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` |
130
+ | Command palette | `Command` inside `Dialog` |
131
+ | Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
132
+ | Empty states | `Empty` |
133
+ | Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
134
+ | Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
135
+
136
+ ## Z-Index Elevation System
137
+
138
+ nqui uses a centralized z-index system via CSS variables. **Never use hardcoded z-index values.**
139
+
140
+ Reference: [elevation.css](../../../src/styles/elevation.css)
141
+
142
+ ```tsx
143
+ // Use CSS variable z-index, not hardcoded numbers
144
+ <div className="z-[var(--z-modal)]"> // correct
145
+ <div className="z-50"> // wrong
146
+ <div className="z-[var(--z-popover)]"> // correct
147
+ ```
148
+
149
+ ## Quick Reference
150
+
151
+ ```tsx
152
+ // Import all components from @nqlib/nqui
153
+ import { Button, Card, Dialog, Input, ToggleGroup } from "@nqlib/nqui"
154
+
155
+ // Or use path alias if configured
156
+ import { Button, Card } from "@/components/ui/..."
157
+ ```
158
+
159
+ ## Detailed References
160
+
161
+ - [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index
162
+ - [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states
163
+ - [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
164
+ - [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, Hugeicons
@@ -140,8 +140,8 @@ Combine with `Field` for labelled toggle groups:
140
140
  <Field orientation="horizontal">
141
141
  <FieldTitle id="theme-label">Theme</FieldTitle>
142
142
  <ToggleGroup aria-labelledby="theme-label" spacing={2}>
143
- <ToggleGroupItem value="light">Light</ToggleGroup<ToggleGroupItem valueItem>
144
- ="dark">Dark</ToggleGroupItem>
143
+ <ToggleGroupItem value="light">Light</ToggleGroupItem>
144
+ <ToggleGroupItem value="dark">Dark</ToggleGroupItem>
145
145
  <ToggleGroupItem value="system">System</ToggleGroupItem>
146
146
  </ToggleGroup>
147
147
  </Field>
@@ -187,4 +187,4 @@ Both attributes are needed — `data-invalid`/`data-disabled` styles the field (
187
187
  </Field>
188
188
  ```
189
189
 
190
- Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`.
190
+ Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`.
@@ -177,7 +177,7 @@ import { cn } from "@/lib/utils"
177
177
  </div>
178
178
  ```
179
179
 
180
- Reference: [elevation.css](../src/styles/elevation.css)
180
+ Reference: [elevation.css](../../../../src/styles/elevation.css)
181
181
 
182
182
  | Variable | Value | Use Case |
183
183
  |----------|-------|----------|
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nqlib/nqui",
3
- "version": "0.4.8",
3
+ "version": "0.5.1",
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",
@@ -4,7 +4,8 @@
4
4
  *
5
5
  * Usage: npx @nqlib/nqui init-skills
6
6
  *
7
- * Copies docs/nqui-skills to user's .cursor/nqui-skills
7
+ * Copies docs/nqui-skills .cursor/nqui-skills
8
+ * Copies docs/components → .cursor/nqui-skills/components (per-component docs for offline/Cursor)
8
9
  * Creates AGENTS.md pointing to the skills
9
10
  */
10
11
 
@@ -15,6 +16,8 @@ import { resolveTargetDir } from './resolve-target-dir.js';
15
16
 
16
17
  const root = getPackageRoot();
17
18
  const skillsSource = join(root, 'docs', 'nqui-skills');
19
+ const componentsSource = join(root, 'docs', 'components');
20
+ const componentsDestName = 'components';
18
21
 
19
22
  function ensureDir(dir) {
20
23
  if (!existsSync(dir)) {
@@ -26,12 +29,14 @@ export async function downloadSkills({ force = true }) {
26
29
  const targetDir = resolveTargetDir(process.cwd());
27
30
  const cursorDir = join(targetDir, '.cursor');
28
31
  const skillsDest = join(cursorDir, 'nqui-skills');
32
+ const componentsDest = join(skillsDest, componentsDestName);
29
33
  const agentsFile = join(targetDir, 'AGENTS.md');
30
34
 
31
35
  // Create .cursor directory if needed
32
36
  ensureDir(cursorDir);
33
37
 
34
- // Copy skills folder
38
+ // Copy skills folder (includes COMPONENTS_INDEX.md, nqui-*/SKILL.md, and a **nested** `components/`
39
+ // placeholder only if present in source; component markdown is copied from docs/components below)
35
40
  if (existsSync(skillsSource)) {
36
41
  if (existsSync(skillsDest) && !force) {
37
42
  console.log('⏭️ nqui-skills already exists. Use --force to overwrite.');
@@ -44,6 +49,18 @@ export async function downloadSkills({ force = true }) {
44
49
  return;
45
50
  }
46
51
 
52
+ // Per-component docs: same content as docs/components, nested under nqui-skills for one-tree copy
53
+ if (existsSync(componentsSource)) {
54
+ if (existsSync(componentsDest) && !force) {
55
+ console.log('⏭️ nqui-skills/components already exists. Use --force to overwrite.');
56
+ } else {
57
+ cpSync(componentsSource, componentsDest, { recursive: true });
58
+ console.log('✅ Copied component docs to:', componentsDest);
59
+ }
60
+ } else {
61
+ console.warn('⚠️ Source component docs not found:', componentsSource);
62
+ }
63
+
47
64
  // Create or update AGENTS.md
48
65
  const agentsContent = `# AGENTS.md
49
66
 
@@ -57,16 +74,19 @@ For component implementation and UI design with @nqlib/nqui, load skills from:
57
74
  .cursor/nqui-skills/SKILL.md
58
75
  \`\`\`
59
76
 
60
- The skills include:
61
- - Component implementation guide
62
- - Design system conventions (sizing, z-index)
63
- - ToggleGroup usage rules
64
- - App design patterns
77
+ The skills include (under \`.cursor/nqui-skills/\`):
78
+ - \`SKILL.md\` hub / entry
79
+ - \`HUMAN_GUIDE.md\` task → docs (designers; forms, dashboard, navigation, states)
80
+ - \`COMPONENTS_INDEX.md\` **read first** for which \`nqui-*.md\` to open (token-efficient)
81
+ - \`components/\` per-component docs (same as \`node_modules/@nqlib/nqui/docs/components/\`)
82
+ - \`nqui-components/\`, \`nqui-design-system/\`, \`nqui-shadcn/\` (with rules)
83
+ - \`nqui-bundle-size-best-practices/\`, \`nqui-local-published-toggle/\`, \`nqui-install/\`
65
84
 
66
85
  ## How to Use
67
86
 
68
87
  When working with nqui components, AI assistants should load:
69
- - \`.cursor/nqui-skills/SKILL.md\` - main guide
88
+ - \`.cursor/nqui-skills/SKILL.md\` — hub; open subfolders (e.g. \`nqui-shadcn/SKILL.md\`) by task
89
+ - \`.cursor/nqui-skills/COMPONENTS_INDEX.md\` then **one** file under \`components/nqui-<name>.md\` — avoid loading all component docs or the full \`components/README.md\` unless needed
70
90
 
71
91
  ---
72
92
 
@@ -33,8 +33,9 @@ export default function RootLayout({
33
33
  <body>
34
34
  <ThemeProvider
35
35
  attribute="class"
36
- defaultTheme="system"
37
- enableSystem
36
+ defaultTheme="light"
37
+ enableSystem={false}
38
+ themes={["light", "dark"]}
38
39
  disableTransitionOnChange
39
40
  >
40
41
  <SidebarProvider defaultOpen>
@@ -19,8 +19,9 @@ export default function RootLayout({
19
19
  <body>
20
20
  <ThemeProvider
21
21
  attribute="class"
22
- defaultTheme="system"
23
- enableSystem
22
+ defaultTheme="light"
23
+ enableSystem={false}
24
+ themes={["light", "dark"]}
24
25
  disableTransitionOnChange
25
26
  >
26
27
  {children}
@@ -8,7 +8,13 @@ import App from "./App";
8
8
 
9
9
  createRoot(document.getElementById("root")!).render(
10
10
  <StrictMode>
11
- <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
11
+ <ThemeProvider
12
+ attribute="class"
13
+ defaultTheme="light"
14
+ enableSystem={false}
15
+ themes={["light", "dark"]}
16
+ disableTransitionOnChange
17
+ >
12
18
  <BrowserRouter>
13
19
  <App />
14
20
  </BrowserRouter>