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
Add TreeView.LeadingAction
sub-component
#4546
Conversation
🦋 Changeset detectedLatest commit: 29348d6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
size-limit report 📦
|
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 have some initial feedback, but I really think we need somebody from a11y to take a look. For example, I'm not sure the "right"/accessible way to add interactive controls inside of a role="treeitem"
.
It also looks like consumers will have to write their own drag-and-drop functionality since there are no props to handle onDragStart
/onDragEnd
callbacks. Also, we should agree on the right designs/interactions for a tree view when it's actively being dragged. For example:
- What does the node look like when the user is dragging it? Probably just knock the opacity down to make it look like a "ghost" of the original node.
- How do we indicate the prospective new position of the node while it's being dragged? Probably a blue line that acts like a cursor to show what position the node will be in after it's dropped.
const [isLoading, setIsLoading] = React.useState(false) | ||
const [asyncItems, setAsyncItems] = React.useState<string[]>([]) | ||
|
||
let state: SubTreeState = 'initial' | ||
|
||
if (isLoading) { | ||
state = 'loading' | ||
} else if (asyncItems.length > 0) { | ||
state = 'done' | ||
} |
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 don't think we need to include the loading functionality for this story. We can just focus on the drag-and-drop behavior.
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.
+1
{dragHandle ? ( | ||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions | ||
<div className="PRIVATE_TreeView-item-drag-handle" onMouseDown={() => setIsExpanded(false)}> | ||
{dragHandle} |
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 don't think we should require folks to style their own drag handles. We should ship with a default recommended style.
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.
+1 to this idea. The internal consistency will help users understand what the UI does.
@@ -337,6 +358,7 @@ export type TreeViewItemProps = { | |||
onExpandedChange?: (expanded: boolean) => void | |||
onSelect?: (event: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void | |||
className?: string | |||
dragHandle?: ReactElement |
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'm not sure we need to require folks to pass dragHandle
if they're passing dragAndDrop
on the parent.
If we have a tree view where some nodes are draggable and others are not, we could assume all nodes are draggable by default. Then, we could add a prop to TreeView.Item
that allows people to disable drag-and-drop functionality.
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.
+1 to this one as well. I don't think we'll have a scenario where individual tree nodes would be prevented from being dragged and dropped?
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 don't think we'll have a scenario where individual tree nodes would be prevented from being dragged and dropped?
@ericwbailey we could. If the user didn't have access to see an item, they wouldn't be able to drag it.
@@ -488,6 +511,12 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>( | |||
<div style={{gridArea: 'spacer', display: 'flex'}}> | |||
<LevelIndicatorLines level={level} /> | |||
</div> | |||
{dragHandle ? ( | |||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions | |||
<div className="PRIVATE_TreeView-item-drag-handle" onMouseDown={() => setIsExpanded(false)}> |
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.
How would a keyboard user drag and drop?
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 can resolve this one for now, with this overall approach being understood.
@@ -488,6 +511,12 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>( | |||
<div style={{gridArea: 'spacer', display: 'flex'}}> | |||
<LevelIndicatorLines level={level} /> | |||
</div> | |||
{dragHandle ? ( | |||
// eslint-disable-next-line jsx-a11y/no-static-element-interactions | |||
<div className="PRIVATE_TreeView-item-drag-handle" onMouseDown={() => setIsExpanded(false)}> |
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.
After I interact with the drag handle, I can't get focus to go back into the tree view. Maybe I'm just doing something wrong - so you and other reviewers should check to make sure you can reproduce this bug.
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.
Not just you, I can reproduce this as well. The focus shifts to body after interacting with the drag handle
focus.shifts.to.body.mov
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.
only show dragHandle when user hovers over TreeView.Item
One small thing I'd love to sneak in: can we also show the DragHandle via :focus-visible
? That way it will also be shown to keyboard-only user.
I have some initial feedback, but I really think we need somebody from a11y to take a look. For example, I'm not sure the "right"/accessible way to add interactive controls inside of a role="treeitem".
This interaction pattern is really unique to the web, so it's going to take some time to research this. The other thought here is that my understanding of the shape of this work is getting initial functionality in for demo purposes, and then follow up with accessibility tweaks so it does not block the work.
With that context, I think we can consider this merge-able once other things folks reviewing have pointed out have been addressed. I simply don't know what will be needed yet. We have a plan in place for discovering that, but it will take time.
So, I'd consider this an approval from accessibility at a high level with the idea that there may need to be larger structural updates to follow as we learn what they'll be.
@mperrotti @ericwbailey the initial idea was to just have some space before the expand/collapse toggle where users can place any element. I used If we were to ship with a default recommended style to always have the I think the better way to put this is: do you have recommendations on what we could do if we just wanted to place an element before the TreeView.Item toggle? The consumer would handle what that element does (i.e clicking on it, dragging it, etc). |
@ericwbailey Do you know if should add an |
@siddharthkp Yup, that'd be great. Ultimately, we'd need it and also |
Just noticed the changes made since my last review, looks like that would have to live in the application instead of primer/react because primer does not control the contents of the slot :( |
@@ -459,7 +463,7 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>( | |||
id={itemId} | |||
role="treeitem" | |||
aria-labelledby={labelId} | |||
aria-describedby={`${leadingVisualId} ${trailingVisualId}`} | |||
aria-describedby={`${leadingActionId} ${leadingVisualId} ${trailingVisualId}`} |
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.
Don't think the action should be used to describe the item, especially when the action itself is not accessible (aria-hidden + tab-index=-1) 🤔
cc @ericwbailey for confirmation
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.
aria-hidden
should hypothetically override this, but yes, I think we should remove it.
We'll likely want to include a hint down the line in the aria-label
about state, such as "draggable". That's something we'll be researching with TetraLogical.
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.
@ayy-bc Final thing for you to look at :)
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.
@siddharthkp I was just following what LeadingVisual
and TrailingVisual
in this case. Looking at it again, I think removing this makes sense and we can add the required labels to the components itself when we pass 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.
I can remove it or you can do so, whatever you prefer (lmk).
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.
@siddharthkp I was just following what LeadingVisual and TrailingVisual in this case. Looking at it again, I think removing this makes sense
I can remove it or you can do so, whatever you prefer (lmk).
Yes please, I think actions have to be treated differently than visuals
we can add the required labels to the components itself when we pass it.
Makes sense, but be careful about keyboard navigation. For example, the toggle element (picture below) is not accessible by keyboard and hence does not have a label and is aria-hidden
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.
@siddharthkp pushed the changes
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.
Final conversation left to resolve and then we're good to go: f9b880d#r1598579584
@siddharthkp replied to your comment above with some more context :) |
@@ -45,6 +45,7 @@ const ItemContext = React.createContext<{ | |||
setIsExpanded: (isExpanded: boolean) => void | |||
leadingVisualId: string | |||
trailingVisualId: string | |||
leadingActionId: string |
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.
@ayy-bc Don't think we need this anymore either, removed it :)
@mperrotti Hi 👋 Can I get a review for the snapshots? |
* add grid area for drag handle when data-drag-and-drop is true * add test to verify dnd attribute * css updates * add docs * test(vrt): update snapshots * Sid/treeview leading action (#4569) * wip: leading action slot * clean up a little * change from prop to subcomponent * remove outdated test * spacer should come before leadingAction * merge snapshots from main * merge package-lock from main * add visual tests * use IconButton for leadingAction * add example of drag handle on hover * Create tame-nails-live.md * test(vrt): update snapshots * change LeadingActio type to React.FC<TreeViewVisualProps> and accept children * change LeadingAction of type React.FC<TreeViewVisualProps> * typo * add `variant="invisible"` to icon button in stories * add leadingActionId and aria-hidden to LeadingAction subcomponent * remove `leadingActionId` to describe the tree view item * remove unused leadingActionId * remove docs from changeset --------- Co-authored-by: ayy-bc <[email protected]> Co-authored-by: Siddharth Kshetrapal <[email protected]> Co-authored-by: siddharthkp <[email protected]>
Closes #4542
Changelog
This PR adds
TreeView.LeadingAction
sub component (like the leading visual, trailing visual) to create a slot for elements that go before the toggle and can be used for various use cases such as a drag handle.Rollout strategy
Testing & Reviewing
Merge checklist