ding-react-admin 2.0.8 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.css +1 -1
- package/dist/index.js +1604 -1119
- package/dist/src/components/AppHub.d.ts +8 -0
- package/dist/src/components/AppHub.d.ts.map +1 -0
- package/dist/src/components/AppLauncherButton.d.ts +7 -0
- package/dist/src/components/AppLauncherButton.d.ts.map +1 -0
- package/dist/src/components/index.d.ts +2 -0
- package/dist/src/components/index.d.ts.map +1 -1
- 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 +4 -4
- 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/router/createAdminRouter.d.ts.map +1 -1
- package/dist/src/types.d.ts +23 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AppHub.tsx +47 -0
- package/src/components/AppLauncherButton.tsx +33 -0
- package/src/components/NavMenuLabel.tsx +1 -1
- package/src/components/index.ts +2 -0
- 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 +11 -0
- package/src/layouts/AdminLayout.tsx +47 -9
- package/src/layouts/navMenuItems.tsx +11 -2
- package/src/router/createAdminRouter.tsx +3 -1
- package/src/types.ts +24 -0
|
@@ -8,12 +8,15 @@ import {
|
|
|
8
8
|
type ReactNode,
|
|
9
9
|
} from "react";
|
|
10
10
|
import type { FilterDefinition } from "../types";
|
|
11
|
+
import { DEFAULT_TEXT_FILTER_DEBOUNCE_MS } from "../filters/TextFilter";
|
|
11
12
|
|
|
12
13
|
type FilterContextValue = {
|
|
13
14
|
registerFilter: (def: FilterDefinition) => () => void;
|
|
14
15
|
filters: FilterDefinition[];
|
|
15
16
|
values: Record<string, unknown>;
|
|
16
17
|
setFilterValue: (source: string, value: unknown) => void;
|
|
18
|
+
/** Default debounce for `TextFilter` controls on this list (ms). */
|
|
19
|
+
textFilterDebounceMs: number;
|
|
17
20
|
};
|
|
18
21
|
|
|
19
22
|
const FilterContext = createContext<FilterContextValue | null>(null);
|
|
@@ -22,10 +25,12 @@ export function FilterContextProvider({
|
|
|
22
25
|
children,
|
|
23
26
|
values,
|
|
24
27
|
setFilterValue,
|
|
28
|
+
textFilterDebounceMs = DEFAULT_TEXT_FILTER_DEBOUNCE_MS,
|
|
25
29
|
}: {
|
|
26
30
|
children: ReactNode;
|
|
27
31
|
values: Record<string, unknown>;
|
|
28
32
|
setFilterValue: (source: string, value: unknown) => void;
|
|
33
|
+
textFilterDebounceMs?: number;
|
|
29
34
|
}) {
|
|
30
35
|
const [filters, setFilters] = useState<FilterDefinition[]>([]);
|
|
31
36
|
|
|
@@ -49,8 +54,9 @@ export function FilterContextProvider({
|
|
|
49
54
|
values,
|
|
50
55
|
setFilterValue,
|
|
51
56
|
registerFilter,
|
|
57
|
+
textFilterDebounceMs,
|
|
52
58
|
}),
|
|
53
|
-
[filters, values, setFilterValue, registerFilter],
|
|
59
|
+
[filters, values, setFilterValue, registerFilter, textFilterDebounceMs],
|
|
54
60
|
);
|
|
55
61
|
|
|
56
62
|
return (
|
|
@@ -7,6 +7,7 @@ export type BooleanFieldProps = BaseSourceProps & {
|
|
|
7
7
|
required?: boolean;
|
|
8
8
|
rules?: FieldRules;
|
|
9
9
|
hideLabel?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export function BooleanField({
|
|
@@ -16,6 +17,7 @@ export function BooleanField({
|
|
|
16
17
|
required,
|
|
17
18
|
rules,
|
|
18
19
|
hideLabel,
|
|
20
|
+
disabled: disabledProp,
|
|
19
21
|
}: BooleanFieldProps) {
|
|
20
22
|
return (
|
|
21
23
|
<FieldWrapper
|
|
@@ -30,7 +32,7 @@ export function BooleanField({
|
|
|
30
32
|
<Switch
|
|
31
33
|
checked={Boolean(value)}
|
|
32
34
|
onChange={onChange}
|
|
33
|
-
disabled={disabled}
|
|
35
|
+
disabled={disabled || disabledProp}
|
|
34
36
|
/>
|
|
35
37
|
)}
|
|
36
38
|
</FieldWrapper>
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { DatePicker } from "antd";
|
|
2
|
-
import dayjs from "dayjs";
|
|
3
2
|
import type { BaseSourceProps, FieldRules } from "../types";
|
|
3
|
+
import { parseDayjsValue } from "../utils/parseDayjsValue";
|
|
4
4
|
import { FieldWrapper } from "./FieldWrapper";
|
|
5
5
|
|
|
6
6
|
const DATE_FORMAT = "YYYY-MM-DD";
|
|
7
|
+
const DATE_TIME_FORMAT = `${DATE_FORMAT} HH:mm:ss`;
|
|
8
|
+
const PARSE_FORMATS = [DATE_FORMAT, DATE_TIME_FORMAT, "YYYY-MM-DDTHH:mm:ss", "YYYY-MM-DDTHH:mm:ssZ"];
|
|
7
9
|
|
|
8
10
|
export type DateFieldProps = BaseSourceProps & {
|
|
9
11
|
name?: string;
|
|
@@ -33,18 +35,18 @@ export function DateField({
|
|
|
33
35
|
>
|
|
34
36
|
{({ value, onChange, onBlur, disabled }) => (
|
|
35
37
|
<DatePicker
|
|
36
|
-
value={value ?
|
|
38
|
+
value={parseDayjsValue(value, showTime ? [...PARSE_FORMATS, DATE_TIME_FORMAT] : PARSE_FORMATS)}
|
|
37
39
|
onChange={(d) =>
|
|
38
40
|
onChange(
|
|
39
41
|
d
|
|
40
|
-
? d.format(showTime ?
|
|
42
|
+
? d.format(showTime ? DATE_TIME_FORMAT : DATE_FORMAT)
|
|
41
43
|
: null,
|
|
42
44
|
)
|
|
43
45
|
}
|
|
44
46
|
onBlur={onBlur}
|
|
45
47
|
showTime={showTime}
|
|
46
48
|
disabled={disabled}
|
|
47
|
-
format={showTime ?
|
|
49
|
+
format={showTime ? DATE_TIME_FORMAT : DATE_FORMAT}
|
|
48
50
|
style={{ width: "100%" }}
|
|
49
51
|
/>
|
|
50
52
|
)}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { DateFieldProps } from "./DateField";
|
|
2
|
+
import { DateField } from "./DateField";
|
|
3
|
+
|
|
4
|
+
export type DateTimeFieldProps = Omit<DateFieldProps, "showTime">;
|
|
5
|
+
|
|
6
|
+
/** Shorthand for `<DateField showTime />` — date + time via Ant Design DatePicker. */
|
|
7
|
+
export function DateTimeField(props: DateTimeFieldProps) {
|
|
8
|
+
return <DateField showTime {...props} />;
|
|
9
|
+
}
|
|
@@ -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
|
+
}
|