From a27ae592c86599ee2ea1df5057ee4f6f63f9e91f Mon Sep 17 00:00:00 2001 From: Alexandr Isaev Date: Fri, 2 Feb 2024 12:39:10 +0300 Subject: [PATCH] fix: rework new list item view text crop behaviour --- .../TreeSelectItem/TreeSelectItem.tsx | 8 +- ...pSelectionControlledStateAndCustomIcon.tsx | 14 +- .../WithItemLinksAndActionsExample.tsx | 22 ++- .../__stories__/components/FlattenList.tsx | 10 +- .../components/ListItemView/ListItemView.scss | 11 ++ .../components/ListItemView/ListItemView.tsx | 35 +++-- .../__stories__/ListItemView.stories.tsx | 143 +++++++++++++++--- 7 files changed, 185 insertions(+), 58 deletions(-) diff --git a/src/components/TreeSelect/TreeSelectItem/TreeSelectItem.tsx b/src/components/TreeSelect/TreeSelectItem/TreeSelectItem.tsx index a5334a7532..6a28815dea 100644 --- a/src/components/TreeSelect/TreeSelectItem/TreeSelectItem.tsx +++ b/src/components/TreeSelect/TreeSelectItem/TreeSelectItem.tsx @@ -10,18 +10,18 @@ const b = block('tree-select-item'); export interface TreeSelectItemProps extends Omit { as?: 'div' | 'li'; - itemClassName?: string; + wrapperClassName?: string; } export const TreeSelectItem = React.forwardRef(function TreeSelectItem( - {as = 'div', className, itemClassName, ...props}: TreeSelectItemProps, + {as = 'div', className, wrapperClassName, ...props}: TreeSelectItemProps, ref?: any, ) { const Tag: React.ElementType = as; return ( - - + + ); }); diff --git a/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx b/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx index c9f6435ca3..bec53a1706 100644 --- a/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx +++ b/src/components/TreeSelect/__stories__/components/WithGroupSelectionControlledStateAndCustomIcon.tsx @@ -53,7 +53,14 @@ export const WithGroupSelectionControlledStateAndCustomIconExample = ({ renderControlContent={mapCustomDataStructureToKnownProps} expandedById={expandedById} value={value} - renderItem={(item, state, {groupState}) => { + renderItem={( + item, + { + expanded, // don't use default ListItemView expand icon + ...state + }, + {groupState}, + ) => { return ( - + ) : undefined } diff --git a/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx b/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx index d44b9e8ebd..8b71822585 100644 --- a/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx +++ b/src/components/TreeSelect/__stories__/components/WithItemLinksAndActionsExample.tsx @@ -5,7 +5,7 @@ import {ChevronDown, ChevronUp, FolderOpen} from '@gravity-ui/icons'; import {Button} from '../../../Button'; import {DropdownMenu} from '../../../DropdownMenu'; import {Icon} from '../../../Icon'; -import {Flex, spacing} from '../../../layout'; +import {Flex} from '../../../layout'; import type {ListItemId} from '../../../useList'; import {createRandomizedData} from '../../../useList/__stories__/utils/makeData'; import {TreeSelect} from '../../TreeSelect'; @@ -42,7 +42,14 @@ export const WithItemLinksAndActionsExample = (props: WithItemLinksAndActionsExa setOpen((x) => !x); }} expandedById={expandedById} - renderItem={(item, state, {groupState}) => { + renderItem={( + item, + { + expanded, // don't use build in expand icon ListItemView behavior + ...state + }, + {groupState}, + ) => { return ( // eslint-disable-next-line jsx-a11y/anchor-is-valid { e.stopPropagation(); e.preventDefault(); @@ -92,12 +98,18 @@ export const WithItemLinksAndActionsExample = (props: WithItemLinksAndActionsExa }} > ) : ( - + 0 ? {ml: 1} : undefined} + > + + ) } /> diff --git a/src/components/useList/__stories__/components/FlattenList.tsx b/src/components/useList/__stories__/components/FlattenList.tsx index be60ffa0c2..225b24de25 100644 --- a/src/components/useList/__stories__/components/FlattenList.tsx +++ b/src/components/useList/__stories__/components/FlattenList.tsx @@ -84,20 +84,14 @@ export const FlattenList = ({itemsCount, size}: FlattenListProps) => { } > {(id) => { - const {data, props, context} = getItemRenderState({ + const {data, props} = getItemRenderState({ id, size, onItemClick, ...list, ...listState, }); - return ( - - ); + return ; }} diff --git a/src/components/useList/components/ListItemView/ListItemView.scss b/src/components/useList/components/ListItemView/ListItemView.scss index 9eefa73fed..f9273cbaae 100644 --- a/src/components/useList/components/ListItemView/ListItemView.scss +++ b/src/components/useList/components/ListItemView/ListItemView.scss @@ -5,6 +5,11 @@ $block: '.#{variables.$ns}list-item-view'; #{$block} { flex-shrink: 0; + &__main-content { + display: grid; + gap: var(--g-spacing-half, 2px); + } + &:hover#{$block}_activeOnHover, &_active#{$block}_activeOnHover, &_active { @@ -33,7 +38,13 @@ $block: '.#{variables.$ns}list-item-view'; border-radius: var(--g-list-item-border-radius, 8px); } + &__icon { + flex-shrink: 0; + } + &__slot { + flex-shrink: 0; + &_indent_1 { width: 16px; } diff --git a/src/components/useList/components/ListItemView/ListItemView.tsx b/src/components/useList/components/ListItemView/ListItemView.tsx index 51654bfb8e..90be600114 100644 --- a/src/components/useList/components/ListItemView/ListItemView.tsx +++ b/src/components/useList/components/ListItemView/ListItemView.tsx @@ -63,7 +63,12 @@ interface SlotProps extends FlexProps { indentation?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10; } -export const Slot = ({children, indentation: indent = 1, className, ...props}: SlotProps) => { +export const ListItemViewSlot = ({ + children, + indentation: indent = 1, + className, + ...props +}: SlotProps) => { return ( {children} @@ -73,7 +78,9 @@ export const Slot = ({children, indentation: indent = 1, className, ...props}: S const renderSafeIndentation = (indentation?: number) => { if (indentation && indentation >= 1 && indentation < 11) { - return ; + return ( + + ); } return null; }; @@ -123,7 +130,7 @@ export const ListItemView = React.forwardRef( spacing({px: 2}, className), )} style={{ - height: height ?? modToHeight[size][Number(Boolean(subtitle))], + minHeight: height ?? modToHeight[size][Number(Boolean(subtitle))], ...style, }} as={as} @@ -136,7 +143,8 @@ export const ListItemView = React.forwardRef( > {hasSelectionIcon && ( - + {selected ? ( ) : null} - + )} {renderSafeIndentation(indentation)} - {startSlot ?? - (isGroup ? ( - - ) : null)} + {isGroup ? ( + + ) : null} + + {startSlot} - +
{typeof title === 'string' ? ( +
{endSlot} diff --git a/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx b/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx index b339718a9b..4cf8902d9b 100644 --- a/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx +++ b/src/components/useList/components/ListItemView/__stories__/ListItemView.stories.tsx @@ -3,9 +3,10 @@ import React from 'react'; import type {Meta, StoryFn} from '@storybook/react'; import {Avatar} from '../../../../Avatar'; -import {Flex} from '../../../../layout'; +import {DropdownMenu} from '../../../../DropdownMenu'; +import {Text} from '../../../../Text'; +import {Flex, sp} from '../../../../layout'; import {useListState} from '../../../hooks/useListState'; -import type {ListItemId} from '../../../types'; import {ListItemView as ListItemViewComponent} from '../ListItemView'; import type {ListItemViewProps} from '../ListItemView'; @@ -17,6 +18,31 @@ export default { const title = 'title'; const subtitle = 'subtitle'; +const StartSlot = ({selfStart}: {selfStart?: boolean}) => ( + + + +); + +const EndSlot = ({selfStart}: {selfStart?: boolean}) => ( + + { + e.stopPropagation(); + e.preventDefault(); + }} + items={[ + { + action: () => { + console.log(`Clicked by action 1`); + }, + text: 'action 1', + }, + ]} + /> + +); + const stories: ListItemViewProps[] = [ { id: '1', @@ -24,15 +50,14 @@ const stories: ListItemViewProps[] = [ activeOnHover: false, subtitle, disabled: true, - startSlot: ( - - ), + startSlot: , }, { id: '2', title, subtitle, activeOnHover: false, + endSlot: , }, { id: '3', @@ -40,9 +65,7 @@ const stories: ListItemViewProps[] = [ size: 'l', subtitle, hasSelectionIcon: false, - startSlot: ( - - ), + startSlot: , }, { id: '4', @@ -50,66 +73,136 @@ const stories: ListItemViewProps[] = [ disabled: true, size: 'xl', height: 60, - startSlot: ( - - ), + startSlot: , }, { id: '5', size: 'l', - startSlot: ( - - ), + startSlot: , title, }, { id: '6', - title, + title: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos officiis, voluptates nobis doloribus veritatis quo odit sequi eligendi aliquam quasi officia qui deserunt autem quas necessitatibus nam possimus aperiam.', size: 'l', subtitle: 'indentation 1', - startSlot: ( - - ), + startSlot: , indentation: 1, selected: true, + endSlot: , }, { id: '7', expanded: true, size: 'xl', title: 'Group 1', + endSlot: , }, { id: '8', hasSelectionIcon: false, expanded: true, + disabled: true, size: 'xl', title: 'Group 1', }, + { + id: '9', + hasSelectionIcon: false, + title: ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos officiis, + voluptates nobis doloribus veritatis quo odit sequi eligendi aliquam quasi officia + qui deserunt autem quas necessitatibus nam possimus aperiam. + + ), + size: 'l', + subtitle: ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos officiis, + voluptates nobis doloribus veritatis quo odit sequi eligendi aliquam quasi officia + qui deserunt autem quas necessitatibus nam possimus aperiam. + + ), + startSlot: , + selected: true, + className: sp({p: 2}), + endSlot: , + }, + { + id: '10', + title: ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos officiis, + voluptates nobis doloribus veritatis quo odit sequi eligendi aliquam quasi officia + qui deserunt autem quas necessitatibus nam possimus aperiam. + + ), + size: 'l', + subtitle: ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos officiis, + voluptates nobis doloribus veritatis quo odit sequi eligendi aliquam quasi officia + qui deserunt autem quas necessitatibus nam possimus aperiam. + + ), + startSlot: , + selected: true, + indentation: 1, + className: sp({p: 2}), + endSlot: , + }, + { + id: '11', + title: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos officiis, voluptates nobis doloribus veritatis quo odit sequi eligendi aliquam quasi officia qui deserunt autem quas necessitatibus nam possimus aperiam.', + size: 'l', + subtitle: ( + + Lorem ipsum dolor sit amet consectetur adipisicing elit. Ex quos officiis, + voluptates nobis doloribus veritatis quo odit sequi eligendi aliquam quasi officia + qui deserunt autem quas necessitatibus nam possimus aperiam. + + ), + expanded: true, + startSlot: , + indentation: 1, + selected: true, + endSlot: , + }, ]; const ListItemViewTemplate: StoryFn = () => { const listState = useListState(); return ( - + {stories.map((props, i) => ( ))} ); - function handleClick(id: ListItemId) { + function handleClick({id, expanded}: ListItemViewProps) { + const isGroup = typeof expanded === 'boolean'; + return () => { - listState.setSelected((prevState) => ({ - ...prevState, - [id]: !prevState[id], - })); + if (isGroup) { + listState.setExpanded((prevState) => ({ + ...prevState, + [id]: typeof prevState[id] === 'undefined' ? !expanded : !prevState[id], + })); + } else { + listState.setSelected((prevState) => ({ + ...prevState, + [id]: !prevState[id], + })); + } }; } };