Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "feat: TreeItem itemType restructure",
"packageName": "@fluentui/react-tree",
"email": "[email protected]",
"dependentChangeType": "patch"
}
71 changes: 38 additions & 33 deletions packages/react-components/react-tree/etc/react-tree.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { ArrowRight } from '@fluentui/keyboard-keys';
import { ArrowUp } from '@fluentui/keyboard-keys';
import type { AvatarContextValue } from '@fluentui/react-avatar';
import type { AvatarSize } from '@fluentui/react-avatar';
import type { ButtonContextValue } from '@fluentui/react-button';
import { ButtonContextValue } from '@fluentui/react-button';
import type { ComponentProps } from '@fluentui/react-utilities';
import type { ComponentState } from '@fluentui/react-utilities';
import { ContextSelector } from '@fluentui/react-context-selector';
Expand Down Expand Up @@ -48,11 +48,11 @@ export type FlatTreeItem<Props extends FlatTreeItemProps<unknown> = FlatTreeItem
value: Props['value'];
parentValue: Props['parentValue'];
ref: React_2.RefObject<HTMLDivElement>;
getTreeItemProps(): Required<Pick<Props, 'value' | 'aria-setsize' | 'aria-level' | 'aria-posinset' | 'leaf'>> & Omit<Props, 'parentValue'>;
getTreeItemProps(): Required<Pick<Props, 'value' | 'aria-setsize' | 'aria-level' | 'aria-posinset' | 'itemType'>> & Omit<Props, 'parentValue'>;
};

// @public (undocumented)
export type FlatTreeItemProps<Value = string> = TreeItemProps<Value> & {
export type FlatTreeItemProps<Value = string> = Omit<TreeItemProps<Value>, 'itemType'> & Partial<Pick<TreeItemProps<Value>, 'itemType'>> & {
value: Value;
parentValue?: Value;
};
Expand All @@ -64,7 +64,7 @@ export type FlatTreeProps<Value = string> = Required<Pick<TreeProps<Value>, 'ope
};

// @public (undocumented)
export type NestedTreeItem<Props extends TreeItemProps<unknown>> = Omit<Props, 'subtree'> & {
export type NestedTreeItem<Props extends TreeItemProps<unknown>> = Omit<Props, 'subtree' | 'itemType'> & {
subtree?: NestedTreeItem<Props>[];
};

Expand All @@ -90,7 +90,7 @@ export const Tree: React_2.ForwardRefExoticComponent<Omit<TreeSlots, "root"> & O
ref?: ((instance: HTMLDivElement | null) => void) | React_2.RefObject<HTMLDivElement> | null | undefined;
}>;
}, "ref"> & {
appearance?: "subtle" | "subtle-alpha" | "transparent" | undefined;
appearance?: "transparent" | "subtle" | "subtle-alpha" | undefined;
size?: "small" | "medium" | undefined;
openItems?: Iterable<string> | undefined;
defaultOpenItems?: Iterable<string> | undefined;
Expand All @@ -107,8 +107,7 @@ export type TreeContextValue = {
appearance: 'subtle' | 'subtle-alpha' | 'transparent';
size: 'small' | 'medium';
openItems: ImmutableSet<unknown>;
requestOpenChange(data: TreeOpenChangeData<unknown>): void;
requestNavigation(data: TreeNavigationData_unstable<unknown>): void;
requestTreeResponse(request: TreeItemRequest<unknown>): void;
};

// @public
Expand All @@ -124,7 +123,7 @@ export const TreeItem: React_2.ForwardRefExoticComponent<Omit<Partial<TreeItemSl
style?: TreeItemCSSProperties | undefined;
}, "ref"> & {
value?: string | undefined;
leaf?: boolean | undefined;
itemType: TreeItemType;
} & React_2.RefAttributes<HTMLDivElement>> & (<Value = string>(props: TreeItemProps<Value>) => JSX.Element);

// @public (undocumented)
Expand All @@ -141,14 +140,20 @@ export type TreeItemLayoutProps = ComponentProps<Partial<TreeItemLayoutSlots>>;

// @public (undocumented)
export type TreeItemLayoutSlots = {
root: Slot<'span'>;
iconBefore?: Slot<'span'>;
iconAfter?: Slot<'span'>;
aside?: Slot<'span'>;
root: Slot<'div'>;
expandIcon?: Slot<'div'>;
iconBefore?: Slot<'div'>;
iconAfter?: Slot<'div'>;
aside?: Slot<'div'>;
actions?: Slot<'div'>;
};

// @public
export type TreeItemLayoutState = ComponentState<TreeItemLayoutSlots> & TreeItemContextValue;
export type TreeItemLayoutState = ComponentState<TreeItemLayoutSlots> & {
buttonContextValue: ButtonContextValue;
isActionsVisible: boolean;
isAsideVisible: boolean;
};

// @public (undocumented)
export const treeItemLevelToken: "--fluent-TreeItem--level";
Expand All @@ -164,46 +169,44 @@ export type TreeItemPersonaLayoutProps = ComponentProps<Partial<TreeItemPersonaL

// @public (undocumented)
export type TreeItemPersonaLayoutSlots = {
root: Slot<'span'>;
media: NonNullable<Slot<'span'>>;
main: Slot<'span'>;
description?: Slot<'span'>;
aside?: Slot<'span'>;
content: Slot<'div'>;
root: NonNullable<Slot<'div'>>;
expandIcon?: Slot<'div'>;
media: NonNullable<Slot<'div'>>;
main: NonNullable<Slot<'div'>>;
description?: Slot<'div'>;
content: NonNullable<Slot<'div'>>;
aside?: Slot<'div'>;
actions?: Slot<'div'>;
};

// @public
export type TreeItemPersonaLayoutState = ComponentState<TreeItemPersonaLayoutSlots> & TreeItemContextValue & {
export type TreeItemPersonaLayoutState = ComponentState<TreeItemPersonaLayoutSlots> & {
avatarSize: AvatarSize;
buttonContextValue: ButtonContextValue;
isActionsVisible: boolean;
isAsideVisible: boolean;
};

// @public
export type TreeItemProps<Value = string> = ComponentProps<Partial<TreeItemSlots>> & {
value?: Value;
leaf?: boolean;
itemType: TreeItemType;
};

// @public (undocumented)
export const TreeItemProvider: React_2.Provider<TreeItemContextValue | undefined>;
export const TreeItemProvider: React_2.Provider<TreeItemContextValue | undefined> & React_2.FC<React_2.ProviderProps<TreeItemContextValue | undefined>>;

// @public (undocumented)
export type TreeItemSlots = {
root: Slot<ExtractSlotProps<Slot<'div'> & {
style?: TreeItemCSSProperties;
}>>;
content: NonNullable<Slot<'div'>>;
subtree?: Slot<'span'>;
expandIcon?: Slot<'span'>;
actions?: Slot<'span'>;
};

// @public
export type TreeItemState = ComponentState<TreeItemSlots> & {
open: boolean;
isLeaf: boolean;
export type TreeItemState = ComponentState<TreeItemSlots> & TreeItemContextValue & {
level: number;
buttonSize: 'small';
isActionsVisible: boolean;
itemType: TreeItemType;
};

// @public (undocumented)
Expand Down Expand Up @@ -287,7 +290,9 @@ export type TreeSlots = {
};

// @public
export type TreeState = ComponentState<TreeSlots> & TreeContextValue;
export type TreeState = ComponentState<TreeSlots> & TreeContextValue & {
open: boolean;
};

// @public
export function useFlatTree_unstable<Props extends FlatTreeItemProps<unknown> = FlatTreeItemProps>(flatTreeItemProps: Props[], options?: FlatTreeOptions<Props>): FlatTree<Props>;
Expand All @@ -305,7 +310,7 @@ export function useTreeContextValues_unstable(state: TreeState): TreeContextValu
export function useTreeItem_unstable<Value = string>(props: TreeItemProps<Value>, ref: React_2.Ref<HTMLDivElement>): TreeItemState;

// @public (undocumented)
export const useTreeItemContext_unstable: () => TreeItemContextValue;
export const useTreeItemContext_unstable: <T>(selector: ContextSelector<TreeItemContextValue, T>) => T;

// @public
export const useTreeItemLayout_unstable: (props: TreeItemLayoutProps, ref: React_2.Ref<HTMLElement>) => TreeItemLayoutState;
Expand Down
3 changes: 2 additions & 1 deletion packages/react-components/react-tree/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"generate-api": "just-scripts generate-api",
"type-check": "tsc -b tsconfig.json",
"storybook": "start-storybook",
"start": "yarn storybook"
"start": "yarn storybook",
"test-ssr": "test-ssr ./stories/**/*.stories.tsx"
},
"devDependencies": {
"@fluentui/eslint-plugin": "*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,38 @@ import {
TreeItemLayout,
TreeProps,
treeItemLayoutClassNames,
treeItemClassNames,
useFlatTree_unstable,
} from '@fluentui/react-tree';
import { Button } from '@fluentui/react-button';
import { flattenTreeFromElement } from '../../utils/flattenTree';
import { flattenTreeFromElement } from '../../testing/flattenTreeFromElement';

const mount = (element: JSX.Element) => {
mountBase(<FluentProvider theme={teamsLightTheme}>{element}</FluentProvider>);
};

const treeItems = (
<>
<TreeItem value="item1" data-testid="item1">
<TreeItem itemType="branch" value="item1" data-testid="item1">
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItem itemType="leaf" value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
<TreeItem value="item1__item2" data-testid="item1__item2">
<TreeItem itemType="leaf" value="item1__item2" data-testid="item1__item2">
<TreeItemLayout>level 2, item 2</TreeItemLayout>
</TreeItem>
<TreeItem value="item1__item3" data-testid="item1__item3">
<TreeItem itemType="leaf" value="item1__item3" data-testid="item1__item3">
<TreeItemLayout>level 2, item 3</TreeItemLayout>
</TreeItem>
</Tree>
</TreeItem>
<TreeItem value="item2" data-testid="item2">
<TreeItem itemType="branch" value="item2" data-testid="item2">
<TreeItemLayout>level 1, item 2</TreeItemLayout>
<Tree>
<TreeItem value="item2__item1" data-testid="item2__item1">
<TreeItem itemType="branch" value="item2__item1" data-testid="item2__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
<Tree>
<TreeItem value="item2__item1__item1" data-testid="item2__item1__item1">
<TreeItem itemType="leaf" value="item2__item1__item1" data-testid="item2__item1__item1">
<TreeItemLayout>level 3, item 1</TreeItemLayout>
</TreeItem>
</Tree>
Expand Down Expand Up @@ -114,9 +113,9 @@ for (const TreeTest of [NestedTree, FlatTree]) {
/>,
);
cy.get('[data-testid="item1__item1"]').should('not.exist');
cy.get(`[data-testid="item1"] .${treeItemClassNames.expandIcon}`).realClick();
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.expandIcon}`).realClick();
cy.get('[data-testid="item1__item1"]').should('exist');
cy.get(`[data-testid="item1"] .${treeItemClassNames.expandIcon}`).realClick();
cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.expandIcon}`).realClick();
cy.get('[data-testid="item1__item1"]').should('not.exist');

cy.get(`[data-testid="item1"] .${treeItemLayoutClassNames.root}`).realClick();
Expand All @@ -125,18 +124,10 @@ for (const TreeTest of [NestedTree, FlatTree]) {
it('should not expand/collapse item on actions click', () => {
mount(
<TreeTest id="tree" aria-label="Tree">
<TreeItem
actions={
<>
<Button id="action">action</Button>
</>
}
value="item1"
data-testid="item1"
>
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<TreeItem itemType="branch" value="item1" data-testid="item1">
<TreeItemLayout actions={<Button id="action">action!</Button>}>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItem itemType="leaf" value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
</Tree>
Expand Down Expand Up @@ -176,18 +167,10 @@ for (const TreeTest of [NestedTree, FlatTree]) {
it('should focus on actions when pressing tab key', () => {
mount(
<TreeTest id="tree" aria-label="Tree">
<TreeItem
actions={
<>
<Button id="action">action</Button>
</>
}
value="item1"
data-testid="item1"
>
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<TreeItem itemType="branch" value="item1" data-testid="item1">
<TreeItemLayout actions={<Button id="action">action</Button>}>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItem itemType="leaf" value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
</Tree>
Expand All @@ -203,18 +186,10 @@ for (const TreeTest of [NestedTree, FlatTree]) {
it('should not expand/collapse item on actions Enter/Space key', () => {
mount(
<TreeTest id="tree" aria-label="Tree">
<TreeItem
actions={
<>
<Button id="action">action</Button>
</>
}
value="item1"
data-testid="item1"
>
<TreeItemLayout>level 1, item 1</TreeItemLayout>
<TreeItem itemType="branch" value="item1" data-testid="item1">
<TreeItemLayout actions={<Button id="action">action</Button>}>level 1, item 1</TreeItemLayout>
<Tree>
<TreeItem value="item1__item1" data-testid="item1__item1">
<TreeItem itemType="leaf" value="item1__item1" data-testid="item1__item1">
<TreeItemLayout>level 2, item 1</TreeItemLayout>
</TreeItem>
</Tree>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,7 @@ export type TreeProps<Value = string> = ComponentProps<TreeSlots> & {
/**
* State used in rendering Tree
*/
export type TreeState = ComponentState<TreeSlots> & TreeContextValue;
export type TreeState = ComponentState<TreeSlots> &
TreeContextValue & {
open: boolean;
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import { TreeProvider } from '../../contexts';

export const renderTree_unstable = (state: TreeState, contextValues: TreeContextValues) => {
const { slots, slotProps } = getSlotsNext<TreeSlots>(state);

return (
<TreeProvider value={contextValues.tree}>
<slots.root {...slotProps.root}>{slotProps.root.children}</slots.root>
{state.open && <slots.root {...slotProps.root}>{slotProps.root.children}</slots.root>}
</TreeProvider>
);
};
Loading