@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.
Files changed (204) hide show
  1. package/README.md +294 -90
  2. package/dist/{chunk-3IPB4QQJ.js → chunk-35SPIU4U.js} +10 -6
  3. package/dist/chunk-35SPIU4U.js.map +1 -0
  4. package/dist/components/account-list.js +128 -0
  5. package/dist/components/account-list.js.map +1 -0
  6. package/dist/components/account-menu-profile.js +114 -0
  7. package/dist/components/account-menu-profile.js.map +1 -0
  8. package/dist/components/account-menu.js +50 -23
  9. package/dist/components/account-menu.js.map +1 -1
  10. package/dist/components/account-settings-dialog.js +84 -0
  11. package/dist/components/account-settings-dialog.js.map +1 -0
  12. package/dist/components/alert-dialog.js +3 -2
  13. package/dist/components/alert-dialog.js.map +1 -1
  14. package/dist/components/animated-number-columns.js +105 -0
  15. package/dist/components/animated-number-columns.js.map +1 -0
  16. package/dist/components/animated-number.js +157 -0
  17. package/dist/components/animated-number.js.map +1 -0
  18. package/dist/components/app-shell.js +9 -0
  19. package/dist/components/app-shell.js.map +1 -1
  20. package/dist/components/aside.js +140 -0
  21. package/dist/components/aside.js.map +1 -0
  22. package/dist/components/badge.js +1 -1
  23. package/dist/components/badge.js.map +1 -1
  24. package/dist/components/button.js +3 -11
  25. package/dist/components/button.js.map +1 -1
  26. package/dist/components/chat-composer.js +141 -0
  27. package/dist/components/chat-composer.js.map +1 -0
  28. package/dist/components/chat-message.js +195 -0
  29. package/dist/components/chat-message.js.map +1 -0
  30. package/dist/components/chat-panel.js +137 -0
  31. package/dist/components/chat-panel.js.map +1 -0
  32. package/dist/components/chat-stream-text.js +69 -0
  33. package/dist/components/chat-stream-text.js.map +1 -0
  34. package/dist/components/chat-typing.js +26 -0
  35. package/dist/components/chat-typing.js.map +1 -0
  36. package/dist/components/command-palette.js +119 -0
  37. package/dist/components/command-palette.js.map +1 -0
  38. package/dist/components/command.js +3 -1
  39. package/dist/components/command.js.map +1 -1
  40. package/dist/components/confirm-dialog.js +123 -0
  41. package/dist/components/confirm-dialog.js.map +1 -0
  42. package/dist/components/context-menu.js +53 -5
  43. package/dist/components/context-menu.js.map +1 -1
  44. package/dist/components/dialog.js +3 -2
  45. package/dist/components/dialog.js.map +1 -1
  46. package/dist/components/drawer.js +3 -1
  47. package/dist/components/drawer.js.map +1 -1
  48. package/dist/components/dropdown-menu.js +64 -5
  49. package/dist/components/dropdown-menu.js.map +1 -1
  50. package/dist/components/form-row.js +51 -0
  51. package/dist/components/form-row.js.map +1 -0
  52. package/dist/components/header-controls.js +149 -0
  53. package/dist/components/header-controls.js.map +1 -0
  54. package/dist/components/icon-button.js +35 -0
  55. package/dist/components/icon-button.js.map +1 -0
  56. package/dist/components/key-value.js +68 -0
  57. package/dist/components/key-value.js.map +1 -0
  58. package/dist/components/menubar.js +39 -5
  59. package/dist/components/menubar.js.map +1 -1
  60. package/dist/components/native-select.js +3 -4
  61. package/dist/components/native-select.js.map +1 -1
  62. package/dist/components/nav-collapse.js +142 -0
  63. package/dist/components/nav-collapse.js.map +1 -0
  64. package/dist/components/nav-model.js.map +1 -1
  65. package/dist/components/navigation-menu.js +4 -5
  66. package/dist/components/navigation-menu.js.map +1 -1
  67. package/dist/components/page-frame.js +2 -5
  68. package/dist/components/page-frame.js.map +1 -1
  69. package/dist/components/popover-form.js +60 -0
  70. package/dist/components/popover-form.js.map +1 -0
  71. package/dist/components/resource-error.js +1 -1
  72. package/dist/components/resource-error.js.map +1 -1
  73. package/dist/components/section-nav.js +73 -20
  74. package/dist/components/section-nav.js.map +1 -1
  75. package/dist/components/select.js +16 -6
  76. package/dist/components/select.js.map +1 -1
  77. package/dist/components/settings.js +94 -0
  78. package/dist/components/settings.js.map +1 -0
  79. package/dist/components/sheet.js +3 -2
  80. package/dist/components/sheet.js.map +1 -1
  81. package/dist/components/shell-nav.js +80 -38
  82. package/dist/components/shell-nav.js.map +1 -1
  83. package/dist/components/smart-header.js +53 -0
  84. package/dist/components/smart-header.js.map +1 -0
  85. package/dist/components/status-item.js +42 -0
  86. package/dist/components/status-item.js.map +1 -0
  87. package/dist/components/system-banner.js +22 -12
  88. package/dist/components/system-banner.js.map +1 -1
  89. package/dist/components/top-bar.js +4 -3
  90. package/dist/components/top-bar.js.map +1 -1
  91. package/dist/components/ui-icons.js +77 -0
  92. package/dist/components/ui-icons.js.map +1 -0
  93. package/dist/components/use-count-tween.js +42 -0
  94. package/dist/components/use-count-tween.js.map +1 -0
  95. package/dist/components/workspace-tab.js +140 -0
  96. package/dist/components/workspace-tab.js.map +1 -0
  97. package/dist/generated/accent.js +2 -2
  98. package/dist/generated/accent.js.map +1 -1
  99. package/dist/generated/account-list.js +35 -0
  100. package/dist/generated/account-list.js.map +1 -0
  101. package/dist/generated/account-settings-dialog.js +27 -0
  102. package/dist/generated/account-settings-dialog.js.map +1 -0
  103. package/dist/generated/alert-dialog.js +1 -1
  104. package/dist/generated/alert-dialog.js.map +1 -1
  105. package/dist/generated/alert.js +2 -2
  106. package/dist/generated/alert.js.map +1 -1
  107. package/dist/generated/aside.js +36 -0
  108. package/dist/generated/aside.js.map +1 -0
  109. package/dist/generated/badge.js +7 -6
  110. package/dist/generated/badge.js.map +1 -1
  111. package/dist/generated/band.js +1 -1
  112. package/dist/generated/band.js.map +1 -1
  113. package/dist/generated/breadcrumb.js +2 -2
  114. package/dist/generated/breadcrumb.js.map +1 -1
  115. package/dist/generated/button-group.js +2 -2
  116. package/dist/generated/button-group.js.map +1 -1
  117. package/dist/generated/button.js +4 -4
  118. package/dist/generated/button.js.map +1 -1
  119. package/dist/generated/card.js +6 -6
  120. package/dist/generated/card.js.map +1 -1
  121. package/dist/generated/chat-composer.js +21 -0
  122. package/dist/generated/chat-composer.js.map +1 -0
  123. package/dist/generated/chat-message.js +86 -0
  124. package/dist/generated/chat-message.js.map +1 -0
  125. package/dist/generated/chat-typing.js +9 -0
  126. package/dist/generated/chat-typing.js.map +1 -0
  127. package/dist/generated/command-palette.js +17 -0
  128. package/dist/generated/command-palette.js.map +1 -0
  129. package/dist/generated/command.js +3 -3
  130. package/dist/generated/command.js.map +1 -1
  131. package/dist/generated/confirm-dialog.js +28 -0
  132. package/dist/generated/confirm-dialog.js.map +1 -0
  133. package/dist/generated/context-menu.js +1 -1
  134. package/dist/generated/context-menu.js.map +1 -1
  135. package/dist/generated/date-time-picker.js +1 -1
  136. package/dist/generated/date-time-picker.js.map +1 -1
  137. package/dist/generated/dialog.js +1 -1
  138. package/dist/generated/dialog.js.map +1 -1
  139. package/dist/generated/drawer.js +1 -1
  140. package/dist/generated/drawer.js.map +1 -1
  141. package/dist/generated/dropdown-menu.js +1 -1
  142. package/dist/generated/dropdown-menu.js.map +1 -1
  143. package/dist/generated/form-row.js +17 -0
  144. package/dist/generated/form-row.js.map +1 -0
  145. package/dist/generated/header-controls.js +56 -0
  146. package/dist/generated/header-controls.js.map +1 -0
  147. package/dist/generated/icon-button.js +13 -0
  148. package/dist/generated/icon-button.js.map +1 -0
  149. package/dist/generated/key-value.js +15 -0
  150. package/dist/generated/key-value.js.map +1 -0
  151. package/dist/generated/menubar.js +1 -1
  152. package/dist/generated/menubar.js.map +1 -1
  153. package/dist/generated/navigation-menu.js +1 -1
  154. package/dist/generated/navigation-menu.js.map +1 -1
  155. package/dist/generated/popover-form.js +13 -0
  156. package/dist/generated/popover-form.js.map +1 -0
  157. package/dist/generated/section-label.js +10 -0
  158. package/dist/generated/section-label.js.map +1 -0
  159. package/dist/generated/select.js +15 -3
  160. package/dist/generated/select.js.map +1 -1
  161. package/dist/generated/settings.js +36 -0
  162. package/dist/generated/settings.js.map +1 -0
  163. package/dist/generated/sheet.js +1 -1
  164. package/dist/generated/sheet.js.map +1 -1
  165. package/dist/generated/slider.js +1 -1
  166. package/dist/generated/slider.js.map +1 -1
  167. package/dist/generated/smart-header.js +11 -0
  168. package/dist/generated/smart-header.js.map +1 -0
  169. package/dist/generated/sonner.js +4 -4
  170. package/dist/generated/sonner.js.map +1 -1
  171. package/dist/generated/status-item.js +17 -0
  172. package/dist/generated/status-item.js.map +1 -0
  173. package/dist/generated/system-banner.js +33 -0
  174. package/dist/generated/system-banner.js.map +1 -0
  175. package/dist/generated/top-bar.js +9 -0
  176. package/dist/generated/top-bar.js.map +1 -0
  177. package/dist/generated/workspace-tab.js +17 -0
  178. package/dist/generated/workspace-tab.js.map +1 -0
  179. package/dist/index.d.ts +852 -156
  180. package/dist/index.js +21 -1
  181. package/dist/lib/cn.js +8 -1
  182. package/dist/lib/cn.js.map +1 -1
  183. package/dist/lib/number-parts.js +90 -0
  184. package/dist/lib/number-parts.js.map +1 -0
  185. package/dist/palette-cli.js +1 -1
  186. package/dist/palette.d.ts +6 -4
  187. package/dist/palette.js +3 -1
  188. package/dist/primitives/dismissable-layer.js +76 -4
  189. package/dist/primitives/dismissable-layer.js.map +1 -1
  190. package/dist/primitives/focus-scope.js +20 -5
  191. package/dist/primitives/focus-scope.js.map +1 -1
  192. package/dist/primitives/use-floating.js +16 -1
  193. package/dist/primitives/use-floating.js.map +1 -1
  194. package/dist/primitives/use-reduced-motion.js +20 -0
  195. package/dist/primitives/use-reduced-motion.js.map +1 -0
  196. package/package.json +1 -1
  197. package/styles/ev.css +281 -49
  198. package/styles/theme.css +335 -39
  199. package/styles/tokens.css +643 -103
  200. package/dist/chunk-3IPB4QQJ.js.map +0 -1
  201. package/dist/components/terminal.js +0 -206
  202. package/dist/components/terminal.js.map +0 -1
  203. package/dist/generated/terminal.js +0 -70
  204. package/dist/generated/terminal.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/primitives/use-floating.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nexport type Side = \"top\" | \"right\" | \"bottom\" | \"left\";\nexport type Align = \"start\" | \"center\" | \"end\";\n\nexport interface FloatingResult {\n floatingRef: React.RefObject<HTMLElement | null>;\n style: React.CSSProperties;\n side: Side;\n align: Align;\n}\n\n// The element sits at its containing block's origin and is moved with the\n// `translate` property, never with `top`/`left`: an absolute box's\n// shrink-to-fit width is \"containing block minus `left`\", so a `w-fit` surface\n// pushed past the viewport's right edge (horizontal scroll, a nested scroller)\n// would collapse to one word per line. `translate` composes with `transform`,\n// so the enter animations (which animate `transform`) are unaffected. The\n// offset never goes through React: `place()` / `sync()` write it straight to\n// the element, so this style prop stays one frozen object.\nconst ABSOLUTE: React.CSSProperties = Object.freeze({ position: \"absolute\", top: 0, left: 0 });\n\nconst POSITIONED = new Set([\"relative\", \"absolute\", \"fixed\", \"sticky\"]);\n\n/**\n * Converts a viewport-relative point into the coordinate space of the\n * floating element's containing block, i.e. the numbers `position: absolute`\n * expects. floating-ui's `strategy: \"absolute\"` does the same walk.\n */\nfunction toContainingBlock(\n floating: HTMLElement,\n x: number,\n y: number,\n): { x: number; y: number; root: boolean } {\n const parent = floating.offsetParent;\n const doc = floating.ownerDocument;\n const root = parent === doc.body || parent === doc.documentElement || parent === null;\n // `offsetParent` reports `body` even when nothing is positioned; only a\n // positioned body/html really forms a containing block.\n const positioned =\n parent instanceof HTMLElement &&\n (!root || POSITIONED.has(doc.defaultView?.getComputedStyle(parent).position ?? \"\"));\n if (!positioned) {\n const win = doc.defaultView;\n return { x: x + (win?.scrollX ?? 0), y: y + (win?.scrollY ?? 0), root: true };\n }\n const p = parent.getBoundingClientRect();\n return {\n x: x - p.left - parent.clientLeft + parent.scrollLeft,\n y: y - p.top - parent.clientTop + parent.scrollTop,\n root: false,\n };\n}\n\ninterface Size2D {\n width: number;\n height: number;\n}\n\n// The layout box, not `getBoundingClientRect()`: the latter reports the box\n// mid-animation (`zoom-in-95` scales it to 0.95 on the first frame), which\n// placed a top-side popover 5 % of its height too low, over the anchor.\nfunction layoutSize(el: HTMLElement): Size2D {\n return { width: el.offsetWidth, height: el.offsetHeight };\n}\n\nfunction viewport(): { vw: number; vh: number } {\n // `clientWidth` excludes the scrollbar, which the floating element cannot\n // sit under anyway; jsdom reports 0 there, hence the fallback.\n const el = document.documentElement;\n return {\n vw: el.clientWidth || window.innerWidth,\n vh: el.clientHeight || window.innerHeight,\n };\n}\n\n/**\n * Positions a floating element against an anchor with `position: absolute` in\n * the coordinate space of its containing block (the `Portal` puts it in\n * `document.body`, so normally document coordinates) — the dep-light stand-in\n * for `@radix-ui/react-popper` / floating-ui. Supports a preferred\n * `side`/`align`, an `offset`, and a single collision flip onto the opposite\n * side when the preferred side overflows the viewport.\n *\n * Two update modes:\n * - `place()` — full pass (flip + clamp into the viewport) on open, on window\n * `resize`, and whenever the anchor or the floating element changes size.\n * - `sync()` — re-derives the coordinates from the anchor with the side and\n * the clamp shift already chosen; runs on every `scroll` (captured on\n * `window`, so nested scroll containers count too).\n *\n * Because the element is absolutely positioned in the document, a document\n * scroll moves it natively with the page — `sync()` then computes the same\n * numbers and is a no-op — so the overlay never lags behind the compositor.\n * Only a nested scroll container changes the coordinates, and there a frame\n * of lag is acceptable. The side is never revisited on scroll: no jumping.\n * A page that scrolls `body` instead of the document (`html { overflow:\n * hidden } body { overflow: auto }`) is such a nested scroller: correct, but\n * with the frame of lag the `fixed` strategy had.\n *\n * Rust's overlays use CSS-only placement (`data-side`) and do not measure; see\n * the README \"Limitations\".\n */\nexport function useFloating(opts: {\n anchorRef: React.RefObject<Element | null>;\n open: boolean;\n side?: Side;\n align?: Align;\n offset?: number;\n}): FloatingResult {\n const { anchorRef, open, side = \"bottom\", align = \"center\", offset = 4 } = opts;\n const floatingRef = React.useRef<HTMLElement | null>(null);\n const [placedSide, setPlacedSide] = React.useState<Side>(side);\n // The last full placement: the side that won the flip and how far the\n // viewport clamp pushed the element off its ideal spot. `sync()` reuses both\n // instead of measuring against the viewport again.\n const placement = React.useRef<{ side: Side; shiftX: number; shiftY: number }>({\n side,\n shiftX: 0,\n shiftY: 0,\n });\n\n React.useLayoutEffect(() => {\n if (!open) return;\n\n function ideal(a: DOMRect, f: Size2D, placed: Side): { top: number; left: number } {\n let top = 0;\n let left = 0;\n const vertical = placed === \"top\" || placed === \"bottom\";\n\n if (placed === \"bottom\") top = a.bottom + offset;\n else if (placed === \"top\") top = a.top - offset - f.height;\n else if (placed === \"right\") left = a.right + offset;\n else left = a.left - offset - f.width;\n\n if (vertical) {\n if (align === \"start\") left = a.left;\n else if (align === \"end\") left = a.right - f.width;\n else left = a.left + a.width / 2 - f.width / 2;\n } else {\n if (align === \"start\") top = a.top;\n else if (align === \"end\") top = a.bottom - f.height;\n else top = a.top + a.height / 2 - f.height / 2;\n }\n return { top, left };\n }\n\n // `contain` keeps the box inside the initial containing block: a box past\n // the document's edge grows the page (a scrollbar shows for as long as the\n // overlay is open). `place()` already clamps against the viewport;\n // `sync()` follows a nested scroller's anchor and only needs this bound.\n function write(floating: HTMLElement, f: Size2D, top: number, left: number, contain: boolean) {\n const p = toContainingBlock(floating, left, top);\n let { x, y } = p;\n if (contain && p.root) {\n const icbWidth = floating.ownerDocument.documentElement.clientWidth || window.innerWidth;\n x = Math.max(0, Math.min(x, icbWidth - f.width));\n y = Math.max(0, y);\n }\n floating.style.translate = `${Math.round(x)}px ${Math.round(y)}px`;\n }\n\n function place() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { vw, vh } = viewport();\n\n let placed: Side = side;\n if (side === \"bottom\" && a.bottom + offset + f.height > vh && a.top - offset - f.height > 0)\n placed = \"top\";\n else if (side === \"top\" && a.top - offset - f.height < 0 && a.bottom + offset + f.height < vh)\n placed = \"bottom\";\n else if (side === \"right\" && a.right + offset + f.width > vw && a.left - offset - f.width > 0)\n placed = \"left\";\n else if (side === \"left\" && a.left - offset - f.width < 0 && a.right + offset + f.width < vw)\n placed = \"right\";\n\n const { top, left } = ideal(a, f, placed);\n const clampedLeft = Math.max(offset, Math.min(left, vw - f.width - offset));\n const clampedTop = Math.max(offset, Math.min(top, vh - f.height - offset));\n\n placement.current = {\n side: placed,\n shiftX: clampedLeft - left,\n shiftY: clampedTop - top,\n };\n write(floating, f, clampedTop, clampedLeft, false);\n // Only a real change may re-render: this runs on every resize tick.\n setPlacedSide(prev => (prev === placed ? prev : placed));\n }\n\n function sync() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { side: placed, shiftX, shiftY } = placement.current;\n const { top, left } = ideal(a, f, placed);\n write(floating, f, top + shiftY, left + shiftX, true);\n }\n\n place();\n // No rAF: `scroll` already fires at most once per frame, and a synchronous\n // DOM write from the handler lands in that same frame — rAF would cost one.\n window.addEventListener(\"scroll\", sync, true);\n window.addEventListener(\"resize\", place);\n\n let observer: ResizeObserver | undefined;\n if (typeof ResizeObserver !== \"undefined\") {\n observer = new ResizeObserver(place);\n if (anchorRef.current) observer.observe(anchorRef.current);\n if (floatingRef.current) observer.observe(floatingRef.current);\n }\n\n return () => {\n window.removeEventListener(\"scroll\", sync, true);\n window.removeEventListener(\"resize\", place);\n observer?.disconnect();\n };\n }, [open, side, align, offset, anchorRef]);\n\n return { floatingRef, style: ABSOLUTE, side: placedSide, align };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/primitives/use-floating.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nexport type Side = \"top\" | \"right\" | \"bottom\" | \"left\";\nexport type Align = \"start\" | \"center\" | \"end\";\n\nexport interface FloatingResult {\n floatingRef: React.RefObject<HTMLElement | null>;\n style: React.CSSProperties;\n side: Side;\n align: Align;\n}\n\n// The element sits at its containing block's origin and is moved with the\n// `translate` property, never with `top`/`left`: an absolute box's\n// shrink-to-fit width is \"containing block minus `left`\", so a `w-fit` surface\n// pushed past the viewport's right edge (horizontal scroll, a nested scroller)\n// would collapse to one word per line. `translate` composes with `transform`,\n// so the enter animations (which animate `transform`) are unaffected. The\n// offset never goes through React: `place()` / `sync()` write it straight to\n// the element, so this style prop stays one frozen object.\nconst ABSOLUTE: React.CSSProperties = Object.freeze({ position: \"absolute\", top: 0, left: 0 });\n\nconst POSITIONED = new Set([\"relative\", \"absolute\", \"fixed\", \"sticky\"]);\n\n/**\n * Converts a viewport-relative point into the coordinate space of the\n * floating element's containing block, i.e. the numbers `position: absolute`\n * expects. floating-ui's `strategy: \"absolute\"` does the same walk.\n */\nfunction toContainingBlock(\n floating: HTMLElement,\n x: number,\n y: number,\n): { x: number; y: number; root: boolean } {\n const parent = floating.offsetParent;\n const doc = floating.ownerDocument;\n const root = parent === doc.body || parent === doc.documentElement || parent === null;\n // `offsetParent` reports `body` even when nothing is positioned; only a\n // positioned body/html really forms a containing block.\n const positioned =\n parent instanceof HTMLElement &&\n (!root || POSITIONED.has(doc.defaultView?.getComputedStyle(parent).position ?? \"\"));\n if (!positioned) {\n const win = doc.defaultView;\n return { x: x + (win?.scrollX ?? 0), y: y + (win?.scrollY ?? 0), root: true };\n }\n const p = parent.getBoundingClientRect();\n return {\n x: x - p.left - parent.clientLeft + parent.scrollLeft,\n y: y - p.top - parent.clientTop + parent.scrollTop,\n root: false,\n };\n}\n\ninterface Size2D {\n width: number;\n height: number;\n}\n\n// The layout box, not `getBoundingClientRect()`: the latter reports the box\n// mid-animation (`zoom-in-95` scales it to 0.95 on the first frame), which\n// placed a top-side popover 5 % of its height too low, over the anchor.\nfunction layoutSize(el: HTMLElement): Size2D {\n return { width: el.offsetWidth, height: el.offsetHeight };\n}\n\n// jsdom has no `getAnimations`; there nothing animates.\nfunction hasRunningAnimationAbove(el: Element): boolean {\n for (let node = el.parentElement; node; node = node.parentElement) {\n if (node.getAnimations?.().some((a) => a.playState === \"running\")) return true;\n }\n return false;\n}\n\nfunction viewport(): { vw: number; vh: number } {\n // `clientWidth` excludes the scrollbar, which the floating element cannot\n // sit under anyway; jsdom reports 0 there, hence the fallback.\n const el = document.documentElement;\n return {\n vw: el.clientWidth || window.innerWidth,\n vh: el.clientHeight || window.innerHeight,\n };\n}\n\n/**\n * Positions a floating element against an anchor with `position: absolute` in\n * the coordinate space of its containing block (the `Portal` puts it in\n * `document.body`, so normally document coordinates) — the dep-light stand-in\n * for `@radix-ui/react-popper` / floating-ui. Supports a preferred\n * `side`/`align`, an `offset`, and a single collision flip onto the opposite\n * side when the preferred side overflows the viewport.\n *\n * Two update modes:\n * - `place()` — full pass (flip + clamp into the viewport) on open, on window\n * `resize`, and whenever the anchor or the floating element changes size.\n * - `sync()` — re-derives the coordinates from the anchor with the side and\n * the clamp shift already chosen; runs on every `scroll` (captured on\n * `window`, so nested scroll containers count too).\n *\n * Because the element is absolutely positioned in the document, a document\n * scroll moves it natively with the page — `sync()` then computes the same\n * numbers and is a no-op — so the overlay never lags behind the compositor.\n * Only a nested scroll container changes the coordinates, and there a frame\n * of lag is acceptable. The side is never revisited on scroll: no jumping.\n * A page that scrolls `body` instead of the document (`html { overflow:\n * hidden } body { overflow: auto }`) is such a nested scroller: correct, but\n * with the frame of lag the `fixed` strategy had.\n *\n * Rust's overlays use CSS-only placement (`data-side`) and do not measure; see\n * the README \"Limitations\".\n */\nexport function useFloating(opts: {\n anchorRef: React.RefObject<Element | null>;\n open: boolean;\n side?: Side;\n align?: Align;\n offset?: number;\n}): FloatingResult {\n const { anchorRef, open, side = \"bottom\", align = \"center\", offset = 4 } = opts;\n const floatingRef = React.useRef<HTMLElement | null>(null);\n const [placedSide, setPlacedSide] = React.useState<Side>(side);\n // The last full placement: the side that won the flip and how far the\n // viewport clamp pushed the element off its ideal spot. `sync()` reuses both\n // instead of measuring against the viewport again.\n const placement = React.useRef<{ side: Side; shiftX: number; shiftY: number }>({\n side,\n shiftX: 0,\n shiftY: 0,\n });\n\n React.useLayoutEffect(() => {\n if (!open) return;\n\n function ideal(a: DOMRect, f: Size2D, placed: Side): { top: number; left: number } {\n let top = 0;\n let left = 0;\n const vertical = placed === \"top\" || placed === \"bottom\";\n\n if (placed === \"bottom\") top = a.bottom + offset;\n else if (placed === \"top\") top = a.top - offset - f.height;\n else if (placed === \"right\") left = a.right + offset;\n else left = a.left - offset - f.width;\n\n if (vertical) {\n if (align === \"start\") left = a.left;\n else if (align === \"end\") left = a.right - f.width;\n else left = a.left + a.width / 2 - f.width / 2;\n } else {\n if (align === \"start\") top = a.top;\n else if (align === \"end\") top = a.bottom - f.height;\n else top = a.top + a.height / 2 - f.height / 2;\n }\n return { top, left };\n }\n\n // `contain` keeps the box inside the initial containing block: a box past\n // the document's edge grows the page (a scrollbar shows for as long as the\n // overlay is open). `place()` already clamps against the viewport;\n // `sync()` follows a nested scroller's anchor and only needs this bound.\n function write(floating: HTMLElement, f: Size2D, top: number, left: number, contain: boolean) {\n const p = toContainingBlock(floating, left, top);\n let { x, y } = p;\n if (contain && p.root) {\n const icbWidth = floating.ownerDocument.documentElement.clientWidth || window.innerWidth;\n x = Math.max(0, Math.min(x, icbWidth - f.width));\n y = Math.max(0, y);\n }\n floating.style.translate = `${Math.round(x)}px ${Math.round(y)}px`;\n }\n\n function place() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { vw, vh } = viewport();\n\n let placed: Side = side;\n if (side === \"bottom\" && a.bottom + offset + f.height > vh && a.top - offset - f.height > 0)\n placed = \"top\";\n else if (side === \"top\" && a.top - offset - f.height < 0 && a.bottom + offset + f.height < vh)\n placed = \"bottom\";\n else if (side === \"right\" && a.right + offset + f.width > vw && a.left - offset - f.width > 0)\n placed = \"left\";\n else if (side === \"left\" && a.left - offset - f.width < 0 && a.right + offset + f.width < vw)\n placed = \"right\";\n\n const { top, left } = ideal(a, f, placed);\n const clampedLeft = Math.max(offset, Math.min(left, vw - f.width - offset));\n const clampedTop = Math.max(offset, Math.min(top, vh - f.height - offset));\n\n placement.current = {\n side: placed,\n shiftX: clampedLeft - left,\n shiftY: clampedTop - top,\n };\n write(floating, f, clampedTop, clampedLeft, false);\n // Only a real change may re-render: this runs on every resize tick.\n setPlacedSide(prev => (prev === placed ? prev : placed));\n }\n\n function sync() {\n const anchor = anchorRef.current;\n const floating = floatingRef.current;\n if (!anchor || !floating) return;\n\n const a = anchor.getBoundingClientRect();\n const f = layoutSize(floating);\n const { side: placed, shiftX, shiftY } = placement.current;\n const { top, left } = ideal(a, f, placed);\n write(floating, f, top + shiftY, left + shiftX, true);\n }\n\n // An anchor inside an entering dialog is measured scaled and shifted by the\n // dialog's animation (`zoom-in-95`), which places the floating element off\n // its anchor for good: no resize or scroll follows the animation's end. So\n // keep re-placing, a frame at a time, until no ancestor animates.\n let frame = 0;\n function follow() {\n frame = 0;\n place();\n if (anchorRef.current && hasRunningAnimationAbove(anchorRef.current)) {\n frame = requestAnimationFrame(follow);\n }\n }\n follow();\n // No rAF: `scroll` already fires at most once per frame, and a synchronous\n // DOM write from the handler lands in that same frame — rAF would cost one.\n window.addEventListener(\"scroll\", sync, true);\n window.addEventListener(\"resize\", place);\n\n let observer: ResizeObserver | undefined;\n if (typeof ResizeObserver !== \"undefined\") {\n observer = new ResizeObserver(place);\n if (anchorRef.current) observer.observe(anchorRef.current);\n if (floatingRef.current) observer.observe(floatingRef.current);\n }\n\n return () => {\n if (frame) cancelAnimationFrame(frame);\n window.removeEventListener(\"scroll\", sync, true);\n window.removeEventListener(\"resize\", place);\n observer?.disconnect();\n };\n }, [open, side, align, offset, anchorRef]);\n\n return { floatingRef, style: ABSOLUTE, side: placedSide, align };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ import * as React from "react";
3
+ const QUERY = "(prefers-reduced-motion: reduce)";
4
+ function subscribe(notify) {
5
+ if (typeof window.matchMedia !== "function") return () => {
6
+ };
7
+ const mq = window.matchMedia(QUERY);
8
+ mq.addEventListener("change", notify);
9
+ return () => mq.removeEventListener("change", notify);
10
+ }
11
+ function snapshot() {
12
+ return typeof window.matchMedia === "function" && window.matchMedia(QUERY).matches;
13
+ }
14
+ function useReducedMotion() {
15
+ return React.useSyncExternalStore(subscribe, snapshot, () => false);
16
+ }
17
+ export {
18
+ useReducedMotion
19
+ };
20
+ //# sourceMappingURL=use-reduced-motion.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/primitives/use-reduced-motion.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\nconst QUERY = \"(prefers-reduced-motion: reduce)\";\n\nfunction subscribe(notify: () => void): () => void {\n if (typeof window.matchMedia !== \"function\") return () => {};\n const mq = window.matchMedia(QUERY);\n mq.addEventListener(\"change\", notify);\n return () => mq.removeEventListener(\"change\", notify);\n}\n\nfunction snapshot(): boolean {\n return typeof window.matchMedia === \"function\" && window.matchMedia(QUERY).matches;\n}\n\n/**\n * True when the user asked for less motion. The server and the hydration pass\n * answer false, so markup matches; a reduced-motion client corrects it on the\n * first commit, before anything has animated.\n */\nexport function useReducedMotion(): boolean {\n return React.useSyncExternalStore(subscribe, snapshot, () => false);\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAEvB,MAAM,QAAQ;AAEd,SAAS,UAAU,QAAgC;AACjD,MAAI,OAAO,OAAO,eAAe,WAAY,QAAO,MAAM;AAAA,EAAC;AAC3D,QAAM,KAAK,OAAO,WAAW,KAAK;AAClC,KAAG,iBAAiB,UAAU,MAAM;AACpC,SAAO,MAAM,GAAG,oBAAoB,UAAU,MAAM;AACtD;AAEA,SAAS,WAAoB;AAC3B,SAAO,OAAO,OAAO,eAAe,cAAc,OAAO,WAAW,KAAK,EAAE;AAC7E;AAOO,SAAS,mBAA4B;AAC1C,SAAO,MAAM,qBAAqB,WAAW,UAAU,MAAM,KAAK;AACpE;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evinvest/uikit",
3
- "version": "0.29.4",
3
+ "version": "0.31.0",
4
4
  "description": "Dep-light React UI kit: shadcn-semantics components with zero @radix-ui / cva, mirroring the ev_lib::uikit Rust feature. Ships the shared design tokens.",
5
5
  "license": "BlueOak-1.0.0",
6
6
  "publishConfig": {
package/styles/ev.css CHANGED
@@ -1,75 +1,307 @@
1
1
  /* AUTO-GENERATED by `nix run .#gen` from the repo-root ev.css — do not edit. */
2
2
  /* ═══ PALETTE: EV ════════════════════════════════════════════════
3
3
  *
4
- * Zero specificity: EV is the default a brand scope overrides, whatever order
5
- * the two sheets are imported in.
4
+ * Three themes, each a zero-specificity rule: a brand scope overrides them
5
+ * whatever order the sheets are imported in, and later rules here win over
6
+ * earlier ones on the same element (so the order below is load-bearing).
6
7
  *
7
- * Surface ladder — dark UI reads as unfinished when every plane is the same
8
- * value, so the steps below are deliberate and measured; keep them apart if you
9
- * retune. background #070d18 → secondary #0d1526 (1.07:1) → card #16233c (1.24:1
10
- * vs background) → popover #1d2c49 (1.13:1 vs card) → muted #26375c. Elevation
11
- * rides the same ladder via --shadow-card / -elevated / -overlay. Card is also
12
- * pinned from above by a second floor: --primary has to clear 3:1 against it
13
- * (3.39:1 today), so lifting card one step breaks the tokens test.
8
+ * u2 Dark the default: `:root`, `[data-brand="ev"]`, or a `.dark` wrapper
9
+ * u2 Light a `.light` wrapper (on <html> or on any section)
10
+ * Oldschool `[data-theme="oldschool"]`: the navy look the kit shipped before
11
+ * 1.0, single-polarity, rounder, with shadows
12
+ *
13
+ * Each rule pins every EV-specific token, so a wrapper of one theme nested in
14
+ * another replaces all of it and leaks nothing. A brand scope is not EV: the
15
+ * `:not(…)` guard keeps these pins out of a `[data-brand]` subtree (and off the
16
+ * brand's own element), where the brand's palette and the derived formulas in
17
+ * theme.css apply instead. Without it a brand's `.light` would inherit EV
18
+ * Light's hover and borders.
19
+ *
20
+ * Surface ladder — the u2 planes sit 1.04–1.14:1 apart, which is the design
21
+ * (flat, no tinted fills) and under the ~1.2 floor in AGENTS.md. Planes are
22
+ * separated by a `--border` hairline instead: 1.37–1.46:1 on Dark and 1.35:1 on
23
+ * Light, measured with the alpha composited onto the plane. Do not lift a plane
24
+ * to chase the ratio; retune the hairline, and tokens.test.ts will measure it.
25
+ * Oldschool keeps its own, wider ladder: #070d18 → card #16233c (1.24:1) →
26
+ * popover #1d2c49 (1.13:1), with --primary held at 3:1 against the popover.
27
+ *
28
+ * Where a Figma value failed a floor the nearest passing one is pinned
29
+ * instead; the comment beside each says which.
14
30
  */
15
- :where(:root, [data-brand="ev"]) {
31
+
32
+ /* ── u2 Dark ─────────────────────────────────────────────────── */
33
+ :where(:is(:root, [data-brand="ev"], .dark):not([data-brand]:not([data-brand="ev"]), [data-brand]:not([data-brand="ev"]) *)) {
16
34
  /* the polarity, for what the platform draws itself (a native <select>'s
17
35
  * popup); a custom property so nothing but the kit's opt-ins reads it */
18
36
  --scheme: dark;
19
37
 
20
- /* ── surfaces ────────────────────────────────────────────────── */
38
+ /* surfaces */
39
+ --background: #0d0d0f;
40
+ --card: #151518;
41
+ --secondary: #1c1c20;
42
+ --on-secondary: var(--ink);
43
+ --popover: #252529;
44
+ --muted: #070709;
45
+ --hover: #e6e1d30f;
46
+ --active: #e6e1d31a;
47
+ --selected: #e6e1d31a;
48
+ --scrim: #000000a8;
49
+
50
+ /* ink: the Figma steps, opaque, so the text floor is measured on the value
51
+ * that paints (tertiary is 4.53:1 on the popover, the tightest plane) */
52
+ --ink: #e6e1d3;
53
+ --ink-mid: #bebab1;
54
+ --ink-soft: #8d8c87;
55
+ --ink-faint: #5e5e5b;
56
+
57
+ /* lines. --input stays the derived 48 % ink: the control edge owes 3:1 and
58
+ * Figma has no variable for it. */
59
+ --border-subtle: #e6e1d314;
60
+ --border: #e6e1d324;
61
+ --border-strong: #e6e1d342;
62
+ --ring: #35a395;
63
+
64
+ /* brand and primary. A fill and an ink are different jobs (docs/spec/accents.md):
65
+ * --primary carries white at 4.62:1 and reads as a shape on every plane (3.30:1
66
+ * on the popover); --primary-ink is the teal that reads as text (4.96:1 there).
67
+ * Figma's hover #18998b is 3.52:1 under white, so #127c71 (5.06:1) is pinned. */
68
+ --brand: #0d1627;
69
+ --primary: #138377;
70
+ --on-primary: #ffffff;
71
+ --primary-ink: #35a395;
72
+ --primary-hover: #127c71;
73
+ --primary-border: #35a3958c;
74
+
75
+ /* status. Fills carry the dark label (white fails 3:1–3.4:1 on these); the
76
+ * *-ink values are the ones to use as text. */
77
+ --positive: #2e9e5c;
78
+ --on-positive: #0d0d0f;
79
+ --positive-ink: #4abf76;
80
+ --accent-trace: #6b7fa0;
81
+ --on-accent-trace: #0d0d0f;
82
+ --accent-debug: #35a395;
83
+ --on-accent-debug: #0d0d0f;
84
+ --accent-info: #4990d8;
85
+ --on-accent-info: #0d0d0f;
86
+ --accent-info-ink: #6ca6e4;
87
+ --accent-warn: #f5c84e;
88
+ --on-accent-warn: #0d0d0f;
89
+ --accent-error: #f05b52;
90
+ --on-accent-error: #0d0d0f;
91
+
92
+ --chart-1: #35a395;
93
+ --chart-2: #4990d8;
94
+ --chart-3: #f5c84e;
95
+ --chart-4: #e68aaf;
96
+ --chart-5: #2e9e5c;
97
+ --chart-6: #988eef;
98
+
99
+ --button-primary-bg: #e6e1d300;
100
+ --button-primary-hover: #e6e1d314;
101
+ --button-primary-border: #e6e1d399;
102
+ --button-primary-ink: #e6e1d3;
103
+ --button-danger-bg: #f05b5200;
104
+ --button-danger-hover: #f05b521a;
105
+ --button-danger-border: #f05b52;
106
+ --button-danger-ink: var(--accent-error);
107
+
108
+ /* chips are outlines: a transparent fill and the label's hue at 50 % */
109
+ --chip-neutral-bg: var(--hover);
110
+ --chip-neutral-border: var(--border);
111
+ --chip-primary-bg: #35a39500;
112
+ --chip-primary-border: #35a3958c;
113
+ --chip-positive-bg: #2e9e5c00;
114
+ --chip-positive-border: #4abf7680;
115
+ --chip-info-bg: #4990d800;
116
+ --chip-info-border: #6ca6e480;
117
+ --chip-warn-bg: #f5c84e00;
118
+ --chip-warn-border: #f5c84e80;
119
+ --chip-error-bg: #f05b5200;
120
+ --chip-error-border: #f05b5280;
121
+ }
122
+
123
+ /* ── u2 Light ────────────────────────────────────────────────── */
124
+ :where(.light:not([data-brand]:not([data-brand="ev"]), [data-brand]:not([data-brand="ev"]) *)) {
125
+ --scheme: light;
126
+
127
+ --background: #efebdf;
128
+ --card: #f6f3eb;
129
+ --secondary: #fbfaf6;
130
+ --on-secondary: var(--ink);
131
+ --popover: #ffffff;
132
+ --muted: #e6e1d3;
133
+ --hover: #0d0d0f0d;
134
+ --active: #0d0d0f17;
135
+ --selected: #0d0d0f14;
136
+ --scrim: #00000066;
137
+
138
+ --ink: #0d0d0f;
139
+ --ink-mid: #3d3d41;
140
+ --ink-soft: #67696c;
141
+ --ink-faint: #9a9b9e;
142
+
143
+ --border-subtle: #0d0d0f14;
144
+ --border: #0d0d0f24;
145
+ --border-strong: #0d0d0f47;
146
+ --ring: #138377;
147
+
148
+ --brand: #080e1a;
149
+ --primary: #138377;
150
+ --on-primary: #ffffff;
151
+ --primary-ink: #116f65;
152
+ --primary-hover: #116f65;
153
+ --primary-border: #116f658c;
154
+
155
+ /* Figma's positive #228a4e carries neither label at 4.5:1 (white 4.36, dark
156
+ * 4.45), so the fill is one step darker: white reads at 5.01:1. */
157
+ --positive: #1f7f48;
158
+ --on-positive: #ffffff;
159
+ --positive-ink: #1b7943;
160
+ --accent-trace: #5a6b7c;
161
+ --on-accent-trace: #ffffff;
162
+ --accent-debug: #116f65;
163
+ --on-accent-debug: #ffffff;
164
+ --accent-info: #2d79c2;
165
+ --on-accent-info: #ffffff;
166
+ --accent-info-ink: #25639f;
167
+ /* Figma's #d8a523 is 1.89:1 on the page plane; warn is text in its own
168
+ * colour, so it is darkened toward Figma's warn text #846305 until it clears
169
+ * the non-text 3:1 (3.10:1) */
170
+ --accent-warn: #a77f12;
171
+ --on-accent-warn: #0d0d0f;
172
+ --accent-error: #db3834;
173
+ --on-accent-error: #ffffff;
174
+
175
+ --chart-1: #116f65;
176
+ --chart-2: #2d79c2;
177
+ --chart-3: #d8a523;
178
+ --chart-4: #b74679;
179
+ --chart-5: #228a4e;
180
+ --chart-6: #6d5acc;
181
+
182
+ --button-primary-bg: #0d0d0f00;
183
+ --button-primary-hover: #0d0d0f0f;
184
+ --button-primary-border: #0d0d0fa6;
185
+ --button-primary-ink: #080e1a;
186
+ --button-danger-bg: #db383400;
187
+ --button-danger-hover: #db383414;
188
+ --button-danger-border: #db3834;
189
+ --button-danger-ink: var(--accent-error);
190
+
191
+ /* the solid hue at 50 % (warning 60 %), where Dark uses the ink's */
192
+ --chip-neutral-bg: var(--hover);
193
+ --chip-neutral-border: var(--border);
194
+ --chip-primary-bg: #13837700;
195
+ --chip-primary-border: #116f658c;
196
+ --chip-positive-bg: #228a4e00;
197
+ --chip-positive-border: #228a4e80;
198
+ --chip-info-bg: #2d79c200;
199
+ --chip-info-border: #2d79c280;
200
+ --chip-warn-bg: #a77f1200;
201
+ --chip-warn-border: #a77f1299;
202
+ --chip-error-bg: #db383400;
203
+ --chip-error-border: #db383480;
204
+ }
205
+
206
+ /* ── Oldschool ───────────────────────────────────────────────── *
207
+ * Opt-in. Dark-only, so a `.light` or `.dark` inside it takes these values
208
+ * too. The surfaces are the ones the kit shipped (they pass every floor in
209
+ * tokens.test.ts); Figma's raised/overlay #1f2d4b measured 2.96:1 for the fill
210
+ * and 4.06:1 for tertiary text, so the popover stays #1d2c49. Its ink steps and
211
+ * --border are the derived formulas for the same reason: Figma's opaque
212
+ * border #1b2843 is 1.1:1 on the card and would leave the planes unseparated.
213
+ */
214
+ :where([data-theme="oldschool"], [data-theme="oldschool"] :is(.light, .dark)) {
215
+ --scheme: dark;
216
+
217
+ --brand-font-serif: var(--font-playfair, "Playfair Display");
218
+ --brand-font-mono: var(--font-inter, "Inter");
219
+
21
220
  --background: #070d18;
22
221
  --card: #16233c;
222
+ --secondary: #0d1526;
223
+ --on-secondary: var(--ink);
23
224
  --popover: #1d2c49;
24
225
  --muted: #26375c;
226
+ --hover: #e6e1d312;
227
+ --active: #e6e1d31f;
228
+ --selected: #35a3952e;
229
+ --scrim: #02050ea3;
25
230
 
26
- /* ── ink ─────────────────────────────────────────────────────── */
27
231
  --ink: #e6e1d3;
232
+ --ink-mid: color-mix(in srgb, var(--ink) 82%, transparent);
233
+ --ink-soft: color-mix(in srgb, var(--ink) 60%, transparent);
234
+ --ink-faint: color-mix(in srgb, var(--ink) 38%, transparent);
28
235
 
29
- /* ── roles ───────────────────────────────────────────────────── */
30
- --brand: #001e4e;
31
- /* teal, not the brand navy: navy filled a Button/Switch at ~1.1:1 against
32
- * these surfaces, i.e. an invisible control, and consumers hardcoded teal to
33
- * get around it. The brand navy still lives in --brand.
34
- *
35
- * A fill and an ink are different jobs, so the primary role carries two
36
- * values (docs/spec/accents.md). --primary-ink is the teal that reads on the
37
- * surfaces: 5.85:1 on background, 4.72:1 on card. --primary is the same hue
38
- * and chroma one step down in lightness — OKLCH L 0.550 C 0.094 H 183°
39
- * against the ink's L 0.630 C 0.101 H 183° — so the CTA carries white:
40
- * --on-primary reads on it at 4.63:1, and the fill clears the 3:1 non-text
41
- * floor against background (4.20:1), secondary (3.94:1), card (3.39:1) and
42
- * popover (3.01:1), so a checked Switch on a Card still reads as one. Muted
43
- * is the one surface under the floor (2.54:1); the controls that fill there
44
- * carry an on-primary mark, see the spec. Darkening the single value instead
45
- * would have dropped every teal label below AA. */
46
- --primary: #128377;
236
+ --border-subtle: #e6e1d312;
237
+ --border: color-mix(in srgb, var(--ink) 14%, transparent);
238
+ --border-strong: #2e3e64;
239
+ --ring: #35a395;
240
+
241
+ --brand: #080e1a;
242
+ --primary: #138377;
47
243
  --on-primary: #ffffff;
48
- --primary-ink: #2a9d8f;
49
- --secondary: #0d1526;
50
- --on-secondary: var(--ink);
51
- /* valence, not significance: a success reads as good while staying quiet */
52
- --positive: #2e9e5b;
244
+ --primary-ink: #35a395;
245
+ --primary-hover: #127c71;
246
+ --primary-border: #35a39500;
247
+
248
+ --positive: #2e9e5c;
53
249
  --on-positive: #070d18;
250
+ --positive-ink: #4abf76;
54
251
  --accent-trace: #6b7fa0;
55
252
  --on-accent-trace: #070d18;
56
- --accent-debug: #2a9d8f;
253
+ --accent-debug: #35a395;
57
254
  --on-accent-debug: #070d18;
58
- --accent-info: #4a90d9;
255
+ --accent-info: #4990d8;
59
256
  --on-accent-info: #070d18;
60
- --accent-warn: #f2c94c;
257
+ --accent-info-ink: #6ca6e4;
258
+ --accent-warn: #f5c84e;
61
259
  --on-accent-warn: #070d18;
62
- /* lifted off the brand red #da251d, which failed AA on a card. This one value
63
- * is the fill and the text on a bare surface; on its own 10 % tint (a
64
- * destructive menu row) the text is the derived --accent-error-ink, which
65
- * EV leaves to the contract: #ec8379, 4.83:1 on that tint over popover. The
66
- * vivid brand red is a gitops constant, not a UI token. */
67
- --accent-error: #ef5b52;
260
+ --accent-error: #f05b52;
68
261
  --on-accent-error: #070d18;
69
262
 
70
- --chart-1: #2a9d8f;
71
- --chart-2: #2e9e5b;
72
- --chart-3: #f2c94c;
73
- --chart-4: #e58aae;
74
- --chart-5: #4a90d9;
263
+ --chart-1: #35a395;
264
+ --chart-2: #4990d8;
265
+ --chart-3: #f5c84e;
266
+ --chart-4: #e68aaf;
267
+ --chart-5: #2e9e5c;
268
+ --chart-6: #988eef;
269
+
270
+ /* filled buttons. The danger label stays dark: white on #f05b52 is 3.32:1. */
271
+ --button-primary-bg: #138377;
272
+ --button-primary-hover: #127c71;
273
+ --button-primary-border: #138377;
274
+ --button-primary-ink: #ffffff;
275
+ --button-danger-bg: #f05b52;
276
+ --button-danger-hover: #f06c61;
277
+ --button-danger-border: #f05b52;
278
+ --button-danger-ink: #070d18;
279
+
280
+ /* chips are tinted pills with no edge */
281
+ --chip-neutral-bg: var(--hover);
282
+ --chip-neutral-border: #e6e1d300;
283
+ /* Figma tints the accent chip 16 %, which leaves its teal label at 4.03:1 on
284
+ * the card; 8 % holds 4.56:1 */
285
+ --chip-primary-bg: #35a39514;
286
+ --chip-primary-border: #35a39500;
287
+ --chip-positive-bg: #2e9e5c2e;
288
+ --chip-positive-border: #4abf7600;
289
+ --chip-info-bg: #4990d82e;
290
+ --chip-info-border: #6ca6e400;
291
+ --chip-warn-bg: #f5c84e29;
292
+ --chip-warn-border: #f5c84e00;
293
+ --chip-error-bg: #f05b522e;
294
+ --chip-error-border: #f06c6100;
295
+
296
+ --radius: 0.5rem;
297
+ --control-radius: 0.375rem;
298
+ --chip-radius: 9999px;
299
+ /* the ladder is three tiers, not one drop shadow: on a dark theme the value
300
+ * steps alone are too close to separate a resting card from a menu from a
301
+ * modal, so each tier gets its own spread and opacity */
302
+ --depth-card: 0 1px 2px 0 rgb(0 0 0 / 0.3);
303
+ --depth-elevated:
304
+ 0 4px 12px -2px rgb(0 0 0 / 0.4),
305
+ 0 2px 4px -2px rgb(0 0 0 / 0.3);
306
+ --depth-overlay: 0 16px 48px -12px rgb(0 0 0 / 0.6);
75
307
  }