diff --git a/packages/dev/s2-docs/pages/react-aria/Tree.mdx b/packages/dev/s2-docs/pages/react-aria/Tree.mdx index 590a4ce1c26..7c21ad49f84 100644 --- a/packages/dev/s2-docs/pages/react-aria/Tree.mdx +++ b/packages/dev/s2-docs/pages/react-aria/Tree.mdx @@ -448,15 +448,17 @@ function Example() { ## API + -```tsx links={{Tree: '#tree', TreeItem: '#treeitem', TreeItemContent: '#treeitemcontent', TreeSection: '#treesection', TreeHeader: '#treeheader', Button: 'Button', Checkbox: 'Checkbox', TreeLoadMoreItem: '#treeloadmoreitem', SelectionIndicator: 'selection#animated-selectionindicator'}} +```tsx links={{Tree: '#tree', TreeItem: '#treeitem', TreeItemContent: '#treeitemcontent', TreeSection: '#treesection', TreeHeader: '#treeheader', Button: 'Button', Checkbox: 'Checkbox', TreeLoadMoreItem: '#treeloadmoreitem', Text: 'Text', SelectionIndicator: 'selection#animated-selectionindicator'}} or + {/* ... */} diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index c2a05801f2b..5f2f9069a0f 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -107,6 +107,7 @@ import React, { } from 'react'; import {SelectionIndicatorContext} from './SelectionIndicator'; import {SharedElementTransition} from './SharedElementTransition'; +import {TextContext} from './Text'; import {TreeDropTargetDelegate} from './TreeDropTargetDelegate'; import {TreeState, useTreeState} from 'react-stately/useTreeState'; import {useCachedChildren} from 'react-aria/private/collections/useCachedChildren'; @@ -782,8 +783,6 @@ export const TreeItem = /*#__PURE__*/ createBranchComponent( let isDraggable = dragState && !(dragState.isDisabled || dragState.selectionManager.isDisabled(item.key)); - // TODO: remove this when we support description in tree row - // eslint-disable-next-line @typescript-eslint/no-unused-vars let {rowProps, gridCellProps, expandButtonProps, descriptionProps, ...states} = useTreeItem( { node: item, @@ -1020,7 +1019,7 @@ export const TreeItem = /*#__PURE__*/ createBranchComponent( } } ], - // TODO: support description in the tree row + [TextContext, {slots: {[DEFAULT_SLOT]: {}, description: descriptionProps}}], // TODO: don't think I need to pass isExpanded to the button here since it can be sourced from the renderProps? Might be worthwhile passing it down? [ ButtonContext, diff --git a/packages/react-aria-components/stories/Tree.stories.tsx b/packages/react-aria-components/stories/Tree.stories.tsx index 8acea296766..e3fc5fdad12 100644 --- a/packages/react-aria-components/stories/Tree.stories.tsx +++ b/packages/react-aria-components/stories/Tree.stories.tsx @@ -298,6 +298,36 @@ const TreeExampleStaticNoActionsRender = (args: TreeProps): JS ); +export const WithDescriptions: TreeStory = args => ( + + + + {({isExpanded}) => ( + <> + {isExpanded ? '⏷' : '⏵'} + + Projects + + Shared project files + + + > + )} + + + + + Report + + Last updated today + + + + + + +); + export const TreeExampleStatic: StoryObj = { render: args => , args: { diff --git a/packages/react-aria-components/test/Tree.test.tsx b/packages/react-aria-components/test/Tree.test.tsx index c0087e78fd9..8081c9aca2c 100644 --- a/packages/react-aria-components/test/Tree.test.tsx +++ b/packages/react-aria-components/test/Tree.test.tsx @@ -411,6 +411,47 @@ describe('Tree', () => { } }); + describe('descriptions', () => { + function DescriptionTree({ + hideDescription = false, + itemAriaLabel + }: { + hideDescription?: boolean; + itemAriaLabel?: string; + }) { + return ( + + + + Projects + {!hideDescription && ( + + Shared files + + )} + + + + ); + } + + it('includes the description in the row name', () => { + let {getByRole} = render(); + expect(getByRole('row')).toHaveAccessibleName('Projects Shared files'); + }); + + it('includes the description with an explicit item aria-label', () => { + let {getByRole} = render(); + expect(getByRole('row')).toHaveAccessibleName('Team projects Shared files'); + }); + + it('keeps the row name when there is no description', () => { + let {getByRole} = render(); + expect(getByRole('row')).toHaveAccessibleName('Projects'); + expect(getByRole('row')).not.toHaveAttribute('aria-labelledby'); + }); + }); + it('should render a Tree with custom classes', () => { let {getByRole, getAllByRole} = render(