@evinvest/uikit 0.29.4 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +294 -90
- package/dist/{chunk-3IPB4QQJ.js → chunk-35SPIU4U.js} +10 -6
- package/dist/chunk-35SPIU4U.js.map +1 -0
- package/dist/components/account-list.js +128 -0
- package/dist/components/account-list.js.map +1 -0
- package/dist/components/account-menu-profile.js +114 -0
- package/dist/components/account-menu-profile.js.map +1 -0
- package/dist/components/account-menu.js +50 -23
- package/dist/components/account-menu.js.map +1 -1
- package/dist/components/account-settings-dialog.js +84 -0
- package/dist/components/account-settings-dialog.js.map +1 -0
- package/dist/components/alert-dialog.js +3 -2
- package/dist/components/alert-dialog.js.map +1 -1
- package/dist/components/animated-number-columns.js +105 -0
- package/dist/components/animated-number-columns.js.map +1 -0
- package/dist/components/animated-number.js +157 -0
- package/dist/components/animated-number.js.map +1 -0
- package/dist/components/app-shell.js +9 -0
- package/dist/components/app-shell.js.map +1 -1
- package/dist/components/aside.js +140 -0
- package/dist/components/aside.js.map +1 -0
- package/dist/components/badge.js +1 -1
- package/dist/components/badge.js.map +1 -1
- package/dist/components/button.js +3 -11
- package/dist/components/button.js.map +1 -1
- package/dist/components/chat-composer.js +141 -0
- package/dist/components/chat-composer.js.map +1 -0
- package/dist/components/chat-message.js +195 -0
- package/dist/components/chat-message.js.map +1 -0
- package/dist/components/chat-panel.js +137 -0
- package/dist/components/chat-panel.js.map +1 -0
- package/dist/components/chat-stream-text.js +69 -0
- package/dist/components/chat-stream-text.js.map +1 -0
- package/dist/components/chat-typing.js +26 -0
- package/dist/components/chat-typing.js.map +1 -0
- package/dist/components/command-palette.js +119 -0
- package/dist/components/command-palette.js.map +1 -0
- package/dist/components/command.js +3 -1
- package/dist/components/command.js.map +1 -1
- package/dist/components/confirm-dialog.js +123 -0
- package/dist/components/confirm-dialog.js.map +1 -0
- package/dist/components/context-menu.js +53 -5
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/dialog.js +3 -2
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/drawer.js +3 -1
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/dropdown-menu.js +64 -5
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/form-row.js +51 -0
- package/dist/components/form-row.js.map +1 -0
- package/dist/components/header-controls.js +149 -0
- package/dist/components/header-controls.js.map +1 -0
- package/dist/components/icon-button.js +35 -0
- package/dist/components/icon-button.js.map +1 -0
- package/dist/components/key-value.js +68 -0
- package/dist/components/key-value.js.map +1 -0
- package/dist/components/menubar.js +39 -5
- package/dist/components/menubar.js.map +1 -1
- package/dist/components/native-select.js +3 -4
- package/dist/components/native-select.js.map +1 -1
- package/dist/components/nav-collapse.js +142 -0
- package/dist/components/nav-collapse.js.map +1 -0
- package/dist/components/nav-model.js.map +1 -1
- package/dist/components/navigation-menu.js +4 -5
- package/dist/components/navigation-menu.js.map +1 -1
- package/dist/components/page-frame.js +2 -5
- package/dist/components/page-frame.js.map +1 -1
- package/dist/components/popover-form.js +60 -0
- package/dist/components/popover-form.js.map +1 -0
- package/dist/components/resource-error.js +1 -1
- package/dist/components/resource-error.js.map +1 -1
- package/dist/components/section-nav.js +73 -20
- package/dist/components/section-nav.js.map +1 -1
- package/dist/components/select.js +16 -6
- package/dist/components/select.js.map +1 -1
- package/dist/components/settings.js +94 -0
- package/dist/components/settings.js.map +1 -0
- package/dist/components/sheet.js +3 -2
- package/dist/components/sheet.js.map +1 -1
- package/dist/components/shell-nav.js +80 -38
- package/dist/components/shell-nav.js.map +1 -1
- package/dist/components/smart-header.js +53 -0
- package/dist/components/smart-header.js.map +1 -0
- package/dist/components/status-item.js +42 -0
- package/dist/components/status-item.js.map +1 -0
- package/dist/components/system-banner.js +22 -12
- package/dist/components/system-banner.js.map +1 -1
- package/dist/components/top-bar.js +4 -3
- package/dist/components/top-bar.js.map +1 -1
- package/dist/components/ui-icons.js +77 -0
- package/dist/components/ui-icons.js.map +1 -0
- package/dist/components/use-count-tween.js +42 -0
- package/dist/components/use-count-tween.js.map +1 -0
- package/dist/components/workspace-tab.js +140 -0
- package/dist/components/workspace-tab.js.map +1 -0
- package/dist/generated/accent.js +2 -2
- package/dist/generated/accent.js.map +1 -1
- package/dist/generated/account-list.js +35 -0
- package/dist/generated/account-list.js.map +1 -0
- package/dist/generated/account-settings-dialog.js +27 -0
- package/dist/generated/account-settings-dialog.js.map +1 -0
- package/dist/generated/alert-dialog.js +1 -1
- package/dist/generated/alert-dialog.js.map +1 -1
- package/dist/generated/alert.js +2 -2
- package/dist/generated/alert.js.map +1 -1
- package/dist/generated/aside.js +36 -0
- package/dist/generated/aside.js.map +1 -0
- package/dist/generated/badge.js +7 -6
- package/dist/generated/badge.js.map +1 -1
- package/dist/generated/band.js +1 -1
- package/dist/generated/band.js.map +1 -1
- package/dist/generated/breadcrumb.js +2 -2
- package/dist/generated/breadcrumb.js.map +1 -1
- package/dist/generated/button-group.js +2 -2
- package/dist/generated/button-group.js.map +1 -1
- package/dist/generated/button.js +4 -4
- package/dist/generated/button.js.map +1 -1
- package/dist/generated/card.js +6 -6
- package/dist/generated/card.js.map +1 -1
- package/dist/generated/chat-composer.js +21 -0
- package/dist/generated/chat-composer.js.map +1 -0
- package/dist/generated/chat-message.js +86 -0
- package/dist/generated/chat-message.js.map +1 -0
- package/dist/generated/chat-typing.js +9 -0
- package/dist/generated/chat-typing.js.map +1 -0
- package/dist/generated/command-palette.js +17 -0
- package/dist/generated/command-palette.js.map +1 -0
- package/dist/generated/command.js +3 -3
- package/dist/generated/command.js.map +1 -1
- package/dist/generated/confirm-dialog.js +28 -0
- package/dist/generated/confirm-dialog.js.map +1 -0
- package/dist/generated/context-menu.js +1 -1
- package/dist/generated/context-menu.js.map +1 -1
- package/dist/generated/date-time-picker.js +1 -1
- package/dist/generated/date-time-picker.js.map +1 -1
- package/dist/generated/dialog.js +1 -1
- package/dist/generated/dialog.js.map +1 -1
- package/dist/generated/drawer.js +1 -1
- package/dist/generated/drawer.js.map +1 -1
- package/dist/generated/dropdown-menu.js +1 -1
- package/dist/generated/dropdown-menu.js.map +1 -1
- package/dist/generated/form-row.js +17 -0
- package/dist/generated/form-row.js.map +1 -0
- package/dist/generated/header-controls.js +56 -0
- package/dist/generated/header-controls.js.map +1 -0
- package/dist/generated/icon-button.js +13 -0
- package/dist/generated/icon-button.js.map +1 -0
- package/dist/generated/key-value.js +15 -0
- package/dist/generated/key-value.js.map +1 -0
- package/dist/generated/menubar.js +1 -1
- package/dist/generated/menubar.js.map +1 -1
- package/dist/generated/navigation-menu.js +1 -1
- package/dist/generated/navigation-menu.js.map +1 -1
- package/dist/generated/popover-form.js +13 -0
- package/dist/generated/popover-form.js.map +1 -0
- package/dist/generated/section-label.js +10 -0
- package/dist/generated/section-label.js.map +1 -0
- package/dist/generated/select.js +15 -3
- package/dist/generated/select.js.map +1 -1
- package/dist/generated/settings.js +36 -0
- package/dist/generated/settings.js.map +1 -0
- package/dist/generated/sheet.js +1 -1
- package/dist/generated/sheet.js.map +1 -1
- package/dist/generated/slider.js +1 -1
- package/dist/generated/slider.js.map +1 -1
- package/dist/generated/smart-header.js +11 -0
- package/dist/generated/smart-header.js.map +1 -0
- package/dist/generated/sonner.js +4 -4
- package/dist/generated/sonner.js.map +1 -1
- package/dist/generated/status-item.js +17 -0
- package/dist/generated/status-item.js.map +1 -0
- package/dist/generated/system-banner.js +33 -0
- package/dist/generated/system-banner.js.map +1 -0
- package/dist/generated/top-bar.js +9 -0
- package/dist/generated/top-bar.js.map +1 -0
- package/dist/generated/workspace-tab.js +17 -0
- package/dist/generated/workspace-tab.js.map +1 -0
- package/dist/index.d.ts +852 -156
- package/dist/index.js +21 -1
- package/dist/lib/cn.js +8 -1
- package/dist/lib/cn.js.map +1 -1
- package/dist/lib/number-parts.js +90 -0
- package/dist/lib/number-parts.js.map +1 -0
- package/dist/palette-cli.js +1 -1
- package/dist/palette.d.ts +6 -4
- package/dist/palette.js +3 -1
- package/dist/primitives/dismissable-layer.js +76 -4
- package/dist/primitives/dismissable-layer.js.map +1 -1
- package/dist/primitives/focus-scope.js +20 -5
- package/dist/primitives/focus-scope.js.map +1 -1
- package/dist/primitives/use-floating.js +16 -1
- package/dist/primitives/use-floating.js.map +1 -1
- package/dist/primitives/use-reduced-motion.js +20 -0
- package/dist/primitives/use-reduced-motion.js.map +1 -0
- package/package.json +1 -1
- package/styles/ev.css +281 -49
- package/styles/theme.css +335 -39
- package/styles/tokens.css +643 -103
- package/dist/chunk-3IPB4QQJ.js.map +0 -1
- package/dist/components/terminal.js +0 -206
- package/dist/components/terminal.js.map +0 -1
- package/dist/generated/terminal.js +0 -70
- package/dist/generated/terminal.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
# @evinvest/uikit
|
|
2
2
|
|
|
3
3
|
A **dep-light** React UI kit with shadcn-style semantics — the TypeScript mirror
|
|
4
|
-
of the
|
|
5
|
-
(`ev_lib::
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
of the UI kit of the [`ev_lib`](https://github.com/EV-invest/lib) Rust crate,
|
|
5
|
+
which ships it for Dioxus (`ev_lib::uikit_dioxus`) and Leptos
|
|
6
|
+
(`ev_lib::uikit_leptos`) over one framework-free core. All three are driven from
|
|
7
|
+
the same canonical class strings and the same design tokens, so a component
|
|
8
|
+
looks and behaves the same whether it is rendered by React, Dioxus or Leptos.
|
|
8
9
|
|
|
9
10
|
"Dep-light" means: **no `@radix-ui/*`, no `class-variance-authority`, no
|
|
10
11
|
`lucide-react`, no charting / carousel / date-picker / OTP / toast libraries.**
|
|
@@ -59,14 +60,23 @@ your Tailwind v4 entrypoint — this is the load-bearing part of the kit:
|
|
|
59
60
|
|
|
60
61
|
| group | names |
|
|
61
62
|
|---|---|
|
|
62
|
-
| scope | `:root` · `[data-brand="…"]` — a theme root; `.light` · `.dark` — a polarity beneath it |
|
|
63
|
-
| surfaces | `background` `card` `
|
|
64
|
-
|
|
|
65
|
-
|
|
|
66
|
-
|
|
|
67
|
-
|
|
|
63
|
+
| scope | `:root` · `[data-brand="…"]` — a theme root; `.light` · `.dark` — a polarity beneath it; `[data-theme="oldschool"]` — EV's navy look |
|
|
64
|
+
| surfaces | `background` (page) · `card` (surface) · `secondary` (raised) · `popover` (overlay) · `muted` (inset, input fill) |
|
|
65
|
+
| states | `hover` · `active` · `selected` — translucent tints; `scrim` — behind a modal |
|
|
66
|
+
| ink | `ink` · `ink-mid` · `ink-soft` — hierarchy, loudest first; `ink-faint` — disabled only |
|
|
67
|
+
| lines | `border-subtle` (row rule) · `border` (hairline, 14 %) · `border-strong` (state and overlay edges) · `input` (control edge, ≥ 3:1) · `ring` |
|
|
68
|
+
| roles | `brand` `primary` `secondary` `positive` `accent-trace` `accent-debug` `accent-info` `accent-warn` `accent-error`, each with `on-*` where it gets filled; `primary-ink` — the primary role as ink on a surface; `primary-hover` · `primary-border` — the primary button's states |
|
|
69
|
+
| status inks | `positive-ink` · `accent-info-ink` — a quiet status role as text on a surface (derived, a palette may pin them); warn and error text wears `accent-warn` · `accent-error` itself, held to 3:1 |
|
|
70
|
+
| faces | `button-danger-*` (`bg` `hover` `border` `ink`) — the destructive `Button`; `chip-{neutral,primary,positive,info,warn,error}-{bg,border}` — the `Badge`; `button-primary-*` — no kit component paints it; derived for a brand, pinned per theme by EV |
|
|
71
|
+
| type | `text-caption` 11/14 · `body-s` 12/16 · `body` 13/18 · `body-l` 14/20 · `h3` 14/20 · `h2` 24/30 · `h1` 32/38 · `figure-m` 20/24 · `figure-l` 28/32 (size and line height each) |
|
|
72
|
+
| scalars | `radius` (panel, 6 px) · `control-radius` (4 px) · `chip-radius` (4 px) · `depth-card` `depth-elevated` `depth-overlay` · `control-py` `display-scale` `band-py` `page-max` `page-px` `shell-rail-w` `shell-tab-bar-h` · `font-*` |
|
|
68
73
|
| motion | `ev-ease-out` `ev-ease-in-out` `ev-dur-fast` `ev-dur-base` `ev-dur-slow` `ev-rise` `ev-stagger` `ev-stagger-section` |
|
|
69
|
-
| charts | `chart-1` … `chart-
|
|
74
|
+
| charts | `chart-1` … `chart-6` |
|
|
75
|
+
|
|
76
|
+
A palette owes **29 names**: the colours that are not derived, which is every
|
|
77
|
+
role and surface above plus `chart-1` … `chart-6`. The states, lines, inks,
|
|
78
|
+
status inks, faces and chips are derived from them with formulas, so a brand
|
|
79
|
+
file that pins none of them still renders.
|
|
70
80
|
|
|
71
81
|
Custom properties inherit, so a scope class on a `<section>` re-themes its
|
|
72
82
|
subtree — `bg-card text-ink` is correct on both polarities with no prop. That is
|
|
@@ -80,14 +90,38 @@ polarity — so `on-{role}` exists exactly there.
|
|
|
80
90
|
|
|
81
91
|
A fill and an ink are different jobs, and `primary` is the one role that
|
|
82
92
|
names them separately: `primary` is the fill (`bg-primary text-on-primary` on
|
|
83
|
-
a button, a checked switch or checkbox — `#
|
|
84
|
-
4.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
`
|
|
89
|
-
|
|
90
|
-
|
|
93
|
+
a primary button, a checked switch or checkbox — `#138377`, white reads on it at
|
|
94
|
+
4.62:1 and it clears 3:1 against every plane) and `primary-ink` is the same hue
|
|
95
|
+
as ink (`text-primary-ink` on a link or eyebrow, a radio dot, a slider range,
|
|
96
|
+
the focus `ring` — `#35a395` in Dark, `#116f65` in Light). The status roles
|
|
97
|
+
follow the same rule with their `-ink` values. A consumer sheet must define
|
|
98
|
+
`--primary-ink`; see [`docs/spec/accents.md`](../../docs/spec/accents.md).
|
|
99
|
+
|
|
100
|
+
### Themes
|
|
101
|
+
|
|
102
|
+
EV ships three, each a zero-specificity rule that pins every EV token:
|
|
103
|
+
|
|
104
|
+
| theme | how to switch | look |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| **Dark** (default) | nothing: no class, no attribute | near-black neutrals, warm ink, one teal accent, flat |
|
|
107
|
+
| **Light** | `class="light"` on `<html>` or on any wrapper | warm paper planes, same shapes |
|
|
108
|
+
| **Oldschool** | `data-theme="oldschool"` on `<html>` or on any wrapper | the navy look: radius 8 / control 6 / pill chips, tinted chips, real shadows |
|
|
109
|
+
|
|
110
|
+
Dark and Light are flat: depth is surface steps and hairlines, and the
|
|
111
|
+
`--depth-*` tiers behind `shadow-card`, `shadow-elevated` and `shadow-overlay`
|
|
112
|
+
are a zero shadow. Oldschool gives them back.
|
|
113
|
+
|
|
114
|
+
Scoping works the way the polarity classes always did. A `.light` or
|
|
115
|
+
`[data-theme="oldschool"]` wrapper inside a Dark page takes its own full token
|
|
116
|
+
set; a `.light` or `.dark` inside an Oldschool region also takes Oldschool.
|
|
117
|
+
EV's rules do not match inside a `[data-brand]` scope or on its element, so a
|
|
118
|
+
brand's own `.light` and `.dark` never pick EV's values up. Keep `data-theme`
|
|
119
|
+
and `data-brand` on separate elements. For a side-by-side view give each scoped
|
|
120
|
+
wrapper its own `PortalProvider` container so overlays stay inside the scope.
|
|
121
|
+
|
|
122
|
+
The planes sit only 1.04–1.14:1 apart on purpose, so what separates them is the
|
|
123
|
+
`--border` hairline (measured 1.37–1.46:1 on Dark, 1.35:1 on Light). Do not lift
|
|
124
|
+
a plane to chase the ratio; see [`docs/spec/accents.md`](../../docs/spec/accents.md).
|
|
91
125
|
|
|
92
126
|
Accents are decorative and **ordered by significance**, quiet to loud. Pick a
|
|
93
127
|
rung by how loud the thing should be, never by what it means; see
|
|
@@ -105,29 +139,29 @@ It has two halves, each also shipped as a flat sheet of its own —
|
|
|
105
139
|
`styles/theme.css` (the contract) and `styles/ev.css` (EV's palette):
|
|
106
140
|
|
|
107
141
|
- **the contract** — the `@theme inline` mapping, the geometry, and the tokens
|
|
108
|
-
*derived* from a palette (`hover
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
`:root`, `[data-brand]`, `.light
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
142
|
+
*derived* from a palette (`hover` `active` `selected` `border-*` `ink-mid`
|
|
143
|
+
`ink-soft` `ink-faint` `input` from `ink`; `scrim`; `ring` from `primary-ink`;
|
|
144
|
+
`primary-hover` `primary-border`; the status inks; the button faces and
|
|
145
|
+
chips). The derived ones are redeclared on `:root`, `[data-brand]`, `.light`,
|
|
146
|
+
`.dark` and `[data-theme]` at zero specificity, so a brand scope gets its own
|
|
147
|
+
borders, hover and focus ring rather than inheriting the root's, and a
|
|
148
|
+
palette may still pin any of them outright. The geometry (`radius`,
|
|
149
|
+
`page-px`, `band-py`, …) and its responsive scale-ups restart on every
|
|
150
|
+
`[data-brand]` the same way.
|
|
151
|
+
- **EV's palette** — Dark on `:root`, `[data-brand="ev"]` and `.dark`; Light on
|
|
152
|
+
`.light`; Oldschool on `[data-theme="oldschool"]`. None of them matches inside
|
|
153
|
+
another brand's scope.
|
|
120
154
|
|
|
121
155
|
### Brand palettes
|
|
122
156
|
|
|
123
|
-
A second brand is a palette against the same names, scoped to
|
|
157
|
+
A second brand is a palette against the same names (29 of them, `chart-6` among them), scoped to
|
|
124
158
|
`[data-brand="<slug>"]`; light is its default and `.light` / `.dark` beneath it
|
|
125
159
|
pick a polarity, so a `<Section polarity="dark">` inside the brand takes the
|
|
126
160
|
brand's dark values. The package ships a generator for it, reading the
|
|
127
161
|
`brand.toml` shape (`[colors.light]` and `[colors.dark]`, keyed by token name;
|
|
128
162
|
optional top-level `mark` and `mark-aspect` become `--brand-mark` and
|
|
129
163
|
`--brand-aspect`; the family names in `[fonts]` — `sans` (or aquafix's `text`),
|
|
130
|
-
`
|
|
164
|
+
`serif`, `mono` — become the [font parameters](#fonts); other keys and tables
|
|
131
165
|
are ignored). Each polarity block also declares `--scheme: light | dark`, which
|
|
132
166
|
the platform-drawn parts of the kit read (a `NativeSelect`'s popup):
|
|
133
167
|
|
|
@@ -192,13 +226,16 @@ under a brand under another brand may match either.
|
|
|
192
226
|
|
|
193
227
|
### Fonts
|
|
194
228
|
|
|
195
|
-
The families are parameters, one per role
|
|
229
|
+
The families are parameters, one per role, and not part of the 29 colour names:
|
|
230
|
+
a palette is complete without them, and the generator only carries what a
|
|
231
|
+
brand file's `[fonts]` declares. Unset, each is EV's, and Oldschool carries the
|
|
232
|
+
families the navy look shipped with:
|
|
196
233
|
|
|
197
|
-
| parameter | utilities | default (EV) |
|
|
198
|
-
|
|
199
|
-
| `--brand-font-sans` | `font-sans`, the page's base family | `--font-inter`, else `"Inter"` |
|
|
200
|
-
| `--brand-font-
|
|
201
|
-
| `--brand-font-mono` | `font-mono` | `--font-inter`, else `"Inter"` |
|
|
234
|
+
| parameter | utilities | default (EV) | Oldschool |
|
|
235
|
+
|---|---|---|---|
|
|
236
|
+
| `--brand-font-sans` | `font-sans`, the page's base family | `--font-inter`, else `"Inter"` | same |
|
|
237
|
+
| `--brand-font-serif` | `font-serif` | `--font-inter-tight`, else `"Inter Tight"` | `--font-playfair`, else `"Playfair Display"` |
|
|
238
|
+
| `--brand-font-mono` | `font-mono` | `--font-jetbrains-mono`, else `"JetBrains Mono"` | `--font-inter`, else `"Inter"` |
|
|
202
239
|
|
|
203
240
|
Declare them on the brand scope (the palette generator writes them from
|
|
204
241
|
`[fonts]`) and load the files yourself — `next/font`, or your own `@font-face`;
|
|
@@ -206,10 +243,13 @@ the kit ships no font files:
|
|
|
206
243
|
|
|
207
244
|
```css
|
|
208
245
|
[data-brand="aquafix"] {
|
|
209
|
-
--brand-font-
|
|
246
|
+
--brand-font-serif: "Archivo";
|
|
210
247
|
}
|
|
211
248
|
```
|
|
212
249
|
|
|
250
|
+
The serif role is the second face, for headings, figures, wordmarks, italic
|
|
251
|
+
accents and the avatar initial; it need not have serifs.
|
|
252
|
+
|
|
213
253
|
The utilities resolve the chain on the element, so a scope re-fonts everything
|
|
214
254
|
under it that carries one. The page's base family resolves once, on `<html>`: a
|
|
215
255
|
brand scoped lower than `<html>` puts `font-sans` on its own element so plain
|
|
@@ -305,12 +345,12 @@ through hydration. Three parts of the kit make that hold:
|
|
|
305
345
|
```
|
|
306
346
|
|
|
307
347
|
**Focus.** A control that paints a fill — `Button` `primary` / `secondary` /
|
|
308
|
-
`destructive` or any accent fill, a
|
|
309
|
-
shows focus as the solid `ring` standing 2px off it, so both edges of
|
|
310
|
-
indicator are against the surface: the ring reads on every surface at
|
|
311
|
-
and so does the fill, on every palette and polarity `test/tokens.test.ts`
|
|
348
|
+
`destructive` or any accent fill, a `Badge` link, `Switch`, `Checkbox`,
|
|
349
|
+
`Toggle` — shows focus as the solid `ring` standing 2px off it, so both edges of
|
|
350
|
+
the indicator are against the surface: the ring reads on every surface at
|
|
351
|
+
≥ 3:1 and so does the fill, on every palette and polarity `test/tokens.test.ts`
|
|
312
352
|
measures. Controls that sit on the surface (`outline`, `ghost`, inputs) keep
|
|
313
|
-
the `ring` border plus its halo.
|
|
353
|
+
the `ring` border plus its halo; list rows draw an inset ring.
|
|
314
354
|
|
|
315
355
|
## App shell
|
|
316
356
|
|
|
@@ -318,7 +358,8 @@ The frame a signed-in app sits in — a rail beside the page on a wide screen, a
|
|
|
318
358
|
tab bar under it on a phone, one page inset and title scale, and the motion
|
|
319
359
|
between screens. It knows nothing about routes, roles or data: the nav is a
|
|
320
360
|
list you build, visibility is a list you filter, and the router comes in as two
|
|
321
|
-
props. TS-only (see [Limitations](#limitations))
|
|
361
|
+
props. TS-only (see [Limitations](#limitations)), except `TopBar`,
|
|
362
|
+
`SystemBanner` and `SectionLabel`, which have Dioxus twins.
|
|
322
363
|
|
|
323
364
|
```tsx
|
|
324
365
|
// app/(app)/layout.tsx — the layout stays a Server Component; the nav is a client leaf
|
|
@@ -393,6 +434,15 @@ export function Nav() {
|
|
|
393
434
|
middle click does not. One marker per group slides between rows on a CSS
|
|
394
435
|
`transform` transition — it is placed by measurement, so until hydration the
|
|
395
436
|
marked row carries the fill itself.
|
|
437
|
+
*Collapsible sections* are opt-in: set `collapsible` on a group (or on
|
|
438
|
+
`ShellNav` for every labelled group; a group's own value wins) and its label
|
|
439
|
+
becomes a header with a chevron. `defaultOpen`, controlled `open` /
|
|
440
|
+
`onOpenChange`, and `href` (the label links to a landing page, only the
|
|
441
|
+
chevron toggles) go on the group; the group holding the current route opens
|
|
442
|
+
when the route moves into it. Height runs on a `0fr → 1fr` grid track with a
|
|
443
|
+
180 ms fade and chevron turn (`--ev-dur-fast`, `--ev-ease-in-out`), none under
|
|
444
|
+
reduced motion; closed rows are `inert`. `labels` also takes `expand` /
|
|
445
|
+
`collapse` (the chevron reads "Expand Guides"). `SectionNav` takes the same.
|
|
396
446
|
- **`BottomTabBar`** — `items` (five fit a phone), the same router props,
|
|
397
447
|
callbacks and optimistic mark, `labels` `{ nav, badge(n) }`. One rule slides by
|
|
398
448
|
whole tab widths and fades on a route no tab claims; a numeric `badge` sits
|
|
@@ -415,8 +465,8 @@ export function Nav() {
|
|
|
415
465
|
onValueChange?, linkComponent?, labels: { nav } }` — a page's own section
|
|
416
466
|
rail (settings panes); items are buttons, or links when they have an `href`.
|
|
417
467
|
- **`ResourceError`** `{ message, variant: "inline" (onRetry?, retrying?) |
|
|
418
|
-
"alert" (title?), labels: { retry } }` and **`SystemBanner`** `{ tone: "
|
|
419
|
-
"info", title?, icon?, onDismiss?, labels: { dismiss } }` — presentation
|
|
468
|
+
"alert" (title?), labels: { retry } }` and **`SystemBanner`** `{ tone: "neutral" |
|
|
469
|
+
"primary" | "info" | "warn", title?, icon?, onDismiss?, labels: { dismiss } }` — presentation
|
|
420
470
|
only; what failed and whether a dismissal sticks are yours.
|
|
421
471
|
- **`Settled`** `{ loading, skeleton, children }` — the skeleton → content
|
|
422
472
|
handover: the content fades and rises in after a skeleton was shown, and cuts
|
|
@@ -463,6 +513,57 @@ The timing is tokens on every theme root, for your own transitions too
|
|
|
463
513
|
`--ev-rise` (8px), `--ev-stagger` (35ms), `--ev-stagger-section` (50ms). The
|
|
464
514
|
shell's geometry is `--shell-rail-w` (248px) and `--shell-tab-bar-h` (64px).
|
|
465
515
|
|
|
516
|
+
|
|
517
|
+
### Animated numbers
|
|
518
|
+
|
|
519
|
+
`AnimatedNumber` shows a number that moves to its new value. It needs no
|
|
520
|
+
animation library: the digits are CSS transforms and transitions, and the
|
|
521
|
+
count-up is `requestAnimationFrame`.
|
|
522
|
+
|
|
523
|
+
```tsx
|
|
524
|
+
<AnimatedNumber value={balance} format={{ style: "currency", currency: "USD" }} />
|
|
525
|
+
<AnimatedNumber value={change} format={{ style: "percent", signDisplay: "exceptZero" }} />
|
|
526
|
+
<AnimatedNumber value={volume} format={{ notation: "compact" }} mode="count" animateOnMount />
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
| prop | |
|
|
530
|
+
|---|---|
|
|
531
|
+
| `value` | the number |
|
|
532
|
+
| `format`, `locales` | passed to `Intl.NumberFormat` |
|
|
533
|
+
| `mode` | `roll` (default): digit columns spin. `count`: the number is tweened and re-formatted each frame |
|
|
534
|
+
| `trend` | roll only. `1` every column spins up, `-1` down, `0` each column takes its shorter way. Default: the way the value moved |
|
|
535
|
+
| `duration` | ms per change: spin 750 (roll), whole tween 800 (count) |
|
|
536
|
+
| `prefix`, `suffix` | React nodes beside the figure, not animated |
|
|
537
|
+
| `live` | announce each new value politely; off by default |
|
|
538
|
+
| `animateOnMount`, `from` | animate the first render from `from` (default 0) |
|
|
539
|
+
|
|
540
|
+
It inherits font, size and colour, so it fits `text-figure-l font-serif
|
|
541
|
+
font-light` as well as a `text-body` table cell. `tabular-nums` is on.
|
|
542
|
+
`useCountTween(value, { duration, easing, from, animateOnMount })` returns the
|
|
543
|
+
in-flight number for a chart or a label of your own. `HeaderBalance` takes
|
|
544
|
+
`amount` (USD unless `format` says otherwise); any `figure`, `value` or other
|
|
545
|
+
`ReactNode` slot takes an `<AnimatedNumber />` as is.
|
|
546
|
+
|
|
547
|
+
**How roll works.** `formatToParts` splits the value. Integer digits are keyed
|
|
548
|
+
by place value from the right, fraction digits by position, so `9 -> 10` keeps
|
|
549
|
+
the existing column and only a new one enters. Symbols (currency, sign, group,
|
|
550
|
+
decimal, percent, compact suffix) are keyed by type. A digit is a clipped
|
|
551
|
+
one-line window over a strip moved with `translateY` on a spring-shaped
|
|
552
|
+
`linear()` easing (~4 % overshoot), with soft top and bottom edges. The strip
|
|
553
|
+
is unbounded, so a forced direction wraps (9 to 0 going up). Entering and leaving
|
|
554
|
+
parts animate width (`0fr` to `1fr`) and opacity in 350 ms, so their neighbours
|
|
555
|
+
slide.
|
|
556
|
+
|
|
557
|
+
**Accessibility.** The final formatted value is in the DOM once, as
|
|
558
|
+
visually hidden text; the moving glyphs are `aria-hidden` and not selectable,
|
|
559
|
+
so a copy gives the formatted text.
|
|
560
|
+
|
|
561
|
+
**Reduced motion.** Under `prefers-reduced-motion: reduce` a new value swaps in
|
|
562
|
+
at once: no spin, no width animation, no tween frames.
|
|
563
|
+
|
|
564
|
+
**SSR.** The server prints the final formatted value, also with `animateOnMount`,
|
|
565
|
+
so hydration does not jump; the count-up starts after hydration.
|
|
566
|
+
|
|
466
567
|
## Usage
|
|
467
568
|
|
|
468
569
|
```tsx
|
|
@@ -473,7 +574,7 @@ export function Example() {
|
|
|
473
574
|
<Card>
|
|
474
575
|
<CardHeader>
|
|
475
576
|
<CardTitle>Portfolio</CardTitle>
|
|
476
|
-
<Badge variant="
|
|
577
|
+
<Badge variant="positive">+12.4%</Badge>
|
|
477
578
|
</CardHeader>
|
|
478
579
|
<CardContent>
|
|
479
580
|
<Button onClick={() => {}}>Invest</Button>
|
|
@@ -584,9 +685,11 @@ hold focus, so a bare list is pointer-only. The live demo is `CommandDemo` in
|
|
|
584
685
|
|
|
585
686
|
The Rust crate is the source of truth; this package preserves its _semantics_
|
|
586
687
|
while reading like idiomatic React. Canonical class strings are identical per
|
|
587
|
-
element across
|
|
688
|
+
element across all ports. The two Rust ports render the same HTML (pinned by
|
|
689
|
+
`rust/tests/parity.rs`) and share every row below; where Leptos idiom differs
|
|
690
|
+
the cell says so.
|
|
588
691
|
|
|
589
|
-
| Concept | Rust (`ev_lib::
|
|
692
|
+
| Concept | Rust (`ev_lib::uikit_dioxus` / `ev_lib::uikit_leptos`) | TS (`@evinvest/uikit`) |
|
|
590
693
|
| --- | --- | --- |
|
|
591
694
|
| class fusion | `cn!` macro (`tailwind_fuse`) | `cn` (`clsx` + `tailwind-merge`) |
|
|
592
695
|
| variants | `enum` + `fn class()` match | `as const` map keyed by variant |
|
|
@@ -595,8 +698,17 @@ element across the two ports.
|
|
|
595
698
|
| keyboard nav | `use_roving_focus` | `useRovingFocus` |
|
|
596
699
|
| overlay placement | inline `position:fixed` + `data-side` | `Portal` + `useFloating` |
|
|
597
700
|
| dismiss / focus trap | full-screen backdrop / native order | `useDismissableLayer` / `useFocusScope` |
|
|
598
|
-
|
|
599
|
-
|
|
701
|
+
| brand webfonts | `Fonts`: Dioxus bundles the faces through `asset!`; Leptos has no asset pipeline, so `Fonts base="/fonts"` points the same `@font-face` rules at `base` and the consumer serves `FONT_FILES` there | none — load the files yourself ([Fonts](#fonts)) |
|
|
702
|
+
| optional slot present | an explicit prop, never read off `children`: `FieldSeparator { content: true, "or" }` renders the copy pill; `FormMessage { error, .. }` renders only while `error` (the same flag as `FormControl`'s) | `!!children` / `if (!children) return null` |
|
|
703
|
+
| option label | `SelectItem { value, text_value, .. }`: the text type-ahead matches, an explicit prop in both ports — never read off `children` | `SelectItem`'s `textValue`, else the text of its children |
|
|
704
|
+
| toasts | `ToasterProvider` + `use_toaster()` → `ToasterHandle` (`.toast` `.positive` `.error` `.info` `.warn` `.show` `.dismiss`), same in both ports | global `toast(...)` / `toast.dismiss` |
|
|
705
|
+
| children present | `SettingsSection` and `SystemBanner`: Dioxus checks that the children render something, Leptos that children were passed | `children !== undefined` |
|
|
706
|
+
| `SystemBanner` glyph | `icon` replaces the tone's glyph; Dioxus drops it with an empty `icon: rsx! {}`, Leptos with `icon=None` | `icon={null}` |
|
|
707
|
+
| `KeyValue` label | Dioxus: an `Element`; Leptos: `KeyValueLabel::{Text, View}` (`.into()` from a string), since only text names the copy button | `label` (`ReactNode`) |
|
|
708
|
+
| time-varying props | Dioxus: plain values; Leptos: signals — `HeaderBalance`, `NotificationBell`, `ModelChip`, `ChatComposer` `streaming` / `disabled`, `ConfirmDialog` `pending` / `error`, `ChatAttachment` `progress`, `AsidePanel` `open` | props |
|
|
709
|
+
| Ctrl+K hook | `use_command_palette_shortcut(on_trigger, enabled)`: `enabled` is a `bool` in Dioxus, a `Signal<bool>` in Leptos | `useCommandPaletteShortcut` |
|
|
710
|
+
|
|
711
|
+
### Component inventory (all bricks)
|
|
600
712
|
|
|
601
713
|
- **Tier A — static (23):** badge, button, button-group, card, input, textarea,
|
|
602
714
|
label, field, separator, skeleton, spinner, kbd, table, container, alert,
|
|
@@ -614,27 +726,99 @@ element across the two ports.
|
|
|
614
726
|
/ `Forbidden` / `ServerError` presets (404 / 403 / 500). The kit ships no
|
|
615
727
|
artwork: `Logo` masks whatever `--brand-mark` / `--brand-aspect` the consumer
|
|
616
728
|
declares beside the palette, and the status pages use it for their mark.
|
|
617
|
-
- **App shell — TS-only (
|
|
618
|
-
nav-badge (`NavBadge` / `NavDot`),
|
|
619
|
-
`
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
`
|
|
624
|
-
`
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
729
|
+
- **App shell — TS-only (10):** app-shell, shell-nav, bottom-tab-bar,
|
|
730
|
+
nav-badge (`NavBadge` / `NavDot`), account-menu, mobile-app-bar, page-frame
|
|
731
|
+
(`PageFrame` / `PageHeading`), section-nav, resource-error, settled — see
|
|
732
|
+
[App shell](#app-shell). `TopBar`, `SystemBanner` and `SectionLabel` have
|
|
733
|
+
Dioxus twins (next item).
|
|
734
|
+
- **Composed parts — Rust and TS:** the class strings live once in
|
|
735
|
+
`rust/classes/src/<name>.rs`, and `nix run .#gen` writes them to
|
|
736
|
+
`ts/uikit/src/generated/<name>.ts`.
|
|
737
|
+
- *elements:* `IconButton` (required `aria-label`), `StatusItem` / `StatusDot`,
|
|
738
|
+
`WorkspaceTabs` / `WorkspaceTab`, `KeyValue` (copy button), `FormRow`,
|
|
739
|
+
`SystemBanner`, `SectionLabel`;
|
|
740
|
+
- *settings:* `SettingsLayout` / `SettingsSection` / `SettingsRow`,
|
|
741
|
+
`AccountSettingsDialog`, `ConfirmDialog` (async confirm), `AccountList` /
|
|
742
|
+
`AccountListItem`, `PopoverForm`, `CommandPalette` and
|
|
743
|
+
`useCommandPaletteShortcut` (Rust: `use_command_palette_shortcut`);
|
|
744
|
+
- *header and asides:* `TopBar`, `SmartHeader` (`default`, `collapsed`,
|
|
745
|
+
`banner`, `search`), `HeaderBalance`, `HeaderSearch`, `NotificationBell`,
|
|
746
|
+
`AsideHeader`, `AsideRail`, `AsidePanel` (`docked` or `overlay`);
|
|
747
|
+
- *AI chat:* `ChatMessage`, `ChatBubble` (`streaming` shows a caret),
|
|
748
|
+
`ChatMarker`, `ChatAttachment`, `ChatComposer`, `ModelChip` and `ChatTyping`
|
|
749
|
+
(the "thinking" indicator).
|
|
750
|
+
- **Composed parts — TS-only:** `AnimatedNumber` / `useCountTween`,
|
|
751
|
+
`ChatPanel`, `ChatDock`, `ChatStreamText` (pass the whole growing string; only
|
|
752
|
+
the new words fade in), the `AppShell` `aside` slot, and the profile form on
|
|
753
|
+
`AccountMenu`. How the Rust twins differ is in [Limitations](#limitations).
|
|
754
|
+
|
|
755
|
+
### Variant vocabulary
|
|
756
|
+
|
|
757
|
+
A variant names the state ([`docs/spec/variants.md`](../../docs/spec/variants.md)).
|
|
758
|
+
|
|
759
|
+
| component | values |
|
|
760
|
+
|---|---|
|
|
761
|
+
| `Button` | `primary` (the filled call to action, default) · `secondary` · `outline` · `ghost` · `destructive` · `link` |
|
|
762
|
+
| `Badge` | `neutral` (default) · `primary` · `positive` · `info` · `warn` · `error` |
|
|
763
|
+
| `Alert` | `neutral` (default) · `destructive` · `success` · `info` |
|
|
764
|
+
| menu item (`DropdownMenuItem`, `ContextMenuItem`, `MenubarItem`) | `neutral` · `destructive` |
|
|
765
|
+
| `SystemBanner` | `neutral` · `primary` · `info` (default) · `warn` |
|
|
628
766
|
|
|
629
767
|
The canonical variant set is the **superset** of the original cabinet (Rust) and
|
|
630
|
-
landing (TS) sources
|
|
631
|
-
|
|
632
|
-
|
|
768
|
+
landing (TS) sources: `Button` keeps landing's square icon shape at every size,
|
|
769
|
+
as the separate `icon` boolean it always was in Rust. A `destructive` button
|
|
770
|
+
wears the `button-danger-*` face: an outline in Dark and Light, a fill in
|
|
771
|
+
Oldschool.
|
|
633
772
|
|
|
634
773
|
`size="touch"` is the 44 px pointer-target floor (WCAG 2.5.5) for touch-first
|
|
635
774
|
screens: `min-h-11` with `lg`'s padding, and a 44 × 44 square with `icon`. It is
|
|
636
775
|
a button step only — `Input` and `SelectTrigger` already clear it at `lg`.
|
|
637
776
|
|
|
777
|
+
## Upgrading from 0.30
|
|
778
|
+
|
|
779
|
+
**Renames and removals**
|
|
780
|
+
|
|
781
|
+
| 0.30 | now |
|
|
782
|
+
|---|---|
|
|
783
|
+
| `Badge variant="outline"` · `"secondary"` | `neutral` |
|
|
784
|
+
| `Badge variant="destructive"` · `"success"` | `error` · `positive` (new: `primary`, `info`, `warn`) |
|
|
785
|
+
| `SystemBanner` tones `warn`, `info` | the same, plus `neutral` and `primary`; `info` draws its icon in `accent-info-ink` |
|
|
786
|
+
| `accent-error-ink` (`text-accent-error-ink`, a brand file's `accent-error-ink`) | `accent-error`; destructive menu rows read in it, and `evinvest-palette` refuses the key |
|
|
787
|
+
| `Terminal`, `TerminalPane*`, `TerminalChart`, `TerminalTicker`, `TickerStat`, `OrderBook*`, `TradesTapeRow`, `OrderForm*`, `OpenOrdersEmpty` and their types | removed from the TS kit (the Rust terminal stays) |
|
|
788
|
+
|
|
789
|
+
**Silent changes: the compiler does not flag these.**
|
|
790
|
+
|
|
791
|
+
- A `Badge` with no `variant` was filled `primary` and is now `neutral`:
|
|
792
|
+
`rg -n '<Badge(\s|>)' --glob '*.tsx' | rg -v 'variant='`.
|
|
793
|
+
- A `destructive` `Button` wears the theme's `button-danger-*` face, an outline
|
|
794
|
+
in Dark and Light; an `outline` one is raised (`secondary` fill and a hairline).
|
|
795
|
+
|
|
796
|
+
**Tokens and themes**
|
|
797
|
+
|
|
798
|
+
- EV's navy palette is opt-in: `data-theme="oldschool"`. Dark is the default
|
|
799
|
+
and is a different colour set; `.light` is Light.
|
|
800
|
+
- Font defaults are Inter (sans), Inter Tight (serif role) and JetBrains Mono
|
|
801
|
+
(mono). `data-theme="oldschool"` sets Playfair Display and Inter for the serif
|
|
802
|
+
and mono roles, as the navy look shipped. Load the families a theme uses
|
|
803
|
+
(`--font-inter-tight`, `--font-jetbrains-mono`, or `--font-playfair` for
|
|
804
|
+
Oldschool); the kit ships no font files.
|
|
805
|
+
- The font role `display` is `serif`: `--brand-font-display` / `font-display`
|
|
806
|
+
become `--brand-font-serif` / `font-serif` (see [Fonts](#fonts)).
|
|
807
|
+
- A brand file must add `chart-6` (29 names now); `evinvest-palette` names it.
|
|
808
|
+
- Panel radius is 6 px at every width (the `sm` bump to 8 px is gone), controls
|
|
809
|
+
and chips 4 px, and the depth tiers are flat. **Aquafix, vifnet and any other
|
|
810
|
+
brand take these and lose their card shadow** unless their scope overrides
|
|
811
|
+
`--radius`, `--control-radius`, `--chip-radius` and `--depth-card`,
|
|
812
|
+
`--depth-elevated`, `--depth-overlay`. Check each brand before releasing.
|
|
813
|
+
|
|
814
|
+
**Layers across bundles**
|
|
815
|
+
|
|
816
|
+
- The shared layer stack (Escape order, outside clicks, the one shared scrim)
|
|
817
|
+
keeps its global key `@1`, so a micro-frontend bundling uikit 0.30 and a newer
|
|
818
|
+
host still share one stack. A 0.30 copy never notifies, so a newer modal's
|
|
819
|
+
shared scrim may not update when a 0.30 layer opens or closes under it; move
|
|
820
|
+
hosts and the MFEs they mount together to get the full behaviour.
|
|
821
|
+
|
|
638
822
|
## Limitations
|
|
639
823
|
|
|
640
824
|
Reproducing everything dep-light means some behaviour is intentionally reduced,
|
|
@@ -668,18 +852,15 @@ measuring needs host-only `web-sys`). Known gaps:
|
|
|
668
852
|
(`modal`, `pointerOutside`) and `DismissableLayerScope`; mark a floating
|
|
669
853
|
region that should dismiss nothing with `LAYER_IGNORE_ATTR`.
|
|
670
854
|
- **overlay layers (Rust):** `Select`, `Popover`, `Dialog` and `DropdownMenu`
|
|
671
|
-
share one dismissable-layer stack per `VirtualDom`
|
|
672
|
-
(`primitives::use_dismissable_layer`)
|
|
855
|
+
share one dismissable-layer stack per `VirtualDom` in Dioxus, per root owner
|
|
856
|
+
(each island its own) in Leptos (`primitives::use_dismissable_layer`). The
|
|
857
|
+
stack follows renders, not handlers: a layer that closes itself stays on top
|
|
858
|
+
until it re-renders closed. Escape and a click on a backdrop act
|
|
673
859
|
on the top layer only, so a `Select` inside a `Dialog` closes first. There is
|
|
674
860
|
no document listener — an Escape counts only once it reaches an open layer's
|
|
675
861
|
`onkeydown` — and `AlertDialog`, `Sheet`, `Drawer`, `ContextMenu`, `Menubar`,
|
|
676
862
|
`NavigationMenu`, `HoverCard`, `Tooltip`, `InfoTip`, `DateTimePicker`,
|
|
677
863
|
`CommandDialog` and `DropdownMenuSub` are not on the stack yet.
|
|
678
|
-
- **terminal:** no plotting engine and no state. `TerminalChart` is a sized
|
|
679
|
-
`relative` host that hands its element out (`ref` in TS, `id` in Rust) for a
|
|
680
|
-
consumer-chosen charting library to mount into; `OrderForm` only prevents the
|
|
681
|
-
submit's default — inputs, validation and the order itself are the
|
|
682
|
-
consumer's. The open-orders pane reuses `Table` and `Tabs`.
|
|
683
864
|
- **calendar:** single month, single-date selection (no range/multi-month, no
|
|
684
865
|
dropdown captions). The grid is always six fixed weeks of 36px cells, so its
|
|
685
866
|
size never changes with the month or the locale's weekday labels, and it
|
|
@@ -691,7 +872,7 @@ measuring needs host-only `web-sys`). Known gaps:
|
|
|
691
872
|
locale whose short form contains whitespace (vi's "Thứ 2" → "T2", he's
|
|
692
873
|
"יום ב׳" → "ב׳") as long as narrow still yields seven distinct labels. Rust has no `Intl`, so its captions
|
|
693
874
|
stay English. Rust does manual date math; TS uses the built-in `Date`.
|
|
694
|
-
- **date-time-picker:** the kit's own bricks only —
|
|
875
|
+
- **date-time-picker:** the kit's own bricks only — a raised trigger, a
|
|
695
876
|
`Popover` with the `Calendar` and two numeric 24-hour hours/minutes fields (no
|
|
696
877
|
native `datetime-local` / `time` input, so the browser's locale popup never
|
|
697
878
|
appears). Its ARIA and button strings are `labels` overrides with English
|
|
@@ -707,19 +888,19 @@ measuring needs host-only `web-sys`). Known gaps:
|
|
|
707
888
|
focus, via the shared `data-stack` CSS in `tokens.css`. The enter is a CSS
|
|
708
889
|
keyframe (plays on insertion), the exit slides out on `data-state="closed"` and
|
|
709
890
|
unmounts on `transitionend` (no host timer). React measures each toast's height
|
|
710
|
-
for exact expanded spacing;
|
|
711
|
-
|
|
712
|
-
list uniform. Both **auto-dismiss** (default 4000ms) and **pause while the stack
|
|
891
|
+
for exact expanded spacing; Rust measures nothing, so it assumes a constant
|
|
892
|
+
height — collapsed pile exact, expanded list uniform. Both **auto-dismiss** (default 4000ms) and **pause while the stack
|
|
713
893
|
is hovered/focused**, and support **persistent** toasts; React does it with a
|
|
714
|
-
`setTimeout` (`duration: Infinity` to persist),
|
|
894
|
+
`setTimeout` (`duration: Infinity` to persist), both Rust ports with a host-timer-free
|
|
715
895
|
CSS "life" animation whose `animationend` closes the toast (`use_toaster().show(
|
|
716
896
|
msg, variant, None)` to persist). Swipe-to-dismiss (horizontal drag, past 45px
|
|
717
897
|
or a fast flick) is TS-only pointer physics.
|
|
718
898
|
- **form:** react-hook-form is dropped — these are presentational + ARIA-id
|
|
719
899
|
wiring; consumers own validation/state. Rust `FormControl` can't inject ids
|
|
720
900
|
onto an arbitrary child (no `Slot`), so it publishes them as a
|
|
721
|
-
`FormControlContext` that `Input`/`Textarea` consume; wrap a bare
|
|
722
|
-
you wire them yourself.
|
|
901
|
+
`FormControlContext` signal that `Input`/`Textarea` consume; wrap a bare
|
|
902
|
+
element and you wire them yourself. Rust `FormMessage` renders on its `error`
|
|
903
|
+
flag, not on having children.
|
|
723
904
|
- **carousel:** pointer-drag physics are TS-only (prev/next + keyboard in
|
|
724
905
|
Rust). Embla momentum is not reproduced.
|
|
725
906
|
- **drawer:** the Vaul-style enter/exit motion is shared by both ports via
|
|
@@ -739,12 +920,34 @@ measuring needs host-only `web-sys`). Known gaps:
|
|
|
739
920
|
The lock-up is composed from `mark` + `brand` (display family) + `tagline`,
|
|
740
921
|
or passed whole as `lockup`. Brand-coloured text reads `primary-ink`.
|
|
741
922
|
- **TS-only for now:** `NativeSelect`, the `Field` id hand-off,
|
|
742
|
-
`SelectValue` labels, `ListRows` / `ListRow` (a table's phone form),
|
|
743
|
-
|
|
744
|
-
`
|
|
745
|
-
`
|
|
746
|
-
|
|
747
|
-
`
|
|
923
|
+
`SelectValue` labels, `ListRows` / `ListRow` (a table's phone form), the
|
|
924
|
+
rest of the [app shell](#app-shell) (`AppShell`, `ShellNav`, `BottomTabBar`,
|
|
925
|
+
`NavBadge` / `NavDot`, `AccountMenu` and its profile form, `MobileAppBar`,
|
|
926
|
+
`PageFrame` / `PageHeading`, `SectionNav`, `ResourceError`, `Settled`) and
|
|
927
|
+
the parts that need a browser API Rust does not wrap: `AnimatedNumber` /
|
|
928
|
+
`useCountTween` (a frame loop and `Intl`), `ChatStreamText` (word diffing and
|
|
929
|
+
timers), and `ChatPanel` / `ChatDock` (`ResizeObserver`, scroll pinning).
|
|
930
|
+
Collapsible `ShellNav` / `SectionNav` groups are TS-only too. Their CSS (the
|
|
931
|
+
entrances, the markers, the motion and shell tokens) ships in the shared
|
|
932
|
+
sheet, so a Rust app can use the `data-enter` contract today.
|
|
933
|
+
- **Rust twins of the composed parts:** they keep the TS markup and class strings, with
|
|
934
|
+
these differences. Overlays keep the limits of the older Rust overlays (inline
|
|
935
|
+
`position: fixed`, no portal, no focus trap, not on the layer stack).
|
|
936
|
+
`HeaderBalance` takes a formatted `String` (no rolling digits, no `Settled`).
|
|
937
|
+
`ConfirmDialog` has `pending` and `error` props in place of a promise, and a
|
|
938
|
+
`pending` dialog ignores Esc, the scrim and the buttons. `KeyValue` takes a
|
|
939
|
+
`copy` callback; with the `wasm` feature and no callback it writes through
|
|
940
|
+
`web-sys`. `CommandPalette` is controllable (`open`, `default_open`,
|
|
941
|
+
`on_open_change`), and `use_command_palette_shortcut` listens for Ctrl or Cmd
|
|
942
|
+
K by key code on the `wasm` feature only, so it does nothing in an SSR build.
|
|
943
|
+
`AsidePanel` has no `matchMedia`: with `AsideCollapse::Md` it renders the
|
|
944
|
+
docked or sheet form and the drawer form together, and breakpoint classes show
|
|
945
|
+
one, so its children mount twice. Dialogs, popovers and menus do not name
|
|
946
|
+
themselves: pass `aria_labelledby` and the title `id`, or `aria_label`. The
|
|
947
|
+
TS tests that stub `matchMedia`, or that need `SectionNav` or `AppShell`, are
|
|
948
|
+
ported as markup checks or not at all. `dioxus-ssr` runs no effects, so the
|
|
949
|
+
clipboard write, the Ctrl+K listener and focus moves are checked in the
|
|
950
|
+
gallery only.
|
|
748
951
|
- **app shell:** `ShellNav`'s marker is measured (one forced layout read per
|
|
749
952
|
navigation); the tab bar's rule is arithmetic and assumes equal tabs. A
|
|
750
953
|
`data-enter` nested inside another still entering composes both rises (the
|
|
@@ -769,7 +972,8 @@ npm run build # tsup → dist/ (ESM + d.ts)
|
|
|
769
972
|
The Rust counterpart is verified from the repo root:
|
|
770
973
|
|
|
771
974
|
```sh
|
|
772
|
-
cargo test -p ev_lib --features
|
|
773
|
-
cargo clippy -p ev_lib --features
|
|
774
|
-
cargo check -p ev_lib --features "
|
|
975
|
+
cargo test -p ev_lib --features "uikit_dioxus uikit_leptos"
|
|
976
|
+
cargo clippy -p ev_lib --features "uikit_dioxus uikit_leptos" --all-targets -- -D warnings
|
|
977
|
+
cargo check -p ev_lib --features "uikit_dioxus wasm" --target wasm32-unknown-unknown
|
|
978
|
+
cargo check -p ev_lib --features "uikit_leptos wasm" --target wasm32-unknown-unknown
|
|
775
979
|
```
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
function references(value) {
|
|
3
3
|
return [...value.matchAll(/var\(--([A-Za-z0-9-]+)/g)].flatMap((m) => m[1] ? [m[1]] : []);
|
|
4
4
|
}
|
|
5
|
-
var DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand])";
|
|
5
|
+
var DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand], [data-theme])";
|
|
6
|
+
var FACES_SCOPE = ":where([data-theme], [data-brand], .light, .dark, :root)";
|
|
6
7
|
function readRules(css) {
|
|
7
8
|
const rules = [];
|
|
8
9
|
const src = css.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
@@ -48,10 +49,12 @@ function readContract(css) {
|
|
|
48
49
|
}
|
|
49
50
|
const derivedRule = rules.find((r) => r.selector === DERIVED_SCOPE);
|
|
50
51
|
if (!derivedRule) throw new Error(`token sheet has no \`${DERIVED_SCOPE}\` rule \u2014 not the kit's contract`);
|
|
51
|
-
const
|
|
52
|
+
const facesRule = rules.find((r) => r.selector === FACES_SCOPE);
|
|
53
|
+
const formulas = new Map([...derivedRule.declarations, ...facesRule?.declarations ?? []]);
|
|
54
|
+
const derived = [...formulas.keys()];
|
|
52
55
|
const stray = derived.filter((d) => !painted.includes(d));
|
|
53
56
|
if (stray.length > 0) throw new Error(`derived tokens with no @theme mapping: ${stray.join(", ")}`);
|
|
54
|
-
const derivedFrom = Object.fromEntries([...
|
|
57
|
+
const derivedFrom = Object.fromEntries([...formulas].map(([name, value]) => [name, references(value)]));
|
|
55
58
|
return { required: painted.filter((p) => !derived.includes(p)), derived, derivedFrom };
|
|
56
59
|
}
|
|
57
60
|
|
|
@@ -144,8 +147,8 @@ function splitTopLevel(inner) {
|
|
|
144
147
|
|
|
145
148
|
// src/palette/brand.ts
|
|
146
149
|
var POLARITIES = ["light", "dark"];
|
|
147
|
-
var FONT_ROLES = ["sans", "
|
|
148
|
-
var FONT_KEYS = { sans: "sans", text: "sans",
|
|
150
|
+
var FONT_ROLES = ["sans", "serif", "mono"];
|
|
151
|
+
var FONT_KEYS = { sans: "sans", text: "sans", serif: "serif", mono: "mono" };
|
|
149
152
|
var PaletteError = class extends Error {
|
|
150
153
|
constructor(problems) {
|
|
151
154
|
super(`invalid brand palette:
|
|
@@ -293,6 +296,7 @@ ${extras.join("")}}
|
|
|
293
296
|
|
|
294
297
|
export {
|
|
295
298
|
DERIVED_SCOPE,
|
|
299
|
+
FACES_SCOPE,
|
|
296
300
|
readRules,
|
|
297
301
|
readContract,
|
|
298
302
|
parseToml,
|
|
@@ -303,4 +307,4 @@ export {
|
|
|
303
307
|
validateBrand,
|
|
304
308
|
renderPalette
|
|
305
309
|
};
|
|
306
|
-
//# sourceMappingURL=chunk-
|
|
310
|
+
//# sourceMappingURL=chunk-35SPIU4U.js.map
|