Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/two-pots-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
---

TreeView: Add `count` and `className` support for trailing actions
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export const TrailingActions: StoryFn = () => {
label: 'Pull Requests',
onClick: () => alert('Pull Requests clicked'),
icon: GitPullRequestIcon,
count: 5,
},
]}
>
Expand Down
78 changes: 56 additions & 22 deletions packages/react/src/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import {useTypeahead} from './useTypeahead'
import {SkeletonAvatar} from '../SkeletonAvatar'
import {SkeletonText} from '../SkeletonText'
import {Dialog} from '../Dialog/Dialog'
import {IconButton} from '../Button'
import {Button, IconButton} from '../Button'
import {ActionList} from '../ActionList'
import {getAccessibleKeybindingHintString} from '../KeybindingHint'
import {useIsMacOS} from '../hooks'
import {Tooltip} from '../TooltipV2'

// ----------------------------------------------------------------------------
// Context
Expand Down Expand Up @@ -80,6 +81,8 @@ export type TreeViewSecondaryActions = {
label: string
onClick: () => void
icon: Icon
count?: number | string
className?: string
}

/* Size of toggle icon in pixels. */
Expand Down Expand Up @@ -739,7 +742,7 @@ const TrailingAction = (props: TreeViewTrailingAction) => {
return (
<>
<div id={trailingActionId} className={clsx('PRIVATE_VisuallyHidden', classes.TreeViewVisuallyHidden)}>
; {shortcutText}
- {shortcutText}
Copy link
Preview

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

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

Changed semicolon to hyphen in visually hidden text. This appears to be an unintended character change that should be reverted.

Suggested change
- {shortcutText}
; {shortcutText}

Copilot uses AI. Check for mistakes.

</div>
<div
className={classes.TreeViewItemTrailingAction}
Expand All @@ -751,25 +754,50 @@ const TrailingAction = (props: TreeViewTrailingAction) => {
}
onKeyDown={event => event.stopPropagation()}
>
{items.map(({label, onClick, icon}, index) => (
<IconButton
icon={icon}
variant="invisible"
aria-label={label}
className={classes.TreeViewItemTrailingActionButton}
onClick={onClick}
tabIndex={-1}
aria-hidden={true}
key={index}
onKeyDown={() => {
// hack to send focus back to the tree item after the action is triggered via click
// this is needed because the trailing action shouldn't be focused, as it does not interact well with
// the focus management of TreeView
const parentElement = document.getElementById(itemId)
parentElement?.focus()
}}
/>
))}
{items.map(({label, onClick, icon, count, className}, index) => {
// If there is a count, we render a Button instead of an IconButton,
// as IconButton doesn't support displaying a count.
if (count) {
return (
<Tooltip key={index} text={label}>
<Button
aria-label={label}
leadingVisual={icon}
variant="invisible"
className={clsx(className, classes.TreeViewItemTrailingActionButton)}
onClick={onClick}
onKeyDown={() => {
// hack to send focus back to the tree item after the action is triggered via click
// this is needed because the trailing action shouldn't be focused, as it does not interact well with
// the focus management of TreeView
const parentElement = document.getElementById(itemId)
parentElement?.focus()
}}
Comment on lines +769 to +775
Copy link
Preview

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

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

The focus management logic is duplicated between the Button and IconButton implementations. Consider extracting this into a shared function to reduce code duplication.

Copilot uses AI. Check for mistakes.

tabIndex={-1}
aria-hidden={true}
count={count}
/>
</Tooltip>
)
}

return (
<IconButton
icon={icon}
variant="invisible"
aria-label={label}
className={clsx(className, classes.TreeViewItemTrailingActionButton)}
onClick={onClick}
tabIndex={-1}
aria-hidden={true}
key={index}
onKeyDown={() => {
const parentElement = document.getElementById(itemId)
parentElement?.focus()
}}
/>
)
})}
</div>
</>
)
Expand Down Expand Up @@ -816,12 +844,18 @@ const ActionDialog: React.FC<TreeViewActionDialogProps> = ({items, onClose}) =>
}}
>
<ActionList>
{items.map(({label, onClick, icon: Icon}, index) => (
{items.map(({label, onClick, icon: Icon, count}, index) => (
<ActionList.Item key={index} onSelect={onClick}>
<ActionList.LeadingVisual>
<Icon />
</ActionList.LeadingVisual>
{label}
{count ? (
<ActionList.TrailingVisual>
{count}
<VisuallyHidden>items</VisuallyHidden>
Copy link
Preview

Copilot AI Oct 6, 2025

Choose a reason for hiding this comment

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

The hardcoded 'items' text may not be appropriate for all use cases of count. Consider making this text configurable or using more generic language like 'count' to better represent various counting scenarios.

Copilot uses AI. Check for mistakes.

</ActionList.TrailingVisual>
) : null}
</ActionList.Item>
))}
</ActionList>
Expand Down
Loading