@kennycorrea/ui 0.11.3 → 1.6.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 (246) hide show
  1. package/CHANGELOG.md +189 -0
  2. package/README.md +251 -1
  3. package/dist/atoms/ActionMenu/ActionMenu.d.ts +37 -0
  4. package/dist/atoms/ActionMenu/ActionMenu.js +28 -0
  5. package/dist/atoms/ActionMenu/index.d.ts +1 -0
  6. package/dist/atoms/ActionMenu/index.js +1 -0
  7. package/dist/atoms/ActionSheet/ActionSheet.d.ts +10 -4
  8. package/dist/atoms/ActionSheet/ActionSheet.js +6 -15
  9. package/dist/atoms/ActionSheet/confirm.d.ts +1 -1
  10. package/dist/atoms/ActionSheet/confirm.js +1 -1
  11. package/dist/atoms/Avatar/Avatar.d.ts +1 -1
  12. package/dist/atoms/Badge/Badge.js +4 -4
  13. package/dist/atoms/Button/Button.js +2 -3
  14. package/dist/atoms/Button/Button.types.d.ts +5 -8
  15. package/dist/atoms/Card/Card.d.ts +5 -3
  16. package/dist/atoms/Card/Card.js +21 -4
  17. package/dist/atoms/ContextMenu/ContextMenu.d.ts +1 -1
  18. package/dist/atoms/ContextMenu/ContextMenu.js +1 -1
  19. package/dist/atoms/Drawer/Drawer.js +1 -1
  20. package/dist/atoms/Icon/Icon.d.ts +7 -9
  21. package/dist/atoms/Icon/Icon.js +23 -8
  22. package/dist/atoms/Pill/Pill.d.ts +17 -0
  23. package/dist/atoms/Pill/Pill.js +12 -0
  24. package/dist/atoms/Pill/index.d.ts +1 -0
  25. package/dist/atoms/Pill/index.js +1 -0
  26. package/dist/atoms/ResponsiveDialog/ResponsiveDialog.d.ts +9 -2
  27. package/dist/atoms/ResponsiveDialog/ResponsiveDialog.js +6 -15
  28. package/dist/atoms/Sheet/Sheet.js +1 -1
  29. package/dist/atoms/StatusIndicator/StatusIndicator.d.ts +14 -0
  30. package/dist/atoms/StatusIndicator/StatusIndicator.js +8 -0
  31. package/dist/atoms/StatusIndicator/index.d.ts +1 -0
  32. package/dist/atoms/StatusIndicator/index.js +1 -0
  33. package/dist/atoms/TextLink/TextLink.d.ts +36 -0
  34. package/dist/atoms/TextLink/TextLink.js +17 -0
  35. package/dist/atoms/TextLink/index.d.ts +1 -0
  36. package/dist/atoms/TextLink/index.js +1 -0
  37. package/dist/atoms/Toast/Toast.d.ts +1 -1
  38. package/dist/atoms/Toast/Toast.js +1 -1
  39. package/dist/atoms/index.d.ts +4 -0
  40. package/dist/atoms/index.js +4 -0
  41. package/dist/hooks/breakpoints.d.ts +20 -0
  42. package/dist/hooks/breakpoints.js +13 -0
  43. package/dist/hooks/index.d.ts +4 -0
  44. package/dist/hooks/index.js +4 -0
  45. package/dist/hooks/useControllableState.d.ts +17 -0
  46. package/dist/hooks/useControllableState.js +22 -0
  47. package/dist/hooks/useElementWidth.d.ts +7 -0
  48. package/dist/hooks/useElementWidth.js +22 -0
  49. package/dist/hooks/useMediaQuery.d.ts +18 -0
  50. package/dist/hooks/useMediaQuery.js +51 -0
  51. package/dist/index.d.ts +4 -0
  52. package/dist/index.js +4 -0
  53. package/dist/molecules/AccentPicker/AccentPicker.d.ts +30 -0
  54. package/dist/molecules/AccentPicker/AccentPicker.js +27 -0
  55. package/dist/molecules/AccentPicker/index.d.ts +1 -0
  56. package/dist/molecules/AccentPicker/index.js +1 -0
  57. package/dist/molecules/Breadcrumbs/Breadcrumbs.d.ts +27 -0
  58. package/dist/molecules/Breadcrumbs/Breadcrumbs.js +40 -0
  59. package/dist/molecules/Breadcrumbs/index.d.ts +1 -0
  60. package/dist/molecules/Breadcrumbs/index.js +1 -0
  61. package/dist/molecules/Calendar/Calendar.d.ts +42 -0
  62. package/dist/molecules/Calendar/Calendar.js +111 -0
  63. package/dist/molecules/Calendar/index.d.ts +1 -0
  64. package/dist/molecules/Calendar/index.js +1 -0
  65. package/dist/molecules/DateInput/DateInput.d.ts +35 -0
  66. package/dist/molecules/DateInput/DateInput.js +41 -0
  67. package/dist/molecules/DateInput/index.d.ts +1 -0
  68. package/dist/molecules/DateInput/index.js +1 -0
  69. package/dist/molecules/EmptyState/EmptyState.d.ts +19 -0
  70. package/dist/molecules/EmptyState/EmptyState.js +7 -0
  71. package/dist/molecules/EmptyState/index.d.ts +1 -0
  72. package/dist/molecules/EmptyState/index.js +1 -0
  73. package/dist/molecules/ErrorState/ErrorState.d.ts +18 -0
  74. package/dist/molecules/ErrorState/ErrorState.js +12 -0
  75. package/dist/molecules/ErrorState/index.d.ts +1 -0
  76. package/dist/molecules/ErrorState/index.js +1 -0
  77. package/dist/molecules/ErrorSummary/ErrorSummary.d.ts +25 -0
  78. package/dist/molecules/ErrorSummary/ErrorSummary.js +32 -0
  79. package/dist/molecules/ErrorSummary/index.d.ts +1 -0
  80. package/dist/molecules/ErrorSummary/index.js +1 -0
  81. package/dist/molecules/FilterChips/FilterChips.d.ts +10 -0
  82. package/dist/molecules/FilterChips/FilterChips.js +13 -0
  83. package/dist/molecules/FilterChips/index.d.ts +1 -0
  84. package/dist/molecules/FilterChips/index.js +1 -0
  85. package/dist/molecules/FilterPopover/FilterPopover.d.ts +27 -0
  86. package/dist/molecules/FilterPopover/FilterPopover.js +32 -0
  87. package/dist/molecules/FilterPopover/index.d.ts +1 -0
  88. package/dist/molecules/FilterPopover/index.js +1 -0
  89. package/dist/molecules/FormActions/FormActions.d.ts +16 -0
  90. package/dist/molecules/FormActions/FormActions.js +5 -0
  91. package/dist/molecules/FormActions/index.d.ts +1 -0
  92. package/dist/molecules/FormActions/index.js +1 -0
  93. package/dist/molecules/FormField/FormField.d.ts +52 -0
  94. package/dist/molecules/FormField/FormField.js +63 -0
  95. package/dist/molecules/FormField/index.d.ts +1 -0
  96. package/dist/molecules/FormField/index.js +1 -0
  97. package/dist/molecules/FormSection/FormSection.d.ts +14 -0
  98. package/dist/molecules/FormSection/FormSection.js +10 -0
  99. package/dist/molecules/FormSection/index.d.ts +1 -0
  100. package/dist/molecules/FormSection/index.js +1 -0
  101. package/dist/molecules/KeyValueList/KeyValueList.d.ts +22 -0
  102. package/dist/molecules/KeyValueList/KeyValueList.js +9 -0
  103. package/dist/molecules/KeyValueList/index.d.ts +1 -0
  104. package/dist/molecules/KeyValueList/index.js +1 -0
  105. package/dist/molecules/LoadingState/LoadingState.d.ts +20 -0
  106. package/dist/molecules/LoadingState/LoadingState.js +22 -0
  107. package/dist/molecules/LoadingState/index.d.ts +1 -0
  108. package/dist/molecules/LoadingState/index.js +1 -0
  109. package/dist/molecules/NavItem/NavItem.d.ts +31 -0
  110. package/dist/molecules/NavItem/NavItem.js +72 -0
  111. package/dist/molecules/NavItem/context.d.ts +14 -0
  112. package/dist/molecules/NavItem/context.js +5 -0
  113. package/dist/molecules/NavItem/index.d.ts +1 -0
  114. package/dist/molecules/NavItem/index.js +1 -0
  115. package/dist/molecules/Pagination/Pagination.d.ts +32 -0
  116. package/dist/molecules/Pagination/Pagination.js +57 -0
  117. package/dist/molecules/Pagination/index.d.ts +1 -0
  118. package/dist/molecules/Pagination/index.js +1 -0
  119. package/dist/molecules/PasswordInput/PasswordInput.d.ts +14 -0
  120. package/dist/molecules/PasswordInput/PasswordInput.js +16 -0
  121. package/dist/molecules/PasswordInput/index.d.ts +1 -0
  122. package/dist/molecules/PasswordInput/index.js +1 -0
  123. package/dist/molecules/SearchField/SearchField.d.ts +24 -0
  124. package/dist/molecules/SearchField/SearchField.js +58 -0
  125. package/dist/molecules/SearchField/index.d.ts +1 -0
  126. package/dist/molecules/SearchField/index.js +1 -0
  127. package/dist/molecules/StatGroup/StatGroup.d.ts +21 -0
  128. package/dist/molecules/StatGroup/StatGroup.js +10 -0
  129. package/dist/molecules/StatGroup/index.d.ts +1 -0
  130. package/dist/molecules/StatGroup/index.js +1 -0
  131. package/dist/molecules/ThemeToggle/ThemeToggle.d.ts +23 -0
  132. package/dist/molecules/ThemeToggle/ThemeToggle.js +26 -0
  133. package/dist/molecules/ThemeToggle/index.d.ts +1 -0
  134. package/dist/molecules/ThemeToggle/index.js +1 -0
  135. package/dist/molecules/UserMenu/UserMenu.d.ts +20 -0
  136. package/dist/molecules/UserMenu/UserMenu.js +14 -0
  137. package/dist/molecules/UserMenu/index.d.ts +1 -0
  138. package/dist/molecules/UserMenu/index.js +1 -0
  139. package/dist/molecules/index.d.ts +21 -0
  140. package/dist/molecules/index.js +21 -0
  141. package/dist/organisms/AppShell/AppShell.d.ts +29 -0
  142. package/dist/organisms/AppShell/AppShell.js +34 -0
  143. package/dist/organisms/AppShell/context.d.ts +14 -0
  144. package/dist/organisms/AppShell/context.js +6 -0
  145. package/dist/organisms/AppShell/index.d.ts +1 -0
  146. package/dist/organisms/AppShell/index.js +1 -0
  147. package/dist/organisms/AuthForms/AuthForms.d.ts +83 -0
  148. package/dist/organisms/AuthForms/AuthForms.js +57 -0
  149. package/dist/organisms/AuthForms/index.d.ts +2 -0
  150. package/dist/organisms/AuthForms/index.js +1 -0
  151. package/dist/organisms/AuthForms/shared.d.ts +36 -0
  152. package/dist/organisms/AuthForms/shared.js +36 -0
  153. package/dist/organisms/BulkActionBar/BulkActionBar.d.ts +25 -0
  154. package/dist/organisms/BulkActionBar/BulkActionBar.js +18 -0
  155. package/dist/organisms/BulkActionBar/index.d.ts +1 -0
  156. package/dist/organisms/BulkActionBar/index.js +1 -0
  157. package/dist/organisms/Chart/CategoryCharts.d.ts +34 -0
  158. package/dist/organisms/Chart/CategoryCharts.js +93 -0
  159. package/dist/organisms/Chart/ChartFrame.d.ts +26 -0
  160. package/dist/organisms/Chart/ChartFrame.js +18 -0
  161. package/dist/organisms/Chart/DonutChart.d.ts +25 -0
  162. package/dist/organisms/Chart/DonutChart.js +53 -0
  163. package/dist/organisms/Chart/Sparkline.d.ts +15 -0
  164. package/dist/organisms/Chart/Sparkline.js +18 -0
  165. package/dist/organisms/Chart/index.d.ts +5 -0
  166. package/dist/organisms/Chart/index.js +5 -0
  167. package/dist/organisms/Chart/shared.d.ts +51 -0
  168. package/dist/organisms/Chart/shared.js +113 -0
  169. package/dist/organisms/DataTable/DataTable.d.ts +63 -0
  170. package/dist/organisms/DataTable/DataTable.js +119 -0
  171. package/dist/organisms/DataTable/index.d.ts +3 -0
  172. package/dist/organisms/DataTable/index.js +3 -0
  173. package/dist/organisms/DataTable/types.d.ts +39 -0
  174. package/dist/organisms/DataTable/types.js +1 -0
  175. package/dist/organisms/DataTable/useClientTable.d.ts +27 -0
  176. package/dist/organisms/DataTable/useClientTable.js +51 -0
  177. package/dist/organisms/DateRangePicker/DateRangePicker.d.ts +43 -0
  178. package/dist/organisms/DateRangePicker/DateRangePicker.js +54 -0
  179. package/dist/organisms/DateRangePicker/index.d.ts +1 -0
  180. package/dist/organisms/DateRangePicker/index.js +1 -0
  181. package/dist/organisms/FilterBar/FilterBar.d.ts +28 -0
  182. package/dist/organisms/FilterBar/FilterBar.js +16 -0
  183. package/dist/organisms/FilterBar/index.d.ts +1 -0
  184. package/dist/organisms/FilterBar/index.js +1 -0
  185. package/dist/organisms/PageHeader/PageHeader.d.ts +31 -0
  186. package/dist/organisms/PageHeader/PageHeader.js +19 -0
  187. package/dist/organisms/PageHeader/index.d.ts +1 -0
  188. package/dist/organisms/PageHeader/index.js +1 -0
  189. package/dist/organisms/PageTabs/PageTabs.d.ts +27 -0
  190. package/dist/organisms/PageTabs/PageTabs.js +31 -0
  191. package/dist/organisms/PageTabs/index.d.ts +1 -0
  192. package/dist/organisms/PageTabs/index.js +1 -0
  193. package/dist/organisms/Sidebar/Sidebar.d.ts +48 -0
  194. package/dist/organisms/Sidebar/Sidebar.js +61 -0
  195. package/dist/organisms/Sidebar/index.d.ts +1 -0
  196. package/dist/organisms/Sidebar/index.js +1 -0
  197. package/dist/organisms/Topbar/Topbar.d.ts +24 -0
  198. package/dist/organisms/Topbar/Topbar.js +18 -0
  199. package/dist/organisms/Topbar/index.d.ts +1 -0
  200. package/dist/organisms/Topbar/index.js +1 -0
  201. package/dist/organisms/index.d.ts +11 -0
  202. package/dist/organisms/index.js +11 -0
  203. package/dist/strings.d.ts +108 -15
  204. package/dist/strings.js +73 -7
  205. package/dist/templates/AdminLayout/AdminLayout.d.ts +14 -0
  206. package/dist/templates/AdminLayout/AdminLayout.js +9 -0
  207. package/dist/templates/AdminLayout/index.d.ts +1 -0
  208. package/dist/templates/AdminLayout/index.js +1 -0
  209. package/dist/templates/AuthLayout/AuthLayout.d.ts +28 -0
  210. package/dist/templates/AuthLayout/AuthLayout.js +11 -0
  211. package/dist/templates/AuthLayout/index.d.ts +1 -0
  212. package/dist/templates/AuthLayout/index.js +1 -0
  213. package/dist/templates/DashboardPage/DashboardPage.d.ts +25 -0
  214. package/dist/templates/DashboardPage/DashboardPage.js +12 -0
  215. package/dist/templates/DashboardPage/index.d.ts +1 -0
  216. package/dist/templates/DashboardPage/index.js +1 -0
  217. package/dist/templates/DetailPage/DetailPage.d.ts +20 -0
  218. package/dist/templates/DetailPage/DetailPage.js +11 -0
  219. package/dist/templates/DetailPage/index.d.ts +1 -0
  220. package/dist/templates/DetailPage/index.js +1 -0
  221. package/dist/templates/FormPage/FormPage.d.ts +26 -0
  222. package/dist/templates/FormPage/FormPage.js +11 -0
  223. package/dist/templates/FormPage/index.d.ts +1 -0
  224. package/dist/templates/FormPage/index.js +1 -0
  225. package/dist/templates/ListPage/ListPage.d.ts +17 -0
  226. package/dist/templates/ListPage/ListPage.js +8 -0
  227. package/dist/templates/ListPage/index.d.ts +1 -0
  228. package/dist/templates/ListPage/index.js +1 -0
  229. package/dist/templates/SettingsPage/SettingsPage.d.ts +32 -0
  230. package/dist/templates/SettingsPage/SettingsPage.js +13 -0
  231. package/dist/templates/SettingsPage/index.d.ts +1 -0
  232. package/dist/templates/SettingsPage/index.js +1 -0
  233. package/dist/templates/StatusPage/StatusPage.d.ts +18 -0
  234. package/dist/templates/StatusPage/StatusPage.js +8 -0
  235. package/dist/templates/StatusPage/index.d.ts +1 -0
  236. package/dist/templates/StatusPage/index.js +1 -0
  237. package/dist/templates/index.d.ts +8 -0
  238. package/dist/templates/index.js +8 -0
  239. package/dist/utils/dates.d.ts +67 -0
  240. package/dist/utils/dates.js +154 -0
  241. package/dist/utils/index.d.ts +1 -0
  242. package/dist/utils/index.js +1 -0
  243. package/dist/utils/theme.d.ts +4 -0
  244. package/dist/utils/theme.js +7 -3
  245. package/package.json +4 -1
  246. package/tokens.css +513 -21
@@ -0,0 +1,22 @@
1
+ import { useCallback, useRef, useState } from 'react';
2
+ /**
3
+ * The `value` / `defaultValue` / `onValueChange` pattern in one place, so every component that
4
+ * offers both modes behaves the same way: a controlled component never changes on its own,
5
+ * and an uncontrolled one still reports each change.
6
+ *
7
+ * Whether it is controlled is decided by the first render. Switching between the two is a
8
+ * bug in the caller, the same rule React applies to inputs.
9
+ */
10
+ export function useControllableState({ value, defaultValue, onChange, }) {
11
+ const controlled = useRef(value !== undefined).current;
12
+ const [inner, setInner] = useState(defaultValue);
13
+ const current = controlled ? value : inner;
14
+ const onChangeRef = useRef(onChange);
15
+ onChangeRef.current = onChange;
16
+ const set = useCallback((next) => {
17
+ if (!controlled)
18
+ setInner(next);
19
+ onChangeRef.current?.(next);
20
+ }, [controlled]);
21
+ return [current, set];
22
+ }
@@ -0,0 +1,7 @@
1
+ import { type RefObject } from 'react';
2
+ /**
3
+ * The content width of an element, kept current with one ResizeObserver. `0` until measured,
4
+ * and where ResizeObserver does not exist (the server, some test environments), so callers
5
+ * should fall back to a sensible default.
6
+ */
7
+ export declare function useElementWidth(ref: RefObject<HTMLElement | null>): number;
@@ -0,0 +1,22 @@
1
+ import { useLayoutEffect, useState } from 'react';
2
+ /**
3
+ * The content width of an element, kept current with one ResizeObserver. `0` until measured,
4
+ * and where ResizeObserver does not exist (the server, some test environments), so callers
5
+ * should fall back to a sensible default.
6
+ */
7
+ export function useElementWidth(ref) {
8
+ const [width, setWidth] = useState(0);
9
+ useLayoutEffect(() => {
10
+ const el = ref.current;
11
+ if (!el || typeof ResizeObserver === 'undefined')
12
+ return;
13
+ setWidth(el.getBoundingClientRect().width);
14
+ const observer = new ResizeObserver(([entry]) => {
15
+ if (entry)
16
+ setWidth(entry.contentRect.width);
17
+ });
18
+ observer.observe(el);
19
+ return () => observer.disconnect();
20
+ }, [ref]);
21
+ return width;
22
+ }
@@ -0,0 +1,18 @@
1
+ import { type BreakpointOrInitial } from './breakpoints.js';
2
+ /**
3
+ * Whether a media query matches, kept current as it changes.
4
+ *
5
+ * For switching *structure* (a dialog that becomes a sheet). A visual change belongs in CSS,
6
+ * where it needs no JavaScript and does not flash on hydration.
7
+ *
8
+ * On the server, and wherever `matchMedia` is missing, it returns `serverValue`. The default is
9
+ * `true` because the existing atoms render their desktop branch first.
10
+ */
11
+ export declare function useMediaQuery(query: string, serverValue?: boolean): boolean;
12
+ /**
13
+ * The largest breakpoint the viewport has reached, or `initial` below `xs`.
14
+ *
15
+ * On the server it returns `serverValue`, default `lg`, for the same desktop-first reason as
16
+ * {@link useMediaQuery}.
17
+ */
18
+ export declare function useBreakpoint(serverValue?: BreakpointOrInitial): BreakpointOrInitial;
@@ -0,0 +1,51 @@
1
+ import { useMemo, useSyncExternalStore } from 'react';
2
+ import { BREAKPOINTS, minWidth } from './breakpoints.js';
3
+ function hasMatchMedia() {
4
+ return typeof window !== 'undefined' && typeof window.matchMedia === 'function';
5
+ }
6
+ function subscribeTo(queries) {
7
+ return (onChange) => {
8
+ if (!hasMatchMedia())
9
+ return () => { };
10
+ const lists = queries.map((q) => window.matchMedia(q));
11
+ for (const l of lists)
12
+ l.addEventListener('change', onChange);
13
+ return () => {
14
+ for (const l of lists)
15
+ l.removeEventListener('change', onChange);
16
+ };
17
+ };
18
+ }
19
+ /**
20
+ * Whether a media query matches, kept current as it changes.
21
+ *
22
+ * For switching *structure* (a dialog that becomes a sheet). A visual change belongs in CSS,
23
+ * where it needs no JavaScript and does not flash on hydration.
24
+ *
25
+ * On the server, and wherever `matchMedia` is missing, it returns `serverValue`. The default is
26
+ * `true` because the existing atoms render their desktop branch first.
27
+ */
28
+ export function useMediaQuery(query, serverValue = true) {
29
+ // A new subscribe function each render would make React resubscribe on every render.
30
+ const subscribe = useMemo(() => subscribeTo([query]), [query]);
31
+ return useSyncExternalStore(subscribe, () => (hasMatchMedia() ? window.matchMedia(query).matches : serverValue), () => serverValue);
32
+ }
33
+ const ORDER = Object.keys(BREAKPOINTS);
34
+ const subscribeToBreakpoints = subscribeTo(ORDER.map(minWidth));
35
+ /**
36
+ * The largest breakpoint the viewport has reached, or `initial` below `xs`.
37
+ *
38
+ * On the server it returns `serverValue`, default `lg`, for the same desktop-first reason as
39
+ * {@link useMediaQuery}.
40
+ */
41
+ export function useBreakpoint(serverValue = 'lg') {
42
+ return useSyncExternalStore(subscribeToBreakpoints, () => {
43
+ if (!hasMatchMedia())
44
+ return serverValue;
45
+ let current = 'initial';
46
+ for (const bp of ORDER)
47
+ if (window.matchMedia(minWidth(bp)).matches)
48
+ current = bp;
49
+ return current;
50
+ }, () => serverValue);
51
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  export * from './atoms/index.js';
2
+ export * from './hooks/index.js';
2
3
  export * from './icons/index.js';
4
+ export * from './molecules/index.js';
5
+ export * from './organisms/index.js';
3
6
  export * from './primitives/index.js';
4
7
  export * from './strings.js';
8
+ export * from './templates/index.js';
5
9
  export * from './utils/index.js';
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
1
  export * from './atoms/index.js';
2
+ export * from './hooks/index.js';
2
3
  export * from './icons/index.js';
4
+ export * from './molecules/index.js';
5
+ export * from './organisms/index.js';
3
6
  export * from './primitives/index.js';
4
7
  export * from './strings.js';
8
+ export * from './templates/index.js';
5
9
  export * from './utils/index.js';
@@ -0,0 +1,30 @@
1
+ /** The part of a `createTheming` instance the picker uses. */
2
+ export interface AccentStore<A extends string = string> {
3
+ readonly accents: readonly A[];
4
+ readAccent(): A;
5
+ storeAccent(accent: A): void;
6
+ }
7
+ export interface AccentPickerProps<A extends string = string> {
8
+ /** Radix colour names. Default: `theming.accents`. One of the two is required. */
9
+ accents?: readonly A[];
10
+ value?: A;
11
+ /** Initial value when uncontrolled. Default: `theming.readAccent()`, else the first accent. */
12
+ defaultValue?: A;
13
+ onValueChange?: (accent: A) => void;
14
+ /** A `createTheming` instance: supplies the list, reads the stored choice, stores new ones. */
15
+ theming?: AccentStore<A>;
16
+ /**
17
+ * Each swatch's accessible name. The fallback is the Radix colour name ("jade"), which is
18
+ * English and technical, so an app should pass its own words.
19
+ */
20
+ labels?: Partial<Record<A, string>>;
21
+ className?: string;
22
+ }
23
+ /**
24
+ * A row of colour swatches, one choice. Native radio inputs underneath, so arrow keys, the
25
+ * group's name and the checked state come from the browser.
26
+ *
27
+ * Like ThemeToggle it chooses and remembers; the app applies the accent to `<Theme
28
+ * accentColor>`. Each accent's Radix colour scale must be imported for its swatch to show.
29
+ */
30
+ export declare function AccentPicker<A extends string = string>({ accents, value, defaultValue, onValueChange, theming, labels, className, }: AccentPickerProps<A>): import("react").JSX.Element;
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useId } from 'react';
3
+ import { useControllableState } from '../../hooks/useControllableState.js';
4
+ import { useUiStrings } from '../../strings.js';
5
+ /**
6
+ * A row of colour swatches, one choice. Native radio inputs underneath, so arrow keys, the
7
+ * group's name and the checked state come from the browser.
8
+ *
9
+ * Like ThemeToggle it chooses and remembers; the app applies the accent to `<Theme
10
+ * accentColor>`. Each accent's Radix colour scale must be imported for its swatch to show.
11
+ */
12
+ export function AccentPicker({ accents, value, defaultValue, onValueChange, theming, labels, className, }) {
13
+ const strings = useUiStrings();
14
+ const name = useId();
15
+ const list = accents ?? theming?.accents ?? [];
16
+ const [current, setCurrent] = useControllableState({
17
+ value,
18
+ defaultValue: defaultValue ?? theming?.readAccent() ?? list[0],
19
+ onChange: (next) => {
20
+ if (next === undefined)
21
+ return;
22
+ theming?.storeAccent(next);
23
+ onValueChange?.(next);
24
+ },
25
+ });
26
+ return (_jsxs("fieldset", { className: ['ui-accent-picker', className].filter(Boolean).join(' '), children: [_jsx("legend", { className: "ui-sr-only", children: strings.accent }), list.map((accent) => (_jsxs("label", { className: "ui-accent-picker__option", children: [_jsx("input", { type: "radio", className: "ui-accent-picker__input", name: name, value: accent, checked: current === accent, onChange: () => setCurrent(accent), "aria-label": labels?.[accent] ?? accent }), _jsx("span", { className: "ui-accent-picker__swatch", "aria-hidden": "true", style: { '--swatch': `var(--${accent}-9)` } })] }, accent)))] }));
27
+ }
@@ -0,0 +1 @@
1
+ export * from './AccentPicker.js';
@@ -0,0 +1 @@
1
+ export * from './AccentPicker.js';
@@ -0,0 +1,27 @@
1
+ import type { RenderLink } from '../../atoms/TextLink/TextLink.js';
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ /** Left out on the last item, which is the current page. */
5
+ href?: string;
6
+ render?: RenderLink;
7
+ }
8
+ export interface BreadcrumbsProps {
9
+ /** From the root to the current page. The last one is the current page. */
10
+ items: BreadcrumbItem[];
11
+ /**
12
+ * The most items shown before the middle ones fold into a menu. Default 4, minimum 3
13
+ * (first, menu, parent, current).
14
+ */
15
+ maxItems?: number;
16
+ /** Overrides the landmark's name (otherwise the `breadcrumbs` UI string). */
17
+ 'aria-label'?: string;
18
+ className?: string;
19
+ }
20
+ /**
21
+ * Where the current page sits: a `nav` landmark holding an ordered list, the last item marked
22
+ * `aria-current="page"`.
23
+ *
24
+ * Long trails fold their middle into a menu. Below 768px only the parent shows, as a back link,
25
+ * which is what a phone has room for and what people use breadcrumbs for there.
26
+ */
27
+ export declare function Breadcrumbs({ items, maxItems, 'aria-label': ariaLabel, className, }: BreadcrumbsProps): import("react").JSX.Element | null;
@@ -0,0 +1,40 @@
1
+ import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { ChevronLeftIcon, DotsHorizontalIcon } from '@radix-ui/react-icons';
3
+ import { DropdownMenu as ThemedDropdownMenu } from '@radix-ui/themes';
4
+ import { useUiStrings } from '../../strings.js';
5
+ function crumbLink(item, className, extra) {
6
+ const children = (_jsxs(_Fragment, { children: [extra, item.label] }));
7
+ if (item.href === undefined)
8
+ return _jsx("span", { className: className, children: children });
9
+ const props = { href: item.href, className, children };
10
+ return item.render ? item.render(props) : _jsx("a", { ...props });
11
+ }
12
+ /**
13
+ * Where the current page sits: a `nav` landmark holding an ordered list, the last item marked
14
+ * `aria-current="page"`.
15
+ *
16
+ * Long trails fold their middle into a menu. Below 768px only the parent shows, as a back link,
17
+ * which is what a phone has room for and what people use breadcrumbs for there.
18
+ */
19
+ export function Breadcrumbs({ items, maxItems = 4, 'aria-label': ariaLabel, className, }) {
20
+ const strings = useUiStrings();
21
+ if (items.length === 0)
22
+ return null;
23
+ const limit = Math.max(3, maxItems);
24
+ const fold = items.length > limit;
25
+ // Kept: the first item, and the last `limit - 2` (the menu takes one slot).
26
+ const tailStart = fold ? items.length - (limit - 2) : 1;
27
+ const hidden = fold ? items.slice(1, tailStart) : [];
28
+ const shown = fold ? [items[0], ...items.slice(tailStart)] : items;
29
+ const parentIndex = shown.length - 2;
30
+ return (_jsx("nav", { "aria-label": ariaLabel ?? strings.breadcrumbs, className: className, children: _jsx("ol", { className: "ui-breadcrumbs__list", children: shown.map((item, i) => {
31
+ const last = i === shown.length - 1;
32
+ const sep = i > 0 ? (_jsx("span", { className: "ui-breadcrumbs__sep", "aria-hidden": "true", children: "/" })) : null;
33
+ return (_jsxs(BreadcrumbEntry, { isParent: i === parentIndex, children: [sep, last ? (_jsx("span", { "aria-current": "page", children: item.label })) : (crumbLink(item, undefined, i === parentIndex ? (_jsx("span", { className: "ui-breadcrumbs__back", "aria-hidden": "true", children: _jsx(ChevronLeftIcon, {}) })) : null)), fold && i === 0 ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "ui-breadcrumbs__sep", "aria-hidden": "true", children: "/" }), _jsxs(ThemedDropdownMenu.Root, { children: [_jsx(ThemedDropdownMenu.Trigger, { children: _jsx("button", { type: "button", className: "ui-breadcrumbs__more", "aria-label": strings.showMoreBreadcrumbs, children: _jsx(DotsHorizontalIcon, { "aria-hidden": "true" }) }) }), _jsx(ThemedDropdownMenu.Content, { align: "start", children: hidden.map((h, j) => (_jsx(ThemedDropdownMenu.Item
34
+ // biome-ignore lint/suspicious/noArrayIndexKey: positional, labels can repeat.
35
+ , { asChild: h.href !== undefined, children: crumbLink(h) }, `${j}-${h.label}`))) })] })] })) : null] }, `${i}-${item.label}`));
36
+ }) }) }));
37
+ }
38
+ function BreadcrumbEntry({ isParent, children }) {
39
+ return (_jsx("li", { className: "ui-breadcrumbs__item", "data-parent": isParent ? '' : undefined, children: children }));
40
+ }
@@ -0,0 +1 @@
1
+ export * from './Breadcrumbs.js';
@@ -0,0 +1 @@
1
+ export * from './Breadcrumbs.js';
@@ -0,0 +1,42 @@
1
+ import { type DateRange, type IsoDate, type Weekday } from '../../utils/dates.js';
2
+ interface CalendarCommon {
3
+ /** Earliest choosable date. */
4
+ min?: IsoDate;
5
+ /** Latest choosable date. */
6
+ max?: IsoDate;
7
+ /** Any other date that cannot be chosen (weekends, holidays). Still focusable, never chosen. */
8
+ isDateDisabled?: (date: IsoDate) => boolean;
9
+ /** Default: the `UiStringsProvider` locale, else the runtime's. */
10
+ locale?: string;
11
+ /** 0 = Sunday … 6 = Saturday. Default: the locale's own first day. */
12
+ weekStartsOn?: Weekday;
13
+ /** Months side by side. Default 1. */
14
+ numberOfMonths?: 1 | 2;
15
+ /** The first month shown, initially. Default: the selected date's month, else today's. */
16
+ defaultMonth?: IsoDate;
17
+ /** Moves focus into the grid on mount, as a picker opening should. */
18
+ autoFocus?: boolean;
19
+ className?: string;
20
+ }
21
+ export interface CalendarSingleProps extends CalendarCommon {
22
+ mode?: 'single';
23
+ value: IsoDate | null;
24
+ onValueChange: (value: IsoDate) => void;
25
+ }
26
+ export interface CalendarRangeProps extends CalendarCommon {
27
+ mode: 'range';
28
+ value: DateRange | null;
29
+ /** Called on both clicks: first with `to: null`, then with the whole range, in order. */
30
+ onValueChange: (value: DateRange) => void;
31
+ }
32
+ export type CalendarProps = CalendarSingleProps | CalendarRangeProps;
33
+ /**
34
+ * A month grid for choosing a date or a range, following the WAI-ARIA date picker pattern.
35
+ *
36
+ * One Tab stop for the whole grid. Arrows move by a day or a week, Home/End go to the start or
37
+ * end of the week, PageUp/PageDown by a month (with Shift, by a year), Enter or Space chooses.
38
+ * Every day button is named with the full date, today is `aria-current="date"`, and the month
39
+ * heading is announced when it changes. Dates are ISO strings throughout; see `utils/dates`.
40
+ */
41
+ export declare function Calendar(props: CalendarProps): import("react").JSX.Element;
42
+ export {};
@@ -0,0 +1,111 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // biome-ignore-all lint/a11y/noNoninteractiveElementToInteractiveRole: the WAI-ARIA date picker pattern is a table with role="grid" and gridcells.
3
+ // biome-ignore-all lint/a11y/useFocusableInteractive: each gridcell holds the focusable day button, as the pattern prescribes.
4
+ // biome-ignore-all lint/suspicious/noArrayIndexKey: blank cells and weeks are positional within a fixed six-by-seven grid.
5
+ import { ChevronLeftIcon, ChevronRightIcon } from '@radix-ui/react-icons';
6
+ import { useEffect, useId, useMemo, useRef, useState } from 'react';
7
+ import { useUiLocale, useUiStrings } from '../../strings.js';
8
+ import { addDays, addMonths, clampDate, endOfWeek, firstDayOfWeek, formatIsoDate, isInRange, monthGrid, parseIsoDate, startOfMonth, startOfWeek, todayIso, weekdayNames, } from '../../utils/dates.js';
9
+ /**
10
+ * A month grid for choosing a date or a range, following the WAI-ARIA date picker pattern.
11
+ *
12
+ * One Tab stop for the whole grid. Arrows move by a day or a week, Home/End go to the start or
13
+ * end of the week, PageUp/PageDown by a month (with Shift, by a year), Enter or Space chooses.
14
+ * Every day button is named with the full date, today is `aria-current="date"`, and the month
15
+ * heading is announced when it changes. Dates are ISO strings throughout; see `utils/dates`.
16
+ */
17
+ export function Calendar(props) {
18
+ const { min, max, isDateDisabled, locale: localeProp, weekStartsOn: weekStartProp, numberOfMonths = 1, defaultMonth, autoFocus = false, className, } = props;
19
+ const strings = useUiStrings();
20
+ const providerLocale = useUiLocale();
21
+ const locale = localeProp ?? providerLocale;
22
+ const weekStartsOn = weekStartProp ?? firstDayOfWeek(locale);
23
+ const id = useId();
24
+ const selectedStart = props.mode === 'range' ? (props.value?.from ?? null) : (props.value ?? null);
25
+ const today = todayIso();
26
+ const [focused, setFocused] = useState(() => clampDate(selectedStart ?? defaultMonth ?? today, min, max));
27
+ const [visible, setVisible] = useState(() => startOfMonth(defaultMonth ?? focused));
28
+ const moveFocus = useRef(autoFocus);
29
+ const root = useRef(null);
30
+ const months = useMemo(() => Array.from({ length: numberOfMonths }, (_, i) => addMonths(visible, i)), [visible, numberOfMonths]);
31
+ const lastVisible = months[months.length - 1];
32
+ const names = useMemo(() => weekdayNames(locale, weekStartsOn), [locale, weekStartsOn]);
33
+ useEffect(() => {
34
+ if (!moveFocus.current)
35
+ return;
36
+ moveFocus.current = false;
37
+ root.current?.querySelector(`[data-date="${focused}"]`)?.focus();
38
+ }, [focused]);
39
+ const disabled = (date) => (!!min && date < min) || (!!max && date > max) || !!isDateDisabled?.(date);
40
+ const focusDate = (date) => {
41
+ const next = clampDate(date, min, max);
42
+ if (next < visible)
43
+ setVisible(startOfMonth(next));
44
+ else if (next.slice(0, 7) > lastVisible.slice(0, 7))
45
+ setVisible(startOfMonth(addMonths(next, -(numberOfMonths - 1))));
46
+ moveFocus.current = true;
47
+ setFocused(next);
48
+ };
49
+ const choose = (date) => {
50
+ if (disabled(date))
51
+ return;
52
+ setFocused(date);
53
+ if (props.mode === 'range') {
54
+ const current = props.value;
55
+ if (!current || current.to !== null)
56
+ props.onValueChange({ from: date, to: null });
57
+ else if (date < current.from)
58
+ props.onValueChange({ from: date, to: current.from });
59
+ else
60
+ props.onValueChange({ from: current.from, to: date });
61
+ }
62
+ else {
63
+ props.onValueChange(date);
64
+ }
65
+ };
66
+ const onKeyDown = (e, date) => {
67
+ const step = {
68
+ ArrowLeft: () => addDays(date, -1),
69
+ ArrowRight: () => addDays(date, 1),
70
+ ArrowUp: () => addDays(date, -7),
71
+ ArrowDown: () => addDays(date, 7),
72
+ Home: () => startOfWeek(date, weekStartsOn),
73
+ End: () => endOfWeek(date, weekStartsOn),
74
+ PageUp: () => addMonths(date, e.shiftKey ? -12 : -1),
75
+ PageDown: () => addMonths(date, e.shiftKey ? 12 : 1),
76
+ };
77
+ const go = step[e.key];
78
+ if (go) {
79
+ e.preventDefault();
80
+ focusDate(go());
81
+ }
82
+ };
83
+ const stepMonth = (delta) => {
84
+ const nextVisible = addMonths(visible, delta);
85
+ setVisible(nextVisible);
86
+ // Keep the grid's Tab stop on a visible day, without taking focus from the button.
87
+ setFocused(clampDate(addMonths(focused, delta), min, max));
88
+ };
89
+ // The grid's one Tab stop: the focused day when it is on screen, else the first shown day.
90
+ const tabStop = focused >= visible && focused.slice(0, 7) <= lastVisible.slice(0, 7) ? focused : visible;
91
+ const range = props.mode === 'range' ? props.value : null;
92
+ const singleValue = props.mode === 'range' ? null : props.value;
93
+ const prevDisabled = !!min && addMonths(visible, -1).slice(0, 7) < min.slice(0, 7);
94
+ const nextDisabled = !!max && addMonths(lastVisible, 1).slice(0, 7) > max.slice(0, 7);
95
+ return (_jsxs("div", { ref: root, className: ['ui-calendar', className].filter(Boolean).join(' '), children: [_jsxs("div", { className: "ui-calendar__nav", children: [_jsx("button", { type: "button", className: "ui-calendar__step", "aria-label": strings.previousMonth, disabled: prevDisabled, onClick: () => stepMonth(-1), children: _jsx(ChevronLeftIcon, { "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: "ui-calendar__step", "aria-label": strings.nextMonth, disabled: nextDisabled, onClick: () => stepMonth(1), children: _jsx(ChevronRightIcon, { "aria-hidden": "true" }) })] }), _jsx("div", { className: "ui-calendar__months", children: months.map((month, mi) => {
96
+ const p = parseIsoDate(month);
97
+ if (!p)
98
+ return null;
99
+ const titleId = `${id}-m${mi}`;
100
+ const weeks = monthGrid(p.year, p.month, weekStartsOn);
101
+ return (_jsxs("div", { className: "ui-calendar__month", children: [_jsx("div", { id: titleId, className: "ui-calendar__title", "aria-live": "polite", children: formatIsoDate(month, locale, { month: 'long', year: 'numeric' }) }), _jsxs("table", { role: "grid", "aria-labelledby": titleId, className: "ui-calendar__grid", children: [_jsx("thead", { children: _jsx("tr", { children: names.map((n) => (_jsx("th", { scope: "col", abbr: n.long, children: n.short }, n.long))) }) }), _jsx("tbody", { children: weeks.map((week) => (_jsx("tr", { children: week.map((date, di) => {
102
+ if (!date)
103
+ return _jsx("td", {}, `e${di}`);
104
+ const isDisabled = disabled(date);
105
+ const selected = date === singleValue ||
106
+ (range ? date === range.from || date === range.to : false);
107
+ const within = range ? isInRange(date, range) : false;
108
+ return (_jsx("td", { role: "gridcell", "aria-selected": selected || within, children: _jsx("button", { type: "button", className: "ui-calendar__day", "data-date": date, "data-selected": selected ? '' : undefined, "data-in-range": within && !selected ? '' : undefined, "data-range-start": range && date === range.from ? '' : undefined, "data-range-end": range && date === (range.to ?? '') ? '' : undefined, tabIndex: date === tabStop ? 0 : -1, "aria-label": formatIsoDate(date, locale, { dateStyle: 'full' }), "aria-current": date === today ? 'date' : undefined, "aria-disabled": isDisabled || undefined, onClick: () => choose(date), onKeyDown: (e) => onKeyDown(e, date), onFocus: () => setFocused(date), children: Number(date.slice(8)) }) }, date));
109
+ }) }, week.find(Boolean) ?? `${month}-${weeks.indexOf(week)}`))) })] })] }, month));
110
+ }) })] }));
111
+ }
@@ -0,0 +1 @@
1
+ export * from './Calendar.js';
@@ -0,0 +1 @@
1
+ export * from './Calendar.js';
@@ -0,0 +1,35 @@
1
+ import { type Breakpoint } from '../../hooks/breakpoints.js';
2
+ import { type IsoDate, type Weekday } from '../../utils/dates.js';
3
+ export interface DateInputProps {
4
+ value: IsoDate | null;
5
+ onValueChange: (value: IsoDate | null) => void;
6
+ min?: IsoDate;
7
+ max?: IsoDate;
8
+ isDateDisabled?: (date: IsoDate) => boolean;
9
+ locale?: string;
10
+ weekStartsOn?: Weekday;
11
+ /** The browser's own `<input type="date">` instead of the calendar. Typing works there. */
12
+ native?: boolean;
13
+ /** Below this the calendar opens in a bottom Sheet. Default `sm`. `false` keeps the popover. */
14
+ sheetBelow?: Breakpoint | false;
15
+ /** The trigger text while empty. Default: the `chooseDate` UI string. */
16
+ placeholder?: string;
17
+ /** How the chosen date reads on the trigger. Default `{ dateStyle: 'medium' }`. */
18
+ format?: Intl.DateTimeFormatOptions;
19
+ disabled?: boolean;
20
+ invalid?: boolean;
21
+ required?: boolean;
22
+ id?: string;
23
+ 'aria-describedby'?: string;
24
+ 'aria-invalid'?: boolean | 'true' | 'false';
25
+ 'aria-label'?: string;
26
+ className?: string;
27
+ }
28
+ /**
29
+ * A date field: a button showing the date that opens a Calendar, in a popover on wide screens
30
+ * and a bottom sheet on narrow ones. Works inside `FormField`, which names it.
31
+ *
32
+ * The chosen date is in the button's description, not its name, so the label stays the name
33
+ * ("Due date") and the value is still read ("Wednesday 21 October 2026").
34
+ */
35
+ export declare function DateInput({ value, onValueChange, min, max, isDateDisabled, locale: localeProp, weekStartsOn, native, sheetBelow, placeholder, format, disabled, invalid, required, id, 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, className, }: DateInputProps): import("react").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { CalendarIcon } from '@radix-ui/react-icons';
3
+ import { Button as ThemedButton, Popover as ThemedPopover } from '@radix-ui/themes';
4
+ import { useId, useState } from 'react';
5
+ import { Input } from '../../atoms/Input/Input.js';
6
+ import { Sheet } from '../../atoms/Sheet/Sheet.js';
7
+ import { minWidth } from '../../hooks/breakpoints.js';
8
+ import { useMediaQuery } from '../../hooks/useMediaQuery.js';
9
+ import { useUiLocale, useUiStrings } from '../../strings.js';
10
+ import { formatIsoDate, isIsoDate } from '../../utils/dates.js';
11
+ import { Calendar } from '../Calendar/Calendar.js';
12
+ /**
13
+ * A date field: a button showing the date that opens a Calendar, in a popover on wide screens
14
+ * and a bottom sheet on narrow ones. Works inside `FormField`, which names it.
15
+ *
16
+ * The chosen date is in the button's description, not its name, so the label stays the name
17
+ * ("Due date") and the value is still read ("Wednesday 21 October 2026").
18
+ */
19
+ export function DateInput({ value, onValueChange, min, max, isDateDisabled, locale: localeProp, weekStartsOn, native = false, sheetBelow = 'sm', placeholder, format = { dateStyle: 'medium' }, disabled, invalid, required, id, 'aria-describedby': describedBy, 'aria-invalid': ariaInvalid, 'aria-label': ariaLabel, className, }) {
20
+ const strings = useUiStrings();
21
+ const providerLocale = useUiLocale();
22
+ const locale = localeProp ?? providerLocale;
23
+ const wide = useMediaQuery(minWidth(sheetBelow || 'xs'));
24
+ const [open, setOpen] = useState(false);
25
+ const valueId = useId();
26
+ const isInvalid = invalid || ariaInvalid === true || ariaInvalid === 'true';
27
+ if (native) {
28
+ return (_jsx(Input, { type: "date", id: id, value: value ?? '', min: min, max: max, disabled: disabled, required: required, invalid: isInvalid, "aria-describedby": describedBy, "aria-label": ariaLabel, className: className, onChange: (e) => onValueChange(isIsoDate(e.target.value) ? e.target.value : null) }));
29
+ }
30
+ const text = value ? formatIsoDate(value, locale, format) : (placeholder ?? strings.chooseDate);
31
+ const fullValue = value ? formatIsoDate(value, locale, { dateStyle: 'full' }) : '';
32
+ const trigger = (_jsxs(ThemedButton, { type: "button", variant: "surface", color: isInvalid ? 'red' : 'gray', id: id, disabled: disabled, "aria-haspopup": "dialog", "aria-label": ariaLabel, "aria-invalid": isInvalid || undefined, "aria-required": required || undefined, "aria-describedby": [value ? valueId : null, describedBy].filter(Boolean).join(' ') || undefined, className: ['ui-date-input', className].filter(Boolean).join(' '), "data-empty": value ? undefined : '', onClick: sheetBelow !== false && !wide ? () => setOpen(true) : undefined, children: [_jsx(CalendarIcon, { "aria-hidden": "true" }), _jsx("span", { "aria-hidden": value ? 'true' : undefined, children: text }), value ? (_jsx("span", { id: valueId, className: "ui-sr-only", children: fullValue })) : null] }));
33
+ const calendar = (_jsx(Calendar, { value: value, onValueChange: (next) => {
34
+ onValueChange(next);
35
+ setOpen(false);
36
+ }, min: min, max: max, isDateDisabled: isDateDisabled, locale: locale, weekStartsOn: weekStartsOn, autoFocus: true }));
37
+ if (sheetBelow !== false && !wide) {
38
+ return (_jsxs(_Fragment, { children: [trigger, _jsx(Sheet, { open: open, onOpenChange: setOpen, title: ariaLabel ?? strings.chooseDate, showCloseButton: true, children: calendar })] }));
39
+ }
40
+ return (_jsxs(ThemedPopover.Root, { open: open, onOpenChange: setOpen, children: [_jsx(ThemedPopover.Trigger, { children: trigger }), _jsx(ThemedPopover.Content, { align: "start", "aria-label": ariaLabel ?? strings.chooseDate, children: calendar })] }));
41
+ }
@@ -0,0 +1 @@
1
+ export * from './DateInput.js';
@@ -0,0 +1 @@
1
+ export * from './DateInput.js';
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface EmptyStateProps {
3
+ /** A single icon, shown in a soft circle. Decorative. */
4
+ icon?: ReactNode;
5
+ title: ReactNode;
6
+ description?: ReactNode;
7
+ /** The way forward: "Create the first one", "Clear filters". */
8
+ action?: ReactNode;
9
+ secondaryAction?: ReactNode;
10
+ /** `sm` for inside a table or card. Default `md`. */
11
+ size?: 'sm' | 'md';
12
+ /** The title's heading level. Default `h2`; use `p` where a heading would break the outline. */
13
+ as?: 'h2' | 'h3' | 'p';
14
+ className?: string;
15
+ }
16
+ /**
17
+ * What a place shows when it has nothing in it yet. Says why, and offers the next step.
18
+ */
19
+ export declare function EmptyState({ icon, title, description, action, secondaryAction, size, as: Title, className, }: EmptyStateProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * What a place shows when it has nothing in it yet. Says why, and offers the next step.
4
+ */
5
+ export function EmptyState({ icon, title, description, action, secondaryAction, size = 'md', as: Title = 'h2', className, }) {
6
+ return (_jsxs("div", { className: ['ui-state', className].filter(Boolean).join(' '), "data-size": size, children: [icon ? (_jsx("span", { className: "ui-state__icon", "aria-hidden": "true", children: icon })) : null, _jsx(Title, { className: "ui-state__title", children: title }), description ? _jsx("p", { className: "ui-state__description", children: description }) : null, action || secondaryAction ? (_jsxs("div", { className: "ui-state__actions", children: [action, secondaryAction] })) : null] }));
7
+ }
@@ -0,0 +1 @@
1
+ export * from './EmptyState.js';
@@ -0,0 +1 @@
1
+ export * from './EmptyState.js';
@@ -0,0 +1,18 @@
1
+ import type { ReactNode } from 'react';
2
+ export interface ErrorStateProps {
3
+ /** Default: the `somethingWentWrong` UI string. */
4
+ title?: ReactNode;
5
+ description?: ReactNode;
6
+ /** Shows a retry button (the `retry` UI string). */
7
+ onRetry?: () => void;
8
+ /** Technical detail (an error id, a message) behind a disclosure. */
9
+ details?: ReactNode;
10
+ size?: 'sm' | 'md';
11
+ as?: 'h2' | 'h3' | 'p';
12
+ className?: string;
13
+ }
14
+ /**
15
+ * What a place shows when loading it failed. `role="alert"`, so a screen reader hears it when
16
+ * it replaces content that was loading.
17
+ */
18
+ export declare function ErrorState({ title, description, onRetry, details, size, as: Title, className, }: ErrorStateProps): import("react").JSX.Element;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ExclamationTriangleIcon } from '@radix-ui/react-icons';
3
+ import { Button } from '../../atoms/Button/Button.js';
4
+ import { useUiStrings } from '../../strings.js';
5
+ /**
6
+ * What a place shows when loading it failed. `role="alert"`, so a screen reader hears it when
7
+ * it replaces content that was loading.
8
+ */
9
+ export function ErrorState({ title, description, onRetry, details, size = 'md', as: Title = 'h2', className, }) {
10
+ const strings = useUiStrings();
11
+ return (_jsxs("div", { role: "alert", className: ['ui-state', className].filter(Boolean).join(' '), "data-size": size, "data-tone": "danger", children: [_jsx("span", { className: "ui-state__icon", "aria-hidden": "true", children: _jsx(ExclamationTriangleIcon, {}) }), _jsx(Title, { className: "ui-state__title", children: title ?? strings.somethingWentWrong }), description ? _jsx("p", { className: "ui-state__description", children: description }) : null, onRetry ? (_jsx("div", { className: "ui-state__actions", children: _jsx(Button, { variant: "secondary", onClick: onRetry, children: strings.retry }) })) : null, details ? (_jsxs("details", { className: "ui-state__details", children: [_jsx("summary", { children: strings.showDetails }), _jsx("pre", { children: details })] })) : null] }));
12
+ }
@@ -0,0 +1 @@
1
+ export * from './ErrorState.js';
@@ -0,0 +1 @@
1
+ export * from './ErrorState.js';