@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.
- package/README.md +712 -72
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8660 -4725
- package/dist/index.js.map +1 -1
- package/dist/manifest.json +18 -1
- package/dist/script-BVJ9BbPm.cjs +2 -0
- package/dist/script-BVJ9BbPm.cjs.map +1 -0
- package/dist/script-D1fn4Dm4.js +6 -0
- package/dist/script-D1fn4Dm4.js.map +1 -0
- package/dist/src/auth/AuthGuard.d.ts +69 -4
- package/dist/src/auth/LoginForm.d.ts +41 -7
- package/dist/src/auth/engine.d.ts +15 -13
- package/dist/src/auth/provider.d.ts +10 -10
- package/dist/src/auth/storage.d.ts +1 -1
- package/dist/src/auth/types.d.ts +3 -3
- package/dist/src/components/brand/karni-mark.d.ts +4 -4
- package/dist/src/components/chart/data-chart.d.ts +66 -11
- package/dist/src/components/empty-state/empty-state.d.ts +22 -2
- package/dist/src/components/file-upload/FileUpload.d.ts +67 -3
- package/dist/src/components/layout/layout.d.ts +104 -9
- package/dist/src/components/layout/route-focus.d.ts +43 -0
- package/dist/src/components/modal/modal.d.ts +49 -8
- package/dist/src/components/page/page.d.ts +36 -5
- package/dist/src/components/primitives/button.d.ts +1 -0
- package/dist/src/components/primitives/checkbox.d.ts +4 -1
- package/dist/src/components/primitives/dropdown-menu.d.ts +1 -1
- package/dist/src/components/primitives/icons.d.ts +9 -1
- package/dist/src/components/primitives/input.d.ts +4 -1
- package/dist/src/components/primitives/label.d.ts +4 -1
- package/dist/src/components/primitives/select.d.ts +37 -6
- package/dist/src/components/primitives/skeleton.d.ts +4 -1
- package/dist/src/components/primitives/switch.d.ts +4 -1
- package/dist/src/components/primitives/tabs.d.ts +2 -2
- package/dist/src/components/primitives/textarea.d.ts +4 -1
- package/dist/src/components/resource/ResourceDetail.d.ts +3 -3
- package/dist/src/components/resource/ResourceForm.d.ts +4 -3
- package/dist/src/components/resource/ResourceSelect.d.ts +3 -1
- package/dist/src/components/resource/ResourceTable.d.ts +9 -3
- package/dist/src/components/resource/audit-fields.d.ts +1 -1
- package/dist/src/components/resource/detail/ResourceDetail.d.ts +109 -11
- package/dist/src/components/resource/empty-value.d.ts +17 -0
- package/dist/src/components/resource/form/ResourceForm.d.ts +65 -6
- package/dist/src/components/resource/form/field-inputs.d.ts +15 -1
- package/dist/src/components/resource/form/form-states.d.ts +87 -0
- package/dist/src/components/resource/form/prepare.d.ts +3 -3
- package/dist/src/components/resource/form/server-errors.d.ts +14 -0
- package/dist/src/components/resource/history/ChangeHistory.d.ts +46 -4
- package/dist/src/components/resource/import/ResourceImport.d.ts +73 -7
- package/dist/src/components/resource/nouns.d.ts +12 -0
- package/dist/src/components/resource/query-state.d.ts +20 -0
- package/dist/src/components/resource/resource-names.d.ts +23 -0
- package/dist/src/components/resource/select/ResourceSelect.d.ts +39 -8
- package/dist/src/components/resource/select/select-states.d.ts +68 -0
- package/dist/src/components/resource/select/use-option-pages.d.ts +37 -0
- package/dist/src/components/resource/select/venue-backend.d.ts +144 -0
- package/dist/src/components/resource/table/BulkActionsBar.d.ts +62 -5
- package/dist/src/components/resource/table/CardList.d.ts +31 -0
- package/dist/src/components/resource/table/ConfirmDialog.d.ts +28 -0
- package/dist/src/components/resource/table/ExportButtons.d.ts +2 -2
- package/dist/src/components/resource/table/FilterBar.d.ts +3 -3
- package/dist/src/components/resource/table/PaginationControls.d.ts +15 -3
- package/dist/src/components/resource/table/ResourceTable.d.ts +79 -14
- package/dist/src/components/resource/table/RowCard.d.ts +31 -0
- package/dist/src/components/resource/table/TableView.d.ts +29 -0
- package/dist/src/components/resource/table/ViewOptions.d.ts +19 -0
- package/dist/src/components/resource/table/access.d.ts +12 -0
- package/dist/src/components/resource/table/derive-columns.d.ts +2 -2
- package/dist/src/components/resource/table/grid-navigation.d.ts +23 -0
- package/dist/src/components/resource/table/layout.d.ts +37 -0
- package/dist/src/components/resource/table/preferences.d.ts +12 -0
- package/dist/src/components/resource/table/slots.d.ts +41 -0
- package/dist/src/components/resource/table/states.d.ts +91 -0
- package/dist/src/components/resource/table/story-fixtures.d.ts +25 -0
- package/dist/src/components/resource/table/structural-columns.d.ts +25 -0
- package/dist/src/components/resource/table/table-icons.d.ts +16 -0
- package/dist/src/components/resource/table/table-utils.d.ts +27 -3
- package/dist/src/components/resource/table/url-state.d.ts +67 -0
- package/dist/src/components/resource/table/use-container-width.d.ts +10 -0
- package/dist/src/components/resource/testapp-fixtures.d.ts +78 -0
- package/dist/src/components/stat/stat.d.ts +36 -6
- package/dist/src/components/toast/toast-store.d.ts +1 -1
- package/dist/src/components/toast/toaster.d.ts +39 -6
- package/dist/src/components/toast/use-toast.d.ts +1 -1
- package/dist/src/hooks/keys.d.ts +4 -4
- package/dist/src/hooks/multipart.d.ts +9 -9
- package/dist/src/hooks/params.d.ts +1 -1
- package/dist/src/hooks/types.d.ts +3 -3
- package/dist/src/hooks/useChangeHistory.d.ts +3 -2
- package/dist/src/hooks/useResourceImport.d.ts +2 -2
- package/dist/src/hooks/useResourceMutations.d.ts +2 -2
- package/dist/src/hooks/useUnsavedChangesGuard.d.ts +31 -0
- package/dist/src/index.d.ts +49 -31
- package/dist/src/lib/roles.d.ts +4 -4
- package/dist/src/lib/variants.d.ts +1 -1
- package/dist/src/primitives/registry.d.ts +81 -0
- package/dist/src/schema/client.d.ts +10 -10
- package/dist/src/schema/parse.d.ts +2 -2
- package/dist/src/schema/provider.d.ts +10 -3
- package/dist/src/schema/types.d.ts +18 -9
- package/dist/src/schema/useSchema.d.ts +5 -1
- package/dist/src/states/ErrorState.d.ts +43 -0
- package/dist/src/states/PendingButton.d.ts +16 -0
- package/dist/src/states/describe-error.d.ts +25 -0
- package/dist/src/states/hooks.d.ts +10 -0
- package/dist/src/theme/script.d.ts +12 -0
- package/dist/src/theme/theme.d.ts +45 -0
- package/dist/styles.css +174 -13
- package/dist/tailwind-preset.cjs +2 -2
- package/dist/tailwind-preset.cjs.map +1 -1
- package/dist/tailwind-preset.d.ts +83 -1
- package/dist/tailwind-preset.js +181 -62
- package/dist/tailwind-preset.js.map +1 -1
- package/dist/tailwind-v4.css +31 -0
- package/dist/vite-plugin.cjs +2 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.ts +19 -0
- package/dist/vite-plugin.js +16 -0
- package/dist/vite-plugin.js.map +1 -0
- package/eslint-plugin/index.js +4 -0
- package/eslint-plugin/rules/max-page-lines.js +1 -1
- package/eslint-plugin/rules/no-deep-imports.js +1 -1
- package/eslint-plugin/rules/no-direct-http.js +1 -1
- package/eslint-plugin/rules/no-raw-elements.js +1 -1
- package/eslint-plugin/rules/no-style-literals.js +222 -0
- package/eslint-plugin/rules.json +10 -4
- package/package.json +22 -19
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Barrel-facing path for <ResourceForm>. The implementation lives in
|
|
3
|
-
* ./form/ (resource form builder)
|
|
3
|
+
* ./form/ (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 { ResourceForm } from './form/ResourceForm';
|
|
7
|
-
export type { ResourceFormProps, FieldOverride, KarniFieldProps } from './form/ResourceForm';
|
|
6
|
+
export { ResourceForm, defaultResourceFormSlots } from './form/ResourceForm';
|
|
7
|
+
export type { ResourceFormProps, ResourceFormSlots, FieldOverride, KarniFieldProps, } from './form/ResourceForm';
|
|
8
|
+
export type { ResourceFormErrorSummaryProps, ResourceFormFieldError, ResourceFormForbiddenProps, ResourceFormLoadingProps, ResourceFormSubmitErrorProps, } from './form/form-states';
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Barrel-facing path for <ResourceSelect>. The implementation lives in
|
|
3
|
-
* ./select/ (resource form builder)
|
|
3
|
+
* ./select/ (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
6
|
export { ResourceSelect } from './select/ResourceSelect';
|
|
7
7
|
export type { ResourceSelectProps } from './select/ResourceSelect';
|
|
8
|
+
export { defaultResourceSelectSlots } from './select/select-states';
|
|
9
|
+
export type { ResourceSelectChipProps, ResourceSelectEmptyProps, ResourceSelectLoadingProps, ResourceSelectOptionProps, ResourceSelectSlots, } from './select/select-states';
|
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
* Public entry for ResourceTable (owner: resource table builder).
|
|
3
3
|
*
|
|
4
4
|
* The implementation lives in ./table/ (ResourceTable + filter bar +
|
|
5
|
-
* pagination controls + bulk select, contract "ResourceTable" + D5)
|
|
5
|
+
* pagination controls + bulk select, contract "ResourceTable" + D5). This
|
|
6
6
|
* module keeps the barrel's import path and exported names stable.
|
|
7
7
|
*/
|
|
8
8
|
export { ResourceTable } from './table/ResourceTable';
|
|
9
|
-
export type { ResourceTableProps } from './table/ResourceTable';
|
|
9
|
+
export type { BulkResult, ResourceTableDensity, ResourceTableKeyboardNavigation, ResourceTableProps, } from './table/ResourceTable';
|
|
10
10
|
export type { ResourceColumn } from './table/derive-columns';
|
|
11
|
-
export type { BulkAction } from './table/BulkActionsBar';
|
|
11
|
+
export type { BulkAction, BulkActionReturn, BulkOutcome } from './table/BulkActionsBar';
|
|
12
|
+
export { defaultResourceTableSlots } from './table/slots';
|
|
13
|
+
export type { ResourceTableBulkActionsProps, ResourceTableForbiddenProps, ResourceTableLoadingProps, ResourceTableNoRecordsProps, ResourceTableNoResultsProps, ResourceTableOfflineProps, ResourceTablePaginationProps, ResourceTableRowCardProps, ResourceTableSlots, } from './table/slots';
|
|
14
|
+
export type { ResourceTableActiveFilter } from './table/states';
|
|
15
|
+
export type { ResourceTableCardField } from './table/RowCard';
|
|
16
|
+
export { createHistoryStateStore } from './table/url-state';
|
|
17
|
+
export type { ResourceTableStateStore } from './table/url-state';
|
|
@@ -3,7 +3,7 @@ import { FieldDef } from '../../schema/types';
|
|
|
3
3
|
export declare const AUDIT_FIELDS: readonly string[];
|
|
4
4
|
/**
|
|
5
5
|
* True for a schema field that is one of AUDIT_FIELDS. The backend always
|
|
6
|
-
* emits these readOnly
|
|
6
|
+
* emits these readOnly. A writable field that merely shares a name (a natural
|
|
7
7
|
* `id` key, say) is domain data and is never treated as audit.
|
|
8
8
|
*/
|
|
9
9
|
export declare function isAuditField(field: Pick<FieldDef, "name" | "readOnly">): boolean;
|
|
@@ -1,30 +1,128 @@
|
|
|
1
|
+
import { FieldDef } from '../../../schema/types';
|
|
2
|
+
import { ErrorSlotProps } from '../../../states/ErrorState';
|
|
1
3
|
import * as React from "react";
|
|
4
|
+
type Item = Record<string, unknown>;
|
|
2
5
|
export interface ResourceDetailFieldOverride {
|
|
3
6
|
label?: string;
|
|
4
7
|
hidden?: boolean;
|
|
5
8
|
render?: (value: unknown, item: Record<string, unknown>) => React.ReactNode;
|
|
6
9
|
}
|
|
10
|
+
/** What the `Loading` slot receives. */
|
|
11
|
+
export interface ResourceDetailLoadingProps {
|
|
12
|
+
/** The domain fields the record will show, in order. Undefined while the schema loads. */
|
|
13
|
+
fields: readonly FieldDef[] | undefined;
|
|
14
|
+
/** The audit fields of the "Record" group. Undefined while the schema loads. */
|
|
15
|
+
auditFields: readonly FieldDef[] | undefined;
|
|
16
|
+
/** True once loading has taken longer than SLOW_AFTER_MS (500ms): show the placeholder now. */
|
|
17
|
+
slow: boolean;
|
|
18
|
+
}
|
|
19
|
+
/** What the `Error` slot receives: ErrorSlotProps plus the `back` prop. */
|
|
20
|
+
export interface ResourceDetailErrorProps extends ErrorSlotProps {
|
|
21
|
+
/** The way out of the screen (the `back` prop). */
|
|
22
|
+
back?: React.ReactNode;
|
|
23
|
+
}
|
|
24
|
+
/** What the `NotFound` slot receives (the backend answered 404). */
|
|
25
|
+
export interface ResourceDetailNotFoundProps {
|
|
26
|
+
error: unknown;
|
|
27
|
+
/** The resource in words, singular: "event". */
|
|
28
|
+
resourceLabel: string;
|
|
29
|
+
back?: React.ReactNode;
|
|
30
|
+
}
|
|
31
|
+
/** What the `Deleted` slot receives, after a delete from this view succeeded. */
|
|
32
|
+
export interface ResourceDetailDeletedProps {
|
|
33
|
+
item: Item;
|
|
34
|
+
resourceLabel: string;
|
|
35
|
+
back?: React.ReactNode;
|
|
36
|
+
}
|
|
37
|
+
/** What the `EmptyValue` slot receives: a visible field with no value. */
|
|
38
|
+
export interface ResourceDetailEmptyValueProps {
|
|
39
|
+
field: FieldDef;
|
|
40
|
+
}
|
|
41
|
+
/** What the `DeleteDialog` slot receives: the confirmation before a delete. */
|
|
42
|
+
export interface ResourceDetailDeleteDialogProps {
|
|
43
|
+
open: boolean;
|
|
44
|
+
onOpenChange: (open: boolean) => void;
|
|
45
|
+
item: Item;
|
|
46
|
+
/** displayLabel(item): "Launch party". */
|
|
47
|
+
itemLabel: string;
|
|
48
|
+
resourceLabel: string;
|
|
49
|
+
onConfirm: () => void;
|
|
50
|
+
/** True while the delete request runs. */
|
|
51
|
+
pending: boolean;
|
|
52
|
+
/** The failed attempt's error (null before an attempt and while one runs). */
|
|
53
|
+
error: unknown;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The replaceable parts of ResourceDetail. Pass any subset as `slots`.
|
|
57
|
+
* `defaultResourceDetailSlots` holds Karni's own, to wrap them.
|
|
58
|
+
*/
|
|
59
|
+
export interface ResourceDetailSlots {
|
|
60
|
+
/** The field placeholders while the record loads (the header is ResourceDetail's own). */
|
|
61
|
+
Loading: React.ComponentType<ResourceDetailLoadingProps>;
|
|
62
|
+
/** Any failure but a 404: offline, server error, 403, ... */
|
|
63
|
+
Error: React.ComponentType<ResourceDetailErrorProps>;
|
|
64
|
+
/** The backend answered 404: the record does not exist or is outside the user's scope. */
|
|
65
|
+
NotFound: React.ComponentType<ResourceDetailNotFoundProps>;
|
|
66
|
+
/** A visible field whose value is empty. */
|
|
67
|
+
EmptyValue: React.ComponentType<ResourceDetailEmptyValueProps>;
|
|
68
|
+
/** The delete confirmation. */
|
|
69
|
+
DeleteDialog: React.ComponentType<ResourceDetailDeleteDialogProps>;
|
|
70
|
+
/** Shown in place of the record once it is deleted from this view. */
|
|
71
|
+
Deleted: React.ComponentType<ResourceDetailDeletedProps>;
|
|
72
|
+
}
|
|
7
73
|
export interface ResourceDetailProps {
|
|
8
74
|
resource: string;
|
|
9
75
|
id: string | number;
|
|
10
76
|
fields?: Record<string, ResourceDetailFieldOverride>;
|
|
11
|
-
/** Appended below the field list (e.g. a child <ResourceTable>). */
|
|
77
|
+
/** Appended below the field list (e.g. a child <ResourceTable>), even while the record loads. */
|
|
12
78
|
sections?: ReadonlyArray<React.ReactNode>;
|
|
13
79
|
expand?: readonly string[];
|
|
14
|
-
|
|
15
|
-
|
|
80
|
+
/** The heading. A function receives the loaded record. A placeholder stands in until then. */
|
|
81
|
+
title?: React.ReactNode | ((item: Item) => React.ReactNode);
|
|
82
|
+
/** Shown in the header next to Edit and Delete. A function receives the loaded record. */
|
|
83
|
+
actions?: React.ReactNode | ((item: Item) => React.ReactNode);
|
|
84
|
+
/** The resource in words, singular, for messages ("Couldn't find this event"). Default: from `resource`. */
|
|
85
|
+
resourceLabel?: string;
|
|
86
|
+
/** A way back (a link or button to the list), shown when the record is missing, fails or is deleted. */
|
|
87
|
+
back?: React.ReactNode;
|
|
88
|
+
/** Shows an Edit button that calls this with the record. */
|
|
89
|
+
onEdit?: (item: Item) => void;
|
|
90
|
+
/** Whether the user may edit this record (the role, the record's state). Default true. */
|
|
91
|
+
canEdit?: boolean | ((item: Item) => boolean);
|
|
92
|
+
/** Shows a Delete button, which asks for confirmation first. */
|
|
93
|
+
enableDelete?: boolean;
|
|
94
|
+
/** Whether the user may delete this record. Default true. */
|
|
95
|
+
canDelete?: boolean | ((item: Item) => boolean);
|
|
96
|
+
/** Called after a confirmed delete succeeded (navigate away from here). */
|
|
97
|
+
onDeleted?: (item: Item) => void;
|
|
98
|
+
/** Replace parts of the view: Loading, Error, NotFound, EmptyValue, DeleteDialog, Deleted. */
|
|
99
|
+
slots?: Partial<ResourceDetailSlots>;
|
|
16
100
|
className?: string;
|
|
17
101
|
}
|
|
102
|
+
/** Karni's own slots, exported so a project can wrap one instead of rewriting it. */
|
|
103
|
+
export declare const defaultResourceDetailSlots: ResourceDetailSlots;
|
|
18
104
|
/**
|
|
19
105
|
* Schema-driven read view of one row: every readable field, labelled and formatted.
|
|
20
106
|
*
|
|
21
|
-
* Contract "ResourceDetail": a definition list of
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* (
|
|
28
|
-
*
|
|
107
|
+
* Contract "ResourceDetail": a definition list of every non-writeOnly field
|
|
108
|
+
* the payload carries, with domain fields first (schema order), then the audit
|
|
109
|
+
* fields (AUDIT_FIELDS: id, created_at, updated_at, created_by, updated_by)
|
|
110
|
+
* in a visually muted "Record" group. A field the role may not read
|
|
111
|
+
* (`Karni.field_roles`) is absent from the per-role schema and the payload,
|
|
112
|
+
* so it has no row at all. A visible field with no value shows EmptyValue
|
|
113
|
+
* (a dash, read as "empty"). Relations render through displayLabel when the
|
|
114
|
+
* object is expanded (the field is in `expand`), else as the raw id in <code>.
|
|
115
|
+
*
|
|
116
|
+
* States: a placeholder in the record's own layout while it loads (drawn
|
|
117
|
+
* after 500ms, the header in place from the start). A 404 shows "Couldn't
|
|
118
|
+
* find this <resource>", and Karni also answers 404 for a record outside the
|
|
119
|
+
* user's scope. Everything else shows describeError's words, with "Try again"
|
|
120
|
+
* when it can help (it refetches the schema too when that failed). Offline
|
|
121
|
+
* says so and reloads when the connection returns.
|
|
122
|
+
*
|
|
123
|
+
* Escape hatches: per-field `fields` overrides (label/hidden/render, audit
|
|
124
|
+
* fields included), appended `sections`, the title/actions header, the
|
|
125
|
+
* `canEdit`/`canDelete` predicates, and `slots` for every state.
|
|
29
126
|
*/
|
|
30
127
|
export declare function ResourceDetail(props: ResourceDetailProps): React.ReactElement;
|
|
128
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/** What a screen reader says for a field that has no value. */
|
|
3
|
+
export declare const EMPTY_VALUE_LABEL = "empty";
|
|
4
|
+
export interface EmptyValueProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
/** Read by screen readers in place of the dash. Default "empty". */
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* A field that has no value (null, "", an empty list), the same in every
|
|
10
|
+
* Karni component: ResourceTable's cells, ResourceDetail's rows and
|
|
11
|
+
* ChangeHistory's before/after values. Sighted users see a muted en dash,
|
|
12
|
+
* and screen readers hear "empty" instead of "dash" or nothing.
|
|
13
|
+
*
|
|
14
|
+
* A field the role may not read is never rendered as empty: it has no row
|
|
15
|
+
* or cell at all, so an empty value never hints that a hidden one exists.
|
|
16
|
+
*/
|
|
17
|
+
export declare const EmptyValue: React.ForwardRefExoticComponent<EmptyValueProps & React.RefAttributes<HTMLSpanElement>>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { Control, FieldValues } from 'react-hook-form';
|
|
1
|
+
import { Control, FieldValues, Mode } from 'react-hook-form';
|
|
2
2
|
import { FieldDef } from '../../../schema/types';
|
|
3
|
+
import { ErrorSlotProps } from '../../../states/ErrorState';
|
|
4
|
+
import { ResourceLabel } from '../resource-names';
|
|
5
|
+
import { ResourceFormErrorSummaryProps, ResourceFormForbiddenProps, ResourceFormLoadingProps, ResourceFormSubmitErrorProps } from './form-states';
|
|
3
6
|
import * as React from "react";
|
|
4
7
|
/** Props passed to a custom field component (the `fields` escape hatch). */
|
|
5
8
|
export interface KarniFieldProps {
|
|
@@ -19,12 +22,31 @@ export interface FieldOverride {
|
|
|
19
22
|
hidden?: boolean;
|
|
20
23
|
props?: Record<string, unknown>;
|
|
21
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The replaceable parts of ResourceForm (`slots`). Each default is in
|
|
27
|
+
* `defaultResourceFormSlots`.
|
|
28
|
+
*/
|
|
29
|
+
export interface ResourceFormSlots {
|
|
30
|
+
/** While the schema or the edited row loads. */
|
|
31
|
+
Loading: React.ComponentType<ResourceFormLoadingProps>;
|
|
32
|
+
/** A failed load of the schema or the row, with retry when it can help. */
|
|
33
|
+
Error: React.ComponentType<ErrorSlotProps>;
|
|
34
|
+
/** A 403 while loading the row: the role cannot edit it. */
|
|
35
|
+
Forbidden: React.ComponentType<ResourceFormForbiddenProps>;
|
|
36
|
+
/** After a failed submit with more than one problem: every one, each field focusable. */
|
|
37
|
+
ErrorSummary: React.ComponentType<ResourceFormErrorSummaryProps>;
|
|
38
|
+
/** A submit that failed for a reason other than the values (offline, 403, 409, 5xx). */
|
|
39
|
+
SubmitError: React.ComponentType<ResourceFormSubmitErrorProps>;
|
|
40
|
+
}
|
|
41
|
+
/** ResourceForm's own slot components, for wrapping rather than rewriting. */
|
|
42
|
+
export declare const defaultResourceFormSlots: ResourceFormSlots;
|
|
22
43
|
export interface ResourceFormProps {
|
|
23
44
|
resource: string;
|
|
24
45
|
/** Present → edit mode: loads the UNEXPANDED item, PATCHes dirty fields only (D10). */
|
|
25
46
|
id?: string | number;
|
|
26
47
|
defaultValues?: Record<string, unknown>;
|
|
27
48
|
onSuccess?: (result: Record<string, unknown>) => void;
|
|
49
|
+
/** Called with every failed submit's error (the form shows it as well). */
|
|
28
50
|
onError?: (error: unknown) => void;
|
|
29
51
|
fields?: Record<string, FieldOverride>;
|
|
30
52
|
/** Custom RHF-registered nodes appended after the derived fields. */
|
|
@@ -32,7 +54,41 @@ export interface ResourceFormProps {
|
|
|
32
54
|
transformSubmit?: (values: Record<string, unknown>) => Record<string, unknown>;
|
|
33
55
|
/** Replaces the submit row. */
|
|
34
56
|
actions?: React.ReactNode;
|
|
57
|
+
/** The submit button's text. Default "Create" or "Save changes". */
|
|
58
|
+
submitLabel?: React.ReactNode;
|
|
35
59
|
className?: string;
|
|
60
|
+
/** Replaces parts of the form: loading, load error, permission, error summary, submit error. */
|
|
61
|
+
slots?: Partial<ResourceFormSlots>;
|
|
62
|
+
/**
|
|
63
|
+
* When a field first validates (react-hook-form's `mode`). Default "onBlur":
|
|
64
|
+
* a field is checked when the user leaves it, never on every keystroke.
|
|
65
|
+
*/
|
|
66
|
+
validationMode?: Mode;
|
|
67
|
+
/**
|
|
68
|
+
* When a field validates again after a submit (react-hook-form's
|
|
69
|
+
* `reValidateMode`). Default "onBlur". Independently of both, a field that
|
|
70
|
+
* shows an error re-validates on every change, so the error clears as soon
|
|
71
|
+
* as the value is fixed.
|
|
72
|
+
*/
|
|
73
|
+
reValidateMode?: Exclude<Mode, "onTouched" | "all">;
|
|
74
|
+
/** Focus the first field when the form appears. Default: true to create, false to edit. */
|
|
75
|
+
autoFocus?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Warn before the page unloads with unsaved changes (`beforeunload`).
|
|
78
|
+
* Default true. Pass false to opt out. In-app navigation: see `onDirtyChange`.
|
|
79
|
+
*/
|
|
80
|
+
guardUnsavedChanges?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Called when the form gains or loses unsaved changes, and with false just
|
|
83
|
+
* before `onSuccess`. Feed it to `useUnsavedChangesGuard().setDirty` and
|
|
84
|
+
* the router's blocker to stop in-app navigation away from a dirty form.
|
|
85
|
+
*/
|
|
86
|
+
onDirtyChange?: (dirty: boolean) => void;
|
|
87
|
+
/**
|
|
88
|
+
* How the resource is named in messages. The default is derived from
|
|
89
|
+
* `resource` ("events" → "event").
|
|
90
|
+
*/
|
|
91
|
+
resourceLabel?: ResourceLabel;
|
|
36
92
|
}
|
|
37
93
|
/**
|
|
38
94
|
* Schema-driven create and edit form for one resource: inputs, validation and
|
|
@@ -40,11 +96,14 @@ export interface ResourceFormProps {
|
|
|
40
96
|
*
|
|
41
97
|
* Contract "ResourceForm" + D2/D3/D10.
|
|
42
98
|
*
|
|
43
|
-
* Create mode POSTs every prepared value
|
|
99
|
+
* Create mode POSTs every prepared value. Edit mode loads the UNEXPANDED
|
|
44
100
|
* detail record and PATCHes only dirty fields at top-level granularity, with
|
|
45
|
-
* emptied nullable fields sent as null (D3).
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
101
|
+
* emptied nullable fields sent as null (D3). Fields validate when they lose
|
|
102
|
+
* focus. A failed submit keeps every typed value, puts DRF's messages on their
|
|
103
|
+
* fields (a summary lists them when more than one fails) and moves focus there.
|
|
104
|
+
* Any other failure (offline, 403, 409, 5xx) is a banner with "Try again" when
|
|
105
|
+
* it can help. Escape hatches: per-field overrides (`fields`), `extraFields`,
|
|
106
|
+
* `transformSubmit`, `actions`, `slots`, `validationMode` / `reValidateMode`
|
|
107
|
+
* and `guardUnsavedChanges` / `onDirtyChange`.
|
|
49
108
|
*/
|
|
50
109
|
export declare function ResourceForm(props: ResourceFormProps): React.ReactElement;
|
|
@@ -5,16 +5,30 @@ import { FieldDef } from '../../../schema/types';
|
|
|
5
5
|
* text → Textarea, number/integer → Input[type=number], boolean → Switch,
|
|
6
6
|
* date/datetime → native inputs, enum → Select, relation → ResourceSelect
|
|
7
7
|
* (multiple for many), file → FileUpload, json → Textarea with validation).
|
|
8
|
+
*
|
|
9
|
+
* Every control receives react-hook-form's ref (so a failed submit can move
|
|
10
|
+
* focus to it), `aria-invalid`, `aria-describedby` (help text and error) and,
|
|
11
|
+
* where its role supports it, `aria-required`. A field that shows an error
|
|
12
|
+
* re-validates on every change, so the error goes away as soon as the value
|
|
13
|
+
* is fixed. A field without one validates when it loses focus.
|
|
8
14
|
*/
|
|
9
15
|
import * as React from "react";
|
|
10
16
|
export interface DefaultFieldControlProps {
|
|
11
17
|
field: FieldDef;
|
|
12
18
|
control: Control<FieldValues>;
|
|
13
19
|
inputId: string;
|
|
20
|
+
/** The label's id, for controls whose popup needs naming too (ResourceSelect's list). */
|
|
21
|
+
labelId?: string;
|
|
14
22
|
describedBy?: string;
|
|
15
23
|
invalid: boolean;
|
|
24
|
+
/** Sets `aria-required` on controls whose role supports it. */
|
|
25
|
+
required?: boolean;
|
|
16
26
|
disabled?: boolean;
|
|
27
|
+
/** Re-validate on every change while the field shows an error. Default true. */
|
|
28
|
+
revalidateWhileInvalid?: boolean;
|
|
17
29
|
/** FieldOverride.props pass-through, spread onto the rendered input. */
|
|
18
30
|
inputProps?: Record<string, unknown>;
|
|
19
31
|
}
|
|
20
|
-
|
|
32
|
+
/** Kinds whose control role cannot carry aria-required (their label says "required" instead). */
|
|
33
|
+
export declare const KINDS_WITHOUT_ARIA_REQUIRED: ReadonlySet<string>;
|
|
34
|
+
export declare function DefaultFieldControl({ field, control, inputId, labelId, describedBy, invalid, required, disabled, revalidateWhileInvalid, inputProps, }: DefaultFieldControlProps): React.ReactElement;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { FieldDef } from '../../../schema/types';
|
|
2
|
+
import { ErrorSlotProps } from '../../../states/ErrorState';
|
|
3
|
+
/**
|
|
4
|
+
* The states <ResourceForm> shows around its fields, as replaceable slots:
|
|
5
|
+
* the loading skeleton, the permission panel, the error summary after a
|
|
6
|
+
* failed submit and the banner for a submit that failed for another reason.
|
|
7
|
+
* Their defaults are exported (`defaultResourceFormSlots`) so a project can
|
|
8
|
+
* wrap them rather than rewrite them.
|
|
9
|
+
*/
|
|
10
|
+
import * as React from "react";
|
|
11
|
+
/**
|
|
12
|
+
* A field's label-to-control gap as Tailwind 3's `space-y` draws it: a top
|
|
13
|
+
* margin on every child after the first. Tailwind 4's `space-y` puts a bottom
|
|
14
|
+
* margin on the inline <label> instead, which an inline element ignores, so
|
|
15
|
+
* the gap is spelled out to render the same under both.
|
|
16
|
+
*/
|
|
17
|
+
export declare const FIELD_GAP = "[&>:not([hidden])~:not([hidden])]:mt-2";
|
|
18
|
+
/** The submit row, shared by the form and its skeleton so nothing moves when the form arrives. */
|
|
19
|
+
export declare const SUBMIT_ROW = "flex items-center justify-end gap-3 pt-2";
|
|
20
|
+
export interface ResourceFormLoadingProps {
|
|
21
|
+
/**
|
|
22
|
+
* The fields the form will render, labels already overridden, once the
|
|
23
|
+
* schema is known (edit mode while the row loads). Undefined while the
|
|
24
|
+
* schema itself loads.
|
|
25
|
+
*/
|
|
26
|
+
fields?: readonly FieldDef[];
|
|
27
|
+
/** "create" or "update". */
|
|
28
|
+
mode: "create" | "update";
|
|
29
|
+
/** The submit button's label, so the skeleton's button has the real one's width. */
|
|
30
|
+
submitLabel: React.ReactNode;
|
|
31
|
+
/** False when the form's `actions` prop replaces the submit row. */
|
|
32
|
+
showSubmitRow: boolean;
|
|
33
|
+
}
|
|
34
|
+
export interface ResourceFormForbiddenProps {
|
|
35
|
+
/** The verb the role lacks: "edit". */
|
|
36
|
+
action: string;
|
|
37
|
+
/** What it applies to: "this event". */
|
|
38
|
+
resource: string;
|
|
39
|
+
}
|
|
40
|
+
/** One entry of the error summary. */
|
|
41
|
+
export interface ResourceFormFieldError {
|
|
42
|
+
/** The field's name. Absent for a message that belongs to no field. */
|
|
43
|
+
name?: string;
|
|
44
|
+
/** The field's label, as the form shows it. */
|
|
45
|
+
label?: string;
|
|
46
|
+
message: string;
|
|
47
|
+
/** Moves focus to the field's input. Absent for a message that belongs to no field. */
|
|
48
|
+
focus?: () => void;
|
|
49
|
+
/** `#<the input's id>`, for a link to the field. */
|
|
50
|
+
href?: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ResourceFormErrorSummaryProps {
|
|
53
|
+
/** "2 fields need attention", or what failed when no field did. */
|
|
54
|
+
title: string;
|
|
55
|
+
errors: readonly ResourceFormFieldError[];
|
|
56
|
+
}
|
|
57
|
+
export interface ResourceFormSubmitErrorProps extends ErrorSlotProps {
|
|
58
|
+
/** Edit mode, after a conflict: loads the latest version of the row (typed values are kept). */
|
|
59
|
+
onReload?: () => void;
|
|
60
|
+
/** True while that reload runs. */
|
|
61
|
+
reloading?: boolean;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The loading state: the form's own layout with shimmering controls. Once the
|
|
65
|
+
* schema is known it has the real field count, kinds, labels and help text,
|
|
66
|
+
* and the submit row sits where the real one will, so nothing shifts when the
|
|
67
|
+
* values arrive.
|
|
68
|
+
*/
|
|
69
|
+
export declare function ResourceFormSkeleton({ fields, submitLabel, showSubmitRow, }: ResourceFormLoadingProps): React.ReactElement;
|
|
70
|
+
/** The 403-on-load state: the role cannot edit this row, said plainly. */
|
|
71
|
+
export declare function ResourceFormForbidden({ action, resource, }: ResourceFormForbiddenProps): React.ReactElement;
|
|
72
|
+
/** A failed load, with "Try again" when it can help. */
|
|
73
|
+
export declare function ResourceFormLoadError(props: ErrorSlotProps): React.ReactElement;
|
|
74
|
+
/**
|
|
75
|
+
* Every problem after a failed submit, at the top of the form, each field a
|
|
76
|
+
* link that moves focus to it (links wrap like text, so a long message keeps
|
|
77
|
+
* its bullet on its first line). Announced when it appears.
|
|
78
|
+
*/
|
|
79
|
+
export declare function ResourceFormErrorSummary({ title, errors, }: ResourceFormErrorSummaryProps): React.ReactElement;
|
|
80
|
+
/**
|
|
81
|
+
* A submit that failed for a reason other than the values (offline, the
|
|
82
|
+
* server, permission, a conflict): a compact banner above the fields, in
|
|
83
|
+
* describeError's plain words, with "Try again" when it can help and "Load
|
|
84
|
+
* the latest version" after a conflict. The values the user typed stay in
|
|
85
|
+
* the fields.
|
|
86
|
+
*/
|
|
87
|
+
export declare function ResourceFormSubmitError({ error, context, onRetry, retrying, onReload, reloading, }: ResourceFormSubmitErrorProps): React.ReactElement;
|
|
@@ -38,7 +38,7 @@ export interface PrepareSubmitInput {
|
|
|
38
38
|
/** Resolver output from handleSubmit. */
|
|
39
39
|
raw: FormValues;
|
|
40
40
|
mode: "create" | "update";
|
|
41
|
-
/** react-hook-form dirtyFields
|
|
41
|
+
/** react-hook-form dirtyFields, only consulted in "update" mode (D3). */
|
|
42
42
|
dirtyFields?: Record<string, unknown>;
|
|
43
43
|
}
|
|
44
44
|
export interface PreparedSubmit {
|
|
@@ -47,8 +47,8 @@ export interface PreparedSubmit {
|
|
|
47
47
|
jsonErrors: JsonFieldError[];
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* The D3 submit pipeline. Create mode sends every defined value
|
|
51
|
-
* sends only fields whose dirtyFields subtree is truthy
|
|
50
|
+
* The D3 submit pipeline. Create mode sends every defined value. Update mode
|
|
51
|
+
* sends only fields whose dirtyFields subtree is truthy, and always the WHOLE
|
|
52
52
|
* field value. Keys without a schema field (extraFields) pass through as-is.
|
|
53
53
|
*/
|
|
54
54
|
export declare function prepareSubmit({ fields, raw, mode, dirtyFields, }: PrepareSubmitInput): PreparedSubmit;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface MappedServerErrors {
|
|
2
|
+
/** Field path → its messages, joined into one sentence run. */
|
|
3
|
+
fields: Record<string, string>;
|
|
4
|
+
/** Messages that belong to no field the form shows. */
|
|
5
|
+
root: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface MapServerErrorsOptions {
|
|
8
|
+
/** Whether the form renders an input for `path` ("title", "address.city"). */
|
|
9
|
+
isField: (path: string) => boolean;
|
|
10
|
+
/** How a path is named in a form-level message. The default humanizes it. */
|
|
11
|
+
labelOf?: (path: string) => string;
|
|
12
|
+
}
|
|
13
|
+
/** Splits a DRF error body into field messages and form-level messages. */
|
|
14
|
+
export declare function mapServerErrors(data: unknown, { isField, labelOf }: MapServerErrorsOptions): MappedServerErrors;
|
|
@@ -1,18 +1,60 @@
|
|
|
1
|
+
import { ChangeLogEntry } from '../../../hooks/useChangeHistory';
|
|
2
|
+
import { ErrorSlotProps } from '../../../states/ErrorState';
|
|
1
3
|
import * as React from "react";
|
|
4
|
+
/** What the `Loading` slot receives. */
|
|
5
|
+
export interface ChangeHistoryLoadingProps {
|
|
6
|
+
/** How many entry placeholders to draw. */
|
|
7
|
+
rows: number;
|
|
8
|
+
/** True once loading has taken longer than SLOW_AFTER_MS (500ms): show the placeholder now. */
|
|
9
|
+
slow: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** What the `Empty` slot receives: the row has no recorded change. */
|
|
12
|
+
export interface ChangeHistoryEmptyProps {
|
|
13
|
+
/** The `empty` prop ("No changes recorded." by default). */
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/** What the `Entry` slot receives: one recorded change. */
|
|
17
|
+
export interface ChangeHistoryEntryProps {
|
|
18
|
+
entry: ChangeLogEntry;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The replaceable parts of ChangeHistory. Pass any subset as `slots`.
|
|
22
|
+
* `defaultChangeHistorySlots` holds Karni's own, to wrap them.
|
|
23
|
+
*/
|
|
24
|
+
export interface ChangeHistorySlots {
|
|
25
|
+
Loading: React.ComponentType<ChangeHistoryLoadingProps>;
|
|
26
|
+
/** Any failure: offline, server error, 403, a model that keeps no history (404). */
|
|
27
|
+
Error: React.ComponentType<ErrorSlotProps>;
|
|
28
|
+
Empty: React.ComponentType<ChangeHistoryEmptyProps>;
|
|
29
|
+
/** One entry of the list (rendered inside its <li>). */
|
|
30
|
+
Entry: React.ComponentType<ChangeHistoryEntryProps>;
|
|
31
|
+
}
|
|
2
32
|
export interface ChangeHistoryProps {
|
|
3
33
|
resource: string;
|
|
4
34
|
id: string | number;
|
|
5
|
-
/** Entries to show (the backend paginates
|
|
35
|
+
/** Entries to show (the backend paginates). Default: the API's page size. */
|
|
6
36
|
pageSize?: number;
|
|
7
37
|
title?: React.ReactNode;
|
|
8
38
|
/** Shown when the row has no recorded changes. */
|
|
9
39
|
empty?: React.ReactNode;
|
|
40
|
+
/** The resource in words, singular, for messages ("the history of this event"). Default: from `resource`. */
|
|
41
|
+
resourceLabel?: string;
|
|
42
|
+
/** Replace parts of the list: Loading, Error, Empty, Entry. */
|
|
43
|
+
slots?: Partial<ChangeHistorySlots>;
|
|
10
44
|
className?: string;
|
|
11
45
|
}
|
|
46
|
+
/** Karni's own slots, exported so a project can wrap one instead of rewriting it. */
|
|
47
|
+
export declare const defaultChangeHistorySlots: ChangeHistorySlots;
|
|
12
48
|
/**
|
|
13
49
|
* The field-level change history of one row: when, by whom, what changed
|
|
14
50
|
* from what to what. Reads `GET /<resource>/<id>/history/` (models that opt
|
|
15
|
-
* in with `class Karni: audit = True`)
|
|
16
|
-
* already absent from each diff, redacted ones say so
|
|
51
|
+
* in with `class Karni: audit = True`). Fields the role may not see are
|
|
52
|
+
* already absent from each diff, redacted ones say so, and an empty value
|
|
53
|
+
* is the shared EmptyValue (a dash, read as "empty").
|
|
54
|
+
*
|
|
55
|
+
* States: entry placeholders while it loads (drawn after 500ms), failures
|
|
56
|
+
* in describeError's words with "Try again" when it can help. A 403 reads
|
|
57
|
+
* "You can't view the history of this <resource>". Offline says so and
|
|
58
|
+
* reloads when the connection returns. Escape hatch: `slots`.
|
|
17
59
|
*/
|
|
18
|
-
export declare function ChangeHistory({ resource, id, pageSize, title, empty, className, }: ChangeHistoryProps): React.ReactElement;
|
|
60
|
+
export declare function ChangeHistory({ resource, id, pageSize, title, empty, resourceLabel, slots, className, }: ChangeHistoryProps): React.ReactElement;
|
|
@@ -1,18 +1,84 @@
|
|
|
1
|
-
import { ImportJob } from '../../../hooks/useResourceImport';
|
|
1
|
+
import { ImportJob, ImportProblem, ImportResult } from '../../../hooks/useResourceImport';
|
|
2
|
+
import { ErrorSlotProps } from '../../../states/ErrorState';
|
|
2
3
|
import * as React from "react";
|
|
4
|
+
/** What the `Loading` slot receives while the import description loads. */
|
|
5
|
+
export interface ResourceImportLoadingProps {
|
|
6
|
+
/** True once loading has taken longer than SLOW_AFTER_MS (500ms): show the placeholder now. */
|
|
7
|
+
slow: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** What the `Progress` slot receives while the backend previews or commits a job. */
|
|
10
|
+
export interface ImportProgressProps {
|
|
11
|
+
job: ImportJob;
|
|
12
|
+
}
|
|
13
|
+
/** What the `Summary` slot receives: a preview's or a commit's counts. */
|
|
14
|
+
export interface ImportSummaryProps {
|
|
15
|
+
job: ImportJob;
|
|
16
|
+
result: ImportResult;
|
|
17
|
+
}
|
|
18
|
+
/** What the `Problems` slot receives: the rows the backend refused, with why. */
|
|
19
|
+
export interface ImportProblemsProps {
|
|
20
|
+
job: ImportJob;
|
|
21
|
+
problems: readonly ImportProblem[];
|
|
22
|
+
}
|
|
23
|
+
/** What the `ConfirmCommit` slot receives: the step between a preview and its commit. */
|
|
24
|
+
export interface ImportConfirmProps {
|
|
25
|
+
open: boolean;
|
|
26
|
+
onOpenChange: (open: boolean) => void;
|
|
27
|
+
job: ImportJob;
|
|
28
|
+
/** The preview's counts: what the commit will do. */
|
|
29
|
+
result: ImportResult;
|
|
30
|
+
/** The resource in words, plural: "events". */
|
|
31
|
+
resourceLabel: string;
|
|
32
|
+
onConfirm: () => void;
|
|
33
|
+
/** True while the commit request runs. */
|
|
34
|
+
pending: boolean;
|
|
35
|
+
/** Why the last commit attempt failed (null before an attempt and while one runs). */
|
|
36
|
+
error: unknown;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The replaceable parts of ResourceImport. Pass any subset as `slots`.
|
|
40
|
+
* `defaultResourceImportSlots` holds Karni's own, to wrap them.
|
|
41
|
+
*/
|
|
42
|
+
export interface ResourceImportSlots {
|
|
43
|
+
/** The upload form's placeholder while the import description loads. */
|
|
44
|
+
Loading: React.ComponentType<ResourceImportLoadingProps>;
|
|
45
|
+
/** A failure: loading what the import accepts, an upload, a preview. */
|
|
46
|
+
Error: React.ComponentType<ErrorSlotProps>;
|
|
47
|
+
/** A running preview or commit. */
|
|
48
|
+
Progress: React.ComponentType<ImportProgressProps>;
|
|
49
|
+
/** Created / updated / skipped counts. */
|
|
50
|
+
Summary: React.ComponentType<ImportSummaryProps>;
|
|
51
|
+
/** The table of rows with errors. */
|
|
52
|
+
Problems: React.ComponentType<ImportProblemsProps>;
|
|
53
|
+
/** The confirmation before a commit. */
|
|
54
|
+
ConfirmCommit: React.ComponentType<ImportConfirmProps>;
|
|
55
|
+
}
|
|
3
56
|
export interface ResourceImportProps {
|
|
4
57
|
resource: string;
|
|
5
58
|
title?: React.ReactNode;
|
|
6
59
|
/** Called when a commit finishes (the job is `done`). */
|
|
7
60
|
onDone?: (job: ImportJob) => void;
|
|
61
|
+
/** The resource in words, plural, for messages ("You can't import events"). Default: from `resource`. */
|
|
62
|
+
resourceLabel?: string;
|
|
63
|
+
/** Replace parts of the flow: Loading, Error, Progress, Summary, Problems, ConfirmCommit. */
|
|
64
|
+
slots?: Partial<ResourceImportSlots>;
|
|
8
65
|
className?: string;
|
|
9
66
|
}
|
|
67
|
+
/** Karni's own slots, exported so a project can wrap one instead of rewriting it. */
|
|
68
|
+
export declare const defaultResourceImportSlots: ResourceImportSlots;
|
|
10
69
|
/**
|
|
11
70
|
* Import a CSV or XLSX file into a resource: upload, map columns to fields,
|
|
12
|
-
* preview (the mandatory dry run
|
|
13
|
-
* row's errors), then commit. Drives
|
|
14
|
-
* validates every row through the same
|
|
15
|
-
* inside the user's scope and refuses
|
|
16
|
-
* files run in the background with
|
|
71
|
+
* preview (the mandatory dry run, which shows N created / updated / skipped
|
|
72
|
+
* with every row's errors), confirm, then commit. Drives
|
|
73
|
+
* `/<resource>/import/…`. The backend validates every row through the same
|
|
74
|
+
* serializer the API uses, keeps rows inside the user's scope and refuses
|
|
75
|
+
* fields the role may not write. Large files run in the background with
|
|
76
|
+
* progress.
|
|
77
|
+
*
|
|
78
|
+
* States: a placeholder in the form's layout while the import description
|
|
79
|
+
* loads (drawn after 500ms). When that fails the form is not shown: a 403
|
|
80
|
+
* reads "You can't import <resource>". A failed upload keeps the chosen file,
|
|
81
|
+
* offers "Try again" when it can help and moves focus to the error. Offline
|
|
82
|
+
* requests wait for the connection and say so. Escape hatch: `slots`.
|
|
17
83
|
*/
|
|
18
|
-
export declare function ResourceImport({ resource, title, onDone, className, }: ResourceImportProps): React.ReactElement;
|
|
84
|
+
export declare function ResourceImport({ resource, title, onDone, resourceLabel, slots, className, }: ResourceImportProps): React.ReactElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The words a resource's messages use: "events" → "event" ("Couldn't find
|
|
3
|
+
* this event"), "loose-slots" → "loose slots" ("You can't import loose
|
|
4
|
+
* slots"). English rules only. Every component that words a message takes
|
|
5
|
+
* a `resourceLabel` prop to name the resource in the project's own words.
|
|
6
|
+
*
|
|
7
|
+
* Internal to @karni/ui.
|
|
8
|
+
*/
|
|
9
|
+
/** The router prefix as words: "loose-slots" → "loose slots". */
|
|
10
|
+
export declare function pluralNoun(resource: string): string;
|
|
11
|
+
/** One of the resource: "events" → "event", "categories" → "category", "addresses" → "address". */
|
|
12
|
+
export declare function singularNoun(resource: string): string;
|