@viliha/vui-ui 1.5.4 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT.md +38 -38
- package/CHANGELOG.md +31 -3
- package/README.md +18 -18
- package/package.json +1 -1
- package/template/.env.example +14 -1
- package/template/app/_components/app-sidebar.tsx +4 -3
- package/template/app/_components/brand.tsx +130 -0
- package/template/app/_components/logo.tsx +11 -10
- package/template/app/_components/wordmark.tsx +6 -3
- package/template/app/auth/signin/page.tsx +2 -1
- package/template/app/layout.tsx +2 -1
- package/template/app/onboarding/layout.tsx +2 -1
- package/template/app/register-business/layout.tsx +2 -1
- package/template/lib/seo.ts +9 -4
package/AGENT.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Using VUI in your project (AI agent guide)
|
|
2
2
|
|
|
3
|
-
> **Audience:** developers
|
|
3
|
+
> **Audience:** developers (and their AI agents) **consuming** `@viliha/vui-ui`
|
|
4
4
|
> in a downstream app. This file ships with the npm package. To auto-load these
|
|
5
|
-
> rules, copy the ready-made pointer to your project root
|
|
5
|
+
> rules, copy the ready-made pointer to your project root; it `@`-imports this
|
|
6
6
|
> file, so you never duplicate the rules:
|
|
7
7
|
>
|
|
8
8
|
> ```bash
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
> [CONTRIBUTING.md](https://github.com/myviliha/vui-starter/blob/main/CONTRIBUTING.md)
|
|
15
15
|
> and [AGENTS.md](https://github.com/myviliha/vui-starter/blob/main/AGENTS.md).
|
|
16
16
|
|
|
17
|
-
You are a frontend architect building enterprise applications on **VUI Starter**. Your job is to ship apps that are consistent, maintainable, accessible, and production-ready by leaning fully on the VUI Design System. Don't reinvent the framework
|
|
17
|
+
You are a frontend architect building enterprise applications on **VUI Starter**. Your job is to ship apps that are consistent, maintainable, accessible, and production-ready by leaning fully on the VUI Design System. Don't reinvent the framework; build with it.
|
|
18
18
|
|
|
19
19
|
> **What you get from the package vs. what to copy.** `@viliha/vui-ui` ships the
|
|
20
20
|
> component primitives (`Button`, `Input`, `Select`, `Dialog`, `Menu`,
|
|
21
21
|
> `RecordView`, `ChartContainer`, `Breadcrumbs`, `theme.css`, …). App-shell
|
|
22
|
-
> patterns referenced below
|
|
23
|
-
> auth screens
|
|
22
|
+
> patterns referenced below (`SetPageTitle`, the sidebar, and the `AuthCard*`
|
|
23
|
+
> auth screens) are **reference-app patterns**, not package exports.
|
|
24
24
|
> The breadcrumb *trail-building logic* (`crumbsFor` + `nav-config` +
|
|
25
25
|
> `route-meta`) is also a reference-app pattern: the package exports the
|
|
26
26
|
> presentational `Breadcrumbs` component; the app derives the trail from the
|
|
@@ -49,7 +49,7 @@ When several approaches would work, pick the one that aligns best with VUI.
|
|
|
49
49
|
|
|
50
50
|
# Think Before You Build
|
|
51
51
|
|
|
52
|
-
Before you create anything, search for what already exists
|
|
52
|
+
Before you create anything, search for what already exists: a VUI component, a page pattern, a layout, a variant, or a utility. Never duplicate functionality VUI already provides.
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
@@ -66,7 +66,7 @@ npx @viliha/vui-ui init # interactive decision tree
|
|
|
66
66
|
|
|
67
67
|
Files land in the consumer's repo (they own them). Questions:
|
|
68
68
|
0. **Next.js or Turborepo?** (`--nextjs` / `--turbo` + `--dir <path>`, default
|
|
69
|
-
`apps/web
|
|
69
|
+
`apps/web`; turbo scaffolds into that app directory)
|
|
70
70
|
1. **Fresh project?** (`--fresh` / `--existing`)
|
|
71
71
|
2. **Pre-built theme?** (`--prebuilt` = shell + demo pages / `--theme-only` = just
|
|
72
72
|
the theme wiring you configure)
|
|
@@ -78,8 +78,8 @@ dependencies** with the package manager it detects from the lockfile (npm / pnpm
|
|
|
78
78
|
|
|
79
79
|
- **fresh + prebuilt** → full runnable app (config + shell + demo).
|
|
80
80
|
- **fresh + theme-only** → just `globals.css` + `next.config` wiring; build your own.
|
|
81
|
-
- **existing + prebuilt** → shell + demo added; config **never** overwritten
|
|
82
|
-
prints the merge steps (`transpilePackages`, the `theme.css` import, the `@/*`
|
|
81
|
+
- **existing + prebuilt** → shell + demo added; config **never** overwritten, and
|
|
82
|
+
it prints the merge steps (`transpilePackages`, the `theme.css` import, the `@/*`
|
|
83
83
|
alias, `import "./globals.css"`).
|
|
84
84
|
- **existing + theme-only** → nothing copied; prints the wiring steps.
|
|
85
85
|
|
|
@@ -88,7 +88,7 @@ Other flags: `--yes` / `--force` / `--dry-run`. If you only need the components,
|
|
|
88
88
|
|
|
89
89
|
### Inside a Turborepo / monorepo
|
|
90
90
|
|
|
91
|
-
**Never scaffold into the repo root
|
|
91
|
+
**Never scaffold into the repo root**; a monorepo root has no `app/` and no Next
|
|
92
92
|
app. Target the specific application, e.g. `apps/web`. Two equivalent ways:
|
|
93
93
|
|
|
94
94
|
```bash
|
|
@@ -115,14 +115,14 @@ dev`. The theme import, `transpilePackages`, and the `@/*` alias all belong in
|
|
|
115
115
|
|
|
116
116
|
### End-to-end walkthroughs
|
|
117
117
|
|
|
118
|
-
- **New standalone app
|
|
118
|
+
- **New standalone app**: `npx create-next-app@latest my-app --ts --tailwind
|
|
119
119
|
--app --no-src-dir --use-npm`, then `cd my-app && npx @viliha/vui-ui init`
|
|
120
120
|
(fresh + prebuilt). It scaffolds config + shell + demo, installs deps, and you
|
|
121
121
|
run `npm run dev` → `/dashboard`.
|
|
122
|
-
- **New app in a monorepo
|
|
122
|
+
- **New app in a monorepo**: scaffold your app under `apps/<name>` (e.g. with
|
|
123
123
|
`create-next-app`), then from that app dir run `npx @viliha/vui-ui init` (or
|
|
124
124
|
`--turbo --dir apps/<name>` from the root) and install deps in that app.
|
|
125
|
-
- **Existing app
|
|
125
|
+
- **Existing app**: run `npx @viliha/vui-ui init --existing`. It never
|
|
126
126
|
overwrites your config; wire up the four things it prints (`transpilePackages`,
|
|
127
127
|
the `theme.css` import, the `@/*` alias, `import "./globals.css"`). Prefer only
|
|
128
128
|
the components? Use `--theme-only` (copies nothing) and follow the setup below.
|
|
@@ -152,7 +152,7 @@ Import the theme once. No extra transpilation needed.
|
|
|
152
152
|
|
|
153
153
|
# Upgrading
|
|
154
154
|
|
|
155
|
-
When you bump `@viliha/vui-ui`, follow these steps
|
|
155
|
+
When you bump `@viliha/vui-ui`, follow these steps. The package version and the
|
|
156
156
|
scaffolded files in the repo are two separate things.
|
|
157
157
|
|
|
158
158
|
1. **Read the [CHANGELOG](./CHANGELOG.md) for the target version.** Note anything
|
|
@@ -165,7 +165,7 @@ scaffolded files in the repo are two separate things.
|
|
|
165
165
|
In a monorepo, install in the specific app (or with a workspace filter, e.g.
|
|
166
166
|
`pnpm --filter <app> up @viliha/vui-ui`), never at the repo root.
|
|
167
167
|
3. **No rebuild needed.** The package ships TypeScript source, so there's no
|
|
168
|
-
build/dts step
|
|
168
|
+
build/dts step; just restart the dev server. If a new version adds a peer
|
|
169
169
|
dependency, install it (the CHANGELOG and the "Module not found" error name it).
|
|
170
170
|
4. **Scaffolded files are yours.** `init` copies the shell and demo pages into
|
|
171
171
|
the repo once; upgrading the package does **not** touch them. To pull
|
|
@@ -181,7 +181,7 @@ scaffolded files in the repo are two separate things.
|
|
|
181
181
|
|
|
182
182
|
# Design Tokens
|
|
183
183
|
|
|
184
|
-
VUI is entirely token-driven. Never hardcode colors, spacing, radius, typography, shadows, or borders
|
|
184
|
+
VUI is entirely token-driven. Never hardcode colors, spacing, radius, typography, shadows, or borders; reach for the semantic design token instead. For example: `--button-primary`, `--button-primary-hover`, `--background`, `--foreground`, `--border`, `--ring`, `--chart-1`, `--sidebar-primary`.
|
|
185
185
|
|
|
186
186
|
Avoid arbitrary values unless there's truly no token for the job.
|
|
187
187
|
|
|
@@ -239,11 +239,11 @@ Group content into bordered cards, and avoid deeply nested layouts. Reach for sp
|
|
|
239
239
|
|
|
240
240
|
# Navigation
|
|
241
241
|
|
|
242
|
-
Navigate with the three provided pieces
|
|
242
|
+
Navigate with the three provided pieces: breadcrumbs, sidebar, and top navigation. Don't build a custom navigation system unless the project explicitly demands one.
|
|
243
243
|
|
|
244
244
|
## Breadcrumbs
|
|
245
245
|
|
|
246
|
-
One trail, **derived from the route
|
|
246
|
+
One trail, **derived from the route, never hand-written per page.**
|
|
247
247
|
|
|
248
248
|
- Render with the shared `Breadcrumbs` component (`@viliha/vui-ui/breadcrumbs`):
|
|
249
249
|
last crumb is the current page (bold, non-interactive), earlier crumbs are
|
|
@@ -251,7 +251,7 @@ One trail, **derived from the route — never hand-written per page.**
|
|
|
251
251
|
- Build the trail from the URL against a single nav config (copy `crumbsFor` +
|
|
252
252
|
`nav-config` + `route-meta` from the reference app). Root it at **Home**
|
|
253
253
|
(your dashboard route). A section/group parent has no page of its own, so its
|
|
254
|
-
crumb links to its **first child
|
|
254
|
+
crumb links to its **first child**; clicking a section lands on that
|
|
255
255
|
section's first page.
|
|
256
256
|
- Ship **one** landing page, labeled "Home". Don't split "Home" and "Dashboard"
|
|
257
257
|
into two routes.
|
|
@@ -261,17 +261,17 @@ To reorder or rename, edit the nav config; the trail follows automatically.
|
|
|
261
261
|
## Sidebar: sections & collapsible groups
|
|
262
262
|
|
|
263
263
|
The sidebar is driven by one `NAV` config (copy `nav-config.ts`). There are
|
|
264
|
-
**two grouping shapes
|
|
264
|
+
**two grouping shapes: use them, don't invent a third:**
|
|
265
265
|
|
|
266
|
-
- **Section** (`NavSection`, `{ title?, items }`)
|
|
266
|
+
- **Section** (`NavSection`, `{ title?, items }`): a top-level band with an
|
|
267
267
|
optional `title` heading. Items are **always visible** (no collapse). Use it to
|
|
268
268
|
cluster related pages under a label (e.g. *Records*, *System*). The first
|
|
269
269
|
section usually has no title.
|
|
270
|
-
- **Collapsible group** (`NavGroup`, an entry with `children`)
|
|
270
|
+
- **Collapsible group** (`NavGroup`, an entry with `children`): a parent row with
|
|
271
271
|
a chevron that **hides/unhides** its nested links; it auto-opens when a child is
|
|
272
272
|
the active route. Use it for a set of sub-pages under one parent (e.g. *Auth*,
|
|
273
273
|
*CRM*, *System*) to keep the sidebar short. A group parent has no page of its
|
|
274
|
-
own
|
|
274
|
+
own; its breadcrumb points at its first child.
|
|
275
275
|
|
|
276
276
|
```ts
|
|
277
277
|
export const NAV: NavSection[] = [
|
|
@@ -288,14 +288,14 @@ export const NAV: NavSection[] = [
|
|
|
288
288
|
```
|
|
289
289
|
|
|
290
290
|
When adding a page, always add it to `NAV` and mirror its color in
|
|
291
|
-
`route-meta.ts
|
|
291
|
+
`route-meta.ts`; the sidebar, breadcrumbs, and tabs all derive from these.
|
|
292
292
|
|
|
293
293
|
## Open tabs (keep-alive)
|
|
294
294
|
|
|
295
295
|
Enterprise apps keep several pages open at once. The reference app ships a
|
|
296
|
-
browser-style **tab strip** under the top bar
|
|
296
|
+
browser-style **tab strip** under the top bar, a reference-app pattern (copy
|
|
297
297
|
`open-tabs.tsx`), not a package export. **This is a first-class feature; wire it
|
|
298
|
-
in
|
|
298
|
+
in, because a fresh install won't have it.**
|
|
299
299
|
|
|
300
300
|
It is **keep-alive**: every opened page stays mounted (inactive ones hidden), so
|
|
301
301
|
switching is instant (no remount/flash) and each page keeps its live state
|
|
@@ -309,7 +309,7 @@ switching is instant (no remount/flash) and each page keeps its live state
|
|
|
309
309
|
</OpenTabsProvider>
|
|
310
310
|
```
|
|
311
311
|
|
|
312
|
-
- Labels/icons/colors derive from `nav-config.ts` + `route-meta.ts
|
|
312
|
+
- Labels/icons/colors derive from `nav-config.ts` + `route-meta.ts`, with no per-tab wiring.
|
|
313
313
|
- The list persists in `sessionStorage`, capped by `NEXT_PUBLIC_MAX_TABS`
|
|
314
314
|
(default 5; oldest FIFO-evicted with a warning).
|
|
315
315
|
- Tabs are drag-reorderable and right-click-taggable with one of seven colors.
|
|
@@ -319,27 +319,27 @@ switching is instant (no remount/flash) and each page keeps its live state
|
|
|
319
319
|
|
|
320
320
|
Keep-alive relies on a static-export (all-client) shell. If your app renders
|
|
321
321
|
server components per route, either adopt the demo's static-export shell or fall
|
|
322
|
-
back to a plain navigation-tab model (router push per tab)
|
|
322
|
+
back to a plain navigation-tab model (router push per tab); the strip,
|
|
323
323
|
persistence, and nav-config wiring stay identical.
|
|
324
324
|
|
|
325
325
|
---
|
|
326
326
|
|
|
327
327
|
# Forms
|
|
328
328
|
|
|
329
|
-
Build forms with VUI and shadcn/ui together, reaching for shadcn Form, React Hook Form, and Zod. Every form handles the full lifecycle
|
|
329
|
+
Build forms with VUI and shadcn/ui together, reaching for shadcn Form, React Hook Form, and Zod. Every form handles the full lifecycle: validation, loading, success, error, disabled, and keyboard navigation. Never use a placeholder in place of a label.
|
|
330
330
|
|
|
331
331
|
---
|
|
332
332
|
|
|
333
333
|
# Tables
|
|
334
334
|
|
|
335
|
-
Never hand-build an HTML table
|
|
335
|
+
Never hand-build an HTML table; always use `RecordView`. Configure columns through its field props (`editable`, `required`, `copyable`, `options`, `render`), and let `RecordView` own the rest: sorting, filtering, pagination, bulk actions, and import/export.
|
|
336
336
|
|
|
337
337
|
## Add / edit form
|
|
338
338
|
|
|
339
339
|
The buffered add/edit form renders in one of two layouts:
|
|
340
340
|
|
|
341
|
-
- **Slide-over panel
|
|
342
|
-
- **Full-page form
|
|
341
|
+
- **Slide-over panel**: the default; nothing to configure.
|
|
342
|
+
- **Full-page form**: set `formMode="page"` (with `formColumns={1 | 2}`). Add
|
|
343
343
|
`formDescription` and a per-field `description` to show an AWS-style help
|
|
344
344
|
panel beside the form.
|
|
345
345
|
|
|
@@ -352,7 +352,7 @@ shared `@viliha/vui-ui/breadcrumbs` component.
|
|
|
352
352
|
|
|
353
353
|
# Charts
|
|
354
354
|
|
|
355
|
-
Build every chart with `ChartContainer` plus Recharts. Never hardcode chart colors
|
|
355
|
+
Build every chart with `ChartContainer` plus Recharts. Never hardcode chart colors; map them through the chart tokens.
|
|
356
356
|
|
|
357
357
|
---
|
|
358
358
|
|
|
@@ -361,10 +361,10 @@ Build every chart with `ChartContainer` plus Recharts. Never hardcode chart colo
|
|
|
361
361
|
The auth screens are a **demo pattern in the reference app**, not exports of the
|
|
362
362
|
`@viliha/vui-ui` package. `AuthCard`, `AuthCardHeader`, `AuthCardBody`,
|
|
363
363
|
`AuthCardFooter` and `AuthCardAside` live in
|
|
364
|
-
`apps/backoffice/app/_components/auth.tsx
|
|
364
|
+
`apps/backoffice/app/_components/auth.tsx`; **copy and adapt them** into your
|
|
365
365
|
app (they are built from published primitives: `Button`, `Input`, tokens).
|
|
366
366
|
|
|
367
|
-
Do not `import … from "@viliha/vui-ui/auth"
|
|
367
|
+
Do not `import … from "@viliha/vui-ui/auth"`; no such entry point exists.
|
|
368
368
|
|
|
369
369
|
Wrap the forms in semantic HTML.
|
|
370
370
|
|
|
@@ -374,7 +374,7 @@ Wrap the forms in semantic HTML.
|
|
|
374
374
|
|
|
375
375
|
VUI and shadcn/ui complement each other, so split the work along their strengths. Reach for shadcn for forms, dialogs, sheets, tabs, popovers, and accordions; reach for VUI for layouts, `RecordView`, enterprise components, charts, and navigation patterns.
|
|
376
376
|
|
|
377
|
-
VUI owns the design tokens
|
|
377
|
+
VUI owns the design tokens, so delete any duplicate token definitions shadcn generates.
|
|
378
378
|
|
|
379
379
|
---
|
|
380
380
|
|
|
@@ -398,7 +398,7 @@ Default to Server Components, streaming, lazy loading, and dynamic imports. Avoi
|
|
|
398
398
|
|
|
399
399
|
# UX Standards
|
|
400
400
|
|
|
401
|
-
Every feature communicates its current state
|
|
401
|
+
Every feature communicates its current state: loading, success, empty, and error. Give long-running actions visible progress, and never leave users wondering what happened.
|
|
402
402
|
|
|
403
403
|
---
|
|
404
404
|
|
|
@@ -436,7 +436,7 @@ Panel
|
|
|
436
436
|
|
|
437
437
|
# AI Development Rules
|
|
438
438
|
|
|
439
|
-
Before you write code, reuse what's already there
|
|
439
|
+
Before you write code, reuse what's already there (components, layouts, utilities, and variants), and extend an existing component before creating a new one. Never duplicate code or styling. Keep APIs simple and components focused.
|
|
440
440
|
|
|
441
441
|
---
|
|
442
442
|
|
package/CHANGELOG.md
CHANGED
|
@@ -7,13 +7,41 @@ backward-compatible features, **major** for breaking changes.
|
|
|
7
7
|
|
|
8
8
|
To upgrade, see [Upgrading](./AGENT.md#upgrading) in the agent guide.
|
|
9
9
|
|
|
10
|
+
## 1.6.1 — 2026-07-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `NEXT_PUBLIC_APP_URL` sets the deploy origin (`SITE.url`), so `metadataBase`,
|
|
15
|
+
canonical URLs, and Open Graph URLs resolve against your domain instead of the
|
|
16
|
+
hard-coded demo host. Falls back to the demo URL when unset. Completes the
|
|
17
|
+
env-driven brand set (`APP_NAME` / `APP_TAGLINE` / `APP_DESCRIPTION` / `APP_URL`).
|
|
18
|
+
|
|
19
|
+
## 1.6.0 — 2026-07-25
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- Documentation polish: package-manager commands in the docs now use tabs
|
|
24
|
+
(npm / pnpm / yarn / bun), and the prose across the README, `AGENT.md`, and the
|
|
25
|
+
docs site was cleaned of em-dashes for a more natural read.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **Runtime branding via `BrandProvider` / `useBrand()`** (in the scaffold) — for
|
|
30
|
+
white-label / multi-tenant apps that get their branding from an API. Env vars
|
|
31
|
+
are the defaults; override them live from a `NEXT_PUBLIC_BRAND_URL` JSON
|
|
32
|
+
endpoint, a `<BrandProvider initial={…}>` seed, or `useBrand().setBrand(…)`.
|
|
33
|
+
The name, tagline, description, logo, and the browser-tab title all update at
|
|
34
|
+
runtime — no rebuild. Also routes the last hard-coded name spots (onboarding /
|
|
35
|
+
register-business headers, the signin aside) through the runtime brand.
|
|
36
|
+
|
|
10
37
|
## 1.5.4 — 2026-07-24
|
|
11
38
|
|
|
12
39
|
### Added
|
|
13
40
|
|
|
14
|
-
- `NEXT_PUBLIC_APP_NAME`
|
|
15
|
-
|
|
16
|
-
|
|
41
|
+
- `NEXT_PUBLIC_APP_NAME` renames the app — the sidebar, wordmark, auth screens,
|
|
42
|
+
and browser-tab metadata read from one place (`SITE.name`) instead of a
|
|
43
|
+
hard-coded "Vui Starter". `NEXT_PUBLIC_APP_TAGLINE` and
|
|
44
|
+
`NEXT_PUBLIC_APP_DESCRIPTION` complete the tab-title/meta rebrand.
|
|
17
45
|
|
|
18
46
|
### Fixed
|
|
19
47
|
|
package/README.md
CHANGED
|
@@ -52,14 +52,14 @@ npx @viliha/vui-ui init
|
|
|
52
52
|
It's **interactive** and walks through a short decision tree:
|
|
53
53
|
|
|
54
54
|
1. **Fresh project?** (fresh vs. existing)
|
|
55
|
-
2. **Pre-built theme?**
|
|
55
|
+
2. **Pre-built theme?** The full shell plus demo pages, or just the theme wiring
|
|
56
56
|
for you to build on.
|
|
57
57
|
|
|
58
58
|
Everything it generates is copied into *your* repo, so you own and edit it.
|
|
59
59
|
|
|
60
60
|
| | **Pre-built** (shell + demo) | **Theme-only** (you configure) |
|
|
61
61
|
| --- | --- | --- |
|
|
62
|
-
| **Fresh** | full runnable app: config + shell + demo pages | just the theme wiring (`globals.css`, `next.config`)
|
|
62
|
+
| **Fresh** | full runnable app: config + shell + demo pages | just the theme wiring (`globals.css`, `next.config`). Build your own pages |
|
|
63
63
|
| **Existing** | shell + demo added; your config is **never** overwritten (prints merge steps) | nothing copied; prints the wiring steps |
|
|
64
64
|
|
|
65
65
|
Before that, it asks whether this is a standalone **Next.js** app or a
|
|
@@ -83,8 +83,8 @@ from your lockfile (npm, pnpm, yarn, or bun). It prompts before doing so; pass
|
|
|
83
83
|
|
|
84
84
|
### Fresh + pre-built (recommended for new apps)
|
|
85
85
|
|
|
86
|
-
Start from a `create-next-app` base **without `--src-dir`**
|
|
87
|
-
a root `app/` with `@/*` → `./*`
|
|
86
|
+
Start from a `create-next-app` base **without `--src-dir`** (the scaffold expects
|
|
87
|
+
a root `app/` with `@/*` → `./*`), then run `init`. It scaffolds the app, installs
|
|
88
88
|
the dependencies, and leaves you ready to run `dev`. Any package manager works:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
@@ -111,8 +111,8 @@ dependencies. The demo then runs out of the box at `/dashboard`.
|
|
|
111
111
|
|
|
112
112
|
### Turborepo / monorepo
|
|
113
113
|
|
|
114
|
-
Scaffold **inside the target app** (e.g. `apps/web`), never at the repo root
|
|
115
|
-
|
|
114
|
+
Scaffold **inside the target app** (e.g. `apps/web`), never at the repo root; the
|
|
115
|
+
root has no `app/` and no Next.js app. Run `init` from the app, or name it
|
|
116
116
|
from the root with `--dir`:
|
|
117
117
|
|
|
118
118
|
```bash
|
|
@@ -124,29 +124,29 @@ npx @viliha/vui-ui init --turbo --dir apps/web
|
|
|
124
124
|
```
|
|
125
125
|
|
|
126
126
|
In monorepo mode `init` does **not** auto-install (installs are
|
|
127
|
-
workspace-specific), so add the deps to that app afterward
|
|
127
|
+
workspace-specific), so add the deps to that app afterward: `cd apps/web && pnpm
|
|
128
128
|
add @viliha/vui-ui …`, or from the root `pnpm --filter web add @viliha/vui-ui …`.
|
|
129
129
|
The `transpilePackages` entry, the `theme.css` import, and the `@/*` alias all
|
|
130
130
|
belong to **that app**, not the root.
|
|
131
131
|
|
|
132
|
-
### ⚠️ Existing project
|
|
132
|
+
### ⚠️ Existing project: read this first
|
|
133
133
|
|
|
134
134
|
Adding VUI to an app you already have takes a little care. **`init --existing`
|
|
135
135
|
never overwrites your config.** Choose pre-built to add the shell and pages under
|
|
136
136
|
`app/(app)/` and `app/_components/`, or theme-only to copy nothing. Either way, it
|
|
137
137
|
prints the four things you need to wire up:
|
|
138
138
|
|
|
139
|
-
1. **`next.config
|
|
139
|
+
1. **`next.config`**: add `transpilePackages: ["@viliha/vui-ui"]`.
|
|
140
140
|
(Optional, for the open-tabs *keep-alive*: `output: "export"`,
|
|
141
141
|
`images: { unoptimized: true }`, `trailingSlash: true`.)
|
|
142
|
-
2. **`app/globals.css
|
|
142
|
+
2. **`app/globals.css`**: add:
|
|
143
143
|
```css
|
|
144
144
|
@import "tailwindcss";
|
|
145
145
|
@import "@viliha/vui-ui/theme.css";
|
|
146
146
|
```
|
|
147
|
-
3. **`tsconfig.json
|
|
147
|
+
3. **`tsconfig.json`**: the scaffold imports via `@/*`; map it to your root:
|
|
148
148
|
`"compilerOptions": { "paths": { "@/*": ["./*"] } }`.
|
|
149
|
-
4. **Root `app/layout.tsx
|
|
149
|
+
4. **Root `app/layout.tsx`**: `import "./globals.css"` (and mount fonts to match
|
|
150
150
|
the demo's look).
|
|
151
151
|
|
|
152
152
|
Run **`npx @viliha/vui-ui init --existing --prebuilt --dry-run`** first to preview
|
|
@@ -214,15 +214,15 @@ See the [Data table docs](https://vui.viliha.com/docs/data-table) for the detail
|
|
|
214
214
|
The reference app composes these primitives into the conventions documented at
|
|
215
215
|
[vui.viliha.com/docs/layout](https://vui.viliha.com/docs/layout):
|
|
216
216
|
|
|
217
|
-
- **Five page types
|
|
217
|
+
- **Five page types**: data table, record form (Add / Edit / View), dashboard,
|
|
218
218
|
settings, and kanban board.
|
|
219
|
-
- **Command palette
|
|
219
|
+
- **Command palette**: Quick actions (`⌘K`, navigate pages) and Global search
|
|
220
220
|
(`⌘⌥K`, find records), both built on the exported `CommandPalette`.
|
|
221
|
-
- **Open tabs
|
|
221
|
+
- **Open tabs**: a browser-style strip of opened pages under the top bar
|
|
222
222
|
(⌘-click a nav item for a background tab), persisted across reloads.
|
|
223
|
-
- **Multi-step wizard
|
|
223
|
+
- **Multi-step wizard**: the exported `Steps` indicator + your step state for
|
|
224
224
|
guided flows (see the `/register-business` demo).
|
|
225
|
-
- **Breadcrumbs
|
|
225
|
+
- **Breadcrumbs**: the exported `Breadcrumbs` component fed a route-derived
|
|
226
226
|
trail.
|
|
227
227
|
|
|
228
228
|
Copy them from the
|
|
@@ -257,7 +257,7 @@ wrapper) · `checkbox` · `command-palette` (⌘K launcher) · `dialog` ·
|
|
|
257
257
|
|
|
258
258
|
## Theming
|
|
259
259
|
|
|
260
|
-
Every design decision lives in `@viliha/vui-ui/theme.css` as CSS variables
|
|
260
|
+
Every design decision lives in `@viliha/vui-ui/theme.css` as CSS variables:
|
|
261
261
|
colors, radius, typography, and dark mode. Override any token **after** the import
|
|
262
262
|
to rebrand the entire system:
|
|
263
263
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@viliha/vui-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
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",
|
package/template/.env.example
CHANGED
|
@@ -2,8 +2,21 @@
|
|
|
2
2
|
# All are optional; defaults are used when unset. NEXT_PUBLIC_ vars are read at
|
|
3
3
|
# build time and inlined into the exported site.
|
|
4
4
|
|
|
5
|
-
# App / brand
|
|
5
|
+
# App / brand identity — shown in the sidebar, wordmark, auth screens, and the
|
|
6
|
+
# browser-tab metadata. The tab title is "<APP_NAME> — <APP_TAGLINE>".
|
|
6
7
|
NEXT_PUBLIC_APP_NAME="Vui Starter"
|
|
8
|
+
NEXT_PUBLIC_APP_TAGLINE="React Admin & CRM Design System"
|
|
9
|
+
NEXT_PUBLIC_APP_DESCRIPTION="Your app description for search engines and social cards."
|
|
10
|
+
# Deploy origin (no trailing slash) — drives metadataBase, canonical + OG URLs.
|
|
11
|
+
# Set this to your domain in production.
|
|
12
|
+
NEXT_PUBLIC_APP_URL="https://vui.viliha.com"
|
|
13
|
+
|
|
14
|
+
# Runtime branding (multi-tenant / white-label). Point this at a JSON endpoint
|
|
15
|
+
# returning any of { name, tagline, description, logoUrl, company, companyUrl };
|
|
16
|
+
# BrandProvider fetches it on load and overrides the values above — including the
|
|
17
|
+
# tab title — live, no rebuild. (Or seed <BrandProvider initial={…}> from a
|
|
18
|
+
# loader, or call useBrand().setBrand(…) once your API responds.)
|
|
19
|
+
# NEXT_PUBLIC_BRAND_URL="https://api.example.com/branding"
|
|
7
20
|
|
|
8
21
|
NEXT_PUBLIC_COMPANY_NAME="VILIHA PTE. LTD."
|
|
9
22
|
# Optional — links the company name in the footer:
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
} from "@radix-ui/react-icons";
|
|
14
14
|
|
|
15
15
|
import { cn } from "@/lib/utils";
|
|
16
|
-
import {
|
|
16
|
+
import { useBrand } from "./brand";
|
|
17
17
|
import { Menu as MenuPanel } from "@viliha/vui-ui/menu";
|
|
18
18
|
import { Logo } from "./logo";
|
|
19
19
|
import { QuickActionsLauncher } from "./quick-actions";
|
|
@@ -102,6 +102,7 @@ function SidebarBody({
|
|
|
102
102
|
headerAction?: React.ReactNode;
|
|
103
103
|
}) {
|
|
104
104
|
const pathname = usePathname();
|
|
105
|
+
const { brand } = useBrand();
|
|
105
106
|
const [openGroups, setOpenGroups] = React.useState<Set<string>>(() => {
|
|
106
107
|
const open = new Set<string>();
|
|
107
108
|
for (const section of NAV) {
|
|
@@ -370,13 +371,13 @@ function SidebarBody({
|
|
|
370
371
|
collapsed ? "w-9 shrink-0 justify-center px-0" : "flex-1",
|
|
371
372
|
)}
|
|
372
373
|
aria-label="Switch workspace"
|
|
373
|
-
title={collapsed ?
|
|
374
|
+
title={collapsed ? brand.name : undefined}
|
|
374
375
|
>
|
|
375
376
|
<Logo variant="mark" className="h-6 w-6 shrink-0" />
|
|
376
377
|
{!collapsed && (
|
|
377
378
|
<>
|
|
378
379
|
<span className="min-w-0 flex-1 truncate text-lg font-bold tracking-tight text-foreground">
|
|
379
|
-
{
|
|
380
|
+
{brand.name}
|
|
380
381
|
</span>
|
|
381
382
|
<ChevronsUpDown className="size-4 shrink-0 text-muted-foreground" />
|
|
382
383
|
</>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { usePathname } from "next/navigation";
|
|
5
|
+
|
|
6
|
+
import { SITE } from "@/lib/seo";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Runtime brand configuration.
|
|
10
|
+
*
|
|
11
|
+
* The `NEXT_PUBLIC_*` env vars (see lib/seo.ts) are build-time DEFAULTS. Some
|
|
12
|
+
* apps — white-label / multi-tenant — need branding at RUNTIME from an API. This
|
|
13
|
+
* provider seeds from the env defaults, then lets you override them live:
|
|
14
|
+
*
|
|
15
|
+
* - pass `initial` (e.g. from a server/loader response), and/or
|
|
16
|
+
* - set `NEXT_PUBLIC_BRAND_URL` (or the `configUrl` prop) to a JSON endpoint the
|
|
17
|
+
* provider fetches on mount, and/or
|
|
18
|
+
* - call `useBrand().setBrand(patch)` from anywhere once your API responds.
|
|
19
|
+
*
|
|
20
|
+
* It also keeps the browser-tab title in sync (the app is a static export, so
|
|
21
|
+
* the server-rendered `<title>` is the env default until the client updates it).
|
|
22
|
+
*/
|
|
23
|
+
export type Brand = {
|
|
24
|
+
name: string;
|
|
25
|
+
tagline: string;
|
|
26
|
+
description: string;
|
|
27
|
+
/** Overrides NEXT_PUBLIC_LOGO_URL at runtime; unset → the built-in mark. */
|
|
28
|
+
logoUrl?: string;
|
|
29
|
+
company: string;
|
|
30
|
+
companyUrl: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const DEFAULT_BRAND: Brand = {
|
|
34
|
+
name: SITE.name,
|
|
35
|
+
tagline: SITE.tagline,
|
|
36
|
+
description: SITE.description,
|
|
37
|
+
logoUrl: process.env.NEXT_PUBLIC_LOGO_URL || undefined,
|
|
38
|
+
company: SITE.company,
|
|
39
|
+
companyUrl: SITE.companyUrl,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
type BrandContextValue = {
|
|
43
|
+
brand: Brand;
|
|
44
|
+
/** Merge a partial update — typically the JSON from your branding API. */
|
|
45
|
+
setBrand: (patch: Partial<Brand>) => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const BrandContext = React.createContext<BrandContextValue | null>(null);
|
|
49
|
+
|
|
50
|
+
export function BrandProvider({
|
|
51
|
+
children,
|
|
52
|
+
initial,
|
|
53
|
+
configUrl,
|
|
54
|
+
}: {
|
|
55
|
+
children: React.ReactNode;
|
|
56
|
+
initial?: Partial<Brand>;
|
|
57
|
+
configUrl?: string;
|
|
58
|
+
}) {
|
|
59
|
+
const [brand, setBrandState] = React.useState<Brand>({
|
|
60
|
+
...DEFAULT_BRAND,
|
|
61
|
+
...initial,
|
|
62
|
+
});
|
|
63
|
+
const setBrand = React.useCallback(
|
|
64
|
+
(patch: Partial<Brand>) => setBrandState((b) => ({ ...b, ...patch })),
|
|
65
|
+
[],
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Fetch runtime branding from an API (multi-tenant / white-label). Only the
|
|
69
|
+
// keys present in the response override the defaults; failures keep the
|
|
70
|
+
// defaults so the app never renders unbranded.
|
|
71
|
+
const url = configUrl ?? process.env.NEXT_PUBLIC_BRAND_URL;
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
if (!url) return;
|
|
74
|
+
let alive = true;
|
|
75
|
+
fetch(url)
|
|
76
|
+
.then((r) => (r.ok ? (r.json() as Promise<Partial<Brand>>) : null))
|
|
77
|
+
.then((data) => {
|
|
78
|
+
if (alive && data) setBrand(data);
|
|
79
|
+
})
|
|
80
|
+
.catch(() => {
|
|
81
|
+
// network/API unavailable — keep the env defaults (deliberate no-op).
|
|
82
|
+
});
|
|
83
|
+
return () => {
|
|
84
|
+
alive = false;
|
|
85
|
+
};
|
|
86
|
+
}, [url, setBrand]);
|
|
87
|
+
|
|
88
|
+
// Keep the tab title branded. Next bakes/sets per-route titles that contain
|
|
89
|
+
// the default name (and, on the home page, the default tagline); swap those
|
|
90
|
+
// for the active brand after each navigation.
|
|
91
|
+
const pathname = usePathname();
|
|
92
|
+
React.useEffect(() => {
|
|
93
|
+
if (
|
|
94
|
+
brand.name === DEFAULT_BRAND.name &&
|
|
95
|
+
brand.tagline === DEFAULT_BRAND.tagline
|
|
96
|
+
) {
|
|
97
|
+
return; // nothing overridden — leave Next's titles as-is
|
|
98
|
+
}
|
|
99
|
+
const id = requestAnimationFrame(() => {
|
|
100
|
+
document.title = document.title
|
|
101
|
+
.split(DEFAULT_BRAND.name)
|
|
102
|
+
.join(brand.name)
|
|
103
|
+
.split(DEFAULT_BRAND.tagline)
|
|
104
|
+
.join(brand.tagline);
|
|
105
|
+
});
|
|
106
|
+
return () => cancelAnimationFrame(id);
|
|
107
|
+
}, [pathname, brand.name, brand.tagline]);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<BrandContext.Provider value={{ brand, setBrand }}>
|
|
111
|
+
{children}
|
|
112
|
+
</BrandContext.Provider>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Read the active brand + `setBrand`. Falls back to the env defaults if no
|
|
117
|
+
* provider is mounted, so components work even outside a BrandProvider. */
|
|
118
|
+
export function useBrand(): BrandContextValue {
|
|
119
|
+
return (
|
|
120
|
+
React.useContext(BrandContext) ?? {
|
|
121
|
+
brand: DEFAULT_BRAND,
|
|
122
|
+
setBrand: () => {},
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The active brand name as text — drop it wherever the app name is shown. */
|
|
128
|
+
export function BrandName() {
|
|
129
|
+
return <>{useBrand().brand.name}</>;
|
|
130
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import Image from "next/image";
|
|
2
4
|
|
|
3
5
|
import { cn } from "@/lib/utils";
|
|
4
|
-
import {
|
|
6
|
+
import { useBrand } from "./brand";
|
|
5
7
|
|
|
6
8
|
type LogoProps = {
|
|
7
9
|
/** Kept for call-site compatibility; the mark is the same either way. */
|
|
@@ -9,19 +11,18 @@ type LogoProps = {
|
|
|
9
11
|
className?: string;
|
|
10
12
|
};
|
|
11
13
|
|
|
12
|
-
/** Optional custom logo: drop an image in /public and set NEXT_PUBLIC_LOGO_URL
|
|
13
|
-
* (e.g. "/logo.svg"). Unset falls back to the built-in mark below. */
|
|
14
|
-
const LOGO_URL = process.env.NEXT_PUBLIC_LOGO_URL;
|
|
15
|
-
|
|
16
14
|
/**
|
|
17
|
-
* App logo
|
|
18
|
-
*
|
|
15
|
+
* App logo. Uses the runtime brand's `logoUrl` (from an API or
|
|
16
|
+
* NEXT_PUBLIC_LOGO_URL, via BrandProvider); when unset, falls back to a
|
|
17
|
+
* self-contained rounded blue badge with a stylised "V". Scales with the
|
|
18
|
+
* `className` size (default 24px).
|
|
19
19
|
*/
|
|
20
20
|
export function Logo({ className }: LogoProps) {
|
|
21
|
-
|
|
21
|
+
const { brand } = useBrand();
|
|
22
|
+
if (brand.logoUrl) {
|
|
22
23
|
return (
|
|
23
24
|
<Image
|
|
24
|
-
src={
|
|
25
|
+
src={brand.logoUrl}
|
|
25
26
|
alt="Logo"
|
|
26
27
|
width={24}
|
|
27
28
|
height={24}
|
|
@@ -33,7 +34,7 @@ export function Logo({ className }: LogoProps) {
|
|
|
33
34
|
<svg
|
|
34
35
|
viewBox="0 0 24 24"
|
|
35
36
|
role="img"
|
|
36
|
-
aria-label={
|
|
37
|
+
aria-label={brand.name}
|
|
37
38
|
className={cn("h-6 w-6", className)}
|
|
38
39
|
>
|
|
39
40
|
<rect width="24" height="24" rx="6" fill="var(--brand-indigo)" />
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
1
3
|
import Link from "next/link";
|
|
2
4
|
|
|
3
5
|
import { cn } from "@/lib/utils";
|
|
4
|
-
import {
|
|
6
|
+
import { useBrand } from "./brand";
|
|
5
7
|
import { Logo } from "./logo";
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -21,6 +23,7 @@ export function Wordmark({
|
|
|
21
23
|
logoClassName?: string;
|
|
22
24
|
textClassName?: string;
|
|
23
25
|
}) {
|
|
26
|
+
const { brand } = useBrand();
|
|
24
27
|
const content = (
|
|
25
28
|
<>
|
|
26
29
|
<Logo className={cn("h-6 w-6 shrink-0", logoClassName)} />
|
|
@@ -30,14 +33,14 @@ export function Wordmark({
|
|
|
30
33
|
textClassName,
|
|
31
34
|
)}
|
|
32
35
|
>
|
|
33
|
-
{
|
|
36
|
+
{brand.name}
|
|
34
37
|
</span>
|
|
35
38
|
</>
|
|
36
39
|
);
|
|
37
40
|
const classes = cn("flex items-center gap-2", className);
|
|
38
41
|
|
|
39
42
|
return href ? (
|
|
40
|
-
<Link href={href} aria-label={
|
|
43
|
+
<Link href={href} aria-label={brand.name} className={classes}>
|
|
41
44
|
{content}
|
|
42
45
|
</Link>
|
|
43
46
|
) : (
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
|
|
14
14
|
import { Button } from "@viliha/vui-ui/button";
|
|
15
15
|
import { Input } from "@viliha/vui-ui/input";
|
|
16
|
+
import { BrandName } from "@/app/_components/brand";
|
|
16
17
|
import {
|
|
17
18
|
AuthCard,
|
|
18
19
|
AuthCardAside,
|
|
@@ -212,7 +213,7 @@ export default function SignInPage() {
|
|
|
212
213
|
Email me a magic link
|
|
213
214
|
</Button>
|
|
214
215
|
<AuthCardAside>
|
|
215
|
-
New to
|
|
216
|
+
New to <BrandName />?{" "}
|
|
216
217
|
<Link href="/auth/signup" className="font-medium text-primary hover:underline">
|
|
217
218
|
Create an account
|
|
218
219
|
</Link>
|
package/template/app/layout.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import { Inter, JetBrains_Mono } from "next/font/google";
|
|
|
3
3
|
|
|
4
4
|
import "./globals.css";
|
|
5
5
|
import { themeInitScript } from "./_components/theme-toggle";
|
|
6
|
+
import { BrandProvider } from "./_components/brand";
|
|
6
7
|
import { SITE } from "@/lib/seo";
|
|
7
8
|
|
|
8
9
|
const inter = Inter({
|
|
@@ -96,7 +97,7 @@ export default function RootLayout({
|
|
|
96
97
|
<body
|
|
97
98
|
className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased`}
|
|
98
99
|
>
|
|
99
|
-
{children}
|
|
100
|
+
<BrandProvider>{children}</BrandProvider>
|
|
100
101
|
</body>
|
|
101
102
|
</html>
|
|
102
103
|
);
|
|
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|
|
2
2
|
|
|
3
3
|
import { Logo } from "@/app/_components/logo";
|
|
4
4
|
import { ThemeToggle } from "@/app/_components/theme-toggle";
|
|
5
|
+
import { BrandName } from "@/app/_components/brand";
|
|
5
6
|
|
|
6
7
|
// Onboarding is a post-signup flow — keep it out of the index.
|
|
7
8
|
export const metadata: Metadata = { robots: { index: false, follow: false } };
|
|
@@ -17,7 +18,7 @@ export default function OnboardingLayout({
|
|
|
17
18
|
<div className="flex items-center gap-2">
|
|
18
19
|
<Logo className="h-7 w-7" />
|
|
19
20
|
<span className="bg-gradient-to-r from-brand-indigo to-brand-violet bg-clip-text text-sm font-bold tracking-tight text-transparent">
|
|
20
|
-
|
|
21
|
+
<BrandName />
|
|
21
22
|
</span>
|
|
22
23
|
</div>
|
|
23
24
|
<div className="flex items-center gap-3 text-muted-foreground">
|
|
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
|
|
|
2
2
|
|
|
3
3
|
import { Logo } from "@/app/_components/logo";
|
|
4
4
|
import { ThemeToggle } from "@/app/_components/theme-toggle";
|
|
5
|
+
import { BrandName } from "@/app/_components/brand";
|
|
5
6
|
|
|
6
7
|
export const metadata: Metadata = {
|
|
7
8
|
title: "Register your business",
|
|
@@ -19,7 +20,7 @@ export default function RegisterBusinessLayout({
|
|
|
19
20
|
<div className="flex items-center gap-2">
|
|
20
21
|
<Logo className="h-7 w-7" />
|
|
21
22
|
<span className="bg-gradient-to-r from-brand-indigo to-brand-violet bg-clip-text text-sm font-bold tracking-tight text-transparent">
|
|
22
|
-
|
|
23
|
+
<BrandName />
|
|
23
24
|
</span>
|
|
24
25
|
</div>
|
|
25
26
|
<ThemeToggle />
|
package/template/lib/seo.ts
CHANGED
|
@@ -3,12 +3,17 @@ import type { Metadata } from "next";
|
|
|
3
3
|
/** Single source of truth for site-wide SEO. Deployed as a static export to a
|
|
4
4
|
* custom domain (see ../CNAME), so URLs are absolute against SITE.url. */
|
|
5
5
|
export const SITE = {
|
|
6
|
-
// App/brand
|
|
7
|
-
//
|
|
6
|
+
// App/brand identity shown in the sidebar, wordmark, auth screens, and the
|
|
7
|
+
// browser-tab metadata (title/description). Override per deployment via env
|
|
8
|
+
// (NEXT_PUBLIC_ = inlined at build). The tab title is `${name} — ${tagline}`.
|
|
8
9
|
name: process.env.NEXT_PUBLIC_APP_NAME ?? "Vui Starter",
|
|
9
|
-
tagline:
|
|
10
|
-
|
|
10
|
+
tagline:
|
|
11
|
+
process.env.NEXT_PUBLIC_APP_TAGLINE ?? "React Admin & CRM Design System",
|
|
12
|
+
// Deploy origin — drives metadataBase, canonical URLs, and OG image URLs.
|
|
13
|
+
// Set NEXT_PUBLIC_APP_URL to your domain (no trailing slash) per deployment.
|
|
14
|
+
url: process.env.NEXT_PUBLIC_APP_URL ?? "https://vui.viliha.com",
|
|
11
15
|
description:
|
|
16
|
+
process.env.NEXT_PUBLIC_APP_DESCRIPTION ??
|
|
12
17
|
"Vui Starter is a free, open-source React admin & CRM design system — a token-driven component library (@viliha/vui-ui) plus a full backoffice demo.",
|
|
13
18
|
ogImage: "/brand/pulse-wordmark.png",
|
|
14
19
|
author: "Suman Bonakurthi",
|