diff --git a/docs/Search.md b/docs/Search.md index 65420634adc..9efd5bd352c 100644 --- a/docs/Search.md +++ b/docs/Search.md @@ -159,19 +159,84 @@ export const App = () => ( ## Props +The `` component accepts the following props: -| Prop | Required | Type | Default | Description | -| ----------- | -------- | ------------------ | -------- | ------------------------------------------------------------------ | -| `children` | Optional | `ReactNode` | `` | The search result renderer | -| `options` | Optional | `object` | - | The search options (see details below) | -| `wait` | Optional | `number` | 500 | The delay of debounce for the search to launch after typing in ms. | -| `color` | Optional | `'light' | 'dark'` | 'light' | The color mode for the input, applying light or dark background. | +| Prop | Required | Type | Default | Description | +| ------------- | -------- | --------- | ---------------------- | -------------------------------------------------------------------------------------------------- | +| `children` | Optional | `Element` | `` | A component that will display the results. | +| `color` | Optional | `string` | `light` | The color mode for the input, applying light or dark backgrounds. Accept either `light` or `dark`. | +| `historySize` | Optional | `number` | 5 | The number of past queries to keep in history. | +| `options` | Optional | `Object` | - | An object containing options to apply to the search. | +| `wait` | Optional | `number` | 500 | The delay of debounce for the search to launch after typing in ms. | -The `options` object can contain the following keys: +## `children` -- `targets`: `string[]` An array of the indices on which to perform the search. Defaults to an empty array. -- `historySize`: `number` The max number of search texts kept in the history. Default is 5. -- `{any}`: `{any}` Any custom option to pass to the search engine. +The `` children allow you to customize the way results are displayed. The child component can grab the search result using the `useSearchResult` hook. + +```tsx +import { Admin, AppBar, TitlePortal, Layout } from 'react-admin'; +import { Search, useSearchResult } from '@react-admin/ra-search'; + +const CustomSearchResultsPanel = () => { + const { data, onClose } = useSearchResult(); + + return ( +
    + {data.map(searchResult => ( +
  • {searchResult.content.label}
  • + ))} +
+ ); +}; + +const MyAppBar = () => ( + + + + + + +); + +const MyLayout = props => ; + +export const App = () => ( + + // ... + +); +``` + +## `color` + +If you need it, you can choose to render the `light` or the `dark` version of search input. + +```tsx + +``` + +## `historySize` + +The number of previous user searches to keep in the popover. For example, if a user performs 10 searches and `historySize` is set to 5, the popover will display the user's last 5 queries. + +```tsx + +``` + +## `options` + +An object containing options to apply to the search: + +- `targets`:`string[]`: an array of the indices on which to perform the search. Defaults to an empty array. +- `{any}`:`{any}`: any custom option to pass to the search engine. + +## `wait` + +The number of milliseconds to wait before processing the search request, immediately after the user enters their last character. + +```tsx + +``` ## Customizing The Result Items diff --git a/docs/SearchWithResult.md b/docs/SearchWithResult.md new file mode 100644 index 00000000000..204ef29cc61 --- /dev/null +++ b/docs/SearchWithResult.md @@ -0,0 +1,432 @@ +--- +layout: default +title: "The SearchWithResult Component" +--- + +# `` + +This [Enterprise Edition](https://marmelab.com/ra-enterprise) component, part of [`ra-search`](https://marmelab.com/ra-enterprise/modules/ra-search), renders a search input and the search results directly below the input. It's ideal for dashboards or menu panels. + + + +It relies on the `dataProvider` to provide a `search()` method, so you can use it with any search engine (Lucene, ElasticSearch, Solr, Algolia, Google Cloud Search, and many others). And if you don't have a search engine, no problem! `` can also do the search across several resources [via parallel `dataProvider.getList()` queries](https://marmelab.com/ra-enterprise/modules/ra-search#addsearchmethod-helper). + +By default, `` will group the search results by target, and show their `content.label` and `content.description`. + +## Usage + +### Install `ra-search` + +The `` component is part of the `@react-admin/ra-search` package. To install it, run: + +```sh +yarn add '@react-admin/ra-search' +``` + +This requires a valid subscription to [React-admin Enterprise Edition](https://marmelab.com/ra-enterprise). + +### Implement `dataProvider.search()` + +Your `dataProvider` should support the `search()` method. It should return a Promise for `data` containing an array of `SearchResult` objects and a `total`. A `SearchResult` contains at least the following fields: + +- `id`: Identifier The unique identifier of the search result +- `type`: An arbitrary string which enables grouping +- `url`: The URL where to redirect to on click. It could be a custom page and not a resource if you want to +- `content`: Can contain any data that will be used to display the result. If used with the default `` component, it must contain at least an `id`, `label`, and a `description`. +- `matches`: An optional object containing an extract of the data with matches. Can be anything that will be interpreted by a `` + +As for the `total`, it can be greater than the number of returned results. This is useful e.g. to show that there are more results. + +Here is an example + +```jsx +dataProvider.search("roll").then((response) => console.log(response)); +// { +// data: [ +// { id: 'a7535', type: 'artist', url: '/artists/7535', content: { label: 'The Rolling Stones', description: 'English rock band formed in London in 1962' } } +// { id: 'a5352', type: 'artist', url: '/artists/5352', content: { label: 'Sonny Rollins', description: 'American jazz tenor saxophonist' } } +// { id: 't7524', type: 'track', url: '/tracks/7524', content: { label: 'Like a Rolling Stone', year: 1965, recordCompany: 'Columbia', artistId: 345, albumId: 435456 } } +// { id: 't2386', type: 'track', url: '/tracks/2386', content: { label: "It's Only Rock 'N Roll (But I Like It)", year: 1974, artistId: 7535, albumId: 6325 } } +// { id: 'a6325', type: 'album', url: '/albums/6325', content: { label: "It's Only rock 'N Roll", year: 1974, artistId: 7535 }} +// ], +// total: 5 +// } +``` + +It is your responsibility to add this search method to your `dataProvider` so that react-admin can send queries to and read responses from the search engine. + +If you don't have a search engine, you can use the `addSearchMethod` helper to add a `dataProvider.search()` method that does a parallel `dataProvider.getList()` query for each resource. + +```jsx +// in src/dataProvider.js +import simpleRestProvider from 'ra-data-simple-rest'; +import { addSearchMethod } from '@react-admin/ra-search'; + +const baseDataProvider = simpleRestProvider('http://path.to.my.api/'); + +export const dataProvider = addSearchMethod(baseDataProvider, [ + // search across these resources + 'artists', + 'tracks', + 'albums', +]); +``` + +Then, here's how to include the `` component inside a custom `` component: + +```tsx +import { Card, CardContent } from '@mui/material'; +import { Admin } from 'react-admin'; +import { SearchWithResult } from '@react-admin/ra-search'; +import { searchDataProvider } from './searchDataProvider'; + +const MyDashboard = () => ( + + + + + +); + +export const App = () => ( + + {/*...*/} + +); +``` + +Check [the `ra-search` documentation](https://marmelab.com/ra-enterprise/modules/ra-search) to learn more about the input and output format of `dataProvider.search()`, as well as the possibilities to customize the `addSearchMethod`. + +## Props + +| Prop | Required | Type | Default | Description | +| ------------ | -------- | ---------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | +| `children` | Optional | `Element` | `` | A component that will display the results. | +| `color` | Optional | `string` | The opposite of theme mode. If mode is `light` default is `dark` and vice versa | The color mode for the input, applying light or dark backgrounds. Accept either `light` or `dark`. | +| `onNavigate` | Optional | `function` | `() => undefined` | A callback function to run when the user navigate to a result. | +| `options` | Optional | `Object` | - | An object containing options to apply to the search. | +| `wait` | Optional | `number` | 500 | The delay of debounce for the search to launch after typing in ms. | + +## `children` + +The `` children allow you to customize the way results are displayed. The child component can grab the search result using the `useSearchResult` hook. + +```tsx +import { Admin } from 'react-admin'; +import { SearchWithResult, useSearchResults } from '@react-admin/ra-search'; +import { searchDataProvider } from './searchDataProvider'; + +const MyDashboard = () => ( + + + +); + +const MySearchResultsPanel = () => { + const { data } = useSearchResults(); + return ( +
    + {data.map(item => ( +
  • {item.content.label}
  • + ))} +
+ ); +}; + +export const App = () => ( + + {/*...*/} + +); +``` + +## `color` + +If you need it, you can choose to render the `light` or the `dark` version of search input. + +```tsx + +``` + +## `onNavigate` + +`onNavigate` allows you to perform an action when the user clicks on a search result, e.g. to close a menu ([See below](#use-it-with-solarlayout) for an example with ``). + +```tsx +import { Admin } from 'react-admin'; +import { SearchWithResult } from '@react-admin/ra-search'; +import { searchDataProvider } from './searchDataProvider'; + +const MyDashboard = () => { + const handleNavigate = () => { + console.log('User navigated to a result'); + }; + return ; +}; + +export const App = () => ( + + {/*...*/} + +); +``` + +## `options` + +An object containing options to apply to the search: + +- `targets`: `string[]`: an array of the indices on which to perform the search. Defaults to an empty array. +- `{any}`: `{any}`: any custom option to pass to the search engine. + +## `wait` + +The number of milliseconds to wait before processing the search request, immediately after the user enters their last character. + +```tsx + +``` + +## Customizing the Entire Search Results + +Pass a custom React element as a child of `` to customize the appearance of the search results. This can be useful e.g. to customize the results grouping, or to arrange search results differently. + +`ra-search` renders the `` inside a `SearchContext`. You can use the `useSearchResult` hook to read the search results, as follows: + +{% raw %} +```tsx +import { Card, CardContent } from '@mui/material'; +import { Link } from 'react-router-dom'; +import { Admin } from 'react-admin'; +import { + SearchWithResult, + SearchResultsPanel, + useSearchResults, +} from '@react-admin/ra-search'; +import { searchDataProvider } from './searchDataProvider'; + +const MyDashboard = () => ( + + + + + + + +); + +const MySearchResultsPanel = () => { + const { data } = useSearchResults(); + return ( +
    + {data.map(item => ( +
  • + + {item.content.label} + +

    {item.content.description}

    +
  • + ))} +
+ ); +}; + +export const App = () => ( + + {/*...*/} + +); +``` +{% endraw %} + +## Customizing The Result Items + +By default, `` displays the results in ``, which displays each results in a ``. So rendering `` without children is equivalent to rendering: + +```tsx +const MySearch = () => ( + + + + + +); +``` + +`` renders the `content.label` and `content.description` for each result. You can customize what it renders by providing a function as the `label` and the `description` props. This function takes the search result as a parameter and must return a React element. + +For instance: + +```tsx +import { Card, CardContent } from '@mui/material'; +import Groups3Icon from '@mui/icons-material/Groups3'; +import LibraryMusicIcon from '@mui/icons-material/LibraryMusic'; +import { Admin } from 'react-admin'; +import { + SearchWithResult, + SearchResultsPanel, + SearchResultItem, + useSearchResults, +} from '@react-admin/ra-search'; +import { searchDataProvider } from './searchDataProvider'; + +const MyDashboard = () => ( + + + + + ( + <> + {record.type === 'artists' ? ( + + ) : ( + + )} + {record.content.label} + + )} + /> + + + + +); + +export const App = () => ( + + {/*...*/} + +); +``` + +You can also completely replace the search result item component: + +```tsx +import { Card, CardContent } from '@mui/material'; +import { Link } from 'react-router-dom'; +import { Admin } from 'react-admin'; +import { + SearchWithResult, + SearchResultsPanel, + SearchResultItem, +} from '@react-admin/ra-search'; +import { searchDataProvider } from './searchDataProvider'; + +const MySearchResultItem = ({ data }) => ( +
  • + + {data.content.label} + +

    {data.content.description}

    +
  • +); + +const MyDashboard = () => ( + + + + + + + + + +); + +export const App = () => ( + + {/*...*/} + +); +``` + +## Use It With SolarLayout + +The `` component works perfectly when used inside the [``](https://marmelab.com/ra-enterprise/modules/ra-navigation#solarlayout) menu. + + + +The `useSolarSidebarActiveMenu` hook combined with the `onNavigate` prop allow you to close the `` when the user selects an element in the result. + +Here is an implementation example: + +{% raw %} +```tsx +import { Admin } from 'react-admin'; +import { Box } from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import AlbumIcon from '@mui/icons-material/Album'; +import Groups3Icon from '@mui/icons-material/Groups3'; +import { + SolarLayout, + SolarLayoutProps, + SolarMenu, + useSolarSidebarActiveMenu, +} from '@react-admin/ra-navigation'; +import { SearchWithResult } from '@react-admin/ra-search'; +import { searchDataProvider } from './searchDataProvider'; + +const MySolarLayout = (props: SolarLayoutProps) => ( + +); + +const MySolarMenu = () => ( + }> + } + label="resources.stores.name" + /> + } + label="resources.events.name" + /> + +); + +const CustomBottomToolbar = () => ( + <> + + + +); + +const SearchMenuItem = () => { + const [, setActiveMenu] = useSolarSidebarActiveMenu(); + const handleClose = () => { + setActiveMenu(''); + }; + + return ( + } + label="Search" + name="search" + subMenu={ + + + + } + data-testid="search-button" + /> + ); +}; + +export const App = () => ( + + {/*...*/} + +); +``` +{% endraw %} \ No newline at end of file diff --git a/docs/navigation.html b/docs/navigation.html index 66476722ab2..c3c92778043 100644 --- a/docs/navigation.html +++ b/docs/navigation.html @@ -247,6 +247,7 @@
  • <Title>
  • <Breadcrumb>
  • <Search>
  • +
  • <SearchWithResult>
  • <Confirm>
  • Buttons
  • <CheckForApplicationUpdate>