ding-react-admin 2.0.7 → 2.0.9

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 (69) hide show
  1. package/dist/index.css +1 -1
  2. package/dist/index.js +1564 -1139
  3. package/dist/src/crud/InlineFormSet.d.ts +6 -2
  4. package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
  5. package/dist/src/crud/ResourceList.d.ts +6 -1
  6. package/dist/src/crud/ResourceList.d.ts.map +1 -1
  7. package/dist/src/crud/context/FilterContext.d.ts +4 -1
  8. package/dist/src/crud/context/FilterContext.d.ts.map +1 -1
  9. package/dist/src/crud/fields/BooleanField.d.ts +2 -1
  10. package/dist/src/crud/fields/BooleanField.d.ts.map +1 -1
  11. package/dist/src/crud/fields/DateField.d.ts.map +1 -1
  12. package/dist/src/crud/fields/DateTimeField.d.ts +5 -0
  13. package/dist/src/crud/fields/DateTimeField.d.ts.map +1 -0
  14. package/dist/src/crud/fields/ReferenceField.d.ts +3 -1
  15. package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
  16. package/dist/src/crud/fields/ReferenceManyField.d.ts +4 -1
  17. package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
  18. package/dist/src/crud/fields/TextAreaField.d.ts +19 -0
  19. package/dist/src/crud/fields/TextAreaField.d.ts.map +1 -0
  20. package/dist/src/crud/fields/TimeField.d.ts +11 -0
  21. package/dist/src/crud/fields/TimeField.d.ts.map +1 -0
  22. package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
  23. package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
  24. package/dist/src/crud/filters/TextFilter.d.ts +9 -1
  25. package/dist/src/crud/filters/TextFilter.d.ts.map +1 -1
  26. package/dist/src/crud/index.d.ts +6 -1
  27. package/dist/src/crud/index.d.ts.map +1 -1
  28. package/dist/src/crud/types.d.ts +9 -0
  29. package/dist/src/crud/types.d.ts.map +1 -1
  30. package/dist/src/crud/utils/parseDayjsValue.d.ts +3 -0
  31. package/dist/src/crud/utils/parseDayjsValue.d.ts.map +1 -0
  32. package/dist/src/crud/utils/referenceSelectDropdownProps.d.ts +11 -0
  33. package/dist/src/crud/utils/referenceSelectDropdownProps.d.ts.map +1 -0
  34. package/dist/src/crud/utils/useDebouncedValue.d.ts +3 -0
  35. package/dist/src/crud/utils/useDebouncedValue.d.ts.map +1 -0
  36. package/dist/src/index.d.ts +3 -3
  37. package/dist/src/index.d.ts.map +1 -1
  38. package/dist/src/layouts/AdminLayout.d.ts +1 -1
  39. package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
  40. package/dist/src/layouts/navMenuItems.d.ts +3 -0
  41. package/dist/src/layouts/navMenuItems.d.ts.map +1 -1
  42. package/dist/src/types.d.ts +16 -0
  43. package/dist/src/types.d.ts.map +1 -1
  44. package/package.json +4 -4
  45. package/src/components/NavMenuLabel.tsx +1 -1
  46. package/src/components/navMenu.css +107 -0
  47. package/src/context/AppThemeProvider.tsx +1 -1
  48. package/src/crud/InlineFormSet.tsx +32 -14
  49. package/src/crud/ResourceList.tsx +211 -35
  50. package/src/crud/context/FilterContext.tsx +7 -1
  51. package/src/crud/fields/BooleanField.tsx +3 -1
  52. package/src/crud/fields/DateField.tsx +6 -4
  53. package/src/crud/fields/DateTimeField.tsx +9 -0
  54. package/src/crud/fields/ReferenceField.tsx +10 -0
  55. package/src/crud/fields/ReferenceManyField.tsx +13 -1
  56. package/src/crud/fields/TextAreaField.tsx +58 -0
  57. package/src/crud/fields/TimeField.tsx +48 -0
  58. package/src/crud/filters/ReferenceFilter.tsx +10 -0
  59. package/src/crud/filters/TextFilter.tsx +80 -9
  60. package/src/crud/index.ts +9 -1
  61. package/src/crud/resourceListTable.css +113 -0
  62. package/src/crud/types.ts +9 -0
  63. package/src/crud/utils/parseDayjsValue.ts +14 -0
  64. package/src/crud/utils/referenceSelectDropdownProps.ts +30 -0
  65. package/src/crud/utils/useDebouncedValue.ts +17 -0
  66. package/src/index.ts +7 -0
  67. package/src/layouts/AdminLayout.tsx +43 -6
  68. package/src/layouts/navMenuItems.tsx +11 -2
  69. package/src/types.ts +17 -0
@@ -9,6 +9,7 @@ import type {
9
9
  ReferenceProps,
10
10
  } from "../types";
11
11
  import { valueAsId, resolveOptionLabel } from "../utils/choiceSelectionUtils";
12
+ import { referenceSelectDropdownProps } from "../utils/referenceSelectDropdownProps";
12
13
  import { referenceSelectNotFoundContent } from "../utils/referenceSelectNotFoundContent";
13
14
  import { useChoices } from "../utils/useChoices";
14
15
  import { FieldWrapper } from "./FieldWrapper";
@@ -42,6 +43,8 @@ export type ReferenceFieldProps = BaseSourceProps &
42
43
  referenceModalWidth?: number;
43
44
  /** When false, hide add/edit buttons. Default true when `referenceForm` is set. */
44
45
  referenceActions?: boolean;
46
+ popupMatchSelectWidth?: boolean | number;
47
+ popupMinWidth?: number;
45
48
  };
46
49
 
47
50
  type ReferenceFieldSelectProps = Omit<
@@ -77,6 +80,8 @@ function ReferenceFieldSelect({
77
80
  referenceDefaultValues,
78
81
  referenceModalWidth,
79
82
  referenceActions = true,
83
+ popupMatchSelectWidth,
84
+ popupMinWidth,
80
85
  }: ReferenceFieldSelectProps) {
81
86
  const [searchText, setSearchText] = useState<string | undefined>();
82
87
  const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -124,6 +129,7 @@ function ReferenceFieldSelect({
124
129
 
125
130
  const select = (
126
131
  <Select
132
+ {...referenceSelectDropdownProps({ popupMatchSelectWidth, popupMinWidth })}
127
133
  value={selectValue}
128
134
  onChange={(next) => {
129
135
  onChange(next);
@@ -192,6 +198,8 @@ export function ReferenceField({
192
198
  referenceDefaultValues,
193
199
  referenceModalWidth,
194
200
  referenceActions = true,
201
+ popupMatchSelectWidth,
202
+ popupMinWidth,
195
203
  }: ReferenceFieldProps) {
196
204
  const embeddedRecord = useWatch({
197
205
  name: recordSource ?? "",
@@ -230,6 +238,8 @@ export function ReferenceField({
230
238
  referenceDefaultValues={referenceDefaultValues}
231
239
  referenceModalWidth={referenceModalWidth}
232
240
  referenceActions={referenceActions}
241
+ popupMatchSelectWidth={popupMatchSelectWidth}
242
+ popupMinWidth={popupMinWidth}
233
243
  />
234
244
  )}
235
245
  </FieldWrapper>
@@ -3,6 +3,7 @@ import { useMemo, useState } from "react";
3
3
  import { useWatch } from "react-hook-form";
4
4
  import type { BaseSourceProps, FieldRules, ReferenceProps } from "../types";
5
5
  import { valuesAsIds } from "../utils/choiceSelectionUtils";
6
+ import { referenceSelectDropdownProps } from "../utils/referenceSelectDropdownProps";
6
7
  import { referenceSelectNotFoundContent } from "../utils/referenceSelectNotFoundContent";
7
8
  import { useChoices } from "../utils/useChoices";
8
9
  import { FieldWrapper } from "./FieldWrapper";
@@ -18,6 +19,7 @@ export type ReferenceManyFieldProps = BaseSourceProps &
18
19
  search?: boolean;
19
20
  allowClear?: boolean;
20
21
  hideLabel?: boolean;
22
+ disabled?: boolean;
21
23
  referenceForm?: ReactNode;
22
24
  referencePermissions?: ResourcePermissions;
23
25
  referenceTitle?: string;
@@ -25,6 +27,8 @@ export type ReferenceManyFieldProps = BaseSourceProps &
25
27
  referenceModalWidth?: number;
26
28
  /** When false, hide add button even if catalog defines a form. Default true. */
27
29
  referenceActions?: boolean;
30
+ popupMatchSelectWidth?: boolean | number;
31
+ popupMinWidth?: number;
28
32
  };
29
33
 
30
34
  type ReferenceManyFieldSelectProps = Omit<
@@ -56,6 +60,8 @@ function ReferenceManyFieldSelect({
56
60
  referenceDefaultValues,
57
61
  referenceModalWidth,
58
62
  referenceActions = true,
63
+ popupMatchSelectWidth,
64
+ popupMinWidth,
59
65
  }: ReferenceManyFieldSelectProps) {
60
66
  const [searchText, setSearchText] = useState<string | undefined>();
61
67
  const [dropdownOpen, setDropdownOpen] = useState(false);
@@ -88,6 +94,7 @@ function ReferenceManyFieldSelect({
88
94
 
89
95
  const select = (
90
96
  <Select
97
+ {...referenceSelectDropdownProps({ popupMatchSelectWidth, popupMinWidth })}
91
98
  mode="multiple"
92
99
  value={selectValue}
93
100
  onChange={onChange}
@@ -149,6 +156,7 @@ export function ReferenceManyField({
149
156
  search,
150
157
  allowClear = true,
151
158
  hideLabel,
159
+ disabled: disabledProp,
152
160
  lazy = true,
153
161
  recordSource,
154
162
  fetchSelected = true,
@@ -158,6 +166,8 @@ export function ReferenceManyField({
158
166
  referenceDefaultValues,
159
167
  referenceModalWidth,
160
168
  referenceActions = true,
169
+ popupMatchSelectWidth,
170
+ popupMinWidth,
161
171
  }: ReferenceManyFieldProps) {
162
172
  const embeddedRecords = useWatch({
163
173
  name: recordSource ?? "",
@@ -185,7 +195,7 @@ export function ReferenceManyField({
185
195
  fetchSelected={fetchSelected}
186
196
  value={value}
187
197
  onChange={onChange}
188
- disabled={disabled}
198
+ disabled={disabled || disabledProp}
189
199
  selectedRecords={recordSource ? embeddedRecords : undefined}
190
200
  referenceForm={referenceForm}
191
201
  referencePermissions={referencePermissions}
@@ -193,6 +203,8 @@ export function ReferenceManyField({
193
203
  referenceDefaultValues={referenceDefaultValues}
194
204
  referenceModalWidth={referenceModalWidth}
195
205
  referenceActions={referenceActions}
206
+ popupMatchSelectWidth={popupMatchSelectWidth}
207
+ popupMinWidth={popupMinWidth}
196
208
  />
197
209
  )}
198
210
  </FieldWrapper>
@@ -0,0 +1,58 @@
1
+ import { Input } from "antd";
2
+ import type { CSSProperties } from "react";
3
+ import type { BaseSourceProps, FieldRules } from "../types";
4
+ import { FieldWrapper } from "./FieldWrapper";
5
+
6
+ export type TextAreaFieldProps = BaseSourceProps & {
7
+ name?: string;
8
+ required?: boolean;
9
+ rules?: FieldRules;
10
+ placeholder?: string;
11
+ inputStyle?: CSSProperties;
12
+ hideLabel?: boolean;
13
+ rows?: number;
14
+ maxLength?: number;
15
+ showCount?: boolean;
16
+ autoSize?: boolean | { minRows?: number; maxRows?: number };
17
+ };
18
+
19
+ export function TextAreaField({
20
+ source,
21
+ name,
22
+ label,
23
+ required,
24
+ rules,
25
+ placeholder,
26
+ inputStyle,
27
+ hideLabel,
28
+ rows = 4,
29
+ maxLength,
30
+ showCount,
31
+ autoSize,
32
+ }: TextAreaFieldProps) {
33
+ return (
34
+ <FieldWrapper
35
+ source={source}
36
+ name={name}
37
+ label={label}
38
+ required={required}
39
+ rules={rules}
40
+ hideLabel={hideLabel}
41
+ >
42
+ {({ value, onChange, onBlur, disabled }) => (
43
+ <Input.TextArea
44
+ value={value as string | undefined}
45
+ onChange={(e) => onChange(e.target.value)}
46
+ onBlur={onBlur}
47
+ placeholder={placeholder}
48
+ disabled={disabled}
49
+ style={inputStyle}
50
+ rows={autoSize ? undefined : rows}
51
+ maxLength={maxLength}
52
+ showCount={showCount}
53
+ autoSize={autoSize}
54
+ />
55
+ )}
56
+ </FieldWrapper>
57
+ );
58
+ }
@@ -0,0 +1,48 @@
1
+ import { TimePicker } from "antd";
2
+ import type { BaseSourceProps, FieldRules } from "../types";
3
+ import { parseDayjsValue } from "../utils/parseDayjsValue";
4
+ import { FieldWrapper } from "./FieldWrapper";
5
+
6
+ const DEFAULT_TIME_FORMAT = "HH:mm:ss";
7
+ const PARSE_FORMATS = [DEFAULT_TIME_FORMAT, "HH:mm", "H:mm:ss", "H:mm"];
8
+
9
+ export type TimeFieldProps = BaseSourceProps & {
10
+ name?: string;
11
+ required?: boolean;
12
+ rules?: FieldRules;
13
+ hideLabel?: boolean;
14
+ /** Output / display format. Default `HH:mm:ss` (Django TimeField). */
15
+ format?: string;
16
+ };
17
+
18
+ export function TimeField({
19
+ source,
20
+ name,
21
+ label,
22
+ required,
23
+ rules,
24
+ hideLabel,
25
+ format = DEFAULT_TIME_FORMAT,
26
+ }: TimeFieldProps) {
27
+ return (
28
+ <FieldWrapper
29
+ source={source}
30
+ name={name}
31
+ label={label}
32
+ required={required}
33
+ rules={rules}
34
+ hideLabel={hideLabel}
35
+ >
36
+ {({ value, onChange, onBlur, disabled }) => (
37
+ <TimePicker
38
+ value={parseDayjsValue(value, PARSE_FORMATS)}
39
+ onChange={(d) => onChange(d ? d.format(format) : null)}
40
+ onBlur={onBlur}
41
+ disabled={disabled}
42
+ format={format}
43
+ style={{ width: "100%" }}
44
+ />
45
+ )}
46
+ </FieldWrapper>
47
+ );
48
+ }
@@ -3,6 +3,7 @@ import { useMemo, useState } from "react";
3
3
  import type { BaseSourceProps, ChoiceOption, ReferenceProps } from "../types";
4
4
  import { useRegisterFilter } from "../context/FilterContext";
5
5
  import { useChoices } from "../utils/useChoices";
6
+ import { referenceSelectDropdownProps } from "../utils/referenceSelectDropdownProps";
6
7
  import { referenceSelectNotFoundContent } from "../utils/referenceSelectNotFoundContent";
7
8
 
8
9
  export type SelectFilterProps = BaseSourceProps & {
@@ -85,6 +86,8 @@ function ReferenceFilterInput({
85
86
  search,
86
87
  lazy = true,
87
88
  fetchSelected = true,
89
+ popupMatchSelectWidth,
90
+ popupMinWidth,
88
91
  value,
89
92
  onChange,
90
93
  }: ReferenceFilterProps & {
@@ -106,6 +109,7 @@ function ReferenceFilterInput({
106
109
 
107
110
  return (
108
111
  <Select
112
+ {...referenceSelectDropdownProps({ popupMatchSelectWidth, popupMinWidth })}
109
113
  allowClear
110
114
  mode={multiple ? "multiple" : undefined}
111
115
  placeholder={label ?? source}
@@ -141,6 +145,8 @@ export function ReferenceFilter({
141
145
  search,
142
146
  lazy = true,
143
147
  fetchSelected = true,
148
+ popupMatchSelectWidth,
149
+ popupMinWidth,
144
150
  }: ReferenceFilterProps) {
145
151
  const def = useMemo(
146
152
  () => ({
@@ -165,6 +171,8 @@ export function ReferenceFilter({
165
171
  search={search}
166
172
  lazy={lazy}
167
173
  fetchSelected={fetchSelected}
174
+ popupMatchSelectWidth={popupMatchSelectWidth}
175
+ popupMinWidth={popupMinWidth}
168
176
  value={value}
169
177
  onChange={onChange}
170
178
  />
@@ -181,6 +189,8 @@ export function ReferenceFilter({
181
189
  search,
182
190
  lazy,
183
191
  fetchSelected,
192
+ popupMatchSelectWidth,
193
+ popupMinWidth,
184
194
  ],
185
195
  );
186
196
  useRegisterFilter(def);
@@ -1,13 +1,85 @@
1
1
  import { Input } from "antd";
2
- import { useMemo } from "react";
2
+ import { useEffect, useMemo, useState } from "react";
3
3
  import type { BaseSourceProps } from "../types";
4
- import { useRegisterFilter } from "../context/FilterContext";
4
+ import { useFilterContext, useRegisterFilter } from "../context/FilterContext";
5
+ import { useDebouncedValue } from "../utils/useDebouncedValue";
6
+
7
+ /** Default debounce for list text filters (ms). */
8
+ export const DEFAULT_TEXT_FILTER_DEBOUNCE_MS = 300;
5
9
 
6
10
  export type TextFilterProps = BaseSourceProps & {
7
11
  placeholder?: string;
12
+ /**
13
+ * Debounce delay before applying the filter to the list / URL (ms).
14
+ * Defaults to `ResourceList` `textFilterDebounceMs`, then {@link DEFAULT_TEXT_FILTER_DEBOUNCE_MS}.
15
+ * Set `0` for immediate apply (legacy behavior).
16
+ */
17
+ debounceMs?: number;
8
18
  };
9
19
 
10
- export function TextFilter({ source, label, placeholder }: TextFilterProps) {
20
+ function normalizeFilterText(value: unknown): string | undefined {
21
+ if (value === undefined || value === null || value === "") return undefined;
22
+ return String(value);
23
+ }
24
+
25
+ function TextFilterInput({
26
+ value,
27
+ onChange,
28
+ placeholder,
29
+ debounceMs,
30
+ }: {
31
+ value: unknown;
32
+ onChange: (value: unknown) => void;
33
+ placeholder?: string;
34
+ debounceMs: number;
35
+ }) {
36
+ const [draft, setDraft] = useState(() => (value as string) ?? "");
37
+ const debouncedDraft = useDebouncedValue(draft, debounceMs);
38
+
39
+ useEffect(() => {
40
+ setDraft((value as string) ?? "");
41
+ }, [value]);
42
+
43
+ useEffect(() => {
44
+ if (debounceMs <= 0) return;
45
+ // Ignore stale debounced values while the user is still typing or after an immediate clear.
46
+ if (debouncedDraft !== draft) return;
47
+ const next = normalizeFilterText(debouncedDraft);
48
+ const current = normalizeFilterText(value);
49
+ if (next !== current) {
50
+ onChange(next);
51
+ }
52
+ }, [debouncedDraft, draft, debounceMs, onChange, value]);
53
+
54
+ return (
55
+ <Input
56
+ allowClear
57
+ placeholder={placeholder}
58
+ value={draft}
59
+ onChange={(e) => {
60
+ const nextDraft = e.target.value;
61
+ setDraft(nextDraft);
62
+ if (debounceMs <= 0 || nextDraft === "") {
63
+ onChange(normalizeFilterText(nextDraft));
64
+ }
65
+ }}
66
+ style={{ minWidth: 160 }}
67
+ />
68
+ );
69
+ }
70
+
71
+ export function TextFilter({
72
+ source,
73
+ label,
74
+ placeholder,
75
+ debounceMs: debounceMsProp,
76
+ }: TextFilterProps) {
77
+ const filterCtx = useFilterContext();
78
+ const debounceMs =
79
+ debounceMsProp ??
80
+ filterCtx?.textFilterDebounceMs ??
81
+ DEFAULT_TEXT_FILTER_DEBOUNCE_MS;
82
+
11
83
  const def = useMemo(
12
84
  () => ({
13
85
  key: source,
@@ -20,16 +92,15 @@ export function TextFilter({ source, label, placeholder }: TextFilterProps) {
20
92
  value: unknown;
21
93
  onChange: (value: unknown) => void;
22
94
  }) => (
23
- <Input
24
- allowClear
95
+ <TextFilterInput
96
+ value={value}
97
+ onChange={onChange}
25
98
  placeholder={placeholder ?? label ?? source}
26
- value={(value as string) ?? ""}
27
- onChange={(e) => onChange(e.target.value || undefined)}
28
- style={{ minWidth: 160 }}
99
+ debounceMs={debounceMs}
29
100
  />
30
101
  ),
31
102
  }),
32
- [source, label, placeholder],
103
+ [source, label, placeholder, debounceMs],
33
104
  );
34
105
  useRegisterFilter(def);
35
106
  return null;
package/src/crud/index.ts CHANGED
@@ -17,9 +17,13 @@ export { FormSteps, FormStep } from "./FormSteps";
17
17
  export type { FormStepsProps, FormStepProps } from "./FormSteps";
18
18
 
19
19
  export { TextField } from "./fields/TextField";
20
+ export { TextAreaField } from "./fields/TextAreaField";
21
+ export type { TextAreaFieldProps } from "./fields/TextAreaField";
20
22
  export { NumberField } from "./fields/NumberField";
21
23
  export { BooleanField } from "./fields/BooleanField";
22
24
  export { DateField } from "./fields/DateField";
25
+ export { DateTimeField } from "./fields/DateTimeField";
26
+ export { TimeField } from "./fields/TimeField";
23
27
  export { SelectField } from "./fields/SelectField";
24
28
  export { PasswordField } from "./fields/PasswordField";
25
29
  export { ReferenceField } from "./fields/ReferenceField";
@@ -45,7 +49,11 @@ export { ReferenceManyColumn } from "./columns/ReferenceManyColumn";
45
49
  export { ImageColumn } from "./columns/ImageColumn";
46
50
  export { CustomColumn } from "./columns/CustomColumn";
47
51
 
48
- export { TextFilter } from "./filters/TextFilter";
52
+ export {
53
+ TextFilter,
54
+ DEFAULT_TEXT_FILTER_DEBOUNCE_MS,
55
+ } from "./filters/TextFilter";
56
+ export type { TextFilterProps } from "./filters/TextFilter";
49
57
  export { NumberFilter } from "./filters/NumberFilter";
50
58
  export { BooleanFilter } from "./filters/BooleanFilter";
51
59
  export { DateFilter } from "./filters/DateFilter";
@@ -0,0 +1,113 @@
1
+ .ding-admin-scroll.ding-admin-resource-list-scroll,
2
+ .ding-admin-scroll:has(.ding-admin-resource-list-root) {
3
+ display: flex !important;
4
+ flex-direction: column !important;
5
+ overflow: hidden !important;
6
+ }
7
+
8
+ .ding-admin-resource-list-root {
9
+ display: flex;
10
+ flex-direction: column;
11
+ flex: 1;
12
+ min-height: 0;
13
+ }
14
+
15
+ .ding-admin-resource-list-root > :not(.ant-card) {
16
+ flex-shrink: 0;
17
+ }
18
+
19
+ .ding-admin-resource-list-root > .ant-card {
20
+ flex: 1;
21
+ min-height: 0;
22
+ display: flex;
23
+ flex-direction: column;
24
+ }
25
+
26
+ .ding-admin-resource-list-root > .ant-card > .ant-card-head {
27
+ flex-shrink: 0;
28
+ }
29
+
30
+ .ding-admin-resource-list-root > .ant-card > .ant-card-body {
31
+ flex: 1;
32
+ min-height: 0;
33
+ display: flex;
34
+ flex-direction: column;
35
+ }
36
+
37
+ .ding-admin-resource-list-root > .ant-card > .ant-card-body > :not(.ding-admin-resource-list-table) {
38
+ flex-shrink: 0;
39
+ }
40
+
41
+ .ding-admin-resource-list-table {
42
+ flex: 1;
43
+ min-height: 0;
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+
48
+ .ding-admin-resource-list-table .ant-spin-nested-loading,
49
+ .ding-admin-resource-list-table .ant-spin-container,
50
+ .ding-admin-resource-list-table .ant-table-wrapper,
51
+ .ding-admin-resource-list-table .ant-table,
52
+ .ding-admin-resource-list-table .ant-table-container {
53
+ flex: 1;
54
+ min-height: 0;
55
+ display: flex;
56
+ flex-direction: column;
57
+ }
58
+
59
+ .ding-admin-resource-list-table .ant-table-header {
60
+ flex-shrink: 0;
61
+ }
62
+
63
+ .ding-admin-resource-list-pagination {
64
+ display: flex;
65
+ justify-content: flex-end;
66
+ margin-top: 16px;
67
+ flex-shrink: 0;
68
+ position: relative;
69
+ z-index: 2;
70
+ }
71
+
72
+ .ding-admin-resource-list-table .ant-table-body,
73
+ .ding-admin-resource-list-table .ant-table-content {
74
+ scrollbar-width: thin;
75
+ scrollbar-color: var(--ding-scroll-thumb) transparent;
76
+ }
77
+
78
+ .ding-admin-resource-list-table .ant-table-body::-webkit-scrollbar,
79
+ .ding-admin-resource-list-table .ant-table-content::-webkit-scrollbar {
80
+ width: 6px;
81
+ height: 6px;
82
+ }
83
+
84
+ .ding-admin-resource-list-table .ant-table-body::-webkit-scrollbar-track,
85
+ .ding-admin-resource-list-table .ant-table-content::-webkit-scrollbar-track {
86
+ background: transparent;
87
+ }
88
+
89
+ .ding-admin-resource-list-table .ant-table-body::-webkit-scrollbar-thumb,
90
+ .ding-admin-resource-list-table .ant-table-content::-webkit-scrollbar-thumb {
91
+ background-color: var(--ding-scroll-thumb);
92
+ border-radius: 999px;
93
+ border: 1px solid transparent;
94
+ background-clip: padding-box;
95
+ }
96
+
97
+ .ding-admin-resource-list-table .ant-table-body::-webkit-scrollbar-thumb:hover,
98
+ .ding-admin-resource-list-table .ant-table-content::-webkit-scrollbar-thumb:hover {
99
+ background-color: var(--ding-scroll-thumb-hover);
100
+ }
101
+
102
+ .ding-admin-resource-list-table .ant-table-body::-webkit-scrollbar-corner,
103
+ .ding-admin-resource-list-table .ant-table-content::-webkit-scrollbar-corner {
104
+ background: transparent;
105
+ }
106
+
107
+ .ding-admin-resource-list-table .ding-admin-row-edit-link {
108
+ color: var(--ant-color-primary);
109
+ }
110
+
111
+ .ding-admin-resource-list-table .ding-admin-row-edit-link:hover {
112
+ color: var(--ant-color-primary-hover, var(--ant-color-primary));
113
+ }
package/src/crud/types.ts CHANGED
@@ -42,6 +42,13 @@ export type ReferenceProps = {
42
42
  * already embedded in the form record. Set `false` to never call `getOne`.
43
43
  */
44
44
  fetchSelected?: boolean;
45
+ /**
46
+ * When false (default), the options dropdown may be wider than the input.
47
+ * Pass true to match input width, or a number for a fixed popup width.
48
+ */
49
+ popupMatchSelectWidth?: boolean | number;
50
+ /** Minimum popup width in px when `popupMatchSelectWidth` is false. Default 360. */
51
+ popupMinWidth?: number;
45
52
  };
46
53
 
47
54
  export type DisplayProps = {
@@ -75,6 +82,8 @@ export type InlineFormSetBaseProps = {
75
82
  /** API key when different from `field`. */
76
83
  payloadKey?: string;
77
84
  transformRows?: (rows: Record<string, unknown>[]) => unknown;
85
+ /** Values applied when appending a new inline row (e.g. `{ is_active: true }`). */
86
+ defaultRow?: Record<string, unknown>;
78
87
  };
79
88
 
80
89
  /** Passed to each tabular inline column's `cell` renderer. */
@@ -0,0 +1,14 @@
1
+ import dayjs, { type Dayjs } from "dayjs";
2
+ import customParseFormat from "dayjs/plugin/customParseFormat";
3
+
4
+ dayjs.extend(customParseFormat);
5
+
6
+ export function parseDayjsValue(
7
+ value: unknown,
8
+ formats: string[],
9
+ ): Dayjs | null {
10
+ if (value == null || value === "") return null;
11
+ if (dayjs.isDayjs(value)) return value;
12
+ const parsed = dayjs(String(value), formats, true);
13
+ return parsed.isValid() ? parsed : dayjs(String(value)).isValid() ? dayjs(String(value)) : null;
14
+ }
@@ -0,0 +1,30 @@
1
+ import type { SelectProps } from "antd";
2
+
3
+ export const REFERENCE_SELECT_POPUP_MIN_WIDTH = 360;
4
+
5
+ export type ReferenceSelectDropdownOptions = {
6
+ /** When false (default), the dropdown may be wider than the input. */
7
+ popupMatchSelectWidth?: boolean | number;
8
+ /** Minimum popup width in px when `popupMatchSelectWidth` is false. */
9
+ popupMinWidth?: number;
10
+ };
11
+
12
+ /** Ant Design Select props so reference dropdowns are not clipped to input width. */
13
+ export function referenceSelectDropdownProps(
14
+ options: ReferenceSelectDropdownOptions = {},
15
+ ): Pick<SelectProps, "popupMatchSelectWidth" | "styles"> {
16
+ const popupMatchSelectWidth = options.popupMatchSelectWidth ?? false;
17
+ if (popupMatchSelectWidth !== false) {
18
+ return { popupMatchSelectWidth };
19
+ }
20
+
21
+ const minWidth = options.popupMinWidth ?? REFERENCE_SELECT_POPUP_MIN_WIDTH;
22
+ return {
23
+ popupMatchSelectWidth: false,
24
+ styles: {
25
+ popup: {
26
+ root: { minWidth },
27
+ },
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,17 @@
1
+ import { useEffect, useState } from "react";
2
+
3
+ /** Returns `value` after it stays unchanged for `delayMs`. When `delayMs <= 0`, returns `value` immediately. */
4
+ export function useDebouncedValue<T>(value: T, delayMs: number): T {
5
+ const [debounced, setDebounced] = useState(value);
6
+
7
+ useEffect(() => {
8
+ if (delayMs <= 0) {
9
+ setDebounced(value);
10
+ return;
11
+ }
12
+ const timer = window.setTimeout(() => setDebounced(value), delayMs);
13
+ return () => window.clearTimeout(timer);
14
+ }, [value, delayMs]);
15
+
16
+ return delayMs <= 0 ? value : debounced;
17
+ }
package/src/index.ts CHANGED
@@ -114,6 +114,7 @@ export type {
114
114
  AuthPageLayoutProps,
115
115
  LoginPageProps,
116
116
  NavItem,
117
+ NavMenuItemDivider,
117
118
  RouteAccess,
118
119
  ThemeDensity,
119
120
  ThemeMode,
@@ -130,9 +131,12 @@ export {
130
131
  FormSteps,
131
132
  FormStep,
132
133
  TextField,
134
+ TextAreaField,
133
135
  NumberField,
134
136
  BooleanField,
135
137
  DateField,
138
+ DateTimeField,
139
+ TimeField,
136
140
  SelectField,
137
141
  PasswordField,
138
142
  ReferenceField,
@@ -149,6 +153,7 @@ export {
149
153
  ImageColumn,
150
154
  CustomColumn,
151
155
  TextFilter,
156
+ DEFAULT_TEXT_FILTER_DEBOUNCE_MS,
152
157
  NumberFilter,
153
158
  BooleanFilter,
154
159
  DateFilter,
@@ -182,6 +187,7 @@ export type {
182
187
  FormStepProps,
183
188
  InlineFormSetLayout,
184
189
  ResourceListProps,
190
+ TextFilterProps,
185
191
  BaseSourceProps,
186
192
  ChoiceOption,
187
193
  ChoicesLoader,
@@ -196,6 +202,7 @@ export type {
196
202
  InlineFormSetBaseProps,
197
203
  InlineRowContext,
198
204
  InlineFieldRegistration,
205
+ TextAreaFieldProps,
199
206
  FieldWrapperProps,
200
207
  ImageFieldProps,
201
208
  FileFieldProps,