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 b35c29f663c5ef..2f6c6cc1814c3e 100644 --- a/packages/react-components/react-tree/etc/react-tree.api.md +++ b/packages/react-components/react-tree/etc/react-tree.api.md @@ -4,54 +4,60 @@ ```ts +import type { ARIAButtonElement } from '@fluentui/react-aria'; +import type { ARIAButtonSlotProps } from '@fluentui/react-aria'; import type { ComponentProps } from '@fluentui/react-utilities'; import type { ComponentState } from '@fluentui/react-utilities'; +import { ContextSelector } from '@fluentui/react-context-selector'; +import { FC } from 'react'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { Provider } from 'react'; +import { ProviderProps } from 'react'; +import * as React_2 from 'react'; import type { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; // @public export const Tree: ForwardRefComponent; -// @public -export const TreeBranch: ForwardRefComponent; - -// @public (undocumented) -export const treeBranchClassNames: SlotClassNames; - // @public (undocumented) -export type TreeBranchProps = ComponentProps & {}; +export const treeClassNames: SlotClassNames; // @public (undocumented) -export type TreeBranchSlots = { - root: Slot<'div'>; +export type TreeContextValue = { + level: number; + openSubtrees: string[]; + focusFirstSubtreeItem(target: TreeItemElement): void; + focusSubtreeOwnerItem(target: TreeItemElement): void; + requestOpenChange(data: TreeOpenChangeData): void; }; // @public -export type TreeBranchState = ComponentState; +export const TreeItem: ForwardRefComponent; // @public (undocumented) -export const treeClassNames: SlotClassNames; +export const treeItemClassNames: SlotClassNames; // @public -export const TreeLeaf: ForwardRefComponent; +export type TreeItemProps = ComponentProps; // @public (undocumented) -export const treeLeafClassNames: SlotClassNames; +export type TreeItemSlots = { + root: NonNullable>>; +}; -// @public (undocumented) -export type TreeLeafProps = ComponentProps & {}; +// @public +export type TreeItemState = ComponentState; // @public (undocumented) -export type TreeLeafSlots = { - root: Slot<'div'>; +export type TreeProps = ComponentProps & { + openSubtrees?: string | string[]; + defaultOpenSubtrees?: string | string[]; + onOpenChange?(event: TreeOpenChangeEvent, data: TreeOpenChangeData): void; }; // @public (undocumented) -export type TreeLeafState = ComponentState; - -// @public (undocumented) -export type TreeProps = ComponentProps & {}; +export const TreeProvider: Provider & FC>; // @public (undocumented) export type TreeSlots = { @@ -59,7 +65,12 @@ export type TreeSlots = { }; // @public -export type TreeState = ComponentState; +export type TreeState = ComponentState & TreeContextValue & { + open: boolean; +}; + +// @public (undocumented) +export const useTreeContext_unstable: (selector: ContextSelector) => T; // (No @packageDocumentation comment for this package) diff --git a/packages/react-components/react-tree/package.json b/packages/react-components/react-tree/package.json index 41073c42785174..dc8180b39912ae 100644 --- a/packages/react-components/react-tree/package.json +++ b/packages/react-components/react-tree/package.json @@ -31,6 +31,11 @@ "@fluentui/scripts": "^1.0.0" }, "dependencies": { + "@fluentui/react-context-selector": "^9.1.2", + "@fluentui/react-shared-contexts": "^9.1.1", + "@fluentui/react-aria": "^9.3.2", + "@fluentui/react-tabster": "^9.3.1", + "@fluentui/keyboard-keys": "^9.0.1", "@fluentui/react-theme": "^9.1.2", "@fluentui/react-utilities": "^9.2.2", "@griffel/react": "^1.4.2", diff --git a/packages/react-components/react-tree/src/TreeBranch.ts b/packages/react-components/react-tree/src/TreeBranch.ts deleted file mode 100644 index a97fd86a504ba8..00000000000000 --- a/packages/react-components/react-tree/src/TreeBranch.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './components/TreeBranch/index'; diff --git a/packages/react-components/react-tree/src/TreeItem.ts b/packages/react-components/react-tree/src/TreeItem.ts new file mode 100644 index 00000000000000..017ab04d8956bd --- /dev/null +++ b/packages/react-components/react-tree/src/TreeItem.ts @@ -0,0 +1 @@ +export * from './components/TreeItem/index'; diff --git a/packages/react-components/react-tree/src/TreeLeaf.ts b/packages/react-components/react-tree/src/TreeLeaf.ts deleted file mode 100644 index 02826ef47af596..00000000000000 --- a/packages/react-components/react-tree/src/TreeLeaf.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './components/TreeLeaf/index'; diff --git a/packages/react-components/react-tree/src/components/Tree/Tree.test.tsx b/packages/react-components/react-tree/src/components/Tree/Tree.test.tsx index d72d4ff4a7e472..0d01a78d0ee37f 100644 --- a/packages/react-components/react-tree/src/components/Tree/Tree.test.tsx +++ b/packages/react-components/react-tree/src/components/Tree/Tree.test.tsx @@ -7,10 +7,11 @@ describe('Tree', () => { isConformant({ Component: Tree, displayName: 'Tree', + disabledTests: ['consistent-callback-args'], }); it('renders a default state', () => { - const result = render(Default Tree); + const result = render(); expect(result.container).toMatchSnapshot(); }); }); diff --git a/packages/react-components/react-tree/src/components/Tree/Tree.tsx b/packages/react-components/react-tree/src/components/Tree/Tree.tsx index d056c9a3d12c01..77673c412fcb06 100644 --- a/packages/react-components/react-tree/src/components/Tree/Tree.tsx +++ b/packages/react-components/react-tree/src/components/Tree/Tree.tsx @@ -4,15 +4,21 @@ import { renderTree_unstable } from './renderTree'; import { useTreeStyles_unstable } from './useTreeStyles'; import type { TreeProps } from './Tree.types'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import { useTreeContextValues_unstable } from './useTreeContextValues'; /** - * A tree component provides a hierarchical list + * A tree view widget presents a hierarchical list. + * Any item in the hierarchy may have child items, + * and items that have children may be expanded or collapsed to show or hide the children. + * For example, in a file system navigator that uses a tree view to display folders and files, + * an item representing a folder can be expanded to reveal the contents of the folder, + * which may be files, folders, or both. */ export const Tree: ForwardRefComponent = React.forwardRef((props, ref) => { const state = useTree_unstable(props, ref); - useTreeStyles_unstable(state); - return renderTree_unstable(state); + const contextValues = useTreeContextValues_unstable(state); + return renderTree_unstable(state, contextValues); }); Tree.displayName = 'Tree'; diff --git a/packages/react-components/react-tree/src/components/Tree/Tree.types.ts b/packages/react-components/react-tree/src/components/Tree/Tree.types.ts index a67aee8b7f77f3..43ac520762ed49 100644 --- a/packages/react-components/react-tree/src/components/Tree/Tree.types.ts +++ b/packages/react-components/react-tree/src/components/Tree/Tree.types.ts @@ -1,12 +1,56 @@ +import * as React from 'react'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { TreeItemElement } from '../TreeItem/TreeItem.types'; +import { TreeContextValue } from '../../contexts/treeContext'; export type TreeSlots = { root: Slot<'div'>; }; -export type TreeProps = ComponentProps & {}; +export type TreeOpenChangeData = { open: boolean; id: string } & ( + | { + event: React.MouseEvent; + type: 'click'; + } + | { + event: React.KeyboardEvent; + type: 'arrowRight' | 'arrowLeft'; + } +); + +export type TreeOpenChangeEvent = TreeOpenChangeData['event']; + +export type TreeContextValues = { + tree: TreeContextValue; +}; + +export type TreeProps = ComponentProps & { + /** + * Controls the state of the open subtrees. + * These property is ignored for subtrees. + */ + openSubtrees?: string | string[]; + /** + * Default value for the uncontrolled state of open subtrees. + * These property is ignored for subtrees. + */ + defaultOpenSubtrees?: string | string[]; + /** + * Callback fired when the component changes value from open state. + * These property is ignored for subtrees. + * + * @param event - a React's Synthetic event + * @param data - A data object with relevant information, + * such as open value and type of interaction that created the event + * and the id of the subtree that is being opened/closed + */ + onOpenChange?(event: TreeOpenChangeEvent, data: TreeOpenChangeData): void; +}; /** * State used in rendering Tree */ -export type TreeState = ComponentState; +export type TreeState = ComponentState & + TreeContextValue & { + open: boolean; + }; diff --git a/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap b/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap index fcc033a896e88e..5bfda408145671 100644 --- a/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap +++ b/packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap @@ -4,8 +4,8 @@ exports[`Tree renders a default state 1`] = `
- Default Tree -
+ data-tabster="{\\"mover\\":{\\"cyclic\\":false,\\"direction\\":1,\\"tabbable\\":true}}" + role="tree" + />
`; diff --git a/packages/react-components/react-tree/src/components/Tree/renderTree.tsx b/packages/react-components/react-tree/src/components/Tree/renderTree.tsx index 7ae8187ba06fc0..b821e143fad8d9 100644 --- a/packages/react-components/react-tree/src/components/Tree/renderTree.tsx +++ b/packages/react-components/react-tree/src/components/Tree/renderTree.tsx @@ -1,9 +1,15 @@ import * as React from 'react'; import { getSlots } from '@fluentui/react-utilities'; -import type { TreeState, TreeSlots } from './Tree.types'; +import type { TreeState, TreeSlots, TreeContextValues } from './Tree.types'; +import { TreeProvider } from '../../contexts'; -export const renderTree_unstable = (state: TreeState) => { +export const renderTree_unstable = (state: TreeState, contextValues: TreeContextValues) => { + const { open } = state; const { slots, slotProps } = getSlots(state); - return ; + return ( + + {open && {slotProps.root.children}} + + ); }; diff --git a/packages/react-components/react-tree/src/components/Tree/useTree.ts b/packages/react-components/react-tree/src/components/Tree/useTree.ts index debd64978dfb67..3c74f414e1a88f 100644 --- a/packages/react-components/react-tree/src/components/Tree/useTree.ts +++ b/packages/react-components/react-tree/src/components/Tree/useTree.ts @@ -1,6 +1,16 @@ import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; -import type { TreeProps, TreeState } from './Tree.types'; +import { + getNativeElementProps, + useControllableState, + useEventCallback, + useMergedRefs, +} from '@fluentui/react-utilities'; +import { useFluent_unstable } from '@fluentui/react-shared-contexts'; +import { useArrowNavigationGroup } from '@fluentui/react-tabster'; +import type { TreeOpenChangeData, TreeProps, TreeState } from './Tree.types'; +import { useTreeContext_unstable } from '../../contexts/treeContext'; +import { useTreeWalker } from '../../utils/useTreeWalker'; +import { TreeItemElement } from '../TreeItem/TreeItem.types'; /** * Create the state required to render Tree. @@ -12,13 +22,168 @@ import type { TreeProps, TreeState } from './Tree.types'; * @param ref - reference to root HTMLElement of Tree */ export const useTree_unstable = (props: TreeProps, ref: React.Ref): TreeState => { + const isSubtree = useTreeContext_unstable(ctx => ctx.level > 0); + // as isSubtree is static, this doesn't break rule of hooks + // and if this becomes an issue later on, this can be easily converted + // eslint-disable-next-line react-hooks/rules-of-hooks + return isSubtree ? useSubtree(props, ref) : useRootTree(props, ref); +}; + +/** + * Create the common state required to render Tree. + * + * The returned state can be modified with hooks such as useTreeStyles_unstable, + * before being passed to renderTree_unstable. + * + * @param props - props from this instance of Tree + * @param ref - reference to root HTMLElement of Tree + */ +function useSubtree(props: TreeProps, ref: React.Ref): TreeState { + const parentLevel = useTreeContext_unstable(ctx => ctx.level); + const focusFirstSubtreeItem = useTreeContext_unstable(ctx => ctx.focusFirstSubtreeItem); + const focusSubtreeOwnerItem = useTreeContext_unstable(ctx => ctx.focusSubtreeOwnerItem); + const openSubtrees = useTreeContext_unstable(ctx => ctx.openSubtrees); + const requestOpenChange = useTreeContext_unstable(ctx => ctx.requestOpenChange); + const isSubtree = parentLevel > 0; + + if (isSubtree) { + warnIfNoProperPropsSubtree(props); + } + + const open = useTreeContext_unstable( + ctx => !isSubtree || props.id === undefined || ctx.openSubtrees.includes(props.id), + ); + const arrowNavigationProps = useArrowNavigationGroup({ + tabbable: true, + axis: 'vertical', + }); + return { components: { root: 'div', }, + open, + level: parentLevel + 1, + openSubtrees, + requestOpenChange, + focusFirstSubtreeItem, + focusSubtreeOwnerItem, root: getNativeElementProps('div', { ref, + role: isSubtree ? 'group' : 'tree', ...props, + ...(isSubtree ? undefined : arrowNavigationProps), }), }; -}; +} + +/** + * Create the state required to render the root Tree. + * + * The returned state can be modified with hooks such as useTreeStyles_unstable, + * before being passed to renderTree_unstable. + * + * @param props - props from this instance of Tree + * @param ref - reference to root HTMLElement of Tree + */ +function useRootTree(props: TreeProps, ref: React.Ref): TreeState { + warnIfNoProperPropsRootTree(props); + const { openSubtrees: stateOpenSubtrees, defaultOpenSubtrees, onOpenChange } = props; + const [openSubtrees, setOpenSubtrees] = useControllableState({ + state: React.useMemo(() => normalizeOpenSubtreesOrUndefined(stateOpenSubtrees), [stateOpenSubtrees]), + defaultState: () => normalizeOpenSubtrees(defaultOpenSubtrees), + initialState: [], + }); + const { targetDocument } = useFluent_unstable(); + const requestOpenChange = useEventCallback((data: TreeOpenChangeData) => { + onOpenChange?.(data.event, data); + if (!data.event.isDefaultPrevented()) { + setOpenSubtrees(updateOpenSubtrees(data, openSubtrees)); + } + }); + const { treeWalker: treeWalkerRef, root: treeRef } = useTreeWalker(NodeFilter.SHOW_ELEMENT, { + acceptNode: filterTreeItemAndSubtree, + }); + const commonState = useSubtree(props, useMergedRefs(ref, treeRef)); + return { + ...commonState, + openSubtrees, + requestOpenChange, + focusFirstSubtreeItem: useEventCallback(target => { + const treeWalker = treeWalkerRef.current; + if (!treeWalker) { + return; + } + const groupId = target.getAttribute('aria-owns'); + if (groupId && targetDocument) { + const element = targetDocument.getElementById(groupId); + if (treeWalker && element) { + treeWalker.currentNode = element; + const firstTreeItem = treeWalker.firstChild() as TreeItemElement | null; + return firstTreeItem?.focus(); + } + } + }), + focusSubtreeOwnerItem: useEventCallback(target => { + const treeWalker = treeWalkerRef.current; + if (!treeWalker) { + return; + } + treeWalker.currentNode = target; + const group = treeWalker.parentNode() as HTMLElement | null; + if (group) { + while (treeWalker.previousNode()) { + const treeItem = treeWalker.currentNode as TreeItemElement; + if (treeItem.getAttribute('aria-owns') === group.id) { + return treeItem.focus(); + } + } + } + }), + }; +} + +function filterTreeItemAndSubtree(node: Node) { + const element = node as HTMLElement & { role: string }; + return element.role === 'treeitem' || element.role === 'group' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP; +} + +function warnIfNoProperPropsSubtree(props: Pick) { + if (process.env.NODE_ENV === 'development') { + if (!props.id) { + // eslint-disable-next-line no-console + console.warn('as sub Tree must have an id to be referred by a TreeItem'); + } + } +} + +function warnIfNoProperPropsRootTree(props: Pick) { + if (process.env.NODE_ENV === 'development') { + if (!props['aria-label'] && !props['aria-labelledby']) { + // eslint-disable-next-line no-console + console.warn('Tree must have either a `aria-label` or `aria-labelledby` property defined'); + } + } +} + +function normalizeOpenSubtrees(openSubtrees?: string | string[]) { + if (!openSubtrees) { + return []; + } + return Array.isArray(openSubtrees) ? openSubtrees : [openSubtrees]; +} + +function normalizeOpenSubtreesOrUndefined(openSubtrees?: string | string[]) { + if (!openSubtrees) { + return undefined; + } + return normalizeOpenSubtrees(openSubtrees); +} + +function updateOpenSubtrees(data: TreeOpenChangeData, previousOpenSubtrees: string[]) { + if (data.open) { + return previousOpenSubtrees.includes(data.id) ? previousOpenSubtrees : [...previousOpenSubtrees, data.id]; + } + const nextOpenItems = previousOpenSubtrees.filter(value => value !== data.id); + return nextOpenItems.length === previousOpenSubtrees.length ? previousOpenSubtrees : nextOpenItems; +} diff --git a/packages/react-components/react-tree/src/components/Tree/useTreeContextValues.ts b/packages/react-components/react-tree/src/components/Tree/useTreeContextValues.ts new file mode 100644 index 00000000000000..1c7c24a4cd1c97 --- /dev/null +++ b/packages/react-components/react-tree/src/components/Tree/useTreeContextValues.ts @@ -0,0 +1,19 @@ +import { TreeContextValue } from '../../contexts'; +import type { TreeContextValues, TreeState } from './Tree.types'; + +export function useTreeContextValues_unstable(state: TreeState): TreeContextValues { + const { openSubtrees, level, requestOpenChange, focusFirstSubtreeItem, focusSubtreeOwnerItem } = state; + /** + * This context is created with "@fluentui/react-context-selector", + * there is no sense to memoize it + */ + const tree: TreeContextValue = { + level, + openSubtrees, + requestOpenChange, + focusFirstSubtreeItem, + focusSubtreeOwnerItem, + }; + + return { tree }; +} diff --git a/packages/react-components/react-tree/src/components/Tree/useTreeStyles.ts b/packages/react-components/react-tree/src/components/Tree/useTreeStyles.ts index 640380271dba46..60eb775afd9fa3 100644 --- a/packages/react-components/react-tree/src/components/Tree/useTreeStyles.ts +++ b/packages/react-components/react-tree/src/components/Tree/useTreeStyles.ts @@ -1,4 +1,4 @@ -import { makeStyles, mergeClasses } from '@griffel/react'; +import { mergeClasses } from '@griffel/react'; import type { TreeSlots, TreeState } from './Tree.types'; import type { SlotClassNames } from '@fluentui/react-utilities'; @@ -6,18 +6,8 @@ export const treeClassNames: SlotClassNames = { root: 'fui-Tree', }; -const useRootStyles = makeStyles({ - root: { - display: 'block', - alignItems: 'center', - position: 'relative', - }, -}); - export const useTreeStyles_unstable = (state: TreeState): TreeState => { - const rootStyles = useRootStyles(); - - state.root.className = mergeClasses(treeClassNames.root, rootStyles.root, state.root.className); + state.root.className = mergeClasses(treeClassNames.root, state.root.className); return state; }; diff --git a/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.test.tsx b/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.test.tsx deleted file mode 100644 index cb4d448cb80de9..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { TreeBranch } from './TreeBranch'; -import { isConformant } from '../../testing/isConformant'; - -describe('TreeBranch', () => { - isConformant({ - Component: TreeBranch, - displayName: 'TreeBranch', - }); - - it('renders a default state', () => { - const result = render(Default TreeBranch); - expect(result.container).toMatchSnapshot(); - }); -}); diff --git a/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.tsx b/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.tsx deleted file mode 100644 index b6062e5174f912..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import { useTreeBranch_unstable } from './useTreeBranch'; -import { renderTreeBranch_unstable } from './renderTreeBranch'; -import { useTreeBranchStyles_unstable } from './useTreeBranchStyles'; -import type { TreeBranchProps } from './TreeBranch.types'; -import type { ForwardRefComponent } from '@fluentui/react-utilities'; - -/** - * A tree branch component allows you to create more branches in a tree component - */ -export const TreeBranch: ForwardRefComponent = React.forwardRef((props, ref) => { - const state = useTreeBranch_unstable(props, ref); - - useTreeBranchStyles_unstable(state); - return renderTreeBranch_unstable(state); -}); - -TreeBranch.displayName = 'TreeBranch'; diff --git a/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.types.ts b/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.types.ts deleted file mode 100644 index 476ad60cece5ad..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/TreeBranch.types.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; - -export type TreeBranchSlots = { - root: Slot<'div'>; -}; - -export type TreeBranchProps = ComponentProps & {}; - -/** - * State used in rendering TreeBranch - */ -export type TreeBranchState = ComponentState; diff --git a/packages/react-components/react-tree/src/components/TreeBranch/__snapshots__/TreeBranch.test.tsx.snap b/packages/react-components/react-tree/src/components/TreeBranch/__snapshots__/TreeBranch.test.tsx.snap deleted file mode 100644 index 87dc5d70e1d84c..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/__snapshots__/TreeBranch.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TreeBranch renders a default state 1`] = ` -
-
- Default TreeBranch -
-
-`; diff --git a/packages/react-components/react-tree/src/components/TreeBranch/index.ts b/packages/react-components/react-tree/src/components/TreeBranch/index.ts deleted file mode 100644 index 06463af34a56ee..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './TreeBranch'; -export * from './TreeBranch.types'; -export * from './renderTreeBranch'; -export * from './useTreeBranch'; -export * from './useTreeBranchStyles'; diff --git a/packages/react-components/react-tree/src/components/TreeBranch/renderTreeBranch.tsx b/packages/react-components/react-tree/src/components/TreeBranch/renderTreeBranch.tsx deleted file mode 100644 index 3348b23084f64f..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/renderTreeBranch.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; -import type { TreeBranchState, TreeBranchSlots } from './TreeBranch.types'; - -export const renderTreeBranch_unstable = (state: TreeBranchState) => { - const { slots, slotProps } = getSlots(state); - - return ; -}; diff --git a/packages/react-components/react-tree/src/components/TreeBranch/useTreeBranch.ts b/packages/react-components/react-tree/src/components/TreeBranch/useTreeBranch.ts deleted file mode 100644 index 5e0555e94281c7..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/useTreeBranch.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; -import type { TreeBranchProps, TreeBranchState } from './TreeBranch.types'; - -/** - * Create the state required to render TreeBranch. - * - * The returned state can be modified with hooks such as useTreeBranchStyles_unstable, - * before being passed to renderTreeBranch_unstable. - * - * @param props - props from this instance of TreeBranch - * @param ref - reference to root HTMLElement of TreeBranch - */ -export const useTreeBranch_unstable = (props: TreeBranchProps, ref: React.Ref): TreeBranchState => { - return { - components: { - root: 'div', - }, - root: getNativeElementProps('div', { - ref, - ...props, - }), - }; -}; diff --git a/packages/react-components/react-tree/src/components/TreeBranch/useTreeBranchStyles.ts b/packages/react-components/react-tree/src/components/TreeBranch/useTreeBranchStyles.ts deleted file mode 100644 index 4c9e5eb6de86ad..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeBranch/useTreeBranchStyles.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { mergeClasses } from '@griffel/react'; -import type { TreeBranchSlots, TreeBranchState } from './TreeBranch.types'; -import type { SlotClassNames } from '@fluentui/react-utilities'; - -export const treeBranchClassNames: SlotClassNames = { - root: 'fui-TreeBranch', -}; - -export const useTreeBranchStyles_unstable = (state: TreeBranchState): TreeBranchState => { - state.root.className = mergeClasses(treeBranchClassNames.root, state.root.className); - return state; -}; diff --git a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.test.tsx b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.test.tsx new file mode 100644 index 00000000000000..b3ff9049811c1d --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.test.tsx @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { render } from '@testing-library/react'; +import { TreeItem } from './TreeItem'; +import { isConformant } from '../../testing/isConformant'; +import { TreeItemProps } from './index'; +import { resetIdsForTests } from '@fluentui/react-utilities'; + +describe('TreeItem', () => { + beforeEach(() => { + resetIdsForTests(); + }); + isConformant({ + Component: TreeItem, + displayName: 'TreeItem', + }); + + // TODO add more tests here, and create visual regression tests in /apps/vr-tests + + it('renders a default state', () => { + const result = render(Default TreeItem); + expect(result.container).toMatchSnapshot(); + }); +}); diff --git a/packages/react-components/react-tree/src/components/TreeItem/TreeItem.tsx b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.tsx new file mode 100644 index 00000000000000..53d527258abcb7 --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useTreeItem_unstable } from './useTreeItem'; +import { renderTreeItem_unstable } from './renderTreeItem'; +import { useTreeItemStyles_unstable } from './useTreeItemStyles'; +import type { TreeItemProps } from './TreeItem.types'; +import type { ForwardRefComponent } from '@fluentui/react-utilities'; + +/** + * TreeItem component - Represents a single node on the Tree + */ +export const TreeItem: ForwardRefComponent = React.forwardRef((props, ref) => { + const state = useTreeItem_unstable(props, ref); + + useTreeItemStyles_unstable(state); + return renderTreeItem_unstable(state); +}); + +TreeItem.displayName = 'TreeItem'; 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 new file mode 100644 index 00000000000000..8df84ff9cfd759 --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts @@ -0,0 +1,24 @@ +import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { ARIAButtonElement, ARIAButtonElementIntersection, ARIAButtonSlotProps } from '@fluentui/react-aria'; + +export type TreeItemElement = ARIAButtonElement<'div' | 'a'>; + +/** @internal */ +export type TreeItemElementIntersection = ARIAButtonElementIntersection<'div' | 'a'>; + +export type TreeItemSlots = { + /** + * TreeItem root wraps around `props.content` + */ + root: NonNullable>>; +}; + +/** + * TreeItem Props + */ +export type TreeItemProps = ComponentProps; + +/** + * State used in rendering TreeItem + */ +export type TreeItemState = ComponentState; diff --git a/packages/react-components/react-tree/src/components/TreeItem/__snapshots__/TreeItem.test.tsx.snap b/packages/react-components/react-tree/src/components/TreeItem/__snapshots__/TreeItem.test.tsx.snap new file mode 100644 index 00000000000000..5a041001ae09e1 --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/__snapshots__/TreeItem.test.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`TreeItem renders a default state 1`] = ` +
+
+ Default TreeItem +
+
+`; diff --git a/packages/react-components/react-tree/src/components/TreeItem/index.ts b/packages/react-components/react-tree/src/components/TreeItem/index.ts new file mode 100644 index 00000000000000..782392551a2c7e --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/index.ts @@ -0,0 +1,5 @@ +export * from './TreeItem'; +export * from './TreeItem.types'; +export * from './renderTreeItem'; +export * from './useTreeItem'; +export * from './useTreeItemStyles'; diff --git a/packages/react-components/react-tree/src/components/TreeItem/renderTreeItem.tsx b/packages/react-components/react-tree/src/components/TreeItem/renderTreeItem.tsx new file mode 100644 index 00000000000000..542de9bcab404f --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/renderTreeItem.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import { getSlots } from '@fluentui/react-utilities'; +import type { TreeItemState, TreeItemSlots } from './TreeItem.types'; + +/** + * Render the final JSX of TreeItem + */ +export const renderTreeItem_unstable = (state: TreeItemState) => { + const { slots, slotProps } = getSlots(state); + + return ; +}; diff --git a/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.ts b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.ts new file mode 100644 index 00000000000000..d7c59f8a271ad5 --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/useTreeItem.ts @@ -0,0 +1,82 @@ +import * as React from 'react'; +import { getNativeElementProps, useEventCallback } from '@fluentui/react-utilities'; +import type { TreeItemElement, TreeItemElementIntersection, TreeItemProps, TreeItemState } from './TreeItem.types'; +import { useARIAButtonProps } from '@fluentui/react-aria'; +import { ArrowRight, ArrowLeft } from '@fluentui/keyboard-keys'; +import { useTreeContext_unstable } from '../../contexts/treeContext'; +/** + * Create the state required to render TreeItem. + * + * The returned state can be modified with hooks such as useTreeItemStyles_unstable, + * before being passed to renderTreeItem_unstable. + * + * @param props - props from this instance of TreeItem + * @param ref - reference to root HTMLElement of TreeItem + */ +export const useTreeItem_unstable = (props: TreeItemProps, ref: React.Ref): TreeItemState => { + 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 handleKeyDown = useEventCallback((event: React.KeyboardEvent) => { + onKeyDown?.(event); + if (event.isDefaultPrevented()) { + return; + } + switch (event.code) { + case ArrowRight: { + return handleArrowRight(event); + } + case ArrowLeft: { + return handleArrowLeft(event); + } + } + }); + return { + components: { + root: 'div', + }, + root: getNativeElementProps( + as, + useARIAButtonProps(as, { + ...rest, + // casting here is required to convert union to intersection + ref: ref as React.Ref, + '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/TreeItem/useTreeItemStyles.ts b/packages/react-components/react-tree/src/components/TreeItem/useTreeItemStyles.ts new file mode 100644 index 00000000000000..55236f4b10ee46 --- /dev/null +++ b/packages/react-components/react-tree/src/components/TreeItem/useTreeItemStyles.ts @@ -0,0 +1,15 @@ +import { mergeClasses } from '@griffel/react'; +import type { TreeItemSlots, TreeItemState } from './TreeItem.types'; +import type { SlotClassNames } from '@fluentui/react-utilities'; + +export const treeItemClassNames: SlotClassNames = { + root: 'fui-TreeItem', +}; + +/** + * Apply styling to the TreeItem slots based on the state + */ +export const useTreeItemStyles_unstable = (state: TreeItemState): TreeItemState => { + state.root.className = mergeClasses(treeItemClassNames.root, state.root.className); + return state; +}; diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.test.tsx b/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.test.tsx deleted file mode 100644 index 8c5275448d9d69..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.test.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import { render } from '@testing-library/react'; -import { TreeLeaf } from './TreeLeaf'; -import { isConformant } from '../../testing/isConformant'; - -describe('TreeLeaf', () => { - isConformant({ - Component: TreeLeaf, - displayName: 'TreeLeaf', - }); - - it('renders a default state', () => { - const result = render(Default TreeLeaf); - expect(result.container).toMatchSnapshot(); - }); -}); diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.tsx b/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.tsx deleted file mode 100644 index 610c9609142ddd..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import * as React from 'react'; -import { useTreeLeaf_unstable } from './useTreeLeaf'; -import { renderTreeLeaf_unstable } from './renderTreeLeaf'; -import { useTreeLeafStyles_unstable } from './useTreeLeafStyles'; -import type { TreeLeafProps } from './TreeLeaf.types'; -import type { ForwardRefComponent } from '@fluentui/react-utilities'; - -/** - * A tree leaf component are the items in the tree component and its branches - */ -export const TreeLeaf: ForwardRefComponent = React.forwardRef((props, ref) => { - const state = useTreeLeaf_unstable(props, ref); - - useTreeLeafStyles_unstable(state); - return renderTreeLeaf_unstable(state); -}); - -TreeLeaf.displayName = 'TreeLeaf'; diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.types.ts b/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.types.ts deleted file mode 100644 index 4e5bdc127d2399..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/TreeLeaf.types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; - -export type TreeLeafSlots = { - root: Slot<'div'>; -}; - -export type TreeLeafProps = ComponentProps & {}; - -export type TreeLeafState = ComponentState; diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/__snapshots__/TreeLeaf.test.tsx.snap b/packages/react-components/react-tree/src/components/TreeLeaf/__snapshots__/TreeLeaf.test.tsx.snap deleted file mode 100644 index 4cfffcd0d648e2..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/__snapshots__/TreeLeaf.test.tsx.snap +++ /dev/null @@ -1,11 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`TreeLeaf renders a default state 1`] = ` -
-
- Default TreeLeaf -
-
-`; diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/index.ts b/packages/react-components/react-tree/src/components/TreeLeaf/index.ts deleted file mode 100644 index 6d17d11b94f886..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './TreeLeaf'; -export * from './TreeLeaf.types'; -export * from './renderTreeLeaf'; -export * from './useTreeLeaf'; -export * from './useTreeLeafStyles'; diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/renderTreeLeaf.tsx b/packages/react-components/react-tree/src/components/TreeLeaf/renderTreeLeaf.tsx deleted file mode 100644 index 8ca755c8ef7f88..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/renderTreeLeaf.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import * as React from 'react'; -import { getSlots } from '@fluentui/react-utilities'; -import type { TreeLeafState, TreeLeafSlots } from './TreeLeaf.types'; - -export const renderTreeLeaf_unstable = (state: TreeLeafState) => { - const { slots, slotProps } = getSlots(state); - - return ; -}; diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/useTreeLeaf.ts b/packages/react-components/react-tree/src/components/TreeLeaf/useTreeLeaf.ts deleted file mode 100644 index 4948fda2a329e2..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/useTreeLeaf.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as React from 'react'; -import { getNativeElementProps } from '@fluentui/react-utilities'; -import type { TreeLeafProps, TreeLeafState } from './TreeLeaf.types'; - -/** - * Create the state required to render TreeLeaf. - * - * The returned state can be modified with hooks such as useTreeLeafStyles_unstable, - * before being passed to renderTreeLeaf_unstable. - * - * @param props - props from this instance of TreeLeaf - * @param ref - reference to root HTMLElement of TreeLeaf - */ -export const useTreeLeaf_unstable = (props: TreeLeafProps, ref: React.Ref): TreeLeafState => { - return { - components: { - root: 'div', - }, - root: getNativeElementProps('div', { - ref, - ...props, - }), - }; -}; diff --git a/packages/react-components/react-tree/src/components/TreeLeaf/useTreeLeafStyles.ts b/packages/react-components/react-tree/src/components/TreeLeaf/useTreeLeafStyles.ts deleted file mode 100644 index 434b6929b5be77..00000000000000 --- a/packages/react-components/react-tree/src/components/TreeLeaf/useTreeLeafStyles.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { makeStyles, mergeClasses } from '@griffel/react'; -import type { TreeLeafSlots, TreeLeafState } from './TreeLeaf.types'; -import type { SlotClassNames } from '@fluentui/react-utilities'; - -export const treeLeafClassNames: SlotClassNames = { - root: 'fui-TreeLeaf', -}; - -/** - * Styles for the root slot - */ -const useStyles = makeStyles({ - root: {}, -}); - -export const useTreeLeafStyles_unstable = (state: TreeLeafState): TreeLeafState => { - const styles = useStyles(); - state.root.className = mergeClasses(treeLeafClassNames.root, styles.root, state.root.className); - - return state; -}; diff --git a/packages/react-components/react-tree/src/contexts/index.ts b/packages/react-components/react-tree/src/contexts/index.ts new file mode 100644 index 00000000000000..4393133d515137 --- /dev/null +++ b/packages/react-components/react-tree/src/contexts/index.ts @@ -0,0 +1 @@ +export * from './treeContext'; diff --git a/packages/react-components/react-tree/src/contexts/treeContext.ts b/packages/react-components/react-tree/src/contexts/treeContext.ts new file mode 100644 index 00000000000000..3c893315a10157 --- /dev/null +++ b/packages/react-components/react-tree/src/contexts/treeContext.ts @@ -0,0 +1,36 @@ +import { Context, ContextSelector, createContext, useContextSelector } from '@fluentui/react-context-selector'; +import { TreeOpenChangeData } from '../Tree'; +import { TreeItemElement } from '../TreeItem'; + +export type TreeContextValue = { + level: number; + openSubtrees: string[]; + focusFirstSubtreeItem(target: TreeItemElement): void; + focusSubtreeOwnerItem(target: TreeItemElement): void; + /** + * Requests dialog main component to update it's internal open state + */ + requestOpenChange(data: TreeOpenChangeData): void; +}; + +const defaultContextValue: TreeContextValue = { + level: 0, + openSubtrees: [], + focusFirstSubtreeItem() { + /* noop */ + }, + focusSubtreeOwnerItem() { + /* noop */ + }, + requestOpenChange() { + /* noop */ + }, +}; + +export const TreeContext: Context = createContext( + undefined, +); + +export const { Provider: TreeProvider } = TreeContext; +export const useTreeContext_unstable = (selector: ContextSelector): T => + useContextSelector(TreeContext, (ctx = defaultContextValue) => selector(ctx)); diff --git a/packages/react-components/react-tree/src/index.ts b/packages/react-components/react-tree/src/index.ts index e60de76ce33b3c..50487eaadfc156 100644 --- a/packages/react-components/react-tree/src/index.ts +++ b/packages/react-components/react-tree/src/index.ts @@ -1,8 +1,7 @@ export { Tree, treeClassNames } from './Tree'; export type { TreeProps, TreeState, TreeSlots } from './Tree'; -export { TreeBranch, treeBranchClassNames } from './TreeBranch'; -export type { TreeBranchProps, TreeBranchState, TreeBranchSlots } from './TreeBranch'; +export { TreeItem, treeItemClassNames } from './TreeItem'; +export type { TreeItemProps, TreeItemState, TreeItemSlots } from './TreeItem'; -export { TreeLeaf, treeLeafClassNames } from './TreeLeaf'; -export type { TreeLeafProps, TreeLeafState, TreeLeafSlots } from './TreeLeaf'; +export type { TreeContextValue, useTreeContext_unstable, TreeProvider } from './contexts'; diff --git a/packages/react-components/react-tree/src/utils/useTreeWalker.ts b/packages/react-components/react-tree/src/utils/useTreeWalker.ts new file mode 100644 index 00000000000000..8fbde4eb82de17 --- /dev/null +++ b/packages/react-components/react-tree/src/utils/useTreeWalker.ts @@ -0,0 +1,16 @@ +import { useFluent_unstable } from '@fluentui/react-shared-contexts'; +import * as React from 'react'; + +export function useTreeWalker(whatToShow?: number, filter?: NodeFilter | null) { + const rootRef = React.useRef(null); + const treeWalkerRef = React.useRef(null); + const { targetDocument } = useFluent_unstable(); + + React.useEffect(() => { + if (rootRef.current && targetDocument) { + treeWalkerRef.current = targetDocument.createTreeWalker(rootRef.current, whatToShow, filter); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return { treeWalker: treeWalkerRef as React.RefObject, root: rootRef }; +} diff --git a/packages/react-components/react-tree/stories/Tree/TreeControllingOpenAndClose.stories.tsx b/packages/react-components/react-tree/stories/Tree/TreeControllingOpenAndClose.stories.tsx new file mode 100644 index 00000000000000..3d0c6561930954 --- /dev/null +++ b/packages/react-components/react-tree/stories/Tree/TreeControllingOpenAndClose.stories.tsx @@ -0,0 +1,27 @@ +import * as React from 'react'; +import { Tree, TreeItem } from '@fluentui/react-tree'; +import { TreeOpenChangeData, TreeOpenChangeEvent } from '../../src/Tree'; + +export const ControllingOpenAndClose = () => { + const [openSubtrees, setOpenSubtrees] = React.useState([]); + const handleOpenChange = (event: TreeOpenChangeEvent, data: TreeOpenChangeData) => { + setOpenSubtrees(curr => (data.open ? [...curr, data.id] : curr.filter(id => id !== data.id))); + }; + return ( + + level 1, item 1 + + level 2, item 1 + level 2, item 2 + level 2, item 3 + + level 1, item 2 + + level 2, item 1 + + level 3, item 1 + + + + ); +}; diff --git a/packages/react-components/react-tree/stories/Tree/TreeDefault.stories.tsx b/packages/react-components/react-tree/stories/Tree/TreeDefault.stories.tsx index e114e005b5a88f..b33478f1b3f0e3 100644 --- a/packages/react-components/react-tree/stories/Tree/TreeDefault.stories.tsx +++ b/packages/react-components/react-tree/stories/Tree/TreeDefault.stories.tsx @@ -1,4 +1,22 @@ import * as React from 'react'; -import { Tree, TreeProps } from '@fluentui/react-tree'; +import { Tree, TreeItem } from '@fluentui/react-tree'; -export const Default = (props: Partial) => ; +export const Default = () => { + return ( + + level 1, item 1 + + level 2, item 1 + level 2, item 2 + level 2, item 3 + + level 1, item 2 + + level 2, item 1 + + level 3, item 1 + + + + ); +}; diff --git a/packages/react-components/react-tree/stories/Tree/index.stories.tsx b/packages/react-components/react-tree/stories/Tree/index.stories.tsx index b69ab0b47d79a5..af4e97c4dfa3ab 100644 --- a/packages/react-components/react-tree/stories/Tree/index.stories.tsx +++ b/packages/react-components/react-tree/stories/Tree/index.stories.tsx @@ -4,6 +4,7 @@ import descriptionMd from './TreeDescription.md'; import bestPracticesMd from './TreeBestPractices.md'; export { Default } from './TreeDefault.stories'; +export { ControllingOpenAndClose } from './TreeControllingOpenAndClose.stories'; export default { title: 'Preview Components/Tree', diff --git a/packages/react-components/react-tree/stories/TreeBranch/TreeBranchBestPractices.md b/packages/react-components/react-tree/stories/TreeBranch/TreeBranchBestPractices.md deleted file mode 100644 index 08ff8ddeeb5f86..00000000000000 --- a/packages/react-components/react-tree/stories/TreeBranch/TreeBranchBestPractices.md +++ /dev/null @@ -1,5 +0,0 @@ -## Best practices - -### Do - -### Don't diff --git a/packages/react-components/react-tree/stories/TreeBranch/TreeBranchDefault.stories.tsx b/packages/react-components/react-tree/stories/TreeBranch/TreeBranchDefault.stories.tsx deleted file mode 100644 index 44a9adf616849a..00000000000000 --- a/packages/react-components/react-tree/stories/TreeBranch/TreeBranchDefault.stories.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import * as React from 'react'; -import { TreeBranch, TreeBranchProps } from '@fluentui/react-tree'; - -export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-tree/stories/TreeBranch/TreeBranchDescription.md b/packages/react-components/react-tree/stories/TreeBranch/TreeBranchDescription.md deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/packages/react-components/react-tree/stories/TreeBranch/index.stories.tsx b/packages/react-components/react-tree/stories/TreeBranch/index.stories.tsx deleted file mode 100644 index 8d63b4b44a73c4..00000000000000 --- a/packages/react-components/react-tree/stories/TreeBranch/index.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { TreeBranch } from '@fluentui/react-tree'; - -import descriptionMd from './TreeBranchDescription.md'; -import bestPracticesMd from './TreeBranchBestPractices.md'; - -export { Default } from './TreeBranchDefault.stories'; - -export default { - title: 'Preview Components/TreeBranch', - component: TreeBranch, - parameters: { - docs: { - description: { - component: [descriptionMd, bestPracticesMd].join('\n'), - }, - }, - }, -}; diff --git a/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafBestPractices.md b/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafBestPractices.md deleted file mode 100644 index 08ff8ddeeb5f86..00000000000000 --- a/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafBestPractices.md +++ /dev/null @@ -1,5 +0,0 @@ -## Best practices - -### Do - -### Don't diff --git a/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafDefault.stories.tsx b/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafDefault.stories.tsx deleted file mode 100644 index 58f608d72e3188..00000000000000 --- a/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafDefault.stories.tsx +++ /dev/null @@ -1,4 +0,0 @@ -import * as React from 'react'; -import { TreeLeaf, TreeLeafProps } from '@fluentui/react-tree'; - -export const Default = (props: Partial) => ; diff --git a/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafDescription.md b/packages/react-components/react-tree/stories/TreeLeaf/TreeLeafDescription.md deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/packages/react-components/react-tree/stories/TreeLeaf/index.stories.tsx b/packages/react-components/react-tree/stories/TreeLeaf/index.stories.tsx deleted file mode 100644 index 5f2ce8a9b525b7..00000000000000 --- a/packages/react-components/react-tree/stories/TreeLeaf/index.stories.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { TreeLeaf } from '@fluentui/react-tree'; - -import descriptionMd from './TreeLeafDescription.md'; -import bestPracticesMd from './TreeLeafBestPractices.md'; - -export { Default } from './TreeLeafDefault.stories'; - -export default { - title: 'Preview Components/TreeLeaf', - component: TreeLeaf, - parameters: { - docs: { - description: { - component: [descriptionMd, bestPracticesMd].join('\n'), - }, - }, - }, -}; diff --git a/packages/react-components/react-tree/tsconfig.spec.json b/packages/react-components/react-tree/tsconfig.spec.json index 469fcba4d7ba75..911456fe4b4d91 100644 --- a/packages/react-components/react-tree/tsconfig.spec.json +++ b/packages/react-components/react-tree/tsconfig.spec.json @@ -5,5 +5,13 @@ "outDir": "dist", "types": ["jest", "node"] }, - "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.test.ts", "**/*.test.tsx", "**/*.d.ts"] + "include": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.d.ts", + "./src/testing/**/*.ts", + "./src/testing/**/*.tsx" + ] }