@autono/open-pages 0.4.0 → 0.6.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.
Files changed (83) hide show
  1. package/dist/{build-L50pau9v.js → build-6hM9Ga2d.js} +2 -2
  2. package/dist/cli/bin.js +14 -5
  3. package/dist/{config-C9or4m4J.d.ts → config-C-sasGou.d.ts} +1 -1
  4. package/dist/{config-CDUdFE8N.js → config-CBbwLHOo.js} +1 -1
  5. package/dist/{dev-CzJYXFOO.js → dev-BO0cHiYy.js} +2 -2
  6. package/dist/index.d.ts +2 -2
  7. package/dist/locale/index.d.ts +1 -1
  8. package/dist/{preview-CPufRmuj.js → preview-sOKhzyWB.js} +2 -2
  9. package/dist/sync-ui-CDydLpY4.js +134 -0
  10. package/dist/{update-CV62NhBT.js → update-D1U8TVwd.js} +13 -1
  11. package/dist/{update-package-Cxt57pz7.js → update-package-D9zws9nc.js} +7 -1
  12. package/dist/vite/index.d.ts +2 -2
  13. package/dist/vite/index.js +2 -2
  14. package/package.json +3 -2
  15. package/skills/create-page/SKILL.md +1 -1
  16. package/skills/page-authoring/SKILL.md +2 -2
  17. package/skills/page-authoring/references/interactivity.md +13 -21
  18. package/workspace/components.json +25 -0
  19. package/workspace/deps.json +24 -0
  20. package/workspace/hooks/use-mobile.ts +19 -0
  21. package/workspace/lib/utils.ts +6 -0
  22. package/workspace/styles/globals.css +135 -0
  23. package/workspace/ui/accordion.tsx +79 -0
  24. package/workspace/ui/alert-dialog.tsx +197 -0
  25. package/workspace/ui/alert.tsx +76 -0
  26. package/workspace/ui/aspect-ratio.tsx +11 -0
  27. package/workspace/ui/attachment.tsx +204 -0
  28. package/workspace/ui/avatar.tsx +110 -0
  29. package/workspace/ui/badge.tsx +49 -0
  30. package/workspace/ui/breadcrumb.tsx +122 -0
  31. package/workspace/ui/bubble.tsx +125 -0
  32. package/workspace/ui/button-group.tsx +83 -0
  33. package/workspace/ui/button.tsx +67 -0
  34. package/workspace/ui/calendar.tsx +222 -0
  35. package/workspace/ui/card.tsx +103 -0
  36. package/workspace/ui/carousel.tsx +240 -0
  37. package/workspace/ui/chart.tsx +373 -0
  38. package/workspace/ui/checkbox.tsx +31 -0
  39. package/workspace/ui/collapsible.tsx +33 -0
  40. package/workspace/ui/combobox.tsx +297 -0
  41. package/workspace/ui/command.tsx +195 -0
  42. package/workspace/ui/context-menu.tsx +261 -0
  43. package/workspace/ui/dialog.tsx +168 -0
  44. package/workspace/ui/direction.tsx +22 -0
  45. package/workspace/ui/drawer.tsx +132 -0
  46. package/workspace/ui/dropdown-menu.tsx +269 -0
  47. package/workspace/ui/empty.tsx +104 -0
  48. package/workspace/ui/field.tsx +236 -0
  49. package/workspace/ui/hover-card.tsx +42 -0
  50. package/workspace/ui/input-group.tsx +156 -0
  51. package/workspace/ui/input-otp.tsx +87 -0
  52. package/workspace/ui/input.tsx +19 -0
  53. package/workspace/ui/item.tsx +196 -0
  54. package/workspace/ui/kbd.tsx +26 -0
  55. package/workspace/ui/label.tsx +22 -0
  56. package/workspace/ui/marker.tsx +69 -0
  57. package/workspace/ui/menubar.tsx +280 -0
  58. package/workspace/ui/message-scroller.tsx +129 -0
  59. package/workspace/ui/message.tsx +92 -0
  60. package/workspace/ui/native-select.tsx +61 -0
  61. package/workspace/ui/navigation-menu.tsx +164 -0
  62. package/workspace/ui/pagination.tsx +129 -0
  63. package/workspace/ui/popover.tsx +87 -0
  64. package/workspace/ui/progress.tsx +31 -0
  65. package/workspace/ui/radio-group.tsx +42 -0
  66. package/workspace/ui/resizable.tsx +50 -0
  67. package/workspace/ui/scroll-area.tsx +53 -0
  68. package/workspace/ui/select.tsx +192 -0
  69. package/workspace/ui/separator.tsx +26 -0
  70. package/workspace/ui/sheet.tsx +147 -0
  71. package/workspace/ui/sidebar.tsx +702 -0
  72. package/workspace/ui/skeleton.tsx +13 -0
  73. package/workspace/ui/slider.tsx +59 -0
  74. package/workspace/ui/sonner.tsx +47 -0
  75. package/workspace/ui/spinner.tsx +10 -0
  76. package/workspace/ui/switch.tsx +33 -0
  77. package/workspace/ui/table.tsx +114 -0
  78. package/workspace/ui/tabs.tsx +90 -0
  79. package/workspace/ui/textarea.tsx +18 -0
  80. package/workspace/ui/toggle-group.tsx +87 -0
  81. package/workspace/ui/toggle.tsx +45 -0
  82. package/workspace/ui/tooltip.tsx +57 -0
  83. /package/dist/{types-C5NCdDT-.d.ts → types-BzxkFHhd.d.ts} +0 -0
@@ -1,7 +1,7 @@
1
1
  import "./design-C13iz9_4.js";
2
2
  import { findPages, loadUserConfig } from "./open-pages-plugin-C0poXLsx.js";
3
- import { createViteConfig } from "./config-CDUdFE8N.js";
4
- import "./update-package-Cxt57pz7.js";
3
+ import { createViteConfig } from "./config-CBbwLHOo.js";
4
+ import "./update-package-D9zws9nc.js";
5
5
  import { readCoreVersion } from "./version-B6AiiGvT.js";
6
6
  import { buildPage } from "./export-DbVwSmKW.js";
7
7
  import path from "node:path";
package/dist/cli/bin.js CHANGED
@@ -47,6 +47,10 @@ async function runSkillsDriftCheck(skillsDir) {
47
47
  rl.close();
48
48
  }
49
49
  }
50
+ function resolveWorkspaceSetDir() {
51
+ const here = path.dirname(fileURLToPath(import.meta.url));
52
+ return path.resolve(here, "..", "..", "workspace");
53
+ }
50
54
  function resolveBuiltinSkillsDir() {
51
55
  const here = path.dirname(fileURLToPath(import.meta.url));
52
56
  return path.resolve(here, "..", "..", "skills");
@@ -57,15 +61,15 @@ async function run(argv) {
57
61
  program.name("open-pages").description("Author web pages — we handle the Vite/React stack.").version(version, "-v, --version", "print version").helpOption("-h, --help", "show help").showHelpAfterError(chalk.dim("(run `open-pages --help` for usage)"));
58
62
  program.command("dev").description("Start the dev server").addOption(new Option("-p, --port <port>", "port to listen on").argParser(parsePort)).addOption(new Option("--host [host]", "expose on the network (optional host)")).option("--open", "open the browser on start").option("--no-skills-check", "skip the built-in skills drift check").action(async (flags) => {
59
63
  if (flags.skillsCheck !== false) await runSkillsDriftCheck(resolveBuiltinSkillsDir());
60
- const { dev } = await import("../dev-CzJYXFOO.js");
64
+ const { dev } = await import("../dev-BO0cHiYy.js");
61
65
  await dev(flags);
62
66
  });
63
67
  program.command("build").description("Build the workspace as a static site").option("--out-dir <dir>", "output directory (defaults to `dist`)").action(async (flags) => {
64
- const { build } = await import("../build-L50pau9v.js");
68
+ const { build } = await import("../build-6hM9Ga2d.js");
65
69
  await build(flags);
66
70
  });
67
71
  program.command("preview").description("Preview the production build").addOption(new Option("-p, --port <port>", "port to listen on").argParser(parsePort)).addOption(new Option("--host [host]", "expose on the network (optional host)")).option("--open", "open the browser on start").action(async (flags) => {
68
- const { preview } = await import("../preview-CPufRmuj.js");
72
+ const { preview } = await import("../preview-sOKhzyWB.js");
69
73
  await preview(flags);
70
74
  });
71
75
  program.command("export").description("Build pages into self-contained static folders (one per page)").argument("[pages...]", "page ids to export (default: all)").option("--out-dir <dir>", "output directory (defaults to `export`)").action(async (pages, flags) => {
@@ -79,8 +83,13 @@ async function run(argv) {
79
83
  const { syncSkills: syncSkills$1 } = await import("../sync-lKEXyPOh.js");
80
84
  await syncSkills$1(resolveBuiltinSkillsDir(), flags);
81
85
  });
82
- program.command("update").description("Update @autono/open-pages to the latest version and sync skills").option("--force", "reinstall even if already on the latest version").option("--no-skills", "skip the skills sync after updating").action(async (flags) => {
83
- const { update } = await import("../update-CV62NhBT.js");
86
+ program.command("sync:ui").description("Sync ui/, lib/, and hooks/ from @autono/open-pages — files you edited are kept").option("--dry-run", "show what would change without writing").option("--force", "overwrite files even where you have edited them").action(async (flags) => {
87
+ const { printUiSummary, syncUi } = await import("../sync-ui-CDydLpY4.js");
88
+ const result = await syncUi(process.cwd(), resolveWorkspaceSetDir(), flags);
89
+ printUiSummary(result, flags.dryRun ?? false);
90
+ });
91
+ program.command("update").description("Update @autono/open-pages to the latest version and sync skills").option("--force", "reinstall even if already on the latest version").option("--no-skills", "skip the skills sync after updating").option("--no-ui", "skip the ui set sync after updating").action(async (flags) => {
92
+ const { update } = await import("../update-D1U8TVwd.js");
84
93
  await update({
85
94
  current: version,
86
95
  ...flags
@@ -1,4 +1,4 @@
1
- import { Locale } from "./types-C5NCdDT-.js";
1
+ import { Locale } from "./types-BzxkFHhd.js";
2
2
 
3
3
  //#region src/config.d.ts
4
4
  type OpenPagesBuildConfig = {
@@ -1,6 +1,6 @@
1
1
  import { defaultDesign } from "./design-C13iz9_4.js";
2
2
  import { PAGE_ID_RE, duplicatePageDir, invalidatePagesModule, loadUserConfig, openPagesPlugin, resolvePageEntry, rmPageDir, updateMetaTitleInSource, validatePageName } from "./open-pages-plugin-C0poXLsx.js";
3
- import { detectPackageManager, fetchLatest, formatCommand, invalidateLatestCache, isOutdated, localOpenPagesCommand, runCommand, updateCommandFor } from "./update-package-Cxt57pz7.js";
3
+ import { detectPackageManager, fetchLatest, formatCommand, invalidateLatestCache, isOutdated, localOpenPagesCommand, runCommand, updateCommandFor } from "./update-package-D9zws9nc.js";
4
4
  import { APP_ROOT, PKG_ROOT, readCoreVersion } from "./version-B6AiiGvT.js";
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
@@ -1,7 +1,7 @@
1
1
  import "./design-C13iz9_4.js";
2
2
  import "./open-pages-plugin-C0poXLsx.js";
3
- import { DEV_SUPERVISED_ENV, RESTART_EXIT_CODE, createViteConfig } from "./config-CDUdFE8N.js";
4
- import "./update-package-Cxt57pz7.js";
3
+ import { DEV_SUPERVISED_ENV, RESTART_EXIT_CODE, createViteConfig } from "./config-CBbwLHOo.js";
4
+ import "./update-package-D9zws9nc.js";
5
5
  import "./version-B6AiiGvT.js";
6
6
  import chalk from "chalk";
7
7
  import path from "node:path";
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Locale, Plural } from "./types-C5NCdDT-.js";
2
- import { OpenPagesConfig } from "./config-C9or4m4J.js";
1
+ import { Locale, Plural } from "./types-BzxkFHhd.js";
2
+ import { OpenPagesConfig } from "./config-C-sasGou.js";
3
3
  import { ComponentType } from "react";
4
4
 
5
5
  //#region src/app/lib/design.d.ts
@@ -1,4 +1,4 @@
1
- import { Locale, Plural } from "../types-C5NCdDT-.js";
1
+ import { Locale, Plural } from "../types-BzxkFHhd.js";
2
2
 
3
3
  //#region src/locale/en.d.ts
4
4
  declare const en: Locale;
@@ -1,7 +1,7 @@
1
1
  import "./design-C13iz9_4.js";
2
2
  import "./open-pages-plugin-C0poXLsx.js";
3
- import { createViteConfig } from "./config-CDUdFE8N.js";
4
- import "./update-package-Cxt57pz7.js";
3
+ import { createViteConfig } from "./config-CBbwLHOo.js";
4
+ import "./update-package-D9zws9nc.js";
5
5
  import "./version-B6AiiGvT.js";
6
6
  import { mergeConfig, preview as preview$1 } from "vite";
7
7
 
@@ -0,0 +1,134 @@
1
+ import chalk from "chalk";
2
+ import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { createHash } from "node:crypto";
5
+ import { existsSync } from "node:fs";
6
+
7
+ //#region src/cli/sync-ui.ts
8
+ const UI_MANIFEST = path.join("ui", ".sync-manifest.json");
9
+ const ENTRIES = [
10
+ "ui",
11
+ "lib",
12
+ "hooks"
13
+ ];
14
+ function hashOf(data) {
15
+ return createHash("sha256").update(data).digest("hex");
16
+ }
17
+ async function collectFiles(dir, prefix = "") {
18
+ const out = [];
19
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
20
+ const rel = prefix ? path.join(prefix, entry.name) : entry.name;
21
+ if (entry.isDirectory()) out.push(...await collectFiles(path.join(dir, entry.name), rel));
22
+ else if (entry.isFile()) out.push(rel);
23
+ }
24
+ return out;
25
+ }
26
+ async function readManifest(file) {
27
+ try {
28
+ const parsed = JSON.parse(await readFile(file, "utf8"));
29
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) return parsed;
30
+ } catch {}
31
+ return null;
32
+ }
33
+ /** Hashes of the canonical set, keyed by workspace-relative path. */
34
+ async function canonicalHashes(canonicalDir) {
35
+ const hashes = {};
36
+ for (const entry of ENTRIES) {
37
+ const root = path.join(canonicalDir, entry);
38
+ if (!existsSync(root)) continue;
39
+ for (const rel of await collectFiles(root)) {
40
+ const wsRel = path.join(entry, rel).split(path.sep).join("/");
41
+ hashes[wsRel] = hashOf(await readFile(path.join(root, rel)));
42
+ }
43
+ }
44
+ return hashes;
45
+ }
46
+ async function syncUi(workspaceDir, canonicalDir, opts = {}) {
47
+ const { dryRun = false, force = false } = opts;
48
+ const result = {
49
+ added: [],
50
+ updated: [],
51
+ unchanged: [],
52
+ skipped: [],
53
+ forced: [],
54
+ removedUpstream: [],
55
+ addedDeps: []
56
+ };
57
+ const manifestPath = path.join(workspaceDir, UI_MANIFEST);
58
+ const manifest = await readManifest(manifestPath) ?? {};
59
+ const next = {};
60
+ const canonical = await canonicalHashes(canonicalDir);
61
+ for (const [rel, canonicalHash] of Object.entries(canonical)) {
62
+ const dst = path.join(workspaceDir, rel);
63
+ const src = path.join(canonicalDir, rel);
64
+ const write = async () => {
65
+ if (dryRun) return;
66
+ await mkdir(path.dirname(dst), { recursive: true });
67
+ await writeFile(dst, await readFile(src));
68
+ };
69
+ if (!existsSync(dst)) {
70
+ await write();
71
+ next[rel] = canonicalHash;
72
+ result.added.push(rel);
73
+ continue;
74
+ }
75
+ const currentHash = hashOf(await readFile(dst));
76
+ if (currentHash === canonicalHash) {
77
+ next[rel] = canonicalHash;
78
+ result.unchanged.push(rel);
79
+ continue;
80
+ }
81
+ const pristine = manifest[rel] !== void 0 && manifest[rel] === currentHash;
82
+ if (pristine || force) {
83
+ await write();
84
+ next[rel] = canonicalHash;
85
+ (pristine ? result.updated : result.forced).push(rel);
86
+ continue;
87
+ }
88
+ if (manifest[rel] !== void 0) next[rel] = manifest[rel];
89
+ result.skipped.push(rel);
90
+ }
91
+ for (const rel of Object.keys(manifest)) if (!(rel in canonical) && existsSync(path.join(workspaceDir, rel))) {
92
+ next[rel] ??= manifest[rel];
93
+ result.removedUpstream.push(rel);
94
+ }
95
+ result.addedDeps = await ensureDeps(workspaceDir, canonicalDir, dryRun);
96
+ if (!dryRun) {
97
+ const sorted = Object.fromEntries(Object.entries(next).sort(([a], [b]) => a.localeCompare(b)));
98
+ await mkdir(path.dirname(manifestPath), { recursive: true });
99
+ await writeFile(manifestPath, `${JSON.stringify(sorted, null, 2)}\n`, "utf8");
100
+ }
101
+ return result;
102
+ }
103
+ async function ensureDeps(workspaceDir, canonicalDir, dryRun) {
104
+ const depsFile = path.join(canonicalDir, "deps.json");
105
+ const pkgFile = path.join(workspaceDir, "package.json");
106
+ if (!existsSync(depsFile) || !existsSync(pkgFile)) return [];
107
+ const wanted = JSON.parse(await readFile(depsFile, "utf8"));
108
+ const pkg = JSON.parse(await readFile(pkgFile, "utf8"));
109
+ const missing = Object.entries(wanted).filter(([name]) => !pkg.dependencies?.[name] && !pkg.devDependencies?.[name]);
110
+ if (missing.length === 0) return [];
111
+ if (!dryRun) {
112
+ pkg.dependencies ??= {};
113
+ for (const [name, range] of missing) pkg.dependencies[name] = range;
114
+ pkg.dependencies = Object.fromEntries(Object.entries(pkg.dependencies).sort(([a], [b]) => a.localeCompare(b)));
115
+ await writeFile(pkgFile, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
116
+ }
117
+ return missing.map(([name]) => name);
118
+ }
119
+ function printUiSummary(result, dryRun) {
120
+ const header = dryRun ? chalk.bold("Dry run — no files written:") : chalk.bold("Synced the workspace ui set:");
121
+ process.stdout.write(`${header}\n`);
122
+ const row = (symbol, rel, label) => process.stdout.write(` ${symbol} ${rel} ${chalk.dim(`(${label})`)}\n`);
123
+ for (const rel of result.added) row(chalk.green("+"), rel, "added");
124
+ for (const rel of result.updated) row(chalk.yellow("~"), rel, "updated");
125
+ for (const rel of result.forced) row(chalk.red("!"), rel, "overwrote your edits (--force)");
126
+ for (const rel of result.skipped) row(chalk.dim("#"), rel, "edited by you — skipped");
127
+ for (const rel of result.removedUpstream) row(chalk.dim("-"), rel, "no longer shipped — yours to keep or delete");
128
+ process.stdout.write(chalk.dim(`\n${result.added.length} added, ${result.updated.length + result.forced.length} updated, ${result.unchanged.length} unchanged, ${result.skipped.length} skipped.\n`));
129
+ if (result.skipped.length > 0 && result.forced.length === 0) process.stdout.write(chalk.dim("Skipped files differ from what was scaffolded. Re-run with --force to overwrite them.\n"));
130
+ if (result.addedDeps.length > 0) process.stdout.write(`${chalk.yellow("!")} Added ${result.addedDeps.map((d) => chalk.bold(d)).join(", ")} to package.json — run your package manager's install.\n`);
131
+ }
132
+
133
+ //#endregion
134
+ export { printUiSummary, syncUi };
@@ -1,4 +1,4 @@
1
- import { PKG, detectPackageManager, fetchLatest, formatCommand, isOutdated, localOpenPagesCommand, runCommand, updateCommandFor } from "./update-package-Cxt57pz7.js";
1
+ import { PKG, detectPackageManager, fetchLatest, formatCommand, installCommandFor, isOutdated, localOpenPagesCommand, runCommand, updateCommandFor } from "./update-package-D9zws9nc.js";
2
2
  import chalk from "chalk";
3
3
  import { readFile } from "node:fs/promises";
4
4
  import path from "node:path";
@@ -30,6 +30,18 @@ async function update(opts) {
30
30
  process.stdout.write(chalk.dim("$ open-pages sync:skills\n"));
31
31
  await runCommand(localOpenPagesCommand(cwd, ["sync:skills"]), cwd, { stdio: "inherit" });
32
32
  }
33
+ if (opts.ui !== false) {
34
+ process.stdout.write(chalk.dim("$ open-pages sync:ui\n"));
35
+ const pkgFile = path.join(cwd, "package.json");
36
+ const before = await readFile(pkgFile, "utf8").catch(() => "");
37
+ await runCommand(localOpenPagesCommand(cwd, ["sync:ui"]), cwd, { stdio: "inherit" });
38
+ const after = await readFile(pkgFile, "utf8").catch(() => "");
39
+ if (after !== before) {
40
+ const reinstall = installCommandFor(packageManager);
41
+ process.stdout.write(chalk.dim(`$ ${formatCommand(reinstall)}\n`));
42
+ await runCommand(reinstall, cwd, { stdio: "inherit" });
43
+ }
44
+ }
33
45
  process.stdout.write(`${chalk.green("✓")} Updated ${PKG} to ${target}. Restart \`open-pages dev\` to use it.\n`);
34
46
  }
35
47
 
@@ -100,6 +100,12 @@ async function updateCommandFor(packageManager, cwd) {
100
100
  * The freshly installed package's own CLI, so skills come from the new
101
101
  * version rather than whatever process is driving the update.
102
102
  */
103
+ function installCommandFor(packageManager) {
104
+ return {
105
+ cmd: packageManager,
106
+ args: ["install"]
107
+ };
108
+ }
103
109
  function localOpenPagesCommand(cwd, args) {
104
110
  const bin = path.join(cwd, "node_modules", ...PKG.split("/"), "bin.js");
105
111
  return {
@@ -149,4 +155,4 @@ async function runCommand(spec, cwd, opts = {}) {
149
155
  }
150
156
 
151
157
  //#endregion
152
- export { PKG, detectPackageManager, fetchLatest, formatCommand, invalidateLatestCache, isOutdated, localOpenPagesCommand, runCommand, updateCommandFor };
158
+ export { PKG, detectPackageManager, fetchLatest, formatCommand, installCommandFor, invalidateLatestCache, isOutdated, localOpenPagesCommand, runCommand, updateCommandFor };
@@ -1,5 +1,5 @@
1
- import "../types-C5NCdDT-.js";
2
- import { OpenPagesConfig } from "../config-C9or4m4J.js";
1
+ import "../types-BzxkFHhd.js";
2
+ import { OpenPagesConfig } from "../config-C-sasGou.js";
3
3
  import { InlineConfig } from "vite";
4
4
 
5
5
  //#region src/vite/config.d.ts
@@ -1,7 +1,7 @@
1
1
  import "../design-C13iz9_4.js";
2
2
  import "../open-pages-plugin-C0poXLsx.js";
3
- import { createViteConfig } from "../config-CDUdFE8N.js";
4
- import "../update-package-Cxt57pz7.js";
3
+ import { createViteConfig } from "../config-CBbwLHOo.js";
4
+ import "../update-package-D9zws9nc.js";
5
5
  import "../version-B6AiiGvT.js";
6
6
 
7
7
  export { createViteConfig };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autono/open-pages",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Runtime and CLI for open-pages — live preview of React and HTML pages, click-to-comment inspector, and static export.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -30,7 +30,8 @@
30
30
  "src/app",
31
31
  "src/locale",
32
32
  "skills",
33
- "README.md"
33
+ "README.md",
34
+ "workspace"
34
35
  ],
35
36
  "scripts": {
36
37
  "build": "tsdown",
@@ -77,7 +77,7 @@ Before writing code, map every planned section to the shadcn components it shoul
77
77
  | --- | --- |
78
78
  | Landing / marketing | `navigation-menu` or plain links + `sheet` for mobile nav, `button` (CTAs), `badge` (eyebrows), `card` (features, pricing tiers), `tabs` or `switch` (billing toggle), `accordion` (FAQ), `separator` |
79
79
  | Dashboard / app UI | `sidebar` (`SidebarProvider` + `SidebarInset`), `table`, `chart`, `card` (KPIs), `tabs`, `select`, `dropdown-menu`, `badge` (status), `skeleton`, `empty` |
80
- | Form / signup | `form` (react-hook-form + zod) or `field`, `input`, `select`, `checkbox`, `radio-group`, `switch`, `textarea`, `button`, `alert` |
80
+ | Form / signup | `field`, `input`, `select`, `checkbox`, `radio-group`, `switch`, `textarea`, `button`, `alert` |
81
81
  | Docs / long-form | `sidebar` or a sticky `scroll-area` TOC, `breadcrumb`, prose in a `max-w-[65ch]` column, `kbd`, `table`, `alert` (callouts) |
82
82
  | Portfolio / gallery | `carousel`, `aspect-ratio`, `card`, `dialog` (lightbox), `hover-card` |
83
83
  | Internal tool | `command` (palette), `combobox`, `resizable`, `context-menu`, `tooltip`, `sonner` (toasts), `alert-dialog` (confirm) |
@@ -25,10 +25,10 @@ Every workspace is a shadcn/ui project out of the box. Nothing needs installing:
25
25
  | `lib/utils.ts` | `cn()` for merging class names. Import as `@/lib/utils`. |
26
26
  | `hooks/use-mobile.ts` | `useIsMobile()` breakpoint hook. Import as `@/hooks/use-mobile`. |
27
27
  | `styles/globals.css` | Tailwind v4 entry: `@source` for `pages/ ui/ lib/ hooks/ themes/`, the `dark` variant, `:root`/`.dark` OKLCH tokens, `@theme inline`, base layer. Loaded into every page automatically. |
28
- | `components.json` | shadcn config (style `new-york`, base `radix`, aliases `@/ui`, `@/lib/utils`, `@/hooks`, `@/components`). Activates the `shadcn` skill and CLI lookups. |
28
+ | `components.json` | shadcn config (style `radix-nova`, aliases `@/ui`, `@/lib/utils`, `@/hooks`, `@/components`). Activates the `shadcn` skill and CLI lookups. |
29
29
  | `themes/<id>.css` | Optional token overrides a page opts into with `meta.theme` (see Themes). |
30
30
 
31
- Installed deps you may import directly: `lucide-react` (icons), `radix-ui`, `class-variance-authority`, `clsx`, `tailwind-merge`, `cmdk`, `sonner`, `vaul`, `recharts`, `react-day-picker`, `date-fns`, `react-hook-form`, `zod`, `@hookform/resolvers`, `input-otp`, `embla-carousel-react`, `react-resizable-panels`, `next-themes`. Nothing else.
31
+ Installed deps you may import directly: `lucide-react` (icons), `radix-ui`, `class-variance-authority`, `clsx`, `tailwind-merge`, `cmdk`, `sonner`, `vaul`, `recharts`, `react-day-picker`, `date-fns`, `input-otp`, `embla-carousel-react`, `react-resizable-panels`, `next-themes`. Nothing else.
32
32
 
33
33
  `npx shadcn@latest add <item>` is only for **blocks** (`dashboard-01`, `login-03`, …) or third-party registries; every core component is already under `ui/`. For lookups use `npx shadcn@latest docs <component> --json`, `view`, `search` — details in the `shadcn` skill.
34
34
 
@@ -35,32 +35,24 @@ const [billing, setBilling] = useState<'monthly' | 'yearly'>('monthly');
35
35
 
36
36
  Simple forms: `<form onSubmit>` with `@/ui/field`, `@/ui/input`, `@/ui/label`,
37
37
  `@/ui/select`, `@/ui/checkbox`, `@/ui/textarea`, `@/ui/button`. Validated
38
- forms: `react-hook-form` + `zod` through `@/ui/form`:
38
+ forms: native form state (or your own) presented with `@/ui/field`:
39
39
 
40
40
  ```tsx
41
- import { zodResolver } from '@hookform/resolvers/zod';
42
- import { useForm } from 'react-hook-form';
43
- import { z } from 'zod';
44
41
  import { Button } from '@/ui/button';
45
- import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/ui/form';
42
+ import { Field, FieldDescription, FieldError, FieldGroup, FieldLabel } from '@/ui/field';
46
43
  import { Input } from '@/ui/input';
47
44
 
48
- const schema = z.object({ email: z.string().email('Enter a valid work email') });
49
-
50
- const form = useForm<z.infer<typeof schema>>({ resolver: zodResolver(schema), defaultValues: { email: '' } });
51
-
52
- <Form {...form}>
53
- <form onSubmit={form.handleSubmit((values) => setSubmitted(values))} className="flex flex-col gap-4">
54
- <FormField control={form.control} name="email" render={({ field }) => (
55
- <FormItem>
56
- <FormLabel>Work email</FormLabel>
57
- <FormControl><Input type="email" autoComplete="email" {...field} /></FormControl>
58
- <FormMessage />
59
- </FormItem>
60
- )} />
61
- <Button type="submit">Join the waitlist</Button>
62
- </form>
63
- </Form>
45
+ <form onSubmit={handleSubmit}>
46
+ <FieldGroup>
47
+ <Field>
48
+ <FieldLabel htmlFor="email">Work email</FieldLabel>
49
+ <Input id="email" name="email" type="email" required />
50
+ <FieldDescription>We never share it.</FieldDescription>
51
+ {error && <FieldError>{error}</FieldError>}
52
+ </Field>
53
+ <Button type="submit">Create account</Button>
54
+ </FieldGroup>
55
+ </form>
64
56
  ```
65
57
 
66
58
  - Every input has a label (`<Label htmlFor>` or `<FormLabel>`), a `name`, and
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-nova",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "styles/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "lucide",
14
+ "rtl": false,
15
+ "aliases": {
16
+ "components": "@/components",
17
+ "utils": "@/lib/utils",
18
+ "ui": "@/ui",
19
+ "lib": "@/lib",
20
+ "hooks": "@/hooks"
21
+ },
22
+ "menuColor": "default",
23
+ "menuAccent": "subtle",
24
+ "registries": {}
25
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "react": "^19.2.0",
3
+ "react-dom": "^19.2.0",
4
+ "@base-ui/react": "^1.7.0",
5
+ "@fontsource-variable/geist": "^5.3.0",
6
+ "@shadcn/react": "^0.3.0",
7
+ "class-variance-authority": "^0.7.1",
8
+ "clsx": "^2.1.1",
9
+ "cmdk": "^1.1.1",
10
+ "date-fns": "^4.4.0",
11
+ "embla-carousel-react": "^8.6.0",
12
+ "input-otp": "^1.5.0",
13
+ "lucide-react": "^1.37.0",
14
+ "next-themes": "^0.4.6",
15
+ "radix-ui": "^1.6.7",
16
+ "react-day-picker": "^10.0.1",
17
+ "react-resizable-panels": "^4.12.3",
18
+ "recharts": "^3.8.0",
19
+ "shadcn": "^4.19.0",
20
+ "sonner": "^2.0.8",
21
+ "tailwind-merge": "^3.6.0",
22
+ "tw-animate-css": "^1.4.0",
23
+ "vaul": "^1.1.2"
24
+ }
@@ -0,0 +1,19 @@
1
+ import * as React from "react"
2
+
3
+ const MOBILE_BREAKPOINT = 768
4
+
5
+ export function useIsMobile() {
6
+ const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
7
+
8
+ React.useEffect(() => {
9
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
10
+ const onChange = () => {
11
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
12
+ }
13
+ mql.addEventListener("change", onChange)
14
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
15
+ return () => mql.removeEventListener("change", onChange)
16
+ }, [])
17
+
18
+ return !!isMobile
19
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,135 @@
1
+ @import "tailwindcss" source(none);
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+ @import "@fontsource-variable/geist";
5
+ @source "../pages";
6
+ @source "../ui";
7
+ @source "../lib";
8
+ @source "../hooks";
9
+ @source "../themes";
10
+
11
+ @custom-variant dark (&:is(.dark *));
12
+
13
+ @theme inline {
14
+ --font-heading: var(--font-sans);
15
+ --font-sans: 'Geist Variable', sans-serif;
16
+ --color-sidebar-ring: var(--sidebar-ring);
17
+ --color-sidebar-border: var(--sidebar-border);
18
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
19
+ --color-sidebar-accent: var(--sidebar-accent);
20
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
21
+ --color-sidebar-primary: var(--sidebar-primary);
22
+ --color-sidebar-foreground: var(--sidebar-foreground);
23
+ --color-sidebar: var(--sidebar);
24
+ --color-chart-5: var(--chart-5);
25
+ --color-chart-4: var(--chart-4);
26
+ --color-chart-3: var(--chart-3);
27
+ --color-chart-2: var(--chart-2);
28
+ --color-chart-1: var(--chart-1);
29
+ --color-ring: var(--ring);
30
+ --color-input: var(--input);
31
+ --color-border: var(--border);
32
+ --color-destructive: var(--destructive);
33
+ --color-accent-foreground: var(--accent-foreground);
34
+ --color-accent: var(--accent);
35
+ --color-muted-foreground: var(--muted-foreground);
36
+ --color-muted: var(--muted);
37
+ --color-secondary-foreground: var(--secondary-foreground);
38
+ --color-secondary: var(--secondary);
39
+ --color-primary-foreground: var(--primary-foreground);
40
+ --color-primary: var(--primary);
41
+ --color-popover-foreground: var(--popover-foreground);
42
+ --color-popover: var(--popover);
43
+ --color-card-foreground: var(--card-foreground);
44
+ --color-card: var(--card);
45
+ --color-foreground: var(--foreground);
46
+ --color-background: var(--background);
47
+ --radius-sm: calc(var(--radius) * 0.6);
48
+ --radius-md: calc(var(--radius) * 0.8);
49
+ --radius-lg: var(--radius);
50
+ --radius-xl: calc(var(--radius) * 1.4);
51
+ --radius-2xl: calc(var(--radius) * 1.8);
52
+ --radius-3xl: calc(var(--radius) * 2.2);
53
+ --radius-4xl: calc(var(--radius) * 2.6);
54
+ }
55
+
56
+ :root {
57
+ --background: oklch(1 0 0);
58
+ --foreground: oklch(0.145 0 0);
59
+ --card: oklch(1 0 0);
60
+ --card-foreground: oklch(0.145 0 0);
61
+ --popover: oklch(1 0 0);
62
+ --popover-foreground: oklch(0.145 0 0);
63
+ --primary: oklch(0.205 0 0);
64
+ --primary-foreground: oklch(0.985 0 0);
65
+ --secondary: oklch(0.97 0 0);
66
+ --secondary-foreground: oklch(0.205 0 0);
67
+ --muted: oklch(0.97 0 0);
68
+ --muted-foreground: oklch(0.556 0 0);
69
+ --accent: oklch(0.97 0 0);
70
+ --accent-foreground: oklch(0.205 0 0);
71
+ --destructive: oklch(0.577 0.245 27.325);
72
+ --border: oklch(0.922 0 0);
73
+ --input: oklch(0.922 0 0);
74
+ --ring: oklch(0.708 0 0);
75
+ --chart-1: oklch(0.87 0 0);
76
+ --chart-2: oklch(0.556 0 0);
77
+ --chart-3: oklch(0.439 0 0);
78
+ --chart-4: oklch(0.371 0 0);
79
+ --chart-5: oklch(0.269 0 0);
80
+ --radius: 0.625rem;
81
+ --sidebar: oklch(0.985 0 0);
82
+ --sidebar-foreground: oklch(0.145 0 0);
83
+ --sidebar-primary: oklch(0.205 0 0);
84
+ --sidebar-primary-foreground: oklch(0.985 0 0);
85
+ --sidebar-accent: oklch(0.97 0 0);
86
+ --sidebar-accent-foreground: oklch(0.205 0 0);
87
+ --sidebar-border: oklch(0.922 0 0);
88
+ --sidebar-ring: oklch(0.708 0 0);
89
+ }
90
+
91
+ .dark {
92
+ --background: oklch(0.145 0 0);
93
+ --foreground: oklch(0.985 0 0);
94
+ --card: oklch(0.205 0 0);
95
+ --card-foreground: oklch(0.985 0 0);
96
+ --popover: oklch(0.205 0 0);
97
+ --popover-foreground: oklch(0.985 0 0);
98
+ --primary: oklch(0.922 0 0);
99
+ --primary-foreground: oklch(0.205 0 0);
100
+ --secondary: oklch(0.269 0 0);
101
+ --secondary-foreground: oklch(0.985 0 0);
102
+ --muted: oklch(0.269 0 0);
103
+ --muted-foreground: oklch(0.708 0 0);
104
+ --accent: oklch(0.269 0 0);
105
+ --accent-foreground: oklch(0.985 0 0);
106
+ --destructive: oklch(0.704 0.191 22.216);
107
+ --border: oklch(1 0 0 / 10%);
108
+ --input: oklch(1 0 0 / 15%);
109
+ --ring: oklch(0.556 0 0);
110
+ --chart-1: oklch(0.87 0 0);
111
+ --chart-2: oklch(0.556 0 0);
112
+ --chart-3: oklch(0.439 0 0);
113
+ --chart-4: oklch(0.371 0 0);
114
+ --chart-5: oklch(0.269 0 0);
115
+ --sidebar: oklch(0.205 0 0);
116
+ --sidebar-foreground: oklch(0.985 0 0);
117
+ --sidebar-primary: oklch(0.488 0.243 264.376);
118
+ --sidebar-primary-foreground: oklch(0.985 0 0);
119
+ --sidebar-accent: oklch(0.269 0 0);
120
+ --sidebar-accent-foreground: oklch(0.985 0 0);
121
+ --sidebar-border: oklch(1 0 0 / 10%);
122
+ --sidebar-ring: oklch(0.556 0 0);
123
+ }
124
+
125
+ @layer base {
126
+ * {
127
+ @apply border-border outline-ring/50;
128
+ }
129
+ body {
130
+ @apply bg-background text-foreground;
131
+ }
132
+ html {
133
+ @apply font-sans;
134
+ }
135
+ }