-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add layout and orientation props to RAC ListBox #4669
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
if (modality === 'keyboard') { | ||
scrollIntoViewport(element, {containingElement: ref.current}); | ||
} | ||
scrollIntoViewport(element, {containingElement: ref.current}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The changes in this file ensure we don't scroll an item into view when clicking with a mouse, only when navigating via a keyboard. This was already the case for virtualized collections, but I guess we never made the changes here as well.
[slot=drag][data-focus-visible] { | ||
box-shadow: 0 0 0 2px var(--highlight-foreground); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused CSS (copied from GridList). There is no drag affordance in ListBox.
@@ -518,7 +518,7 @@ The `--trigger-width` CSS custom property will be set on the popover, which you | |||
|
|||
```css render=false | |||
.react-aria-Popover { | |||
width: var(--button-width); | |||
width: var(--trigger-width); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Random small bug (matching text in paragraph above).
Build successful! 🎉 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we've addressed this before, but I can currently mouse down on an item in the new ListBox examples in the docs, and if I drag off and back on to an item, it will toggle the selected state each time my mouse re-enters the element. probably not actually related to your changes.
https://reactspectrum.blob.core.windows.net/reactspectrum/ac065e7221226cf2ff5c664d7256543a9308d855/docs/react-aria/ListBox.html#column-layout
On the same example, page up behavior is a bit strange, it goes to the last item in the column instead of the first item in the column.
@@ -568,6 +568,239 @@ import {Text} from 'react-aria-components'; | |||
</ListBox> | |||
``` | |||
|
|||
### Horizontal layout | |||
|
|||
By default, ListBox expects items to be arranged in a vertical stack, and implements keyboard navigation and drag and drop accordingly. The `layout` and `orientation` props can be used to change this behavior, allowing you to build horizontal stack, grid, and column layouts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
row, column, and grid layouts?
seems weird to say horizontal stack but not vertical stack, and CSS flex refers to them as row and column, so I think that's natural to say. where as stacks are really from iOS and a few other places that aren't web
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i've been going back and forth while reviewing, because I do like layout: stack | grid
because you could implement the grid using flex, so layout: flex | grid
doesn't make sense, so I can understand using horizontal stack as well, but I still think we should choose either horizontal/vertical stack and grid
or stack and grid
or row, column, and grid
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah flex
doesn't really describe how the items are arranged. I said vertical stack in the previous sentence when describing the default so I didn't think I needed to repeat that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personally, "column" layout is a bit confusing since my initial interpretation was "don't grids already have columns"? I like allowing you to build a horizontal stack or a grid
personally, maybe further explaining that you can control the way the grid would "wrap" its contents persay (aka pointing out that it is like https://developer.mozilla.org/en-US/docs/Web/CSS/flex-flow, or at least thats how I think about it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Behavior looks good to me, tested the RAC story w/ DnD in a variety of locales + orientation/layout combos. Just some questions and requests for clarifications
@@ -568,6 +568,239 @@ import {Text} from 'react-aria-components'; | |||
</ListBox> | |||
``` | |||
|
|||
### Horizontal layout | |||
|
|||
By default, ListBox expects items to be arranged in a vertical stack, and implements keyboard navigation and drag and drop accordingly. The `layout` and `orientation` props can be used to change this behavior, allowing you to build horizontal stack, grid, and column layouts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
personally, "column" layout is a bit confusing since my initial interpretation was "don't grids already have columns"? I like allowing you to build a horizontal stack or a grid
personally, maybe further explaining that you can control the way the grid would "wrap" its contents persay (aka pointing out that it is like https://developer.mozilla.org/en-US/docs/Web/CSS/flex-flow, or at least thats how I think about it).
|
||
### Column layout | ||
|
||
The `layout="grid"` and `orientation="horizontal"` props can be combined to build a two dimensional grid where the items are grouped into columns, and the grid scrolls horizontally. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, I felt it kinda confusing that this is called "Column layout" since the items are also grouped into columns in "Grid layout". Maybe state instead that items fill each column's height before wrapping/moving to the next column?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for your question about if some of this should be split out into a separate GridKeyboardDelegate, IMO it is fine to stay in here since it isn't a true grid movement pattern if that makes sense. The contents of a layout="grid"
are "wrapping" in a specific direction rather than being truly laid out in a grid layout and thus left/right or up/down will move focus onto the next row/column depending on the orientation.
@@ -35,11 +104,22 @@ export class ListDropTargetDelegate implements DropTargetDelegate { | |||
let item = items[mid]; | |||
let element = elementMap.get(String(item.key)); | |||
let rect = element.getBoundingClientRect(); | |||
let update = (isGreater: boolean) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mind adding a comment or two to this code block (aka the stuff within the chain of if statements)? At a glance I think this is narrowing down which item in the collection to consider the drop target with respect to LTR/RTL + the primary/secondary directions but I can't easily tell if the else
at the end only happens once that is determined.
private isSameRow(prevRect: DOMRect, itemRect: DOMRect) { | ||
return prevRect.top === itemRect.top || prevRect.left !== itemRect.left; | ||
} | ||
|
||
private isSameColumn(prevRect: DOMRect, itemRect: DOMRect) { | ||
return prevRect.left === itemRect.left || prevRect.top !== itemRect.top; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have to account for RTL here? Thinking about the case where the rects in a list aren't uniform in width and thus it matters whether or not we are looking at the rect.left
or rect.right
Build successful! 🎉 |
Build successful! 🎉 |
## API Changes
unknown top level export { type: 'identifier', name: 'Column' } @react-aria/dndDragPreview ListDropTargetDelegate {
- constructor: (Collection<Node<unknown>>, RefObject<HTMLElement>) => void
+ constructor: (Collection<Node<unknown>>, RefObject<HTMLElement>, ListDropTargetDelegateOptions) => void
getDropTargetFromPoint: (number, number, (DropTarget) => boolean) => DropTarget
} @react-aria/selectionListKeyboardDelegate ListKeyboardDelegate<T> {
constructor: (Array<any>) => void
getFirstKey: () => void
getKeyAbove: (Key) => void
getKeyBelow: (Key) => void
getKeyForSearch: (string, Key) => void
getKeyLeftOf: (Key) => void
getKeyPageAbove: (Key) => void
getKeyPageBelow: (Key) => void
getKeyRightOf: (Key) => void
getLastKey: () => void
+ getNextKey: (Key) => void
+ getPreviousKey: (Key) => void
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This adds two new props to the RAC ListBox, as well as the
ListKeyboardDelegate
andListDropTargetDelegate
:layout
, andorientation
. This enables creating lists that are arranged in a horizontal stack, or a vertically or horizontally scrolling 2d grid (e.g. cards). Selection and keyboard navigation already worked, butuseDroppableCollection
did require some work to get left/right arrows working (as mentioned in #4506).There is some complexity to handling all of these combinations (along with RTL) in the same delegates. I tried making separate
GridKeyboardDelegate
andGridDropTargetDelegate
implementations but found I had to duplicate a lot of code vs just adding options. Open to changing it back if people think it's weird to have it all in the list implementations.