adobe / react-spectrum

A collection of libraries and tools that help you build adaptive, accessible, and robust user experiences.
https://react-spectrum.adobe.com
Apache License 2.0
13.04k stars 1.13k forks source link

Improve shift selection and responsive layout in S2 CardView #7030

Closed devongovett closed 2 months ago

devongovett commented 2 months ago

Three updates for S2 CardView:

  1. The previous behavior when using the Shift key to select multiple items in Waterfall layout was a bit unpredictable. Shift selection normally adds all items in collection order between the first and last item you clicked on. With waterfall layout, this doesn't make sense because the items may be rendered out of order (according to the shortest column). Now the LayoutDelegate interface supports overriding the getKeyRange behavior of SelectionManager. WaterfallLayout uses this to implement a spacial algorithm that selects items contained by the rectangle formed between the first and last item you shift clicked on.

  2. When switching layout options like t-shirt size and density, the entire CardView would be unmounted and re-mounted. This was due to Virtualizer in RAC returning a new CollectionRoot component each time the layout changed (due to useMemo). Now Virtualizer passes the layout via a context and always uses the same component. We also avoid changing the layout entirely by using the layoutOptions object to pass info to the existing layout.

  3. On small screens, small areas (e.g. resizable rails), or high zoom levels, the larger card sizes may no longer fit in the available space. We now treat the CardView size prop more like a maximum size. If at last two columns do not fit, we will reduce the t-shirt size until we find a size that does fit. This enables CardView to be more responsive out of the box. I also added render props support to Card so its contents can respond to the currently rendered size, e.g. change the size of an Avatar or hide an ActionMenu.

Question: Is this a good default? Do we need a way to set a minimum t-shirt size that a card could go down to, or a way to disable this behavior entirely? Rename size to maxSize (not sure I like this)?

rspbot commented 2 months ago

Build successful! 🎉

rspbot commented 2 months ago

Build successful! 🎉

rspbot commented 2 months ago
## API Changes ### react-aria-components #### /react-aria-components:UNSTABLE_Virtualizer ```diff -UNSTABLE_Virtualizer { +UNSTABLE_Virtualizer { children: ReactNode - layout: ILayout + layout: ILayout + layoutOptions?: O } ``` #### /react-aria-components:VirtualizerProps ```diff -VirtualizerProps { +VirtualizerProps { children: ReactNode - layout: ILayout + layout: ILayout + layoutOptions?: O } ``` ----------------------------------- ### @react-spectrum/s2 #### /@react-spectrum/s2:Card ```diff Card { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode density?: 'compact' | 'regular' | 'spacious' = 'regular' download?: boolean | string href?: Href hrefLang?: string isDisabled?: boolean onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary' } ``` #### /@react-spectrum/s2:AssetCard ```diff AssetCard { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode download?: boolean | string href?: Href hrefLang?: string id?: Key onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary' } ``` #### /@react-spectrum/s2:UserCard ```diff UserCard { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode density?: 'compact' | 'regular' | 'spacious' = 'regular' download?: boolean | string href?: Href hrefLang?: string isDisabled?: boolean onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary' } ``` #### /@react-spectrum/s2:ProductCard ```diff ProductCard { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode download?: boolean | string href?: Href hrefLang?: string id?: Key onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' } ``` #### /@react-spectrum/s2:CardProps ```diff CardProps { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode density?: 'compact' | 'regular' | 'spacious' = 'regular' download?: boolean | string href?: Href hrefLang?: string isDisabled?: boolean onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary' } ``` #### /@react-spectrum/s2:AssetCardProps ```diff AssetCardProps { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode download?: boolean | string href?: Href hrefLang?: string id?: Key onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' | 'quiet' = 'primary' } ``` #### /@react-spectrum/s2:ProductCardProps ```diff ProductCardProps { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode download?: boolean | string href?: Href hrefLang?: string id?: Key onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' } ``` #### /@react-spectrum/s2:UserCardProps ```diff UserCardProps { UNSAFE_className?: string UNSAFE_style?: CSSProperties - children: ReactNode + children: ReactNode | (CardRenderProps) => ReactNode download?: boolean | string href?: Href hrefLang?: string id?: Key onAction?: () => void onHoverChange?: (boolean) => void onHoverEnd?: (HoverEvent) => void onHoverStart?: (HoverEvent) => void ping?: string referrerPolicy?: HTMLAttributeReferrerPolicy rel?: string routerOptions?: RouterOptions size?: 'XS' | 'S' | 'M' | 'L' | 'XL' = 'M' styles?: StylesProp target?: HTMLAttributeAnchorTarget textValue?: string value?: T variant?: 'primary' | 'secondary' | 'tertiary' } ``` ----------------------------------- ### @react-stately/list #### /@react-stately/list:ListProps ```diff ListProps { allowDuplicateSelectionEvents?: boolean collection?: Collection> defaultSelectedKeys?: 'all' | Iterable disabledBehavior?: DisabledBehavior disabledKeys?: Iterable disallowEmptySelection?: boolean filter?: (Iterable>) => Iterable> + layoutDelegate?: LayoutDelegate onSelectionChange?: (Selection) => void selectedKeys?: 'all' | Iterable selectionBehavior?: SelectionBehavior selectionMode?: SelectionMode ``` -----------------------------------