@entifix/react-controls 0.18.0 → 0.18.1
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/dist/ui/atoms/external-link/external-link.d.ts +1 -1
- package/dist/ui/atoms/external-link/external-link.d.ts.map +1 -1
- package/dist/ui/atoms/external-link/external-link.js +7 -2
- package/dist/ui/atoms/external-link/external-link.js.map +1 -1
- package/dist/ui/molecules/nav-menu/nav-menu.d.ts.map +1 -1
- package/dist/ui/molecules/nav-menu/nav-menu.js +4 -1
- package/dist/ui/molecules/nav-menu/nav-menu.js.map +1 -1
- package/dist/ui/molecules/segmented-control/segmented-control.d.ts.map +1 -1
- package/dist/ui/molecules/segmented-control/segmented-control.js +6 -3
- package/dist/ui/molecules/segmented-control/segmented-control.js.map +1 -1
- package/dist/ui/molecules/segmented-nav/segmented-nav.d.ts.map +1 -1
- package/dist/ui/molecules/segmented-nav/segmented-nav.js +9 -1
- package/dist/ui/molecules/segmented-nav/segmented-nav.js.map +1 -1
- package/dist/ui/molecules/split-view/split-view.d.ts.map +1 -1
- package/dist/ui/molecules/split-view/split-view.js +6 -3
- package/dist/ui/molecules/split-view/split-view.js.map +1 -1
- package/dist/ui/story-support/descriptors.d.ts +9 -0
- package/dist/ui/story-support/descriptors.d.ts.map +1 -0
- package/dist/ui/story-support/descriptors.js +27 -0
- package/dist/ui/story-support/descriptors.js.map +1 -0
- package/package.json +19 -5
|
@@ -3,7 +3,7 @@ import type { ComponentPropsWithoutRef } from 'react';
|
|
|
3
3
|
* A link that leaves the site: a new tab, with no `window.opener` and no
|
|
4
4
|
* referrer handed to the other side.
|
|
5
5
|
*/
|
|
6
|
-
export declare function ExternalLink(props: ComponentPropsWithoutRef<'a'> & {
|
|
6
|
+
export declare function ExternalLink({ children, ...props }: ComponentPropsWithoutRef<'a'> & {
|
|
7
7
|
href: string;
|
|
8
8
|
}): import("react").JSX.Element;
|
|
9
9
|
//# sourceMappingURL=external-link.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-link.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/external-link/external-link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"external-link.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/external-link/external-link.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,wBAAwB,CAAC,GAAG,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,+BAMlD"}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
2
3
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
4
|
/**
|
|
4
5
|
* A link that leaves the site: a new tab, with no `window.opener` and no
|
|
5
6
|
* referrer handed to the other side.
|
|
6
|
-
*/ export function ExternalLink(
|
|
7
|
+
*/ export function ExternalLink(_0) {
|
|
8
|
+
let { children } = _0, props = _object_without_properties_loose(_0, [
|
|
9
|
+
"children"
|
|
10
|
+
]);
|
|
7
11
|
return /*#__PURE__*/ _jsx("a", _extends({}, props, {
|
|
8
12
|
target: "_blank",
|
|
9
|
-
rel: "noopener noreferrer"
|
|
13
|
+
rel: "noopener noreferrer",
|
|
14
|
+
children: children
|
|
10
15
|
}));
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/atoms/external-link/external-link.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef } from 'react';\n\n/**\n * A link that leaves the site: a new tab, with no `window.opener` and no\n * referrer handed to the other side.\n */\nexport function ExternalLink(\n props: ComponentPropsWithoutRef<'a'> & { href: string }
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/atoms/external-link/external-link.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef } from 'react';\n\n/**\n * A link that leaves the site: a new tab, with no `window.opener` and no\n * referrer handed to the other side.\n */\nexport function ExternalLink({\n children,\n ...props\n}: ComponentPropsWithoutRef<'a'> & { href: string }) {\n return (\n <a {...props} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n}\n"],"names":["ExternalLink","children","props","a","target","rel"],"mappings":";;;AAEA;;;CAGC,GACD,OAAO,SAASA,aAAa,EAGsB;QAHtB,EAC3BC,QAAQ,EAEyC,OAD9CC;;;IAEH,qBACE,KAACC,kBAAMD;QAAOE,QAAO;QAASC,KAAI;kBAC/BJ;;AAGP"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav-menu.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/nav-menu/nav-menu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,OAAc,EACd,SAAS,EACT,gBAA0B,EAC1B,QAAQ,GACT,EAAE;IACD,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,SAAS,CAAC;CACrB,+
|
|
1
|
+
{"version":3,"file":"nav-menu.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/nav-menu/nav-menu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAQvC;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,IAAI,EACJ,OAAO,EACP,OAAc,EACd,SAAS,EACT,gBAA0B,EAC1B,QAAQ,GACT,EAAE;IACD,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAiCA;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,+BAW7D"}
|
|
@@ -17,11 +17,14 @@ import { cn } from '../../utils/cn.js';
|
|
|
17
17
|
className: cn('relative', className),
|
|
18
18
|
children: [
|
|
19
19
|
/*#__PURE__*/ _jsxs("summary", {
|
|
20
|
-
"aria-label": label,
|
|
21
20
|
title: label,
|
|
22
21
|
"data-slot": "nav-menu-trigger",
|
|
23
22
|
className: triggerClassName,
|
|
24
23
|
children: [
|
|
24
|
+
/*#__PURE__*/ _jsx("span", {
|
|
25
|
+
className: "sr-only",
|
|
26
|
+
children: label
|
|
27
|
+
}),
|
|
25
28
|
icon,
|
|
26
29
|
summary,
|
|
27
30
|
chevron && /*#__PURE__*/ _jsx("svg", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/nav-menu/nav-menu.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\n/** A bar's trigger: quiet until hovered or open. */\nconst TRIGGER =\n 'focus-ring flex h-[2rem] cursor-pointer list-none items-center gap-3xs rounded-md border border-transparent px-2xs text-step-sm font-medium text-content select-none transition-[background-color,border-color] duration-(--duration-fast,150ms) hover:border-border hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] [&::-webkit-details-marker]:hidden [[open]>&]:border-border [[open]>&]:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)]';\n\n/**\n * A dropdown in a navigation bar: a native `<details>` disclosure, so it opens\n * and its links work with scripting off. What a dropdown is expected to do on\n * top — one open at a time, closed by a click elsewhere or Escape — is the\n * page's to add.\n *\n * No hooks, so a client component can render it too. Its parts carry\n * `data-slot` (`nav-menu`, `nav-menu-trigger`, `nav-menu-chevron`,\n * `nav-menu-panel`), so a page can restyle them — a panel as wide as the bar\n * on a phone, say — without knowing the classes underneath.\n */\nexport function NavMenu({\n label,\n icon,\n summary,\n chevron = true,\n className,\n triggerClassName = TRIGGER,\n children,\n}: {\n /** The accessible name of the trigger. */\n label: string;\n icon: ReactNode;\n /** What the trigger shows beside the icon, if anything. */\n summary?: ReactNode;\n /** A chevron after the trigger's content: a menu of choices has one. */\n chevron?: boolean;\n className?: string;\n /** Replaces the trigger's look: a split button draws it as its own segment. */\n triggerClassName?: string;\n children: ReactNode;\n}) {\n return (\n <details data-slot=\"nav-menu\" className={cn('relative', className)}>\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/nav-menu/nav-menu.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\n/** A bar's trigger: quiet until hovered or open. */\nconst TRIGGER =\n 'focus-ring flex h-[2rem] cursor-pointer list-none items-center gap-3xs rounded-md border border-transparent px-2xs text-step-sm font-medium text-content select-none transition-[background-color,border-color] duration-(--duration-fast,150ms) hover:border-border hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] [&::-webkit-details-marker]:hidden [[open]>&]:border-border [[open]>&]:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)]';\n\n/**\n * A dropdown in a navigation bar: a native `<details>` disclosure, so it opens\n * and its links work with scripting off. What a dropdown is expected to do on\n * top — one open at a time, closed by a click elsewhere or Escape — is the\n * page's to add.\n *\n * No hooks, so a client component can render it too. Its parts carry\n * `data-slot` (`nav-menu`, `nav-menu-trigger`, `nav-menu-chevron`,\n * `nav-menu-panel`), so a page can restyle them — a panel as wide as the bar\n * on a phone, say — without knowing the classes underneath.\n */\nexport function NavMenu({\n label,\n icon,\n summary,\n chevron = true,\n className,\n triggerClassName = TRIGGER,\n children,\n}: {\n /** The accessible name of the trigger. */\n label: string;\n icon: ReactNode;\n /** What the trigger shows beside the icon, if anything. */\n summary?: ReactNode;\n /** A chevron after the trigger's content: a menu of choices has one. */\n chevron?: boolean;\n className?: string;\n /** Replaces the trigger's look: a split button draws it as its own segment. */\n triggerClassName?: string;\n children: ReactNode;\n}) {\n return (\n <details data-slot=\"nav-menu\" className={cn('relative', className)}>\n {/* The name is the label followed by the summary, so what a sighted\n user reads on the trigger (\"EN\") is part of what is announced and\n what a voice user says (WCAG 2.5.3), rather than replaced by it. */}\n <summary\n title={label}\n data-slot=\"nav-menu-trigger\"\n className={triggerClassName}\n >\n <span className=\"sr-only\">{label}</span>\n {icon}\n {summary}\n {chevron && (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n data-slot=\"nav-menu-chevron\"\n className=\"size-[0.875rem] flex-none fill-none stroke-current stroke-2 text-content-muted transition-[rotate] duration-(--duration-fast,150ms) [stroke-linecap:round] [stroke-linejoin:round] [[open]>summary>&]:rotate-180\"\n >\n <path d=\"m7 10 5 5 5-5\" />\n </svg>\n )}\n </summary>\n <div\n data-slot=\"nav-menu-panel\"\n className=\"right-0 ease-out starting:-translate-y-1 [&_ul]:m-0 [&_ul]:p-0 absolute top-[calc(100%+var(--spacing-3xs))] z-60 min-w-[10rem] rounded-lg border border-border bg-surface-elevated p-3xs shadow-[var(--shadow-overlay)] transition-[opacity,translate] duration-[120ms] motion-reduce:transition-none starting:opacity-0 [&_ul]:list-none\"\n >\n {children}\n </div>\n </details>\n );\n}\n\n/** A check beside the chosen entry; an equal space beside the others. */\nexport function NavMenuCheck({ checked }: { checked: boolean }) {\n return (\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 24 24\"\n data-slot=\"nav-menu-check\"\n className=\"size-[1rem] flex-none fill-none stroke-current stroke-2 [stroke-linecap:round] [stroke-linejoin:round]\"\n >\n {checked && <path d=\"m5 12.5 4.5 4.5L19 7.5\" />}\n </svg>\n );\n}\n"],"names":["cn","TRIGGER","NavMenu","label","icon","summary","chevron","className","triggerClassName","children","details","data-slot","title","span","svg","aria-hidden","viewBox","path","d","div","NavMenuCheck","checked"],"mappings":";AAEA,SAASA,EAAE,QAAQ,oBAAoB;AAEvC,kDAAkD,GAClD,MAAMC,UACJ;AAEF;;;;;;;;;;CAUC,GACD,OAAO,SAASC,QAAQ,EACtBC,KAAK,EACLC,IAAI,EACJC,OAAO,EACPC,UAAU,IAAI,EACdC,SAAS,EACTC,mBAAmBP,OAAO,EAC1BQ,QAAQ,EAaT;IACC,qBACE,MAACC;QAAQC,aAAU;QAAWJ,WAAWP,GAAG,YAAYO;;0BAItD,MAACF;gBACCO,OAAOT;gBACPQ,aAAU;gBACVJ,WAAWC;;kCAEX,KAACK;wBAAKN,WAAU;kCAAWJ;;oBAC1BC;oBACAC;oBACAC,yBACC,KAACQ;wBACCC,eAAY;wBACZC,SAAQ;wBACRL,aAAU;wBACVJ,WAAU;kCAEV,cAAA,KAACU;4BAAKC,GAAE;;;;;0BAId,KAACC;gBACCR,aAAU;gBACVJ,WAAU;0BAETE;;;;AAIT;AAEA,uEAAuE,GACvE,OAAO,SAASW,aAAa,EAAEC,OAAO,EAAwB;IAC5D,qBACE,KAACP;QACCC,eAAY;QACZC,SAAQ;QACRL,aAAU;QACVJ,WAAU;kBAETc,yBAAW,KAACJ;YAAKC,GAAE;;;AAG1B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/segmented-control/segmented-control.tsx"],"names":[],"mappings":"AAsBA;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,SAAS,MAAM,EAAE,EACnD,KAAK,EACL,OAAO,EACP,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,KAAK,EAAE,GAAG,CAAC;IACX,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+
|
|
1
|
+
{"version":3,"file":"segmented-control.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/segmented-control/segmented-control.tsx"],"names":[],"mappings":"AAsBA;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,SAAS,MAAM,EAAE,EACnD,KAAK,EACL,OAAO,EACP,KAAK,EACL,QAAQ,EACR,SAAS,GACV,EAAE;IACD,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,KAAK,EAAE,GAAG,CAAC;IACX,QAAQ,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAkDA"}
|
|
@@ -21,20 +21,22 @@ import { CURRENT, LIST, OTHER } from '../segmented-nav/segment-classes.js';
|
|
|
21
21
|
* `segmented-control-option`).
|
|
22
22
|
*/ export function SegmentedControl({ label, options, value, onChange, className }) {
|
|
23
23
|
const chosen = Math.max(0, options.findIndex((option)=>option.key === value));
|
|
24
|
+
// On each radio rather than the group: the group is not focusable, so the
|
|
25
|
+
// keys belong to the segment that has focus.
|
|
24
26
|
const move = (event)=>{
|
|
25
|
-
var
|
|
27
|
+
var _event_currentTarget_closest, _radios_next;
|
|
26
28
|
const to = MOVES[event.key];
|
|
27
29
|
if (to === undefined) return;
|
|
28
30
|
event.preventDefault();
|
|
29
31
|
const next = to(chosen, options.length);
|
|
30
|
-
(
|
|
32
|
+
const radios = (_event_currentTarget_closest = event.currentTarget.closest('[data-slot="segmented-control"]')) == null ? void 0 : _event_currentTarget_closest.querySelectorAll('[role="radio"]');
|
|
33
|
+
radios == null ? void 0 : (_radios_next = radios[next]) == null ? void 0 : _radios_next.focus();
|
|
31
34
|
onChange(options[next].key);
|
|
32
35
|
};
|
|
33
36
|
return /*#__PURE__*/ _jsx("div", {
|
|
34
37
|
role: "radiogroup",
|
|
35
38
|
"aria-label": label,
|
|
36
39
|
"data-slot": "segmented-control",
|
|
37
|
-
onKeyDown: move,
|
|
38
40
|
className: cn(LIST, className),
|
|
39
41
|
children: options.map((option, index)=>{
|
|
40
42
|
const checked = index === chosen;
|
|
@@ -43,6 +45,7 @@ import { CURRENT, LIST, OTHER } from '../segmented-nav/segment-classes.js';
|
|
|
43
45
|
role: "radio",
|
|
44
46
|
"aria-checked": checked,
|
|
45
47
|
tabIndex: checked ? 0 : -1,
|
|
48
|
+
onKeyDown: move,
|
|
46
49
|
"data-slot": "segmented-control-option",
|
|
47
50
|
className: cn(checked ? `${CURRENT} focus-ring` : `${OTHER} bg-transparent`, BUTTON),
|
|
48
51
|
onClick: ()=>onChange(option.key),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/segmented-control/segmented-control.tsx"],"sourcesContent":["'use client';\n\nimport type { KeyboardEvent } from 'react';\n\nimport { cn } from '../../utils/cn.js';\nimport { CURRENT, LIST, OTHER } from '../segmented-nav/segment-classes.js';\n\n/** A segment as a button: the shared look, without a link's defaults. */\nconst BUTTON = 'cursor-pointer border-0 font-[inherit]';\n\n/** The keys that move the choice, and where each moves it. */\nconst MOVES: Readonly<\n Record<string, (index: number, count: number) => number>\n> = {\n ArrowRight: (index, count) => (index + 1) % count,\n ArrowDown: (index, count) => (index + 1) % count,\n ArrowLeft: (index, count) => (index - 1 + count) % count,\n ArrowUp: (index, count) => (index - 1 + count) % count,\n Home: () => 0,\n End: (_, count) => count - 1,\n};\n\n/**\n * A choice within the page, drawn as `SegmentedNav` draws a choice between\n * pages: one bordered track, the chosen segment filled.\n *\n * A radio group: one segment in the tab order, the arrow keys, Home and End\n * moving the choice and the focus with it. It holds no state; the page keeps\n * the value. Parts carry `data-slot` (`segmented-control`,\n * `segmented-control-option`).\n */\nexport function SegmentedControl<Key extends string>({\n label,\n options,\n value,\n onChange,\n className,\n}: {\n /** The group's accessible name. */\n label: string;\n options: readonly { readonly key: Key; readonly label: string }[];\n value: Key;\n onChange: (key: Key) => void;\n className?: string;\n}) {\n const chosen = Math.max(\n 0,\n options.findIndex(option => option.key === value),\n );\n\n const move = (event: KeyboardEvent<
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/segmented-control/segmented-control.tsx"],"sourcesContent":["'use client';\n\nimport type { KeyboardEvent } from 'react';\n\nimport { cn } from '../../utils/cn.js';\nimport { CURRENT, LIST, OTHER } from '../segmented-nav/segment-classes.js';\n\n/** A segment as a button: the shared look, without a link's defaults. */\nconst BUTTON = 'cursor-pointer border-0 font-[inherit]';\n\n/** The keys that move the choice, and where each moves it. */\nconst MOVES: Readonly<\n Record<string, (index: number, count: number) => number>\n> = {\n ArrowRight: (index, count) => (index + 1) % count,\n ArrowDown: (index, count) => (index + 1) % count,\n ArrowLeft: (index, count) => (index - 1 + count) % count,\n ArrowUp: (index, count) => (index - 1 + count) % count,\n Home: () => 0,\n End: (_, count) => count - 1,\n};\n\n/**\n * A choice within the page, drawn as `SegmentedNav` draws a choice between\n * pages: one bordered track, the chosen segment filled.\n *\n * A radio group: one segment in the tab order, the arrow keys, Home and End\n * moving the choice and the focus with it. It holds no state; the page keeps\n * the value. Parts carry `data-slot` (`segmented-control`,\n * `segmented-control-option`).\n */\nexport function SegmentedControl<Key extends string>({\n label,\n options,\n value,\n onChange,\n className,\n}: {\n /** The group's accessible name. */\n label: string;\n options: readonly { readonly key: Key; readonly label: string }[];\n value: Key;\n onChange: (key: Key) => void;\n className?: string;\n}) {\n const chosen = Math.max(\n 0,\n options.findIndex(option => option.key === value),\n );\n\n // On each radio rather than the group: the group is not focusable, so the\n // keys belong to the segment that has focus.\n const move = (event: KeyboardEvent<HTMLButtonElement>) => {\n const to = MOVES[event.key];\n if (to === undefined) return;\n event.preventDefault();\n const next = to(chosen, options.length);\n const radios = event.currentTarget\n .closest('[data-slot=\"segmented-control\"]')\n ?.querySelectorAll<HTMLElement>('[role=\"radio\"]');\n radios?.[next]?.focus();\n onChange((options[next] as (typeof options)[number]).key);\n };\n\n return (\n <div\n role=\"radiogroup\"\n aria-label={label}\n data-slot=\"segmented-control\"\n className={cn(LIST, className)}\n >\n {options.map((option, index) => {\n const checked = index === chosen;\n return (\n <button\n key={option.key}\n type=\"button\"\n role=\"radio\"\n aria-checked={checked}\n tabIndex={checked ? 0 : -1}\n onKeyDown={move}\n data-slot=\"segmented-control-option\"\n className={cn(\n checked ? `${CURRENT} focus-ring` : `${OTHER} bg-transparent`,\n BUTTON,\n )}\n onClick={() => onChange(option.key)}\n >\n {option.label}\n </button>\n );\n })}\n </div>\n );\n}\n"],"names":["cn","CURRENT","LIST","OTHER","BUTTON","MOVES","ArrowRight","index","count","ArrowDown","ArrowLeft","ArrowUp","Home","End","_","SegmentedControl","label","options","value","onChange","className","chosen","Math","max","findIndex","option","key","move","event","radios","to","undefined","preventDefault","next","length","currentTarget","closest","querySelectorAll","focus","div","role","aria-label","data-slot","map","checked","button","type","aria-checked","tabIndex","onKeyDown","onClick"],"mappings":"AAAA;;AAIA,SAASA,EAAE,QAAQ,oBAAoB;AACvC,SAASC,OAAO,EAAEC,IAAI,EAAEC,KAAK,QAAQ,sCAAsC;AAE3E,uEAAuE,GACvE,MAAMC,SAAS;AAEf,4DAA4D,GAC5D,MAAMC,QAEF;IACFC,YAAY,CAACC,OAAOC,QAAU,AAACD,CAAAA,QAAQ,CAAA,IAAKC;IAC5CC,WAAW,CAACF,OAAOC,QAAU,AAACD,CAAAA,QAAQ,CAAA,IAAKC;IAC3CE,WAAW,CAACH,OAAOC,QAAU,AAACD,CAAAA,QAAQ,IAAIC,KAAI,IAAKA;IACnDG,SAAS,CAACJ,OAAOC,QAAU,AAACD,CAAAA,QAAQ,IAAIC,KAAI,IAAKA;IACjDI,MAAM,IAAM;IACZC,KAAK,CAACC,GAAGN,QAAUA,QAAQ;AAC7B;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASO,iBAAqC,EACnDC,KAAK,EACLC,OAAO,EACPC,KAAK,EACLC,QAAQ,EACRC,SAAS,EAQV;IACC,MAAMC,SAASC,KAAKC,GAAG,CACrB,GACAN,QAAQO,SAAS,CAACC,CAAAA,SAAUA,OAAOC,GAAG,KAAKR;IAG7C,0EAA0E;IAC1E,6CAA6C;IAC7C,MAAMS,OAAO,CAACC;YAKGA,8BAGfC;QAPA,MAAMC,KAAKzB,KAAK,CAACuB,MAAMF,GAAG,CAAC;QAC3B,IAAII,OAAOC,WAAW;QACtBH,MAAMI,cAAc;QACpB,MAAMC,OAAOH,GAAGT,QAAQJ,QAAQiB,MAAM;QACtC,MAAML,UAASD,+BAAAA,MAAMO,aAAa,CAC/BC,OAAO,CAAC,uDADIR,6BAEXS,gBAAgB,CAAc;QAClCR,2BAAAA,eAAAA,MAAQ,CAACI,KAAK,qBAAdJ,aAAgBS,KAAK;QACrBnB,SAAS,AAACF,OAAO,CAACgB,KAAK,CAA8BP,GAAG;IAC1D;IAEA,qBACE,KAACa;QACCC,MAAK;QACLC,cAAYzB;QACZ0B,aAAU;QACVtB,WAAWpB,GAAGE,MAAMkB;kBAEnBH,QAAQ0B,GAAG,CAAC,CAAClB,QAAQlB;YACpB,MAAMqC,UAAUrC,UAAUc;YAC1B,qBACE,KAACwB;gBAECC,MAAK;gBACLN,MAAK;gBACLO,gBAAcH;gBACdI,UAAUJ,UAAU,IAAI,CAAC;gBACzBK,WAAWtB;gBACXe,aAAU;gBACVtB,WAAWpB,GACT4C,UAAU,GAAG3C,QAAQ,WAAW,CAAC,GAAG,GAAGE,MAAM,eAAe,CAAC,EAC7DC;gBAEF8C,SAAS,IAAM/B,SAASM,OAAOC,GAAG;0BAEjCD,OAAOT,KAAK;eAbRS,OAAOC,GAAG;QAgBrB;;AAGN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segmented-nav.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/segmented-nav/segmented-nav.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAItD,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"segmented-nav.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/segmented-nav/segmented-nav.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAItD,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,GAAG,aAAa,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC,CAAC;AAMH;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,KAAK,EACL,IAAI,EAAE,IAAa,EACnB,SAAS,GACV,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACnC,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BA+BA"}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
2
3
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
4
|
import { CURRENT, LIST, OTHER } from './segment-classes.js';
|
|
4
|
-
const ANCHOR = (
|
|
5
|
+
const ANCHOR = (_0)=>{
|
|
6
|
+
let { children } = _0, props = _object_without_properties_loose(_0, [
|
|
7
|
+
"children"
|
|
8
|
+
]);
|
|
9
|
+
return /*#__PURE__*/ _jsx("a", _extends({}, props, {
|
|
10
|
+
children: children
|
|
11
|
+
}));
|
|
12
|
+
};
|
|
5
13
|
/**
|
|
6
14
|
* A choice between pages, drawn as one segmented control: each segment is a
|
|
7
15
|
* link, the current page's is a marked span. A visible label names the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/segmented-nav/segmented-nav.tsx"],"sourcesContent":["import type { ComponentType, ReactNode } from 'react';\n\nimport { CURRENT, LIST, OTHER } from './segment-classes.js';\n\n/** One segment: a page of its own, the current one marked. */\nexport interface SegmentedNavItem {\n readonly key: string;\n readonly label: ReactNode;\n readonly href: string;\n readonly current: boolean;\n}\n\n/** What renders a segment that leads elsewhere: an `<a>`, or a router's link. */\nexport type SegmentedNavLink = ComponentType<{\n href: string;\n className?: string;\n children: ReactNode;\n}>;\n\nconst ANCHOR: SegmentedNavLink = props => <a {...props}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/segmented-nav/segmented-nav.tsx"],"sourcesContent":["import type { ComponentType, ReactNode } from 'react';\n\nimport { CURRENT, LIST, OTHER } from './segment-classes.js';\n\n/** One segment: a page of its own, the current one marked. */\nexport interface SegmentedNavItem {\n readonly key: string;\n readonly label: ReactNode;\n readonly href: string;\n readonly current: boolean;\n}\n\n/** What renders a segment that leads elsewhere: an `<a>`, or a router's link. */\nexport type SegmentedNavLink = ComponentType<{\n href: string;\n className?: string;\n children: ReactNode;\n}>;\n\nconst ANCHOR: SegmentedNavLink = ({ children, ...props }) => (\n <a {...props}>{children}</a>\n);\n\n/**\n * A choice between pages, drawn as one segmented control: each segment is a\n * link, the current page's is a marked span. A visible label names the\n * choice; the `<nav>` carries it for assistive technology.\n *\n * No hooks, so a server component renders it, and every segment works with\n * scripting off. Its parts carry `data-slot` (`segmented-nav`,\n * `segmented-nav-label`, `segmented-nav-list`, `segmented-nav-item`).\n */\nexport function SegmentedNav({\n label,\n items,\n link: Link = ANCHOR,\n className,\n}: {\n label: string;\n items: readonly SegmentedNavItem[];\n link?: SegmentedNavLink;\n className?: string;\n}) {\n return (\n <nav data-slot=\"segmented-nav\" aria-label={label} className={className}>\n <span\n data-slot=\"segmented-nav-label\"\n aria-hidden=\"true\"\n className=\"text-step-sm text-content-muted\"\n >\n {label}\n </span>\n <ul data-slot=\"segmented-nav-list\" className={LIST}>\n {items.map(item => (\n <li\n key={item.key}\n data-slot=\"segmented-nav-item\"\n className=\"flex-none\"\n >\n {item.current ? (\n <span aria-current=\"page\" className={CURRENT}>\n {item.label}\n </span>\n ) : (\n <Link href={item.href} className={OTHER}>\n {item.label}\n </Link>\n )}\n </li>\n ))}\n </ul>\n </nav>\n );\n}\n"],"names":["CURRENT","LIST","OTHER","ANCHOR","children","props","a","SegmentedNav","label","items","link","Link","className","nav","data-slot","aria-label","span","aria-hidden","ul","map","item","li","current","aria-current","href","key"],"mappings":";;;AAEA,SAASA,OAAO,EAAEC,IAAI,EAAEC,KAAK,QAAQ,uBAAuB;AAiB5D,MAAMC,SAA2B;QAAC,EAAEC,QAAQ,EAAY,OAAPC;;;yBAC/C,KAACC,kBAAMD;kBAAQD;;;AAGjB;;;;;;;;CAQC,GACD,OAAO,SAASG,aAAa,EAC3BC,KAAK,EACLC,KAAK,EACLC,MAAMC,OAAOR,MAAM,EACnBS,SAAS,EAMV;IACC,qBACE,MAACC;QAAIC,aAAU;QAAgBC,cAAYP;QAAOI,WAAWA;;0BAC3D,KAACI;gBACCF,aAAU;gBACVG,eAAY;gBACZL,WAAU;0BAETJ;;0BAEH,KAACU;gBAAGJ,aAAU;gBAAqBF,WAAWX;0BAC3CQ,MAAMU,GAAG,CAACC,CAAAA,qBACT,KAACC;wBAECP,aAAU;wBACVF,WAAU;kCAETQ,KAAKE,OAAO,iBACX,KAACN;4BAAKO,gBAAa;4BAAOX,WAAWZ;sCAClCoB,KAAKZ,KAAK;2CAGb,KAACG;4BAAKa,MAAMJ,KAAKI,IAAI;4BAAEZ,WAAWV;sCAC/BkB,KAAKZ,KAAK;;uBAVVY,KAAKK,GAAG;;;;AAkBzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"split-view.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/split-view/split-view.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAYD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE;IACD,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+
|
|
1
|
+
{"version":3,"file":"split-view.d.ts","sourceRoot":"","sources":["../../../../src/ui/molecules/split-view/split-view.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA6B,SAAS,EAAE,MAAM,OAAO,CAAC;AAIlE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;IAC3B,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAYD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,EACxB,KAAK,EACL,KAAK,EACL,QAAQ,EACR,QAAQ,EACR,SAAS,GACV,EAAE;IACD,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,+BAiEA"}
|
|
@@ -30,13 +30,16 @@ import { cn } from '../../utils/cn.js';
|
|
|
30
30
|
event.preventDefault();
|
|
31
31
|
onSelect(id);
|
|
32
32
|
};
|
|
33
|
+
// On each trigger rather than the list: a list is not interactive, so the
|
|
34
|
+
// keys belong to the row that has focus.
|
|
33
35
|
const move = (event)=>{
|
|
34
|
-
var
|
|
36
|
+
var _event_currentTarget_closest, _triggers_next;
|
|
35
37
|
const to = MOVES[event.key];
|
|
36
38
|
if (to === undefined) return;
|
|
37
39
|
event.preventDefault();
|
|
38
40
|
const next = to(focusable, items.length);
|
|
39
|
-
(
|
|
41
|
+
const triggers = (_event_currentTarget_closest = event.currentTarget.closest('[data-slot="split-list"]')) == null ? void 0 : _event_currentTarget_closest.querySelectorAll('[data-slot="split-trigger"]');
|
|
42
|
+
triggers == null ? void 0 : (_triggers_next = triggers[next]) == null ? void 0 : _triggers_next.focus();
|
|
40
43
|
onSelect(items[next].id);
|
|
41
44
|
};
|
|
42
45
|
return /*#__PURE__*/ _jsx("div", {
|
|
@@ -45,7 +48,6 @@ import { cn } from '../../utils/cn.js';
|
|
|
45
48
|
children: /*#__PURE__*/ _jsx("ol", {
|
|
46
49
|
"data-slot": "split-list",
|
|
47
50
|
"aria-label": label,
|
|
48
|
-
onKeyDown: move,
|
|
49
51
|
className: "m-0 p-0 lg:max-h-[28rem] lg:w-[40%] lg:overflow-y-auto list-none",
|
|
50
52
|
children: items.map((item, at)=>{
|
|
51
53
|
const chosen = at === index;
|
|
@@ -58,6 +60,7 @@ import { cn } from '../../utils/cn.js';
|
|
|
58
60
|
"aria-current": chosen ? 'true' : undefined,
|
|
59
61
|
tabIndex: at === focusable ? 0 : -1,
|
|
60
62
|
onClick: (event)=>choose(event, item.id),
|
|
63
|
+
onKeyDown: move,
|
|
61
64
|
className: "block rounded-md px-xs py-2xs text-inherit no-underline focus-ring hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] aria-[current]:bg-[color-mix(in_srgb,var(--color-primary)_14%,transparent)]",
|
|
62
65
|
children: item.head
|
|
63
66
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/molecules/split-view/split-view.tsx"],"sourcesContent":["'use client';\n\nimport type { KeyboardEvent, MouseEvent, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface SplitViewItem {\n readonly id: string;\n /** What the list shows of the item: its number and title, say. */\n readonly head: ReactNode;\n /** What the pane shows once the item is chosen. */\n readonly detail: ReactNode;\n /** Where the item lives on its own: followed without scripting. */\n readonly href: string;\n}\n\n/** The keys that move the choice, and where each moves it. */\nconst MOVES: Readonly<\n Record<string, (index: number, count: number) => number>\n> = {\n ArrowDown: (index, count) => Math.min(index + 1, count - 1),\n ArrowUp: index => Math.max(index - 1, 0),\n Home: () => 0,\n End: (_, count) => count - 1,\n};\n\n/**\n * A list beside a reading pane: the chosen item's detail shows next to the\n * list on a wide screen, and under its own row on a narrow one, where the\n * list reads as an accordion.\n *\n * One markup for both: the chosen row carries its detail. On a wide screen\n * the list is a column of fixed height that scrolls, and the detail is placed\n * against the whole view, so the list's overflow neither clips nor scrolls it.\n *\n * Each row is a link to the item's own page, which is what a visitor without\n * scripting follows. With it, a plain click chooses instead, and ↑, ↓, Home\n * and End move the choice, focus following it (one row in the tab order).\n * Parts carry `data-slot` (`split-view`, `split-list`, `split-row`,\n * `split-trigger`, `split-detail`) for a page to restyle.\n */\nexport function SplitView({\n label,\n items,\n selected,\n onSelect,\n className,\n}: {\n /** The list's accessible name. */\n label: string;\n items: readonly SplitViewItem[];\n /** The chosen item's id; an unknown one chooses nothing. */\n selected: string | undefined;\n onSelect: (id: string) => void;\n className?: string;\n}) {\n const index = items.findIndex(item => item.id === selected);\n const focusable = index === -1 ? 0 : index;\n\n const choose = (event: MouseEvent, id: string) => {\n // A new tab or window is the link's own business.\n if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey)\n return;\n event.preventDefault();\n onSelect(id);\n };\n\n const move = (event: KeyboardEvent<
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/molecules/split-view/split-view.tsx"],"sourcesContent":["'use client';\n\nimport type { KeyboardEvent, MouseEvent, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport interface SplitViewItem {\n readonly id: string;\n /** What the list shows of the item: its number and title, say. */\n readonly head: ReactNode;\n /** What the pane shows once the item is chosen. */\n readonly detail: ReactNode;\n /** Where the item lives on its own: followed without scripting. */\n readonly href: string;\n}\n\n/** The keys that move the choice, and where each moves it. */\nconst MOVES: Readonly<\n Record<string, (index: number, count: number) => number>\n> = {\n ArrowDown: (index, count) => Math.min(index + 1, count - 1),\n ArrowUp: index => Math.max(index - 1, 0),\n Home: () => 0,\n End: (_, count) => count - 1,\n};\n\n/**\n * A list beside a reading pane: the chosen item's detail shows next to the\n * list on a wide screen, and under its own row on a narrow one, where the\n * list reads as an accordion.\n *\n * One markup for both: the chosen row carries its detail. On a wide screen\n * the list is a column of fixed height that scrolls, and the detail is placed\n * against the whole view, so the list's overflow neither clips nor scrolls it.\n *\n * Each row is a link to the item's own page, which is what a visitor without\n * scripting follows. With it, a plain click chooses instead, and ↑, ↓, Home\n * and End move the choice, focus following it (one row in the tab order).\n * Parts carry `data-slot` (`split-view`, `split-list`, `split-row`,\n * `split-trigger`, `split-detail`) for a page to restyle.\n */\nexport function SplitView({\n label,\n items,\n selected,\n onSelect,\n className,\n}: {\n /** The list's accessible name. */\n label: string;\n items: readonly SplitViewItem[];\n /** The chosen item's id; an unknown one chooses nothing. */\n selected: string | undefined;\n onSelect: (id: string) => void;\n className?: string;\n}) {\n const index = items.findIndex(item => item.id === selected);\n const focusable = index === -1 ? 0 : index;\n\n const choose = (event: MouseEvent, id: string) => {\n // A new tab or window is the link's own business.\n if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey)\n return;\n event.preventDefault();\n onSelect(id);\n };\n\n // On each trigger rather than the list: a list is not interactive, so the\n // keys belong to the row that has focus.\n const move = (event: KeyboardEvent<HTMLAnchorElement>) => {\n const to = MOVES[event.key];\n if (to === undefined) return;\n event.preventDefault();\n const next = to(focusable, items.length);\n const triggers = event.currentTarget\n .closest('[data-slot=\"split-list\"]')\n ?.querySelectorAll<HTMLElement>('[data-slot=\"split-trigger\"]');\n triggers?.[next]?.focus();\n onSelect((items[next] as SplitViewItem).id);\n };\n\n return (\n <div\n data-slot=\"split-view\"\n className={cn('lg:min-h-[28rem] relative', className)}\n >\n <ol\n data-slot=\"split-list\"\n aria-label={label}\n className=\"m-0 p-0 lg:max-h-[28rem] lg:w-[40%] lg:overflow-y-auto list-none\"\n >\n {items.map((item, at) => {\n const chosen = at === index;\n return (\n <li key={item.id} data-slot=\"split-row\">\n <a\n href={item.href}\n data-slot=\"split-trigger\"\n aria-current={chosen ? 'true' : undefined}\n tabIndex={at === focusable ? 0 : -1}\n onClick={event => choose(event, item.id)}\n onKeyDown={move}\n className=\"block rounded-md px-xs py-2xs text-inherit no-underline focus-ring hover:bg-[color-mix(in_srgb,var(--color-content)_6%,transparent)] aria-[current]:bg-[color-mix(in_srgb,var(--color-primary)_14%,transparent)]\"\n >\n {item.head}\n </a>\n {chosen && (\n <div\n data-slot=\"split-detail\"\n className=\"lg:absolute lg:top-0 lg:right-0 lg:max-h-[28rem] lg:w-[58%] lg:overflow-y-auto lg:p-0 px-xs py-s\"\n >\n {item.detail}\n </div>\n )}\n </li>\n );\n })}\n </ol>\n </div>\n );\n}\n"],"names":["cn","MOVES","ArrowDown","index","count","Math","min","ArrowUp","max","Home","End","_","SplitView","label","items","selected","onSelect","className","findIndex","item","id","focusable","choose","event","button","metaKey","ctrlKey","shiftKey","preventDefault","move","triggers","to","key","undefined","next","length","currentTarget","closest","querySelectorAll","focus","div","data-slot","ol","aria-label","map","at","chosen","li","a","href","aria-current","tabIndex","onClick","onKeyDown","head","detail"],"mappings":"AAAA;;AAIA,SAASA,EAAE,QAAQ,oBAAoB;AAYvC,4DAA4D,GAC5D,MAAMC,QAEF;IACFC,WAAW,CAACC,OAAOC,QAAUC,KAAKC,GAAG,CAACH,QAAQ,GAAGC,QAAQ;IACzDG,SAASJ,CAAAA,QAASE,KAAKG,GAAG,CAACL,QAAQ,GAAG;IACtCM,MAAM,IAAM;IACZC,KAAK,CAACC,GAAGP,QAAUA,QAAQ;AAC7B;AAEA;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASQ,UAAU,EACxBC,KAAK,EACLC,KAAK,EACLC,QAAQ,EACRC,QAAQ,EACRC,SAAS,EASV;IACC,MAAMd,QAAQW,MAAMI,SAAS,CAACC,CAAAA,OAAQA,KAAKC,EAAE,KAAKL;IAClD,MAAMM,YAAYlB,UAAU,CAAC,IAAI,IAAIA;IAErC,MAAMmB,SAAS,CAACC,OAAmBH;QACjC,kDAAkD;QAClD,IAAIG,MAAMC,MAAM,KAAK,KAAKD,MAAME,OAAO,IAAIF,MAAMG,OAAO,IAAIH,MAAMI,QAAQ,EACxE;QACFJ,MAAMK,cAAc;QACpBZ,SAASI;IACX;IAEA,0EAA0E;IAC1E,yCAAyC;IACzC,MAAMS,OAAO,CAACN;YAKKA,8BAGjBO;QAPA,MAAMC,KAAK9B,KAAK,CAACsB,MAAMS,GAAG,CAAC;QAC3B,IAAID,OAAOE,WAAW;QACtBV,MAAMK,cAAc;QACpB,MAAMM,OAAOH,GAAGV,WAAWP,MAAMqB,MAAM;QACvC,MAAML,YAAWP,+BAAAA,MAAMa,aAAa,CACjCC,OAAO,CAAC,gDADMd,6BAEbe,gBAAgB,CAAc;QAClCR,6BAAAA,iBAAAA,QAAU,CAACI,KAAK,qBAAhBJ,eAAkBS,KAAK;QACvBvB,SAAS,AAACF,KAAK,CAACoB,KAAK,CAAmBd,EAAE;IAC5C;IAEA,qBACE,KAACoB;QACCC,aAAU;QACVxB,WAAWjB,GAAG,6BAA6BiB;kBAE3C,cAAA,KAACyB;YACCD,aAAU;YACVE,cAAY9B;YACZI,WAAU;sBAETH,MAAM8B,GAAG,CAAC,CAACzB,MAAM0B;gBAChB,MAAMC,SAASD,OAAO1C;gBACtB,qBACE,MAAC4C;oBAAiBN,aAAU;;sCAC1B,KAACO;4BACCC,MAAM9B,KAAK8B,IAAI;4BACfR,aAAU;4BACVS,gBAAcJ,SAAS,SAASb;4BAChCkB,UAAUN,OAAOxB,YAAY,IAAI,CAAC;4BAClC+B,SAAS7B,CAAAA,QAASD,OAAOC,OAAOJ,KAAKC,EAAE;4BACvCiC,WAAWxB;4BACXZ,WAAU;sCAETE,KAAKmC,IAAI;;wBAEXR,wBACC,KAACN;4BACCC,aAAU;4BACVxB,WAAU;sCAETE,KAAKoC,MAAM;;;mBAjBTpC,KAAKC,EAAE;YAsBpB;;;AAIR"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EntityFieldDescriptor, MetaAccessorType } from '@entifix/core';
|
|
2
|
+
/**
|
|
3
|
+
* A field descriptor as the metadata layer reports one, for stories of the
|
|
4
|
+
* controls that take descriptors rather than an entity class.
|
|
5
|
+
*/
|
|
6
|
+
export declare const descriptor: (name: string, label: string, type?: MetaAccessorType, extra?: Partial<EntityFieldDescriptor>) => EntityFieldDescriptor;
|
|
7
|
+
/** The members of the widget the table and form stories also use. */
|
|
8
|
+
export declare const WIDGET_FIELDS: readonly EntityFieldDescriptor[];
|
|
9
|
+
//# sourceMappingURL=descriptors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"descriptors.d.ts","sourceRoot":"","sources":["../../../src/ui/story-support/descriptors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAE7E;;;GAGG;AACH,eAAO,MAAM,UAAU,GACrB,MAAM,MAAM,EACZ,OAAO,MAAM,EACb,OAAM,gBAA2B,EACjC,QAAO,OAAO,CAAC,qBAAqB,CAAM,KACzC,qBAeD,CAAC;AAEH,qEAAqE;AACrE,eAAO,MAAM,aAAa,EAAE,SAAS,qBAAqB,EAKzD,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* A field descriptor as the metadata layer reports one, for stories of the
|
|
4
|
+
* controls that take descriptors rather than an entity class.
|
|
5
|
+
*/ export const descriptor = (name, label, type = 'string', extra = {})=>_extends({
|
|
6
|
+
name,
|
|
7
|
+
key: name,
|
|
8
|
+
label,
|
|
9
|
+
type,
|
|
10
|
+
sortable: true,
|
|
11
|
+
filterable: true,
|
|
12
|
+
order: 0,
|
|
13
|
+
readonly: false,
|
|
14
|
+
required: false,
|
|
15
|
+
linkLabelProperty: 'name',
|
|
16
|
+
linkSearchProperty: 'name',
|
|
17
|
+
linkSerialization: 'id',
|
|
18
|
+
resetOnClone: false
|
|
19
|
+
}, extra);
|
|
20
|
+
/** The members of the widget the table and form stories also use. */ export const WIDGET_FIELDS = [
|
|
21
|
+
descriptor('name', 'Name'),
|
|
22
|
+
descriptor('stock', 'Units in stock', 'number'),
|
|
23
|
+
descriptor('active', 'Active', 'boolean'),
|
|
24
|
+
descriptor('releasedAt', 'Released', 'date')
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
//# sourceMappingURL=descriptors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/story-support/descriptors.ts"],"sourcesContent":["import type { EntityFieldDescriptor, MetaAccessorType } from '@entifix/core';\n\n/**\n * A field descriptor as the metadata layer reports one, for stories of the\n * controls that take descriptors rather than an entity class.\n */\nexport const descriptor = (\n name: string,\n label: string,\n type: MetaAccessorType = 'string',\n extra: Partial<EntityFieldDescriptor> = {},\n): EntityFieldDescriptor => ({\n name,\n key: name,\n label,\n type,\n sortable: true,\n filterable: true,\n order: 0,\n readonly: false,\n required: false,\n linkLabelProperty: 'name',\n linkSearchProperty: 'name',\n linkSerialization: 'id',\n resetOnClone: false,\n ...extra,\n});\n\n/** The members of the widget the table and form stories also use. */\nexport const WIDGET_FIELDS: readonly EntityFieldDescriptor[] = [\n descriptor('name', 'Name'),\n descriptor('stock', 'Units in stock', 'number'),\n descriptor('active', 'Active', 'boolean'),\n descriptor('releasedAt', 'Released', 'date'),\n];\n"],"names":["descriptor","name","label","type","extra","key","sortable","filterable","order","readonly","required","linkLabelProperty","linkSearchProperty","linkSerialization","resetOnClone","WIDGET_FIELDS"],"mappings":";AAEA;;;CAGC,GACD,OAAO,MAAMA,aAAa,CACxBC,MACAC,OACAC,OAAyB,QAAQ,EACjCC,QAAwC,CAAC,CAAC,GACf;QAC3BH;QACAI,KAAKJ;QACLC;QACAC;QACAG,UAAU;QACVC,YAAY;QACZC,OAAO;QACPC,UAAU;QACVC,UAAU;QACVC,mBAAmB;QACnBC,oBAAoB;QACpBC,mBAAmB;QACnBC,cAAc;OACXV,OACF;AAEH,mEAAmE,GACnE,OAAO,MAAMW,gBAAkD;IAC7Df,WAAW,QAAQ;IACnBA,WAAW,SAAS,kBAAkB;IACtCA,WAAW,UAAU,UAAU;IAC/BA,WAAW,cAAc,YAAY;CACtC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entifix/react-controls",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "Entity-agnostic React UI — table, form, filters, wizard, command palette — rendered from entity metadata, plus layout primitives.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"@headlessui/react": "^2.2.10",
|
|
20
20
|
"class-variance-authority": "^0.7.1",
|
|
21
21
|
"idb": "^8.0.4",
|
|
22
|
-
"@entifix/core": "0.18.
|
|
22
|
+
"@entifix/core": "0.18.1"
|
|
23
23
|
},
|
|
24
24
|
"exports": {
|
|
25
25
|
"./package.json": "./package.json",
|
|
@@ -45,16 +45,18 @@
|
|
|
45
45
|
"LICENSE"
|
|
46
46
|
],
|
|
47
47
|
"devDependencies": {
|
|
48
|
+
"@storybook/addon-a11y": "^10.6.5",
|
|
48
49
|
"@storybook/addon-docs": "^10.6.5",
|
|
49
50
|
"@storybook/addon-themes": "^10.6.5",
|
|
51
|
+
"@storybook/addon-vitest": "^10.6.5",
|
|
50
52
|
"@storybook/react-vite": "^10.6.5",
|
|
51
53
|
"effect": "4.0.7",
|
|
52
54
|
"i18next": "26.6.0",
|
|
53
55
|
"react": "19.3.1",
|
|
54
56
|
"react-i18next": "17.1.10",
|
|
55
57
|
"storybook": "^10.6.5",
|
|
56
|
-
"@entifix/i18n": "0.18.
|
|
57
|
-
"@entifix/style": "0.18.
|
|
58
|
+
"@entifix/i18n": "0.18.1",
|
|
59
|
+
"@entifix/style": "0.18.1"
|
|
58
60
|
},
|
|
59
61
|
"nx": {
|
|
60
62
|
"tags": [
|
|
@@ -89,6 +91,18 @@
|
|
|
89
91
|
"outputs": [
|
|
90
92
|
"{projectRoot}/storybook-static"
|
|
91
93
|
]
|
|
94
|
+
},
|
|
95
|
+
"test-a11y": {
|
|
96
|
+
"command": "vitest run -c vitest.storybook.config.mts",
|
|
97
|
+
"options": {
|
|
98
|
+
"cwd": "{projectRoot}"
|
|
99
|
+
},
|
|
100
|
+
"cache": true,
|
|
101
|
+
"inputs": [
|
|
102
|
+
"default",
|
|
103
|
+
"^default",
|
|
104
|
+
"{workspaceRoot}/vitest.shared.mts"
|
|
105
|
+
]
|
|
92
106
|
}
|
|
93
107
|
}
|
|
94
108
|
},
|
|
@@ -97,7 +111,7 @@
|
|
|
97
111
|
"i18next": ">=26 <27",
|
|
98
112
|
"react": ">=19 <20",
|
|
99
113
|
"react-i18next": ">=17 <18",
|
|
100
|
-
"@entifix/i18n": "0.18.
|
|
114
|
+
"@entifix/i18n": "0.18.1"
|
|
101
115
|
},
|
|
102
116
|
"peerDependenciesMeta": {
|
|
103
117
|
"@entifix/i18n": {
|