Skip to content
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

AvatarStack: Add keyboard support to AvatarStack #5134

Merged
merged 12 commits into from
Oct 25, 2024

Conversation

TylerJDev
Copy link
Contributor

@TylerJDev TylerJDev commented Oct 16, 2024

Closes https://github.com/github/primer/issues/3348

Changelog

New

  • Adds keyboard support to AvatarStack

Changed

Removed

Rollout strategy

  • Patch release
  • Minor release
  • Major release; if selected, include a written rollout or migration plan
  • None; if selected, include a brief description as to why

Testing & Reviewing

Merge checklist

Copy link

changeset-bot bot commented Oct 16, 2024

🦋 Changeset detected

Latest commit: 321553a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@primer/react Minor

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

Copy link
Contributor

👋 Hi, this pull request contains changes to the source code that github/github depends on. If you are GitHub staff, we recommend testing these changes with github/github using the integration workflow. Thanks!

@github-actions github-actions bot added the integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm label Oct 16, 2024
@github-actions github-actions bot temporarily deployed to storybook-preview-5134 October 16, 2024 16:59 Inactive
Copy link
Contributor

github-actions bot commented Oct 16, 2024

size-limit report 📦

Path Size
packages/react/dist/browser.esm.js 97.35 KB (+0.31% 🔺)
packages/react/dist/browser.umd.js 97.59 KB (+0.21% 🔺)

@github-actions github-actions bot temporarily deployed to storybook-preview-5134 October 16, 2024 17:06 Inactive
@TylerJDev TylerJDev marked this pull request as ready for review October 16, 2024 17:08
@TylerJDev TylerJDev requested a review from a team as a code owner October 16, 2024 17:08
Copy link
Member

@joshblack joshblack left a comment

Choose a reason for hiding this comment

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

Looking great! Left a couple comments/questions/suggestions 👀

@@ -0,0 +1,23 @@
const interactiveElements = [
Copy link
Member

Choose a reason for hiding this comment

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

One package we could use for this: https://www.npmjs.com/package/focusable-selectors (either as a dependency or inline with accreditation) that has a pretty robust list 👀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely! Added some more selectors that could be handy.

'[tabindex="0"]',
]

export function getInteractiveNodes(node: HTMLElement | null, ignoreSelectors?: string) {
Copy link
Member

Choose a reason for hiding this comment

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

One style suggestion, since we're returning a boolean this could be hasInteractiveNodes as opposed to getInteractiveNodes which might seem like we are returning the list of interactive nodes within the given parent node.

Suggested change
export function getInteractiveNodes(node: HTMLElement | null, ignoreSelectors?: string) {
export function hasInteractiveContent(node: HTMLElement | null, ignoreSelectors?: string): boolean {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's a good callout. I changed the names of the function + file!

@@ -195,6 +203,9 @@ const AvatarStack = ({
className,
sx: sxProp = defaultSxProp,
}: AvatarStackProps) => {
const [hasInteractiveChildren, setHasInteractiveChildren] = useState<boolean | undefined>(false)
const AvatarStackContainer = useRef(null)
Copy link
Member

Choose a reason for hiding this comment

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

style nit (and definitely non blocking lol) we could use lowercase for useRef here since we typically only use pascal case for component names or modules

Suggested change
const AvatarStackContainer = useRef(null)
const avatarStackContainer = useRef(null)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Didn't realize I was using pascal case 🤦 Should be updated now!

useEffect(() => {
const hasInteractive = getInteractiveNodes(AvatarStackContainer.current, '[tabindex]')
setHasInteractiveChildren(hasInteractive)
}, [children])
Copy link
Member

Choose a reason for hiding this comment

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

Would this be equivalent to:

useEffect(() => {
  // ...
})

I wasn't sure if children would change each render or not if they are not memoized. If that's the case, would it be worth running as a mutation observer, potentially?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could! My assumption is that we'd want to utilize the mutation observer inside of hasInteractiveNodes?

Copy link
Member

Choose a reason for hiding this comment

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

I think you could use it in there if the function was updated to have a callback/subscriber for when it changed or could use the mutation observer directly and call hasInteractiveNodes in it, if that makes sense. Put another way:

Suggested change
}, [children])
useEffect(() => {
const observer = new MutationObserver((entries) => {
setHasInteractiveChildren(hasInteractiveContent(ref.current));
});
observer.observer(ref.current, {
/* whatever options make sense */
});
return () => {
observer.disconnect();
};
}, []);

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 see, this is great! Thank you for the example too. I'll rework this a bit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After implementing this, I realize that the need for checking the children for changes is probably very slim 😅 I removed the children dependency so it should only run on the initial render, as that's the only time we'd actually want to run this function/check.

@primer-integration
Copy link

primer-integration bot commented Oct 16, 2024

🟢 golden-jobs completed with status success.

@github-actions github-actions bot temporarily deployed to storybook-preview-5134 October 21, 2024 16:04 Inactive
@primer-integration
Copy link

👋 Hi from github/github! Your integration PR is ready: https://github.com/github/github/pull/348018

@TylerJDev
Copy link
Contributor Author

Requires small changes in Dotcom for tests, https://github.com/github/github/pull/348018/commits/833ad355c270f37218f3bcc4071319846b0e552a. Removes empty space that AvatarStack previously had. More context at https://github.com/github/memex/issues/16409.

Comment on lines 263 to 266
useEffect(() => {
const hasInteractive = hasInteractiveNodes(avatarStackContainer.current, 'div[tabindex]')
setHasInteractiveChildren(hasInteractive)
}, [])
Copy link
Member

Choose a reason for hiding this comment

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

With the move to not running on children changing, wanted to ask if this is something that should be setup in a mutation observer for when contents change in case the "hasInteractiveNodes" check could 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.

We could! I was thinking that we'd know whether to make the container interactive or not based on the initial render as I don't think there are cases where interactive items are rendered in after the first render. But we could add a mutation observer just to be safe.

@TylerJDev TylerJDev added this pull request to the merge queue Oct 25, 2024
Merged via the queue into main with commit 6713e72 Oct 25, 2024
43 checks passed
@TylerJDev TylerJDev deleted the tylerjdev/avatarstack-keyboard-support branch October 25, 2024 13:23
@primer primer bot mentioned this pull request Oct 25, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
integration-tests: recommended This change needs to be tested for breaking changes. See https://arc.net/l/quote/tdmpakpm
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants