From ca4ddc85b8707f81fb64bddede3b32c699889571 Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Thu, 12 Jan 2023 08:25:51 -0300 Subject: [PATCH] chore(react-tree): removes BaseTreeItem premature generalization (#26302) --- .../react-tree/etc/react-tree.api.md | 38 ++------ .../react-tree/src/BaseTreeItem.ts | 1 - .../BaseTreeItem/BaseTreeItem.test.tsx | 23 ----- .../components/BaseTreeItem/BaseTreeItem.tsx | 18 ---- .../BaseTreeItem/BaseTreeItem.types.ts | 18 ---- .../__snapshots__/BaseTreeItem.test.tsx.snap | 14 --- .../src/components/BaseTreeItem/index.ts | 5 - .../BaseTreeItem/renderBaseTreeItem.tsx | 12 --- .../BaseTreeItem/useBaseTreeItem.ts | 91 ------------------- .../BaseTreeItem/useBaseTreeItemStyles.ts | 15 --- .../src/components/TreeItem/TreeItem.types.ts | 21 +++-- .../src/components/TreeItem/useTreeItem.tsx | 86 ++++++++++++++---- .../react-components/react-tree/src/index.ts | 9 -- 13 files changed, 86 insertions(+), 265 deletions(-) delete mode 100644 packages/react-components/react-tree/src/BaseTreeItem.ts delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.test.tsx delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.tsx delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.types.ts delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/__snapshots__/BaseTreeItem.test.tsx.snap delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/index.ts delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/renderBaseTreeItem.tsx delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItem.ts delete mode 100644 packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItemStyles.ts diff --git a/packages/react-components/react-tree/etc/react-tree.api.md b/packages/react-components/react-tree/etc/react-tree.api.md index 0aeb610ba2e3c..6c91f37495f7c 100644 --- a/packages/react-components/react-tree/etc/react-tree.api.md +++ b/packages/react-components/react-tree/etc/react-tree.api.md @@ -17,29 +17,6 @@ import * as React_2 from 'react'; import type { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; -// @public -export const BaseTreeItem: ForwardRefComponent; - -// @public (undocumented) -export const baseTreeItemClassNames: SlotClassNames; - -// @public -export type BaseTreeItemProps = ComponentProps; - -// @public (undocumented) -export type BaseTreeItemSlots = { - root: Slot<'div'>; -}; - -// @public -export type BaseTreeItemState = ComponentState & { - open: boolean; - isLeaf: boolean; -}; - -// @public -export const renderBaseTreeItem_unstable: (state: BaseTreeItemState) => JSX.Element; - // @public (undocumented) export const renderTree_unstable: (state: TreeState, contextValues: TreeContextValues) => JSX.Element; @@ -70,10 +47,11 @@ export const TreeItem: ForwardRefComponent; export const treeItemClassNames: SlotClassNames; // @public -export type TreeItemProps = ComponentProps> & BaseTreeItemProps; +export type TreeItemProps = ComponentProps>; // @public (undocumented) -export type TreeItemSlots = BaseTreeItemSlots & { +export type TreeItemSlots = { + root: Slot<'div'>; expandIcon?: Slot<'span'>; iconBefore?: Slot<'span'>; iconAfter?: Slot<'span'>; @@ -83,7 +61,9 @@ export type TreeItemSlots = BaseTreeItemSlots & { }; // @public -export type TreeItemState = ComponentState & BaseTreeItemState & { +export type TreeItemState = ComponentState & { + open: boolean; + isLeaf: boolean; keepActionsOpen: boolean; }; @@ -109,12 +89,6 @@ export type TreeState = ComponentState & TreeContextValue & { open: boolean; }; -// @public -export const useBaseTreeItem_unstable: (props: BaseTreeItemProps, ref: React_2.Ref) => BaseTreeItemState; - -// @public -export const useBaseTreeItemStyles_unstable: (state: BaseTreeItemState) => BaseTreeItemState; - // @public export const useTree_unstable: (props: TreeProps, ref: React_2.Ref) => TreeState; diff --git a/packages/react-components/react-tree/src/BaseTreeItem.ts b/packages/react-components/react-tree/src/BaseTreeItem.ts deleted file mode 100644 index 085079234fb62..0000000000000 --- a/packages/react-components/react-tree/src/BaseTreeItem.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './components/BaseTreeItem/index'; diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.test.tsx b/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.test.tsx deleted file mode 100644 index 7da490345cf31..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.test.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { BaseTreeItem } from './BaseTreeItem'; -import { isConformant } from '../../testing/isConformant'; -import { BaseTreeItemProps } from './index'; -import { resetIdsForTests } from '@fluentui/react-utilities'; - -describe('BaseTreeItem', () => { - beforeEach(() => { - resetIdsForTests(); - }); - isConformant({ - Component: BaseTreeItem, - displayName: 'BaseTreeItem', - }); - - // TODO add more tests here, and create visual regression tests in /apps/vr-tests - - it('renders a default state', () => { - const result = render(Default BaseTreeItem); - expect(result.container).toMatchSnapshot(); - }); -}); diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.tsx b/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.tsx deleted file mode 100644 index 52112e930dc8e..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import { useBaseTreeItem_unstable } from './useBaseTreeItem'; -import { renderBaseTreeItem_unstable } from './renderBaseTreeItem'; -import { useBaseTreeItemStyles_unstable } from './useBaseTreeItemStyles'; -import type { BaseTreeItemProps } from './BaseTreeItem.types'; -import type { ForwardRefComponent } from '@fluentui/react-utilities'; - -/** - * BaseTreeItem component - Represents a single node on the Tree - */ -export const BaseTreeItem: ForwardRefComponent = React.forwardRef((props, ref) => { - const state = useBaseTreeItem_unstable(props, ref); - - useBaseTreeItemStyles_unstable(state); - return renderBaseTreeItem_unstable(state); -}); - -BaseTreeItem.displayName = 'BaseTreeItem'; diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.types.ts b/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.types.ts deleted file mode 100644 index c6a016bf475b4..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/BaseTreeItem.types.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; - -export type BaseTreeItemSlots = { - root: Slot<'div'>; -}; - -/** - * BaseTreeItem Props - */ -export type BaseTreeItemProps = ComponentProps; - -/** - * State used in rendering BaseTreeItem - */ -export type BaseTreeItemState = ComponentState & { - open: boolean; - isLeaf: boolean; -}; diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/__snapshots__/BaseTreeItem.test.tsx.snap b/packages/react-components/react-tree/src/components/BaseTreeItem/__snapshots__/BaseTreeItem.test.tsx.snap deleted file mode 100644 index d9ab4028871ae..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/__snapshots__/BaseTreeItem.test.tsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`BaseTreeItem renders a default state 1`] = ` -
-
- Default BaseTreeItem -
-
-`; diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/index.ts b/packages/react-components/react-tree/src/components/BaseTreeItem/index.ts deleted file mode 100644 index 11bf08a9d0b64..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './BaseTreeItem'; -export * from './BaseTreeItem.types'; -export * from './renderBaseTreeItem'; -export * from './useBaseTreeItem'; -export * from './useBaseTreeItemStyles'; diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/renderBaseTreeItem.tsx b/packages/react-components/react-tree/src/components/BaseTreeItem/renderBaseTreeItem.tsx deleted file mode 100644 index 92a0de70bed0b..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/renderBaseTreeItem.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; -import type { BaseTreeItemState, BaseTreeItemSlots } from './BaseTreeItem.types'; - -/** - * Render the final JSX of BaseTreeItem - */ -export const renderBaseTreeItem_unstable = (state: BaseTreeItemState) => { - const { slots, slotProps } = getSlots(state); - - return ; -}; diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItem.ts b/packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItem.ts deleted file mode 100644 index 86b3bf3f8be89..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItem.ts +++ /dev/null @@ -1,91 +0,0 @@ -import * as React from 'react'; -import { getNativeElementProps, useEventCallback } from '@fluentui/react-utilities'; -import type { BaseTreeItemProps, BaseTreeItemState } from './BaseTreeItem.types'; -import { ArrowRight, ArrowLeft, Enter } from '@fluentui/keyboard-keys'; -import { useTreeContext_unstable } from '../../contexts/treeContext'; -/** - * Create the state required to render BaseTreeItem. - * - * The returned state can be modified with hooks such as useBaseTreeItemStyles_unstable, - * before being passed to renderBaseTreeItem_unstable. - * - * @param props - props from this instance of BaseTreeItem - * @param ref - reference to root HTMLElement of BaseTreeItem - */ -export const useBaseTreeItem_unstable = ( - props: BaseTreeItemProps, - ref: React.Ref, -): BaseTreeItemState => { - const { 'aria-owns': ariaOwns, as = 'div', onKeyDown, ...rest } = props; - - const level = useTreeContext_unstable(ctx => ctx.level); - const requestOpenChange = useTreeContext_unstable(ctx => ctx.requestOpenChange); - const focusFirstSubtreeItem = useTreeContext_unstable(ctx => ctx.focusFirstSubtreeItem); - const focusSubtreeOwnerItem = useTreeContext_unstable(ctx => ctx.focusSubtreeOwnerItem); - - const isBranch = typeof ariaOwns === 'string'; - const open = useTreeContext_unstable(ctx => isBranch && ctx.openSubtrees.includes(ariaOwns!)); - - const handleClick = useEventCallback((event: React.MouseEvent) => { - if (isBranch) { - requestOpenChange({ event, open: !open, type: 'click', id: ariaOwns! }); - } - }); - const handleArrowRight = (event: React.KeyboardEvent) => { - if (open && isBranch) { - focusFirstSubtreeItem(event.currentTarget); - } - if (isBranch && !open) { - requestOpenChange({ event, open: true, type: 'arrowRight', id: ariaOwns! }); - } - }; - const handleArrowLeft = (event: React.KeyboardEvent) => { - if (!isBranch || !open) { - focusSubtreeOwnerItem(event.currentTarget); - } - if (isBranch && open) { - requestOpenChange({ event, open: false, type: 'arrowLeft', id: ariaOwns! }); - } - }; - const handleEnter = (event: React.KeyboardEvent) => { - if (isBranch) { - requestOpenChange({ event, open: !open, type: 'enter', id: ariaOwns! }); - } - }; - const handleKeyDown = useEventCallback((event: React.KeyboardEvent) => { - onKeyDown?.(event); - if (event.isDefaultPrevented()) { - return; - } - switch (event.key) { - case Enter: { - return handleEnter(event); - } - case ArrowRight: { - return handleArrowRight(event); - } - case ArrowLeft: { - return handleArrowLeft(event); - } - } - }); - return { - components: { - root: 'div', - }, - isLeaf: !isBranch, - open, - root: getNativeElementProps(as, { - ...rest, - ref, - tabIndex: 0, - 'aria-owns': ariaOwns, - 'aria-level': level, - // FIXME: tabster fails to navigate when aria-expanded is true - // 'aria-expanded': isBranch ? isOpen : undefined, - role: 'treeitem', - onClick: handleClick, - onKeyDown: handleKeyDown, - }), - }; -}; diff --git a/packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItemStyles.ts b/packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItemStyles.ts deleted file mode 100644 index 1f18836c33bd0..0000000000000 --- a/packages/react-components/react-tree/src/components/BaseTreeItem/useBaseTreeItemStyles.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { mergeClasses } from '@griffel/react'; -import type { BaseTreeItemSlots, BaseTreeItemState } from './BaseTreeItem.types'; -import type { SlotClassNames } from '@fluentui/react-utilities'; - -export const baseTreeItemClassNames: SlotClassNames = { - root: 'fui-BaseTreeItem', -}; - -/** - * Apply styling to the BaseTreeItem slots based on the state - */ -export const useBaseTreeItemStyles_unstable = (state: BaseTreeItemState): BaseTreeItemState => { - state.root.className = mergeClasses(baseTreeItemClassNames.root, state.root.className); - return state; -}; diff --git a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts index 60a8e04a24efd..7e06b78dee4e8 100644 --- a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts +++ b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts @@ -1,7 +1,7 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; -import { BaseTreeItemProps, BaseTreeItemSlots, BaseTreeItemState } from '../BaseTreeItem/index'; -export type TreeItemSlots = BaseTreeItemSlots & { +export type TreeItemSlots = { + root: Slot<'div'>; /** * Expand icon slot, * by default renders a chevron icon to indicate opening and closing @@ -30,15 +30,16 @@ export type TreeItemSlots = BaseTreeItemSlots & { /** * TreeItem Props */ -export type TreeItemProps = ComponentProps> & BaseTreeItemProps; +export type TreeItemProps = ComponentProps>; /** * State used in rendering TreeItem */ -export type TreeItemState = ComponentState & - BaseTreeItemState & { - /** - * boolean indicating that actions should remain open due to focus on some portal - */ - keepActionsOpen: boolean; - }; +export type TreeItemState = ComponentState & { + open: boolean; + isLeaf: boolean; + /** + * boolean indicating that actions should remain open due to focus on some portal + */ + keepActionsOpen: boolean; +}; diff --git a/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx index 4bd12049fd7ac..b523ea2aea1ae 100644 --- a/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx +++ b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx @@ -1,15 +1,15 @@ import * as React from 'react'; -import { isResolvedShorthand, resolveShorthand } from '@fluentui/react-utilities'; +import { getNativeElementProps, isResolvedShorthand, resolveShorthand } from '@fluentui/react-utilities'; import { ChevronRightRegular } from '@fluentui/react-icons'; import { useFluent_unstable } from '@fluentui/react-shared-contexts'; import { useEventCallback } from '@fluentui/react-utilities'; import { useFocusableGroup } from '@fluentui/react-tabster'; import { expandIconInlineStyles } from './useTreeItemStyles'; -import { useBaseTreeItem_unstable } from '../BaseTreeItem/index'; -import { Enter } from '@fluentui/keyboard-keys'; +import { ArrowLeft, ArrowRight, Enter } from '@fluentui/keyboard-keys'; import { useMergedRefs } from '@fluentui/react-utilities'; import { elementContains } from '@fluentui/react-portal'; import type { TreeItemProps, TreeItemState } from './TreeItem.types'; +import { useTreeContext_unstable } from '../../contexts/index'; /** * Create the state required to render TreeItem. @@ -21,30 +21,74 @@ import type { TreeItemProps, TreeItemState } from './TreeItem.types'; * @param ref - reference to root HTMLElement of TreeItem */ export const useTreeItem_unstable = (props: TreeItemProps, ref: React.Ref): TreeItemState => { - const treeItemState = useBaseTreeItem_unstable(props, ref); + const { 'aria-owns': ariaOwns, as = 'div', onClick, onKeyDown, ...rest } = props; + const level = useTreeContext_unstable(ctx => ctx.level); + const requestOpenChange = useTreeContext_unstable(ctx => ctx.requestOpenChange); + const focusFirstSubtreeItem = useTreeContext_unstable(ctx => ctx.focusFirstSubtreeItem); + const focusSubtreeOwnerItem = useTreeContext_unstable(ctx => ctx.focusSubtreeOwnerItem); + + const isBranch = typeof ariaOwns === 'string'; + const open = useTreeContext_unstable(ctx => isBranch && ctx.openSubtrees.includes(ariaOwns!)); const { expandIcon, iconBefore, iconAfter, actions, badges, groupper } = props; const { dir, targetDocument } = useFluent_unstable(); - const expandIconRotation = treeItemState.open ? 90 : dir !== 'rtl' ? 0 : 180; + const expandIconRotation = open ? 90 : dir !== 'rtl' ? 0 : 180; const groupperProps = useFocusableGroup(); const actionsRef = React.useRef(null); + const handleArrowRight = (event: React.KeyboardEvent) => { + if (open && isBranch) { + focusFirstSubtreeItem(event.currentTarget); + } + if (isBranch && !open) { + requestOpenChange({ event, open: true, type: 'arrowRight', id: ariaOwns! }); + } + }; + const handleArrowLeft = (event: React.KeyboardEvent) => { + if (!isBranch || !open) { + focusSubtreeOwnerItem(event.currentTarget); + } + if (isBranch && open) { + requestOpenChange({ event, open: false, type: 'arrowLeft', id: ariaOwns! }); + } + }; + const handleEnter = (event: React.KeyboardEvent) => { + // if Enter keydown event comes from actions, ignore it + if (actionsRef.current && elementContains(actionsRef.current, event.target as Node)) { + return; + } + if (isBranch) { + requestOpenChange({ event, open: !open, type: 'enter', id: ariaOwns! }); + } + }; + const handleClick = useEventCallback((event: React.MouseEvent) => { + onClick?.(event); // if click event originates from actions, ignore it if (actionsRef.current && elementContains(actionsRef.current, event.target as Node)) { return; } - treeItemState.root.onClick?.(event); + if (isBranch) { + requestOpenChange({ event, open: !open, type: 'click', id: ariaOwns! }); + } }); const handleKeyDown = useEventCallback((event: React.KeyboardEvent) => { - if (event.key === Enter) { - // if Enter keydown event comes from actions, ignore it - if (actionsRef.current && elementContains(actionsRef.current, event.target as Node)) { - return; + onKeyDown?.(event); + if (event.isDefaultPrevented()) { + return; + } + switch (event.key) { + case Enter: { + return handleEnter(event); + } + case ArrowRight: { + return handleArrowRight(event); + } + case ArrowLeft: { + return handleArrowLeft(event); } } - treeItemState.root.onKeyDown?.(event); }); const [keepActionsOpen, setKeepActionsOpen] = React.useState(false); @@ -64,10 +108,11 @@ export const useTreeItem_unstable = (props: TreeItemProps, ref: React.Ref, 'aria-hidden': true, diff --git a/packages/react-components/react-tree/src/index.ts b/packages/react-components/react-tree/src/index.ts index 092a9d9e09c4f..9d529a020b62f 100644 --- a/packages/react-components/react-tree/src/index.ts +++ b/packages/react-components/react-tree/src/index.ts @@ -1,15 +1,6 @@ export { Tree, treeClassNames, renderTree_unstable, useTreeStyles_unstable, useTree_unstable } from './Tree'; export type { TreeProps, TreeState, TreeSlots } from './Tree'; -export { - BaseTreeItem, - baseTreeItemClassNames, - renderBaseTreeItem_unstable, - useBaseTreeItemStyles_unstable, - useBaseTreeItem_unstable, -} from './BaseTreeItem'; -export type { BaseTreeItemProps, BaseTreeItemState, BaseTreeItemSlots } from './BaseTreeItem'; - export type { TreeContextValue, useTreeContext_unstable, TreeProvider } from './contexts'; export {