Skip to content

Commit

Permalink
Merge branch 'main' into withTableSettings/deprecate-render-controls
Browse files Browse the repository at this point in the history
  • Loading branch information
ArturAbdullin authored Mar 18, 2024
2 parents e4aafcf + 5e7d8b9 commit fc13d4b
Show file tree
Hide file tree
Showing 39 changed files with 1,181 additions and 310 deletions.
9 changes: 8 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,14 @@ 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"
mapItemDataToProps={(title) => ({title})}
/>
);
}}
/>
</React.Fragment>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,4 @@ $block: '.#{variables.$ns}table-column-setup';
&__controls {
margin: var(--g-spacing-1) var(--g-spacing-1) 0;
}

// to override this https://github.com/gravity-ui/uikit/blob/main/src/components/useList/components/ListItemView/ListItemView.scss#L25
&__required-item {
background: inherit;

&:hover {
/* stylelint-disable declaration-no-important */
background: var(--g-color-base-simple-hover-solid) !important;
/* stylelint-enable declaration-no-important */
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,13 @@ import i18n from './i18n';

import './TableColumnSetup.scss';

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

const b = block('table-column-setup');
const tableColumnSetupCn = b(null);
const controlsCn = b('controls');
const requiredDndItemCn = b('required-item');

const reorderArray = <T extends unknown>(list: T[], startIndex: number, endIndex: number): T[] => {
const result = [...list];
Expand All @@ -46,8 +49,7 @@ const prepareDndItems = (tableColumnItems: TableColumnSetupItem[]) => {
...tableColumnItem,
startSlot: tableColumnItem.isRequired ? <Icon data={Lock} /> : undefined,
hasSelectionIcon,
// to overwrite select background effect - https://github.com/gravity-ui/uikit/blob/main/src/components/useList/components/ListItemView/ListItemView.tsx#L125
className: hasSelectionIcon ? undefined : requiredDndItemCn,
selected: hasSelectionIcon ? tableColumnItem.isSelected : undefined,
};
});
};
Expand Down Expand Up @@ -152,7 +154,7 @@ const useDndRenderItem = (sortable: boolean | undefined) => {
{...provided.draggableProps}
{...provided.dragHandleProps}
style={style}
active={snapshot.isDragging}
dragging={snapshot.isDragging}
/>
);
}}
Expand Down Expand Up @@ -280,6 +282,7 @@ export const TableColumnSetup = (props: TableColumnSetupProps) => {
return (
<TreeSelect
className={tableColumnSetupCn}
mapItemDataToProps={identity}
multiple
size="l"
open={open}
Expand Down
127 changes: 127 additions & 0 deletions src/components/TreeList/TreeList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React from 'react';

import {useUniqId} from '../../hooks';
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';

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

export const TreeList = <T,>({
id,
size = 'm',
items,
className,
expandedById,
disabledById,
activeItemId,
selectedById,
getId,
renderItem: propsRenderItem,
renderContainer = TreeListContainer,
onItemClick,
multiple,
setActiveItemId,
containerRef: propsContainerRef,
mapItemDataToProps,
}: TreeListProps<T>) => {
const uniqId = useUniqId();
const treeListId = id ?? uniqId;
const containerRefLocal = React.useRef<HTMLDivElement>(null);
const containerRef = propsContainerRef ?? containerRefLocal;

const listParsedState = useList({
items,
getId,
expandedById,
disabledById,
activeItemId,
selectedById,
});

const handleItemClick = React.useCallback(
(listItemId: ListItemId) => {
onItemClick?.({
id: listItemId,
data: listParsedState.itemsById[listItemId],
disabled: disabledById
? Boolean(disabledById[listItemId])
: Boolean(listParsedState.initialState.disabledById[listItemId]),
isLastItem:
listParsedState.visibleFlattenIds[
listParsedState.visibleFlattenIds.length - 1
] === listItemId,
groupState: listParsedState.groupsState[listItemId],
itemState: listParsedState.itemsState[listItemId],
});
},
[
disabledById,
listParsedState.groupsState,
listParsedState.initialState.disabledById,
listParsedState.itemsById,
listParsedState.itemsState,
listParsedState.visibleFlattenIds,
onItemClick,
],
);

useListKeydown({
containerRef,
onItemClick: handleItemClick,
...listParsedState,
activeItemId,
disabledById,
setActiveItemId,
});

const renderItem: TreeListRenderContainerProps<T>['renderItem'] = (
itemId,
index,
renderContextProps,
) => {
const renderState = getItemRenderState({
id: itemId,
size,
mapItemDataToProps,
onItemClick: handleItemClick,
...listParsedState,
expandedById,
disabledById,
activeItemId,
selectedById,
});

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

if (propsRenderItem) {
return propsRenderItem({
data: renderState.data,
props: renderState.props,
itemState: renderState.context,
index,
renderContext: renderContextProps,
});
}

return <ListItemView {...renderState.props} {...renderContextProps} />;
};

// not JSX decl here is from weird `react-beautiful-dnd` render bug
return renderContainer({
id: `list-${treeListId}`,
size,
containerRef,
className: b(null, className),
...listParsedState,
expandedById,
disabledById,
activeItemId,
selectedById,
renderItem,
});
};
58 changes: 58 additions & 0 deletions src/components/TreeList/__stories__/TreeList.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type {Meta, StoryObj} from '@storybook/react';

import {TreeList} from '../TreeList';

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;

type DefaultStoryObj = StoryObj<typeof DefaultStory>;

export const Default: DefaultStoryObj = {
render: DefaultStory,
};

type InfinityScrollStoryObj = StoryObj<typeof InfinityScrollStory>;

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

type WithDndListStoryObj = StoryObj<typeof WithDndListStory>;

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,
};

type WithFiltrationAndControlsStoryObj = StoryObj<typeof WithFiltrationAndControlsStory>;

export const WithFiltrationAndControls: WithFiltrationAndControlsStoryObj = {
render: WithFiltrationAndControlsStory,
};

type WithGroupSelectionAndCustomIconStoryObj = StoryObj<
typeof WithGroupSelectionAndCustomIconStory
>;

export const WithGroupSelectionAndCustomIcon: WithGroupSelectionAndCustomIconStoryObj = {
render: WithGroupSelectionAndCustomIconStory,
};

type WithItemLinksAndActionsStoryObj = StoryObj<typeof WithItemLinksAndActionsStory>;

export const WithItemLinksAndActions: WithItemLinksAndActionsStoryObj = {
render: WithItemLinksAndActionsStory,
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';

import {ListContainerView, computeItemSize} from '../../../useList';
import {VirtualizedListContainer} from '../../../useList/__stories__/components/VirtualizedListContainer';
import type {TreeSelectRenderContainerProps} from '../../types';
import type {TreeListRenderContainerProps} from '../../types';

// custom container renderer example
export const RenderVirtualizedContainer = <T,>({
Expand All @@ -11,7 +11,7 @@ export const RenderVirtualizedContainer = <T,>({
visibleFlattenIds,
renderItem,
size,
}: TreeSelectRenderContainerProps<T>) => {
}: TreeListRenderContainerProps<T>) => {
return (
<ListContainerView fixedHeight id={id} ref={containerRef}>
<VirtualizedListContainer
Expand Down
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' | 'mapItemDataToProps'> {
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} mapItemDataToProps={identity} />
</Flex>
);
};
Loading

0 comments on commit fc13d4b

Please sign in to comment.