@nqlib/nqui 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/INSTALLATION.md +234 -0
  2. package/README.md +109 -151
  3. package/dist/button-CJHdCq9I.js +155 -0
  4. package/dist/button-R304rhsj.cjs +1 -0
  5. package/dist/calendar.cjs.js +1 -1
  6. package/dist/calendar.es.js +1 -1
  7. package/dist/carousel-D1FMVglR.cjs +1 -0
  8. package/dist/carousel-U7RZhYZj.js +179 -0
  9. package/dist/carousel.cjs.js +1 -1
  10. package/dist/carousel.es.js +1 -1
  11. package/dist/command-palette-DCtLpM3Q.js +694 -0
  12. package/dist/command-palette-MHc03bBf.cjs +5 -0
  13. package/dist/command.cjs.js +1 -1
  14. package/dist/command.es.js +1 -1
  15. package/dist/components/custom/color-picker.d.ts +1 -1
  16. package/dist/components/custom/color-picker.d.ts.map +1 -1
  17. package/dist/components/custom/color-slider.d.ts +4 -10
  18. package/dist/components/custom/color-slider.d.ts.map +1 -1
  19. package/dist/components/custom/enhanced-radio-group.d.ts +13 -4
  20. package/dist/components/custom/enhanced-radio-group.d.ts.map +1 -1
  21. package/dist/components/custom/enhanced-tabs.d.ts.map +1 -1
  22. package/dist/components/debug/debug-features.d.ts +29 -0
  23. package/dist/components/debug/debug-features.d.ts.map +1 -0
  24. package/dist/components/debug/debug-panel.d.ts.map +1 -1
  25. package/dist/components/error-boundary.d.ts +20 -0
  26. package/dist/components/error-boundary.d.ts.map +1 -0
  27. package/dist/components/index.d.ts +103 -0
  28. package/dist/components/index.d.ts.map +1 -0
  29. package/dist/components/ui/badge.d.ts +16 -5
  30. package/dist/components/ui/badge.d.ts.map +1 -1
  31. package/dist/components/ui/button.d.ts +38 -4
  32. package/dist/components/ui/button.d.ts.map +1 -1
  33. package/dist/components/ui/checkbox.d.ts +16 -2
  34. package/dist/components/ui/checkbox.d.ts.map +1 -1
  35. package/dist/components/ui/combobox.d.ts +2 -1
  36. package/dist/components/ui/combobox.d.ts.map +1 -1
  37. package/dist/components/ui/frosted-glass.d.ts.map +1 -1
  38. package/dist/components/ui/input-group.d.ts +1 -1
  39. package/dist/components/ui/input-group.d.ts.map +1 -1
  40. package/dist/components/ui/pagination.d.ts +3 -2
  41. package/dist/components/ui/pagination.d.ts.map +1 -1
  42. package/dist/components/ui/radio-group.d.ts +3 -1
  43. package/dist/components/ui/radio-group.d.ts.map +1 -1
  44. package/dist/components/ui/select.d.ts +6 -1
  45. package/dist/components/ui/select.d.ts.map +1 -1
  46. package/dist/components/ui/sidebar.d.ts +1 -1
  47. package/dist/components/ui/sidebar.d.ts.map +1 -1
  48. package/dist/components/ui/slider.d.ts +10 -2
  49. package/dist/components/ui/slider.d.ts.map +1 -1
  50. package/dist/components/ui/sonner.d.ts +18 -2
  51. package/dist/components/ui/sonner.d.ts.map +1 -1
  52. package/dist/components/ui/spinner.d.ts +2 -1
  53. package/dist/components/ui/spinner.d.ts.map +1 -1
  54. package/dist/components/ui/switch.d.ts +15 -2
  55. package/dist/components/ui/switch.d.ts.map +1 -1
  56. package/dist/components/ui/tabs.d.ts +1 -1
  57. package/dist/components/ui/tabs.d.ts.map +1 -1
  58. package/dist/components/ui/toggle.d.ts +1 -1
  59. package/dist/components/ui/toggle.d.ts.map +1 -1
  60. package/dist/debug-panel-CNKk-No5.cjs +75 -0
  61. package/dist/debug-panel-pg39-6xw.js +9011 -0
  62. package/dist/debug.cjs.js +1 -0
  63. package/dist/debug.es.js +7 -0
  64. package/dist/{drawer-CU4lkcz7.js → drawer-DO26uhym.js} +31 -31
  65. package/dist/drawer-DVarEy65.cjs +1 -0
  66. package/dist/drawer.cjs.js +1 -1
  67. package/dist/drawer.es.js +1 -1
  68. package/dist/{enhanced-calendar-BENbxw7_.js → enhanced-calendar-BGlsSYJd.js} +1 -1
  69. package/dist/{enhanced-calendar-5PA8CeF7.cjs → enhanced-calendar-C7EQIr6i.cjs} +1 -1
  70. package/dist/entries/debug.d.ts +14 -0
  71. package/dist/entries/debug.d.ts.map +1 -0
  72. package/dist/entries/sonner.d.ts +1 -2
  73. package/dist/entries/sonner.d.ts.map +1 -1
  74. package/dist/hooks/use-mobile.d.ts.map +1 -1
  75. package/dist/hooks/use-scroll-spy.d.ts.map +1 -1
  76. package/dist/index-CI756mSv.cjs +41 -0
  77. package/dist/index-CgfzsUO6.js +1069 -0
  78. package/dist/index.d.ts +2 -98
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/lib/index.d.ts +9 -0
  81. package/dist/lib/index.d.ts.map +1 -0
  82. package/dist/lib/wrap-inline-label-text.d.ts +7 -0
  83. package/dist/lib/wrap-inline-label-text.d.ts.map +1 -0
  84. package/dist/nqui.cjs.js +49 -245
  85. package/dist/nqui.es.js +7402 -16735
  86. package/dist/sonner-CpmECDBk.js +179 -0
  87. package/dist/sonner-nE9hIalJ.cjs +48 -0
  88. package/dist/sonner.cjs.js +1 -1
  89. package/dist/sonner.es.js +3 -2
  90. package/dist/styles.css +237 -10
  91. package/docs/components/README.md +109 -10
  92. package/docs/components/nqui-badge.md +1 -0
  93. package/docs/components/nqui-button.md +3 -1
  94. package/docs/components/nqui-card.md +8 -0
  95. package/docs/components/nqui-carousel.md +6 -0
  96. package/docs/components/nqui-checkbox.md +38 -1
  97. package/docs/components/nqui-color-slider.md +5 -3
  98. package/docs/components/nqui-combobox.md +58 -37
  99. package/docs/components/nqui-drawer.md +1 -1
  100. package/docs/components/nqui-frosted-glass.md +83 -5
  101. package/docs/components/nqui-radio-group.md +47 -2
  102. package/docs/components/nqui-scroll-area.md +1 -1
  103. package/docs/components/nqui-select.md +2 -2
  104. package/docs/components/nqui-sheet.md +1 -1
  105. package/docs/components/nqui-slider.md +13 -0
  106. package/docs/components/nqui-spinner.md +6 -1
  107. package/docs/components/nqui-switch.md +23 -1
  108. package/docs/components/nqui-tabs.md +11 -1
  109. package/docs/components/nqui-toaster.md +5 -1
  110. package/docs/internal-notes/PUBLISHING.md +46 -4
  111. package/docs/nqui-skills/SKILL.md +106 -0
  112. package/docs/nqui-skills/design-system.md +143 -0
  113. package/docs/nqui-skills/rules/composition.md +183 -0
  114. package/docs/nqui-skills/rules/forms.md +190 -0
  115. package/docs/nqui-skills/rules/icons.md +158 -0
  116. package/docs/nqui-skills/rules/styling.md +192 -0
  117. package/package.json +23 -12
  118. package/scripts/build-styles.js +16 -0
  119. package/scripts/cli.js +1 -0
  120. package/scripts/download-skills.js +91 -0
  121. package/scripts/examples/nextjs-layout-sidebar.tsx +100 -0
  122. package/scripts/examples/nextjs-page-sidebar.tsx +81 -0
  123. package/scripts/examples/vite-app.tsx +135 -0
  124. package/scripts/examples/vite-main.tsx +17 -0
  125. package/scripts/examples.js +92 -6
  126. package/scripts/generate-docs.js +169 -0
  127. package/scripts/init-css.js +34 -14
  128. package/scripts/init-cursor.js +8 -0
  129. package/scripts/init-debug-css.js +4 -2
  130. package/scripts/post-install.js +41 -9
  131. package/scripts/publish-npmjs.js +17 -3
  132. package/scripts/resolve-target-dir.js +20 -1
  133. package/scripts/setup-helper.js +13 -1
  134. package/scripts/verify-build.js +1 -1
  135. package/scripts/wizard.js +12 -7
  136. package/dist/button-CYFTFDKe.cjs +0 -1
  137. package/dist/button-nJvDl3w8.js +0 -44
  138. package/dist/carousel-DEyyJi49.js +0 -179
  139. package/dist/carousel-Dhhz8m5V.cjs +0 -1
  140. package/dist/command-palette-UHk8zZOg.cjs +0 -45
  141. package/dist/command-palette-d-TrdBsM.js +0 -1778
  142. package/dist/components/custom/enhanced-badge.d.ts +0 -33
  143. package/dist/components/custom/enhanced-badge.d.ts.map +0 -1
  144. package/dist/components/custom/enhanced-button.d.ts +0 -34
  145. package/dist/components/custom/enhanced-button.d.ts.map +0 -1
  146. package/dist/components/custom/enhanced-checkbox.d.ts +0 -28
  147. package/dist/components/custom/enhanced-checkbox.d.ts.map +0 -1
  148. package/dist/components/custom/enhanced-combobox.d.ts +0 -35
  149. package/dist/components/custom/enhanced-combobox.d.ts.map +0 -1
  150. package/dist/components/custom/enhanced-select.d.ts +0 -30
  151. package/dist/components/custom/enhanced-select.d.ts.map +0 -1
  152. package/dist/components/custom/enhanced-sonner.d.ts +0 -16
  153. package/dist/components/custom/enhanced-sonner.d.ts.map +0 -1
  154. package/dist/drawer-BcIxWRN8.cjs +0 -1
  155. package/dist/sonner-Co6YpYVs.js +0 -546
  156. package/dist/sonner-DbQhVp8m.cjs +0 -330
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Generates downloadable documentation files for nqui.
3
+ *
4
+ * Outputs:
5
+ * - INSTALL.md: Installation instructions
6
+ * - COMPONENTS.md: All component documentation bundled
7
+ *
8
+ * Usage:
9
+ * node scripts/generate-docs.js
10
+ * node scripts/generate-docs.js --install
11
+ * node scripts/generate-docs.js --components
12
+ */
13
+
14
+ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'fs';
15
+ import { join, dirname } from 'path';
16
+ import { fileURLToPath } from 'url';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const DOCS_DIR = join(__dirname, '../docs');
20
+ const OUTPUT_DIR = join(__dirname, '../dist/docs');
21
+
22
+ // Ensure output directory exists
23
+ import { mkdirSync, rmSync } from 'fs';
24
+
25
+ function ensureDir(dir) {
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+ }
30
+
31
+ function readDoc(filePath) {
32
+ try {
33
+ return readFileSync(filePath, 'utf-8');
34
+ } catch (e) {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ function generateInstallDoc() {
40
+ const installPath = join(DOCS_DIR, 'internal-notes/INSTALLATION.md');
41
+ let content = readDoc(installPath);
42
+
43
+ if (!content) {
44
+ console.error('Could not find INSTALLATION.md');
45
+ return null;
46
+ }
47
+
48
+ // Add header
49
+ const header = `# @nqlib/nqui Installation Guide
50
+
51
+ > Generated for LLM consumption. Use this file to understand how to install and set up nqui.
52
+
53
+ `;
54
+
55
+ return header + content;
56
+ }
57
+
58
+ function generateComponentsDoc() {
59
+ // Read the main README for shared conventions
60
+ const readmePath = join(DOCS_DIR, 'components/README.md');
61
+ let readmeContent = readDoc(readmePath);
62
+
63
+ if (!readmeContent) {
64
+ console.error('Could not find README.md');
65
+ return null;
66
+ }
67
+
68
+ // Extract sections from README that are useful for implementation
69
+ // (Prerequisites, Shared Conventions, When to Use tables)
70
+ const sectionsToKeep = [
71
+ '# nqui Component Instructions',
72
+ '## Prerequisites',
73
+ '## Shared Conventions',
74
+ '## When to Use',
75
+ '## AI Implementation Checklist'
76
+ ];
77
+
78
+ // Build the content
79
+ let content = `# @nqlib/nqui Component Reference
80
+
81
+ > Generated for LLM consumption. Use this file when implementing nqui components.
82
+
83
+ **Import:** \`import { X } from "@nqlib/nqui"\`
84
+ **CSS:** \`@import "@nqlib/nqui/styles"\` (via \`npx @nqlib/nqui init-css\`)
85
+
86
+ ---
87
+
88
+ `;
89
+
90
+ // Add the main sections from README
91
+ let currentSection = '';
92
+ const lines = readmeContent.split('\n');
93
+
94
+ for (const line of lines) {
95
+ // Track current section
96
+ if (line.startsWith('##')) {
97
+ currentSection = line;
98
+ }
99
+
100
+ // Keep specific sections
101
+ if (sectionsToKeep.some(s => line.includes(s) || currentSection.includes(s.replace('## ', '').replace('# ', '')))) {
102
+ content += line + '\n';
103
+ }
104
+ }
105
+
106
+ // Add component docs
107
+ content += '\n---\n\n## Component Documentation\n\n';
108
+
109
+ const componentsDir = join(DOCS_DIR, 'components');
110
+ const files = readdirSync(componentsDir)
111
+ .filter(f => f.startsWith('nqui-') && f.endsWith('.md'))
112
+ .sort();
113
+
114
+ for (const file of files) {
115
+ const componentPath = join(componentsDir, file);
116
+ const componentContent = readDoc(componentPath);
117
+
118
+ if (componentContent) {
119
+ // Extract the relevant part (without frontmatter if present)
120
+ let docContent = componentContent;
121
+
122
+ // Remove frontmatter
123
+ if (docContent.startsWith('---')) {
124
+ const endOfFm = docContent.indexOf('---', 3);
125
+ if (endOfFm !== -1) {
126
+ docContent = docContent.substring(endOfFm + 3);
127
+ }
128
+ }
129
+
130
+ // Clean up and add
131
+ docContent = docContent.trim();
132
+ content += docContent + '\n\n---\n\n';
133
+ }
134
+ }
135
+
136
+ return content;
137
+ }
138
+
139
+ function main() {
140
+ const args = process.argv.slice(2);
141
+ const generateInstall = args.includes('--install') || args.length === 0;
142
+ const generateComponents = args.includes('--components') || args.length === 0;
143
+
144
+ ensureDir(OUTPUT_DIR);
145
+
146
+ console.log('Generating nqui documentation...\n');
147
+
148
+ if (generateInstall) {
149
+ const installDoc = generateInstallDoc();
150
+ if (installDoc) {
151
+ const outputPath = join(OUTPUT_DIR, 'INSTALL.md');
152
+ writeFileSync(outputPath, installDoc);
153
+ console.log(`Generated: ${outputPath}`);
154
+ }
155
+ }
156
+
157
+ if (generateComponents) {
158
+ const componentsDoc = generateComponentsDoc();
159
+ if (componentsDoc) {
160
+ const outputPath = join(OUTPUT_DIR, 'COMPONENTS.md');
161
+ writeFileSync(outputPath, componentsDoc);
162
+ console.log(`Generated: ${outputPath}`);
163
+ }
164
+ }
165
+
166
+ console.log('\nDone!');
167
+ }
168
+
169
+ main();
@@ -16,12 +16,12 @@ import { runPipeline } from './pipeline/index.js';
16
16
  import { wizard, askAboutExamples } from './wizard.js';
17
17
  import { detectFramework, findMainCssFile } from './framework.js';
18
18
  import { generateSetupContent } from './setup-helper.js';
19
- import { copyNextJsExamples } from './examples.js';
19
+ import { copyNextJsExamples, copyViteExamples } from './examples.js';
20
20
  import { emit } from './pipeline/emit.js';
21
21
 
22
22
  // Guard: if first arg is a subcommand, user has old package (main bin was init-css). Redirect.
23
23
  const firstArg = process.argv[2];
24
- const subcommands = ['init-cursor', 'install-peers', 'init-debug', 'init-debug-css', 'setup'];
24
+ const subcommands = ['init-cursor', 'init-skills', 'install-peers', 'init-debug', 'init-debug-css', 'setup'];
25
25
  if (subcommands.includes(firstArg)) {
26
26
  console.error(`
27
27
  ❌ Outdated @nqlib/nqui — "npx @nqlib/nqui ${firstArg}" routed to init-css.
@@ -29,17 +29,17 @@ if (subcommands.includes(firstArg)) {
29
29
  Fix: npm install @nqlib/nqui@latest
30
30
  Then: npx @nqlib/nqui ${firstArg}
31
31
 
32
- Or run the binary directly: npm exec nqui-init-cursor
32
+ Or run the binary directly: npm exec nqui-init-skills (for init-skills), etc.
33
33
  `);
34
34
  process.exit(1);
35
35
  }
36
36
 
37
- // Filter out command names from argv
38
- const commandNames = ['init-css', 'nqui', 'nqui-init-css'];
37
+ // Filter out command names from argv (so they are never used as output path)
38
+ const commandNames = ['init-css', 'init-skills', 'nqui', 'nqui-init-css'];
39
39
  const filteredArgs = process.argv.slice(2).filter(arg => !commandNames.includes(arg));
40
40
 
41
41
  const args = minimist(filteredArgs, {
42
- boolean: ['js', 'tokens-only', 'force', 'dry-run', 'wizard', 'setup', 'help', 'version', 'local-copy'],
42
+ boolean: ['js', 'tokens-only', 'force', 'dry-run', 'wizard', 'setup', 'help', 'version', 'local-copy', 'sidebar'],
43
43
  alias: { h: 'help', v: 'version' },
44
44
  });
45
45
 
@@ -55,7 +55,11 @@ if (args.version) {
55
55
  process.exit(0);
56
56
  }
57
57
 
58
- const output = args._[0] || 'nqui/index.css';
58
+ // Prevent subcommand names or bare filenames from becoming output path (would e.g. create init-skills or root nqui-setup.css)
59
+ const rawOutput = args._[0] || 'nqui/index.css';
60
+ const output = !rawOutput || rawOutput === 'init-skills' || rawOutput === 'init-cursor' || !dirname(rawOutput)
61
+ ? 'nqui/index.css'
62
+ : rawOutput;
59
63
 
60
64
  /**
61
65
  * Generate index.css that imports from library package
@@ -101,29 +105,45 @@ function generateIndexCssContent() {
101
105
  });
102
106
  }
103
107
 
104
- // Generate setup helper file (always, unless dry-run)
108
+ // Generate setup helper file (always, unless dry-run). Always under nqui/ to avoid writing to project root.
105
109
  if (!args['dry-run'] && (args.setup || !args['tokens-only'])) {
106
110
  const setupContent = generateSetupContent(framework, output, useLibraryImport);
107
- const setupPath = resolve(process.cwd(), dirname(output), 'nqui-setup.css');
111
+ const setupDir = dirname(output) || 'nqui';
112
+ const setupPath = resolve(process.cwd(), setupDir, 'nqui-setup.css');
108
113
  emit(setupPath, setupContent, { force: args.force, dryRun: false });
109
114
 
110
115
  const mainCssFile = findMainCssFile(framework);
111
116
 
112
117
  console.log(`\n📝 Next steps:`);
113
- console.log(` 1. Copy the contents of ${dirname(output)}/nqui-setup.css`);
118
+ console.log(` 1. Copy the contents of ${setupDir}/nqui-setup.css`);
114
119
  console.log(` 2. Paste them at the VERY TOP of your main CSS file`);
115
120
  console.log(` (e.g. ${mainCssFile})\n`);
116
121
  }
117
122
 
118
- // Ask about copying examples (if not in wizard mode, ask for Next.js projects)
123
+ // Copy examples: when --sidebar and --force, auto-enable so nqui:init is non-interactive
119
124
  let shouldCopyExamples = wiz.copyExamples;
120
- if (!args['dry-run'] && !shouldCopyExamples && framework === 'nextjs' && !args.wizard) {
121
- shouldCopyExamples = await askAboutExamples(framework);
125
+ let useSidebar = wiz.sidebarLayout || args.sidebar;
126
+ if (!args.wizard && (framework === 'nextjs' || framework === 'vite')) {
127
+ if (args.sidebar && args.force) {
128
+ shouldCopyExamples = true;
129
+ useSidebar = true;
130
+ } else if (!args['dry-run'] && !shouldCopyExamples) {
131
+ shouldCopyExamples = await askAboutExamples(framework, useSidebar);
132
+ if (shouldCopyExamples) {
133
+ const sidebarAnswer = await askQuestion('\n🎨 Use sidebar layout (recommended for apps)? (y/n): ');
134
+ useSidebar = sidebarAnswer === 'y' || sidebarAnswer === 'yes';
135
+ }
136
+ }
122
137
  }
123
138
 
124
139
  // Copy example files if requested
125
140
  if (!args['dry-run'] && shouldCopyExamples) {
126
- const copied = await copyNextJsExamples(framework, { force: args.force });
141
+ let copied;
142
+ if (framework === 'nextjs') {
143
+ copied = await copyNextJsExamples(framework, { force: args.force, sidebar: useSidebar });
144
+ } else if (framework === 'vite') {
145
+ copied = await copyViteExamples(framework, { force: args.force, sidebar: useSidebar });
146
+ }
127
147
  if (copied && copied.length === 0) {
128
148
  // Files were skipped (user said no to overwrite)
129
149
  console.log('\n⏭️ Example files skipped. Run with --force to overwrite existing files.\n');
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'url';
13
13
  import { FULL_PEER_LIST } from './peer-deps.js';
14
14
  import { buildInstallSkill, buildComponentsSkill } from './skill-templates.js';
15
15
  import { resolveTargetDir } from './resolve-target-dir.js';
16
+ import { downloadSkills } from './download-skills.js';
16
17
 
17
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
19
  const cwd = process.cwd();
@@ -153,12 +154,19 @@ function main() {
153
154
  }
154
155
 
155
156
  writeCursorRule(targetDir);
157
+
158
+ // Also download nqui-skills to user's .cursor folder
159
+ console.log('\n📚 Downloading nqui-skills...');
160
+ downloadSkills({ force: false });
161
+
156
162
  console.log(`
157
163
  ✅ Cursor rules + skills installed
158
164
 
159
165
  ${displayPath}/.cursor/rules/nqui-components.mdc
160
166
  ${displayPath}/.cursor/skills/nqui-install/
161
167
  ${displayPath}/.cursor/skills/nqui-components/
168
+ ${displayPath}/.cursor/nqui-skills/
169
+ ${displayPath}/AGENTS.md
162
170
 
163
171
  Open this folder in Cursor for skills to work. Docs: node_modules/@nqlib/nqui/docs/components/
164
172
  `);
@@ -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(' 3. Add DebugPanel to your root component');
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
 
@@ -7,7 +7,7 @@
7
7
  * Auto-injects Cursor rules so consumers don't need to remember init-cursor.
8
8
  */
9
9
 
10
- import { existsSync } from 'fs';
10
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
11
11
  import { resolve } from 'path';
12
12
 
13
13
  // Skip in CI to reduce noise
@@ -36,6 +36,31 @@ function getInstallCmd(pkgs) {
36
36
  }
37
37
  }
38
38
 
39
+ function addNquiInitScript() {
40
+ const cwd = process.cwd();
41
+ const packageJsonPath = resolve(cwd, 'package.json');
42
+
43
+ if (!existsSync(packageJsonPath)) return false;
44
+
45
+ try {
46
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
47
+
48
+ // Check if script already exists
49
+ if (pkg.scripts?.['nqui:init']) {
50
+ return false; // Already exists
51
+ }
52
+
53
+ // Add the script
54
+ pkg.scripts = pkg.scripts || {};
55
+ pkg.scripts['nqui:init'] = 'npx @nqlib/nqui install-peers && npx @nqlib/nqui init-cursor && npx @nqlib/nqui init-skills && npx @nqlib/nqui init-css --sidebar --force';
56
+
57
+ writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
58
+ return true;
59
+ } catch (e) {
60
+ return false;
61
+ }
62
+ }
63
+
39
64
  import { FULL_PEER_LIST } from './peer-deps.js';
40
65
  import { writeCursorRule } from './init-cursor.js';
41
66
  import { resolveTargetDir } from './resolve-target-dir.js';
@@ -47,21 +72,28 @@ const installRequired = getInstallCmd(requiredPeers);
47
72
  const installFull = getInstallCmd(['@nqlib/nqui', ...FULL_PEER_LIST]);
48
73
  const installRecommended = getInstallCmd(recommended);
49
74
 
75
+ // Auto-add nqui:init script to package.json
76
+ const scriptAdded = addNquiInitScript();
77
+
50
78
  const msg = `
51
79
  ╔══════════════════════════════════════════════════════════════════╗
52
- ║ nqui – Next steps
80
+ ║ nqui – Next steps
53
81
  ╚══════════════════════════════════════════════════════════════════╝
54
82
 
55
- 1. Set up CSS (required)
56
- npx @nqlib/nqui init-css
83
+ ${scriptAdded ? '✅ Added "nqui:init" script to package.json\n' : ''}Run the full setup:
84
+
85
+ npm run nqui:init
57
86
 
58
- 2. Install peers: ${installRequired}
59
- Full: npx @nqlib/nqui install-peers
87
+ Or step by step:
60
88
 
61
- 3. Optional: tw-animate-css, next-themes
89
+ npx @nqlib/nqui init-css --sidebar # CSS + sidebar layout
90
+ npx @nqlib/nqui install-peers # Install dependencies
91
+ npx @nqlib/nqui init-cursor # Setup Cursor skills
62
92
 
63
- 4. Cursor skills: auto-injected. Open this folder in Cursor.
64
- Refresh: npx @nqlib/nqui init-cursor
93
+ Manual commands:
94
+ - Install peers: ${installRequired}
95
+ - Full peers: npx @nqlib/nqui install-peers
96
+ - Refresh skills: npx @nqlib/nqui init-skills
65
97
 
66
98
  → Run "npx nqui-setup" anytime to see this again.
67
99
  `;
@@ -19,8 +19,19 @@ const npmrcPath = join(rootDir, '.npmrc');
19
19
  // Read package.json
20
20
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
21
21
 
22
- // Save original publishConfig
22
+ // Save originals for restore
23
23
  const originalPublishConfig = packageJson.publishConfig;
24
+ const originalDependencies = { ...packageJson.dependencies };
25
+
26
+ // Remove workspace:* deps (npm doesn't support workspace: protocol).
27
+ // nqcode/nqappbuilder are dev/showcase-only and not bundled in the library.
28
+ const workspaceDeps = ['@nqlib/nqcode', '@nqlib/nqappbuilder'];
29
+ for (const name of workspaceDeps) {
30
+ if (packageJson.dependencies?.[name] === 'workspace:*') {
31
+ delete packageJson.dependencies[name];
32
+ console.log(` Removed ${name} (workspace dep)`);
33
+ }
34
+ }
24
35
 
25
36
  // Override publishConfig for npmjs.com
26
37
  packageJson.publishConfig = {
@@ -99,14 +110,17 @@ try {
99
110
  }
100
111
  throw error;
101
112
  } finally {
102
- // Restore original publishConfig
113
+ // Restore original publishConfig and dependencies
103
114
  if (originalPublishConfig) {
104
115
  packageJson.publishConfig = originalPublishConfig;
105
116
  } else {
106
117
  delete packageJson.publishConfig;
107
118
  }
119
+ if (originalDependencies) {
120
+ packageJson.dependencies = originalDependencies;
121
+ }
108
122
  writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
109
- console.log('🔄 Restored publishConfig');
123
+ console.log('🔄 Restored package.json');
110
124
 
111
125
  // Restore .npmrc if it existed
112
126
  if (npmrcExists && originalNpmrc !== null) {
@@ -59,11 +59,24 @@ function getWorkspacePackageDirs(workspaceRoot) {
59
59
  }
60
60
 
61
61
  function isNquiSourceDir(dir) {
62
+ // Must NOT be inside node_modules (that means we're installed as a dep, not dev source)
63
+ if (resolve(dir).includes('node_modules')) return false;
62
64
  return existsSync(resolve(dir, 'docs/components/README.md')) &&
63
65
  existsSync(resolve(dir, 'package.json')) &&
64
66
  readFileSync(resolve(dir, 'package.json'), 'utf8').includes('"name": "@nqlib/nqui"');
65
67
  }
66
68
 
69
+ function findProjectRootFromNodeModules(startDir) {
70
+ let dir = resolve(startDir);
71
+ for (let i = 0; i < 20; i++) {
72
+ if (existsSync(resolve(dir, 'node_modules/@nqlib/nqui'))) return dir;
73
+ const parent = dirname(dir);
74
+ if (parent === dir) break;
75
+ dir = parent;
76
+ }
77
+ return null;
78
+ }
79
+
67
80
  /**
68
81
  * Returns the directory where .cursor/ should be written.
69
82
  * Prefers a dir with node_modules/@nqlib/nqui so docs path resolves.
@@ -72,7 +85,13 @@ function isNquiSourceDir(dir) {
72
85
  * @returns {string} Absolute path to target directory
73
86
  */
74
87
  export function resolveTargetDir(startDir) {
75
- const cwd = resolve(startDir);
88
+ let cwd = resolve(startDir);
89
+
90
+ // 0. Running from node_modules (postinstall as dep) -> use host project root
91
+ if (cwd.includes('node_modules')) {
92
+ const projectRoot = findProjectRootFromNodeModules(cwd);
93
+ if (projectRoot) return projectRoot;
94
+ }
76
95
 
77
96
  // 1. Current dir has nqui in node_modules -> use it
78
97
  if (hasNquiInNodeModules(cwd)) return cwd;
@@ -53,6 +53,8 @@ export function generateSetupContent(framework, nquiCssPath, useLibraryImport =
53
53
  `,
54
54
  vite: `
55
55
  @import "tw-animate-css";
56
+
57
+ @custom-variant dark (&:is(.dark *));
56
58
  `,
57
59
  'create-react-app': `
58
60
  @import "tw-animate-css";
@@ -67,6 +69,16 @@ export function generateSetupContent(framework, nquiCssPath, useLibraryImport =
67
69
 
68
70
  const finalImport = `\n/* Import nqui design tokens */\n@import "${relativeImport}";\n`;
69
71
 
70
- return base + (extras[framework] || extras.generic) + finalImport;
72
+ const viteTailwindSources =
73
+ framework === 'vite'
74
+ ? `
75
+ /* Tailwind v4: scan app + nqui dist when utilities from the package are missing (see INSTALLATION.md §2c) */
76
+ @source "./**/*.{js,ts,jsx,tsx,mdx}";
77
+ @source "../components/**/*.{js,ts,jsx,tsx,mdx}";
78
+ @source "../node_modules/@nqlib/nqui/dist/**/*.js";
79
+ `
80
+ : '';
81
+
82
+ return base + (extras[framework] || extras.generic) + finalImport + viteTailwindSources;
71
83
  }
72
84
 
@@ -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
- { name: 'Viewport lock (html, body, #root)', pattern: /html\s*,\s*\n?\s*body\s*,\s*\n?\s*#root\s*\{[^}]*height:\s*100%[^}]*overflow:\s*hidden/s },
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/scripts/wizard.js CHANGED
@@ -35,23 +35,28 @@ export async function wizard() {
35
35
 
36
36
  const framework = detectFramework();
37
37
  let copyExamples = false;
38
+ let sidebarLayout = false;
38
39
 
39
- if (framework === 'nextjs') {
40
- copyExamples = (await ask('\nCopy Next.js example files (page.tsx, layout.tsx)? (y/n): ')) === 'y';
40
+ if (framework === 'nextjs' || framework === 'vite') {
41
+ copyExamples = (await ask(`\nCopy ${framework} example files? (y/n): `)) === 'y';
42
+ if (copyExamples) {
43
+ sidebarLayout = (await ask('\nUse sidebar layout (recommended for apps)? (y/n): ')) === 'y';
44
+ }
41
45
  }
42
46
 
43
- return { tokensOnly, js, copyExamples };
47
+ return { tokensOnly, js, copyExamples, sidebarLayout };
44
48
  }
45
49
 
46
50
  /**
47
- * Ask about copying examples (used in default mode for Next.js)
51
+ * Ask about copying examples (used in default mode)
48
52
  */
49
- export async function askAboutExamples(framework) {
50
- if (framework !== 'nextjs') {
53
+ export async function askAboutExamples(framework, useSidebar = false) {
54
+ if (framework !== 'nextjs' && framework !== 'vite') {
51
55
  return false;
52
56
  }
53
57
 
54
- const answer = await askQuestion('\n📦 Copy Next.js example files (page.tsx, layout.tsx)? (y/n): ');
58
+ const layoutType = useSidebar ? 'sidebar' : 'basic';
59
+ const answer = await askQuestion(`\n📦 Copy ${framework} example files (${layoutType} layout)? (y/n): `);
55
60
  return answer === 'y' || answer === 'yes';
56
61
  }
57
62
 
@@ -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;
@@ -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
- };