Skip to content

Commit

Permalink
fix(TreeList): review fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaevAlexandr committed Mar 16, 2024
1 parent 0fae2dc commit a04fe77
Show file tree
Hide file tree
Showing 21 changed files with 205 additions and 245 deletions.
5 changes: 4 additions & 1 deletion src/components/Table/__stories__/Table.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,10 @@ const WithTableActionsTemplate: StoryFn<TableProps<DataItem>> = (args) => {
}

const items = ['action 1', 'action 2', 'action 3'];
return <TreeSelect items={items} size="s" />;

return (
<TreeSelect items={items} size="s" getItemContent={(title) => ({title})} />
);
}}
/>
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const tableColumnSetupCn = b(null);
const controlsCn = b('controls');
const requiredDndItemCn = b('required-item');

function identity<T>(value: T): T {
return value;
}

const reorderArray = <T extends unknown>(list: T[], startIndex: number, endIndex: number): T[] => {
const result = [...list];
const [removed] = result.splice(startIndex, 1);
Expand Down Expand Up @@ -277,6 +281,7 @@ export const TableColumnSetup = (props: TableColumnSetupProps) => {
return (
<TreeSelect
className={tableColumnSetupCn}
getItemContent={identity}
multiple
size="l"
open={open}
Expand Down
6 changes: 0 additions & 6 deletions src/components/TreeList/TreeList.scss

This file was deleted.

63 changes: 25 additions & 38 deletions src/components/TreeList/TreeList.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,33 @@
import React from 'react';

import {useUniqId} from '../../hooks';
import {
ListItemView,
getItemRenderState,
isKnownStructureGuard,
useList,
useListKeydown,
} from '../useList';
import {ListItemView, getItemRenderState, useList, useListKeydown} from '../useList';
import type {ListItemId} from '../useList';
import {block} from '../utils/cn';

import {TreeListContainer} from './components/TreeListContainer/TreeListContainer';
import type {TreeListProps, TreeListRenderContainerProps} from './types';

import './TreeList.scss';

const b = block('tree-list');

export const TreeList = <T,>(props: TreeListProps<T>) => {
const {
id,
size = 'm',
items,
className,
expandedById,
disabledById,
activeItemId,
selectedById,
getId,
renderItem: propsRenderItem,
renderContainer = TreeListContainer,
onItemClick,
multiple,
setActiveItemId,
containerRef: propsContainerRef,
} = props; // do not use props spread. Pass props explicitly

export const TreeList = <T,>({
id,
size = 'm',
items,
className,
expandedById,
disabledById,
activeItemId,
selectedById,
getId,
renderItem: propsRenderItem,
renderContainer = TreeListContainer,
onItemClick,
multiple,
setActiveItemId,
containerRef: propsContainerRef,
getItemContent,
}: TreeListProps<T>) => {
const uniqId = useUniqId();
const treeListId = id ?? uniqId;
const containerRefLocal = React.useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -95,10 +86,13 @@ export const TreeList = <T,>(props: TreeListProps<T>) => {
size,
onItemClick: handleItemClick,
...listParsedState,
...{expandedById, disabledById, activeItemId, selectedById},
expandedById,
disabledById,
activeItemId,
selectedById,
});

// assign components scope logic
// redefining the view logic for groups and multiple selection of list items
renderState.props.hasSelectionIcon = Boolean(multiple) && !renderState.context.groupState;

if (propsRenderItem) {
Expand All @@ -111,17 +105,10 @@ export const TreeList = <T,>(props: TreeListProps<T>) => {
});
}

const itemData = listParsedState.itemsById[itemId];

return (
<ListItemView
{...renderState.props}
// eslint-disable-next-line no-nested-ternary
{...('getItemContent' in props
? props.getItemContent(itemData)
: isKnownStructureGuard(itemData)
? itemData
: {title: itemData as string})}
{...getItemContent(listParsedState.itemsById[itemId])}
{...renderContextProps}
/>
);
Expand Down
100 changes: 35 additions & 65 deletions src/components/TreeList/__stories__/TreeList.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,88 +1,58 @@
import React from 'react';
import type {Meta, StoryObj} from '@storybook/react';

import type {Meta, StoryFn} from '@storybook/react';

import {Flex} from '../../layout';
import {createRandomizedData} from '../../useList/__stories__/utils/makeData';
import {TreeList} from '../TreeList';
import type {TreeListProps} from '../types';

import {InfinityScrollExample} from './components/InfinityScrollExample';
import type {InfinityScrollExampleProps} from './components/InfinityScrollExample';
import {WithDndListExample} from './components/WithDndListExample';
import type {WithDndListExampleProps} from './components/WithDndListExample';
import {WithFiltrationAndControlsExample} from './components/WithFiltrationAndControlsExample';
import type {WithFiltrationAndControlsExampleProps} from './components/WithFiltrationAndControlsExample';
import {WithGroupSelectionControlledStateAndCustomIconExample} from './components/WithGroupSelectionControlledStateAndCustomIcon';
import type {WithGroupSelectionControlledStateAndCustomIconExampleProps} from './components/WithGroupSelectionControlledStateAndCustomIcon';
import {WithItemLinksAndActionsExample} from './components/WithItemLinksAndActionsExample';
import type {WithItemLinksAndActionsExampleProps} from './components/WithItemLinksAndActionsExample';
import {DefaultStory} from './stories/DefaultStory';
import {InfinityScrollStory} from './stories/InfinityScrollStory';
import {WithDndListStory} from './stories/WithDndListStory';
import {WithFiltrationAndControlsStory} from './stories/WithFiltrationAndControlsStory';
import {WithGroupSelectionAndCustomIconStory} from './stories/WithGroupSelectionAndCustomIconStory';
import {WithItemLinksAndActionsStory} from './stories/WithItemLinksAndActionsStory';

export default {
title: 'Unstable/TreeList',
component: TreeList,
} as Meta;

const DefaultTemplate: StoryFn<
Omit<TreeListProps<{title: string}>, 'value' | 'onUpdate' | 'items' | 'getItemContent'> & {
itemsCount?: number;
}
> = ({itemsCount = 5, ...props}) => {
const items = React.useMemo(() => createRandomizedData({num: itemsCount}), [itemsCount]);
type DefaultStoryObj = StoryObj<typeof DefaultStory>;

return (
<Flex width="500">
<TreeList {...props} items={items} />
</Flex>
);
};
export const Default = DefaultTemplate.bind({});
Default.args = {
size: 'm',
export const Default: DefaultStoryObj = {
render: DefaultStory,
};

const WithGroupSelectionControlledStateAndCustomIconTemplate: StoryFn<
WithGroupSelectionControlledStateAndCustomIconExampleProps
> = (props) => {
return <WithGroupSelectionControlledStateAndCustomIconExample {...props} />;
type InfinityScrollStoryObj = StoryObj<typeof InfinityScrollStory>;

export const InfinityScroll: InfinityScrollStoryObj = {
render: InfinityScrollStory,
};

export const WithGroupSelectionControlledStateAndCustomIcon =
WithGroupSelectionControlledStateAndCustomIconTemplate.bind({});
WithGroupSelectionControlledStateAndCustomIcon.args = {};
type WithDndListStoryObj = StoryObj<typeof WithDndListStory>;

const InfinityScrollTemplate: StoryFn<InfinityScrollExampleProps> = (props) => {
return <InfinityScrollExample {...props} />;
export const WithDndList: WithDndListStoryObj = {
parameters: {
// Strict mode ruins sortable list due to this react-beautiful-dnd issue
// https://github.com/atlassian/react-beautiful-dnd/issues/2350
disableStrictMode: true,
},
render: WithDndListStory,
};
export const InfinityScroll = InfinityScrollTemplate.bind({});
InfinityScroll.args = {};

const WithFiltrationAndControlsTemplate: StoryFn<WithFiltrationAndControlsExampleProps> = (
props,
) => {
return <WithFiltrationAndControlsExample {...props} />;
};
export const WithFiltrationAndControls = WithFiltrationAndControlsTemplate.bind({});
WithFiltrationAndControls.args = {
size: 'l',
};
type WithFiltrationAndControlsStoryObj = StoryObj<typeof WithFiltrationAndControlsStory>;

const WithItemLinksAndActionsTemplate: StoryFn<WithItemLinksAndActionsExampleProps> = (props) => {
return <WithItemLinksAndActionsExample {...props} />;
export const WithFiltrationAndControls: WithFiltrationAndControlsStoryObj = {
render: WithFiltrationAndControlsStory,
};
export const WithItemLinksAndActions = WithItemLinksAndActionsTemplate.bind({});
WithItemLinksAndActions.args = {};

const WithDndListTemplate: StoryFn<WithDndListExampleProps> = (props) => {
return <WithDndListExample {...props} />;
};
export const WithDndList = WithDndListTemplate.bind({});
type WithGroupSelectionAndCustomIconStoryObj = StoryObj<
typeof WithGroupSelectionAndCustomIconStory
>;

WithDndList.args = {
size: 'l',
export const WithGroupSelectionAndCustomIcon: WithGroupSelectionAndCustomIconStoryObj = {
render: WithGroupSelectionAndCustomIconStory,
};
WithDndList.parameters = {
// Strict mode ruins sortable list due to this react-beautiful-dnd issue
// https://github.com/atlassian/react-beautiful-dnd/issues/2350
disableStrictMode: true,

type WithItemLinksAndActionsStoryObj = StoryObj<typeof WithItemLinksAndActionsStory>;

export const WithItemLinksAndActions: WithItemLinksAndActionsStoryObj = {
render: WithItemLinksAndActionsStory,
};
25 changes: 25 additions & 0 deletions src/components/TreeList/__stories__/stories/DefaultStory.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

import {Flex} from '../../../layout';
import {createRandomizedData} from '../../../useList/__stories__/utils/makeData';
import {TreeList} from '../../TreeList';
import type {TreeListProps} from '../../types';

function identity<T>(value: T): T {
return value;
}

export interface DefaultStoryProps
extends Omit<TreeListProps<{title: string}>, 'items' | 'getItemContent'> {
itemsCount?: number;
}

export const DefaultStory = ({itemsCount = 5, ...props}: DefaultStoryProps) => {
const items = React.useMemo(() => createRandomizedData({num: itemsCount}), [itemsCount]);

return (
<Flex width="500">
<TreeList {...props} items={items} getItemContent={identity} />
</Flex>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,21 @@ import {IntersectionContainer} from '../../../useList/__stories__/components/Int
import {useInfinityFetch} from '../../../useList/__stories__/utils/useInfinityFetch';
import {TreeList} from '../../TreeList';
import type {TreeListOnItemClick, TreeListProps} from '../../types';
import {RenderVirtualizedContainer} from '../components/RenderVirtualizedContainer';

import {RenderVirtualizedContainer} from './RenderVirtualizedContainer';
export interface InfinityScrollExampleProps
function identity<T>(value: T): T {
return value;
}

export interface InfinityScrollStoryProps
extends Omit<
TreeListProps<{title: string}>,
'value' | 'onUpdate' | 'items' | 'getItemContent' | 'multiple' | 'size'
'value' | 'onUpdate' | 'items' | 'multiple' | 'size' | 'getItemContent'
> {
itemsCount?: number;
}

export const InfinityScrollExample = ({
itemsCount = 5,
...storyProps
}: InfinityScrollExampleProps) => {
export const InfinityScrollStory = ({itemsCount = 5, ...storyProps}: InfinityScrollStoryProps) => {
const listState = useListState();

const handleItemClick: TreeListOnItemClick<{title: string}> = ({id, isGroup, disabled}) => {
Expand Down Expand Up @@ -55,6 +56,7 @@ export const InfinityScrollExample = ({
size="l"
{...storyProps}
{...listState}
getItemContent={identity}
items={items}
multiple
onItemClick={handleItemClick}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ const DraggableListItem = ({

type CustomDataType = {someRandomKey: string; id: string};

export interface WithDndListExampleProps
extends Omit<TreeListProps<CustomDataType>, 'items' | 'getItemContent' | 'getItemContent'> {}

const randomItems: CustomDataType[] = createRandomizedData({
num: 10,
depth: 0,
getData: (title) => title,
}).map(({data}, idx) => ({someRandomKey: data, id: String(idx)}));

export const WithDndListExample = (storyProps: WithDndListExampleProps) => {
export interface WithDndListStoryProps
extends Omit<TreeListProps<CustomDataType>, 'items' | 'getItemContent' | 'getItemContent'> {}

export const WithDndListStory = (storyProps: WithDndListStoryProps) => {
const [items, setItems] = React.useState(randomItems);
const listState = useListState();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@ import {useListFilter, useListState} from '../../../useList';
import {createRandomizedData} from '../../../useList/__stories__/utils/makeData';
import {TreeList} from '../../TreeList';
import type {TreeListProps, TreeListRenderContainerProps} from '../../types';
import {RenderVirtualizedContainer} from '../components/RenderVirtualizedContainer';

import {RenderVirtualizedContainer} from './RenderVirtualizedContainer';

export interface WithFiltrationAndControlsExampleProps
extends Omit<TreeListProps<{title: string}>, 'value' | 'onUpdate' | 'items'> {
export interface WithFiltrationAndControlsStoryProps
extends Omit<
TreeListProps<{title: string}>,
'value' | 'onUpdate' | 'items' | 'getItemContent'
> {
itemsCount?: number;
}

export const WithFiltrationAndControlsExample = ({
export const WithFiltrationAndControlsStory = ({
itemsCount = 5,
...treeSelectProps
}: WithFiltrationAndControlsExampleProps) => {
}: WithFiltrationAndControlsStoryProps) => {
const {items, renderContainer} = React.useMemo(() => {
const baseItems = createRandomizedData({num: itemsCount});
const containerRenderer = (props: TreeListRenderContainerProps<{title: string}>) => {
Expand Down Expand Up @@ -60,16 +62,10 @@ export const WithFiltrationAndControlsExample = ({
if (disabled) return;

if (isGroup) {
listState.setExpanded((prevState) =>
treeSelectProps.multiple
? {
...prevState,
[id]: id in prevState ? !prevState[id] : false,
}
: {
[id]: id in prevState ? !prevState[id] : false,
},
);
listState.setExpanded((prevState) => ({
...prevState,
[id]: id in prevState ? !prevState[id] : false,
}));
} else {
listState.setSelected((prevState) =>
treeSelectProps.multiple
Expand All @@ -85,6 +81,7 @@ export const WithFiltrationAndControlsExample = ({

listState.setActiveItemId(id);
}}
getItemContent={(x) => x}
renderContainer={renderContainer}
items={filterState.items}
/>
Expand Down
Loading

0 comments on commit a04fe77

Please sign in to comment.