Skip to content

Commit

Permalink
add resizing story and fix text truncation/border cut off/addition pr…
Browse files Browse the repository at this point in the history
…op considerations
  • Loading branch information
LFDanLu committed Mar 7, 2024
1 parent e1805c4 commit 0447bd8
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 102 deletions.
43 changes: 30 additions & 13 deletions packages/@react-spectrum/tree/src/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,18 @@

import {AriaTreeGridListProps} from '@react-aria/tree';
import {ButtonContext, Collection, Tree, TreeItem, TreeItemContent, TreeItemContentRenderProps, TreeItemProps, TreeItemRenderProps, useContextProps} from 'react-aria-components';
import React, {createContext, isValidElement, ReactElement, ReactNode, useContext, useRef} from 'react';
import {Checkbox} from '@react-spectrum/checkbox';
import ChevronLeftMedium from '@spectrum-icons/ui/ChevronLeftMedium';
import ChevronRightMedium from '@spectrum-icons/ui/ChevronRightMedium';
import {DOMRef, Expandable, Key, SpectrumSelectionProps, StyleProps} from '@react-types/shared';
import {isAndroid} from '@react-aria/utils';
import {Text} from '@react-spectrum/text';
import React, {createContext, isValidElement, ReactElement, ReactNode, useContext, useRef} from 'react';
import {SlotProvider, useDOMRef, useStyleProps} from '@react-spectrum/utils';
import {style} from '@react-spectrum/style-macro-s1' with {type: 'macro'};
import {Text} from '@react-spectrum/text';
import {useButton} from '@react-aria/button';
import {useLocale} from '@react-aria/i18n';


export interface SpectrumTreeViewProps<T> extends Omit<AriaTreeGridListProps<T>, 'children'>, StyleProps, SpectrumSelectionProps, Expandable {
/** Provides content to display when there are no items in the tree. */
renderEmptyState?: () => JSX.Element,
Expand All @@ -40,9 +39,14 @@ export interface SpectrumTreeViewProps<T> extends Omit<AriaTreeGridListProps<T>,
children?: ReactNode | ((item: T) => ReactNode)
}

export interface SpectrumTreeViewItemProps extends TreeItemProps {
title?: string,
children: ReactNode
// TODO: I removed title since tree rows can have action buttons and stuff unlike other instances of items that use title (aka Sections and Columns) that typically don't have
// any other content that their internal text content
// TODO: write tests for all of these props to make sure things are propagating
export interface SpectrumTreeViewItemProps extends Omit<TreeItemProps, 'className' | 'style' | 'value'> {
/** Rendered contents of the tree item or child items. */
children: ReactNode,
/** Whether this item has children, even if not loaded yet. */
hasChildItems?: boolean
}

interface TreeRendererContextValue {
Expand All @@ -54,7 +58,16 @@ function useTreeRendererContext(): TreeRendererContextValue {
return useContext(TreeRendererContext)!;
}

// TODO rename file to TreeView
// TODO: the below is needed so the borders of the top and bottom row isn't cut off if the TreeView is wrapped within a container by always reserving the 2px needed for the
// keyboard focus ring
const tree = style({
borderWidth: 2,
boxSizing: 'border-box',
borderXWidth: 0,
borderStyle: 'solid',
borderColor: 'transparent'
});

function TreeView<T extends object>(props: SpectrumTreeViewProps<T>, ref: DOMRef<HTMLDivElement>) {
let {children} = props;

Expand All @@ -68,7 +81,7 @@ function TreeView<T extends object>(props: SpectrumTreeViewProps<T>, ref: DOMRef

return (
<TreeRendererContext.Provider value={{renderer}}>
<Tree {...props} {...styleProps} ref={domRef}>
<Tree {...props} {...styleProps} className={tree()} ref={domRef}>
{props.children}
</Tree>
</TreeRendererContext.Provider>
Expand Down Expand Up @@ -129,7 +142,10 @@ const treeContent = style<Pick<TreeItemContentRenderProps, 'isDisabled'>>({
gridArea: 'content',
color: {
isDisabled: 'gray-400'
}
},
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden'
});

const treeActions = style({
Expand Down Expand Up @@ -171,7 +187,8 @@ const treeRowOutline = style({
export const TreeViewItem = (props: SpectrumTreeViewItemProps) => {
let {
children,
childItems
childItems,
hasChildItems
} = props;

let content;
Expand Down Expand Up @@ -219,19 +236,19 @@ export const TreeViewItem = (props: SpectrumTreeViewItemProps) => {
slot="selection" />
)}
<div style={{gridArea: 'level-padding', marginInlineEnd: `calc(${level - 1} * var(--spectrum-global-dimension-size-200))`}} />
{hasChildRows && <ExpandableRowChevronMacros isDisabled={isDisabled} isExpanded={isExpanded} />}
{(hasChildRows || hasChildItems) && <ExpandableRowChevronMacros isDisabled={isDisabled} isExpanded={isExpanded} />}
<SlotProvider
slots={{
text: {UNSAFE_className: treeContent({isDisabled})},
// TODO update this
icon: {UNSAFE_className: treeIcon(), size: 'S'},
actionButton: {UNSAFE_className: treeActions(), isQuiet: true},
actionGroup: {
UNSAFE_className: treeActions(),
isQuiet: true,
density: 'compact',
buttonLabelBehavior: 'hide',
isDisabled
isDisabled,
overflowMode: 'collapse'
}
// TODO handle action menu the same way as in ListView. Should it support a action menu?
// actionMenu: {UNSAFE_className: styles['react-spectrum-ListViewItem-actionmenu'], isQuiet: true}
Expand Down
167 changes: 85 additions & 82 deletions packages/@react-spectrum/tree/stories/TreeView.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,39 +26,26 @@ export default {

// TODO: audit the package json
// TODO add href story and onAction story for static and dynamic

// TODO add a resizable wrapper around this but for now apply a widht and height
// TODO: This story crashes on save and story switch, not sure why or if only local...
export const TreeExampleStatic = (args: SpectrumTreeViewProps<unknown>) => (
<TreeView {...args} height={300} width={300} disabledKeys={['projects-1']} aria-label="test static tree" onExpandedChange={action('onExpandedChange')} onSelectionChange={action('onSelectionChange')}>
<TreeViewItem id="Photos" textValue="Photos">
<Text>Photos</Text>
<Folder />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
<TreeViewItem id="projects" textValue="Projects">
<Text>Projects</Text>
<Folder />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
<TreeViewItem id="projects-1" textValue="Projects-1">
<Text>Projects-1</Text>
<div style={{width: '300px', resize: 'both', height: '90vh', overflow: 'auto'}}>
<TreeView {...args} disabledKeys={['projects-1']} aria-label="test static tree" onExpandedChange={action('onExpandedChange')} onSelectionChange={action('onSelectionChange')}>
<TreeViewItem id="Photos" textValue="Photos">
<Text>Photos</Text>
<Folder />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
<TreeViewItem id="projects" textValue="Projects">
<Text>Projects</Text>
<Folder />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
Expand All @@ -70,8 +57,50 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps<unknown>) => (
<Text>Delete</Text>
</Item>
</ActionGroup>
<TreeViewItem id="projects-1A" textValue="Projects-1A">
<Text>Projects-1A</Text>
<TreeViewItem id="projects-1" textValue="Projects-1">
<Text>Projects-1</Text>
<Folder />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
<TreeViewItem id="projects-1A" textValue="Projects-1A">
<Text>Projects-1A</Text>
<FileTxt />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
</TreeViewItem>
<TreeViewItem id="projects-2" textValue="Projects-2">
<Text>Projects-2</Text>
<FileTxt />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
<TreeViewItem id="projects-3" textValue="Projects-3">
<Text>Projects-3</Text>
<FileTxt />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
Expand All @@ -85,36 +114,8 @@ export const TreeExampleStatic = (args: SpectrumTreeViewProps<unknown>) => (
</ActionGroup>
</TreeViewItem>
</TreeViewItem>
<TreeViewItem id="projects-2" textValue="Projects-2">
<Text>Projects-2</Text>
<FileTxt />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
<TreeViewItem id="projects-3" textValue="Projects-3">
<Text>Projects-3</Text>
<FileTxt />
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
</TreeViewItem>
</TreeView>
</TreeView>
</div>
);

TreeExampleStatic.story = {
Expand Down Expand Up @@ -176,24 +177,26 @@ let rows = [
];

export const TreeExampleDynamic = (args: SpectrumTreeViewProps<unknown>) => (
<TreeView {...args} width={300} height={300} defaultExpandedKeys="all" disabledKeys={['reports-1AB']} aria-label="test dynamic tree" items={rows} onExpandedChange={action('onExpandedChange')} onSelectionChange={action('onSelectionChange')}>
{(item) => (
<TreeViewItem childItems={item.childItems} textValue={item.name}>
<Text>{item.name}</Text>
{item.icon}
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
)}
</TreeView>
<div style={{width: '300px', resize: 'both', height: '90vh', overflow: 'auto'}}>
<TreeView {...args} defaultExpandedKeys="all" disabledKeys={['reports-1AB']} aria-label="test dynamic tree" items={rows} onExpandedChange={action('onExpandedChange')} onSelectionChange={action('onSelectionChange')}>
{(item) => (
<TreeViewItem childItems={item.childItems} textValue={item.name}>
<Text>{item.name}</Text>
{item.icon}
<ActionGroup onAction={action('onActionGroup action')}>
<Item key="edit">
<Edit />
<Text>Edit</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionGroup>
</TreeViewItem>
)}
</TreeView>
</div>
);

TreeExampleDynamic.story = {
Expand Down
14 changes: 7 additions & 7 deletions packages/react-aria-components/src/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,23 +267,23 @@ export {_Tree as Tree};

// TODO: readd the rest of the render props when tree supports them
export interface TreeItemRenderProps extends Omit<ItemRenderProps, 'allowsDragging' | 'isDragging' | 'isDropTarget'> {
// Whether the tree row is expanded.
// Whether the tree item is expanded.
isExpanded: boolean
}

export interface TreeItemProps<T = object> extends StyleRenderProps<TreeItemRenderProps>, LinkDOMProps {
/** The unique id of the tree row. */
/** The unique id of the tree item. */
id?: Key,
/** The object value that this tree row represents. When using dynamic collections, this is set automatically. */
/** The object value that this tree item represents. When using dynamic collections, this is set automatically. */
value?: T,
/** A string representation of the tree row's contents, used for features like typeahead. */
/** A string representation of the tree item's contents, used for features like typeahead. */
textValue: string,
/** An accessibility label for this tree row. */
/** An accessibility label for this tree item. */
'aria-label'?: string,
/** A list of child tree row objects used when dynamically rendering the tree row children. */
/** A list of child tree row objects used when dynamically rendering the tree item children. */
childItems?: Iterable<T>,
// TODO: made this required since the user needs to pass Content at least
/** The content of the tree row along with any nested children. Supports static items or a function for dynamic rendering. */
/** The content of the tree item along with any nested children. Supports static items or a function for dynamic rendering. */
children: ReactNode | ((item: T) => ReactElement)
}

Expand Down

0 comments on commit 0447bd8

Please sign in to comment.