Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 39 additions & 1 deletion packages/dev/s2-docs/pages/react-aria/Tree.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,43 @@ import {TextField} from 'vanilla-starter/TextField';
</Tree>
```

## Animation

Rows revealed by expanding a parent are marked with the `isEntering` state, and rows hidden by collapsing a
parent stay mounted with the `isExiting` state until their animations finish. Use the corresponding
`data-entering` and `data-exiting` selectors to animate rows in and out.

Expansion state itself is never delayed: `aria-expanded` updates immediately, and exiting rows are `inert`,
so they are excluded from keyboard navigation and hidden from assistive technology while they animate away.

```css render=false
.react-aria-TreeItem {
height: var(--tree-item-height, auto);
overflow: clip;
transition: height 200ms, padding 200ms, opacity 200ms;

&[data-entering],
&[data-exiting] {
padding-block: 0;
opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
transition: none;
}
}
```

`height: auto` can't be animated, so while a row is entering or exiting the Tree sets
`--tree-item-height` to the animation's target height (zero when collapsing, the measured CSS height
when expanding), and back to `auto` once the animation finishes. Resting size changes are tracked with
`ResizeObserver`. This is only measured when the row declares a transition on `height` or `all`.
A row can't shrink below its own padding or minimum height, so animate those alongside the height.

Expand/collapse animations are not supported in a [virtualized](Virtualizer) Tree. Virtualized rows
may unmount before an animation completes, so expansion and collapse remain immediate there.
Load-more indicators also disappear immediately when their parent collapses.

## Drag and drop

Tree supports drag and drop interactions when the `dragAndDropHooks` prop is provided using the <TypeLink links={docs.links} type={docs.exports.useDragAndDrop} /> hook. Users can drop data on the list as a whole, on individual items, insert new items between existing ones, or reorder items. React Aria supports drag and drop via mouse, touch, keyboard, and screen reader interactions. See the [drag and drop guide](dnd?component=Tree) to learn more.
Expand Down Expand Up @@ -481,7 +518,8 @@ function Example() {
links={docs.links}
showDescription
cssVariables={{
'--tree-item-level': "The depth of the item within the tree. Useful to calculate indentation."
'--tree-item-level': "The depth of the item within the tree. Useful to calculate indentation.",
'--tree-item-height': "The target CSS height in pixels while expanding or collapsing, and auto otherwise. Set when height or all is used in the CSS transition."
}} />

### TreeItemContent
Expand Down
23 changes: 13 additions & 10 deletions packages/react-aria-components/src/Collection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ export interface CollectionBranchProps {
/** The parent node of the items to render. */
parent: Node<unknown>;
/** A function that renders a drop indicator between items. */
renderDropIndicator?: (target: ItemDropTarget) => ReactNode;
renderDropIndicator?: (target: ItemDropTarget, source?: Node<unknown>) => ReactNode;
}

export interface CollectionRootProps extends HTMLAttributes<HTMLElement> {
Expand All @@ -182,7 +182,7 @@ export interface CollectionRootProps extends HTMLAttributes<HTMLElement> {
/** A ref to the scroll container for the collection. */
scrollRef?: RefObject<HTMLElement | null>;
/** A function that renders a drop indicator between items. */
renderDropIndicator?: (target: ItemDropTarget) => ReactNode;
renderDropIndicator?: (target: ItemDropTarget, source?: Node<unknown>) => ReactNode;
}

export interface CollectionRenderer {
Expand Down Expand Up @@ -210,7 +210,7 @@ export const DefaultCollectionRenderer: CollectionRenderer = {
function useCollectionRender(
collection: ICollection<Node<unknown>>,
parent: Node<unknown> | null,
renderDropIndicator?: (target: ItemDropTarget) => ReactNode
renderDropIndicator?: (target: ItemDropTarget, source?: Node<unknown>) => ReactNode
) {
return useCachedChildren({
items: parent ? collection.getChildren!(parent.key) : collection,
Expand All @@ -229,7 +229,7 @@ function useCollectionRender(

return (
<>
{renderDropIndicator({type: 'item', key: node.key, dropPosition: 'before'})}
{renderDropIndicator({type: 'item', key: node.key, dropPosition: 'before'}, node)}
{rendered}
{renderAfterDropIndicators(collection, node, renderDropIndicator)}
</>
Expand All @@ -241,7 +241,7 @@ function useCollectionRender(
export function renderAfterDropIndicators(
collection: ICollection<Node<unknown>>,
node: Node<unknown>,
renderDropIndicator: (target: ItemDropTarget) => ReactNode
renderDropIndicator: (target: ItemDropTarget, source?: Node<unknown>) => ReactNode
): ReactNode {
let key = node.key;
let keyAfter = collection.getKeyAfter(key);
Expand Down Expand Up @@ -269,11 +269,14 @@ export function renderAfterDropIndicators(
(current.parentKey !== nextItemInFlattenedCollection.parentKey &&
nextItemInFlattenedCollection.level < current.level))
) {
let indicator = renderDropIndicator({
type: 'item',
key: current.key,
dropPosition: 'after'
});
let indicator = renderDropIndicator(
{
type: 'item',
key: current.key,
dropPosition: 'after'
},
node
);
if (isValidElement(indicator)) {
afterIndicators.push(cloneElement(indicator, {key: `${current.key}-after`}));
}
Expand Down
Loading