Skip to content

Commit 8fcefae

Browse files
bsunderhusling1726
andauthored
chore: creates TreeItem and basic Tree (#25742)
* chore: initial Tree & TreeItem implementation * chore: convert context to use context selector * chore: converts focus to use a tree walker * Update packages/react-components/react-tree/src/components/Tree/useTree.ts Co-authored-by: ling1726 <[email protected]> Co-authored-by: ling1726 <[email protected]>
1 parent 6b90a6e commit 8fcefae

53 files changed

Lines changed: 603 additions & 332 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/react-components/react-tree/etc/react-tree.api.md

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,62 +4,73 @@
44
55
```ts
66

7+
import type { ARIAButtonElement } from '@fluentui/react-aria';
8+
import type { ARIAButtonSlotProps } from '@fluentui/react-aria';
79
import type { ComponentProps } from '@fluentui/react-utilities';
810
import type { ComponentState } from '@fluentui/react-utilities';
11+
import { ContextSelector } from '@fluentui/react-context-selector';
12+
import { FC } from 'react';
913
import type { ForwardRefComponent } from '@fluentui/react-utilities';
14+
import { Provider } from 'react';
15+
import { ProviderProps } from 'react';
16+
import * as React_2 from 'react';
1017
import type { Slot } from '@fluentui/react-utilities';
1118
import type { SlotClassNames } from '@fluentui/react-utilities';
1219

1320
// @public
1421
export const Tree: ForwardRefComponent<TreeProps>;
1522

16-
// @public
17-
export const TreeBranch: ForwardRefComponent<TreeBranchProps>;
18-
19-
// @public (undocumented)
20-
export const treeBranchClassNames: SlotClassNames<TreeBranchSlots>;
21-
2223
// @public (undocumented)
23-
export type TreeBranchProps = ComponentProps<TreeBranchSlots> & {};
24+
export const treeClassNames: SlotClassNames<TreeSlots>;
2425

2526
// @public (undocumented)
26-
export type TreeBranchSlots = {
27-
root: Slot<'div'>;
27+
export type TreeContextValue = {
28+
level: number;
29+
openSubtrees: string[];
30+
focusFirstSubtreeItem(target: TreeItemElement): void;
31+
focusSubtreeOwnerItem(target: TreeItemElement): void;
32+
requestOpenChange(data: TreeOpenChangeData): void;
2833
};
2934

3035
// @public
31-
export type TreeBranchState = ComponentState<TreeBranchSlots>;
36+
export const TreeItem: ForwardRefComponent<TreeItemProps>;
3237

3338
// @public (undocumented)
34-
export const treeClassNames: SlotClassNames<TreeSlots>;
39+
export const treeItemClassNames: SlotClassNames<TreeItemSlots>;
3540

3641
// @public
37-
export const TreeLeaf: ForwardRefComponent<TreeLeafProps>;
42+
export type TreeItemProps = ComponentProps<TreeItemSlots>;
3843

3944
// @public (undocumented)
40-
export const treeLeafClassNames: SlotClassNames<TreeLeafSlots>;
45+
export type TreeItemSlots = {
46+
root: NonNullable<Slot<ARIAButtonSlotProps<'div' | 'a'>>>;
47+
};
4148

42-
// @public (undocumented)
43-
export type TreeLeafProps = ComponentProps<TreeLeafSlots> & {};
49+
// @public
50+
export type TreeItemState = ComponentState<TreeItemSlots>;
4451

4552
// @public (undocumented)
46-
export type TreeLeafSlots = {
47-
root: Slot<'div'>;
53+
export type TreeProps = ComponentProps<TreeSlots> & {
54+
openSubtrees?: string | string[];
55+
defaultOpenSubtrees?: string | string[];
56+
onOpenChange?(event: TreeOpenChangeEvent, data: TreeOpenChangeData): void;
4857
};
4958

5059
// @public (undocumented)
51-
export type TreeLeafState = ComponentState<TreeLeafSlots>;
52-
53-
// @public (undocumented)
54-
export type TreeProps = ComponentProps<TreeSlots> & {};
60+
export const TreeProvider: Provider<TreeContextValue | undefined> & FC<ProviderProps<TreeContextValue | undefined>>;
5561

5662
// @public (undocumented)
5763
export type TreeSlots = {
5864
root: Slot<'div'>;
5965
};
6066

6167
// @public
62-
export type TreeState = ComponentState<TreeSlots>;
68+
export type TreeState = ComponentState<TreeSlots> & TreeContextValue & {
69+
open: boolean;
70+
};
71+
72+
// @public (undocumented)
73+
export const useTreeContext_unstable: <T>(selector: ContextSelector<TreeContextValue, T>) => T;
6374

6475
// (No @packageDocumentation comment for this package)
6576

packages/react-components/react-tree/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@
3131
"@fluentui/scripts": "^1.0.0"
3232
},
3333
"dependencies": {
34+
"@fluentui/react-context-selector": "^9.1.2",
35+
"@fluentui/react-shared-contexts": "^9.1.1",
36+
"@fluentui/react-aria": "^9.3.2",
37+
"@fluentui/react-tabster": "^9.3.1",
38+
"@fluentui/keyboard-keys": "^9.0.1",
3439
"@fluentui/react-theme": "^9.1.2",
3540
"@fluentui/react-utilities": "^9.2.2",
3641
"@griffel/react": "^1.4.2",

packages/react-components/react-tree/src/TreeBranch.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './components/TreeItem/index';

packages/react-components/react-tree/src/TreeLeaf.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/react-components/react-tree/src/components/Tree/Tree.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ describe('Tree', () => {
77
isConformant({
88
Component: Tree,
99
displayName: 'Tree',
10+
disabledTests: ['consistent-callback-args'],
1011
});
1112

1213
it('renders a default state', () => {
13-
const result = render(<Tree>Default Tree</Tree>);
14+
const result = render(<Tree />);
1415
expect(result.container).toMatchSnapshot();
1516
});
1617
});

packages/react-components/react-tree/src/components/Tree/Tree.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ import { renderTree_unstable } from './renderTree';
44
import { useTreeStyles_unstable } from './useTreeStyles';
55
import type { TreeProps } from './Tree.types';
66
import type { ForwardRefComponent } from '@fluentui/react-utilities';
7+
import { useTreeContextValues_unstable } from './useTreeContextValues';
78

89
/**
9-
* A tree component provides a hierarchical list
10+
* A tree view widget presents a hierarchical list.
11+
* Any item in the hierarchy may have child items,
12+
* and items that have children may be expanded or collapsed to show or hide the children.
13+
* For example, in a file system navigator that uses a tree view to display folders and files,
14+
* an item representing a folder can be expanded to reveal the contents of the folder,
15+
* which may be files, folders, or both.
1016
*/
1117
export const Tree: ForwardRefComponent<TreeProps> = React.forwardRef((props, ref) => {
1218
const state = useTree_unstable(props, ref);
13-
1419
useTreeStyles_unstable(state);
15-
return renderTree_unstable(state);
20+
const contextValues = useTreeContextValues_unstable(state);
21+
return renderTree_unstable(state, contextValues);
1622
});
1723

1824
Tree.displayName = 'Tree';
Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,56 @@
1+
import * as React from 'react';
12
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
3+
import type { TreeItemElement } from '../TreeItem/TreeItem.types';
4+
import { TreeContextValue } from '../../contexts/treeContext';
25

36
export type TreeSlots = {
47
root: Slot<'div'>;
58
};
69

7-
export type TreeProps = ComponentProps<TreeSlots> & {};
10+
export type TreeOpenChangeData = { open: boolean; id: string } & (
11+
| {
12+
event: React.MouseEvent<TreeItemElement>;
13+
type: 'click';
14+
}
15+
| {
16+
event: React.KeyboardEvent<TreeItemElement>;
17+
type: 'arrowRight' | 'arrowLeft';
18+
}
19+
);
20+
21+
export type TreeOpenChangeEvent = TreeOpenChangeData['event'];
22+
23+
export type TreeContextValues = {
24+
tree: TreeContextValue;
25+
};
26+
27+
export type TreeProps = ComponentProps<TreeSlots> & {
28+
/**
29+
* Controls the state of the open subtrees.
30+
* These property is ignored for subtrees.
31+
*/
32+
openSubtrees?: string | string[];
33+
/**
34+
* Default value for the uncontrolled state of open subtrees.
35+
* These property is ignored for subtrees.
36+
*/
37+
defaultOpenSubtrees?: string | string[];
38+
/**
39+
* Callback fired when the component changes value from open state.
40+
* These property is ignored for subtrees.
41+
*
42+
* @param event - a React's Synthetic event
43+
* @param data - A data object with relevant information,
44+
* such as open value and type of interaction that created the event
45+
* and the id of the subtree that is being opened/closed
46+
*/
47+
onOpenChange?(event: TreeOpenChangeEvent, data: TreeOpenChangeData): void;
48+
};
849

950
/**
1051
* State used in rendering Tree
1152
*/
12-
export type TreeState = ComponentState<TreeSlots>;
53+
export type TreeState = ComponentState<TreeSlots> &
54+
TreeContextValue & {
55+
open: boolean;
56+
};

packages/react-components/react-tree/src/components/Tree/__snapshots__/Tree.test.tsx.snap

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ exports[`Tree renders a default state 1`] = `
44
<div>
55
<div
66
class="fui-Tree"
7-
>
8-
Default Tree
9-
</div>
7+
data-tabster="{\\"mover\\":{\\"cyclic\\":false,\\"direction\\":1,\\"tabbable\\":true}}"
8+
role="tree"
9+
/>
1010
</div>
1111
`;
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
import * as React from 'react';
22
import { getSlots } from '@fluentui/react-utilities';
3-
import type { TreeState, TreeSlots } from './Tree.types';
3+
import type { TreeState, TreeSlots, TreeContextValues } from './Tree.types';
4+
import { TreeProvider } from '../../contexts';
45

5-
export const renderTree_unstable = (state: TreeState) => {
6+
export const renderTree_unstable = (state: TreeState, contextValues: TreeContextValues) => {
7+
const { open } = state;
68
const { slots, slotProps } = getSlots<TreeSlots>(state);
79

8-
return <slots.root {...slotProps.root} />;
10+
return (
11+
<TreeProvider value={contextValues.tree}>
12+
{open && <slots.root {...slotProps.root}>{slotProps.root.children}</slots.root>}
13+
</TreeProvider>
14+
);
915
};

0 commit comments

Comments
 (0)