feat: add highlight selection to S2 TableView#9811
Conversation
|
Build successful! 🎉 |
|
Build successful! 🎉 |
| ); | ||
| } | ||
|
|
||
| function isNextSelected(id: Key | undefined, state: ListState<unknown>) { |
There was a problem hiding this comment.
moved to the utils file since it was being shared by ListView, TreeView, and TableView
|
Build successful! 🎉 |
| } | ||
| }; | ||
|
|
||
| export const Highlight: StoryObj<typeof StaticTable> = { |
There was a problem hiding this comment.
Need it added to docs
do we need a special one for storybook? or can the controls cover this case?
There was a problem hiding this comment.
i added it only because it was easier to have an already made example of it rather than having to use the controls. we also do the same in ListView but i can remove it
There was a problem hiding this comment.
Yeah, let's aim for less stories <3
We'll cover the initial rendering with chromatic
| forcedColorAdjust: 'none' | ||
| }); | ||
|
|
||
| // Unlike the other items, the first item needs to render a border on top when it is selected in highlight mode |
There was a problem hiding this comment.
is this true if the item is marked with inert? or aria-hidden?
There was a problem hiding this comment.
im not sure if i understand what you mean by this. are you saying that this style might not appear if the row is marked as inert or aria-hidden? or are you saying that should the style be different if marked with inert or aria-hidden
There was a problem hiding this comment.
I was more asking if the "count" is still off if the extra divs have those attributes
There was a problem hiding this comment.
this style will only apply if the selected item is the first row since it's kinda a special case. generally, we are rendering the bottom border, not the top, which is what makes the first row special. i also tested it out myself by adding inert and aria-hidden to the first row and the styles seem to be fine. not sure if that really answers you question. wasn't exactly sure what you meant by "count"
|
Build successful! 🎉 |
|
Build successful! 🎉 |
|
please ignore the 200+ changes in chromatic...pretty sure those aren't related to this PR just wanted to check that the checkbox styles hadn't changed |
| function CellFocusRing() { | ||
| return <div role="presentation" className={style({...cellFocus, position: 'absolute', inset: 0, pointerEvents: 'none'})({isFocusVisible: true})} />; | ||
| function CellFocusRing({selectionStyle} : {selectionStyle?: 'checkbox' | 'highlight'}) { | ||
| return <div role="presentation" className={style({...cellFocus, position: 'absolute', inset: 0, pointerEvents: 'none', top: 0, bottom: '[-1px]'})({isFocusVisible: true, selectionStyle})} />; |
There was a problem hiding this comment.
was this just correcting how the cell focus ring is placed with respect to the bottom of the row?
There was a problem hiding this comment.
this was to change the color in HCM if it was highlight selection since the Highlight outline blends in with a selected row's background
There was a problem hiding this comment.
guess this could be changed if we decide to update hcm
| default: colorMix('gray-25', 'blue-900', 10), | ||
| isHovered: colorMix('gray-25', 'blue-900', 15), | ||
| isPressed: colorMix('gray-25', 'blue-900', 15), | ||
| forcedColors: 'Highlight' |
There was a problem hiding this comment.
I actually removed this in ListView because it made the drag handle/drag handle's focus ring hard in HCM. Do we need this or is the focus indicator at the left edge of the row sufficient? Not having a background color in HCM mirrors v3 I believe
There was a problem hiding this comment.
How're you indicating selection now. I assume it's this same issue. #9805 (comment)
There was a problem hiding this comment.
yeah right now this highlight selection just matches what we have in ListView and TreeView. maybe that hcm can be done separately after we get the changes in for dnd and highlight selection and we can discuss what we want to do
| highlight: { | ||
| default: 'focus-ring', | ||
| forcedColors: 'ButtonBorder' | ||
| } |
There was a problem hiding this comment.
can probably get rid of this if we end up getting rid of the HCM background color on the row
| // In order to prevent layout shifts, we use box shadows to render the borders since we can't add an absolute position div (it messes up the cell count due to the way Table collections are built) | ||
| // In highlight mode, selected groups also have gray borders between the items in addition to having a blue outer border | ||
| // Having a border have two colors is possible, the issue is that the browser will render a diagonal line where the two borders meet | ||
| // Using box shadows gives us a bit more control on how the border colors appear | ||
| boxShadow: { |
There was a problem hiding this comment.
I'm not sure I entirely understand this part, at the moment each table row renders just the bottom border right so could we just change the color of that border based on if the row is selected and is the last/first row in its selection group?
There was a problem hiding this comment.
Oh is it because of the top most row and if it is selected? Could we use a box shadow for that case only or something?
There was a problem hiding this comment.
For highlight selection, the borders actually have a width of 0px. The reasoning for the borders having a width of 0px for highlight is for two reasons: 1) layout shifts 2) border rendering in browsers
-
For ListView or TreeView, we render a border on all four sides and then only conditionally change the width to 0px if the next or previous item was selected. This didn't cause any layout shifts because this was done to an absolute position div. However, we can't conditionally change the border width in TableView without causing a layout shift because we can't add an absolute position div. As a result, we use box-shadows to avoid this issue.
-
In the case for a selected item (assuming the next item is also selected), the left and right borders will be blue while the bottom border will be gray. Using borders, the blue and gray borders meet at a diagonal edge due to how browsers render border joins. See codepen. This wasn't an issue in ListView or TreeView because selected items don't have any borders between them.
There was a problem hiding this comment.
The border for the first row is handled slightly differently due to issues with the CSS paint order. Initially, I also just used a box shadow to render the top border of the first item if it was selected, but if a cell has a divider, the divider gets rendered on top of it so you have a gray line on top of the blue line. So in order to fix that, I had to use a pseudo-element to basically force it on top of the divider. I tried isolation and z-indexes which didn't work
There was a problem hiding this comment.
hm gotcha, I actually used a outline here (kinda like the commented out code) for the "on" drop indicators, could we do the same here instead then?
react-spectrum/packages/@react-spectrum/s2/src/TableView.tsx
Lines 1744 to 1766 in 083f4e5
If not, I'm happy to switch my implementation to use box shadows completely, but whichever way we choose should be used for both "on" drop and highlight selection for sure
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:RowRenderProps RowRenderProps {
allowsDragging?: boolean
hasChildItems: boolean
id?: Key
isDisabled: boolean
isDragging?: boolean
isDropTarget?: boolean
isExpanded: boolean
isFocusVisible: boolean
isFocusVisibleWithin: boolean
isFocused: boolean
isHovered: boolean
isPressed: boolean
isSelected: boolean
level: number
selectionBehavior: SelectionBehavior
selectionMode: SelectionMode
+ state: TableState<unknown>
}@internationalized/date/@internationalized/date:DateValue-DateValue {
- D: undefined
-}@react-spectrum/s2/@react-spectrum/s2:TableView TableView {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'spacious' | 'regular' = 'regular'
disabledBehavior?: DisabledBehavior = "all"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
expandedKeys?: Iterable<Key>
id?: string
isQuiet?: boolean
loadingState?: LoadingState
onAction?: (Key) => void
onExpandedChange?: (Set<Key>) => any
onLoadMore?: () => any
onResize?: (Map<Key, ColumnSize>) => void
onResizeEnd?: (Map<Key, ColumnSize>) => void
onResizeStart?: (Map<Key, ColumnSize>) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
overflowMode?: 'wrap' | 'truncate' = 'truncate'
renderActionBar?: ('all' | Set<Key>) => ReactElement
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
+ selectionStyle?: 'checkbox' | 'highlight' = 'checkbox'
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
styles?: StylesPropWithHeight
}/@react-spectrum/s2:TableViewProps TableViewProps {
UNSAFE_className?: UnsafeClassName
UNSAFE_style?: CSSProperties
aria-describedby?: string
aria-details?: string
aria-label?: string
aria-labelledby?: string
children?: ReactNode
defaultExpandedKeys?: Iterable<Key>
defaultSelectedKeys?: 'all' | Iterable<Key>
density?: 'compact' | 'spacious' | 'regular' = 'regular'
disabledBehavior?: DisabledBehavior = "all"
disabledKeys?: Iterable<Key>
disallowEmptySelection?: boolean
escapeKeyBehavior?: 'clearSelection' | 'none' = 'clearSelection'
expandedKeys?: Iterable<Key>
id?: string
isQuiet?: boolean
loadingState?: LoadingState
onAction?: (Key) => void
onExpandedChange?: (Set<Key>) => any
onLoadMore?: () => any
onResize?: (Map<Key, ColumnSize>) => void
onResizeEnd?: (Map<Key, ColumnSize>) => void
onResizeStart?: (Map<Key, ColumnSize>) => void
onSelectionChange?: (Selection) => void
onSortChange?: (SortDescriptor) => any
overflowMode?: 'wrap' | 'truncate' = 'truncate'
renderActionBar?: ('all' | Set<Key>) => ReactElement
selectedKeys?: 'all' | Iterable<Key>
selectionMode?: SelectionMode
+ selectionStyle?: 'checkbox' | 'highlight' = 'checkbox'
shouldSelectOnPressUp?: boolean
slot?: string | null
sortDescriptor?: SortDescriptor
styles?: StylesPropWithHeight
}@react-spectrum/toast/@react-spectrum/toast:CloseFunction-CloseFunction {
- C: undefined
-}@react-stately/data/@react-stately/data:AsyncListLoadFunction-AsyncListLoadFunction {
- A: undefined
-}/@react-stately/data:AsyncListLoadOptions-AsyncListLoadOptions <C, T> {
- cursor?: C
- filterText?: string
- items: Array<T>
- loadingState?: LoadingState
- selectedKeys: Selection
- signal: AbortSignal
- sortDescriptor?: SortDescriptor
-}/@react-stately/data:AsyncListStateUpdate-AsyncListStateUpdate <C, T> {
- cursor?: C
- filterText?: string
- items: Iterable<T>
- selectedKeys?: Iterable<Key>
- sortDescriptor?: SortDescriptor
-} |


✅ Pull Request Checklist:
📝 Test Instructions:
🧢 Your Project: