@karni/ui 0.3.1 → 0.4.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 (127) hide show
  1. package/README.md +712 -72
  2. package/dist/index.cjs +5 -5
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.js +8660 -4725
  5. package/dist/index.js.map +1 -1
  6. package/dist/manifest.json +18 -1
  7. package/dist/script-BVJ9BbPm.cjs +2 -0
  8. package/dist/script-BVJ9BbPm.cjs.map +1 -0
  9. package/dist/script-D1fn4Dm4.js +6 -0
  10. package/dist/script-D1fn4Dm4.js.map +1 -0
  11. package/dist/src/auth/AuthGuard.d.ts +69 -4
  12. package/dist/src/auth/LoginForm.d.ts +41 -7
  13. package/dist/src/auth/engine.d.ts +15 -13
  14. package/dist/src/auth/provider.d.ts +10 -10
  15. package/dist/src/auth/storage.d.ts +1 -1
  16. package/dist/src/auth/types.d.ts +3 -3
  17. package/dist/src/components/brand/karni-mark.d.ts +4 -4
  18. package/dist/src/components/chart/data-chart.d.ts +66 -11
  19. package/dist/src/components/empty-state/empty-state.d.ts +22 -2
  20. package/dist/src/components/file-upload/FileUpload.d.ts +67 -3
  21. package/dist/src/components/layout/layout.d.ts +104 -9
  22. package/dist/src/components/layout/route-focus.d.ts +43 -0
  23. package/dist/src/components/modal/modal.d.ts +49 -8
  24. package/dist/src/components/page/page.d.ts +36 -5
  25. package/dist/src/components/primitives/button.d.ts +1 -0
  26. package/dist/src/components/primitives/checkbox.d.ts +4 -1
  27. package/dist/src/components/primitives/dropdown-menu.d.ts +1 -1
  28. package/dist/src/components/primitives/icons.d.ts +9 -1
  29. package/dist/src/components/primitives/input.d.ts +4 -1
  30. package/dist/src/components/primitives/label.d.ts +4 -1
  31. package/dist/src/components/primitives/select.d.ts +37 -6
  32. package/dist/src/components/primitives/skeleton.d.ts +4 -1
  33. package/dist/src/components/primitives/switch.d.ts +4 -1
  34. package/dist/src/components/primitives/tabs.d.ts +2 -2
  35. package/dist/src/components/primitives/textarea.d.ts +4 -1
  36. package/dist/src/components/resource/ResourceDetail.d.ts +3 -3
  37. package/dist/src/components/resource/ResourceForm.d.ts +4 -3
  38. package/dist/src/components/resource/ResourceSelect.d.ts +3 -1
  39. package/dist/src/components/resource/ResourceTable.d.ts +9 -3
  40. package/dist/src/components/resource/audit-fields.d.ts +1 -1
  41. package/dist/src/components/resource/detail/ResourceDetail.d.ts +109 -11
  42. package/dist/src/components/resource/empty-value.d.ts +17 -0
  43. package/dist/src/components/resource/form/ResourceForm.d.ts +65 -6
  44. package/dist/src/components/resource/form/field-inputs.d.ts +15 -1
  45. package/dist/src/components/resource/form/form-states.d.ts +87 -0
  46. package/dist/src/components/resource/form/prepare.d.ts +3 -3
  47. package/dist/src/components/resource/form/server-errors.d.ts +14 -0
  48. package/dist/src/components/resource/history/ChangeHistory.d.ts +46 -4
  49. package/dist/src/components/resource/import/ResourceImport.d.ts +73 -7
  50. package/dist/src/components/resource/nouns.d.ts +12 -0
  51. package/dist/src/components/resource/query-state.d.ts +20 -0
  52. package/dist/src/components/resource/resource-names.d.ts +23 -0
  53. package/dist/src/components/resource/select/ResourceSelect.d.ts +39 -8
  54. package/dist/src/components/resource/select/select-states.d.ts +68 -0
  55. package/dist/src/components/resource/select/use-option-pages.d.ts +37 -0
  56. package/dist/src/components/resource/select/venue-backend.d.ts +144 -0
  57. package/dist/src/components/resource/table/BulkActionsBar.d.ts +62 -5
  58. package/dist/src/components/resource/table/CardList.d.ts +31 -0
  59. package/dist/src/components/resource/table/ConfirmDialog.d.ts +28 -0
  60. package/dist/src/components/resource/table/ExportButtons.d.ts +2 -2
  61. package/dist/src/components/resource/table/FilterBar.d.ts +3 -3
  62. package/dist/src/components/resource/table/PaginationControls.d.ts +15 -3
  63. package/dist/src/components/resource/table/ResourceTable.d.ts +79 -14
  64. package/dist/src/components/resource/table/RowCard.d.ts +31 -0
  65. package/dist/src/components/resource/table/TableView.d.ts +29 -0
  66. package/dist/src/components/resource/table/ViewOptions.d.ts +19 -0
  67. package/dist/src/components/resource/table/access.d.ts +12 -0
  68. package/dist/src/components/resource/table/derive-columns.d.ts +2 -2
  69. package/dist/src/components/resource/table/grid-navigation.d.ts +23 -0
  70. package/dist/src/components/resource/table/layout.d.ts +37 -0
  71. package/dist/src/components/resource/table/preferences.d.ts +12 -0
  72. package/dist/src/components/resource/table/slots.d.ts +41 -0
  73. package/dist/src/components/resource/table/states.d.ts +91 -0
  74. package/dist/src/components/resource/table/story-fixtures.d.ts +25 -0
  75. package/dist/src/components/resource/table/structural-columns.d.ts +25 -0
  76. package/dist/src/components/resource/table/table-icons.d.ts +16 -0
  77. package/dist/src/components/resource/table/table-utils.d.ts +27 -3
  78. package/dist/src/components/resource/table/url-state.d.ts +67 -0
  79. package/dist/src/components/resource/table/use-container-width.d.ts +10 -0
  80. package/dist/src/components/resource/testapp-fixtures.d.ts +78 -0
  81. package/dist/src/components/stat/stat.d.ts +36 -6
  82. package/dist/src/components/toast/toast-store.d.ts +1 -1
  83. package/dist/src/components/toast/toaster.d.ts +39 -6
  84. package/dist/src/components/toast/use-toast.d.ts +1 -1
  85. package/dist/src/hooks/keys.d.ts +4 -4
  86. package/dist/src/hooks/multipart.d.ts +9 -9
  87. package/dist/src/hooks/params.d.ts +1 -1
  88. package/dist/src/hooks/types.d.ts +3 -3
  89. package/dist/src/hooks/useChangeHistory.d.ts +3 -2
  90. package/dist/src/hooks/useResourceImport.d.ts +2 -2
  91. package/dist/src/hooks/useResourceMutations.d.ts +2 -2
  92. package/dist/src/hooks/useUnsavedChangesGuard.d.ts +31 -0
  93. package/dist/src/index.d.ts +49 -31
  94. package/dist/src/lib/roles.d.ts +4 -4
  95. package/dist/src/lib/variants.d.ts +1 -1
  96. package/dist/src/primitives/registry.d.ts +81 -0
  97. package/dist/src/schema/client.d.ts +10 -10
  98. package/dist/src/schema/parse.d.ts +2 -2
  99. package/dist/src/schema/provider.d.ts +10 -3
  100. package/dist/src/schema/types.d.ts +18 -9
  101. package/dist/src/schema/useSchema.d.ts +5 -1
  102. package/dist/src/states/ErrorState.d.ts +43 -0
  103. package/dist/src/states/PendingButton.d.ts +16 -0
  104. package/dist/src/states/describe-error.d.ts +25 -0
  105. package/dist/src/states/hooks.d.ts +10 -0
  106. package/dist/src/theme/script.d.ts +12 -0
  107. package/dist/src/theme/theme.d.ts +45 -0
  108. package/dist/styles.css +174 -13
  109. package/dist/tailwind-preset.cjs +2 -2
  110. package/dist/tailwind-preset.cjs.map +1 -1
  111. package/dist/tailwind-preset.d.ts +83 -1
  112. package/dist/tailwind-preset.js +181 -62
  113. package/dist/tailwind-preset.js.map +1 -1
  114. package/dist/tailwind-v4.css +31 -0
  115. package/dist/vite-plugin.cjs +2 -0
  116. package/dist/vite-plugin.cjs.map +1 -0
  117. package/dist/vite-plugin.d.ts +19 -0
  118. package/dist/vite-plugin.js +16 -0
  119. package/dist/vite-plugin.js.map +1 -0
  120. package/eslint-plugin/index.js +4 -0
  121. package/eslint-plugin/rules/max-page-lines.js +1 -1
  122. package/eslint-plugin/rules/no-deep-imports.js +1 -1
  123. package/eslint-plugin/rules/no-direct-http.js +1 -1
  124. package/eslint-plugin/rules/no-raw-elements.js +1 -1
  125. package/eslint-plugin/rules/no-style-literals.js +222 -0
  126. package/eslint-plugin/rules.json +10 -4
  127. package/package.json +22 -19
@@ -1,10 +1,40 @@
1
1
  import * as React from "react";
2
2
  /** Why a file was refused by <FileUpload> (surfaced through `onReject`). */
3
3
  export type FileUploadRejectReason = "accept" | "size";
4
+ /** What the `Dropzone` slot receives: the props of the button it replaces, plus its state. */
5
+ export interface FileUploadDropzoneProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
6
+ /** Attach to the button: focus returns to it after a file is removed. */
7
+ buttonRef: React.Ref<HTMLButtonElement>;
8
+ /** A file is being dragged over the dropzone. */
9
+ dragging: boolean;
10
+ multiple: boolean;
11
+ /** The `uploading` prop. */
12
+ uploading: boolean;
13
+ /** The `children` prop (custom content), or undefined for the default text. */
14
+ children?: React.ReactNode;
15
+ }
16
+ /** What the `FileRow` slot receives: one current file. Render an <li>. */
17
+ export interface FileUploadFileRowProps {
18
+ /** A pending `File`, or the URL string of a file already uploaded. */
19
+ file: File | string;
20
+ /** The file name (a URL's last path segment). */
21
+ name: string;
22
+ /** A pending file's size ("12 KB"), undefined for a URL. */
23
+ size?: string;
24
+ /** Removes the file. Undefined when it cannot be removed (clearable=false, disabled, uploading). */
25
+ onRemove?: () => void;
26
+ }
27
+ /** The parts of the picker a project can replace (`slots={{ FileRow: Thumbnail }}`). */
28
+ export interface FileUploadSlots {
29
+ /** The drop target: a button that opens the file picker. */
30
+ Dropzone: React.ComponentType<FileUploadDropzoneProps>;
31
+ /** One row of the current-files list. */
32
+ FileRow: React.ComponentType<FileUploadFileRowProps>;
33
+ }
4
34
  export interface FileUploadProps {
5
35
  /**
6
36
  * Current value. A `string` is an existing uploaded file (the display URL a
7
- * karni backend returns); a `File` is a pending upload. Arrays are for
37
+ * karni backend returns), and a `File` is a pending upload. Arrays are for
8
38
  * `multiple` mode and may mix both.
9
39
  */
10
40
  value?: File | string | ReadonlyArray<File | string> | null;
@@ -22,6 +52,19 @@ export interface FileUploadProps {
22
52
  maxSize?: number;
23
53
  /** Called once per refused file with the reason ("accept" | "size"). */
24
54
  onReject?: (file: File, reason: FileUploadRejectReason) => void;
55
+ /**
56
+ * The sentence shown (and announced) for a refused file. Default: "notes.txt
57
+ * isn't an accepted file type." / "big.png is larger than 1.0 MB.". Return
58
+ * null to show nothing (when `onReject` reports it elsewhere).
59
+ */
60
+ rejectionMessage?: (file: File, reason: FileUploadRejectReason, limits: {
61
+ accept?: string;
62
+ maxSize?: number;
63
+ }) => React.ReactNode;
64
+ /** An error to show under the dropzone (announced). It marks the field invalid. */
65
+ error?: React.ReactNode;
66
+ /** True while the file uploads: the dropzone shows progress and takes no new files. */
67
+ uploading?: boolean;
25
68
  id?: string;
26
69
  /** Forwarded to the hidden native file input. */
27
70
  name?: string;
@@ -30,15 +73,36 @@ export interface FileUploadProps {
30
73
  children?: React.ReactNode;
31
74
  /** Set false to hide the per-file remove affordance. Default true. */
32
75
  clearable?: boolean;
76
+ /** Replaces the Dropzone or FileRow (see FileUploadSlots). */
77
+ slots?: Partial<FileUploadSlots>;
33
78
  "aria-label"?: string;
79
+ /** Ids of elements that label the dropzone (or point a <label htmlFor> at `id`). */
80
+ "aria-labelledby"?: string;
81
+ /** Ids of elements that describe the dropzone (help text, a form's error message). */
82
+ "aria-describedby"?: string;
83
+ /** Marks the dropzone invalid (a destructive border). `error` sets it too. */
84
+ "aria-invalid"?: React.AriaAttributes["aria-invalid"];
85
+ /** Called when focus leaves the dropzone (react-hook-form's onBlur). */
86
+ onBlur?: React.FocusEventHandler<HTMLButtonElement>;
34
87
  }
88
+ /** The default pieces of the picker, for wrapping one. */
89
+ export declare const defaultFileUploadSlots: FileUploadSlots;
35
90
  /**
36
91
  * Drag-and-drop or click-to-browse file picker that shows the current file and the pending ones.
37
92
  *
38
93
  * Contract "FileUpload".
39
94
  *
40
95
  * Shows an existing uploaded value (URL string) as a link, pending `File`
41
- * values with name and size, and emits `File` objects — the hooks layer turns
96
+ * values with name and size, and emits `File` objects. The hooks layer turns
42
97
  * them into multipart bodies per amendment D2.
98
+ *
99
+ * Keyboard and screen readers: the dropzone is a button (Enter or Space opens
100
+ * the picker), labelled by its text, `aria-label`, `aria-labelledby` or a
101
+ * <label htmlFor={id}>. A refused file and the `error` prop are announced and
102
+ * linked through aria-describedby. `aria-invalid` (or `error`) marks it
103
+ * invalid. Removing a file returns focus to the dropzone.
104
+ *
105
+ * Escape hatches: `slots` replaces the Dropzone or FileRow, `children`
106
+ * replaces the dropzone's content, `rejectionMessage` rewords refusals.
43
107
  */
44
- export declare function FileUpload(props: FileUploadProps): React.ReactElement;
108
+ export declare const FileUpload: React.ForwardRefExoticComponent<FileUploadProps & React.RefAttributes<HTMLButtonElement>>;
@@ -1,4 +1,6 @@
1
+ import { LayoutRouteFocusOptions } from './route-focus';
1
2
  import * as React from "react";
3
+ export type { LayoutRouteFocusOptions } from './route-focus';
2
4
  export interface LayoutNavItem {
3
5
  /** Visible label of the nav entry. */
4
6
  label: React.ReactNode;
@@ -11,14 +13,76 @@ export interface LayoutNavItem {
11
13
  /** React key override when `href` is not unique. */
12
14
  key?: string;
13
15
  }
16
+ /** What the `NavLink` slot receives: the props of the <a> it replaces, plus the item. */
17
+ export interface LayoutNavLinkProps {
18
+ item: LayoutNavItem;
19
+ href: string;
20
+ /** The styled class, focus indicator and active state included. */
21
+ className: string;
22
+ "aria-current"?: "page";
23
+ /** Closes the mobile drawer. Call it when the link navigates. */
24
+ onClick: () => void;
25
+ /** The icon and the label. */
26
+ children: React.ReactNode;
27
+ }
28
+ /** What the `Sidebar` slot (the static sidebar, md and up) receives. */
29
+ export interface LayoutSidebarProps {
30
+ /** The `sidebarHeader` (the Karni mark by default), or null. */
31
+ header: React.ReactNode;
32
+ /** The derived nav, or the `sidebar` prop. */
33
+ nav: React.ReactNode;
34
+ /** The `sidebarFooter`, or null. */
35
+ footer: React.ReactNode;
36
+ /** `sidebarClassName`. */
37
+ className?: string;
38
+ }
39
+ /** What the `MobileDrawer` slot (the sidebar below md) receives. */
40
+ export interface LayoutMobileDrawerProps extends LayoutSidebarProps {
41
+ open: boolean;
42
+ onOpenChange: (open: boolean) => void;
43
+ /** The id the menu button's aria-controls names while the drawer is open. */
44
+ id: string;
45
+ /** The menu button: where focus returns when the drawer closes. */
46
+ returnFocusRef: React.RefObject<HTMLButtonElement | null>;
47
+ }
48
+ /** What the `Header` slot (the top bar) receives. */
49
+ export interface LayoutHeaderProps {
50
+ /** The button that opens the drawer below md. Render it, or the drawer cannot open. */
51
+ menuButton: React.ReactNode;
52
+ /** The `header` prop's content. */
53
+ children?: React.ReactNode;
54
+ /** `headerClassName`. */
55
+ className?: string;
56
+ }
57
+ /** What the `SkipLink` slot receives: the first focusable element of the page. */
58
+ export interface LayoutSkipLinkProps {
59
+ /** `#` + the id of <main>. */
60
+ href: string;
61
+ /** Moves focus to <main> without changing the URL (hash routers keep their route). */
62
+ onClick: (event: React.MouseEvent<HTMLAnchorElement>) => void;
63
+ children: React.ReactNode;
64
+ }
65
+ /** The parts of the shell a project can replace (`slots={{ NavLink: RouterLink }}`). */
66
+ export interface LayoutSlots {
67
+ /** The top bar with the menu button and the `header` content. */
68
+ Header: React.ComponentType<LayoutHeaderProps>;
69
+ /** The static sidebar (md and up). The default hides itself below md. */
70
+ Sidebar: React.ComponentType<LayoutSidebarProps>;
71
+ /** Each derived nav link. Replace it with your router's link. */
72
+ NavLink: React.ComponentType<LayoutNavLinkProps>;
73
+ /** The sidebar below md: a modal drawer (focus trapped, Escape closes). */
74
+ MobileDrawer: React.ComponentType<LayoutMobileDrawerProps>;
75
+ /** "Skip to content", visible only while focused. `() => null` removes it. */
76
+ SkipLink: React.ComponentType<LayoutSkipLinkProps>;
77
+ }
14
78
  export interface LayoutProps extends React.HTMLAttributes<HTMLDivElement> {
15
79
  /** Items of the derived sidebar nav. */
16
80
  navItems?: readonly LayoutNavItem[];
17
81
  /**
18
- * Escape hatch for router integration: wraps/replaces the default anchor of
19
- * one item. `defaultNode` is the styled <a> the layout would render — return
20
- * it, or your own node (e.g. a TanStack Router <Link> with
21
- * `className={defaultClassName}`).
82
+ * Wraps or replaces the default link of one item. `defaultNode` is the
83
+ * link the layout would render. Return it, or your own node (e.g. a
84
+ * TanStack Router <Link> with `className={defaultClassName}`).
85
+ * Prefer the `NavLink` slot, which also closes the mobile drawer.
22
86
  */
23
87
  renderNavItem?: (item: LayoutNavItem, defaults: {
24
88
  defaultNode: React.ReactNode;
@@ -28,7 +92,7 @@ export interface LayoutProps extends React.HTMLAttributes<HTMLDivElement> {
28
92
  sidebar?: React.ReactNode;
29
93
  /**
30
94
  * Brand/logo slot pinned to the top of the sidebar. Defaults to the Karni
31
- * mark (`<KarniMark />`); pass your own brand, or `null` for no header.
95
+ * mark (`<KarniMark />`). Pass your own brand, or `null` for no header.
32
96
  */
33
97
  sidebarHeader?: React.ReactNode;
34
98
  /** Slot pinned to the bottom of the sidebar (user menu, logout, …). */
@@ -37,19 +101,50 @@ export interface LayoutProps extends React.HTMLAttributes<HTMLDivElement> {
37
101
  header?: React.ReactNode;
38
102
  /** Accessible name of the sidebar navigation (default "Main"). */
39
103
  navLabel?: string;
104
+ /** Controlled open state of the mobile drawer (pair with onMobileNavOpenChange). */
105
+ mobileNavOpen?: boolean;
106
+ /** Initial open state of the mobile drawer when uncontrolled. */
107
+ defaultMobileNavOpen?: boolean;
108
+ /** Called when the drawer opens or closes (menu button, Escape, backdrop, a link). */
109
+ onMobileNavOpenChange?: (open: boolean) => void;
110
+ /** Replaces internal pieces of the shell. See LayoutSlots. */
111
+ slots?: Partial<LayoutSlots>;
112
+ /**
113
+ * Focus after a client-side navigation. Default `true`: once the new page
114
+ * is in, focus moves to its `<h1>` (else `<main>`) and a polite live region
115
+ * says the page title. Nothing moves on the first load (a redirect before
116
+ * the user acts included), when only search params change (filters,
117
+ * pagination, sorting), or when something took focus for the page itself
118
+ * (an autofocused field, a control inside `<main>`, a dialog). `false`
119
+ * turns it off. An object keeps it on and sets the route key, the target
120
+ * or the announcement (see LayoutRouteFocusOptions).
121
+ */
122
+ routeFocus?: boolean | LayoutRouteFocusOptions;
40
123
  sidebarClassName?: string;
41
124
  headerClassName?: string;
42
125
  contentClassName?: string;
43
126
  children?: React.ReactNode;
44
127
  }
128
+ /** The default pieces of the shell, which you spread to wrap one (`{ ...defaultLayoutSlots }`). */
129
+ export declare const defaultLayoutSlots: LayoutSlots;
45
130
  /**
46
131
  * App shell: sidebar navigation + header slot + content area.
47
132
  *
48
133
  * Responsive: under the `md` breakpoint the sidebar collapses behind a menu
49
- * button and opens as an overlay drawer (Escape or backdrop click closes it).
134
+ * button and opens as a modal drawer: focus moves into it and stays there,
135
+ * Escape, the backdrop, the close button or a link close it, and focus
136
+ * returns to the menu button. A "Skip to content" link is the first stop of
137
+ * the keyboard.
138
+ *
139
+ * After a client-side navigation, focus moves to the new page's `<h1>` and
140
+ * a live region says its title (`routeFocus`). Render the Layout once,
141
+ * around your router's outlet, so it sees the route change.
50
142
  *
51
- * Escape hatches: `renderNavItem` wraps each derived link (router
52
- * integration), `sidebar` replaces the derived nav wholesale, and
53
- * `sidebarHeader`/`sidebarFooter`/`header` are free slots.
143
+ * Escape hatches: `slots` replaces the Header, Sidebar, NavLink,
144
+ * MobileDrawer or SkipLink. `renderNavItem` wraps each derived link.
145
+ * `sidebar` replaces the derived nav wholesale. `sidebarHeader`,
146
+ * `sidebarFooter` and `header` are free slots. `mobileNavOpen` controls
147
+ * the drawer. `routeFocus={false}` turns the focus move off, and an object
148
+ * sets its route key, target or announcement.
54
149
  */
55
150
  export declare const Layout: React.ForwardRefExoticComponent<LayoutProps & React.RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Layout's focus management after a client-side navigation, the way
3
+ * accessible SPA routers do it: once the new page is in, focus moves to its
4
+ * main heading (else <main>) and a polite live region says its title.
5
+ *
6
+ * It needs no router. The route is the URL's path (a hash route such as
7
+ * `#/events` included, search params left out), or the `routeKey` option. A
8
+ * change is noticed when the Layout renders and when anything in <main>
9
+ * changes, so a router that re-renders only its outlet is covered too.
10
+ * Internal to @karni/ui. Layout's `routeFocus` prop is the public surface.
11
+ */
12
+ import * as React from "react";
13
+ /** What Layout's `routeFocus` prop takes when it is an object. */
14
+ export interface LayoutRouteFocusOptions {
15
+ /**
16
+ * The current route: focus moves when it changes. Default: the URL's path
17
+ * (`location.pathname`, plus a hash route such as `#/events`), so a change
18
+ * of search params only (filters, pagination, sorting) never moves focus.
19
+ * Pass your router's pathname or route id when the URL does not say which
20
+ * page is showing (a memory router).
21
+ */
22
+ routeKey?: string;
23
+ /**
24
+ * The element that takes focus after a navigation. Default: the first
25
+ * `<h1>` in `<main>`, else `<main>` itself. An element that cannot take
26
+ * focus gets `tabindex="-1"`.
27
+ */
28
+ getTarget?: (main: HTMLElement) => HTMLElement | null;
29
+ /**
30
+ * What the live region says after a navigation. Default: the document
31
+ * title when the new page changed it, else the heading's text. Return ""
32
+ * to say nothing.
33
+ */
34
+ getAnnouncement?: (target: HTMLElement) => string;
35
+ }
36
+ export interface RouteFocusOptions extends LayoutRouteFocusOptions {
37
+ enabled: boolean;
38
+ }
39
+ /**
40
+ * Watches the route and, after a navigation, moves focus into the new page.
41
+ * Returns the live region's text.
42
+ */
43
+ export declare function useRouteFocus(mainRef: React.RefObject<HTMLElement | null>, options: RouteFocusOptions): string;
@@ -1,12 +1,46 @@
1
1
  import * as DialogPrimitive from "@radix-ui/react-dialog";
2
2
  import * as React from "react";
3
3
  /**
4
- * Escape hatch for footer buttons: any element wrapped in <ModalClose asChild>
5
- * closes the modal on click (re-exported Radix Dialog.Close).
4
+ * Escape hatch for footer buttons: any element wrapped in
5
+ * `<ModalClose asChild>` closes the modal on click (re-exported Radix
6
+ * Dialog.Close).
6
7
  */
7
8
  export declare const ModalClose: React.ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & React.RefAttributes<HTMLButtonElement>>;
9
+ /**
10
+ * The modal's title: names the dialog (aria-labelledby). A custom `Header`
11
+ * slot renders it so the dialog stays labelled.
12
+ */
13
+ export declare const ModalTitle: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogTitleProps & React.RefAttributes<HTMLHeadingElement>, "ref"> & React.RefAttributes<HTMLHeadingElement>>;
14
+ /** The modal's description: describes the dialog (aria-describedby). */
15
+ export declare const ModalDescription: React.ForwardRefExoticComponent<Omit<DialogPrimitive.DialogDescriptionProps & React.RefAttributes<HTMLParagraphElement>, "ref"> & React.RefAttributes<HTMLParagraphElement>>;
16
+ /** What the `Header` slot receives. Render ModalTitle (and ModalDescription) inside it. */
17
+ export interface ModalHeaderProps {
18
+ title: React.ReactNode;
19
+ /** The `description` prop, or undefined. */
20
+ description?: React.ReactNode;
21
+ }
22
+ /** What the `Footer` slot receives: the `footer` prop. */
23
+ export interface ModalFooterProps {
24
+ children: React.ReactNode;
25
+ }
26
+ /** What the `CloseButton` slot receives. Wrap your button in <ModalClose asChild>. */
27
+ export interface ModalCloseButtonProps {
28
+ /** Its accessible name ("Close"). */
29
+ label: string;
30
+ }
31
+ /** The parts of the dialog a project can replace (`slots={{ Footer: StickyFooter }}`). */
32
+ export interface ModalSlots {
33
+ /** Title and description. It must render ModalTitle. */
34
+ Header: React.ComponentType<ModalHeaderProps>;
35
+ /** The actions row. */
36
+ Footer: React.ComponentType<ModalFooterProps>;
37
+ /** The top-right close button. It must close through ModalClose. */
38
+ CloseButton: React.ComponentType<ModalCloseButtonProps>;
39
+ }
40
+ /** The default pieces of the dialog, for wrapping one. */
41
+ export declare const defaultModalSlots: ModalSlots;
8
42
  export interface ModalProps {
9
- /** Dialog title — always rendered (Radix requires it for accessibility). */
43
+ /** Dialog title, always rendered (Radix requires it for accessibility). */
10
44
  title: React.ReactNode;
11
45
  /** Optional description under the title, linked via aria-describedby. */
12
46
  description?: React.ReactNode;
@@ -26,6 +60,8 @@ export interface ModalProps {
26
60
  onOpenChange?: (open: boolean) => void;
27
61
  /** Hides the top-right X button. */
28
62
  hideCloseButton?: boolean;
63
+ /** Replaces the Header, Footer or CloseButton. See ModalSlots. */
64
+ slots?: Partial<ModalSlots>;
29
65
  /** Extra classes for the content panel. */
30
66
  className?: string;
31
67
  /**
@@ -36,10 +72,15 @@ export interface ModalProps {
36
72
  }
37
73
  /**
38
74
  * Themed Radix Dialog. Works controlled (`open` + `onOpenChange`) or
39
- * uncontrolled via the `trigger` slot; `footer` is the actions row.
75
+ * uncontrolled via the `trigger` slot, and `footer` is the actions row.
76
+ *
77
+ * Keyboard and screen readers: the dialog is named by its title and
78
+ * described by its description. Focus moves into it on open, Tab stays
79
+ * inside, Escape closes, and focus returns to what opened it.
40
80
  *
41
- * Escape hatches: `contentProps` reaches the Radix content part, `className`
42
- * restyles the panel, `hideCloseButton` drops the X, and `ModalClose` lets any
43
- * footer element close the dialog.
81
+ * Escape hatches: `slots` replaces the Header, Footer or CloseButton,
82
+ * `contentProps` reaches the Radix content part, `className` restyles the
83
+ * panel, `hideCloseButton` drops the X, and `ModalClose` lets any footer
84
+ * element close the dialog.
44
85
  */
45
- export declare function Modal({ title, description, children, footer, trigger, open, defaultOpen, onOpenChange, hideCloseButton, className, contentProps, }: ModalProps): React.ReactElement;
86
+ export declare function Modal({ title, description, children, footer, trigger, open, defaultOpen, onOpenChange, hideCloseButton, slots, className, contentProps, }: ModalProps): React.ReactElement;
@@ -1,4 +1,30 @@
1
1
  import * as React from "react";
2
+ /** What the `Header` slot receives: the title row's pieces, already rendered by their slots. */
3
+ export interface PageHeaderProps {
4
+ /** The Title slot holding `title`. */
5
+ title: React.ReactNode;
6
+ /** The Description slot holding `description`, or null. */
7
+ description: React.ReactNode;
8
+ /** The Actions slot holding `actions`, or null. */
9
+ actions: React.ReactNode;
10
+ /** `headerClassName`. */
11
+ className?: string;
12
+ }
13
+ /** What the `Title`, `Description` and `Actions` slots receive. */
14
+ export interface PageSlotContentProps {
15
+ children: React.ReactNode;
16
+ }
17
+ /** The parts of the page chrome a project can replace (`slots={{ Title: MyHeading }}`). */
18
+ export interface PageSlots {
19
+ /** The row holding the title, description and actions. */
20
+ Header: React.ComponentType<PageHeaderProps>;
21
+ /** The page heading. The default is the page's <h1>. */
22
+ Title: React.ComponentType<PageSlotContentProps>;
23
+ /** The line under the title. */
24
+ Description: React.ComponentType<PageSlotContentProps>;
25
+ /** The container of the actions, right of the title. */
26
+ Actions: React.ComponentType<PageSlotContentProps>;
27
+ }
2
28
  export interface PageProps extends Omit<React.HTMLAttributes<HTMLElement>, "title"> {
3
29
  /** Page heading, rendered as an <h1>. */
4
30
  title: React.ReactNode;
@@ -7,23 +33,28 @@ export interface PageProps extends Omit<React.HTMLAttributes<HTMLElement>, "titl
7
33
  /** Slot rendered to the right of the title (buttons, menus, …). */
8
34
  actions?: React.ReactNode;
9
35
  /**
10
- * Escape hatch: replaces the entire derived header (title/description/
11
- * actions are ignored when given).
36
+ * Replaces the entire derived header (title/description/actions are
37
+ * ignored when given). Prefer the `Header` slot, which still receives them.
12
38
  */
13
39
  header?: React.ReactNode;
40
+ /** Replaces pieces of the chrome. See PageSlots. */
41
+ slots?: Partial<PageSlots>;
14
42
  /** Extra classes for the derived header row. */
15
43
  headerClassName?: string;
16
44
  /** Extra classes for the content area under the header. */
17
45
  contentClassName?: string;
18
46
  children?: React.ReactNode;
19
47
  }
48
+ /** The default pieces of the page chrome, for wrapping one. */
49
+ export declare const defaultPageSlots: PageSlots;
20
50
  /**
21
- * Standard page chrome: an <h1> title row with an actions slot, then the
51
+ * Standard page chrome: an `<h1>` title row with an actions slot, then the
22
52
  * content. The target usage `<Page title="Events"><ResourceTable … /></Page>`
23
53
  * is a complete screen.
24
54
  *
25
- * Escape hatches: `header` replaces the whole derived header; `className`,
26
- * `headerClassName` and `contentClassName` merge through cn(); every other
55
+ * Escape hatches: `slots` replaces the Header, Title, Description or
56
+ * Actions. `header` replaces the whole derived header. `className`,
57
+ * `headerClassName` and `contentClassName` merge through cn(). Every other
27
58
  * prop passes through to the root <section>.
28
59
  */
29
60
  export declare const Page: React.ForwardRefExoticComponent<PageProps & React.RefAttributes<HTMLElement>>;
@@ -25,6 +25,7 @@ export type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & Button
25
25
  /**
26
26
  * Themed button. `type` defaults to "button" (pass type="submit" in forms).
27
27
  * `className` is merged via cn(), so consumer classes win.
28
+ * A project replaces it with its own through `primitives={{ button: ... }}` on KarniProvider.
28
29
  */
29
30
  export declare const Button: React.ForwardRefExoticComponent<React.ButtonHTMLAttributes<HTMLButtonElement> & import('../..').VariantSelection<{
30
31
  variant: {
@@ -1,5 +1,8 @@
1
1
  import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
2
2
  import * as React from "react";
3
3
  export type CheckboxProps = React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>;
4
- /** Themed checkbox (Radix Checkbox) with a check mark indicator. */
4
+ /** Themed checkbox (Radix Checkbox) with a check mark indicator.
5
+ *
6
+ * A project replaces it with its own through `primitives={{ checkbox: ... }}` on KarniProvider.
7
+ */
5
8
  export declare const Checkbox: React.ForwardRefExoticComponent<Omit<CheckboxPrimitive.CheckboxProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
@@ -10,7 +10,7 @@ export declare const DropdownMenuGroup: React.ForwardRefExoticComponent<Dropdown
10
10
  export declare const DropdownMenuPortal: React.FC<DropdownMenuPrimitive.DropdownMenuPortalProps>;
11
11
  /** The styled menu panel. */
12
12
  export declare const DropdownMenuContent: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
13
- /** One menu item; `onSelect` runs the action. */
13
+ /** One menu item, whose `onSelect` runs the action. */
14
14
  export declare const DropdownMenuItem: React.ForwardRefExoticComponent<Omit<DropdownMenuPrimitive.DropdownMenuItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & {
15
15
  inset?: boolean;
16
16
  } & React.RefAttributes<HTMLDivElement>>;
@@ -2,10 +2,18 @@ import * as React from "react";
2
2
  /**
3
3
  * Minimal inline SVG icons used by the primitives (no icon dependency).
4
4
  * All strokes use `currentColor`, so they follow the text color tokens.
5
- * Internal to @karni/ui — not part of the public barrel.
5
+ * Internal to @karni/ui, not part of the public barrel.
6
6
  */
7
7
  type IconProps = React.SVGAttributes<SVGSVGElement>;
8
8
  export declare function CheckIcon(props: IconProps): React.ReactElement;
9
9
  export declare function ChevronDownIcon(props: IconProps): React.ReactElement;
10
10
  export declare function ChevronUpIcon(props: IconProps): React.ReactElement;
11
+ export declare function AlertIcon(props: IconProps): React.ReactElement;
12
+ export declare function LockIcon(props: IconProps): React.ReactElement;
13
+ export declare function OfflineIcon(props: IconProps): React.ReactElement;
14
+ export declare function SearchIcon(props: IconProps): React.ReactElement;
15
+ export declare function SpinnerIcon(props: IconProps): React.ReactElement;
16
+ export declare function SunIcon(props: IconProps): React.ReactElement;
17
+ export declare function MoonIcon(props: IconProps): React.ReactElement;
18
+ export declare function MonitorIcon(props: IconProps): React.ReactElement;
11
19
  export {};
@@ -1,4 +1,7 @@
1
1
  import * as React from "react";
2
2
  export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
3
- /** Themed text input; every native `<input>` prop passes through. */
3
+ /** Themed text input that passes every native `<input>` prop through.
4
+ *
5
+ * A project replaces it with its own through `primitives={{ input: ... }}` on KarniProvider.
6
+ */
4
7
  export declare const Input: React.ForwardRefExoticComponent<InputProps & React.RefAttributes<HTMLInputElement>>;
@@ -1,5 +1,8 @@
1
1
  import * as LabelPrimitive from "@radix-ui/react-label";
2
2
  import * as React from "react";
3
3
  export type LabelProps = React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>;
4
- /** Themed form label (Radix Label) that dims when its field is disabled. */
4
+ /** Themed form label (Radix Label) that dims when its field is disabled.
5
+ *
6
+ * A project replaces it with its own through `primitives={{ label: ... }}` on KarniProvider.
7
+ */
5
8
  export declare const Label: React.ForwardRefExoticComponent<Omit<LabelPrimitive.LabelProps & React.RefAttributes<HTMLLabelElement>, "ref"> & React.RefAttributes<HTMLLabelElement>>;
@@ -13,15 +13,30 @@ export declare const SelectRoot: React.FC<SelectPrimitive.SelectProps>;
13
13
  export declare const SelectGroup: React.ForwardRefExoticComponent<SelectPrimitive.SelectGroupProps & React.RefAttributes<HTMLDivElement>>;
14
14
  /** Shows the selected option's label inside `SelectTrigger`. */
15
15
  export declare const SelectValue: React.ForwardRefExoticComponent<SelectPrimitive.SelectValueProps & React.RefAttributes<HTMLSpanElement>>;
16
- /** The styled button that opens the select. */
16
+ /** The styled button that opens the select.
17
+ *
18
+ * Part of the `select` family a project can replace (`primitives={{ select: ... }}` on KarniProvider).
19
+ */
17
20
  export declare const SelectTrigger: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
18
- /** The styled dropdown panel that lists the options. */
21
+ /** The styled dropdown panel that lists the options.
22
+ *
23
+ * Part of the `select` family a project can replace (`primitives={{ select: ... }}` on KarniProvider).
24
+ */
19
25
  export declare const SelectContent: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
20
- /** A heading inside a `SelectGroup`. */
26
+ /** A heading inside a `SelectGroup`.
27
+ *
28
+ * Part of the `select` family a project can replace (`primitives={{ select: ... }}` on KarniProvider).
29
+ */
21
30
  export declare const SelectLabel: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectLabelProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
22
- /** One selectable option; `value` is what the select reports. */
31
+ /** One selectable option, whose `value` is what the select reports.
32
+ *
33
+ * Part of the `select` family a project can replace (`primitives={{ select: ... }}` on KarniProvider).
34
+ */
23
35
  export declare const SelectItem: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectItemProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
24
- /** A divider line between option groups. */
36
+ /** A divider line between option groups.
37
+ *
38
+ * Part of the `select` family a project can replace (`primitives={{ select: ... }}` on KarniProvider).
39
+ */
25
40
  export declare const SelectSeparator: React.ForwardRefExoticComponent<Omit<SelectPrimitive.SelectSeparatorProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
26
41
  export interface SelectOption {
27
42
  /** Must be non-empty and not the reserved sentinel. */
@@ -38,7 +53,8 @@ export interface SelectProps {
38
53
  placeholder?: React.ReactNode;
39
54
  /**
40
55
  * Renders a clear item at the top of the list (contract D8): pass it for
41
- * nullable fields (label "—" by default) and filter contexts (e.g. "All").
56
+ * nullable fields (labelled with `EmptyValue` by default, a dash read as
57
+ * "empty") and filter contexts (e.g. "All").
42
58
  * Choosing it emits `null`.
43
59
  */
44
60
  clearable?: boolean;
@@ -51,6 +67,21 @@ export interface SelectProps {
51
67
  className?: string;
52
68
  contentClassName?: string;
53
69
  "aria-label"?: string;
70
+ /** Forwarded to the trigger, like the other `aria-*` props below. */
71
+ "aria-labelledby"?: string;
72
+ /** Ids of the help text and error message that describe the field. */
73
+ "aria-describedby"?: string;
74
+ /** Marks the field invalid: the trigger reads it and draws its destructive border. */
75
+ "aria-invalid"?: React.AriaAttributes["aria-invalid"];
76
+ "aria-required"?: boolean | "true" | "false";
77
+ /**
78
+ * Called when focus leaves the select, never when it moves into the open
79
+ * list, so react-hook-form's onBlur validation does not run while the user
80
+ * is still choosing.
81
+ */
82
+ onBlur?: React.FocusEventHandler<HTMLButtonElement>;
83
+ /** Called when the list opens or closes. */
84
+ onOpenChange?: (open: boolean) => void;
54
85
  /** Extra <SelectItem>/<SelectGroup> nodes appended after `options` (escape hatch). */
55
86
  children?: React.ReactNode;
56
87
  }
@@ -1,4 +1,7 @@
1
1
  import * as React from "react";
2
2
  export type SkeletonProps = React.HTMLAttributes<HTMLDivElement>;
3
- /** Loading placeholder block; size it with width/height utility classes. */
3
+ /** Loading placeholder block that you size with width/height utility classes.
4
+ *
5
+ * A project replaces it with its own through `primitives={{ skeleton: ... }}` on KarniProvider.
6
+ */
4
7
  export declare const Skeleton: React.ForwardRefExoticComponent<SkeletonProps & React.RefAttributes<HTMLDivElement>>;
@@ -1,5 +1,8 @@
1
1
  import * as SwitchPrimitive from "@radix-ui/react-switch";
2
2
  import * as React from "react";
3
3
  export type SwitchProps = React.ComponentPropsWithoutRef<typeof SwitchPrimitive.Root>;
4
- /** Themed on/off toggle (Radix Switch). */
4
+ /** Themed on/off toggle (Radix Switch).
5
+ *
6
+ * A project replaces it with its own through `primitives={{ switch: ... }}` on KarniProvider.
7
+ */
5
8
  export declare const Switch: React.ForwardRefExoticComponent<Omit<SwitchPrimitive.SwitchProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
@@ -1,10 +1,10 @@
1
1
  import * as TabsPrimitive from "@radix-ui/react-tabs";
2
2
  import * as React from "react";
3
- /** Tab set root (Radix Tabs); holds the selected value. */
3
+ /** Tab set root (Radix Tabs) that holds the selected value. */
4
4
  export declare const Tabs: React.ForwardRefExoticComponent<TabsPrimitive.TabsProps & React.RefAttributes<HTMLDivElement>>;
5
5
  /** The row of tab buttons. */
6
6
  export declare const TabsList: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsListProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
7
- /** One tab button; `value` names the panel it opens. */
7
+ /** One tab button, whose `value` names the panel it opens. */
8
8
  export declare const TabsTrigger: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>>;
9
9
  /** The panel shown while its `value` is the selected tab. */
10
10
  export declare const TabsContent: React.ForwardRefExoticComponent<Omit<TabsPrimitive.TabsContentProps & React.RefAttributes<HTMLDivElement>, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -1,4 +1,7 @@
1
1
  import * as React from "react";
2
2
  export type TextareaProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;
3
- /** Themed multi-line text input; every native `<textarea>` prop passes through. */
3
+ /** Themed multi-line text input that passes every native `<textarea>` prop through.
4
+ *
5
+ * A project replaces it with its own through `primitives={{ textarea: ... }}` on KarniProvider.
6
+ */
4
7
  export declare const Textarea: React.ForwardRefExoticComponent<TextareaProps & React.RefAttributes<HTMLTextAreaElement>>;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Barrel-facing path for <ResourceDetail>. The implementation lives in
3
- * ./detail/ (resource form builder); every contract-pinned export name is
3
+ * ./detail/ (resource form builder). Every contract-pinned export name is
4
4
  * re-exported here so src/index.ts keeps resolving this module.
5
5
  */
6
- export { ResourceDetail } from './detail/ResourceDetail';
7
- export type { ResourceDetailProps, ResourceDetailFieldOverride } from './detail/ResourceDetail';
6
+ export { ResourceDetail, defaultResourceDetailSlots } from './detail/ResourceDetail';
7
+ export type { ResourceDetailProps, ResourceDetailFieldOverride, ResourceDetailSlots, ResourceDetailLoadingProps, ResourceDetailErrorProps, ResourceDetailNotFoundProps, ResourceDetailDeletedProps, ResourceDetailEmptyValueProps, ResourceDetailDeleteDialogProps, } from './detail/ResourceDetail';