@viliha/vui-ui 1.1.8 → 1.2.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 (137) hide show
  1. package/AGENT.md +79 -9
  2. package/README.md +60 -0
  3. package/bin/vui.mjs +211 -0
  4. package/package.json +10 -2
  5. package/template/.env.example +20 -0
  6. package/template/app/(app)/branches/branches-table.tsx +69 -0
  7. package/template/app/(app)/branches/page.tsx +13 -0
  8. package/template/app/(app)/businesses/businesses-table.tsx +39 -0
  9. package/template/app/(app)/businesses/page.tsx +13 -0
  10. package/template/app/(app)/calendar/page.tsx +725 -0
  11. package/template/app/(app)/charts/charts-content.tsx +190 -0
  12. package/template/app/(app)/charts/layout.tsx +8 -0
  13. package/template/app/(app)/charts/page.tsx +44 -0
  14. package/template/app/(app)/chat/layout.tsx +14 -0
  15. package/template/app/(app)/chat/page.tsx +311 -0
  16. package/template/app/(app)/components/layout.tsx +8 -0
  17. package/template/app/(app)/components/page.tsx +464 -0
  18. package/template/app/(app)/crm/companies/companies-table.tsx +56 -0
  19. package/template/app/(app)/crm/companies/page.tsx +13 -0
  20. package/template/app/(app)/crm/opportunities/opportunities-board.tsx +484 -0
  21. package/template/app/(app)/crm/opportunities/page.tsx +13 -0
  22. package/template/app/(app)/crm/people/page.tsx +13 -0
  23. package/template/app/(app)/crm/people/people-table.tsx +50 -0
  24. package/template/app/(app)/dashboard/layout.tsx +8 -0
  25. package/template/app/(app)/dashboard/page.tsx +239 -0
  26. package/template/app/(app)/departments/departments-table.tsx +55 -0
  27. package/template/app/(app)/departments/page.tsx +13 -0
  28. package/template/app/(app)/employees/employees-table.tsx +64 -0
  29. package/template/app/(app)/employees/page.tsx +13 -0
  30. package/template/app/(app)/forms/layout.tsx +8 -0
  31. package/template/app/(app)/forms/page.tsx +388 -0
  32. package/template/app/(app)/layout.tsx +74 -0
  33. package/template/app/(app)/markets/markets-table.tsx +76 -0
  34. package/template/app/(app)/markets/page.tsx +13 -0
  35. package/template/app/(app)/organizations/edit/page.tsx +72 -0
  36. package/template/app/(app)/organizations/new/page.tsx +46 -0
  37. package/template/app/(app)/organizations/organizations-config.tsx +97 -0
  38. package/template/app/(app)/organizations/organizations-table.tsx +42 -0
  39. package/template/app/(app)/organizations/page.tsx +13 -0
  40. package/template/app/(app)/settings/layout.tsx +8 -0
  41. package/template/app/(app)/settings/page.tsx +255 -0
  42. package/template/app/(app)/steps/page.tsx +263 -0
  43. package/template/app/(app)/support/layout.tsx +14 -0
  44. package/template/app/(app)/support/page.tsx +345 -0
  45. package/template/app/(app)/system/cities/cities-table.tsx +34 -0
  46. package/template/app/(app)/system/cities/page.tsx +13 -0
  47. package/template/app/(app)/system/countries/countries-table.tsx +34 -0
  48. package/template/app/(app)/system/countries/page.tsx +13 -0
  49. package/template/app/(app)/system/currencies/currencies-table.tsx +34 -0
  50. package/template/app/(app)/system/currencies/page.tsx +13 -0
  51. package/template/app/(app)/system/languages/languages-table.tsx +32 -0
  52. package/template/app/(app)/system/languages/page.tsx +13 -0
  53. package/template/app/(app)/system/regions/page.tsx +13 -0
  54. package/template/app/(app)/system/regions/regions-table.tsx +32 -0
  55. package/template/app/(app)/users/page.tsx +13 -0
  56. package/template/app/(app)/users/users-table.tsx +97 -0
  57. package/template/app/_components/app-sidebar.tsx +565 -0
  58. package/template/app/_components/auth.tsx +184 -0
  59. package/template/app/_components/breadcrumbs.tsx +26 -0
  60. package/template/app/_components/global-search.tsx +209 -0
  61. package/template/app/_components/logo.tsx +49 -0
  62. package/template/app/_components/nav-config.ts +138 -0
  63. package/template/app/_components/open-tabs.tsx +530 -0
  64. package/template/app/_components/quick-actions.tsx +140 -0
  65. package/template/app/_components/route-meta.ts +137 -0
  66. package/template/app/_components/set-page-title.tsx +15 -0
  67. package/template/app/_components/showcase.tsx +29 -0
  68. package/template/app/_components/stat-card.tsx +56 -0
  69. package/template/app/_components/theme-toggle.tsx +46 -0
  70. package/template/app/_components/top-bar.tsx +109 -0
  71. package/template/app/_components/user-menu.tsx +109 -0
  72. package/template/app/_components/wordmark.tsx +45 -0
  73. package/template/app/apple-icon.png +0 -0
  74. package/template/app/auth/forgot-password/page.tsx +102 -0
  75. package/template/app/auth/layout.tsx +43 -0
  76. package/template/app/auth/page.tsx +13 -0
  77. package/template/app/auth/reset-password/page.tsx +108 -0
  78. package/template/app/auth/signin/page.tsx +238 -0
  79. package/template/app/auth/signup/page.tsx +159 -0
  80. package/template/app/auth/verify/page.tsx +86 -0
  81. package/template/app/error.tsx +56 -0
  82. package/template/app/globals.css +6 -0
  83. package/template/app/icon.png +0 -0
  84. package/template/app/icon.svg +4 -0
  85. package/template/app/layout.tsx +103 -0
  86. package/template/app/not-found.tsx +29 -0
  87. package/template/app/onboarding/layout.tsx +31 -0
  88. package/template/app/onboarding/page.tsx +399 -0
  89. package/template/app/page.tsx +13 -0
  90. package/template/app/register-business/layout.tsx +30 -0
  91. package/template/app/register-business/page.tsx +234 -0
  92. package/template/components/ui/accordion.tsx +66 -0
  93. package/template/components/ui/alert-dialog.tsx +196 -0
  94. package/template/components/ui/alert.tsx +66 -0
  95. package/template/components/ui/aspect-ratio.tsx +11 -0
  96. package/template/components/ui/avatar.tsx +109 -0
  97. package/template/components/ui/badge.tsx +48 -0
  98. package/template/components/ui/breadcrumb.tsx +109 -0
  99. package/template/components/ui/button.tsx +64 -0
  100. package/template/components/ui/calendar.tsx +220 -0
  101. package/template/components/ui/card.tsx +92 -0
  102. package/template/components/ui/checkbox.tsx +32 -0
  103. package/template/components/ui/collapsible.tsx +33 -0
  104. package/template/components/ui/command.tsx +184 -0
  105. package/template/components/ui/dialog.tsx +158 -0
  106. package/template/components/ui/dropdown-menu.tsx +257 -0
  107. package/template/components/ui/form.tsx +167 -0
  108. package/template/components/ui/hover-card.tsx +44 -0
  109. package/template/components/ui/input-otp.tsx +77 -0
  110. package/template/components/ui/input.tsx +21 -0
  111. package/template/components/ui/label.tsx +24 -0
  112. package/template/components/ui/popover.tsx +89 -0
  113. package/template/components/ui/progress.tsx +31 -0
  114. package/template/components/ui/radio-group.tsx +45 -0
  115. package/template/components/ui/scroll-area.tsx +58 -0
  116. package/template/components/ui/select.tsx +190 -0
  117. package/template/components/ui/separator.tsx +28 -0
  118. package/template/components/ui/sheet.tsx +143 -0
  119. package/template/components/ui/skeleton.tsx +13 -0
  120. package/template/components/ui/slider.tsx +63 -0
  121. package/template/components/ui/sonner.tsx +40 -0
  122. package/template/components/ui/switch.tsx +35 -0
  123. package/template/components/ui/table.tsx +116 -0
  124. package/template/components/ui/tabs.tsx +91 -0
  125. package/template/components/ui/textarea.tsx +18 -0
  126. package/template/components/ui/toggle-group.tsx +83 -0
  127. package/template/components/ui/toggle.tsx +47 -0
  128. package/template/components/ui/tooltip.tsx +57 -0
  129. package/template/lib/auth-demo.ts +94 -0
  130. package/template/lib/crm-data.ts +80 -0
  131. package/template/lib/demo-data.ts +267 -0
  132. package/template/lib/mock-data.ts +169 -0
  133. package/template/lib/org-store.ts +50 -0
  134. package/template/lib/seo.ts +188 -0
  135. package/template/lib/utils.ts +3 -0
  136. package/template/next.config.mjs +13 -0
  137. package/template/postcss.config.mjs +7 -0
package/AGENT.md CHANGED
@@ -67,6 +67,23 @@ Never duplicate functionality already provided by VUI.
67
67
 
68
68
  # Installation Requirements
69
69
 
70
+ ## Scaffold the shell + demo (`init`)
71
+
72
+ The package ships the components; the **app shell** (layout, sidebar, open tabs,
73
+ command palette, nav config, logo) and **demo pages** are scaffolded with:
74
+
75
+ ```bash
76
+ npx @viliha/vui-ui init # interactive: fresh vs existing, with/without demo
77
+ ```
78
+
79
+ Files land in the consumer's repo (they own them). **Fresh project:** writes
80
+ config too, runs out of the box. **Existing project:** `init --existing` never
81
+ overwrites config — it adds the shell/pages and prints the merge steps
82
+ (`transpilePackages`, the `theme.css` import, the `@/*` alias, `import
83
+ "./globals.css"`). Flags: `--fresh` / `--existing` / `--demo` / `--no-demo` /
84
+ `--yes` / `--force` / `--dry-run`. If you only need components, skip `init` and
85
+ just do the setup below.
86
+
70
87
  VUI ships as TypeScript source.
71
88
 
72
89
  ## Next.js
@@ -207,16 +224,69 @@ One trail, **derived from the route — never hand-written per page.**
207
224
 
208
225
  To reorder or rename, edit the nav config; the trail follows automatically.
209
226
 
210
- ## Open tabs
227
+ ## Sidebar: sections & collapsible groups
228
+
229
+ The sidebar is driven by one `NAV` config (copy `nav-config.ts`). There are
230
+ **two grouping shapes — use them, don't invent a third:**
231
+
232
+ - **Section** (`NavSection`, `{ title?, items }`) — a top-level band with an
233
+ optional `title` heading. Items are **always visible** (no collapse). Use it to
234
+ cluster related pages under a label (e.g. *Records*, *System*). The first
235
+ section usually has no title.
236
+ - **Collapsible group** (`NavGroup`, an entry with `children`) — a parent row with
237
+ a chevron that **hides/unhides** its nested links; it auto-opens when a child is
238
+ the active route. Use it for a set of sub-pages under one parent (e.g. *Auth*,
239
+ *CRM*, *System*) to keep the sidebar short. A group parent has no page of its
240
+ own — its breadcrumb points at its first child.
241
+
242
+ ```ts
243
+ export const NAV: NavSection[] = [
244
+ { items: [ // untitled section
245
+ { label: "Home", href: "/dashboard", icon: Home },
246
+ { label: "Auth", icon: Lock, children: [ // collapsible group (hide/unhide)
247
+ { label: "Sign in", href: "/auth/signin", icon: LogIn },
248
+ ]},
249
+ ]},
250
+ { title: "Records", items: [ // titled section (static band)
251
+ { label: "Organizations", href: "/organizations", icon: Building2 },
252
+ ]},
253
+ ];
254
+ ```
255
+
256
+ When adding a page, always add it to `NAV` and mirror its color in
257
+ `route-meta.ts` — the sidebar, breadcrumbs, and tabs all derive from these.
258
+
259
+ ## Open tabs (keep-alive)
260
+
261
+ Enterprise apps keep several pages open at once. The reference app ships a
262
+ browser-style **tab strip** under the top bar — a reference-app pattern (copy
263
+ `open-tabs.tsx`), not a package export. **This is a first-class feature; wire it
264
+ in — a fresh install won't have it.**
265
+
266
+ It is **keep-alive**: every opened page stays mounted (inactive ones hidden), so
267
+ switching is instant (no remount/flash) and each page keeps its live state
268
+ (scroll, form input, filters). Mount the three pieces once in your app layout:
269
+
270
+ ```tsx
271
+ <OpenTabsProvider>
272
+ <AppSidebar /> <TopBar />
273
+ <TabStrip /> {/* the strip, under the top bar */}
274
+ <KeepAliveTabs>{children}</KeepAliveTabs> {/* keeps open pages mounted */}
275
+ </OpenTabsProvider>
276
+ ```
211
277
 
212
- Enterprise apps often keep several pages open at once. The reference app ships a
213
- browser-style **tab strip** under the top bar (a reference-app pattern — copy
214
- `open-tabs.tsx`): an `OpenTabsProvider` tracks opened routes, switching is a
215
- router push, and the list persists in `sessionStorage`. Tab labels/icons come
216
- from the same nav config as the sidebar. ⌘/Ctrl-click a nav item opens a
217
- background tab; expose an `openTab(href, { background })` for custom "open in new
218
- tab" buttons. Keep it a navigation-tab model (don't try to keep every page
219
- mounted) unless you specifically need live state preserved across tabs.
278
+ - Labels/icons/colors derive from `nav-config.ts` + `route-meta.ts` no per-tab wiring.
279
+ - The list persists in `sessionStorage`, capped by `NEXT_PUBLIC_MAX_TABS`
280
+ (default 5; oldest FIFO-evicted with a warning).
281
+ - Tabs are drag-reorderable and right-click-taggable with one of seven colors.
282
+ - ⌘/Ctrl-click a nav item opens a **background tab**; call
283
+ `useOpenTabs().openTab(href, { background: true })` for custom "open in new tab"
284
+ buttons. `tabKey` normalizes trailing slashes so `/foo` and `/foo/` are one tab.
285
+
286
+ Keep-alive relies on a static-export (all-client) shell. If your app renders
287
+ server components per route, either adopt the demo's static-export shell or fall
288
+ back to a plain navigation-tab model (router push per tab) — the strip,
289
+ persistence, and nav-config wiring stay identical.
220
290
 
221
291
  ---
222
292
 
package/README.md CHANGED
@@ -38,6 +38,66 @@ export default nextConfig;
38
38
 
39
39
  (Vite and most bundlers need no extra config.)
40
40
 
41
+ ## Scaffold the full app + demo (`init`)
42
+
43
+ The package ships the **components**; the **app shell** (layout, sidebar,
44
+ browser-style **open tabs**, command palette, nav config, logo) and the **demo
45
+ pages** live in a one-shot scaffolder:
46
+
47
+ ```bash
48
+ npx @viliha/vui-ui init
49
+ ```
50
+
51
+ It's **interactive** — it asks whether this is a **fresh** or **existing**
52
+ project and whether to include the **demo pages** (dashboard, CRM, calendar,
53
+ chat, support, settings, auth). Files are copied into *your* repo, so you own and
54
+ edit them. It's **non-destructive**: existing files are skipped.
55
+
56
+ ```
57
+ Flags (for CI / agents, skip the prompts):
58
+ --fresh | --existing project type
59
+ --demo | --no-demo include demo pages (default: with demo)
60
+ --yes, -y accept defaults (fresh, with demo)
61
+ --force overwrite existing files
62
+ --dry-run preview without writing
63
+ ```
64
+
65
+ After it runs, install the peer deps it prints, then `npm run dev` → `/dashboard`.
66
+
67
+ ### Fresh project (recommended)
68
+
69
+ Start from a `create-next-app` base. `init --fresh` also writes `next.config.mjs`,
70
+ `app/globals.css`, and the root layout, so the demo runs out of the box.
71
+
72
+ ### ⚠️ Existing project — read this first
73
+
74
+ Adding VUI to an app you already have needs care. **`init --existing` never
75
+ overwrites your config** — it adds the shell + pages under `app/(app)/` and
76
+ `app/_components/`, then prints the four things to merge yourself:
77
+
78
+ 1. **`next.config`** — add `transpilePackages: ["@viliha/vui-ui"]`.
79
+ (Optional, for the open-tabs *keep-alive*: `output: "export"`,
80
+ `images: { unoptimized: true }`, `trailingSlash: true`.)
81
+ 2. **`app/globals.css`** — add:
82
+ ```css
83
+ @import "tailwindcss";
84
+ @import "@viliha/vui-ui/theme.css";
85
+ ```
86
+ 3. **`tsconfig.json`** — the scaffold imports via `@/*`; map it to your root:
87
+ `"compilerOptions": { "paths": { "@/*": ["./*"] } }`.
88
+ 4. **Root `app/layout.tsx`** — `import "./globals.css"` (and mount fonts to match
89
+ the demo's look).
90
+
91
+ Run **`npx @viliha/vui-ui init --existing --dry-run`** first to see exactly what
92
+ it will add. If you only want the components (not the shell/pages), **skip `init`
93
+ entirely** — the [Setup](#setup) above is all you need, then import from
94
+ `@viliha/vui-ui/*`.
95
+
96
+ > **Note on the theme in an existing app:** VUI owns its design tokens in
97
+ > `theme.css`. If your app already defines shadcn/ui or other CSS variables with
98
+ > the same names, import `theme.css` **last** and remove the duplicates, or the
99
+ > two token sets will fight. On a fresh project this never comes up.
100
+
41
101
  ## Usage
42
102
 
43
103
  Each component is its own entry point, so you only ship what you use:
package/bin/vui.mjs ADDED
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ // @viliha/vui-ui CLI — scaffolds the app shell + demo pages into a consumer's
3
+ // Next.js project (shadcn-style: the files land in YOUR repo, you own them).
4
+ //
5
+ // npx @viliha/vui-ui init [options]
6
+ //
7
+ // Interactive by default: asks whether this is a fresh or existing project and
8
+ // whether to include the demo pages. Non-interactive with flags (for CI/agents).
9
+ import { copyFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
10
+ import { dirname, join, relative, sep } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { createInterface } from "node:readline/promises";
13
+ import { stdin, stdout } from "node:process";
14
+
15
+ const TEMPLATE = fileURLToPath(new URL("../template/", import.meta.url));
16
+ const args = process.argv.slice(2);
17
+ const cmd = args[0];
18
+ const has = (f) => args.includes(f);
19
+
20
+ function usage() {
21
+ console.log(`@viliha/vui-ui
22
+
23
+ Usage:
24
+ npx @viliha/vui-ui init [options]
25
+
26
+ init Scaffold the VUI app shell (+ optional demo pages) into this
27
+ project. Interactive unless flags are given. Non-destructive:
28
+ existing files are skipped (use --force to overwrite).
29
+
30
+ Options:
31
+ --fresh Treat as a brand-new project (copies config too).
32
+ --existing Treat as an existing project (skips config; prints merge steps).
33
+ --demo Include the demo pages (dashboard, CRM, calendar, chat, …).
34
+ --no-demo Shell + dashboard only, no other demo pages.
35
+ --yes, -y Accept defaults without prompting (fresh, with demo).
36
+ --force Overwrite files that already exist.
37
+ --dry-run Show what would be written without touching the disk.
38
+ `);
39
+ }
40
+
41
+ if (cmd !== "init") {
42
+ usage();
43
+ process.exit(cmd ? 1 : 0);
44
+ }
45
+ if (!existsSync(TEMPLATE)) {
46
+ console.error(
47
+ "vui: bundled template not found. Reinstall @viliha/vui-ui (the template " +
48
+ "ships with the published package).",
49
+ );
50
+ process.exit(1);
51
+ }
52
+
53
+ const dry = has("--dry-run");
54
+ const force = has("--force");
55
+
56
+ async function ask(question, def) {
57
+ if (has("--yes") || has("-y") || !stdin.isTTY) return def;
58
+ const rl = createInterface({ input: stdin, output: stdout });
59
+ const a = (await rl.question(question)).trim().toLowerCase();
60
+ rl.close();
61
+ return a || def;
62
+ }
63
+
64
+ // Config/root files that an existing project almost certainly already has —
65
+ // copying them would collide, so in "existing" mode we skip + print merge steps.
66
+ const CONFIG_FILES = new Set([
67
+ "next.config.mjs",
68
+ "postcss.config.mjs",
69
+ "app/globals.css",
70
+ "app/layout.tsx",
71
+ "app/page.tsx",
72
+ ]);
73
+
74
+ // A file is a "demo page" (optional) vs the shell (always). The dashboard stays
75
+ // in the shell so `/` has a landing page.
76
+ function isDemo(rel) {
77
+ const p = rel.split(sep).join("/");
78
+ if (p.startsWith("app/auth/")) return true;
79
+ if (p.startsWith("app/onboarding/")) return true;
80
+ if (p.startsWith("app/register-business/")) return true;
81
+ if (p.startsWith("app/(app)/")) {
82
+ return !(p === "app/(app)/layout.tsx" || p.startsWith("app/(app)/dashboard/"));
83
+ }
84
+ return false;
85
+ }
86
+
87
+ async function main() {
88
+ // 1. Fresh vs existing.
89
+ let mode = has("--existing") ? "existing" : has("--fresh") ? "fresh" : null;
90
+ if (!mode) {
91
+ const a = await ask(
92
+ "Is this a fresh project or an existing one? [F(resh)/e(xisting)] ",
93
+ "f",
94
+ );
95
+ mode = a.startsWith("e") ? "existing" : "fresh";
96
+ }
97
+
98
+ // 2. Include the demo pages?
99
+ let demo = has("--demo") ? true : has("--no-demo") ? false : null;
100
+ if (demo === null) {
101
+ const a = await ask(
102
+ "Install the demo pages (dashboard, CRM, calendar, chat, support…)? [Y/n] ",
103
+ "y",
104
+ );
105
+ demo = !a.startsWith("n");
106
+ }
107
+
108
+ const cwd = process.cwd();
109
+ let created = 0;
110
+ let skipped = 0;
111
+ const configSkipped = [];
112
+
113
+ const files = [];
114
+ (function walk(dir) {
115
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
116
+ const abs = join(dir, entry.name);
117
+ if (entry.isDirectory()) walk(abs);
118
+ else files.push(abs);
119
+ }
120
+ })(TEMPLATE);
121
+
122
+ console.log(
123
+ `\nScaffolding VUI (${mode}${demo ? ", with demo" : ", no demo"}) into ${cwd}${
124
+ dry ? " [dry run]" : ""
125
+ }\n`,
126
+ );
127
+
128
+ for (const abs of files) {
129
+ const rel = relative(TEMPLATE, abs);
130
+ const norm = rel.split(sep).join("/");
131
+
132
+ if (isDemo(rel) && !demo) continue; // demo not selected
133
+ if (mode === "existing" && CONFIG_FILES.has(norm)) {
134
+ configSkipped.push(norm);
135
+ continue;
136
+ }
137
+
138
+ const dest = join(cwd, rel);
139
+ if (existsSync(dest) && !force) {
140
+ skipped++;
141
+ console.log(` skip ${rel}`);
142
+ continue;
143
+ }
144
+ if (!dry) {
145
+ mkdirSync(dirname(dest), { recursive: true });
146
+ copyFileSync(abs, dest);
147
+ }
148
+ created++;
149
+ console.log(` ${dry ? "would add" : "added "} ${rel}`);
150
+ }
151
+
152
+ console.log(
153
+ `\nDone — ${created} file(s) ${dry ? "would be added" : "added"}, ${skipped} skipped.`,
154
+ );
155
+
156
+ const DEPS = [
157
+ "@radix-ui/react-icons",
158
+ "@radix-ui/react-slot",
159
+ "@tanstack/react-table",
160
+ "class-variance-authority",
161
+ "clsx",
162
+ "cmdk",
163
+ "date-fns",
164
+ "input-otp",
165
+ "lucide-react",
166
+ "next-themes",
167
+ "radix-ui",
168
+ "react-day-picker",
169
+ "react-hook-form",
170
+ "@hookform/resolvers",
171
+ "recharts",
172
+ "sonner",
173
+ "tailwind-merge",
174
+ "zod",
175
+ ].join(" ");
176
+
177
+ if (mode === "existing") {
178
+ console.log(`
179
+ ⚠ Existing project — config files were NOT written to avoid clobbering yours.
180
+ Merge these into your project manually:
181
+
182
+ • next.config: add
183
+ transpilePackages: ["@viliha/vui-ui"]
184
+ (for the demo's tab keep-alive: output:"export", images:{unoptimized:true},
185
+ trailingSlash:true — optional, only if you want static-export keep-alive)
186
+ • app/globals.css: add
187
+ @import "tailwindcss";
188
+ @import "@viliha/vui-ui/theme.css";
189
+ • tsconfig.json: map the alias the scaffold uses
190
+ "compilerOptions": { "paths": { "@/*": ["./*"] } }
191
+ • Root app/layout.tsx: import "./globals.css" and mount fonts if you want the
192
+ demo's exact look (see the scaffolded app/(app)/layout.tsx for the shell).
193
+
194
+ The shell + pages were added under app/(app)/ and app/_components/ — review
195
+ them (re-run with --dry-run first if unsure) before committing.
196
+ `);
197
+ }
198
+
199
+ console.log(`Next steps:
200
+ 1. Install peer dependencies:
201
+ npm i ${DEPS}
202
+ 2. Start the app:
203
+ npm run dev -> http://localhost:3000 (redirects to /dashboard)
204
+
205
+ Everything landed in YOUR repo — edit app/_components/nav-config.ts, set your
206
+ logo (NEXT_PUBLIC_LOGO_URL in .env), and delete demo pages you don't need.
207
+ Docs: /docs/navigation (sidebar, tabs) and /docs/configuration (logo, env).
208
+ `);
209
+ }
210
+
211
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@viliha/vui-ui",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "description": "Vui UI — a clean, token-driven React admin/CRM component library built on Tailwind CSS v4, shadcn-style patterns, and Radix Icons. Ships as TypeScript source (Just-in-Time), compiled by the consuming app.",
5
5
  "license": "MIT",
6
6
  "author": "Suman Bonakurthi",
@@ -33,9 +33,15 @@
33
33
  "src",
34
34
  "!src/**/*.test.ts",
35
35
  "!src/**/*.test.tsx",
36
+ "bin",
37
+ "template",
36
38
  "AGENT.md",
37
39
  "CLAUDE.template.md"
38
40
  ],
41
+ "bin": {
42
+ "vui-ui": "bin/vui.mjs",
43
+ "vui": "bin/vui.mjs"
44
+ },
39
45
  "exports": {
40
46
  "./theme.css": "./src/theme.css",
41
47
  "./utils": "./src/utils.ts",
@@ -48,7 +54,9 @@
48
54
  "lint": "eslint . --max-warnings 0",
49
55
  "generate:component": "turbo gen react-component",
50
56
  "check-types": "tsgo --noEmit",
51
- "test": "vitest run"
57
+ "test": "vitest run",
58
+ "sync-template": "node scripts/sync-template.mjs",
59
+ "prepublishOnly": "node scripts/sync-template.mjs"
52
60
  },
53
61
  "peerDependencies": {
54
62
  "react": "^18 || ^19",
@@ -0,0 +1,20 @@
1
+ # Footer & brand identity — copy to .env.local and edit per deployment.
2
+ # All are optional; defaults are used when unset. NEXT_PUBLIC_ vars are read at
3
+ # build time and inlined into the exported site.
4
+
5
+ NEXT_PUBLIC_COMPANY_NAME="VILIHA PTE. LTD."
6
+ # Optional — links the company name in the footer:
7
+ NEXT_PUBLIC_COMPANY_URL="https://viliha.com"
8
+ NEXT_PUBLIC_LICENSE="MIT Licensed"
9
+ # The copyright year is automatic (always the current/build year) — no env needed.
10
+
11
+ # Optional — your logo image, served from /public; falls back to the built-in
12
+ # mark. e.g. drop public/logo.svg and set:
13
+ # NEXT_PUBLIC_LOGO_URL="/logo.svg"
14
+
15
+ # Override the entire footer line at once (takes precedence over the vars above):
16
+ # NEXT_PUBLIC_FOOTER_NOTICE="© 2026 Acme Inc. · All rights reserved"
17
+
18
+ # Max pages kept open in the tab strip (client-side). Default 5, min 1. Opening
19
+ # more evicts the oldest tab with a warning.
20
+ NEXT_PUBLIC_MAX_TABS="5"
@@ -0,0 +1,69 @@
1
+ "use client";
2
+
3
+ import {
4
+ CodeIcon as Hash,
5
+ CubeIcon as Building,
6
+ DotFilledIcon as CircleDot,
7
+ EnvelopeClosedIcon as Mail,
8
+ MobileIcon as Phone,
9
+ SewingPinFilledIcon as MapPin,
10
+ Share2Icon as Network,
11
+ } from "@radix-ui/react-icons";
12
+
13
+ import { usePathname } from "next/navigation";
14
+
15
+ import { Badge } from "@viliha/vui-ui/badge";
16
+ import { RecordView, type RecordField } from "@viliha/vui-ui/record-view";
17
+ import { branches, type Branch } from "@/lib/mock-data";
18
+
19
+ const fields: RecordField<Branch>[] = [
20
+ { key: "name", label: "Name", editable: true, required: true, group: "General", hideInTable: true },
21
+ { key: "organization", label: "Organization", icon: Building, editable: true, width: 180, group: "General" },
22
+ { key: "code", label: "Code", icon: Hash, editable: true, group: "General" },
23
+ { key: "email", label: "Email", icon: Mail, editable: true, copyable: true, width: 220, group: "General" },
24
+ { key: "phone", label: "Phone", icon: Phone, editable: true, copyable: true, width: 160, group: "General" },
25
+ { key: "city", label: "City", icon: MapPin, editable: true, group: "General" },
26
+ {
27
+ key: "isHeadquarters",
28
+ label: "HQ",
29
+ icon: CircleDot,
30
+ // Wide enough for the "Headquarters" badge so it never spills into Actions.
31
+ width: 150,
32
+ group: "System",
33
+ render: (row) =>
34
+ row.isHeadquarters ? (
35
+ <Badge variant="secondary">Headquarters</Badge>
36
+ ) : (
37
+ <Badge variant="muted">Branch</Badge>
38
+ ),
39
+ },
40
+ ];
41
+
42
+ export function BranchesTable() {
43
+ const pathname = usePathname();
44
+ return (
45
+ <RecordView
46
+ persistKey={pathname}
47
+ title="Branches"
48
+ singular="Branch"
49
+ icon={Network}
50
+ fields={fields}
51
+ initialData={branches}
52
+ getPrimary={(row) => ({
53
+ title: row.name,
54
+ subtitle: row.organization,
55
+ initials: (row.code || row.name).slice(0, 2).toUpperCase(),
56
+ })}
57
+ makeEmptyRow={() => ({
58
+ id: Date.now(),
59
+ organization: "",
60
+ name: "",
61
+ code: "",
62
+ email: "",
63
+ phone: "",
64
+ city: "",
65
+ isHeadquarters: false,
66
+ })}
67
+ />
68
+ );
69
+ }
@@ -0,0 +1,13 @@
1
+ import { pageMeta } from "@/lib/seo";
2
+
3
+ import { BranchesTable } from "./branches-table";
4
+
5
+ export const metadata = pageMeta("/branches");
6
+
7
+ export default function BranchesPage() {
8
+ return (
9
+ <main className="h-full">
10
+ <BranchesTable />
11
+ </main>
12
+ );
13
+ }
@@ -0,0 +1,39 @@
1
+ "use client";
2
+
3
+ import {
4
+ BackpackIcon as Briefcase,
5
+ CodeIcon as Hash,
6
+ TextAlignLeftIcon as AlignLeft,
7
+ } from "@radix-ui/react-icons";
8
+
9
+ import { RecordView, type RecordField } from "@viliha/vui-ui/record-view";
10
+ import { businesses, type Business } from "@/lib/mock-data";
11
+
12
+ const fields: RecordField<Business>[] = [
13
+ { key: "title", label: "Title", editable: true, required: true, group: "General", hideInTable: true },
14
+ { key: "code", label: "Code", icon: Hash, editable: true, group: "General" },
15
+ { key: "description", label: "Description", icon: AlignLeft, editable: true, width: 360, group: "General" },
16
+ ];
17
+
18
+ export function BusinessesTable() {
19
+ return (
20
+ <RecordView
21
+ title="Businesses"
22
+ singular="Business"
23
+ icon={Briefcase}
24
+ fields={fields}
25
+ initialData={businesses}
26
+ getPrimary={(row) => ({
27
+ title: row.title,
28
+ subtitle: row.code,
29
+ initials: row.code.slice(0, 2).toUpperCase(),
30
+ })}
31
+ makeEmptyRow={() => ({
32
+ id: Date.now(),
33
+ title: "",
34
+ code: "",
35
+ description: "",
36
+ })}
37
+ />
38
+ );
39
+ }
@@ -0,0 +1,13 @@
1
+ import { pageMeta } from "@/lib/seo";
2
+
3
+ import { BusinessesTable } from "./businesses-table";
4
+
5
+ export const metadata = pageMeta("/businesses");
6
+
7
+ export default function BusinessesPage() {
8
+ return (
9
+ <main className="h-full">
10
+ <BusinessesTable />
11
+ </main>
12
+ );
13
+ }