@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
@@ -0,0 +1,67 @@
1
+ import { FilterValue } from '../../../hooks/types';
2
+ import { FieldDef } from '../../../schema/types';
3
+ /**
4
+ * Where a table keeps its view state. The table only touches its own
5
+ * (namespaced) keys.
6
+ *
7
+ * An adapter for a router reads the router's current search params in
8
+ * `read`, navigates in `write`, and either calls the `subscribe` listener
9
+ * when the params change or simply passes a new store object on each change
10
+ * (the table re-reads whenever the store changes).
11
+ */
12
+ export interface ResourceTableStateStore {
13
+ /** Every current query value by key. */
14
+ read(): Readonly<Record<string, string>>;
15
+ /**
16
+ * Merge `changes` into the URL (`null` removes a key). `push` adds a
17
+ * history entry (page, sort, filter changes). Otherwise the current entry
18
+ * is replaced (a search refined while typing, a page clamped).
19
+ */
20
+ write(changes: Readonly<Record<string, string | null>>, options: {
21
+ push: boolean;
22
+ }): void;
23
+ /** Call `onChange` when the values change outside the table. Returns an unsubscribe. */
24
+ subscribe(onChange: () => void): () => void;
25
+ }
26
+ /**
27
+ * The default store: the browser's URL through `window.history`
28
+ * (`pushState` / `replaceState`, `popstate` for Back and Forward). Other
29
+ * query parameters and the hash are kept.
30
+ */
31
+ export declare function createHistoryStateStore(): ResourceTableStateStore;
32
+ /** The view a table shows: what the URL holds for it. */
33
+ export interface TableView {
34
+ page: number;
35
+ pageSize: number;
36
+ /** DRF ordering ("-starts_at,title"), where "" is "no ordering". */
37
+ ordering: string;
38
+ search: string;
39
+ filters: Readonly<Record<string, FilterValue>>;
40
+ }
41
+ export interface TableViewDefaults {
42
+ pageSize: number;
43
+ ordering: string;
44
+ /** Page sizes a URL may ask for. Anything else falls back to the default. */
45
+ pageSizes: readonly number[];
46
+ }
47
+ /** Encodes one filter value for the URL (arrays comma-joined). */
48
+ export declare function encodeFilter(value: FilterValue): string | null;
49
+ /** Decodes a URL value back to the type the filter control and the API expect. */
50
+ export declare function decodeFilter(raw: string, field: FieldDef): FilterValue;
51
+ /** Drops cleared filters so "no filter" has one representation. */
52
+ export declare function compactFilters(filters: Readonly<Record<string, FilterValue>>): Record<string, FilterValue>;
53
+ export type SetTableView = (patch: Partial<TableView>, options: {
54
+ push: boolean;
55
+ }) => void;
56
+ export interface UseTableViewOptions {
57
+ /** False keeps the view in component state only (the URL is untouched). */
58
+ syncUrl: boolean;
59
+ /** Where the URL state lives. Default: `window.history`. */
60
+ store?: ResourceTableStateStore;
61
+ namespace: string;
62
+ defaults: TableViewDefaults;
63
+ /** The fields the filter bar offers: only these are read from the URL. */
64
+ filterFields: readonly FieldDef[];
65
+ }
66
+ /** The table's view state, from the URL (or component state) with a setter. */
67
+ export declare function useTableView(options: UseTableViewOptions): [TableView, SetTableView];
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The width of an element, kept current with a ResizeObserver. ResourceTable
3
+ * switches to cards from its own container's width, not the viewport's, so a
4
+ * table in a narrow sidebar or a split view lays out for the room it has.
5
+ * `undefined` until measured (and where there is no layout, as in jsdom), so
6
+ * the table keeps its default layout rather than guessing.
7
+ * Internal to @karni/ui.
8
+ */
9
+ import * as React from "react";
10
+ export declare function useContainerWidth(ref: React.RefObject<HTMLElement | null>): number | undefined;
@@ -0,0 +1,78 @@
1
+ import { RequestHandler } from 'msw';
2
+ import { ChangeHistoryPage } from '../../hooks/useChangeHistory';
3
+ import { ImportDescription, ImportJob } from '../../hooks/useResourceImport';
4
+ /** The OpenAPI document `/api/schema/` serves for the testapp (a superuser's view). */
5
+ export declare const testappOpenApi: Record<string, unknown>;
6
+ /**
7
+ * The per-role document of a role `Karni.field_roles` hides `fields` of the
8
+ * Event from: the fields are absent from it, as they are from the payload.
9
+ */
10
+ export declare function openApiHidingEventFields(fields: readonly string[]): Record<string, unknown>;
11
+ export declare const DEMO_EVENT_ID = "3f6f75d8-f305-48a8-9587-32884b211936";
12
+ export declare const demoVenue: {
13
+ id: string;
14
+ name: string;
15
+ city: string;
16
+ };
17
+ /** A testapp Event as `GET /api/events/<id>/?expand=venue` returns it. */
18
+ export declare const demoEvent: Record<string, unknown>;
19
+ /** The same Event as a role that may not read `fields` receives it: the keys are absent, not null. */
20
+ export declare function eventHiding(fields: readonly string[]): Record<string, unknown>;
21
+ /**
22
+ * How one endpoint answers: "ok", "pending" (never answers: the loading
23
+ * state), "network" (the request fails, as offline), a status with an
24
+ * optional DRF body, or a delay before answering "ok".
25
+ */
26
+ export type Outcome = "ok" | "pending" | "network" | {
27
+ status: number;
28
+ body?: unknown;
29
+ } | {
30
+ delayMs: number;
31
+ };
32
+ /** The 404 Karni answers for a row outside the read scope: the scope is named, existence is not. */
33
+ export declare const SCOPED_NOT_FOUND: {
34
+ readonly status: 404;
35
+ readonly body: {
36
+ readonly detail: "No Event matches the given query. Rows are limited by EventViewSet.access read=Granted().";
37
+ };
38
+ };
39
+ export interface DetailBackend {
40
+ schema?: Outcome;
41
+ /** The document `/api/schema/` serves (default: the testapp snapshot). */
42
+ openApi?: Record<string, unknown>;
43
+ event?: Outcome;
44
+ /** The payload `GET /api/events/<id>/` answers (default: demoEvent). */
45
+ record?: Record<string, unknown>;
46
+ remove?: Outcome;
47
+ history?: Outcome;
48
+ historyPage?: ChangeHistoryPage;
49
+ }
50
+ /** msw handlers for the schema, one Event, its delete and its history. */
51
+ export declare function detailBackend(options?: DetailBackend): RequestHandler[];
52
+ export declare const demoHistory: ChangeHistoryPage;
53
+ /** What `GET /api/events/import/` answers. */
54
+ export declare const demoImportDescription: ImportDescription;
55
+ export declare const DEMO_JOB_ID = "6b1f0e9a-3c2d-4e5f-8a7b-9c0d1e2f3a4b";
56
+ /** An import job in any state (default: just uploaded). */
57
+ export declare function demoJob(partial?: Partial<ImportJob>): ImportJob;
58
+ /** A preview: most rows import, two are refused with the reason. */
59
+ export declare const previewedJob: ImportJob;
60
+ export declare const doneJob: ImportJob;
61
+ export interface ImportBackend {
62
+ describe?: Outcome;
63
+ upload?: Outcome;
64
+ /** The job uploads create (default: demoJob()). */
65
+ uploaded?: ImportJob;
66
+ /** Answers of `GET .../import/<job>/` after a preview or commit started, in turn: polling. */
67
+ polls?: readonly ImportJob[];
68
+ preview?: Outcome;
69
+ previewed?: ImportJob;
70
+ commit?: Outcome;
71
+ committed?: ImportJob;
72
+ }
73
+ /**
74
+ * msw handlers for `/api/events/import/…`: describe, upload, poll, preview,
75
+ * commit. `GET .../import/<job>/` answers the job as the last step left it
76
+ * (after `polls`, while those last), as the backend does.
77
+ */
78
+ export declare function importBackend(options?: ImportBackend): RequestHandler[];
@@ -1,24 +1,54 @@
1
1
  import * as React from "react";
2
+ /** What the `Label`, `Value` and `Icon` slots receive. */
3
+ export interface StatSlotProps {
4
+ children: React.ReactNode;
5
+ }
6
+ /** What the `Delta` slot receives. */
7
+ export interface StatDeltaProps {
8
+ /** The `delta` prop: a number, or any node. */
9
+ delta: number | React.ReactNode;
10
+ /** The `formatDelta` prop (or the default signed format). */
11
+ formatDelta: (delta: number) => string;
12
+ }
13
+ /** The parts of the tile a project can replace (`slots={{ Delta: GoodWhenDown }}`). */
14
+ export interface StatSlots {
15
+ /** What the number measures. */
16
+ Label: React.ComponentType<StatSlotProps>;
17
+ /** The headline value. */
18
+ Value: React.ComponentType<StatSlotProps>;
19
+ /** The change indicator. The default treats a rise as neutral and a fall as destructive. */
20
+ Delta: React.ComponentType<StatDeltaProps>;
21
+ /** Wraps `icon`. Decorative, hidden from assistive tech. */
22
+ Icon: React.ComponentType<StatSlotProps>;
23
+ }
2
24
  export interface StatProps extends React.HTMLAttributes<HTMLDivElement> {
3
25
  /** What the number measures ("Active users"). */
4
26
  label: React.ReactNode;
5
- /** The headline value — preformatted string or any node. */
27
+ /** The headline value: a preformatted string or any node. */
6
28
  value: React.ReactNode;
7
29
  /**
8
- * Change indicator. A number renders a signed figure with an up/down arrow
9
- * (negative numbers use the destructive token) plus screen-reader context;
10
- * any other node renders verbatim in the delta slot.
30
+ * Change indicator. A number renders a signed figure with an up, down or
31
+ * flat marker (negative numbers use the destructive token) plus
32
+ * screen-reader context. Any other node renders verbatim in the delta slot.
11
33
  */
12
34
  delta?: number | React.ReactNode;
13
35
  /** Formats a numeric `delta` (default: signed, locale formatted). */
14
36
  formatDelta?: (delta: number) => string;
15
37
  /** Optional icon slot, top-right. */
16
38
  icon?: React.ReactNode;
39
+ /** True while the value loads: a placeholder replaces the value and the delta. */
40
+ loading?: boolean;
41
+ /** Replaces the Label, Value, Delta or Icon. See StatSlots. */
42
+ slots?: Partial<StatSlots>;
17
43
  }
44
+ /** The default pieces of the tile, for wrapping one. */
45
+ export declare const defaultStatSlots: StatSlots;
18
46
  /**
19
47
  * KPI tile: label, value, optional delta and icon.
20
48
  *
21
- * Escape hatches: `value`/`delta`/`icon` accept arbitrary nodes,
22
- * `formatDelta` overrides numeric formatting, `className` merges via cn().
49
+ * Escape hatches: `slots` replaces the Label, Value, Delta or Icon (a Delta
50
+ * for metrics where down is good, say), `value`/`delta`/`icon` accept
51
+ * arbitrary nodes, `formatDelta` overrides numeric formatting, and `className`
52
+ * merges via cn().
23
53
  */
24
54
  export declare const Stat: React.ForwardRefExoticComponent<StatProps & React.RefAttributes<HTMLDivElement>>;
@@ -31,6 +31,6 @@ export declare function subscribeToToasts(listener: Listener): () => void;
31
31
  export declare function getToasts(): readonly ToastInstance[];
32
32
  export declare function dismissToast(id?: string): void;
33
33
  export declare function addToast(options: ToastOptions): ToastHandle;
34
- /** Test helper — clears all toasts and listeners' state (not in the barrel). */
34
+ /** Test helper: clears all toasts and listeners' state (not in the barrel). */
35
35
  export declare function resetToastStore(): void;
36
36
  export {};
@@ -1,8 +1,9 @@
1
1
  import { VariantProps } from '../../lib/variants';
2
+ import { ToastVariant } from './toast-store';
2
3
  import * as React from "react";
3
4
  /**
4
- * Per-variant toast styling — colors resolve through --karni-* tokens only
5
- * (success leans on primary, error on destructive, info on muted).
5
+ * Per-variant toast styling, with colors that resolve through --karni-* tokens
6
+ * only (success leans on primary, error on destructive, info on muted).
6
7
  */
7
8
  export declare const toastVariants: import('../..').VariantFn<{
8
9
  variant: {
@@ -13,6 +14,28 @@ export declare const toastVariants: import('../..').VariantFn<{
13
14
  };
14
15
  }>;
15
16
  export type ToastVariantProps = VariantProps<typeof toastVariants>;
17
+ /** What the `Title` and `Description` slots receive. */
18
+ export interface ToasterSlotProps {
19
+ children: React.ReactNode;
20
+ variant: ToastVariant;
21
+ }
22
+ /** What the `CloseButton` slot receives. */
23
+ export interface ToasterCloseButtonProps {
24
+ /** Its accessible name ("Dismiss"). */
25
+ label: string;
26
+ variant: ToastVariant;
27
+ /** Removes the toast. */
28
+ dismiss: () => void;
29
+ }
30
+ /** The parts of each toast a project can replace (`slots={{ CloseButton: MyClose }}`). */
31
+ export interface ToasterSlots {
32
+ Title: React.ComponentType<ToasterSlotProps>;
33
+ Description: React.ComponentType<ToasterSlotProps>;
34
+ /** The top-right dismiss button. */
35
+ CloseButton: React.ComponentType<ToasterCloseButtonProps>;
36
+ }
37
+ /** The default pieces of a toast, for wrapping one. */
38
+ export declare const defaultToasterSlots: ToasterSlots;
16
39
  export interface ToasterProps {
17
40
  /** Auto-dismiss duration in ms for toasts without their own (default 5000). */
18
41
  duration?: number;
@@ -20,14 +43,24 @@ export interface ToasterProps {
20
43
  swipeDirection?: "right" | "left" | "up" | "down";
21
44
  /** Accessible label prefix announced with each toast (Radix Provider). */
22
45
  label?: string;
23
- /** Extra classes for the viewport (position/stacking escape hatch). */
46
+ /** Extra classes for the viewport, the list the toasts render in. */
47
+ className?: string;
48
+ /** Same as `className` (kept for compatibility). */
24
49
  viewportClassName?: string;
25
50
  /** Extra classes for every toast root. */
26
51
  toastClassName?: string;
52
+ /** Replaces the Title, Description or CloseButton of every toast (see ToasterSlots). */
53
+ slots?: Partial<ToasterSlots>;
27
54
  }
28
55
  /**
29
56
  * Renders the toasts pushed through useToast(). Mount exactly one near the
30
- * app root. Position defaults to the bottom-right corner; override with
31
- * `viewportClassName` (it merges via cn(), so e.g. "top-0 bottom-auto" wins).
57
+ * app root. Position defaults to the bottom-right corner. Override it with
58
+ * `className` (it merges via cn(), so e.g. "top-0 bottom-auto" wins).
59
+ *
60
+ * Keyboard and screen readers: an error is announced at once (assertive),
61
+ * other toasts politely. F8 moves focus to the toasts. Tab reaches each one
62
+ * and its buttons, and Escape dismisses the focused toast. A toast's timer
63
+ * pauses while the pointer is over the toasts or focus is inside them, and
64
+ * while the window is in the background.
32
65
  */
33
- export declare function Toaster({ duration, swipeDirection, label, viewportClassName, toastClassName, }: ToasterProps): React.ReactElement;
66
+ export declare function Toaster({ duration, swipeDirection, label, className, viewportClassName, toastClassName, slots, }: ToasterProps): React.ReactElement;
@@ -18,7 +18,7 @@ export interface UseToastResult {
18
18
  * Imperative toast API + the live toast list.
19
19
  *
20
20
  * Requires a mounted <Toaster/> somewhere in the tree to actually render the
21
- * toasts; the functions themselves are stable module-level references, safe
21
+ * toasts. The functions themselves are stable module-level references, safe
22
22
  * to use in effects and callbacks without dependency churn.
23
23
  */
24
24
  export declare function useToast(): UseToastResult;
@@ -1,24 +1,24 @@
1
1
  import { ResourceItemParams, ResourceListParams } from './types';
2
- /** TanStack Query keys for the schema, a resource, its lists and its items; invalidate `karniKeys.resource(name)` to refresh every view of a resource. */
2
+ /** TanStack Query keys for the schema, a resource, its lists and its items (invalidate `karniKeys.resource(name)` to refresh every view of a resource). */
3
3
  export declare const karniKeys: {
4
4
  /** Cache key of the parsed OpenAPI schema document. */
5
5
  schema(): readonly unknown[];
6
6
  /**
7
- * The invalidation root for one resource — every list and detail key of
7
+ * The invalidation root for one resource. Every list and detail key of
8
8
  * `resource` lives under this prefix.
9
9
  */
10
10
  resource(resource: string): readonly unknown[];
11
11
  /**
12
12
  * Key for one list page. Params are normalized (D4): undefined/null
13
13
  * dropped, keys sorted recursively, values stringified as they hit the
14
- * query string — `{search: "x", page: 2}` and `{page: 2, search: "x"}`
14
+ * query string, so `{search: "x", page: 2}` and `{page: 2, search: "x"}`
15
15
  * share one entry.
16
16
  */
17
17
  list(resource: string, params?: ResourceListParams): readonly unknown[];
18
18
  /**
19
19
  * Key for one item. The id is always stringified, so `detail("events", 5)`
20
20
  * and `detail("events", "5")` share one entry. Params (expand) append a
21
- * normalized fifth element — see the module docs.
21
+ * normalized fifth element (see the module docs).
22
22
  */
23
23
  detail(resource: string, id: string | number, params?: ResourceItemParams): readonly unknown[];
24
24
  };
@@ -2,20 +2,20 @@
2
2
  * Mutation body encoding (contract D2) + PATCH file-URL omission.
3
3
  *
4
4
  * D2: after the form's `transformSubmit` has run, a payload containing any
5
- * top-level `File` (or an array containing one) is encoded as `FormData`;
6
- * everything else is sent as JSON. FormData rules:
5
+ * top-level `File` (or an array containing one) is encoded as `FormData`.
6
+ * Everything else is sent as JSON. FormData rules:
7
7
  * - arrays append as repeated keys,
8
8
  * - plain objects/arrays-of-objects (json fields) are JSON-stringified,
9
9
  * - numbers/booleans are stringified,
10
- * - `null`/`undefined` entries are OMITTED — multipart cannot express null
11
- * (DRF would parse the string "null"); to null a field, send a JSON PATCH
10
+ * - `null`/`undefined` entries are OMITTED: multipart cannot express null
11
+ * (DRF would parse the string "null"). To null a field, send a JSON PATCH
12
12
  * without the file field instead,
13
13
  * - Content-Type is never set manually: fetch derives the multipart boundary.
14
14
  *
15
15
  * PATCH omission (D2): a string value on a file-kind field is the unchanged
16
- * display URL the backend returned (D1: response-side `format: uri`); sending
16
+ * display URL the backend returned (D1: response-side `format: uri`). Sending
17
17
  * it back would 400 ("not a file"), so it is dropped from PATCH payloads.
18
- * Field kinds come from the cached parsed schema when present; without it the
18
+ * Field kinds come from the cached parsed schema when present. Without it the
19
19
  * payload passes through untouched (graceful degradation).
20
20
  *
21
21
  * Internal module: not re-exported from the barrel.
@@ -37,8 +37,8 @@ export declare function encodeBody(values: Record<string, unknown>): Record<stri
37
37
  */
38
38
  export declare function fileFieldNames(cachedSchema: unknown, resource: string): ReadonlySet<string>;
39
39
  /**
40
- * Drops unchanged file values — strings (or arrays of strings) on file-kind
41
- * fields are the backend's display URLs, not new uploads — from a PATCH
42
- * payload (D2).
40
+ * Drops unchanged file values from a PATCH payload (D2). Strings (or arrays
41
+ * of strings) on file-kind fields are the backend's display URLs, not new
42
+ * uploads.
43
43
  */
44
44
  export declare function omitUnchangedFileUrls(values: Record<string, unknown>, fileFields: ReadonlySet<string>): Record<string, unknown>;
@@ -21,5 +21,5 @@ export declare function buildItemQuery(params?: ResourceItemParams): Record<stri
21
21
  export declare function normalizeParams(value: Record<string, unknown>): NormalizedParams;
22
22
  /** `/events/` for resource "events" (the DRF router prefix, api B13). */
23
23
  export declare function listPath(resource: string): string;
24
- /** `/events/5/` — ids are encoded so UUIDs and slugs are safe. */
24
+ /** `/events/5/`. Ids are encoded so UUIDs and slugs are safe. */
25
25
  export declare function detailPath(resource: string, id: string | number): string;
@@ -8,7 +8,7 @@ export interface Paginated<T> {
8
8
  }
9
9
  /**
10
10
  * A single filter value as accepted by the backend's FilterSet (api B5/B6).
11
- * Arrays become repeated query keys; `null`/`undefined` entries are dropped
11
+ * Arrays become repeated query keys. `null`/`undefined` entries are dropped
12
12
  * (they mean "no constraint", not "filter by null").
13
13
  */
14
14
  export type FilterValue = string | number | boolean | ReadonlyArray<string | number | boolean> | null | undefined;
@@ -20,7 +20,7 @@ export interface ResourceListParams {
20
20
  pageSize?: number;
21
21
  /** DRF ordering expression, e.g. `"-created_at"` (`ordering`). */
22
22
  ordering?: string;
23
- /** Full-text search term (`search`); empty string is treated as unset. */
23
+ /** Full-text search term (`search`). An empty string is treated as unset. */
24
24
  search?: string;
25
25
  /** Extra filter params, spread flat into the query string. Explicit params above win on a name clash. */
26
26
  filters?: Record<string, FilterValue>;
@@ -34,7 +34,7 @@ export interface ResourceItemParams {
34
34
  export interface ResourceMutationsOptions {
35
35
  /**
36
36
  * Extra query keys invalidated after any successful mutation, on top of
37
- * ["karni", resource]. Never pass the bare ["karni"] root (D4) — it would
37
+ * ["karni", resource]. Never pass the bare ["karni"] root (D4). It would
38
38
  * also invalidate the cached schema document.
39
39
  */
40
40
  invalidateKeys?: readonly QueryKey[];
@@ -1,5 +1,6 @@
1
1
  import { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
2
- export type ChangeAction = "create" | "update" | "delete" | "restore";
2
+ /** `transition`: a StateField moved along a declared transition (`diff` holds `[from, to]`). */
3
+ export type ChangeAction = "create" | "update" | "delete" | "restore" | "transition";
3
4
  export interface ChangeLogEntry {
4
5
  id: number;
5
6
  timestamp: string;
@@ -8,7 +9,7 @@ export interface ChangeLogEntry {
8
9
  email?: string;
9
10
  } | null;
10
11
  action: ChangeAction;
11
- /** `{ field: [old, new] }`; a redacted field carries `"[redacted]"` on both sides. */
12
+ /** `{ field: [old, new] }`. A redacted field carries `"[redacted]"` on both sides. */
12
13
  diff: Record<string, [unknown, unknown]>;
13
14
  fields_changed: string[];
14
15
  }
@@ -26,7 +26,7 @@ export interface ImportDescription {
26
26
  fields: ImportColumn[];
27
27
  /** The mapping the user last confirmed for this resource (header → field). */
28
28
  mapping: Record<string, string>;
29
- /** Files with more rows than this run in Celery; poll the job. */
29
+ /** Files with more rows than this run in Celery. Poll the job. */
30
30
  inline_rows: number;
31
31
  max_rows: number;
32
32
  key: string | null;
@@ -91,7 +91,7 @@ export interface UseImportJobOptions {
91
91
  export declare function useImportJob(resource: string, jobId: string | undefined, options?: UseImportJobOptions): UseQueryResult<ImportJob>;
92
92
  export interface UploadImportInput {
93
93
  file: File;
94
- /** An explicit mapping; otherwise the backend suggests or remembers one. */
94
+ /** An explicit mapping. Otherwise the backend suggests or remembers one. */
95
95
  mapping?: Record<string, string>;
96
96
  }
97
97
  export interface PreviewImportInput {
@@ -1,6 +1,6 @@
1
1
  import { UseMutationResult } from '@tanstack/react-query';
2
2
  import { ResourceMutationsOptions } from './types';
3
- /** Result bundle — the three mutations, each a full TanStack mutation object. */
3
+ /** Result bundle: the three mutations, each a full TanStack mutation object. */
4
4
  export interface ResourceMutations<T> {
5
5
  create: UseMutationResult<T, unknown, Record<string, unknown>>;
6
6
  update: UseMutationResult<T, unknown, {
@@ -9,5 +9,5 @@ export interface ResourceMutations<T> {
9
9
  }>;
10
10
  remove: UseMutationResult<void, unknown, string | number>;
11
11
  }
12
- /** Create, update and remove mutations for a resource; each success refreshes the resource's cached lists and items. */
12
+ /** Create, update and remove mutations for a resource that refresh the resource's cached lists and items on each success. */
13
13
  export declare function useResourceMutations<T = Record<string, unknown>>(resource: string, options?: ResourceMutationsOptions): ResourceMutations<T>;
@@ -0,0 +1,31 @@
1
+ /** What the leave warning says. Browsers show their own text for reloads and closed tabs. */
2
+ export declare const UNSAVED_CHANGES_MESSAGE = "You have unsaved changes. Leave without saving them?";
3
+ export interface UseUnsavedChangesGuardOptions {
4
+ /** Controlled mode: whether something is unsaved. Omit it and call `setDirty` instead. */
5
+ dirty?: boolean;
6
+ /** Turns the guard off without unmounting it (an opt-out). Default true. */
7
+ enabled?: boolean;
8
+ /** Warn on reload, tab close and links out of the app (`beforeunload`). Default true. */
9
+ beforeUnload?: boolean;
10
+ /** The question `shouldBlock` asks. Default UNSAVED_CHANGES_MESSAGE. */
11
+ message?: string;
12
+ /** Asks the user whether to leave and returns true to leave. Default `window.confirm`. */
13
+ confirm?: (message: string) => boolean;
14
+ }
15
+ export interface UnsavedChangesGuard {
16
+ /** Whether something is unsaved (re-renders when it changes). */
17
+ dirty: boolean;
18
+ /** Reports the form's state. Pass it as ResourceForm's `onDirtyChange`. */
19
+ setDirty: (dirty: boolean) => void;
20
+ /** The latest state, read synchronously (for a router blocker's callback). */
21
+ isDirty: () => boolean;
22
+ /**
23
+ * For a router's blocker: true when there are unsaved changes and the user
24
+ * chose to stay, false (let the navigation happen) otherwise.
25
+ */
26
+ shouldBlock: () => boolean;
27
+ /** The question asked before leaving. */
28
+ message: string;
29
+ }
30
+ /** Tracks unsaved changes, warns on reload or close, and gives a router blocker what it needs. */
31
+ export declare function useUnsavedChangesGuard(options?: UseUnsavedChangesGuardOptions): UnsavedChangesGuard;
@@ -4,13 +4,24 @@
4
4
  * Every public name of the package is re-exported here, explicitly, so that
5
5
  * scripts/generate-manifest.mjs can parse this file and emit dist/manifest.json
6
6
  * (contract D7). Keep exports in the explicit `export { ... } from` /
7
- * `export type { ... } from` form — no `export *`.
7
+ * `export type { ... } from` form, never `export *`.
8
8
  */
9
9
  export { cn } from './lib/cn';
10
10
  export { registerComponentRole, getRoleRegistry } from './lib/roles';
11
11
  export type { RegisterComponentRoleOptions } from './lib/roles';
12
12
  export { variants } from './lib/variants';
13
13
  export type { VariantShape, VariantSelection, VariantsConfig, VariantFn, VariantProps, } from './lib/variants';
14
+ export { describeError, errorSummary } from './states/describe-error';
15
+ export type { KarniErrorContext, KarniErrorDescription, KarniErrorKind, } from './states/describe-error';
16
+ export { ErrorState, PermissionDenied } from './states/ErrorState';
17
+ export type { ErrorSlotProps, ErrorStateProps, PermissionDeniedProps } from './states/ErrorState';
18
+ export { PendingButton } from './states/PendingButton';
19
+ export type { PendingButtonProps } from './states/PendingButton';
20
+ export { SLOW_AFTER_MS, useDelayedFlag, useOnlineStatus } from './states/hooks';
21
+ export { KARNI_THEME_SCRIPT, THEME_STORAGE_KEY, ThemeProvider, ThemeToggle, useTheme, } from './theme/theme';
22
+ export type { ResolvedTheme, ThemePreference, ThemeProviderProps, ThemeToggleProps, } from './theme/theme';
23
+ export { KarniPrimitivesProvider, useKarniPrimitives } from './primitives/registry';
24
+ export type { KarniPrimitiveOverrides, KarniPrimitiveRole, KarniPrimitives, KarniPrimitivesProviderProps, KarniSelectParts, } from './primitives/registry';
14
25
  export { Button, buttonVariants } from './components/primitives/button';
15
26
  export type { ButtonProps, ButtonVariantProps } from './components/primitives/button';
16
27
  export { Input } from './components/primitives/input';
@@ -31,21 +42,28 @@ export { Skeleton } from './components/primitives/skeleton';
31
42
  export type { SkeletonProps } from './components/primitives/skeleton';
32
43
  export { ResourceTable } from './components/resource/ResourceTable';
33
44
  export type { ResourceTableProps, ResourceColumn, BulkAction, } from './components/resource/ResourceTable';
34
- export { ResourceForm } from './components/resource/ResourceForm';
35
- export type { ResourceFormProps, FieldOverride, KarniFieldProps, } from './components/resource/ResourceForm';
36
- export { ResourceDetail } from './components/resource/ResourceDetail';
37
- export type { ResourceDetailProps, ResourceDetailFieldOverride, } from './components/resource/ResourceDetail';
38
- export { ResourceSelect } from './components/resource/ResourceSelect';
39
- export type { ResourceSelectProps } from './components/resource/ResourceSelect';
40
- export { ChangeHistory } from './components/resource/history/ChangeHistory';
41
- export type { ChangeHistoryProps } from './components/resource/history/ChangeHistory';
45
+ export { createHistoryStateStore, defaultResourceTableSlots, } from './components/resource/ResourceTable';
46
+ export type { BulkActionReturn, BulkOutcome, BulkResult, ResourceTableActiveFilter, ResourceTableBulkActionsProps, ResourceTableCardField, ResourceTableDensity, ResourceTableForbiddenProps, ResourceTableKeyboardNavigation, ResourceTableLoadingProps, ResourceTableNoRecordsProps, ResourceTableNoResultsProps, ResourceTableOfflineProps, ResourceTablePaginationProps, ResourceTableRowCardProps, ResourceTableSlots, ResourceTableStateStore, } from './components/resource/ResourceTable';
47
+ export type { KarniAccessRule } from './schema/types';
48
+ export { ResourceForm, defaultResourceFormSlots } from './components/resource/ResourceForm';
49
+ export type { ResourceFormProps, ResourceFormSlots, ResourceFormErrorSummaryProps, ResourceFormFieldError, ResourceFormForbiddenProps, ResourceFormLoadingProps, ResourceFormSubmitErrorProps, FieldOverride, KarniFieldProps, } from './components/resource/ResourceForm';
50
+ export { UNSAVED_CHANGES_MESSAGE, useUnsavedChangesGuard } from './hooks/useUnsavedChangesGuard';
51
+ export type { UnsavedChangesGuard, UseUnsavedChangesGuardOptions, } from './hooks/useUnsavedChangesGuard';
52
+ export { ResourceDetail, defaultResourceDetailSlots } from './components/resource/ResourceDetail';
53
+ export type { ResourceDetailProps, ResourceDetailFieldOverride, ResourceDetailSlots, ResourceDetailLoadingProps, ResourceDetailErrorProps, ResourceDetailNotFoundProps, ResourceDetailDeletedProps, ResourceDetailEmptyValueProps, ResourceDetailDeleteDialogProps, } from './components/resource/ResourceDetail';
54
+ export { ResourceSelect, defaultResourceSelectSlots } from './components/resource/ResourceSelect';
55
+ export type { ResourceSelectProps, ResourceSelectSlots, ResourceSelectChipProps, ResourceSelectEmptyProps, ResourceSelectLoadingProps, ResourceSelectOptionProps, } from './components/resource/ResourceSelect';
56
+ export { ChangeHistory, defaultChangeHistorySlots, } from './components/resource/history/ChangeHistory';
57
+ export type { ChangeHistoryProps, ChangeHistorySlots, ChangeHistoryLoadingProps, ChangeHistoryEmptyProps, ChangeHistoryEntryProps, } from './components/resource/history/ChangeHistory';
42
58
  export { useChangeHistory, changeHistoryKey } from './hooks/useChangeHistory';
43
59
  export type { ChangeAction, ChangeLogEntry, ChangeHistoryPage, ChangeHistoryParams, } from './hooks/useChangeHistory';
44
- export { ResourceImport } from './components/resource/import/ResourceImport';
45
- export type { ResourceImportProps } from './components/resource/import/ResourceImport';
60
+ export { ResourceImport, defaultResourceImportSlots, } from './components/resource/import/ResourceImport';
61
+ export type { ResourceImportProps, ResourceImportSlots, ResourceImportLoadingProps, ImportProgressProps, ImportSummaryProps, ImportProblemsProps, ImportConfirmProps, } from './components/resource/import/ResourceImport';
46
62
  export { useImportDescribe, useImportJob, useImportActions, downloadResourceExport, downloadImportTemplate, importDescribeKey, importJobKey, isImportInFlight, } from './hooks/useResourceImport';
47
63
  export type { ExportFormat, ImportActions, ImportColumn, ImportColumnKind, ImportDescription, ImportJob, ImportProblem, ImportResult, ImportStatus, PreviewImportInput, UploadImportInput, UseImportJobOptions, } from './hooks/useResourceImport';
48
64
  export { displayLabel } from './components/resource/displayLabel';
65
+ export { EmptyValue, EMPTY_VALUE_LABEL } from './components/resource/empty-value';
66
+ export type { EmptyValueProps } from './components/resource/empty-value';
49
67
  export { AUDIT_FIELDS } from './components/resource/audit-fields';
50
68
  export { karniKeys } from './hooks/keys';
51
69
  export { useResourceList } from './hooks/useResourceList';
@@ -63,33 +81,33 @@ export { zodFor } from './schema/zod';
63
81
  export type { FieldKind, RelationRef, FieldDef, OperationDef, ResourceSchema, } from './schema/types';
64
82
  export { AuthProvider, useAuth } from './auth/provider';
65
83
  export type { AuthProviderProps, AuthContextValue } from './auth/provider';
66
- export { AuthGuard } from './auth/AuthGuard';
67
- export type { AuthGuardProps } from './auth/AuthGuard';
68
- export { LoginForm } from './auth/LoginForm';
69
- export type { LoginFormProps } from './auth/LoginForm';
84
+ export { AuthGuard, defaultAuthGuardSlots } from './auth/AuthGuard';
85
+ export type { AuthGuardProps, AuthGuardSlots, AuthGuardAnonymousProps, AuthGuardDeniedProps, } from './auth/AuthGuard';
86
+ export { LoginForm, defaultLoginFormSlots } from './auth/LoginForm';
87
+ export type { LoginFormProps, LoginFormSlots, LoginFormErrorProps } from './auth/LoginForm';
70
88
  export { usePermission } from './auth/usePermission';
71
89
  export type { PermissionHelpers } from './auth/usePermission';
72
90
  export { createLocalStorageTokenStorage, createMemoryTokenStorage } from './auth/storage';
73
91
  export type { TokenStorage } from './auth/storage';
74
92
  export type { KarniUser, AuthStatus, LoginResult } from './auth/types';
75
- export { Page } from './components/page/page';
76
- export type { PageProps } from './components/page/page';
77
- export { Layout } from './components/layout/layout';
78
- export type { LayoutProps, LayoutNavItem } from './components/layout/layout';
93
+ export { Page, defaultPageSlots } from './components/page/page';
94
+ export type { PageProps, PageSlots, PageHeaderProps, PageSlotContentProps, } from './components/page/page';
95
+ export { Layout, defaultLayoutSlots } from './components/layout/layout';
96
+ export type { LayoutProps, LayoutNavItem, LayoutSlots, LayoutHeaderProps, LayoutSidebarProps, LayoutNavLinkProps, LayoutMobileDrawerProps, LayoutSkipLinkProps, LayoutRouteFocusOptions, } from './components/layout/layout';
79
97
  export { KarniMark } from './components/brand/karni-mark';
80
98
  export type { KarniMarkProps } from './components/brand/karni-mark';
81
- export { Modal, ModalClose } from './components/modal/modal';
82
- export type { ModalProps } from './components/modal/modal';
83
- export { Toaster, toastVariants } from './components/toast/toaster';
84
- export type { ToasterProps, ToastVariantProps } from './components/toast/toaster';
99
+ export { Modal, ModalClose, ModalTitle, ModalDescription, defaultModalSlots, } from './components/modal/modal';
100
+ export type { ModalProps, ModalSlots, ModalHeaderProps, ModalFooterProps, ModalCloseButtonProps, } from './components/modal/modal';
101
+ export { Toaster, toastVariants, defaultToasterSlots } from './components/toast/toaster';
102
+ export type { ToasterProps, ToastVariantProps, ToasterSlots, ToasterSlotProps, ToasterCloseButtonProps, } from './components/toast/toaster';
85
103
  export { useToast } from './components/toast/use-toast';
86
104
  export type { UseToastResult } from './components/toast/use-toast';
87
105
  export type { ToastHandle, ToastInstance, ToastOptions, ToastVariant, } from './components/toast/toast-store';
88
- export { EmptyState } from './components/empty-state/empty-state';
89
- export type { EmptyStateProps } from './components/empty-state/empty-state';
90
- export { FileUpload } from './components/file-upload/FileUpload';
91
- export type { FileUploadProps, FileUploadRejectReason } from './components/file-upload/FileUpload';
92
- export { Stat } from './components/stat/stat';
93
- export type { StatProps } from './components/stat/stat';
94
- export { DataChart } from './components/chart/data-chart';
95
- export type { DataChartProps, DataChartKind, DataChartPoint, DataChartSeries, DataChartTooltipContext, } from './components/chart/data-chart';
106
+ export { EmptyState, defaultEmptyStateSlots } from './components/empty-state/empty-state';
107
+ export type { EmptyStateProps, EmptyStateSlots, EmptyStateSlotProps, } from './components/empty-state/empty-state';
108
+ export { FileUpload, defaultFileUploadSlots } from './components/file-upload/FileUpload';
109
+ export type { FileUploadProps, FileUploadRejectReason, FileUploadSlots, FileUploadDropzoneProps, FileUploadFileRowProps, } from './components/file-upload/FileUpload';
110
+ export { Stat, defaultStatSlots } from './components/stat/stat';
111
+ export type { StatProps, StatSlots, StatSlotProps, StatDeltaProps } from './components/stat/stat';
112
+ export { DataChart, defaultDataChartSlots } from './components/chart/data-chart';
113
+ export type { DataChartProps, DataChartKind, DataChartPoint, DataChartSeries, DataChartTooltipContext, DataChartSlots, DataChartTooltipProps, DataChartLegendProps, DataChartEmptyProps, DataChartLoadingProps, } from './components/chart/data-chart';