@nqlib/nqui 0.4.7 → 0.5.0
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 +22 -18
- package/dist/{button-CJHdCq9I.js → button-BrroJ39H.js} +35 -34
- package/dist/button-CrLihxcE.cjs +1 -0
- package/dist/calendar.cjs.js +1 -1
- package/dist/calendar.es.js +1 -1
- package/dist/{carousel-U7RZhYZj.js → carousel-C976t5jo.js} +1 -1
- package/dist/{carousel-D1FMVglR.cjs → carousel-VldrniuT.cjs} +1 -1
- package/dist/carousel.cjs.js +1 -1
- package/dist/carousel.es.js +1 -1
- package/dist/{command-palette-D_SFxXyQ.js → command-palette-2V1VSlOM.js} +1 -1
- package/dist/{command-palette-DSQYbRiH.cjs → command-palette-4Sv4QjZ2.cjs} +1 -1
- package/dist/command.cjs.js +1 -1
- package/dist/command.es.js +1 -1
- package/dist/components/custom/enhanced-tabs.d.ts +1 -0
- package/dist/components/custom/enhanced-tabs.d.ts.map +1 -1
- package/dist/components/ui/button.d.ts +4 -0
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/card.d.ts +1 -0
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/sonner.d.ts.map +1 -1
- package/dist/components/ui/tabs.d.ts +6 -4
- package/dist/components/ui/tabs.d.ts.map +1 -1
- package/dist/{debug-panel-BtOvhfpR.js → debug-panel-BnZDhlaR.js} +1621 -1749
- package/dist/{debug-panel-DLfH4sdK.cjs → debug-panel-R2ZLkucO.cjs} +10 -10
- package/dist/debug.cjs.js +1 -1
- package/dist/debug.es.js +1 -1
- package/dist/{enhanced-calendar-C7EQIr6i.cjs → enhanced-calendar-BEIfybRx.cjs} +1 -1
- package/dist/{enhanced-calendar-BGlsSYJd.js → enhanced-calendar-Cj-4xhqf.js} +1 -1
- package/dist/hooks/use-resolved-theme.d.ts +3 -4
- package/dist/hooks/use-resolved-theme.d.ts.map +1 -1
- package/dist/nqui.cjs.js +3 -23
- package/dist/nqui.es.js +607 -629
- package/dist/{sonner-nE9hIalJ.cjs → sonner-CEx2-yKh.cjs} +2 -2
- package/dist/{sonner-CpmECDBk.js → sonner-FXwPWWq8.js} +24 -24
- package/dist/sonner.cjs.js +1 -1
- package/dist/sonner.es.js +1 -1
- package/dist/styles.css +40 -80
- package/docs/components/README.md +11 -2
- package/docs/components/nqui-scroll-area.md +74 -0
- package/docs/components/nqui-tooltip.md +17 -2
- package/docs/nqui-skills/COMPONENTS_INDEX.md +48 -0
- package/docs/nqui-skills/HUMAN_GUIDE.md +21 -0
- package/docs/nqui-skills/README.md +20 -0
- package/docs/nqui-skills/SKILL.md +36 -89
- package/docs/nqui-skills/nqui-bundle-size-best-practices/SKILL.md +78 -0
- package/docs/nqui-skills/nqui-components/SKILL.md +101 -0
- package/docs/nqui-skills/{design-system.md → nqui-design-system/SKILL.md} +55 -25
- package/docs/nqui-skills/nqui-install/SKILL.md +65 -0
- package/docs/nqui-skills/nqui-local-published-toggle/SKILL.md +132 -0
- package/docs/nqui-skills/nqui-local-published-toggle/scripts/toggle-nqui.js +203 -0
- package/docs/nqui-skills/nqui-shadcn/SKILL.md +164 -0
- package/docs/nqui-skills/{rules → nqui-shadcn/rules}/forms.md +3 -3
- package/docs/nqui-skills/{rules → nqui-shadcn/rules}/styling.md +1 -1
- package/package.json +1 -1
- package/scripts/download-skills.js +28 -8
- package/scripts/examples/nextjs-layout-sidebar.tsx +3 -2
- package/scripts/examples/nextjs-layout.tsx +3 -2
- package/scripts/examples/vite-main.tsx +7 -1
- package/scripts/init-cursor.js +4 -3
- package/scripts/skill-templates.js +16 -6
- package/dist/button-R304rhsj.cjs +0 -1
- /package/docs/nqui-skills/{rules → nqui-shadcn/rules}/composition.md +0 -0
- /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</
|
|
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](
|
|
180
|
+
Reference: [elevation.css](../../../../src/styles/elevation.css)
|
|
181
181
|
|
|
182
182
|
| Variable | Value | Use Case |
|
|
183
183
|
|----------|-------|----------|
|
package/package.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage: npx @nqlib/nqui init-skills
|
|
6
6
|
*
|
|
7
|
-
* Copies docs/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
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
-
|
|
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\` -
|
|
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="
|
|
37
|
-
enableSystem
|
|
36
|
+
defaultTheme="mid"
|
|
37
|
+
enableSystem={false}
|
|
38
|
+
themes={["mid", "dark"]}
|
|
38
39
|
disableTransitionOnChange
|
|
39
40
|
>
|
|
40
41
|
<SidebarProvider defaultOpen>
|
|
@@ -8,7 +8,13 @@ import App from "./App";
|
|
|
8
8
|
|
|
9
9
|
createRoot(document.getElementById("root")!).render(
|
|
10
10
|
<StrictMode>
|
|
11
|
-
<ThemeProvider
|
|
11
|
+
<ThemeProvider
|
|
12
|
+
attribute="class"
|
|
13
|
+
defaultTheme="mid"
|
|
14
|
+
enableSystem={false}
|
|
15
|
+
themes={["mid", "dark"]}
|
|
16
|
+
disableTransitionOnChange
|
|
17
|
+
>
|
|
12
18
|
<BrowserRouter>
|
|
13
19
|
<App />
|
|
14
20
|
</BrowserRouter>
|