Skip to content
This repository has been archived by the owner on Sep 26, 2024. It is now read-only.

Commit

Permalink
feat(UIKIT-1592,TreeLikeList): Реализовано раскрытие списка и скролл …
Browse files Browse the repository at this point in the history
…до первочально выбранных элементов (#1110)
  • Loading branch information
mfrolov89 authored Aug 28, 2024
1 parent d3a6e2b commit c9de34b
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 51 deletions.
3 changes: 2 additions & 1 deletion packages/components/src/Tree/TreeList/TreeItem/TreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export const TreeItem = (props: TreeItemProps) => {
isDefaultExpanded,
isDisabled,
disableReason,
nextLevel,
handleChange,
} = useLogic(props);

Expand Down Expand Up @@ -105,7 +106,7 @@ export const TreeItem = (props: TreeItemProps) => {
{...child}
prefixId={prefixId}
renderItem={renderItem}
level={level + 1}
level={nextLevel}
isInitialExpanded={isInitialExpanded}
expandedLevel={expandedLevel}
chainToSelectedItem={chainToSelectedItem}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export const useLogic = ({
const isDisabled = Boolean(disabledItem);
const disableReason = disabledItem?.disableReason;

const nextLevel = level + 1;

const handleChange = () => {
onChange?.(id);
};
Expand All @@ -30,6 +32,7 @@ export const useLogic = ({
isDefaultExpanded,
isDisabled,
disableReason,
nextLevel,
handleChange,
};
};
45 changes: 28 additions & 17 deletions packages/components/src/TreeLikeList/TreeItem/TreeItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export type TreeItemProps = TreeListData & {
*/
value?: MultipleValue;

/**
* Уникальный префикс для идентификаторов в рамках дерева
*/
prefixId?: string;

/**
* Render-props, позволяет более гибко настраивать содержимое item
*/
Expand All @@ -36,6 +41,11 @@ export type TreeItemProps = TreeListData & {
*/
expandedLevel: number;

/**
* Цепочки идентификаторов до выбранных элементов дерева
*/
chainsToSelectedItem?: Array<MultipleValue>;

/**
* Список `value` элементов дерева, которые не доступны для взаимодействия
*/
Expand All @@ -47,35 +57,31 @@ export type TreeItemProps = TreeListData & {
onChange: (value: MultipleValue) => void;
};

export const TreeItem = ({
id,
label,
note,
level,
renderItem,
children = [],
value,
isInitialExpanded,
expandedLevel,
disabledItems,
onChange,
}: TreeItemProps) => {
export const TreeItem = (props: TreeItemProps) => {
const {
isSelected,
isDefaultExpanded,
disableReason,
isDisabled,
nextLevel,
handleChange,
} = useLogic({
} = useLogic(props);

const {
id,
value,
prefixId,
label,
note,
level,
renderItem,
children = [],
value,
isInitialExpanded,
expandedLevel,
chainsToSelectedItem,
disabledItems,
onChange,
});
} = props;

/**
* Предотвращаем всплытие события, так как клик в области чекбокса или label вызывает обработчик на уровне всего item
Expand All @@ -85,7 +91,8 @@ export const TreeItem = ({
if (children.length) {
return (
<StyledTreeItem
isRoot
id={id}
prefixId={prefixId}
isSelected={isSelected}
isDefaultExpanded={isDefaultExpanded}
disableReason={disableReason}
Expand All @@ -109,11 +116,13 @@ export const TreeItem = ({
{children.map((child) => (
<TreeItem
key={child.id}
prefixId={prefixId}
{...child}
renderItem={renderItem}
level={nextLevel}
isInitialExpanded={isInitialExpanded}
expandedLevel={expandedLevel}
chainsToSelectedItem={chainsToSelectedItem}
disabledItems={disabledItems}
value={value}
onChange={onChange}
Expand All @@ -126,6 +135,8 @@ export const TreeItem = ({

return (
<StyledTreeItem
id={id}
prefixId={prefixId}
isSelected={isSelected}
isDisabled={isDisabled}
disableReason={disableReason}
Expand Down
22 changes: 11 additions & 11 deletions packages/components/src/TreeLikeList/TreeItem/useLogic/useLogic.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
import { type MultipleValue } from '../../types';
import { type TreeItemProps } from '../TreeItem';

import { checkIsSelected } from './utils';

type UseLogicProps = Pick<
TreeItemProps,
| 'id'
| 'value'
| 'level'
| 'isInitialExpanded'
| 'expandedLevel'
| 'disabledItems'
| 'onChange'
>;
type UseLogicProps = TreeItemProps;

export const useLogic = ({
id,
value = [],
level,
isInitialExpanded,
expandedLevel,
chainsToSelectedItem = [],
disabledItems,
onChange,
}: UseLogicProps) => {
const isSelected = checkIsSelected(value, id);

const isDefaultExpanded = isInitialExpanded && level <= expandedLevel - 1;
const flatChainsToSelectedItem: MultipleValue = chainsToSelectedItem?.reduce(
(acc, chain: MultipleValue) => [...(acc || []), ...(chain || [])],
[],
);

const isDefaultExpanded =
flatChainsToSelectedItem?.includes(id) ||
(isInitialExpanded && level <= expandedLevel - 1);

const disabledItem = disabledItems?.find((item) => item.id === id);
const isDisabled = Boolean(disabledItem);
Expand Down
29 changes: 27 additions & 2 deletions packages/components/src/TreeLikeList/TreeLikeList.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,43 @@ const FAKE_NOTE_TREE_LIST_DATA = [
},
];

const Wrapper = styled.div`
height: 240px;
overflow-y: auto;
`;

export const Example = () => {
const [value, setValue] = useState<Array<string> | undefined>();
const [value, setValue] = useState<Array<string> | undefined>(['211', '4']);

const fakeData = [
{
id: 'a',
label: 'Item A',
},
{
id: 'b',
label: 'Item B',
},
{
id: 'c',
label: 'Item C',
},
...FAKE_TREE_LIST_DATA,
{
id: '3',
label: 'Item 3',
},
{
id: '4',
label: 'Item 4',
},
];

return <TreeLikeList data={fakeData} value={value} onChange={setValue} />;
return (
<Wrapper>
<TreeLikeList data={fakeData} value={value} onChange={setValue} />
</Wrapper>
);
};

/**
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/TreeLikeList/TreeLikeList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ describe('TreeLikeList', () => {
return <TreeLikeList value={value} data={fakeData} onChange={setValue} />;
};

window.HTMLElement.prototype.scrollIntoView = () => {};
renderWithTheme(<TestComponent />);

const checked = screen.getAllByRole('checkbox', { checked: true });
Expand Down
26 changes: 13 additions & 13 deletions packages/components/src/TreeLikeList/TreeLikeList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@ export type { TreeLikeListProps };

const INITIAL_LEVEL = 0;

export const TreeLikeList = ({
data,
value,
className,
expandedLevel = 10,
disabledItems,
...props
}: TreeLikeListProps) => {
const { formattedDisabledItems } = useLogic({ disabledItems });
export const TreeLikeList = (props: TreeLikeListProps) => {
const { listProps, itemProps } = useLogic(props);

const {
data,
className,
expandedLevel = 10,
disabledItems,
...restProps
} = props;

return (
<List className={className}>
<List className={className} {...listProps}>
{data.map((item) => (
<TreeItem
key={item.id}
value={value}
{...item}
level={INITIAL_LEVEL}
expandedLevel={expandedLevel}
disabledItems={formattedDisabledItems}
{...props}
{...itemProps}
{...restProps}
/>
))}
</List>
Expand Down
45 changes: 38 additions & 7 deletions packages/components/src/TreeLikeList/useLogic/useLogic.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,48 @@
import { useMemo } from 'react';
import { useEffect, useMemo, useRef } from 'react';

import { useId } from '../../hooks/useId';
import { getFormatDisabledItems } from '../../Tree/utils';
import { type DisabledItemValue } from '../types';
import { type TreeLikeListProps } from '../TreeLikeList';

type UseLogicParams = {
disabledItems?: Array<DisabledItemValue>;
};
import { getChainsId } from './utils';

type UseLogicParams = TreeLikeListProps;

export const useLogic = ({ data, value, disabledItems }: UseLogicParams) => {
const prefixId = useId();

const listRef = useRef<HTMLUListElement>(null);

useEffect(() => {
if (listRef.current && value?.length) {
// Выбираем первый элемент из списка value
const targetItem = listRef.current.querySelector(`
li[id="${prefixId}${value[0]}"]`);

if (targetItem) {
targetItem.scrollIntoView({ block: 'center' });
}
}
}, [listRef, prefixId]);

export const useLogic = ({ disabledItems }: UseLogicParams) => {
const formattedDisabledItems = useMemo(
() => getFormatDisabledItems(disabledItems),
[disabledItems],
);

return { formattedDisabledItems };
const chainsToSelectedItem = useMemo(
() => getChainsId(data, value),
[data, value],
);

return {
listProps: {
ref: listRef,
},
itemProps: {
chainsToSelectedItem,
disabledItems: formattedDisabledItems,
prefixId,
},
};
};
Loading

0 comments on commit c9de34b

Please sign in to comment.