ding-react-admin 2.0.0 → 2.0.4

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 (169) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +254 -195
  3. package/dist/index.css +2 -0
  4. package/dist/index.js +1772 -1169
  5. package/dist/src/components/NavMenuLabel.d.ts +8 -0
  6. package/dist/src/components/NavMenuLabel.d.ts.map +1 -0
  7. package/dist/src/components/NavMenuSearch.d.ts +9 -0
  8. package/dist/src/components/NavMenuSearch.d.ts.map +1 -0
  9. package/dist/src/components/ScrollableArea.d.ts +10 -0
  10. package/dist/src/components/ScrollableArea.d.ts.map +1 -0
  11. package/dist/src/components/index.d.ts +3 -0
  12. package/dist/src/components/index.d.ts.map +1 -1
  13. package/dist/src/context/AuthProvider.d.ts +2 -0
  14. package/dist/src/context/AuthProvider.d.ts.map +1 -1
  15. package/dist/src/crud/FormGlobalErrorsAlert.d.ts +5 -0
  16. package/dist/src/crud/FormGlobalErrorsAlert.d.ts.map +1 -0
  17. package/dist/src/crud/ResourceForm.d.ts +1 -4
  18. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  19. package/dist/src/crud/ResourceFormModal.d.ts +7 -1
  20. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  21. package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
  22. package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
  23. package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
  24. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  25. package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
  26. package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
  27. package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
  28. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  29. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  30. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  31. package/dist/src/crud/index.d.ts +5 -0
  32. package/dist/src/crud/index.d.ts.map +1 -1
  33. package/dist/src/crud/types.d.ts +12 -0
  34. package/dist/src/crud/types.d.ts.map +1 -1
  35. package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
  36. package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
  37. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
  38. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
  39. package/dist/src/crud/utils/formErrors.d.ts +18 -7
  40. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  41. package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
  42. package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
  43. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +1 -0
  44. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -1
  45. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
  46. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
  47. package/dist/src/crud/utils/toFormData.d.ts +1 -1
  48. package/dist/src/crud/utils/toFormData.d.ts.map +1 -1
  49. package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
  50. package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
  51. package/dist/src/crud/utils/useChoices.d.ts +27 -1
  52. package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
  53. package/dist/src/crud/utils/useFormRecord.d.ts +9 -7
  54. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -1
  55. package/dist/src/data/parseFormErrorHelpers.d.ts +6 -27
  56. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  57. package/dist/src/index.d.ts +2 -2
  58. package/dist/src/index.d.ts.map +1 -1
  59. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  60. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  61. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
  62. package/dist/src/layouts/navFilter.d.ts +5 -0
  63. package/dist/src/layouts/navFilter.d.ts.map +1 -0
  64. package/dist/src/layouts/navMenuItems.d.ts +6 -0
  65. package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
  66. package/dist/src/types.d.ts +12 -0
  67. package/dist/src/types.d.ts.map +1 -1
  68. package/package.json +79 -59
  69. package/src/app/AdminApp.tsx +34 -34
  70. package/src/components/AuthAlternateLink.tsx +23 -23
  71. package/src/components/DensitySwitch.tsx +31 -31
  72. package/src/components/NavMenuLabel.tsx +26 -0
  73. package/src/components/NavMenuSearch.tsx +76 -0
  74. package/src/components/ScrollableArea.tsx +46 -0
  75. package/src/components/ThemeSwitch.tsx +28 -28
  76. package/src/components/ThemeToolbar.tsx +34 -34
  77. package/src/components/index.ts +7 -4
  78. package/src/components/navMenu.css +21 -0
  79. package/src/components/scrollable.css +28 -0
  80. package/src/context/AppThemeProvider.tsx +128 -128
  81. package/src/context/AuthProvider.tsx +89 -77
  82. package/src/context/DataProvider.tsx +30 -30
  83. package/src/context/PermissionsProvider.tsx +57 -57
  84. package/src/crud/FilterBar.tsx +36 -36
  85. package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
  86. package/src/crud/FormSteps.tsx +135 -135
  87. package/src/crud/FormTabs.tsx +126 -126
  88. package/src/crud/InlineFormSet.tsx +162 -162
  89. package/src/crud/ListActionsBar.tsx +106 -106
  90. package/src/crud/ResourceForm.tsx +83 -160
  91. package/src/crud/ResourceFormModal.tsx +75 -133
  92. package/src/crud/ResourceRecordForm.tsx +166 -0
  93. package/src/crud/columns/BooleanColumn.tsx +32 -32
  94. package/src/crud/columns/CustomColumn.tsx +33 -33
  95. package/src/crud/columns/DateColumn.tsx +28 -28
  96. package/src/crud/columns/ImageColumn.tsx +58 -58
  97. package/src/crud/columns/NumberColumn.tsx +31 -31
  98. package/src/crud/columns/ReferenceColumn.tsx +85 -85
  99. package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
  100. package/src/crud/columns/TextColumn.tsx +38 -38
  101. package/src/crud/context/FilterContext.tsx +71 -71
  102. package/src/crud/context/FormContext.tsx +39 -39
  103. package/src/crud/context/FormSectionContext.tsx +22 -22
  104. package/src/crud/context/InlineFieldsRegistry.tsx +61 -61
  105. package/src/crud/context/ListContext.tsx +93 -93
  106. package/src/crud/fields/BooleanField.tsx +38 -38
  107. package/src/crud/fields/DateField.tsx +53 -53
  108. package/src/crud/fields/FileField.tsx +123 -123
  109. package/src/crud/fields/ImageField.tsx +120 -120
  110. package/src/crud/fields/NumberField.tsx +52 -52
  111. package/src/crud/fields/PasswordField.tsx +99 -99
  112. package/src/crud/fields/ReferenceField.tsx +237 -96
  113. package/src/crud/fields/ReferenceInputActions.tsx +106 -0
  114. package/src/crud/fields/ReferenceManyField.tsx +200 -76
  115. package/src/crud/fields/SelectField.tsx +48 -48
  116. package/src/crud/fields/TextField.tsx +46 -46
  117. package/src/crud/fields/uploadFieldUtils.ts +21 -21
  118. package/src/crud/fields/useUploadPreviewUrl.ts +20 -20
  119. package/src/crud/filters/BooleanFilter.tsx +38 -38
  120. package/src/crud/filters/DateFilter.tsx +35 -35
  121. package/src/crud/filters/NumberFilter.tsx +33 -33
  122. package/src/crud/filters/ReferenceFilter.tsx +192 -173
  123. package/src/crud/filters/SelectFilter.tsx +44 -44
  124. package/src/crud/filters/TextFilter.tsx +36 -36
  125. package/src/crud/index.ts +99 -94
  126. package/src/crud/types.ts +146 -134
  127. package/src/crud/utils/buildFormPayload.ts +24 -24
  128. package/src/crud/utils/buildInlineRowsPayload.ts +37 -37
  129. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -29
  130. package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
  131. package/src/crud/utils/choiceSelectionUtils.ts +116 -0
  132. package/src/crud/utils/formErrors.test.ts +168 -0
  133. package/src/crud/utils/formErrors.ts +138 -42
  134. package/src/crud/utils/formSectionErrors.ts +85 -85
  135. package/src/crud/utils/getFormValue.ts +13 -13
  136. package/src/crud/utils/hasUploadValues.ts +9 -9
  137. package/src/crud/utils/nestedFieldPath.ts +8 -8
  138. package/src/crud/utils/prepareFormSubmitBody.ts +22 -17
  139. package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
  140. package/src/crud/utils/setFormValue.ts +18 -18
  141. package/src/crud/utils/sortQueryParam.ts +37 -37
  142. package/src/crud/utils/toFormData.ts +79 -81
  143. package/src/crud/utils/uploadReferenceUtils.ts +44 -0
  144. package/src/crud/utils/useAbortableEffect.ts +17 -17
  145. package/src/crud/utils/useChoices.ts +330 -182
  146. package/src/crud/utils/useFormRecord.ts +179 -156
  147. package/src/crud/utils/useListQueryState.ts +191 -191
  148. package/src/data/abortError.test.ts +19 -19
  149. package/src/data/abortError.ts +10 -10
  150. package/src/data/createMemoryResourceHandlers.ts +78 -78
  151. package/src/data/createRestResourceHandlers.ts +63 -63
  152. package/src/data/dataUtils.test.ts +228 -220
  153. package/src/data/inMemoryList.ts +97 -97
  154. package/src/data/parseFormErrorHelpers.test.ts +201 -138
  155. package/src/data/parseFormErrorHelpers.ts +376 -311
  156. package/src/data/sortHelpers.ts +48 -48
  157. package/src/index.ts +211 -207
  158. package/src/layouts/AdminLayout.tsx +530 -382
  159. package/src/layouts/AuthPageLayout.tsx +87 -81
  160. package/src/layouts/navFilter.ts +49 -0
  161. package/src/layouts/navMenuItems.tsx +44 -0
  162. package/src/pages/LoginPage.tsx +80 -80
  163. package/src/pages/PlaceholderPage.tsx +9 -9
  164. package/src/permissions/resourcePermissions.ts +47 -47
  165. package/src/router/createAdminRouter.tsx +64 -64
  166. package/src/router/guards.tsx +63 -63
  167. package/src/router/routeAccess.ts +89 -89
  168. package/src/types.ts +10 -0
  169. package/src/vite-env.d.ts +1 -1
@@ -0,0 +1,166 @@
1
+ import { App, Button, Form, Space, Spin } from "antd";
2
+ import {
3
+ FormProvider,
4
+ useForm,
5
+ type DefaultValues,
6
+ type FieldValues,
7
+ } from "react-hook-form";
8
+ import { useRef, useState, type ReactNode } from "react";
9
+ import { Link } from "react-router-dom";
10
+ import { useDataProvider } from "../context/DataProvider";
11
+ import { FormMetaProvider } from "./context/FormContext";
12
+ import { PayloadFieldsProvider } from "./context/PayloadFieldsContext";
13
+ import {
14
+ InlineFieldsRegistryProvider,
15
+ type InlineFieldRegistration,
16
+ } from "./context/InlineFieldsRegistry";
17
+ import { FormGlobalErrorsAlert } from "./FormGlobalErrorsAlert";
18
+ import { useFormRecordLoad, useFormRecordSave } from "./utils/useFormRecord";
19
+
20
+ export type ResourceRecordFormProps<T extends FieldValues & { id?: unknown }> = {
21
+ resource: string;
22
+ id: string | undefined;
23
+ children: ReactNode;
24
+ defaultValues?: Partial<T>;
25
+ /** When false, skip loading (e.g. modal closed). Defaults to true. */
26
+ enabled?: boolean;
27
+ canSave?: boolean;
28
+ onCancel: () => void;
29
+ /** When set, Cancel renders as a router Link instead of a button. */
30
+ cancelHref?: string;
31
+ onSuccess?: (record: T) => void;
32
+ /** overlay: dim form + spinner; replace: show spinner instead of form. */
33
+ loadingMode?: "overlay" | "replace";
34
+ };
35
+
36
+ /**
37
+ * Shared create/edit form — load record, save, field providers, and actions.
38
+ * Used by ResourceForm (page) and ResourceFormModal.
39
+ */
40
+ export function ResourceRecordForm<T extends FieldValues & { id?: unknown }>({
41
+ resource,
42
+ id,
43
+ children,
44
+ defaultValues,
45
+ enabled = true,
46
+ canSave = true,
47
+ onCancel,
48
+ cancelHref,
49
+ onSuccess,
50
+ loadingMode = "overlay",
51
+ }: ResourceRecordFormProps<T>) {
52
+ const isNew = id === "new" || !id;
53
+ const recordId = isNew ? undefined : id;
54
+ const dp = useDataProvider();
55
+ const { message } = App.useApp();
56
+ const payloadFieldsRef = useRef(new Set<string>());
57
+ const inlineRegistryRef = useRef(new Map<string, InlineFieldRegistration>());
58
+ const [globalErrors, setGlobalErrors] = useState<string[]>([]);
59
+
60
+ const form = useForm<T>({
61
+ defaultValues: defaultValues as DefaultValues<T>,
62
+ });
63
+
64
+ const { loading, formVersion } = useFormRecordLoad({
65
+ dp,
66
+ resource,
67
+ id: recordId,
68
+ isNew,
69
+ form,
70
+ message,
71
+ defaultValues,
72
+ enabled,
73
+ });
74
+
75
+ const { onSubmit, saving } = useFormRecordSave({
76
+ dp,
77
+ resource,
78
+ id: recordId,
79
+ isNew,
80
+ form,
81
+ message,
82
+ payloadFieldsRef,
83
+ inlineRegistryRef,
84
+ setGlobalErrors,
85
+ onSuccess,
86
+ });
87
+
88
+ const busy = loading || saving;
89
+
90
+ const handleFinish = () => {
91
+ void form.handleSubmit(onSubmit, () => {
92
+ message.warning("Please fix the errors below.");
93
+ })();
94
+ };
95
+
96
+ const cancelButton = (
97
+ <Button disabled={busy} onClick={cancelHref ? undefined : onCancel}>
98
+ Cancel
99
+ </Button>
100
+ );
101
+
102
+ const formContent = (
103
+ <FormMetaProvider resource={resource} isNew={isNew}>
104
+ <PayloadFieldsProvider fieldsRef={payloadFieldsRef}>
105
+ <InlineFieldsRegistryProvider registryRef={inlineRegistryRef}>
106
+ <div style={{ position: "relative" }}>
107
+ {busy && loadingMode === "overlay" ? (
108
+ <div
109
+ style={{
110
+ position: "absolute",
111
+ inset: 0,
112
+ display: "flex",
113
+ alignItems: "center",
114
+ justifyContent: "center",
115
+ zIndex: 1,
116
+ }}
117
+ >
118
+ <Spin />
119
+ </div>
120
+ ) : null}
121
+ <FormProvider {...form} key={formVersion}>
122
+ <Form
123
+ layout="vertical"
124
+ onFinish={handleFinish}
125
+ style={
126
+ loadingMode === "overlay"
127
+ ? {
128
+ opacity: busy ? 0.4 : 1,
129
+ pointerEvents: busy ? "none" : undefined,
130
+ }
131
+ : undefined
132
+ }
133
+ >
134
+ <FormGlobalErrorsAlert errors={globalErrors} />
135
+ {children}
136
+ <Form.Item style={{ marginTop: 16, marginBottom: 0 }}>
137
+ <Space>
138
+ <Button
139
+ type="primary"
140
+ htmlType="submit"
141
+ loading={saving}
142
+ disabled={busy || !canSave}
143
+ >
144
+ Save
145
+ </Button>
146
+ {cancelHref ? (
147
+ <Link to={cancelHref}>{cancelButton}</Link>
148
+ ) : (
149
+ cancelButton
150
+ )}
151
+ </Space>
152
+ </Form.Item>
153
+ </Form>
154
+ </FormProvider>
155
+ </div>
156
+ </InlineFieldsRegistryProvider>
157
+ </PayloadFieldsProvider>
158
+ </FormMetaProvider>
159
+ );
160
+
161
+ if (loading && !saving && loadingMode === "replace") {
162
+ return <Spin />;
163
+ }
164
+
165
+ return formContent;
166
+ }
@@ -1,32 +1,32 @@
1
- import { useMemo } from "react";
2
- import type { BaseSourceProps } from "../types";
3
- import { useRegisterColumn } from "../context/ListContext";
4
-
5
- export type BooleanColumnProps = BaseSourceProps & {
6
- sortable?: boolean;
7
- };
8
-
9
- export function BooleanColumn({
10
- source,
11
- label,
12
- sortable = true,
13
- }: BooleanColumnProps) {
14
- const def = useMemo(
15
- () => ({
16
- key: source,
17
- source,
18
- label,
19
- sortable,
20
- buildColumn: () => ({
21
- title: label ?? source,
22
- dataIndex: source,
23
- key: source,
24
- sorter: sortable ? true : undefined,
25
- render: (v: boolean) => (v ? "Yes" : "No"),
26
- }),
27
- }),
28
- [source, label, sortable],
29
- );
30
- useRegisterColumn(def);
31
- return null;
32
- }
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+
5
+ export type BooleanColumnProps = BaseSourceProps & {
6
+ sortable?: boolean;
7
+ };
8
+
9
+ export function BooleanColumn({
10
+ source,
11
+ label,
12
+ sortable = true,
13
+ }: BooleanColumnProps) {
14
+ const def = useMemo(
15
+ () => ({
16
+ key: source,
17
+ source,
18
+ label,
19
+ sortable,
20
+ buildColumn: () => ({
21
+ title: label ?? source,
22
+ dataIndex: source,
23
+ key: source,
24
+ sorter: sortable ? true : undefined,
25
+ render: (v: boolean) => (v ? "Yes" : "No"),
26
+ }),
27
+ }),
28
+ [source, label, sortable],
29
+ );
30
+ useRegisterColumn(def);
31
+ return null;
32
+ }
@@ -1,33 +1,33 @@
1
- import { useMemo, type ReactNode } from "react";
2
- import { useRegisterColumn } from "../context/ListContext";
3
-
4
- export type CustomColumnProps = {
5
- source: string;
6
- label?: string;
7
- sortable?: boolean;
8
- render: (record: Record<string, unknown>) => ReactNode;
9
- };
10
-
11
- export function CustomColumn({
12
- source,
13
- label,
14
- sortable = false,
15
- render,
16
- }: CustomColumnProps) {
17
- const def = useMemo(
18
- () => ({
19
- key: source,
20
- source,
21
- label,
22
- sortable,
23
- buildColumn: () => ({
24
- title: label ?? source,
25
- key: source,
26
- render: (_: unknown, record: Record<string, unknown>) => render(record),
27
- }),
28
- }),
29
- [source, label, sortable, render],
30
- );
31
- useRegisterColumn(def);
32
- return null;
33
- }
1
+ import { useMemo, type ReactNode } from "react";
2
+ import { useRegisterColumn } from "../context/ListContext";
3
+
4
+ export type CustomColumnProps = {
5
+ source: string;
6
+ label?: string;
7
+ sortable?: boolean;
8
+ render: (record: Record<string, unknown>) => ReactNode;
9
+ };
10
+
11
+ export function CustomColumn({
12
+ source,
13
+ label,
14
+ sortable = false,
15
+ render,
16
+ }: CustomColumnProps) {
17
+ const def = useMemo(
18
+ () => ({
19
+ key: source,
20
+ source,
21
+ label,
22
+ sortable,
23
+ buildColumn: () => ({
24
+ title: label ?? source,
25
+ key: source,
26
+ render: (_: unknown, record: Record<string, unknown>) => render(record),
27
+ }),
28
+ }),
29
+ [source, label, sortable, render],
30
+ );
31
+ useRegisterColumn(def);
32
+ return null;
33
+ }
@@ -1,28 +1,28 @@
1
- import { useMemo } from "react";
2
- import type { BaseSourceProps } from "../types";
3
- import { useRegisterColumn } from "../context/ListContext";
4
-
5
- export type DateColumnProps = BaseSourceProps & {
6
- sortable?: boolean;
7
- };
8
-
9
- export function DateColumn({ source, label, sortable = true }: DateColumnProps) {
10
- const def = useMemo(
11
- () => ({
12
- key: source,
13
- source,
14
- label,
15
- sortable,
16
- buildColumn: () => ({
17
- title: label ?? source,
18
- dataIndex: source,
19
- key: source,
20
- sorter: sortable ? true : undefined,
21
- render: (v: string) => (v ? String(v).slice(0, 10) : "—"),
22
- }),
23
- }),
24
- [source, label, sortable],
25
- );
26
- useRegisterColumn(def);
27
- return null;
28
- }
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+
5
+ export type DateColumnProps = BaseSourceProps & {
6
+ sortable?: boolean;
7
+ };
8
+
9
+ export function DateColumn({ source, label, sortable = true }: DateColumnProps) {
10
+ const def = useMemo(
11
+ () => ({
12
+ key: source,
13
+ source,
14
+ label,
15
+ sortable,
16
+ buildColumn: () => ({
17
+ title: label ?? source,
18
+ dataIndex: source,
19
+ key: source,
20
+ sorter: sortable ? true : undefined,
21
+ render: (v: string) => (v ? String(v).slice(0, 10) : "—"),
22
+ }),
23
+ }),
24
+ [source, label, sortable],
25
+ );
26
+ useRegisterColumn(def);
27
+ return null;
28
+ }
@@ -1,58 +1,58 @@
1
- import { useMemo, type CSSProperties } from "react";
2
- import type { BaseSourceProps } from "../types";
3
- import { useRegisterColumn } from "../context/ListContext";
4
-
5
- export type ImageColumnProps = BaseSourceProps & {
6
- sortable?: boolean;
7
- /** Thumbnail width in pixels. */
8
- width?: number;
9
- /** Thumbnail height in pixels. */
10
- height?: number;
11
- objectFit?: CSSProperties["objectFit"];
12
- borderRadius?: number;
13
- alt?: string;
14
- };
15
-
16
- export function ImageColumn({
17
- source,
18
- label,
19
- sortable = false,
20
- width = 40,
21
- height = 40,
22
- objectFit = "cover",
23
- borderRadius = 4,
24
- alt = "",
25
- }: ImageColumnProps) {
26
- const def = useMemo(
27
- () => ({
28
- key: source,
29
- source,
30
- label,
31
- sortable,
32
- buildColumn: () => ({
33
- title: label ?? source,
34
- dataIndex: source,
35
- key: source,
36
- sorter: sortable ? true : undefined,
37
- render: (value: unknown) => {
38
- if (value == null || value === "") return null;
39
- return (
40
- <img
41
- src={String(value)}
42
- alt={alt}
43
- style={{
44
- width,
45
- height,
46
- objectFit,
47
- borderRadius,
48
- }}
49
- />
50
- );
51
- },
52
- }),
53
- }),
54
- [source, label, sortable, width, height, objectFit, borderRadius, alt],
55
- );
56
- useRegisterColumn(def);
57
- return null;
58
- }
1
+ import { useMemo, type CSSProperties } from "react";
2
+ import type { BaseSourceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+
5
+ export type ImageColumnProps = BaseSourceProps & {
6
+ sortable?: boolean;
7
+ /** Thumbnail width in pixels. */
8
+ width?: number;
9
+ /** Thumbnail height in pixels. */
10
+ height?: number;
11
+ objectFit?: CSSProperties["objectFit"];
12
+ borderRadius?: number;
13
+ alt?: string;
14
+ };
15
+
16
+ export function ImageColumn({
17
+ source,
18
+ label,
19
+ sortable = false,
20
+ width = 40,
21
+ height = 40,
22
+ objectFit = "cover",
23
+ borderRadius = 4,
24
+ alt = "",
25
+ }: ImageColumnProps) {
26
+ const def = useMemo(
27
+ () => ({
28
+ key: source,
29
+ source,
30
+ label,
31
+ sortable,
32
+ buildColumn: () => ({
33
+ title: label ?? source,
34
+ dataIndex: source,
35
+ key: source,
36
+ sorter: sortable ? true : undefined,
37
+ render: (value: unknown) => {
38
+ if (value == null || value === "") return null;
39
+ return (
40
+ <img
41
+ src={String(value)}
42
+ alt={alt}
43
+ style={{
44
+ width,
45
+ height,
46
+ objectFit,
47
+ borderRadius,
48
+ }}
49
+ />
50
+ );
51
+ },
52
+ }),
53
+ }),
54
+ [source, label, sortable, width, height, objectFit, borderRadius, alt],
55
+ );
56
+ useRegisterColumn(def);
57
+ return null;
58
+ }
@@ -1,31 +1,31 @@
1
- import { useMemo } from "react";
2
- import type { BaseSourceProps } from "../types";
3
- import { useRegisterColumn } from "../context/ListContext";
4
-
5
- export type NumberColumnProps = BaseSourceProps & {
6
- sortable?: boolean;
7
- };
8
-
9
- export function NumberColumn({
10
- source,
11
- label,
12
- sortable = true,
13
- }: NumberColumnProps) {
14
- const def = useMemo(
15
- () => ({
16
- key: source,
17
- source,
18
- label,
19
- sortable,
20
- buildColumn: () => ({
21
- title: label ?? source,
22
- dataIndex: source,
23
- key: source,
24
- sorter: sortable ? true : undefined,
25
- }),
26
- }),
27
- [source, label, sortable],
28
- );
29
- useRegisterColumn(def);
30
- return null;
31
- }
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+
5
+ export type NumberColumnProps = BaseSourceProps & {
6
+ sortable?: boolean;
7
+ };
8
+
9
+ export function NumberColumn({
10
+ source,
11
+ label,
12
+ sortable = true,
13
+ }: NumberColumnProps) {
14
+ const def = useMemo(
15
+ () => ({
16
+ key: source,
17
+ source,
18
+ label,
19
+ sortable,
20
+ buildColumn: () => ({
21
+ title: label ?? source,
22
+ dataIndex: source,
23
+ key: source,
24
+ sorter: sortable ? true : undefined,
25
+ }),
26
+ }),
27
+ [source, label, sortable],
28
+ );
29
+ useRegisterColumn(def);
30
+ return null;
31
+ }