@nextop-os/ui-system 0.0.15 → 0.0.17

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 (35) hide show
  1. package/AGENTS.md +84 -0
  2. package/README.md +86 -0
  3. package/UI_SYSTEM_GUIDELINES.md +148 -0
  4. package/agent/install-skill.mjs +181 -0
  5. package/agent/nextop-ui-system/SKILL.md +79 -0
  6. package/agent/nextop-ui-system/references/extract-base-component.md +43 -0
  7. package/agent/nextop-ui-system/references/maintain-inventory.md +40 -0
  8. package/agent/nextop-ui-system/references/promote-business-component.md +352 -0
  9. package/agent/nextop-ui-system/references/use-existing-component.md +37 -0
  10. package/agent/nextop-ui-system/scripts/create-business-preview.mjs +658 -0
  11. package/dist/{chunk-G6KJIFD2.js → chunk-FT633NLJ.js} +2 -2
  12. package/dist/{chunk-OBW6ALOJ.js → chunk-NFSMZKML.js} +78 -3
  13. package/dist/chunk-NFSMZKML.js.map +1 -0
  14. package/dist/chunk-XHA7R2WC.js +292 -0
  15. package/dist/chunk-XHA7R2WC.js.map +1 -0
  16. package/dist/components/index.js +2 -2
  17. package/dist/dev-vite.d.ts +9 -0
  18. package/dist/dev-vite.js +578 -0
  19. package/dist/dev-vite.js.map +1 -0
  20. package/dist/icons/index.d.ts +14 -1
  21. package/dist/icons/index.js +16 -2
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.js +17 -3
  24. package/dist/metadata/components.json +1464 -0
  25. package/dist/metadata/components.schema.json +106 -0
  26. package/dist/metadata/index.d.ts +25 -0
  27. package/dist/metadata/index.js +1472 -0
  28. package/dist/metadata/index.js.map +1 -0
  29. package/dist/styles/semantic.css +1 -0
  30. package/dist/styles/theme.css +12 -0
  31. package/package.json +25 -3
  32. package/dist/chunk-IK2XRJQG.js +0 -56
  33. package/dist/chunk-IK2XRJQG.js.map +0 -1
  34. package/dist/chunk-OBW6ALOJ.js.map +0 -1
  35. /package/dist/{chunk-G6KJIFD2.js.map → chunk-FT633NLJ.js.map} +0 -0
@@ -0,0 +1,658 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { constants } from "node:fs";
4
+ import { access, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
5
+ import { dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+ import { tmpdir } from "node:os";
8
+ import { createRequire } from "node:module";
9
+
10
+ const scriptDirectory = dirname(fileURLToPath(import.meta.url));
11
+ const sourcePackageRoot = resolve(scriptDirectory, "..", "..", "..");
12
+ const cwdRequire = createRequire(join(process.cwd(), "package.json"));
13
+ const packageRoot = await resolvePackageRoot(sourcePackageRoot);
14
+
15
+ const options = parseArgs(process.argv.slice(2));
16
+
17
+ if (options.help) {
18
+ printHelp();
19
+ process.exit(0);
20
+ }
21
+
22
+ if (!options.componentId) {
23
+ throw new Error("--component-id is required");
24
+ }
25
+
26
+ if (!options.componentName) {
27
+ throw new Error("--component-name is required");
28
+ }
29
+
30
+ if (!options.stateMatrixPath) {
31
+ throw new Error("--state-matrix is required");
32
+ }
33
+
34
+ const componentId = normalizeComponentId(options.componentId);
35
+ const componentName = normalizeComponentName(options.componentName);
36
+ const serverUrl = options.serverUrl || "http://127.0.0.1:4100";
37
+ const outputDirectory = resolve(
38
+ options.outDir || join(tmpdir(), `nextop-ui-system-preview-${componentId}`)
39
+ );
40
+ const stateMatrix = normalizeStateMatrix(
41
+ JSON.parse(await readFile(resolve(options.stateMatrixPath), "utf8"))
42
+ );
43
+
44
+ await assertReadableDirectory(packageRoot);
45
+
46
+ if (await pathExists(outputDirectory)) {
47
+ if (!options.force) {
48
+ throw new Error(
49
+ `Preview directory already exists: ${outputDirectory}\n` +
50
+ "Run with --force to replace it."
51
+ );
52
+ }
53
+
54
+ await rm(outputDirectory, { recursive: true, force: true });
55
+ }
56
+
57
+ await mkdir(join(outputDirectory, "src"), { recursive: true });
58
+
59
+ const files = new Map([
60
+ ["package.json", packageJson(componentId)],
61
+ ["index.html", indexHtml(componentName)],
62
+ ["vite.config.ts", viteConfig(serverUrl)],
63
+ ["tsconfig.json", tsconfigJson()],
64
+ ["src/main.tsx", mainTsx()],
65
+ ["src/Preview.tsx", previewTsx(componentName)],
66
+ ["src/stateMatrix.ts", stateMatrixTs(stateMatrix)],
67
+ ["src/style.css", styleCss()]
68
+ ]);
69
+
70
+ await Promise.all(
71
+ Array.from(files, ([relativePath, content]) =>
72
+ writeFile(join(outputDirectory, relativePath), content)
73
+ )
74
+ );
75
+
76
+ console.log(`Created preview scaffold at ${outputDirectory}`);
77
+ console.log("");
78
+ console.log("Run:");
79
+ console.log(` cd ${outputDirectory}`);
80
+ console.log(" pnpm install");
81
+ console.log(" pnpm dev -- --host 127.0.0.1");
82
+ console.log("");
83
+ console.log("Before reviewing, make sure the UI-system dev server is running:");
84
+ console.log(" pnpm --filter @nextop-os/ui-system dev:server");
85
+ console.log(` curl ${serverUrl}/health`);
86
+
87
+ function parseArgs(args) {
88
+ const parsed = {
89
+ componentId: "",
90
+ componentName: "",
91
+ force: false,
92
+ help: false,
93
+ outDir: "",
94
+ serverUrl: "",
95
+ stateMatrixPath: ""
96
+ };
97
+
98
+ for (let index = 0; index < args.length; index += 1) {
99
+ const arg = args[index];
100
+
101
+ if (arg === "--help" || arg === "-h") {
102
+ parsed.help = true;
103
+ continue;
104
+ }
105
+
106
+ if (arg === "--force") {
107
+ parsed.force = true;
108
+ continue;
109
+ }
110
+
111
+ if (arg === "--component-id") {
112
+ parsed.componentId = requireValue(args, index, arg);
113
+ index += 1;
114
+ continue;
115
+ }
116
+
117
+ if (arg === "--component-name") {
118
+ parsed.componentName = requireValue(args, index, arg);
119
+ index += 1;
120
+ continue;
121
+ }
122
+
123
+ if (arg === "--state-matrix") {
124
+ parsed.stateMatrixPath = requireValue(args, index, arg);
125
+ index += 1;
126
+ continue;
127
+ }
128
+
129
+ if (arg === "--out-dir") {
130
+ parsed.outDir = requireValue(args, index, arg);
131
+ index += 1;
132
+ continue;
133
+ }
134
+
135
+ if (arg === "--server-url") {
136
+ parsed.serverUrl = requireValue(args, index, arg);
137
+ index += 1;
138
+ continue;
139
+ }
140
+
141
+ throw new Error(`Unknown option: ${arg}`);
142
+ }
143
+
144
+ return parsed;
145
+ }
146
+
147
+ function requireValue(args, index, optionName) {
148
+ const value = args[index + 1];
149
+
150
+ if (!value) {
151
+ throw new Error(`${optionName} requires a value`);
152
+ }
153
+
154
+ return value;
155
+ }
156
+
157
+ function printHelp() {
158
+ console.log(`Usage: create-business-preview.mjs --component-id <id> --component-name <Name> --state-matrix <path> [options]
159
+
160
+ Generates a temporary Vite React preview scaffold for a proposed
161
+ @nextop-os/ui-system business component promotion.
162
+
163
+ Options:
164
+ --component-id <id> Stable kebab-case component id.
165
+ --component-name <Name> Proposed React export name.
166
+ --state-matrix <path> JSON file with an array of states or { "states": [] }.
167
+ --out-dir <path> Output directory. Defaults to $TMPDIR/nextop-ui-system-preview-<id>.
168
+ --server-url <url> UI-system dev server URL. Defaults to http://127.0.0.1:4100.
169
+ --force Replace an existing output directory.
170
+ -h, --help Show this help message.
171
+
172
+ State matrix item fields:
173
+ name, evidence, propsData, hostOwnedBehavior, includedInContract,
174
+ description, boundaryNotes`);
175
+ }
176
+
177
+ function normalizeComponentId(value) {
178
+ const normalized = value
179
+ .trim()
180
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
181
+ .replace(/[^a-zA-Z0-9]+/g, "-")
182
+ .replace(/^-+|-+$/g, "")
183
+ .toLowerCase();
184
+
185
+ if (!normalized) {
186
+ throw new Error(
187
+ "Component id must contain at least one alphanumeric character"
188
+ );
189
+ }
190
+
191
+ return normalized;
192
+ }
193
+
194
+ function normalizeComponentName(value) {
195
+ const normalized = value.trim();
196
+
197
+ if (!/^[A-Z][A-Za-z0-9]*$/.test(normalized)) {
198
+ throw new Error(
199
+ "--component-name must be a PascalCase React component name"
200
+ );
201
+ }
202
+
203
+ return normalized;
204
+ }
205
+
206
+ function normalizeStateMatrix(value) {
207
+ const states = Array.isArray(value) ? value : value?.states;
208
+
209
+ if (!Array.isArray(states) || states.length === 0) {
210
+ throw new Error(
211
+ 'State matrix must be a non-empty array or { "states": [] }'
212
+ );
213
+ }
214
+
215
+ return states.map((state, index) => {
216
+ if (state === null || typeof state !== "object" || Array.isArray(state)) {
217
+ throw new Error(`State matrix item ${index + 1} must be an object`);
218
+ }
219
+
220
+ const name = stringField(state, "name", `state-${index + 1}`);
221
+
222
+ return {
223
+ name,
224
+ evidence: stringListField(state, "evidence"),
225
+ propsData: stringListField(state, "propsData"),
226
+ hostOwnedBehavior: stringListField(state, "hostOwnedBehavior"),
227
+ includedInContract: Boolean(state.includedInContract),
228
+ description: stringField(state, "description", ""),
229
+ boundaryNotes: stringListField(state, "boundaryNotes")
230
+ };
231
+ });
232
+ }
233
+
234
+ function stringField(object, field, fallback) {
235
+ const value = object[field];
236
+
237
+ if (typeof value === "string") {
238
+ return value;
239
+ }
240
+
241
+ return fallback;
242
+ }
243
+
244
+ function stringListField(object, field) {
245
+ const value = object[field];
246
+
247
+ if (Array.isArray(value)) {
248
+ return value.map((item) => String(item));
249
+ }
250
+
251
+ if (typeof value === "string" && value.length > 0) {
252
+ return [value];
253
+ }
254
+
255
+ return [];
256
+ }
257
+
258
+ async function assertReadableDirectory(path) {
259
+ await access(path, constants.R_OK);
260
+ const pathStats = await stat(path);
261
+
262
+ if (!pathStats.isDirectory()) {
263
+ throw new Error(`Expected directory: ${path}`);
264
+ }
265
+ }
266
+
267
+ async function resolvePackageRoot(sourceCandidate) {
268
+ if (await isUISystemPackageRoot(sourceCandidate)) {
269
+ return sourceCandidate;
270
+ }
271
+
272
+ let entrypoint;
273
+
274
+ try {
275
+ entrypoint = cwdRequire.resolve("@nextop-os/ui-system");
276
+ } catch {
277
+ throw new Error(
278
+ "Unable to resolve @nextop-os/ui-system. Run this from a Nextop checkout " +
279
+ "or a project with @nextop-os/ui-system installed."
280
+ );
281
+ }
282
+
283
+ const resolvedRoot = await findPackageRoot(dirname(entrypoint));
284
+
285
+ if (resolvedRoot === null) {
286
+ throw new Error(
287
+ `Unable to find @nextop-os/ui-system package root from ${entrypoint}`
288
+ );
289
+ }
290
+
291
+ return resolvedRoot;
292
+ }
293
+
294
+ async function isUISystemPackageRoot(candidate) {
295
+ try {
296
+ const packageJson = JSON.parse(
297
+ await readFile(join(candidate, "package.json"), "utf8")
298
+ );
299
+
300
+ return packageJson.name === "@nextop-os/ui-system";
301
+ } catch {
302
+ return false;
303
+ }
304
+ }
305
+
306
+ async function findPackageRoot(startDirectory) {
307
+ let current = startDirectory;
308
+
309
+ while (true) {
310
+ if (await isUISystemPackageRoot(current)) {
311
+ return current;
312
+ }
313
+
314
+ const parent = dirname(current);
315
+
316
+ if (parent === current) {
317
+ return null;
318
+ }
319
+
320
+ current = parent;
321
+ }
322
+ }
323
+
324
+ async function pathExists(path) {
325
+ try {
326
+ await access(path, constants.F_OK);
327
+ return true;
328
+ } catch {
329
+ return false;
330
+ }
331
+ }
332
+
333
+ function packageJson(componentId) {
334
+ return `${JSON.stringify(
335
+ {
336
+ name: `nextop-ui-system-preview-${componentId}`,
337
+ private: true,
338
+ type: "module",
339
+ scripts: {
340
+ dev: "vite",
341
+ typecheck: "tsc --noEmit"
342
+ },
343
+ dependencies: {
344
+ "@nextop-os/ui-system": `file:${packageRoot}`,
345
+ "@vitejs/plugin-react": "^5.1.1",
346
+ vite: "^6.4.2",
347
+ typescript: "^5.8.3",
348
+ react: "^19.1.0",
349
+ "react-dom": "^19.1.0"
350
+ },
351
+ devDependencies: {
352
+ "@types/react": "^19.1.6",
353
+ "@types/react-dom": "^19.1.5"
354
+ }
355
+ },
356
+ null,
357
+ 2
358
+ )}\n`;
359
+ }
360
+
361
+ function indexHtml(componentName) {
362
+ return `<!doctype html>
363
+ <html lang="en">
364
+ <head>
365
+ <meta charset="UTF-8" />
366
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
367
+ <title>${componentName} Promotion Preview</title>
368
+ </head>
369
+ <body>
370
+ <div id="root"></div>
371
+ <script type="module" src="/src/main.tsx"></script>
372
+ </body>
373
+ </html>
374
+ `;
375
+ }
376
+
377
+ function viteConfig(serverUrl) {
378
+ return `import react from "@vitejs/plugin-react";
379
+ import { defineConfig } from "vite";
380
+ import { nextopUISystemDev } from "@nextop-os/ui-system/dev-vite";
381
+
382
+ export default defineConfig({
383
+ plugins: [
384
+ nextopUISystemDev({ serverUrl: ${JSON.stringify(serverUrl)} }),
385
+ react()
386
+ ],
387
+ server: {
388
+ host: "127.0.0.1"
389
+ }
390
+ });
391
+ `;
392
+ }
393
+
394
+ function tsconfigJson() {
395
+ return `${JSON.stringify(
396
+ {
397
+ compilerOptions: {
398
+ target: "ES2022",
399
+ useDefineForClassFields: true,
400
+ lib: ["DOM", "DOM.Iterable", "ES2022"],
401
+ allowJs: false,
402
+ skipLibCheck: true,
403
+ esModuleInterop: true,
404
+ allowSyntheticDefaultImports: true,
405
+ strict: true,
406
+ forceConsistentCasingInFileNames: true,
407
+ module: "ESNext",
408
+ moduleResolution: "Bundler",
409
+ resolveJsonModule: true,
410
+ isolatedModules: true,
411
+ noEmit: true,
412
+ jsx: "react-jsx"
413
+ },
414
+ include: ["src", "vite.config.ts"]
415
+ },
416
+ null,
417
+ 2
418
+ )}\n`;
419
+ }
420
+
421
+ function mainTsx() {
422
+ return `import { StrictMode } from "react";
423
+ import { createRoot } from "react-dom/client";
424
+
425
+ import { Preview } from "./Preview";
426
+ import "./style.css";
427
+
428
+ createRoot(document.getElementById("root")!).render(
429
+ <StrictMode>
430
+ <Preview />
431
+ </StrictMode>
432
+ );
433
+ `;
434
+ }
435
+
436
+ function previewTsx(componentName) {
437
+ return `import "@nextop-os/ui-system/styles.css";
438
+
439
+ import {
440
+ Badge,
441
+ Button,
442
+ Card,
443
+ CardContent,
444
+ CardDescription,
445
+ CardHeader,
446
+ CardTitle
447
+ } from "@nextop-os/ui-system/components";
448
+
449
+ import { stateMatrix, type PreviewState } from "./stateMatrix";
450
+
451
+ type ${componentName}DraftProps = {
452
+ state: PreviewState;
453
+ onPrimaryAction?: (stateName: string) => void;
454
+ };
455
+
456
+ function ${componentName}Draft({
457
+ state,
458
+ onPrimaryAction = () => undefined
459
+ }: ${componentName}DraftProps) {
460
+ return (
461
+ <Card className="preview-draft">
462
+ <CardHeader>
463
+ <div className="preview-draft-header">
464
+ <div>
465
+ <CardTitle>{state.name}</CardTitle>
466
+ <CardDescription>
467
+ {state.description || "Draft visual surface for review."}
468
+ </CardDescription>
469
+ </div>
470
+ <Badge variant={state.includedInContract ? "default" : "secondary"}>
471
+ {state.includedInContract ? "in contract" : "excluded"}
472
+ </Badge>
473
+ </div>
474
+ </CardHeader>
475
+ <CardContent className="preview-draft-body">
476
+ <dl>
477
+ <BoundaryRow label="Evidence" values={state.evidence} />
478
+ <BoundaryRow label="Props/Data" values={state.propsData} />
479
+ <BoundaryRow
480
+ label="Host owned"
481
+ values={state.hostOwnedBehavior}
482
+ />
483
+ <BoundaryRow label="Boundary notes" values={state.boundaryNotes} />
484
+ </dl>
485
+ <Button
486
+ type="button"
487
+ size="sm"
488
+ variant="secondary"
489
+ onClick={() => onPrimaryAction(state.name)}
490
+ >
491
+ Draft callback
492
+ </Button>
493
+ </CardContent>
494
+ </Card>
495
+ );
496
+ }
497
+
498
+ function BoundaryRow({
499
+ label,
500
+ values
501
+ }: {
502
+ label: string;
503
+ values: string[];
504
+ }) {
505
+ return (
506
+ <div className="boundary-row">
507
+ <dt>{label}</dt>
508
+ <dd>{values.length > 0 ? values.join("; ") : "None recorded"}</dd>
509
+ </div>
510
+ );
511
+ }
512
+
513
+ export function Preview() {
514
+ return (
515
+ <main className="preview-shell">
516
+ <header className="preview-header">
517
+ <p className="preview-kicker">Nextop UI-system business promotion</p>
518
+ <h1>${componentName} Preview</h1>
519
+ <p>
520
+ Review the state coverage, visual behavior, props boundary, and
521
+ host-owned behavior before promoting this component into
522
+ <code>@nextop-os/ui-system</code>.
523
+ </p>
524
+ </header>
525
+
526
+ <section className="preview-grid">
527
+ {stateMatrix.map((state) => (
528
+ <${componentName}Draft
529
+ key={state.name}
530
+ state={state}
531
+ onPrimaryAction={(stateName) => {
532
+ console.log("Draft callback fired for", stateName);
533
+ }}
534
+ />
535
+ ))}
536
+ </section>
537
+ </main>
538
+ );
539
+ }
540
+ `;
541
+ }
542
+
543
+ function stateMatrixTs(stateMatrix) {
544
+ return `export type PreviewState = {
545
+ name: string;
546
+ evidence: string[];
547
+ propsData: string[];
548
+ hostOwnedBehavior: string[];
549
+ includedInContract: boolean;
550
+ description: string;
551
+ boundaryNotes: string[];
552
+ };
553
+
554
+ export const stateMatrix = ${JSON.stringify(stateMatrix, null, 2)} satisfies PreviewState[];
555
+ `;
556
+ }
557
+
558
+ function styleCss() {
559
+ return `@source "../.nextop-ui-system-dev";
560
+ @source "../node_modules/@nextop-os/ui-system/dist";
561
+
562
+ :root {
563
+ color: hsl(var(--foreground));
564
+ background: hsl(var(--background));
565
+ font-family:
566
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
567
+ sans-serif;
568
+ }
569
+
570
+ body {
571
+ margin: 0;
572
+ min-width: 320px;
573
+ }
574
+
575
+ code {
576
+ border: 1px solid hsl(var(--border));
577
+ border-radius: 4px;
578
+ background: hsl(var(--muted));
579
+ padding: 0.1rem 0.3rem;
580
+ }
581
+
582
+ .preview-shell {
583
+ margin: 0 auto;
584
+ max-width: 1180px;
585
+ padding: 32px;
586
+ }
587
+
588
+ .preview-header {
589
+ max-width: 760px;
590
+ padding-bottom: 24px;
591
+ }
592
+
593
+ .preview-header h1 {
594
+ margin: 0;
595
+ font-size: 32px;
596
+ line-height: 1.1;
597
+ }
598
+
599
+ .preview-header p {
600
+ color: hsl(var(--muted-foreground));
601
+ }
602
+
603
+ .preview-kicker {
604
+ margin: 0 0 8px;
605
+ font-size: 12px;
606
+ font-weight: 700;
607
+ letter-spacing: 0.08em;
608
+ text-transform: uppercase;
609
+ }
610
+
611
+ .preview-grid {
612
+ display: grid;
613
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
614
+ gap: 16px;
615
+ }
616
+
617
+ .preview-draft {
618
+ min-height: 100%;
619
+ }
620
+
621
+ .preview-draft-header {
622
+ display: flex;
623
+ align-items: flex-start;
624
+ justify-content: space-between;
625
+ gap: 16px;
626
+ }
627
+
628
+ .preview-draft-body {
629
+ display: grid;
630
+ gap: 16px;
631
+ }
632
+
633
+ .boundary-row {
634
+ display: grid;
635
+ gap: 4px;
636
+ padding: 10px 0;
637
+ border-top: 1px solid hsl(var(--border));
638
+ }
639
+
640
+ .boundary-row:first-child {
641
+ border-top: 0;
642
+ padding-top: 0;
643
+ }
644
+
645
+ .boundary-row dt {
646
+ color: hsl(var(--muted-foreground));
647
+ font-size: 12px;
648
+ font-weight: 700;
649
+ text-transform: uppercase;
650
+ }
651
+
652
+ .boundary-row dd {
653
+ margin: 0;
654
+ color: hsl(var(--foreground));
655
+ font-size: 14px;
656
+ }
657
+ `;
658
+ }
@@ -4,7 +4,7 @@ import {
4
4
  ChevronRightIcon,
5
5
  ChevronUpIcon,
6
6
  CloseIcon
7
- } from "./chunk-OBW6ALOJ.js";
7
+ } from "./chunk-NFSMZKML.js";
8
8
  import {
9
9
  cn
10
10
  } from "./chunk-DGPY4WP3.js";
@@ -1156,4 +1156,4 @@ export {
1156
1156
  ToastClose,
1157
1157
  ToastViewport
1158
1158
  };
1159
- //# sourceMappingURL=chunk-G6KJIFD2.js.map
1159
+ //# sourceMappingURL=chunk-FT633NLJ.js.map