ding-react-admin 2.0.8 → 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.
- package/dist/index.css +1 -1
- package/dist/index.js +1564 -1139
- package/dist/src/crud/InlineFormSet.d.ts +6 -2
- package/dist/src/crud/InlineFormSet.d.ts.map +1 -1
- package/dist/src/crud/ResourceList.d.ts +6 -1
- package/dist/src/crud/ResourceList.d.ts.map +1 -1
- package/dist/src/crud/context/FilterContext.d.ts +4 -1
- package/dist/src/crud/context/FilterContext.d.ts.map +1 -1
- package/dist/src/crud/fields/BooleanField.d.ts +2 -1
- package/dist/src/crud/fields/BooleanField.d.ts.map +1 -1
- package/dist/src/crud/fields/DateField.d.ts.map +1 -1
- package/dist/src/crud/fields/DateTimeField.d.ts +5 -0
- package/dist/src/crud/fields/DateTimeField.d.ts.map +1 -0
- package/dist/src/crud/fields/ReferenceField.d.ts +3 -1
- package/dist/src/crud/fields/ReferenceField.d.ts.map +1 -1
- package/dist/src/crud/fields/ReferenceManyField.d.ts +4 -1
- package/dist/src/crud/fields/ReferenceManyField.d.ts.map +1 -1
- package/dist/src/crud/fields/TextAreaField.d.ts +19 -0
- package/dist/src/crud/fields/TextAreaField.d.ts.map +1 -0
- package/dist/src/crud/fields/TimeField.d.ts +11 -0
- package/dist/src/crud/fields/TimeField.d.ts.map +1 -0
- package/dist/src/crud/filters/ReferenceFilter.d.ts +1 -1
- package/dist/src/crud/filters/ReferenceFilter.d.ts.map +1 -1
- package/dist/src/crud/filters/TextFilter.d.ts +9 -1
- package/dist/src/crud/filters/TextFilter.d.ts.map +1 -1
- package/dist/src/crud/index.d.ts +6 -1
- package/dist/src/crud/index.d.ts.map +1 -1
- package/dist/src/crud/types.d.ts +9 -0
- package/dist/src/crud/types.d.ts.map +1 -1
- package/dist/src/crud/utils/parseDayjsValue.d.ts +3 -0
- package/dist/src/crud/utils/parseDayjsValue.d.ts.map +1 -0
- package/dist/src/crud/utils/referenceSelectDropdownProps.d.ts +11 -0
- package/dist/src/crud/utils/referenceSelectDropdownProps.d.ts.map +1 -0
- package/dist/src/crud/utils/useDebouncedValue.d.ts +3 -0
- package/dist/src/crud/utils/useDebouncedValue.d.ts.map +1 -0
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/layouts/AdminLayout.d.ts +1 -1
- package/dist/src/layouts/AdminLayout.d.ts.map +1 -1
- package/dist/src/layouts/navMenuItems.d.ts +3 -0
- package/dist/src/layouts/navMenuItems.d.ts.map +1 -1
- package/dist/src/types.d.ts +16 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/NavMenuLabel.tsx +1 -1
- package/src/components/navMenu.css +107 -0
- package/src/context/AppThemeProvider.tsx +1 -1
- package/src/crud/InlineFormSet.tsx +32 -14
- package/src/crud/ResourceList.tsx +211 -35
- package/src/crud/context/FilterContext.tsx +7 -1
- package/src/crud/fields/BooleanField.tsx +3 -1
- package/src/crud/fields/DateField.tsx +6 -4
- package/src/crud/fields/DateTimeField.tsx +9 -0
- package/src/crud/fields/ReferenceField.tsx +10 -0
- package/src/crud/fields/ReferenceManyField.tsx +13 -1
- package/src/crud/fields/TextAreaField.tsx +58 -0
- package/src/crud/fields/TimeField.tsx +48 -0
- package/src/crud/filters/ReferenceFilter.tsx +10 -0
- package/src/crud/filters/TextFilter.tsx +80 -9
- package/src/crud/index.ts +9 -1
- package/src/crud/resourceListTable.css +113 -0
- package/src/crud/types.ts +9 -0
- package/src/crud/utils/parseDayjsValue.ts +14 -0
- package/src/crud/utils/referenceSelectDropdownProps.ts +30 -0
- package/src/crud/utils/useDebouncedValue.ts +17 -0
- package/src/index.ts +7 -0
- package/src/layouts/AdminLayout.tsx +43 -6
- package/src/layouts/navMenuItems.tsx +11 -2
- 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
|
-
|
|
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
|
-
<
|
|
24
|
-
|
|
95
|
+
<TextFilterInput
|
|
96
|
+
value={value}
|
|
97
|
+
onChange={onChange}
|
|
25
98
|
placeholder={placeholder ?? label ?? source}
|
|
26
|
-
|
|
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 {
|
|
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,
|