Skip to content

Commit

Permalink
feat(TreeSelect): added TreeSelect unstable component and new list hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
IsaevAlexandr committed Nov 28, 2023
1 parent 4b2b5cb commit dbbe45a
Show file tree
Hide file tree
Showing 55 changed files with 2,871 additions and 8 deletions.
8 changes: 8 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@
"require": "./build/cjs/components/utils/addComponentKeysets.js",
"import": "./build/esm/components/utils/addComponentKeysets.js"
},
"./unstable": {
"types": "./build/esm/unstable.d.ts",
"require": "./build/cjs/unstable.js",
"import": "./build/esm/unstable.js"
},
"./styles/*": "./styles/*"
},
"main": "./build/cjs/index.js",
Expand All @@ -46,6 +51,9 @@
],
"i18n": [
"./build/esm/components/utils/addComponentKeysets.d.ts"
],
"unstable": [
"./build/esm/unstable.d.ts"
]
}
},
Expand Down
19 changes: 19 additions & 0 deletions src/components/ListNext/ListRadiuses.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/* stylelint-disable declaration-no-important */
@use '../variables';

$block: '.#{variables.$ns}list-radiuses';

#{$block} {
&_s {
border-radius: 5px !important;
}
&_m {
border-radius: 6px !important;
}
&_l {
border-radius: 8px !important;
}
&_xl {
border-radius: 10px !important;
}
}
27 changes: 27 additions & 0 deletions src/components/ListNext/__stories__/FlattenRenderer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

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

import {Flex} from '../../layout';

import {FlattenList, FlattenListProps} from './components/FlattenList';

export default {
title: 'Unstable/useList/FlattenRenderer(Virtualized)',
component: FlattenList,
} as Meta;

const DefaultTemplate: StoryFn<FlattenListProps> = (props) => {
return (
<Flex width={400} style={{height: 500}}>
<FlattenList {...props} />
</Flex>
);
};

export const Examples = DefaultTemplate.bind({});

Examples.args = {
size: 's',
itemsCount: 1000,
};
18 changes: 18 additions & 0 deletions src/components/ListNext/__stories__/ListInfinityScroll.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';

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

import {InfinityScrollList, InfinityScrollListProps} from './components/InfinityScrollList';

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

const ListInfinityScroll: StoryFn<InfinityScrollListProps> = (props) => {
return <InfinityScrollList {...props} />;
};
export const Examples = ListInfinityScroll.bind({});
Examples.args = {
size: 'm',
};
25 changes: 25 additions & 0 deletions src/components/ListNext/__stories__/PopupWithToggler.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';

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

import {Flex} from '../../layout';

import {PopupWithTogglerList, PopupWithTogglerListProps} from './components/PopupWithTogglerList';

export default {
title: 'Unstable/useList/PopupWithToggler',
component: PopupWithTogglerList,
} as Meta;

const PopupWithTogglerScroll: StoryFn<PopupWithTogglerListProps> = (props) => {
return (
<Flex>
<PopupWithTogglerList {...props} />
</Flex>
);
};
export const Examples = PopupWithTogglerScroll.bind({});
Examples.args = {
itemsCount: 10,
size: 'm',
};
27 changes: 27 additions & 0 deletions src/components/ListNext/__stories__/RecursiveRenderer.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

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

import {Flex} from '../../layout';

import {RecursiveList, RecursiveListProps} from './components/RecursiveList';

export default {
title: 'Unstable/useList/RecursiveRenderer',
component: RecursiveList,
} as Meta;

const DefaultTemplate: StoryFn<RecursiveListProps> = (props) => {
return (
<Flex width={400}>
<RecursiveList {...props} />
</Flex>
);
};

export const Examples = DefaultTemplate.bind({});

Examples.args = {
size: 's',
itemsCount: 10,
};
103 changes: 103 additions & 0 deletions src/components/ListNext/__stories__/components/FlattenList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React from 'react';

import get from 'lodash/get';
import identity from 'lodash/identity';

import {TextInput} from '../../../controls';
import {Flex} from '../../../layout';
import {ItemRenderer} from '../../components/ItemRenderer/ItemRenderer';
import {defaultItemRendererBuilder} from '../../components/ItemRenderer/defaultItemRendererBuilder';
import {ListContainerView} from '../../components/ListContainerView/ListContainerView';
import {VirtualizedListContainer} from '../../components/VirtualizedListContainer/VirtualizedListContainer';
import {useList} from '../../hooks/useList';
import {useListFilter} from '../../hooks/useListFilter';
import {useListKeydown} from '../../hooks/useListKeydown';
import type {ListItemId, ListSizeTypes} from '../../types';
import {computeItemSize} from '../../utils/computeItemSize';
import {createRandomizedData} from '../utils/makeData';

export interface FlattenListProps {
itemsCount: number;
size: ListSizeTypes;
}

export const FlattenList = ({itemsCount, size}: FlattenListProps) => {
const containerRef = React.useRef(null);
const items = React.useMemo(
() => createRandomizedData<{title: string}>(itemsCount),
[itemsCount],
);

const filterState = useListFilter({items});

const [listParsedState, listState] = useList({
items,
});

const onItemClick = React.useCallback(
(id: ListItemId) => {
if (id in listParsedState.groupsState) {
listState.setExpanded((state) => ({
...state,
[id]: id in state ? !state[id] : false,
}));
} else {
listState.setSelected((state) => ({
// can select only one item
[id]: !state[id],
}));
}

listState.setActiveItemId(id);
},
[listParsedState, listState],
);

useListKeydown({
containerRef,
onItemClick,
...listParsedState,
...listState,
});

return (
<Flex direction="column" gap="5" grow>
<TextInput
autoComplete="off"
value={filterState.filter}
onUpdate={filterState.onChange}
ref={filterState.filterRef}
/>

<ListContainerView ref={containerRef}>
<VirtualizedListContainer
items={listParsedState.flattenIdsOrder}
itemSize={(index) =>
computeItemSize(
size,
Boolean(
get(
listParsedState.byId[listParsedState.flattenIdsOrder[index]],
'subtitle',
),
),
)
}
>
{(id) => (
<ItemRenderer
size={size}
{...listParsedState}
{...listState}
id={id}
onItemClick={onItemClick}
renderItem={defaultItemRendererBuilder({
getItemContent: identity,
})}
/>
)}
</VirtualizedListContainer>
</ListContainerView>
</Flex>
);
};
145 changes: 145 additions & 0 deletions src/components/ListNext/__stories__/components/InfinityScrollList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import React from 'react';

import identity from 'lodash/identity';

import {Button} from '../../../Button';
import {Loader} from '../../../Loader';
import {TextInput} from '../../../controls';
import {Flex} from '../../../layout';
import {IntersectionContainer} from '../../components/IntersectionContainer/IntersectionContainer';
import {ItemRenderer} from '../../components/ItemRenderer/ItemRenderer';
import {defaultItemRendererBuilder} from '../../components/ItemRenderer/defaultItemRendererBuilder';
import {ListContainerView} from '../../components/ListContainerView/ListContainerView';
import {ListItemRecursiveRenderer} from '../../components/ListRecursiveRenderer/ListRecursiveRenderer';
import {useList} from '../../hooks/useList';
import {useListFilter} from '../../hooks/useListFilter';
import {useListKeydown} from '../../hooks/useListKeydown';
import type {ListItemId, ListSizeTypes} from '../../types';
import {useInfinityFetch} from '../utils/useInfinityFetch';

export interface InfinityScrollListProps {
size: ListSizeTypes;
}

export const InfinityScrollList = ({size}: InfinityScrollListProps) => {
const containerRef = React.useRef(null);
const {data, onFetchMore, canFetchMore, isLoading} = useInfinityFetch<{title: string}>();
const filterState = useListFilter({items: data});

const [listParsedState, listState] = useList({
items: filterState.items,
});

const onItemClick = (id: ListItemId) => {
if (id in listParsedState.groupsState) {
listState.setExpanded((state) => ({
...state,
[id]: id in state ? !state[id] : false,
}));
} else {
listState.setSelected((state) => ({...state, [id]: !state[id]}));
}

listState.setActiveItemId(id);
};

useListKeydown({
containerRef,
onItemClick,
...listParsedState,
...listState,
});

const handleReset = () => {
filterState.reset();
listState.setExpanded({});
listState.setSelected({});
listState.setActiveItemId(undefined);
};

const handleAccept = () => {
alert(
JSON.stringify(
Object.keys(listState.selected).map((id) => listParsedState.byId[id]),
null,
2,
),
);
};

return (
<React.StrictMode>
<Flex direction="column" gap="3" style={{height: 500}}>
{data.length > 0 && (
<React.Fragment>
<TextInput
autoComplete="off"
value={filterState.filter}
onUpdate={filterState.onChange}
ref={filterState.filterRef}
/>

<ListContainerView ref={containerRef}>
{filterState.items.map((item, index) => (
<ListItemRecursiveRenderer
itemSchema={item}
key={index}
index={index}
expanded={listState.expanded}
>
{(id) => (
<ItemRenderer
size={size}
{...listParsedState}
{...listState}
id={id}
onItemClick={onItemClick}
renderItem={defaultItemRendererBuilder({
itemWrapper: (node, {isLastItem}) => {
if (isLastItem) {
return (
<IntersectionContainer
onIntersect={
canFetchMore &&
!filterState.filter
? onFetchMore
: undefined
}
>
{node}
</IntersectionContainer>
);
}

return node;
},
getItemContent: identity,
})}
/>
)}
</ListItemRecursiveRenderer>
))}
</ListContainerView>
</React.Fragment>
)}

{isLoading && (
<Flex justifyContent="center" alignItems="center" grow>
<Loader
// @ts-expect-error loader doesn't support `xl` type
size={size}
/>
</Flex>
)}
<Flex gap="2">
<Button onClick={handleReset} width="max" size={size}>
Reset
</Button>
<Button view="action" onClick={handleAccept} width="max" size={size}>
Accept
</Button>
</Flex>
</Flex>
</React.StrictMode>
);
};
Loading

0 comments on commit dbbe45a

Please sign in to comment.