diff --git a/.changeset/breezy-bobcats-explain.md b/.changeset/breezy-bobcats-explain.md
new file mode 100644
index 00000000000..541de9f31d6
--- /dev/null
+++ b/.changeset/breezy-bobcats-explain.md
@@ -0,0 +1,5 @@
+---
+"@primer/react": patch
+---
+
+TreeView: Add `TreeView.LoadingItem` component
diff --git a/docs/content/TreeView.mdx b/docs/content/TreeView.mdx
index 931faf43bb1..0c0250368c5 100644
--- a/docs/content/TreeView.mdx
+++ b/docs/content/TreeView.mdx
@@ -181,6 +181,10 @@ Since stateful directory icons are a common use case for TreeView, we provide a
```
+### With asynchronously loaded items
+
+See [Storybook](https://primer.style/react/storybook?path=/story/components-treeview--async-success) for examples with asynchronously loaded items.
+
## Props
### TreeView
@@ -266,6 +270,10 @@ Since stateful directory icons are a common use case for TreeView, we provide a
{/* */}
+### TreeView.LoadingItem
+
+{/* */}
+
### TreeView.SubTree
@@ -299,8 +307,6 @@ Since stateful directory icons are a common use case for TreeView, we provide a
{/* */}
-
-
## Status
setTimeout(resolve, ms))
+}
+
+async function loadItems(responseTime: number) {
+ await wait(responseTime)
+ return ['Avatar.tsx', 'Button.tsx', 'Checkbox.tsx']
+}
+
+export const AsyncSuccess: Story = args => {
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [asyncItems, setAsyncItems] = React.useState([])
+
+ return (
+
+
+
+ )
+}
+
+AsyncSuccess.args = {
+ responseTime: 2000
+}
+
+async function alwaysFails(responseTime: number) {
+ await wait(responseTime)
+ throw new Error('Failed to load items')
+ return []
+}
+
+export const AsyncError: Story = args => {
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [isExpanded, setIsExpanded] = React.useState(false)
+ const [asyncItems, setAsyncItems] = React.useState([])
+ const [error, setError] = React.useState(null)
+
+ async function loadItems() {
+ if (asyncItems.length === 0) {
+ // Show loading indicator after a short delay
+ const timeout = setTimeout(() => setIsLoading(true), 300)
+ try {
+ // Try to load items
+ const items = await alwaysFails(args.responseTime)
+ setAsyncItems(items)
+ } catch (error) {
+ setError(error as Error)
+ } finally {
+ clearTimeout(timeout)
+ setIsLoading(false)
+ }
+ }
+ }
+
+ return (
+
+
+
+ )
+}
+
+AsyncError.args = {
+ responseTime: 2000
+}
+
export default meta
diff --git a/src/TreeView/TreeView.tsx b/src/TreeView/TreeView.tsx
index 5aea8195772..b9b816bb363 100644
--- a/src/TreeView/TreeView.tsx
+++ b/src/TreeView/TreeView.tsx
@@ -8,8 +8,9 @@ import {useSSRSafeId} from '@react-aria/ssr'
import React from 'react'
import styled from 'styled-components'
import Box from '../Box'
-import StyledOcticon from '../StyledOcticon'
import {useControllableState} from '../hooks/useControllableState'
+import Spinner from '../Spinner'
+import StyledOcticon from '../StyledOcticon'
import sx, {SxProp} from '../sx'
import Text from '../Text'
import {Theme} from '../ThemeProvider'
@@ -282,7 +283,6 @@ const Item: React.FC = ({
>
{slots => (
- // QUESTION: How should leading and trailing visuals impact the aria-label?
<>
{slots.LeadingVisual}
= ({href, onSelect, ...props}) =
)
}
+// ----------------------------------------------------------------------------
+// TreeView.LoadingItem
+
+const LoadingItem: React.FC = () => {
+ return (
+ // TODO: What aria attributes do we need to add here?
+ -
+
+
+
+ Loading...
+
+ )
+}
+
+LoadingItem.displayName = 'TreeView.LoadingItem'
+
// ----------------------------------------------------------------------------
// TreeView.SubTree
@@ -417,7 +434,7 @@ const LeadingVisual: React.FC = props => {
const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children
return (
- {children}
+ {children}
)
}
@@ -427,7 +444,7 @@ const TrailingVisual: React.FC = props => {
const children = typeof props.children === 'function' ? props.children({isExpanded}) : props.children
return (
- {children}
+ {children}
)
}
@@ -448,6 +465,7 @@ const DirectoryIcon = () => {
export const TreeView = Object.assign(Root, {
Item,
LinkItem,
+ LoadingItem,
SubTree,
LeadingVisual,
TrailingVisual,