@milktop/react-ui 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/README.md +173 -2
  2. package/dist/accordion/Accordion.d.ts +65 -0
  3. package/dist/accordion/Accordion.js +61 -0
  4. package/dist/accordion/accordion.css +17 -0
  5. package/dist/action-bar/ActionBar.d.ts +33 -0
  6. package/dist/action-bar/ActionBar.js +48 -0
  7. package/dist/action-bar/action-bar.css +22 -0
  8. package/dist/alert/Alert.d.ts +28 -0
  9. package/dist/alert/Alert.js +12 -0
  10. package/dist/alert/alert.css +32 -0
  11. package/dist/app-shell/AppShell.d.ts +38 -0
  12. package/dist/app-shell/AppShell.js +92 -0
  13. package/dist/app-shell/Topbar.d.ts +20 -0
  14. package/dist/app-shell/Topbar.js +13 -0
  15. package/dist/app-shell/app-shell.css +41 -0
  16. package/dist/app-shell/shell.d.ts +9 -0
  17. package/dist/app-shell/shell.js +3 -0
  18. package/dist/attachments/Attachments.d.ts +84 -0
  19. package/dist/attachments/Attachments.js +235 -0
  20. package/dist/attachments/attachments.css +86 -0
  21. package/dist/avatar/Avatar.d.ts +31 -0
  22. package/dist/avatar/Avatar.js +41 -0
  23. package/dist/avatar/AvatarGroup.d.ts +41 -0
  24. package/dist/avatar/AvatarGroup.js +17 -0
  25. package/dist/avatar/avatar.css +73 -0
  26. package/dist/badge/Badge.d.ts +26 -0
  27. package/dist/badge/Badge.js +10 -0
  28. package/dist/badge/badge.css +24 -0
  29. package/dist/banner/Banner.d.ts +33 -0
  30. package/dist/banner/Banner.js +14 -0
  31. package/dist/banner/banner.css +25 -0
  32. package/dist/breadcrumbs/Breadcrumbs.d.ts +39 -0
  33. package/dist/breadcrumbs/Breadcrumbs.js +28 -0
  34. package/dist/breadcrumbs/breadcrumbs.css +18 -0
  35. package/dist/button/Button.d.ts +40 -0
  36. package/dist/button/Button.js +19 -0
  37. package/dist/button/button.css +63 -0
  38. package/dist/card/Card.d.ts +355 -0
  39. package/dist/card/Card.js +56 -0
  40. package/dist/card/card.css +43 -0
  41. package/dist/chart/AreaChart.d.ts +19 -0
  42. package/dist/chart/AreaChart.js +61 -0
  43. package/dist/chart/BarChart.d.ts +3 -0
  44. package/dist/chart/BarChart.js +60 -0
  45. package/dist/chart/ChartFrame.d.ts +25 -0
  46. package/dist/chart/ChartFrame.js +22 -0
  47. package/dist/chart/DonutChart.d.ts +20 -0
  48. package/dist/chart/DonutChart.js +41 -0
  49. package/dist/chart/Sparkline.d.ts +26 -0
  50. package/dist/chart/Sparkline.js +36 -0
  51. package/dist/chart/chart.css +51 -0
  52. package/dist/chart/chart.d.ts +23 -0
  53. package/dist/chart/chart.js +19 -0
  54. package/dist/checkbox/Checkbox.d.ts +50 -0
  55. package/dist/checkbox/Checkbox.js +19 -0
  56. package/dist/checkbox/CheckboxGroup.d.ts +44 -0
  57. package/dist/checkbox/CheckboxGroup.js +80 -0
  58. package/dist/checkbox/checkbox.css +49 -0
  59. package/dist/collapsible/Collapsible.d.ts +41 -0
  60. package/dist/collapsible/Collapsible.js +15 -0
  61. package/dist/collapsible/collapsible.css +20 -0
  62. package/dist/color-scheme.d.ts +18 -0
  63. package/dist/color-scheme.js +84 -0
  64. package/dist/command/Command.d.ts +64 -0
  65. package/dist/command/Command.js +162 -0
  66. package/dist/command/command.css +47 -0
  67. package/dist/confirm/confirm.css +5 -0
  68. package/dist/confirm/confirm.d.ts +42 -0
  69. package/dist/confirm/confirm.js +115 -0
  70. package/dist/copy-button/CopyButton.d.ts +30 -0
  71. package/dist/copy-button/CopyButton.js +23 -0
  72. package/dist/copy-button/copy-button.css +12 -0
  73. package/dist/css.d.ts +2 -0
  74. package/dist/data-list/DataList.d.ts +55 -0
  75. package/dist/data-list/DataList.js +43 -0
  76. package/dist/data-list/data-list.css +19 -0
  77. package/dist/date-picker/DatePicker.d.ts +95 -0
  78. package/dist/date-picker/DatePicker.js +237 -0
  79. package/dist/date-picker/date-picker.css +53 -0
  80. package/dist/dialog/Dialog.d.ts +98 -0
  81. package/dist/dialog/Dialog.js +92 -0
  82. package/dist/dialog/dialog.css +31 -0
  83. package/dist/divider/Divider.d.ts +27 -0
  84. package/dist/divider/Divider.js +8 -0
  85. package/dist/divider/divider.css +12 -0
  86. package/dist/editable/Editable.d.ts +47 -0
  87. package/dist/editable/Editable.js +51 -0
  88. package/dist/editable/editable.css +25 -0
  89. package/dist/editor/Editor.d.ts +59 -0
  90. package/dist/editor/Editor.js +220 -0
  91. package/dist/editor/editor.css +42 -0
  92. package/dist/editor/image.d.ts +1 -0
  93. package/dist/editor/image.js +23 -0
  94. package/dist/empty-state/EmptyState.d.ts +22 -0
  95. package/dist/empty-state/EmptyState.js +9 -0
  96. package/dist/empty-state/empty-state.css +14 -0
  97. package/dist/event-calendar/EventCalendar.d.ts +213 -0
  98. package/dist/event-calendar/EventCalendar.js +686 -0
  99. package/dist/event-calendar/dates.d.ts +8 -0
  100. package/dist/event-calendar/dates.js +21 -0
  101. package/dist/event-calendar/event-calendar.css +157 -0
  102. package/dist/field/Field.d.ts +66 -0
  103. package/dist/field/Field.js +23 -0
  104. package/dist/field/field.css +26 -0
  105. package/dist/file-upload/FileUpload.d.ts +60 -0
  106. package/dist/file-upload/FileUpload.js +102 -0
  107. package/dist/file-upload/file-upload.css +32 -0
  108. package/dist/format/FormatNumber.d.ts +42 -0
  109. package/dist/format/FormatNumber.js +59 -0
  110. package/dist/format/LocaleProvider.d.ts +33 -0
  111. package/dist/format/LocaleProvider.js +48 -0
  112. package/dist/format/format.d.ts +37 -0
  113. package/dist/format/format.js +74 -0
  114. package/dist/format/messages.d.ts +211 -0
  115. package/dist/format/messages.js +202 -0
  116. package/dist/highlight/Highlight.d.ts +11 -0
  117. package/dist/highlight/Highlight.js +14 -0
  118. package/dist/highlight/highlight.css +17 -0
  119. package/dist/hover-card/HoverCard.d.ts +47 -0
  120. package/dist/hover-card/HoverCard.js +18 -0
  121. package/dist/hover-card/hover-card.css +19 -0
  122. package/dist/index.d.ts +84 -0
  123. package/dist/index.js +85 -0
  124. package/dist/input/Input.d.ts +41 -0
  125. package/dist/input/Input.js +48 -0
  126. package/dist/input/input.css +25 -0
  127. package/dist/kbd/Kbd.d.ts +19 -0
  128. package/dist/kbd/Kbd.js +20 -0
  129. package/dist/kbd/kbd.css +8 -0
  130. package/dist/menu/Menu.d.ts +223 -0
  131. package/dist/menu/Menu.js +275 -0
  132. package/dist/menu/menu.css +38 -0
  133. package/dist/native-select/NativeSelect.d.ts +36 -0
  134. package/dist/native-select/NativeSelect.js +51 -0
  135. package/dist/native-select/native-select.css +16 -0
  136. package/dist/number-input/NumberInput.d.ts +56 -0
  137. package/dist/number-input/NumberInput.js +67 -0
  138. package/dist/number-input/number-input.css +21 -0
  139. package/dist/page/Page.d.ts +30 -0
  140. package/dist/page/Page.js +9 -0
  141. package/dist/page/page.css +17 -0
  142. package/dist/pagination/Pagination.d.ts +31 -0
  143. package/dist/pagination/Pagination.js +58 -0
  144. package/dist/pagination/pagination.css +15 -0
  145. package/dist/password-input/PasswordInput.d.ts +47 -0
  146. package/dist/password-input/PasswordInput.js +35 -0
  147. package/dist/password-input/password-input.css +18 -0
  148. package/dist/pin-input/PinInput.d.ts +45 -0
  149. package/dist/pin-input/PinInput.js +22 -0
  150. package/dist/pin-input/pin-input.css +13 -0
  151. package/dist/popover/Popover.d.ts +65 -0
  152. package/dist/popover/Popover.js +26 -0
  153. package/dist/popover/popover.css +16 -0
  154. package/dist/progress/Progress.d.ts +56 -0
  155. package/dist/progress/Progress.js +28 -0
  156. package/dist/progress/progress.css +24 -0
  157. package/dist/prose/Prose.d.ts +23 -0
  158. package/dist/prose/Prose.js +10 -0
  159. package/dist/prose/prose.css +29 -0
  160. package/dist/radio-group/RadioGroup.d.ts +59 -0
  161. package/dist/radio-group/RadioGroup.js +37 -0
  162. package/dist/radio-group/radio-group.css +30 -0
  163. package/dist/rolling-number/RollingNumber.d.ts +27 -0
  164. package/dist/rolling-number/RollingNumber.js +18 -0
  165. package/dist/rolling-number/rolling-number.css +10 -0
  166. package/dist/segment-group/Segmented.d.ts +68 -0
  167. package/dist/segment-group/Segmented.js +79 -0
  168. package/dist/segment-group/segment-group.css +25 -0
  169. package/dist/select/ListSelect.d.ts +5 -0
  170. package/dist/select/ListSelect.js +82 -0
  171. package/dist/select/SearchSelect.d.ts +43 -0
  172. package/dist/select/SearchSelect.js +219 -0
  173. package/dist/select/Select.d.ts +147 -0
  174. package/dist/select/Select.js +32 -0
  175. package/dist/select/items.d.ts +17 -0
  176. package/dist/select/items.js +14 -0
  177. package/dist/select/search-select.css +45 -0
  178. package/dist/select/select.css +52 -0
  179. package/dist/sheet/Sheet.d.ts +88 -0
  180. package/dist/sheet/Sheet.js +66 -0
  181. package/dist/sheet/sheet.css +44 -0
  182. package/dist/sidebar/NavGroup.d.ts +29 -0
  183. package/dist/sidebar/NavGroup.js +78 -0
  184. package/dist/sidebar/NavItem.d.ts +33 -0
  185. package/dist/sidebar/NavItem.js +50 -0
  186. package/dist/sidebar/NavSection.d.ts +20 -0
  187. package/dist/sidebar/NavSection.js +44 -0
  188. package/dist/sidebar/Sidebar.d.ts +65 -0
  189. package/dist/sidebar/Sidebar.js +59 -0
  190. package/dist/sidebar/SidebarUser.d.ts +31 -0
  191. package/dist/sidebar/SidebarUser.js +14 -0
  192. package/dist/sidebar/context.d.ts +13 -0
  193. package/dist/sidebar/context.js +6 -0
  194. package/dist/sidebar/sidebar.css +109 -0
  195. package/dist/skeleton/Skeleton.d.ts +25 -0
  196. package/dist/skeleton/Skeleton.js +8 -0
  197. package/dist/skeleton/skeleton.css +13 -0
  198. package/dist/slider/Slider.d.ts +70 -0
  199. package/dist/slider/Slider.js +65 -0
  200. package/dist/slider/slider.css +17 -0
  201. package/dist/sortable-list/SortableList.d.ts +46 -0
  202. package/dist/sortable-list/SortableList.js +85 -0
  203. package/dist/sortable-list/sortable-list.css +25 -0
  204. package/dist/spinner/Spinner.d.ts +18 -0
  205. package/dist/spinner/Spinner.js +11 -0
  206. package/dist/spinner/spinner.css +9 -0
  207. package/dist/stat/Stat.d.ts +42 -0
  208. package/dist/stat/Stat.js +21 -0
  209. package/dist/stat/Stats.d.ts +23 -0
  210. package/dist/stat/Stats.js +8 -0
  211. package/dist/stat/stat.css +34 -0
  212. package/dist/swatch-picker/SwatchPicker.d.ts +45 -0
  213. package/dist/swatch-picker/SwatchPicker.js +54 -0
  214. package/dist/swatch-picker/swatch-picker.css +38 -0
  215. package/dist/switch/Switch.d.ts +39 -0
  216. package/dist/switch/Switch.js +17 -0
  217. package/dist/switch/switch.css +13 -0
  218. package/dist/table/Table.d.ts +90 -0
  219. package/dist/table/Table.js +145 -0
  220. package/dist/table/table.css +44 -0
  221. package/dist/tabs/Tabs.d.ts +87 -0
  222. package/dist/tabs/Tabs.js +90 -0
  223. package/dist/tabs/tabs.css +53 -0
  224. package/dist/tags-input/TagsInput.d.ts +69 -0
  225. package/dist/tags-input/TagsInput.js +95 -0
  226. package/dist/tags-input/tags-input.css +37 -0
  227. package/dist/textarea/Textarea.d.ts +24 -0
  228. package/dist/textarea/Textarea.js +51 -0
  229. package/dist/textarea/textarea.css +14 -0
  230. package/dist/theme-toggle/ThemeToggle.d.ts +49 -0
  231. package/dist/theme-toggle/ThemeToggle.js +71 -0
  232. package/dist/theme-toggle/theme-toggle.css +23 -0
  233. package/dist/theme.css +121 -0
  234. package/dist/time-input/TimeInput.d.ts +25 -0
  235. package/dist/time-input/TimeInput.js +20 -0
  236. package/dist/timeline/Timeline.d.ts +50 -0
  237. package/dist/timeline/Timeline.js +15 -0
  238. package/dist/timeline/timeline.css +29 -0
  239. package/dist/toast/Toaster.d.ts +36 -0
  240. package/dist/toast/Toaster.js +25 -0
  241. package/dist/toast/toast.css +33 -0
  242. package/dist/tooltip/Tooltip.d.ts +55 -0
  243. package/dist/tooltip/Tooltip.js +48 -0
  244. package/dist/tooltip/tooltip.css +18 -0
  245. package/dist/tree-view/TreeView.d.ts +86 -0
  246. package/dist/tree-view/TreeView.js +74 -0
  247. package/dist/tree-view/tree-view.css +29 -0
  248. package/dist/utils.d.ts +19 -0
  249. package/dist/utils.js +59 -0
  250. package/package.json +99 -4
@@ -0,0 +1,25 @@
1
+ import { type HTMLAttributes, type PointerEvent, type ReactNode } from 'react';
2
+ import { type Classes } from '../utils.js';
3
+ import type { ChartRow, Series } from './chart.js';
4
+ import '../theme.css';
5
+ import './chart.css';
6
+ export type ChartClasses = Classes<'root' | 'frame' | 'svg' | 'grid' | 'tick' | 'area' | 'line' | 'cursor' | 'dot' | 'bar' | 'band' | 'tooltip' | 'tooltipLabel' | 'tooltipRow' | 'tooltipName' | 'tooltipValue' | 'swatch' | 'legend' | 'legendItem'>;
7
+ type FrameProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
8
+ rows: ChartRow[];
9
+ x: string;
10
+ list: Series[];
11
+ fmt: (value: unknown) => string;
12
+ height: number;
13
+ label: string;
14
+ hover: number | null;
15
+ onHover: (index: number | null) => void;
16
+ onMove: (e: PointerEvent<HTMLDivElement>) => void;
17
+ tipLeft: number;
18
+ flip: boolean;
19
+ classes?: ChartClasses;
20
+ width: number;
21
+ onWidth: (width: number) => void;
22
+ children: ReactNode;
23
+ };
24
+ export declare function ChartFrame({ rows, x, list, fmt, height, label, hover, onHover, onMove, tipLeft, flip, width, onWidth, className, classes, children, ...rest }: FrameProps): import("react").JSX.Element;
25
+ export {};
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // What area and bar charts share: the measured frame, the hover tooltip,
3
+ // the legend and a table of the data for assistive tech. Internal.
4
+ import { useEffect, useRef } from 'react';
5
+ import { cx, partsOf } from '../utils.js';
6
+ import '../theme.css';
7
+ import './chart.css';
8
+ export function ChartFrame({ rows, x, list, fmt, height, label, hover, onHover, onMove, tipLeft, flip, width, onWidth, className, classes, children, ...rest }) {
9
+ const part = partsOf('chart', classes);
10
+ const frame = useRef(null);
11
+ const latest = useRef(onWidth);
12
+ useEffect(() => { latest.current = onWidth; });
13
+ useEffect(() => {
14
+ const el = frame.current;
15
+ if (!el)
16
+ return;
17
+ const observer = new ResizeObserver(() => latest.current(el.clientWidth));
18
+ observer.observe(el);
19
+ return () => observer.disconnect();
20
+ }, []);
21
+ return (_jsxs("div", { ...rest, ...part('root'), className: cx(className, classes?.root), children: [_jsxs("div", { ...part('frame'), ref: frame, style: { height }, onPointerMove: onMove, onPointerLeave: () => onHover(null), role: "presentation", children: [width > 0 && children, hover != null && rows[hover] && (_jsxs("div", { ...part('tooltip'), "data-flip": flip || undefined, style: { left: tipLeft }, children: [_jsx("div", { ...part('tooltip-label'), children: String(rows[hover][x] ?? '') }), list.map((s) => (_jsxs("div", { ...part('tooltip-row'), children: [_jsx("span", { ...part('swatch'), style: { background: s.color } }), _jsx("span", { ...part('tooltip-name'), children: s.label }), _jsx("span", { ...part('tooltip-value'), children: fmt(rows[hover][s.key]) })] }, s.key)))] }))] }), list.length > 1 && (_jsx("div", { ...part('legend'), children: list.map((s) => _jsxs("span", { ...part('legend-item'), children: [_jsx("span", { ...part('swatch'), style: { background: s.color } }), s.label] }, s.key)) })), _jsxs("table", { ...part('sr-only'), children: [_jsx("caption", { children: label }), _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("th", { children: x }), list.map((s) => _jsx("th", { children: s.label }, s.key))] }), rows.map((row, i) => _jsxs("tr", { children: [_jsx("td", { children: String(row[x] ?? '') }), list.map((s) => _jsx("td", { children: fmt(row[s.key]) }, s.key))] }, i))] })] })] }));
22
+ }
@@ -0,0 +1,20 @@
1
+ import { type HTMLAttributes, type ReactNode } from 'react';
2
+ import { type Classes } from '../utils.js';
3
+ import '../theme.css';
4
+ import './chart.css';
5
+ export type DonutSlice = {
6
+ label: string;
7
+ value: number;
8
+ color?: string;
9
+ };
10
+ export type DonutChartProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
11
+ data?: DonutSlice[];
12
+ size?: number;
13
+ thickness?: number;
14
+ center?: ReactNode;
15
+ legend?: 'right' | 'bottom' | false;
16
+ format?: ((value: number) => string) | null;
17
+ label?: string;
18
+ classes?: Classes<'root' | 'svg' | 'slice' | 'center' | 'legend' | 'legendItem' | 'swatch' | 'tooltip'>;
19
+ };
20
+ export declare function DonutChart({ data, size, thickness, center, legend, format, label: labelProp, className, classes, ...rest }: DonutChartProps): import("react").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Parts of a whole as a ring (or a pie), with the total (or your text) in the
4
+ * middle, a legend, and the value on hover.
5
+ *
6
+ * <DonutChart data={[{ label: 'Consults', value: 42 }, { label: 'Surgery', value: 9 }]} />
7
+ * <DonutChart data={byKind} label="Appointments by kind" center={<strong>51</strong>} format={(v) => `${v}`} />
8
+ *
9
+ * - `data`: `{ label, value, color }` (colours from the theme's
10
+ * --ui-chart-1…5 by default, or 'accent', 'success', 'danger', any CSS colour)
11
+ * - `size` (160 px), `thickness` (a share of the radius, 0.32; 1 for a pie)
12
+ * - `center`: what the middle shows (the total by default; null for nothing)
13
+ * - `legend`: beside it ('right', default), 'bottom' or false
14
+ * - `format(value)`, for the total, legend and tooltip
15
+ * - `label`: what it shows, for assistive tech (a hidden table of the data)
16
+ */
17
+ import { useLocale, useMessages } from '../format/LocaleProvider.js';
18
+ import { useState } from 'react';
19
+ import { arc, pie } from 'd3-shape';
20
+ import { seriesColor, formatter } from './chart.js';
21
+ import { cx, partsOf } from '../utils.js';
22
+ import '../theme.css';
23
+ import './chart.css';
24
+ export function DonutChart({ data = [], size = 160, thickness = 0.32, center, legend = 'right', format = null, label: labelProp, className, classes, ...rest }) {
25
+ const t = useMessages();
26
+ const label = labelProp ?? t.chart.label;
27
+ const part = partsOf('donut', classes);
28
+ const [hover, setHover] = useState(null);
29
+ const fmt = formatter(format, useLocale());
30
+ const total = data.reduce((sum, slice) => sum + Math.max(0, slice.value), 0);
31
+ const radius = size / 2;
32
+ const inner = thickness >= 1 ? 0 : radius * (1 - thickness);
33
+ const slices = pie().value((d) => Math.max(0, d.value)).sort(null).padAngle(data.length > 1 && inner > 0 ? 0.012 : 0)(data);
34
+ const shape = arc().innerRadius(inner).outerRadius(radius).cornerRadius(inner > 0 ? 2 : 0);
35
+ const grown = arc().innerRadius(inner).outerRadius(radius + 4).cornerRadius(inner > 0 ? 2 : 0);
36
+ const colorOf = (slice, i) => seriesColor(slice.color, i);
37
+ const shown = hover != null ? data[hover] : null;
38
+ return (_jsxs("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-legend": legend || undefined, children: [_jsxs("div", { ...part('frame'), style: { width: size, height: size }, children: [_jsx("svg", { ...part('svg'), width: size, height: size, viewBox: `${-radius - 4} ${-radius - 4} ${size + 8} ${size + 8}`, "aria-hidden": "true", children: slices.map((s, i) => (_jsx("path", { ...part('slice'), d: (hover === i ? grown : shape)(s) ?? '', fill: colorOf(data[i], i), "data-dim": (hover != null && hover !== i) || undefined, onPointerEnter: () => setHover(i), onPointerLeave: () => setHover(null) }, data[i].label))) }), inner > 0 && center !== null && (_jsx("div", { ...part('center'), children: shown
39
+ ? _jsxs(_Fragment, { children: [_jsx("strong", { children: fmt(shown.value) }), _jsx("span", { children: shown.label })] })
40
+ : center ?? _jsxs(_Fragment, { children: [_jsx("strong", { children: fmt(total) }), _jsx("span", { children: t.chart.total })] }) }))] }), legend && (_jsx("ul", { ...part('legend'), children: data.map((slice, i) => (_jsxs("li", { ...part('legend-item'), "data-dim": (hover != null && hover !== i) || undefined, onPointerEnter: () => setHover(i), onPointerLeave: () => setHover(null), children: [_jsx("span", { ...part('swatch'), style: { background: colorOf(slice, i) } }), _jsx("span", { ...part('legend-label'), children: slice.label }), _jsxs("span", { ...part('legend-value'), children: [fmt(slice.value), total > 0 && _jsxs("small", { children: [" ", Math.round((slice.value / total) * 100), "%"] })] })] }, slice.label))) })), _jsxs("table", { ...part('sr-only'), children: [_jsx("caption", { children: label }), _jsx("tbody", { children: data.map((slice) => _jsxs("tr", { children: [_jsx("td", { children: slice.label }), _jsx("td", { children: fmt(slice.value) })] }, slice.label)) })] })] }));
41
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * A tiny chart without axes, for a trend beside a number or in a table cell.
3
+ *
4
+ * <Sparkline data={[3, 5, 4, 8, 6, 9]} />
5
+ * <Sparkline data={months} y="revenue" variant="area" color="success" />
6
+ *
7
+ * - `data`: numbers (or objects, with `y` naming the value)
8
+ * - `variant`: 'line' (default), 'area' or 'bar'
9
+ * - `color`, `height` (px, 32; it fills its width)
10
+ * - `label`: what it shows, for assistive tech
11
+ */
12
+ import { type HTMLAttributes } from 'react';
13
+ import { type ChartRow } from './chart.js';
14
+ import { type Classes } from '../utils.js';
15
+ import '../theme.css';
16
+ import './chart.css';
17
+ export type SparklineProps = Omit<HTMLAttributes<HTMLDivElement>, 'color'> & {
18
+ data?: (number | ChartRow)[];
19
+ y?: string | null;
20
+ variant?: 'line' | 'area' | 'bar';
21
+ color?: string | null;
22
+ height?: number;
23
+ label?: string | null;
24
+ classes?: Classes<'root' | 'svg' | 'line'>;
25
+ };
26
+ export declare function Sparkline({ data, y, variant, color, height, label, className, classes, style, ...rest }: SparklineProps): import("react").JSX.Element;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /**
3
+ * A tiny chart without axes, for a trend beside a number or in a table cell.
4
+ *
5
+ * <Sparkline data={[3, 5, 4, 8, 6, 9]} />
6
+ * <Sparkline data={months} y="revenue" variant="area" color="success" />
7
+ *
8
+ * - `data`: numbers (or objects, with `y` naming the value)
9
+ * - `variant`: 'line' (default), 'area' or 'bar'
10
+ * - `color`, `height` (px, 32; it fills its width)
11
+ * - `label`: what it shows, for assistive tech
12
+ */
13
+ import { useId } from 'react';
14
+ import { scaleLinear, scaleBand } from 'd3-scale';
15
+ import { area, line, curveMonotoneX } from 'd3-shape';
16
+ import { seriesColor, num } from './chart.js';
17
+ import { cx, partsOf } from '../utils.js';
18
+ import '../theme.css';
19
+ import './chart.css';
20
+ export function Sparkline({ data = [], y = null, variant = 'line', color = null, height = 32, label = null, className, classes, style, ...rest }) {
21
+ const part = partsOf('sparkline', classes);
22
+ const id = useId().replace(/[^\w-]/g, '');
23
+ // Drawn in a 100-wide box stretched to fit; strokes keep their width.
24
+ const values = (data ?? []).map((d) => num(y && typeof d === 'object' ? d[y] : d));
25
+ const max = Math.max(...values, 0);
26
+ const min = variant === 'bar' ? 0 : Math.min(...values, max);
27
+ const ys = scaleLinear().domain([min, max === min ? min + 1 : max]).range([height - 2, 2]);
28
+ const xs = scaleLinear().domain([0, Math.max(1, values.length - 1)]).range([1, 99]);
29
+ const colour = seriesColor(color, 0);
30
+ const path = line().x((_, i) => xs(i)).y((v) => ys(v)).curve(curveMonotoneX);
31
+ const fill = area().x((_, i) => xs(i)).y0(height).y1((v) => ys(v)).curve(curveMonotoneX);
32
+ const band = scaleBand().domain(values.map((_, i) => i)).range([0, 100]).padding(0.25);
33
+ return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), role: "img", "aria-label": label ?? `Trend: ${values.join(', ')}`, style: { ...style, height }, children: _jsx("svg", { ...part('svg'), viewBox: `0 0 100 ${height}`, preserveAspectRatio: "none", "aria-hidden": "true", children: variant === 'bar'
34
+ ? values.map((value, i) => (_jsx("rect", { x: band(i), y: ys(value), width: band.bandwidth(), height: Math.max(0, height - ys(value)), fill: colour, rx: "0.5" }, i)))
35
+ : _jsxs(_Fragment, { children: [variant === 'area' && _jsxs(_Fragment, { children: [_jsx("defs", { children: _jsxs("linearGradient", { id: id, x1: "0", x2: "0", y1: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: colour, stopOpacity: "0.3" }), _jsx("stop", { offset: "100%", stopColor: colour, stopOpacity: "0" })] }) }), _jsx("path", { d: fill(values) ?? '', fill: `url(#${id})` })] }), _jsx("path", { ...part('line'), d: path(values) ?? '', fill: "none", stroke: colour })] }) }) }));
36
+ }
@@ -0,0 +1,51 @@
1
+ @layer ui {
2
+ [data-scope=chart][data-part=root] { display: block; position: relative; width: 100%; min-width: 0; color: var(--ui-text); font-family: var(--ui-font); font-size: 14px; }
3
+ [data-scope=chart][data-part=frame] { position: relative; width: 100%; }
4
+ [data-scope=chart][data-part=svg] { display: block; overflow: visible; }
5
+ [data-scope=chart][data-part=grid] { stroke: var(--ui-border); stroke-dasharray: 3 3; }
6
+ [data-scope=chart][data-part=tick] { fill: var(--ui-muted); font-size: 11px; }
7
+ [data-scope=chart][data-part=line] { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
8
+ [data-scope=chart][data-part=cursor] { stroke: var(--ui-muted); stroke-width: 1; stroke-dasharray: 3 3; }
9
+ [data-scope=chart][data-part=dot] { stroke: var(--ui-surface); stroke-width: 2; }
10
+ [data-scope=chart][data-part=bar] { transition: opacity 120ms; &[data-dim] { opacity: 0.45; } }
11
+ [data-scope=chart][data-part=band] { fill: var(--ui-hover); }
12
+ /* The tooltip sits beside the hovered point (to its left near the right edge), clear of the line and dots. */
13
+ [data-scope=chart][data-part=tooltip] { position: absolute; top: 0; z-index: 5; min-width: 8rem; padding: 0.5rem 0.625rem;
14
+ background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-radius); box-shadow: var(--ui-shadow);
15
+ font-size: 12px; pointer-events: none; white-space: nowrap; transform: translateX(12px);
16
+ &[data-flip] { transform: translateX(calc(-100% - 12px)); } }
17
+ [data-scope=chart][data-part=tooltip-label] { font-weight: 600; margin-bottom: 0.25rem; }
18
+ [data-scope=chart][data-part=tooltip-row] { display: flex; align-items: center; gap: 0.5rem; line-height: 1.6; }
19
+ [data-scope=chart][data-part=tooltip-name] { color: var(--ui-muted); flex-grow: 1; }
20
+ [data-scope=chart][data-part=tooltip-value] { font-weight: 500; }
21
+ [data-scope=chart][data-part=swatch] { display: inline-block; width: 0.625rem; height: 0.625rem; border-radius: 9999px; flex-shrink: 0; }
22
+ [data-scope=chart][data-part=legend] { display: flex; flex-wrap: wrap; gap: 1rem; margin-top: 0.5rem; font-size: 12px; color: var(--ui-muted); }
23
+ [data-scope=chart][data-part=legend-item] { display: inline-flex; align-items: center; gap: 0.375rem; }
24
+ [data-scope=chart][data-part=sr-only] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
25
+
26
+ [data-scope=sparkline][data-part=root] { display: block; width: 100%; min-width: 4rem; }
27
+ [data-scope=sparkline][data-part=svg] { display: block; width: 100%; height: 100%; overflow: visible; }
28
+ /* Strokes keep their width though the drawing is stretched to fit. */
29
+ [data-scope=sparkline][data-part=line] { stroke-width: 1.75; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
30
+ }
31
+ @layer ui {
32
+ [data-scope=donut][data-part=root] { display: inline-flex; align-items: center; gap: 1.5rem; max-width: 100%; color: var(--ui-text); font-family: var(--ui-font); font-size: 13px;
33
+ &[data-legend=bottom] { flex-direction: column; gap: 1rem; } }
34
+ [data-scope=donut][data-part=frame] { position: relative; flex-shrink: 0; }
35
+ [data-scope=donut][data-part=svg] { display: block; overflow: visible; }
36
+ [data-scope=donut][data-part=slice] { transition: opacity 120ms; cursor: default;
37
+ &[data-dim] { opacity: 0.45; } }
38
+ [data-scope=donut][data-part=center] { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
39
+ pointer-events: none; text-align: center;
40
+ & strong { font-size: 20px; font-weight: 700; line-height: 1.1; }
41
+ & span { color: var(--ui-muted); font-size: 12px; } }
42
+ [data-scope=donut][data-part=legend] { display: flex; flex-direction: column; gap: 0.375rem; margin: 0; padding: 0; list-style: none; min-width: 0;
43
+ [data-legend=bottom] > & { flex-direction: row; flex-wrap: wrap; justify-content: center; gap: 0.375rem 1rem; } }
44
+ [data-scope=donut][data-part=legend-item] { display: flex; align-items: center; gap: 0.5rem; transition: opacity 120ms;
45
+ &[data-dim] { opacity: 0.45; } }
46
+ [data-scope=donut][data-part=swatch] { width: 0.625rem; height: 0.625rem; border-radius: 9999px; flex-shrink: 0; }
47
+ [data-scope=donut][data-part=legend-label] { flex-grow: 1; color: var(--ui-muted); }
48
+ [data-scope=donut][data-part=legend-value] { font-weight: 600; font-variant-numeric: tabular-nums;
49
+ & small { color: var(--ui-muted); font-weight: 400; font-size: 11px; } }
50
+ [data-scope=donut][data-part=sr-only] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
51
+ }
@@ -0,0 +1,23 @@
1
+ export type ChartRow = Record<string, unknown>;
2
+ export type ChartSeries = {
3
+ key: string;
4
+ label?: string;
5
+ color?: string;
6
+ };
7
+ export type Series = {
8
+ key: string;
9
+ label: string;
10
+ color: string;
11
+ };
12
+ export declare function seriesColor(color: string | null | undefined, index: number): string;
13
+ export declare const seriesOf: (series: ChartSeries[] | null | undefined, y: string | null | undefined) => Series[];
14
+ export declare const formatter: (format: ((value: number) => string) | null | undefined, locale?: string) => (value: unknown) => string;
15
+ export declare const pad: {
16
+ top: number;
17
+ bottom: number;
18
+ right: number;
19
+ };
20
+ export declare const yScale: (max: number, innerH: number) => import("d3-scale").ScaleLinear<number, number, never>;
21
+ export declare const leftFor: (ticks: number[], fmt: (value: unknown) => string) => number;
22
+ export declare const labelStep: (count: number, innerW: number) => number;
23
+ export declare const num: (value: unknown) => number;
@@ -0,0 +1,19 @@
1
+ import { scaleLinear } from 'd3-scale';
2
+ export function seriesColor(color, index) {
3
+ if (!color)
4
+ return `var(--ui-chart-${(index % 5) + 1})`;
5
+ if (['accent', 'success', 'danger'].includes(color))
6
+ return `var(--ui-${color})`;
7
+ return color;
8
+ }
9
+ // The series to draw: `series`, or one from `y`.
10
+ export const seriesOf = (series, y) => (series ?? (y ? [{ key: y, label: y }] : [])).map((s, i) => ({ ...s, label: s.label ?? s.key, color: seriesColor(s.color, i) }));
11
+ export const formatter = (format, locale) => (value) => format ? format(value) : typeof value === 'number' ? value.toLocaleString(locale) : String(value ?? '');
12
+ // The plot area inside the axes.
13
+ export const pad = { top: 8, bottom: 24, right: 8 };
14
+ export const yScale = (max, innerH) => scaleLinear().domain([0, max || 1]).nice().range([innerH, 0]);
15
+ // Room for the longest tick label.
16
+ export const leftFor = (ticks, fmt) => Math.max(...ticks.map((tick) => fmt(tick).length), 1) * 7 + 12;
17
+ // Every nth x label, so they don't collide.
18
+ export const labelStep = (count, innerW) => Math.max(1, Math.ceil(count / Math.max(1, Math.floor(innerW / 64))));
19
+ export const num = (value) => +value || 0;
@@ -0,0 +1,50 @@
1
+ /**
2
+ * A checkbox with its label.
3
+ *
4
+ * <Checkbox checked={agreed} onCheckedChange={setAgreed}>I agree to the terms</Checkbox>
5
+ *
6
+ * - `checked`, `defaultChecked`: true, false or 'indeterminate'
7
+ * - `onCheckedChange`: called with the new checked state (a boolean)
8
+ * - `label`: its text (or children, for richer content)
9
+ * - `labelHidden`: keep the label for assistive tech only (a table's row checkbox)
10
+ * - `indicator`: replaces the tick and dash; a node, or ({ indeterminate }) => node
11
+ * - `name`, `value`: for plain form posts
12
+ * - `disabled`, `invalid`, `required`
13
+ *
14
+ * By hand, with Ark's parts:
15
+ *
16
+ * <Checkbox.Root>
17
+ * <Checkbox.Control>
18
+ * <Checkbox.Indicator>✓</Checkbox.Indicator>
19
+ * <Checkbox.Indicator indeterminate>–</Checkbox.Indicator>
20
+ * </Checkbox.Control>
21
+ * <Checkbox.Label>Terms</Checkbox.Label>
22
+ * <Checkbox.HiddenInput />
23
+ * </Checkbox.Root>
24
+ */
25
+ import type { ReactNode } from 'react';
26
+ import { Checkbox as Ark, type CheckboxCheckedChangeDetails } from '@ark-ui/react/checkbox';
27
+ import 'iconify-icon';
28
+ import { type Classes, type Slot } from '../utils.js';
29
+ import '../theme.css';
30
+ import './checkbox.css';
31
+ export type CheckboxProps = Omit<Ark.RootProps, 'onCheckedChange' | 'label'> & {
32
+ onCheckedChange?: (checked: boolean, details: CheckboxCheckedChangeDetails) => void;
33
+ label?: ReactNode;
34
+ labelHidden?: boolean;
35
+ indicator?: Slot<{
36
+ indeterminate: boolean;
37
+ }>;
38
+ classes?: Classes<'root' | 'control' | 'indicator' | 'label'>;
39
+ };
40
+ declare function CheckboxShortcut({ onCheckedChange, label, labelHidden, indicator, className, classes, children, ...rest }: CheckboxProps): import("react").JSX.Element;
41
+ export declare const Checkbox: typeof CheckboxShortcut & {
42
+ Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLLabelElement>>;
43
+ Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
44
+ Indicator: import("react").ForwardRefExoticComponent<Ark.IndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
45
+ Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLSpanElement>>;
46
+ HiddenInput: import("react").ForwardRefExoticComponent<Ark.HiddenInputProps & import("react").RefAttributes<HTMLInputElement>>;
47
+ Group: import("react").ForwardRefExoticComponent<Ark.GroupProps & import("react").RefAttributes<HTMLDivElement>>;
48
+ Context: (props: Ark.ContextProps) => ReactNode;
49
+ };
50
+ export {};
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Checkbox as Ark } from '@ark-ui/react/checkbox';
3
+ import 'iconify-icon';
4
+ import { cx, on, partsOf, slot, withParts } from '../utils.js';
5
+ import '../theme.css';
6
+ import './checkbox.css';
7
+ function CheckboxShortcut({ onCheckedChange, label = null, labelHidden = false, indicator, className, classes, children, ...rest }) {
8
+ const part = partsOf('checkbox', classes);
9
+ return (_jsxs(Ark.Root, { ...rest, ...part('root'), className: cx(className, classes?.root), ...on('onCheckedChange', onCheckedChange, (details) => details.checked === true), children: [_jsxs(Ark.Control, { ...part('control'), children: [_jsx(Ark.Indicator, { ...part('indicator'), children: slot(indicator, _jsx("iconify-icon", { icon: "lucide:check", "aria-hidden": "true" }), { indeterminate: false }) }), _jsx(Ark.Indicator, { ...part('indicator'), indeterminate: true, children: slot(indicator, _jsx("iconify-icon", { icon: "lucide:minus", "aria-hidden": "true" }), { indeterminate: true }) })] }), (label || children) && _jsx(Ark.Label, { ...part('label'), "data-hidden": labelHidden || undefined, children: children ?? label }), _jsx(Ark.HiddenInput, {})] }));
10
+ }
11
+ export const Checkbox = withParts(CheckboxShortcut, {
12
+ Root: Ark.Root,
13
+ Control: Ark.Control,
14
+ Indicator: Ark.Indicator,
15
+ Label: Ark.Label,
16
+ HiddenInput: Ark.HiddenInput,
17
+ Group: Ark.Group,
18
+ Context: Ark.Context,
19
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * A group of checkboxes, for an array of checked values.
3
+ *
4
+ * <CheckboxGroup label="Subjects" items={['Maths', 'Physics', 'Chemistry']} value={subjects} onValueChange={setSubjects} selectAll="All subjects" />
5
+ *
6
+ * - `items`: strings, or objects (see `labelKey`, `valueKey`, `disabledKey`)
7
+ * - `value`, `defaultValue`: the checked items' own values, in item order;
8
+ * `onValueChange`: called with the new array
9
+ * - `selectAll`: label for a parent checkbox that selects all or none, and is
10
+ * indeterminate when only some are (disabled items keep their state)
11
+ * - `label`: a label above it (skipped inside a Field)
12
+ * - `orientation`: 'vertical' or 'horizontal'
13
+ * - `variant`: 'list' (default); 'cards': each a bordered card (with the
14
+ * item's `description` and `icon`), ticked ones outlined; 'chips': pills
15
+ * that toggle, e.g. quick filters above a table
16
+ * - `name`: each checkbox posts its value with plain forms
17
+ *
18
+ * Shift-clicking a checkbox ticks (or clears) every item between it and the
19
+ * last one clicked, as in mail and file lists.
20
+ */
21
+ import 'iconify-icon';
22
+ import { type ComponentProps, type ReactNode } from 'react';
23
+ import { type Classes } from '../utils.js';
24
+ import '../theme.css';
25
+ import './checkbox.css';
26
+ type Value<T, K extends string> = T extends string | number ? T : K extends keyof T ? T[K] : unknown;
27
+ export type CheckboxGroupProps<T, K extends string = 'value'> = Omit<ComponentProps<'div'>, 'defaultValue'> & {
28
+ items: readonly T[];
29
+ value?: NoInfer<Value<T, K>>[];
30
+ defaultValue?: NoInfer<Value<T, K>>[];
31
+ onValueChange?: (value: NoInfer<Value<T, K>>[]) => void;
32
+ label?: ReactNode;
33
+ selectAll?: ReactNode;
34
+ orientation?: 'vertical' | 'horizontal';
35
+ variant?: 'list' | 'cards' | 'chips';
36
+ labelKey?: string;
37
+ valueKey?: K;
38
+ disabledKey?: string;
39
+ name?: string;
40
+ disabled?: boolean;
41
+ classes?: Classes<'root' | 'label' | 'items' | 'cardText' | 'icon' | 'cardLabel' | 'description'>;
42
+ };
43
+ export declare function CheckboxGroup<T, K extends string = 'value'>({ items, value, defaultValue, onValueChange, label, selectAll, orientation, variant, labelKey, valueKey, disabledKey, name, disabled, className, classes, ...rest }: CheckboxGroupProps<T, K>): import("react").JSX.Element;
44
+ export {};
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A group of checkboxes, for an array of checked values.
4
+ *
5
+ * <CheckboxGroup label="Subjects" items={['Maths', 'Physics', 'Chemistry']} value={subjects} onValueChange={setSubjects} selectAll="All subjects" />
6
+ *
7
+ * - `items`: strings, or objects (see `labelKey`, `valueKey`, `disabledKey`)
8
+ * - `value`, `defaultValue`: the checked items' own values, in item order;
9
+ * `onValueChange`: called with the new array
10
+ * - `selectAll`: label for a parent checkbox that selects all or none, and is
11
+ * indeterminate when only some are (disabled items keep their state)
12
+ * - `label`: a label above it (skipped inside a Field)
13
+ * - `orientation`: 'vertical' or 'horizontal'
14
+ * - `variant`: 'list' (default); 'cards': each a bordered card (with the
15
+ * item's `description` and `icon`), ticked ones outlined; 'chips': pills
16
+ * that toggle, e.g. quick filters above a table
17
+ * - `name`: each checkbox posts its value with plain forms
18
+ *
19
+ * Shift-clicking a checkbox ticks (or clears) every item between it and the
20
+ * last one clicked, as in mail and file lists.
21
+ */
22
+ import 'iconify-icon';
23
+ import { useId, useRef } from 'react';
24
+ import { useFieldContext } from '@ark-ui/react/field';
25
+ import { Checkbox } from './Checkbox.js';
26
+ import { cx, partsOf, useControllable } from '../utils.js';
27
+ import '../theme.css';
28
+ import './checkbox.css';
29
+ export function CheckboxGroup({ items, value, defaultValue, onValueChange, label = null, selectAll = null, orientation = 'vertical', variant = 'list', labelKey = 'label', valueKey = 'value', disabledKey = 'disabled', name, disabled = false, className, classes, ...rest }) {
30
+ const field = useFieldContext();
31
+ const part = partsOf('checkbox-group', classes);
32
+ const id = useId();
33
+ const [checked, setChecked] = useControllable(value, defaultValue ?? [], onValueChange);
34
+ // The latest value, so two changes before a render build on each other.
35
+ const latest = useRef(checked);
36
+ latest.current = checked;
37
+ const key = (item, k) => item[k];
38
+ const isObject = (item) => typeof item === 'object' && item !== null;
39
+ const labelOf = (item) => String(isObject(item) ? key(item, labelKey) : item);
40
+ const valueOf = (item) => (isObject(item) ? key(item, valueKey) : item);
41
+ // As a card: the item's icon and description with its label.
42
+ const labelFor = (item) => {
43
+ const icon = isObject(item) ? key(item, 'icon') : undefined;
44
+ const description = isObject(item) ? key(item, 'description') : null;
45
+ if (variant !== 'cards' || (!icon && !description))
46
+ return labelOf(item);
47
+ return _jsxs("span", { ...part('card-text'), children: [icon && _jsx("iconify-icon", { ...part('icon'), icon: icon, "aria-hidden": "true" }), _jsxs("span", { children: [_jsx("span", { ...part('card-label'), children: labelOf(item) }), description && _jsx("span", { ...part('description'), children: description })] })] });
48
+ };
49
+ const isSelected = (item) => latest.current.includes(valueOf(item));
50
+ const isLocked = (item) => disabled || (isObject(item) && !!key(item, disabledKey));
51
+ const select = (list) => {
52
+ latest.current = list.map(valueOf);
53
+ setChecked(latest.current);
54
+ };
55
+ // Checkbox changes don't carry the Shift key, so it's noted on the way in
56
+ // (Shift+mousedown would also select the labels' text between).
57
+ const shift = useRef(false);
58
+ const anchor = useRef(null);
59
+ function noteShift(e) {
60
+ shift.current = e.shiftKey;
61
+ if (e.shiftKey && e.type === 'mousedown')
62
+ e.preventDefault();
63
+ }
64
+ function toggle(item, on) {
65
+ const index = items.indexOf(item);
66
+ const from = anchor.current;
67
+ anchor.current = index;
68
+ if (shift.current && from != null && from !== index && items[from]) {
69
+ const [lo, hi] = [Math.min(from, index), Math.max(from, index)];
70
+ return select(items.filter((it, n) => (n >= lo && n <= hi && !isLocked(it) ? on : isSelected(it))));
71
+ }
72
+ select(items.filter((it) => (it === item ? on : isSelected(it))));
73
+ }
74
+ const toggleAll = (on) => select(items.filter((it) => (isLocked(it) ? isSelected(it) : on)));
75
+ // The select-all box: checked when every enabled item is, indeterminate when only some are.
76
+ const open = items.filter((it) => !isLocked(it));
77
+ const on = open.filter(isSelected).length;
78
+ const allState = on === 0 ? false : on === open.length ? true : 'indeterminate';
79
+ return (_jsxs("div", { ...rest, ...part('root'), className: cx(className, classes?.root), role: "group", "data-variant": variant, "aria-labelledby": field ? field.ids.label : label ? `${id}-label` : undefined, children: [label && !field && _jsx("span", { ...part('label'), id: `${id}-label`, children: label }), selectAll && _jsx(Checkbox, { label: selectAll, disabled: disabled, checked: allState, onCheckedChange: toggleAll }), _jsx("div", { ...part('items'), "data-orientation": orientation, "data-indented": selectAll ? '' : undefined, onPointerDownCapture: noteShift, onMouseDownCapture: noteShift, onKeyDownCapture: noteShift, children: items.map((item) => (_jsx(Checkbox, { label: labelFor(item), value: String(valueOf(item)), disabled: isLocked(item), ...(name && { name }), checked: isSelected(item), onCheckedChange: (on) => toggle(item, on) }, String(valueOf(item))))) })] }));
80
+ }
@@ -0,0 +1,49 @@
1
+ @layer ui {
2
+ [data-scope=checkbox][data-part=root] { display: inline-flex; align-items: center; gap: 0.5rem; vertical-align: top; cursor: pointer;
3
+ color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
4
+ &[data-disabled] { opacity: 0.5; cursor: not-allowed; } }
5
+ [data-scope=checkbox][data-part=control] { display: grid; place-items: center; flex-shrink: 0; width: 1rem; height: 1rem; box-sizing: border-box;
6
+ background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) - 2px); color: var(--ui-accent-text);
7
+ &:is([data-state=checked], [data-state=indeterminate]) { background: var(--ui-accent); border-color: var(--ui-accent); }
8
+ &[data-focus-visible] { outline: 2px solid var(--ui-ring-soft); border-color: var(--ui-ring); }
9
+ &[data-invalid] { border-color: var(--ui-danger); } }
10
+ [data-scope=checkbox][data-part=label][data-hidden] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
11
+ [data-scope=checkbox][data-part=indicator] { display: none; font-size: 12px; line-height: 1;
12
+ &:is([data-state=checked], [data-state=indeterminate]) { display: grid; }
13
+ /* Ark renders one for the tick and one for the dash, hiding the other. */
14
+ &[hidden] { display: none; } }
15
+
16
+ [data-scope=checkbox-group][data-part=root] { display: block; color: var(--ui-text); font-family: var(--ui-font); }
17
+ [data-scope=checkbox-group][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
18
+ [data-scope=checkbox-group][data-part=root] > [data-scope=checkbox] { margin-bottom: 0.5rem; }
19
+ [data-scope=checkbox-group][data-part=items] { display: flex; flex-direction: column; gap: 0.5rem;
20
+ &[data-orientation=horizontal] { flex-direction: row; flex-wrap: wrap; gap: 0.5rem 1rem; }
21
+ &[data-indented] { padding-left: 1.5rem; } }
22
+ /* Cards: each a bordered card (icon, label, description), ticked ones outlined. */
23
+ [data-scope=checkbox-group][data-variant=cards] {
24
+ & > [data-part=items] { display: grid; grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr)); gap: 0.5rem; }
25
+ & [data-scope=checkbox][data-part=root] { align-items: flex-start; gap: 0.625rem; padding: 0.75rem; border: 1px solid var(--ui-border);
26
+ border-radius: calc(var(--ui-radius) + 2px); background: var(--ui-surface); transition: border-color 120ms, background 120ms;
27
+ &:hover { border-color: var(--ui-muted); }
28
+ &[data-state=checked] { border-color: var(--ui-accent); background: color-mix(in srgb, var(--ui-accent-soft) 45%, var(--ui-surface));
29
+ box-shadow: 0 0 0 1px var(--ui-accent); }
30
+ &[data-state=checked] [data-part=icon] { color: var(--ui-accent); } }
31
+ & [data-scope=checkbox][data-part=control] { margin-top: 0.125rem; } }
32
+ [data-scope=checkbox-group][data-part=card-text] { display: flex; align-items: flex-start; gap: 0.625rem; }
33
+ [data-scope=checkbox-group][data-part=icon] { flex-shrink: 0; color: var(--ui-muted); font-size: 18px; }
34
+ [data-scope=checkbox-group][data-part=card-label] { display: block; font-weight: 500; }
35
+ [data-scope=checkbox-group][data-part=description] { display: block; margin-top: 0.125rem; color: var(--ui-muted); font-size: 12px; font-weight: 400; }
36
+
37
+ /* Chips: pills that toggle (the tick shows inside when on). */
38
+ [data-scope=checkbox-group][data-variant=chips] {
39
+ & > [data-part=items] { flex-direction: row; flex-wrap: wrap; gap: 0.375rem; }
40
+ & [data-scope=checkbox][data-part=root] { gap: 0.25rem; height: 1.875rem; padding: 0 0.75rem; box-sizing: border-box; border: 1px solid var(--ui-border);
41
+ border-radius: 9999px; background: var(--ui-surface); font-size: 13px; transition: background 120ms, border-color 120ms;
42
+ &:hover { border-color: var(--ui-muted); }
43
+ &[data-state=checked] { border-color: transparent; background: var(--ui-accent-soft); color: var(--ui-accent-soft-text); }
44
+ &:has([data-focus-visible]) { outline: 2px solid var(--ui-ring-soft); }
45
+ &[data-disabled] { opacity: 0.5; } }
46
+ /* The box is hidden; a tick shows only when it's on. */
47
+ & [data-scope=checkbox][data-part=control] { width: auto; height: auto; border: none; background: transparent; color: inherit; outline: none;
48
+ &:not([data-state=checked]) { display: none; } } }
49
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * A section that opens and closes, animating its height.
3
+ *
4
+ * <Collapsible heading="Lesson notes">…</Collapsible>
5
+ * <Collapsible open={open} onOpenChange={setOpen} trigger={<Button>Show more</Button>}>…</Collapsible>
6
+ *
7
+ * - `heading`: a built-in toggle with a chevron
8
+ * - `trigger`: your own toggle instead (it gets the trigger's props, so a
9
+ * component must pass them on, as Button does)
10
+ * - `open`, `defaultOpen`; `onOpenChange`: called with the new open state
11
+ * - `disabled`
12
+ *
13
+ * By hand, with Ark's parts:
14
+ *
15
+ * <Collapsible.Root>
16
+ * <Collapsible.Trigger>Notes <Collapsible.Indicator>▾</Collapsible.Indicator></Collapsible.Trigger>
17
+ * <Collapsible.Content>…</Collapsible.Content>
18
+ * </Collapsible.Root>
19
+ */
20
+ import type { ReactElement, ReactNode } from 'react';
21
+ import { Collapsible as Ark, type CollapsibleOpenChangeDetails } from '@ark-ui/react/collapsible';
22
+ import 'iconify-icon';
23
+ import { type Classes } from '../utils.js';
24
+ import '../theme.css';
25
+ import './collapsible.css';
26
+ export type CollapsibleProps = Omit<Ark.RootProps, 'onOpenChange'> & {
27
+ heading?: ReactNode;
28
+ trigger?: ReactElement | null;
29
+ onOpenChange?: (open: boolean, details: CollapsibleOpenChangeDetails) => void;
30
+ classes?: Classes<'root' | 'trigger' | 'heading' | 'indicator' | 'content' | 'inner'>;
31
+ };
32
+ declare function CollapsibleShortcut({ heading, trigger, onOpenChange, className, classes, children, ...rest }: CollapsibleProps): import("react").JSX.Element;
33
+ export declare const Collapsible: typeof CollapsibleShortcut & {
34
+ Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
35
+ RootProvider: import("react").ForwardRefExoticComponent<Ark.RootProviderProps & import("react").RefAttributes<HTMLDivElement>>;
36
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
37
+ Indicator: import("react").ForwardRefExoticComponent<Ark.IndicatorProps & import("react").RefAttributes<HTMLDivElement>>;
38
+ Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
39
+ Context: (props: Ark.ContextProps) => ReactNode;
40
+ };
41
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Collapsible as Ark } from '@ark-ui/react/collapsible';
3
+ import 'iconify-icon';
4
+ import { cx, on, partsOf, withParts } from '../utils.js';
5
+ import '../theme.css';
6
+ import './collapsible.css';
7
+ function CollapsibleShortcut({ heading = null, trigger = null, onOpenChange, className, classes, children, ...rest }) {
8
+ const part = partsOf('collapsible', classes);
9
+ return (_jsxs(Ark.Root, { ...rest, ...part('root'), className: cx(className, classes?.root), ...on('onOpenChange', onOpenChange, (details) => details.open), children: [trigger
10
+ ? _jsx(Ark.Trigger, { asChild: true, children: trigger })
11
+ : heading && (_jsxs(Ark.Trigger, { ...part('trigger'), children: [_jsx("span", { ...part('heading'), children: heading }), _jsx(Ark.Indicator, { ...part('indicator'), children: _jsx("iconify-icon", { icon: "lucide:chevron-down" }) })] })), _jsx(Ark.Content, { ...part('content'), children: _jsx("div", { ...part('inner'), children: children }) })] }));
12
+ }
13
+ export const Collapsible = withParts(CollapsibleShortcut, {
14
+ Root: Ark.Root, RootProvider: Ark.RootProvider, Trigger: Ark.Trigger, Indicator: Ark.Indicator, Content: Ark.Content, Context: Ark.Context,
15
+ });
@@ -0,0 +1,20 @@
1
+ @layer ui {
2
+ /* Full width, so the toggle doesn't grow when the content opens. */
3
+ [data-scope=collapsible][data-part=root] { display: block; width: 100%; color: var(--ui-text); font-family: var(--ui-font); }
4
+ [data-scope=collapsible][data-part=trigger]:not([data-scope=button]) {
5
+ display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; width: 100%; padding: 0.5rem 0;
6
+ border: none; background: transparent; color: inherit; font: inherit; font-size: 14px; font-weight: 500; text-align: left;
7
+ cursor: pointer; outline: none; border-radius: 4px;
8
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); outline-offset: 2px; }
9
+ &:disabled { opacity: 0.5; cursor: not-allowed; }
10
+ }
11
+ [data-scope=collapsible][data-part=indicator] { display: inline-flex; color: var(--ui-muted); font-size: 16px; transition: transform 200ms;
12
+ &[data-state=open] { transform: rotate(180deg); } }
13
+ /* Zag measures the content into --height and keeps it shown until the closing animation ends. */
14
+ [data-scope=collapsible][data-part=content] { overflow: hidden;
15
+ &[data-state=open] { animation: ui-collapsible-open 200ms ease-out; }
16
+ &[data-state=closed] { animation: ui-collapsible-close 160ms ease-in; } }
17
+ [data-scope=collapsible][data-part=inner] { padding-top: 0.5rem; font-size: 14px; }
18
+ @keyframes ui-collapsible-open { from { height: 0; opacity: 0; } to { height: var(--height); opacity: 1; } }
19
+ @keyframes ui-collapsible-close { from { height: var(--height); opacity: 1; } to { height: 0; opacity: 0; } }
20
+ }