@misoto22/design 0.3.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/LICENSE +21 -0
- package/dist/components/Accordion/Accordion.d.ts +34 -0
- package/dist/components/Accordion/Accordion.js +37 -0
- package/dist/components/Accordion/Accordion.js.map +1 -0
- package/dist/components/Alert/Alert.d.ts +32 -0
- package/dist/components/Alert/Alert.js +49 -0
- package/dist/components/Alert/Alert.js.map +1 -0
- package/dist/components/AppShell/AppShell.d.ts +38 -0
- package/dist/components/AppShell/AppShell.js +92 -0
- package/dist/components/AppShell/AppShell.js.map +1 -0
- package/dist/components/Avatar/Avatar.d.ts +29 -0
- package/dist/components/Avatar/Avatar.js +39 -0
- package/dist/components/Avatar/Avatar.js.map +1 -0
- package/dist/components/Badge/Badge.d.ts +30 -0
- package/dist/components/Badge/Badge.js +29 -0
- package/dist/components/Badge/Badge.js.map +1 -0
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +31 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +31 -0
- package/dist/components/Breadcrumb/Breadcrumb.js.map +1 -0
- package/dist/components/Button/Button.d.ts +70 -0
- package/dist/components/Button/Button.js +81 -0
- package/dist/components/Button/Button.js.map +1 -0
- package/dist/components/Calendar/Calendar.d.ts +28 -0
- package/dist/components/Calendar/Calendar.js +87 -0
- package/dist/components/Calendar/Calendar.js.map +1 -0
- package/dist/components/Card/Card.d.ts +49 -0
- package/dist/components/Card/Card.js +64 -0
- package/dist/components/Card/Card.js.map +1 -0
- package/dist/components/Checkbox/Checkbox.d.ts +23 -0
- package/dist/components/Checkbox/Checkbox.js +24 -0
- package/dist/components/Checkbox/Checkbox.js.map +1 -0
- package/dist/components/Collapsible/Collapsible.d.ts +31 -0
- package/dist/components/Collapsible/Collapsible.js +35 -0
- package/dist/components/Collapsible/Collapsible.js.map +1 -0
- package/dist/components/Combobox/Combobox.d.ts +63 -0
- package/dist/components/Combobox/Combobox.js +151 -0
- package/dist/components/Combobox/Combobox.js.map +1 -0
- package/dist/components/Command/Command.d.ts +98 -0
- package/dist/components/Command/Command.js +166 -0
- package/dist/components/Command/Command.js.map +1 -0
- package/dist/components/ContextMenu/ContextMenu.d.ts +38 -0
- package/dist/components/ContextMenu/ContextMenu.js +79 -0
- package/dist/components/ContextMenu/ContextMenu.js.map +1 -0
- package/dist/components/DatePicker/DatePicker.d.ts +98 -0
- package/dist/components/DatePicker/DatePicker.js +214 -0
- package/dist/components/DatePicker/DatePicker.js.map +1 -0
- package/dist/components/Dialog/Dialog.d.ts +48 -0
- package/dist/components/Dialog/Dialog.js +72 -0
- package/dist/components/Dialog/Dialog.js.map +1 -0
- package/dist/components/DropdownMenu/DropdownMenu.d.ts +49 -0
- package/dist/components/DropdownMenu/DropdownMenu.js +89 -0
- package/dist/components/DropdownMenu/DropdownMenu.js.map +1 -0
- package/dist/components/EmptyState/EmptyState.d.ts +31 -0
- package/dist/components/EmptyState/EmptyState.js +30 -0
- package/dist/components/EmptyState/EmptyState.js.map +1 -0
- package/dist/components/ErrorState/ErrorState.d.ts +34 -0
- package/dist/components/ErrorState/ErrorState.js +32 -0
- package/dist/components/ErrorState/ErrorState.js.map +1 -0
- package/dist/components/Field/Field.d.ts +44 -0
- package/dist/components/Field/Field.js +54 -0
- package/dist/components/Field/Field.js.map +1 -0
- package/dist/components/FigureBand/FigureBand.d.ts +51 -0
- package/dist/components/FigureBand/FigureBand.js +52 -0
- package/dist/components/FigureBand/FigureBand.js.map +1 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.d.ts +29 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js +32 -0
- package/dist/components/FloatingIconButton/FloatingIconButton.js.map +1 -0
- package/dist/components/Input/Input.d.ts +22 -0
- package/dist/components/Input/Input.js +21 -0
- package/dist/components/Input/Input.js.map +1 -0
- package/dist/components/Kbd/Kbd.d.ts +20 -0
- package/dist/components/Kbd/Kbd.js +21 -0
- package/dist/components/Kbd/Kbd.js.map +1 -0
- package/dist/components/LinkArrow/LinkArrow.d.ts +24 -0
- package/dist/components/LinkArrow/LinkArrow.js +24 -0
- package/dist/components/LinkArrow/LinkArrow.js.map +1 -0
- package/dist/components/NativeSelect/NativeSelect.d.ts +27 -0
- package/dist/components/NativeSelect/NativeSelect.js +46 -0
- package/dist/components/NativeSelect/NativeSelect.js.map +1 -0
- package/dist/components/NavItem/NavItem.d.ts +39 -0
- package/dist/components/NavItem/NavItem.js +31 -0
- package/dist/components/NavItem/NavItem.js.map +1 -0
- package/dist/components/Pagination/Pagination.d.ts +44 -0
- package/dist/components/Pagination/Pagination.js +109 -0
- package/dist/components/Pagination/Pagination.js.map +1 -0
- package/dist/components/Popover/Popover.d.ts +39 -0
- package/dist/components/Popover/Popover.js +57 -0
- package/dist/components/Popover/Popover.js.map +1 -0
- package/dist/components/Progress/Progress.d.ts +31 -0
- package/dist/components/Progress/Progress.js +45 -0
- package/dist/components/Progress/Progress.js.map +1 -0
- package/dist/components/RadioGroup/RadioGroup.d.ts +43 -0
- package/dist/components/RadioGroup/RadioGroup.js +43 -0
- package/dist/components/RadioGroup/RadioGroup.js.map +1 -0
- package/dist/components/ScrollArea/ScrollArea.d.ts +31 -0
- package/dist/components/ScrollArea/ScrollArea.js +54 -0
- package/dist/components/ScrollArea/ScrollArea.js.map +1 -0
- package/dist/components/SearchableMenu/SearchableMenu.d.ts +59 -0
- package/dist/components/SearchableMenu/SearchableMenu.js +79 -0
- package/dist/components/SearchableMenu/SearchableMenu.js.map +1 -0
- package/dist/components/Select/Select.d.ts +71 -0
- package/dist/components/Select/Select.js +114 -0
- package/dist/components/Select/Select.js.map +1 -0
- package/dist/components/Separator/Separator.d.ts +30 -0
- package/dist/components/Separator/Separator.js +35 -0
- package/dist/components/Separator/Separator.js.map +1 -0
- package/dist/components/Sheet/Sheet.d.ts +38 -0
- package/dist/components/Sheet/Sheet.js +68 -0
- package/dist/components/Sheet/Sheet.js.map +1 -0
- package/dist/components/Skeleton/Skeleton.d.ts +63 -0
- package/dist/components/Skeleton/Skeleton.js +44 -0
- package/dist/components/Skeleton/Skeleton.js.map +1 -0
- package/dist/components/Slider/Slider.d.ts +35 -0
- package/dist/components/Slider/Slider.js +52 -0
- package/dist/components/Slider/Slider.js.map +1 -0
- package/dist/components/Spinner/Spinner.d.ts +50 -0
- package/dist/components/Spinner/Spinner.js +48 -0
- package/dist/components/Spinner/Spinner.js.map +1 -0
- package/dist/components/StatusDot/StatusDot.d.ts +29 -0
- package/dist/components/StatusDot/StatusDot.js +44 -0
- package/dist/components/StatusDot/StatusDot.js.map +1 -0
- package/dist/components/StatusPill/StatusPill.d.ts +25 -0
- package/dist/components/StatusPill/StatusPill.js +31 -0
- package/dist/components/StatusPill/StatusPill.js.map +1 -0
- package/dist/components/Switch/Switch.d.ts +23 -0
- package/dist/components/Switch/Switch.js +23 -0
- package/dist/components/Switch/Switch.js.map +1 -0
- package/dist/components/Table/Table.d.ts +96 -0
- package/dist/components/Table/Table.js +136 -0
- package/dist/components/Table/Table.js.map +1 -0
- package/dist/components/Tabs/Tabs.d.ts +35 -0
- package/dist/components/Tabs/Tabs.js +48 -0
- package/dist/components/Tabs/Tabs.js.map +1 -0
- package/dist/components/Tag/Tag.d.ts +28 -0
- package/dist/components/Tag/Tag.js +22 -0
- package/dist/components/Tag/Tag.js.map +1 -0
- package/dist/components/Textarea/Textarea.d.ts +20 -0
- package/dist/components/Textarea/Textarea.js +32 -0
- package/dist/components/Textarea/Textarea.js.map +1 -0
- package/dist/components/Toast/Toast.d.ts +24 -0
- package/dist/components/Toast/Toast.js +41 -0
- package/dist/components/Toast/Toast.js.map +1 -0
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +41 -0
- package/dist/components/ToggleGroup/ToggleGroup.js +78 -0
- package/dist/components/ToggleGroup/ToggleGroup.js.map +1 -0
- package/dist/components/Tooltip/Tooltip.d.ts +43 -0
- package/dist/components/Tooltip/Tooltip.js +40 -0
- package/dist/components/Tooltip/Tooltip.js.map +1 -0
- package/dist/fonts/hanken-grotesk-300.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-400.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-500.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-600.woff2 +0 -0
- package/dist/fonts/hanken-grotesk-700.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-400.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-500.woff2 +0 -0
- package/dist/fonts/ibm-plex-mono-600.woff2 +0 -0
- package/dist/fonts/newsreader-300.woff2 +0 -0
- package/dist/fonts/newsreader-400.woff2 +0 -0
- package/dist/fonts/newsreader-500.woff2 +0 -0
- package/dist/fonts/newsreader-600.woff2 +0 -0
- package/dist/fonts.css +96 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/keyframes.css +212 -0
- package/dist/lib/cn.d.ts +21 -0
- package/dist/lib/cn.js +9 -0
- package/dist/lib/cn.js.map +1 -0
- package/dist/lib/control.d.ts +31 -0
- package/dist/lib/control.js +14 -0
- package/dist/lib/control.js.map +1 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/overlay-container.d.ts +38 -0
- package/dist/lib/overlay-container.js +15 -0
- package/dist/lib/overlay-container.js.map +1 -0
- package/dist/lib/useSelectionIndicator.d.ts +29 -0
- package/dist/lib/useSelectionIndicator.js +45 -0
- package/dist/lib/useSelectionIndicator.js.map +1 -0
- package/dist/semantic.css +127 -0
- package/dist/styles.css +98 -0
- package/dist/themes.css +115 -0
- package/dist/tokens/brand.d.ts +47 -0
- package/dist/tokens/brand.js +38 -0
- package/dist/tokens/brand.js.map +1 -0
- package/dist/tokens.css +323 -0
- package/dist/tokens.d.ts +153 -0
- package/dist/tokens.js +766 -0
- package/dist/tokens.json +765 -0
- package/package.json +130 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ChevronDown } from "lucide-react";
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
|
+
import { CONTROL_BASE, CONTROL_BORDER, isInvalid } from "../../lib/control.js";
|
|
5
|
+
function NativeSelect({
|
|
6
|
+
invalid,
|
|
7
|
+
className,
|
|
8
|
+
ref,
|
|
9
|
+
children,
|
|
10
|
+
"aria-invalid": ariaInvalid,
|
|
11
|
+
...rest
|
|
12
|
+
}) {
|
|
13
|
+
const bad = isInvalid(invalid, ariaInvalid);
|
|
14
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
15
|
+
/* @__PURE__ */ jsx(
|
|
16
|
+
"select",
|
|
17
|
+
{
|
|
18
|
+
ref,
|
|
19
|
+
"aria-invalid": ariaInvalid ?? (invalid ? true : void 0),
|
|
20
|
+
className: cn(
|
|
21
|
+
CONTROL_BASE,
|
|
22
|
+
"cursor-pointer appearance-none pe-9",
|
|
23
|
+
bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,
|
|
24
|
+
className
|
|
25
|
+
),
|
|
26
|
+
...rest,
|
|
27
|
+
children
|
|
28
|
+
}
|
|
29
|
+
),
|
|
30
|
+
/* @__PURE__ */ jsx(
|
|
31
|
+
ChevronDown,
|
|
32
|
+
{
|
|
33
|
+
size: 16,
|
|
34
|
+
strokeWidth: 1.5,
|
|
35
|
+
className: "pointer-events-none absolute end-3 top-1/2 -translate-y-1/2 text-(--ink-3-aa)",
|
|
36
|
+
"aria-hidden": true
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
] });
|
|
40
|
+
}
|
|
41
|
+
var NativeSelect_default = NativeSelect;
|
|
42
|
+
export {
|
|
43
|
+
NativeSelect,
|
|
44
|
+
NativeSelect_default as default
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=NativeSelect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/NativeSelect/NativeSelect.tsx"],"sourcesContent":["import { ChevronDown } from 'lucide-react'\nimport type { Ref, SelectHTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\nimport { CONTROL_BASE, CONTROL_BORDER, isInvalid } from '../../lib/control'\n\nexport interface NativeSelectProps extends SelectHTMLAttributes<HTMLSelectElement> {\n /** Paints the resting border with `--danger` and reflects `aria-invalid`. */\n invalid?: boolean\n ref?: Ref<HTMLSelectElement>\n}\n\n/**\n * A native `<select>`, restyled.\n *\n * The escape hatch, not the default — `Select` is the styled one. Reach for\n * this where the platform's own picker is genuinely better: a very long list on\n * a phone, a form that must work without JavaScript, a page where the last\n * kilobyte matters. The browser gives typeahead and the mobile wheel for free,\n * and those are real.\n *\n * What it cannot do is look like the rest of the system once open. The option\n * list is drawn by the operating system, so it carries none of these tokens —\n * which is exactly why it stopped being the default.\n *\n * @example\n * <NativeSelect defaultValue=\"au\"><option value=\"au\">Australia</option></NativeSelect>\n */\nexport function NativeSelect({\n invalid,\n className,\n ref,\n children,\n 'aria-invalid': ariaInvalid,\n ...rest\n}: NativeSelectProps) {\n const bad = isInvalid(invalid, ariaInvalid)\n return (\n <div className=\"relative\">\n <select\n ref={ref}\n aria-invalid={ariaInvalid ?? (invalid ? true : undefined)}\n className={cn(\n CONTROL_BASE,\n 'cursor-pointer appearance-none pe-9',\n bad ? CONTROL_BORDER.invalid : CONTROL_BORDER.resting,\n className,\n )}\n {...rest}\n >\n {children}\n </select>\n <ChevronDown\n size={16}\n strokeWidth={1.5}\n className=\"pointer-events-none absolute end-3 top-1/2 -translate-y-1/2 text-(--ink-3-aa)\"\n aria-hidden\n />\n </div>\n )\n}\n\nexport default NativeSelect\n"],"mappings":"AAqCI,SACE,KADF;AArCJ,SAAS,mBAAmB;AAE5B,SAAS,UAAU;AACnB,SAAS,cAAc,gBAAgB,iBAAiB;AAwBjD,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB,GAAG;AACL,GAAsB;AACpB,QAAM,MAAM,UAAU,SAAS,WAAW;AAC1C,SACE,qBAAC,SAAI,WAAU,YACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,gBAAc,gBAAgB,UAAU,OAAO;AAAA,QAC/C,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA,MAAM,eAAe,UAAU,eAAe;AAAA,UAC9C;AAAA,QACF;AAAA,QACC,GAAG;AAAA,QAEH;AAAA;AAAA,IACH;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,aAAa;AAAA,QACb,WAAU;AAAA,QACV,eAAW;AAAA;AAAA,IACb;AAAA,KACF;AAEJ;AAEA,IAAO,uBAAQ;","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { AnchorHTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
import { LucideIcon } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
interface NavItemProps extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> {
|
|
6
|
+
href: string;
|
|
7
|
+
/** Optional leading icon (a lucide-react component, rendered at 18px). */
|
|
8
|
+
icon?: LucideIcon;
|
|
9
|
+
/** Marks the current route. Also sets `aria-current="page"`. */
|
|
10
|
+
active?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Hand the styling to a router's own `Link` instead of a native `<a>`.
|
|
13
|
+
*
|
|
14
|
+
* The slotted child receives the classes and `aria-current`, and becomes the
|
|
15
|
+
* row — so `icon` is not rendered in this mode; put it inside the child.
|
|
16
|
+
*/
|
|
17
|
+
asChild?: boolean;
|
|
18
|
+
children: ReactNode;
|
|
19
|
+
className?: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A row in a sidebar.
|
|
23
|
+
*
|
|
24
|
+
* `aria-current="page"` and not only a colour: the active row has to be
|
|
25
|
+
* identifiable to a reader who cannot see that it is darker. In this system it
|
|
26
|
+
* is also carried by weight and by a filled ground, so it survives monochrome
|
|
27
|
+
* printing and low contrast.
|
|
28
|
+
*
|
|
29
|
+
* Framework-agnostic. `asChild` is how a Next or React Router app gets
|
|
30
|
+
* client-side navigation without this package importing either.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* <NavItem href="/components/button" icon={Square} active>Button</NavItem>
|
|
34
|
+
* @example
|
|
35
|
+
* <NavItem asChild href="/work"><Link href="/work">Work</Link></NavItem>
|
|
36
|
+
*/
|
|
37
|
+
declare function NavItem({ href, icon: Icon, active, asChild, children, className, ...rest }: NavItemProps): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
export { NavItem, type NavItemProps, NavItem as default };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cn } from "../../lib/cn.js";
|
|
4
|
+
function NavItem({
|
|
5
|
+
href,
|
|
6
|
+
icon: Icon,
|
|
7
|
+
active = false,
|
|
8
|
+
asChild = false,
|
|
9
|
+
children,
|
|
10
|
+
className,
|
|
11
|
+
...rest
|
|
12
|
+
}) {
|
|
13
|
+
const cls = cn(
|
|
14
|
+
"flex min-h-(--control-h-sm) items-center gap-3 rounded-(--radius) px-3 py-2 text-sm transition-colors duration-(--duration-fast)",
|
|
15
|
+
active ? "bg-(--stone) font-medium text-(--ink)" : "text-(--ink-3-aa) hover:bg-(--stone) hover:text-(--ink)",
|
|
16
|
+
className
|
|
17
|
+
);
|
|
18
|
+
if (asChild) {
|
|
19
|
+
return /* @__PURE__ */ jsx(Slot, { "aria-current": active ? "page" : void 0, className: cls, ...rest, children });
|
|
20
|
+
}
|
|
21
|
+
return /* @__PURE__ */ jsxs("a", { href, "aria-current": active ? "page" : void 0, className: cls, ...rest, children: [
|
|
22
|
+
Icon && /* @__PURE__ */ jsx(Icon, { size: 18, strokeWidth: 1.5, "aria-hidden": true, className: "shrink-0" }),
|
|
23
|
+
children
|
|
24
|
+
] });
|
|
25
|
+
}
|
|
26
|
+
var NavItem_default = NavItem;
|
|
27
|
+
export {
|
|
28
|
+
NavItem,
|
|
29
|
+
NavItem_default as default
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=NavItem.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/NavItem/NavItem.tsx"],"sourcesContent":["import { Slot } from '@radix-ui/react-slot'\nimport type { LucideIcon } from 'lucide-react'\nimport type { AnchorHTMLAttributes, ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface NavItemProps\n extends Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'className' | 'children'> {\n href: string\n /** Optional leading icon (a lucide-react component, rendered at 18px). */\n icon?: LucideIcon\n /** Marks the current route. Also sets `aria-current=\"page\"`. */\n active?: boolean\n /**\n * Hand the styling to a router's own `Link` instead of a native `<a>`.\n *\n * The slotted child receives the classes and `aria-current`, and becomes the\n * row — so `icon` is not rendered in this mode; put it inside the child.\n */\n asChild?: boolean\n children: ReactNode\n className?: string\n}\n\n/**\n * A row in a sidebar.\n *\n * `aria-current=\"page\"` and not only a colour: the active row has to be\n * identifiable to a reader who cannot see that it is darker. In this system it\n * is also carried by weight and by a filled ground, so it survives monochrome\n * printing and low contrast.\n *\n * Framework-agnostic. `asChild` is how a Next or React Router app gets\n * client-side navigation without this package importing either.\n *\n * @example\n * <NavItem href=\"/components/button\" icon={Square} active>Button</NavItem>\n * @example\n * <NavItem asChild href=\"/work\"><Link href=\"/work\">Work</Link></NavItem>\n */\nexport function NavItem({\n href,\n icon: Icon,\n active = false,\n asChild = false,\n children,\n className,\n ...rest\n}: NavItemProps) {\n const cls = cn(\n 'flex min-h-(--control-h-sm) items-center gap-3 rounded-(--radius) px-3 py-2 text-sm transition-colors duration-(--duration-fast)',\n active\n ? 'bg-(--stone) font-medium text-(--ink)'\n : 'text-(--ink-3-aa) hover:bg-(--stone) hover:text-(--ink)',\n className,\n )\n\n if (asChild) {\n // Slot demands exactly ONE child, and `{Icon && …}` is a second one the\n // moment `icon` is omitted — which is how a whole sidebar of slotted rows\n // failed to render at all. So a slotted row takes its icon inside the child\n // it was handed, and this branch passes `children` through untouched.\n return (\n <Slot aria-current={active ? 'page' : undefined} className={cls} {...rest}>\n {children}\n </Slot>\n )\n }\n\n return (\n <a href={href} aria-current={active ? 'page' : undefined} className={cls} {...rest}>\n {Icon && <Icon size={18} strokeWidth={1.5} aria-hidden className=\"shrink-0\" />}\n {children}\n </a>\n )\n}\n\nexport default NavItem\n"],"mappings":"AA8DM,cAOF,YAPE;AA9DN,SAAS,YAAY;AAGrB,SAAS,UAAU;AAoCZ,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA,MAAM;AAAA,EACN,SAAS;AAAA,EACT,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAiB;AACf,QAAM,MAAM;AAAA,IACV;AAAA,IACA,SACI,0CACA;AAAA,IACJ;AAAA,EACF;AAEA,MAAI,SAAS;AAKX,WACE,oBAAC,QAAK,gBAAc,SAAS,SAAS,QAAW,WAAW,KAAM,GAAG,MAClE,UACH;AAAA,EAEJ;AAEA,SACE,qBAAC,OAAE,MAAY,gBAAc,SAAS,SAAS,QAAW,WAAW,KAAM,GAAG,MAC3E;AAAA,YAAQ,oBAAC,QAAK,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC,WAAU,YAAW;AAAA,IAC3E;AAAA,KACH;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {
|
|
5
|
+
/** 1-based. */
|
|
6
|
+
page: number;
|
|
7
|
+
/** Total pages. A value of 1 or less renders nothing. */
|
|
8
|
+
pageCount: number;
|
|
9
|
+
onPageChange: (page: number) => void;
|
|
10
|
+
/** How many numbered pages sit either side of the current one. */
|
|
11
|
+
siblings?: number;
|
|
12
|
+
label?: string;
|
|
13
|
+
}
|
|
14
|
+
declare const GAP: "\u2026";
|
|
15
|
+
/**
|
|
16
|
+
* Builds the visible page list: always the first and last page, a window around
|
|
17
|
+
* the current one, and an ellipsis wherever the sequence skips.
|
|
18
|
+
*
|
|
19
|
+
* Returned as numbers and a literal ellipsis rather than as pre-rendered nodes,
|
|
20
|
+
* so the shape is testable without a DOM — the off-by-one at the window edges
|
|
21
|
+
* is the whole difficulty of this component.
|
|
22
|
+
*/
|
|
23
|
+
declare function paginationRange(page: number, pageCount: number, siblings?: number): (number | typeof GAP)[];
|
|
24
|
+
/**
|
|
25
|
+
* Numbered pagination.
|
|
26
|
+
*
|
|
27
|
+
* The current page is marked by one filled pill that TRAVELS between the
|
|
28
|
+
* numbers rather than by a background switching off on one and on on another.
|
|
29
|
+
* Two backgrounds cross-fading reads as two things changing; a shape moving
|
|
30
|
+
* reads as the one thing that actually did.
|
|
31
|
+
*
|
|
32
|
+
* A `<nav>` wrapping a list, and the current page is a `<button aria-current>`
|
|
33
|
+
* rather than a styled `<span>` — a reader jumping by landmark needs to find
|
|
34
|
+
* the control, and a reader on the current page needs to be told they are
|
|
35
|
+
* already there.
|
|
36
|
+
*
|
|
37
|
+
* Renders nothing at one page or fewer. A pager for a single page is furniture.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* <Pagination page={page} pageCount={12} onPageChange={setPage} />
|
|
41
|
+
*/
|
|
42
|
+
declare function Pagination({ page, pageCount, onPageChange, siblings, label, className, ...rest }: PaginationProps): react.JSX.Element | null;
|
|
43
|
+
|
|
44
|
+
export { Pagination, type PaginationProps, Pagination as default, paginationRange };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
import { useSelectionIndicator } from "../../lib/useSelectionIndicator.js";
|
|
6
|
+
const GAP = "\u2026";
|
|
7
|
+
function paginationRange(page, pageCount, siblings = 1) {
|
|
8
|
+
const first = 1;
|
|
9
|
+
const last = pageCount;
|
|
10
|
+
const widest = 2 * siblings + 5;
|
|
11
|
+
if (pageCount <= widest) {
|
|
12
|
+
return Array.from({ length: pageCount }, (_, index) => index + 1);
|
|
13
|
+
}
|
|
14
|
+
const start = Math.max(first, page - siblings);
|
|
15
|
+
const end = Math.min(last, page + siblings);
|
|
16
|
+
const pages = [];
|
|
17
|
+
if (start > first) {
|
|
18
|
+
pages.push(first);
|
|
19
|
+
if (start > first + 2) pages.push(GAP);
|
|
20
|
+
else if (start === first + 2) pages.push(first + 1);
|
|
21
|
+
}
|
|
22
|
+
for (let n = start; n <= end; n += 1) pages.push(n);
|
|
23
|
+
if (end < last) {
|
|
24
|
+
if (end < last - 2) pages.push(GAP);
|
|
25
|
+
else if (end === last - 2) pages.push(last - 1);
|
|
26
|
+
pages.push(last);
|
|
27
|
+
}
|
|
28
|
+
return pages;
|
|
29
|
+
}
|
|
30
|
+
const STEP = "inline-flex size-(--control-h-sm) items-center justify-center rounded-(--radius-pill) border transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none";
|
|
31
|
+
function Pagination({
|
|
32
|
+
page,
|
|
33
|
+
pageCount,
|
|
34
|
+
onPageChange,
|
|
35
|
+
siblings = 1,
|
|
36
|
+
label = "Pagination",
|
|
37
|
+
className,
|
|
38
|
+
...rest
|
|
39
|
+
}) {
|
|
40
|
+
const [listRef, indicator] = useSelectionIndicator(String(page));
|
|
41
|
+
if (pageCount <= 1) return null;
|
|
42
|
+
const pages = paginationRange(page, pageCount, siblings);
|
|
43
|
+
return /* @__PURE__ */ jsxs("nav", { "aria-label": label, className: cn("flex items-center gap-1.5", className), ...rest, children: [
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
"button",
|
|
46
|
+
{
|
|
47
|
+
type: "button",
|
|
48
|
+
"aria-label": "Previous page",
|
|
49
|
+
disabled: page <= 1,
|
|
50
|
+
onClick: () => onPageChange(page - 1),
|
|
51
|
+
className: cn(STEP, "border-(--rule-2) text-(--ink-2) hover:border-(--ink) hover:text-(--ink)"),
|
|
52
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16, strokeWidth: 1.5, "aria-hidden": true })
|
|
53
|
+
}
|
|
54
|
+
),
|
|
55
|
+
/* @__PURE__ */ jsxs("ol", { ref: listRef, className: "relative m-0 flex list-none items-center gap-1 p-0", children: [
|
|
56
|
+
indicator.ready && /* @__PURE__ */ jsx(
|
|
57
|
+
"span",
|
|
58
|
+
{
|
|
59
|
+
"aria-hidden": true,
|
|
60
|
+
"data-m22-animated": true,
|
|
61
|
+
className: "absolute rounded-(--radius-pill) bg-(--accent) transition-[transform,width] duration-(--duration-base) ease-(--ease-out-expo) motion-reduce:transition-none",
|
|
62
|
+
style: {
|
|
63
|
+
transform: `translate(${indicator.offset}px, ${indicator.top}px)`,
|
|
64
|
+
width: indicator.width,
|
|
65
|
+
height: indicator.height,
|
|
66
|
+
insetInlineStart: 0,
|
|
67
|
+
top: 0
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
pages.map(
|
|
72
|
+
(entry, index) => entry === GAP ? /* @__PURE__ */ jsx("li", { "aria-hidden": "true", className: "px-1 mono-meta text-(--ink-3-aa)", children: GAP }, `gap-${index}`) : /* @__PURE__ */ jsx("li", { className: "relative z-1", children: /* @__PURE__ */ jsx(
|
|
73
|
+
"button",
|
|
74
|
+
{
|
|
75
|
+
type: "button",
|
|
76
|
+
"aria-current": entry === page ? "page" : void 0,
|
|
77
|
+
"aria-label": `Page ${entry}`,
|
|
78
|
+
"data-indicator-active": entry === page ? "true" : void 0,
|
|
79
|
+
onClick: () => onPageChange(entry),
|
|
80
|
+
className: cn(
|
|
81
|
+
STEP,
|
|
82
|
+
"mono-meta tabular-nums",
|
|
83
|
+
entry === page ? "border-transparent text-(--accent-foreground)" : "border-transparent text-(--ink-2) hover:border-(--rule-2) hover:text-(--ink)"
|
|
84
|
+
),
|
|
85
|
+
children: entry
|
|
86
|
+
}
|
|
87
|
+
) }, entry)
|
|
88
|
+
)
|
|
89
|
+
] }),
|
|
90
|
+
/* @__PURE__ */ jsx(
|
|
91
|
+
"button",
|
|
92
|
+
{
|
|
93
|
+
type: "button",
|
|
94
|
+
"aria-label": "Next page",
|
|
95
|
+
disabled: page >= pageCount,
|
|
96
|
+
onClick: () => onPageChange(page + 1),
|
|
97
|
+
className: cn(STEP, "border-(--rule-2) text-(--ink-2) hover:border-(--ink) hover:text-(--ink)"),
|
|
98
|
+
children: /* @__PURE__ */ jsx(ChevronRight, { size: 16, strokeWidth: 1.5, "aria-hidden": true })
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
] });
|
|
102
|
+
}
|
|
103
|
+
var Pagination_default = Pagination;
|
|
104
|
+
export {
|
|
105
|
+
Pagination,
|
|
106
|
+
Pagination_default as default,
|
|
107
|
+
paginationRange
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=Pagination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Pagination/Pagination.tsx"],"sourcesContent":["'use client'\n\nimport { ChevronLeft, ChevronRight } from 'lucide-react'\nimport type { HTMLAttributes } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useSelectionIndicator } from '../../lib/useSelectionIndicator'\n\nexport interface PaginationProps extends Omit<HTMLAttributes<HTMLElement>, 'onChange'> {\n /** 1-based. */\n page: number\n /** Total pages. A value of 1 or less renders nothing. */\n pageCount: number\n onPageChange: (page: number) => void\n /** How many numbered pages sit either side of the current one. */\n siblings?: number\n label?: string\n}\n\nconst GAP = '…' as const\n\n/**\n * Builds the visible page list: always the first and last page, a window around\n * the current one, and an ellipsis wherever the sequence skips.\n *\n * Returned as numbers and a literal ellipsis rather than as pre-rendered nodes,\n * so the shape is testable without a DOM — the off-by-one at the window edges\n * is the whole difficulty of this component.\n */\nexport function paginationRange(page: number, pageCount: number, siblings = 1): (number | typeof GAP)[] {\n const first = 1\n const last = pageCount\n\n // Below the width the elided form would occupy — first, last, the window and\n // two ellipses — printing every page is both shorter and more useful. Without\n // this, a five-page list renders \"1 2 … 5\", which hides a page to save\n // nothing.\n const widest = 2 * siblings + 5\n if (pageCount <= widest) {\n return Array.from({ length: pageCount }, (_, index) => index + 1)\n }\n\n const start = Math.max(first, page - siblings)\n const end = Math.min(last, page + siblings)\n\n const pages: (number | typeof GAP)[] = []\n if (start > first) {\n pages.push(first)\n // A single skipped page is printed, not elided: \"1 … 3\" is longer than\n // \"1 2 3\" and tells the reader less.\n if (start > first + 2) pages.push(GAP)\n else if (start === first + 2) pages.push(first + 1)\n }\n for (let n = start; n <= end; n += 1) pages.push(n)\n if (end < last) {\n if (end < last - 2) pages.push(GAP)\n else if (end === last - 2) pages.push(last - 1)\n pages.push(last)\n }\n return pages\n}\n\nconst STEP =\n 'inline-flex size-(--control-h-sm) items-center justify-center rounded-(--radius-pill) border transition-colors duration-(--duration-fast) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none'\n\n/**\n * Numbered pagination.\n *\n * The current page is marked by one filled pill that TRAVELS between the\n * numbers rather than by a background switching off on one and on on another.\n * Two backgrounds cross-fading reads as two things changing; a shape moving\n * reads as the one thing that actually did.\n *\n * A `<nav>` wrapping a list, and the current page is a `<button aria-current>`\n * rather than a styled `<span>` — a reader jumping by landmark needs to find\n * the control, and a reader on the current page needs to be told they are\n * already there.\n *\n * Renders nothing at one page or fewer. A pager for a single page is furniture.\n *\n * @example\n * <Pagination page={page} pageCount={12} onPageChange={setPage} />\n */\nexport function Pagination({\n page,\n pageCount,\n onPageChange,\n siblings = 1,\n label = 'Pagination',\n className,\n ...rest\n}: PaginationProps) {\n // Called before the early return, because a hook cannot be conditional — and\n // keyed on the page so the pill re-measures when the elided window shifts and\n // the numbers under it change.\n const [listRef, indicator] = useSelectionIndicator<HTMLOListElement>(String(page))\n\n if (pageCount <= 1) return null\n const pages = paginationRange(page, pageCount, siblings)\n\n return (\n <nav aria-label={label} className={cn('flex items-center gap-1.5', className)} {...rest}>\n <button\n type=\"button\"\n aria-label=\"Previous page\"\n disabled={page <= 1}\n onClick={() => onPageChange(page - 1)}\n className={cn(STEP, 'border-(--rule-2) text-(--ink-2) hover:border-(--ink) hover:text-(--ink)')}\n >\n <ChevronLeft size={16} strokeWidth={1.5} aria-hidden />\n </button>\n\n <ol ref={listRef} className=\"relative m-0 flex list-none items-center gap-1 p-0\">\n {indicator.ready && (\n <span\n aria-hidden\n data-m22-animated\n className=\"absolute rounded-(--radius-pill) bg-(--accent) transition-[transform,width] duration-(--duration-base) ease-(--ease-out-expo) motion-reduce:transition-none\"\n style={{\n transform: `translate(${indicator.offset}px, ${indicator.top}px)`,\n width: indicator.width,\n height: indicator.height,\n insetInlineStart: 0,\n top: 0,\n }}\n />\n )}\n {pages.map((entry, index) =>\n entry === GAP ? (\n <li key={`gap-${index}`} aria-hidden=\"true\" className=\"px-1 mono-meta text-(--ink-3-aa)\">\n {GAP}\n </li>\n ) : (\n <li key={entry} className=\"relative z-1\">\n <button\n type=\"button\"\n aria-current={entry === page ? 'page' : undefined}\n aria-label={`Page ${entry}`}\n data-indicator-active={entry === page ? 'true' : undefined}\n onClick={() => onPageChange(entry)}\n className={cn(\n STEP,\n 'mono-meta tabular-nums',\n entry === page\n ? 'border-transparent text-(--accent-foreground)'\n : 'border-transparent text-(--ink-2) hover:border-(--rule-2) hover:text-(--ink)',\n )}\n >\n {entry}\n </button>\n </li>\n ),\n )}\n </ol>\n\n <button\n type=\"button\"\n aria-label=\"Next page\"\n disabled={page >= pageCount}\n onClick={() => onPageChange(page + 1)}\n className={cn(STEP, 'border-(--rule-2) text-(--ink-2) hover:border-(--ink) hover:text-(--ink)')}\n >\n <ChevronRight size={16} strokeWidth={1.5} aria-hidden />\n </button>\n </nav>\n )\n}\n\nexport default Pagination\n"],"mappings":";AA4GQ,cAGF,YAHE;AA1GR,SAAS,aAAa,oBAAoB;AAE1C,SAAS,UAAU;AACnB,SAAS,6BAA6B;AAatC,MAAM,MAAM;AAUL,SAAS,gBAAgB,MAAc,WAAmB,WAAW,GAA4B;AACtG,QAAM,QAAQ;AACd,QAAM,OAAO;AAMb,QAAM,SAAS,IAAI,WAAW;AAC9B,MAAI,aAAa,QAAQ;AACvB,WAAO,MAAM,KAAK,EAAE,QAAQ,UAAU,GAAG,CAAC,GAAG,UAAU,QAAQ,CAAC;AAAA,EAClE;AAEA,QAAM,QAAQ,KAAK,IAAI,OAAO,OAAO,QAAQ;AAC7C,QAAM,MAAM,KAAK,IAAI,MAAM,OAAO,QAAQ;AAE1C,QAAM,QAAiC,CAAC;AACxC,MAAI,QAAQ,OAAO;AACjB,UAAM,KAAK,KAAK;AAGhB,QAAI,QAAQ,QAAQ,EAAG,OAAM,KAAK,GAAG;AAAA,aAC5B,UAAU,QAAQ,EAAG,OAAM,KAAK,QAAQ,CAAC;AAAA,EACpD;AACA,WAAS,IAAI,OAAO,KAAK,KAAK,KAAK,EAAG,OAAM,KAAK,CAAC;AAClD,MAAI,MAAM,MAAM;AACd,QAAI,MAAM,OAAO,EAAG,OAAM,KAAK,GAAG;AAAA,aACzB,QAAQ,OAAO,EAAG,OAAM,KAAK,OAAO,CAAC;AAC9C,UAAM,KAAK,IAAI;AAAA,EACjB;AACA,SAAO;AACT;AAEA,MAAM,OACJ;AAoBK,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,QAAQ;AAAA,EACR;AAAA,EACA,GAAG;AACL,GAAoB;AAIlB,QAAM,CAAC,SAAS,SAAS,IAAI,sBAAwC,OAAO,IAAI,CAAC;AAEjF,MAAI,aAAa,EAAG,QAAO;AAC3B,QAAM,QAAQ,gBAAgB,MAAM,WAAW,QAAQ;AAEvD,SACE,qBAAC,SAAI,cAAY,OAAO,WAAW,GAAG,6BAA6B,SAAS,GAAI,GAAG,MACjF;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,UAAU,QAAQ;AAAA,QAClB,SAAS,MAAM,aAAa,OAAO,CAAC;AAAA,QACpC,WAAW,GAAG,MAAM,0EAA0E;AAAA,QAE9F,8BAAC,eAAY,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,IACvD;AAAA,IAEA,qBAAC,QAAG,KAAK,SAAS,WAAU,sDACzB;AAAA,gBAAU,SACT;AAAA,QAAC;AAAA;AAAA,UACC,eAAW;AAAA,UACX,qBAAiB;AAAA,UACjB,WAAU;AAAA,UACV,OAAO;AAAA,YACL,WAAW,aAAa,UAAU,MAAM,OAAO,UAAU,GAAG;AAAA,YAC5D,OAAO,UAAU;AAAA,YACjB,QAAQ,UAAU;AAAA,YAClB,kBAAkB;AAAA,YAClB,KAAK;AAAA,UACP;AAAA;AAAA,MACF;AAAA,MAED,MAAM;AAAA,QAAI,CAAC,OAAO,UACjB,UAAU,MACR,oBAAC,QAAwB,eAAY,QAAO,WAAU,oCACnD,iBADM,OAAO,KAAK,EAErB,IAEA,oBAAC,QAAe,WAAU,gBACxB;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,gBAAc,UAAU,OAAO,SAAS;AAAA,YACxC,cAAY,QAAQ,KAAK;AAAA,YACzB,yBAAuB,UAAU,OAAO,SAAS;AAAA,YACjD,SAAS,MAAM,aAAa,KAAK;AAAA,YACjC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,cACA,UAAU,OACN,kDACA;AAAA,YACN;AAAA,YAEC;AAAA;AAAA,QACH,KAhBO,KAiBT;AAAA,MAEJ;AAAA,OACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAW;AAAA,QACX,UAAU,QAAQ;AAAA,QAClB,SAAS,MAAM,aAAa,OAAO,CAAC;AAAA,QACpC,WAAW,GAAG,MAAM,0EAA0E;AAAA,QAE9F,8BAAC,gBAAa,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,IACxD;AAAA,KACF;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import * as PopoverPrimitive from '@radix-ui/react-popover';
|
|
4
|
+
|
|
5
|
+
/** Radix Popover root, trigger, anchor and close, as typed passthroughs. */
|
|
6
|
+
declare const Popover: react.FC<PopoverPrimitive.PopoverProps>;
|
|
7
|
+
declare const PopoverTrigger: react.ForwardRefExoticComponent<PopoverPrimitive.PopoverTriggerProps & react.RefAttributes<HTMLButtonElement>>;
|
|
8
|
+
declare const PopoverAnchor: react.ForwardRefExoticComponent<PopoverPrimitive.PopoverAnchorProps & react.RefAttributes<HTMLDivElement>>;
|
|
9
|
+
declare const PopoverClose: react.ForwardRefExoticComponent<PopoverPrimitive.PopoverCloseProps & react.RefAttributes<HTMLButtonElement>>;
|
|
10
|
+
interface PopoverContentProps extends ComponentProps<typeof PopoverPrimitive.Content> {
|
|
11
|
+
/** Names the panel for assistive tech. Required — a popover is a dialog. */
|
|
12
|
+
label: string;
|
|
13
|
+
/** Show the top-end close control. */
|
|
14
|
+
showClose?: boolean;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* A panel anchored to a control, holding content the reader can interact with.
|
|
18
|
+
*
|
|
19
|
+
* The line against `Tooltip` is not visual, it is behavioural: a tooltip
|
|
20
|
+
* describes and cannot be entered; a popover holds things you tab to. Anything
|
|
21
|
+
* with a link, a field or a button in it is a popover, and putting that inside
|
|
22
|
+
* a tooltip makes it unreachable — the tooltip closes as soon as focus tries to
|
|
23
|
+
* move into it.
|
|
24
|
+
*
|
|
25
|
+
* Against `DropdownMenu`: a menu is a list of actions with menu semantics and
|
|
26
|
+
* arrow-key navigation. A popover is free-form, and its contents Tab like the
|
|
27
|
+
* rest of the page.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* <Popover>
|
|
31
|
+
* <PopoverTrigger asChild><Button variant="secondary">Filters</Button></PopoverTrigger>
|
|
32
|
+
* <PopoverContent label="Filters">
|
|
33
|
+
* <Field label="Status"><Select>…</Select></Field>
|
|
34
|
+
* </PopoverContent>
|
|
35
|
+
* </Popover>
|
|
36
|
+
*/
|
|
37
|
+
declare function PopoverContent({ label, showClose, className, sideOffset, children, ...rest }: PopoverContentProps): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
export { Popover, PopoverAnchor, PopoverClose, PopoverContent, type PopoverContentProps, PopoverTrigger, Popover as default };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
import { cn } from "../../lib/cn.js";
|
|
6
|
+
import { useOverlayContainer } from "../../lib/overlay-container.js";
|
|
7
|
+
const Popover = PopoverPrimitive.Root;
|
|
8
|
+
const PopoverTrigger = PopoverPrimitive.Trigger;
|
|
9
|
+
const PopoverAnchor = PopoverPrimitive.Anchor;
|
|
10
|
+
const PopoverClose = PopoverPrimitive.Close;
|
|
11
|
+
function PopoverContent({
|
|
12
|
+
label,
|
|
13
|
+
showClose = false,
|
|
14
|
+
className,
|
|
15
|
+
sideOffset = 8,
|
|
16
|
+
children,
|
|
17
|
+
...rest
|
|
18
|
+
}) {
|
|
19
|
+
const container = useOverlayContainer();
|
|
20
|
+
return /* @__PURE__ */ jsx(PopoverPrimitive.Portal, { container: container ?? void 0, children: /* @__PURE__ */ jsxs(
|
|
21
|
+
PopoverPrimitive.Content,
|
|
22
|
+
{
|
|
23
|
+
collisionBoundary: container ?? void 0,
|
|
24
|
+
collisionPadding: 8,
|
|
25
|
+
"aria-label": label,
|
|
26
|
+
sideOffset,
|
|
27
|
+
"data-m22-animated": true,
|
|
28
|
+
className: cn(
|
|
29
|
+
"z-(--z-dropdown) w-72 rounded-(--radius) border border-(--rule-2) bg-(--paper) p-4",
|
|
30
|
+
"data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)",
|
|
31
|
+
className
|
|
32
|
+
),
|
|
33
|
+
...rest,
|
|
34
|
+
children: [
|
|
35
|
+
children,
|
|
36
|
+
showClose && /* @__PURE__ */ jsx(
|
|
37
|
+
PopoverPrimitive.Close,
|
|
38
|
+
{
|
|
39
|
+
"aria-label": "Close",
|
|
40
|
+
className: "absolute end-2 top-2 grid size-8 place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)",
|
|
41
|
+
children: /* @__PURE__ */ jsx(X, { size: 14, strokeWidth: 1.5, "aria-hidden": true })
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
) });
|
|
47
|
+
}
|
|
48
|
+
var Popover_default = Popover;
|
|
49
|
+
export {
|
|
50
|
+
Popover,
|
|
51
|
+
PopoverAnchor,
|
|
52
|
+
PopoverClose,
|
|
53
|
+
PopoverContent,
|
|
54
|
+
PopoverTrigger,
|
|
55
|
+
Popover_default as default
|
|
56
|
+
};
|
|
57
|
+
//# sourceMappingURL=Popover.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Popover/Popover.tsx"],"sourcesContent":["'use client'\n\nimport * as PopoverPrimitive from '@radix-ui/react-popover'\nimport { X } from 'lucide-react'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\nimport { useOverlayContainer } from '../../lib/overlay-container'\n\n/** Radix Popover root, trigger, anchor and close, as typed passthroughs. */\nexport const Popover = PopoverPrimitive.Root\nexport const PopoverTrigger = PopoverPrimitive.Trigger\nexport const PopoverAnchor = PopoverPrimitive.Anchor\nexport const PopoverClose = PopoverPrimitive.Close\n\nexport interface PopoverContentProps\n extends ComponentProps<typeof PopoverPrimitive.Content> {\n /** Names the panel for assistive tech. Required — a popover is a dialog. */\n label: string\n /** Show the top-end close control. */\n showClose?: boolean\n}\n\n/**\n * A panel anchored to a control, holding content the reader can interact with.\n *\n * The line against `Tooltip` is not visual, it is behavioural: a tooltip\n * describes and cannot be entered; a popover holds things you tab to. Anything\n * with a link, a field or a button in it is a popover, and putting that inside\n * a tooltip makes it unreachable — the tooltip closes as soon as focus tries to\n * move into it.\n *\n * Against `DropdownMenu`: a menu is a list of actions with menu semantics and\n * arrow-key navigation. A popover is free-form, and its contents Tab like the\n * rest of the page.\n *\n * @example\n * <Popover>\n * <PopoverTrigger asChild><Button variant=\"secondary\">Filters</Button></PopoverTrigger>\n * <PopoverContent label=\"Filters\">\n * <Field label=\"Status\"><Select>…</Select></Field>\n * </PopoverContent>\n * </Popover>\n */\nexport function PopoverContent({\n label,\n showClose = false,\n className,\n sideOffset = 8,\n children,\n ...rest\n}: PopoverContentProps) {\n const container = useOverlayContainer()\n\n return (\n <PopoverPrimitive.Portal container={container ?? undefined}>\n <PopoverPrimitive.Content\n collisionBoundary={container ?? undefined}\n collisionPadding={8}\n aria-label={label}\n sideOffset={sideOffset}\n data-m22-animated\n className={cn(\n 'z-(--z-dropdown) w-72 rounded-(--radius) border border-(--rule-2) bg-(--paper) p-4',\n 'data-[state=open]:animate-[m22-pop-in_var(--duration-fast)_var(--ease-out-expo)] data-[state=closed]:animate-[m22-pop-out_var(--duration-fast)_var(--ease)] origin-(--radix-popper-transform-origin)',\n className,\n )}\n {...rest}\n >\n {children}\n {showClose && (\n <PopoverPrimitive.Close\n aria-label=\"Close\"\n className=\"absolute end-2 top-2 grid size-8 place-items-center rounded-(--radius-pill) text-(--ink-3-aa) transition-colors duration-(--duration-fast) hover:bg-(--stone) hover:text-(--ink)\"\n >\n <X size={14} strokeWidth={1.5} aria-hidden />\n </PopoverPrimitive.Close>\n )}\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n )\n}\n\nexport default Popover\n"],"mappings":";AAuDM,SAmBM,KAnBN;AArDN,YAAY,sBAAsB;AAClC,SAAS,SAAS;AAElB,SAAS,UAAU;AACnB,SAAS,2BAA2B;AAG7B,MAAM,UAAU,iBAAiB;AACjC,MAAM,iBAAiB,iBAAiB;AACxC,MAAM,gBAAgB,iBAAiB;AACvC,MAAM,eAAe,iBAAiB;AA+BtC,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA,aAAa;AAAA,EACb;AAAA,EACA,GAAG;AACL,GAAwB;AACtB,QAAM,YAAY,oBAAoB;AAEtC,SACE,oBAAC,iBAAiB,QAAjB,EAAwB,WAAW,aAAa,QAC/C;AAAA,IAAC,iBAAiB;AAAA,IAAjB;AAAA,MACC,mBAAmB,aAAa;AAAA,MAChC,kBAAkB;AAAA,MAClB,cAAY;AAAA,MACZ;AAAA,MACA,qBAAiB;AAAA,MACjB,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,QACA,aACC;AAAA,UAAC,iBAAiB;AAAA,UAAjB;AAAA,YACC,cAAW;AAAA,YACX,WAAU;AAAA,YAEV,8BAAC,KAAE,MAAM,IAAI,aAAa,KAAK,eAAW,MAAC;AAAA;AAAA,QAC7C;AAAA;AAAA;AAAA,EAEJ,GACF;AAEJ;AAEA,IAAO,kBAAQ;","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import * as ProgressPrimitive from '@radix-ui/react-progress';
|
|
4
|
+
|
|
5
|
+
interface ProgressProps extends Omit<ComponentProps<typeof ProgressPrimitive.Root>, 'value'> {
|
|
6
|
+
/**
|
|
7
|
+
* 0–100. Omit (or pass `null`) when the duration is genuinely unknown — the
|
|
8
|
+
* bar then sweeps instead of filling, and Radix drops `aria-valuenow` so a
|
|
9
|
+
* screen reader is told "indeterminate" rather than a number that is a guess.
|
|
10
|
+
*/
|
|
11
|
+
value?: number | null;
|
|
12
|
+
/** Names what is progressing. Required: a bare bar announces nothing. */
|
|
13
|
+
label: string;
|
|
14
|
+
/** Prints the percentage above the bar. Only meaningful when `value` is set. */
|
|
15
|
+
showValue?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A bar that fills, or sweeps when the end is unknown.
|
|
19
|
+
*
|
|
20
|
+
* Flat: a track in `--stone`, a fill in `--ink`. The White Reset has no
|
|
21
|
+
* gradient and no glow, so the only thing carrying the reading is the boundary
|
|
22
|
+
* between the two.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* <Progress value={62} label="Uploading photos" showValue />
|
|
26
|
+
* @example
|
|
27
|
+
* <Progress label="Indexing" />
|
|
28
|
+
*/
|
|
29
|
+
declare function Progress({ value, label, showValue, className, ...rest }: ProgressProps): react.JSX.Element;
|
|
30
|
+
|
|
31
|
+
export { Progress, type ProgressProps, Progress as default };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as ProgressPrimitive from "@radix-ui/react-progress";
|
|
4
|
+
import { cn } from "../../lib/cn.js";
|
|
5
|
+
function Progress({ value = null, label, showValue = false, className, ...rest }) {
|
|
6
|
+
const indeterminate = value === null || value === void 0;
|
|
7
|
+
const clamped = indeterminate ? 0 : Math.min(100, Math.max(0, value));
|
|
8
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex w-full flex-col gap-2", className), children: [
|
|
9
|
+
showValue && !indeterminate && /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between mono-meta text-(--ink-3-aa)", children: [
|
|
10
|
+
/* @__PURE__ */ jsx("span", { children: label }),
|
|
11
|
+
/* @__PURE__ */ jsxs("span", { className: "tabular-nums", children: [
|
|
12
|
+
Math.round(clamped),
|
|
13
|
+
"%"
|
|
14
|
+
] })
|
|
15
|
+
] }),
|
|
16
|
+
/* @__PURE__ */ jsx(
|
|
17
|
+
ProgressPrimitive.Root,
|
|
18
|
+
{
|
|
19
|
+
value: indeterminate ? null : clamped,
|
|
20
|
+
"aria-label": label,
|
|
21
|
+
className: "relative h-1 w-full overflow-hidden rounded-(--radius-pill) bg-(--stone)",
|
|
22
|
+
...rest,
|
|
23
|
+
children: indeterminate ? /* @__PURE__ */ jsx(
|
|
24
|
+
"span",
|
|
25
|
+
{
|
|
26
|
+
"data-m22-animated": true,
|
|
27
|
+
className: "absolute inset-y-0 start-0 w-1/4 rounded-(--radius-pill) bg-(--accent) rtl:-scale-x-100 motion-safe:animate-[m22-sweep_1.4s_var(--ease)_infinite] motion-reduce:w-full motion-reduce:opacity-40"
|
|
28
|
+
}
|
|
29
|
+
) : /* @__PURE__ */ jsx(
|
|
30
|
+
ProgressPrimitive.Indicator,
|
|
31
|
+
{
|
|
32
|
+
className: "h-full rounded-(--radius-pill) bg-(--accent) transition-[width] duration-(--duration-slow) ease-(--ease-out-expo)",
|
|
33
|
+
style: { width: `${clamped}%` }
|
|
34
|
+
}
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
)
|
|
38
|
+
] });
|
|
39
|
+
}
|
|
40
|
+
var Progress_default = Progress;
|
|
41
|
+
export {
|
|
42
|
+
Progress,
|
|
43
|
+
Progress_default as default
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=Progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/Progress/Progress.tsx"],"sourcesContent":["'use client'\n\nimport * as ProgressPrimitive from '@radix-ui/react-progress'\nimport type { ComponentProps } from 'react'\nimport { cn } from '../../lib/cn'\n\nexport interface ProgressProps\n extends Omit<ComponentProps<typeof ProgressPrimitive.Root>, 'value'> {\n /**\n * 0–100. Omit (or pass `null`) when the duration is genuinely unknown — the\n * bar then sweeps instead of filling, and Radix drops `aria-valuenow` so a\n * screen reader is told \"indeterminate\" rather than a number that is a guess.\n */\n value?: number | null\n /** Names what is progressing. Required: a bare bar announces nothing. */\n label: string\n /** Prints the percentage above the bar. Only meaningful when `value` is set. */\n showValue?: boolean\n}\n\n/**\n * A bar that fills, or sweeps when the end is unknown.\n *\n * Flat: a track in `--stone`, a fill in `--ink`. The White Reset has no\n * gradient and no glow, so the only thing carrying the reading is the boundary\n * between the two.\n *\n * @example\n * <Progress value={62} label=\"Uploading photos\" showValue />\n * @example\n * <Progress label=\"Indexing\" />\n */\nexport function Progress({ value = null, label, showValue = false, className, ...rest }: ProgressProps) {\n const indeterminate = value === null || value === undefined\n const clamped = indeterminate ? 0 : Math.min(100, Math.max(0, value))\n\n return (\n <div className={cn('flex w-full flex-col gap-2', className)}>\n {showValue && !indeterminate && (\n <div className=\"flex items-baseline justify-between mono-meta text-(--ink-3-aa)\">\n <span>{label}</span>\n <span className=\"tabular-nums\">{Math.round(clamped)}%</span>\n </div>\n )}\n <ProgressPrimitive.Root\n value={indeterminate ? null : clamped}\n aria-label={label}\n className=\"relative h-1 w-full overflow-hidden rounded-(--radius-pill) bg-(--stone)\"\n {...rest}\n >\n {indeterminate ? (\n <span\n data-m22-animated\n // A quarter-width bar travelling the track. `transform` only, so it\n // runs on the compositor and never triggers layout. `rtl:-scale-x-100`\n // mirrors the whole motion rather than needing a second keyframe:\n // in a right-to-left document the sweep has to run the other way, or\n // it reads as progress running backwards.\n className=\"absolute inset-y-0 start-0 w-1/4 rounded-(--radius-pill) bg-(--accent) rtl:-scale-x-100 motion-safe:animate-[m22-sweep_1.4s_var(--ease)_infinite] motion-reduce:w-full motion-reduce:opacity-40\"\n />\n ) : (\n <ProgressPrimitive.Indicator\n // Width, not a translate. A `translateX(-N%)` fill has to be negated\n // in a right-to-left document, and an inline style cannot carry a\n // direction variant — so the bar grew from the wrong edge. Width\n // grows from the inline start on its own, in either direction.\n className=\"h-full rounded-(--radius-pill) bg-(--accent) transition-[width] duration-(--duration-slow) ease-(--ease-out-expo)\"\n style={{ width: `${clamped}%` }}\n />\n )}\n </ProgressPrimitive.Root>\n </div>\n )\n}\n\nexport default Progress\n"],"mappings":";AAwCU,cACA,YADA;AAtCV,YAAY,uBAAuB;AAEnC,SAAS,UAAU;AA4BZ,SAAS,SAAS,EAAE,QAAQ,MAAM,OAAO,YAAY,OAAO,WAAW,GAAG,KAAK,GAAkB;AACtG,QAAM,gBAAgB,UAAU,QAAQ,UAAU;AAClD,QAAM,UAAU,gBAAgB,IAAI,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC;AAEpE,SACE,qBAAC,SAAI,WAAW,GAAG,8BAA8B,SAAS,GACvD;AAAA,iBAAa,CAAC,iBACb,qBAAC,SAAI,WAAU,mEACb;AAAA,0BAAC,UAAM,iBAAM;AAAA,MACb,qBAAC,UAAK,WAAU,gBAAgB;AAAA,aAAK,MAAM,OAAO;AAAA,QAAE;AAAA,SAAC;AAAA,OACvD;AAAA,IAEF;AAAA,MAAC,kBAAkB;AAAA,MAAlB;AAAA,QACC,OAAO,gBAAgB,OAAO;AAAA,QAC9B,cAAY;AAAA,QACZ,WAAU;AAAA,QACT,GAAG;AAAA,QAEH,0BACC;AAAA,UAAC;AAAA;AAAA,YACC,qBAAiB;AAAA,YAMjB,WAAU;AAAA;AAAA,QACZ,IAEA;AAAA,UAAC,kBAAkB;AAAA,UAAlB;AAAA,YAKC,WAAU;AAAA,YACV,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI;AAAA;AAAA,QAChC;AAAA;AAAA,IAEJ;AAAA,KACF;AAEJ;AAEA,IAAO,mBAAQ;","names":[]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps, ReactNode } from 'react';
|
|
3
|
+
import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
|
|
4
|
+
|
|
5
|
+
type RadioGroupProps = ComponentProps<typeof RadioGroupPrimitive.Root>;
|
|
6
|
+
/**
|
|
7
|
+
* A set of mutually exclusive choices.
|
|
8
|
+
*
|
|
9
|
+
* Radix owns the roving tabindex, so the whole group is ONE tab stop and the
|
|
10
|
+
* arrow keys move between options — which is what the ARIA radiogroup pattern
|
|
11
|
+
* requires and what a stack of hand-rolled `<input type="radio">` wrappers
|
|
12
|
+
* usually gets wrong.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <RadioGroup defaultValue="light" aria-label="Theme">
|
|
16
|
+
* <RadioGroupItem value="light">Light</RadioGroupItem>
|
|
17
|
+
* <RadioGroupItem value="dark">Dark</RadioGroupItem>
|
|
18
|
+
* </RadioGroup>
|
|
19
|
+
*/
|
|
20
|
+
declare function RadioGroup({ className, ...props }: RadioGroupProps): react.JSX.Element;
|
|
21
|
+
interface RadioGroupItemProps extends Omit<ComponentProps<typeof RadioGroupPrimitive.Item>, 'children'> {
|
|
22
|
+
/** The visible label. Rendered inside the `<label>` that wraps the control. */
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* One option, and its label, as a single click target.
|
|
27
|
+
*
|
|
28
|
+
* The `<label>` wraps both, so the whole row is clickable — a bare 18px circle
|
|
29
|
+
* is below every pointer-target guideline and is miserable on a phone. It is
|
|
30
|
+
* also what gives the control its accessible name: remove the wrapper and the
|
|
31
|
+
* radio has no name at all.
|
|
32
|
+
*
|
|
33
|
+
* Selection follows focus, which is the half of the pattern that makes a
|
|
34
|
+
* radiogroup usable from the keyboard: moving to an option chooses it, so
|
|
35
|
+
* nobody has to press an extra key to commit. That is implemented here rather
|
|
36
|
+
* than inherited, because the upstream primitive gates it on a flag cleared by
|
|
37
|
+
* `keyup` and loses the race against its own focus move — see ARROW_GRACE_MS.
|
|
38
|
+
* Re-selecting an already-selected option is a no-op, so this stays correct
|
|
39
|
+
* even where the upstream path does fire.
|
|
40
|
+
*/
|
|
41
|
+
declare function RadioGroupItem({ children, className, id, onFocus, ...props }: RadioGroupItemProps): react.JSX.Element;
|
|
42
|
+
|
|
43
|
+
export { RadioGroup, RadioGroupItem, type RadioGroupItemProps, type RadioGroupProps, RadioGroup as default };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
|
4
|
+
import { useEffect, useRef } from "react";
|
|
5
|
+
import { cn } from "../../lib/cn.js";
|
|
6
|
+
const ARROW_GRACE_MS = 300;
|
|
7
|
+
function RadioGroup({ className, ...props }) {
|
|
8
|
+
return /* @__PURE__ */ jsx(RadioGroupPrimitive.Root, { className: cn("flex flex-col gap-2.5", className), ...props });
|
|
9
|
+
}
|
|
10
|
+
function RadioGroupItem({ children, className, id, onFocus, ...props }) {
|
|
11
|
+
const arrowPressedAt = useRef(0);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const onKeyDown = (event) => {
|
|
14
|
+
if (event.key.startsWith("Arrow")) arrowPressedAt.current = Date.now();
|
|
15
|
+
};
|
|
16
|
+
document.addEventListener("keydown", onKeyDown);
|
|
17
|
+
return () => document.removeEventListener("keydown", onKeyDown);
|
|
18
|
+
}, []);
|
|
19
|
+
const selectOnArrowFocus = (event) => {
|
|
20
|
+
onFocus?.(event);
|
|
21
|
+
if (Date.now() - arrowPressedAt.current < ARROW_GRACE_MS) event.currentTarget.click();
|
|
22
|
+
};
|
|
23
|
+
return /* @__PURE__ */ jsxs("label", { className: cn("flex cursor-pointer items-center gap-2.5 text-sm text-(--ink-2)", className), children: [
|
|
24
|
+
/* @__PURE__ */ jsx(
|
|
25
|
+
RadioGroupPrimitive.Item,
|
|
26
|
+
{
|
|
27
|
+
id,
|
|
28
|
+
onFocus: selectOnArrowFocus,
|
|
29
|
+
className: "grid size-[18px] shrink-0 place-items-center rounded-full border border-(--rule-2) bg-(--paper) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none",
|
|
30
|
+
...props,
|
|
31
|
+
children: /* @__PURE__ */ jsx(RadioGroupPrimitive.Indicator, { className: "size-2.5 rounded-full bg-(--accent)" })
|
|
32
|
+
}
|
|
33
|
+
),
|
|
34
|
+
children
|
|
35
|
+
] });
|
|
36
|
+
}
|
|
37
|
+
var RadioGroup_default = RadioGroup;
|
|
38
|
+
export {
|
|
39
|
+
RadioGroup,
|
|
40
|
+
RadioGroupItem,
|
|
41
|
+
RadioGroup_default as default
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=RadioGroup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/components/RadioGroup/RadioGroup.tsx"],"sourcesContent":["'use client'\n\nimport * as RadioGroupPrimitive from '@radix-ui/react-radio-group'\nimport { useEffect, useRef, type ComponentProps, type FocusEvent, type ReactNode } from 'react'\nimport { cn } from '../../lib/cn'\n\n/**\n * How recently an arrow key was pressed, in milliseconds, for selection to\n * still count as \"caused by\" it.\n *\n * A boolean cleared on `keyup` — which is how the upstream primitive tracks\n * this — loses the race: the roving focus moves through a React state update,\n * and by the time the commit lands and the element takes focus, a normal\n * keypress has already released. The result is a group whose arrow keys move\n * the outline and select nothing, which is half the ARIA radiogroup pattern\n * missing. A timestamp cannot be cleared out from under the focus handler.\n */\nconst ARROW_GRACE_MS = 300\n\nexport type RadioGroupProps = ComponentProps<typeof RadioGroupPrimitive.Root>\n\n/**\n * A set of mutually exclusive choices.\n *\n * Radix owns the roving tabindex, so the whole group is ONE tab stop and the\n * arrow keys move between options — which is what the ARIA radiogroup pattern\n * requires and what a stack of hand-rolled `<input type=\"radio\">` wrappers\n * usually gets wrong.\n *\n * @example\n * <RadioGroup defaultValue=\"light\" aria-label=\"Theme\">\n * <RadioGroupItem value=\"light\">Light</RadioGroupItem>\n * <RadioGroupItem value=\"dark\">Dark</RadioGroupItem>\n * </RadioGroup>\n */\nexport function RadioGroup({ className, ...props }: RadioGroupProps) {\n return <RadioGroupPrimitive.Root className={cn('flex flex-col gap-2.5', className)} {...props} />\n}\n\nexport interface RadioGroupItemProps\n extends Omit<ComponentProps<typeof RadioGroupPrimitive.Item>, 'children'> {\n /** The visible label. Rendered inside the `<label>` that wraps the control. */\n children: ReactNode\n}\n\n/**\n * One option, and its label, as a single click target.\n *\n * The `<label>` wraps both, so the whole row is clickable — a bare 18px circle\n * is below every pointer-target guideline and is miserable on a phone. It is\n * also what gives the control its accessible name: remove the wrapper and the\n * radio has no name at all.\n *\n * Selection follows focus, which is the half of the pattern that makes a\n * radiogroup usable from the keyboard: moving to an option chooses it, so\n * nobody has to press an extra key to commit. That is implemented here rather\n * than inherited, because the upstream primitive gates it on a flag cleared by\n * `keyup` and loses the race against its own focus move — see ARROW_GRACE_MS.\n * Re-selecting an already-selected option is a no-op, so this stays correct\n * even where the upstream path does fire.\n */\nexport function RadioGroupItem({ children, className, id, onFocus, ...props }: RadioGroupItemProps) {\n const arrowPressedAt = useRef(0)\n\n useEffect(() => {\n const onKeyDown = (event: KeyboardEvent) => {\n if (event.key.startsWith('Arrow')) arrowPressedAt.current = Date.now()\n }\n document.addEventListener('keydown', onKeyDown)\n return () => document.removeEventListener('keydown', onKeyDown)\n }, [])\n\n const selectOnArrowFocus = (event: FocusEvent<HTMLButtonElement>) => {\n onFocus?.(event)\n if (Date.now() - arrowPressedAt.current < ARROW_GRACE_MS) event.currentTarget.click()\n }\n\n return (\n <label className={cn('flex cursor-pointer items-center gap-2.5 text-sm text-(--ink-2)', className)}>\n <RadioGroupPrimitive.Item\n id={id}\n onFocus={selectOnArrowFocus}\n className=\"grid size-[18px] shrink-0 place-items-center rounded-full border border-(--rule-2) bg-(--paper) transition-colors duration-(--duration-fast) data-[state=checked]:border-(--accent) disabled:opacity-(--disabled-opacity) disabled:pointer-events-none\"\n {...props}\n >\n <RadioGroupPrimitive.Indicator className=\"size-2.5 rounded-full bg-(--accent)\" />\n </RadioGroupPrimitive.Item>\n {children}\n </label>\n )\n}\n\nexport default RadioGroup\n"],"mappings":";AAoCS,cA0CL,YA1CK;AAlCT,YAAY,yBAAyB;AACrC,SAAS,WAAW,cAAoE;AACxF,SAAS,UAAU;AAanB,MAAM,iBAAiB;AAkBhB,SAAS,WAAW,EAAE,WAAW,GAAG,MAAM,GAAoB;AACnE,SAAO,oBAAC,oBAAoB,MAApB,EAAyB,WAAW,GAAG,yBAAyB,SAAS,GAAI,GAAG,OAAO;AACjG;AAwBO,SAAS,eAAe,EAAE,UAAU,WAAW,IAAI,SAAS,GAAG,MAAM,GAAwB;AAClG,QAAM,iBAAiB,OAAO,CAAC;AAE/B,YAAU,MAAM;AACd,UAAM,YAAY,CAAC,UAAyB;AAC1C,UAAI,MAAM,IAAI,WAAW,OAAO,EAAG,gBAAe,UAAU,KAAK,IAAI;AAAA,IACvE;AACA,aAAS,iBAAiB,WAAW,SAAS;AAC9C,WAAO,MAAM,SAAS,oBAAoB,WAAW,SAAS;AAAA,EAChE,GAAG,CAAC,CAAC;AAEL,QAAM,qBAAqB,CAAC,UAAyC;AACnE,cAAU,KAAK;AACf,QAAI,KAAK,IAAI,IAAI,eAAe,UAAU,eAAgB,OAAM,cAAc,MAAM;AAAA,EACtF;AAEA,SACE,qBAAC,WAAM,WAAW,GAAG,mEAAmE,SAAS,GAC/F;AAAA;AAAA,MAAC,oBAAoB;AAAA,MAApB;AAAA,QACC;AAAA,QACA,SAAS;AAAA,QACT,WAAU;AAAA,QACT,GAAG;AAAA,QAEJ,8BAAC,oBAAoB,WAApB,EAA8B,WAAU,uCAAsC;AAAA;AAAA,IACjF;AAAA,IACC;AAAA,KACH;AAEJ;AAEA,IAAO,qBAAQ;","names":[]}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ComponentProps } from 'react';
|
|
3
|
+
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
|
4
|
+
|
|
5
|
+
interface ScrollAreaProps extends ComponentProps<typeof ScrollAreaPrimitive.Root> {
|
|
6
|
+
/**
|
|
7
|
+
* Names the region. Required, and not decoration: a scroll container is a
|
|
8
|
+
* keyboard stop, and an unnamed stop announces "group" and nothing else.
|
|
9
|
+
*/
|
|
10
|
+
label: string;
|
|
11
|
+
orientation?: 'vertical' | 'horizontal' | 'both';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A box that scrolls, with a scrollbar that looks the same on every platform.
|
|
15
|
+
*
|
|
16
|
+
* The reason to reach for this over `overflow-auto` is not the scrollbar — it
|
|
17
|
+
* is that Radix keeps the viewport focusable and the bar operable, which a bare
|
|
18
|
+
* overflow container does not. A scrollable region whose contents are not
|
|
19
|
+
* themselves focusable is unreachable by keyboard: there is nothing to Tab to,
|
|
20
|
+
* so everything past the fold does not exist without a mouse.
|
|
21
|
+
*
|
|
22
|
+
* For a page-level or prose scroll, the `scroll-slim` utility is lighter and
|
|
23
|
+
* needs no component. This is for a bounded panel: a long option list, a log,
|
|
24
|
+
* a sidebar that outgrows its column.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* <ScrollArea label="Deploy log" className="h-48">…</ScrollArea>
|
|
28
|
+
*/
|
|
29
|
+
declare function ScrollArea({ label, orientation, className, children, ...props }: ScrollAreaProps): react.JSX.Element;
|
|
30
|
+
|
|
31
|
+
export { ScrollArea, type ScrollAreaProps, ScrollArea as default };
|