Skip to content

Commit

Permalink
v3 TreeView (#6020)
Browse files Browse the repository at this point in the history
* scaffolding

* more style progress

* add box shadow border styles

* add disabled styles and behavior

* get rid of psudo element in favor of real element in prep for macros conversion

* get macro styles to work for tree

* remove old version in favor of macros

* add dynamic support

* add icons support, types, ref support and misc cleanup

* fix lint and rename Tree file

* add resizing story and fix text truncation/border cut off/addition prop considerations

* fix wiggle

* add more stories, fix chevron grid sizing, add empty state centering

* progress in fixing build issue

* fix bugs from testing

default pointer styling, focus styling when focus is withing the row, clicking on the chevron should move focus into tree, checkbox now appears when single selection

* fix pointer

conditions to macros must have "is" as a prefix?

* tentative announcement for grid list rows with links/actions

* updating HCM, RTL and increasing row width for action button focus ring

* weirdness with styles bleeding into the action menu, may need fix from rainbow for macros

* brainstorming some way to fix tree icon styles from making it to action menu icon

* adding temp test setup and babel config update for macros, tests dont run still

* Tentative approach to get rid of ActionButton icon styles from being erronously overwritten

icon slot propogated styles from TreeView row were making it to the ActionMenu button. Preventing this from happening by making ActionButton clearSlots and only accept text slot props. To preserve ActionGroup classname for icon, moved it to ActionButton directly

* chevron rotate animation

* bump babel so tests with macros can run

* update treeview tests to match spectrum implementation

* wrap up tests and clean up package json

* test skipping the tree tests to see if the 16 and 17 tests pass

* unskip tree tests, doesnt look like those are the problem

* update types and fixes for removal of "all" support for expandable key set

* bring back docs transformer for fork point

* increasing timeout time to see if it helps the test failures

* try running in band

* Fix fork point

Until we move to babel 8, we need to use assert instead of with, see https://babeljs.io/docs/v8-migration#babelplugin-syntax-import-assertions

* revert to "with" and pin parser

* fix build?

* revert runInBand

* adding increased timeout back

* try skipping TreeView test

* lower timeout for RTL async utils so they finish before jest kills workers

* set disabled/HCM color on grid cell and have children inherit this

this allows us make the icon the proper greyText color and avoid some UNSAFE styles

* make it so talkback cant focus the chevron if the row is completely disabled

* fix lint

* add chromatic and add timeout back in

* test limiting jest workers

* get rid of things added to pass the build now that we are using SWC

* still need importAttributes for docs transformer?

* Address review comments

* typo

* fix import
  • Loading branch information
LFDanLu authored Apr 25, 2024
1 parent a597a0c commit f3cbe03
Show file tree
Hide file tree
Showing 28 changed files with 2,353 additions and 279 deletions.
2 changes: 1 addition & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ module.exports = {
tsx: true,
importAssertions: true
},

transform: {
react: {
runtime: 'automatic'
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,8 @@
"exclude": [
"**/*.global.css",
"packages/@react-aria/example-theme/**",
"packages/@react-spectrum/style-macro-s1/**"
"packages/@react-spectrum/style-macro-s1/**",
"packages/@react-spectrum/tree/**"
]
},
"drafts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ governing permissions and limitations under the License.
&.spectrum-ActionGroup-item--iconOnly {
padding-inline-end: var(--spectrum-actionbutton-icon-padding-x);
}

.spectrum-ActionGroup-itemIcon {
padding-inline-end: 0;
}
}

&:focus {
Expand Down
4 changes: 4 additions & 0 deletions packages/@adobe/spectrum-css-temp/components/button/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,10 @@ a.spectrum-ActionButton {
.spectrum-ActionButton-hold + .spectrum-Icon:last-child {
padding-inline-end: var(--spectrum-actionbutton-icon-padding-x);
}

.spectrum-ActionGroup-itemIcon {
padding-inline-end: 0;
}
}

.spectrum-ActionButton-hold {
Expand Down
4 changes: 4 additions & 0 deletions packages/@react-aria/gridlist/intl/ar-AE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"hasActionAnnouncement": "row has action",
"hasLinkAnnouncement": "row has link: {link}"
}
4 changes: 4 additions & 0 deletions packages/@react-aria/gridlist/intl/en-US.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"hasActionAnnouncement": "row has action",
"hasLinkAnnouncement": "row has link: {link}"
}
16 changes: 14 additions & 2 deletions packages/@react-aria/gridlist/src/useGridListItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ import {focusSafely, getFocusableTreeWalker} from '@react-aria/focus';
import {getLastItem} from '@react-stately/collections';
import {getRowId, listMap} from './utils';
import {HTMLAttributes, KeyboardEvent as ReactKeyboardEvent, RefObject, useRef} from 'react';
// @ts-ignore
import intlMessages from '../intl/*.json';
import {isFocusVisible} from '@react-aria/interactions';
import type {ListState} from '@react-stately/list';
import {SelectableItemStates, useSelectableItem} from '@react-aria/selection';
import type {TreeState} from '@react-stately/tree';
import {useLocale} from '@react-aria/i18n';
import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';

export interface AriaGridListItemOptions {
/** An object representing the list item. Contains all the relevant information that makes up the list row. */
Expand Down Expand Up @@ -65,6 +67,7 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
shouldSelectOnPressUp
} = props;

let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-aria/gridlist');
let {direction} = useLocale();
let {onAction, linkBehavior} = listMap.get(state);
let descriptionId = useSlotId();
Expand Down Expand Up @@ -226,11 +229,20 @@ export function useGridListItem<T>(props: AriaGridListItemOptions, state: ListSt
};

let linkProps = itemStates.hasAction ? getSyntheticLinkProps(node.props) : {};
let rowAnnouncement;
if (onAction) {
rowAnnouncement = stringFormatter.format('hasActionAnnouncement');
} else if (hasLink) {
rowAnnouncement = stringFormatter.format('hasLinkAnnouncement', {
link: node.props.href
});
}

let rowProps: DOMAttributes = mergeProps(itemProps, linkProps, {
role: 'row',
onKeyDownCapture: onKeyDown,
onFocus,
'aria-label': node.textValue || undefined,
'aria-label': [(node.textValue || undefined), rowAnnouncement].filter(Boolean).join(', '),
'aria-selected': state.selectionManager.canSelectItem(node.key) ? state.selectionManager.isSelected(node.key) : undefined,
'aria-disabled': state.selectionManager.isDisabled(node.key) || undefined,
'aria-labelledby': descriptionId && node.textValue ? `${getRowId(state, node.key)} ${descriptionId}` : undefined,
Expand Down
5 changes: 2 additions & 3 deletions packages/@react-spectrum/actiongroup/src/ActionGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,13 +308,12 @@ function ActionGroupItem<T>({item, state, isDisabled, isEmphasized, staticColor,
text: {
id: hideButtonText ? textId : null,
isHidden: hideButtonText
},
icon: {
UNSAFE_className: hideButtonText ? classNames(styles, 'spectrum-ActionGroup-itemIcon') : null
}
}}>
<ActionButton
ref={ref}
// @ts-ignore (private)
hideButtonText={hideButtonText}
UNSAFE_className={
classNames(
styles,
Expand Down
42 changes: 27 additions & 15 deletions packages/@react-spectrum/button/src/ActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* governing permissions and limitations under the License.
*/

import {classNames, SlotProvider, useFocusableRef, useSlotProps, useStyleProps} from '@react-spectrum/utils';
import {classNames, ClearSlots, SlotProvider, useFocusableRef, useSlotProps, useStyleProps} from '@react-spectrum/utils';
import CornerTriangle from '@spectrum-icons/ui/CornerTriangle';
import {FocusableRef} from '@react-types/shared';
import {FocusRing} from '@react-aria/focus';
Expand All @@ -26,6 +26,8 @@ import {useProviderProps} from '@react-spectrum/provider';
function ActionButton(props: SpectrumActionButtonProps, ref: FocusableRef<HTMLButtonElement>) {
props = useProviderProps(props);
props = useSlotProps(props, 'actionButton');
let textProps = useSlotProps({UNSAFE_className: classNames(styles, 'spectrum-ActionButton-label')}, 'text');

let {
isQuiet,
isDisabled,
Expand All @@ -34,6 +36,8 @@ function ActionButton(props: SpectrumActionButtonProps, ref: FocusableRef<HTMLBu
autoFocus,
// @ts-ignore (private)
holdAffordance,
// @ts-ignore (private)
hideButtonText,
...otherProps
} = props;

Expand Down Expand Up @@ -68,20 +72,28 @@ function ActionButton(props: SpectrumActionButtonProps, ref: FocusableRef<HTMLBu
{holdAffordance &&
<CornerTriangle UNSAFE_className={classNames(styles, 'spectrum-ActionButton-hold')} />
}
<SlotProvider
slots={{
icon: {
size: 'S',
UNSAFE_className: classNames(styles, 'spectrum-Icon')
},
text: {
UNSAFE_className: classNames(styles, 'spectrum-ActionButton-label')
}
}}>
{typeof children === 'string' || isTextOnly
? <Text>{children}</Text>
: children}
</SlotProvider>
<ClearSlots>
<SlotProvider
slots={{
icon: {
size: 'S',
UNSAFE_className: classNames(
styles,
'spectrum-Icon',
{
'spectrum-ActionGroup-itemIcon': hideButtonText
}
)
},
text: {
...textProps
}
}}>
{typeof children === 'string' || isTextOnly
? <Text>{children}</Text>
: children}
</SlotProvider>
</ClearSlots>
</button>
</FocusRing>
);
Expand Down
3 changes: 2 additions & 1 deletion packages/@react-spectrum/checkbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"@react-types/checkbox": "^3.7.1",
"@react-types/shared": "^3.22.1",
"@spectrum-icons/ui": "^3.6.5",
"@swc/helpers": "^0.5.0"
"@swc/helpers": "^0.5.0",
"react-aria-components": "^1.1.1"
},
"devDependencies": {
"@adobe/spectrum-css-temp": "3.0.0-alpha.1"
Expand Down
8 changes: 5 additions & 3 deletions packages/@react-spectrum/checkbox/src/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* governing permissions and limitations under the License.
*/

import {CheckboxContext, useContextProps} from 'react-aria-components';
import {CheckboxGroupContext} from './context';
import CheckmarkSmall from '@spectrum-icons/ui/CheckmarkSmall';
import {classNames, useFocusableRef, useStyleProps} from '@react-spectrum/utils';
Expand All @@ -27,6 +28,10 @@ import {useToggleState} from '@react-stately/toggle';

function Checkbox(props: SpectrumCheckboxProps, ref: FocusableRef<HTMLLabelElement>) {
let originalProps = props;
let inputRef = useRef<HTMLInputElement>(null);
let domRef = useFocusableRef(ref, inputRef);

[props, domRef] = useContextProps(props, domRef, CheckboxContext);
props = useProviderProps(props);
props = useFormProps(props);
let {
Expand All @@ -38,9 +43,6 @@ function Checkbox(props: SpectrumCheckboxProps, ref: FocusableRef<HTMLLabelEleme
} = props;
let {styleProps} = useStyleProps(otherProps);

let inputRef = useRef<HTMLInputElement>(null);
let domRef = useFocusableRef(ref, inputRef);

// Swap hooks depending on whether this checkbox is inside a CheckboxGroup.
// This is a bit unorthodox. Typically, hooks cannot be called in a conditional,
// but since the checkbox won't move in and out of a group, it should be safe.
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-spectrum/style-macro-s1/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@react-spectrum/style-macro-s1",
"private": true,
"version": "3.5.3",
"version": "1.0.0-alpha.1",
"description": "Spectrum UI components in React",
"license": "Apache-2.0",
"source": "src/index.ts",
Expand Down
125 changes: 125 additions & 0 deletions packages/@react-spectrum/tree/chromatic/TreeView.chromatic-fc.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* Copyright 2024 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 {ActionGroup, Item} from '@react-spectrum/actiongroup';
import {ActionMenu} from '@react-spectrum/menu';
import Add from '@spectrum-icons/workflow/Add';
import Delete from '@spectrum-icons/workflow/Delete';
import Edit from '@spectrum-icons/workflow/Edit';
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 {TreeView, TreeViewItem} from '../src';

export default {
title: 'TreeView'
};

function TestTree(props) {
return (
<div style={{width: '300px', height: '800px'}}>
<TreeView {...props} disabledKeys={['projects-1']} defaultExpandedKeys={['Photos', 'projects', 'projects-1']} aria-label="test static tree">
<TreeViewItem href="https://adobe.com/" id="Photos" textValue="Photos">
<Text>Photos</Text>
<Folder />
<ActionGroup>
<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 />
<ActionMenu>
<Item key="add">
<Add />
<Text>Add</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionMenu>
<TreeViewItem id="projects-1" textValue="Projects-1">
<Text>Projects-1</Text>
<Folder />
<ActionGroup>
<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 />
<ActionMenu>
<Item key="add">
<Add />
<Text>Add</Text>
</Item>
<Item key="delete">
<Delete />
<Text>Delete</Text>
</Item>
</ActionMenu>
</TreeViewItem>
</TreeViewItem>
<TreeViewItem id="projects-2" textValue="Projects-2">
<Text>Projects-2</Text>
<FileTxt />
<ActionGroup>
<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 />
</TreeViewItem>
</TreeViewItem>
</TreeView>
</div>
);
}

export const Default = () => (
<TestTree />
);

export const SelectionMode = () => (
<TestTree selectionMode="multiple" />
);

export const DisabledBehaviorAll = () => (
<TestTree disabledBehavior="all" selectionMode="multiple" />
);

export const HiglightSelectionWithDisabledBehaviorAll = () => (
<TestTree selectionStyle="highlight" selectionMode="multiple" disabledBehavior="all" />
);
Loading

1 comment on commit f3cbe03

@rspbot
Copy link

@rspbot rspbot commented on f3cbe03 Apr 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.