@milktop/react-ui 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +26 -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 +119 -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,33 @@
1
+ /**
2
+ * The app's locale and words, for every component that formats dates, times
3
+ * and numbers (FormatNumber, DatePicker, EventCalendar, Progress, Slider, the
4
+ * charts…) or shows words of its own (placeholders, empty states, screen-reader
5
+ * labels), unless one is given its own.
6
+ *
7
+ * <LocaleProvider locale="fr-FR"><App /></LocaleProvider>
8
+ * <LocaleProvider locale="en-GB" messages={{ table: { empty: 'No patients yet' } }}>…
9
+ *
10
+ * - `locale`: a BCP 47 tag ('en-GB', 'fr-FR'); without a provider, the browser's
11
+ * - `messages`: words to change, merged over the built-in list for the locale
12
+ * (English and French are built in; for another language give them all,
13
+ * starting from `en`). See `Messages` for the keys.
14
+ *
15
+ * `useLocale()` gives the locale (undefined without a provider), e.g. for your
16
+ * own Intl formatters; `useMessages()` the words (English without one). The
17
+ * locale is also given to Ark's LocaleProvider, so Ark's parts built by hand
18
+ * follow it too.
19
+ */
20
+ import { type ReactNode } from 'react';
21
+ import { type DeepPartial, type Messages } from './messages.js';
22
+ export type LocaleProviderProps = {
23
+ locale: string;
24
+ messages?: DeepPartial<Messages>;
25
+ children?: ReactNode;
26
+ };
27
+ export declare function LocaleProvider({ locale, messages, children }: LocaleProviderProps): import("react").JSX.Element;
28
+ /** Gives its children the locale and words the app's LocaleProvider last had. */
29
+ export declare function AmbientLocale({ children }: {
30
+ children?: ReactNode;
31
+ }): import("react").JSX.Element;
32
+ export declare const useLocale: () => string | undefined;
33
+ export declare const useMessages: () => Messages;
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * The app's locale and words, for every component that formats dates, times
4
+ * and numbers (FormatNumber, DatePicker, EventCalendar, Progress, Slider, the
5
+ * charts…) or shows words of its own (placeholders, empty states, screen-reader
6
+ * labels), unless one is given its own.
7
+ *
8
+ * <LocaleProvider locale="fr-FR"><App /></LocaleProvider>
9
+ * <LocaleProvider locale="en-GB" messages={{ table: { empty: 'No patients yet' } }}>…
10
+ *
11
+ * - `locale`: a BCP 47 tag ('en-GB', 'fr-FR'); without a provider, the browser's
12
+ * - `messages`: words to change, merged over the built-in list for the locale
13
+ * (English and French are built in; for another language give them all,
14
+ * starting from `en`). See `Messages` for the keys.
15
+ *
16
+ * `useLocale()` gives the locale (undefined without a provider), e.g. for your
17
+ * own Intl formatters; `useMessages()` the words (English without one). The
18
+ * locale is also given to Ark's LocaleProvider, so Ark's parts built by hand
19
+ * follow it too.
20
+ */
21
+ import { createContext, useContext, useEffect, useMemo } from 'react';
22
+ import { LocaleProvider as ArkLocaleProvider } from '@ark-ui/react/locale';
23
+ import { builtIn, en, mergeMessages } from './messages.js';
24
+ const LocaleContext = createContext(undefined);
25
+ const MessagesContext = createContext(en);
26
+ export function LocaleProvider({ locale, messages, children }) {
27
+ const base = builtIn[locale.split('-')[0].toLowerCase()] ?? en;
28
+ // Keyed on the contents, so an inline `messages` object doesn't remake the words every render.
29
+ const key = JSON.stringify(messages ?? null);
30
+ const words = useMemo(() => mergeMessages(base, messages), [base, key]); // eslint-disable-line react-hooks/exhaustive-deps
31
+ useEffect(() => {
32
+ const mine = { locale, messages: words };
33
+ ambient = mine;
34
+ return () => { if (ambient === mine)
35
+ ambient = { messages: en }; };
36
+ }, [locale, words]);
37
+ return (_jsx(LocaleContext, { value: locale, children: _jsx(MessagesContext, { value: words, children: _jsx(ArkLocaleProvider, { locale: locale, children: children }) }) }));
38
+ }
39
+ // The latest provider's locale and words, for what renders in a root of its
40
+ // own (confirm()), outside the app's tree.
41
+ let ambient = { messages: en };
42
+ /** Gives its children the locale and words the app's LocaleProvider last had. */
43
+ export function AmbientLocale({ children }) {
44
+ const { locale, messages } = ambient;
45
+ return _jsx(LocaleContext, { value: locale, children: _jsx(MessagesContext, { value: messages, children: children }) });
46
+ }
47
+ export const useLocale = () => useContext(LocaleContext);
48
+ export const useMessages = () => useContext(MessagesContext);
@@ -0,0 +1,37 @@
1
+ export type FormatNumberOptions = Intl.NumberFormatOptions & {
2
+ locale?: string;
3
+ };
4
+ /**
5
+ * A number for people: grouped digits, currency, percent, units, compact.
6
+ *
7
+ * formatNumber(1820.5) // '1,820.5'
8
+ * formatNumber(1820, { style: 'currency', currency: 'GBP' }) // '£1,820.00'
9
+ * formatNumber(0.125, { style: 'percent' }) // '13%'
10
+ * formatNumber(48.5, { style: 'unit', unit: 'hour' }) // '48.5 hr'
11
+ * formatNumber(12400, { notation: 'compact' }) // '12K'
12
+ */
13
+ export declare function formatNumber(value: number | bigint | null | undefined, { locale, ...options }?: FormatNumberOptions): string;
14
+ /**
15
+ * A size in bytes: '512 B', '624 KB', '2.4 MB' (decimal units, as file
16
+ * managers show them; `binary` for KiB, MiB…).
17
+ */
18
+ export declare function formatBytes(bytes: number | null | undefined, { locale, binary, maximumFractionDigits }?: {
19
+ locale?: string;
20
+ binary?: boolean;
21
+ maximumFractionDigits?: number;
22
+ }): string;
23
+ /**
24
+ * A time relative to now: '2 hours ago', 'in 3 days', 'yesterday'.
25
+ *
26
+ * formatRelative('2026-10-11T09:00')
27
+ * formatRelative(date, { numeric: 'always' }) // '1 day ago', not 'yesterday'
28
+ *
29
+ * - `now`: what it's relative to (now); `numeric`: 'auto' (default) or 'always';
30
+ * `style`: 'long' (default), 'short' or 'narrow'
31
+ */
32
+ export declare function formatRelative(date: Date | string | number | null | undefined, { locale, now, numeric, style }?: {
33
+ locale?: string;
34
+ now?: Date;
35
+ numeric?: 'auto' | 'always';
36
+ style?: 'long' | 'short' | 'narrow';
37
+ }): string;
@@ -0,0 +1,74 @@
1
+ // Number, size and relative-time formatting, on Intl, with the formatters
2
+ // kept (making one is the slow part), so they're cheap to call in lists.
3
+ // Each takes a `locale` (the browser's when left out; in components,
4
+ // useLocale() gives the app's).
5
+ const cache = new Map();
6
+ function cached(kind, locale, options, make) {
7
+ const key = `${kind}|${locale ?? ''}|${JSON.stringify(options)}`;
8
+ if (!cache.has(key))
9
+ cache.set(key, make());
10
+ return cache.get(key);
11
+ }
12
+ /**
13
+ * A number for people: grouped digits, currency, percent, units, compact.
14
+ *
15
+ * formatNumber(1820.5) // '1,820.5'
16
+ * formatNumber(1820, { style: 'currency', currency: 'GBP' }) // '£1,820.00'
17
+ * formatNumber(0.125, { style: 'percent' }) // '13%'
18
+ * formatNumber(48.5, { style: 'unit', unit: 'hour' }) // '48.5 hr'
19
+ * formatNumber(12400, { notation: 'compact' }) // '12K'
20
+ */
21
+ export function formatNumber(value, { locale, ...options } = {}) {
22
+ if (value == null || (typeof value === 'number' && Number.isNaN(value)))
23
+ return '';
24
+ return cached('number', locale, options, () => new Intl.NumberFormat(locale, options)).format(value);
25
+ }
26
+ /**
27
+ * A size in bytes: '512 B', '624 KB', '2.4 MB' (decimal units, as file
28
+ * managers show them; `binary` for KiB, MiB…).
29
+ */
30
+ export function formatBytes(bytes, { locale, binary = false, maximumFractionDigits = 1 } = {}) {
31
+ if (bytes == null || Number.isNaN(bytes))
32
+ return '';
33
+ const base = binary ? 1024 : 1000;
34
+ const units = ['byte', 'kilobyte', 'megabyte', 'gigabyte', 'terabyte', 'petabyte'];
35
+ let unit = 0;
36
+ let value = Math.abs(bytes);
37
+ while (value >= base && unit < units.length - 1) {
38
+ value /= base;
39
+ unit++;
40
+ }
41
+ const text = formatNumber(Math.sign(bytes) * value, { locale, style: 'unit', unit: units[unit], unitDisplay: unit === 0 ? 'long' : 'short',
42
+ maximumFractionDigits: unit === 0 ? 0 : maximumFractionDigits });
43
+ // Intl writes 'kB'; binary sizes swap in KiB, MiB…
44
+ return binary && unit > 0 ? text.replace(/[kKMGTP]B/, (m) => `${m[0].toUpperCase()}iB`) : text.replace(/\bkB\b/, 'KB');
45
+ }
46
+ const steps = [
47
+ ['second', 60], ['minute', 60], ['hour', 24], ['day', 7], ['week', 4.34524], ['month', 12], ['year', Infinity],
48
+ ];
49
+ /**
50
+ * A time relative to now: '2 hours ago', 'in 3 days', 'yesterday'.
51
+ *
52
+ * formatRelative('2026-10-11T09:00')
53
+ * formatRelative(date, { numeric: 'always' }) // '1 day ago', not 'yesterday'
54
+ *
55
+ * - `now`: what it's relative to (now); `numeric`: 'auto' (default) or 'always';
56
+ * `style`: 'long' (default), 'short' or 'narrow'
57
+ */
58
+ export function formatRelative(date, { locale, now = new Date(), numeric = 'auto', style = 'long' } = {}) {
59
+ if (date == null)
60
+ return '';
61
+ const when = date instanceof Date ? date : new Date(date);
62
+ if (Number.isNaN(when.getTime()))
63
+ return '';
64
+ let value = (when.getTime() - now.getTime()) / 1000;
65
+ let unit = 'second';
66
+ for (const [name, size] of steps) {
67
+ unit = name;
68
+ if (Math.abs(value) < size)
69
+ break;
70
+ value /= size;
71
+ }
72
+ const format = cached('relative', locale, { numeric, style }, () => new Intl.RelativeTimeFormat(locale, { numeric, style }));
73
+ return format.format(Math.round(value), unit);
74
+ }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * The words the components show or say to assistive tech, by component:
3
+ * English (`en`) and French (`fr`) built in. LocaleProvider picks the list
4
+ * for its locale ('fr-CA' → fr, anything else → en) and merges its `messages`
5
+ * over it, so one word can change without restating the rest:
6
+ *
7
+ * <LocaleProvider locale="fr-FR" messages={{ table: { empty: 'Aucun patient' } }}>
8
+ *
9
+ * Words given straight to a component (a `placeholder`, `label`,
10
+ * EventCalendar's `translations`) win over these.
11
+ *
12
+ * Entries that take values are functions. The `zag` group passes on to Zag's
13
+ * own `translations` (the screen-reader labels inside DatePicker, Select,
14
+ * NumberInput, PasswordInput, PinInput, TagsInput, Pagination, FileUpload,
15
+ * CopyButton, TreeView and Editable); English leaves them to Zag.
16
+ */
17
+ import type { DatePicker } from '@ark-ui/react/date-picker';
18
+ import type { NumberInput } from '@ark-ui/react/number-input';
19
+ import type { PasswordInput } from '@ark-ui/react/password-input';
20
+ import type { TagsInput } from '@ark-ui/react/tags-input';
21
+ import type { Pagination } from '@ark-ui/react/pagination';
22
+ import type { FileUpload } from '@ark-ui/react/file-upload';
23
+ import type { Combobox } from '@ark-ui/react/combobox';
24
+ import type { Select } from '@ark-ui/react/select';
25
+ import type { Clipboard } from '@ark-ui/react/clipboard';
26
+ import type { TreeView } from '@ark-ui/react/tree-view';
27
+ import type { Editable } from '@ark-ui/react/editable';
28
+ import type { PinInput } from '@ark-ui/react/pin-input';
29
+ export type CalendarMessages = {
30
+ today: string;
31
+ previous: string;
32
+ next: string;
33
+ allDay: string;
34
+ view: string;
35
+ pickDate: string;
36
+ day: string;
37
+ week: string;
38
+ workweek: string;
39
+ days: (count: number) => string;
40
+ more: (count: number) => string;
41
+ less: string;
42
+ unavailable: string;
43
+ cancel: string;
44
+ placing: (name: string) => string;
45
+ selected: (when: string) => string;
46
+ moved: (name: string, when: string) => string;
47
+ resized: (name: string, time: string) => string;
48
+ };
49
+ export type Messages = {
50
+ common: {
51
+ close: string;
52
+ dismiss: string;
53
+ cancel: string;
54
+ confirm: string;
55
+ save: string;
56
+ edit: string;
57
+ loading: string;
58
+ remove: (name: string) => string;
59
+ more: (count: number) => string;
60
+ };
61
+ confirm: {
62
+ typeToConfirm: (word: string) => string;
63
+ typePrefix: string;
64
+ typeSuffix: string;
65
+ };
66
+ copyButton: {
67
+ copy: string;
68
+ copied: string;
69
+ };
70
+ button: {
71
+ countLabel: string;
72
+ };
73
+ avatar: {
74
+ status: Record<'online' | 'away' | 'busy' | 'offline', string>;
75
+ };
76
+ shell: {
77
+ nav: string;
78
+ openMenu: string;
79
+ closeMenu: string;
80
+ expandSidebar: string;
81
+ collapseSidebar: string;
82
+ expandAll: string;
83
+ collapseAll: string;
84
+ };
85
+ breadcrumbs: {
86
+ label: string;
87
+ };
88
+ pagination: {
89
+ label: string;
90
+ noun: string;
91
+ showing: (from: string, to: string, count: string, noun: string) => string;
92
+ };
93
+ table: {
94
+ empty: string;
95
+ selectAll: string;
96
+ selectRow: string;
97
+ };
98
+ select: {
99
+ placeholder: string;
100
+ searchPlaceholder: string;
101
+ empty: string;
102
+ noMatches: string;
103
+ loading: string;
104
+ create: (text: string) => string;
105
+ };
106
+ tagsInput: {
107
+ placeholder: string;
108
+ };
109
+ datePicker: {
110
+ format: (pattern: string) => string;
111
+ min: (date: string) => string;
112
+ max: (date: string) => string;
113
+ unavailable: string;
114
+ };
115
+ swatchPicker: {
116
+ placeholder: string;
117
+ colors: Record<string, string>;
118
+ };
119
+ command: {
120
+ label: string;
121
+ placeholder: string;
122
+ empty: string;
123
+ loading: string;
124
+ esc: string;
125
+ toNavigate: string;
126
+ toSelect: string;
127
+ };
128
+ toaster: {
129
+ label: string;
130
+ };
131
+ actionBar: {
132
+ label: string;
133
+ };
134
+ chart: {
135
+ label: string;
136
+ total: string;
137
+ };
138
+ stat: {
139
+ up: string;
140
+ down: string;
141
+ same: string;
142
+ };
143
+ themeToggle: {
144
+ label: string;
145
+ light: string;
146
+ dark: string;
147
+ system: string;
148
+ };
149
+ sortableList: {
150
+ handle: string;
151
+ instructions: string;
152
+ position: (index: number, count: number) => string;
153
+ pickedUp: (name: string, place: string) => string;
154
+ movedTo: (name: string, place: string) => string;
155
+ dropped: (name: string, place: string | null) => string;
156
+ cancelled: (name: string, place: string) => string;
157
+ };
158
+ fileUpload: {
159
+ prompt: (multiple: boolean) => string;
160
+ browse: string;
161
+ reasons: Record<'FILE_INVALID_TYPE' | 'FILE_TOO_LARGE' | 'TOO_MANY_FILES' | 'FILE_TOO_SMALL', string>;
162
+ };
163
+ attachments: {
164
+ add: string;
165
+ layout: string;
166
+ grid: string;
167
+ list: string;
168
+ orDrop: string;
169
+ dropToAdd: string;
170
+ download: string;
171
+ open: (name: string) => string;
172
+ downloadFile: (name: string) => string;
173
+ count: (count: number, max: number) => string;
174
+ limit: (max: number) => string;
175
+ notType: string;
176
+ over: (size: string) => string;
177
+ notAdded: (name: string, reason: string) => string;
178
+ };
179
+ editor: {
180
+ toolbar: string;
181
+ link: string;
182
+ image: string;
183
+ remove: string;
184
+ apply: string;
185
+ uploading: string;
186
+ linkPlaceholder: string;
187
+ tools: Record<'bold' | 'italic' | 'underline' | 'strike' | 'code' | 'h2' | 'h3' | 'bulletList' | 'orderedList' | 'blockquote' | 'undo' | 'redo', string>;
188
+ };
189
+ calendar: CalendarMessages;
190
+ zag: {
191
+ datePicker?: DatePicker.RootProps['translations'];
192
+ numberInput?: NumberInput.RootProps['translations'];
193
+ passwordInput?: PasswordInput.RootProps['translations'];
194
+ tagsInput?: TagsInput.RootProps['translations'];
195
+ pagination?: Pagination.RootProps['translations'];
196
+ fileUpload?: FileUpload.RootProps['translations'];
197
+ combobox?: Combobox.RootProps<never>['translations'];
198
+ select?: Select.RootProps<never>['translations'];
199
+ clipboard?: Clipboard.RootProps['translations'];
200
+ treeView?: TreeView.RootProps<never>['translations'];
201
+ editable?: Editable.RootProps['translations'];
202
+ pinInput?: PinInput.RootProps['translations'];
203
+ };
204
+ };
205
+ export declare const en: Messages;
206
+ export declare const fr: Messages;
207
+ export declare const builtIn: Record<string, Messages>;
208
+ export type DeepPartial<T> = {
209
+ [K in keyof T]?: T[K] extends (...args: never[]) => unknown ? T[K] : T[K] extends object ? DeepPartial<T[K]> : T[K];
210
+ };
211
+ export declare function mergeMessages<T>(base: T, over: DeepPartial<T> | null | undefined): T;
@@ -0,0 +1,202 @@
1
+ const capital = (name) => name.charAt(0).toUpperCase() + name.slice(1);
2
+ const swatchNames = ['slate', 'red', 'orange', 'amber', 'yellow', 'lime', 'green', 'emerald', 'teal', 'cyan', 'sky', 'blue',
3
+ 'indigo', 'violet', 'purple', 'fuchsia', 'pink', 'rose'];
4
+ export const en = {
5
+ common: {
6
+ close: 'Close', dismiss: 'Dismiss', cancel: 'Cancel', confirm: 'Confirm', save: 'Save', edit: 'Edit', loading: 'Loading',
7
+ remove: (name) => `Remove ${name}`.trim(), more: (count) => `${count} more`,
8
+ },
9
+ confirm: { typeToConfirm: (word) => `Type ${word} to confirm`, typePrefix: 'Type', typeSuffix: 'to confirm' },
10
+ copyButton: { copy: 'Copy', copied: 'Copied' },
11
+ button: { countLabel: 'new' },
12
+ avatar: { status: { online: 'Online', away: 'Away', busy: 'Busy', offline: 'Offline' } },
13
+ shell: {
14
+ nav: 'Main', openMenu: 'Open menu', closeMenu: 'Close menu', expandSidebar: 'Expand sidebar', collapseSidebar: 'Collapse sidebar',
15
+ expandAll: 'Expand all', collapseAll: 'Collapse all',
16
+ },
17
+ breadcrumbs: { label: 'Breadcrumb' },
18
+ pagination: { label: 'Pagination', noun: 'items', showing: (from, to, count, noun) => `Showing ${from}–${to} of ${count}${noun ? ` ${noun}` : ''}` },
19
+ table: { empty: 'Nothing here yet', selectAll: 'Select all rows', selectRow: 'Select' },
20
+ select: {
21
+ placeholder: 'Select…', searchPlaceholder: 'Search…', empty: 'No options', noMatches: 'No matches', loading: 'Loading…',
22
+ create: (text) => `Create “${text}”`,
23
+ },
24
+ tagsInput: { placeholder: 'Add…' },
25
+ datePicker: {
26
+ format: (pattern) => `Enter a date as ${pattern}`, min: (date) => `Pick a date from ${date}`, max: (date) => `Pick a date up to ${date}`,
27
+ unavailable: 'That date isn’t available',
28
+ },
29
+ swatchPicker: { placeholder: 'Choose a colour', colors: Object.fromEntries(swatchNames.map((name) => [name, capital(name)])) },
30
+ command: {
31
+ label: 'Command menu', placeholder: 'Type a command or search…', empty: 'No results', loading: 'Searching…',
32
+ esc: 'Esc', toNavigate: 'to navigate', toSelect: 'to select',
33
+ },
34
+ toaster: { label: 'Notifications' },
35
+ actionBar: { label: 'Selection actions' },
36
+ chart: { label: 'Chart', total: 'Total' },
37
+ stat: { up: 'Up', down: 'Down', same: 'No change' },
38
+ themeToggle: { label: 'Colour scheme', light: 'Light', dark: 'Dark', system: 'System' },
39
+ sortableList: {
40
+ handle: 'Drag to reorder',
41
+ instructions: 'Press Space to pick this up. While dragging, use the arrow keys to move it, Space to drop it, or Escape to cancel.',
42
+ position: (index, count) => `position ${index} of ${count}`,
43
+ pickedUp: (name, place) => `Picked up ${name}, at ${place}.`,
44
+ movedTo: (name, place) => `${name} moved to ${place}.`,
45
+ dropped: (name, place) => place ? `${name} dropped at ${place}.` : `${name} dropped.`,
46
+ cancelled: (name, place) => `Reordering cancelled: ${name} is back at ${place}.`,
47
+ },
48
+ fileUpload: {
49
+ prompt: (multiple) => `Drop ${multiple ? 'files' : 'a file'} here or`, browse: 'browse',
50
+ reasons: { FILE_INVALID_TYPE: 'Not an allowed type', FILE_TOO_LARGE: 'Too large', TOO_MANY_FILES: 'Too many files', FILE_TOO_SMALL: 'Too small' },
51
+ },
52
+ attachments: {
53
+ add: 'Add files', layout: 'Layout', grid: 'Grid', list: 'List', orDrop: 'or drop files here', dropToAdd: 'Drop to add', download: 'Download',
54
+ open: (name) => `Open ${name}`, downloadFile: (name) => `Download ${name}`,
55
+ count: (count, max) => `${count} of ${max}`, limit: (max) => `The limit is ${max} files`,
56
+ notType: 'not an accepted type', over: (size) => `over ${size}`, notAdded: (name, reason) => `${name} wasn’t added: ${reason}.`,
57
+ },
58
+ editor: {
59
+ toolbar: 'Formatting', link: 'Link', image: 'Image', remove: 'Remove', apply: 'Apply', uploading: 'Uploading…', linkPlaceholder: 'https://',
60
+ tools: {
61
+ bold: 'Bold', italic: 'Italic', underline: 'Underline', strike: 'Strikethrough', code: 'Code', h2: 'Heading', h3: 'Subheading',
62
+ bulletList: 'Bulleted list', orderedList: 'Numbered list', blockquote: 'Quote', undo: 'Undo', redo: 'Redo',
63
+ },
64
+ },
65
+ calendar: {
66
+ today: 'Today', previous: 'Previous', next: 'Next', allDay: 'All day', view: 'View', pickDate: 'pick a date',
67
+ day: 'Day', week: 'Week', workweek: 'Work week', days: (count) => `${count} days`,
68
+ more: (count) => `+${count} more`, less: 'Show less', unavailable: 'Not available', cancel: 'Cancel',
69
+ placing: (name) => `Placing ${name}: click a time, or press Escape to cancel.`,
70
+ selected: (when) => `Selected ${when}.`, moved: (name, when) => `${name} moved to ${when}.`, resized: (name, time) => `${name} now ends at ${time}.`,
71
+ },
72
+ zag: {},
73
+ };
74
+ const frViews = { day: 'jours', month: 'mois', year: 'années' };
75
+ export const fr = {
76
+ common: {
77
+ close: 'Fermer', dismiss: 'Ignorer', cancel: 'Annuler', confirm: 'Confirmer', save: 'Enregistrer', edit: 'Modifier', loading: 'Chargement',
78
+ remove: (name) => `Retirer ${name}`.trim(), more: (count) => `${count} de plus`,
79
+ },
80
+ confirm: { typeToConfirm: (word) => `Saisissez ${word} pour confirmer`, typePrefix: 'Saisissez', typeSuffix: 'pour confirmer' },
81
+ copyButton: { copy: 'Copier', copied: 'Copié' },
82
+ button: { countLabel: 'nouveaux' },
83
+ avatar: { status: { online: 'En ligne', away: 'Absent', busy: 'Occupé', offline: 'Hors ligne' } },
84
+ shell: {
85
+ nav: 'Principal', openMenu: 'Ouvrir le menu', closeMenu: 'Fermer le menu', expandSidebar: 'Déplier la barre latérale',
86
+ collapseSidebar: 'Replier la barre latérale', expandAll: 'Tout déplier', collapseAll: 'Tout replier',
87
+ },
88
+ breadcrumbs: { label: 'Fil d’Ariane' },
89
+ pagination: { label: 'Pagination', noun: 'éléments', showing: (from, to, count, noun) => `${from}–${to} sur ${count}${noun ? ` ${noun}` : ''}` },
90
+ table: { empty: 'Rien pour l’instant', selectAll: 'Sélectionner toutes les lignes', selectRow: 'Sélectionner' },
91
+ select: {
92
+ placeholder: 'Choisir…', searchPlaceholder: 'Rechercher…', empty: 'Aucune option', noMatches: 'Aucun résultat', loading: 'Chargement…',
93
+ create: (text) => `Créer « ${text} »`,
94
+ },
95
+ tagsInput: { placeholder: 'Ajouter…' },
96
+ datePicker: {
97
+ format: (pattern) => `Saisissez une date au format ${pattern.replace(/d/g, 'j').replace(/y/g, 'a')}`,
98
+ min: (date) => `Choisissez une date à partir du ${date}`, max: (date) => `Choisissez une date jusqu’au ${date}`,
99
+ unavailable: 'Cette date n’est pas disponible',
100
+ },
101
+ swatchPicker: {
102
+ placeholder: 'Choisir une couleur',
103
+ colors: {
104
+ slate: 'Ardoise', red: 'Rouge', orange: 'Orange', amber: 'Ambre', yellow: 'Jaune', lime: 'Citron vert', green: 'Vert',
105
+ emerald: 'Émeraude', teal: 'Sarcelle', cyan: 'Cyan', sky: 'Ciel', blue: 'Bleu', indigo: 'Indigo', violet: 'Violet',
106
+ purple: 'Pourpre', fuchsia: 'Fuchsia', pink: 'Rose', rose: 'Rose vif',
107
+ },
108
+ },
109
+ command: {
110
+ label: 'Menu de commandes', placeholder: 'Tapez une commande ou recherchez…', empty: 'Aucun résultat', loading: 'Recherche…',
111
+ esc: 'Échap', toNavigate: 'pour naviguer', toSelect: 'pour choisir',
112
+ },
113
+ toaster: { label: 'Notifications' },
114
+ actionBar: { label: 'Actions sur la sélection' },
115
+ chart: { label: 'Graphique', total: 'Total' },
116
+ stat: { up: 'Hausse', down: 'Baisse', same: 'Stable' },
117
+ themeToggle: { label: 'Thème', light: 'Clair', dark: 'Sombre', system: 'Système' },
118
+ sortableList: {
119
+ handle: 'Glisser pour réordonner',
120
+ instructions: 'Appuyez sur Espace pour saisir cet élément. Déplacez-le ensuite avec les flèches, déposez-le avec Espace, ou appuyez sur Échap pour annuler.',
121
+ position: (index, count) => `position ${index} sur ${count}`,
122
+ pickedUp: (name, place) => `${name} saisi, en ${place}.`,
123
+ movedTo: (name, place) => `${name} déplacé en ${place}.`,
124
+ dropped: (name, place) => place ? `${name} déposé en ${place}.` : `${name} déposé.`,
125
+ cancelled: (name, place) => `Déplacement annulé : ${name} est revenu en ${place}.`,
126
+ },
127
+ fileUpload: {
128
+ prompt: (multiple) => `Déposez ${multiple ? 'des fichiers' : 'un fichier'} ici ou`, browse: 'parcourez',
129
+ reasons: { FILE_INVALID_TYPE: 'Type non autorisé', FILE_TOO_LARGE: 'Trop volumineux', TOO_MANY_FILES: 'Trop de fichiers', FILE_TOO_SMALL: 'Trop petit' },
130
+ },
131
+ attachments: {
132
+ add: 'Ajouter des fichiers', layout: 'Affichage', grid: 'Grille', list: 'Liste', orDrop: 'ou déposez des fichiers ici',
133
+ dropToAdd: 'Déposez pour ajouter', download: 'Télécharger',
134
+ open: (name) => `Ouvrir ${name}`, downloadFile: (name) => `Télécharger ${name}`,
135
+ count: (count, max) => `${count} sur ${max}`, limit: (max) => `La limite est de ${max} fichiers`,
136
+ notType: 'type non accepté', over: (size) => `plus de ${size}`, notAdded: (name, reason) => `${name} n’a pas été ajouté : ${reason}.`,
137
+ },
138
+ editor: {
139
+ toolbar: 'Mise en forme', link: 'Lien', image: 'Image', remove: 'Retirer', apply: 'Appliquer', uploading: 'Envoi…', linkPlaceholder: 'https://',
140
+ tools: {
141
+ bold: 'Gras', italic: 'Italique', underline: 'Souligné', strike: 'Barré', code: 'Code', h2: 'Titre', h3: 'Sous-titre',
142
+ bulletList: 'Liste à puces', orderedList: 'Liste numérotée', blockquote: 'Citation', undo: 'Annuler', redo: 'Rétablir',
143
+ },
144
+ },
145
+ calendar: {
146
+ today: 'Aujourd’hui', previous: 'Précédent', next: 'Suivant', allDay: 'Journée', view: 'Vue', pickDate: 'choisir une date',
147
+ day: 'Jour', week: 'Semaine', workweek: 'Semaine de travail', days: (count) => `${count} jours`,
148
+ more: (count) => `+${count} de plus`, less: 'Réduire', unavailable: 'Indisponible', cancel: 'Annuler',
149
+ placing: (name) => `Placement de ${name} : cliquez sur un créneau, ou Échap pour annuler.`,
150
+ selected: (when) => `${when} sélectionné.`, moved: (name, when) => `${name} déplacé au ${when}.`, resized: (name, time) => `${name} finit maintenant à ${time}.`,
151
+ },
152
+ zag: {
153
+ datePicker: {
154
+ dayCell: (state) => state.unavailable ? `Indisponible. ${state.valueText}` : state.selected ? `Date choisie. ${state.valueText}` : `Choisir ${state.valueText}`,
155
+ trigger: (open) => open ? 'Fermer le calendrier' : 'Ouvrir le calendrier',
156
+ viewTrigger: (view, next) => next ? `Passer à la vue ${frViews[next] ?? next}` : `Vue ${frViews[view] ?? view}`,
157
+ prevTrigger: (view) => view === 'year' ? 'Décennie précédente' : view === 'month' ? 'Année précédente' : 'Mois précédent',
158
+ nextTrigger: (view) => view === 'year' ? 'Décennie suivante' : view === 'month' ? 'Année suivante' : 'Mois suivant',
159
+ presetTrigger: ([start = '', end = '']) => `choisir du ${start} au ${end}`,
160
+ clearTrigger: 'Effacer les dates', content: 'calendrier', monthSelect: 'Choisir le mois', yearSelect: 'Choisir l’année',
161
+ },
162
+ numberInput: { incrementLabel: 'augmenter', decrementLabel: 'diminuer' },
163
+ passwordInput: { visibilityTrigger: (visible) => visible ? 'Masquer le mot de passe' : 'Afficher le mot de passe' },
164
+ tagsInput: {
165
+ clearTriggerLabel: 'Effacer toutes les étiquettes', deleteTagTriggerLabel: (value) => `Supprimer l’étiquette ${value}`,
166
+ tagAdded: (value) => `Étiquette ${value} ajoutée`, tagsPasted: (values) => `${values.length} étiquettes collées`,
167
+ tagEdited: (value) => `Modification de l’étiquette ${value}. Entrée pour enregistrer, Échap pour annuler.`,
168
+ tagUpdated: (value) => `Étiquette modifiée en ${value}`, tagDeleted: (value) => `Étiquette ${value} supprimée`,
169
+ tagSelected: (value) => `Étiquette ${value} sélectionnée. Entrée pour modifier, Suppr ou Retour arrière pour retirer.`,
170
+ noTagsSelected: 'Aucune étiquette sélectionnée', inputLabel: (count) => count === 1 ? '1 étiquette' : `${count} étiquettes`,
171
+ },
172
+ pagination: {
173
+ rootLabel: 'pagination', firstTriggerLabel: 'première page', prevTriggerLabel: 'page précédente',
174
+ nextTriggerLabel: 'page suivante', lastTriggerLabel: 'dernière page',
175
+ itemLabel: ({ page, totalPages }) => `${totalPages > 1 && page === totalPages ? 'dernière page, ' : ''}page ${page}`,
176
+ },
177
+ fileUpload: {
178
+ dropzone: 'zone de dépôt', itemPreview: (file) => `aperçu de ${file.name}`, deleteFile: (file) => `supprimer le fichier ${file.name}`,
179
+ },
180
+ combobox: { triggerLabel: 'Afficher les suggestions', clearTriggerLabel: 'Effacer' },
181
+ select: { clearTriggerLabel: 'Effacer' },
182
+ clipboard: { triggerLabel: (copied) => copied ? 'Copié dans le presse-papiers' : 'Copier dans le presse-papiers' },
183
+ treeView: { treeLabel: 'Arborescence', renameInputLabel: 'Renommer l’élément' },
184
+ editable: { input: 'champ modifiable', edit: 'modifier', submit: 'valider', cancel: 'annuler' },
185
+ pinInput: { inputLabel: (index, length) => `chiffre ${index + 1} sur ${length}` },
186
+ },
187
+ };
188
+ export const builtIn = { en, fr };
189
+ // Merges plain objects; functions and everything else replace.
190
+ export function mergeMessages(base, over) {
191
+ if (!over)
192
+ return base;
193
+ const out = { ...base };
194
+ for (const [key, value] of Object.entries(over)) {
195
+ if (value === undefined)
196
+ continue;
197
+ const current = out[key];
198
+ out[key] = value && typeof value === 'object' && !Array.isArray(value) && current && typeof current === 'object'
199
+ ? mergeMessages(current, value) : value;
200
+ }
201
+ return out;
202
+ }
@@ -0,0 +1,11 @@
1
+ import { type HighlightProps as ArkProps } from '@ark-ui/react/highlight';
2
+ import { type Classes } from '../utils.js';
3
+ import '../theme.css';
4
+ import './highlight.css';
5
+ export type HighlightColor = 'accent' | 'yellow' | 'amber' | 'orange' | 'red' | 'green' | 'teal' | 'blue' | 'purple' | 'pink' | 'gray';
6
+ export type HighlightProps = Omit<ArkProps, 'query' | 'color'> & {
7
+ query: string | string[];
8
+ color?: HighlightColor | (string & {});
9
+ classes?: Classes<'mark'>;
10
+ };
11
+ export declare function Highlight({ text, query, ignoreCase, matchAll, color, className, classes, style, ...rest }: HighlightProps): import("react").JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Highlight as Ark } from '@ark-ui/react/highlight';
3
+ import { partsOf } from '../utils.js';
4
+ import '../theme.css';
5
+ import './highlight.css';
6
+ const named = ['yellow', 'amber', 'orange', 'red', 'green', 'teal', 'blue', 'purple', 'pink', 'gray'];
7
+ export function Highlight({ text, query, ignoreCase = true, matchAll = true, color = 'accent', className, classes, style, ...rest }) {
8
+ const part = partsOf('highlight', classes);
9
+ // Ark marks the matches with <mark>; an empty query marks nothing.
10
+ const queries = [].concat(query).filter((q) => q.trim());
11
+ // A named colour is a data attribute; any other (not 'accent') sets the token on the marks.
12
+ const custom = color !== 'accent' && !named.includes(color);
13
+ return _jsx(Ark, { ...rest, text: text, query: queries, ignoreCase: ignoreCase, matchAll: matchAll, ...part('mark'), className: className ?? classes?.mark, "data-color": named.includes(color) ? color : undefined, style: custom ? { ...style, '--ui-highlight': color } : style });
14
+ }
@@ -0,0 +1,17 @@
1
+ @layer ui {
2
+ mark[data-scope=highlight][data-part=mark] { padding: 0 0.0625em; border-radius: 2px; background: var(--ui-highlight);
3
+ color: inherit; font-weight: 600;
4
+ /* Named colours: a see-through tint, so the text keeps its own colour. */
5
+ &[data-color] { background: color-mix(in srgb, var(--highlight-color) 32%, transparent); }
6
+ &[data-color=yellow] { --highlight-color: #facc15; }
7
+ &[data-color=amber] { --highlight-color: #f59e0b; }
8
+ &[data-color=orange] { --highlight-color: #f97316; }
9
+ &[data-color=red] { --highlight-color: #ef4444; }
10
+ &[data-color=green] { --highlight-color: #22c55e; }
11
+ &[data-color=teal] { --highlight-color: #14b8a6; }
12
+ &[data-color=blue] { --highlight-color: #3b82f6; }
13
+ &[data-color=purple] { --highlight-color: #a855f7; }
14
+ &[data-color=pink] { --highlight-color: #ec4899; }
15
+ &[data-color=gray] { --highlight-color: #71717a; }
16
+ :root.dark &[data-color], [data-theme=dark] &[data-color] { background: color-mix(in srgb, var(--highlight-color) 42%, transparent); } }
17
+ }