@evinvest/uikit 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -8
- package/dist/components/account-menu.js +70 -0
- package/dist/components/account-menu.js.map +1 -0
- package/dist/components/alert-dialog.js +5 -4
- package/dist/components/alert-dialog.js.map +1 -1
- package/dist/components/app-shell.js +11 -0
- package/dist/components/app-shell.js.map +1 -1
- package/dist/components/command.js +129 -20
- package/dist/components/command.js.map +1 -1
- package/dist/components/context-menu.js +8 -8
- package/dist/components/context-menu.js.map +1 -1
- package/dist/components/dialog.js +6 -4
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/drawer.js +15 -9
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/dropdown-menu.js +13 -10
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/info-tip.js +2 -2
- package/dist/components/info-tip.js.map +1 -1
- package/dist/components/list-rows.js +73 -0
- package/dist/components/list-rows.js.map +1 -0
- package/dist/components/menubar.js +18 -5
- package/dist/components/menubar.js.map +1 -1
- package/dist/components/navigation-menu.js +2 -2
- package/dist/components/navigation-menu.js.map +1 -1
- package/dist/components/popover.js +2 -2
- package/dist/components/popover.js.map +1 -1
- package/dist/components/select.js +2 -2
- package/dist/components/select.js.map +1 -1
- package/dist/components/sheet.js +12 -5
- package/dist/components/sheet.js.map +1 -1
- package/dist/components/sonner.js +1 -0
- package/dist/components/sonner.js.map +1 -1
- package/dist/components/table.js +24 -7
- package/dist/components/table.js.map +1 -1
- package/dist/components/toggle-group.js +1 -1
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/top-bar.js +21 -0
- package/dist/components/top-bar.js.map +1 -0
- package/dist/generated/alert-dialog.js +2 -2
- package/dist/generated/alert-dialog.js.map +1 -1
- package/dist/generated/context-menu.js +1 -1
- package/dist/generated/context-menu.js.map +1 -1
- package/dist/generated/dialog.js +3 -3
- package/dist/generated/dialog.js.map +1 -1
- package/dist/generated/drawer.js +1 -1
- package/dist/generated/drawer.js.map +1 -1
- package/dist/generated/dropdown-menu.js +1 -1
- package/dist/generated/dropdown-menu.js.map +1 -1
- package/dist/generated/menubar.js +1 -1
- package/dist/generated/menubar.js.map +1 -1
- package/dist/generated/sheet.js +3 -3
- package/dist/generated/sheet.js.map +1 -1
- package/dist/generated/table.js +20 -3
- package/dist/generated/table.js.map +1 -1
- package/dist/generated/toggle.js +2 -2
- package/dist/generated/toggle.js.map +1 -1
- package/dist/index.d.ts +199 -27
- package/dist/index.js +5 -2
- package/dist/primitives/dismissable-layer.js +80 -15
- package/dist/primitives/dismissable-layer.js.map +1 -1
- package/dist/primitives/focus-scope.js +22 -6
- package/dist/primitives/focus-scope.js.map +1 -1
- package/dist/primitives/use-roving-focus.js +13 -7
- package/dist/primitives/use-roving-focus.js.map +1 -1
- package/package.json +1 -1
- package/styles/ev.css +4 -2
- package/styles/theme.css +34 -2
- package/styles/tokens.css +42 -5
|
@@ -16,15 +16,25 @@ function focusable(root) {
|
|
|
16
16
|
(el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement
|
|
17
17
|
);
|
|
18
18
|
}
|
|
19
|
-
function
|
|
19
|
+
function usable(el) {
|
|
20
|
+
return !!el && el !== document.body && el.isConnected;
|
|
21
|
+
}
|
|
22
|
+
function focusRoot(root) {
|
|
23
|
+
if (!root.hasAttribute("tabindex")) root.tabIndex = -1;
|
|
24
|
+
root.focus();
|
|
25
|
+
}
|
|
26
|
+
function useFocusScope(enabled, { initialFocus = "first", returnFocusTo } = {}) {
|
|
20
27
|
const ref = React.useRef(null);
|
|
28
|
+
const fallbackRef = React.useRef(returnFocusTo);
|
|
29
|
+
fallbackRef.current = returnFocusTo;
|
|
21
30
|
React.useEffect(() => {
|
|
22
31
|
if (!enabled) return;
|
|
23
32
|
const root = ref.current;
|
|
24
33
|
if (!root) return;
|
|
25
34
|
const previouslyFocused = document.activeElement;
|
|
26
|
-
const first = focusable(root)[0];
|
|
27
|
-
(first
|
|
35
|
+
const first = initialFocus === "first" ? focusable(root)[0] : void 0;
|
|
36
|
+
if (first) first.focus();
|
|
37
|
+
else focusRoot(root);
|
|
28
38
|
function onKeyDown(event) {
|
|
29
39
|
if (event.key !== "Tab" || !root) return;
|
|
30
40
|
const items = focusable(root);
|
|
@@ -35,7 +45,10 @@ function useFocusScope(enabled) {
|
|
|
35
45
|
const firstEl = items[0];
|
|
36
46
|
const lastEl = items[items.length - 1];
|
|
37
47
|
const active = document.activeElement;
|
|
38
|
-
if (
|
|
48
|
+
if (active === root) {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
(event.shiftKey ? lastEl : firstEl).focus();
|
|
51
|
+
} else if (event.shiftKey && active === firstEl) {
|
|
39
52
|
event.preventDefault();
|
|
40
53
|
lastEl.focus();
|
|
41
54
|
} else if (!event.shiftKey && active === lastEl) {
|
|
@@ -46,9 +59,12 @@ function useFocusScope(enabled) {
|
|
|
46
59
|
root.addEventListener("keydown", onKeyDown);
|
|
47
60
|
return () => {
|
|
48
61
|
root.removeEventListener("keydown", onKeyDown);
|
|
49
|
-
|
|
62
|
+
const active = document.activeElement;
|
|
63
|
+
if (active && active !== document.body && !root.contains(active)) return;
|
|
64
|
+
const target = usable(previouslyFocused) ? previouslyFocused : fallbackRef.current?.current;
|
|
65
|
+
if (usable(target)) target.focus({ preventScroll: true });
|
|
50
66
|
};
|
|
51
|
-
}, [enabled]);
|
|
67
|
+
}, [enabled, initialFocus]);
|
|
52
68
|
return ref;
|
|
53
69
|
}
|
|
54
70
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/primitives/focus-scope.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n// `tabindex=\"-1\"` opts an element out of Tab order whatever its tag, so the\n// exclusion goes on every branch — otherwise a roving-tabindex grid's parked\n// cells would count as tab stops.\nconst FOCUSABLE = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]\",\n]\n .map(sel => `${sel}:not([tabindex=\"-1\"])`)\n .join(\",\");\n\n/**\n * Every enabled, Tab-reachable candidate under `root` in DOM (= Tab) order,\n * before the layout-based visibility filter; `tabindex=\"-1\"` is skipped on any\n * tag. For content that is fully visible while it exists (a popover), this is\n * the tab order itself. Internal to the kit.\n */\nexport function focusCandidates(root: HTMLElement): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE));\n}\n\nfunction focusable(root: HTMLElement): HTMLElement[] {\n return focusCandidates(root).filter(\n (el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement,\n );\n}\n\n/**\n * Traps Tab focus inside the returned ref's subtree and restores focus to the\n * previously-focused element on unmount — the dep-light core of dialogs/menus,\n * replacing `@radix-ui/react-focus-scope`. On mount, focuses
|
|
1
|
+
{"version":3,"sources":["../../src/primitives/focus-scope.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n// `tabindex=\"-1\"` opts an element out of Tab order whatever its tag, so the\n// exclusion goes on every branch — otherwise a roving-tabindex grid's parked\n// cells would count as tab stops.\nconst FOCUSABLE = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"textarea:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"[tabindex]\",\n]\n .map(sel => `${sel}:not([tabindex=\"-1\"])`)\n .join(\",\");\n\n/**\n * Every enabled, Tab-reachable candidate under `root` in DOM (= Tab) order,\n * before the layout-based visibility filter; `tabindex=\"-1\"` is skipped on any\n * tag. For content that is fully visible while it exists (a popover), this is\n * the tab order itself. Internal to the kit.\n */\nexport function focusCandidates(root: HTMLElement): HTMLElement[] {\n return Array.from(root.querySelectorAll<HTMLElement>(FOCUSABLE));\n}\n\nfunction focusable(root: HTMLElement): HTMLElement[] {\n return focusCandidates(root).filter(\n (el) => el.offsetWidth > 0 || el.offsetHeight > 0 || el === document.activeElement,\n );\n}\n\n/**\n * Where focus lands when the scope opens. `first` is the first Tab stop inside\n * (an input, an alert dialog's least destructive action); `container` is the\n * scope root itself, so a reader starts at the top and the first Tab reaches the\n * first control — right for a panel of content, where the first stop may be an\n * action one keypress away from firing.\n */\nexport type InitialFocus = \"first\" | \"container\";\n\nexport interface FocusScopeOptions {\n /** Default `first`; falls back to the container when nothing inside is focusable. */\n initialFocus?: InitialFocus;\n /**\n * Where focus goes back on close when the element focused before opening is\n * no use: `<body>` (Safari does not focus a clicked button), or detached (the\n * opener was re-rendered away). Pass the overlay's trigger.\n */\n returnFocusTo?: React.RefObject<HTMLElement | null>;\n}\n\nfunction usable(el: HTMLElement | null | undefined): el is HTMLElement {\n return !!el && el !== document.body && el.isConnected;\n}\n\n// A `<div>` without a tabindex ignores `.focus()`, so a scope with no Tab stops\n// used to leave focus on the trigger behind the scrim. `-1` makes the root\n// focusable by script (and by a click on its empty area, which keeps focus in\n// the scope) without adding it to the Tab order.\nfunction focusRoot(root: HTMLElement) {\n if (!root.hasAttribute(\"tabindex\")) root.tabIndex = -1;\n root.focus();\n}\n\n/**\n * Traps Tab focus inside the returned ref's subtree and restores focus to the\n * previously-focused element on unmount — the dep-light core of dialogs/menus,\n * replacing `@radix-ui/react-focus-scope`. On mount, focuses per\n * `initialFocus`; the root gets `tabindex=\"-1\"` whenever it is the target.\n *\n * Rust dialogs rely on the browser's native focus order within a fixed overlay;\n * see the README \"Limitations\".\n */\nexport function useFocusScope(\n enabled: boolean,\n { initialFocus = \"first\", returnFocusTo }: FocusScopeOptions = {},\n): React.RefObject<HTMLDivElement | null> {\n const ref = React.useRef<HTMLDivElement | null>(null);\n // Read at close, not captured at open: a trigger re-rendered while the\n // overlay was up is a new node by then.\n const fallbackRef = React.useRef(returnFocusTo);\n fallbackRef.current = returnFocusTo;\n\n React.useEffect(() => {\n if (!enabled) return;\n const root = ref.current;\n if (!root) return;\n\n const previouslyFocused = document.activeElement as HTMLElement | null;\n const first = initialFocus === \"first\" ? focusable(root)[0] : undefined;\n if (first) first.focus();\n else focusRoot(root);\n\n function onKeyDown(event: KeyboardEvent) {\n if (event.key !== \"Tab\" || !root) return;\n const items = focusable(root);\n if (items.length === 0) {\n event.preventDefault();\n return;\n }\n const firstEl = items[0]!;\n const lastEl = items[items.length - 1]!;\n const active = document.activeElement;\n // From the root itself Shift+Tab would leave the scope for whatever\n // precedes the portal, so both directions are steered explicitly.\n if (active === root) {\n event.preventDefault();\n (event.shiftKey ? lastEl : firstEl).focus();\n } else if (event.shiftKey && active === firstEl) {\n event.preventDefault();\n lastEl.focus();\n } else if (!event.shiftKey && active === lastEl) {\n event.preventDefault();\n firstEl.focus();\n }\n }\n\n root.addEventListener(\"keydown\", onKeyDown);\n return () => {\n root.removeEventListener(\"keydown\", onKeyDown);\n // Focus that already left the scope (a click outside landed on a field)\n // is the user's; only focus still inside, or dropped to <body>, returns.\n const active = document.activeElement;\n if (active && active !== document.body && !root.contains(active)) return;\n const target = usable(previouslyFocused) ? previouslyFocused : fallbackRef.current?.current;\n if (usable(target)) target.focus({ preventScroll: true });\n };\n }, [enabled, initialFocus]);\n\n return ref;\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAKvB,MAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EACG,IAAI,SAAO,GAAG,GAAG,uBAAuB,EACxC,KAAK,GAAG;AAQJ,SAAS,gBAAgB,MAAkC;AAChE,SAAO,MAAM,KAAK,KAAK,iBAA8B,SAAS,CAAC;AACjE;AAEA,SAAS,UAAU,MAAkC;AACnD,SAAO,gBAAgB,IAAI,EAAE;AAAA,IAC3B,CAAC,OAAO,GAAG,cAAc,KAAK,GAAG,eAAe,KAAK,OAAO,SAAS;AAAA,EACvE;AACF;AAsBA,SAAS,OAAO,IAAuD;AACrE,SAAO,CAAC,CAAC,MAAM,OAAO,SAAS,QAAQ,GAAG;AAC5C;AAMA,SAAS,UAAU,MAAmB;AACpC,MAAI,CAAC,KAAK,aAAa,UAAU,EAAG,MAAK,WAAW;AACpD,OAAK,MAAM;AACb;AAWO,SAAS,cACd,SACA,EAAE,eAAe,SAAS,cAAc,IAAuB,CAAC,GACxB;AACxC,QAAM,MAAM,MAAM,OAA8B,IAAI;AAGpD,QAAM,cAAc,MAAM,OAAO,aAAa;AAC9C,cAAY,UAAU;AAEtB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,QAAS;AACd,UAAM,OAAO,IAAI;AACjB,QAAI,CAAC,KAAM;AAEX,UAAM,oBAAoB,SAAS;AACnC,UAAM,QAAQ,iBAAiB,UAAU,UAAU,IAAI,EAAE,CAAC,IAAI;AAC9D,QAAI,MAAO,OAAM,MAAM;AAAA,QAClB,WAAU,IAAI;AAEnB,aAAS,UAAU,OAAsB;AACvC,UAAI,MAAM,QAAQ,SAAS,CAAC,KAAM;AAClC,YAAM,QAAQ,UAAU,IAAI;AAC5B,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,eAAe;AACrB;AAAA,MACF;AACA,YAAM,UAAU,MAAM,CAAC;AACvB,YAAM,SAAS,MAAM,MAAM,SAAS,CAAC;AACrC,YAAM,SAAS,SAAS;AAGxB,UAAI,WAAW,MAAM;AACnB,cAAM,eAAe;AACrB,SAAC,MAAM,WAAW,SAAS,SAAS,MAAM;AAAA,MAC5C,WAAW,MAAM,YAAY,WAAW,SAAS;AAC/C,cAAM,eAAe;AACrB,eAAO,MAAM;AAAA,MACf,WAAW,CAAC,MAAM,YAAY,WAAW,QAAQ;AAC/C,cAAM,eAAe;AACrB,gBAAQ,MAAM;AAAA,MAChB;AAAA,IACF;AAEA,SAAK,iBAAiB,WAAW,SAAS;AAC1C,WAAO,MAAM;AACX,WAAK,oBAAoB,WAAW,SAAS;AAG7C,YAAM,SAAS,SAAS;AACxB,UAAI,UAAU,WAAW,SAAS,QAAQ,CAAC,KAAK,SAAS,MAAM,EAAG;AAClE,YAAM,SAAS,OAAO,iBAAiB,IAAI,oBAAoB,YAAY,SAAS;AACpF,UAAI,OAAO,MAAM,EAAG,QAAO,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,IAC1D;AAAA,EACF,GAAG,CAAC,SAAS,YAAY,CAAC;AAE1B,SAAO;AACT;","names":[]}
|
|
@@ -3,12 +3,17 @@ import * as React from "react";
|
|
|
3
3
|
function useRovingFocus(opts) {
|
|
4
4
|
const { count, orientation = "vertical", loop = true, initial = 0 } = opts;
|
|
5
5
|
const [activeIndex, setActiveIndex] = React.useState(initial);
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
const resolveCount = () => typeof count === "function" ? count() : count;
|
|
7
|
+
const next = (dir) => {
|
|
8
|
+
const total = resolveCount();
|
|
9
|
+
if (total <= 0) return;
|
|
10
|
+
setActiveIndex((i) => {
|
|
11
|
+
let n = Math.min(Math.max(i, 0), total - 1) + dir;
|
|
12
|
+
if (n < 0) n = loop ? total - 1 : 0;
|
|
13
|
+
else if (n >= total) n = loop ? 0 : total - 1;
|
|
14
|
+
return n;
|
|
15
|
+
});
|
|
16
|
+
};
|
|
12
17
|
const onKeyDown = (event) => {
|
|
13
18
|
const horiz = orientation === "horizontal" || orientation === "both";
|
|
14
19
|
const vert = orientation === "vertical" || orientation === "both";
|
|
@@ -29,7 +34,8 @@ function useRovingFocus(opts) {
|
|
|
29
34
|
setActiveIndex(0);
|
|
30
35
|
} else if (event.key === "End") {
|
|
31
36
|
event.preventDefault();
|
|
32
|
-
|
|
37
|
+
const total = resolveCount();
|
|
38
|
+
if (total > 0) setActiveIndex(total - 1);
|
|
33
39
|
}
|
|
34
40
|
};
|
|
35
41
|
return { activeIndex, setActiveIndex, onKeyDown };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/primitives/use-roving-focus.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Arrow-key roving focus over a set of items — the dep-light core of menus,\n * tabs, radio-groups and toolbars (replacing `@radix-ui/react-roving-focus`).\n * Returns an `onKeyDown` to spread on the container and the active index; items\n * should set `tabIndex={index === activeIndex ? 0 : -1}`.\n *\n * Mirrors the keyboard navigation Rust drives with a focused-index signal.\n */\nexport function useRovingFocus(opts: {\n count: number;\n orientation?: \"horizontal\" | \"vertical\" | \"both\";\n loop?: boolean;\n initial?: number;\n}): {\n activeIndex: number;\n setActiveIndex: (i: number) => void;\n onKeyDown: (event: React.KeyboardEvent) => void;\n} {\n const { count, orientation = \"vertical\", loop = true, initial = 0 } = opts;\n const [activeIndex, setActiveIndex] = React.useState(initial);\n\n const next = (dir: 1 | -1)
|
|
1
|
+
{"version":3,"sources":["../../src/primitives/use-roving-focus.ts"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\n\n/**\n * Arrow-key roving focus over a set of items — the dep-light core of menus,\n * tabs, radio-groups and toolbars (replacing `@radix-ui/react-roving-focus`).\n * Returns an `onKeyDown` to spread on the container and the active index; items\n * should set `tabIndex={index === activeIndex ? 0 : -1}`.\n *\n * `count` may be a getter: containers whose items are only known from the DOM\n * (menus with conditionally rendered or disabled items, inline sub-menus) pass\n * one so the bound is read at keydown time — never during render, which keeps\n * the hook safe under SSR.\n *\n * Mirrors the keyboard navigation Rust drives with a focused-index signal.\n */\nexport function useRovingFocus(opts: {\n count: number | (() => number);\n orientation?: \"horizontal\" | \"vertical\" | \"both\";\n loop?: boolean;\n initial?: number;\n}): {\n activeIndex: number;\n setActiveIndex: (i: number) => void;\n onKeyDown: (event: React.KeyboardEvent) => void;\n} {\n const { count, orientation = \"vertical\", loop = true, initial = 0 } = opts;\n const [activeIndex, setActiveIndex] = React.useState(initial);\n\n const resolveCount = () => (typeof count === \"function\" ? count() : count);\n\n const next = (dir: 1 | -1) => {\n const total = resolveCount();\n if (total <= 0) return;\n setActiveIndex((i) => {\n // Clamp first: the item set may have shrunk since the index was set.\n let n = Math.min(Math.max(i, 0), total - 1) + dir;\n if (n < 0) n = loop ? total - 1 : 0;\n else if (n >= total) n = loop ? 0 : total - 1;\n return n;\n });\n };\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n const horiz = orientation === \"horizontal\" || orientation === \"both\";\n const vert = orientation === \"vertical\" || orientation === \"both\";\n if (vert && event.key === \"ArrowDown\") {\n event.preventDefault();\n next(1);\n } else if (vert && event.key === \"ArrowUp\") {\n event.preventDefault();\n next(-1);\n } else if (horiz && event.key === \"ArrowRight\") {\n event.preventDefault();\n next(1);\n } else if (horiz && event.key === \"ArrowLeft\") {\n event.preventDefault();\n next(-1);\n } else if (event.key === \"Home\") {\n event.preventDefault();\n setActiveIndex(0);\n } else if (event.key === \"End\") {\n event.preventDefault();\n const total = resolveCount();\n if (total > 0) setActiveIndex(total - 1);\n }\n };\n\n return { activeIndex, setActiveIndex, onKeyDown };\n}\n"],"mappings":";AAEA,YAAY,WAAW;AAehB,SAAS,eAAe,MAS7B;AACA,QAAM,EAAE,OAAO,cAAc,YAAY,OAAO,MAAM,UAAU,EAAE,IAAI;AACtE,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,OAAO;AAE5D,QAAM,eAAe,MAAO,OAAO,UAAU,aAAa,MAAM,IAAI;AAEpE,QAAM,OAAO,CAAC,QAAgB;AAC5B,UAAM,QAAQ,aAAa;AAC3B,QAAI,SAAS,EAAG;AAChB,mBAAe,CAAC,MAAM;AAEpB,UAAI,IAAI,KAAK,IAAI,KAAK,IAAI,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI;AAC9C,UAAI,IAAI,EAAG,KAAI,OAAO,QAAQ,IAAI;AAAA,eACzB,KAAK,MAAO,KAAI,OAAO,IAAI,QAAQ;AAC5C,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,YAAY,CAAC,UAA+B;AAChD,UAAM,QAAQ,gBAAgB,gBAAgB,gBAAgB;AAC9D,UAAM,OAAO,gBAAgB,cAAc,gBAAgB;AAC3D,QAAI,QAAQ,MAAM,QAAQ,aAAa;AACrC,YAAM,eAAe;AACrB,WAAK,CAAC;AAAA,IACR,WAAW,QAAQ,MAAM,QAAQ,WAAW;AAC1C,YAAM,eAAe;AACrB,WAAK,EAAE;AAAA,IACT,WAAW,SAAS,MAAM,QAAQ,cAAc;AAC9C,YAAM,eAAe;AACrB,WAAK,CAAC;AAAA,IACR,WAAW,SAAS,MAAM,QAAQ,aAAa;AAC7C,YAAM,eAAe;AACrB,WAAK,EAAE;AAAA,IACT,WAAW,MAAM,QAAQ,QAAQ;AAC/B,YAAM,eAAe;AACrB,qBAAe,CAAC;AAAA,IAClB,WAAW,MAAM,QAAQ,OAAO;AAC9B,YAAM,eAAe;AACrB,YAAM,QAAQ,aAAa;AAC3B,UAAI,QAAQ,EAAG,gBAAe,QAAQ,CAAC;AAAA,IACzC;AAAA,EACF;AAEA,SAAO,EAAE,aAAa,gBAAgB,UAAU;AAClD;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evinvest/uikit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.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
|
@@ -60,8 +60,10 @@
|
|
|
60
60
|
--accent-warn: #f2c94c;
|
|
61
61
|
--on-accent-warn: #070d18;
|
|
62
62
|
/* lifted off the brand red #da251d, which failed AA on a card. This one value
|
|
63
|
-
*
|
|
64
|
-
*
|
|
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. */
|
|
65
67
|
--accent-error: #ef5b52;
|
|
66
68
|
--on-accent-error: #070d18;
|
|
67
69
|
|
package/styles/theme.css
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/* AUTO-GENERATED by `nix run .#gen` from the repo-root theme.css — do not edit. */
|
|
2
2
|
/*
|
|
3
|
-
* Toast (sonner) and Drawer enter/exit motion, the
|
|
4
|
-
*
|
|
3
|
+
* Toast (sonner) and Drawer enter/exit motion, the reduced-motion fade for Sheet and
|
|
4
|
+
* the dialogs, the entrances (rise / stagger / settled / skeleton) and the app-shell
|
|
5
|
+
* markers, shared by both ports. Imported by the token
|
|
5
6
|
* sheets so the lifecycle ships with the theme contract.
|
|
6
7
|
*/
|
|
7
8
|
|
|
@@ -307,6 +308,30 @@
|
|
|
307
308
|
}
|
|
308
309
|
}
|
|
309
310
|
|
|
311
|
+
/* ── Sheet and dialogs under reduced motion ──────────────────────────────────
|
|
312
|
+
Both ports animate these with tw-animate-css (`animate-in` / `animate-out` +
|
|
313
|
+
`slide-*` / `zoom-*`), whose `enter` / `exit` keyframes read the
|
|
314
|
+
--tw-enter-* / --tw-exit-* variables the slide and zoom utilities set. Zeroing
|
|
315
|
+
the travel and the scale here leaves a fade: the dialogs already fade, the
|
|
316
|
+
sheet does not, so its opacity is set too. The keyframes still run on their
|
|
317
|
+
usual clock, so the TS port's exit `animationend` unmount is untouched.
|
|
318
|
+
Unlayered, like the rest of this sheet, so it outranks those utilities
|
|
319
|
+
without !important. */
|
|
320
|
+
@media (prefers-reduced-motion: reduce) {
|
|
321
|
+
[data-slot="sheet-content"],
|
|
322
|
+
[data-slot="dialog-content"],
|
|
323
|
+
[data-slot="alert-dialog-content"] {
|
|
324
|
+
--tw-enter-translate-x: 0;
|
|
325
|
+
--tw-enter-translate-y: 0;
|
|
326
|
+
--tw-exit-translate-x: 0;
|
|
327
|
+
--tw-exit-translate-y: 0;
|
|
328
|
+
--tw-enter-scale: 1;
|
|
329
|
+
--tw-exit-scale: 1;
|
|
330
|
+
--tw-enter-opacity: 0;
|
|
331
|
+
--tw-exit-opacity: 0;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
310
335
|
/* ── Entrances: rise, stagger, settled, skeleton ─────────────────────────────
|
|
311
336
|
One keyframe for every "arriving" thing, driven by attributes so a consumer
|
|
312
337
|
without a motion library (and a Server Component) gets it for free:
|
|
@@ -513,6 +538,7 @@
|
|
|
513
538
|
--color-on-accent-warn: var(--on-accent-warn);
|
|
514
539
|
--color-accent-error: var(--accent-error);
|
|
515
540
|
--color-on-accent-error: var(--on-accent-error);
|
|
541
|
+
--color-accent-error-ink: var(--accent-error-ink);
|
|
516
542
|
|
|
517
543
|
/* charts — distinguishable hues carrying no significance */
|
|
518
544
|
--color-chart-1: var(--chart-1);
|
|
@@ -540,6 +566,12 @@
|
|
|
540
566
|
* side — ink over white needs ~47% to clear it, dark lands near 4:1. */
|
|
541
567
|
--input: color-mix(in srgb, var(--ink) 48%, transparent);
|
|
542
568
|
--ring: var(--primary-ink);
|
|
569
|
+
/* the error role as text on its own 10 % tint — a destructive menu row
|
|
570
|
+
* (lib#166). The fill alone read 3.76:1 there on EV and 2.48:1 on a dark
|
|
571
|
+
* brand; 30 % of the ink pulls it toward whichever polarity the text needs.
|
|
572
|
+
* Derived rather than required so a brand file that predates it still
|
|
573
|
+
* renders; one that misses the floor pins its own (docs/spec/accents.md). */
|
|
574
|
+
--accent-error-ink: color-mix(in srgb, var(--accent-error) 70%, var(--ink));
|
|
543
575
|
}
|
|
544
576
|
|
|
545
577
|
/* Geometry. A `[data-brand]` scope is a theme root of its own, so it restarts
|
package/styles/tokens.css
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
* accent-trace · accent-debug · accent-info · accent-warn · accent-error
|
|
22
22
|
* primary-ink — the primary role as ink on a surface, where the
|
|
23
23
|
* fill and the readable value are different colours
|
|
24
|
+
* accent-error-ink — the error role as text on its own tint;
|
|
25
|
+
* derived from accent-error and ink, a palette may pin it
|
|
24
26
|
* SCALARS radius · control-* · display-scale · page-* · shell-* · shadows · fonts
|
|
25
27
|
* MOTION ev-ease-* · ev-dur-* · ev-rise · ev-stagger(-section)
|
|
26
28
|
* CHARTS chart-1 … chart-5
|
|
@@ -37,12 +39,14 @@
|
|
|
37
39
|
*
|
|
38
40
|
* Accents are ordered by significance, not by meaning; see docs/spec/accents.md.
|
|
39
41
|
* Their values are read outside CSS (gitops mirrors them into Discord embed
|
|
40
|
-
* bars), so they stay plain hex — no color-mix.
|
|
42
|
+
* bars), so they stay plain hex — no color-mix. `accent-error-ink` is not an
|
|
43
|
+
* accent but a reading of one, so it lives with the derived tokens.
|
|
41
44
|
*/
|
|
42
45
|
|
|
43
46
|
/*
|
|
44
|
-
* Toast (sonner) and Drawer enter/exit motion, the
|
|
45
|
-
*
|
|
47
|
+
* Toast (sonner) and Drawer enter/exit motion, the reduced-motion fade for Sheet and
|
|
48
|
+
* the dialogs, the entrances (rise / stagger / settled / skeleton) and the app-shell
|
|
49
|
+
* markers, shared by both ports. Imported by the token
|
|
46
50
|
* sheets so the lifecycle ships with the theme contract.
|
|
47
51
|
*/
|
|
48
52
|
|
|
@@ -348,6 +352,30 @@
|
|
|
348
352
|
}
|
|
349
353
|
}
|
|
350
354
|
|
|
355
|
+
/* ── Sheet and dialogs under reduced motion ──────────────────────────────────
|
|
356
|
+
Both ports animate these with tw-animate-css (`animate-in` / `animate-out` +
|
|
357
|
+
`slide-*` / `zoom-*`), whose `enter` / `exit` keyframes read the
|
|
358
|
+
--tw-enter-* / --tw-exit-* variables the slide and zoom utilities set. Zeroing
|
|
359
|
+
the travel and the scale here leaves a fade: the dialogs already fade, the
|
|
360
|
+
sheet does not, so its opacity is set too. The keyframes still run on their
|
|
361
|
+
usual clock, so the TS port's exit `animationend` unmount is untouched.
|
|
362
|
+
Unlayered, like the rest of this sheet, so it outranks those utilities
|
|
363
|
+
without !important. */
|
|
364
|
+
@media (prefers-reduced-motion: reduce) {
|
|
365
|
+
[data-slot="sheet-content"],
|
|
366
|
+
[data-slot="dialog-content"],
|
|
367
|
+
[data-slot="alert-dialog-content"] {
|
|
368
|
+
--tw-enter-translate-x: 0;
|
|
369
|
+
--tw-enter-translate-y: 0;
|
|
370
|
+
--tw-exit-translate-x: 0;
|
|
371
|
+
--tw-exit-translate-y: 0;
|
|
372
|
+
--tw-enter-scale: 1;
|
|
373
|
+
--tw-exit-scale: 1;
|
|
374
|
+
--tw-enter-opacity: 0;
|
|
375
|
+
--tw-exit-opacity: 0;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
351
379
|
/* ── Entrances: rise, stagger, settled, skeleton ─────────────────────────────
|
|
352
380
|
One keyframe for every "arriving" thing, driven by attributes so a consumer
|
|
353
381
|
without a motion library (and a Server Component) gets it for free:
|
|
@@ -554,6 +582,7 @@
|
|
|
554
582
|
--color-on-accent-warn: var(--on-accent-warn);
|
|
555
583
|
--color-accent-error: var(--accent-error);
|
|
556
584
|
--color-on-accent-error: var(--on-accent-error);
|
|
585
|
+
--color-accent-error-ink: var(--accent-error-ink);
|
|
557
586
|
|
|
558
587
|
/* charts — distinguishable hues carrying no significance */
|
|
559
588
|
--color-chart-1: var(--chart-1);
|
|
@@ -581,6 +610,12 @@
|
|
|
581
610
|
* side — ink over white needs ~47% to clear it, dark lands near 4:1. */
|
|
582
611
|
--input: color-mix(in srgb, var(--ink) 48%, transparent);
|
|
583
612
|
--ring: var(--primary-ink);
|
|
613
|
+
/* the error role as text on its own 10 % tint — a destructive menu row
|
|
614
|
+
* (lib#166). The fill alone read 3.76:1 there on EV and 2.48:1 on a dark
|
|
615
|
+
* brand; 30 % of the ink pulls it toward whichever polarity the text needs.
|
|
616
|
+
* Derived rather than required so a brand file that predates it still
|
|
617
|
+
* renders; one that misses the floor pins its own (docs/spec/accents.md). */
|
|
618
|
+
--accent-error-ink: color-mix(in srgb, var(--accent-error) 70%, var(--ink));
|
|
584
619
|
}
|
|
585
620
|
|
|
586
621
|
/* Geometry. A `[data-brand]` scope is a theme root of its own, so it restarts
|
|
@@ -729,8 +764,10 @@
|
|
|
729
764
|
--accent-warn: #f2c94c;
|
|
730
765
|
--on-accent-warn: #070d18;
|
|
731
766
|
/* lifted off the brand red #da251d, which failed AA on a card. This one value
|
|
732
|
-
*
|
|
733
|
-
*
|
|
767
|
+
* is the fill and the text on a bare surface; on its own 10 % tint (a
|
|
768
|
+
* destructive menu row) the text is the derived --accent-error-ink, which
|
|
769
|
+
* EV leaves to the contract: #ec8379, 4.83:1 on that tint over popover. The
|
|
770
|
+
* vivid brand red is a gitops constant, not a UI token. */
|
|
734
771
|
--accent-error: #ef5b52;
|
|
735
772
|
--on-accent-error: #070d18;
|
|
736
773
|
|