@evinvest/uikit 0.1.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 +162 -0
- package/dist/index.d.ts +1183 -0
- package/dist/index.js +6722 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
- package/styles/tokens.css +106 -0
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# @evinvest/uikit
|
|
2
|
+
|
|
3
|
+
A **dep-light** React UI kit with shadcn-style semantics — the TypeScript mirror
|
|
4
|
+
of the `uikit` feature of the [`ev`](https://github.com/EV-invest/lib) Rust crate
|
|
5
|
+
(`ev::uikit`, Dioxus). Both ports are driven from the same canonical class
|
|
6
|
+
strings and the same design tokens, so a component looks and behaves the same
|
|
7
|
+
whether it is rendered by React or by Dioxus.
|
|
8
|
+
|
|
9
|
+
"Dep-light" means: **no `@radix-ui/*`, no `class-variance-authority`, no
|
|
10
|
+
`lucide-react`, no charting / carousel / date-picker / OTP / toast libraries.**
|
|
11
|
+
Variant maps are plain objects fused with [`cn`](#cn); icons are inline SVG; and
|
|
12
|
+
all overlay behaviour (portals, floating placement, focus traps, dismiss layers)
|
|
13
|
+
is hand-rolled in [`./primitives`](src/primitives). Runtime dependencies are just
|
|
14
|
+
`clsx` + `tailwind-merge`, with `react`/`react-dom` as peers.
|
|
15
|
+
|
|
16
|
+
> This is the first library in the monorepo that ships runtime deps — a UI kit
|
|
17
|
+
> can't be zero-dep like the `architecture` kernel. See the repo `AGENTS.md`.
|
|
18
|
+
|
|
19
|
+
The bundle is a **`"use client"`** module (it's interactive — hooks, context,
|
|
20
|
+
the DOM), so it can be imported from React Server Components / the Next.js App
|
|
21
|
+
Router directly. One consequence: `cn` re-exported here is therefore client-only;
|
|
22
|
+
if you need class merging in a **server** component, keep a local `cn`
|
|
23
|
+
(`clsx` + `tailwind-merge`) rather than importing it from the kit.
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
Published to the public npm registry:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm i @evinvest/uikit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Requires Node ≥ 20 and React 18 or 19. `dist/` is built on publish, not committed.
|
|
34
|
+
|
|
35
|
+
## Design tokens — the theme contract
|
|
36
|
+
|
|
37
|
+
Every component's Tailwind classes reference design tokens (`bg-primary`,
|
|
38
|
+
`text-card-foreground`, `border-input`, `ring-ring`, `bg-main-accent-t2`, …).
|
|
39
|
+
Those tokens are **shipped with the package** and must be imported into your
|
|
40
|
+
Tailwind v4 entrypoint — this is the load-bearing part of the kit:
|
|
41
|
+
|
|
42
|
+
```css
|
|
43
|
+
/* app.css — your Tailwind v4 entrypoint */
|
|
44
|
+
@import "tailwindcss";
|
|
45
|
+
@import "@evinvest/uikit/styles/tokens.css";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`styles/tokens.css` is kept byte-for-byte in parity with the Rust feature's
|
|
49
|
+
`tokens.css`; it defines a single **dark** palette via `:root` and wires it into
|
|
50
|
+
Tailwind utilities via `@theme inline`. (The kit drops `dark:*` utility variants
|
|
51
|
+
because the palette is dark by default — see [Limitations](#limitations).)
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
import { Button, Card, CardHeader, CardTitle, CardContent, Badge } from "@evinvest/uikit";
|
|
57
|
+
|
|
58
|
+
export function Example() {
|
|
59
|
+
return (
|
|
60
|
+
<Card>
|
|
61
|
+
<CardHeader>
|
|
62
|
+
<CardTitle>Portfolio</CardTitle>
|
|
63
|
+
<Badge variant="success">+12.4%</Badge>
|
|
64
|
+
</CardHeader>
|
|
65
|
+
<CardContent>
|
|
66
|
+
<Button onClick={() => {}}>Invest</Button>
|
|
67
|
+
</CardContent>
|
|
68
|
+
</Card>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `cn`
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { cn } from "@evinvest/uikit";
|
|
77
|
+
cn("p-4", "p-2"); // "p-2" — tailwind-merge resolves the conflict, rightmost wins
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`cn` (clsx + tailwind-merge) is the mirror of the Rust `cn!` macro
|
|
81
|
+
(`tailwind_fuse::tw_merge!`). A caller's `className`, passed last, beats the base.
|
|
82
|
+
|
|
83
|
+
## Rust ↔ TS parity
|
|
84
|
+
|
|
85
|
+
The Rust crate is the source of truth; this package preserves its _semantics_
|
|
86
|
+
while reading like idiomatic React. Canonical class strings are identical per
|
|
87
|
+
element across the two ports.
|
|
88
|
+
|
|
89
|
+
| Concept | Rust (`ev::uikit`) | TS (`@evinvest/uikit`) |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| class fusion | `cn!` macro (`tailwind_fuse`) | `cn` (`clsx` + `tailwind-merge`) |
|
|
92
|
+
| variants | `enum` + `fn class()` match | `as const` map keyed by variant |
|
|
93
|
+
| `asChild` | n/a (uses `children`) | `Slot` primitive |
|
|
94
|
+
| controlled state | `use_controllable` | `useControllableState` |
|
|
95
|
+
| keyboard nav | per-component signals | `useRovingFocus` |
|
|
96
|
+
| overlay placement | inline `position:fixed` + `data-side` | `Portal` + `useFloating` |
|
|
97
|
+
| dismiss / focus trap | full-screen backdrop / native order | `useDismissableLayer` / `useFocusScope` |
|
|
98
|
+
|
|
99
|
+
### Component inventory (all 63 bricks)
|
|
100
|
+
|
|
101
|
+
- **Tier A — static (22):** badge, button, button-group, card, input, textarea,
|
|
102
|
+
label, field, separator, skeleton, spinner, kbd, table, aspect-ratio, alert,
|
|
103
|
+
breadcrumb, empty, item, input-group, avatar, progress, pagination.
|
|
104
|
+
- **Tier B — interactive (13):** accordion, collapsible, tabs, toggle,
|
|
105
|
+
toggle-group, switch, checkbox, radio-group, slider, sidebar, scroll-area,
|
|
106
|
+
carousel, input-otp.
|
|
107
|
+
- **Tier C — overlay (13):** tooltip, popover, hover-card, dropdown-menu,
|
|
108
|
+
context-menu, menubar, navigation-menu, dialog, alert-dialog, sheet, drawer,
|
|
109
|
+
select, command.
|
|
110
|
+
- **Tier D — engines (5):** chart, calendar, sonner (toaster), form, resizable.
|
|
111
|
+
|
|
112
|
+
The canonical variant set is the **superset** of the original cabinet (Rust) and
|
|
113
|
+
landing (TS) sources — e.g. `Badge` keeps cabinet's `success` variant, `Button`
|
|
114
|
+
keeps landing's `icon-sm`/`icon-lg` sizes.
|
|
115
|
+
|
|
116
|
+
## Limitations
|
|
117
|
+
|
|
118
|
+
Reproducing everything dep-light means some behaviour is intentionally reduced,
|
|
119
|
+
especially on the Rust side (Dioxus has no renderer-agnostic portal, and layout
|
|
120
|
+
measuring needs host-only `web-sys`). Known gaps:
|
|
121
|
+
|
|
122
|
+
- **Theme:** single dark palette; `dark:*` utility variants are dropped.
|
|
123
|
+
- **Rust overlays** (dialog, popover, dropdown, select, menus, tooltip, …) render
|
|
124
|
+
inline with `position:fixed` + a backdrop and CSS-only placement — no portal,
|
|
125
|
+
no viewport-measured floating, native focus order (no trap). TS overlays use a
|
|
126
|
+
real `Portal`, single-flip `useFloating`, `useDismissableLayer`, and
|
|
127
|
+
`useFocusScope`.
|
|
128
|
+
- **chart:** the recharts plotting engine is not bundled. `ChartContainer` is a
|
|
129
|
+
themed SVG host (emits `--color-*` from its config); `ChartTooltipContent` /
|
|
130
|
+
`ChartLegendContent` are presentational and take explicit items. Draw series
|
|
131
|
+
yourself inside the container.
|
|
132
|
+
- **calendar:** single month, single-date selection (no range/multi-month, no
|
|
133
|
+
locale/dropdown features). Rust does manual date math; TS uses the built-in
|
|
134
|
+
`Date`.
|
|
135
|
+
- **sonner:** TS exposes a global `toast()` backed by a module store with
|
|
136
|
+
auto-dismiss; Rust uses a `ToasterProvider` + `use_toaster()` hook and omits
|
|
137
|
+
auto-dismiss (dismiss via the close button or `.dismiss(id)`).
|
|
138
|
+
- **form:** react-hook-form is dropped — these are presentational + ARIA-id
|
|
139
|
+
wiring; consumers own validation/state. Rust `FormControl` can't inject ids
|
|
140
|
+
onto an arbitrary child (no `Slot`), so the consumer wires them.
|
|
141
|
+
- **resizable / carousel / drawer:** pointer-drag physics are TS-only (keyboard
|
|
142
|
+
in Rust for resizable; prev/next + keyboard for carousel; click-to-dismiss for
|
|
143
|
+
drawer). Embla momentum / vaul drag-to-dismiss are not reproduced.
|
|
144
|
+
- **sidebar:** the mobile-sheet integration, cookie persistence, and keyboard
|
|
145
|
+
shortcut are omitted.
|
|
146
|
+
|
|
147
|
+
## Develop
|
|
148
|
+
|
|
149
|
+
```sh
|
|
150
|
+
npm i
|
|
151
|
+
npm run typecheck # tsc --noEmit
|
|
152
|
+
npm run test # vitest (jsdom + @testing-library)
|
|
153
|
+
npm run build # tsup → dist/ (ESM + d.ts)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The Rust counterpart is verified from the repo root:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
cargo test -p ev --features uikit
|
|
160
|
+
cargo clippy -p ev --features uikit --all-targets -- -D warnings
|
|
161
|
+
cargo check -p ev --features "uikit wasm" --target wasm32-unknown-unknown
|
|
162
|
+
```
|