From 300d5942f5d5cb6012d939d7b9744ee06a078d3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20van=20der=20Sande?= Date: Fri, 21 Oct 2022 18:37:33 +0200 Subject: [PATCH] [@mantine/core] add controlled search and per-list wording to transfer-list --- docs/src/docs/core/TransferList.mdx | 21 ++++++ .../TransferList/RenderList/RenderList.tsx | 13 ++-- .../src/TransferList/TransferList.tsx | 42 ++++++++++-- .../TransferList.demo.controlledSearch.tsx | 66 +++++++++++++++++++ ...ransferList.demo.differentPlaceholders.tsx | 34 ++++++++++ .../TransferList.demo.placeholder.tsx | 32 +++++++++ .../src/demos/core/TransferList/index.ts | 3 + 7 files changed, 202 insertions(+), 9 deletions(-) create mode 100644 src/mantine-demos/src/demos/core/TransferList/TransferList.demo.controlledSearch.tsx create mode 100644 src/mantine-demos/src/demos/core/TransferList/TransferList.demo.differentPlaceholders.tsx create mode 100644 src/mantine-demos/src/demos/core/TransferList/TransferList.demo.placeholder.tsx diff --git a/docs/src/docs/core/TransferList.mdx b/docs/src/docs/core/TransferList.mdx index 36966d53f69..ddd0758e58a 100644 --- a/docs/src/docs/core/TransferList.mdx +++ b/docs/src/docs/core/TransferList.mdx @@ -44,6 +44,27 @@ Value should be a tuple of two arrays which contain values from data: +## Controlled search + +You can optionally control the search inputs by providing `searchValues` and `onSearch` props. `searchValues` should +be a tuple of two strings, one for each list: + + + +## Empty search VS empty list + +You can specify a `placeholder` prop, which will be used in place of the `nothingFound` when a list is completely empty, +and no query is set. + + + +## Custom wording for each list + +`placeholder`, `nothingFound` and `searchPlaceholder` props can take a tuple of two strings instead of a single string to +customize each list independently. + + + ## Grouping items diff --git a/src/mantine-core/src/TransferList/RenderList/RenderList.tsx b/src/mantine-core/src/TransferList/RenderList/RenderList.tsx index 25a818d4ba8..99e08552bb5 100644 --- a/src/mantine-core/src/TransferList/RenderList/RenderList.tsx +++ b/src/mantine-core/src/TransferList/RenderList/RenderList.tsx @@ -7,7 +7,7 @@ import { UnstyledButton } from '../../UnstyledButton'; import { ActionIcon } from '../../ActionIcon'; import { TextInput } from '../../TextInput'; import { Text } from '../../Text'; -import { Divider } from '../../Divider/Divider'; +import { Divider } from '../../Divider'; import { LastIcon, NextIcon, FirstIcon, PrevIcon } from '../../Pagination/icons'; import { TransferListItem, TransferListItemComponent } from '../types'; import useStyles from './RenderList.styles'; @@ -20,8 +20,11 @@ export interface RenderListProps extends DefaultProps { selection: string[]; itemComponent: TransferListItemComponent; searchPlaceholder: string; + query?: string; + onSearch(value: string): void; filter(query: string, item: TransferListItem): boolean; nothingFound?: React.ReactNode; + placeholder?: React.ReactNode; title?: React.ReactNode; reversed?: boolean; showTransferAll?: boolean; @@ -55,8 +58,11 @@ export function RenderList({ itemComponent: ItemComponent, listComponent, searchPlaceholder, + query, + onSearch, filter, nothingFound, + placeholder, title, showTransferAll, reversed, @@ -75,7 +81,6 @@ export function RenderList({ ); const unGroupedItems: React.ReactElement[] = []; const groupedItems: React.ReactElement[] = []; - const [query, setQuery] = useState(''); const [hovered, setHovered] = useState(-1); const filteredData = data.filter((item) => filter(query, item)).slice(0, limit); const ListComponent = listComponent || 'div'; @@ -195,7 +200,7 @@ export function RenderList({ unstyled={unstyled} value={query} onChange={(event) => { - setQuery(event.currentTarget.value); + onSearch(event.currentTarget.value); setHovered(0); }} onFocus={() => setHovered(0)} @@ -247,7 +252,7 @@ export function RenderList({ ) : ( - {nothingFound} + {!query && placeholder ? placeholder : nothingFound} )} diff --git a/src/mantine-core/src/TransferList/TransferList.tsx b/src/mantine-core/src/TransferList/TransferList.tsx index 091bd3dbf17..19597b3e057 100644 --- a/src/mantine-core/src/TransferList/TransferList.tsx +++ b/src/mantine-core/src/TransferList/TransferList.tsx @@ -1,5 +1,6 @@ import React, { forwardRef } from 'react'; import { DefaultProps, MantineNumberSize, useComponentDefaultProps } from '@mantine/styles'; +import { useUncontrolled } from '@mantine/hooks'; import { RenderList, RenderListStylesNames } from './RenderList/RenderList'; import { SelectScrollArea } from '../Select/SelectScrollArea/SelectScrollArea'; import { DefaultItem } from './DefaultItem/DefaultItem'; @@ -11,7 +12,7 @@ export type TransferListStylesNames = RenderListStylesNames; export interface TransferListProps extends DefaultProps, - Omit, 'value' | 'onChange'> { + Omit, 'value' | 'onChange' | 'placeholder'> { /** Current value */ value: TransferListData; @@ -24,11 +25,20 @@ export interface TransferListProps /** Custom item component */ itemComponent?: TransferListItemComponent; + /** Controlled search queries */ + searchValues?: [string, string]; + + /** Called when one of the search queries changes */ + onSearch?(value: [string, string]): void; + /** Search fields placeholder */ - searchPlaceholder?: string; + searchPlaceholder?: string | [string, string]; /** Nothing found message */ - nothingFound?: React.ReactNode; + nothingFound?: React.ReactNode | [React.ReactNode, React.ReactNode]; + + /** Displayed when a list is empty and there is no search query */ + placeholder?: React.ReactNode | [React.ReactNode, React.ReactNode]; /** Function to filter search results */ filter?(query: string, item: TransferListItem): boolean; @@ -63,6 +73,7 @@ const defaultProps: Partial = { itemComponent: DefaultItem, filter: defaultFilter, titles: [null, null], + placeholder: [null, null], listHeight: 150, listComponent: SelectScrollArea, showTransferAll: true, @@ -75,8 +86,11 @@ export const TransferList = forwardRef((props onChange, itemComponent, searchPlaceholder, + searchValues, + onSearch, filter, nothingFound, + placeholder, titles, initialSelection, listHeight, @@ -92,6 +106,12 @@ export const TransferList = forwardRef((props } = useComponentDefaultProps('TransferList', defaultProps, props); const [selection, handlers] = useSelectionState(initialSelection); + const [search, handleSearch] = useUncontrolled({ + value: searchValues, + defaultValue: ['', ''], + finalValue: ['', ''], + onChange: onSearch, + }); const handleMoveAll = (listIndex: 0 | 1) => { const items: TransferListData = Array(2) as any; @@ -126,9 +146,7 @@ export const TransferList = forwardRef((props const sharedListProps = { itemComponent, listComponent, - searchPlaceholder, filter, - nothingFound, height: listHeight, showTransferAll, classNames, @@ -154,6 +172,13 @@ export const TransferList = forwardRef((props onMoveAll={() => handleMoveAll(0)} onMove={() => handleMove(0)} title={titles[0]} + placeholder={Array.isArray(placeholder) ? placeholder[0] : placeholder} + searchPlaceholder={ + Array.isArray(searchPlaceholder) ? searchPlaceholder[0] : searchPlaceholder + } + nothingFound={Array.isArray(nothingFound) ? nothingFound[0] : nothingFound} + query={search[0]} + onSearch={(query) => handleSearch([query, search[1]])} unstyled={unstyled} /> @@ -165,6 +190,13 @@ export const TransferList = forwardRef((props onMoveAll={() => handleMoveAll(1)} onMove={() => handleMove(1)} title={titles[1]} + placeholder={Array.isArray(placeholder) ? placeholder[1] : placeholder} + searchPlaceholder={ + Array.isArray(searchPlaceholder) ? searchPlaceholder[1] : searchPlaceholder + } + nothingFound={Array.isArray(nothingFound) ? nothingFound[1] : nothingFound} + query={search[1]} + onSearch={(query) => handleSearch([search[0], query])} reversed unstyled={unstyled} /> diff --git a/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.controlledSearch.tsx b/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.controlledSearch.tsx new file mode 100644 index 00000000000..15571bf5f04 --- /dev/null +++ b/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.controlledSearch.tsx @@ -0,0 +1,66 @@ +import React, { useState } from 'react'; +import { MantineDemo } from '@mantine/ds'; +import { Stack, Text } from '@mantine/core'; +import { Wrapper } from './_wrapper'; + +const code = ` +import { useState } from 'react' +import { TransferList, Stack, Text } from '@mantine/core'; + +function Demo() { + const [search, setSearch] = useState(['', '']); + + return ( + + + Left search: + {search[0] || '---'} + {' / '} + Right search: + {search[1] || '---'} + + + + + ); +} +`; + +function Demo() { + const [search, setSearch] = useState<[string, string]>(['', '']); + + return ( + + + + Left search:{' '} + + {search[0] || '---'} + {' / '} + + Right search:{' '} + + {search[1] || '---'} + + + + + ); +} + +export const controlledSearch: MantineDemo = { + type: 'demo', + component: Demo, + code, +}; diff --git a/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.differentPlaceholders.tsx b/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.differentPlaceholders.tsx new file mode 100644 index 00000000000..d5afd7caf5e --- /dev/null +++ b/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.differentPlaceholders.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import { MantineDemo } from '@mantine/ds'; +import { Wrapper } from './_wrapper'; + +const code = ` +function Demo() { + return ( + + ); +} +`; + +function Demo() { + return ( + + ); +} + +export const differentPlaceholders: MantineDemo = { + type: 'demo', + component: Demo, + code, +}; diff --git a/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.placeholder.tsx b/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.placeholder.tsx new file mode 100644 index 00000000000..9b45924fd32 --- /dev/null +++ b/src/mantine-demos/src/demos/core/TransferList/TransferList.demo.placeholder.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { MantineDemo } from '@mantine/ds'; +import { Wrapper } from './_wrapper'; + +const code = ` +function Demo() { + return ( + + ); +} +`; + +function Demo() { + return ( + + ); +} + +export const placeholder: MantineDemo = { + type: 'demo', + component: Demo, + code, +}; diff --git a/src/mantine-demos/src/demos/core/TransferList/index.ts b/src/mantine-demos/src/demos/core/TransferList/index.ts index 21921971fd8..27c5530a3a8 100644 --- a/src/mantine-demos/src/demos/core/TransferList/index.ts +++ b/src/mantine-demos/src/demos/core/TransferList/index.ts @@ -3,3 +3,6 @@ export { scrollbars } from './TransferList.demo.scrollbars'; export { itemComponent } from './TransferList.demo.itemComponent'; export { initialSelection } from './TransferList.demo.initialSelection'; export { group } from './TransferList.demo.group'; +export { placeholder } from './TransferList.demo.placeholder'; +export { controlledSearch } from './TransferList.demo.controlledSearch'; +export { differentPlaceholders } from './TransferList.demo.differentPlaceholders';