@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,20 @@
1
+ /** A 4xx answer is the server's decision (not found, forbidden, invalid): asking again gives the same answer. */
2
+ export declare function isClientError(error: unknown): boolean;
3
+ /**
4
+ * The query client's own retry policy, except that a 4xx is never retried:
5
+ * TanStack's default (three retries with backoff) would hold a "Couldn't
6
+ * find this event" back for seven seconds.
7
+ */
8
+ export declare function useRetryUnlessClientError(): (failureCount: number, error: unknown) => boolean;
9
+ /**
10
+ * Stands for "the request is waiting for a connection": TanStack Query
11
+ * pauses queries while the browser is offline instead of failing them, and
12
+ * this is what the Error slot receives then (describeError words it as
13
+ * "You're offline").
14
+ */
15
+ export declare function offlineError(): TypeError;
16
+ /**
17
+ * Calls `retry` when the connection comes back while `error` is a network
18
+ * failure, so an offline error clears itself without a click.
19
+ */
20
+ export declare function useRetryWhenOnline(error: unknown, retry: () => void): void;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * How a resource is named in a sentence: "No venues yet", "You can't edit
3
+ * this event". The router prefix is plural ("events", "loose-slots"). The
4
+ * singular is derived with English rules that cover the common cases, and a
5
+ * component's `resourceLabel` prop is the escape hatch for the rest
6
+ * ("people" / "person").
7
+ *
8
+ * Internal to @karni/ui.
9
+ */
10
+ /** The words a resource takes in a sentence, both lower case. */
11
+ export interface ResourceNames {
12
+ /** "event", "loose slot". */
13
+ singular: string;
14
+ /** "events", "loose slots". */
15
+ plural: string;
16
+ }
17
+ /** What a component's `resourceLabel` prop accepts: either word, or both. */
18
+ export interface ResourceLabel {
19
+ singular?: string;
20
+ plural?: string;
21
+ }
22
+ /** "loose-slots" → { singular: "loose slot", plural: "loose slots" }, unless `label` says otherwise. */
23
+ export declare function resourceNames(resource: string, label?: ResourceLabel): ResourceNames;
@@ -1,3 +1,5 @@
1
+ import { ResourceLabel } from '../resource-names';
2
+ import { ResourceSelectSlots } from './select-states';
1
3
  import * as React from "react";
2
4
  export interface ResourceSelectProps {
3
5
  resource: string;
@@ -12,7 +14,7 @@ export interface ResourceSelectProps {
12
14
  filter?: Record<string, string | number | boolean>;
13
15
  id?: string;
14
16
  className?: string;
15
- /** Page size for the option request (only the first page is fetched, D8). */
17
+ /** Rows per options request. Default: the API's own page size. More load as the list scrolls. */
16
18
  pageSize?: number;
17
19
  /** Set false to hide the clear affordance (which emits null). Default true. */
18
20
  clearable?: boolean;
@@ -20,17 +22,46 @@ export interface ResourceSelectProps {
20
22
  searchDebounceMs?: number;
21
23
  "aria-label"?: string;
22
24
  "aria-labelledby"?: string;
25
+ /** Ids of the help text and error message that describe the field. */
26
+ "aria-describedby"?: string;
27
+ /** Marks the field invalid (destructive border). */
28
+ "aria-invalid"?: boolean | "true" | "false";
29
+ "aria-required"?: boolean | "true" | "false";
30
+ /** Called when focus leaves the select (not when it moves into its own list). */
31
+ onBlur?: () => void;
32
+ /** Replaces parts of the picker: Option, Chip, Loading, Empty, Error. */
33
+ slots?: Partial<ResourceSelectSlots>;
34
+ /**
35
+ * Whether typing searches the server (`?search=`). Default: what the
36
+ * schema says (a list without `search_fields` documents no `search`
37
+ * parameter). Without search, typing narrows the rows already loaded and
38
+ * the list says so.
39
+ */
40
+ searchable?: boolean;
41
+ /**
42
+ * The list filter that takes several ids at once, used to label
43
+ * preselected values in one request. Default `"id__in"` when the schema
44
+ * documents it. Otherwise (or with `false`) each id loads from its detail
45
+ * endpoint.
46
+ */
47
+ idsFilter?: string | false;
48
+ /** How the related resource is named in messages. Default: derived from `resource`. */
49
+ resourceLabel?: ResourceLabel;
23
50
  }
24
51
  /**
25
52
  * Searchable picker for a related row, querying the related resource as you type.
26
53
  *
27
54
  * Contract D8.
28
55
  *
29
- * A text input drives a debounced `?search=` list request; options render in
30
- * a NON-MODAL Radix Popover whose auto-focus is prevented so keyboard focus
31
- * never leaves the input. The input implements the ARIA 1.2 combobox pattern
32
- * (combobox → listbox with aria-activedescendant). Pre-selected ids that were
33
- * never seen in a result page are resolved through detail fetches, and the
34
- * clear affordance emits `null`.
56
+ * A text input drives a debounced `?search=` request (or, when the API cannot
57
+ * search, narrows the loaded rows and says so). Options render in a
58
+ * NON-MODAL Radix Popover that never takes focus from the input. Options load
59
+ * a page at a time as the list scrolls or the keyboard reaches its end, never
60
+ * all at once, and long lists render only the rows in view. The input
61
+ * implements the ARIA 1.2 combobox pattern (aria-expanded, aria-controls,
62
+ * aria-activedescendant). Arrows, Home/End and PageUp/PageDown move through
63
+ * every loaded page. Preselected ids are labelled in one request where the
64
+ * API has an id-list filter, else per id. An id that cannot be loaded says
65
+ * so instead of showing a bare id. The clear affordance emits `null`.
35
66
  */
36
- export declare function ResourceSelect(props: ResourceSelectProps): React.ReactElement;
67
+ export declare const ResourceSelect: React.ForwardRefExoticComponent<ResourceSelectProps & React.RefAttributes<HTMLInputElement>>;
@@ -0,0 +1,68 @@
1
+ import { ErrorSlotProps } from '../../../states/ErrorState';
2
+ import { ResourceNames } from '../resource-names';
3
+ /**
4
+ * ResourceSelect's replaceable parts (`slots`) and their defaults, exported
5
+ * as `defaultResourceSelectSlots` so a project can wrap them.
6
+ */
7
+ import * as React from "react";
8
+ export interface ResourceSelectOptionProps {
9
+ /** The row behind the option. */
10
+ item: Record<string, unknown>;
11
+ /** Its label (`getOptionLabel`, else displayLabel). */
12
+ label: React.ReactNode;
13
+ selected: boolean;
14
+ /** Highlighted by the keyboard or the pointer. */
15
+ active: boolean;
16
+ }
17
+ export interface ResourceSelectChipProps {
18
+ /** The selected row's label, or what to say when it could not be loaded. */
19
+ label: React.ReactNode;
20
+ /** "loading" while the row behind a preselected id loads, "failed" when it could not. */
21
+ state: "loaded" | "loading" | "failed";
22
+ /** Removes the value. Absent when the select is disabled. */
23
+ onRemove?: () => void;
24
+ /** The accessible name of the remove button: "Remove Aurora Hall". */
25
+ removeLabel: string;
26
+ }
27
+ export interface ResourceSelectEmptyProps {
28
+ /**
29
+ * "none": the resource has no rows yet. "no-match": the search found
30
+ * nothing. "no-loaded-match": search is not available and none of the
31
+ * loaded rows match the typed text.
32
+ */
33
+ reason: "none" | "no-match" | "no-loaded-match";
34
+ /** The typed text. */
35
+ search: string;
36
+ /** How the resource is named ("venue" / "venues"). */
37
+ names: ResourceNames;
38
+ /** How many rows are loaded (for "no-loaded-match"). */
39
+ loaded: number;
40
+ }
41
+ export interface ResourceSelectLoadingProps {
42
+ /** How many placeholder rows to show. */
43
+ rows: number;
44
+ }
45
+ /** The replaceable parts of ResourceSelect (`slots`). */
46
+ export interface ResourceSelectSlots {
47
+ /** One option's content (the row around it carries the listbox semantics). */
48
+ Option: React.ComponentType<ResourceSelectOptionProps>;
49
+ /** One selected value in `multiple` mode. */
50
+ Chip: React.ComponentType<ResourceSelectChipProps>;
51
+ /** While the first page of options loads. */
52
+ Loading: React.ComponentType<ResourceSelectLoadingProps>;
53
+ /** No options to show, and why. */
54
+ Empty: React.ComponentType<ResourceSelectEmptyProps>;
55
+ /** The options could not be loaded (forbidden, offline, server), with retry when it can help. */
56
+ Error: React.ComponentType<ErrorSlotProps>;
57
+ }
58
+ export declare function ResourceSelectOption({ label, selected, }: ResourceSelectOptionProps): React.ReactElement;
59
+ declare function XIcon(props: React.SVGAttributes<SVGSVGElement>): React.ReactElement;
60
+ export { XIcon as ResourceSelectClearIcon };
61
+ export declare function ResourceSelectChip({ label, state, onRemove, removeLabel, }: ResourceSelectChipProps): React.ReactElement;
62
+ export declare function ResourceSelectLoading({ rows }: ResourceSelectLoadingProps): React.ReactElement;
63
+ /** "No venues yet" and "No venues match “ber”" are different situations, said differently. */
64
+ export declare function emptyText({ reason, search, names, loaded }: ResourceSelectEmptyProps): string;
65
+ export declare function ResourceSelectEmpty(props: ResourceSelectEmptyProps): React.ReactElement;
66
+ export declare function ResourceSelectError(props: ErrorSlotProps): React.ReactElement;
67
+ /** ResourceSelect's own slot components, for wrapping rather than rewriting. */
68
+ export declare const defaultResourceSelectSlots: ResourceSelectSlots;
@@ -0,0 +1,37 @@
1
+ import { UseInfiniteQueryResult, InfiniteData } from '@tanstack/react-query';
2
+ export type OptionRow = Record<string, unknown>;
3
+ /** The query string of one page request. */
4
+ type PageQuery = Record<string, string | readonly string[]>;
5
+ export interface OptionPage {
6
+ /** Undefined when the API does not count (cursor pagination). */
7
+ count?: number;
8
+ next: string | null;
9
+ results: OptionRow[];
10
+ }
11
+ /** `next` → its query string (`?page=2&search=x`, `?cursor=...`). */
12
+ export declare function pageQueryOf(next: string): PageQuery;
13
+ export interface OptionPagesInput {
14
+ resource: string;
15
+ /** The (debounced) search term, "" for none. */
16
+ search: string;
17
+ filter?: Record<string, string | number | boolean>;
18
+ pageSize?: number;
19
+ enabled: boolean;
20
+ }
21
+ export type OptionPagesResult = UseInfiniteQueryResult<InfiniteData<OptionPage, PageQuery | null>>;
22
+ /** Option pages, each fetched on demand. The previous term's rows stay while a new search loads. */
23
+ export declare function useOptionPages({ resource, search, filter, pageSize, enabled, }: OptionPagesInput): OptionPagesResult;
24
+ export interface SelectedRows {
25
+ /** Rows found for the requested ids, by String(id). */
26
+ rows: Record<string, OptionRow>;
27
+ /** Ids that could not be loaded (deleted, hidden from the role, or the request failed). */
28
+ failed: ReadonlySet<string>;
29
+ /** Ids still loading. */
30
+ loading: ReadonlySet<string>;
31
+ }
32
+ /** Loads the rows behind ids the options have not shown yet. */
33
+ export declare function useSelectedRows(resource: string, ids: ReadonlyArray<string | number>, options: {
34
+ idsFilter?: string;
35
+ enabled: boolean;
36
+ }): SelectedRows;
37
+ export {};
@@ -0,0 +1,144 @@
1
+ import { RequestHandler } from 'msw';
2
+ export interface VenueRow {
3
+ id: number;
4
+ name: string;
5
+ city: string;
6
+ }
7
+ /** `count` venues: twelve named ones, then "Venue 00013", "Venue 00014", ... */
8
+ export declare function makeVenues(count?: number): VenueRow[];
9
+ export type Failure = number | "offline";
10
+ export type Delay = number | "infinite";
11
+ export interface VenueBackendOptions {
12
+ /** How many venues exist. Default 12. */
13
+ count?: number;
14
+ /** Whether the list documents and honours `?search=`. Default true. */
15
+ search?: boolean;
16
+ /** Whether the list documents and honours `?id__in=1,2`. Default false (Karni's default). */
17
+ idsFilter?: boolean;
18
+ /** The server's page size when the request names none. Default 50. */
19
+ pageSize?: number;
20
+ /** Fail every list request (a status, or "offline" for a dropped connection). */
21
+ listFailure?: Failure;
22
+ /** Fail list requests for pages after the first. */
23
+ nextPageFailure?: Failure;
24
+ /** Fail every detail request. */
25
+ detailFailure?: Failure;
26
+ /** Delay every list request. */
27
+ listDelay?: Delay;
28
+ /** Delay list requests that carry a search term (the "Searching…" state). */
29
+ searchDelay?: Delay;
30
+ /** Delay every detail request. */
31
+ detailDelay?: Delay;
32
+ }
33
+ export interface VenueBackend {
34
+ handlers: RequestHandler[];
35
+ /** Every venues request, in order. */
36
+ requests: URL[];
37
+ rows: VenueRow[];
38
+ /** The list requests only. */
39
+ listRequests: () => URL[];
40
+ }
41
+ /** The OpenAPI document of the simulated backend (drf-spectacular dialect). */
42
+ export declare function venueOpenApi(options?: Pick<VenueBackendOptions, "search" | "idsFilter">): {
43
+ openapi: string;
44
+ info: {
45
+ title: string;
46
+ version: string;
47
+ };
48
+ "x-karni-auth-mode": string;
49
+ paths: {
50
+ "/api/venues/": {
51
+ get: {
52
+ operationId: string;
53
+ "x-karni-resource": string;
54
+ parameters: Record<string, unknown>[];
55
+ responses: {
56
+ "200": {
57
+ description: string;
58
+ content: {
59
+ "application/json": {
60
+ schema: {
61
+ $ref: string;
62
+ };
63
+ };
64
+ };
65
+ };
66
+ };
67
+ };
68
+ };
69
+ "/api/venues/{id}/": {
70
+ get: {
71
+ operationId: string;
72
+ parameters: {
73
+ in: string;
74
+ name: string;
75
+ required: boolean;
76
+ schema: {
77
+ type: string;
78
+ };
79
+ }[];
80
+ responses: {
81
+ "200": {
82
+ description: string;
83
+ content: {
84
+ "application/json": {
85
+ schema: {
86
+ $ref: string;
87
+ };
88
+ };
89
+ };
90
+ };
91
+ };
92
+ };
93
+ };
94
+ };
95
+ components: {
96
+ schemas: {
97
+ Venue: {
98
+ type: string;
99
+ required: string[];
100
+ properties: {
101
+ id: {
102
+ type: string;
103
+ readOnly: boolean;
104
+ };
105
+ name: {
106
+ type: string;
107
+ maxLength: number;
108
+ };
109
+ city: {
110
+ type: string;
111
+ maxLength: number;
112
+ };
113
+ };
114
+ };
115
+ PaginatedVenueList: {
116
+ type: string;
117
+ required: string[];
118
+ properties: {
119
+ count: {
120
+ type: string;
121
+ };
122
+ next: {
123
+ type: string;
124
+ nullable: boolean;
125
+ format: string;
126
+ };
127
+ previous: {
128
+ type: string;
129
+ nullable: boolean;
130
+ format: string;
131
+ };
132
+ results: {
133
+ type: string;
134
+ items: {
135
+ $ref: string;
136
+ };
137
+ };
138
+ };
139
+ };
140
+ };
141
+ };
142
+ };
143
+ /** Handlers for the schema and the venues endpoints, plus the request log. */
144
+ export declare function venueBackend(options?: VenueBackendOptions): VenueBackend;
@@ -1,20 +1,77 @@
1
1
  /**
2
- * Selected-count toolbar for ResourceTable's bulk select (D5): shows how many
3
- * rows are selected (selection persists across pages), a clear affordance and
4
- * one button per configured bulk action.
5
- * Internal to @karni/ui — the public surface is ResourceTable's props.
2
+ * Selected-count toolbar for ResourceTable's bulk select (D5): how many rows
3
+ * are selected (selection persists across pages) and what "all" covers, a
4
+ * clear affordance, one button per bulk action, and what happened last time
5
+ * (a failure in plain words, or which rows failed). The table runs the
6
+ * actions (confirming destructive ones first). This bar only presents them.
7
+ * The default of ResourceTable's `BulkActions` slot.
6
8
  */
7
9
  import * as React from "react";
10
+ /** What a bulk action may report back: the rows it could not handle, and why. */
11
+ export interface BulkResult {
12
+ /** Each failed row's id (String(row.id)) and the reason to show on that row. */
13
+ failed?: ReadonlyArray<{
14
+ id: string;
15
+ reason: string;
16
+ }>;
17
+ }
18
+ /**
19
+ * What `BulkAction.onClick` returns: nothing (the 0.3 contract, kept), or a
20
+ * promise of nothing or a `BulkResult`.
21
+ */
22
+ export type BulkActionReturn = void | Promise<void | BulkResult>;
8
23
  export interface BulkAction {
9
24
  label: React.ReactNode;
10
- onClick: (selectedIds: string[]) => void;
25
+ /**
26
+ * Runs the action on the selected ids. Return a promise to have the table
27
+ * show progress, report failures and deselect the rows that succeeded.
28
+ * Resolve a `BulkResult` to mark the rows that failed. A plain `void`
29
+ * return leaves the selection as it is.
30
+ */
31
+ onClick: (selectedIds: string[]) => BulkActionReturn;
11
32
  destructive?: boolean;
33
+ /**
34
+ * Ask before running. Default: true for destructive actions. An object
35
+ * words the question ("Archive 3 events?" by default).
36
+ */
37
+ confirm?: boolean | {
38
+ title?: React.ReactNode;
39
+ description?: React.ReactNode;
40
+ confirmLabel?: string;
41
+ };
42
+ /** Announced while the action runs ("Archiving…"). */
43
+ pendingLabel?: string;
12
44
  }
45
+ /** What the last bulk action came to, for the bar to report. */
46
+ export type BulkOutcome = {
47
+ kind: "failed";
48
+ actionLabel: string;
49
+ error: unknown;
50
+ count: number;
51
+ } | {
52
+ kind: "partial";
53
+ actionLabel: string;
54
+ failed: number;
55
+ total: number;
56
+ };
13
57
  export interface BulkActionsBarProps {
14
58
  /** Row ids (String(row.id), D5) currently selected, across all pages. */
15
59
  selectedIds: readonly string[];
16
60
  bulkActions: ReadonlyArray<BulkAction>;
17
61
  onClear: () => void;
62
+ /** Runs (or asks to confirm) the action at `index`. `trigger` gets focus back after a dialog. */
63
+ onAction?: (index: number, trigger: HTMLButtonElement | null) => void;
64
+ /** The action running now, by index. */
65
+ runningIndex?: number | null;
66
+ /** The last action's failure or partial failure, reported until the next one. */
67
+ outcome?: BulkOutcome | null;
68
+ /** Rows on the current page, and whether all of them are selected. */
69
+ pageRowCount?: number;
70
+ allPageRowsSelected?: boolean;
71
+ /** Everything matching the current search and filters (DRF `count`). */
72
+ totalCount?: number;
73
+ /** The resource, plural ("events"). */
74
+ resourceLabel?: string;
18
75
  className?: string;
19
76
  }
20
77
  export declare function BulkActionsBar(props: BulkActionsBarProps): React.ReactElement;
@@ -0,0 +1,31 @@
1
+ import { Table } from '@tanstack/react-table';
2
+ import { ResourceTableDensity } from './preferences';
3
+ import { ResourceTableRowCardProps } from './RowCard';
4
+ import * as React from "react";
5
+ export interface CardListProps<T> {
6
+ table: Table<T>;
7
+ caption: string;
8
+ resourceLabel: string;
9
+ density: ResourceTableDensity;
10
+ Card: React.ComponentType<ResourceTableRowCardProps<unknown>>;
11
+ rowLabel: (row: T) => string;
12
+ /** Overrides the first column as each card's title. */
13
+ useRowLabelAsTitle: boolean;
14
+ onRowClick?: (row: T) => void;
15
+ failures: Readonly<Record<string, string>>;
16
+ /** Plain-text column names, for the sort choice. */
17
+ columnLabel: (columnId: string) => string;
18
+ /** The current ordering and a way to change it ("" is the default order). */
19
+ ordering: string;
20
+ onOrderingChange: (ordering: string) => void;
21
+ /** A sort or page change is loading: the list announces busy. */
22
+ busy: boolean;
23
+ dimmed: boolean;
24
+ /**
25
+ * The list element. Focusable by script only (tabIndex -1): where focus
26
+ * lands when the control that had it is gone (a deleted row, the bulk
27
+ * toolbar after its action), as the table layout's scroll region is.
28
+ */
29
+ listRef?: React.Ref<HTMLUListElement>;
30
+ }
31
+ export declare function CardList<T>(props: CardListProps<T>): React.ReactElement;
@@ -0,0 +1,28 @@
1
+ import { KarniErrorContext } from '../../../states/describe-error';
2
+ /**
3
+ * The question ResourceTable asks before destructive work (a row delete, a
4
+ * destructive bulk action): a Modal (Radix dialog, so focus is trapped and
5
+ * Escape cancels) naming the action and how many rows it touches. While the
6
+ * work runs the confirm button shows progress. A failure is reported inside
7
+ * the dialog in plain words, with the button kept for another try when
8
+ * trying again can help. Focus returns to the control that opened it.
9
+ * Internal to @karni/ui.
10
+ */
11
+ import * as React from "react";
12
+ export interface ConfirmDialogProps {
13
+ open: boolean;
14
+ title: React.ReactNode;
15
+ description?: React.ReactNode;
16
+ confirmLabel: string;
17
+ pendingLabel?: string;
18
+ destructive?: boolean;
19
+ pending: boolean;
20
+ /** The last attempt's failure, shown in the dialog. */
21
+ error?: unknown;
22
+ errorContext?: KarniErrorContext;
23
+ onConfirm: () => void;
24
+ onCancel: () => void;
25
+ /** Where focus goes when the dialog closes (the control that opened it, or a fallback). */
26
+ returnFocus: () => HTMLElement | null;
27
+ }
28
+ export declare function ConfirmDialog(props: ConfirmDialogProps): React.ReactElement;
@@ -3,10 +3,10 @@ import { ExportFormat } from '../../../hooks/useResourceImport';
3
3
  /**
4
4
  * ResourceTable's export buttons (karni.transfer, ADR 0002 P6): one per
5
5
  * format, each downloading `GET /<resource>/export/` with the table's current
6
- * filters, search and ordering — the backend applies the row scope and the
6
+ * filters, search and ordering. The backend applies the row scope and the
7
7
  * role's field visibility, so the file is exactly what the table may show.
8
8
  *
9
- * Internal to @karni/ui — the public surface is ResourceTable's `exportFormats`.
9
+ * Internal to @karni/ui. The public surface is ResourceTable's `exportFormats`.
10
10
  */
11
11
  import * as React from "react";
12
12
  export interface ExportButtonsProps {
@@ -4,13 +4,13 @@ import { FieldDef } from '../../../schema/types';
4
4
  * ResourceTable's search box + declared-params-derived filter bar (D5).
5
5
  *
6
6
  * The parent decides WHICH fields filter (enum/boolean/relation fields ∩ the
7
- * list operation's declared query params, cap 4, `filters` prop overrides);
8
- * this component only renders the controls:
7
+ * list operation's declared query params, cap 4, `filters` prop overrides).
8
+ * This component only renders the controls:
9
9
  * - enum → clearable <Select> ("All" clears),
10
10
  * - boolean → clearable Yes/No <Select>,
11
11
  * - relation → <ResourceSelect> (multiple for many-relations).
12
12
  *
13
- * Internal to @karni/ui — the public surface is ResourceTable's props.
13
+ * Internal to @karni/ui. The public surface is ResourceTable's props.
14
14
  */
15
15
  import * as React from "react";
16
16
  export interface FilterBarProps {
@@ -1,7 +1,13 @@
1
1
  /**
2
2
  * ResourceTable's server-mode pagination row (DRF PageNumberPagination:
3
- * `{count, next, previous, results}` with `page` / `page_size` params).
4
- * Internal to @karni/ui — the public surface is ResourceTable's props.
3
+ * `{count, next, previous, results}` with `page` / `page_size` params):
4
+ * "Page X of Y · N items", a page size choice, first / previous / next /
5
+ * last, and a page number to jump to. Only one page is ever fetched.
6
+ *
7
+ * While a page is on its way (`busy`) the buttons stay focusable but ignore
8
+ * clicks (`aria-disabled`), so a double click never skips a page and a
9
+ * keyboard user's focus stays put. After 500ms the clicked button shows
10
+ * progress. The default of ResourceTable's `Pagination` slot.
5
11
  */
6
12
  import * as React from "react";
7
13
  export interface PaginationControlsProps {
@@ -10,10 +16,16 @@ export interface PaginationControlsProps {
10
16
  pageCount: number;
11
17
  /** Total item count across all pages (DRF `count`). */
12
18
  totalCount: number;
13
- /** From DRF `previous` / `next` — the server is the authority. */
19
+ /** From DRF `previous` / `next`. The server is the authority. */
14
20
  hasPrevious: boolean;
15
21
  hasNext: boolean;
16
22
  onPageChange: (page: number) => void;
23
+ /** Rows per page. With `onPageSizeChange` and `pageSizeOptions` it renders the size choice. */
24
+ pageSize?: number;
25
+ pageSizeOptions?: readonly number[];
26
+ onPageSizeChange?: (pageSize: number) => void;
27
+ /** A page is loading: controls announce busy and ignore clicks. */
28
+ busy?: boolean;
17
29
  className?: string;
18
30
  }
19
31
  export declare function PaginationControls(props: PaginationControlsProps): React.ReactElement;