@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,31 @@
1
+ /**
2
+ * The signed-in user, for the sidebar's footer: avatar, name and a line under
3
+ * it, opening a menu of account actions upwards (to the right in the rail,
4
+ * where only the avatar shows).
5
+ *
6
+ * <SidebarUser name="Ada Lovelace" description="ada@example.com" items={account} onSelect={run} />
7
+ *
8
+ * It's a Menu with the user as its trigger: give it `items`, Menu's child
9
+ * components, or both, and `onSelect` (the rest of Menu's props apply too).
10
+ *
11
+ * - `name`: who's signed in; `description`: a line under it (their email);
12
+ * `src`: their photo
13
+ * - `placement`: where the menu opens ('top-start', or 'right-end' in the rail)
14
+ * - `letters`, `color`: passed to the Avatar
15
+ */
16
+ import type { ReactNode } from 'react';
17
+ import 'iconify-icon';
18
+ import { type MenuProps } from '../menu/Menu.js';
19
+ import { type AvatarColor } from '../avatar/Avatar.js';
20
+ import { type Classes } from '../utils.js';
21
+ import '../theme.css';
22
+ import './sidebar.css';
23
+ export type SidebarUserProps = Omit<MenuProps, 'trigger' | 'classes' | 'color'> & {
24
+ name?: string;
25
+ description?: ReactNode;
26
+ src?: string | null;
27
+ letters?: 1 | 2;
28
+ color?: AvatarColor | 'auto';
29
+ classes?: Classes<'root' | 'trigger' | 'text' | 'name' | 'description' | 'indicator'> & MenuProps['classes'];
30
+ };
31
+ export declare function SidebarUser({ name, description, src, letters, color, placement, className, classes, ...rest }: SidebarUserProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import 'iconify-icon';
3
+ import { Menu } from '../menu/Menu.js';
4
+ import { Avatar } from '../avatar/Avatar.js';
5
+ import { useShell } from '../app-shell/shell.js';
6
+ import { cx, partsOf } from '../utils.js';
7
+ import '../theme.css';
8
+ import './sidebar.css';
9
+ export function SidebarUser({ name = '', description = null, src = null, letters = 2, color = 'accent', placement, className, classes, ...rest }) {
10
+ const shell = useShell();
11
+ const part = partsOf('sidebar-user', classes);
12
+ const rail = !!shell?.rail;
13
+ return (_jsx("div", { ...part('root'), className: cx(className, classes?.root), "data-rail": rail || undefined, children: _jsx(Menu, { ...rest, classes: classes, placement: placement ?? (rail ? 'right-end' : 'top-start'), trigger: _jsxs("button", { ...part('trigger'), type: "button", "aria-label": rail ? name : undefined, children: [_jsx(Avatar, { name: name, src: src, size: "sm", letters: letters, color: color }), _jsxs("span", { ...part('text'), children: [_jsx("span", { ...part('name'), children: name }), description && _jsx("span", { ...part('description'), children: description })] }), _jsx("iconify-icon", { ...part('indicator'), icon: "lucide:chevrons-up-down", "aria-hidden": "true" })] }) }) }));
14
+ }
@@ -0,0 +1,13 @@
1
+ export type SidebarState = {
2
+ accordion: boolean;
3
+ openGroup: string | null;
4
+ setOpenGroup: (id: string | null) => void;
5
+ };
6
+ export declare const SidebarContext: import("react").Context<SidebarState | null>;
7
+ export declare const useSidebar: () => SidebarState | null;
8
+ export type SectionState = {
9
+ register: (id: string, setOpen: (open: boolean) => void) => () => void;
10
+ report: (id: string, open: boolean) => void;
11
+ };
12
+ export declare const SectionContext: import("react").Context<SectionState | null>;
13
+ export declare const FlyoutContext: import("react").Context<boolean>;
@@ -0,0 +1,6 @@
1
+ import { createContext, useContext } from 'react';
2
+ export const SidebarContext = createContext(null);
3
+ export const useSidebar = () => useContext(SidebarContext);
4
+ export const SectionContext = createContext(null);
5
+ // Inside a rail group's flyout, items keep their labels (no rail tooltips).
6
+ export const FlyoutContext = createContext(false);
@@ -0,0 +1,109 @@
1
+ @layer ui {
2
+ [data-scope=sidebar][data-part=root] { display: flex; flex-direction: column; width: var(--ui-sidebar-width); height: 100dvh;
3
+ position: sticky; top: 0; z-index: 35; flex-shrink: 0; box-sizing: border-box; overflow: hidden;
4
+ background: var(--ui-sidebar-surface); border-right: 1px solid var(--ui-border); font-size: 14px; }
5
+ [data-scope=sidebar][data-part=root] {
6
+ transition: width 200ms ease;
7
+ /* The rail: icons only. */
8
+ &[data-rail] { width: var(--ui-sidebar-rail-width); }
9
+ /* The drawer, on phones. */
10
+ &[data-mobile] { position: fixed; left: 0; top: 0; bottom: 0; z-index: 50; width: min(18rem, 85vw); display: none;
11
+ box-shadow: var(--ui-shadow); background: var(--ui-surface); }
12
+ &[data-mobile][data-open] { display: flex; animation: ui-drawer-in 200ms ease-out; } }
13
+ @keyframes ui-drawer-in { from { translate: -100% 0; } }
14
+ [data-scope=sidebar][data-part=header] { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
15
+ height: 3.5rem; padding: 0 0.5rem 0 1rem; flex-shrink: 0; box-sizing: border-box; font-weight: 700; white-space: nowrap;
16
+ [data-rail] > & { justify-content: center; padding: 0; } }
17
+ [data-scope=sidebar][data-part=logo] { display: flex; align-items: center; min-width: 0; overflow: hidden; }
18
+ [data-scope=sidebar][data-part=toggle] { display: grid; place-items: center; width: 2rem; height: 2rem; flex-shrink: 0; padding: 0;
19
+ border: none; background: transparent; border-radius: var(--ui-radius); color: var(--ui-sidebar-icon); font-size: 16px; cursor: pointer;
20
+ &:hover { background: var(--ui-sidebar-hover); color: var(--ui-sidebar-hover-text); }
21
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); }
22
+ [data-part=footer] > & { margin: 0 auto; } }
23
+ [data-scope=sidebar][data-part=nav] { display: flex; flex-direction: column; gap: 1rem; flex-grow: 1; overflow-y: auto; padding: 0.5rem; }
24
+ [data-scope=sidebar][data-part=footer] { display: flex; flex-direction: column; gap: 0.25rem; padding: 0.75rem 0.5rem; flex-shrink: 0; }
25
+
26
+ [data-scope=nav-section][data-part=root] { display: flex; flex-direction: column; gap: 0.125rem; }
27
+ [data-scope=nav-section][data-part=heading-row] { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; height: 1.5rem; padding-right: 0.25rem; }
28
+ [data-scope=nav-section][data-part=heading] { display: flex; align-items: center; flex-grow: 1; padding: 0 0.75rem;
29
+ font-size: 12px; font-weight: 600; color: var(--ui-sidebar-heading); white-space: nowrap; overflow: hidden; }
30
+ [data-scope=nav-section][data-part=collapse-all] { display: grid; place-items: center; width: 1.5rem; height: 1.5rem; padding: 0;
31
+ border: none; border-radius: calc(var(--ui-radius) - 2px); background: transparent; color: var(--ui-sidebar-icon); font-size: 14px; cursor: pointer; opacity: 0.7;
32
+ &:hover { opacity: 1; color: var(--ui-sidebar-hover-text); background: var(--ui-sidebar-hover); }
33
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
34
+
35
+ /* The edge strip sits over the border, inside the sidebar; a line shows on
36
+ hover, and the cursor points the way it goes. */
37
+ [data-scope=sidebar][data-part=edge] { position: absolute; top: 0; bottom: 0; right: 0; z-index: 1; width: 0.375rem; padding: 0;
38
+ border: none; background: transparent; outline: none; cursor: w-resize;
39
+ [data-rail] > & { cursor: e-resize; }
40
+ &::after { content: ''; position: absolute; top: 0; bottom: 0; right: 0; width: 1px; background: transparent; transition: background 150ms; }
41
+ &:hover::after { background: var(--ui-accent); } }
42
+
43
+ [data-scope=nav-item][data-part=link] { border: none; background: transparent; font-family: inherit; text-align: left; cursor: pointer; display: flex; align-items: center; gap: 0.75rem; width: 100%; box-sizing: border-box;
44
+ height: var(--ui-sidebar-item-height); padding: var(--ui-sidebar-item-padding);
45
+ border-radius: var(--ui-sidebar-item-radius); color: var(--ui-sidebar-item-text);
46
+ font-size: 13px; text-decoration: none; white-space: nowrap;
47
+ &:hover { background: var(--ui-sidebar-hover); color: var(--ui-sidebar-hover-text); }
48
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); outline-offset: -2px; }
49
+ [data-active] > & { background: var(--ui-sidebar-active); color: var(--ui-sidebar-active-text);
50
+ font-weight: var(--ui-sidebar-active-weight); box-shadow: var(--ui-sidebar-active-shadow); } }
51
+ [data-scope=nav-item][data-part=icon] { font-size: 16px; width: 1rem; height: 1rem; flex-shrink: 0; }
52
+ [data-scope=nav-item][data-part=text] { flex-grow: 1; overflow: hidden; text-overflow: ellipsis; }
53
+ [data-scope=nav-item][data-part=badge] { flex-shrink: 0; min-width: 1.25rem; height: 1.25rem; padding: 0 0.375rem; box-sizing: border-box;
54
+ display: inline-flex; align-items: center; justify-content: center; border-radius: 9999px;
55
+ background: var(--ui-accent-soft); color: var(--ui-accent-soft-text); font-size: 12px; font-weight: 500; }
56
+
57
+ /* In the rail: icons centred, labels kept for assistive tech only, headings and group contents hidden. */
58
+ [data-scope=sidebar][data-rail] {
59
+ :is([data-scope=nav-item][data-part=link], [data-scope=nav-group][data-part=trigger]) { justify-content: center; padding: 0; }
60
+ :is([data-scope=nav-item], [data-scope=nav-group]):is([data-part=text]) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
61
+ :is([data-scope=nav-item][data-part=badge], [data-scope=nav-group][data-part=indicator], [data-scope=nav-section][data-part=heading]) { display: none; } }
62
+
63
+ /* A group: its trigger looks like an item; its items are indented under it. */
64
+ [data-scope=nav-group][data-part=trigger] { display: flex; align-items: center; gap: 0.75rem; width: 100%; box-sizing: border-box;
65
+ height: var(--ui-sidebar-item-height); padding: var(--ui-sidebar-item-padding); border: none; background: transparent;
66
+ border-radius: var(--ui-sidebar-item-radius); color: var(--ui-sidebar-item-text);
67
+ font-family: inherit; font-size: 13px; text-align: left; white-space: nowrap; cursor: pointer;
68
+ &:hover { background: var(--ui-sidebar-hover); color: var(--ui-sidebar-hover-text); }
69
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); outline-offset: -2px; }
70
+ [data-flyout] > & { background: var(--ui-sidebar-active); color: var(--ui-sidebar-active-text); box-shadow: var(--ui-sidebar-active-shadow); } }
71
+ [data-scope=nav-group][data-part=icon] { font-size: 16px; width: 1rem; height: 1rem; flex-shrink: 0; }
72
+ [data-scope=nav-group][data-part=text] { flex-grow: 1; overflow: hidden; text-overflow: ellipsis; }
73
+ [data-scope=nav-group][data-part=indicator] { color: var(--ui-muted); font-size: 14px; transition: rotate 200ms;
74
+ [data-open] > [data-part=trigger] > & { rotate: 180deg; } }
75
+ [data-scope=nav-group][data-part=panel] { display: flex; flex-direction: column; gap: 0.125rem;
76
+ margin: 0.125rem 0 0 1.25rem; padding-left: 0.5rem; border-left: 1px solid var(--ui-border); }
77
+
78
+ /* In the rail: a panel beside the toggle. The wrapper's left padding
79
+ bridges the gap, so the pointer can cross to it without closing it. */
80
+ [data-scope=nav-group][data-part=children] {
81
+ [data-rail] > & { position: fixed; top: var(--flyout-top); left: var(--flyout-left); z-index: 50; padding-left: 0.5rem; } }
82
+ [data-scope=nav-group][data-part=panel] {
83
+ [data-rail] > [data-part=children] > & { margin: 0; padding: 0.375rem; min-width: 11rem; border: 1px solid var(--ui-border);
84
+ border-radius: calc(var(--ui-radius) + 2px); background: var(--ui-surface); box-shadow: var(--ui-shadow); animation: ui-flyout-in 120ms ease-out;
85
+ /* On the white flyout, the plain (not inset) colours again. */
86
+ --ui-sidebar-hover: var(--ui-hover); --ui-sidebar-active: var(--ui-hover); --ui-sidebar-active-shadow: none; } }
87
+ [data-scope=nav-group][data-part=flyout-heading] { padding: 0.25rem 0.75rem 0.375rem; font-size: 12px; font-weight: 600; color: var(--ui-muted); }
88
+ /* Inside the flyout, items show their labels again. */
89
+ [data-scope=sidebar][data-rail] [data-part=panel] :is([data-scope=nav-item][data-part=link]) { justify-content: flex-start; padding: var(--ui-sidebar-item-padding); }
90
+ [data-scope=sidebar][data-rail] [data-part=panel] [data-scope=nav-item]:is([data-part=text]) { position: static; width: auto; height: auto; overflow: hidden; clip: auto; }
91
+ @keyframes ui-flyout-in { from { opacity: 0; transform: translateX(-4px); } }
92
+
93
+ [data-scope=sidebar-user][data-part=root] { display: block; width: 100%; }
94
+ [data-scope=sidebar-user][data-part=trigger] { display: flex; align-items: center; gap: 0.625rem; width: 100%; padding: 0.375rem; box-sizing: border-box;
95
+ border: none; border-radius: var(--ui-radius); background: transparent; color: var(--ui-text); font-family: inherit; font-size: 13px;
96
+ text-align: left; white-space: nowrap; cursor: pointer;
97
+ &:hover { background: var(--ui-sidebar-hover); }
98
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); outline-offset: -2px; }
99
+ &[aria-expanded=true] { background: var(--ui-sidebar-active); box-shadow: var(--ui-sidebar-active-shadow); }
100
+ [data-rail] > & { justify-content: center; } }
101
+ [data-scope=sidebar-user][data-part=text] { display: flex; flex-direction: column; flex-grow: 1; min-width: 0; line-height: 1.3;
102
+ [data-rail] > [data-part=trigger] > & { display: none; } }
103
+ [data-scope=sidebar-user][data-part=name] { font-weight: 500; overflow: hidden; text-overflow: ellipsis; }
104
+ [data-scope=sidebar-user][data-part=description] { font-size: 12px; color: var(--ui-muted); overflow: hidden; text-overflow: ellipsis; }
105
+ [data-scope=sidebar-user][data-part=indicator] { flex-shrink: 0; color: var(--ui-muted); font-size: 14px;
106
+ [data-rail] > [data-part=trigger] > & { display: none; } }
107
+ /* As wide as the trigger when it opens upwards, and never narrower than 13rem. */
108
+ [data-scope=sidebar-user] [data-scope=menu][data-part=content] { min-width: max(13rem, var(--reference-width)); }
109
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * A placeholder shape while content loads.
3
+ *
4
+ * <Skeleton circle width="2.5rem" height="2.5rem" />
5
+ * <Skeleton lines={3} />
6
+ *
7
+ * - `lines`: that many text-like lines (the last one shorter)
8
+ * - `circle`: a circle, e.g. for an avatar
9
+ * - `width`, `height`: any CSS length (defaults fit a line of text)
10
+ *
11
+ * It is hidden from assistive tech; announce loading elsewhere (aria-busy on
12
+ * the region, or a Spinner's label).
13
+ */
14
+ import type { ComponentProps } from 'react';
15
+ import { type Classes } from '../utils.js';
16
+ import '../theme.css';
17
+ import './skeleton.css';
18
+ export type SkeletonProps = ComponentProps<'div'> & {
19
+ lines?: number;
20
+ circle?: boolean;
21
+ width?: string | null;
22
+ height?: string | null;
23
+ classes?: Classes<'root' | 'line'>;
24
+ };
25
+ export declare function Skeleton({ lines, circle, width, height, className, classes, style, ...rest }: SkeletonProps): import("react").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx, partsOf } from '../utils.js';
3
+ import '../theme.css';
4
+ import './skeleton.css';
5
+ export function Skeleton({ lines = 0, circle = false, width = null, height = null, className, classes, style, ...rest }) {
6
+ const part = partsOf('skeleton', classes);
7
+ return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "aria-hidden": "true", "data-circle": circle || undefined, "data-text": lines > 0 || undefined, style: { ...(width && { width }), ...(height && { height }), ...style }, children: Array.from({ length: lines }, (_, i) => _jsx("span", { ...part('line'), "data-last": (i === lines - 1 && lines > 1) || undefined }, i)) }));
8
+ }
@@ -0,0 +1,13 @@
1
+ @layer ui {
2
+ [data-scope=skeleton][data-part=root] {
3
+ display: block; min-height: 1rem; border-radius: var(--ui-radius); background: var(--ui-skeleton);
4
+ animation: ui-skeleton-pulse 1.6s ease-in-out infinite;
5
+ &[data-circle] { border-radius: 9999px; min-height: 0; }
6
+ &[data-text] { display: flex; flex-direction: column; gap: 0.5rem; min-height: 0; background: transparent; animation: none; }
7
+ }
8
+ [data-scope=skeleton][data-part=line] {
9
+ display: block; height: 0.75rem; border-radius: 4px; background: var(--ui-skeleton);
10
+ animation: ui-skeleton-pulse 1.6s ease-in-out infinite;
11
+ &[data-last] { width: 60%; }
12
+ }
13
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * A slider: pick a number, or a range with two thumbs.
3
+ *
4
+ * <Slider label="Volume" value={volume} onValueChange={setVolume} />
5
+ * <Slider label="Price" value={range} onValueChange={setRange} min={0} max={100} step={5} /> (range = [20, 80])
6
+ *
7
+ * - `value`, `defaultValue`: a number, or an array of two for a range
8
+ * - `onValueChange`: called with the new value (the same shape)
9
+ * - `min`, `max`, `step`
10
+ * - `showValue`: the value beside the label, formatted with `formatOptions` and `locale`
11
+ * (e.g. { style: 'currency', currency: 'GBP', maximumFractionDigits: 0 })
12
+ * - `marks`: values (or { value, label }) shown as ticks under the track
13
+ * - `name`: for plain form posts
14
+ *
15
+ * By hand, with Ark's parts:
16
+ *
17
+ * <Slider.Root defaultValue={[40]}>
18
+ * <Slider.Label>Volume</Slider.Label> <Slider.ValueText />
19
+ * <Slider.Control>
20
+ * <Slider.Track><Slider.Range /></Slider.Track>
21
+ * <Slider.Thumb index={0}><Slider.HiddenInput /></Slider.Thumb>
22
+ * </Slider.Control>
23
+ * </Slider.Root>
24
+ */
25
+ import { type ReactNode } from 'react';
26
+ import { Slider as Ark, type SliderValueChangeDetails } from '@ark-ui/react/slider';
27
+ import { type Classes } from '../utils.js';
28
+ import '../theme.css';
29
+ import './slider.css';
30
+ type Mark = number | {
31
+ value: number;
32
+ label: ReactNode;
33
+ };
34
+ type One = {
35
+ value?: number;
36
+ defaultValue?: number;
37
+ onValueChange?: (value: number, details: SliderValueChangeDetails) => void;
38
+ };
39
+ type Range = {
40
+ value?: number[];
41
+ defaultValue?: number[];
42
+ onValueChange?: (value: number[], details: SliderValueChangeDetails) => void;
43
+ };
44
+ export type SliderProps = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange' | 'label'> & {
45
+ label?: ReactNode;
46
+ showValue?: boolean;
47
+ formatOptions?: Intl.NumberFormatOptions;
48
+ locale?: string;
49
+ marks?: Mark[] | null;
50
+ classes?: Classes<'root' | 'header' | 'label' | 'valueText' | 'control' | 'track' | 'range' | 'thumb' | 'markerGroup' | 'marker'>;
51
+ } & (One | Range);
52
+ declare function SliderShortcut(props: SliderProps): import("react").JSX.Element;
53
+ export declare const Slider: typeof SliderShortcut & {
54
+ Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
55
+ Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
56
+ ValueText: import("react").ForwardRefExoticComponent<Ark.ValueTextProps & import("react").RefAttributes<HTMLDivElement>>;
57
+ Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
58
+ Track: import("react").ForwardRefExoticComponent<Ark.TrackProps & import("react").RefAttributes<HTMLDivElement>>;
59
+ Range: import("react").ForwardRefExoticComponent<Ark.RangeProps & import("react").RefAttributes<HTMLDivElement>>;
60
+ Thumb: import("react").ForwardRefExoticComponent<Ark.ThumbProps & import("react").RefAttributes<HTMLDivElement>>;
61
+ HiddenInput: import("react").ForwardRefExoticComponent<Ark.HiddenInputProps & import("react").RefAttributes<HTMLInputElement>>;
62
+ MarkerGroup: import("react").ForwardRefExoticComponent<Ark.MarkerGroupProps & import("react").RefAttributes<HTMLDivElement>>;
63
+ Marker: import("react").ForwardRefExoticComponent<Ark.MarkerProps & import("react").RefAttributes<HTMLSpanElement>>;
64
+ Context: (props: Ark.ContextProps) => ReactNode;
65
+ Header: {
66
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>>;
67
+ displayName: string;
68
+ };
69
+ };
70
+ export {};
@@ -0,0 +1,65 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A slider: pick a number, or a range with two thumbs.
4
+ *
5
+ * <Slider label="Volume" value={volume} onValueChange={setVolume} />
6
+ * <Slider label="Price" value={range} onValueChange={setRange} min={0} max={100} step={5} /> (range = [20, 80])
7
+ *
8
+ * - `value`, `defaultValue`: a number, or an array of two for a range
9
+ * - `onValueChange`: called with the new value (the same shape)
10
+ * - `min`, `max`, `step`
11
+ * - `showValue`: the value beside the label, formatted with `formatOptions` and `locale`
12
+ * (e.g. { style: 'currency', currency: 'GBP', maximumFractionDigits: 0 })
13
+ * - `marks`: values (or { value, label }) shown as ticks under the track
14
+ * - `name`: for plain form posts
15
+ *
16
+ * By hand, with Ark's parts:
17
+ *
18
+ * <Slider.Root defaultValue={[40]}>
19
+ * <Slider.Label>Volume</Slider.Label> <Slider.ValueText />
20
+ * <Slider.Control>
21
+ * <Slider.Track><Slider.Range /></Slider.Track>
22
+ * <Slider.Thumb index={0}><Slider.HiddenInput /></Slider.Thumb>
23
+ * </Slider.Control>
24
+ * </Slider.Root>
25
+ */
26
+ import { useMemo } from 'react';
27
+ import { Slider as Ark } from '@ark-ui/react/slider';
28
+ import { useFieldContext } from '@ark-ui/react/field';
29
+ import { useLocale } from '../format/LocaleProvider.js';
30
+ import { cx, partOf, partsOf, useControllable, withParts } from '../utils.js';
31
+ import '../theme.css';
32
+ import './slider.css';
33
+ function SliderShortcut(props) {
34
+ const { label = null, value, defaultValue, onValueChange, min = 0, max = 100, step = 1, showValue = true, formatOptions, locale: localeProp, marks = null, className, classes, ...rest } = props;
35
+ const field = useFieldContext();
36
+ const part = partsOf('slider', classes);
37
+ const [own, setOwn] = useControllable(value, defaultValue ?? min, undefined);
38
+ const range = Array.isArray(own);
39
+ const app = useLocale();
40
+ const locale = localeProp ?? app;
41
+ const formatter = useMemo(() => new Intl.NumberFormat(locale, formatOptions), [locale, formatOptions]);
42
+ const format = (n) => formatter.format(n);
43
+ const markOf = (mark) => (typeof mark === 'object' ? mark : { value: mark, label: format(mark) });
44
+ function change(details) {
45
+ const next = range ? details.value : details.value[0];
46
+ setOwn(next);
47
+ onValueChange?.(next, details);
48
+ }
49
+ const values = Array.isArray(own) ? own : [own];
50
+ return (_jsxs(Ark.Root, { ...rest, ...part('root'), className: cx(className, classes?.root), min: min, max: max, step: step, thumbAlignment: "center", value: values, onValueChange: change, ...(field && { ids: { label: field.ids.label }, invalid: field.invalid, disabled: field.disabled }), children: [((label && !field) || showValue) && (_jsxs("div", { ...part('header'), children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), showValue && _jsx(Ark.ValueText, { ...part('value-text'), children: values.map(format).join(' – ') })] })), _jsxs(Ark.Control, { ...part('control'), children: [_jsx(Ark.Track, { ...part('track'), children: _jsx(Ark.Range, { ...part('range') }) }), values.map((_, index) => _jsx(Ark.Thumb, { index: index, ...part('thumb'), "aria-describedby": field?.ariaDescribedby, children: _jsx(Ark.HiddenInput, {}) }, index))] }), marks && (_jsx(Ark.MarkerGroup, { ...part('marker-group'), children: marks.map(markOf).map((mark) => _jsx(Ark.Marker, { value: mark.value, ...part('marker'), children: mark.label }, mark.value)) }))] }));
51
+ }
52
+ export const Slider = withParts(SliderShortcut, {
53
+ Root: Ark.Root,
54
+ Label: Ark.Label,
55
+ ValueText: Ark.ValueText,
56
+ Control: Ark.Control,
57
+ Track: Ark.Track,
58
+ Range: Ark.Range,
59
+ Thumb: Ark.Thumb,
60
+ HiddenInput: Ark.HiddenInput,
61
+ MarkerGroup: Ark.MarkerGroup,
62
+ Marker: Ark.Marker,
63
+ Context: Ark.Context,
64
+ Header: partOf('slider', 'header'),
65
+ });
@@ -0,0 +1,17 @@
1
+ @layer ui {
2
+ [data-scope=slider][data-part=root] { display: block; min-width: 0; color: var(--ui-text); font-family: var(--ui-font); }
3
+ [data-scope=slider][data-part=header] { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin-bottom: 0.5rem; }
4
+ [data-scope=slider][data-part=label] { font-size: 14px; font-weight: 500; }
5
+ [data-scope=slider][data-part=value-text] { margin-left: auto; font-size: 14px; color: var(--ui-muted); font-variant-numeric: tabular-nums; }
6
+ [data-scope=slider][data-part=control] { position: relative; display: flex; align-items: center; height: 1.25rem;
7
+ &[data-disabled] { opacity: 0.5; } }
8
+ [data-scope=slider][data-part=track] { position: relative; flex: 1; height: 0.375rem; background: var(--ui-border); border-radius: 9999px; }
9
+ [data-scope=slider][data-part=range] { position: absolute; top: 0; bottom: 0; background: var(--ui-accent); border-radius: 9999px; }
10
+ [data-scope=slider][data-part=thumb] { width: 1.125rem; height: 1.125rem; box-sizing: border-box; background: white;
11
+ border: 2px solid var(--ui-accent); border-radius: 9999px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); cursor: grab; outline: none;
12
+ &:focus-visible { outline: 3px solid var(--ui-ring-soft); }
13
+ &[data-dragging] { cursor: grabbing; }
14
+ &[data-invalid] { border-color: var(--ui-danger); } }
15
+ [data-scope=slider][data-part=marker-group] { position: relative; height: 1.25rem; margin-top: 0.25rem; }
16
+ [data-scope=slider][data-part=marker] { font-size: 12px; color: var(--ui-muted); white-space: nowrap; }
17
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A list people put in order by dragging (or with the keyboard), on dnd-kit.
3
+ *
4
+ * <SortableList items={topics} onItemsChange={setTopics} renderItem={(topic) => topic.title} />
5
+ *
6
+ * Drag a row by its grip; or focus the grip, press Space to pick it up, the
7
+ * arrow keys to move it, Space to drop it (Escape puts it back). Screen
8
+ * readers hear each step.
9
+ *
10
+ * - `items`, `defaultItems`; `onItemsChange`: called with the reordered list
11
+ * - `itemKey`: the property that identifies an item ('id'), or a function;
12
+ * strings and numbers are their own key
13
+ * - `renderItem(item, { index, dragging })`: a row's content
14
+ * - `handle`: drag by the grip only (default), or false for the whole row
15
+ * (keyboard users still use the row, focused)
16
+ * - `variant`: 'card' (default; rows in a bordered box) or 'plain' (separate,
17
+ * spaced rows)
18
+ * - `itemLabel(item)`: what screen readers call an item as it moves
19
+ * ("Picked up Worksheet 4. … moved to position 3 of 5"); by default a
20
+ * string item itself, or its `label`, `title` or `name`
21
+ * - `disabled`: no reordering; `label`: the list's accessible name
22
+ */
23
+ import { type HTMLAttributes, type ReactNode } from 'react';
24
+ import 'iconify-icon';
25
+ import { type Classes } from '../utils.js';
26
+ import '../theme.css';
27
+ import './sortable-list.css';
28
+ type Key = string | number;
29
+ export type SortableListProps<T> = Omit<HTMLAttributes<HTMLUListElement>, 'children'> & {
30
+ items?: T[];
31
+ defaultItems?: T[];
32
+ onItemsChange?: (items: T[]) => void;
33
+ itemKey?: string | ((item: T) => Key);
34
+ renderItem?: (item: T, state: {
35
+ index: number;
36
+ dragging: boolean;
37
+ }) => ReactNode;
38
+ itemLabel?: (item: T) => string;
39
+ handle?: boolean;
40
+ variant?: 'card' | 'plain';
41
+ disabled?: boolean;
42
+ label?: string;
43
+ classes?: Classes<'root' | 'item' | 'handle' | 'content'>;
44
+ };
45
+ export declare function SortableList<T>({ items: itemsProp, defaultItems, onItemsChange, itemKey, renderItem, itemLabel, handle, variant, disabled, label, className, classes, ...rest }: SortableListProps<T>): import("react").JSX.Element;
46
+ export {};
@@ -0,0 +1,85 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A list people put in order by dragging (or with the keyboard), on dnd-kit.
4
+ *
5
+ * <SortableList items={topics} onItemsChange={setTopics} renderItem={(topic) => topic.title} />
6
+ *
7
+ * Drag a row by its grip; or focus the grip, press Space to pick it up, the
8
+ * arrow keys to move it, Space to drop it (Escape puts it back). Screen
9
+ * readers hear each step.
10
+ *
11
+ * - `items`, `defaultItems`; `onItemsChange`: called with the reordered list
12
+ * - `itemKey`: the property that identifies an item ('id'), or a function;
13
+ * strings and numbers are their own key
14
+ * - `renderItem(item, { index, dragging })`: a row's content
15
+ * - `handle`: drag by the grip only (default), or false for the whole row
16
+ * (keyboard users still use the row, focused)
17
+ * - `variant`: 'card' (default; rows in a bordered box) or 'plain' (separate,
18
+ * spaced rows)
19
+ * - `itemLabel(item)`: what screen readers call an item as it moves
20
+ * ("Picked up Worksheet 4. … moved to position 3 of 5"); by default a
21
+ * string item itself, or its `label`, `title` or `name`
22
+ * - `disabled`: no reordering; `label`: the list's accessible name
23
+ */
24
+ import { useRef } from 'react';
25
+ import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors, } from '@dnd-kit/core';
26
+ import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable';
27
+ import { CSS } from '@dnd-kit/utilities';
28
+ import 'iconify-icon';
29
+ import { cx, partsOf, useControllable } from '../utils.js';
30
+ import { useMessages } from '../format/LocaleProvider.js';
31
+ import '../theme.css';
32
+ import './sortable-list.css';
33
+ // Rows only move up and down.
34
+ const vertical = ({ transform }) => ({ ...transform, x: 0 });
35
+ export function SortableList({ items: itemsProp, defaultItems = [], onItemsChange, itemKey = 'id', renderItem, itemLabel, handle = true, variant = 'card', disabled = false, label, className, classes, ...rest }) {
36
+ const t = useMessages();
37
+ const part = partsOf('sortable-list', classes);
38
+ const [items, setItems] = useControllable(itemsProp, defaultItems, onItemsChange);
39
+ const keyOf = (item) => typeof itemKey === 'function' ? itemKey(item)
40
+ : typeof item === 'string' || typeof item === 'number' ? item : item[itemKey];
41
+ const keys = items.map(keyOf);
42
+ // Spoken as an item moves: its name and place, not dnd-kit's ids.
43
+ const nameOf = (id) => {
44
+ const item = items[keys.indexOf(id)];
45
+ if (itemLabel)
46
+ return itemLabel(item);
47
+ if (typeof item === 'string' || typeof item === 'number')
48
+ return String(item);
49
+ const fields = item;
50
+ return String(fields.label ?? fields.title ?? fields.name ?? id);
51
+ };
52
+ const moved = useRef(false);
53
+ const place = (id) => t.sortableList.position(keys.indexOf(id) + 1, keys.length);
54
+ const announcements = {
55
+ onDragStart: ({ active }) => {
56
+ moved.current = false;
57
+ return t.sortableList.pickedUp(nameOf(active.id), place(active.id));
58
+ },
59
+ // dnd-kit reports the item over its own place as it's picked up: not a move yet.
60
+ onDragOver: ({ active, over }) => {
61
+ if (!over || (over.id === active.id && !moved.current))
62
+ return undefined;
63
+ moved.current = true;
64
+ return t.sortableList.movedTo(nameOf(active.id), place(over.id));
65
+ },
66
+ onDragEnd: ({ active, over }) => t.sortableList.dropped(nameOf(active.id), over ? place(over.id) : null),
67
+ onDragCancel: ({ active }) => t.sortableList.cancelled(nameOf(active.id), place(active.id)),
68
+ };
69
+ const screenReaderInstructions = { draggable: t.sortableList.instructions };
70
+ // A small move before a drag starts, so clicks on the row still work.
71
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 4 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
72
+ function dragEnd({ active, over }) {
73
+ if (!over || active.id === over.id)
74
+ return;
75
+ setItems(arrayMove(items, keys.indexOf(active.id), keys.indexOf(over.id)));
76
+ }
77
+ return (_jsx(DndContext, { sensors: sensors, collisionDetection: closestCenter, modifiers: [vertical], onDragEnd: dragEnd, accessibility: { announcements, screenReaderInstructions }, children: _jsx(SortableContext, { items: keys, strategy: verticalListSortingStrategy, disabled: disabled, children: _jsx("ul", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, "aria-label": label, children: items.map((item, index) => (_jsx(Row, { id: keys[index], part: part, handle: handle, disabled: disabled, children: (dragging) => renderItem ? renderItem(item, { index, dragging }) : String(item) }, keys[index]))) }) }) }));
78
+ }
79
+ function Row({ id, part, handle, disabled, children }) {
80
+ const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ id, disabled });
81
+ const style = { transform: CSS.Translate.toString(transform), transition };
82
+ const drag = { ...attributes, ...listeners };
83
+ const t = useMessages();
84
+ return (_jsxs("li", { ref: setNodeRef, ...part('item'), style: style, "data-dragging": isDragging || undefined, "data-disabled": disabled || undefined, ...(!handle && drag), children: [handle && !disabled && (_jsx("button", { ref: setActivatorNodeRef, ...part('handle'), type: "button", "aria-label": t.sortableList.handle, ...drag, children: _jsx("iconify-icon", { icon: "lucide:grip-vertical", "aria-hidden": "true" }) })), _jsx("div", { ...part('content'), children: children(isDragging) })] }));
85
+ }
@@ -0,0 +1,25 @@
1
+ @layer ui {
2
+ [data-scope=sortable-list][data-part=root] { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none;
3
+ color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
4
+ &[data-variant=card] { background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px);
5
+ box-shadow: var(--ui-card-shadow); }
6
+ &[data-variant=plain] { gap: 0.5rem; } }
7
+ [data-scope=sortable-list][data-part=item] { position: relative; display: flex; align-items: center; gap: 0.5rem; padding: 0.625rem 0.75rem;
8
+ background: var(--ui-surface); box-sizing: border-box; outline: none;
9
+ [data-variant=card] > & { border-bottom: 1px solid var(--ui-border);
10
+ &:first-child { border-radius: calc(var(--ui-radius) + 2px) calc(var(--ui-radius) + 2px) 0 0; }
11
+ &:last-child { border-bottom: none; border-radius: 0 0 calc(var(--ui-radius) + 2px) calc(var(--ui-radius) + 2px); } }
12
+ [data-variant=plain] > & { border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px); }
13
+ /* The whole row is the handle (handle={false}): it shows it can be grabbed. */
14
+ &[role=button] { cursor: grab;
15
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); outline-offset: -2px; } }
16
+ /* Lifted while it's dragged, above the others. */
17
+ &[data-dragging] { z-index: 1; border-radius: calc(var(--ui-radius) + 2px); box-shadow: var(--ui-shadow); cursor: grabbing; } }
18
+ [data-scope=sortable-list][data-part=handle] { display: grid; place-items: center; flex-shrink: 0; width: 1.5rem; height: 1.75rem; margin-left: -0.25rem;
19
+ padding: 0; border: none; border-radius: calc(var(--ui-radius) - 2px); background: transparent; color: var(--ui-muted);
20
+ font-size: 16px; cursor: grab; touch-action: none;
21
+ &:hover { background: var(--ui-hover); color: var(--ui-text); }
22
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); }
23
+ [data-dragging] & { cursor: grabbing; } }
24
+ [data-scope=sortable-list][data-part=content] { flex-grow: 1; min-width: 0; }
25
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A spinner, for short waits.
3
+ *
4
+ * <Spinner size="sm" />
5
+ *
6
+ * - `label`: announced to assistive tech ('Loading')
7
+ * - `size`: 'sm', 'md' (default) or 'lg'
8
+ */
9
+ import type { ComponentProps } from 'react';
10
+ import { type Classes } from '../utils.js';
11
+ import '../theme.css';
12
+ import './spinner.css';
13
+ export type SpinnerProps = ComponentProps<'span'> & {
14
+ label?: string;
15
+ size?: 'sm' | 'md' | 'lg';
16
+ classes?: Classes<'root'>;
17
+ };
18
+ export declare function Spinner({ label: labelProp, size, className, classes, ...rest }: SpinnerProps): import("react").JSX.Element;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { cx, partsOf } from '../utils.js';
3
+ import { useMessages } from '../format/LocaleProvider.js';
4
+ import '../theme.css';
5
+ import './spinner.css';
6
+ export function Spinner({ label: labelProp, size = 'md', className, classes, ...rest }) {
7
+ const t = useMessages();
8
+ const label = labelProp ?? t.common.loading;
9
+ const part = partsOf('spinner', classes);
10
+ return _jsx("span", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-size": size, role: "status", "aria-label": label });
11
+ }
@@ -0,0 +1,9 @@
1
+ @layer ui {
2
+ [data-scope=spinner][data-part=root] {
3
+ display: inline-block; flex-shrink: 0; width: 1.25rem; height: 1.25rem; box-sizing: border-box; vertical-align: middle;
4
+ border: 2px solid var(--ui-border); border-top-color: var(--ui-accent); border-radius: 9999px;
5
+ animation: ui-spin 0.7s linear infinite;
6
+ &[data-size=sm] { width: 1rem; height: 1rem; }
7
+ &[data-size=lg] { width: 2rem; height: 2rem; border-width: 3px; }
8
+ }
9
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * A headline number with its label, e.g. on a dashboard.
3
+ *
4
+ * <Stat label="Lessons this month" value={52} change={12.5} changeLabel="vs September" />
5
+ *
6
+ * - `value` and an optional `unit`: text is shown as given; a number is
7
+ * formatted (grouped digits, the app's locale), with `format` (Intl's
8
+ * options: `{ style: 'currency', currency: 'GBP' }`)
9
+ * - `animate`: a number's digits roll to a new value when it changes (RollingNumber)
10
+ * - `change`: a number, shown as "+12.5%" with an arrow, green when up and red
11
+ * when down; `invert` swaps the colours (for things like cancellations);
12
+ * `changeUnit` replaces the '%'
13
+ * - `changeLabel`, `help`: muted text after the change, and under it all
14
+ * - `icon`: an Iconify name in a tinted square, beside the label
15
+ * - `variant`: 'plain' (default) or 'card'
16
+ * - `size`: 'sm', 'md' (default) or 'lg'
17
+ * - the content goes under it all (a sparkline)
18
+ *
19
+ * Group stats in Stats for a responsive grid.
20
+ */
21
+ import type { HTMLAttributes, ReactNode } from 'react';
22
+ import 'iconify-icon';
23
+ import { type Classes } from '../utils.js';
24
+ import '../theme.css';
25
+ import './stat.css';
26
+ export type StatProps = HTMLAttributes<HTMLDivElement> & {
27
+ label?: ReactNode;
28
+ value?: ReactNode;
29
+ format?: Intl.NumberFormatOptions;
30
+ animate?: boolean;
31
+ unit?: ReactNode;
32
+ change?: number | null;
33
+ changeUnit?: string;
34
+ changeLabel?: ReactNode;
35
+ invert?: boolean;
36
+ help?: ReactNode;
37
+ icon?: string | null;
38
+ variant?: 'plain' | 'card';
39
+ size?: 'sm' | 'md' | 'lg';
40
+ classes?: Classes<'root' | 'header' | 'label' | 'icon' | 'value' | 'unit' | 'footer' | 'change' | 'changeLabel' | 'help'>;
41
+ };
42
+ export declare function Stat({ label, value, format, animate, unit, change, changeUnit, changeLabel, invert, help, icon, variant, size, className, classes, children, ...rest }: StatProps): import("react").JSX.Element;