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,