Skip to content

Commit

Permalink
feat: make renderContentProp optional
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaevAlexandr committed Jan 17, 2024
1 parent 4acb147 commit 432f036
Show file tree
Hide file tree
Showing 11 changed files with 85 additions and 41 deletions.
35 changes: 28 additions & 7 deletions src/components/TreeSelect/TreeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
type ListItemId,
ListItemView,
getItemRenderState,
isKnownStructureGuard,
scrollToListItem,
useList,
useListKeydown,
Expand All @@ -26,7 +27,10 @@ import './TreeSelect.scss';
const b = block('tree-select');

export const TreeSelect = React.forwardRef(function TreeSelect<T>(
{
props: TreeSelectProps<T>,
ref: React.Ref<HTMLButtonElement>,
) {
const {
id,
slotBeforeListBody,
slotAfterListBody,
Expand All @@ -48,14 +52,12 @@ export const TreeSelect = React.forwardRef(function TreeSelect<T>(
onUpdate,
getId,
onOpenChange,
renderControlContent,
renderControl,
renderItem,
renderContainer: RenderContainer = TreeListContainer,
onItemClick,
}: TreeSelectProps<T>,
ref: React.Ref<HTMLButtonElement>,
) {
} = props;

const [mobile] = useMobile();
const uniqId = useUniqId();
const treeSelectId = id ?? uniqId;
Expand Down Expand Up @@ -201,7 +203,19 @@ export const TreeSelect = React.forwardRef(function TreeSelect<T>(
<SelectControl
{...controlProps}
selectedOptionsContent={React.Children.toArray(
value.map((id) => 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"
Expand Down Expand Up @@ -249,10 +263,17 @@ export const TreeSelect = React.forwardRef(function TreeSelect<T>(
return renderItem(item, state, context, renderContextProps);
}

const itemData = listParsedState.byId[id];

return (
<ListItemView
{...state}
{...renderControlContent(item)}
// eslint-disable-next-line no-nested-ternary
{...('renderControlContent' in props
? props.renderControlContent(itemData)
: isKnownStructureGuard(itemData)
? itemData
: {title: itemData as string})}
{...renderContextProps}
/>
);
Expand Down
3 changes: 1 addition & 2 deletions src/components/TreeSelect/__stories__/TreeSelect.stories.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -26,6 +25,7 @@ import {
WithItemLinksAndActionsExampleProps,
} from './components/WithItemLinksAndActionsExample';

// TODO: пример с кастомной структурой данных
export default {
title: 'Unstable/TreeSelect',
component: TreeSelect,
Expand All @@ -45,7 +45,6 @@ const DefaultTemplate: StoryFn<
<Flex>
<TreeSelect
{...props}
renderControlContent={identity}
items={items}
onUpdate={(...args) =>
console.log('Uncontrolled `TreeSelect onUpdate args: `', ...args)
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -31,11 +29,11 @@ export const InfinityScrollExample = ({itemsCount = 5, ...props}: InfinityScroll

return (
<Flex>
<TreeSelect
<TreeSelect<{title: string}>
{...props}
items={data}
value={value}
popupClassName={spacing({p: 2})}
renderControlContent={identity}
renderItem={(item, state, {isLastItem, groupState}) => {
const node = (
<ListItemView
Expand All @@ -62,7 +60,6 @@ export const InfinityScrollExample = ({itemsCount = 5, ...props}: InfinityScroll
return node;
}}
renderContainer={RenderVirtualizedContainer}
items={data}
onUpdate={setValue}
slotAfterListBody={
isLoading && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';

import {Grip} from '@gravity-ui/icons';
import identity from 'lodash/identity';
import {
DragDropContext,
Draggable,
Expand Down Expand Up @@ -36,13 +35,12 @@ const DraggableListItem = ({
};

export interface WithDndListExampleProps
extends Omit<
TreeSelectProps<{title: string}>,
'value' | 'onUpdate' | 'items' | 'getItemContent'
> {}
extends Omit<TreeSelectProps<string>, '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<string[]>([]);

const handleDrugEnd: OnDragEndResponder = ({destination, source}) => {
Expand All @@ -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]);
Expand Down Expand Up @@ -97,7 +94,7 @@ export const WithDndListExample = (props: WithDndListExampleProps) => {
renderItem={(item, state, _listContext, renderContextProps) => {
const commonProps = {
...state,
...item,
title: item,
endSlot: <Icon data={Grip} size={16} />,
};

Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -86,7 +84,6 @@ export const WithFiltrationAndControlsExample = ({
</Flex>
}
value={value}
renderControlContent={identity}
items={filterState.items}
onUpdate={setValue}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CustomDataStructure>,
'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<string[]>([]);
const [expandedById, setExpanded] = React.useState<Record<ListItemId, boolean>>(
Expand All @@ -35,15 +53,15 @@ export const WithGroupSelectionControlledStateAndCustomIconExample = ({
<TreeSelect
{...props}
size="l"
renderControlContent={identity}
renderControlContent={mapCustomDataStructureToKnownProps}
expandedById={expandedById}
popupClassName={spacing({p: 2})}
value={value}
renderItem={(item, state, {groupState}) => {
return (
<ListItemView
{...state}
{...item}
{...mapCustomDataStructureToKnownProps(item)}
startSlot={
<Icon size={16} data={groupState ? Database : PlugConnection} />
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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]);
Expand Down
17 changes: 14 additions & 3 deletions src/components/TreeSelect/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ export type RenderContainerProps<T> = ListParsedState<T> &
containerRef: React.RefObject<HTMLDivElement>;
};

export interface TreeSelectProps<T> extends QAProps, Partial<Omit<ListState, 'selectedById'>> {
interface TreeSelectBaseProps<T> extends QAProps, Partial<Omit<ListState, 'selectedById'>> {
value?: ListItemId[];
defaultOpen?: boolean;
defaultValue?: ListItemId[];
items: ListItemType<T>[];
// items: ListItemType<T>[];
open?: boolean;
id?: string | undefined;
popupClassName?: string;
Expand Down Expand Up @@ -84,7 +84,6 @@ export interface TreeSelectProps<T> extends QAProps, Partial<Omit<ListState, 'se
* Override list item content by you custom node.
*/
renderItem?: RenderItem<T>;
renderControlContent(item: T): KnownItemStructure;
onClose?(): void;
onUpdate?(value: ListItemId[], selectedItems: T[]): void;
onOpenChange?(open: boolean): void;
Expand All @@ -96,3 +95,15 @@ export interface TreeSelectProps<T> extends QAProps, Partial<Omit<ListState, 'se
| 'disabled'
| ((defaultClickCallback: () => void, content: OverrideItemContext) => void);
}

type TreeSelectKnownProps<T> = TreeSelectBaseProps<T> & {
items: ListItemType<T>[];
};
type TreeSelectUnknownProps<T> = TreeSelectBaseProps<T> & {
items: ListItemType<T>[];
renderControlContent(item: T): KnownItemStructure;
};

export type TreeSelectProps<T> = T extends KnownItemStructure | string
? TreeSelectKnownProps<T>
: TreeSelectUnknownProps<T>;
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -22,7 +22,7 @@ const stories: ListItemViewProps[] = [
subtitle,
disabled: true,
startSlot: (
<UserAvatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
<Avatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
),
},
{
Expand All @@ -38,7 +38,7 @@ const stories: ListItemViewProps[] = [
subtitle,
selected: true,
startSlot: (
<UserAvatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
<Avatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
),
},
{
Expand All @@ -48,7 +48,7 @@ const stories: ListItemViewProps[] = [
size: 'xl',
height: 60,
startSlot: (
<UserAvatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
<Avatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
),
},
{
Expand All @@ -61,7 +61,7 @@ const stories: ListItemViewProps[] = [
size: 'l',
subtitle,
startSlot: (
<UserAvatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
<Avatar imgUrl="https://avatars.mds.yandex.net/get-yapic/69015/enc-137b8b64288fa6fc5ec58c6b83aea00e7723c8fa5638c078312a1134d8ee32ac/islands-retina-50" />
),
indentation: 1,
selected: true,
Expand Down
1 change: 1 addition & 0 deletions src/components/useList/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export * from './utils/getItemRenderState';
export * from './utils/scrollToListItem';
export * from './utils/getListParsedState';
export {modToHeight} from './constants';
export {isKnownStructureGuard} from './utils';
5 changes: 5 additions & 0 deletions src/components/useList/utils.ts
Original file line number Diff line number Diff line change
@@ -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;
};

0 comments on commit 432f036

Please sign in to comment.