@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,55 @@
1
+ /**
2
+ * Labels and their values, as a description list (<dl>), e.g. a student's details.
3
+ *
4
+ * <DataList items={[
5
+ * { label: 'Email', value: 'ada@example.com' },
6
+ * { label: 'Year', value: 11, info: 'School year in September' },
7
+ * ]} />
8
+ *
9
+ * Or as markup, when values need more than text:
10
+ *
11
+ * <DataList>
12
+ * <DataList.Item label="Status"><Badge variant="success">Active</Badge></DataList.Item>
13
+ * </DataList>
14
+ *
15
+ * - `items`: `{ label, value, info }`
16
+ * - `orientation`: 'horizontal' (default; labels beside values) or 'vertical' (labels above)
17
+ * - `variant`: 'plain' (default), 'divided' (lines between items) or 'card'
18
+ * (a bordered box with lines between)
19
+ * - `columns`: lays items out in a grid of this many columns (stacking on
20
+ * narrow screens); best with vertical items
21
+ * - `labelWidth`: the label column's width when horizontal ('10rem')
22
+ * - DataList.Item (also DataItem): `label`; `info` adds an icon with this
23
+ * text in a tooltip; its content is the value
24
+ */
25
+ import { type HTMLAttributes, type ReactNode } from 'react';
26
+ import 'iconify-icon';
27
+ import { type Classes } from '../utils.js';
28
+ import '../theme.css';
29
+ import './data-list.css';
30
+ type ItemClasses = Classes<'item' | 'label' | 'info' | 'value'>;
31
+ export type DataEntry = {
32
+ label: ReactNode;
33
+ value?: ReactNode;
34
+ info?: ReactNode;
35
+ };
36
+ export type DataListProps = HTMLAttributes<HTMLDivElement> & {
37
+ items?: DataEntry[] | null;
38
+ orientation?: 'horizontal' | 'vertical';
39
+ variant?: 'plain' | 'divided' | 'card';
40
+ columns?: number | null;
41
+ labelWidth?: string | null;
42
+ classes?: Classes<'root' | 'list'> & ItemClasses;
43
+ };
44
+ declare function DataListShortcut({ items, orientation, variant, columns, labelWidth, className, classes, style, children, ...rest }: DataListProps): import("react").JSX.Element;
45
+ export type DataItemProps = Omit<HTMLAttributes<HTMLDivElement>, 'title'> & {
46
+ label?: ReactNode;
47
+ info?: ReactNode;
48
+ classes?: ItemClasses;
49
+ };
50
+ /** One label and its value (the content), in a DataList. */
51
+ export declare function DataItem({ label, info, className, classes, children, ...rest }: DataItemProps): import("react").JSX.Element;
52
+ export declare const DataList: typeof DataListShortcut & {
53
+ Item: typeof DataItem;
54
+ };
55
+ export {};
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Labels and their values, as a description list (<dl>), e.g. a student's details.
4
+ *
5
+ * <DataList items={[
6
+ * { label: 'Email', value: 'ada@example.com' },
7
+ * { label: 'Year', value: 11, info: 'School year in September' },
8
+ * ]} />
9
+ *
10
+ * Or as markup, when values need more than text:
11
+ *
12
+ * <DataList>
13
+ * <DataList.Item label="Status"><Badge variant="success">Active</Badge></DataList.Item>
14
+ * </DataList>
15
+ *
16
+ * - `items`: `{ label, value, info }`
17
+ * - `orientation`: 'horizontal' (default; labels beside values) or 'vertical' (labels above)
18
+ * - `variant`: 'plain' (default), 'divided' (lines between items) or 'card'
19
+ * (a bordered box with lines between)
20
+ * - `columns`: lays items out in a grid of this many columns (stacking on
21
+ * narrow screens); best with vertical items
22
+ * - `labelWidth`: the label column's width when horizontal ('10rem')
23
+ * - DataList.Item (also DataItem): `label`; `info` adds an icon with this
24
+ * text in a tooltip; its content is the value
25
+ */
26
+ import { createContext, useContext } from 'react';
27
+ import 'iconify-icon';
28
+ import { Tooltip } from '../tooltip/Tooltip.js';
29
+ import { cx, partsOf, withParts } from '../utils.js';
30
+ import '../theme.css';
31
+ import './data-list.css';
32
+ const PartContext = createContext(null);
33
+ function DataListShortcut({ items = null, orientation = 'horizontal', variant = 'plain', columns = null, labelWidth = null, className, classes, style, children, ...rest }) {
34
+ const part = partsOf('data-list', classes);
35
+ return (_jsx(PartContext, { value: part, children: _jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-orientation": orientation, "data-variant": variant, "data-grid": columns ? '' : undefined, style: { ...style, ...(columns && { '--columns': columns }), ...(labelWidth && { '--label-width': labelWidth }) }, children: _jsxs("dl", { ...part('list'), children: [(items ?? []).map((item, i) => _jsx(DataItem, { label: item.label, info: item.info, children: item.value ?? '' }, i)), children] }) }) }));
36
+ }
37
+ /** One label and its value (the content), in a DataList. */
38
+ export function DataItem({ label = null, info = null, className, classes, children, ...rest }) {
39
+ const inherited = useContext(PartContext);
40
+ const part = classes || !inherited ? partsOf('data-list', classes) : inherited;
41
+ return (_jsxs("div", { ...rest, ...part('item'), className: cx(className, part('item').className), children: [_jsxs("dt", { ...part('label'), children: [_jsx("span", { children: label }), info && (_jsx(Tooltip, { content: info, children: _jsx("span", { ...part('info'), tabIndex: 0, "aria-label": typeof info === 'string' ? info : undefined, children: _jsx("iconify-icon", { icon: "lucide:info" }) }) }))] }), _jsx("dd", { ...part('value'), children: children })] }));
42
+ }
43
+ export const DataList = withParts(DataListShortcut, { Item: DataItem });
@@ -0,0 +1,19 @@
1
+ @layer ui {
2
+ [data-scope=data-list][data-part=root] { display: block; width: 100%; color: var(--ui-text); font-family: var(--ui-font); font-size: 14px; }
3
+ /* Each item's bottom line overlaps the next (and the last is clipped), so lines only sit between items. */
4
+ [data-scope=data-list][data-part=list] { display: grid; margin: 0; padding: 0; overflow: hidden;
5
+ [data-variant=card] > & { background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px);
6
+ box-shadow: var(--ui-card-shadow); padding: 0 1rem; }
7
+ [data-grid] > & { column-gap: 1.5rem; }
8
+ @media (min-width: 640px) { [data-grid] > & { grid-template-columns: repeat(var(--columns, 2), minmax(0, 1fr)); } } }
9
+ [data-scope=data-list][data-part=item] {
10
+ [data-orientation=horizontal] > [data-part=list] > & { display: grid; grid-template-columns: var(--label-width, 10rem) minmax(0, 1fr); gap: 1rem; padding: 0.5rem 0; align-items: baseline; }
11
+ [data-orientation=vertical] > [data-part=list] > & { display: flex; flex-direction: column; gap: 0.125rem; padding: 0.5rem 0; }
12
+ :is([data-variant=divided], [data-variant=card]) > [data-part=list] > & { padding: 0.75rem 0; border-bottom: 1px solid var(--ui-border); margin-bottom: -1px; }
13
+ }
14
+ [data-scope=data-list][data-part=label] { display: flex; align-items: center; gap: 0.25rem; margin: 0; color: var(--ui-muted); }
15
+ [data-scope=data-list][data-part=value] { margin: 0; min-width: 0; overflow-wrap: anywhere; }
16
+ [data-scope=data-list][data-part=info] { display: inline-flex; color: var(--ui-muted); cursor: help; border-radius: 9999px; font-size: 13px;
17
+ &:hover { color: var(--ui-text); }
18
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
19
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * A date picker: type a date, or pick it from a calendar.
3
+ *
4
+ * <DatePicker label="Lesson date" value={date} onValueChange={setDate} min="2026-01-01" />
5
+ *
6
+ * - `value`, `defaultValue`: an ISO date ('YYYY-MM-DD'), or an array of two with `range`
7
+ * - `onValueChange`: called with the new value (the same shape)
8
+ * - `range`: pick a start and an end
9
+ * - `min`, `max`: ISO dates
10
+ * - `unavailable`: function(CalendarDate) returning true for dates that can't
11
+ * be picked (weekends, holidays); they show struck through
12
+ * - `locale`: for formatting and the first day of the week (the
13
+ * LocaleProvider's, or 'en-GB')
14
+ * - `placement`: where the calendar opens ('bottom-start')
15
+ * - `inline`: just the calendar, always showing (no input or popup), e.g. a
16
+ * month beside a schedule
17
+ * - `prev`, `next`: elements to use as those buttons; they get the trigger's
18
+ * props, so a component must pass them on (as Button does)
19
+ *
20
+ * Up and Down in the input step its date by a day (Shift: a week), skipping
21
+ * unavailable ones; on an empty input the first press picks today. Typing
22
+ * puts in the locale's separators (11102026 → 11/10/2026; 11.10.2026 in
23
+ * German); a separator typed by hand, or a pasted date, works too.
24
+ *
25
+ * A typed date is taken on Enter or when the input loses focus. One it can't
26
+ * take (past `min` or `max`, `unavailable`, or not a whole date) stays in the
27
+ * input, marked invalid with the reason under it, and the value doesn't
28
+ * change; the input's validity is set too, so a form won't submit with it.
29
+ * - `onInvalidChange`: called with the reason when that happens, and null
30
+ * once the input holds a date it takes (or is cleared)
31
+ *
32
+ * By hand, with Ark's parts: DatePicker.Root, Control, Input, Trigger,
33
+ * Positioner, Content, View (one per view), ViewControl, PrevTrigger,
34
+ * ViewTrigger, NextTrigger, Table, TableHead, TableHeader, TableBody,
35
+ * TableRow, TableCell, TableCellTrigger, with DatePicker.Context for the
36
+ * weeks, months and years to lay out.
37
+ */
38
+ import { type ReactElement, type ReactNode } from 'react';
39
+ import { DatePicker as Ark, parseDate, type DatePickerValueChangeDetails, type DateValue } from '@ark-ui/react/date-picker';
40
+ import 'iconify-icon';
41
+ import { type Classes } from '../utils.js';
42
+ import '../theme.css';
43
+ import './date-picker.css';
44
+ type One = {
45
+ range?: false;
46
+ value?: string | null;
47
+ defaultValue?: string | null;
48
+ onValueChange?: (value: string | null, details: DatePickerValueChangeDetails) => void;
49
+ };
50
+ type Range = {
51
+ range: true;
52
+ value?: string[];
53
+ defaultValue?: string[];
54
+ onValueChange?: (value: string[], details: DatePickerValueChangeDetails) => void;
55
+ };
56
+ export type DatePickerProps = Omit<Ark.RootProps, 'value' | 'defaultValue' | 'onValueChange' | 'min' | 'max' | 'label' | 'selectionMode'> & {
57
+ label?: ReactNode;
58
+ min?: string | null;
59
+ max?: string | null;
60
+ unavailable?: ((date: DateValue) => boolean) | null;
61
+ placement?: NonNullable<Ark.RootProps['positioning']>['placement'];
62
+ prev?: ReactElement | null;
63
+ next?: ReactElement | null;
64
+ inline?: boolean;
65
+ onInvalidChange?: ((reason: string | null) => void) | null;
66
+ classes?: Classes<'root' | 'label' | 'control' | 'input' | 'separator' | 'clearTrigger' | 'trigger' | 'positioner' | 'content' | 'view' | 'viewControl' | 'prevTrigger' | 'viewTrigger' | 'nextTrigger' | 'table' | 'tableHeader' | 'tableCell' | 'tableCellTrigger' | 'error'>;
67
+ } & (One | Range);
68
+ declare function DatePickerShortcut(props: DatePickerProps): import("react").JSX.Element;
69
+ export declare const DatePicker: typeof DatePickerShortcut & {
70
+ Root: import("react").ForwardRefExoticComponent<Ark.RootProps & import("react").RefAttributes<HTMLDivElement>>;
71
+ RootProvider: import("react").ForwardRefExoticComponent<Ark.RootProviderProps & import("react").RefAttributes<HTMLDivElement>>;
72
+ Label: import("react").ForwardRefExoticComponent<Ark.LabelProps & import("react").RefAttributes<HTMLLabelElement>>;
73
+ Control: import("react").ForwardRefExoticComponent<Ark.ControlProps & import("react").RefAttributes<HTMLDivElement>>;
74
+ Input: import("react").ForwardRefExoticComponent<Ark.InputProps & import("react").RefAttributes<HTMLInputElement>>;
75
+ ClearTrigger: import("react").ForwardRefExoticComponent<Ark.ClearTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
76
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
77
+ Positioner: import("react").ForwardRefExoticComponent<Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>>;
78
+ Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
79
+ View: import("react").ForwardRefExoticComponent<Ark.ViewProps & import("react").RefAttributes<HTMLDivElement>>;
80
+ ViewControl: import("react").ForwardRefExoticComponent<Ark.ViewControlProps & import("react").RefAttributes<HTMLDivElement>>;
81
+ PrevTrigger: import("react").ForwardRefExoticComponent<Ark.PrevTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
82
+ ViewTrigger: import("react").ForwardRefExoticComponent<Ark.ViewTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
83
+ NextTrigger: import("react").ForwardRefExoticComponent<Ark.NextTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
84
+ RangeText: import("react").ForwardRefExoticComponent<Ark.RangeTextProps & import("react").RefAttributes<HTMLDivElement>>;
85
+ Table: import("react").ForwardRefExoticComponent<Ark.TableProps & import("react").RefAttributes<HTMLTableElement>>;
86
+ TableHead: import("react").ForwardRefExoticComponent<Ark.TableHeadProps & import("react").RefAttributes<HTMLTableSectionElement>>;
87
+ TableHeader: import("react").ForwardRefExoticComponent<Ark.TableHeaderProps & import("react").RefAttributes<HTMLTableCellElement>>;
88
+ TableBody: import("react").ForwardRefExoticComponent<Ark.TableBodyProps & import("react").RefAttributes<HTMLTableSectionElement>>;
89
+ TableRow: import("react").ForwardRefExoticComponent<Ark.TableRowProps & import("react").RefAttributes<HTMLTableRowElement>>;
90
+ TableCell: import("react").ForwardRefExoticComponent<Ark.TableCellProps & import("react").RefAttributes<HTMLTableCellElement>>;
91
+ TableCellTrigger: import("react").ForwardRefExoticComponent<Ark.TableCellTriggerProps & import("react").RefAttributes<HTMLDivElement>>;
92
+ PresetTrigger: import("react").ForwardRefExoticComponent<Ark.PresetTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
93
+ Context: (props: Ark.ContextProps) => ReactNode;
94
+ };
95
+ export { parseDate };
@@ -0,0 +1,237 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A date picker: type a date, or pick it from a calendar.
4
+ *
5
+ * <DatePicker label="Lesson date" value={date} onValueChange={setDate} min="2026-01-01" />
6
+ *
7
+ * - `value`, `defaultValue`: an ISO date ('YYYY-MM-DD'), or an array of two with `range`
8
+ * - `onValueChange`: called with the new value (the same shape)
9
+ * - `range`: pick a start and an end
10
+ * - `min`, `max`: ISO dates
11
+ * - `unavailable`: function(CalendarDate) returning true for dates that can't
12
+ * be picked (weekends, holidays); they show struck through
13
+ * - `locale`: for formatting and the first day of the week (the
14
+ * LocaleProvider's, or 'en-GB')
15
+ * - `placement`: where the calendar opens ('bottom-start')
16
+ * - `inline`: just the calendar, always showing (no input or popup), e.g. a
17
+ * month beside a schedule
18
+ * - `prev`, `next`: elements to use as those buttons; they get the trigger's
19
+ * props, so a component must pass them on (as Button does)
20
+ *
21
+ * Up and Down in the input step its date by a day (Shift: a week), skipping
22
+ * unavailable ones; on an empty input the first press picks today. Typing
23
+ * puts in the locale's separators (11102026 → 11/10/2026; 11.10.2026 in
24
+ * German); a separator typed by hand, or a pasted date, works too.
25
+ *
26
+ * A typed date is taken on Enter or when the input loses focus. One it can't
27
+ * take (past `min` or `max`, `unavailable`, or not a whole date) stays in the
28
+ * input, marked invalid with the reason under it, and the value doesn't
29
+ * change; the input's validity is set too, so a form won't submit with it.
30
+ * - `onInvalidChange`: called with the reason when that happens, and null
31
+ * once the input holds a date it takes (or is cleared)
32
+ *
33
+ * By hand, with Ark's parts: DatePicker.Root, Control, Input, Trigger,
34
+ * Positioner, Content, View (one per view), ViewControl, PrevTrigger,
35
+ * ViewTrigger, NextTrigger, Table, TableHead, TableHeader, TableBody,
36
+ * TableRow, TableCell, TableCellTrigger, with DatePicker.Context for the
37
+ * weeks, months and years to lay out.
38
+ */
39
+ import { Fragment, useId, useMemo, useState } from 'react';
40
+ import { DatePicker as Ark, parseDate, useDatePicker } from '@ark-ui/react/date-picker';
41
+ import { useFieldContext } from '@ark-ui/react/field';
42
+ import { DateFormatter, getLocalTimeZone, today } from '@internationalized/date';
43
+ import { parseDateString } from '@zag-js/date-utils';
44
+ import 'iconify-icon';
45
+ import { useLocale, useMessages } from '../format/LocaleProvider.js';
46
+ import { cx, partsOf, useControllable, withParts } from '../utils.js';
47
+ import '../theme.css';
48
+ import './date-picker.css';
49
+ // Zag formats the input when the picker starts: a new locale (say, from the
50
+ // LocaleProvider) starts it afresh, so the dates show in the new format.
51
+ // The input's date pattern for a locale: its fields in order (with how many
52
+ // digits each takes) and the separator between them ('/', '.', '-').
53
+ function patternOf(locale) {
54
+ const parts = new Intl.DateTimeFormat(locale).formatToParts(new Date(2000, 0, 1));
55
+ const fields = parts.filter((p) => p.type === 'day' || p.type === 'month' || p.type === 'year').map((p) => (p.type === 'year' ? 4 : 2));
56
+ const separator = parts.find((p) => p.type === 'literal')?.value.trim() || '/';
57
+ return { fields, separator };
58
+ }
59
+ // Typing at the end: once a field has its digits, add the separator after it;
60
+ // more digits than it takes (inserted in one go) carry on into the next field;
61
+ // a separator typed straight after one we added is dropped (no "11//").
62
+ // Runs before Zag's own handler, which then reads the new value.
63
+ function separate(e, { fields, separator }) {
64
+ const input = e.currentTarget;
65
+ const native = e.nativeEvent;
66
+ if (native.inputType !== 'insertText' || input.selectionStart !== input.value.length)
67
+ return;
68
+ if (native.data === separator && input.value.endsWith(separator + separator)) {
69
+ input.value = input.value.slice(0, -1);
70
+ return;
71
+ }
72
+ const pieces = input.value.split(separator);
73
+ if (!pieces.every((piece) => /^\d*$/.test(piece)))
74
+ return;
75
+ while (pieces.length < fields.length && pieces[pieces.length - 1].length > fields[pieces.length - 1]) {
76
+ const last = pieces.pop();
77
+ pieces.push(last.slice(0, fields[pieces.length]), last.slice(fields[pieces.length]));
78
+ }
79
+ const index = pieces.length - 1;
80
+ if (index < fields.length - 1 && pieces[index].length === fields[index])
81
+ pieces.push('');
82
+ const next = pieces.join(separator);
83
+ if (next !== input.value)
84
+ input.value = next;
85
+ }
86
+ function DatePickerShortcut(props) {
87
+ const app = useLocale();
88
+ const locale = props.locale ?? app ?? 'en-GB';
89
+ return _jsx(DatePickerInner, { ...props, locale: locale }, locale);
90
+ }
91
+ function DatePickerInner(props) {
92
+ const { label = null, value, defaultValue, onValueChange, range = false, min = null, max = null, unavailable = null, locale: localeProp, placement = 'bottom-start', prev = null, next = null, inline = false, onInvalidChange = null, className, classes, ...rest } = props;
93
+ const field = useFieldContext();
94
+ const t = useMessages();
95
+ // Its own, the LocaleProvider's, or British (for day-month order and a Monday start).
96
+ const locale = localeProp ?? 'en-GB';
97
+ const pattern = useMemo(() => patternOf(locale), [locale]);
98
+ const part = partsOf('date-picker', classes);
99
+ const [current, setCurrent] = useControllable(value, defaultValue ?? (range ? [] : null), undefined);
100
+ // CalendarDate#toString is ISO.
101
+ function change(details) {
102
+ report(null);
103
+ const iso = details.value.map(String);
104
+ const next = range ? iso : iso[0] ?? null;
105
+ setCurrent(next);
106
+ onValueChange?.(next, details);
107
+ }
108
+ const picker = useDatePicker({
109
+ locale, selectionMode: range ? 'range' : 'single',
110
+ value: [].concat(current ?? []).filter((iso) => !!iso).map((iso) => parseDate(iso)),
111
+ onValueChange: change,
112
+ ...(min && { min: parseDate(min) }), ...(max && { max: parseDate(max) }),
113
+ ...(unavailable && { isDateUnavailable: unavailable }),
114
+ positioning: { placement },
115
+ ...(t.zag.datePicker && { translations: t.zag.datePicker }),
116
+ ...(inline && { inline: true }),
117
+ // Inside a Field, its label points at the first input.
118
+ ...(field && { ids: { label: () => field.ids.label, input: (index) => (index === 0 ? field.ids.control : `${field.ids.control}-${index}`) }, invalid: field.invalid }),
119
+ });
120
+ // A typed date it can't take: the reason, shown under the input and set as
121
+ // its validity (so a form won't submit), until a date it takes is chosen.
122
+ const [problem, setProblem] = useState(null);
123
+ const errorId = useId();
124
+ function report(next, input) {
125
+ if (next)
126
+ input?.setCustomValidity(next.reason);
127
+ else
128
+ document.querySelectorAll(`[data-error-for="${errorId}"]`).forEach((el) => el.setCustomValidity(''));
129
+ if (next?.reason === problem?.reason && next?.index === problem?.index)
130
+ return;
131
+ setProblem(next);
132
+ onInvalidChange?.(next?.reason ?? null);
133
+ }
134
+ // Why a typed date can't be taken, or the date itself.
135
+ const dateText = (date) => new DateFormatter(locale).format(date.toDate(getLocalTimeZone()));
136
+ function check(input) {
137
+ const text = input.value.trim();
138
+ if (!text)
139
+ return null;
140
+ const pieces = text.split(pattern.separator);
141
+ const date = pieces.length === pattern.fields.length && pieces.every((piece) => /^\d+$/.test(piece))
142
+ ? parseDateString(text, locale, getLocalTimeZone()) : undefined;
143
+ if (!date)
144
+ return { reason: t.datePicker.format(input.placeholder) };
145
+ if (min && date.compare(parseDate(min)) < 0)
146
+ return { reason: t.datePicker.min(dateText(parseDate(min))) };
147
+ if (max && date.compare(parseDate(max)) > 0)
148
+ return { reason: t.datePicker.max(dateText(parseDate(max))) };
149
+ if (picker.isUnavailable(date))
150
+ return { reason: t.datePicker.unavailable };
151
+ return { date };
152
+ }
153
+ // Takes the typed date, or reports why not (Zag would clamp or revert it).
154
+ function commit(input, index) {
155
+ const result = check(input);
156
+ if (!result) {
157
+ report(null, input);
158
+ return true;
159
+ }
160
+ if ('reason' in result) {
161
+ report({ index, ...result }, input);
162
+ return false;
163
+ }
164
+ report(null, input);
165
+ const values = picker.value.slice();
166
+ values[index] = result.date;
167
+ if (range && !values[1 - index])
168
+ values[1 - index] = result.date;
169
+ if (range && values[0] && values[1] && values[0].compare(values[1]) > 0)
170
+ values.reverse();
171
+ picker.setValue(values);
172
+ return true;
173
+ }
174
+ // Up/Down step an input's date by a day (Shift: a week), skipping unavailable
175
+ // ones; in range mode the other end moves along so start never passes end.
176
+ // Captured, so Zag's own key handling (which skips handled keys) stands aside.
177
+ function step(e, index) {
178
+ // Enter takes the typed date only if it can (Zag shows "undefined" for a
179
+ // part-typed one, 11/1); otherwise it reports why.
180
+ if (e.key === 'Enter') {
181
+ if (!commit(e.currentTarget, index))
182
+ e.preventDefault();
183
+ return;
184
+ }
185
+ const dir = { ArrowUp: 1, ArrowDown: -1 }[e.key];
186
+ if (!dir || e.altKey || e.metaKey || e.ctrlKey)
187
+ return;
188
+ e.preventDefault();
189
+ const values = picker.value.slice();
190
+ let date = values[index] ? values[index].add({ days: dir * (e.shiftKey ? 7 : 1) }) : today(getLocalTimeZone());
191
+ for (let tries = 0; picker.isUnavailable(date); date = date.add({ days: dir }))
192
+ if (++tries > 366)
193
+ return;
194
+ values[index] = date;
195
+ if (range) {
196
+ const other = 1 - index;
197
+ const crossed = values[other] && (index === 0 ? date.compare(values[1]) > 0 : date.compare(values[0]) < 0);
198
+ if (!values[other] || crossed)
199
+ values[other] = date;
200
+ }
201
+ picker.setValue(values);
202
+ }
203
+ const view = picker.view;
204
+ const content = (_jsx(Ark.Content, { ...part('content'), children: _jsxs(Ark.View, { view: view, ...part('view'), children: [_jsxs(Ark.ViewControl, { ...part('view-control'), children: [prev ? _jsx(Ark.PrevTrigger, { asChild: true, children: prev })
205
+ : _jsx(Ark.PrevTrigger, { ...part('prev-trigger'), children: _jsx("iconify-icon", { icon: "lucide:chevron-left" }) }), _jsx(Ark.ViewTrigger, { ...part('view-trigger'), children: view === 'day' ? picker.visibleRangeText.start
206
+ : view === 'month' ? picker.visibleRange.start.year
207
+ : `${picker.getDecade().start} – ${picker.getDecade().end}` }), next ? _jsx(Ark.NextTrigger, { asChild: true, children: next })
208
+ : _jsx(Ark.NextTrigger, { ...part('next-trigger'), children: _jsx("iconify-icon", { icon: "lucide:chevron-right" }) })] }), view === 'day' && (_jsxs(Ark.Table, { ...part('table'), children: [_jsx(Ark.TableHead, { children: _jsx(Ark.TableRow, { children: picker.weekDays.map((day, i) => _jsx(Ark.TableHeader, { ...part('table-header'), "aria-label": day.long, children: day.narrow }, i)) }) }), _jsx(Ark.TableBody, { children: picker.weeks.map((week, i) => (_jsx(Ark.TableRow, { children: week.map((date) => (_jsx(Ark.TableCell, { value: date, ...part('table-cell'), children: _jsx(Ark.TableCellTrigger, { ...part('table-cell-trigger'), children: date.day }) }, date.toString()))) }, i))) })] })), view === 'month' && (_jsx(Ark.Table, { columns: 4, ...part('table'), children: _jsx(Ark.TableBody, { children: picker.getMonthsGrid({ columns: 4, format: 'short' }).map((row, i) => (_jsx(Ark.TableRow, { children: row.map((month) => (_jsx(Ark.TableCell, { value: month.value, columns: 4, ...part('table-cell'), children: _jsx(Ark.TableCellTrigger, { ...part('table-cell-trigger'), "data-wide": "", children: month.label }) }, month.value))) }, i))) }) })), view === 'year' && (_jsx(Ark.Table, { columns: 4, ...part('table'), children: _jsx(Ark.TableBody, { children: picker.getYearsGrid({ columns: 4 }).map((row, i) => (_jsx(Ark.TableRow, { children: row.map((year) => (_jsx(Ark.TableCell, { value: year.value, columns: 4, ...part('table-cell'), children: _jsx(Ark.TableCellTrigger, { ...part('table-cell-trigger'), "data-wide": "", children: year.label }) }, year.value))) }, i))) }) }))] }) }));
209
+ return (_jsxs(Ark.RootProvider, { value: picker, ...rest, ...part('root'), className: cx(className, classes?.root), "data-inline": inline || undefined, children: [label && !field && _jsx(Ark.Label, { ...part('label'), children: label }), !inline && _jsxs(Ark.Control, { ...part('control'), children: [(range ? [0, 1] : [0]).map((index) => _jsxs(Fragment, { children: [index === 1 && _jsx("span", { ...part('separator'), children: "\u2013" }), _jsx(Ark.Input, { index: index, ...part('input'), size: 10, fixOnBlur: false, "data-error-for": errorId, onKeyDownCapture: (e) => step(e, index), onInputCapture: (e) => separate(e, pattern), onBlur: (e) => commit(e.currentTarget, index), "aria-invalid": problem?.index === index || undefined, "aria-describedby": [field?.ariaDescribedby, problem?.index === index && errorId].filter(Boolean).join(' ') || undefined })] }, index)), _jsx(Ark.ClearTrigger, { ...part('clear-trigger'), onClick: () => report(null), children: _jsx("iconify-icon", { icon: "lucide:x" }) }), _jsx(Ark.Trigger, { ...part('trigger'), children: _jsx("iconify-icon", { icon: "lucide:calendar" }) })] }), problem && !inline && _jsx("div", { ...part('error'), id: errorId, role: "alert", children: problem.reason }), inline ? content : _jsx(Ark.Positioner, { ...part('positioner'), children: content })] }));
210
+ }
211
+ export const DatePicker = withParts(DatePickerShortcut, {
212
+ Root: Ark.Root,
213
+ RootProvider: Ark.RootProvider,
214
+ Label: Ark.Label,
215
+ Control: Ark.Control,
216
+ Input: Ark.Input,
217
+ ClearTrigger: Ark.ClearTrigger,
218
+ Trigger: Ark.Trigger,
219
+ Positioner: Ark.Positioner,
220
+ Content: Ark.Content,
221
+ View: Ark.View,
222
+ ViewControl: Ark.ViewControl,
223
+ PrevTrigger: Ark.PrevTrigger,
224
+ ViewTrigger: Ark.ViewTrigger,
225
+ NextTrigger: Ark.NextTrigger,
226
+ RangeText: Ark.RangeText,
227
+ Table: Ark.Table,
228
+ TableHead: Ark.TableHead,
229
+ TableHeader: Ark.TableHeader,
230
+ TableBody: Ark.TableBody,
231
+ TableRow: Ark.TableRow,
232
+ TableCell: Ark.TableCell,
233
+ TableCellTrigger: Ark.TableCellTrigger,
234
+ PresetTrigger: Ark.PresetTrigger,
235
+ Context: Ark.Context,
236
+ });
237
+ export { parseDate };
@@ -0,0 +1,53 @@
1
+ /* Parts are matched by Zag's own data-part names (prev-trigger,
2
+ table-cell-trigger…); the `classes` prop uses the short ones (prev, cell). */
3
+ @layer ui {
4
+ [data-scope=date-picker][data-part=root] { display: inline-block; position: relative;
5
+ color: var(--ui-text); font-family: var(--ui-font);
6
+ /* The calendar's own width, even where a grid or flex column would stretch it. */
7
+ &[data-inline] { width: fit-content; } }
8
+ [data-scope=date-picker][data-part=label] { display: block; font-size: 14px; font-weight: 500; margin-bottom: 0.375rem; }
9
+ [data-scope=date-picker][data-part=control] { display: flex; align-items: center; gap: 0.25rem; padding: 0 0.5rem;
10
+ height: var(--ui-control-height); box-sizing: border-box; background: var(--ui-surface);
11
+ border: 1px solid var(--ui-border); border-radius: var(--ui-radius);
12
+ &:focus-within { border-color: var(--ui-ring); outline: 2px solid var(--ui-ring-soft); }
13
+ &[data-invalid], &:has([aria-invalid=true]) { border-color: var(--ui-danger); }
14
+ &[data-disabled] { opacity: 0.5; } }
15
+ [data-scope=date-picker][data-part=input] { min-width: 0; padding: 0; border: none; background: transparent;
16
+ outline: none; font-size: 14px; color: inherit; field-sizing: content;
17
+ &::placeholder { color: var(--ui-muted); } }
18
+ [data-scope=date-picker][data-part=input]:last-of-type { flex-grow: 1; }
19
+ [data-scope=date-picker][data-part=error] { margin-top: 0.375rem; font-size: 12px; color: var(--ui-danger); }
20
+ [data-scope=date-picker][data-part=separator] { color: var(--ui-muted); font-size: 14px; padding: 0 0.25rem; }
21
+ [data-scope=date-picker]:is([data-part=clear-trigger], [data-part=trigger], [data-part=prev-trigger], [data-part=next-trigger]) {
22
+ display: grid; place-items: center; width: 1.75rem; height: 1.75rem; border: none; background: transparent;
23
+ border-radius: 4px; color: var(--ui-muted); cursor: pointer;
24
+ &:hover { background: var(--ui-hover); color: var(--ui-text); } }
25
+
26
+ /* Zag copies the content's z-index onto its positioner. */
27
+ [data-scope=date-picker][data-part=content] { z-index: 50; padding: 0.75rem; background: var(--ui-surface); outline: none;
28
+ border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px); box-shadow: var(--ui-shadow);
29
+ /* An inline calendar in a popover (EventCalendar's date jump): the popover is the card. */
30
+ [data-scope=popover] & { padding: 0; border: none; border-radius: 0; box-shadow: none; background: transparent; } }
31
+ [data-scope=date-picker][data-part=view-control] { display: flex; align-items: center; justify-content: space-between;
32
+ margin-bottom: 0.5rem; }
33
+ [data-scope=date-picker][data-part=view-trigger] { font-weight: 600; font-size: 14px; border: none; background: transparent;
34
+ color: inherit; border-radius: 4px; padding: 0.25rem 0.5rem; cursor: pointer;
35
+ &:hover { background: var(--ui-hover); } }
36
+ [data-scope=date-picker][data-part=table] { border-collapse: collapse; }
37
+ [data-scope=date-picker][data-part=table-header] { font-size: 12px; font-weight: 500; color: var(--ui-muted);
38
+ width: 2.25rem; height: 2rem; }
39
+ [data-scope=date-picker][data-part=table-cell] { padding: 0; }
40
+ [data-scope=date-picker][data-part=table-cell-trigger] { display: grid; place-items: center; width: 2.25rem; height: 2.25rem;
41
+ border-radius: var(--ui-radius); font-size: 14px; cursor: pointer; user-select: none;
42
+ &:hover { background: var(--ui-hover); }
43
+ &[data-wide] { width: 4rem; height: 2.5rem; }
44
+ &[data-outside-range] { color: var(--ui-muted); opacity: 0.5; }
45
+ &[data-today] { font-weight: 700; color: var(--ui-accent); }
46
+ &[data-in-range] { background: var(--ui-accent-soft); border-radius: 0; }
47
+ &[data-range-start] { border-start-start-radius: var(--ui-radius); border-end-start-radius: var(--ui-radius); }
48
+ &[data-range-end] { border-start-end-radius: var(--ui-radius); border-end-end-radius: var(--ui-radius); }
49
+ &[data-selected] { background: var(--ui-accent); color: var(--ui-accent-text); }
50
+ &[data-focus] { outline: 2px solid var(--ui-ring); outline-offset: -2px; }
51
+ &:is([data-disabled], [data-unavailable]) { opacity: 0.3; cursor: not-allowed; }
52
+ &[data-unavailable] { text-decoration: line-through; } }
53
+ }
@@ -0,0 +1,98 @@
1
+ /**
2
+ * A modal dialog, rendered at the end of <body> so no ancestor's stacking or
3
+ * overflow can trap it.
4
+ *
5
+ * <Dialog heading="Delete lesson?" description="This cannot be undone."
6
+ * trigger={<Button>Delete</Button>}
7
+ * footer={<Button onClick={…}>Cancel</Button>}>
8
+ * …body…
9
+ * </Dialog>
10
+ *
11
+ * Focus moves in and is trapped while it is open, the page behind doesn't
12
+ * scroll, Escape or a click on the backdrop closes it, and focus returns to
13
+ * the trigger.
14
+ *
15
+ * - `open`, `defaultOpen`; `onOpenChange`: called with the new open state;
16
+ * `onExitComplete`: called once it has animated out
17
+ * - `trigger`: the element that opens it (optional; it gets the trigger's props,
18
+ * so a component must pass them and its ref on, as Button does)
19
+ * - `heading`, `description`: label and describe it for assistive tech
20
+ * - `footer`: buttons along the bottom
21
+ * - `alert`: an alertdialog, for confirmations; the backdrop doesn't close it
22
+ * - `size`: 'sm', 'md' (default) or 'lg'
23
+ * - `closable`: false hides the close button
24
+ *
25
+ * Or composed, with the overlay still done for you (backdrop, positioning,
26
+ * close button), from Header, Body and Footer; a Dialog.Trigger child opens it:
27
+ *
28
+ * <Dialog>
29
+ * <Dialog.Trigger asChild><Button>Edit</Button></Dialog.Trigger>
30
+ * <Dialog.Header heading="Edit student" description="Saved to their profile." />
31
+ * <Dialog.Body>…</Dialog.Body>
32
+ * <Dialog.Footer><Dialog.CloseTrigger asChild><Button>Cancel</Button></Dialog.CloseTrigger>…</Dialog.Footer>
33
+ * </Dialog>
34
+ *
35
+ * Fully by hand, with Ark's parts (and Header, Body and Footer for the layout):
36
+ *
37
+ * <Dialog.Root>
38
+ * <Dialog.Trigger asChild><Button>Open</Button></Dialog.Trigger>
39
+ * <Portal>
40
+ * <Dialog.Backdrop />
41
+ * <Dialog.Positioner>
42
+ * <Dialog.Content>
43
+ * <Dialog.Header><Dialog.Title>Title</Dialog.Title></Dialog.Header>
44
+ * <Dialog.Body>…</Dialog.Body>
45
+ * <Dialog.Footer><Dialog.CloseTrigger asChild><Button>Close</Button></Dialog.CloseTrigger></Dialog.Footer>
46
+ * </Dialog.Content>
47
+ * </Dialog.Positioner>
48
+ * </Portal>
49
+ * </Dialog.Root>
50
+ */
51
+ import { type ComponentProps, type ReactElement, type ReactNode } from 'react';
52
+ import { Dialog as Ark, type DialogOpenChangeDetails } from '@ark-ui/react/dialog';
53
+ import 'iconify-icon';
54
+ import { type Classes } from '../utils.js';
55
+ import '../theme.css';
56
+ import './dialog.css';
57
+ export type DialogProps = Omit<Ark.ContentProps, 'children'> & {
58
+ open?: boolean;
59
+ defaultOpen?: boolean;
60
+ onOpenChange?: (open: boolean, details: DialogOpenChangeDetails) => void;
61
+ onExitComplete?: () => void;
62
+ heading?: ReactNode;
63
+ description?: ReactNode;
64
+ alert?: boolean;
65
+ size?: 'sm' | 'md' | 'lg';
66
+ closable?: boolean;
67
+ trigger?: ReactElement | null;
68
+ footer?: ReactNode;
69
+ classes?: Classes<'backdrop' | 'positioner' | 'content' | 'closeTrigger' | 'header' | 'title' | 'description' | 'body' | 'footer'>;
70
+ children?: ReactNode;
71
+ };
72
+ type HeaderProps = ComponentProps<'div'> & {
73
+ heading?: ReactNode;
74
+ description?: ReactNode;
75
+ };
76
+ declare function Header({ heading, description, children, ...rest }: HeaderProps): import("react").JSX.Element;
77
+ declare function DialogShortcut({ open, defaultOpen, onOpenChange, onExitComplete, heading, description, alert, size, closable, trigger, footer, className, classes, children, ...rest }: DialogProps): import("react").JSX.Element;
78
+ export declare const Dialog: typeof DialogShortcut & {
79
+ Root: (props: Ark.RootProps) => import("react").JSX.Element;
80
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
81
+ Backdrop: import("react").ForwardRefExoticComponent<Ark.BackdropProps & import("react").RefAttributes<HTMLDivElement>>;
82
+ Positioner: import("react").ForwardRefExoticComponent<Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>>;
83
+ Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
84
+ CloseTrigger: import("react").ForwardRefExoticComponent<Ark.CloseTriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
85
+ Title: import("react").ForwardRefExoticComponent<Ark.TitleProps & import("react").RefAttributes<HTMLHeadingElement>>;
86
+ Description: import("react").ForwardRefExoticComponent<Ark.DescriptionProps & import("react").RefAttributes<HTMLDivElement>>;
87
+ Context: (props: Ark.ContextProps) => ReactNode;
88
+ Header: typeof Header;
89
+ Body: {
90
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): ReactElement<any, string | import("react").JSXElementConstructor<any>>;
91
+ displayName: string;
92
+ };
93
+ Footer: {
94
+ (props: import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>): ReactElement<any, string | import("react").JSXElementConstructor<any>>;
95
+ displayName: string;
96
+ };
97
+ };
98
+ export {};