@viglet/viglet-design-system 2026.3.9 → 2026.3.11
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/README.md +528 -44
- package/consumers.json +186 -0
- package/dist/badge-locale-CJkHDlPg.cjs +1 -0
- package/dist/badge-locale-DxDp8Q3S.js +397 -0
- package/dist/bento/bento-actions-menu.d.ts +8 -0
- package/dist/bento/bento-actions-menu.d.ts.map +1 -1
- package/dist/bento/bento-back-to-top.d.ts +9 -1
- package/dist/bento/bento-back-to-top.d.ts.map +1 -1
- package/dist/bento/bento-command-palette.d.ts +56 -9
- package/dist/bento/bento-command-palette.d.ts.map +1 -1
- package/dist/bento/bento-data-table.d.ts +81 -0
- package/dist/bento/bento-data-table.d.ts.map +1 -0
- package/dist/bento/bento-diff.d.ts +41 -0
- package/dist/bento/bento-diff.d.ts.map +1 -0
- package/dist/bento/bento-empty-state.d.ts +8 -1
- package/dist/bento/bento-empty-state.d.ts.map +1 -1
- package/dist/bento/bento-entity-shell.d.ts.map +1 -1
- package/dist/bento/bento-filter-bar.d.ts +60 -0
- package/dist/bento/bento-filter-bar.d.ts.map +1 -0
- package/dist/bento/bento-form-section.d.ts +14 -1
- package/dist/bento/bento-form-section.d.ts.map +1 -1
- package/dist/bento/bento-inline-edit.d.ts.map +1 -1
- package/dist/bento/bento-nav-rail.d.ts +13 -13
- package/dist/bento/bento-nav-rail.d.ts.map +1 -1
- package/dist/bento/bento-nav.d.ts +10 -2
- package/dist/bento/bento-nav.d.ts.map +1 -1
- package/dist/bento/bento-palette-trigger.d.ts +18 -0
- package/dist/bento/bento-palette-trigger.d.ts.map +1 -0
- package/dist/bento/bento-section-chrome.d.ts +19 -39
- package/dist/bento/bento-section-chrome.d.ts.map +1 -1
- package/dist/bento/bento-shell-shortcuts.d.ts +67 -0
- package/dist/bento/bento-shell-shortcuts.d.ts.map +1 -0
- package/dist/bento/bento-shell.d.ts +53 -0
- package/dist/bento/bento-shell.d.ts.map +1 -0
- package/dist/bento/bento-shortcuts-dialog.d.ts +9 -7
- package/dist/bento/bento-shortcuts-dialog.d.ts.map +1 -1
- package/dist/bento/bento-status-marker.d.ts +5 -2
- package/dist/bento/bento-status-marker.d.ts.map +1 -1
- package/dist/bento/bento-tile.d.ts.map +1 -1
- package/dist/bento/bento-user-menu.d.ts +9 -8
- package/dist/bento/bento-user-menu.d.ts.map +1 -1
- package/dist/bento/bento-version-rail.d.ts +31 -0
- package/dist/bento/bento-version-rail.d.ts.map +1 -0
- package/dist/bento/index.d.ts +15 -6
- package/dist/bento/index.d.ts.map +1 -1
- package/dist/bento.cjs +5 -1
- package/dist/bento.css +70 -4
- package/dist/bento.es.js +1778 -701
- package/dist/catalogue.json +5283 -0
- package/dist/components/app-footer.d.ts +7 -0
- package/dist/components/app-footer.d.ts.map +1 -1
- package/dist/components/app-switcher.d.ts.map +1 -1
- package/dist/components/backend-status.d.ts +19 -11
- package/dist/components/backend-status.d.ts.map +1 -1
- package/dist/components/badge-colorful.d.ts +13 -0
- package/dist/components/badge-colorful.d.ts.map +1 -1
- package/dist/components/badge-locale.d.ts +7 -0
- package/dist/components/badge-locale.d.ts.map +1 -1
- package/dist/components/error-boundary.d.ts +15 -13
- package/dist/components/error-boundary.d.ts.map +1 -1
- package/dist/components/language-select.d.ts +8 -0
- package/dist/components/language-select.d.ts.map +1 -1
- package/dist/components/login/login.d.ts +27 -0
- package/dist/components/login/login.d.ts.map +1 -1
- package/dist/components/mode-toggle.d.ts +9 -0
- package/dist/components/mode-toggle.d.ts.map +1 -1
- package/dist/components/nav-secondary.d.ts +7 -0
- package/dist/components/nav-secondary.d.ts.map +1 -1
- package/dist/components/router/dialog.delete.d.ts +8 -0
- package/dist/components/router/dialog.delete.d.ts.map +1 -1
- package/dist/components/router/gradient-button-link.d.ts +6 -0
- package/dist/components/router/gradient-button-link.d.ts.map +1 -1
- package/dist/components/router/index.d.ts +0 -11
- package/dist/components/router/index.d.ts.map +1 -1
- package/dist/components/router/loading-provider.d.ts +4 -0
- package/dist/components/router/loading-provider.d.ts.map +1 -1
- package/dist/components/startup-first/startup-first.d.ts +29 -0
- package/dist/components/startup-first/startup-first.d.ts.map +1 -1
- package/dist/components/ui/accordion.d.ts +17 -0
- package/dist/components/ui/accordion.d.ts.map +1 -1
- package/dist/components/ui/avatar.d.ts +14 -0
- package/dist/components/ui/avatar.d.ts.map +1 -1
- package/dist/components/ui/badge.d.ts +6 -0
- package/dist/components/ui/badge.d.ts.map +1 -1
- package/dist/components/ui/breadcrumb.d.ts +25 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -1
- package/dist/components/ui/button.d.ts +15 -1
- package/dist/components/ui/button.d.ts.map +1 -1
- package/dist/components/ui/card.d.ts +27 -0
- package/dist/components/ui/card.d.ts.map +1 -1
- package/dist/components/ui/checkbox.d.ts +4 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -1
- package/dist/components/ui/dialog.d.ts +36 -0
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/drawer.d.ts +35 -0
- package/dist/components/ui/drawer.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu.d.ts +53 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/floating-formulas-bg.d.ts +8 -0
- package/dist/components/ui/floating-formulas-bg.d.ts.map +1 -1
- package/dist/components/ui/form-actions.d.ts +7 -0
- package/dist/components/ui/form-actions.d.ts.map +1 -1
- package/dist/components/ui/form-item-two-columns.d.ts +7 -0
- package/dist/components/ui/form-item-two-columns.d.ts.map +1 -1
- package/dist/components/ui/form.d.ts +25 -0
- package/dist/components/ui/form.d.ts.map +1 -1
- package/dist/components/ui/glass-card.d.ts +18 -14
- package/dist/components/ui/glass-card.d.ts.map +1 -1
- package/dist/components/ui/gradient-button.d.ts +15 -1
- package/dist/components/ui/gradient-button.d.ts.map +1 -1
- package/dist/components/ui/gradient-switch.d.ts +6 -0
- package/dist/components/ui/gradient-switch.d.ts.map +1 -1
- package/dist/components/ui/hover-card.d.ts +14 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -1
- package/dist/components/ui/icon-picker-dialog.d.ts.map +1 -1
- package/dist/components/ui/index.d.ts +6 -4
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/input.d.ts +6 -0
- package/dist/components/ui/input.d.ts.map +1 -1
- package/dist/components/ui/label.d.ts +4 -0
- package/dist/components/ui/label.d.ts.map +1 -1
- package/dist/components/ui/navigation-menu.d.ts +41 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -1
- package/dist/components/ui/pagination.d.ts +25 -0
- package/dist/components/ui/pagination.d.ts.map +1 -1
- package/dist/components/ui/popover.d.ts +24 -0
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/progress.d.ts +4 -0
- package/dist/components/ui/progress.d.ts.map +1 -1
- package/dist/components/ui/pulse-ring.d.ts +9 -11
- package/dist/components/ui/pulse-ring.d.ts.map +1 -1
- package/dist/components/ui/resizable.d.ts +11 -0
- package/dist/components/ui/resizable.d.ts.map +1 -1
- package/dist/components/ui/section-card.d.ts +9 -0
- package/dist/components/ui/section-card.d.ts.map +1 -1
- package/dist/components/ui/select.d.ts +36 -0
- package/dist/components/ui/select.d.ts.map +1 -1
- package/dist/components/ui/separator.d.ts +4 -0
- package/dist/components/ui/separator.d.ts.map +1 -1
- package/dist/components/ui/sheet.d.ts +29 -0
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sidebar.d.ts +83 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -1
- package/dist/components/ui/skeleton.d.ts +4 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -1
- package/dist/components/ui/slider.d.ts +4 -0
- package/dist/components/ui/slider.d.ts.map +1 -1
- package/dist/components/ui/sonner.d.ts +16 -0
- package/dist/components/ui/sonner.d.ts.map +1 -1
- package/dist/components/ui/stepper.d.ts +8 -0
- package/dist/components/ui/stepper.d.ts.map +1 -1
- package/dist/components/ui/sticky-save-bar.d.ts +7 -0
- package/dist/components/ui/sticky-save-bar.d.ts.map +1 -1
- package/dist/components/ui/switch.d.ts +4 -0
- package/dist/components/ui/switch.d.ts.map +1 -1
- package/dist/components/ui/table.d.ts +31 -0
- package/dist/components/ui/table.d.ts.map +1 -1
- package/dist/components/ui/tabs.d.ts +16 -0
- package/dist/components/ui/tabs.d.ts.map +1 -1
- package/dist/components/ui/textarea.d.ts +4 -0
- package/dist/components/ui/textarea.d.ts.map +1 -1
- package/dist/components/ui/toggle-group.d.ts +7 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -1
- package/dist/components/ui/toggle.d.ts +4 -0
- package/dist/components/ui/toggle.d.ts.map +1 -1
- package/dist/components/ui/tooltip.d.ts +18 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -1
- package/dist/components/ui/user-avatar.d.ts +2 -1
- package/dist/components/ui/user-avatar.d.ts.map +1 -1
- package/dist/components/ui/viglet-assistant.d.ts +103 -0
- package/dist/components/ui/viglet-assistant.d.ts.map +1 -0
- package/dist/components/ui/viglet-avatar.d.ts +44 -0
- package/dist/components/ui/viglet-avatar.d.ts.map +1 -0
- package/dist/contexts/breadcrumb.context.d.ts +7 -0
- package/dist/contexts/breadcrumb.context.d.ts.map +1 -1
- package/dist/contexts/user.context.d.ts +8 -0
- package/dist/contexts/user.context.d.ts.map +1 -1
- package/dist/exports.json +359 -30
- package/dist/floating-formulas-bg.cjs +1 -1
- package/dist/floating-formulas-bg.es.js +56 -55
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/use-assistant-notifications.d.ts +15 -0
- package/dist/hooks/use-assistant-notifications.d.ts.map +1 -0
- package/dist/i18n/index.d.ts +168 -0
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/locales/en/assistant.json.d.ts +25 -0
- package/dist/i18n/locales/en/bento.json.d.ts +48 -2
- package/dist/i18n/locales/en/common.json.d.ts +6 -1
- package/dist/i18n/locales/en/language.json.d.ts +8 -0
- package/dist/i18n/locales/pt/assistant.json.d.ts +25 -0
- package/dist/i18n/locales/pt/bento.json.d.ts +48 -2
- package/dist/i18n/locales/pt/common.json.d.ts +6 -1
- package/dist/i18n/locales/pt/language.json.d.ts +8 -0
- package/dist/i18n.cjs +1 -1
- package/dist/i18n.es.js +572 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.es.js +2280 -733
- package/dist/lib/busy.d.ts +26 -0
- package/dist/lib/busy.d.ts.map +1 -0
- package/dist/lib/corner-slot.d.ts +15 -0
- package/dist/lib/corner-slot.d.ts.map +1 -0
- package/dist/{loading-provider-BWWhhfEn.js → loading-provider-ClDkFPuv.js} +84 -81
- package/dist/loading-provider-DGuCfsl9.cjs +1 -0
- package/dist/preset.css +7 -10
- package/dist/router.cjs +1 -1
- package/dist/router.es.js +2 -776
- package/dist/user.context-Cr3GAQ_r.js +762 -0
- package/dist/user.context-hbdvmXOn.cjs +1 -0
- package/dist/utils-Fk5jmzzb.js +41 -0
- package/dist/utils-Oagwe_oa.cjs +1 -0
- package/dist/viglet-design-system.css +1 -1
- package/dist/vite/boot-loader.d.ts +33 -2
- package/dist/vite/boot-loader.d.ts.map +1 -1
- package/dist/vite.cjs +16 -5
- package/dist/vite.es.js +27 -14
- package/docs/BENTO-AUTHORING.md +81 -11
- package/docs/reference/grounds.dc.html +9 -9
- package/package.json +16 -4
- package/scripts/consumer-entries.mjs +209 -0
- package/scripts/lib/contrast.mjs +301 -0
- package/scripts/mcp.mjs +300 -0
- package/scripts/page-lint.mjs +428 -0
- package/scripts/page-reference.mjs +54 -10
- package/dist/badge-locale-CoEEv7s5.js +0 -581
- package/dist/badge-locale-hQyWhAKe.cjs +0 -7
- package/dist/breadcrumb.context-KQut7j8U.js +0 -917
- package/dist/breadcrumb.context-Vwc3lAlf.cjs +0 -1
- package/dist/components/router/blank-slate.d.ts +0 -21
- package/dist/components/router/blank-slate.d.ts.map +0 -1
- package/dist/components/router/grid.list.d.ts +0 -39
- package/dist/components/router/grid.list.d.ts.map +0 -1
- package/dist/components/router/internal.sidebar.d.ts +0 -61
- package/dist/components/router/internal.sidebar.d.ts.map +0 -1
- package/dist/components/router/nav-main.d.ts +0 -25
- package/dist/components/router/nav-main.d.ts.map +0 -1
- package/dist/components/router/nav-user.d.ts +0 -24
- package/dist/components/router/nav-user.d.ts.map +0 -1
- package/dist/components/router/page-content.d.ts +0 -12
- package/dist/components/router/page-content.d.ts.map +0 -1
- package/dist/components/router/page-header.d.ts +0 -23
- package/dist/components/router/page-header.d.ts.map +0 -1
- package/dist/components/router/page.d.ts +0 -22
- package/dist/components/router/page.d.ts.map +0 -1
- package/dist/components/router/sticky.page.header.d.ts +0 -85
- package/dist/components/router/sticky.page.header.d.ts.map +0 -1
- package/dist/components/router/sub.page.d.ts +0 -50
- package/dist/components/router/sub.page.d.ts.map +0 -1
- package/dist/components/router/sub.page.header.d.ts +0 -37
- package/dist/components/router/sub.page.header.d.ts.map +0 -1
- package/dist/i18n-DD3rQOWi.js +0 -426
- package/dist/i18n-DrBq-qJJ.cjs +0 -1
- package/dist/lib/react-markers.d.ts +0 -17
- package/dist/lib/react-markers.d.ts.map +0 -1
- package/dist/loading-provider-CmeMqLGk.cjs +0 -1
- package/dist/user.context-B9vIBzY5.cjs +0 -1
- package/dist/user.context-D1PxP4D4.js +0 -512
package/README.md
CHANGED
|
@@ -1,25 +1,31 @@
|
|
|
1
1
|
# Viglet Design System
|
|
2
2
|
|
|
3
3
|
Shared component library, design tokens, hooks, utilities, and i18n for the
|
|
4
|
-
|
|
5
|
-
the Cloud Console, the Cloud Home, Schools,
|
|
6
|
-
`consumers.json` is the list, and it is
|
|
4
|
+
nine applications that install it: the Turing, Dumont and Shio consoles,
|
|
5
|
+
the Cloud Console, the Cloud Home, Schools, the Roadkeep GUI,
|
|
6
|
+
the Openviglet Website and Viglet Docs. `consumers.json` is the list, and it is
|
|
7
|
+
what the guards read.
|
|
7
8
|
|
|
8
9
|
**[Browse the component catalogue](https://openviglet.github.io/viglet-design-system/)** — every
|
|
9
10
|
component, its variants and its props, rebuilt on each commit to `2026.3`. Look
|
|
10
11
|
there before writing a component: the catalogue is the answer to "does this
|
|
11
12
|
already exist".
|
|
12
13
|
|
|
13
|
-
The
|
|
14
|
+
The nine applications that install this package are the consumers it holds itself
|
|
14
15
|
to, declared in [`consumers.json`](consumers.json) rather than remembered: each
|
|
15
|
-
entry names its framework, its chrome, the accent it renders with
|
|
16
|
-
subpaths it takes, and CI refuses prose that names a subset as
|
|
17
|
-
the whole set.
|
|
16
|
+
entry names its framework, its chrome, the accent it renders with, where its
|
|
17
|
+
source is and the subpaths it takes, and CI refuses prose that names a subset as
|
|
18
|
+
though it were the whole set. A tenth is one entry there, and the checks widen
|
|
19
|
+
with it. The file ships in the package, and a consumer holds its own entry to its
|
|
20
|
+
source with [`viglet-ds-consumer-entries`](#keeping-your-entry-in-the-register-true).
|
|
18
21
|
|
|
19
22
|
Three are Vite consoles (Turing, Shio, Dumont), one is a Vite platform home
|
|
20
|
-
(the Cloud Home), one is a Vite desktop shell (the Roadkeep GUI),
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
(the Cloud Home), one is a Vite desktop shell (the Roadkeep GUI), one is a Vite
|
|
24
|
+
marketing site (the Openviglet Website), two are Next applications
|
|
25
|
+
(the Cloud Console and Schools), and one is Docusaurus (Viglet Docs). The split
|
|
26
|
+
matters: a
|
|
27
|
+
Next consumer takes no `./router` and no `./vite`, and Viglet Docs takes no root
|
|
28
|
+
entry at all — only `./preset` and the background.
|
|
23
29
|
|
|
24
30
|
## Installation
|
|
25
31
|
|
|
@@ -27,30 +33,41 @@ consumer takes no `./router` and no `./vite`.
|
|
|
27
33
|
pnpm add @viglet/viglet-design-system
|
|
28
34
|
```
|
|
29
35
|
|
|
36
|
+
**Declare `react-hook-form` yourself.** It is a required peer, not something this
|
|
37
|
+
package brings: the root entry exports `Form`, and that module re-exports
|
|
38
|
+
`FormProvider`, `useFormContext` and `useFormState` straight from it. A second
|
|
39
|
+
copy in your tree is a second React context, and `useFormContext` then returns
|
|
40
|
+
null inside a form that looks correctly wired. Declaring it is what keeps the
|
|
41
|
+
install to one copy — `npm ls react-hook-form` should say `deduped`.
|
|
42
|
+
|
|
30
43
|
## Trying a change in a product before publishing
|
|
31
44
|
|
|
32
45
|
A change here is a change to shared chrome, so the question is always what it
|
|
33
|
-
does to Shio, Turing, Dumont, the Cloud Console, the Cloud Home, Schools
|
|
34
|
-
Roadkeep GUI
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
does to Shio, Turing, Dumont, the Cloud Console, the Cloud Home, Schools,
|
|
47
|
+
the Roadkeep GUI, the Openviglet Website and Viglet Docs — and the answer should
|
|
48
|
+
not require a publish. The last two take the package by caret range rather than
|
|
49
|
+
pinning it, so they also receive each release the moment they install one. From
|
|
50
|
+
this checkout:
|
|
37
51
|
|
|
38
52
|
```bash
|
|
39
|
-
pnpm use:local # build, then push dist into every
|
|
53
|
+
pnpm use:local # build, then push dist into every consumer checked out here
|
|
40
54
|
pnpm use:local --list # show what it would write to, and stop
|
|
41
55
|
```
|
|
42
56
|
|
|
43
|
-
It finds the
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
the
|
|
57
|
+
It finds each consumer at the `checkout` its entry in `consumers.json` declares,
|
|
58
|
+
relative to this repository, with `{line}` standing for this checkout's version
|
|
59
|
+
line, so a 2026.2 worktree pushes into the 2026.2 products. A declared checkout
|
|
60
|
+
that is not there is named, not skipped in silence, and finding none at all is an
|
|
61
|
+
error. It finds where each consumer keeps the installed copy by following the
|
|
62
|
+
link its own package manager made, so neither a version bump nor a switch of
|
|
63
|
+
package manager breaks the loop. Nothing in the product's manifest or lockfile
|
|
64
|
+
changes: `pnpm install` in the product puts the published build back.
|
|
48
65
|
|
|
49
|
-
Pass directories to narrow it, and `--no-build` to reuse the
|
|
50
|
-
disk:
|
|
66
|
+
Pass consumer ids or directories to narrow it, and `--no-build` to reuse the
|
|
67
|
+
`dist` already on disk:
|
|
51
68
|
|
|
52
69
|
```bash
|
|
53
|
-
pnpm use:local
|
|
70
|
+
pnpm use:local shio --no-build
|
|
54
71
|
```
|
|
55
72
|
|
|
56
73
|
One thing it refuses to do: push over a tree whose dependencies are behind. It
|
|
@@ -74,8 +91,8 @@ viglet-ds-check-duplicates src
|
|
|
74
91
|
It fails, naming the import that replaces each local copy:
|
|
75
92
|
|
|
76
93
|
```
|
|
77
|
-
src/components/
|
|
78
|
-
replace it with: import {
|
|
94
|
+
src/components/bento-hero.tsx:18 declares BentoHero
|
|
95
|
+
replace it with: import { BentoHero } from "@viglet/viglet-design-system/bento"
|
|
79
96
|
```
|
|
80
97
|
|
|
81
98
|
One-line re-export shims are the sanctioned pattern and are skipped. When a
|
|
@@ -98,6 +115,114 @@ worked through, and drop it once the count is zero. `--json` prints the findings
|
|
|
98
115
|
for a bot to read; `--manifest <path>` checks against an export list other than
|
|
99
116
|
the installed one.
|
|
100
117
|
|
|
118
|
+
## Linting a page against the shell
|
|
119
|
+
|
|
120
|
+
Two rules in [docs/BENTO-AUTHORING.md](docs/BENTO-AUTHORING.md) pass every other
|
|
121
|
+
gate when broken: a page that sets its own width, gutter or vertical rhythm, and a
|
|
122
|
+
stylesheet that sets `--vg-primary` directly instead of through its
|
|
123
|
+
`--vg-primary-*-base` inputs. `viglet-ds-page-lint` checks both. Point it at the
|
|
124
|
+
directories whose pages render inside `BentoShell`:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
viglet-ds-page-lint src
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
src/app/home.page.tsx:125 page.column the page's outermost element sets max-w-7xl px-6 pb-12
|
|
132
|
+
remove them: BentoShell sets the column, and column="narrow" or "wide" names the other widths
|
|
133
|
+
src/index.css:14 primary.direct sets --vg-primary
|
|
134
|
+
claim it through the --vg-primary-base and --vg-primary-foreground-base inputs, light and -dark, at :root
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
A page is a file ending in `.page.tsx` or `.page.jsx`, and the lint checks the
|
|
138
|
+
outermost element its default export returns. `--page-pattern <regex>` names your
|
|
139
|
+
own convention. It reads pages with the TypeScript compiler, so `typescript` must
|
|
140
|
+
be installed in the product. A deliberate exception takes a reason, in the file:
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
// viglet-ds-allow-page-column -- a kiosk rendered outside the shell
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`viglet-ds-allow-primary -- <reason>` does the same for the second rule.
|
|
147
|
+
`--warn` and `--json` work as they do for `viglet-ds-check-duplicates`.
|
|
148
|
+
|
|
149
|
+
Pass the stylesheet where you re-key the palette to also measure it:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
viglet-ds-page-lint src --contrast src/index.css
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
It lays your stylesheet over the preset the way the two cascade and measures every
|
|
156
|
+
token pair your stylesheet changes on both grounds: each `--vg-X` with its
|
|
157
|
+
`--vg-X-foreground`, text and muted text on the page, the accented label on the
|
|
158
|
+
page, and the white label on the accent fill. A pair under 4.5:1 on either ground
|
|
159
|
+
is a finding at the line that declares the token, with both grounds' ratios:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
src/index.css:41 contrast.pair --vg-accent-fg on --vg-background is 3.59:1 on the light ground and 7.53:1 on the dark ground; 4.5:1 needed
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
It reads `oklch()`, hex, `rgb()`, `hsl()`, `var()` chains and
|
|
166
|
+
`color-mix(in oklab, …)`. Anything else is reported as unread rather than
|
|
167
|
+
guessed. `viglet-ds-allow-contrast -- <reason>` in the stylesheet keeps a pair
|
|
168
|
+
you have signed off.
|
|
169
|
+
|
|
170
|
+
## Keeping your entry in the register true
|
|
171
|
+
|
|
172
|
+
`consumers.json` records the subpaths each product takes, and this package's
|
|
173
|
+
guards read it. `viglet-ds-consumer-entries` measures what your source actually imports and
|
|
174
|
+
compares it with your entry there, in both directions. Run it from the product's
|
|
175
|
+
package root, in its own test step:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
viglet-ds-consumer-entries
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
It finds your entry by the `name` in your `package.json` (`--consumer <id>`
|
|
182
|
+
names it outright), scans the `sourceRoots` that entry declares, and fails on a
|
|
183
|
+
subpath you import and do not declare, or one you declare and import nowhere:
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
consumer-entries: shio's source and its entry in consumers.json disagree
|
|
187
|
+
|
|
188
|
+
imported, and not declared:
|
|
189
|
+
./router first at src/components/dialog.delete.tsx:17
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Tests, stories and comments inside the roots are not counted. A failure means
|
|
193
|
+
the entry here is wrong, so the fix is a change to `consumers.json` in this
|
|
194
|
+
repository. `--json` prints the measurement; `--root <dir>` measures a checkout
|
|
195
|
+
other than the current directory.
|
|
196
|
+
|
|
197
|
+
## Using it from Claude Code
|
|
198
|
+
|
|
199
|
+
This repository publishes a Claude Code plugin, `viglet-ds`, for a product that
|
|
200
|
+
installs the package. It serves the component catalogue over MCP, loads a skill
|
|
201
|
+
that points a session at it, adds `/viglet-ds-check` to run the CLIs above
|
|
202
|
+
against the working tree, and denies a write that declares a component the
|
|
203
|
+
package already exports, naming the import to use instead.
|
|
204
|
+
|
|
205
|
+
The catalogue server is a bin of its own, `viglet-ds-mcp`, so any MCP client can
|
|
206
|
+
start it from a product's install. It has two tools: `find_component` ranks the
|
|
207
|
+
components that do a job, and `read_component` returns one component's purpose,
|
|
208
|
+
props, the contract sections that govern it and an import to start from. The
|
|
209
|
+
authoring contract, the boundary and the token reference are resources
|
|
210
|
+
(`viglet-ds://authoring`, `viglet-ds://boundary`, `viglet-ds://tokens`). The data
|
|
211
|
+
is `dist/catalogue.json`, generated at build time from the declarations you
|
|
212
|
+
compile against and also exported as `./catalogue.json`. A component's purpose is
|
|
213
|
+
its own doc comment, and every exported component has one: the build refuses a
|
|
214
|
+
component that arrives without it.
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
/plugin marketplace add openviglet/viglet-design-system
|
|
218
|
+
/plugin install viglet-ds@viglet-design-system
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
With the plugin enabled in a repository's `.claude/settings.json`,
|
|
222
|
+
`viglet-ds-page-reference` writes only the artboards and leaves the skill to the
|
|
223
|
+
plugin. [claude-plugin/README.md](claude-plugin/README.md) has the settings to
|
|
224
|
+
commit and the package range the plugin supports.
|
|
225
|
+
|
|
101
226
|
## Setup
|
|
102
227
|
|
|
103
228
|
### 1. Import styles
|
|
@@ -147,6 +272,12 @@ one the catalogue shows.
|
|
|
147
272
|
|
|
148
273
|
### 2. Initialize i18n
|
|
149
274
|
|
|
275
|
+
`/i18n` is the only way in. The root entry used to re-export these three, which
|
|
276
|
+
meant every consumer of `.` resolved `i18next` and
|
|
277
|
+
`i18next-browser-languagedetector` and carried the locale bundle whether or not
|
|
278
|
+
it ever asked for a translation; since 2026.3 it does not, and a product that
|
|
279
|
+
imported them from `.` changes the specifier and nothing else.
|
|
280
|
+
|
|
150
281
|
```ts
|
|
151
282
|
import { initVigI18n } from "@viglet/viglet-design-system/i18n";
|
|
152
283
|
|
|
@@ -166,6 +297,31 @@ import i18n from "i18next";
|
|
|
166
297
|
registerVigTranslations(i18n);
|
|
167
298
|
```
|
|
168
299
|
|
|
300
|
+
**Both merge key by key, and your key always wins.** Shipping your own `common`
|
|
301
|
+
does not cost you the package's — `common.save` stays yours and `common.next`
|
|
302
|
+
still resolves to the word this package ships. `registerVigTranslations` never
|
|
303
|
+
replaces a key the host already has, so calling it twice is safe.
|
|
304
|
+
|
|
305
|
+
**`initVigI18n` takes any language, not only the two this package ships.** Pass
|
|
306
|
+
`es`, `fr` or anything else and it arrives whole; `en` and `pt` still merge leaf
|
|
307
|
+
by leaf with yours. The package's own strings exist in `en` and `pt` only, so
|
|
308
|
+
`fallbackLng` stays `en` and a screen asking for a string the package ships
|
|
309
|
+
reads English under a third language until you translate it yourself.
|
|
310
|
+
|
|
311
|
+
This used to merge a namespace at a time: a product with its own `common`
|
|
312
|
+
replaced the package's outright, and every string the package asked for under
|
|
313
|
+
that namespace fell back to its English default — in a Portuguese product,
|
|
314
|
+
silently. If you worked around it by renaming a namespace or copying the
|
|
315
|
+
package's keys into yours, you can stop.
|
|
316
|
+
|
|
317
|
+
**One string lands before any of this.** The boot loader from
|
|
318
|
+
`@viglet/viglet-design-system/vite` is HTML written at build time and paints
|
|
319
|
+
before a bundle runs, so it cannot ask i18next for anything. Its status region is
|
|
320
|
+
named `"Loading <title>"` unless you say otherwise — pass `loadingLabel` for the
|
|
321
|
+
phrase in your language, or `loadingLabels` (a language tag to a phrase) for a
|
|
322
|
+
product that ships several, which the loader's inline script picks from
|
|
323
|
+
`navigator.language`.
|
|
324
|
+
|
|
169
325
|
### 3. Setup providers
|
|
170
326
|
|
|
171
327
|
```tsx
|
|
@@ -260,6 +416,14 @@ import {
|
|
|
260
416
|
} from "@viglet/viglet-design-system";
|
|
261
417
|
```
|
|
262
418
|
|
|
419
|
+
`Button` and `GradientButton` take `loading` for an action in flight. The button
|
|
420
|
+
shows a spinner, sets `aria-busy`, ignores a second press (including a form's
|
|
421
|
+
submit) and keeps keyboard focus. Do not pass `disabled` for that: a disabled
|
|
422
|
+
button drops the focus of the control just pressed, just as the result is
|
|
423
|
+
announced. For a control that is unavailable for now, such as a Save with nothing
|
|
424
|
+
to save, pass `aria-disabled="true"`. It stays focusable and ignores activation.
|
|
425
|
+
Keep `disabled` for a control that is genuinely unavailable.
|
|
426
|
+
|
|
263
427
|
### App Components
|
|
264
428
|
|
|
265
429
|
```tsx
|
|
@@ -274,16 +438,25 @@ import {
|
|
|
274
438
|
```
|
|
275
439
|
|
|
276
440
|
A page's chrome — its header, its list, its sidebar — comes from the
|
|
277
|
-
[bento layer](#the-bento-layer)
|
|
278
|
-
|
|
441
|
+
[bento layer](#the-bento-layer). It is the only page vocabulary this package
|
|
442
|
+
ships; if you are upgrading from a release that still had `PageHeader`,
|
|
443
|
+
`GridList` or `InternalSidebar`, read the next section.
|
|
279
444
|
|
|
280
|
-
###
|
|
445
|
+
### Moving off the console era
|
|
281
446
|
|
|
282
|
-
This package
|
|
447
|
+
This package used to export two complete page vocabularies. The **console era**
|
|
448
|
+
is gone: `pnpm chrome:census` counts, per consumer checked out beside this
|
|
449
|
+
repository, the source files taking a console-era component — directly or
|
|
450
|
+
through a re-export shim — and it reads zero across all nine, so the eleven
|
|
451
|
+
components below were removed. The **[bento layer](#the-bento-layer)** is what
|
|
452
|
+
remains, and with one chrome left there is nothing to choose between: the
|
|
453
|
+
`SectionCardChromeProvider` that used to declare which one a page rendered in
|
|
454
|
+
went with them.
|
|
283
455
|
|
|
284
|
-
|
|
456
|
+
Each row is a swap rather than a rename: the replacement takes its own props,
|
|
457
|
+
and the note says where the two differ.
|
|
285
458
|
|
|
286
|
-
|
|
|
459
|
+
| Removed | Use instead | |
|
|
287
460
|
|---|---|---|
|
|
288
461
|
| `PageHeader` | `BentoHero` | Same title/subtitle/icon, plus a tone — the chip follows the product accent. |
|
|
289
462
|
| `SubPageHeader` | `BentoHero` | With `BentoBackLink` for the back target and `BentoActionsMenu` for the menu. |
|
|
@@ -294,9 +467,166 @@ Nothing is being removed. No product has started cutting over, and a removal wil
|
|
|
294
467
|
| `NavMain` | `BentoNavRail` | |
|
|
295
468
|
| `NavUser` | `BentoUserMenu` | Routes and feature flags arrive as props. |
|
|
296
469
|
| `SubPage` | `BentoEntityShell` | Renders through a render prop, so the shell never learns what an entity is. |
|
|
297
|
-
| `Page`, `PageContent` |
|
|
470
|
+
| `Page`, `PageContent` | `BentoShell` | Takes the rail, the header's two edges and the page; it owns `main` and the reading column, so a page sets no width. |
|
|
471
|
+
|
|
472
|
+
`DialogDelete`, `LoadProvider` and `GradientButtonLink` were never console-era — the bento layer uses them itself, and they are the whole of `./router` now.
|
|
473
|
+
|
|
474
|
+
### The mascot
|
|
475
|
+
|
|
476
|
+
`VigletAvatar` is the Viglet mascot: a small sun that shows what the system is
|
|
477
|
+
doing. It is one `<canvas>` and no new dependency — the faceted core is drawn
|
|
478
|
+
directly, so a product gets the mascot by installing this package and nothing
|
|
479
|
+
else.
|
|
480
|
+
|
|
481
|
+
```tsx
|
|
482
|
+
import { VigletAvatar } from "@viglet/viglet-design-system";
|
|
483
|
+
|
|
484
|
+
<VigletAvatar state="working" size={128} />;
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
The five states are named for the toast kinds, so a product that already reports
|
|
488
|
+
through `toast` has nothing to translate:
|
|
489
|
+
|
|
490
|
+
| `state` | When | What it does |
|
|
491
|
+
|---|---|---|
|
|
492
|
+
| `idle` | nothing is happening | a slow, low glow |
|
|
493
|
+
| `working` | a request is in flight | an orbit with a travelling arc |
|
|
494
|
+
| `success` | it finished | a swell, then a pulse outward |
|
|
495
|
+
| `error` | it failed | the light goes out — it does not turn red |
|
|
496
|
+
| `attention` | something arrived | a ring rises from below |
|
|
497
|
+
|
|
498
|
+
Four further props shape it rather than drive it: `compact` pulls the camera in
|
|
499
|
+
and drops the pool of light, for a collapsed dock; `unread` holds a slow orbit
|
|
500
|
+
while an answer waits; `activity` is a counter you bump to make the mascot react
|
|
501
|
+
to something smaller than a state change, like a keystroke; and `seed` fixes the
|
|
502
|
+
field of rising embers. The same props always draw the same sparks, so a product
|
|
503
|
+
that wants a different field per visit passes a changing `seed` itself.
|
|
504
|
+
|
|
505
|
+
The mascot is decorative (`aria-hidden`) and says nothing a screen reader can
|
|
506
|
+
use — give the surface that wraps it the accessible name. It draws one frame and
|
|
507
|
+
starts no animation loop for a reader whose system asks for reduced motion, and
|
|
508
|
+
`paused` does the same for a product with its own motion switch. When nothing is
|
|
509
|
+
happening it stops drawing altogether rather than holding a loop open.
|
|
510
|
+
|
|
511
|
+
### The assistant dock
|
|
512
|
+
|
|
513
|
+
`VigletAssistant` is the surface the mascot lives in. Collapsed it is a status
|
|
514
|
+
light with the system's last sentence typed beside it; open it is a panel with a
|
|
515
|
+
transcript and a composer.
|
|
516
|
+
|
|
517
|
+
```tsx
|
|
518
|
+
import { VigletAssistant } from "@viglet/viglet-design-system";
|
|
519
|
+
|
|
520
|
+
<VigletAssistant
|
|
521
|
+
state={state}
|
|
522
|
+
caption="/q3-results published at 14:02."
|
|
523
|
+
messages={messages}
|
|
524
|
+
busy={busy}
|
|
525
|
+
onSend={(text) => ask(text)}
|
|
526
|
+
/>;
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
**The dock knows no backend.** `messages`, `busy` and `onSend` are the whole
|
|
530
|
+
contract — the endpoint, the prompt and the key stay in your product. Leaving
|
|
531
|
+
`onSend` off is how you turn the chat off: the composer is not rendered at all
|
|
532
|
+
and the dock becomes a place the system reports from, which is what a product
|
|
533
|
+
that only wants notification feedback should mount.
|
|
534
|
+
|
|
535
|
+
A message may carry one `action`, so an answer can offer something to do with
|
|
536
|
+
it — the label is your copy, already translated:
|
|
298
537
|
|
|
299
|
-
|
|
538
|
+
```tsx
|
|
539
|
+
messages={[{
|
|
540
|
+
role: "assistant",
|
|
541
|
+
text: "Lead with the number.",
|
|
542
|
+
action: { label: "Use this title", onSelect: () => setTitle(suggested) },
|
|
543
|
+
}]}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**What the system reports is a `report`, not an assistant message.** A publish,
|
|
547
|
+
a failure or an arrival has a `tone` (one of the mascot's five states), a time,
|
|
548
|
+
a `read` flag and up to three `actions`, and it renders apart from the
|
|
549
|
+
conversation. It never opens a composer, so a dock with no `onSend` lists reports
|
|
550
|
+
on their own. You own the list, as you own the messages:
|
|
551
|
+
|
|
552
|
+
```tsx
|
|
553
|
+
<VigletAssistant
|
|
554
|
+
messages={reports}
|
|
555
|
+
onRead={(id) => markRead(id)}
|
|
556
|
+
onDismiss={(id) => dismiss(id)}
|
|
557
|
+
/>
|
|
558
|
+
|
|
559
|
+
const reports = [{
|
|
560
|
+
id: "publish-q3",
|
|
561
|
+
role: "report",
|
|
562
|
+
tone: "error",
|
|
563
|
+
text: "Could not publish /q3-results.",
|
|
564
|
+
at: failedAt,
|
|
565
|
+
actions: [
|
|
566
|
+
{ label: "Retry", onSelect: retry },
|
|
567
|
+
{ label: "Open the page", onSelect: openPage },
|
|
568
|
+
],
|
|
569
|
+
}];
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
Collapsed, the orb counts the unread reports, and the caption is the newest one
|
|
573
|
+
unless you pass `caption`. The mascot's state follows that report's tone unless
|
|
574
|
+
you pass `state`. Pass `unread` a number to count something else. `onRead` fires
|
|
575
|
+
for each unread report when the dock opens or closes, since it was on screen,
|
|
576
|
+
and a dismiss button appears on each report once you pass `onDismiss`. A report
|
|
577
|
+
is announced once, when it arrives, and not again when the dock opens or
|
|
578
|
+
re-renders.
|
|
579
|
+
|
|
580
|
+
It pins itself to the bottom-right of the viewport; pass `inline` to render it
|
|
581
|
+
in flow and place it yourself. In a bento product, pass it to `BentoShell` as
|
|
582
|
+
`dock` instead: the shell holds the corner, stacks `BentoBackToTop` above the
|
|
583
|
+
dock, and keeps either from covering the other. `open` / `onOpenChange` make it controlled,
|
|
584
|
+
Escape collapses it, and the caption is typed for everyone while a screen reader
|
|
585
|
+
is handed the whole sentence at once.
|
|
586
|
+
|
|
587
|
+
Its own words — the state names, the two buttons, the placeholder — come from
|
|
588
|
+
the `assistant.*` keys this package ships in `en` and `pt`. See
|
|
589
|
+
[Initialize i18n](#2-initialize-i18n); everything else it says is your copy.
|
|
590
|
+
|
|
591
|
+
#### Letting notifications drive it
|
|
592
|
+
|
|
593
|
+
You already report outcomes through `toast`. `useAssistantNotifications` reads
|
|
594
|
+
the Toaster back, so the mascot moves without a second call beside each one:
|
|
595
|
+
|
|
596
|
+
```tsx
|
|
597
|
+
import {
|
|
598
|
+
Toaster,
|
|
599
|
+
VigletAssistant,
|
|
600
|
+
useAssistantNotifications,
|
|
601
|
+
} from "@viglet/viglet-design-system";
|
|
602
|
+
|
|
603
|
+
function Chrome() {
|
|
604
|
+
const { state, caption, activity } = useAssistantNotifications();
|
|
605
|
+
|
|
606
|
+
return (
|
|
607
|
+
<>
|
|
608
|
+
<Toaster />
|
|
609
|
+
<VigletAssistant state={state} caption={caption} activity={activity} />
|
|
610
|
+
</>
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
It **wraps nothing**. Call sonner from anywhere in your app — from a module that
|
|
616
|
+
has never heard of this hook — and the mascot still moves.
|
|
617
|
+
|
|
618
|
+
| Toast | State |
|
|
619
|
+
|---|---|
|
|
620
|
+
| `toast.loading` | `working` |
|
|
621
|
+
| `toast.success` | `success` |
|
|
622
|
+
| `toast.error` | `error` |
|
|
623
|
+
| `toast.warning`, `toast.info` | `attention` |
|
|
624
|
+
| nothing on screen | `idle` |
|
|
625
|
+
|
|
626
|
+
With several up, work in progress outranks an outcome and a failure outranks a
|
|
627
|
+
success. `caption` is the toast's own title when it is a string — a toast
|
|
628
|
+
rendered as JSX has no sentence to lift, so you get `null` rather than a guess.
|
|
629
|
+
The hook is opt-in: pass `state` yourself and nothing here interferes.
|
|
300
630
|
|
|
301
631
|
### Hooks
|
|
302
632
|
|
|
@@ -306,6 +636,7 @@ import {
|
|
|
306
636
|
useDateLocale,
|
|
307
637
|
useGridAdapter,
|
|
308
638
|
useSubPageBreadcrumb,
|
|
639
|
+
useAssistantNotifications,
|
|
309
640
|
useTheme,
|
|
310
641
|
useBreadcrumb,
|
|
311
642
|
useCurrentUser,
|
|
@@ -357,19 +688,29 @@ import type {
|
|
|
357
688
|
|
|
358
689
|
## What's Included
|
|
359
690
|
|
|
360
|
-
### UI Primitives (
|
|
691
|
+
### UI Primitives (48 components)
|
|
692
|
+
|
|
693
|
+
Accordion, Avatar, Badge, Breadcrumb, Button, Card, Checkbox, Dialog, Drawer, DropdownMenu, FloatingFormulasBg, Form, FormActions, FormItemTwoColumns, GlassCard, GradientButton, GradientSwitch, HoverCard, IconPickerDialog, Input, Label, NavigationMenu, Pagination, Popover, Progress, PulseRing, Resizable, SectionCard, Select, Separator, Sheet, Sidebar, Skeleton, Slider, Stepper, StickySaveBar, Switch, Table, Tabs, Textarea, Toaster (sonner), Toggle, ToggleGroup, Tooltip, UserAvatar, UserMenu, VigletAssistant ([the dock](#the-assistant-dock)), VigletAvatar ([the mascot](#the-mascot))
|
|
694
|
+
|
|
695
|
+
### App Components (19 components)
|
|
361
696
|
|
|
362
|
-
|
|
697
|
+
AppFooter, AppSwitcher, BackendStatusBanner, BackendStatusProvider, BadgeColorful, BadgeLocale, DialogDelete, ErrorBoundary, GradientButtonLink, LanguageSelect, LanguageSwitcher, LoadProvider, Login, ModeToggle, ModeToggleSidebar, NavSecondary, StartupFirst, ThemeProvider, VigletAppSwitcher
|
|
363
698
|
|
|
364
|
-
|
|
699
|
+
`DialogDelete`, `GradientButtonLink` and `LoadProvider` are imported from `@viglet/viglet-design-system/router`; every other name above comes from the package root. `Login` and `StartupFirst` are compounds — each is a root with named parts on it, the way `Card` is.
|
|
365
700
|
|
|
366
|
-
|
|
701
|
+
### Bento Layer (31 components)
|
|
367
702
|
|
|
368
|
-
|
|
703
|
+
AdaptiveSectionCard, BentoActionsMenu, BentoBackLink, BentoBackToTop, BentoCommandPalette, BentoCountTile, BentoDataTable, BentoDiff, BentoEmptyState, BentoEntityShell, BentoEntityTile, BentoFilterBar, BentoFormHero, BentoFormSection, BentoHero, BentoHeroIconPicker, BentoInlineEdit, BentoListPage, BentoNavRail, BentoPaletteTrigger, BentoPanel, BentoSaveBar, BentoScrollSaveBar, BentoSection, BentoShell, BentoShortcutsDialog, BentoStatusMarker, BentoTile, BentoTileGrid, BentoUserMenu, BentoVersionRail
|
|
704
|
+
|
|
705
|
+
All from `@viglet/viglet-design-system/bento`, with `@viglet/viglet-design-system/bento.css` beside it. [The bento layer](#the-bento-layer) is what each one is for and how a page composes them; this is the inventory.
|
|
706
|
+
|
|
707
|
+
Removed with the console era — see [where each one went](#moving-off-the-console-era): BlankSlate, GridList, InternalSidebar, NavMain, NavUser, Page, PageContent, PageHeader, StickyPageHeader, SubPage, SubPageHeader
|
|
369
708
|
|
|
370
709
|
### Hooks
|
|
371
710
|
|
|
372
|
-
`useIsMobile`, `useDateLocale`, `useGridAdapter`, `useSubPageBreadcrumb`
|
|
711
|
+
`useIsMobile`, `useDateLocale`, `useGridAdapter`, `useSubPageBreadcrumb`, `useAssistantNotifications`, `useSidebarOptional`
|
|
712
|
+
|
|
713
|
+
`useSidebarOptional()` is `useSidebar()` for a component that may not have a `SidebarProvider` above it — it returns `undefined` instead of throwing, which is what a Module Federation remote needs when the host owns the sidebar and the remote only renders into it.
|
|
373
714
|
|
|
374
715
|
`useGridAdapter(data, config)` tracks each extractor in `config`, not the object holding them, so a `url` builder that closes over a route param or a locale re-maps the rows when it changes. Pass the config inline if you like — keep the extractors themselves stable (a field name, a module-level function, a `useCallback`) and the memo holds.
|
|
375
716
|
|
|
@@ -419,7 +760,7 @@ This is separate from a component's **colour palette**. `SectionCard` (`blue | v
|
|
|
419
760
|
|
|
420
761
|
### i18n
|
|
421
762
|
|
|
422
|
-
Base translations (EN/PT) for common UI strings: buttons, form labels, dialog text, navigation, theme.
|
|
763
|
+
Base translations (EN/PT) for common UI strings: buttons, form labels, dialog text, navigation, theme, and the assistant dock.
|
|
423
764
|
|
|
424
765
|
## Tech Stack
|
|
425
766
|
|
|
@@ -454,6 +795,7 @@ of it:
|
|
|
454
795
|
```ts
|
|
455
796
|
import "@viglet/viglet-design-system/bento.css";
|
|
456
797
|
import {
|
|
798
|
+
BentoShell,
|
|
457
799
|
BentoEntityShell,
|
|
458
800
|
BentoListPage,
|
|
459
801
|
BentoFormHero,
|
|
@@ -466,11 +808,144 @@ The stylesheet is a separate import from the components, so a consumer taking
|
|
|
466
808
|
only the layout maths does not pull CSS it never renders. It reads the preset's
|
|
467
809
|
tokens, so import the preset too.
|
|
468
810
|
|
|
811
|
+
`BentoShell` is the page frame. It reserves the gutter for the rail you pass,
|
|
812
|
+
lays out the header's leading and trailing edges, holds the corner (the
|
|
813
|
+
`VigletAssistant` you pass as `dock`, with `BentoBackToTop` stacked above it;
|
|
814
|
+
`backToTop={false}` drops the button), and owns `main`: the width, the gutters
|
|
815
|
+
and the vertical rhythm. A page
|
|
816
|
+
inside it sets none of those. Choose the width by name with `column`: `default`,
|
|
817
|
+
`narrow` for a single-question form, `wide` for a table, or `full` for a tool
|
|
818
|
+
that fills the viewport. Each width is a custom property
|
|
819
|
+
(`--bento-column-default`, `--bento-column-narrow`, `--bento-column-wide`) that
|
|
820
|
+
you can redefine at `:root`.
|
|
821
|
+
|
|
822
|
+
```tsx
|
|
823
|
+
<BentoShell
|
|
824
|
+
column={isForm ? "narrow" : "default"}
|
|
825
|
+
rail={<BentoNavRail groups={groups} homeRoute={ROUTES.HOME} />}
|
|
826
|
+
headerStart={<ProductMark />}
|
|
827
|
+
headerEnd={<BentoUserMenu accountRoute={ROUTES.ACCOUNT} logoutUrl={ROUTES.LOGOUT} />}
|
|
828
|
+
>
|
|
829
|
+
<Outlet />
|
|
830
|
+
</BentoShell>
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
`BentoDataTable` is the list a curator sorts, range-selects and acts on at scale.
|
|
834
|
+
It mounts only the rows in view, so ten thousand cost what twenty do. Pass `rows`,
|
|
835
|
+
`getRowId`, `columns` (a header, a `cell`, and a `sortValue` to make it sortable),
|
|
836
|
+
and optionally `rowActions` for a per-row menu and `selectionActions` for the bar
|
|
837
|
+
that appears over a selection. The arrows move between rows, Shift with an arrow
|
|
838
|
+
or a click extends a selection, and Enter calls `onRowOpen`. The column picker
|
|
839
|
+
reports `onLayoutChange`, and you store the layout, as you do for `BentoListPage`.
|
|
840
|
+
|
|
841
|
+
```tsx
|
|
842
|
+
<BentoDataTable
|
|
843
|
+
rows={posts}
|
|
844
|
+
getRowId={(p) => p.id}
|
|
845
|
+
getRowLabel={(p) => p.title}
|
|
846
|
+
label={t("posts")}
|
|
847
|
+
columns={[
|
|
848
|
+
{ id: "title", header: t("title"), cell: (p) => p.title, sortValue: (p) => p.title, hideable: false },
|
|
849
|
+
{ id: "updated", header: t("updated"), cell: (p) => format(p.updated), sortValue: (p) => p.updated },
|
|
850
|
+
]}
|
|
851
|
+
rowActions={[{ id: "trash", label: t("trash"), icon: IconTrash, tone: "destructive", onSelect: trash }]}
|
|
852
|
+
selectionActions={[{ id: "trash", label: t("trash"), icon: IconTrash, onSelect: trash }]}
|
|
853
|
+
onRowOpen={(p) => navigate(p.url)}
|
|
854
|
+
/>
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
`AdaptiveSectionCard` renders the console's compound form markup — a `Header`
|
|
858
|
+
child and a `Content` child — as a frosted bento section, so a form written that
|
|
859
|
+
way moves without being rewritten. That compound API is the whole difference
|
|
860
|
+
from `BentoFormSection`, which takes the same icon, title and description as
|
|
861
|
+
props. A section whose header it cannot read keeps the frosted surface and all
|
|
862
|
+
of its children; a frosted box that is not part of a form is `BentoPanel`.
|
|
863
|
+
|
|
864
|
+
**`SectionCardChromeProvider`, `useSectionChrome`, the `SectionChrome` type and
|
|
865
|
+
the `defaultOpen` prop were removed in this release.** They picked between the
|
|
866
|
+
console card and the frosted section, and every consumer finished its cutover —
|
|
867
|
+
so every remaining call site passed `chrome="bento"`, the value it would have got
|
|
868
|
+
anyway. Delete the provider and the hook; the sections beneath render as they
|
|
869
|
+
already did. A `useSectionChrome()` call that drove a product's own branch can be
|
|
870
|
+
replaced by the bento arm of that branch.
|
|
871
|
+
|
|
872
|
+
**Give every `BentoActionsMenu` item an `id`**, a stable name for the verb such as
|
|
873
|
+
`"post.delete"`, and the same for `BentoEntityShell`'s `extraActions`. It is
|
|
874
|
+
rendered as `data-action-id` and means nothing to the package, but it lets a
|
|
875
|
+
product check its console verbs against its agent's, since a menu item is
|
|
876
|
+
otherwise an anonymous `onSelect`. An item without one warns outside production
|
|
877
|
+
in this release and stops type-checking in the next.
|
|
878
|
+
|
|
879
|
+
`BentoFilterBar` is the query row above a list: a search field, the facets you
|
|
880
|
+
declare (a `choice` of options, single or `multiple`, or a `date` range), the
|
|
881
|
+
active filters as removable chips, and a clear-all. It holds no state. You pass
|
|
882
|
+
`value` and get one `onChange` per action, typing included once it pauses, so the
|
|
883
|
+
value can live in the URL and a filtered view is a link. Pass it to a
|
|
884
|
+
`BentoDataTable` as `selectionScope` too, and a selection clears when the filters
|
|
885
|
+
change the rows:
|
|
886
|
+
|
|
887
|
+
```tsx
|
|
888
|
+
<BentoFilterBar
|
|
889
|
+
value={filters}
|
|
890
|
+
onChange={setFilters}
|
|
891
|
+
facets={[
|
|
892
|
+
{ id: "state", kind: "choice", label: t("state"), multiple: true, options: states },
|
|
893
|
+
{ id: "updated", kind: "date", label: t("updated") },
|
|
894
|
+
]}
|
|
895
|
+
/>
|
|
896
|
+
<BentoDataTable rows={filtered} selectionScope={JSON.stringify(filters)} … />
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
`BentoDiff` compares two versions of structured content field by field, for
|
|
900
|
+
revision history, a review of what an agent changed, or a translation against its
|
|
901
|
+
source. Pass `before`, `after` and the `fields` to compare. A field is `text`
|
|
902
|
+
(compared by word), `rich` (HTML, compared on the blocks it renders, so markup
|
|
903
|
+
alone is no change) or `value` (compared whole). Unchanged fields collapse behind
|
|
904
|
+
a count. A side that is `null` renders the other whole, as a created or deleted
|
|
905
|
+
item. Every change is named in text as well as coloured. `BentoVersionRail` lists
|
|
906
|
+
the revisions (author, person or agent, time) as a listbox a reader picks two
|
|
907
|
+
from, by keyboard or pointer. Neither fetches anything.
|
|
908
|
+
|
|
909
|
+
`BentoCommandPalette` takes a second group whose items the product supplies per
|
|
910
|
+
query, beside the nav items it matches itself. Pass `onQueryChange` to hear the
|
|
911
|
+
query as typed and `group` to answer it:
|
|
912
|
+
|
|
913
|
+
```tsx
|
|
914
|
+
<BentoCommandPalette
|
|
915
|
+
open={open}
|
|
916
|
+
onOpenChange={setOpen}
|
|
917
|
+
items={navItems}
|
|
918
|
+
onQueryChange={setQuery}
|
|
919
|
+
group={{ label: t("documents"), items: hits, pending, onSelect: openDocument }}
|
|
920
|
+
/>
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
Those items are rendered in the order given and never re-ranked — your search
|
|
924
|
+
already ranked them by relevance it understands, and a substring match here
|
|
925
|
+
would only undo that. Set `pending` while the query is in flight so the dialog
|
|
926
|
+
says so rather than showing an empty list that is not empty yet.
|
|
927
|
+
|
|
928
|
+
Bind the shell's keys with `useBentoShellShortcuts` rather than a keydown listener
|
|
929
|
+
of your own. ⌘K on macOS, Ctrl+K elsewhere, or `/` calls `onPalette`, and `?`
|
|
930
|
+
calls `onShortcuts`. A bare key typed into an input, a textarea, a select or an
|
|
931
|
+
editable node is left to the field. `BentoPaletteTrigger` is the header button,
|
|
932
|
+
with the same chord as its hint, and `BentoShortcutsDialog` lists the same set
|
|
933
|
+
(`BENTO_SHELL_SHORTCUTS`), so the guide shows exactly the keys that work:
|
|
934
|
+
|
|
935
|
+
```tsx
|
|
936
|
+
useBentoShellShortcuts({
|
|
937
|
+
onPalette: () => setPaletteOpen((open) => !open),
|
|
938
|
+
onShortcuts: () => setShortcutsOpen((open) => !open),
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
<BentoShell headerStart={<><ProductMark /><BentoPaletteTrigger onClick={() => setPaletteOpen(true)} /></>}>
|
|
942
|
+
```
|
|
943
|
+
|
|
469
944
|
"Carries none of it" is measured rather than claimed. `pnpm run build` ends by
|
|
470
945
|
bundling three fixtures through this package's own `exports` map, and fails if a
|
|
471
946
|
bento module, a bento class name or a run of `bento.css`'s selectors reaches a
|
|
472
947
|
consumer that imported only the root entry. `size-budget.json` records what each
|
|
473
|
-
entry costs: today the whole root entry is
|
|
948
|
+
entry costs: today the whole root entry is 91 KB gzipped and `./bento` 31 KB,
|
|
474
949
|
measured with everything the build externalises left out, so the numbers
|
|
475
950
|
describe this package rather than its dependencies. The preset's
|
|
476
951
|
`--vg-bento-tone-*` tokens are deliberately not counted as the layer: they ship
|
|
@@ -484,7 +959,7 @@ correct rather than a leak. The subpath is for a consumer who wants only that
|
|
|
484
959
|
background.
|
|
485
960
|
|
|
486
961
|
Every subpath in the table above is also **imported, required and type-checked
|
|
487
|
-
as you would use it** on each build: one fixture pulls all
|
|
962
|
+
as you would use it** on each build: one fixture pulls all seventeen through the
|
|
488
963
|
real `exports` map, a CommonJS probe requires the seven that offer it, and a
|
|
489
964
|
generated TypeScript probe imports a value from each typed entry and uses it —
|
|
490
965
|
so a `types` field resolving to the wrong declarations fails here rather than
|
|
@@ -502,6 +977,14 @@ do, and npm cannot mark a peer required for one entry point and optional for
|
|
|
502
977
|
another. `pnpm run build` fails if any other entry starts importing it, so the
|
|
503
978
|
split above stays true rather than becoming folklore.
|
|
504
979
|
|
|
980
|
+
**`react-hook-form` is a required peer, and `react-router-dom` is the only
|
|
981
|
+
optional one.** It was a plain dependency until VDS77, which is the one case
|
|
982
|
+
where that is wrong: every other library this package shares state through is a
|
|
983
|
+
peer, and this is the one carrying a React context a duplicate would split. The
|
|
984
|
+
build already left it for the consumer to supply, so the manifest was promising
|
|
985
|
+
something the bundle did not deliver; `scripts/externals.test.ts` now fails if
|
|
986
|
+
anything externalised as a peer is declared a dependency again.
|
|
987
|
+
|
|
505
988
|
[docs/BENTO-AUTHORING.md](docs/BENTO-AUTHORING.md) is the contract for writing a
|
|
506
989
|
bento page: the three page shapes, what the package will not hold for you, and
|
|
507
990
|
the colour, i18n, accessibility and layout rules that keep two consoles looking
|
|
@@ -521,8 +1004,9 @@ The catalogue proves a component; these prove the arrangement.
|
|
|
521
1004
|
## Contributing
|
|
522
1005
|
|
|
523
1006
|
The package manager here is pnpm, matching the Shio, Turing, Dumont, Cloud Home
|
|
524
|
-
and Schools workspaces. The Cloud Console
|
|
525
|
-
|
|
1007
|
+
and Schools workspaces. The Cloud Console, the Roadkeep GUI,
|
|
1008
|
+
the Openviglet Website and Viglet Docs install with npm, which is a fact about
|
|
1009
|
+
those repositories and not about this one.
|
|
526
1010
|
|
|
527
1011
|
```bash
|
|
528
1012
|
pnpm install
|