@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,71 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * A light, dark and system switch, driving `colorScheme`, so every toggle on
4
+ * the page stays in step and the choice is saved.
5
+ *
6
+ * <ThemeToggle />
7
+ * <ThemeToggle variant="menu" />
8
+ * <ThemeToggle mobile="menu" />
9
+ * <ThemeToggle variant="toggle" />
10
+ *
11
+ * - `variant`: 'segmented' (default: icon-only buttons with tooltips), 'menu'
12
+ * (one button showing the current choice, opening a menu of all of them) or
13
+ * 'toggle' (one button flipping between light and dark)
14
+ * - `mobile`: the variant below `breakpoint` (e.g. 'menu' where space is tight)
15
+ * - `breakpoint`: the width in px `mobile` applies under (768)
16
+ * - `options`: which schemes to offer, in order (system, light, dark)
17
+ * - `system`: false leaves out 'system' whatever `options` says
18
+ * - `labels`: the tooltips and accessible names, `{ light, dark, system }`
19
+ * - `label`: names the group for assistive tech
20
+ * - `size`: 'sm', 'md' (default) or 'lg', for every variant
21
+ * - `placement`: where the menu variant opens ('bottom-end', or 'bottom' with `iconOnly`)
22
+ * - `keepOpen`: the menu variant stays open after a choice
23
+ * - `iconOnly`: the menu variant lists just the icons, the current one highlighted
24
+ * - `onChange`: called with 'light', 'dark' or 'system'
25
+ */
26
+ import { useEffect, useState } from 'react';
27
+ import { Menu } from '../menu/Menu.js';
28
+ import 'iconify-icon';
29
+ import { Segmented } from '../segment-group/Segmented.js';
30
+ import { Tooltip } from '../tooltip/Tooltip.js';
31
+ import { useColorScheme } from '../color-scheme.js';
32
+ import { cx, partsOf } from '../utils.js';
33
+ import { useMessages } from '../format/LocaleProvider.js';
34
+ import '../theme.css';
35
+ import './theme-toggle.css';
36
+ const icons = { light: 'lucide:sun', dark: 'lucide:moon', system: 'lucide:monitor' };
37
+ // Whether the viewport is under `breakpoint` (only watched when `on`).
38
+ function useNarrow(on, breakpoint) {
39
+ const [narrow, setNarrow] = useState(false);
40
+ useEffect(() => {
41
+ if (!on)
42
+ return;
43
+ const query = matchMedia(`(max-width: ${breakpoint - 1}px)`);
44
+ const update = () => setNarrow(query.matches);
45
+ update();
46
+ query.addEventListener('change', update);
47
+ return () => query.removeEventListener('change', update);
48
+ }, [on, breakpoint]);
49
+ return narrow;
50
+ }
51
+ export function ThemeToggle({ variant = 'segmented', mobile = null, breakpoint = 768, options = ['system', 'light', 'dark'], system = true, labels: labelsProp, label: labelProp, placement = null, size = 'md', keepOpen = false, iconOnly = false, onChange, className, classes, ...rest }) {
52
+ const t = useMessages();
53
+ const labels = labelsProp ?? t.themeToggle;
54
+ const label = labelProp ?? t.themeToggle.label;
55
+ const part = partsOf('theme-toggle', classes);
56
+ const { scheme, dark, setScheme } = useColorScheme();
57
+ const narrow = useNarrow(!!mobile, breakpoint);
58
+ const mode = mobile && narrow ? mobile : variant;
59
+ const offersSystem = system && options.includes('system');
60
+ const items = options.filter((s) => icons[s] && (s !== 'system' || system)).map((s) => ({ value: s, label: labels[s], icon: icons[s] }));
61
+ // Without the system option, show what the OS currently resolves to.
62
+ const value = offersSystem ? scheme : dark ? 'dark' : 'light';
63
+ const shown = dark ? 'dark' : 'light';
64
+ function pick(next) {
65
+ if (next == null || next === value)
66
+ return;
67
+ setScheme(next);
68
+ onChange?.(next);
69
+ }
70
+ return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), role: "group", "aria-label": label, "data-variant": mode, "data-size": size, "data-icon-only": (iconOnly && mode === 'menu') || undefined, children: mode === 'menu' ? (_jsx(Menu, { placement: placement ?? (iconOnly ? 'bottom' : 'bottom-end'), keepOpen: keepOpen, trigger: (_jsx("button", { ...part('trigger'), type: "button", "aria-label": `${label}: ${labels[value]}`, children: _jsx("iconify-icon", { ...part('trigger-icon'), icon: icons[value], "aria-hidden": "true" }) })), children: _jsx(Menu.RadioGroup, { value: value, onValueChange: (next) => pick(next), children: items.map((item) => _jsx(Menu.Radio, { value: item.value, icon: item.icon, children: item.label }, item.value)) }) })) : mode === 'toggle' ? (_jsx(Tooltip, { content: labels[shown], children: _jsx("button", { ...part('trigger'), type: "button", "aria-label": labels.dark, "aria-pressed": dark, onClick: () => pick(dark ? 'light' : 'dark'), children: _jsx("iconify-icon", { ...part('trigger-icon'), icon: icons[shown], "aria-hidden": "true" }) }) })) : (_jsx(Segmented, { iconOnly: true, size: size, items: items, value: value, onValueChange: pick })) }));
71
+ }
@@ -0,0 +1,23 @@
1
+ @layer ui {
2
+ [data-scope=theme-toggle][data-part=root] { display: inline-block;
3
+ --tt-h: var(--ui-control-height); --tt-icon: 16px;
4
+ &[data-size=sm] { --tt-h: var(--ui-control-height-sm); --tt-icon: 15px; }
5
+ &[data-size=lg] { --tt-h: var(--ui-control-height-lg); --tt-icon: 18px; } }
6
+ /* The menu and toggle variants: one square button, like a segmented item. */
7
+ [data-scope=theme-toggle][data-part=trigger] { display: inline-flex; align-items: center; justify-content: center; width: var(--tt-h); height: var(--tt-h);
8
+ padding: 0; box-sizing: border-box; border: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-text);
9
+ border-radius: var(--ui-radius); cursor: pointer;
10
+ &:hover, &[data-state=open] { background: var(--ui-hover); }
11
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); } }
12
+ [data-scope=theme-toggle][data-part=trigger-icon] { display: block; font-size: var(--tt-icon); }
13
+ /* Icons only: a narrow column, the current scheme highlighted instead of
14
+ dotted, names kept for screen readers. */
15
+ [data-scope=theme-toggle][data-icon-only] {
16
+ & [data-scope=menu][data-part=content] { min-width: 0; padding: 0.25rem; }
17
+ & [data-scope=menu][data-part=item] { justify-content: center; width: var(--tt-h); height: var(--tt-h); padding: 0; box-sizing: border-box;
18
+ &[data-state=checked] { background: var(--ui-hover); }
19
+ &[data-state=checked] [data-part=icon] { color: var(--ui-text); } }
20
+ & [data-scope=menu][data-part=indicator] { display: none; }
21
+ & [data-scope=menu][data-part=icon] { font-size: var(--tt-icon); }
22
+ & [data-scope=menu][data-part=label] { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } }
23
+ }
package/dist/theme.css ADDED
@@ -0,0 +1,121 @@
1
+ /* Design tokens (imba-ui's), and a few base rules. Override any token in your
2
+ app, on :root for everything or on an element for what's inside it:
3
+
4
+ :root { --ui-accent: #e11d48; }
5
+ <DatePicker style="--ui-radius: 12px" />
6
+
7
+ Dark values apply under html.dark or [data-theme=dark].
8
+
9
+ All the library's CSS sits in @layer ui, so your CSS (and Tailwind's
10
+ utilities) beat it without !important. With Tailwind, declare the order
11
+ first in your app's CSS, so its reset stays below the library:
12
+
13
+ @layer theme, base, ui, components, utilities;
14
+ @import "tailwindcss"; */
15
+
16
+ @layer ui {
17
+ :root {
18
+ --ui-text: #18181b;
19
+ --ui-muted: #71717a;
20
+ --ui-surface: white;
21
+ --ui-canvas: #f2f4f7;
22
+ /* The inset app shell: its sidebar (a lighter shade of the canvas) and the frame round the page. */
23
+ --ui-sidebar-bg: #f8f9fb;
24
+ --ui-frame: white;
25
+ --ui-border: #e4e4e7;
26
+ --ui-hover: #f4f4f5;
27
+ /* Loading placeholders: visible on the canvas and on cards alike. */
28
+ --ui-skeleton: #e4e4e7;
29
+ --ui-accent: #2563eb;
30
+ --ui-accent-text: white;
31
+ --ui-accent-soft: #dbeafe;
32
+ --ui-accent-soft-text: #1e40af;
33
+ /* Selected text, and Highlight's marks. */
34
+ --ui-selection: color-mix(in srgb, var(--ui-accent) 24%, transparent);
35
+ --ui-highlight: color-mix(in srgb, var(--ui-accent) 22%, transparent);
36
+ --ui-ring: #3b82f6;
37
+ --ui-ring-soft: #3b82f633;
38
+ --ui-danger: #dc2626;
39
+ --ui-success: #16a34a;
40
+ --ui-warning: #d97706;
41
+ /* Chart series, in order (the first is the accent). */
42
+ --ui-chart-1: var(--ui-accent);
43
+ --ui-chart-2: #14b8a6;
44
+ --ui-chart-3: #f59e0b;
45
+ --ui-chart-4: #f43f5e;
46
+ --ui-chart-5: #8b5cf6;
47
+ --ui-radius: 6px;
48
+ --ui-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.25);
49
+ --ui-card-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
50
+ --ui-font: inherit;
51
+ /* One height for buttons and inputs, so they line up in a row. */
52
+ --ui-control-height: 2.25rem;
53
+ --ui-control-height-sm: 2rem;
54
+ --ui-control-height-lg: 2.75rem;
55
+ /* Sidebar and its items. */
56
+ --ui-sidebar-width: 15rem;
57
+ --ui-sidebar-rail-width: 3.5rem;
58
+ --ui-sidebar-item-height: 2.125rem;
59
+ --ui-sidebar-item-padding: 0 0.5rem 0 0.75rem;
60
+ --ui-sidebar-item-radius: var(--ui-radius);
61
+ --ui-sidebar-item-text: var(--ui-muted);
62
+ /* The sidebar itself (outside the inset layout, which uses --ui-sidebar-bg),
63
+ its section headings, its own icon buttons, and hovered items' text.
64
+ With the item tokens, enough for a coloured sidebar. */
65
+ --ui-sidebar-surface: var(--ui-surface);
66
+ --ui-sidebar-heading: var(--ui-muted);
67
+ --ui-sidebar-icon: var(--ui-muted);
68
+ --ui-sidebar-hover-text: var(--ui-text);
69
+ --ui-sidebar-hover: var(--ui-hover);
70
+ --ui-sidebar-active: var(--ui-hover);
71
+ --ui-sidebar-active-text: var(--ui-text);
72
+ --ui-sidebar-active-weight: 500;
73
+ --ui-sidebar-active-shadow: none;
74
+ /* The inset layout's sidebar is grey, so its hovered and current items go white. */
75
+ --ui-sidebar-inset-hover: color-mix(in srgb, var(--ui-surface) 75%, transparent);
76
+ --ui-sidebar-inset-active: var(--ui-surface);
77
+ --ui-sidebar-inset-active-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
78
+ }
79
+
80
+ :root.dark, [data-theme=dark] {
81
+ --ui-text: #fafafa;
82
+ --ui-muted: #a1a1aa;
83
+ --ui-surface: #1c1c21;
84
+ --ui-canvas: #141418;
85
+ --ui-sidebar-bg: #0c0c0e;
86
+ --ui-frame: #0c0c0e;
87
+ --ui-border: #2b2b31;
88
+ --ui-hover: #27272d;
89
+ --ui-skeleton: #303036;
90
+ --ui-accent: #3b82f6;
91
+ --ui-accent-soft: #1e3a8a;
92
+ --ui-accent-soft-text: #bfdbfe;
93
+ --ui-selection: color-mix(in srgb, var(--ui-accent) 40%, transparent);
94
+ --ui-highlight: color-mix(in srgb, var(--ui-accent) 35%, transparent);
95
+ --ui-ring: #60a5fa;
96
+ --ui-ring-soft: #60a5fa33;
97
+ --ui-danger: #f87171;
98
+ --ui-success: #4ade80;
99
+ --ui-warning: #fbbf24;
100
+ --ui-chart-2: #2dd4bf;
101
+ --ui-chart-3: #fbbf24;
102
+ --ui-chart-4: #fb7185;
103
+ --ui-chart-5: #a78bfa;
104
+ --ui-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.6);
105
+ /* Shadows don't read on dark; borders do the work. */
106
+ --ui-card-shadow: none;
107
+ }
108
+
109
+ ::selection { background: var(--ui-selection); }
110
+
111
+ /* Zag hides closed parts with `hidden`; that beats any display a component sets. */
112
+ [data-scope][hidden] { display: none !important; }
113
+ [data-scope]:focus:not(:focus-visible) { outline: none; }
114
+
115
+ @media (prefers-reduced-motion: reduce) {
116
+ [data-scope][data-part] { animation: none !important; transition: none !important; }
117
+ }
118
+
119
+ @keyframes ui-spin { to { rotate: 360deg; } }
120
+ @keyframes ui-skeleton-pulse { 50% { opacity: 0.5; } }
121
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * A time of day, typed or picked with the browser's own time control (a wheel
3
+ * on phones), as 'HH:mm'.
4
+ *
5
+ * <TimeInput label="Starts" value={time} onValueChange={setTime} step={15} min="08:00" max="18:00" />
6
+ *
7
+ * Inside a Field it takes the field's label, hint and error. (For booking
8
+ * from free slots, show the slots instead: a free time picked here can clash.)
9
+ *
10
+ * - `value`, `defaultValue`: 'HH:mm' (or null); `onValueChange`: called with it
11
+ * - `step`: minutes between times (1); the arrow keys move by it
12
+ * - `min`, `max`: 'HH:mm'
13
+ * - `icon`: an Iconify name before it ('lucide:clock'; null for none)
14
+ * - and Input's props: `label`, `size`, `disabled`, `name`…
15
+ */
16
+ import { type InputProps } from '../input/Input.js';
17
+ export type TimeInputProps = Omit<InputProps, 'type' | 'value' | 'defaultValue' | 'onValueChange' | 'step' | 'min' | 'max'> & {
18
+ value?: string | null;
19
+ defaultValue?: string | null;
20
+ onValueChange?: (value: string | null) => void;
21
+ step?: number;
22
+ min?: string;
23
+ max?: string;
24
+ };
25
+ export declare function TimeInput({ value, defaultValue, onValueChange, step, icon, ...rest }: TimeInputProps): import("react").JSX.Element;
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * A time of day, typed or picked with the browser's own time control (a wheel
4
+ * on phones), as 'HH:mm'.
5
+ *
6
+ * <TimeInput label="Starts" value={time} onValueChange={setTime} step={15} min="08:00" max="18:00" />
7
+ *
8
+ * Inside a Field it takes the field's label, hint and error. (For booking
9
+ * from free slots, show the slots instead: a free time picked here can clash.)
10
+ *
11
+ * - `value`, `defaultValue`: 'HH:mm' (or null); `onValueChange`: called with it
12
+ * - `step`: minutes between times (1); the arrow keys move by it
13
+ * - `min`, `max`: 'HH:mm'
14
+ * - `icon`: an Iconify name before it ('lucide:clock'; null for none)
15
+ * - and Input's props: `label`, `size`, `disabled`, `name`…
16
+ */
17
+ import { Input } from '../input/Input.js';
18
+ export function TimeInput({ value, defaultValue, onValueChange, step = 1, icon = 'lucide:clock', ...rest }) {
19
+ return (_jsx(Input, { ...rest, type: "time", icon: icon, step: step * 60, ...(value !== undefined && { value: value ?? '' }), ...(defaultValue != null && { defaultValue }), onValueChange: (next) => onValueChange?.(next || null) }));
20
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Events down a line, e.g. a student's history.
3
+ *
4
+ * <Timeline items={[
5
+ * { title: 'Lesson booked', time: '2 Oct', icon: 'lucide:calendar-plus' },
6
+ * { title: 'Invoice paid', time: '1 Oct', description: '£120', color: 'success' },
7
+ * ]} />
8
+ *
9
+ * Or as markup, when an event needs more than text:
10
+ *
11
+ * <Timeline>
12
+ * <Timeline.Item title="Note added" time="30 Sep" icon="lucide:notebook-pen"><Card>…</Card></Timeline.Item>
13
+ * </Timeline>
14
+ *
15
+ * - `items`: objects with a Timeline.Item's props (only `title` is needed)
16
+ * - `variant`: 'line' (default) or 'cards' (each item's content in a card)
17
+ * - `size`: 'sm' or 'md' (default)
18
+ * - Timeline.Item (also TimelineItem): `title`; `time` (text, so format it
19
+ * first); `description`, a line under the title; `icon`, an Iconify name in
20
+ * a circle (a dot without one); `color` tints the marker: 'accent',
21
+ * 'success', 'warning' or 'danger' (grey by default). Its content shows
22
+ * under the title
23
+ */
24
+ import type { HTMLAttributes, LiHTMLAttributes, ReactNode } from 'react';
25
+ import 'iconify-icon';
26
+ import { type Classes } from '../utils.js';
27
+ import '../theme.css';
28
+ import './timeline.css';
29
+ type ItemClasses = Classes<'item' | 'marker' | 'icon' | 'dot' | 'body' | 'header' | 'heading' | 'time' | 'description' | 'content'>;
30
+ export type TimelineItemProps = Omit<LiHTMLAttributes<HTMLLIElement>, 'title' | 'color'> & {
31
+ title?: ReactNode;
32
+ time?: ReactNode;
33
+ description?: ReactNode;
34
+ icon?: string | null;
35
+ color?: 'accent' | 'success' | 'warning' | 'danger' | null;
36
+ classes?: ItemClasses;
37
+ };
38
+ export type TimelineProps = HTMLAttributes<HTMLDivElement> & {
39
+ items?: TimelineItemProps[] | null;
40
+ variant?: 'line' | 'cards';
41
+ size?: 'sm' | 'md';
42
+ classes?: Classes<'root' | 'list'>;
43
+ };
44
+ declare function TimelineShortcut({ items, variant, size, className, classes, children, ...rest }: TimelineProps): import("react").JSX.Element;
45
+ /** One event in a Timeline; its content shows under the title. */
46
+ export declare function TimelineItem({ title, time, description, icon, color, className, classes, children, ...rest }: TimelineItemProps): import("react").JSX.Element;
47
+ export declare const Timeline: typeof TimelineShortcut & {
48
+ Item: typeof TimelineItem;
49
+ };
50
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import 'iconify-icon';
3
+ import { cx, partsOf, withParts } from '../utils.js';
4
+ import '../theme.css';
5
+ import './timeline.css';
6
+ function TimelineShortcut({ items = null, variant = 'line', size = 'md', className, classes, children, ...rest }) {
7
+ const part = partsOf('timeline', classes);
8
+ return (_jsx("div", { ...rest, ...part('root'), className: cx(className, classes?.root), "data-variant": variant, "data-size": size, children: _jsxs("ol", { ...part('list'), children: [(items ?? []).map((item, i) => _jsx(TimelineItem, { ...item }, i)), children] }) }));
9
+ }
10
+ /** One event in a Timeline; its content shows under the title. */
11
+ export function TimelineItem({ title = null, time = null, description = null, icon = null, color = null, className, classes, children, ...rest }) {
12
+ const part = partsOf('timeline', classes);
13
+ return (_jsxs("li", { ...rest, ...part('item'), className: cx(className, classes?.item), "data-color": color ?? 'neutral', children: [_jsx("div", { ...part('marker'), "aria-hidden": "true", children: icon ? _jsx("span", { ...part('icon'), children: _jsx("iconify-icon", { icon: icon }) }) : _jsx("span", { ...part('dot') }) }), _jsxs("div", { ...part('body'), children: [_jsxs("div", { ...part('header'), children: [title && _jsx("span", { ...part('heading'), children: title }), time && _jsx("time", { ...part('time'), children: time })] }), description && _jsx("div", { ...part('description'), children: description }), children && _jsx("div", { ...part('content'), children: children })] })] }));
14
+ }
15
+ export const Timeline = withParts(TimelineShortcut, { Item: TimelineItem });
@@ -0,0 +1,29 @@
1
+ @layer ui {
2
+ [data-scope=timeline][data-part=root] { display: block; width: 100%; color: var(--ui-text); font-family: var(--ui-font); font-size: 14px; }
3
+ [data-scope=timeline][data-part=list] { display: flex; flex-direction: column; margin: 0; padding: 0; list-style: none; }
4
+ [data-scope=timeline][data-part=item] { display: flex; gap: 0.75rem; position: relative; --tint: var(--ui-muted); --tint-bg: var(--ui-hover);
5
+ &[data-color=accent] { --tint: var(--ui-accent); --tint-text: var(--ui-accent-soft-text); --tint-bg: var(--ui-accent-soft); }
6
+ &[data-color=success] { --tint: var(--ui-success); --tint-bg: color-mix(in srgb, var(--ui-success) 15%, var(--ui-surface)); }
7
+ &[data-color=warning] { --tint: #f59e0b; --tint-text: #b45309; --tint-bg: #fef3c7; }
8
+ &[data-color=danger] { --tint: var(--ui-danger); --tint-bg: color-mix(in srgb, var(--ui-danger) 12%, var(--ui-surface)); } }
9
+ /* The connector runs down the marker column to the next item, behind the markers, and stops at the last. */
10
+ [data-scope=timeline][data-part=marker] { position: relative; display: flex; justify-content: center; width: 2rem; flex-shrink: 0;
11
+ &::after { content: ''; position: absolute; top: 0; bottom: 0; left: calc(50% - 0.5px); width: 1px; background: var(--ui-border); }
12
+ [data-part=item]:last-child > &::after { display: none; } }
13
+ [data-scope=timeline][data-part=icon] { position: relative; z-index: 1; display: grid; place-items: center; width: 2rem; height: 2rem; box-sizing: border-box;
14
+ border-radius: 9999px; background: var(--tint-bg); color: var(--tint-text, var(--tint)); font-size: 15px; border: 1px solid var(--ui-border);
15
+ [data-color]:not([data-color=neutral]) > [data-part=marker] > & { border-color: transparent; }
16
+ [data-size=sm] & { width: 1.5rem; height: 1.5rem; font-size: 13px; } }
17
+ /* A dot, ringed in the page colour so the line doesn't touch it. */
18
+ [data-scope=timeline][data-part=dot] { position: relative; z-index: 1; width: 0.625rem; height: 0.625rem; margin-top: 0.625rem;
19
+ border-radius: 9999px; background: var(--tint); border: 3px solid var(--ui-surface); box-sizing: content-box; }
20
+ [data-scope=timeline][data-part=body] { flex-grow: 1; min-width: 0; padding: 0.375rem 0 1.5rem;
21
+ [data-size=sm] & { padding-bottom: 1rem; }
22
+ [data-variant=cards] & { padding: 0.75rem; margin-bottom: 0.75rem; border: 1px solid var(--ui-border);
23
+ border-radius: calc(var(--ui-radius) + 2px); background: var(--ui-surface); } }
24
+ [data-scope=timeline][data-part=header] { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; flex-wrap: wrap; }
25
+ [data-scope=timeline][data-part=heading] { font-weight: 500; }
26
+ [data-scope=timeline][data-part=time] { font-size: 12px; color: var(--ui-muted); white-space: nowrap; }
27
+ [data-scope=timeline][data-part=description] { margin-top: 0.125rem; color: var(--ui-muted); }
28
+ [data-scope=timeline][data-part=content] { margin-top: 0.5rem; }
29
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The region toasts stack in, rendered at the end of <body>. Put one in the
3
+ * app layout, then call `toaster` from anywhere:
4
+ *
5
+ * <Toaster />
6
+ * toaster.success({ title: 'Lesson booked', description: 'Thursday at 16:00' })
7
+ * toaster.error({ title: 'Could not save' })
8
+ * const id = toaster.create({ type: 'loading', title: 'Uploading…' })
9
+ * toaster.update(id, { type: 'success', title: 'Uploaded' })
10
+ *
11
+ * Toasts pause while hovered or focused, Escape dismisses the focused one, and
12
+ * Alt+T jumps to them. An `action: { label, onClick }` adds a button.
13
+ *
14
+ * - `store`: a toaster from createToaster (the shared `toaster` by default)
15
+ * - `label`: the region's accessible name ('Notifications')
16
+ * - `render`: draws each toast yourself, given its data (inside Toast.Root,
17
+ * with Toast.Title, Description, ActionTrigger, CloseTrigger)
18
+ */
19
+ import type { ReactNode } from 'react';
20
+ import { Toast, createToaster, type CreateToasterReturn, type ToastOptions } from '@ark-ui/react/toast';
21
+ import 'iconify-icon';
22
+ import { type Classes } from '../utils.js';
23
+ import '../theme.css';
24
+ import './toast.css';
25
+ declare const make: (options?: Partial<Parameters<typeof createToaster>[0]>) => CreateToasterReturn<ReactNode>;
26
+ export { make as createToaster };
27
+ export declare const toaster: CreateToasterReturn;
28
+ export type ToasterProps = {
29
+ store?: CreateToasterReturn;
30
+ label?: string;
31
+ render?: (toast: ToastOptions) => ReactNode;
32
+ className?: string;
33
+ classes?: Classes<'group' | 'root' | 'icon' | 'spinner' | 'text' | 'title' | 'description' | 'actionTrigger' | 'closeTrigger'>;
34
+ };
35
+ export declare function Toaster({ store, label: labelProp, render, className, classes }: ToasterProps): import("react").JSX.Element;
36
+ export { Toast };
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Toaster as ArkToaster, Toast, createToaster } from '@ark-ui/react/toast';
3
+ import { Portal } from '@ark-ui/react/portal';
4
+ import 'iconify-icon';
5
+ import { cx, partsOf } from '../utils.js';
6
+ import { useMessages } from '../format/LocaleProvider.js';
7
+ import '../theme.css';
8
+ import './toast.css';
9
+ // Toasts: brief notifications stacked in a corner. The shared `toaster`
10
+ // stacks at bottom-end; make another with createToaster and pass it to a
11
+ // Toaster as `store`.
12
+ const make = (options = {}) => createToaster({ placement: 'bottom-end', overlap: true, max: 5, ...options });
13
+ export { make as createToaster };
14
+ export const toaster = make();
15
+ const icons = {
16
+ info: 'lucide:info', success: 'lucide:circle-check', warning: 'lucide:triangle-alert', error: 'lucide:circle-alert',
17
+ };
18
+ export function Toaster({ store = toaster, label: labelProp, render, className, classes }) {
19
+ const t = useMessages();
20
+ const label = labelProp ?? t.toaster.label;
21
+ const part = partsOf('toast', classes);
22
+ return (_jsx(Portal, { children: _jsx(ArkToaster, { toaster: store, label: label, ...part('group'), className: cx(className, classes?.group), children: (toast) => render ? _jsx(Toast.Root, { ...part('root'), children: render(toast) }) : (_jsxs(Toast.Root, { ...part('root'), children: [toast.type === 'loading' ? _jsx("span", { ...part('spinner'), "aria-hidden": "true" })
23
+ : toast.type && icons[toast.type] && _jsx("iconify-icon", { ...part('icon'), icon: icons[toast.type], "aria-hidden": "true" }), _jsxs("div", { ...part('text'), children: [toast.title && _jsx(Toast.Title, { ...part('title'), children: toast.title }), toast.description && _jsx(Toast.Description, { ...part('description'), children: toast.description })] }), toast.action && _jsx(Toast.ActionTrigger, { ...part('action-trigger'), children: toast.action.label }), _jsx(Toast.CloseTrigger, { ...part('close-trigger'), "aria-label": t.common.dismiss, children: _jsx("iconify-icon", { icon: "lucide:x" }) })] })) }) }));
24
+ }
25
+ export { Toast };
@@ -0,0 +1,33 @@
1
+ @layer ui {
2
+ [data-scope=toast][data-part=root] {
3
+ display: flex; align-items: flex-start; gap: 0.75rem; width: 22rem; max-width: calc(100vw - 2rem); padding: 0.75rem 2.5rem 0.75rem 0.75rem; box-sizing: border-box;
4
+ background: var(--ui-surface); color: var(--ui-text); font-family: var(--ui-font); font-size: 14px;
5
+ border: 1px solid var(--ui-border); border-radius: calc(var(--ui-radius) + 2px); box-shadow: var(--ui-shadow); outline: none;
6
+ /* Zag positions each toast through these variables. Toasts behind the
7
+ front one shrink a little (Zag's own --scale would mirror them). */
8
+ translate: var(--x) var(--y); scale: calc(2 - (var(--base-scale, 1)));
9
+ z-index: var(--z-index); height: var(--height); opacity: var(--opacity);
10
+ transition: translate 400ms, scale 400ms, opacity 400ms, height 400ms;
11
+ transition-timing-function: cubic-bezier(0.21, 1.02, 0.73, 1);
12
+ &[data-state=closed] { transition: translate 400ms, scale 400ms, opacity 200ms; transition-timing-function: cubic-bezier(0.06, 0.71, 0.55, 1); }
13
+ &:focus-visible { outline: 2px solid var(--ui-ring-soft); }
14
+ }
15
+ [data-scope=toast][data-part=icon] { flex-shrink: 0; margin-top: 2px; font-size: 18px; color: var(--ui-muted);
16
+ [data-type=success] > & { color: #16a34a; }
17
+ [data-type=warning] > & { color: #d97706; }
18
+ [data-type=error] > & { color: var(--ui-danger); }
19
+ [data-type=info] > & { color: var(--ui-accent); } }
20
+ [data-scope=toast][data-part=spinner] { display: block; width: 1rem; height: 1rem; margin-top: 2px; box-sizing: border-box; flex-shrink: 0;
21
+ border: 2px solid currentColor; border-top-color: transparent; border-radius: 9999px; animation: ui-spin 0.7s linear infinite; }
22
+ [data-scope=toast][data-part=text] { flex: 1; min-width: 0; }
23
+ [data-scope=toast][data-part=title] { font-weight: 600; }
24
+ [data-scope=toast][data-part=description] { color: var(--ui-muted);
25
+ [data-part=title] + & { margin-top: 0.125rem; } }
26
+ [data-scope=toast][data-part=action-trigger] { align-self: center; flex-shrink: 0; height: 1.75rem; padding: 0 0.625rem;
27
+ border: 1px solid var(--ui-border); background: var(--ui-surface); color: var(--ui-text); border-radius: 6px;
28
+ font: inherit; font-size: 12px; font-weight: 500; cursor: pointer;
29
+ &:hover { background: var(--ui-hover); } }
30
+ [data-scope=toast][data-part=close-trigger] { position: absolute; top: 0.5rem; right: 0.5rem; display: grid; place-items: center; width: 1.75rem; height: 1.75rem;
31
+ border: none; background: transparent; border-radius: 4px; color: var(--ui-muted); cursor: pointer; font-size: 14px;
32
+ &:hover { background: var(--ui-hover); color: var(--ui-text); } }
33
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * A tooltip: a short hint on hover and keyboard focus.
3
+ *
4
+ * <Tooltip content="Archive"><Button icon="lucide:archive" aria-label="Archive" /></Tooltip>
5
+ * <Tooltip content="Ada Lovelace">Ada</Tooltip>
6
+ *
7
+ * - children: the element it describes, which gets the trigger's props (a
8
+ * component must pass them and its ref on, as Button does); text, several
9
+ * elements or a disabled one (which fires no events) are wrapped in a span
10
+ * that takes them
11
+ * - `content`: what it says (text or markup)
12
+ * - `placement`: 'top' (default), 'bottom-start', …
13
+ * - `openDelay` (0), `closeDelay` in ms; `interactive`: stays open while the
14
+ * pointer is over it; `disabled`
15
+ * - `open`, `defaultOpen`; `onOpenChange`: called with the new open state
16
+ *
17
+ * It closes on Escape, click or scroll. With `interactive` it stays open while
18
+ * the pointer is over it, so its text can be selected.
19
+ *
20
+ * By hand, with Ark's parts:
21
+ *
22
+ * <Tooltip.Root>
23
+ * <Tooltip.Trigger asChild><Button>Save</Button></Tooltip.Trigger>
24
+ * <Tooltip.Positioner><Tooltip.Content><Tooltip.Arrow><Tooltip.ArrowTip /></Tooltip.Arrow>Saves a draft</Tooltip.Content></Tooltip.Positioner>
25
+ * </Tooltip.Root>
26
+ */
27
+ import { type ReactNode } from 'react';
28
+ import { Tooltip as Ark, type TooltipOpenChangeDetails } from '@ark-ui/react/tooltip';
29
+ import { type Classes } from '../utils.js';
30
+ import '../theme.css';
31
+ import './tooltip.css';
32
+ export type TooltipProps = Omit<Ark.ContentProps, 'content' | 'children'> & {
33
+ content?: ReactNode;
34
+ placement?: NonNullable<Ark.RootProps['positioning']>['placement'];
35
+ openDelay?: number;
36
+ closeDelay?: number;
37
+ interactive?: boolean;
38
+ disabled?: boolean;
39
+ open?: boolean;
40
+ defaultOpen?: boolean;
41
+ onOpenChange?: (open: boolean, details: TooltipOpenChangeDetails) => void;
42
+ children?: ReactNode;
43
+ classes?: Classes<'trigger' | 'positioner' | 'content' | 'arrow' | 'arrowTip'>;
44
+ };
45
+ declare function TooltipShortcut({ content, placement, openDelay, closeDelay, interactive, disabled, open, defaultOpen, onOpenChange, className, classes, children, ...rest }: TooltipProps): import("react").JSX.Element;
46
+ export declare const Tooltip: typeof TooltipShortcut & {
47
+ Root: (props: Ark.RootProps) => import("react").JSX.Element;
48
+ Trigger: import("react").ForwardRefExoticComponent<Ark.TriggerProps & import("react").RefAttributes<HTMLButtonElement>>;
49
+ Positioner: import("react").ForwardRefExoticComponent<Ark.PositionerProps & import("react").RefAttributes<HTMLDivElement>>;
50
+ Content: import("react").ForwardRefExoticComponent<Ark.ContentProps & import("react").RefAttributes<HTMLDivElement>>;
51
+ Arrow: import("react").ForwardRefExoticComponent<Ark.ArrowProps & import("react").RefAttributes<HTMLDivElement>>;
52
+ ArrowTip: import("react").ForwardRefExoticComponent<Ark.ArrowTipProps & import("react").RefAttributes<HTMLDivElement>>;
53
+ Context: (props: Ark.ContextProps) => ReactNode;
54
+ };
55
+ export {};
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * A tooltip: a short hint on hover and keyboard focus.
4
+ *
5
+ * <Tooltip content="Archive"><Button icon="lucide:archive" aria-label="Archive" /></Tooltip>
6
+ * <Tooltip content="Ada Lovelace">Ada</Tooltip>
7
+ *
8
+ * - children: the element it describes, which gets the trigger's props (a
9
+ * component must pass them and its ref on, as Button does); text, several
10
+ * elements or a disabled one (which fires no events) are wrapped in a span
11
+ * that takes them
12
+ * - `content`: what it says (text or markup)
13
+ * - `placement`: 'top' (default), 'bottom-start', …
14
+ * - `openDelay` (0), `closeDelay` in ms; `interactive`: stays open while the
15
+ * pointer is over it; `disabled`
16
+ * - `open`, `defaultOpen`; `onOpenChange`: called with the new open state
17
+ *
18
+ * It closes on Escape, click or scroll. With `interactive` it stays open while
19
+ * the pointer is over it, so its text can be selected.
20
+ *
21
+ * By hand, with Ark's parts:
22
+ *
23
+ * <Tooltip.Root>
24
+ * <Tooltip.Trigger asChild><Button>Save</Button></Tooltip.Trigger>
25
+ * <Tooltip.Positioner><Tooltip.Content><Tooltip.Arrow><Tooltip.ArrowTip /></Tooltip.Arrow>Saves a draft</Tooltip.Content></Tooltip.Positioner>
26
+ * </Tooltip.Root>
27
+ */
28
+ import { isValidElement } from 'react';
29
+ import { Tooltip as Ark } from '@ark-ui/react/tooltip';
30
+ import { Portal } from '@ark-ui/react/portal';
31
+ import { cx, on, partsOf, withParts } from '../utils.js';
32
+ import '../theme.css';
33
+ import './tooltip.css';
34
+ function TooltipShortcut({ content = null, placement = 'top', openDelay = 0, closeDelay, interactive = false, disabled = false, open, defaultOpen, onOpenChange, className, classes, children, ...rest }) {
35
+ const part = partsOf('tooltip', classes);
36
+ return (_jsxs(Ark.Root, { openDelay: openDelay, interactive: interactive, disabled: disabled, lazyMount: true, unmountOnExit: true, positioning: { placement, strategy: 'fixed', gutter: 8 }, ...(closeDelay !== undefined && { closeDelay }), ...(open !== undefined && { open }), ...(defaultOpen !== undefined && { defaultOpen }), ...on('onOpenChange', onOpenChange, (details) => details.open), children: [isValidElement(children) && !children.props.disabled
37
+ ? _jsx(Ark.Trigger, { asChild: true, children: children })
38
+ : _jsx(Ark.Trigger, { asChild: true, children: _jsx("span", { ...part('trigger'), children: children }) }), _jsx(Portal, { children: _jsx(Ark.Positioner, { ...part('positioner'), children: _jsxs(Ark.Content, { ...rest, ...part('content'), className: cx(className, classes?.content), children: [_jsx(Ark.Arrow, { ...part('arrow'), children: _jsx(Ark.ArrowTip, { ...part('arrow-tip') }) }), content] }) }) })] }));
39
+ }
40
+ export const Tooltip = withParts(TooltipShortcut, {
41
+ Root: Ark.Root,
42
+ Trigger: Ark.Trigger,
43
+ Positioner: Ark.Positioner,
44
+ Content: Ark.Content,
45
+ Arrow: Ark.Arrow,
46
+ ArrowTip: Ark.ArrowTip,
47
+ Context: Ark.Context,
48
+ });
@@ -0,0 +1,18 @@
1
+ @layer ui {
2
+ [data-scope=tooltip][data-part=trigger] { display: inline-flex; }
3
+ /* Zag copies the content's z-index onto its positioner. */
4
+ [data-scope=tooltip][data-part=content] {
5
+ z-index: 60; max-width: 16rem; padding: 0.25rem 0.5rem; white-space: normal; font-weight: 400;
6
+ background: var(--ui-text); color: var(--ui-surface); font-family: var(--ui-font); font-size: 12px; line-height: 1.4;
7
+ border-radius: calc(var(--ui-radius) - 2px); box-shadow: var(--ui-shadow);
8
+ --arrow-size: 8px; --arrow-background: var(--ui-text);
9
+ /* Slides in from the trigger's side. */
10
+ --dx: 0px; --dy: 0px;
11
+ &[data-side=top] { --dy: 4px; }
12
+ &[data-side=bottom] { --dy: -4px; }
13
+ &[data-side=left] { --dx: 4px; }
14
+ &[data-side=right] { --dx: -4px; }
15
+ &[data-state=open] { animation: ui-tooltip-in 140ms ease-out; }
16
+ }
17
+ @keyframes ui-tooltip-in { from { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(0.96); } }
18
+ }