@kennycorrea/ui 0.11.4 → 1.7.0

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 (242) hide show
  1. package/CHANGELOG.md +191 -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/ContextMenu/ContextMenu.d.ts +1 -1
  16. package/dist/atoms/ContextMenu/ContextMenu.js +1 -1
  17. package/dist/atoms/Icon/Icon.d.ts +7 -9
  18. package/dist/atoms/Icon/Icon.js +23 -8
  19. package/dist/atoms/Pill/Pill.d.ts +17 -0
  20. package/dist/atoms/Pill/Pill.js +12 -0
  21. package/dist/atoms/Pill/index.d.ts +1 -0
  22. package/dist/atoms/Pill/index.js +1 -0
  23. package/dist/atoms/ResponsiveDialog/ResponsiveDialog.d.ts +9 -2
  24. package/dist/atoms/ResponsiveDialog/ResponsiveDialog.js +6 -15
  25. package/dist/atoms/StatusIndicator/StatusIndicator.d.ts +14 -0
  26. package/dist/atoms/StatusIndicator/StatusIndicator.js +8 -0
  27. package/dist/atoms/StatusIndicator/index.d.ts +1 -0
  28. package/dist/atoms/StatusIndicator/index.js +1 -0
  29. package/dist/atoms/TextLink/TextLink.d.ts +36 -0
  30. package/dist/atoms/TextLink/TextLink.js +17 -0
  31. package/dist/atoms/TextLink/index.d.ts +1 -0
  32. package/dist/atoms/TextLink/index.js +1 -0
  33. package/dist/atoms/Toast/Toast.d.ts +1 -1
  34. package/dist/atoms/Toast/Toast.js +1 -1
  35. package/dist/atoms/index.d.ts +4 -0
  36. package/dist/atoms/index.js +4 -0
  37. package/dist/hooks/breakpoints.d.ts +20 -0
  38. package/dist/hooks/breakpoints.js +13 -0
  39. package/dist/hooks/index.d.ts +4 -0
  40. package/dist/hooks/index.js +4 -0
  41. package/dist/hooks/useControllableState.d.ts +17 -0
  42. package/dist/hooks/useControllableState.js +22 -0
  43. package/dist/hooks/useElementWidth.d.ts +7 -0
  44. package/dist/hooks/useElementWidth.js +22 -0
  45. package/dist/hooks/useMediaQuery.d.ts +18 -0
  46. package/dist/hooks/useMediaQuery.js +51 -0
  47. package/dist/index.d.ts +4 -0
  48. package/dist/index.js +4 -0
  49. package/dist/molecules/AccentPicker/AccentPicker.d.ts +30 -0
  50. package/dist/molecules/AccentPicker/AccentPicker.js +27 -0
  51. package/dist/molecules/AccentPicker/index.d.ts +1 -0
  52. package/dist/molecules/AccentPicker/index.js +1 -0
  53. package/dist/molecules/Breadcrumbs/Breadcrumbs.d.ts +27 -0
  54. package/dist/molecules/Breadcrumbs/Breadcrumbs.js +40 -0
  55. package/dist/molecules/Breadcrumbs/index.d.ts +1 -0
  56. package/dist/molecules/Breadcrumbs/index.js +1 -0
  57. package/dist/molecules/Calendar/Calendar.d.ts +42 -0
  58. package/dist/molecules/Calendar/Calendar.js +111 -0
  59. package/dist/molecules/Calendar/index.d.ts +1 -0
  60. package/dist/molecules/Calendar/index.js +1 -0
  61. package/dist/molecules/DateInput/DateInput.d.ts +35 -0
  62. package/dist/molecules/DateInput/DateInput.js +41 -0
  63. package/dist/molecules/DateInput/index.d.ts +1 -0
  64. package/dist/molecules/DateInput/index.js +1 -0
  65. package/dist/molecules/EmptyState/EmptyState.d.ts +19 -0
  66. package/dist/molecules/EmptyState/EmptyState.js +7 -0
  67. package/dist/molecules/EmptyState/index.d.ts +1 -0
  68. package/dist/molecules/EmptyState/index.js +1 -0
  69. package/dist/molecules/ErrorState/ErrorState.d.ts +18 -0
  70. package/dist/molecules/ErrorState/ErrorState.js +12 -0
  71. package/dist/molecules/ErrorState/index.d.ts +1 -0
  72. package/dist/molecules/ErrorState/index.js +1 -0
  73. package/dist/molecules/ErrorSummary/ErrorSummary.d.ts +25 -0
  74. package/dist/molecules/ErrorSummary/ErrorSummary.js +32 -0
  75. package/dist/molecules/ErrorSummary/index.d.ts +1 -0
  76. package/dist/molecules/ErrorSummary/index.js +1 -0
  77. package/dist/molecules/FilterChips/FilterChips.d.ts +10 -0
  78. package/dist/molecules/FilterChips/FilterChips.js +13 -0
  79. package/dist/molecules/FilterChips/index.d.ts +1 -0
  80. package/dist/molecules/FilterChips/index.js +1 -0
  81. package/dist/molecules/FilterPopover/FilterPopover.d.ts +27 -0
  82. package/dist/molecules/FilterPopover/FilterPopover.js +32 -0
  83. package/dist/molecules/FilterPopover/index.d.ts +1 -0
  84. package/dist/molecules/FilterPopover/index.js +1 -0
  85. package/dist/molecules/FormActions/FormActions.d.ts +16 -0
  86. package/dist/molecules/FormActions/FormActions.js +5 -0
  87. package/dist/molecules/FormActions/index.d.ts +1 -0
  88. package/dist/molecules/FormActions/index.js +1 -0
  89. package/dist/molecules/FormField/FormField.d.ts +52 -0
  90. package/dist/molecules/FormField/FormField.js +63 -0
  91. package/dist/molecules/FormField/index.d.ts +1 -0
  92. package/dist/molecules/FormField/index.js +1 -0
  93. package/dist/molecules/FormSection/FormSection.d.ts +14 -0
  94. package/dist/molecules/FormSection/FormSection.js +10 -0
  95. package/dist/molecules/FormSection/index.d.ts +1 -0
  96. package/dist/molecules/FormSection/index.js +1 -0
  97. package/dist/molecules/KeyValueList/KeyValueList.d.ts +22 -0
  98. package/dist/molecules/KeyValueList/KeyValueList.js +9 -0
  99. package/dist/molecules/KeyValueList/index.d.ts +1 -0
  100. package/dist/molecules/KeyValueList/index.js +1 -0
  101. package/dist/molecules/LoadingState/LoadingState.d.ts +20 -0
  102. package/dist/molecules/LoadingState/LoadingState.js +22 -0
  103. package/dist/molecules/LoadingState/index.d.ts +1 -0
  104. package/dist/molecules/LoadingState/index.js +1 -0
  105. package/dist/molecules/NavItem/NavItem.d.ts +34 -0
  106. package/dist/molecules/NavItem/NavItem.js +72 -0
  107. package/dist/molecules/NavItem/context.d.ts +19 -0
  108. package/dist/molecules/NavItem/context.js +5 -0
  109. package/dist/molecules/NavItem/index.d.ts +1 -0
  110. package/dist/molecules/NavItem/index.js +1 -0
  111. package/dist/molecules/Pagination/Pagination.d.ts +32 -0
  112. package/dist/molecules/Pagination/Pagination.js +57 -0
  113. package/dist/molecules/Pagination/index.d.ts +1 -0
  114. package/dist/molecules/Pagination/index.js +1 -0
  115. package/dist/molecules/PasswordInput/PasswordInput.d.ts +14 -0
  116. package/dist/molecules/PasswordInput/PasswordInput.js +16 -0
  117. package/dist/molecules/PasswordInput/index.d.ts +1 -0
  118. package/dist/molecules/PasswordInput/index.js +1 -0
  119. package/dist/molecules/SearchField/SearchField.d.ts +24 -0
  120. package/dist/molecules/SearchField/SearchField.js +58 -0
  121. package/dist/molecules/SearchField/index.d.ts +1 -0
  122. package/dist/molecules/SearchField/index.js +1 -0
  123. package/dist/molecules/StatGroup/StatGroup.d.ts +21 -0
  124. package/dist/molecules/StatGroup/StatGroup.js +10 -0
  125. package/dist/molecules/StatGroup/index.d.ts +1 -0
  126. package/dist/molecules/StatGroup/index.js +1 -0
  127. package/dist/molecules/ThemeToggle/ThemeToggle.d.ts +23 -0
  128. package/dist/molecules/ThemeToggle/ThemeToggle.js +26 -0
  129. package/dist/molecules/ThemeToggle/index.d.ts +1 -0
  130. package/dist/molecules/ThemeToggle/index.js +1 -0
  131. package/dist/molecules/UserMenu/UserMenu.d.ts +20 -0
  132. package/dist/molecules/UserMenu/UserMenu.js +14 -0
  133. package/dist/molecules/UserMenu/index.d.ts +1 -0
  134. package/dist/molecules/UserMenu/index.js +1 -0
  135. package/dist/molecules/index.d.ts +21 -0
  136. package/dist/molecules/index.js +21 -0
  137. package/dist/organisms/AppShell/AppShell.d.ts +29 -0
  138. package/dist/organisms/AppShell/AppShell.js +34 -0
  139. package/dist/organisms/AppShell/context.d.ts +14 -0
  140. package/dist/organisms/AppShell/context.js +6 -0
  141. package/dist/organisms/AppShell/index.d.ts +1 -0
  142. package/dist/organisms/AppShell/index.js +1 -0
  143. package/dist/organisms/AuthForms/AuthForms.d.ts +83 -0
  144. package/dist/organisms/AuthForms/AuthForms.js +57 -0
  145. package/dist/organisms/AuthForms/index.d.ts +2 -0
  146. package/dist/organisms/AuthForms/index.js +1 -0
  147. package/dist/organisms/AuthForms/shared.d.ts +36 -0
  148. package/dist/organisms/AuthForms/shared.js +36 -0
  149. package/dist/organisms/BulkActionBar/BulkActionBar.d.ts +25 -0
  150. package/dist/organisms/BulkActionBar/BulkActionBar.js +18 -0
  151. package/dist/organisms/BulkActionBar/index.d.ts +1 -0
  152. package/dist/organisms/BulkActionBar/index.js +1 -0
  153. package/dist/organisms/Chart/CategoryCharts.d.ts +34 -0
  154. package/dist/organisms/Chart/CategoryCharts.js +93 -0
  155. package/dist/organisms/Chart/ChartFrame.d.ts +26 -0
  156. package/dist/organisms/Chart/ChartFrame.js +18 -0
  157. package/dist/organisms/Chart/DonutChart.d.ts +25 -0
  158. package/dist/organisms/Chart/DonutChart.js +53 -0
  159. package/dist/organisms/Chart/Sparkline.d.ts +15 -0
  160. package/dist/organisms/Chart/Sparkline.js +18 -0
  161. package/dist/organisms/Chart/index.d.ts +5 -0
  162. package/dist/organisms/Chart/index.js +5 -0
  163. package/dist/organisms/Chart/shared.d.ts +51 -0
  164. package/dist/organisms/Chart/shared.js +113 -0
  165. package/dist/organisms/DataTable/DataTable.d.ts +63 -0
  166. package/dist/organisms/DataTable/DataTable.js +120 -0
  167. package/dist/organisms/DataTable/index.d.ts +3 -0
  168. package/dist/organisms/DataTable/index.js +3 -0
  169. package/dist/organisms/DataTable/types.d.ts +39 -0
  170. package/dist/organisms/DataTable/types.js +1 -0
  171. package/dist/organisms/DataTable/useClientTable.d.ts +27 -0
  172. package/dist/organisms/DataTable/useClientTable.js +51 -0
  173. package/dist/organisms/DateRangePicker/DateRangePicker.d.ts +43 -0
  174. package/dist/organisms/DateRangePicker/DateRangePicker.js +54 -0
  175. package/dist/organisms/DateRangePicker/index.d.ts +1 -0
  176. package/dist/organisms/DateRangePicker/index.js +1 -0
  177. package/dist/organisms/FilterBar/FilterBar.d.ts +28 -0
  178. package/dist/organisms/FilterBar/FilterBar.js +16 -0
  179. package/dist/organisms/FilterBar/index.d.ts +1 -0
  180. package/dist/organisms/FilterBar/index.js +1 -0
  181. package/dist/organisms/PageHeader/PageHeader.d.ts +31 -0
  182. package/dist/organisms/PageHeader/PageHeader.js +19 -0
  183. package/dist/organisms/PageHeader/index.d.ts +1 -0
  184. package/dist/organisms/PageHeader/index.js +1 -0
  185. package/dist/organisms/PageTabs/PageTabs.d.ts +27 -0
  186. package/dist/organisms/PageTabs/PageTabs.js +31 -0
  187. package/dist/organisms/PageTabs/index.d.ts +1 -0
  188. package/dist/organisms/PageTabs/index.js +1 -0
  189. package/dist/organisms/Sidebar/Sidebar.d.ts +48 -0
  190. package/dist/organisms/Sidebar/Sidebar.js +73 -0
  191. package/dist/organisms/Sidebar/index.d.ts +1 -0
  192. package/dist/organisms/Sidebar/index.js +1 -0
  193. package/dist/organisms/Topbar/Topbar.d.ts +24 -0
  194. package/dist/organisms/Topbar/Topbar.js +18 -0
  195. package/dist/organisms/Topbar/index.d.ts +1 -0
  196. package/dist/organisms/Topbar/index.js +1 -0
  197. package/dist/organisms/index.d.ts +11 -0
  198. package/dist/organisms/index.js +11 -0
  199. package/dist/strings.d.ts +108 -15
  200. package/dist/strings.js +73 -7
  201. package/dist/templates/AdminLayout/AdminLayout.d.ts +14 -0
  202. package/dist/templates/AdminLayout/AdminLayout.js +9 -0
  203. package/dist/templates/AdminLayout/index.d.ts +1 -0
  204. package/dist/templates/AdminLayout/index.js +1 -0
  205. package/dist/templates/AuthLayout/AuthLayout.d.ts +28 -0
  206. package/dist/templates/AuthLayout/AuthLayout.js +11 -0
  207. package/dist/templates/AuthLayout/index.d.ts +1 -0
  208. package/dist/templates/AuthLayout/index.js +1 -0
  209. package/dist/templates/DashboardPage/DashboardPage.d.ts +25 -0
  210. package/dist/templates/DashboardPage/DashboardPage.js +12 -0
  211. package/dist/templates/DashboardPage/index.d.ts +1 -0
  212. package/dist/templates/DashboardPage/index.js +1 -0
  213. package/dist/templates/DetailPage/DetailPage.d.ts +20 -0
  214. package/dist/templates/DetailPage/DetailPage.js +11 -0
  215. package/dist/templates/DetailPage/index.d.ts +1 -0
  216. package/dist/templates/DetailPage/index.js +1 -0
  217. package/dist/templates/FormPage/FormPage.d.ts +26 -0
  218. package/dist/templates/FormPage/FormPage.js +11 -0
  219. package/dist/templates/FormPage/index.d.ts +1 -0
  220. package/dist/templates/FormPage/index.js +1 -0
  221. package/dist/templates/ListPage/ListPage.d.ts +17 -0
  222. package/dist/templates/ListPage/ListPage.js +8 -0
  223. package/dist/templates/ListPage/index.d.ts +1 -0
  224. package/dist/templates/ListPage/index.js +1 -0
  225. package/dist/templates/SettingsPage/SettingsPage.d.ts +32 -0
  226. package/dist/templates/SettingsPage/SettingsPage.js +13 -0
  227. package/dist/templates/SettingsPage/index.d.ts +1 -0
  228. package/dist/templates/SettingsPage/index.js +1 -0
  229. package/dist/templates/StatusPage/StatusPage.d.ts +18 -0
  230. package/dist/templates/StatusPage/StatusPage.js +8 -0
  231. package/dist/templates/StatusPage/index.d.ts +1 -0
  232. package/dist/templates/StatusPage/index.js +1 -0
  233. package/dist/templates/index.d.ts +8 -0
  234. package/dist/templates/index.js +8 -0
  235. package/dist/utils/dates.d.ts +67 -0
  236. package/dist/utils/dates.js +154 -0
  237. package/dist/utils/index.d.ts +1 -0
  238. package/dist/utils/index.js +1 -0
  239. package/dist/utils/theme.d.ts +4 -0
  240. package/dist/utils/theme.js +7 -3
  241. package/package.json +4 -1
  242. package/tokens.css +500 -21
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';
@@ -0,0 +1,25 @@
1
+ import { type ReactNode } from 'react';
2
+ export interface ErrorSummaryItem {
3
+ /** The id of the control with the problem (FormField's `id`). */
4
+ fieldId: string;
5
+ message: ReactNode;
6
+ }
7
+ export interface ErrorSummaryProps {
8
+ errors: ErrorSummaryItem[];
9
+ /**
10
+ * Increment on each submit. The summary takes focus whenever this changes while there are
11
+ * errors, so a second failed submit moves focus back to it.
12
+ */
13
+ submitCount: number;
14
+ /** Default: the `fixErrors` UI string. */
15
+ title?: ReactNode;
16
+ className?: string;
17
+ }
18
+ /**
19
+ * The list of a form's problems, shown above the form after a failed submit. It takes focus,
20
+ * so a screen reader reads the heading and the count, and every item links to its field:
21
+ * following one focuses the control itself, not just scrolls to it.
22
+ *
23
+ * Renders nothing while there are no errors.
24
+ */
25
+ export declare function ErrorSummary({ errors, submitCount, title, className }: ErrorSummaryProps): import("react").JSX.Element | null;
@@ -0,0 +1,32 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useId, useRef } from 'react';
3
+ import { useUiStrings } from '../../strings.js';
4
+ /**
5
+ * The list of a form's problems, shown above the form after a failed submit. It takes focus,
6
+ * so a screen reader reads the heading and the count, and every item links to its field:
7
+ * following one focuses the control itself, not just scrolls to it.
8
+ *
9
+ * Renders nothing while there are no errors.
10
+ */
11
+ export function ErrorSummary({ errors, submitCount, title, className }) {
12
+ const strings = useUiStrings();
13
+ const ref = useRef(null);
14
+ const titleId = useId();
15
+ const hasErrors = errors.length > 0;
16
+ // biome-ignore lint/correctness/useExhaustiveDependencies: focus follows submits, not every change to the list.
17
+ useEffect(() => {
18
+ if (hasErrors)
19
+ ref.current?.focus();
20
+ }, [submitCount]);
21
+ if (!hasErrors)
22
+ return null;
23
+ const focusField = (e, fieldId) => {
24
+ const target = document.getElementById(fieldId);
25
+ if (!target)
26
+ return;
27
+ e.preventDefault();
28
+ target.focus();
29
+ target.scrollIntoView?.({ block: 'center' });
30
+ };
31
+ return (_jsxs("section", { ref: ref, tabIndex: -1, "aria-labelledby": titleId, className: ['ui-error-summary', className].filter(Boolean).join(' '), children: [_jsx("h2", { id: titleId, className: "ui-error-summary__title", children: title ?? strings.fixErrors(errors.length) }), _jsx("ul", { className: "ui-error-summary__list", children: errors.map((error) => (_jsx("li", { children: _jsx("a", { href: `#${error.fieldId}`, onClick: (e) => focusField(e, error.fieldId), children: error.message }) }, error.fieldId))) })] }));
32
+ }
@@ -0,0 +1 @@
1
+ export * from './ErrorSummary.js';
@@ -0,0 +1 @@
1
+ export * from './ErrorSummary.js';
@@ -0,0 +1,10 @@
1
+ import { type ChipGroupProps } from '../../atoms/ChipGroup/ChipGroup.js';
2
+ export type FilterChipsProps = ChipGroupProps & {
3
+ /** Shows a "Clear all" button while anything is selected. */
4
+ onClear?: () => void;
5
+ };
6
+ /**
7
+ * Quick filters as chips: a `ChipGroup` with a clear-all beside it. Every ChipGroup behaviour
8
+ * (single or multiple, scrolling with fade edges, arrow keys) is unchanged.
9
+ */
10
+ export declare function FilterChips({ onClear, ...group }: FilterChipsProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from '../../atoms/Button/Button.js';
3
+ import { ChipGroup } from '../../atoms/ChipGroup/ChipGroup.js';
4
+ import { useUiStrings } from '../../strings.js';
5
+ /**
6
+ * Quick filters as chips: a `ChipGroup` with a clear-all beside it. Every ChipGroup behaviour
7
+ * (single or multiple, scrolling with fade edges, arrow keys) is unchanged.
8
+ */
9
+ export function FilterChips({ onClear, ...group }) {
10
+ const strings = useUiStrings();
11
+ const active = group.multiple ? group.value.length > 0 : group.value !== null;
12
+ return (_jsxs("div", { className: "flex min-w-0 items-center gap-2", children: [_jsx("div", { className: "min-w-0 flex-1", children: _jsx(ChipGroup, { ...group }) }), onClear && active ? (_jsx(Button, { variant: "ghost", size: "sm", onClick: onClear, children: strings.clearFilters })) : null] }));
13
+ }
@@ -0,0 +1 @@
1
+ export * from './FilterChips.js';
@@ -0,0 +1 @@
1
+ export * from './FilterChips.js';
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from 'react';
2
+ import { type Breakpoint } from '../../hooks/breakpoints.js';
3
+ export interface FilterPopoverProps {
4
+ /** The trigger's text and the sheet's title. Default: the `filters` UI string. */
5
+ label?: string;
6
+ /** How many filters are set. Shown as a count on the trigger and read with it. */
7
+ activeCount?: number;
8
+ /** The filter controls: FormFields, a FormFieldset of checkboxes. */
9
+ children: ReactNode;
10
+ /**
11
+ * With this, the controls are a draft: Apply calls it and closes. Without it, the controls
12
+ * apply as they change and there is no Apply button.
13
+ */
14
+ onApply?: () => void;
15
+ /** Shows a Reset button. */
16
+ onReset?: () => void;
17
+ open?: boolean;
18
+ defaultOpen?: boolean;
19
+ onOpenChange?: (open: boolean) => void;
20
+ /** Below this the panel is a bottom Sheet. Default `sm` (768px). `false` keeps the popover. */
21
+ sheetBelow?: Breakpoint | false;
22
+ }
23
+ /**
24
+ * A "Filters" button that opens the filter controls: a popover anchored to the button on wide
25
+ * screens, a bottom sheet on narrow ones, where a popover would be cramped and hard to reach.
26
+ */
27
+ export declare function FilterPopover({ label, activeCount, children, onApply, onReset, open, defaultOpen, onOpenChange, sheetBelow, }: FilterPopoverProps): import("react").JSX.Element;