From e97bce1839ae448edbb3a6acd076eb2920a42d3f Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 6 Feb 2025 16:34:02 +1100 Subject: [PATCH 01/22] docs: RAC Tree docs --- packages/react-aria-components/docs/Tree.mdx | 250 +++++++++++++++++- .../docs/TreeAnatomy.svg | 194 ++++++++++++++ .../docs/examples/file-system.mdx | 163 ++++++++++++ .../docs/examples/file-system.png | Bin 0 -> 135119 bytes packages/react-aria-components/src/Tree.tsx | 10 +- 5 files changed, 608 insertions(+), 9 deletions(-) create mode 100644 packages/react-aria-components/docs/TreeAnatomy.svg create mode 100644 packages/react-aria-components/docs/examples/file-system.mdx create mode 100644 packages/react-aria-components/docs/examples/file-system.png diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index 2b9a2de801d..2887cb5fb76 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -14,13 +14,22 @@ import docs from 'docs:react-aria-components'; import {PropTable, HeaderInfo, TypeLink, PageDescription, StateTable, ContextTable, ClassAPI, VersionBadge} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; import packageData from 'react-aria-components/package.json'; +import Anatomy from './TreeAnatomy.svg'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; -import {InlineAlert, Content, Heading} from '@adobe/react-spectrum'; +import {Divider} from '@react-spectrum/divider'; +import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; +import {ExampleList} from '@react-spectrum/docs/src/ExampleList'; +import {Keyboard} from '@react-spectrum/text'; +import Collections from '@react-spectrum/docs/pages/assets/component-illustrations/Collections.svg'; +import Selection from '@react-spectrum/docs/pages/assets/component-illustrations/Selection.svg'; +import Checkbox from '@react-spectrum/docs/pages/assets/component-illustrations/Checkbox.svg'; +import Button from '@react-spectrum/docs/pages/assets/component-illustrations/Button.svg'; import treeUtils from 'docs:@react-aria/test-utils/src/tree.ts'; +import {StarterKits} from '@react-spectrum/docs/src/StarterKits'; --- category: Collections -keywords: [disclosure, collapse, expand, aria] +keywords: [disclosure, collapse, expand, aria, tree, grid] type: component preRelease: beta --- @@ -36,11 +45,6 @@ preRelease: beta {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/'} ]} /> - - Under construction - This component is in beta. More documentation is coming soon! - - ## Example ```tsx example @@ -237,6 +241,232 @@ let items = [ +{/* Which example do we like first? they are both more complex than in other components base setup due to the conditional button for expanding */} +## Static Tree Example + +```tsx example +import {Button} from 'react-aria-components'; + +function MyTreeItemContent(props) { + return ( + + {({hasChildRows}) => ( + <> + {hasChildRows && + } + {props.children} + + )} + + ); +} + + + + + Documents + + + + Project + + + + Weekly Report + + + + + + + Photos + + + + Image 1 + + + + + Image 2 + + + + +``` + + +## Features + +A tree can be built using the [<ul>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul), [<li>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li), + and [<ol>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol), but is very limited in functionality especially when it comes to user interactions. +HTML lists are meant for static content, rather than heirarchies with rich interactions like focusable elements within cells, keyboard navigation, row selection, sorting, etc. +`Tree` helps achieve accessible and interactive tree components that can be styled as needed. + +* **Row selection** – Single or multiple selection, with optional checkboxes, disabled rows, and both `toggle` and `replace` selection behaviors. +* **Interactive children** – Tree rows may include interactive elements such as buttons, menus, etc. +* **Actions** – Rows support optional actions such as navigation via click, tap, double click, or Enter key. +* **Keyboard navigation** – Tree rows and focusable children can be navigated using the arrow keys, along with page up/down, home/end, etc. Typeahead, auto scrolling, and selection modifier keys are supported as well. +* **Touch friendly** – Selection and actions adapt their behavior depending on the device. For example, selection is activated via long press on touch when row actions are present. +* **Accessible** – Follows the [ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/), with additional selection announcements via an ARIA live region. Extensively tested across many devices and [assistive technologies](accessibility.html#testing) to ensure announcements and behaviors are consistent. + +## Anatomy + + + +A Tree consists of a container element, with rows containing data inside. The rows within a tree may contain focusable elements or plain text content. Each row may also contain a button to toggle the expandable state of that row. + +If the tree supports row selection, each row can optionally include a selection checkbox. + + +### Concepts + +`Tree` makes use of the following concepts: + +
+ + + + + + + + + +
+ +### Composed components + +A `Tree` uses the following components, which may also be used standalone or reused in other components. + +
+ + + + + + +
+ +## Examples + + + +## Starter kits + +To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured [Storybook](https://storybook.js.org/) that you can experiment with, or use as a starting point for your own component library. + +{/* */} + + +## Reusable wrappers + +If you will use a Tree in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency. + +The following example includes a custom +{/* Need idea for example here */} + +## Content +{/* +Do we need this section? we've kinda already covered this */} + +```tsx example export=true + +let items = [ + {id: 1, title: 'Documents', children: [ + {id: 2, title: 'Project', children: [ + {id: 3, title: 'Weekly Report', children: []} + ]} + ]}, + {id: 4, title: 'Photos', children: [ + {id: 5, title: 'Image 1', children: []}, + {id: 6, title: 'Image 2', children: []} + ]} +]; +function FileTree(props) { + return ( + + {function renderItem(item) { + return ( + + + {({hasChildRows}) => ( + <> + {hasChildRows ? : null} + + {item.title} + + + )} + + + {renderItem} + + + ); + }} + + ) +} +``` + +## Selection + +### Single selection + +By default, `Tree` doesn't allow row selection but this can be enabled using the `selectionMode` prop. Use `defaultSelectedKeys` to provide a default set of selected rows. +Note that the value of the selected keys must match the `id` prop of the row. + +The example below enables single selection mode, and uses `defaultSelectedKeys` to select the row with id equal to `2`. +A user can click on a different row to change the selection, or click on the same row again to deselect it entirely. + +```tsx example +// Using the example above + +``` + +### Multiple selection + +Multiple selection can be enabled by setting `selectionMode` to `multiple`. + +```tsx example +// Using the example above + +``` + +### Disallow empty selection + +Table also supports a `disallowEmptySelection` prop which forces the user to have at least one row in the Table selected at all times. +In this mode, if a single row is selected and the user presses it, it will not be deselected. + +```tsx example +// Using the example above + +``` + ## Props ### Tree @@ -322,6 +552,12 @@ TreeItem also exposes a `--tree-item-level` CSS custom property, which you can u } ``` +### TreeItemContent + +A `TreeItemContent` can be targeted with the `.react-aria-TreeItemContent` CSS selector, or by overriding with a custom `className`. It supports the following states and render props: + + + ## Testing ### Test utils diff --git a/packages/react-aria-components/docs/TreeAnatomy.svg b/packages/react-aria-components/docs/TreeAnatomy.svg new file mode 100644 index 00000000000..0a4fd051714 --- /dev/null +++ b/packages/react-aria-components/docs/TreeAnatomy.svg @@ -0,0 +1,194 @@ + + + + + Column + + + Size + + + + + + 214 KB + + 120 KB + 88 KB + 24 KB + Proposal + Budget + Welcome + Onboarding + File name + + + + Cell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Select allcheckbox + + + + + + + + + + + + Table body + + + + + + + + + Table header + + + + + + + + + + + Row + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Selectioncheckbox + + + + + + + + Dragbutton + + + + + + + + + + + Columnresizer + + + + diff --git a/packages/react-aria-components/docs/examples/file-system.mdx b/packages/react-aria-components/docs/examples/file-system.mdx new file mode 100644 index 00000000000..2185e52f4dc --- /dev/null +++ b/packages/react-aria-components/docs/examples/file-system.mdx @@ -0,0 +1,163 @@ +{/* Copyright 2023 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. */} + +import {ExampleLayout} from '@react-spectrum/docs'; +export default ExampleLayout; + +import docs from 'docs:react-aria-components'; +import {TypeLink} from '@react-spectrum/docs'; +import styles from '@react-spectrum/docs/src/docs.css'; +import Table from '@react-spectrum/docs/pages/assets/component-illustrations/Table.svg'; +import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; +import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; + +--- +keywords: [example, tree, aria, accessibility, react, component] +type: component +image: file-system.png +description: A tree with multiple selection and nested items. +--- + +# File System Tree + +A file system [Tree](../Tree.html) featuring multiple selection and styled with [Tailwind CSS](https://tailwindcss.com/). + +## Example + +```tsx import +import './tailwind.global.css'; + +const filesystem = [ + // mock up a file system with 50 items total and nested children up to 4 levels deep + {'id': 'documents', name: 'Documents', children: [ + {'id': 'photos', name: 'Photos', children: [ + {'id': 'summer', name: 'Summer', children: [ + {'id': 'beach', name: 'Beach'}, + {'id': 'mountains', name: 'Mountains'}, + {'id': 'forest', name: 'Forest'}, + {'id': 'desert', name: 'Desert'} + ]}, + {'id': 'winter', name: 'Winter', children: [ + {'id': 'skiing', name: 'Skiing'}, + {'id': 'snowboarding', name: 'Snowboarding'}, + {'id': 'snowmobiling', name: 'Snowmobiling'}, + {'id': 'snowshoeing', name: 'Snowshoeing'} + ]} + ]}, + {'id': 'videos', name: 'Videos', children: [ + {'id': 'family', name: 'Family'}, + {'id': 'friends', name: 'Friends'}, + {'id': 'pets', name: 'Pets'}, + {'id': 'vacations', name: 'Vacations'} + ]}, + {'id': 'music', name: 'Music', children: [ + {'id': 'rock', name: 'Rock', children: [ + {'id': 'classic', name: 'Classic'}, + {'id': 'alternative', name: 'Alternative'}, + {'id': 'punk', name: 'Punk'}, + {'id': 'metal', name: 'Metal'} + ]}, + {'id': 'pop', name: 'Pop', children: [ + {'id': 'dance', name: 'Dance'}, + {'id': 'hip-hop', name: 'Hip Hop'}, + {'id': 'r&b', name: 'R&B'}, + {'id': 'soul', name: 'Soul'} + ]} + ]}, + {'id': 'movies', name: 'Movies', children: [ + {'id': 'action', name: 'Action'}, + {'id': 'comedy', name: 'Comedy'}, + {'id': 'drama', name: 'Drama'}, + {'id': 'horror', name: 'Horror'} + ]} + ]} +]; +``` + +```tsx example standalone +import {Button, Collection, UNSTABLE_Tree as Tree, UNSTABLE_TreeItem as TreeItem, UNSTABLE_TreeItemContent as TreeItemContent} from 'react-aria-components'; +import type {ColumnProps, RowProps, CellProps} from 'react-aria-components'; +import ChevronIcon from '@spectrum-icons/ui/ChevronRightMedium'; +import {useState, useMemo} from 'react'; + +function FileSystemExample() { + return ( +
+ + {function renderItem(item) { + return ( + + + {({hasChildRows}) => ( +
+ {hasChildRows ? :
} +
{item.name}
+
+ )} + + + {renderItem} + + + ) + }} + +
+ ); +} +``` + +### Tailwind config + +This example uses the [tailwindcss-react-aria-components](../styling.html#plugin) plugin. When using Tailwind v4, add it to your CSS: + +```css render=false +@import "tailwindcss"; +@plugin "tailwindcss-react-aria-components"; +``` + +
+ + Tailwind v3 + +When using Tailwind v3, add the plugin to your `tailwind.config.js` instead: + +```tsx +module.exports = { + // ... + plugins: [ + require('tailwindcss-react-aria-components') + ] +}; +``` + +**Note**: When using Tailwind v3, install `tailwindcss-react-aria-components` version 1.x instead of 2.x. + +
+ +## Components + +
+ + + + + + diff --git a/packages/react-aria-components/docs/examples/file-system.png b/packages/react-aria-components/docs/examples/file-system.png new file mode 100644 index 0000000000000000000000000000000000000000..ada85e3e2dc0a24e82d3396177c842f83578bf14 GIT binary patch literal 135119 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

6y!6U#O%bym()bfy+o>HlITip)Fzd=} zG|KbLakTqnv*-H5O7X^uJ6;1ki74as8i13mIf8b{?8*YS9j+BU3=(W_q(*01hr-qk z#Un5E6_;$EkZ+>^C@CpGrr@~gs(ew=C2go38r`e9gd-AM?kG00$H`M5FvmdyIy{>u z*F7kZDf!7gmUy%Es<90P0m2&qnB^!d-XB7*4vPT&z%;*ZI(c%!9>A51r|vNz7vbi6 zK5+dIjwZ_tefpCF4{#4mwtoi>R+nk~6u}yk!r$iKo!H zs}9dJc=cnY-?58hQ;As}n16aGvbbU)x?M$73NSn)h;E|hF8w?St;*e~_g{L$XJkA7 zOE~S<)RG1~=qjEibIN%uo)Jscc)NvSw=EDzv;n5o!Dk;g^1xsG91hS#${^|=>+k!f z456e9>ArwbGz)q;c-C7|&!p+%0UDMOh2P#mGtyyLTYpS>&Hrx7vtV_&v3 z6T`zG@bY+5XzbY_5c#!PR(4!{+UY&F33#(IcbW~tYxZ1}Wl!n(j9h+W;fr-c{4<>P z&mI)pd2}&{w0S{jgv6_lMINdKl9TlWz}i%QaF4swfJWrGR1ZdStdxG=baFNBKE7y*1z{5SIMiQNTq3 zeMki_9=zl}u z&AdOy>YJ)(`<%U70Wq0#b|$!4R9pn!Wdt`Otu+-~^uQZP5(rBpXN>w{Ip@CPbscsX zHothgJ}M1{lAxalhNL22jS&K8AWGLK6E!YAwi&g2OrQnTI!R0xRaSQ-x4;v3H@RjD z?NYCQT-s?o3J*}WRpN7h!U^C7-Z5fOP$3OETMdHN5mRoY%DzA*JU7jAEBhav*;5t& z=*(^^#+!jx;yt|(ZkR7#nlU8CJ*>uB!Cp7}_BE=#l$Shmy$3da*NC?{n-|q*!wwL; zlyUeUG^n@CH|x>!CIg5QeWZLb5X}wSyVvcg@#}{WH($+9I6;X1`^s$ZvNOHClRF}I z_c!aZFPG10_!oaH7vLXvqJN=Av$KTAox$+B#UvJMv$b|Rz98G7l0(eHjo5l8z6LYl zOlwQ@Ct4T`QVN%Emp`L63xpS+;QU`HKJh>Nu}x3hD{ay4)6H>Ld-o`>TY!IdZ%`71 z*`3^#Y-ZwfkW;l1ZSHhscs5OUb%gEVkst7zpteXfdD2+g_vGdi+qds+@+$ui^8Ugr z%69MLhA(miq(Qm_!9YT!VJJnA6hx3NL8QBfZYe=PX+cTp?rs>6k?tB|XoeaFpA+2I zzOQTFd*AQ9-Y3?2*7F~@#*^dxoxkJz{e0ekWEBPX2E4b&&NC;!_ffD6`YTmx>c<^+ z)%p7eorY3e3*PngXmyOIVC^iS5tLm5_=-m$Gm12&Qs0>nX;%7OPaB&4xAin&nfmv7 zT0^vd4fk~@@hw0iSYGQt(YYS^+!ibW9u-NEx{Sq>9ydwVw z@ORycdMlyc&2D7&^AJAKbe&tj_*W9aAOm#2rnHj5&6ybl#XEeU3AGhB*mQgb_@L$e zN^Wzy%9gwwy2TjZ*GDc5wW`@XtO19#C8P)>T;ZtPAXBmkYb&9p%>YwVnYD?#j}Yo> zA!(v-Ksh3&qHEw5{zjDnqB?F<^>jSt`#~<~Vb4GNX+1zcZB7DqGIK7{2c}o-a4%nv zs9jE~T0qTu-{=r-V4hT$VzHZ89zc5Hv+)E1dn)9&FR}Esy>qiWN^Wla4<9t*10x@l zeX>Yd?kJ{rzhR6n$m3QFgR32O!%YO9-%3u}^~}dMNDdfF_?9LAO+l?HEGsUjP3+#) zS`S!N0M%X6jU`heqn~lx6Bufi64QqqOL;&+4bgPflo%Svm>Uz=fW!^bxyam~{YH1w zQT^`10Pusk2A2A5^(MT2vG2Cvb`RM1-PCuN+&BYlB&Hz{ZC~{t?L^x5UP28rνW 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 K)Qav~1pa>u?W}PC literal 0 HcmV?d00001 diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 072d041d489..75c236825d2 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -260,7 +260,10 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne // TODO: readd the rest of the render props when tree supports them export interface TreeItemRenderProps extends Omit { - /** Whether the tree item is expanded. */ + /** + * Whether the tree item is expanded. + * @selector [data-expanded] + */ isExpanded: boolean, // TODO: api discussion, how do we feel about the below? This is so we can still style the row as grey when a child element within is focused // Maybe should have this for the other collection item render props @@ -269,7 +272,10 @@ export interface TreeItemRenderProps extends Omit Date: Thu, 13 Feb 2025 17:47:06 +1100 Subject: [PATCH 02/22] Finish docs page for RAC Tree --- packages/react-aria-components/docs/Tree.mdx | 611 +++++++++++++++---- 1 file changed, 493 insertions(+), 118 deletions(-) diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index b080e43a099..1faffd22472 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -47,6 +47,8 @@ preRelease: beta ## Example +This example's MyTreeItemContent is from the [Reusable Wrappers](#reusable-wrappers) section below. + ```tsx example import { UNSTABLE_Tree as Tree, @@ -57,38 +59,37 @@ import { } from 'react-aria-components'; import {MyCheckbox} from './Checkbox'; -let items = [ - {id: 1, title: 'Documents', children: [ - {id: 2, title: 'Project', children: [ - {id: 3, title: 'Weekly Report', children: []} - ]} - ]}, - {id: 4, title: 'Photos', children: [ - {id: 5, title: 'Image 1', children: []}, - {id: 6, title: 'Image 2', children: []} - ]} -]; - - - {function renderItem(item) { - return ( - - - {item.children.length ? : null} - - {item.title} - - - - {renderItem} - + + + + Documents + + + + Project + + + + Weekly Report + - ); - }} + + + + + Photos + + + + Image 1 + + + + + Image 2 + + + ``` @@ -241,86 +242,27 @@ let items = [ -{/* Which example do we like first? they are both more complex than in other components base setup due to the conditional button for expanding */} -## Static Tree Example - -```tsx example -import {Button} from 'react-aria-components'; - -function MyTreeItemContent(props) { - return ( - - {({hasChildRows}) => ( - <> - {hasChildRows && - } - {props.children} - - )} - - ); -} - - - - - Documents - - - - Project - - - - Weekly Report - - - - - - - Photos - - - - Image 1 - - - - - Image 2 - - - - -``` - - ## Features A tree can be built using the [<ul>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul), [<li>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li), and [<ol>](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol), but is very limited in functionality especially when it comes to user interactions. -HTML lists are meant for static content, rather than heirarchies with rich interactions like focusable elements within cells, keyboard navigation, row selection, sorting, etc. +HTML lists are meant for static content, rather than heirarchies with rich interactions like focusable elements within cells, keyboard navigation, item selection, sorting, etc. `Tree` helps achieve accessible and interactive tree components that can be styled as needed. -* **Row selection** – Single or multiple selection, with optional checkboxes, disabled rows, and both `toggle` and `replace` selection behaviors. -* **Interactive children** – Tree rows may include interactive elements such as buttons, menus, etc. -* **Actions** – Rows support optional actions such as navigation via click, tap, double click, or Enter key. -* **Keyboard navigation** – Tree rows and focusable children can be navigated using the arrow keys, along with page up/down, home/end, etc. Typeahead, auto scrolling, and selection modifier keys are supported as well. -* **Touch friendly** – Selection and actions adapt their behavior depending on the device. For example, selection is activated via long press on touch when row actions are present. -* **Accessible** – Follows the [ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/grid/), with additional selection announcements via an ARIA live region. Extensively tested across many devices and [assistive technologies](accessibility.html#testing) to ensure announcements and behaviors are consistent. +* **Item selection** – Single or multiple selection, with optional checkboxes, disabled items, and both `toggle` and `replace` selection behaviors. +* **Interactive children** – Tree items may include interactive elements such as buttons, menus, etc. +* **Actions** – Items support optional actions such as navigation via click, tap, double click, or Enter key. +* **Keyboard navigation** – Tree items and focusable children can be navigated using the arrow keys, along with page up/down, home/end, etc. Typeahead, auto scrolling, and selection modifier keys are supported as well. +* **Touch friendly** – Selection and actions adapt their behavior depending on the device. For example, selection is activated via long press on touch when item actions are present. +* **Accessible** – Follows the [ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/), with additional selection announcements via an ARIA live region. Extensively tested across many devices and [assistive technologies](accessibility.html#testing) to ensure announcements and behaviors are consistent. ## Anatomy -A Tree consists of a container element, with rows containing data inside. The rows within a tree may contain focusable elements or plain text content. Each row may also contain a button to toggle the expandable state of that row. +A Tree consists of a container element, with items containing data inside. The items within a tree may contain focusable elements or plain text content. Each item may also contain a button to toggle the expandable state of that item. -If the tree supports row selection, each row can optionally include a selection checkbox. +If the tree supports item selection, each item can optionally include a selection checkbox. ### Concepts @@ -382,15 +324,70 @@ To help kick-start your project, we offer starter kits that include example impl If you will use a Tree in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency. -The following example includes a custom -{/* Need idea for example here */} +The following example includes a custom tree item with an action button. + +```tsx example export=true render=false +import {Button} from 'react-aria-components'; + +function MyTreeItemContent(props) { + return ( + + {({hasChildItems}) => ( + <> + {hasChildItems && + } + {props.children} + + + )} + + ); +} +``` + +The `TreeItem` can also be wrapped, for example, it could include the TreeItemContent automatically. + +```tsx example export=true render=false +function MyTreeItem(props) { + return ( + + + {props.title} + + {props.children} + + ); +} +``` + +Now we can render a Tree using far less code. + +```tsx example + + + + + + + + + + + +``` ## Content -{/* -Do we need this section? we've kinda already covered this */} -```tsx example export=true +So far, our examples have shown static collections, where the data is hard coded. +Dynamic collections, as shown below, can be used when the tree data comes from an external data source such as an API, +or updates over time. In the example below, data for each item is provided to the tree via a render function. +```tsx example export=true +import type {TreeProps} from 'react-aria-components'; let items = [ {id: 1, title: 'Documents', children: [ {id: 2, title: 'Project', children: [ @@ -402,21 +399,22 @@ let items = [ {id: 6, title: 'Image 2', children: []} ]} ]; -function FileTree(props) { + +function FileTree(props: TreeProps) { return ( - + {function renderItem(item) { return ( - {({hasChildRows}) => ( + {({hasChildItems}) => ( <> - {hasChildRows ? : null} - {item.title} @@ -431,17 +429,18 @@ function FileTree(props) { ) } + ``` ## Selection ### Single selection -By default, `Tree` doesn't allow row selection but this can be enabled using the `selectionMode` prop. Use `defaultSelectedKeys` to provide a default set of selected rows. -Note that the value of the selected keys must match the `id` prop of the row. +By default, `Tree` doesn't allow item selection but this can be enabled using the `selectionMode` prop. Use `defaultSelectedKeys` to provide a default set of selected items. +Note that the value of the selected keys must match the `id` prop of the item. -The example below enables single selection mode, and uses `defaultSelectedKeys` to select the row with id equal to `2`. -A user can click on a different row to change the selection, or click on the same row again to deselect it entirely. +The example below enables single selection mode, and uses `defaultSelectedKeys` to select the item with id equal to `2`. +A user can click on a different item to change the selection, or click on the same item again to deselect it entirely. ```tsx example // Using the example above @@ -459,14 +458,294 @@ Multiple selection can be enabled by setting `selectionMode` to `multiple`. ### Disallow empty selection -Table also supports a `disallowEmptySelection` prop which forces the user to have at least one row in the Table selected at all times. -In this mode, if a single row is selected and the user presses it, it will not be deselected. +Tree also supports a `disallowEmptySelection` prop which forces the user to have at least one item in the Tree selected at all times. +In this mode, if a single item is selected and the user presses it, it will not be deselected. ```tsx example // Using the example above ``` + +### Controlled selection + +To programmatically control item selection, use the `selectedKeys` prop paired with the `onSelectionChange` callback. The `id` prop from the selected items will +be passed into the callback when the item is pressed, allowing you to update state accordingly. + +```tsx example export=true +import type {Selection} from 'react-aria-components'; + +interface Pokemon { + id: number, + name: string, + type: string, + level: string +} + +interface PokemonEvolutionTreeProps extends TreeProps { + items?: Pokemon[], + renderEmptyState?: () => string +} + +function PokemonEvolutionTree(props: PokemonEvolutionTreeProps) { + let items = props.items || [ + {id: 1, name: 'Bulbasaur', children: [ + {id: 2, name: 'Ivysaur', children: [ + {id: 3, name: 'Venusaur'} + ]} + ]}, + {id: 4, name: 'Charmander', children: [ + {id: 5, name: 'Charmeleon', children: [ + {id: 6, name: 'Charizard'} + ]} + ]}, + {id: 7, name: 'Squirtle', children: [ + {id: 8, name: 'Wartortle', children: [ + {id: 9, name: 'Blastoise'} + ]} + ]} + ]; + + ///- begin highlight -/// + let [selectedKeys, setSelectedKeys] = React.useState(new Set()); + ///- end highlight -/// + + return ( + + {function renderItem(item) { + return ( + + + {({hasChildItems, selectionBehavior}) => ( + <> + {selectionBehavior === 'toggle' && } + {hasChildItems ? : null} + {item.name} + + )} + + + {renderItem} + + + ); + }} + + ); +} + + +``` + +### Selection behavior + +By default, `Tree` uses the `"toggle"` selection behavior, which behaves like a checkbox group: clicking, tapping, or pressing the Space or Enter keys toggles selection for the focused item. Using the arrow keys moves focus but does not change selection. The `"toggle"` selection mode is often paired with checkboxes in each item as an explicit affordance for selection. + +When the `selectionBehavior` prop is set to `"replace"`, clicking a item with the mouse _replaces_ the selection with only that item. Using the arrow keys moves both focus and selection. To select multiple items, modifier keys such as Ctrl, Cmd, and Shift can be used. To move focus without moving selection, the Ctrl key on Windows or the Option key on macOS can be held while pressing the arrow keys. Holding this modifier while pressing the Space key toggles selection for the focused item, which allows multiple selection of non-contiguous items. On touch screen devices, selection always behaves as toggle since modifier keys may not be available. This behavior emulates native platforms such as macOS and Windows, and is often used when checkboxes in each item are not desired. + +```tsx example + +``` + +## Item actions + +`Tree` supports item actions via the `onAction` prop, which is useful for functionality such as navigation. In the default `"toggle"` selection behavior, when nothing is selected, clicking or tapping the item triggers the item action. +When at least one item is selected, the tree is in selection mode, and clicking or tapping an item toggles the selection. Actions may also be triggered via the Enter key, and selection using the Space key. + +This behavior is slightly different in the `"replace"` selection behavior, where single clicking selects the item and actions are performed via double click. On touch devices, the action becomes the primary tap interaction, +and a long press enters into selection mode, which temporarily swaps the selection behavior to `"toggle"` to perform selection (you may wish to display checkboxes when this happens). Deselecting all items exits selection mode +and reverts the selection behavior back to `"replace"`. Keyboard behaviors are unaffected. + +```tsx example +

+ alert(`Opening item ${key}...`)} + ///- end highlight -/// + selectionMode="multiple" /> + alert(`Opening item ${key}...`)} + selectionBehavior="replace" + ///- end highlight -/// + selectionMode="multiple" /> +
+``` + +Items may also have an action specified by directly applying `onAction` on the `TreeItem` itself. This may be especially convenient in static collections. If `onAction` is also provided to the `Tree`, both the trees's and the item's `onAction` are called. + +```tsx example + + {/*- begin highlight -*/} + alert(`Opening Bulbasaur...`)}> + {/*- end highlight -*/} + + Bulbasaur + + {/*- begin highlight -*/} + alert(`Opening Ivysaur...`)}> + {/*- end highlight -*/} + + Ivysaur + + {/*- begin highlight -*/} + alert(`Opening Venisaur...`)}> + {/*- end highlight -*/} + + Venisaur + + + + + +``` + + +### Links + +Tree items may also be links to another page or website. This can be achieved by passing the `href` prop to the `` component. Links behave the same way as described above for item actions depending on the `selectionMode` and `selectionBehavior`. + +```tsx example + + + + Bulbasaur + + + + Ivysaur + + + + Venisaur + + + + + +``` + +```css hidden +.react-aria-TreeItem[data-href] { + cursor: pointer; +} +``` + +#### Client side routing + +The `` component works with frameworks and client side routers like [Next.js](https://nextjs.org/) and [React Router](https://reactrouter.com/en/main). As with other React Aria components that support links, this works via the component at the root of your app. See the [client side routing guide](routing.html) to learn how to set this up. + +## Disabled items + +A `TreeItem` can be disabled with the `isDisabled` prop. This will disable all interactions on the item, +unless the `disabledBehavior` prop on `Tree` is used to change this behavior. +Note that you are responsible for the styling of disabled items, however, the selection checkbox will be automatically disabled. + +```tsx example + + + + Bulbasaur + + {/*- begin highlight -*/} + + {/*- end highlight -*/} + + Ivysaur + + + + Venisaur + + + + + +``` + +By default, only item selection is disabled. When `disabledBehavior` is set to `all`, all interactions such as focus, dragging, and actions are also disabled. + +```tsx example + + + + Bulbasaur + + {/*- begin highlight -*/} + + {/*- end highlight -*/} + + Ivysaur + + + + Venisaur + + + + + +``` + +In dynamic collections, it may be more convenient to use the `disabledKeys` prop at the `Tree` level instead of `isDisabled` on individual items. +This accepts a list of item ids that are disabled. An item is considered disabled if its key exists in `disabledKeys` or if it has `isDisabled`. + +```tsx example +// Using the same tree as above + +``` + +## Empty state + +Use the `renderEmptyState` prop to customize what the `Tree` will display if there are no items. + +```tsx example + 'No results found.'} style={{height: '100px'}}> + {[]} + +``` + +
+ Show CSS + +```css +.react-aria-Tree { + &[data-empty] { + display: flex; + align-items: center; + justify-content: center; + font-style: italic; + } +} +``` + +
+ + ## Props ### Tree @@ -522,10 +801,10 @@ Render props may also be used as children to alter what elements are rendered ba ```jsx {({selectionMode}) => ( - <> + {selectionMode !== 'none' && } Item - + )} ``` @@ -558,6 +837,102 @@ A `TreeItemContent` can be targeted with the `.react-aria-TreeItemContent` CSS s +## Advanced customization + +### Contexts + +All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in [mergeProps](mergeProps.html)). + + + +This example shows a component that accepts a `Tree` and a [ToggleButton](ToggleButton.html) as children, and allows the user to turn selection mode for the tree on and off by pressing the button. + +```tsx example render=false export=true +import type {SelectionMode} from 'react-aria-components'; +import {ToggleButtonContext, UNSTABLE_TreeContext as TreeContext} from 'react-aria-components'; + +function Selectable({children}) { + let [isSelected, onChange] = React.useState(false); + let selectionMode: SelectionMode = isSelected ? 'multiple' : 'none'; + return ( + + {/*- begin highlight -*/} + + {/*- end highlight -*/} + {children} + + + ); +} +``` + +The `Selectable` component can be reused to make the selection mode of any nested `Tree` controlled by a `ToggleButton`. + +```tsx example +import {ToggleButton} from 'react-aria-components'; + + + Select + + +``` + +
+ Show CSS + +```css +.react-aria-ToggleButton { + margin-bottom: 8px; +} +``` +
+ + +### Custom children + +Tree passes props to its child components, such as the selection checkboxes, via their associated contexts. These contexts are exported so you can also consume them in your own custom components. This enables you to reuse existing components from your app or component library together with React Aria Components. + + + +This example consumes from `CheckboxContext` in an existing styled checkbox component to make it compatible with React Aria Components. The hook merges the local props and ref with the ones provided via context by Tree. See [useCheckbox](useCheckbox.html) for more details about the hooks used in this example. + +```tsx +import type {CheckboxProps} from 'react-aria-components'; +import {CheckboxContext, useContextProps} from 'react-aria-components'; +import {useToggleState} from 'react-stately'; +import {useCheckbox} from 'react-aria'; + +const MyCustomCheckbox = React.forwardRef((props: CheckboxProps, ref: React.ForwardedRef) => { + // Merge the local props and ref with the ones provided via context. + ///- begin highlight -/// + [props, ref] = useContextProps(props, ref, CheckboxContext); + ///- end highlight -/// + + let state = useToggleState(props); + let {inputProps} = useCheckbox(props, state, ref); + return ; +}); +``` + +Now you can use `MyCustomCheckbox` within a `Tree`, in place of the builtin React Aria Components `Checkbox`. + +```tsx + + + + {/*- begin highlight -*/} + + {/*- end highlight -*/} + {/* ... */} + + + +``` + +### Hooks + +If you need to customize things even further, such as accessing internal state or customizing DOM structure, you can drop down to the lower level Hook-based API. See [useTree](useTree.html) for more details. + ## Testing ### Test utils @@ -573,7 +948,7 @@ import {User} from '@react-aria/test-utils'; let testUtilUser = new User({interactionType: 'mouse'}); // ... -it('Tree can select a row via keyboard', async function () { +it('Tree can select a item via keyboard', async function () { // Render your test component/app and initialize the Tree tester let {getByTestId} = render( From bda733da90932c00c6000aa04e52aabeb7eeee3a Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 13 Feb 2025 18:13:29 +1100 Subject: [PATCH 03/22] fix all the types --- packages/react-aria-components/docs/Tree.mdx | 31 ++++++++++++------- .../docs/examples/file-system.mdx | 10 +++--- packages/react-aria-components/src/Tree.tsx | 13 ++++++-- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index 1faffd22472..4d59834eb4c 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -327,9 +327,10 @@ If you will use a Tree in multiple places in your app, you can wrap all of the p The following example includes a custom tree item with an action button. ```tsx example export=true render=false +import type {TreeItemContentProps} from 'react-aria-components'; import {Button} from 'react-aria-components'; -function MyTreeItemContent(props) { +function MyTreeItemContent(props: TreeItemContentProps) { return ( {({hasChildItems}) => ( @@ -352,7 +353,9 @@ function MyTreeItemContent(props) { The `TreeItem` can also be wrapped, for example, it could include the TreeItemContent automatically. ```tsx example export=true render=false -function MyTreeItem(props) { +import {TreeItemProps} from 'react-aria-components'; + +function MyTreeItem(props: Omit & Partial> & {title: string}) { return ( @@ -399,8 +402,13 @@ let items = [ {id: 6, title: 'Image 2', children: []} ]} ]; +interface FileType { + id: number, + title: string, + children: FileType[] +} -function FileTree(props: TreeProps) { +function FileTree(props: TreeProps) { return ( {function renderItem(item) { @@ -478,17 +486,16 @@ import type {Selection} from 'react-aria-components'; interface Pokemon { id: number, name: string, - type: string, - level: string + children?: Pokemon[] } -interface PokemonEvolutionTreeProps extends TreeProps { - items?: Pokemon[], +interface PokemonEvolutionTreeProps extends TreeProps { + items?: T[], renderEmptyState?: () => string } -function PokemonEvolutionTree(props: PokemonEvolutionTreeProps) { - let items = props.items || [ +function PokemonEvolutionTree(props: PokemonEvolutionTreeProps) { + let items: Pokemon[] = props.items ?? [ {id: 1, name: 'Bulbasaur', children: [ {id: 2, name: 'Ivysaur', children: [ {id: 3, name: 'Venusaur'} @@ -514,13 +521,13 @@ function PokemonEvolutionTree(props: PokemonEvolutionTreeProps) { {function renderItem(item) { return ( @@ -897,8 +904,8 @@ Tree passes props to its child components, such as the selection checkboxes, via This example consumes from `CheckboxContext` in an existing styled checkbox component to make it compatible with React Aria Components. The hook merges the local props and ref with the ones provided via context by Tree. See [useCheckbox](useCheckbox.html) for more details about the hooks used in this example. ```tsx -import type {CheckboxProps} from 'react-aria-components'; -import {CheckboxContext, useContextProps} from 'react-aria-components'; +import type {CheckboxProps, useContextProps} from 'react-aria-components'; +import {CheckboxContext} from 'react-aria-components'; import {useToggleState} from 'react-stately'; import {useCheckbox} from 'react-aria'; diff --git a/packages/react-aria-components/docs/examples/file-system.mdx b/packages/react-aria-components/docs/examples/file-system.mdx index 2185e52f4dc..61e265038f2 100644 --- a/packages/react-aria-components/docs/examples/file-system.mdx +++ b/packages/react-aria-components/docs/examples/file-system.mdx @@ -82,9 +82,7 @@ const filesystem = [ ```tsx example standalone import {Button, Collection, UNSTABLE_Tree as Tree, UNSTABLE_TreeItem as TreeItem, UNSTABLE_TreeItemContent as TreeItemContent} from 'react-aria-components'; -import type {ColumnProps, RowProps, CellProps} from 'react-aria-components'; import ChevronIcon from '@spectrum-icons/ui/ChevronRightMedium'; -import {useState, useMemo} from 'react'; function FileSystemExample() { return ( @@ -94,14 +92,16 @@ function FileSystemExample() { overflow-auto rounded-lg shadow-lg`}> {function renderItem(item) { return ( - - {({hasChildRows}) => ( + {({hasChildItems}) => (
- {hasChildRows ? :
} diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 6d06d2f9f96..03e7a23c719 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -16,7 +16,7 @@ import {CheckboxContext} from './RSPContexts'; import {Collection, CollectionBuilder, CollectionNode, createBranchComponent, createLeafComponent, useCachedChildren} from '@react-aria/collections'; import {CollectionProps, CollectionRendererContext, DefaultCollectionRenderer, ItemRenderProps, usePersistedKeys} from './Collection'; import {ContextValue, DEFAULT_SLOT, Provider, RenderProps, ScrollableProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps} from './utils'; -import {DisabledBehavior, Expandable, forwardRefType, HoverEvents, Key, LinkDOMProps, RefObject} from '@react-types/shared'; +import {DisabledBehavior, Expandable, forwardRefType, HoverEvents, Key, LinkDOMProps, MultipleSelection, RefObject} from '@react-types/shared'; import {filterDOMProps, useObjectRef} from '@react-aria/utils'; import {FocusScope, mergeProps, useFocusRing, useGridListSelectionCheckbox, useHover} from 'react-aria'; import {Collection as ICollection, Node, SelectionBehavior, TreeState, useTreeState} from 'react-stately'; @@ -120,7 +120,7 @@ export interface TreeRenderProps { export interface TreeEmptyStateRenderProps extends Omit {} -export interface TreeProps extends Omit, 'children'>, CollectionProps, StyleRenderProps, SlotProps, ScrollableProps, Expandable { +export interface TreeProps extends Omit, 'children'>, MultipleSelection, CollectionProps, StyleRenderProps, SlotProps, ScrollableProps, Expandable { /** How multiple selection should behave in the tree. */ selectionBehavior?: SelectionBehavior, /** Provides content to display when there are no items in the list. */ @@ -318,7 +318,14 @@ export interface TreeItemProps extends StyleRenderProps void } /** From 3c05b291f12cc076770538d33f441453864a703d Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 13 Feb 2025 18:30:29 +1100 Subject: [PATCH 04/22] make some styles nicer --- packages/react-aria-components/docs/Tree.mdx | 31 ++++++++------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index 4d59834eb4c..1d1460be6e4 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -129,8 +129,7 @@ import {MyCheckbox} from './Checkbox'; gap: 0.571rem; min-height: 28px; padding: 0.286rem 0.286rem 0.286rem 0.571rem; - --padding: 20px; - padding-left: calc((var(--tree-item-level) - 1) * 20px + 0.571rem + var(--padding)); + --padding: 8px; border-radius: 6px; outline: none; cursor: default; @@ -139,10 +138,6 @@ import {MyCheckbox} from './Checkbox'; position: relative; transform: translateZ(0); - &[data-has-child-items] { - --padding: 0px; - } - .react-aria-Button[slot=chevron] { all: unset; display: flex; @@ -150,6 +145,7 @@ import {MyCheckbox} from './Checkbox'; justify-content: center; width: 1.143rem; height: 1.143rem; + padding-left: calc((var(--tree-item-level) - 1) * var(--padding)); svg { rotate: 0deg; @@ -335,12 +331,11 @@ function MyTreeItemContent(props: TreeItemContentProps) { {({hasChildItems}) => ( <> - {hasChildItems && - } + {props.children} @@ -418,11 +413,11 @@ function FileTree(props: TreeProps) { {({hasChildItems}) => ( <> - {hasChildItems ? : null} + {item.title} @@ -533,14 +528,14 @@ function PokemonEvolutionTree(props: PokemonEvolutionTreeProps) { return ( - {({hasChildItems, selectionBehavior}) => ( + {({hasChildItems, selectionBehavior, selectionMode}) => ( <> - {selectionBehavior === 'toggle' && } - {hasChildItems ? : null} + {item.name} )} From 604efb8add05104ec91c0eb5b33113be6426c1e3 Mon Sep 17 00:00:00 2001 From: GitHub Date: Fri, 14 Feb 2025 12:08:15 +1100 Subject: [PATCH 05/22] remove unstable, add anatomy --- .../s2/chromatic/TreeView.stories.tsx | 30 +- packages/@react-spectrum/s2/src/TreeView.tsx | 18 +- packages/@react-spectrum/s2/src/index.ts | 2 +- .../s2/stories/TreeView.stories.tsx | 32 +-- .../@react-spectrum/s2/test/Tree.test.tsx | 136 +++++----- .../tree/chromatic-fc/TreeView.stories.tsx | 26 +- .../tree/chromatic/TreeView.stories.tsx | 30 +- .../@react-spectrum/tree/docs/TreeView.mdx | 70 ++--- .../@react-spectrum/tree/src/TreeView.tsx | 25 +- packages/@react-spectrum/tree/src/index.ts | 2 +- .../tree/stories/TreeView.stories.tsx | 30 +- .../tree/test/TreeView.test.tsx | 50 ++-- .../docs/TableAnatomy.svg | 2 +- packages/react-aria-components/docs/Tree.mdx | 24 +- .../docs/TreeAnatomy.svg | 256 ++++++------------ .../docs/examples/file-system.mdx | 6 +- packages/react-aria-components/src/Tree.tsx | 20 +- packages/react-aria-components/src/index.ts | 2 +- .../stories/Tree.stories.tsx | 82 +++--- .../test/Tree.ssr.test.js | 96 +++++++ .../react-aria-components/test/Tree.test.tsx | 62 ++--- 21 files changed, 505 insertions(+), 496 deletions(-) create mode 100644 packages/react-aria-components/test/Tree.ssr.test.js diff --git a/packages/@react-spectrum/s2/chromatic/TreeView.stories.tsx b/packages/@react-spectrum/s2/chromatic/TreeView.stories.tsx index f2fff3b9725..5a0edf91c54 100644 --- a/packages/@react-spectrum/s2/chromatic/TreeView.stories.tsx +++ b/packages/@react-spectrum/s2/chromatic/TreeView.stories.tsx @@ -10,7 +10,7 @@ * governing permissions and limitations under the License. */ -import {ActionMenu, Collection, Content, Heading, IllustratedMessage, Link, MenuItem, Text, TreeItemContent, TreeView, TreeViewItem} from '../src'; +import {ActionMenu, Collection, Content, Heading, IllustratedMessage, Link, MenuItem, Text, TreeView, TreeViewItem, TreeViewItemContent} from '../src'; import Delete from '../s2wf-icons/S2_Icon_Delete_20_N.svg'; import Edit from '../s2wf-icons/S2_Icon_Edit_20_N.svg'; import FileTxt from '../s2wf-icons/S2_Icon_FileText_20_N.svg'; @@ -38,7 +38,7 @@ function TreeExample(props) { expandedKeys={['projects']}> - + Photos @@ -51,10 +51,10 @@ function TreeExample(props) { Delete - + - + Projects @@ -67,9 +67,9 @@ function TreeExample(props) { Delete - + - + Projects-1 @@ -82,9 +82,9 @@ function TreeExample(props) { Delete - + - + Projects-1A @@ -97,11 +97,11 @@ function TreeExample(props) { Delete - + - + Projects-2 @@ -114,10 +114,10 @@ function TreeExample(props) { Delete - + - + Projects-3 @@ -130,7 +130,7 @@ function TreeExample(props) { Delete - + @@ -225,7 +225,7 @@ const DynamicTreeItem = (props) => { return ( <> - + {name} {icon} @@ -238,7 +238,7 @@ const DynamicTreeItem = (props) => { Delete - + {(item: any) => ( ) { - tree({isEmpty, isDetached}, props.styles)} selectionBehavior="toggle" ref={domRef}> {props.children} - + @@ -306,7 +306,7 @@ export const TreeViewItem = (props: TreeViewItemProps) => { let {isDetached, isEmphasized} = useContext(InternalTreeContext); return ( - treeRow({ ...renderProps, @@ -315,7 +315,7 @@ export const TreeViewItem = (props: TreeViewItemProps) => { ); }; -export const TreeItemContent = (props: Omit & {children: ReactNode}) => { +export const TreeViewItemContent = (props: Omit & {children: ReactNode}) => { let { children } = props; @@ -323,7 +323,7 @@ export const TreeItemContent = (props: Omit & let scale = useScale(); return ( - + {({isExpanded, hasChildItems, selectionMode, selectionBehavior, isDisabled, isFocusVisible, isSelected, id, state}) => { let isNextSelected = false; let isNextFocused = false; @@ -365,7 +365,7 @@ export const TreeItemContent = (props: Omit &
); }} - + ); }; diff --git a/packages/@react-spectrum/s2/src/index.ts b/packages/@react-spectrum/s2/src/index.ts index 52c6cf7fce0..7a7e0fbb0be 100644 --- a/packages/@react-spectrum/s2/src/index.ts +++ b/packages/@react-spectrum/s2/src/index.ts @@ -76,7 +76,7 @@ export {TextArea, TextField, TextAreaContext, TextFieldContext} from './TextFiel export {ToggleButton, ToggleButtonContext} from './ToggleButton'; export {ToggleButtonGroup, ToggleButtonGroupContext} from './ToggleButtonGroup'; export {Tooltip, TooltipTrigger} from './Tooltip'; -export {TreeView, TreeViewItem, TreeItemContent} from './TreeView'; +export {TreeView, TreeViewItem, TreeViewItemContent} from './TreeView'; export {pressScale} from './pressScale'; diff --git a/packages/@react-spectrum/s2/stories/TreeView.stories.tsx b/packages/@react-spectrum/s2/stories/TreeView.stories.tsx index e272ae1d5c4..f1738c6df58 100644 --- a/packages/@react-spectrum/s2/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/s2/stories/TreeView.stories.tsx @@ -20,9 +20,9 @@ import { Link, MenuItem, Text, - TreeItemContent, TreeView, - TreeViewItem + TreeViewItem, + TreeViewItemContent } from '../src'; import {categorizeArgTypes} from './utils'; import Delete from '../s2wf-icons/S2_Icon_Delete_20_N.svg'; @@ -79,7 +79,7 @@ const TreeExampleStatic = (args) => ( onExpandedChange={action('onExpandedChange')} onSelectionChange={action('onSelectionChange')}> - + Photos @@ -92,10 +92,10 @@ const TreeExampleStatic = (args) => ( Delete - + - + Projects @@ -108,9 +108,9 @@ const TreeExampleStatic = (args) => ( Delete - + - + Projects-1 @@ -123,9 +123,9 @@ const TreeExampleStatic = (args) => ( Delete - + - + Projects-1A @@ -138,11 +138,11 @@ const TreeExampleStatic = (args) => ( Delete - + - + Projects-2 @@ -155,10 +155,10 @@ const TreeExampleStatic = (args) => ( Delete - + - + Projects-3 @@ -171,7 +171,7 @@ const TreeExampleStatic = (args) => ( Delete - + @@ -221,7 +221,7 @@ const DynamicTreeItem = (props) => { return ( <> - + {name} {icon} @@ -234,7 +234,7 @@ const DynamicTreeItem = (props) => { Delete - + {(item: any) => ( render( - + Photos - + - + Projects - + - + Projects-1 - + - + Projects-1A - + - + Projects-2 - + - + Projects-3 - + - + School - + - + Homework-1 - + - + Homework-1A - + - + Homework-2 - + - + Homework-3 - + @@ -101,69 +101,69 @@ AriaTreeTests({ singleSelection: () => render( - + Photos - + - + Projects - + - + Projects-1 - + - + Projects-1A - + - + Projects-2 - + - + Projects-3 - + - + School - + - + Homework-1 - + - + Homework-1A - + - + Homework-2 - + - + Homework-3 - + @@ -171,69 +171,69 @@ AriaTreeTests({ allInteractionsDisabled: () => render( - + Photos - + - + Projects - + - + Projects-1 - + - + Projects-1A - + - + Projects-2 - + - + Projects-3 - + - + School - + - + Homework-1 - + - + Homework-1A - + - + Homework-2 - + - + Homework-3 - + diff --git a/packages/@react-spectrum/tree/chromatic-fc/TreeView.stories.tsx b/packages/@react-spectrum/tree/chromatic-fc/TreeView.stories.tsx index 19728d0823c..00cec78bf0e 100644 --- a/packages/@react-spectrum/tree/chromatic-fc/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/chromatic-fc/TreeView.stories.tsx @@ -19,7 +19,7 @@ import FileTxt from '@spectrum-icons/workflow/FileTxt'; import Folder from '@spectrum-icons/workflow/Folder'; import React from 'react'; import {Text} from '@react-spectrum/text'; -import {TreeItemContent, TreeView, TreeViewItem} from '../src'; +import {TreeView, TreeViewItem, TreeViewItemContent} from '../src'; export default { title: 'TreeView' @@ -30,7 +30,7 @@ function TestTree(props) {
- + Photos @@ -43,10 +43,10 @@ function TestTree(props) { Delete - + - + Projects @@ -59,9 +59,9 @@ function TestTree(props) { Delete - + - + Projects-1 @@ -74,9 +74,9 @@ function TestTree(props) { Delete - + - + Projects-1A @@ -89,11 +89,11 @@ function TestTree(props) { Delete - + - + Projects-2 @@ -106,13 +106,13 @@ function TestTree(props) { Delete - + - + Projects-3 - + diff --git a/packages/@react-spectrum/tree/chromatic/TreeView.stories.tsx b/packages/@react-spectrum/tree/chromatic/TreeView.stories.tsx index 20fc3b429c5..df3441545c9 100644 --- a/packages/@react-spectrum/tree/chromatic/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/chromatic/TreeView.stories.tsx @@ -24,7 +24,7 @@ import {Heading, Text} from '@react-spectrum/text'; import {IllustratedMessage} from '@react-spectrum/illustratedmessage'; import {Meta} from '@storybook/react'; import React from 'react'; -import {SpectrumTreeViewProps, TreeItemContent, TreeView, TreeViewItem} from '../src'; +import {SpectrumTreeViewProps, TreeView, TreeViewItem, TreeViewItemContent} from '../src'; let states = [ {selectionMode: ['multiple', 'single']}, @@ -78,7 +78,7 @@ const Template = ({combos}) => ( - + Photos @@ -91,10 +91,10 @@ const Template = ({combos}) => ( Delete - + - + Projects @@ -107,9 +107,9 @@ const Template = ({combos}) => ( Delete - + - + Projects-1 @@ -122,9 +122,9 @@ const Template = ({combos}) => ( Delete - + - + Projects-1A @@ -137,11 +137,11 @@ const Template = ({combos}) => ( Delete - + - + Projects-2 @@ -154,13 +154,13 @@ const Template = ({combos}) => ( Delete - + - + Projects-3 - + @@ -190,9 +190,9 @@ const EmptyTemplate = () => renderEmptyState={renderEmptyState}> {() => ( - + dummy content - + )} diff --git a/packages/@react-spectrum/tree/docs/TreeView.mdx b/packages/@react-spectrum/tree/docs/TreeView.mdx index 44d74459b20..273ea9f537f 100644 --- a/packages/@react-spectrum/tree/docs/TreeView.mdx +++ b/packages/@react-spectrum/tree/docs/TreeView.mdx @@ -26,7 +26,7 @@ import Image from '@spectrum-icons/workflow/Image'; import Edit from '@spectrum-icons/workflow/Edit'; import Delete from '@spectrum-icons/workflow/Delete'; import {Text} from '@react-spectrum/text'; -import {Collection, TreeView, TreeViewItem, TreeItemContent} from '@react-spectrum/tree'; +import {Collection, TreeView, TreeViewItem, TreeViewItemContent} from '@react-spectrum/tree'; import {JSX} from "react"; import {Key} from "@react-types/shared"; import {ActionGroup, Item} from '@react-spectrum/actiongroup'; @@ -54,57 +54,57 @@ keywords: [tree, grid] ```tsx example - + Documents - + - + Project A - + - + Weekly Report - + - + Document 1 - + - + Document 2 - + - + Photos - + - + Image 1 - + - + Image 2 - + - + Image 3 - + @@ -146,10 +146,10 @@ const DynamicTreeItem = (props) => { return ( <> - + {props.name} {props.icon} - + {(item: any) => ( - + Bookmarks - + - + Adobe - + - + Google - + - + New York Times - + @@ -421,7 +421,7 @@ The `` component works with frameworks and client side routers lik {(item: MyItem) => ( - + {item.name} {item.icon} alert(`Item: ${item.id}, Action: ${key}`)}> @@ -434,7 +434,7 @@ The `` component works with frameworks and client side routers lik Delete - + )} @@ -446,7 +446,7 @@ The `` component works with frameworks and client side routers lik {(item: MyItem) => ( - + {item.name} {item.icon} alert(`Item: ${item.id}, Action: ${key}`)}> @@ -459,7 +459,7 @@ The `` component works with frameworks and client side routers lik Delete - + )} @@ -471,9 +471,9 @@ The `` component works with frameworks and client side routers lik -### TreeItemContent props +### TreeViewItemContent props - + ### TreeViewItem props diff --git a/packages/@react-spectrum/tree/src/TreeView.tsx b/packages/@react-spectrum/tree/src/TreeView.tsx index a42d0152a93..56ebaf2f718 100644 --- a/packages/@react-spectrum/tree/src/TreeView.tsx +++ b/packages/@react-spectrum/tree/src/TreeView.tsx @@ -11,7 +11,18 @@ */ import {AriaTreeGridListProps} from '@react-aria/tree'; -import {ButtonContext, TreeItemContentProps, TreeItemContentRenderProps, TreeItemProps, TreeItemRenderProps, TreeRenderProps, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, useContextProps} from 'react-aria-components'; +import { + ButtonContext, + Tree, + TreeItem, + TreeItemContent, + TreeItemContentProps, + TreeItemContentRenderProps, + TreeItemProps, + TreeItemRenderProps, + TreeRenderProps, + useContextProps +} from 'react-aria-components'; import {Checkbox} from '@react-spectrum/checkbox'; import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium'; import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium'; @@ -97,9 +108,9 @@ export const TreeView = React.forwardRef(function TreeView(pro return ( - tree({isEmpty})} selectionBehavior={selectionBehavior as SelectionBehavior} ref={domRef}> + tree({isEmpty})} selectionBehavior={selectionBehavior as SelectionBehavior} ref={domRef}> {props.children} - + ); }) as (props: SpectrumTreeViewProps & {ref?: DOMRef}) => ReactElement; @@ -223,7 +234,7 @@ export const TreeViewItem = (props: SpectrumTreeViewItemProps< } = props; return ( - treeRow({ ...renderProps, @@ -233,13 +244,13 @@ export const TreeViewItem = (props: SpectrumTreeViewItemProps< }; -export const TreeItemContent = (props: Omit & {children: ReactNode}) => { +export const TreeViewItemContent = (props: Omit & {children: ReactNode}) => { let { children } = props; return ( - + {({isExpanded, hasChildItems, level, selectionMode, selectionBehavior, isDisabled, isSelected, isFocusVisible}) => (
{selectionMode !== 'none' && selectionBehavior === 'toggle' && ( @@ -275,7 +286,7 @@ export const TreeItemContent = (props: Omit &
)} - + ); }; diff --git a/packages/@react-spectrum/tree/src/index.ts b/packages/@react-spectrum/tree/src/index.ts index c6b9cae2689..b74185a6188 100644 --- a/packages/@react-spectrum/tree/src/index.ts +++ b/packages/@react-spectrum/tree/src/index.ts @@ -12,6 +12,6 @@ /// -export {TreeViewItem, TreeView, TreeItemContent} from './TreeView'; +export {TreeViewItem, TreeView, TreeViewItemContent} from './TreeView'; export {Collection} from 'react-aria-components'; export type {SpectrumTreeViewProps, SpectrumTreeViewItemProps} from './TreeView'; diff --git a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx index 2d68536f804..2c99783ec8e 100644 --- a/packages/@react-spectrum/tree/stories/TreeView.stories.tsx +++ b/packages/@react-spectrum/tree/stories/TreeView.stories.tsx @@ -22,7 +22,7 @@ import {Heading, Text} from '@react-spectrum/text'; import {IllustratedMessage} from '@react-spectrum/illustratedmessage'; import {Link} from '@react-spectrum/link'; import React from 'react'; -import {SpectrumTreeViewProps, TreeItemContent, TreeView, TreeViewItem} from '../src'; +import {SpectrumTreeViewProps, TreeView, TreeViewItem, TreeViewItemContent} from '../src'; export default { title: 'TreeView', @@ -48,7 +48,7 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => (
- + Photos @@ -61,10 +61,10 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Delete - + - + Projects @@ -77,9 +77,9 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Delete - + - + Projects-1 @@ -92,9 +92,9 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Delete - + - + Projects-1A @@ -107,11 +107,11 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Delete - + - + Projects-2 @@ -124,10 +124,10 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Delete - + - + Projects-3 @@ -140,7 +140,7 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps) => ( Delete - + @@ -209,7 +209,7 @@ const DynamicTreeItem = (props) => { return ( <> - + {name} {icon} @@ -222,7 +222,7 @@ const DynamicTreeItem = (props) => { Delete - + {(item: any) => ( ( - + Photos @@ -46,10 +46,10 @@ let StaticTree = ({treeProps = {}, rowProps = {}}) => ( Delete - + - + Projects @@ -62,9 +62,9 @@ let StaticTree = ({treeProps = {}, rowProps = {}}) => ( Delete - + - + Projects-1 @@ -77,9 +77,9 @@ let StaticTree = ({treeProps = {}, rowProps = {}}) => ( Delete - + - + Projects-1A @@ -92,11 +92,11 @@ let StaticTree = ({treeProps = {}, rowProps = {}}) => ( Delete - + - + Projects-2 @@ -109,10 +109,10 @@ let StaticTree = ({treeProps = {}, rowProps = {}}) => ( Delete - + - + Projects-3 @@ -125,7 +125,7 @@ let StaticTree = ({treeProps = {}, rowProps = {}}) => ( Delete - + @@ -166,7 +166,7 @@ const DynamicTreeItem = (props) => { return ( <> - + {name} @@ -178,7 +178,7 @@ const DynamicTreeItem = (props) => { Delete - + {(item: any) => ( { let {getAllByRole} = render( - + Test - + ); @@ -488,9 +488,9 @@ describe('Tree', () => { let {getAllByRole} = render( - + Test - + ); @@ -1238,9 +1238,9 @@ describe('Tree', () => { let tree = render( - + Test - + ); @@ -1252,9 +1252,9 @@ describe('Tree', () => { tree, - + Test - + ); @@ -1266,9 +1266,9 @@ describe('Tree', () => { tree, - + Test - + ); diff --git a/packages/react-aria-components/docs/TableAnatomy.svg b/packages/react-aria-components/docs/TableAnatomy.svg index 0a4fd051714..b760f97603b 100644 --- a/packages/react-aria-components/docs/TableAnatomy.svg +++ b/packages/react-aria-components/docs/TableAnatomy.svg @@ -1,4 +1,4 @@ - + diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index 1d1460be6e4..0c157703a4f 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -36,11 +36,11 @@ preRelease: beta # Tree -{docs.exports.UNSTABLE_Tree.description} +{docs.exports.Tree.description} @@ -51,13 +51,12 @@ This example's MyTreeItemContent is from the [Reusable Wrappers](#reusable-wrapp ```tsx example import { - UNSTABLE_Tree as Tree, - UNSTABLE_TreeItem as TreeItem, - UNSTABLE_TreeItemContent as TreeItemContent, + Tree, + TreeItem, + TreeItemContent, Button, Collection } from 'react-aria-components'; -import {MyCheckbox} from './Checkbox'; @@ -254,7 +253,7 @@ HTML lists are meant for static content, rather than heirarchies with rich inter ## Anatomy - + A Tree consists of a container element, with items containing data inside. The items within a tree may contain focusable elements or plain text content. Each item may also contain a button to toggle the expandable state of that item. @@ -386,6 +385,7 @@ or updates over time. In the example below, data for each item is provided to th ```tsx example export=true import type {TreeProps} from 'react-aria-components'; +import {MyCheckbox} from './Checkbox'; let items = [ {id: 1, title: 'Documents', children: [ {id: 2, title: 'Project', children: [ @@ -752,15 +752,15 @@ Use the `renderEmptyState` prop to customize what the `Tree` will display if the ### Tree - + ### TreeItem - + ### TreeItemContent - + ## Styling @@ -845,13 +845,13 @@ A `TreeItemContent` can be targeted with the `.react-aria-TreeItemContent` CSS s All React Aria Components export a corresponding context that can be used to send props to them from a parent element. This enables you to build your own compositional APIs similar to those found in React Aria Components itself. You can send any prop or ref via context that you could pass to the corresponding component. The local props and ref on the component are merged with the ones passed via context, with the local props taking precedence (following the rules documented in [mergeProps](mergeProps.html)). - + This example shows a component that accepts a `Tree` and a [ToggleButton](ToggleButton.html) as children, and allows the user to turn selection mode for the tree on and off by pressing the button. ```tsx example render=false export=true import type {SelectionMode} from 'react-aria-components'; -import {ToggleButtonContext, UNSTABLE_TreeContext as TreeContext} from 'react-aria-components'; +import {ToggleButtonContext, TreeContext} from 'react-aria-components'; function Selectable({children}) { let [isSelected, onChange] = React.useState(false); diff --git a/packages/react-aria-components/docs/TreeAnatomy.svg b/packages/react-aria-components/docs/TreeAnatomy.svg index 0a4fd051714..e2191f2c3e9 100644 --- a/packages/react-aria-components/docs/TreeAnatomy.svg +++ b/packages/react-aria-components/docs/TreeAnatomy.svg @@ -1,194 +1,96 @@ - - - - - Column - - - Size - - - - - - 214 KB - - 120 KB - 88 KB - 24 KB - Proposal - Budget - Welcome - Onboarding - File name - - - - Cell - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - Select allcheckbox - + + + - - - - - - - - - - Table body - - - - - - - - - Table header - - - - - - - - + + + + + + + + + + Checkbox (optional) + + + + + + + Collapse and expand button + + + + + + + + + Photos + + PDFs + + + + + + Documents + + Tree item + + + - Row - - - - - - - - - - - - - + Tree + + + + + + + - - - - - - - - - - - - + + + + - - - - - - - - - - - - + + + + - - - - - - - - - - - - - + + + - - Selectioncheckbox - - - - + + + + - - - Dragbutton - - - - + + + - - - - - Columnresizer - - diff --git a/packages/react-aria-components/docs/examples/file-system.mdx b/packages/react-aria-components/docs/examples/file-system.mdx index 61e265038f2..8ce3cd18f21 100644 --- a/packages/react-aria-components/docs/examples/file-system.mdx +++ b/packages/react-aria-components/docs/examples/file-system.mdx @@ -13,7 +13,7 @@ export default ExampleLayout; import docs from 'docs:react-aria-components'; import {TypeLink} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; -import Table from '@react-spectrum/docs/pages/assets/component-illustrations/Table.svg'; +import Tree from '@react-spectrum/docs/pages/assets/component-illustrations/Table.svg'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; @@ -81,7 +81,7 @@ const filesystem = [ ``` ```tsx example standalone -import {Button, Collection, UNSTABLE_Tree as Tree, UNSTABLE_TreeItem as TreeItem, UNSTABLE_TreeItemContent as TreeItemContent} from 'react-aria-components'; +import {Button, Collection, Tree, TreeItem, TreeItemContent} from 'react-aria-components'; import ChevronIcon from '@spectrum-icons/ui/ChevronRightMedium'; function FileSystemExample() { @@ -157,7 +157,7 @@ module.exports = { url="../Tree.html" title="Tree" description="A tree provides users with a way to navigate nested hierarchical information, with support for keyboard navigation and selection."> -
+ diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index 03e7a23c719..237fb32dce0 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -133,16 +133,16 @@ export interface TreeProps extends Omit, 'children'> } -export const UNSTABLE_TreeContext = createContext, HTMLDivElement>>(null); -export const UNSTABLE_TreeStateContext = createContext | null>(null); +export const TreeContext = createContext, HTMLDivElement>>(null); +export const TreeStateContext = createContext | null>(null); /** * A tree provides users with a way to navigate nested hierarchical information, with support for keyboard navigation * and selection. */ -export const UNSTABLE_Tree = /*#__PURE__*/ (forwardRef as forwardRefType)(function Tree(props: TreeProps, ref: ForwardedRef) { +export const Tree = /*#__PURE__*/ (forwardRef as forwardRefType)(function Tree(props: TreeProps, ref: ForwardedRef) { // Render the portal first so that we have the collection by the time we render the DOM in SSR. - [props, ref] = useContextProps(props, ref, UNSTABLE_TreeContext); + [props, ref] = useContextProps(props, ref, TreeContext); return ( }> @@ -245,7 +245,7 @@ function TreeInner({props, collection, treeRef: ref}: TreeInne data-focus-visible={isFocusVisible || undefined}> , 'children'> {} -export const UNSTABLE_TreeItemContent = /*#__PURE__*/ createLeafComponent('content', function TreeItemContent(props: TreeItemContentProps) { +export const TreeItemContent = /*#__PURE__*/ createLeafComponent('content', function TreeItemContent(props: TreeItemContentProps) { let values = useContext(TreeItemContentContext)!; let renderProps = useRenderProps({ children: props.children, @@ -331,8 +331,8 @@ export interface TreeItemProps extends StyleRenderProps(props: TreeItemProps, ref: ForwardedRef, item: Node) => { - let state = useContext(UNSTABLE_TreeStateContext)!; +export const TreeItem = /*#__PURE__*/ createBranchComponent('item', (props: TreeItemProps, ref: ForwardedRef, item: Node) => { + let state = useContext(TreeStateContext)!; ref = useObjectRef(ref); // TODO: remove this when we support description in tree row // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -474,8 +474,8 @@ export interface TreeLoadingIndicatorRenderProps { export interface TreeLoaderProps extends RenderProps, StyleRenderProps {} -export const UNSTABLE_TreeLoadingIndicator = createLeafComponent('loader', function TreeLoader(props: TreeLoaderProps, ref: ForwardedRef, item: Node) { - let state = useContext(UNSTABLE_TreeStateContext); +export const TreeLoadingIndicator = createLeafComponent('loader', function TreeLoader(props: TreeLoaderProps, ref: ForwardedRef, item: Node) { + let state = useContext(TreeStateContext); // This loader row is is non-interactable, but we want the same aria props calculated as a typical row // @ts-ignore let {rowProps} = useTreeGridListItem({node: item}, state, ref); diff --git a/packages/react-aria-components/src/index.ts b/packages/react-aria-components/src/index.ts index 8c449cec95e..f13c12f7215 100644 --- a/packages/react-aria-components/src/index.ts +++ b/packages/react-aria-components/src/index.ts @@ -74,7 +74,7 @@ export {ToggleButton, ToggleButtonContext} from './ToggleButton'; export {ToggleButtonGroup, ToggleButtonGroupContext, ToggleGroupStateContext} from './ToggleButtonGroup'; export {Toolbar, ToolbarContext} from './Toolbar'; export {TooltipTrigger, Tooltip, TooltipTriggerStateContext, TooltipContext} from './Tooltip'; -export {UNSTABLE_TreeLoadingIndicator, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeContext, UNSTABLE_TreeItemContent, UNSTABLE_TreeStateContext} from './Tree'; +export {TreeLoadingIndicator, Tree, TreeItem, TreeContext, TreeItemContent, TreeStateContext} from './Tree'; export {useDragAndDrop} from './useDragAndDrop'; export {DropIndicator, DropIndicatorContext, DragAndDropContext} from './DragAndDrop'; export {Virtualizer as UNSTABLE_Virtualizer} from './Virtualizer'; diff --git a/packages/react-aria-components/stories/Tree.stories.tsx b/packages/react-aria-components/stories/Tree.stories.tsx index 282a156f13d..8a4d509b029 100644 --- a/packages/react-aria-components/stories/Tree.stories.tsx +++ b/packages/react-aria-components/stories/Tree.stories.tsx @@ -11,12 +11,12 @@ */ import {action} from '@storybook/addon-actions'; -import {Button, Checkbox, CheckboxProps, Collection, Key, UNSTABLE_ListLayout as ListLayout, Menu, MenuTrigger, Popover, Text, TreeItemProps, TreeProps, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, UNSTABLE_Virtualizer as Virtualizer} from 'react-aria-components'; +import {Button, Checkbox, CheckboxProps, Collection, Key, UNSTABLE_ListLayout as ListLayout, Menu, MenuTrigger, Popover, Text, Tree, TreeItem, TreeItemContent, TreeItemProps, TreeProps, UNSTABLE_Virtualizer as Virtualizer} from 'react-aria-components'; import {classNames} from '@react-spectrum/utils'; import {MyMenuItem} from './utils'; import React, {ReactNode, useMemo} from 'react'; import styles from '../example/index.css'; -import {UNSTABLE_TreeLoadingIndicator} from '../src/Tree'; +import {TreeLoadingIndicator} from '../src/Tree'; export default { title: 'React Aria Components' @@ -48,7 +48,7 @@ function MyCheckbox({children, ...props}: CheckboxProps) { const StaticTreeItem = (props: StaticTreeItemProps) => { return ( - classNames(styles, 'tree-item', { focused: isFocused, @@ -56,7 +56,7 @@ const StaticTreeItem = (props: StaticTreeItemProps) => { selected: isSelected, hovered: isHovered })}> - + {({isExpanded, hasChildItems, level, selectionMode, selectionBehavior}) => ( <> {selectionMode !== 'none' && selectionBehavior === 'toggle' && ( @@ -81,14 +81,14 @@ const StaticTreeItem = (props: StaticTreeItemProps) => { )} - + {props.title && props.children} - + ); }; const TreeExampleStaticRender = (args) => ( - + Photos @@ -103,7 +103,7 @@ const TreeExampleStaticRender = (args) => ( Projects-3 - classNames(styles, 'tree-item', { @@ -112,11 +112,11 @@ const TreeExampleStaticRender = (args) => ( selected: isSelected, hovered: isHovered })}> - + Reports - - - + + classNames(styles, 'tree-item', { @@ -125,13 +125,13 @@ const TreeExampleStaticRender = (args) => ( selected: isSelected, hovered: isHovered })}> - + {({isFocused}) => ( {`${isFocused} Tests`} )} - - - + + + ); export const TreeExampleStatic = { @@ -157,7 +157,7 @@ export const TreeExampleStatic = { }, parameters: { description: { - data: 'Note that the last two items are just to test bare minimum UNSTABLE_TreeItem and thus dont have the checkbox or any of the other contents that the other items have. The last item tests the isFocused renderProp' + data: 'Note that the last two items are just to test bare minimum TreeItem and thus dont have the checkbox or any of the other contents that the other items have. The last item tests the isFocused renderProp' } } }; @@ -194,7 +194,7 @@ let rows = [ const MyTreeLoader = () => { return ( - + {({level}) => { let message = `Level ${level} loading spinner`; if (level === 1) { @@ -206,7 +206,7 @@ const MyTreeLoader = () => { ); }} - + ); }; @@ -221,7 +221,7 @@ const DynamicTreeItem = (props: DynamicTreeItemProps) => { let {childItems, renderLoader} = props; return ( <> - classNames(styles, 'tree-item', { focused: isFocused, @@ -229,7 +229,7 @@ const DynamicTreeItem = (props: DynamicTreeItemProps) => { selected: isSelected, hovered: isHovered })}> - + {({isExpanded, hasChildItems, level, selectionBehavior, selectionMode}) => ( <> {selectionMode !== 'none' && selectionBehavior === 'toggle' && ( @@ -252,7 +252,7 @@ const DynamicTreeItem = (props: DynamicTreeItemProps) => { )} - + {(item: any) => ( @@ -260,7 +260,7 @@ const DynamicTreeItem = (props: DynamicTreeItemProps) => { )} - + {/* TODO this would need to check if the parent was loading and then the user would insert this tree loader after last row of that section. theoretically this would look like (loadingKeys.includes(parentKey) && props.id === last key of parent) &&.... both the parentKey of a given item as well as checking if the current tree item is the last item of said parent would need to be done by the user outside of this tree item? @@ -273,13 +273,13 @@ const DynamicTreeItem = (props: DynamicTreeItemProps) => { let defaultExpandedKeys = new Set(['projects', 'project-2', 'project-5', 'reports', 'reports-1', 'reports-1A', 'reports-1AB']); const TreeExampleDynamicRender = (args: TreeProps) => ( - + {(item) => ( {item.name} )} - + ); export const TreeExampleDynamic = { @@ -294,23 +294,23 @@ export const WithActions = { onAction: action('onAction'), ...TreeExampleDynamic.args }, - name: 'UNSTABLE_Tree with actions' + name: 'Tree with actions' }; const WithLinksRender = (args: TreeProps) => ( - + {(item) => ( {item.name} )} - + ); export const WithLinks = { ...TreeExampleDynamic, render: WithLinksRender, - name: 'UNSTABLE_Tree with links', + name: 'Tree with links', parameters: { description: { data: 'every tree item should link to adobe.com' @@ -323,7 +323,7 @@ function renderEmptyLoader({isLoading}) { } const EmptyTreeStatic = (args: {isLoading: boolean}) => ( - ( )} - + ); export const EmptyTreeStaticStory = { @@ -348,7 +348,7 @@ export const EmptyTreeStaticStory = { function LoadingStoryDepOnCollection(args) { return ( - + {(item) => ( id === 'project-2C'} isLoading={args.isLoading} id={item.id} childItems={item.childItems} textValue={item.name}> @@ -357,7 +357,7 @@ function LoadingStoryDepOnCollection(args) { )} {args.isLoading && } - + ); } @@ -376,13 +376,13 @@ export const LoadingStoryDepOnCollectionStory = { function LoadingStoryDepOnTop(args: TreeProps & {isLoading: boolean}) { return ( - + {(item) => ( (id === 'reports' || id === 'project-2C')} isLoading={args.isLoading} id={item.id} childItems={item.childItems} textValue={item.name}> {item.name} )} - + ); } @@ -420,7 +420,7 @@ const DynamicTreeItemWithButtonLoader = (props: DynamicTreeItemProps) => { return ( <> - classNames(styles, 'tree-item', { focused: isFocused, @@ -428,7 +428,7 @@ const DynamicTreeItemWithButtonLoader = (props: DynamicTreeItemProps) => { selected: isSelected, hovered: isHovered })}> - + {({isExpanded, hasChildItems, level, selectionBehavior, selectionMode}) => ( <> {selectionMode !== 'none' && selectionBehavior === 'toggle' && ( @@ -451,7 +451,7 @@ const DynamicTreeItemWithButtonLoader = (props: DynamicTreeItemProps) => { )} - + {(item: any) => ( @@ -459,20 +459,20 @@ const DynamicTreeItemWithButtonLoader = (props: DynamicTreeItemProps) => { )} - + ); }; function ButtonLoadingIndicator(args: TreeProps & {isLoading: boolean}) { return ( - + {(item) => ( (id === 'project-2' || id === 'project-5')} isLoading={args.isLoading} id={item.id} childItems={item.childItems} textValue={item.name}> {item.name} )} - + ); } diff --git a/packages/react-aria-components/test/Tree.ssr.test.js b/packages/react-aria-components/test/Tree.ssr.test.js new file mode 100644 index 00000000000..c8ddaa9a542 --- /dev/null +++ b/packages/react-aria-components/test/Tree.ssr.test.js @@ -0,0 +1,96 @@ +/* + * Copyright 2023 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import {fireEvent, screen, testSSR} from '@react-spectrum/test-utils-internal'; + +describe('Tree SSR', function () { + it('should render without errors', async function () { + await testSSR(__filename, ` + import {Button, Tree, TreeItem, TreeItemContent} from '../'; + + function MyTreeItemContent(props) { + return ( + + {({hasChildItems}) => ( + <> + + {props.children} + + )} + + ); + } + function Test() { + let [show, setShow] = React.useState(false); + return ( + <> + + + + + Documents + + + + Project + + {show && + + Weekly Report + + } + + + + + Photos + + + + Image 1 + + + + + Image 2 + + + + + + ); + } + + + + + `, () => { + // Assert that server rendered stuff into the HTML. + let rows = screen.getAllByRole('row'); + expect(rows.map(o => o.textContent)).toEqual(['Documents', 'Project', 'Photos', 'Image 1', 'Image 2']); + }); + + // Assert that hydrated UI matches what we expect. + let button = screen.getAllByRole('button', {name: 'Show'})[0]; + let rows = screen.getAllByRole('row'); + expect(rows.map(o => o.textContent)).toEqual(['Documents', 'Project', 'Photos', 'Image 1', 'Image 2']); + + // And that it updates correctly. + fireEvent.click(button); + rows = screen.getAllByRole('row'); + expect(rows.map(o => o.textContent)).toEqual(['Documents', 'Project', 'Weekly Report', 'Photos', 'Image 1', 'Image 2']); + }); +}); diff --git a/packages/react-aria-components/test/Tree.test.tsx b/packages/react-aria-components/test/Tree.test.tsx index 366782d3136..4eb9e7b4870 100644 --- a/packages/react-aria-components/test/Tree.test.tsx +++ b/packages/react-aria-components/test/Tree.test.tsx @@ -12,7 +12,7 @@ import {act, fireEvent, mockClickDefault, pointerMap, render, within} from '@react-spectrum/test-utils-internal'; import {AriaTreeTests} from './AriaTree.test-util'; -import {Button, Checkbox, Collection, UNSTABLE_ListLayout as ListLayout, Text, UNSTABLE_Tree, UNSTABLE_TreeItem, UNSTABLE_TreeItemContent, UNSTABLE_Virtualizer as Virtualizer} from '../'; +import {Button, Checkbox, Collection, UNSTABLE_ListLayout as ListLayout, Text, Tree, TreeItem, TreeItemContent, UNSTABLE_Virtualizer as Virtualizer} from '../'; import {composeStories} from '@storybook/react'; import React from 'react'; import * as stories from '../stories/Tree.stories'; @@ -29,8 +29,8 @@ let onExpandedChange = jest.fn(); let StaticTreeItem = (props) => { return ( - - + + {({isExpanded, hasChildItems, selectionMode, selectionBehavior}) => ( <> {(selectionMode !== 'none' || props.href != null) && selectionBehavior === 'toggle' && ( @@ -42,14 +42,14 @@ let StaticTreeItem = (props) => { )} - + {props.title && props.children} - + ); }; let StaticTree = ({treeProps = {}, rowProps = {}}) => ( - + Photos @@ -64,7 +64,7 @@ let StaticTree = ({treeProps = {}, rowProps = {}}) => ( Projects-3 - + ); let rows = [ @@ -99,8 +99,8 @@ let rows = [ let DynamicTreeItem = (props) => { return ( - - + + {({isExpanded, hasChildItems, selectionMode, selectionBehavior}) => ( <> {(selectionMode !== 'none' || props.href != null) && selectionBehavior === 'toggle' && ( @@ -112,7 +112,7 @@ let DynamicTreeItem = (props) => { )} - + {(item: any) => ( @@ -120,18 +120,18 @@ let DynamicTreeItem = (props) => { )} - + ); }; let DynamicTree = ({treeProps = {}, rowProps = {}}) => ( - + {(item: any) => ( {item.name} )} - + ); describe('Tree', () => { @@ -1016,19 +1016,19 @@ describe('Tree', () => { describe('empty state', () => { it('should allow the user to tab to the empty tree', async () => { let {getAllByRole, getByRole} = render( - `isFocused: ${isFocused}, isFocusVisible: ${isFocusVisible}`} aria-label="test empty tree" items={[]} renderEmptyState={({isFocused, isFocusVisible}) => {`Nothing in tree, isFocused: ${isFocused}, isFocusVisible: ${isFocusVisible}`}}> {() => ( - - + + Dummy Value - - + + )} - + ); let tree = getByRole('treegrid'); @@ -1180,7 +1180,7 @@ AriaTreeTests({ prefix: 'rac-static', renderers: { standard: () => render( - + Photos @@ -1208,10 +1208,10 @@ AriaTreeTests({ Homework-3 - + ), singleSelection: () => render( - + Photos @@ -1239,10 +1239,10 @@ AriaTreeTests({ Homework-3 - + ), allInteractionsDisabled: () => render( - + Photos @@ -1270,7 +1270,7 @@ AriaTreeTests({ Homework-3 - + ) } }); @@ -1295,8 +1295,8 @@ let controlledRows = [ let ControlledDynamicTreeItem = (props) => { return ( - - + + {({isExpanded, hasChildItems, selectionMode, selectionBehavior}) => ( <> {(selectionMode !== 'none' || props.href != null) && selectionBehavior === 'toggle' && ( @@ -1308,7 +1308,7 @@ let ControlledDynamicTreeItem = (props) => { )} - + {(item: any) => ( @@ -1316,7 +1316,7 @@ let ControlledDynamicTreeItem = (props) => { )} - + ); }; @@ -1324,13 +1324,13 @@ function ControlledDynamicTree(props) { let [expanded, setExpanded] = React.useState(new Set([])); return ( - + {(item: any) => ( {item.name} )} - + ); } From 5883d9c831e157f8d0dd8e207affc0e684fdaaaa Mon Sep 17 00:00:00 2001 From: GitHub Date: Fri, 14 Feb 2025 12:34:22 +1100 Subject: [PATCH 06/22] Add little svg --- .../assets/component-illustrations/Tree.svg | 61 +++++++++++++++++++ .../docs/TreeAnatomy.svg | 16 ++--- .../docs/examples/file-system.mdx | 2 +- 3 files changed, 67 insertions(+), 12 deletions(-) create mode 100644 packages/dev/docs/pages/assets/component-illustrations/Tree.svg diff --git a/packages/dev/docs/pages/assets/component-illustrations/Tree.svg b/packages/dev/docs/pages/assets/component-illustrations/Tree.svg new file mode 100644 index 00000000000..6b7d724fba5 --- /dev/null +++ b/packages/dev/docs/pages/assets/component-illustrations/Tree.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Photos + + PDFs + + + + + + Documents + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/react-aria-components/docs/TreeAnatomy.svg b/packages/react-aria-components/docs/TreeAnatomy.svg index e2191f2c3e9..6d6d2dd6d11 100644 --- a/packages/react-aria-components/docs/TreeAnatomy.svg +++ b/packages/react-aria-components/docs/TreeAnatomy.svg @@ -31,13 +31,7 @@ - Collapse and expand button - - - - - - + Collapse and expand button Photos @@ -66,10 +60,10 @@ - - - - + + + + diff --git a/packages/react-aria-components/docs/examples/file-system.mdx b/packages/react-aria-components/docs/examples/file-system.mdx index 8ce3cd18f21..f02cab32393 100644 --- a/packages/react-aria-components/docs/examples/file-system.mdx +++ b/packages/react-aria-components/docs/examples/file-system.mdx @@ -13,7 +13,7 @@ export default ExampleLayout; import docs from 'docs:react-aria-components'; import {TypeLink} from '@react-spectrum/docs'; import styles from '@react-spectrum/docs/src/docs.css'; -import Tree from '@react-spectrum/docs/pages/assets/component-illustrations/Table.svg'; +import Tree from '@react-spectrum/docs/pages/assets/component-illustrations/Tree.svg'; import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard'; import ChevronRight from '@spectrum-icons/workflow/ChevronRight'; From 6fe5ca899d9559c06fa7838f0b7914dd6839a785 Mon Sep 17 00:00:00 2001 From: GitHub Date: Mon, 17 Feb 2025 10:03:50 +1100 Subject: [PATCH 07/22] remove beta tag and add example card --- packages/dev/docs/pages/react-aria/components.mdx | 8 ++++++++ packages/react-aria-components/docs/Tree.mdx | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/dev/docs/pages/react-aria/components.mdx b/packages/dev/docs/pages/react-aria/components.mdx index d16f953fa8b..7b9af915e0a 100644 --- a/packages/dev/docs/pages/react-aria/components.mdx +++ b/packages/dev/docs/pages/react-aria/components.mdx @@ -38,6 +38,7 @@ import Menu from '../assets/component-illustrations/Menu.svg'; import ListBox from '../assets/component-illustrations/ListBox.svg'; import ListView from '../assets/component-illustrations/ListView.svg'; import Table from '../assets/component-illustrations/Table.svg'; +import Tree from '../assets/component-illustrations/Tree.svg'; import Calendar from '../assets/component-illustrations/Calendar.svg'; import RangeCalendar from '../assets/component-illustrations/RangeCalendar.svg'; import DateField from '../assets/component-illustrations/DateField.svg'; @@ -162,6 +163,13 @@ order: 5
+ + + + Date: Mon, 17 Feb 2025 15:33:11 +1100 Subject: [PATCH 08/22] fix example contrast problems --- packages/react-aria-components/docs/examples/file-system.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-aria-components/docs/examples/file-system.mdx b/packages/react-aria-components/docs/examples/file-system.mdx index f02cab32393..94596cb6730 100644 --- a/packages/react-aria-components/docs/examples/file-system.mdx +++ b/packages/react-aria-components/docs/examples/file-system.mdx @@ -94,7 +94,7 @@ function FileSystemExample() { return ( @@ -104,7 +104,7 @@ function FileSystemExample() { {hasChildItems ? :
} + cursor-default outline-hidden text-white`}> :
}
{item.name}
)} From 0e5d8259ca66f88715b93cc51677b57fce2f26de Mon Sep 17 00:00:00 2001 From: GitHub Date: Mon, 17 Feb 2025 17:50:55 +1100 Subject: [PATCH 09/22] review updates --- .../assets/component-illustrations/Tree.svg | 205 ++++++++++++++---- .../dev/docs/pages/react-spectrum/index.mdx | 10 +- packages/react-aria-components/docs/Tree.mdx | 93 ++++++-- .../docs/TreeAnatomy.svg | 179 +++++++++------ packages/react-aria-components/src/Tree.tsx | 33 +-- 5 files changed, 372 insertions(+), 148 deletions(-) diff --git a/packages/dev/docs/pages/assets/component-illustrations/Tree.svg b/packages/dev/docs/pages/assets/component-illustrations/Tree.svg index 6b7d724fba5..11283245e7a 100644 --- a/packages/dev/docs/pages/assets/component-illustrations/Tree.svg +++ b/packages/dev/docs/pages/assets/component-illustrations/Tree.svg @@ -1,61 +1,182 @@ - + - - + + - - - - + + + + + + + + + + Documents + - - + + + + + + - - - + + + + + + + - - - - - - + + + + + + - - - Photos - - PDFs - - - - + + + + - Documents + + + + + + + + + + + + 12 items + + + - - - - + Onboarding + PDF + + + + + Budget + XLS + + + + + Sales Pitch + PPT + + + + + - - - + + + + + - - - - + + + + + + + + + + + + + Documents + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 items + + + + + Onboarding + PDF + + + + + Budget + XLS + + + + + Sales Pitch + PPT + + + + + + + + + + + + + + + + + diff --git a/packages/dev/docs/pages/react-spectrum/index.mdx b/packages/dev/docs/pages/react-spectrum/index.mdx index 9af8c8e070c..7d33c1e26b9 100644 --- a/packages/dev/docs/pages/react-spectrum/index.mdx +++ b/packages/dev/docs/pages/react-spectrum/index.mdx @@ -58,6 +58,7 @@ import Menu from '../assets/component-illustrations/Menu.svg'; import ListBox from '../assets/component-illustrations/ListBox.svg'; import ListView from '../assets/component-illustrations/ListView.svg'; import Table from '../assets/component-illustrations/Table.svg'; +import Tree from '../assets/component-illustrations/Tree.svg'; import Calendar from '../assets/component-illustrations/Calendar.svg'; import RangeCalendar from '../assets/component-illustrations/RangeCalendar.svg'; import DateField from '../assets/component-illustrations/DateField.svg'; @@ -225,7 +226,14 @@ A React implementation of Spectrum, Adobe’s design system. description="A table view displays data in rows and columns, with row selection and sorting.">
- + + + + + + Documents + Project + Weekly Report + @@ -76,15 +79,18 @@ import { Photos + Image 1 + Image 2 + @@ -97,6 +103,7 @@ import { ```css hidden @import "@react-aria/example-theme"; @import './Button.mdx' layer(button); +@import './ToggleButton.mdx' layer(togglebutton); @import './Checkbox.mdx' layer(checkbox); ``` @@ -139,6 +146,7 @@ import { .react-aria-Button[slot=chevron] { all: unset; display: flex; + visibility: hidden; align-items: center; justify-content: center; width: 1.143rem; @@ -156,6 +164,10 @@ import { } } + &[data-has-child-items] .react-aria-Button[slot=chevron] { + visibility: visible; + } + &[data-expanded] .react-aria-Button[slot=chevron] svg { rotate: 90deg; } @@ -248,7 +260,7 @@ HTML lists are meant for static content, rather than heirarchies with rich inter * **Actions** – Items support optional actions such as navigation via click, tap, double click, or Enter key. * **Keyboard navigation** – Tree items and focusable children can be navigated using the arrow keys, along with page up/down, home/end, etc. Typeahead, auto scrolling, and selection modifier keys are supported as well. * **Touch friendly** – Selection and actions adapt their behavior depending on the device. For example, selection is activated via long press on touch when item actions are present. -* **Accessible** – Follows the [ARIA grid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treeview/), with additional selection announcements via an ARIA live region. Extensively tested across many devices and [assistive technologies](accessibility.html#testing) to ensure announcements and behaviors are consistent. +* **Accessible** – Follows the [ARIA treegrid pattern](https://www.w3.org/WAI/ARIA/apg/patterns/treegrid/), with additional selection announcements via an ARIA live region. Extensively tested across many devices and [assistive technologies](accessibility.html#testing) to ensure announcements and behaviors are consistent. ## Anatomy @@ -258,6 +270,23 @@ A Tree consists of a container element, with items containing data inside. The i If the tree supports item selection, each item can optionally include a selection checkbox. +```tsx render=false +import {Tree, TreeItem, TreeItemContent, Button, Checkbox} from 'react-aria-components'; + + + + + {props.children} - )} @@ -348,7 +376,11 @@ The `TreeItem` can also be wrapped, for example, it could include the TreeItemCo ```tsx example export=true render=false import {TreeItemProps} from 'react-aria-components'; -function MyTreeItem(props: Omit & Partial> & {title: string}) { +interface MyTreeItemProps extends Omit, Partial> { + title: string +} + +function MyTreeItem(props: MyTreeItemProps) { return ( @@ -385,6 +417,7 @@ or updates over time. In the example below, data for each item is provided to th ```tsx example export=true import type {TreeProps} from 'react-aria-components'; import {MyCheckbox} from './Checkbox'; + let items = [ {id: 1, title: 'Documents', children: [ {id: 2, title: 'Project', children: [ @@ -396,6 +429,7 @@ let items = [ {id: 6, title: 'Image 2', children: []} ]} ]; + interface FileType { id: number, title: string, @@ -404,26 +438,45 @@ interface FileType { function FileTree(props: TreeProps) { return ( - - {function renderItem(item) { + + { + ///- begin highlight -/// + function renderItem(item) { + ///- end highlight -/// return ( - + {({hasChildItems}) => ( <> - {item.title} - + )} - {renderItem} + { + ///- begin highlight -/// + renderItem // same as renderItem function above + ///- end highlight -/// + } ); @@ -530,7 +583,7 @@ function PokemonEvolutionTree(props: PokemonEvolutionTreeProps) { {({hasChildItems, selectionBehavior, selectionMode}) => ( <> {selectionBehavior === 'toggle' && selectionMode !== 'none' && } - + ) :
} + {children} +
+ )} + + ); +} diff --git a/starters/tailwind/stories/Tree.stories.tsx b/starters/tailwind/stories/Tree.stories.tsx new file mode 100644 index 00000000000..e8cf5aed26e --- /dev/null +++ b/starters/tailwind/stories/Tree.stories.tsx @@ -0,0 +1,61 @@ +import type { Meta } from '@storybook/react'; +import { Tree, TreeItem, TreeItemContent } from '../src/Tree'; +import React from 'react'; + +const meta: Meta = { + component: Tree, + parameters: { + layout: 'centered' + }, + tags: ['autodocs'] +}; + +export default meta; + +export const Example = (args: any) => ( + + + + Documents + + + + Project + + + + Weekly Report + + + + + + + Photos + + + + Image 1 + + + + + Image 2 + + + + +); + +Example.args = { + onAction: null, + defaultExpandedKeys: ['documents', 'photos', 'project'], + selectionMode: 'multiple', + defaultSelectedKeys: ['project'] +}; + +export const DisabledItems = (args: any) => ; +DisabledItems.args = { + ...Example.args, + disabledKeys: ['photos'] +}; From e6bd5065d6a50ec47a59c8df4e5902baeb464f5b Mon Sep 17 00:00:00 2001 From: GitHub Date: Wed, 19 Feb 2025 15:38:32 +1100 Subject: [PATCH 13/22] turn on verdaccio --- .circleci/comment.js | 4 ++-- .circleci/config.yml | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.circleci/comment.js b/.circleci/comment.js index 2e86d9c831b..a91994858c5 100644 --- a/.circleci/comment.js +++ b/.circleci/comment.js @@ -11,7 +11,7 @@ async function run() { let pr; // If we aren't running on a PR commit, double check if this is a branch created for a fork. If so, we'll need to // comment the build link on the fork. - if (!process.env.CIRCLE_PULL_REQUEST) { + if (true) { try { const commit = await octokit.git.getCommit({ owner: 'adobe', @@ -41,7 +41,7 @@ async function run() { break; } } - } else if (process.env.CIRCLE_BRANCH === 'main') { + } else if (true) { //If it isn't a PR commit, then we are on main. Create a comment for the test app and docs build await octokit.repos.createCommitComment({ owner: 'adobe', diff --git a/.circleci/config.yml b/.circleci/config.yml index 6e273f4d264..972aaa96d56 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -647,9 +647,6 @@ workflows: requires: - install - docs-verdaccio: - filters: - branches: - only: main requires: - install - deploy: From d181779df566703527ed1a47d7888841c0d4995b Mon Sep 17 00:00:00 2001 From: GitHub Date: Wed, 19 Feb 2025 16:30:59 +1100 Subject: [PATCH 14/22] turn on starterkits --- packages/react-aria-components/docs/Tree.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index 1ce56ea0b53..509bc539919 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -340,7 +340,7 @@ A `Tree` uses the following components, which may also be used standalone or reu To help kick-start your project, we offer starter kits that include example implementations of all React Aria components with various styling solutions. All components are fully styled, including support for dark mode, high contrast mode, and all UI states. Each starter comes with a pre-configured [Storybook](https://storybook.js.org/) that you can experiment with, or use as a starting point for your own component library. -{/* */} + ## Reusable wrappers From 04d50d34decc2e0185dbffa54a7ede2704daa912 Mon Sep 17 00:00:00 2001 From: GitHub Date: Wed, 19 Feb 2025 17:43:39 +1100 Subject: [PATCH 15/22] Revert "turn on verdaccio" This reverts commit e6bd5065d6a50ec47a59c8df4e5902baeb464f5b. --- .circleci/comment.js | 4 ++-- .circleci/config.yml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.circleci/comment.js b/.circleci/comment.js index a91994858c5..2e86d9c831b 100644 --- a/.circleci/comment.js +++ b/.circleci/comment.js @@ -11,7 +11,7 @@ async function run() { let pr; // If we aren't running on a PR commit, double check if this is a branch created for a fork. If so, we'll need to // comment the build link on the fork. - if (true) { + if (!process.env.CIRCLE_PULL_REQUEST) { try { const commit = await octokit.git.getCommit({ owner: 'adobe', @@ -41,7 +41,7 @@ async function run() { break; } } - } else if (true) { + } else if (process.env.CIRCLE_BRANCH === 'main') { //If it isn't a PR commit, then we are on main. Create a comment for the test app and docs build await octokit.repos.createCommitComment({ owner: 'adobe', diff --git a/.circleci/config.yml b/.circleci/config.yml index 972aaa96d56..6e273f4d264 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -647,6 +647,9 @@ workflows: requires: - install - docs-verdaccio: + filters: + branches: + only: main requires: - install - deploy: From 88303ff8ab696dc476c97d23fa2bc49a4b5f505a Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 19 Feb 2025 11:12:42 -0800 Subject: [PATCH 16/22] Make arrow in anatomy diagram closer to checkbox --- packages/react-aria-components/docs/TreeAnatomy.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-aria-components/docs/TreeAnatomy.svg b/packages/react-aria-components/docs/TreeAnatomy.svg index a69051429d0..767accca3f3 100644 --- a/packages/react-aria-components/docs/TreeAnatomy.svg +++ b/packages/react-aria-components/docs/TreeAnatomy.svg @@ -58,7 +58,7 @@ - Item + TreeItem Checkbox (optional) @@ -114,7 +114,7 @@ expand button" transform="translate(274 124.892)" fill="var(--anatomy-gray-800)" - + From f2a8f3e2ff172595dab4627b72ee20d4fba2510a Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Wed, 19 Feb 2025 11:14:53 -0800 Subject: [PATCH 17/22] formatting --- packages/dev/docs/src/StateTable.js | 4 +- packages/react-aria-components/docs/Tree.mdx | 175 ++++++++----------- packages/react-aria-components/src/Tree.tsx | 4 +- 3 files changed, 76 insertions(+), 107 deletions(-) diff --git a/packages/dev/docs/src/StateTable.js b/packages/dev/docs/src/StateTable.js index 3e2a3bd2cbf..a1ba4eb571c 100644 --- a/packages/dev/docs/src/StateTable.js +++ b/packages/dev/docs/src/StateTable.js @@ -17,12 +17,12 @@ import styles from './docs.css'; import tableStyles from '@adobe/spectrum-css-temp/components/table/vars.css'; import typographyStyles from '@adobe/spectrum-css-temp/components/typography/vars.css'; -export function StateTable({properties, showOptional}) { +export function StateTable({properties, showOptional, hideSelector}) { let props = Object.values(properties); if (!showOptional) { props = props.filter(prop => !prop.optional); } - let showSelector = props.some(prop => prop.selector); + let showSelector = !hideSelector && props.some(prop => prop.selector); return (
diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index 509bc539919..6b2e0c722d7 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -280,9 +280,7 @@ import {Tree, TreeItem, TreeItemContent, Button, Checkbox} from 'react-aria-comp - - - + {/* ... */} @@ -347,8 +345,6 @@ To help kick-start your project, we offer starter kits that include example impl If you will use a Tree in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency. -The following example includes a custom tree item with an action button. - ```tsx example export=true render=false import type {TreeItemContentProps} from 'react-aria-components'; import {Button} from 'react-aria-components'; @@ -356,34 +352,34 @@ import {Button} from 'react-aria-components'; function MyTreeItemContent(props: TreeItemContentProps) { return ( - {({hasChildItems, selectionBehavior, selectionMode}) => ( - <> - {selectionBehavior === 'toggle' && selectionMode !== 'none' && } - - {props.children} - - )} + {({hasChildItems, selectionBehavior, selectionMode}) => ( + <> + {selectionBehavior === 'toggle' && selectionMode !== 'none' && } + + {props.children} + + )} ); } ``` -The `TreeItem` can also be wrapped. For example, it could include the TreeItemContent automatically. +The `TreeItem` can also be wrapped. This example accepts a `title` prop and renders the `TreeItemContent` automatically. ```tsx example export=true render=false import {TreeItemProps} from 'react-aria-components'; -interface MyTreeItemProps extends Omit, Partial> { +interface MyTreeItemProps extends Partial { title: string } function MyTreeItem(props: MyTreeItemProps) { return ( - + {props.title} @@ -447,13 +443,11 @@ function FileTree(props: TreeProps) { /*- end highlight -*/ selectionMode="multiple" {...props}> - { - ///- begin highlight -/// - function renderItem(item) { + {/*- begin highlight -*/} + {function renderItem(item) { ///- end highlight -/// return ( - + {item.title} - { - ///- begin highlight -/// - renderItem // same as renderItem function above - ///- end highlight -/// - } + {/*- begin highlight -*/} + {/* recursively render children */} + {renderItem} + {/*- end highlight -*/} ); @@ -572,14 +565,11 @@ function PokemonEvolutionTree( > {function renderItem(item) { return ( - - - {item.name} - + {renderItem} - + ); }} @@ -630,27 +620,22 @@ Items may also have an action specified by directly applying `onAction` on the ` ```tsx example - {/*- begin highlight -*/} - alert(`Opening Bulbasaur...`)}> - {/*- end highlight -*/} - - Bulbasaur - - {/*- begin highlight -*/} - alert(`Opening Ivysaur...`)}> - {/*- end highlight -*/} - - Ivysaur - - {/*- begin highlight -*/} - alert(`Opening Venisaur...`)}> - {/*- end highlight -*/} - - Venisaur - - - - + alert(`Opening Bulbasaur...`)} + /*- end highlight -*/ + id="bulbasaur" + title="Bulbasaur"> + alert(`Opening Ivysaur...`)} + id="ivysaur" + title="Ivysaur"> + alert(`Opening Venisaur...`)} + id="venisaur" + title="Venisaur" /> + + ``` @@ -661,21 +646,25 @@ Tree items may also be links to another page or website. This can be achieved by ```tsx example - - - Bulbasaur - - - - Ivysaur - - - - Venisaur - - - - + + + + + ``` @@ -697,23 +686,13 @@ Note that you are responsible for the styling of disabled items, however, the se ```tsx example - - - Bulbasaur - + {/*- begin highlight -*/} - + {/*- end highlight -*/} - - Ivysaur - - - - Venisaur - - - - + + + ``` @@ -729,23 +708,13 @@ When `disabledBehavior` is set to `selection`, interactions such as focus, dragg disabledBehavior="selection" /*- end highlight -*/ > - - - Bulbasaur - + {/*- begin highlight -*/} - + {/*- end highlight -*/} - - Ivysaur - - - - Venisaur - - - - + + + ``` @@ -857,7 +826,7 @@ A `Tree` can be targeted with the `.react-aria-Tree` CSS selector, or by overrid ### TreeItem -A `TreeItem` can be targeted with the `.react-aria-TreeItem` CSS selector, or by overriding with a custom `className`. It supports the following states and render props: +A `TreeItem` can be targeted with the `.react-aria-TreeItem` CSS selector, or by overriding with a custom `className`. It supports the following states: @@ -871,9 +840,9 @@ TreeItem also exposes a `--tree-item-level` CSS custom property, which you can u ### TreeItemContent -A `TreeItemContent` can be targeted with the `.react-aria-TreeItemContent` CSS selector, or by overriding with a custom `className`. It supports the following states and render props: +`TreeItemContent` does not render a DOM node. It supports the following render props: - + ## Advanced customization diff --git a/packages/react-aria-components/src/Tree.tsx b/packages/react-aria-components/src/Tree.tsx index f197e26a459..abfa0250656 100644 --- a/packages/react-aria-components/src/Tree.tsx +++ b/packages/react-aria-components/src/Tree.tsx @@ -280,9 +280,9 @@ export interface TreeItemRenderProps extends Omit, - // The unique id of the tree row. + /** The unique id of the tree row. */ id: Key } From f5aa5f64fe23c67b1011bca9c9a5d5ce321c74d4 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 20 Feb 2025 07:43:54 +1100 Subject: [PATCH 18/22] Add Tree to vanilla starters --- packages/react-aria-components/docs/Tree.mdx | 6 +++--- scripts/extractStarter.mjs | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/react-aria-components/docs/Tree.mdx b/packages/react-aria-components/docs/Tree.mdx index 6b2e0c722d7..fadb53a78c0 100644 --- a/packages/react-aria-components/docs/Tree.mdx +++ b/packages/react-aria-components/docs/Tree.mdx @@ -346,13 +346,13 @@ To help kick-start your project, we offer starter kits that include example impl If you will use a Tree in multiple places in your app, you can wrap all of the pieces into a reusable component. This way, the DOM structure, styling code, and other logic are defined in a single place and reused everywhere to ensure consistency. ```tsx example export=true render=false -import type {TreeItemContentProps} from 'react-aria-components'; +import type {TreeItemContentProps, TreeItemContentRenderProps} from 'react-aria-components'; import {Button} from 'react-aria-components'; function MyTreeItemContent(props: TreeItemContentProps) { return ( - {({hasChildItems, selectionBehavior, selectionMode}) => ( + {({hasChildItems, selectionBehavior, selectionMode}: TreeItemContentRenderProps) => ( <> {selectionBehavior === 'toggle' && selectionMode !== 'none' && }