Skip to content

Commit fdaa4d8

Browse files
authored
Merge pull request #11020 from marmelab/update-test-ui
[chore] Update `ra-core` Test UI
2 parents c6ef779 + 5cfbb90 commit fdaa4d8

19 files changed

+344
-128
lines changed

packages/ra-core/src/test-ui/Admin.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { CoreAdmin, CoreAdminProps } from '../core';
2+
import { CoreAdmin, CoreAdminProps } from '../core/CoreAdmin';
33

44
import { Layout } from './Layout';
55
import { defaultI18nProvider } from './defaultI18nProvider';
Lines changed: 7 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,10 @@
11
import * as React from 'react';
2-
import {
3-
FieldTitle,
4-
InputProps,
5-
isRequired,
6-
useChoicesContext,
7-
useInput,
8-
} from '../';
2+
import type { InputProps } from '../form/useInput';
3+
import type { ChoicesProps } from '../form/choices/useChoices';
4+
import { AutocompleteInput } from './AutocompleteInput';
95

10-
export const AutocompleteArrayInput = (props: Partial<InputProps>) => {
11-
const { allChoices, source, setFilters, filterValues } =
12-
useChoicesContext();
13-
14-
const { field, fieldState } = useInput({ source, ...props });
15-
16-
return (
17-
<div>
18-
<div>
19-
<FieldTitle
20-
label={props.label}
21-
source={props.source}
22-
resource={props.resource}
23-
isRequired={isRequired(props.validate)}
24-
/>
25-
</div>
26-
<input
27-
type="text"
28-
value={filterValues['q']}
29-
onChange={e =>
30-
setFilters({ ...filterValues, q: e.target.value })
31-
}
32-
/>
33-
<button type="button" onClick={() => field.onChange([])}>
34-
Clear value
35-
</button>
36-
<ul>
37-
{allChoices?.map(choice => (
38-
<li key={choice.id}>
39-
<label>
40-
<input
41-
type="checkbox"
42-
value={choice.id}
43-
onChange={event => {
44-
const newValue = event.target.checked
45-
? [...field.value, choice.id]
46-
: field.value.filter(
47-
(v: any) => v !== choice.id
48-
);
49-
field.onChange(newValue);
50-
}}
51-
checked={field.value.includes(choice.id)}
52-
aria-describedby={
53-
fieldState.error
54-
? `error-${props.source}`
55-
: undefined
56-
}
57-
/>
58-
{choice.name}
59-
</label>
60-
</li>
61-
))}
62-
</ul>
63-
{fieldState.error ? (
64-
<p id={`error-${props.source}`} style={{ color: 'red' }}>
65-
{fieldState.error.message}
66-
</p>
67-
) : null}
68-
</div>
69-
);
6+
export const AutocompleteArrayInput = (
7+
props: Partial<InputProps> & Partial<ChoicesProps> & { multiple?: boolean }
8+
) => {
9+
return <AutocompleteInput {...props} multiple={true} />;
7010
};
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import * as React from 'react';
2+
import { useInput, type InputProps } from '../form/useInput';
3+
import { useChoices, type ChoicesProps } from '../form/choices/useChoices';
4+
import { useChoicesContext } from '../form/choices/useChoicesContext';
5+
import { isRequired } from '../form/validation/validate';
6+
import { FieldTitle } from '../util/FieldTitle';
7+
8+
export const AutocompleteInput = (
9+
props: Partial<InputProps> & Partial<ChoicesProps> & { multiple?: boolean }
10+
) => {
11+
const { allChoices, source, setFilters, filterValues } =
12+
useChoicesContext(props);
13+
const { getChoiceText } = useChoices(props);
14+
15+
const { field, fieldState } = useInput({ source, ...props });
16+
17+
return (
18+
<div>
19+
<div>
20+
<FieldTitle
21+
label={props.label}
22+
source={props.source}
23+
resource={props.resource}
24+
isRequired={isRequired(props.validate)}
25+
/>
26+
</div>
27+
<input
28+
type="text"
29+
value={filterValues['q']}
30+
onChange={e =>
31+
setFilters({ ...filterValues, q: e.target.value })
32+
}
33+
/>
34+
<button
35+
type="button"
36+
onClick={event => {
37+
event.preventDefault();
38+
field.onChange([]);
39+
}}
40+
>
41+
Clear value
42+
</button>
43+
<ul>
44+
{allChoices?.map(choice => (
45+
<li key={choice.id}>
46+
<label>
47+
<input
48+
name={field.name}
49+
type={props.multiple ? 'checkbox' : 'radio'}
50+
value={choice.id}
51+
onChange={event => {
52+
const newValue = event.target.checked
53+
? props.multiple
54+
? [...field.value, choice.id]
55+
: choice.id
56+
: props.multiple
57+
? field.value.filter(
58+
(v: any) => v !== choice.id
59+
)
60+
: null;
61+
field.onChange(newValue);
62+
}}
63+
checked={
64+
props.multiple
65+
? field.value.includes(choice.id)
66+
: // eslint-disable-next-line eqeqeq
67+
field.value == choice.id
68+
}
69+
aria-describedby={
70+
fieldState.error
71+
? `error-${props.source}`
72+
: undefined
73+
}
74+
/>
75+
{getChoiceText(choice)}
76+
</label>
77+
</li>
78+
))}
79+
</ul>
80+
{fieldState.error ? (
81+
<p id={`error-${props.source}`} style={{ color: 'red' }}>
82+
{fieldState.error.message}
83+
</p>
84+
) : null}
85+
</div>
86+
);
87+
};
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import * as React from 'react';
2+
import { useInput, type InputProps } from '../form/useInput';
3+
import { isRequired } from '../form/validation/validate';
4+
import { FieldTitle } from '../util/FieldTitle';
5+
6+
export const BooleanInput = (props: InputProps) => {
7+
const { field } = useInput(props);
8+
9+
return (
10+
<label>
11+
<input
12+
type="checkbox"
13+
checked={field.value}
14+
onChange={event => {
15+
field.onChange(event.target.checked);
16+
}}
17+
/>
18+
<span>
19+
<FieldTitle
20+
label={props.label}
21+
source={props.source}
22+
resource={props.resource}
23+
isRequired={isRequired(props.validate)}
24+
/>
25+
</span>{' '}
26+
</label>
27+
);
28+
};

packages/ra-core/src/test-ui/Confirm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { Translate } from '../';
21
import * as React from 'react';
2+
import { Translate } from '../i18n/Translate';
33

44
export const Confirm = ({
55
isOpen,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as React from 'react';
2+
import { Link } from 'react-router-dom';
3+
import { useResourceContext } from '../core/useResourceContext';
4+
5+
export const CreateButton = (props: { resource?: string }) => {
6+
const resource = useResourceContext(props);
7+
8+
return (
9+
<Link
10+
to={`/${resource}/create`}
11+
onClick={e => {
12+
e.stopPropagation();
13+
}}
14+
>
15+
Create
16+
</Link>
17+
);
18+
};

packages/ra-core/src/test-ui/DataTable.tsx

Lines changed: 43 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import * as React from 'react';
2+
import { useNavigate } from 'react-router';
3+
import type { RaRecord } from '../types';
4+
import { useResourceContext } from '../core/useResourceContext';
5+
import { useRecordContext } from '../controller/record/useRecordContext';
6+
import { RecordContextProvider } from '../controller/record/RecordContext';
7+
import { useListContext } from '../controller/list/useListContext';
8+
import { useFieldValue } from '../util/useFieldValue';
9+
import { useEvent } from '../util/useEvent';
10+
import { useGetPathForRecordCallback } from '../routing/useGetPathForRecordCallback';
11+
import { useDataTableSelectedIdsContext } from '../dataTable/DataTableSelectedIdsContext';
212
import {
3-
DataTableBase,
4-
DataTableBaseProps,
5-
DataTableRenderContext,
6-
RaRecord,
7-
RecordContextProvider,
8-
useDataTableCallbacksContext,
913
useDataTableRenderContext,
10-
useEvent,
11-
useFieldValue,
12-
useGetPathForRecordCallback,
13-
useListContext,
14-
useRecordContext,
15-
useResourceContext,
16-
} from '../';
17-
import { useNavigate } from 'react-router';
14+
DataTableRenderContext,
15+
} from '../dataTable/DataTableRenderContext';
16+
import { useDataTableConfigContext } from '../dataTable/DataTableConfigContext';
17+
import { useDataTableCallbacksContext } from '../dataTable/DataTableCallbacksContext';
18+
import { DataTableBase, DataTableBaseProps } from '../dataTable/DataTableBase';
1819

1920
const DataTableCol = (props: {
2021
children?: React.ReactNode;
@@ -79,7 +80,7 @@ const DataTableCell = (props: {
7980

8081
const DataTableCellValue = (props: { source: string }) => {
8182
const value = useFieldValue(props);
82-
return <>{value}</>;
83+
return <>{value?.toString()}</>;
8384
};
8485

8586
const DataTableRow = (props: {
@@ -101,7 +102,10 @@ const DataTableRow = (props: {
101102
'DataTableRow can only be used within a ResourceContext or be passed a resource prop'
102103
);
103104
}
104-
const { rowClick } = useDataTableCallbacksContext();
105+
106+
const { hasBulkActions = false } = useDataTableConfigContext();
107+
const { handleToggleItem, rowClick } = useDataTableCallbacksContext();
108+
const selectedIds = useDataTableSelectedIdsContext();
105109

106110
const handleClick = useEvent(async (event: React.MouseEvent) => {
107111
event.persist();
@@ -127,21 +131,37 @@ const DataTableRow = (props: {
127131
});
128132
});
129133

130-
return <tr onClick={handleClick}>{props.children}</tr>;
134+
return (
135+
<tr onClick={handleClick}>
136+
{hasBulkActions && (
137+
<DataTableCol>
138+
<input
139+
aria-label="Select this row"
140+
type="checkbox"
141+
checked={selectedIds?.includes(record.id)}
142+
onChange={event => handleToggleItem!(record.id, event)}
143+
/>
144+
</DataTableCol>
145+
)}
146+
{props.children}
147+
</tr>
148+
);
131149
};
132150

133151
const isPromise = (value: any): value is Promise<any> =>
134152
value && typeof value.then === 'function';
135153

136154
export const DataTable = (
137-
props: Omit<DataTableBaseProps, 'hasBulkActions' | 'empty' | 'loading'>
155+
props: Omit<DataTableBaseProps, 'hasBulkActions' | 'empty' | 'loading'> & {
156+
hasBulkActions?: boolean;
157+
}
138158
) => {
139159
const { data } = useListContext();
140160

141161
return (
142162
<DataTableBase
143-
{...props}
144163
hasBulkActions={false}
164+
{...props}
145165
empty={null}
146166
loading={null}
147167
>
@@ -151,7 +171,10 @@ export const DataTable = (
151171
>
152172
<DataTableRenderContext.Provider value="header">
153173
<thead>
154-
<tr>{props.children}</tr>
174+
<tr>
175+
{props.hasBulkActions ? <td></td> : null}
176+
{props.children}
177+
</tr>
155178
</thead>
156179
</DataTableRenderContext.Provider>
157180
<DataTableRenderContext.Provider value="data">

packages/ra-core/src/test-ui/DeleteButton.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import * as React from 'react';
2+
import { Translate } from '../i18n/Translate';
23
import {
3-
Translate,
44
useDeleteController,
55
UseDeleteControllerParams,
6-
useRecordContext,
7-
} from '../';
6+
} from '../controller/button/useDeleteController';
7+
import { useRecordContext } from '../controller/record/useRecordContext';
88

99
export const DeleteButton = (
1010
props: UseDeleteControllerParams & { label: React.ReactNode }
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import * as React from 'react';
2+
import { Link } from 'react-router-dom';
3+
import type { RaRecord } from '../types';
4+
import { useRecordContext } from '../controller/record/useRecordContext';
5+
import { useResourceContext } from '../core/useResourceContext';
6+
7+
export const EditButton = (props: { record?: RaRecord; resource?: string }) => {
8+
const record = useRecordContext(props);
9+
const resource = useResourceContext(props);
10+
if (!record) return null;
11+
return (
12+
<Link
13+
to={`/${resource}/${record.id}`}
14+
onClick={e => {
15+
e.stopPropagation();
16+
}}
17+
>
18+
Edit
19+
</Link>
20+
);
21+
};

packages/ra-core/src/test-ui/Layout.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { useRefresh, useResourceDefinitions, useTranslate } from '../';
21
import * as React from 'react';
32
import { Link } from 'react-router-dom';
3+
import { useResourceDefinitions } from '../core/useResourceDefinitions';
4+
import { useTranslate } from '../i18n/useTranslate';
5+
import { useRefresh } from '../dataProvider/useRefresh';
46
import { Notification } from './Notification';
57

68
export const Layout = ({ children }: { children: React.ReactNode }) => {

0 commit comments

Comments
 (0)