Skip to content

Commit fa982b7

Browse files
committed
feat(react-tree): adds active state to TreeItem
1 parent e0e8de0 commit fa982b7

12 files changed

+43
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "feat: adds active state to TreeItem",
4+
"packageName": "@fluentui/react-tree",
5+
"email": "bernardo.sunderhus@gmail.com",
6+
"dependentChangeType": "patch"
7+
}

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export const TreeItem: React_2.ForwardRefExoticComponent<Omit<Partial<TreeItemSl
122122
} & {
123123
style?: TreeItemCSSProperties | undefined;
124124
}, "ref"> & {
125+
active?: boolean | undefined;
125126
value?: string | undefined;
126127
leaf?: boolean | undefined;
127128
} & React_2.RefAttributes<HTMLDivElement>> & (<Value = string>(props: TreeItemProps<Value>) => JSX.Element);
@@ -178,6 +179,7 @@ export type TreeItemPersonaLayoutState = ComponentState<TreeItemPersonaLayoutSlo
178179

179180
// @public
180181
export type TreeItemProps<Value = string> = ComponentProps<Partial<TreeItemSlots>> & {
182+
active?: boolean;
181183
value?: Value;
182184
leaf?: boolean;
183185
};
@@ -202,7 +204,7 @@ export type TreeItemState = ComponentState<TreeItemSlots> & {
202204
isLeaf: boolean;
203205
level: number;
204206
buttonSize: 'small';
205-
isActionsVisible: boolean;
207+
active: boolean;
206208
};
207209

208210
// @public (undocumented)

packages/react-components/react-tree/src/components/TreeItem/TreeItem.types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export type TreeItemContextValues = {
3131
* TreeItem Props
3232
*/
3333
export type TreeItemProps<Value = string> = ComponentProps<Partial<TreeItemSlots>> & {
34+
/**
35+
* a flag that allows control of the visibility of `actions` slot
36+
*/
37+
active?: boolean;
3438
value?: Value;
3539
/**
3640
* If a TreeItem is a leaf, it'll not present the `expandIcon` slot by default.
@@ -51,5 +55,5 @@ export type TreeItemState = ComponentState<TreeItemSlots> & {
5155
* By design, a button included on the actions slot should be small
5256
*/
5357
buttonSize: 'small';
54-
isActionsVisible: boolean;
58+
active: boolean;
5559
};

packages/react-components/react-tree/src/components/TreeItem/useTreeItem.tsx

+14-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import * as React from 'react';
2-
import { getNativeElementProps, isResolvedShorthand, resolveShorthand, useId } from '@fluentui/react-utilities';
2+
import {
3+
getNativeElementProps,
4+
isResolvedShorthand,
5+
resolveShorthand,
6+
useControllableState,
7+
useId,
8+
} from '@fluentui/react-utilities';
39
import { ChevronRight12Regular } from '@fluentui/react-icons';
410
import { useFluent_unstable } from '@fluentui/react-shared-contexts';
511
import { useEventCallback } from '@fluentui/react-utilities';
@@ -47,6 +53,8 @@ export function useTreeItem_unstable<Value = string>(
4753
const requestOpenChange = useTreeContext_unstable(ctx => ctx.requestOpenChange);
4854
const requestNavigation = useTreeContext_unstable(ctx => ctx.requestNavigation);
4955

56+
const [active, setActive] = useControllableState({ state: props.active, initialState: false });
57+
5058
const isBranch = !isLeaf;
5159

5260
const open = useTreeContext_unstable(ctx => isBranch && ctx.openItems.has(value));
@@ -148,17 +156,16 @@ export function useTreeItem_unstable<Value = string>(
148156
}
149157
});
150158

151-
const [isActionsVisible, setActionsVisible] = React.useState(false);
152159
const showActions = useEventCallback((event: React.SyntheticEvent) => {
153160
const isEventFromSubtree = subtreeRef.current && elementContains(subtreeRef.current, event.target as Node);
154161
if (!isEventFromSubtree) {
155-
setActionsVisible(true);
162+
setActive(true);
156163
}
157164
});
158165
const hideActions = useEventCallback((event: React.SyntheticEvent) => {
159166
const isEventFromSubtree = subtreeRef.current && elementContains(subtreeRef.current, event.target as Node);
160167
if (!isEventFromSubtree) {
161-
setActionsVisible(false);
168+
setActive(false);
162169
}
163170
});
164171

@@ -167,21 +174,22 @@ export function useTreeItem_unstable<Value = string>(
167174
React.useEffect(() => {
168175
if (actionsRef.current) {
169176
const handleFocusOut = (event: FocusEvent) => {
170-
setActionsVisible(elementContains(actionsRef.current, event.relatedTarget as Node));
177+
setActive(elementContains(actionsRef.current, event.relatedTarget as Node));
171178
};
172179
targetDocument?.addEventListener('focusout', handleFocusOut, { passive: true });
173180
return () => {
174181
targetDocument?.removeEventListener('focusout', handleFocusOut);
175182
};
176183
}
184+
// eslint-disable-next-line react-hooks/exhaustive-deps
177185
}, [targetDocument]);
178186

179187
return {
180188
isLeaf,
181189
open,
182190
level,
183191
buttonSize: 'small',
184-
isActionsVisible: actions ? isActionsVisible : false,
192+
active: actions ? active : false,
185193
components: {
186194
content: 'div',
187195
root: 'div',

packages/react-components/react-tree/src/components/TreeItem/useTreeItemContextValues.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import type { TreeItemContextValue } from '../../contexts';
44
import type { TreeItemContextValues, TreeItemState } from './TreeItem.types';
55

66
export function useTreeItemContextValues_unstable(
7-
state: Pick<TreeItemState, 'buttonSize' | 'isActionsVisible'>,
7+
state: Pick<TreeItemState, 'buttonSize' | 'active'>,
88
): TreeItemContextValues {
9-
const { buttonSize, isActionsVisible } = state;
9+
const { buttonSize, active } = state;
1010

11-
const treeItem = React.useMemo<TreeItemContextValue>(() => ({ isActionsVisible }), [isActionsVisible]);
11+
const treeItem = React.useMemo<TreeItemContextValue>(() => ({ active }), [active]);
1212
const button = React.useMemo<ButtonContextValue>(() => ({ size: buttonSize }), [buttonSize]);
1313

1414
return { treeItem, button };

packages/react-components/react-tree/src/components/TreeItem/useTreeItemStyles.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ export const useTreeItemStyles_unstable = (state: TreeItemState): TreeItemState
128128

129129
const appearance = useTreeContext_unstable(ctx => ctx.appearance);
130130

131-
const { actions, subtree, expandIcon, isActionsVisible: showActions, level } = state;
131+
const { actions, subtree, expandIcon, active, level } = state;
132132

133133
state.root.className = mergeClasses(
134134
treeItemClassNames.root,
@@ -152,7 +152,7 @@ export const useTreeItemStyles_unstable = (state: TreeItemState): TreeItemState
152152
actions.className = mergeClasses(
153153
treeItemClassNames.actions,
154154
actionsStyles.base,
155-
showActions && actionsStyles.open,
155+
active && actionsStyles.open,
156156
actions.className,
157157
);
158158
}

packages/react-components/react-tree/src/components/TreeItemLayout/renderTreeItemLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { TreeItemLayoutState, TreeItemLayoutSlots } from './TreeItemLayout.
99
* Render the final JSX of TreeItemLayout
1010
*/
1111
export const renderTreeItemLayout_unstable = (state: TreeItemLayoutState) => {
12-
const { isActionsVisible } = state;
12+
const { active: isActionsVisible } = state;
1313
const { slots, slotProps } = getSlotsNext<TreeItemLayoutSlots>(state);
1414
return (
1515
<slots.root {...slotProps.root}>

packages/react-components/react-tree/src/components/TreeItemPersonaLayout/renderTreeItemPersonaLayout.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const renderTreeItemPersonaLayout_unstable = (
1717
state: TreeItemPersonaLayoutState,
1818
contextValues: TreeItemPersonaLayoutContextValues,
1919
) => {
20-
const { isActionsVisible } = state;
20+
const { active: isActionsVisible } = state;
2121
const { slots, slotProps } = getSlotsNext<TreeItemPersonaLayoutSlots>(state);
2222

2323
return (

packages/react-components/react-tree/src/components/TreeItemPersonaLayout/useTreeItemPersonaLayout.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export const useTreeItemPersonaLayout_unstable = (
1919
): TreeItemPersonaLayoutState => {
2020
const { media, content, children, main, description, aside, as = 'span' } = props;
2121
const size = useTreeContext_unstable(ctx => ctx.size);
22-
const { isActionsVisible } = useTreeItemContext_unstable();
22+
const { active: isActionsVisible } = useTreeItemContext_unstable();
2323
return {
2424
components: {
2525
content: 'div',
@@ -29,7 +29,7 @@ export const useTreeItemPersonaLayout_unstable = (
2929
media: 'span',
3030
aside: 'span',
3131
},
32-
isActionsVisible,
32+
active: isActionsVisible,
3333
avatarSize: treeAvatarSize[size],
3434
root: getNativeElementProps(as, { ...props, ref }),
3535
main: resolveShorthand(main, { required: true, defaultProps: { children } }),

packages/react-components/react-tree/src/contexts/treeItemContext.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as React from 'react';
22

33
export type TreeItemContextValue = {
4-
isActionsVisible: boolean;
4+
active: boolean;
55
};
66

77
const defaultContextValue: TreeItemContextValue = {
8-
isActionsVisible: false,
8+
active: false,
99
};
1010

1111
export const TreeItemContext: React.Context<TreeItemContextValue | undefined> = React.createContext<
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
Add custom actions to each `TreeItem` component to add extra functionality, such as buttons or menus. This can be done by passing a component that renders the desired actions as a prop to the `TreeItem`.
22

33
The `actions` are hidden by default and appear on the right-side when the user hovers over the `TreeItem`.
4+
5+
> To force the visibility of an action, use the `active` property

packages/react-components/react-tree/stories/B_TreeItem/TreeItemActions.stories.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const RenderActions = () => {
2828

2929
export const Actions = () => (
3030
<Tree aria-label="Tree">
31-
<TreeItem actions={<RenderActions />}>
31+
<TreeItem active actions={<RenderActions />}>
3232
<TreeItemLayout>level 1, item 1</TreeItemLayout>
3333
<Tree>
3434
<TreeItem actions={<RenderActions />}>

0 commit comments

Comments
 (0)