diff --git a/docs/Tutorial.md b/docs/Tutorial.md index c52609ad074..5a7a89bc092 100644 --- a/docs/Tutorial.md +++ b/docs/Tutorial.md @@ -26,9 +26,9 @@ The final result is a web application that allows you to list, create, edit, and React-admin is built on React. To start, we'll use [create-react-admin](./CreateReactAdmin.md) to bootstrap a new web application: ```sh -npm create react-admin@latest test-admin +npm create react-admin@latest test-admin -- --interactive # or -yarn create react-admin test-admin +yarn create react-admin test-admin --interactive ``` When prompted, choose **JSON Server** as the data provider, then **None** as the auth provider. Do not add any resources for now and press **Enter**. Next, choose either `npm` or `yarn` and press **Enter**. Once everything is installed, run the following commands: @@ -168,20 +168,22 @@ Copy this code and create a new `UserList` component in a new file called `users ```tsx // in src/users.tsx -import { List, Datagrid, TextField, EmailField } from "react-admin"; +import { List, DataTable, EmailField } from "react-admin"; export const UserList = () => ( - - - - - - - - - - + + + + + + + + + + + + ); ``` @@ -214,16 +216,18 @@ Let's take a closer look at the `` component: ```tsx export const UserList = () => ( - - - - - - - - - - + + + + + + + + + + + + ); ``` @@ -238,7 +242,7 @@ The root component, [``](./List.md), reads the query parameters, fetches d This demonstrates the goal of react-admin: helping developers build sophisticated applications with simple syntax. -In most frameworks, "simple" often implies limited capabilities, making it challenging to extend beyond basic features. React-admin addresses this through *composition*. `` handles data fetching, while rendering is delegated to its child—in this case, [``](./Datagrid.md). Essentially, the code composes the functionalities of `` and `` functionalities. +In most frameworks, "simple" often implies limited capabilities, making it challenging to extend beyond basic features. React-admin addresses this through *composition*. `` handles data fetching, while rendering is delegated to its child—in this case, [``](./DataTable.md). Essentially, the code composes the functionalities of `` and `` functionalities. This means we can compose `` with another component - for instance [``](./SimpleList.md): @@ -275,12 +279,12 @@ React-admin's layout is responsive by default. Try resizing your browser, and yo Your browser does not support the video tag. -However, `` has low information density on desktop. Let's modify `` to use `` on larger screens and `` on smaller screens. We can achieve this using [Material UI's `useMediaQuery` hook](https://mui.com/material-ui/react-use-media-query/): +However, `` has low information density on desktop. Let's modify `` to use `` on larger screens and `` on smaller screens. We can achieve this using [Material UI's `useMediaQuery` hook](https://mui.com/material-ui/react-use-media-query/): ```tsx // in src/users.tsx import { useMediaQuery, Theme } from "@mui/material"; -import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin"; +import { List, SimpleList, DataTable, EmailField } from "react-admin"; export const UserList = () => { const isSmall = useMediaQuery((theme) => theme.breakpoints.down("sm")); @@ -293,16 +297,18 @@ export const UserList = () => { tertiaryText={(record) => record.email} /> ) : ( - - - - - - - - - - + + + + + + + + + + + + )} ); @@ -321,22 +327,27 @@ The `` component's child can be anything—even a custom component with it ## Selecting Columns -Let's get back to ``. It reads the data fetched by ``, then renders a table with one row for each record. `` uses its child components (here, a list of [Field component](./Fields.md)) to render the columns. Each Field component renders one field of the current record, specified by the `source` prop. +Let's get back to ``. +It reads the data fetched by ``, then renders a table with one row for each record. `` uses its child components (here, a list of [Field component](./Fields.md)) to render the columns. +Each `` component renders one field of the current record, specified by the `source` prop. -`` created one column for every field in the API response. That's a bit too much for a usable grid, so let's remove a couple of `` components from the Datagrid and see the effect: +`` created one column for every field in the API response. +That's a bit too much for a usable grid, so let's remove a couple of `` components from the DataTable and see the effect: ```diff // in src/users.tsx - - - -- - -- - - - - + + + +- + + + +- + + + + ``` [![Users List](./img/tutorial_users_list_selected_columns.png)](./img/tutorial_users_list_selected_columns.png) @@ -345,24 +356,29 @@ In react-admin, most configuration is done through components. Instead of using ## Using Field Types -So far, you've used [``](./TextField.md) and [``](./EmailField.md). React-admin provides [many more Field components](./Fields.md) to handle different data types—numbers, dates, images, arrays, and more. +So far, you've used simples [``](.//DataTable.md#datatablecol) and [`EmailField`](./EmailField.md) as [a `DataTable.Col` `field`](./DataTable.md#field). +React-admin provides [many more Field components](./Fields.md) to handle different data types—numbers, dates, images, arrays, and more. For instance, instead of displaying the `website` field as plain text, you could make it a clickable link using [``](./UrlField.md): ```diff // in src/users.tsx --import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin"; -+import { List, SimpleList, Datagrid, TextField, EmailField, UrlField } from "react-admin"; +-import { List, SimpleList, DataTable, EmailField } from "react-admin"; ++import { List, SimpleList, DataTable, EmailField, UrlField } from "react-admin"; // ... - - - - - -- -+ - - + + + + + + + +- ++ ++ ++ + + ``` [![Url Field](./img/tutorial_url_field.png)](./img/tutorial_url_field.png) @@ -371,9 +387,11 @@ This is typical of the early stages of development with react-admin: use a guess ## Writing A Custom Field -In react-admin, fields are just React components. When rendered, they grab the `record` fetched from the API (e.g. `{ "id": 2, "name": "Ervin Howell", "website": "anastasia.net", ... }`) using a custom hook, and use the `source` prop (e.g. `website`) to get the value they should display (e.g. "anastasia.net"). +In react-admin, fields are just React components. +When rendered, they grab the `record` fetched from the API (e.g. `{ "id": 2, "name": "Ervin Howell", "website": "anastasia.net", ... }`) using a custom hook, and use the `source` prop (e.g. `website`) to get the value they should display (e.g. "anastasia.net"). -That means you can do the same to [write a custom field](./Fields.md#writing-your-own-field-component). For instance, here is a simplified version of the ``: +That means you can do the same to [write a custom field](./Fields.md#writing-your-own-field-component). +For instance, here is a simplified version of the ``: ```tsx // in src/MyUrlField.tsx @@ -388,25 +406,27 @@ const MyUrlField = ({ source }: { source: string }) => { export default MyUrlField; ``` -For each row, `` creates a `RecordContext` and stores the current record in it. [`useRecordContext`](./useRecordContext.md) allows you to read that record. It's one of the 50+ headless hooks that react-admin exposes to let you build your own components without forcing a particular UI. +For each row, `` creates a `RecordContext` and stores the current record in it. +[`useRecordContext`](./useRecordContext.md) allows you to read that record. +It's one of the 50+ headless hooks that react-admin exposes to let you build your own components without forcing a particular UI. You can use the `` component in `` instead of react-admin's `` component, and it will work just the same. ```diff // in src/users.tsx --import { List, SimpleList, Datagrid, TextField, EmailField, UrlField } from "react-admin"; -+import { List, SimpleList, Datagrid, TextField, EmailField } from "react-admin"; +-import { List, SimpleList, DataTable, EmailField, UrlField } from "react-admin"; ++import { List, SimpleList, DataTable, EmailField } from "react-admin"; +import MyUrlField from './MyUrlField'; // ... - - - - - -- -+ - - + + + + + +- ++ + + ``` This means react-admin never blocks you: if one react-admin component doesn't perfectly suit your needs, you can just swap it with your own version. @@ -478,20 +498,23 @@ export const App = () => ( [![Guessed Post List](./img/tutorial_guessed_post_list.png)](./img/tutorial_guessed_post_list.png) -The `ListGuesser` suggests using a [``](./ReferenceField.md) for the `userId` field. Let's play with this new field by creating the `PostList` component based on the code dumped by the guesser: +The `ListGuesser` suggests using a [``](./ReferenceField.md) for the `userId` field. +Let's play with this new field by creating the `PostList` component based on the code dumped by the guesser: ```tsx // in src/posts.tsx -import { List, Datagrid, TextField, ReferenceField } from "react-admin"; +import { List, DataTable, ReferenceField } from "react-admin"; export const PostList = () => ( - - - - - - + + + + + + + + ); ``` @@ -521,26 +544,32 @@ When displaying the posts list, react-admin is smart enough to display the `name The `` component fetches the reference data, creates a `RecordContext` with the result, and renders the record representation (or its children). -**Tip**: Look at the network tab of your browser again: react-admin deduplicates requests for users and aggregates them in order to make only *one* HTTP request to the `/users` endpoint for the whole Datagrid. That's one of many optimizations that keep the UI fast and responsive. +**Tip**: Look at the network tab of your browser again: react-admin deduplicates requests for users and aggregates them in order to make only *one* HTTP request to the `/users` endpoint for the whole DataTable. That's one of many optimizations that keep the UI fast and responsive. -To finish the post list, place the post `id` field as the first column, and remove the `body` field. From a UX point of view, fields containing large chunks of text should not appear in a Datagrid, only in detail views. Also, to make the Edit action stand out, let's replace the default `rowClick` action with an explicit action button: +To finish the post list, place the post `id` field as the first column, and remove the `body` field. +From a UX point of view, fields containing large chunks of text should not appear in a DataTable, only in detail views. +Also, to make the Edit action stand out, let's replace the default `rowClick` action with an explicit action button: ```diff // in src/posts.tsx --import { List, Datagrid, TextField, ReferenceField } from "react-admin"; -+import { List, Datagrid, TextField, ReferenceField, EditButton } from "react-admin"; +-import { List, DataTable, ReferenceField } from "react-admin"; ++import { List, DataTable, ReferenceField, EditButton } from "react-admin"; export const PostList = () => ( -- -+ -+ - -- - -- -+ - +- ++ ++ + + + +- + +- ++ ++ ++ + ); ``` @@ -583,13 +612,15 @@ Now that the `users` resource has a `show` view, you can also link to it from th // in src/posts.tsx export const PostList = () => ( - -- -+ - - - - + + +- ++ + + + + + ); ``` @@ -633,8 +664,7 @@ Copy the `` code dumped by the guesser in the console to the `posts.ts // in src/posts.tsx import { List, - Datagrid, - TextField, + DataTable, ReferenceField, EditButton, Edit, @@ -699,7 +729,7 @@ export const PostEdit = () => ( ``` {% endraw %} -If you've understood the `` component, the `` component will be no surprise. It's responsible for fetching the record and displaying the page title. It passes the record down to the [``](./SimpleForm.md) component, which is responsible for the form layout, default values, and validation. Just like ``, `` uses its children to determine the form inputs to display. It expects [*input components*](./Inputs.md) as children. [``](./TextInput.md) and [``](./ReferenceInput.md) are such inputs. +If you've understood the `` component, the `` component will be no surprise. It's responsible for fetching the record and displaying the page title. It passes the record down to the [``](./SimpleForm.md) component, which is responsible for the form layout, default values, and validation. Just like ``, `` uses its children to determine the form inputs to display. It expects [*input components*](./Inputs.md) as children. [``](./TextInput.md) and [``](./ReferenceInput.md) are such inputs. The `` takes the same props as the `` (used earlier in the `` page). `` uses these props to fetch the API for possible references related to the current record (in this case, possible `users` for the current `post`). It then creates a context with the possible choices and renders an [``](./AutocompleteInput.md), which is responsible for displaying the choices and letting the user select one. @@ -711,8 +741,7 @@ Let's allow users to create posts, too. Copy the `` component into a ` // in src/posts.tsx import { List, - Datagrid, - TextField, + DataTable, ReferenceField, EditButton, Edit, diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx index 10c40540cba..ec2d2b6a7b0 100644 --- a/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx +++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.spec.tsx @@ -1,52 +1,44 @@ import * as React from 'react'; import expect from 'expect'; -import { render, screen, waitFor } from '@testing-library/react'; -import { CoreAdminContext } from 'ra-core'; +import { render, screen } from '@testing-library/react'; -import { ShowGuesser } from './ShowGuesser'; -import { ThemeProvider } from '../theme/ThemeProvider'; +import { ShowGuesser } from './ShowGuesser.stories'; describe('', () => { it('should log the guessed Show view based on the fetched record', async () => { const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); - const dataProvider = { - getOne: () => - Promise.resolve({ - data: { - id: 123, - author: 'john doe', - post_id: 6, - score: 3, - body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.", - created_at: new Date('2012-08-02'), - tags_ids: [1, 2], - }, - }), - }; - render( - - - - - - ); - await waitFor(() => { - screen.getByText('john doe'); - }); + render(); + await screen.findByText('john doe'); expect(logSpy).toHaveBeenCalledWith(`Guessed Show: -import { DateField, NumberField, ReferenceArrayField, ReferenceField, Show, SimpleShowLayout, TextField } from 'react-admin'; +import { ArrayField, BooleanField, DataTable, DateField, EmailField, NumberField, ReferenceArrayField, ReferenceField, RichTextField, Show, SimpleShowLayout, TextField, UrlField } from 'react-admin'; -export const CommentShow = () => ( +export const BookShow = () => ( - + + + + + + + + + + + + + + + + + );`); diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx new file mode 100644 index 00000000000..80d9bb66be0 --- /dev/null +++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.stories.tsx @@ -0,0 +1,48 @@ +import * as React from 'react'; +import { Admin } from 'react-admin'; +import { Resource, TestMemoryRouter } from 'ra-core'; +import fakeRestProvider from 'ra-data-fakerest'; + +import { ShowGuesser as RAShowGuesser } from './ShowGuesser'; + +export default { title: 'ra-ui-materialui/detail/ShowGuesser' }; + +const data = { + books: [ + { + id: 123, + authors: [ + { id: 1, name: 'john doe', dob: '1990-01-01' }, + { id: 2, name: 'jane doe', dob: '1992-01-01' }, + ], + post_id: 6, + score: 3, + body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.", + description: `

War and Peace is a novel by the Russian author Leo Tolstoy, +published serially, then in its entirety in 1869.

+

It is regarded as one of Tolstoy's finest literary achievements and remains a classic of world literature.

`, + created_at: new Date('2012-08-02'), + tags_ids: [1, 2], + url: 'https://www.myshop.com/tags/top-seller', + email: 'doe@production.com', + isAlreadyPublished: true, + }, + ], + tags: [ + { id: 1, name: 'top seller' }, + { id: 2, name: 'new' }, + ], + posts: [ + { id: 6, title: 'War and Peace', body: 'A great novel by Leo Tolstoy' }, + ], +}; + +const ShowGuesserWithProdLogs = () => ; + +export const ShowGuesser = () => ( + + + + + +); diff --git a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx index e21d81be0cc..d3ecc4fc15a 100644 --- a/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx +++ b/packages/ra-ui-materialui/src/detail/ShowGuesser.tsx @@ -72,10 +72,16 @@ const ShowViewGuesser = ( ) .sort(); + const importsToLog = components.includes('DataTable') + ? components.filter( + component => !component.startsWith('DataTable.') + ) + : components; + console.log( `Guessed Show: -import { ${components.join(', ')} } from 'react-admin'; +import { ${importsToLog.join(', ')} } from 'react-admin'; export const ${capitalize(singularize(resource))}Show = () => ( diff --git a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx index 7e39fdc5996..ce894912d73 100644 --- a/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx +++ b/packages/ra-ui-materialui/src/detail/showFieldTypes.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import { ReactNode } from 'react'; -import { Datagrid } from '../list/datagrid/Datagrid'; +import type { InferredElement, InferredTypeMap, InputProps } from 'ra-core'; import { ArrayField, BooleanField, @@ -17,8 +17,7 @@ import { ChipField, } from '../field'; import { SimpleShowLayout, SimpleShowLayoutProps } from './SimpleShowLayout'; -import { InferredElement, InferredTypeMap, InputProps } from 'ra-core'; -import { SingleFieldList } from '../list'; +import { DataTable, SingleFieldList } from '../list'; export const showFieldTypes: InferredTypeMap = { show: { @@ -30,18 +29,31 @@ ${children.map(child => ` ${child.getRepresentation()}`).join('\n')} `, }, array: { - component: ({ - children, - ...props - }: { children: ReactNode } & InputProps) => ( + component: ({ children, ...props }: { children } & InputProps) => ( - {children} + + {children && children.length > 0 + ? children.map((child, index) => ( + + {child} + + )) + : children} + ), representation: (props: InputProps, children: InferredElement[]) => - `${children - .map(child => child.getRepresentation()) - .join('\n')}`, + ` + + ${children + .map( + child => ` + ${child.getRepresentation()} + ` + ) + .join('\n ')} + + `, }, boolean: { component: BooleanField, diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx index 1f898e5ea73..0c8f0fcd04c 100644 --- a/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx +++ b/packages/ra-ui-materialui/src/list/ListGuesser.spec.tsx @@ -1,57 +1,81 @@ import * as React from 'react'; import expect from 'expect'; -import { render, screen, waitFor } from '@testing-library/react'; -import { CoreAdminContext, testDataProvider } from 'ra-core'; - -import { ListGuesser } from './ListGuesser'; -import { ThemeProvider } from '../theme/ThemeProvider'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { ManyResources } from './ListGuesser.stories'; describe('', () => { - it('should log the guessed List view based on the fetched records', async () => { + it('should log the guessed List views based on the fetched records', async () => { const logSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); - const dataProvider = testDataProvider({ - getList: () => - Promise.resolve({ - data: [ - { - id: 123, - author: 'john doe', - post_id: 6, - score: 3, - body: "Queen, tossing her head through the wood. 'If it had lost something; and she felt sure it.", - created_at: new Date('2012-08-02'), - tags_ids: [1, 2], - }, - ], - total: 1, - }), - getMany: () => Promise.resolve({ data: [], total: 0 }), - }); - render( - - - - - - ); - await waitFor(() => { - screen.getByText('john doe'); - }); + render(); + await screen.findAllByText('top seller', undefined, { timeout: 2000 }); + expect(logSpy).toHaveBeenCalledWith(`Guessed List: + +import { DataTable, DateField, EmailField, List, ReferenceArrayField, ReferenceField } from 'react-admin'; + +export const ProductList = () => ( + + + + + + + + + + + + + + + + + + + +);`); + logSpy.mockClear(); + + fireEvent.click(screen.getByText('Categories')); + await screen.findByText('Jeans'); + expect(logSpy).toHaveBeenCalledWith(`Guessed List: + +import { ArrayField, BooleanField, ChipField, DataTable, List, SingleFieldList } from 'react-admin'; + +export const CategoryList = () => ( + + + + + + + + + + + + + + + + +);`); + + logSpy.mockClear(); + fireEvent.click(screen.getByText('Tags')); + await screen.findByText('top seller'); expect(logSpy).toHaveBeenCalledWith(`Guessed List: -import { Datagrid, DateField, List, NumberField, ReferenceArrayField, ReferenceField, TextField } from 'react-admin'; +import { DataTable, List, UrlField } from 'react-admin'; -export const CommentList = () => ( +export const TagList = () => ( - - - - - - - - - + + + + + + + );`); }); diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx index 61c93ec4168..165844e50e2 100644 --- a/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx +++ b/packages/ra-ui-materialui/src/list/ListGuesser.stories.tsx @@ -21,6 +21,8 @@ const data = { price: 45.99, category_id: 1, tags_ids: [1], + last_update: new Date('2023-10-01').toISOString(), + email: 'office.jeans@myshop.com', }, { id: 2, @@ -28,6 +30,8 @@ const data = { price: 69.99, category_id: 1, tags_ids: [2, 3], + last_update: new Date('2023-11-01').toISOString(), + email: 'black.elegance.jeans@myshop.com', }, { id: 3, @@ -35,6 +39,8 @@ const data = { price: 55.99, category_id: 1, tags_ids: [2, 4], + last_update: new Date('2023-12-01').toISOString(), + email: 'slim.fit.jeans@myshop.com', }, { id: 4, @@ -42,6 +48,8 @@ const data = { price: 15.99, category_id: 2, tags_ids: [1, 4, 3], + last_update: new Date('2023-10-15').toISOString(), + email: 'basic.t.shirt@myshop.com', }, { id: 5, @@ -49,30 +57,93 @@ const data = { price: 19.99, category_id: 6, tags_ids: [1, 4, 3], + last_update: new Date('2023-10-15').toISOString(), + email: 'basic.cap@myshop.com', }, ], categories: [ - { id: 1, name: 'Jeans' }, - { id: 2, name: 'T-Shirts' }, - { id: 3, name: 'Jackets' }, - { id: 4, name: 'Shoes' }, - { id: 5, name: 'Accessories' }, - { id: 6, name: 'Hats' }, - { id: 7, name: 'Socks' }, - { id: 8, name: 'Shirts' }, - { id: 9, name: 'Sweaters' }, - { id: 10, name: 'Trousers' }, - { id: 11, name: 'Coats' }, - { id: 12, name: 'Dresses' }, - { id: 13, name: 'Skirts' }, - { id: 14, name: 'Swimwear' }, - { id: 15, name: 'Bags' }, + { + id: 1, + name: 'Jeans', + alternativeName: [{ name: 'denims' }, { name: 'pants' }], + isVeganProduction: true, + }, + { + id: 2, + name: 'T-Shirts', + alternativeName: [{ name: 'polo' }, { name: 'tee shirt' }], + isVeganProduction: false, + }, + { + id: 3, + name: 'Jackets', + alternativeName: [{ name: 'coat' }, { name: 'blazers' }], + isVeganProduction: false, + }, + { + id: 4, + name: 'Shoes', + alternativeName: [{ name: 'sneakers' }, { name: 'moccasins' }], + isVeganProduction: false, + }, + { + id: 5, + name: 'Accessories', + alternativeName: [{ name: 'jewelry' }, { name: 'belts' }], + isVeganProduction: true, + }, + { + id: 6, + name: 'Hats', + alternativeName: [{ name: 'caps' }, { name: 'headwear' }], + isVeganProduction: true, + }, + { + id: 7, + name: 'Socks', + alternativeName: [{ name: 'stockings' }, { name: 'hosiery' }], + isVeganProduction: false, + }, + { + id: 8, + name: 'Bags', + alternativeName: [{ name: 'handbags' }, { name: 'purses' }], + isVeganProduction: false, + }, + { + id: 9, + name: 'Dresses', + alternativeName: [{ name: 'robes' }, { name: 'gowns' }], + isVeganProduction: false, + }, + { + id: 10, + name: 'Skirts', + alternativeName: [{ name: 'tutus' }, { name: 'kilts' }], + isVeganProduction: false, + }, ], tags: [ - { id: 1, name: 'top seller' }, - { id: 2, name: 'new' }, - { id: 3, name: 'sale' }, - { id: 4, name: 'promotion' }, + { + id: 1, + name: 'top seller', + url: 'https://www.myshop.com/tags/top-seller', + }, + { + id: 2, + name: 'new', + url: 'https://www.myshop.com/tags/new', + }, + { + id: 3, + name: 'sale', + url: 'https://www.myshop.com/tags/sale', + }, + { + id: 4, + name: 'promotion', + url: 'https://www.myshop.com/tags/promotion', + }, ], }; @@ -119,6 +190,8 @@ const delayedDataProvider = fakeRestProvider( 300 ); +const ListGuesserWithProdLogs = () => ; + export const ManyResources = () => ( ( diff --git a/packages/ra-ui-materialui/src/list/ListGuesser.tsx b/packages/ra-ui-materialui/src/list/ListGuesser.tsx index 595194e7c4f..14a0b34b9cf 100644 --- a/packages/ra-ui-materialui/src/list/ListGuesser.tsx +++ b/packages/ra-ui-materialui/src/list/ListGuesser.tsx @@ -18,11 +18,11 @@ import { listFieldTypes } from './listFieldTypes'; import { capitalize, singularize } from 'inflection'; /** - * List component rendering a based on the result of the + * List component rendering a based on the result of the * dataProvider.getList() call. * * The result (choice and type of columns) isn't configurable, but the - * outputs the it has guessed to the console so that + * outputs the it has guessed to the console so that * developers can start from there. * * To be used as the list prop of a . @@ -132,10 +132,16 @@ const ListViewGuesser = ( .sort(); if (enableLog) { + const importsToLog = components.includes('DataTable') + ? components.filter( + component => !component.startsWith('DataTable.') + ) + : components; + console.log( `Guessed List: -import { ${components.join(', ')} } from 'react-admin'; +import { ${importsToLog.join(', ')} } from 'react-admin'; export const ${capitalize(singularize(resource))}List = () => ( diff --git a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx index 69b1592236c..dcb4745ff10 100644 --- a/packages/ra-ui-materialui/src/list/listFieldTypes.tsx +++ b/packages/ra-ui-materialui/src/list/listFieldTypes.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Datagrid } from './datagrid'; +import { DataTable } from './datatable'; import { SingleFieldList } from './SingleFieldList'; import { ArrayField, @@ -7,80 +7,117 @@ import { ChipField, DateField, EmailField, - NumberField, ReferenceField, ReferenceArrayField, - TextField, UrlField, ArrayFieldProps, + TextField, } from '../field'; export const listFieldTypes = { table: { component: props => { - return ; + return ; }, - representation: (_props, children) => ` + representation: (_props, children) => ` ${children.map(child => ` ${child.getRepresentation()}`).join('\n')} - `, + `, }, array: { component: ({ children, ...props }: ArrayFieldProps) => { const childrenArray = React.Children.toArray(children); return ( - - - 0 && - React.isValidElement(childrenArray[0]) && - childrenArray[0].props.source - } - /> - - + + + + 0 && + React.isValidElement(childrenArray[0]) && + childrenArray[0].props.source + } + /> + + + ); }, representation: (props, children) => - ``, + ` + + + + + + `, }, boolean: { - component: BooleanField, - representation: props => ``, + component: props => ( + + + + ), + representation: props => + ` + + `, }, date: { - component: DateField, - representation: props => ``, + component: props => ( + + + + ), + representation: props => + ` + + `, }, email: { - component: EmailField, - representation: props => ``, + component: props => ( + + + + ), + representation: props => + ` + + `, }, id: { - component: TextField, - representation: props => ``, + component: props => , + representation: props => ``, }, number: { - component: NumberField, - representation: props => ``, + component: DataTable.NumberCol, + representation: props => + ``, }, reference: { - component: ReferenceField, + component: props => ( + + + + ), representation: props => - ``, + ` + + `, }, referenceChild: { component: () => , representation: () => ``, }, referenceArray: { - component: ReferenceArrayField, + component: props => ( + + + + ), representation: props => - ``, + ` + + `, }, referenceArrayChild: { component: () => ( @@ -89,15 +126,24 @@ ${children.map(child => ` ${child.getRepresentation()}`).join('\n')} ), representation: () => - ``, + ` + + `, }, richText: undefined, // never display a rich text field in a datagrid string: { - component: TextField, - representation: props => ``, + component: DataTable.Col, + representation: props => ``, }, url: { - component: UrlField, - representation: props => ``, + component: props => ( + + + + ), + representation: props => + ` + + `, }, };