ding-react-admin 1.0.4 → 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 (230) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +254 -201
  3. package/dist/index.css +2 -0
  4. package/dist/index.js +2073 -1202
  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/InlineFormSet.d.ts +2 -17
  18. package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
  19. package/dist/src/crud/ResourceForm.d.ts +2 -10
  20. package/dist/src/crud/ResourceForm.d.ts.map +1 -1
  21. package/dist/src/crud/ResourceFormModal.d.ts +7 -1
  22. package/dist/src/crud/ResourceFormModal.d.ts.map +1 -1
  23. package/dist/src/crud/ResourceList.d.ts.map +1 -1
  24. package/dist/src/crud/ResourceRecordForm.d.ts +27 -0
  25. package/dist/src/crud/ResourceRecordForm.d.ts.map +1 -0
  26. package/dist/src/crud/columns/ImageColumn.d.ts +14 -0
  27. package/dist/src/crud/columns/ImageColumn.d.ts.map +1 -0
  28. package/dist/src/crud/context/InlineFieldsRegistry.d.ts +15 -0
  29. package/dist/src/crud/context/InlineFieldsRegistry.d.ts.map +1 -0
  30. package/dist/src/crud/context/PayloadFieldsContext.d.ts +18 -0
  31. package/dist/src/crud/context/PayloadFieldsContext.d.ts.map +1 -0
  32. package/dist/src/crud/fields/FileField.d.ts +12 -0
  33. package/dist/src/crud/fields/FileField.d.ts.map +1 -0
  34. package/dist/src/crud/fields/ImageField.d.ts +14 -0
  35. package/dist/src/crud/fields/ImageField.d.ts.map +1 -0
  36. package/dist/src/crud/fields/ReferenceField.d.ts +12 -2
  37. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  38. package/dist/src/crud/fields/ReferenceInputActions.d.ts +16 -0
  39. package/dist/src/crud/fields/ReferenceInputActions.d.ts.map +1 -0
  40. package/dist/src/crud/fields/ReferenceManyField.d.ts +10 -1
  41. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  42. package/dist/src/crud/fields/uploadFieldUtils.d.ts +5 -0
  43. package/dist/src/crud/fields/uploadFieldUtils.d.ts.map +1 -0
  44. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts +4 -0
  45. package/dist/src/crud/fields/useUploadPreviewUrl.d.ts.map +1 -0
  46. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  47. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  48. package/dist/src/crud/index.d.ts +27 -9
  49. package/dist/src/crud/index.d.ts.map +1 -1
  50. package/dist/src/crud/types.d.ts +21 -7
  51. package/dist/src/crud/types.d.ts.map +1 -1
  52. package/dist/src/crud/utils/buildFormPayload.d.ts +6 -0
  53. package/dist/src/crud/utils/buildFormPayload.d.ts.map +1 -0
  54. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts +6 -0
  55. package/dist/src/crud/utils/buildInlineRowsPayload.d.ts.map +1 -0
  56. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts +5 -0
  57. package/dist/src/crud/utils/buildResourceFormSubmitBody.d.ts.map +1 -0
  58. package/dist/src/crud/utils/choiceSelectionUtils.d.ts +10 -0
  59. package/dist/src/crud/utils/choiceSelectionUtils.d.ts.map +1 -0
  60. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts +2 -0
  61. package/dist/src/crud/utils/choiceSelectionUtils.test.d.ts.map +1 -0
  62. package/dist/src/crud/utils/formErrors.d.ts +18 -7
  63. package/dist/src/crud/utils/formErrors.d.ts.map +1 -1
  64. package/dist/src/crud/utils/formErrors.test.d.ts +2 -0
  65. package/dist/src/crud/utils/formErrors.test.d.ts.map +1 -0
  66. package/dist/src/crud/utils/getFormValue.d.ts +3 -0
  67. package/dist/src/crud/utils/getFormValue.d.ts.map +1 -0
  68. package/dist/src/crud/utils/hasUploadValues.d.ts +3 -0
  69. package/dist/src/crud/utils/hasUploadValues.d.ts.map +1 -0
  70. package/dist/src/crud/utils/nestedFieldPath.d.ts +3 -0
  71. package/dist/src/crud/utils/nestedFieldPath.d.ts.map +1 -0
  72. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts +8 -0
  73. package/dist/src/crud/utils/prepareFormSubmitBody.d.ts.map +1 -0
  74. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts +4 -0
  75. package/dist/src/crud/utils/referenceSelectNotFoundContent.d.ts.map +1 -0
  76. package/dist/src/crud/utils/setFormValue.d.ts +3 -0
  77. package/dist/src/crud/utils/setFormValue.d.ts.map +1 -0
  78. package/dist/src/crud/utils/toFormData.d.ts +13 -0
  79. package/dist/src/crud/utils/toFormData.d.ts.map +1 -0
  80. package/dist/src/crud/utils/uploadReferenceUtils.d.ts +5 -0
  81. package/dist/src/crud/utils/uploadReferenceUtils.d.ts.map +1 -0
  82. package/dist/src/crud/utils/useChoices.d.ts +27 -1
  83. package/dist/src/crud/utils/useChoices.d.ts.map +1 -1
  84. package/dist/src/crud/utils/useFormRecord.d.ts +44 -0
  85. package/dist/src/crud/utils/useFormRecord.d.ts.map +1 -0
  86. package/dist/src/data/createRestResourceHandlers.d.ts +5 -5
  87. package/dist/src/data/createRestResourceHandlers.d.ts.map +1 -1
  88. package/dist/src/data/dataProviderTypes.d.ts +6 -5
  89. package/dist/src/data/dataProviderTypes.d.ts.map +1 -1
  90. package/dist/src/data/parseFormErrorHelpers.d.ts +23 -6
  91. package/dist/src/data/parseFormErrorHelpers.d.ts.map +1 -1
  92. package/dist/src/data/resourceHandlers.d.ts +1 -1
  93. package/dist/src/data/resourceHandlers.d.ts.map +1 -1
  94. package/dist/src/index.d.ts +4 -4
  95. package/dist/src/index.d.ts.map +1 -1
  96. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  97. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  98. package/dist/src/layouts/AuthPageLayout.d.ts.map +1 -1
  99. package/dist/src/layouts/navFilter.d.ts +5 -0
  100. package/dist/src/layouts/navFilter.d.ts.map +1 -0
  101. package/dist/src/layouts/navMenuItems.d.ts +6 -0
  102. package/dist/src/layouts/navMenuItems.d.ts.map +1 -0
  103. package/dist/src/types.d.ts +12 -0
  104. package/dist/src/types.d.ts.map +1 -1
  105. package/package.json +79 -59
  106. package/src/app/AdminApp.tsx +34 -34
  107. package/src/components/AuthAlternateLink.tsx +23 -23
  108. package/src/components/DensitySwitch.tsx +31 -31
  109. package/src/components/NavMenuLabel.tsx +26 -0
  110. package/src/components/NavMenuSearch.tsx +76 -0
  111. package/src/components/ScrollableArea.tsx +46 -0
  112. package/src/components/ThemeSwitch.tsx +28 -28
  113. package/src/components/ThemeToolbar.tsx +34 -34
  114. package/src/components/index.ts +7 -4
  115. package/src/components/navMenu.css +21 -0
  116. package/src/components/scrollable.css +28 -0
  117. package/src/context/AppThemeProvider.tsx +128 -128
  118. package/src/context/AuthProvider.tsx +89 -77
  119. package/src/context/DataProvider.tsx +30 -30
  120. package/src/context/PermissionsProvider.tsx +57 -57
  121. package/src/crud/FilterBar.tsx +36 -36
  122. package/src/crud/FormGlobalErrorsAlert.tsx +36 -0
  123. package/src/crud/FormSteps.tsx +135 -135
  124. package/src/crud/FormTabs.tsx +126 -126
  125. package/src/crud/InlineFormSet.tsx +162 -219
  126. package/src/crud/ListActionsBar.tsx +106 -106
  127. package/src/crud/ResourceForm.tsx +83 -166
  128. package/src/crud/ResourceFormModal.tsx +75 -133
  129. package/src/crud/ResourceList.tsx +1 -0
  130. package/src/crud/ResourceRecordForm.tsx +166 -0
  131. package/src/crud/columns/BooleanColumn.tsx +32 -32
  132. package/src/crud/columns/CustomColumn.tsx +33 -33
  133. package/src/crud/columns/DateColumn.tsx +28 -28
  134. package/src/crud/columns/ImageColumn.tsx +58 -0
  135. package/src/crud/columns/NumberColumn.tsx +31 -31
  136. package/src/crud/columns/ReferenceColumn.tsx +85 -85
  137. package/src/crud/columns/ReferenceManyColumn.tsx +67 -67
  138. package/src/crud/columns/TextColumn.tsx +38 -38
  139. package/src/crud/context/FilterContext.tsx +71 -71
  140. package/src/crud/context/FormContext.tsx +39 -39
  141. package/src/crud/context/FormSectionContext.tsx +22 -22
  142. package/src/crud/context/InlineFieldsRegistry.tsx +61 -0
  143. package/src/crud/context/ListContext.tsx +93 -93
  144. package/src/crud/context/PayloadFieldsContext.tsx +62 -0
  145. package/src/crud/fields/BooleanField.tsx +38 -38
  146. package/src/crud/fields/DateField.tsx +53 -53
  147. package/src/crud/fields/FieldWrapper.tsx +87 -87
  148. package/src/crud/fields/FileField.tsx +123 -0
  149. package/src/crud/fields/ImageField.tsx +120 -0
  150. package/src/crud/fields/NumberField.tsx +52 -52
  151. package/src/crud/fields/PasswordField.tsx +99 -99
  152. package/src/crud/fields/ReferenceField.tsx +237 -96
  153. package/src/crud/fields/ReferenceInputActions.tsx +106 -0
  154. package/src/crud/fields/ReferenceManyField.tsx +200 -76
  155. package/src/crud/fields/SelectField.tsx +48 -48
  156. package/src/crud/fields/TextField.tsx +46 -46
  157. package/src/crud/fields/uploadFieldUtils.ts +21 -0
  158. package/src/crud/fields/useUploadPreviewUrl.ts +20 -0
  159. package/src/crud/filters/BooleanFilter.tsx +38 -38
  160. package/src/crud/filters/DateFilter.tsx +35 -35
  161. package/src/crud/filters/NumberFilter.tsx +33 -33
  162. package/src/crud/filters/ReferenceFilter.tsx +192 -173
  163. package/src/crud/filters/SelectFilter.tsx +44 -44
  164. package/src/crud/filters/TextFilter.tsx +36 -36
  165. package/src/crud/index.ts +99 -90
  166. package/src/crud/types.ts +146 -132
  167. package/src/crud/utils/buildFormPayload.ts +24 -0
  168. package/src/crud/utils/buildInlineRowsPayload.ts +37 -0
  169. package/src/crud/utils/buildResourceFormSubmitBody.ts +29 -0
  170. package/src/crud/utils/choiceSelectionUtils.test.ts +45 -0
  171. package/src/crud/utils/choiceSelectionUtils.ts +116 -0
  172. package/src/crud/utils/formErrors.test.ts +168 -0
  173. package/src/crud/utils/formErrors.ts +138 -38
  174. package/src/crud/utils/formSectionErrors.ts +85 -85
  175. package/src/crud/utils/{getByPath.ts → getFormValue.ts} +13 -13
  176. package/src/crud/utils/hasUploadValues.ts +9 -0
  177. package/src/crud/utils/nestedFieldPath.ts +8 -0
  178. package/src/crud/utils/prepareFormSubmitBody.ts +22 -0
  179. package/src/crud/utils/referenceSelectNotFoundContent.tsx +7 -0
  180. package/src/crud/utils/{setByPath.ts → setFormValue.ts} +18 -18
  181. package/src/crud/utils/sortQueryParam.ts +37 -37
  182. package/src/crud/utils/toFormData.ts +79 -0
  183. package/src/crud/utils/uploadReferenceUtils.ts +44 -0
  184. package/src/crud/utils/useAbortableEffect.ts +17 -17
  185. package/src/crud/utils/useChoices.ts +330 -182
  186. package/src/crud/utils/useFormRecord.ts +179 -0
  187. package/src/crud/utils/useListQueryState.ts +191 -191
  188. package/src/data/abortError.test.ts +19 -19
  189. package/src/data/abortError.ts +10 -10
  190. package/src/data/createMemoryResourceHandlers.ts +78 -78
  191. package/src/data/createRestResourceHandlers.ts +63 -57
  192. package/src/data/dataProviderTypes.ts +7 -5
  193. package/src/data/dataUtils.test.ts +228 -108
  194. package/src/data/inMemoryList.ts +97 -97
  195. package/src/data/parseFormErrorHelpers.test.ts +201 -84
  196. package/src/data/parseFormErrorHelpers.ts +376 -224
  197. package/src/data/resourceHandlers.ts +1 -1
  198. package/src/data/sortHelpers.ts +48 -48
  199. package/src/index.ts +211 -195
  200. package/src/layouts/AdminLayout.tsx +530 -381
  201. package/src/layouts/AuthPageLayout.tsx +87 -81
  202. package/src/layouts/navFilter.ts +49 -0
  203. package/src/layouts/navMenuItems.tsx +44 -0
  204. package/src/pages/LoginPage.tsx +80 -80
  205. package/src/pages/PlaceholderPage.tsx +9 -9
  206. package/src/permissions/resourcePermissions.ts +47 -47
  207. package/src/router/createAdminRouter.tsx +64 -64
  208. package/src/router/guards.tsx +63 -63
  209. package/src/router/routeAccess.ts +89 -89
  210. package/src/types.ts +10 -0
  211. package/src/vite-env.d.ts +1 -1
  212. package/dist/src/crud/context/SubmitFieldsContext.d.ts +0 -18
  213. package/dist/src/crud/context/SubmitFieldsContext.d.ts.map +0 -1
  214. package/dist/src/crud/utils/getByPath.d.ts +0 -3
  215. package/dist/src/crud/utils/getByPath.d.ts.map +0 -1
  216. package/dist/src/crud/utils/inlineArrayName.d.ts +0 -3
  217. package/dist/src/crud/utils/inlineArrayName.d.ts.map +0 -1
  218. package/dist/src/crud/utils/inlineFieldName.d.ts +0 -3
  219. package/dist/src/crud/utils/inlineFieldName.d.ts.map +0 -1
  220. package/dist/src/crud/utils/pickBySources.d.ts +0 -6
  221. package/dist/src/crud/utils/pickBySources.d.ts.map +0 -1
  222. package/dist/src/crud/utils/setByPath.d.ts +0 -3
  223. package/dist/src/crud/utils/setByPath.d.ts.map +0 -1
  224. package/dist/src/crud/utils/useResourceFormData.d.ts +0 -47
  225. package/dist/src/crud/utils/useResourceFormData.d.ts.map +0 -1
  226. package/src/crud/context/SubmitFieldsContext.tsx +0 -62
  227. package/src/crud/utils/inlineArrayName.ts +0 -4
  228. package/src/crud/utils/inlineFieldName.ts +0 -8
  229. package/src/crud/utils/pickBySources.ts +0 -24
  230. package/src/crud/utils/useResourceFormData.ts +0 -231
@@ -526,6 +526,7 @@ function ResourceListTable<T extends Record<string, unknown>>({
526
526
  loading={loading}
527
527
  columns={tableColumns}
528
528
  dataSource={data}
529
+ scroll={{ x: "max-content" }}
529
530
  rowSelection={
530
531
  showBulkActions
531
532
  ? {
@@ -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
+ }
@@ -0,0 +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,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
+ }
@@ -1,85 +1,85 @@
1
- import { useMemo } from "react";
2
- import type { BaseSourceProps, DisplayProps, ReferenceProps } from "../types";
3
- import { useRegisterColumn } from "../context/ListContext";
4
- import { useChoices } from "../utils/useChoices";
5
- import { renderDisplayValue } from "./TextColumn";
6
-
7
- export type ReferenceColumnProps = BaseSourceProps &
8
- ReferenceProps &
9
- DisplayProps & {
10
- sortable?: boolean;
11
- };
12
-
13
- function ReferenceColumnCell({
14
- record,
15
- source,
16
- display,
17
- reference,
18
- choices,
19
- optionLabel,
20
- optionValue,
21
- }: ReferenceColumnProps & { record: Record<string, unknown> }) {
22
- const { labelForValue } = useChoices(
23
- choices,
24
- reference,
25
- optionLabel,
26
- optionValue,
27
- );
28
- const raw = record[source];
29
- if (typeof display === "function") return <>{display(record)}</>;
30
- if (display && display !== source) {
31
- const nested = renderDisplayValue(record, source, display);
32
- return <>{nested != null ? String(nested) : "—"}</>;
33
- }
34
- return <>{labelForValue(raw)}</>;
35
- }
36
-
37
- export function ReferenceColumn({
38
- source,
39
- label,
40
- reference,
41
- choices,
42
- optionLabel = "name",
43
- optionValue = "id",
44
- display,
45
- sortable = true,
46
- }: ReferenceColumnProps) {
47
- const def = useMemo(
48
- () => ({
49
- key: source,
50
- source,
51
- label,
52
- sortable,
53
- buildColumn: () => ({
54
- title: label ?? source,
55
- dataIndex: source,
56
- key: source,
57
- sorter: sortable ? true : undefined,
58
- render: (_: unknown, record: Record<string, unknown>) => (
59
- <ReferenceColumnCell
60
- record={record}
61
- source={source}
62
- label={label}
63
- reference={reference}
64
- choices={choices}
65
- optionLabel={optionLabel}
66
- optionValue={optionValue}
67
- display={display ?? optionLabel}
68
- />
69
- ),
70
- }),
71
- }),
72
- [
73
- source,
74
- label,
75
- sortable,
76
- reference,
77
- choices,
78
- optionLabel,
79
- optionValue,
80
- display,
81
- ],
82
- );
83
- useRegisterColumn(def);
84
- return null;
85
- }
1
+ import { useMemo } from "react";
2
+ import type { BaseSourceProps, DisplayProps, ReferenceProps } from "../types";
3
+ import { useRegisterColumn } from "../context/ListContext";
4
+ import { useChoices } from "../utils/useChoices";
5
+ import { renderDisplayValue } from "./TextColumn";
6
+
7
+ export type ReferenceColumnProps = BaseSourceProps &
8
+ ReferenceProps &
9
+ DisplayProps & {
10
+ sortable?: boolean;
11
+ };
12
+
13
+ function ReferenceColumnCell({
14
+ record,
15
+ source,
16
+ display,
17
+ reference,
18
+ choices,
19
+ optionLabel,
20
+ optionValue,
21
+ }: ReferenceColumnProps & { record: Record<string, unknown> }) {
22
+ const { labelForValue } = useChoices(
23
+ choices,
24
+ reference,
25
+ optionLabel,
26
+ optionValue,
27
+ );
28
+ const raw = record[source];
29
+ if (typeof display === "function") return <>{display(record)}</>;
30
+ if (display && display !== source) {
31
+ const nested = renderDisplayValue(record, source, display);
32
+ return <>{nested != null ? String(nested) : "—"}</>;
33
+ }
34
+ return <>{labelForValue(raw)}</>;
35
+ }
36
+
37
+ export function ReferenceColumn({
38
+ source,
39
+ label,
40
+ reference,
41
+ choices,
42
+ optionLabel = "name",
43
+ optionValue = "id",
44
+ display,
45
+ sortable = true,
46
+ }: ReferenceColumnProps) {
47
+ const def = useMemo(
48
+ () => ({
49
+ key: source,
50
+ source,
51
+ label,
52
+ sortable,
53
+ buildColumn: () => ({
54
+ title: label ?? source,
55
+ dataIndex: source,
56
+ key: source,
57
+ sorter: sortable ? true : undefined,
58
+ render: (_: unknown, record: Record<string, unknown>) => (
59
+ <ReferenceColumnCell
60
+ record={record}
61
+ source={source}
62
+ label={label}
63
+ reference={reference}
64
+ choices={choices}
65
+ optionLabel={optionLabel}
66
+ optionValue={optionValue}
67
+ display={display ?? optionLabel}
68
+ />
69
+ ),
70
+ }),
71
+ }),
72
+ [
73
+ source,
74
+ label,
75
+ sortable,
76
+ reference,
77
+ choices,
78
+ optionLabel,
79
+ optionValue,
80
+ display,
81
+ ],
82
+ );
83
+ useRegisterColumn(def);
84
+ return null;
85
+ }