@evinvest/uikit 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +210 -13
  2. package/dist/chunk-3IPB4QQJ.js +306 -0
  3. package/dist/chunk-3IPB4QQJ.js.map +1 -0
  4. package/dist/components/accordion.js +136 -0
  5. package/dist/components/accordion.js.map +1 -0
  6. package/dist/components/alert-dialog.js +175 -0
  7. package/dist/components/alert-dialog.js.map +1 -0
  8. package/dist/components/alert.js +49 -0
  9. package/dist/components/alert.js.map +1 -0
  10. package/dist/components/avatar.js +54 -0
  11. package/dist/components/avatar.js.map +1 -0
  12. package/dist/components/badge.js +25 -0
  13. package/dist/components/badge.js.map +1 -0
  14. package/dist/components/band.js +75 -0
  15. package/dist/components/band.js.map +1 -0
  16. package/dist/components/breadcrumb.js +132 -0
  17. package/dist/components/breadcrumb.js.map +1 -0
  18. package/dist/components/button-group.js +61 -0
  19. package/dist/components/button-group.js.map +1 -0
  20. package/dist/components/button.js +55 -0
  21. package/dist/components/button.js.map +1 -0
  22. package/dist/components/calendar.js +226 -0
  23. package/dist/components/calendar.js.map +1 -0
  24. package/dist/components/card.js +98 -0
  25. package/dist/components/card.js.map +1 -0
  26. package/dist/components/carousel.js +262 -0
  27. package/dist/components/carousel.js.map +1 -0
  28. package/dist/components/chart.js +151 -0
  29. package/dist/components/chart.js.map +1 -0
  30. package/dist/components/checkbox.js +68 -0
  31. package/dist/components/checkbox.js.map +1 -0
  32. package/dist/components/collapsible.js +60 -0
  33. package/dist/components/collapsible.js.map +1 -0
  34. package/dist/components/command.js +274 -0
  35. package/dist/components/command.js.map +1 -0
  36. package/dist/components/container.js +18 -0
  37. package/dist/components/container.js.map +1 -0
  38. package/dist/components/context-menu.js +337 -0
  39. package/dist/components/context-menu.js.map +1 -0
  40. package/dist/components/date-time-picker.js +301 -0
  41. package/dist/components/date-time-picker.js.map +1 -0
  42. package/dist/components/dialog.js +207 -0
  43. package/dist/components/dialog.js.map +1 -0
  44. package/dist/components/drawer.js +351 -0
  45. package/dist/components/drawer.js.map +1 -0
  46. package/dist/components/dropdown-menu.js +356 -0
  47. package/dist/components/dropdown-menu.js.map +1 -0
  48. package/dist/components/empty.js +91 -0
  49. package/dist/components/empty.js.map +1 -0
  50. package/dist/components/field-context.js +30 -0
  51. package/dist/components/field-context.js.map +1 -0
  52. package/dist/components/field.js +216 -0
  53. package/dist/components/field.js.map +1 -0
  54. package/dist/components/footer.js +89 -0
  55. package/dist/components/footer.js.map +1 -0
  56. package/dist/components/form.js +101 -0
  57. package/dist/components/form.js.map +1 -0
  58. package/dist/components/hover-card.js +90 -0
  59. package/dist/components/hover-card.js.map +1 -0
  60. package/dist/components/info-tip.js +161 -0
  61. package/dist/components/info-tip.js.map +1 -0
  62. package/dist/components/input-group.js +124 -0
  63. package/dist/components/input-group.js.map +1 -0
  64. package/dist/components/input-otp.js +120 -0
  65. package/dist/components/input-otp.js.map +1 -0
  66. package/dist/components/input.js +26 -0
  67. package/dist/components/input.js.map +1 -0
  68. package/dist/components/item.js +167 -0
  69. package/dist/components/item.js.map +1 -0
  70. package/dist/components/kbd.js +29 -0
  71. package/dist/components/kbd.js.map +1 -0
  72. package/dist/components/label.js +11 -0
  73. package/dist/components/label.js.map +1 -0
  74. package/dist/components/logo.js +41 -0
  75. package/dist/components/logo.js.map +1 -0
  76. package/dist/components/menubar.js +330 -0
  77. package/dist/components/menubar.js.map +1 -0
  78. package/dist/components/native-select.js +62 -0
  79. package/dist/components/native-select.js.map +1 -0
  80. package/dist/components/navigation-menu.js +204 -0
  81. package/dist/components/navigation-menu.js.map +1 -0
  82. package/dist/components/pagination.js +138 -0
  83. package/dist/components/pagination.js.map +1 -0
  84. package/dist/components/popover.js +104 -0
  85. package/dist/components/popover.js.map +1 -0
  86. package/dist/components/progress.js +30 -0
  87. package/dist/components/progress.js.map +1 -0
  88. package/dist/components/radio-group.js +113 -0
  89. package/dist/components/radio-group.js.map +1 -0
  90. package/dist/components/resizable.js +193 -0
  91. package/dist/components/resizable.js.map +1 -0
  92. package/dist/components/scroll-area.js +40 -0
  93. package/dist/components/scroll-area.js.map +1 -0
  94. package/dist/components/section-descriptor.js +88 -0
  95. package/dist/components/section-descriptor.js.map +1 -0
  96. package/dist/components/select-chevron.js +25 -0
  97. package/dist/components/select-chevron.js.map +1 -0
  98. package/dist/components/select.js +288 -0
  99. package/dist/components/select.js.map +1 -0
  100. package/dist/components/separator.js +31 -0
  101. package/dist/components/separator.js.map +1 -0
  102. package/dist/components/sheet.js +200 -0
  103. package/dist/components/sheet.js.map +1 -0
  104. package/dist/components/sidebar.js +326 -0
  105. package/dist/components/sidebar.js.map +1 -0
  106. package/dist/components/skeleton.js +18 -0
  107. package/dist/components/skeleton.js.map +1 -0
  108. package/dist/components/slider.js +138 -0
  109. package/dist/components/slider.js.map +1 -0
  110. package/dist/components/sonner.js +319 -0
  111. package/dist/components/sonner.js.map +1 -0
  112. package/dist/components/spinner.js +23 -0
  113. package/dist/components/spinner.js.map +1 -0
  114. package/dist/components/status-screen.js +145 -0
  115. package/dist/components/status-screen.js.map +1 -0
  116. package/dist/components/switch.js +54 -0
  117. package/dist/components/switch.js.map +1 -0
  118. package/dist/components/table.js +124 -0
  119. package/dist/components/table.js.map +1 -0
  120. package/dist/components/tabs.js +133 -0
  121. package/dist/components/tabs.js.map +1 -0
  122. package/dist/components/terminal.js +206 -0
  123. package/dist/components/terminal.js.map +1 -0
  124. package/dist/components/textarea.js +22 -0
  125. package/dist/components/textarea.js.map +1 -0
  126. package/dist/components/toggle-group.js +105 -0
  127. package/dist/components/toggle-group.js.map +1 -0
  128. package/dist/components/toggle.js +53 -0
  129. package/dist/components/toggle.js.map +1 -0
  130. package/dist/components/tooltip.js +102 -0
  131. package/dist/components/tooltip.js.map +1 -0
  132. package/dist/generated/accent.js +27 -0
  133. package/dist/generated/accent.js.map +1 -0
  134. package/dist/generated/accordion.js +13 -0
  135. package/dist/generated/accordion.js.map +1 -0
  136. package/dist/generated/alert-dialog.js +15 -0
  137. package/dist/generated/alert-dialog.js.map +1 -0
  138. package/dist/generated/alert.js +16 -0
  139. package/dist/generated/alert.js.map +1 -0
  140. package/dist/generated/avatar.js +9 -0
  141. package/dist/generated/avatar.js.map +1 -0
  142. package/dist/generated/badge.js +13 -0
  143. package/dist/generated/badge.js.map +1 -0
  144. package/dist/generated/band.js +39 -0
  145. package/dist/generated/band.js.map +1 -0
  146. package/dist/generated/breadcrumb.js +15 -0
  147. package/dist/generated/breadcrumb.js.map +1 -0
  148. package/dist/generated/button-group.js +14 -0
  149. package/dist/generated/button-group.js.map +1 -0
  150. package/dist/generated/button.js +30 -0
  151. package/dist/generated/button.js.map +1 -0
  152. package/dist/generated/calendar.js +29 -0
  153. package/dist/generated/calendar.js.map +1 -0
  154. package/dist/generated/card.js +17 -0
  155. package/dist/generated/card.js.map +1 -0
  156. package/dist/generated/carousel.js +31 -0
  157. package/dist/generated/carousel.js.map +1 -0
  158. package/dist/generated/chart.js +9 -0
  159. package/dist/generated/chart.js.map +1 -0
  160. package/dist/generated/checkbox.js +7 -0
  161. package/dist/generated/checkbox.js.map +1 -0
  162. package/dist/generated/command.js +27 -0
  163. package/dist/generated/command.js.map +1 -0
  164. package/dist/generated/container.js +5 -0
  165. package/dist/generated/container.js.map +1 -0
  166. package/dist/generated/context-menu.js +19 -0
  167. package/dist/generated/context-menu.js.map +1 -0
  168. package/dist/generated/date-time-picker.js +15 -0
  169. package/dist/generated/date-time-picker.js.map +1 -0
  170. package/dist/generated/dialog.js +17 -0
  171. package/dist/generated/dialog.js.map +1 -0
  172. package/dist/generated/drawer.js +26 -0
  173. package/dist/generated/drawer.js.map +1 -0
  174. package/dist/generated/dropdown-menu.js +21 -0
  175. package/dist/generated/dropdown-menu.js.map +1 -0
  176. package/dist/generated/empty.js +20 -0
  177. package/dist/generated/empty.js.map +1 -0
  178. package/dist/generated/field.js +33 -0
  179. package/dist/generated/field.js.map +1 -0
  180. package/dist/generated/focus.js +5 -0
  181. package/dist/generated/focus.js.map +1 -0
  182. package/dist/generated/form.js +11 -0
  183. package/dist/generated/form.js.map +1 -0
  184. package/dist/generated/hover-card.js +5 -0
  185. package/dist/generated/hover-card.js.map +1 -0
  186. package/dist/generated/input-group.js +29 -0
  187. package/dist/generated/input-group.js.map +1 -0
  188. package/dist/generated/input-otp.js +15 -0
  189. package/dist/generated/input-otp.js.map +1 -0
  190. package/dist/generated/input.js +11 -0
  191. package/dist/generated/input.js.map +1 -0
  192. package/dist/generated/item.js +40 -0
  193. package/dist/generated/item.js.map +1 -0
  194. package/dist/generated/kbd.js +7 -0
  195. package/dist/generated/kbd.js.map +1 -0
  196. package/dist/generated/label.js +5 -0
  197. package/dist/generated/label.js.map +1 -0
  198. package/dist/generated/menubar.js +27 -0
  199. package/dist/generated/menubar.js.map +1 -0
  200. package/dist/generated/navigation-menu.js +19 -0
  201. package/dist/generated/navigation-menu.js.map +1 -0
  202. package/dist/generated/popover.js +5 -0
  203. package/dist/generated/popover.js.map +1 -0
  204. package/dist/generated/progress.js +7 -0
  205. package/dist/generated/progress.js.map +1 -0
  206. package/dist/generated/radio-group.js +7 -0
  207. package/dist/generated/radio-group.js.map +1 -0
  208. package/dist/generated/resizable.js +11 -0
  209. package/dist/generated/resizable.js.map +1 -0
  210. package/dist/generated/scroll-area.js +14 -0
  211. package/dist/generated/scroll-area.js.map +1 -0
  212. package/dist/generated/select.js +15 -0
  213. package/dist/generated/select.js.map +1 -0
  214. package/dist/generated/separator.js +10 -0
  215. package/dist/generated/separator.js.map +1 -0
  216. package/dist/generated/sheet.js +25 -0
  217. package/dist/generated/sheet.js.map +1 -0
  218. package/dist/generated/sidebar.js +41 -0
  219. package/dist/generated/sidebar.js.map +1 -0
  220. package/dist/generated/skeleton.js +5 -0
  221. package/dist/generated/skeleton.js.map +1 -0
  222. package/dist/generated/slider.js +11 -0
  223. package/dist/generated/slider.js.map +1 -0
  224. package/dist/generated/sonner.js +30 -0
  225. package/dist/generated/sonner.js.map +1 -0
  226. package/dist/generated/switch.js +7 -0
  227. package/dist/generated/switch.js.map +1 -0
  228. package/dist/generated/table.js +21 -0
  229. package/dist/generated/table.js.map +1 -0
  230. package/dist/generated/tabs.js +11 -0
  231. package/dist/generated/tabs.js.map +1 -0
  232. package/dist/generated/terminal.js +70 -0
  233. package/dist/generated/terminal.js.map +1 -0
  234. package/dist/generated/textarea.js +11 -0
  235. package/dist/generated/textarea.js.map +1 -0
  236. package/dist/generated/toggle.js +18 -0
  237. package/dist/generated/toggle.js.map +1 -0
  238. package/dist/generated/tooltip.js +5 -0
  239. package/dist/generated/tooltip.js.map +1 -0
  240. package/dist/index.d.ts +141 -34
  241. package/dist/index.js +73 -8623
  242. package/dist/lib/cn.js +12 -0
  243. package/dist/lib/cn.js.map +1 -0
  244. package/dist/lib/focus.js +5 -0
  245. package/dist/lib/focus.js.map +1 -0
  246. package/dist/palette-cli.js +41 -0
  247. package/dist/palette-cli.js.map +1 -0
  248. package/dist/palette.d.ts +80 -0
  249. package/dist/palette.js +25 -0
  250. package/dist/palette.js.map +1 -0
  251. package/dist/primitives/dismissable-layer.js +35 -0
  252. package/dist/primitives/dismissable-layer.js.map +1 -0
  253. package/dist/primitives/focus-scope.js +58 -0
  254. package/dist/primitives/focus-scope.js.map +1 -0
  255. package/dist/primitives/merge-refs.js +13 -0
  256. package/dist/primitives/merge-refs.js.map +1 -0
  257. package/dist/primitives/portal.js +19 -0
  258. package/dist/primitives/portal.js.map +1 -0
  259. package/dist/primitives/presence.js +45 -0
  260. package/dist/primitives/presence.js.map +1 -0
  261. package/dist/primitives/slot.js +39 -0
  262. package/dist/primitives/slot.js.map +1 -0
  263. package/dist/primitives/use-controllable-state.js +24 -0
  264. package/dist/primitives/use-controllable-state.js.map +1 -0
  265. package/dist/primitives/use-floating.js +128 -0
  266. package/dist/primitives/use-floating.js.map +1 -0
  267. package/dist/primitives/use-hover-intent.js +50 -0
  268. package/dist/primitives/use-hover-intent.js.map +1 -0
  269. package/dist/primitives/use-roving-focus.js +40 -0
  270. package/dist/primitives/use-roving-focus.js.map +1 -0
  271. package/dist/primitives/walk-elements.js +13 -0
  272. package/dist/primitives/walk-elements.js.map +1 -0
  273. package/package.json +15 -5
  274. package/styles/ev.css +73 -0
  275. package/styles/theme.css +470 -0
  276. package/styles/tokens.css +129 -90
  277. package/dist/index.js.map +0 -1
package/README.md CHANGED
@@ -16,11 +16,23 @@ is hand-rolled in [`./primitives`](src/primitives). Runtime dependencies are jus
16
16
  > This is the first library in the monorepo that ships runtime deps — a UI kit
17
17
  > can't be zero-dep like the `architecture` kernel. See the repo `AGENTS.md`.
18
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.
19
+ The kit ships **one file per module**, each with its own boundary: the
20
+ interactive ones (hooks, context, event handlers — `Select`, `Dialog`, `Field`,
21
+ `Input`, …) start with `"use client"`, and the static ones (`Button`,
22
+ `Section`/`Band`, `Table`, `Badge`, `Card`, `Footer`, `Label`, `cn`, …) do not.
23
+ Import everything from `@evinvest/uikit` in a Server Component: a static
24
+ component renders on the server and sends the browser nothing, and a client one
25
+ is a client reference as usual. `cn` is server-safe.
26
+
27
+ Keep it that way when adding to the kit:
28
+
29
+ - a module with a hook, a context or an `on*={…}` handler says `"use client"`
30
+ on its first line, and one without says nothing —
31
+ `test/client-boundary.test.ts` derives which is which from the source;
32
+ - `src/index.ts` names every re-export; `export *` fails the same test. A
33
+ bundler has to load every starred module to learn its names, so one star on
34
+ a client module ships it to every page that imports anything from the kit —
35
+ on the aquafix landing that was ~26 KB gz of components it never rendered.
24
36
 
25
37
  ## Install
26
38
 
@@ -47,7 +59,7 @@ your Tailwind v4 entrypoint — this is the load-bearing part of the kit:
47
59
 
48
60
  | group | names |
49
61
  |---|---|
50
- | scope | `:root` · `.dark` · `.light` — a consumer defines one or both |
62
+ | scope | `:root` · `[data-brand="…"]` — a theme root; `.light` · `.dark` — a polarity beneath it |
51
63
  | surfaces | `background` `card` `popover` `muted` `hover` |
52
64
  | ink | `ink` `ink-mid` `ink-soft` — hierarchy, loudest first |
53
65
  | lines | `border` `input` `ring` |
@@ -57,7 +69,9 @@ your Tailwind v4 entrypoint — this is the load-bearing part of the kit:
57
69
 
58
70
  Custom properties inherit, so a scope class on a `<section>` re-themes its
59
71
  subtree — `bg-card text-ink` is correct on both polarities with no prop. That is
60
- what `<Section polarity="dark">` does.
72
+ what `<Section polarity="dark">` does. Without `polarity` a `Section` sets no
73
+ scope and inherits its parent's, so a dark-first brand stays dark; pass
74
+ `"light"` or `"dark"` only where a band flips.
61
75
 
62
76
  A surface takes its ink from the scope, so it carries no `-foreground`. A
63
77
  **filled role** does not — gold wants black and navy wants white regardless of
@@ -85,9 +99,185 @@ state, never its appearance (`Red`) and never its selection (`Default`); see
85
99
  [`docs/spec/variants.md`](../../docs/spec/variants.md).
86
100
 
87
101
  `styles/tokens.css` is generated by `nix run .#gen` from the repo-root
88
- `tokens.css` shared with the Rust feature — flattened, so it is self-contained —
89
- and carries EV's values: a single dark palette on `:root, .dark`. A consumer
90
- with two polarities writes its own sheet against the same names.
102
+ `tokens.css` shared with the Rust feature — flattened, so it is self-contained.
103
+ It has two halves, each also shipped as a flat sheet of its own —
104
+ `styles/theme.css` (the contract) and `styles/ev.css` (EV's palette):
105
+
106
+ - **the contract** — the `@theme inline` mapping, the geometry, and the tokens
107
+ *derived* from a palette (`hover`, `ink-mid`, `ink-soft`, `border`, `input`
108
+ from `ink`; `ring` from `primary-ink`). The derived ones are redeclared on
109
+ `:root`, `[data-brand]`, `.light` and `.dark` at zero specificity, so a brand
110
+ scope gets its own borders, hover and focus ring rather than inheriting the
111
+ root's, and a palette may still pin any of them outright. The geometry
112
+ (`radius`, `page-px`, `band-py`, …) and its responsive scale-ups restart on
113
+ every `[data-brand]` the same way.
114
+ - **EV's palette** — a single dark palette on `:where(:root, [data-brand="ev"])`.
115
+ It binds neither polarity class, so a `.dark` section inside another brand
116
+ never picks EV's values up, and `<Section polarity="light">` under EV is a
117
+ no-op (EV is single-polarity).
118
+
119
+ ### Brand palettes
120
+
121
+ A second brand is a palette against the same names, scoped to
122
+ `[data-brand="<slug>"]`; light is its default and `.light` / `.dark` beneath it
123
+ pick a polarity, so a `<Section polarity="dark">` inside the brand takes the
124
+ brand's dark values. The package ships a generator for it, reading the
125
+ `brand.toml` shape (`[colors.light]` and `[colors.dark]`, keyed by token name;
126
+ optional top-level `mark` and `mark-aspect` become `--brand-mark` and
127
+ `--brand-aspect`; the family names in `[fonts]` — `sans` (or aquafix's `text`),
128
+ `display`, `mono` — become the [font parameters](#fonts); other keys and tables
129
+ are ignored). Each polarity block also declares `--scheme: light | dark`, which
130
+ the platform-drawn parts of the kit read (a `NativeSelect`'s popup):
131
+
132
+ ```sh
133
+ npx evinvest-palette --brand aquafix --out app/brand.css assets/brand.toml
134
+ ```
135
+
136
+ It fails — naming every hole — unless **both** polarities declare every name the
137
+ contract requires: Tailwind answers an undefined token with no rule at all, so a
138
+ hole would be silent on the page. Values must be `#hex` or `var(--token)`. The
139
+ list of required names is read from the installed kit's own `styles/theme.css`,
140
+ never restated. The same functions are importable for a build script or a Vite
141
+ plugin from `@evinvest/uikit/palette` (Node-only; see `example/vite.config.ts`).
142
+ The generator checks completeness, not contrast: measure a new palette against
143
+ the floors in `test/tokens.test.ts`, which runs them over every palette the kit
144
+ knows.
145
+
146
+ ```css
147
+ @import "tailwindcss";
148
+ @import "@evinvest/uikit/styles/tokens.css";
149
+ @import "./brand.css"; /* order does not matter: EV's palette has zero specificity */
150
+ ```
151
+
152
+ ```html
153
+ <html data-brand="aquafix">
154
+ ```
155
+
156
+ An app that never shows EV's palette imports the contract alone, so a name its
157
+ palette misses stays a visible hole instead of falling back to EV's value:
158
+
159
+ ```css
160
+ @import "tailwindcss";
161
+ @import "@evinvest/uikit/styles/theme.css";
162
+ @import "./brand.css";
163
+ ```
164
+
165
+ Overlays (`Dialog`, `Select`, `Popover`, `Tooltip`, menus, …) portal to
166
+ `document.body` and would leave the brand scope there. Wrap the scope's content
167
+ in `PortalProvider` with an element **inside** the scope — and inside its
168
+ polarity, or a dialog opened from a dark band comes up light:
169
+
170
+ ```tsx
171
+ const [overlays, setOverlays] = useState<HTMLDivElement | null>(null);
172
+
173
+ <section data-brand="aquafix" className="dark">
174
+ <PortalProvider container={overlays}>{children}</PortalProvider>
175
+ <div ref={setOverlays} />
176
+ </section>
177
+ ```
178
+
179
+ Put the container as a direct child of the `[data-brand]` (or polarity)
180
+ element, and never under an ancestor with `overflow: hidden`, a `transform`,
181
+ `filter` or `perspective`, or anything else that makes a containing block or
182
+ an isolated stacking context (`contain`, `isolation: isolate`, a `z-index`ed
183
+ positioned box): overlays are positioned `fixed`/`absolute` against the
184
+ viewport and stacked by `z-index`, so any of those clips them, offsets them, or
185
+ buries them under the page.
186
+
187
+ A brand on `<html>` needs no provider: `document.body` is already inside it.
188
+ Brand scopes do not nest — CSS has no "nearest ancestor", so a polarity class
189
+ under a brand under another brand may match either.
190
+
191
+ ### Fonts
192
+
193
+ The families are parameters, one per role; unset, each is EV's:
194
+
195
+ | parameter | utilities | default (EV) |
196
+ |---|---|---|
197
+ | `--brand-font-sans` | `font-sans`, the page's base family | `--font-inter`, else `"Inter"` |
198
+ | `--brand-font-display` | `font-display`, `font-serif` | `--font-playfair`, else `"Playfair Display"` |
199
+ | `--brand-font-mono` | `font-mono` | `--font-inter`, else `"Inter"` |
200
+
201
+ Declare them on the brand scope (the palette generator writes them from
202
+ `[fonts]`) and load the files yourself — `next/font`, or your own `@font-face`;
203
+ the kit ships no font files:
204
+
205
+ ```css
206
+ [data-brand="aquafix"] {
207
+ --brand-font-display: "Archivo";
208
+ }
209
+ ```
210
+
211
+ The utilities resolve the chain on the element, so a scope re-fonts everything
212
+ under it that carries one. The page's base family resolves once, on `<html>`: a
213
+ brand scoped lower than `<html>` puts `font-sans` on its own element so plain
214
+ text picks the brand's family up.
215
+
216
+ ## Forms that submit before hydration
217
+
218
+ A landing page's form has to work with scripting off and keep its labels wired
219
+ through hydration. Three parts of the kit make that hold:
220
+
221
+ - **`NativeSelect`** is a real `<select name>` wearing the kit's control — the
222
+ kit's arrow instead of the OS one, token colours, the popup following the
223
+ palette's `--scheme`. It posts with the form without any script, and the
224
+ popup is the platform's (keyboard, type-ahead, the mobile picker).
225
+ `NativeSelectOption` / `NativeSelectGroup` are `<option>` / `<optgroup>`.
226
+ `placeholder` adds an empty, unpickable first option, selected until a value
227
+ is chosen and styled `text-ink-soft`; with `required`, the browser refuses to
228
+ submit it. `className` styles the `<select>`, `wrapperClassName` the box that
229
+ holds it and the arrow. That box is `w-full`, like `Input`, so the two line
230
+ up in a form column; narrow it with `wrapperClassName`, not `className`.
231
+
232
+ It is a component of its own rather than a mode of `Select`: none of
233
+ `SelectTrigger` / `SelectValue` / `SelectContent` has a native counterpart.
234
+ `Select` stays the scripted combobox for what a platform popup cannot draw
235
+ (rich rows, a check mark, brand-styled menus everywhere).
236
+ - **`Field` mints the id** with `React.useId` — positional, so the server and the
237
+ hydrating client agree — and hands it to its `FieldLabel` as `for` and to the
238
+ kit's `Input`, `Textarea`, `NativeSelect`, `SelectTrigger`, `Checkbox` and
239
+ `Switch` as `id`, each only when the caller passed none. No `FormControl`
240
+ wrapper is needed. Want your own id — pass `controlId` to the `Field`, and
241
+ both ends take it. One control per `Field`: when it holds more, give the
242
+ others an `id` of their own (a development warning names the collision).
243
+ A `FieldLabel` that wraps its control (`<FieldLabel><input type="checkbox" />
244
+ Accept</FieldLabel>`, or a nested `Field` — the choice card) gets no `for`:
245
+ the wrapping already labels it. A control hidden inside a component of yours
246
+ is invisible to that check; pass `htmlFor={null}` there to drop the `for`.
247
+ (`FormItem` / `FormControl` keep their own `useId` wiring for forms that
248
+ want `aria-describedby` too.)
249
+ - **`SelectValue` shows the label**, not the stored value: the matching
250
+ `SelectItem`'s `textValue`, else the text of its children — text, so an id in
251
+ the item's markup is not rendered a second time in the trigger. It is read off
252
+ the element tree, so it is right on the server and before the popover first
253
+ opens. An item rendered by a component of yours is learnt when it mounts (on
254
+ the first open), and the trigger updates then; pass `SelectValue` children to
255
+ show something else outright.
256
+
257
+ ```tsx
258
+ <form action="/lead" method="post">
259
+ <Field>
260
+ <FieldLabel>Service</FieldLabel>
261
+ <NativeSelect name="service" placeholder="Pick one" required>
262
+ <NativeSelectOption value="leak">Leak</NativeSelectOption>
263
+ <NativeSelectOption value="boiler">Boiler</NativeSelectOption>
264
+ </NativeSelect>
265
+ </Field>
266
+ <Field>
267
+ <FieldLabel>Phone</FieldLabel>
268
+ <Input name="phone" type="tel" />
269
+ </Field>
270
+ <Button type="submit">Request a call</Button>
271
+ </form>
272
+ ```
273
+
274
+ **Focus.** A control that paints a fill — `Button` `primary` / `secondary` /
275
+ `destructive` or any accent fill, a filled `Badge`, `Switch`, `Checkbox` —
276
+ shows focus as the solid `ring` standing 2px off it, so both edges of the
277
+ indicator are against the surface: the ring reads on every surface at ≥ 3:1
278
+ and so does the fill, on every palette and polarity `test/tokens.test.ts`
279
+ measures. Controls that sit on the surface (`outline`, `ghost`, inputs) keep
280
+ the `ring` border plus its halo.
91
281
 
92
282
  ## Usage
93
283
 
@@ -135,11 +325,12 @@ element across the two ports.
135
325
  | overlay placement | inline `position:fixed` + `data-side` | `Portal` + `useFloating` |
136
326
  | dismiss / focus trap | full-screen backdrop / native order | `useDismissableLayer` / `useFocusScope` |
137
327
 
138
- ### Component inventory (all 62 bricks)
328
+ ### Component inventory (all 63 bricks)
139
329
 
140
- - **Tier A — static (22):** badge, button, button-group, card, input, textarea,
330
+ - **Tier A — static (23):** badge, button, button-group, card, input, textarea,
141
331
  label, field, separator, skeleton, spinner, kbd, table, container, alert,
142
- breadcrumb, empty, item, input-group, avatar, progress, pagination.
332
+ breadcrumb, empty, item, input-group, avatar, progress, pagination,
333
+ native-select (TS-only).
143
334
  - **Tier B — interactive (13):** accordion, collapsible, tabs, toggle,
144
335
  toggle-group, switch, checkbox, radio-group, slider, sidebar, scroll-area,
145
336
  carousel, input-otp.
@@ -242,6 +433,12 @@ measuring needs host-only `web-sys`). Known gaps:
242
433
  `max-h-*` on `DrawerContent` and let the body scroll by itself.
243
434
  - **sidebar:** the mobile-sheet integration, cookie persistence, and keyboard
244
435
  shortcut are omitted.
436
+ - **footer:** brings no content of its own — every string, the mark and the
437
+ lock-up are the caller's, and each column renders only when it has content.
438
+ The lock-up is composed from `mark` + `brand` (display family) + `tagline`,
439
+ or passed whole as `lockup`. Brand-coloured text reads `primary-ink`.
440
+ - **TS-only for now:** `NativeSelect`, the `Field` id hand-off and
441
+ `SelectValue` labels.
245
442
  - **brand chrome (header / footer / status pages):** TS routes links through an
246
443
  optional `linkComponent` (e.g. `next/link`) for soft navigation; Rust renders
247
444
  plain `<a>` (a full document load). The Dioxus header drives its scroll state,
@@ -0,0 +1,306 @@
1
+ // src/palette/contract.ts
2
+ function references(value) {
3
+ return [...value.matchAll(/var\(--([A-Za-z0-9-]+)/g)].flatMap((m) => m[1] ? [m[1]] : []);
4
+ }
5
+ var DERIVED_SCOPE = ":where(:root, .dark, .light, [data-brand])";
6
+ function readRules(css) {
7
+ const rules = [];
8
+ const src = css.replace(/\/\*[\s\S]*?\*\//g, "");
9
+ const stack = [];
10
+ let start = 0;
11
+ for (let i = 0; i < src.length; i++) {
12
+ const c = src[i];
13
+ if (c === "{") {
14
+ stack.push(src.slice(start, i).trim());
15
+ start = i + 1;
16
+ } else if (c === "}") {
17
+ const selector = stack.pop();
18
+ if (selector === void 0) throw new Error("unbalanced `}` in token sheet");
19
+ const body = src.slice(start, i);
20
+ if (!body.includes("{")) rules.push({ selector: normalise(selector), declarations: declarations(body) });
21
+ start = i + 1;
22
+ } else if (c === ";" && stack.length === 0) {
23
+ start = i + 1;
24
+ }
25
+ }
26
+ if (stack.length > 0) throw new Error("unbalanced `{` in token sheet");
27
+ return rules;
28
+ }
29
+ function declarations(body) {
30
+ const out = /* @__PURE__ */ new Map();
31
+ for (const decl of body.split(";")) {
32
+ const match = /^\s*--([A-Za-z0-9-]+)\s*:\s*([\s\S]+?)\s*$/.exec(decl);
33
+ if (match?.[1] && match[2]) out.set(match[1], match[2]);
34
+ }
35
+ return out;
36
+ }
37
+ function normalise(selector) {
38
+ return selector.replace(/\s+/g, " ").replace(/\s*,\s*/g, ", ").trim();
39
+ }
40
+ function readContract(css) {
41
+ const rules = readRules(css);
42
+ const theme = rules.find((r) => r.selector === "@theme inline");
43
+ if (!theme) throw new Error("token sheet has no `@theme inline` block \u2014 not the kit's contract");
44
+ const painted = [];
45
+ for (const [name, value] of theme.declarations) {
46
+ const match = /^color-(.+)$/.exec(name);
47
+ if (match?.[1] && value === `var(--${match[1]})`) painted.push(match[1]);
48
+ }
49
+ const derivedRule = rules.find((r) => r.selector === DERIVED_SCOPE);
50
+ if (!derivedRule) throw new Error(`token sheet has no \`${DERIVED_SCOPE}\` rule \u2014 not the kit's contract`);
51
+ const derived = [...derivedRule.declarations.keys()];
52
+ const stray = derived.filter((d) => !painted.includes(d));
53
+ if (stray.length > 0) throw new Error(`derived tokens with no @theme mapping: ${stray.join(", ")}`);
54
+ const derivedFrom = Object.fromEntries([...derivedRule.declarations].map(([name, value]) => [name, references(value)]));
55
+ return { required: painted.filter((p) => !derived.includes(p)), derived, derivedFrom };
56
+ }
57
+
58
+ // src/palette/toml.ts
59
+ var BARE_KEY = /^[A-Za-z0-9_-]+$/;
60
+ function parseToml(text) {
61
+ const root = {};
62
+ let table2 = root;
63
+ const headers = /* @__PURE__ */ new Set();
64
+ text.split(/\r?\n/).forEach((raw, index) => {
65
+ const line = stripComment(raw).trim();
66
+ if (line === "") return;
67
+ const fail = (why) => {
68
+ throw new Error(`TOML line ${index + 1}: ${why}: ${raw.trim()}`);
69
+ };
70
+ if (line.startsWith("[")) {
71
+ if (!line.endsWith("]") || line.startsWith("[[")) fail("only plain [table] headers are supported");
72
+ const path = line.slice(1, -1).trim().split(".").map((part) => part.trim());
73
+ if (headers.has(path.join("."))) fail("duplicate table header");
74
+ headers.add(path.join("."));
75
+ table2 = descend(root, path, fail);
76
+ return;
77
+ }
78
+ const eq = line.indexOf("=");
79
+ if (eq < 0) fail("expected `key = value`");
80
+ const key = line.slice(0, eq).trim();
81
+ if (!BARE_KEY.test(key)) fail("only bare keys are supported");
82
+ if (key in table2) fail(`duplicate key \`${key}\``);
83
+ table2[key] = parseValue(line.slice(eq + 1).trim(), fail);
84
+ });
85
+ return root;
86
+ }
87
+ function descend(root, path, fail) {
88
+ let table2 = root;
89
+ for (const part of path) {
90
+ const key = part.trim();
91
+ if (!BARE_KEY.test(key)) fail("only bare keys are supported in a header");
92
+ const next = table2[key] ?? {};
93
+ if (typeof next !== "object" || Array.isArray(next)) fail(`\`${key}\` is already a value`);
94
+ table2[key] = next;
95
+ table2 = next;
96
+ }
97
+ return table2;
98
+ }
99
+ function stripComment(line) {
100
+ let quoted = false;
101
+ for (let i = 0; i < line.length; i++) {
102
+ const c = line[i];
103
+ if (c === "\\" && quoted) i++;
104
+ else if (c === '"') quoted = !quoted;
105
+ else if (c === "#" && !quoted) return line.slice(0, i);
106
+ }
107
+ return line;
108
+ }
109
+ function parseValue(src, fail) {
110
+ if (src.startsWith('"')) {
111
+ if (src.length < 2 || !src.endsWith('"')) fail("unterminated string");
112
+ const body = src.slice(1, -1);
113
+ if (/(^|[^\\])(\\\\)*"/.test(body)) fail("one string per value");
114
+ if (/\\(?!["\\nt])/.test(body.replace(/\\\\/g, ""))) fail("unsupported escape");
115
+ return body.replace(/\\(["\\nt])/g, (_, c) => ({ n: "\n", t: " " })[c] ?? c);
116
+ }
117
+ if (src.startsWith("[")) {
118
+ if (!src.endsWith("]")) fail("arrays must close on the same line");
119
+ const inner = src.slice(1, -1).trim();
120
+ if (inner === "") return [];
121
+ return splitTopLevel(inner).map((item) => parseValue(item.trim(), fail));
122
+ }
123
+ if (src === "true" || src === "false") return src === "true";
124
+ if (/^[+-]?\d+(\.\d+)?$/.test(src)) return Number(src);
125
+ return fail("unsupported value");
126
+ }
127
+ function splitTopLevel(inner) {
128
+ const items = [];
129
+ let quoted = false;
130
+ let start = 0;
131
+ for (let i = 0; i < inner.length; i++) {
132
+ const c = inner[i];
133
+ if (c === "\\" && quoted) i++;
134
+ else if (c === '"') quoted = !quoted;
135
+ else if (c === "," && !quoted) {
136
+ items.push(inner.slice(start, i));
137
+ start = i + 1;
138
+ }
139
+ }
140
+ const last = inner.slice(start);
141
+ if (last.trim() !== "") items.push(last);
142
+ return items;
143
+ }
144
+
145
+ // src/palette/brand.ts
146
+ var POLARITIES = ["light", "dark"];
147
+ var FONT_ROLES = ["sans", "display", "mono"];
148
+ var FONT_KEYS = { sans: "sans", text: "sans", display: "display", mono: "mono" };
149
+ var PaletteError = class extends Error {
150
+ constructor(problems) {
151
+ super(`invalid brand palette:
152
+ - ${problems.join("\n - ")}`);
153
+ this.problems = problems;
154
+ this.name = "PaletteError";
155
+ }
156
+ problems;
157
+ };
158
+ var COLOUR = /^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|var\(--[a-z0-9-]+\))$/;
159
+ var SLUG = /^[a-z0-9][a-z0-9-]*$/;
160
+ var ASPECT = /^\d+(\.\d+)?\s*\/\s*\d+(\.\d+)?$/;
161
+ var MARK_URL = /^[^"'`\\\s()<>]+$/;
162
+ var FAMILY = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/;
163
+ function brandFromToml(text) {
164
+ const doc = parseToml(text);
165
+ const problems = [];
166
+ const colors = table(doc["colors"]);
167
+ if (!colors) problems.push("no [colors] table");
168
+ const scope = (name) => {
169
+ const t = colors ? table(colors[name]) : void 0;
170
+ if (colors && !t) problems.push(`no [colors.${name}] table`);
171
+ const out = {};
172
+ for (const [key, value] of Object.entries(t ?? {})) {
173
+ if (typeof value === "string") out[key] = value;
174
+ else problems.push(`colors.${name}.${key} is not a string`);
175
+ }
176
+ return out;
177
+ };
178
+ for (const key of Object.keys(colors ?? {})) {
179
+ if (!POLARITIES.includes(key)) problems.push(`[colors.${key}] is not a polarity (light, dark)`);
180
+ }
181
+ const config = { colors: { light: scope("light"), dark: scope("dark") } };
182
+ const mark = optionalString(doc["mark"], "mark", problems);
183
+ const markAspect = optionalString(doc["mark-aspect"], "mark-aspect", problems);
184
+ if (mark !== void 0) config.mark = mark;
185
+ if (markAspect !== void 0) config.markAspect = markAspect;
186
+ const fonts = table(doc["fonts"]);
187
+ for (const [key, role] of Object.entries(FONT_KEYS)) {
188
+ const family = optionalString(fonts?.[key], `fonts.${key}`, problems);
189
+ if (family !== void 0) config.fonts = { ...config.fonts, [role]: family };
190
+ }
191
+ if (problems.length > 0) throw new PaletteError(problems);
192
+ return config;
193
+ }
194
+ function table(value) {
195
+ return typeof value === "object" && !Array.isArray(value) ? value : void 0;
196
+ }
197
+ function optionalString(value, key, problems) {
198
+ if (value === void 0 || typeof value === "string") return value;
199
+ problems.push(`${key} is not a string`);
200
+ return void 0;
201
+ }
202
+ function validateBrand(config, contract) {
203
+ const problems = [];
204
+ const known = /* @__PURE__ */ new Set([...contract.required, ...contract.derived]);
205
+ for (const polarity of POLARITIES) {
206
+ const values = config.colors[polarity];
207
+ const missing = contract.required.filter((t) => !(t in values));
208
+ if (missing.length > 0) problems.push(`${polarity} is missing ${missing.join(", ")}`);
209
+ for (const [key, value] of Object.entries(values)) {
210
+ if (!known.has(key)) problems.push(`${polarity}.${key} is not a token of the contract`);
211
+ else if (!COLOUR.test(value)) problems.push(`${polarity}.${key} = ${JSON.stringify(value)} is not #hex or var(--token)`);
212
+ else {
213
+ for (const ref of references(value)) {
214
+ if (!known.has(ref)) problems.push(`${polarity}.${key} refers to --${ref}, which is not a token of the contract`);
215
+ }
216
+ }
217
+ }
218
+ const cycle = findCycle(values, contract, known);
219
+ if (cycle) problems.push(`${polarity} has a reference cycle: ${cycle.map((t) => `--${t}`).join(" \u2192 ")}`);
220
+ }
221
+ if (config.mark !== void 0 && !MARK_URL.test(config.mark)) problems.push(`mark ${JSON.stringify(config.mark)} is not a plain URL`);
222
+ if (config.markAspect !== void 0 && !ASPECT.test(config.markAspect)) {
223
+ problems.push(`mark-aspect ${JSON.stringify(config.markAspect)} is not "width / height"`);
224
+ }
225
+ for (const [role, family] of Object.entries(config.fonts ?? {})) {
226
+ if (!FAMILY.test(family)) problems.push(`fonts.${role} ${JSON.stringify(family)} is not a plain family name`);
227
+ }
228
+ return problems;
229
+ }
230
+ function findCycle(values, contract, known) {
231
+ const edges = (name) => {
232
+ const value = values[name];
233
+ if (value !== void 0) return references(value).filter((r) => known.has(r));
234
+ return contract.derivedFrom[name] ?? [];
235
+ };
236
+ const done = /* @__PURE__ */ new Set();
237
+ const path = [];
238
+ const visit = (name) => {
239
+ const at = path.indexOf(name);
240
+ if (at >= 0) return [...path.slice(at), name];
241
+ if (done.has(name)) return void 0;
242
+ path.push(name);
243
+ for (const next of edges(name)) {
244
+ const cycle = visit(next);
245
+ if (cycle) return cycle;
246
+ }
247
+ path.pop();
248
+ done.add(name);
249
+ return void 0;
250
+ };
251
+ for (const name of known) {
252
+ const cycle = visit(name);
253
+ if (cycle) return cycle;
254
+ }
255
+ return void 0;
256
+ }
257
+ function renderPalette(brand, config, contract, source = "a brand file") {
258
+ const problems = SLUG.test(brand) ? [] : [`brand ${JSON.stringify(brand)} is not a lowercase slug`];
259
+ problems.push(...validateBrand(config, contract));
260
+ if (problems.length > 0) throw new PaletteError(problems);
261
+ const at = `[data-brand="${brand}"]`;
262
+ const order = [...contract.required, ...contract.derived];
263
+ const block = (selectors, polarity) => {
264
+ const values = config.colors[polarity];
265
+ const body = order.filter((t) => t in values).map((t) => ` --${t}: ${values[t]};
266
+ `);
267
+ return `${selectors.join(",\n")} {
268
+ --scheme: ${polarity};
269
+ ${body.join("")}}
270
+ `;
271
+ };
272
+ const extras = [];
273
+ if (config.mark !== void 0) extras.push(` --brand-mark: url("${config.mark}");
274
+ `);
275
+ if (config.markAspect !== void 0) extras.push(` --brand-aspect: ${config.markAspect};
276
+ `);
277
+ for (const role of FONT_ROLES) {
278
+ const family = config.fonts?.[role];
279
+ if (family !== void 0) extras.push(` --brand-font-${role}: "${family}";
280
+ `);
281
+ }
282
+ return [
283
+ // `source` is a file name, free text as far as CSS is concerned: it must not close the comment.
284
+ `/* GENERATED by evinvest-palette from ${source.replaceAll("*/", "*\\/")} \u2014 do not edit. */
285
+ `,
286
+ block([at, `${at}.light`, `${at} .light`], "light"),
287
+ block([`${at}.dark`, `${at} .dark`], "dark"),
288
+ ...extras.length > 0 ? [`${at} {
289
+ ${extras.join("")}}
290
+ `] : []
291
+ ].join("\n");
292
+ }
293
+
294
+ export {
295
+ DERIVED_SCOPE,
296
+ readRules,
297
+ readContract,
298
+ parseToml,
299
+ POLARITIES,
300
+ FONT_ROLES,
301
+ PaletteError,
302
+ brandFromToml,
303
+ validateBrand,
304
+ renderPalette
305
+ };
306
+ //# sourceMappingURL=chunk-3IPB4QQJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/palette/contract.ts","../src/palette/toml.ts","../src/palette/brand.ts"],"sourcesContent":["/**\n * The colour contract, read out of the kit's own token sheet rather than\n * restated here: `@theme inline` maps every name the class tables can paint\n * (`--color-X: var(--X)`), and the derived rule lists the ones the contract\n * computes from the rest. A palette owes the difference.\n */\nexport interface Contract {\n /** Names every palette scope must declare. */\n required: readonly string[];\n /** Names the contract derives from the palette; a palette may override them. */\n derived: readonly string[];\n /** The tokens each derived default reads, so a palette's references can be checked for cycles through them. */\n derivedFrom: Readonly<Record<string, readonly string[]>>;\n}\n\n/** The `--name`s a value reads through `var()`. */\nexport function references(value: string): string[] {\n return [...value.matchAll(/var\\(--([A-Za-z0-9-]+)/g)].flatMap((m) => (m[1] ? [m[1]] : []));\n}\n\n/** The rule the derived tokens live on — see `theme.css`, the contract half of `tokens.css`. */\nexport const DERIVED_SCOPE = \":where(:root, .dark, .light, [data-brand])\";\n\nexport interface CssRule {\n selector: string;\n /** Custom properties declared directly in the rule, in source order. */\n declarations: Map<string, string>;\n}\n\n/**\n * Top-level and `@media`-nested rules of a flat sheet, comments stripped. Only\n * as much CSS as a token sheet is made of: no strings containing braces.\n */\nexport function readRules(css: string): CssRule[] {\n const rules: CssRule[] = [];\n const src = css.replace(/\\/\\*[\\s\\S]*?\\*\\//g, \"\");\n const stack: string[] = [];\n let start = 0;\n for (let i = 0; i < src.length; i++) {\n const c = src[i];\n if (c === \"{\") {\n stack.push(src.slice(start, i).trim());\n start = i + 1;\n } else if (c === \"}\") {\n const selector = stack.pop();\n if (selector === undefined) throw new Error(\"unbalanced `}` in token sheet\");\n const body = src.slice(start, i);\n if (!body.includes(\"{\")) rules.push({ selector: normalise(selector), declarations: declarations(body) });\n start = i + 1;\n } else if (c === \";\" && stack.length === 0) {\n start = i + 1;\n }\n }\n if (stack.length > 0) throw new Error(\"unbalanced `{` in token sheet\");\n return rules;\n}\n\nfunction declarations(body: string): Map<string, string> {\n const out = new Map<string, string>();\n for (const decl of body.split(\";\")) {\n const match = /^\\s*--([A-Za-z0-9-]+)\\s*:\\s*([\\s\\S]+?)\\s*$/.exec(decl);\n if (match?.[1] && match[2]) out.set(match[1], match[2]);\n }\n return out;\n}\n\nexport function normalise(selector: string): string {\n return selector.replace(/\\s+/g, \" \").replace(/\\s*,\\s*/g, \", \").trim();\n}\n\nexport function readContract(css: string): Contract {\n const rules = readRules(css);\n const theme = rules.find((r) => r.selector === \"@theme inline\");\n if (!theme) throw new Error(\"token sheet has no `@theme inline` block — not the kit's contract\");\n const painted: string[] = [];\n for (const [name, value] of theme.declarations) {\n const match = /^color-(.+)$/.exec(name);\n if (match?.[1] && value === `var(--${match[1]})`) painted.push(match[1]);\n }\n const derivedRule = rules.find((r) => r.selector === DERIVED_SCOPE);\n if (!derivedRule) throw new Error(`token sheet has no \\`${DERIVED_SCOPE}\\` rule — not the kit's contract`);\n const derived = [...derivedRule.declarations.keys()];\n const stray = derived.filter((d) => !painted.includes(d));\n if (stray.length > 0) throw new Error(`derived tokens with no @theme mapping: ${stray.join(\", \")}`);\n const derivedFrom = Object.fromEntries([...derivedRule.declarations].map(([name, value]) => [name, references(value)]));\n return { required: painted.filter((p) => !derived.includes(p)), derived, derivedFrom };\n}\n","/**\n * The subset of TOML a brand file is written in: `[table.headers]`, bare keys,\n * and single-line values that are a basic string, a number, a boolean, or a\n * flat array of those. Anything else is an error naming the line, rather than a\n * silent misread — the kit stays dependency-free, and a brand file has no use\n * for the rest of the language.\n */\n\nexport type TomlValue = string | number | boolean | TomlValue[];\nexport interface TomlTable {\n [key: string]: TomlValue | TomlTable;\n}\n\nconst BARE_KEY = /^[A-Za-z0-9_-]+$/;\n\nexport function parseToml(text: string): TomlTable {\n const root: TomlTable = {};\n let table = root;\n const headers = new Set<string>();\n text.split(/\\r?\\n/).forEach((raw, index) => {\n const line = stripComment(raw).trim();\n if (line === \"\") return;\n const fail = (why: string): never => {\n throw new Error(`TOML line ${index + 1}: ${why}: ${raw.trim()}`);\n };\n\n if (line.startsWith(\"[\")) {\n if (!line.endsWith(\"]\") || line.startsWith(\"[[\")) fail(\"only plain [table] headers are supported\");\n const path = line.slice(1, -1).trim().split(\".\").map((part) => part.trim());\n // Re-opening a table would merge two halves of it silently.\n if (headers.has(path.join(\".\"))) fail(\"duplicate table header\");\n headers.add(path.join(\".\"));\n table = descend(root, path, fail);\n return;\n }\n\n const eq = line.indexOf(\"=\");\n if (eq < 0) fail(\"expected `key = value`\");\n const key = line.slice(0, eq).trim();\n if (!BARE_KEY.test(key)) fail(\"only bare keys are supported\");\n if (key in table) fail(`duplicate key \\`${key}\\``);\n table[key] = parseValue(line.slice(eq + 1).trim(), fail);\n });\n return root;\n}\n\nfunction descend(root: TomlTable, path: string[], fail: (why: string) => never): TomlTable {\n let table = root;\n for (const part of path) {\n const key = part.trim();\n if (!BARE_KEY.test(key)) fail(\"only bare keys are supported in a header\");\n const next = table[key] ?? {};\n if (typeof next !== \"object\" || Array.isArray(next)) fail(`\\`${key}\\` is already a value`);\n table[key] = next;\n table = next as TomlTable;\n }\n return table;\n}\n\n// A `#` inside a string is not a comment; the brand files carry `\"#rrggbb\"`.\nfunction stripComment(line: string): string {\n let quoted = false;\n for (let i = 0; i < line.length; i++) {\n const c = line[i];\n if (c === \"\\\\\" && quoted) i++;\n else if (c === '\"') quoted = !quoted;\n else if (c === \"#\" && !quoted) return line.slice(0, i);\n }\n return line;\n}\n\nfunction parseValue(src: string, fail: (why: string) => never): TomlValue {\n if (src.startsWith('\"')) {\n if (src.length < 2 || !src.endsWith('\"')) fail(\"unterminated string\");\n const body = src.slice(1, -1);\n if (/(^|[^\\\\])(\\\\\\\\)*\"/.test(body)) fail(\"one string per value\");\n // Only the escapes decoded below; `\\u…` and friends would otherwise pass through as literal text.\n if (/\\\\(?![\"\\\\nt])/.test(body.replace(/\\\\\\\\/g, \"\"))) fail(\"unsupported escape\");\n return body.replace(/\\\\([\"\\\\nt])/g, (_, c: string) => ({ n: \"\\n\", t: \"\\t\" })[c] ?? c);\n }\n if (src.startsWith(\"[\")) {\n if (!src.endsWith(\"]\")) fail(\"arrays must close on the same line\");\n const inner = src.slice(1, -1).trim();\n if (inner === \"\") return [];\n return splitTopLevel(inner).map((item) => parseValue(item.trim(), fail));\n }\n if (src === \"true\" || src === \"false\") return src === \"true\";\n if (/^[+-]?\\d+(\\.\\d+)?$/.test(src)) return Number(src);\n return fail(\"unsupported value\");\n}\n\nfunction splitTopLevel(inner: string): string[] {\n const items: string[] = [];\n let quoted = false;\n let start = 0;\n for (let i = 0; i < inner.length; i++) {\n const c = inner[i];\n if (c === \"\\\\\" && quoted) i++;\n else if (c === '\"') quoted = !quoted;\n else if (c === \",\" && !quoted) {\n items.push(inner.slice(start, i));\n start = i + 1;\n }\n }\n const last = inner.slice(start);\n if (last.trim() !== \"\") items.push(last);\n return items;\n}\n","import { references, type Contract } from \"./contract\";\nimport { parseToml, type TomlTable, type TomlValue } from \"./toml\";\n\nexport type PolarityName = \"light\" | \"dark\";\nexport const POLARITIES: readonly PolarityName[] = [\"light\", \"dark\"];\n\n/** A brand's palette: one value per contract name, for each polarity. */\nexport interface BrandConfig {\n colors: Record<PolarityName, Record<string, string>>;\n /** `--brand-mark`: the URL `<Logo>` masks. */\n mark?: string;\n /** `--brand-aspect`: the mark's width / height, e.g. `\"387 / 335\"`. */\n markAspect?: string;\n /** `--brand-font-<role>`: a family name per role; unset roles keep the kit's. */\n fonts?: Partial<Record<FontRole, string>>;\n}\n\nexport type FontRole = \"sans\" | \"display\" | \"mono\";\nexport const FONT_ROLES: readonly FontRole[] = [\"sans\", \"display\", \"mono\"];\n// aquafix's file calls the body family `text`.\nconst FONT_KEYS: Record<string, FontRole> = { sans: \"sans\", text: \"sans\", display: \"display\", mono: \"mono\" };\n\n/** Every problem with a brand file at once, so one run fixes them all. */\nexport class PaletteError extends Error {\n constructor(readonly problems: readonly string[]) {\n super(`invalid brand palette:\\n - ${problems.join(\"\\n - \")}`);\n this.name = \"PaletteError\";\n }\n}\n\n// Written into CSS verbatim, so a value is held to a shape that cannot close\n// the declaration: a hex colour, or a reference to another token.\nconst COLOUR = /^(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{8}|var\\(--[a-z0-9-]+\\))$/;\nconst SLUG = /^[a-z0-9][a-z0-9-]*$/;\nconst ASPECT = /^\\d+(\\.\\d+)?\\s*\\/\\s*\\d+(\\.\\d+)?$/;\n// Written inside `url(\"…\")`: nothing that closes the string or the function,\n// and no markup characters for a sheet that ends up inlined in HTML.\nconst MARK_URL = /^[^\"'`\\\\\\s()<>]+$/;\nconst FAMILY = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/;\n\n/**\n * Reads the aquafix `brand.toml` shape: `[colors.light]` and `[colors.dark]`\n * keyed by contract name, optional top-level `mark` / `mark-aspect`, and the\n * family names in `[fonts]` (`sans` or `text`, `display`, `mono`). The rest of\n * `[fonts]` and every other table (`[print]`, …) belong to other tools and are\n * ignored — loading the font files is the site's job, not the palette's.\n */\nexport function brandFromToml(text: string): BrandConfig {\n const doc = parseToml(text);\n const problems: string[] = [];\n const colors = table(doc[\"colors\"]);\n if (!colors) problems.push(\"no [colors] table\");\n const scope = (name: PolarityName): Record<string, string> => {\n const t = colors ? table(colors[name]) : undefined;\n if (colors && !t) problems.push(`no [colors.${name}] table`);\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(t ?? {})) {\n if (typeof value === \"string\") out[key] = value;\n else problems.push(`colors.${name}.${key} is not a string`);\n }\n return out;\n };\n for (const key of Object.keys(colors ?? {})) {\n if (!(POLARITIES as readonly string[]).includes(key)) problems.push(`[colors.${key}] is not a polarity (light, dark)`);\n }\n const config: BrandConfig = { colors: { light: scope(\"light\"), dark: scope(\"dark\") } };\n const mark = optionalString(doc[\"mark\"], \"mark\", problems);\n const markAspect = optionalString(doc[\"mark-aspect\"], \"mark-aspect\", problems);\n if (mark !== undefined) config.mark = mark;\n if (markAspect !== undefined) config.markAspect = markAspect;\n const fonts = table(doc[\"fonts\"]);\n for (const [key, role] of Object.entries(FONT_KEYS)) {\n const family = optionalString(fonts?.[key], `fonts.${key}`, problems);\n if (family !== undefined) config.fonts = { ...config.fonts, [role]: family };\n }\n if (problems.length > 0) throw new PaletteError(problems);\n return config;\n}\n\nfunction table(value: TomlValue | TomlTable | undefined): TomlTable | undefined {\n return typeof value === \"object\" && !Array.isArray(value) ? value : undefined;\n}\n\nfunction optionalString(value: TomlValue | TomlTable | undefined, key: string, problems: string[]) {\n if (value === undefined || typeof value === \"string\") return value;\n problems.push(`${key} is not a string`);\n return undefined;\n}\n\n/** The problems with `config` against `contract`; empty when it is complete. */\nexport function validateBrand(config: BrandConfig, contract: Contract): string[] {\n const problems: string[] = [];\n const known = new Set([...contract.required, ...contract.derived]);\n for (const polarity of POLARITIES) {\n const values = config.colors[polarity];\n const missing = contract.required.filter((t) => !(t in values));\n if (missing.length > 0) problems.push(`${polarity} is missing ${missing.join(\", \")}`);\n for (const [key, value] of Object.entries(values)) {\n if (!known.has(key)) problems.push(`${polarity}.${key} is not a token of the contract`);\n else if (!COLOUR.test(value)) problems.push(`${polarity}.${key} = ${JSON.stringify(value)} is not #hex or var(--token)`);\n else {\n for (const ref of references(value)) {\n if (!known.has(ref)) problems.push(`${polarity}.${key} refers to --${ref}, which is not a token of the contract`);\n }\n }\n }\n const cycle = findCycle(values, contract, known);\n if (cycle) problems.push(`${polarity} has a reference cycle: ${cycle.map((t) => `--${t}`).join(\" → \")}`);\n }\n if (config.mark !== undefined && !MARK_URL.test(config.mark)) problems.push(`mark ${JSON.stringify(config.mark)} is not a plain URL`);\n if (config.markAspect !== undefined && !ASPECT.test(config.markAspect)) {\n problems.push(`mark-aspect ${JSON.stringify(config.markAspect)} is not \"width / height\"`);\n }\n for (const [role, family] of Object.entries(config.fonts ?? {})) {\n if (!FAMILY.test(family)) problems.push(`fonts.${role} ${JSON.stringify(family)} is not a plain family name`);\n }\n return problems;\n}\n\n/**\n * A `var()` cycle makes every token on it invalid at computed-value time — the\n * same silent hole as a missing token. Edges are the palette's own references,\n * and for a derived token it leaves to the contract, the contract's formula\n * (`border` reads `ink`), so `ink = \"var(--border)\"` is caught too.\n */\nfunction findCycle(values: Record<string, string>, contract: Contract, known: Set<string>): string[] | undefined {\n const edges = (name: string): readonly string[] => {\n const value = values[name];\n if (value !== undefined) return references(value).filter((r) => known.has(r));\n return contract.derivedFrom[name] ?? [];\n };\n const done = new Set<string>();\n const path: string[] = [];\n const visit = (name: string): string[] | undefined => {\n const at = path.indexOf(name);\n if (at >= 0) return [...path.slice(at), name];\n if (done.has(name)) return undefined;\n path.push(name);\n for (const next of edges(name)) {\n const cycle = visit(next);\n if (cycle) return cycle;\n }\n path.pop();\n done.add(name);\n return undefined;\n };\n for (const name of known) {\n const cycle = visit(name);\n if (cycle) return cycle;\n }\n return undefined;\n}\n\n/**\n * A flat sheet scoping `config` under `[data-brand=\"<brand>\"]`: light is the\n * brand's default and `.light` / `.dark` beneath it pick a polarity. Selectors\n * outrank the zero-specificity EV palette, so import order does not matter.\n * Throws a {@link PaletteError} when a scope is incomplete — Tailwind answers an\n * undefined token with no rule at all, so a hole would be silent on the page.\n */\nexport function renderPalette(brand: string, config: BrandConfig, contract: Contract, source = \"a brand file\"): string {\n const problems = SLUG.test(brand) ? [] : [`brand ${JSON.stringify(brand)} is not a lowercase slug`];\n problems.push(...validateBrand(config, contract));\n if (problems.length > 0) throw new PaletteError(problems);\n\n const at = `[data-brand=\"${brand}\"]`;\n const order = [...contract.required, ...contract.derived];\n const block = (selectors: string[], polarity: PolarityName) => {\n const values = config.colors[polarity];\n const body = order.filter((t) => t in values).map((t) => ` --${t}: ${values[t]};\\n`);\n return `${selectors.join(\",\\n\")} {\\n --scheme: ${polarity};\\n${body.join(\"\")}}\\n`;\n };\n const extras: string[] = [];\n if (config.mark !== undefined) extras.push(` --brand-mark: url(\"${config.mark}\");\\n`);\n if (config.markAspect !== undefined) extras.push(` --brand-aspect: ${config.markAspect};\\n`);\n for (const role of FONT_ROLES) {\n const family = config.fonts?.[role];\n if (family !== undefined) extras.push(` --brand-font-${role}: \"${family}\";\\n`);\n }\n\n return [\n // `source` is a file name, free text as far as CSS is concerned: it must not close the comment.\n `/* GENERATED by evinvest-palette from ${source.replaceAll(\"*/\", \"*\\\\/\")} — do not edit. */\\n`,\n block([at, `${at}.light`, `${at} .light`], \"light\"),\n block([`${at}.dark`, `${at} .dark`], \"dark\"),\n ...(extras.length > 0 ? [`${at} {\\n${extras.join(\"\")}}\\n`] : []),\n ].join(\"\\n\");\n}\n"],"mappings":"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","names":["table"]}