Skip to content

[PLA-11449] Improve NavigationDropdown keyboard accessibility #143

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

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

sampullman
Copy link
Contributor

@sampullman sampullman commented Jun 11, 2025

Currently, only arena rows are selectable. I considered allowing header rows to be selected, and opening/closing on enter, but it didn't seem useful enough to spend time on.

This is messier than I expected it to be, I may have missed some existing functionality of VirtualTree that would have made it easier. I'll leave a couple inline comments.

Edit: is the CLA Assistant supposed to automatically prompt me here? I'm not about the format for signing in a PR comment.

s2.mp4

Copy link

vercel bot commented Jun 11, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
plasmic-cms-i18n ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 13, 2025 4:00pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
react-email-demo ⬜️ Ignored (Inspect) Jun 13, 2025 4:00pm

Copy link

vercel bot commented Jun 11, 2025

@sampullman is attempting to deploy a commit to the Plasmic Team on Vercel.

A member of the Team first needs to authorize it.

@sampullman
Copy link
Contributor Author

Extracting useTreeData wasn't too bad, but the LeftGeneralTokensPanel.tsx refactor was a little more complicated that I expected, so it might be worth verifying those changes closely. Overall I think it's a good change, and it enabled a few more simplifications. I'll add a few inline comments!


const [selectedIndex, setSelectedIndex] = React.useState<number>();

// Reset selectionIndex when visibleNodes change
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems messy, but I couldn't think of a better way to ensure there is a valid selection when the tree first opens.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it feels a bit weird because everything is triggered bay prop changes instead of a function call. I think the iteration is okay, but it might be improved by having useTreeData expose a setQuery function (similar to selectNextRow). Then the parent component can call setQuery to trigger all the changes more naturally (e.g. call buildVisibleNodes, reset selectedIndex, recompute expanded nodes, etc).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explored this briefly, it looks like it will simplify some things but is a pretty substantial change. Would you rather I go for it here, or do it in parallel with other tasks (or leave it be for now)?

@@ -364,6 +358,14 @@ function NavigationDropdown_(
return { onClose };
}, [onClose]);

const navigateToArena = React.useCallback(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I extracted the arena navigation from NavigationRows and use a new onClick prop instead. I did have to move the ArenaTreeRow function into NavigationDropdown_ to make it work.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the ArenaTreeRow into here looks suspicious. ArenaTreeRow is considered a component by React, and now the component is being regenerated on every render, so React won't know that it's the same component.

What was the reason it needed to be moved in here? Is it depending on some data?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the correct thing here would be to make a getNodeAction or something similar in useTreeData so we can pass these actions in. But yeah, I think creating the component inside another component degrades React performance, so we want to avoid that.

Copy link
Contributor Author

@sampullman sampullman Jun 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that makes sense. I thought I saw some nested components here and there, but probably didn't understand the full context. I'll see about doing it via getNodeAction or similar!

Copy link
Member

@jaslong jaslong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking good!


const [selectedIndex, setSelectedIndex] = React.useState<number>();

// Reset selectionIndex when visibleNodes change
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it feels a bit weird because everything is triggered bay prop changes instead of a function call. I think the iteration is okay, but it might be improved by having useTreeData expose a setQuery function (similar to selectNextRow). Then the parent component can call setQuery to trigger all the changes more naturally (e.g. call buildVisibleNodes, reset selectedIndex, recompute expanded nodes, etc).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer keeping this in VirtualTree to make the diff easier to read. Or add a commit before this commit just for the move.

@@ -364,6 +358,14 @@ function NavigationDropdown_(
return { onClose };
}, [onClose]);

const navigateToArena = React.useCallback(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moving the ArenaTreeRow into here looks suspicious. ArenaTreeRow is considered a component by React, and now the component is being regenerated on every render, so React won't know that it's the same component.

What was the reason it needed to be moved in here? Is it depending on some data?

Copy link
Contributor

@IcaroG IcaroG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last point from me, all other stuff looks good.

@@ -364,6 +358,14 @@ function NavigationDropdown_(
return { onClose };
}, [onClose]);

const navigateToArena = React.useCallback(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the correct thing here would be to make a getNodeAction or something similar in useTreeData so we can pass these actions in. But yeah, I think creating the component inside another component degrades React performance, so we want to avoid that.

@sampullman
Copy link
Contributor Author

sampullman commented Jun 13, 2025

@jaslong @IcaroG I made the following changes, pinging because the re-review button isn't available.

  • Update the useTreeData refactoring commit to keep it in VirtualTree.tsx
  • Move ArenaTreeRow back out of the NavigationDropdown_ scope

Edit: rebased against master

renderElement: RenderElement<T>;
defaultOpenKeys?: "all" | Key[];
nodeData;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing nodeData type?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right, I somehow messed that up when merging useTreeData back to VirtualTree.tsx. I'm used to relying on noImplicitAny 😅

interface LinearTreeNode<T> {
type SelectDirection = 1 | -1;

type NodeAction<T> = (node: T) => void | Promise<void>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the return should probably be Promise<void> only, so the user has to always handle it.

Copy link
Contributor

@IcaroG IcaroG left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will wait for Jason's review as well

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants