Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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/rotten-hairs-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@primer/react': minor
Comment thread
agreenberry marked this conversation as resolved.
Outdated
---

aria status description is now accurate
Comment thread
agreenberry marked this conversation as resolved.
Outdated
78 changes: 78 additions & 0 deletions src/TreeView/TreeView.features.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,84 @@ export const EmptyDirectories: Story = () => {
)
}

export const NestedTrees: Story = () => {
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'
}

return (
<nav aria-label="Files">
<TreeView aria-label="Files">
<TreeView.Item id="file-1">
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Some file
</TreeView.Item>
<TreeView.Item
id="async-directory"
onExpandedChange={async isExpanded => {
if (asyncItems.length === 0 && isExpanded) {
setIsLoading(true)

// Load items
const items = await loadItems(1000)

setIsLoading(false)
setAsyncItems(items)
}
}}
>
<TreeView.LeadingVisual>
<TreeView.DirectoryIcon />
</TreeView.LeadingVisual>
Directory with async items
<TreeView.SubTree state={state}>
{asyncItems.map(item => (
<TreeView.Item id={`item-${item}`} key={item}>
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
{item}
</TreeView.Item>
))}
<TreeView.Item id="nested-directory">
Nested Sub-tree
<TreeView.SubTree state="done">
<TreeView.Item id="nested-directory/file-1">
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Some file
</TreeView.Item>
<TreeView.Item id="nested-directory/another-file">
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Another file
</TreeView.Item>
</TreeView.SubTree>
</TreeView.Item>
</TreeView.SubTree>
</TreeView.Item>
<TreeView.Item id="another-file">
<TreeView.LeadingVisual>
<FileIcon />
</TreeView.LeadingVisual>
Another file
</TreeView.Item>
</TreeView>
</nav>
)
}

export const NestedScrollContainer: Story = () => {
return (
<Box sx={{maxHeight: '50vh', overflow: 'auto'}}>
Expand Down
10 changes: 9 additions & 1 deletion src/TreeView/TreeView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1077,7 +1077,14 @@ describe('Asyncronous loading', () => {
<TreeView.Item id="parent" defaultExpanded>
Parent
<TreeView.SubTree state={state}>
<TreeView.Item id="child">Child</TreeView.Item>
<TreeView.Item id="child-item">Child Item</TreeView.Item>
<TreeView.Item id="child-subtree">
Child Subtree
<TreeView.SubTree state={'done'}>
<TreeView.Item id="child-subtree-1">Child 1</TreeView.Item>
<TreeView.Item id="child-subtree-2">Child 2</TreeView.Item>
</TreeView.SubTree>
</TreeView.Item>
</TreeView.SubTree>
</TreeView.Item>
</TreeView>
Expand All @@ -1100,6 +1107,7 @@ describe('Asyncronous loading', () => {
})

// Live region should be updated
expect(liveRegion).not.toHaveTextContent('Child 2 is empty')
expect(liveRegion).toHaveTextContent('Parent content loaded')
})

Expand Down
16 changes: 13 additions & 3 deletions src/TreeView/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const UlBox = styled.ul<SxProp>`
* We define styles for the tree items at the root level of the tree
* to avoid recomputing the styles for each item when the tree updates.
* We're sacraficing maintainability for performance because TreeView
* needs to be performant enough to handle large trees (thousands of items).
* needs to be performant enough to handle large trees (thousands of items).
*
* This is intended to be a temporary solution until we can improve the
* performance of our styling patterns.
Expand Down Expand Up @@ -402,6 +402,7 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>(
aria-labelledby={labelId}
aria-describedby={`${leadingVisualId} ${trailingVisualId}`}
aria-level={level}
// aria-expanded={isSubTreeEmpty ? undefined : isExpanded}
Comment thread
agreenberry marked this conversation as resolved.
Outdated
aria-expanded={isSubTreeEmpty ? undefined : isExpanded}
aria-current={isCurrentItem ? 'true' : undefined}
onKeyDown={handleKeyDown}
Expand Down Expand Up @@ -508,6 +509,13 @@ const SubTree: React.FC<TreeViewSubTreeProps> = ({count, state, children}) => {
const {safeSetTimeout} = useSafeTimeout()
const loadingItemRef = React.useRef<HTMLElement>(null)
const ref = React.useRef<HTMLElement>(null)
const [isPending, setPending] = React.useState(state === 'loading')

React.useEffect(() => {
if (state === 'loading') {
setPending(true)
}
}, [state])

React.useEffect(() => {
// If `state` is undefined, we're working in a synchronous context and need
Expand All @@ -525,7 +533,7 @@ const SubTree: React.FC<TreeViewSubTreeProps> = ({count, state, children}) => {

// Announce when content has loaded
React.useEffect(() => {
if (state === 'done') {
if (isPending && state === 'done') {
Comment thread
agreenberry marked this conversation as resolved.
Comment thread
agreenberry marked this conversation as resolved.
const parentItem = document.getElementById(itemId)

if (!parentItem) return
Expand All @@ -540,8 +548,10 @@ const SubTree: React.FC<TreeViewSubTreeProps> = ({count, state, children}) => {
announceUpdate(`${parentName} is empty`)
}
})

setPending(false)
}
}, [state, itemId, announceUpdate, safeSetTimeout])
}, [state, itemId, announceUpdate, safeSetTimeout, isPending])

// Manage loading indicator state
React.useEffect(() => {
Expand Down