From 432f036768c07f54a4cd6a94706556a776861b88 Mon Sep 17 00:00:00 2001 From: Alexandr Isaev Date: Fri, 29 Dec 2023 15:21:16 +0300 Subject: [PATCH] feat: make renderContentProp optional --- src/components/TreeSelect/TreeSelect.tsx | 35 +++++++++++++++---- .../__stories__/TreeSelect.stories.tsx | 3 +- .../components/InfinityScrollExample.tsx | 7 ++-- .../components/WithDndListExample.tsx | 13 +++---- .../WithFiltrationAndControlsExample.tsx | 3 -- ...pSelectionControlledStateAndCustomIcon.tsx | 30 ++++++++++++---- .../WithItemLinksAndActionsExample.tsx | 2 -- src/components/TreeSelect/types.ts | 17 +++++++-- .../__stories__/ListItemView.stories.tsx | 10 +++--- src/components/useList/index.ts | 1 + src/components/useList/utils.ts | 5 +++ 11 files changed, 85 insertions(+), 41 deletions(-) create mode 100644 src/components/useList/utils.ts diff --git a/src/components/TreeSelect/TreeSelect.tsx b/src/components/TreeSelect/TreeSelect.tsx index 96995a9170..aa7c34bef8 100644 --- a/src/components/TreeSelect/TreeSelect.tsx +++ b/src/components/TreeSelect/TreeSelect.tsx @@ -10,6 +10,7 @@ import { type ListItemId, ListItemView, getItemRenderState, + isKnownStructureGuard, scrollToListItem, useList, useListKeydown, @@ -26,7 +27,10 @@ import './TreeSelect.scss'; const b = block('tree-select'); export const TreeSelect = React.forwardRef(function TreeSelect( - { + props: TreeSelectProps, + ref: React.Ref, +) { + const { id, slotBeforeListBody, slotAfterListBody, @@ -48,14 +52,12 @@ export const TreeSelect = React.forwardRef(function TreeSelect( onUpdate, getId, onOpenChange, - renderControlContent, renderControl, renderItem, renderContainer: RenderContainer = TreeListContainer, onItemClick, - }: TreeSelectProps, - ref: React.Ref, -) { + } = props; + const [mobile] = useMobile(); const uniqId = useUniqId(); const treeSelectId = id ?? uniqId; @@ -201,7 +203,19 @@ export const TreeSelect = React.forwardRef(function TreeSelect( renderControlContent(listParsedState.byId[id]).title), + value.map((id) => { + if ('renderControlContent' in props) { + return props.renderControlContent(listParsedState.byId[id]).title; + } + + const items = listParsedState.byId[id]; + + if (isKnownStructureGuard(items)) { + return items.title; + } + + return items as string; + }), ).join(', ')} view="normal" pin="round-round" @@ -249,10 +263,17 @@ export const TreeSelect = React.forwardRef(function TreeSelect( return renderItem(item, state, context, renderContextProps); } + const itemData = listParsedState.byId[id]; + return ( ); diff --git a/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx b/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx index 4675070d55..8a48680eb6 100644 --- a/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx +++ b/src/components/TreeSelect/__stories__/TreeSelect.stories.tsx @@ -1,7 +1,6 @@ import React from 'react'; import type {Meta, StoryFn} from '@storybook/react'; -import identity from 'lodash/identity'; import {Flex} from '../../layout'; import {createRandomizedData} from '../../useList/__stories__/utils/makeData'; @@ -26,6 +25,7 @@ import { WithItemLinksAndActionsExampleProps, } from './components/WithItemLinksAndActionsExample'; +// TODO: пример с кастомной структурой данных export default { title: 'Unstable/TreeSelect', component: TreeSelect, @@ -45,7 +45,6 @@ const DefaultTemplate: StoryFn< console.log('Uncontrolled `TreeSelect onUpdate args: `', ...args) diff --git a/src/components/TreeSelect/__stories__/components/InfinityScrollExample.tsx b/src/components/TreeSelect/__stories__/components/InfinityScrollExample.tsx index 4d2b420c10..0e6a6b3a9e 100644 --- a/src/components/TreeSelect/__stories__/components/InfinityScrollExample.tsx +++ b/src/components/TreeSelect/__stories__/components/InfinityScrollExample.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import identity from 'lodash/identity'; - import {Label} from '../../../Label'; import {Loader} from '../../../Loader'; import {Flex, spacing} from '../../../layout'; @@ -31,11 +29,11 @@ export const InfinityScrollExample = ({itemsCount = 5, ...props}: InfinityScroll return ( - {...props} + items={data} value={value} popupClassName={spacing({p: 2})} - renderControlContent={identity} renderItem={(item, state, {isLastItem, groupState}) => { const node = ( , - 'value' | 'onUpdate' | 'items' | 'getItemContent' - > {} + extends Omit, 'value' | 'onUpdate' | 'items' | 'getItemContent'> {} export const WithDndListExample = (props: WithDndListExampleProps) => { - const [items, setItems] = React.useState(() => createRandomizedData({num: 10, depth: 0})); + const [items, setItems] = React.useState(() => + createRandomizedData({num: 10, depth: 0, getData: (title) => title}), + ); const [value, setValue] = React.useState([]); const handleDrugEnd: OnDragEndResponder = ({destination, source}) => { @@ -57,7 +55,6 @@ export const WithDndListExample = (props: WithDndListExampleProps) => { {...props} value={value} items={items} - renderControlContent={identity} onItemClick={(_, {id, isGroup, disabled}) => { if (!isGroup && !disabled) { setValue([id]); @@ -97,7 +94,7 @@ export const WithDndListExample = (props: WithDndListExampleProps) => { renderItem={(item, state, _listContext, renderContextProps) => { const commonProps = { ...state, - ...item, + title: item, endSlot: , }; diff --git a/src/components/TreeSelect/__stories__/components/WithFiltrationAndControlsExample.tsx b/src/components/TreeSelect/__stories__/components/WithFiltrationAndControlsExample.tsx index 93cc4e3613..0cd53c850f 100644 --- a/src/components/TreeSelect/__stories__/components/WithFiltrationAndControlsExample.tsx +++ b/src/components/TreeSelect/__stories__/components/WithFiltrationAndControlsExample.tsx @@ -1,7 +1,5 @@ import React from 'react'; -import identity from 'lodash/identity'; - import {Button} from '../../../Button'; import {Text} from '../../../Text'; import {TextInput} from '../../../controls'; @@ -86,7 +84,6 @@ export const WithFiltrationAndControlsExample = ({ } value={value} - renderControlContent={identity} items={filterState.items} onUpdate={setValue} /> diff --git a/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx b/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx index 083d1c43ac..b6c1ba14ce 100644 --- a/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx +++ b/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx @@ -1,29 +1,47 @@ import React from 'react'; import {ChevronDown, ChevronUp, Database, PlugConnection} from '@gravity-ui/icons'; -import identity from 'lodash/identity'; import {Button} from '../../../Button'; import {Icon} from '../../../Icon'; import {Flex, spacing} from '../../../layout'; -import {ListItemId, ListItemView, getListParsedState} from '../../../useList'; +import { + type KnownItemStructure, + ListItemId, + ListItemView, + getListParsedState, +} from '../../../useList'; import {createRandomizedData} from '../../../useList/__stories__/utils/makeData'; import {TreeSelect} from '../../TreeSelect'; import type {TreeSelectProps} from '../../types'; +/** + * Just for example how to work with data + */ +interface CustomDataStructure { + a: string; +} + export interface WithGroupSelectionControlledStateAndCustomIconExampleProps extends Omit< - TreeSelectProps<{title: string}>, + TreeSelectProps, 'value' | 'onUpdate' | 'items' | 'getItemContent' | 'size' > { itemsCount?: number; } +const mapCustomDataStructureToKnownProps = (props: CustomDataStructure): KnownItemStructure => ({ + title: props.a, +}); + export const WithGroupSelectionControlledStateAndCustomIconExample = ({ itemsCount = 5, ...props }: WithGroupSelectionControlledStateAndCustomIconExampleProps) => { - const items = React.useMemo(() => createRandomizedData({num: itemsCount}), [itemsCount]); + const items = React.useMemo( + () => createRandomizedData({num: itemsCount, getData: (a) => ({a})}), + [itemsCount], + ); const [value, setValue] = React.useState([]); const [expandedById, setExpanded] = React.useState>( @@ -35,7 +53,7 @@ export const WithGroupSelectionControlledStateAndCustomIconExample = ({ } diff --git a/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx b/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx index 884b5e42fb..a96bf73628 100644 --- a/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx +++ b/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx @@ -1,7 +1,6 @@ import React from 'react'; import {ChevronDown, ChevronUp, FolderOpen} from '@gravity-ui/icons'; -import identity from 'lodash/identity'; import {Button} from '../../../Button'; import {DropdownMenu} from '../../../DropdownMenu'; @@ -33,7 +32,6 @@ export const WithItemLinksAndActionsExample = (props: WithItemLinksAndActionsExa size="l" value={value} items={items} - renderControlContent={identity} onItemClick={(_, {id, isGroup, disabled}) => { if (!isGroup && !disabled) { setValue([id]); diff --git a/src/components/TreeSelect/types.ts b/src/components/TreeSelect/types.ts index 09cb3dc185..000a4bca69 100644 --- a/src/components/TreeSelect/types.ts +++ b/src/components/TreeSelect/types.ts @@ -41,11 +41,11 @@ export type RenderContainerProps = ListParsedState & containerRef: React.RefObject; }; -export interface TreeSelectProps extends QAProps, Partial> { +interface TreeSelectBaseProps extends QAProps, Partial> { value?: ListItemId[]; defaultOpen?: boolean; defaultValue?: ListItemId[]; - items: ListItemType[]; + // items: ListItemType[]; open?: boolean; id?: string | undefined; popupClassName?: string; @@ -84,7 +84,6 @@ export interface TreeSelectProps extends QAProps, Partial; - renderControlContent(item: T): KnownItemStructure; onClose?(): void; onUpdate?(value: ListItemId[], selectedItems: T[]): void; onOpenChange?(open: boolean): void; @@ -96,3 +95,15 @@ export interface TreeSelectProps extends QAProps, Partial void, content: OverrideItemContext) => void); } + +type TreeSelectKnownProps = TreeSelectBaseProps & { + items: ListItemType[]; +}; +type TreeSelectUnknownProps = TreeSelectBaseProps & { + items: ListItemType[]; + renderControlContent(item: T): KnownItemStructure; +}; + +export type TreeSelectProps = T extends KnownItemStructure | string + ? TreeSelectKnownProps + : TreeSelectUnknownProps; diff --git a/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx b/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx index c7474a351b..888a7a0e7a 100644 --- a/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx +++ b/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx @@ -2,7 +2,7 @@ import React from 'react'; import type {Meta, StoryFn} from '@storybook/react'; -import {UserAvatar} from '../../../../UserAvatar'; +import {Avatar} from '../../../../Avatar'; import {Flex} from '../../../../layout'; import {ListItemView as ListItemViewComponent, ListItemViewProps} from '../ListItemView'; @@ -22,7 +22,7 @@ const stories: ListItemViewProps[] = [ subtitle, disabled: true, startSlot: ( - + ), }, { @@ -38,7 +38,7 @@ const stories: ListItemViewProps[] = [ subtitle, selected: true, startSlot: ( - + ), }, { @@ -48,7 +48,7 @@ const stories: ListItemViewProps[] = [ size: 'xl', height: 60, startSlot: ( - + ), }, { @@ -61,7 +61,7 @@ const stories: ListItemViewProps[] = [ size: 'l', subtitle, startSlot: ( - + ), indentation: 1, selected: true, diff --git a/src/components/useList/index.ts b/src/components/useList/index.ts index a2e315811d..0321954b30 100644 --- a/src/components/useList/index.ts +++ b/src/components/useList/index.ts @@ -11,3 +11,4 @@ export * from './utils/getItemRenderState'; export * from './utils/scrollToListItem'; export * from './utils/getListParsedState'; export {modToHeight} from './constants'; +export {isKnownStructureGuard} from './utils'; diff --git a/src/components/useList/utils.ts b/src/components/useList/utils.ts new file mode 100644 index 0000000000..0bd5e91c28 --- /dev/null +++ b/src/components/useList/utils.ts @@ -0,0 +1,5 @@ +import type {KnownItemStructure} from './types'; + +export const isKnownStructureGuard = (item: unknown): item is KnownItemStructure => { + return item !== null && typeof item === 'object' && 'title' in item; +};