Skip to content
Draft
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
7 changes: 7 additions & 0 deletions change/@fluentui-react-table-base-hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🕵🏾‍♀️ visual changes to review in the Visual Change Report

vr-tests-react-components/Charts-DonutChart 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Charts-DonutChart.Dynamic - Dark Mode.default.chromium.png 7530 Changed
vr-tests-react-components/Charts-DonutChart.Dynamic.default.chromium.png 5581 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default - RTL.submenus open.chromium.png 404 Changed
vr-tests-react-components/Menu Converged - submenuIndicator slotted content.default.submenus open.chromium.png 413 Changed
vr-tests-react-components/Positioning 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png 718 Changed
vr-tests-react-components/Positioning.Positioning end.chromium.png 725 Changed
vr-tests-react-components/TagPicker 2 screenshots
Image Name Diff(in Pixels) Image Type
vr-tests-react-components/TagPicker.disabled - RTL.disabled input hover.chromium.png 635 Changed
vr-tests-react-components/TagPicker.disabled.disabled input hover.chromium.png 677 Changed

There were 2 duplicate changes discarded. Check the build logs for more information.

"type": "minor",
"comment": "feat: add base hooks for Table, TableCell, TableRow, TableCellLayout, DataGrid, DataGridRow",
"packageName": "@fluentui/react-table",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
7 changes: 7 additions & 0 deletions change/@fluentui-react-tree-base-hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: add base hooks for Tree, FlatTree, TreeItemPersonaLayout",
"packageName": "@fluentui/react-tree",
"email": "dmytrokirpa@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type {
FlatTreeBaseProps,
FlatTreeBaseState,
FlatTreeContextValues,
FlatTreeProps,
FlatTreeSlots,
Expand All @@ -14,6 +16,7 @@ export {
renderFlatTree_unstable,
useFlatTreeContextValues_unstable,
useFlatTreeStyles_unstable,
useFlatTreeBase_unstable,
useFlatTree_unstable,
useHeadlessFlatTree_unstable,
} from './components/FlatTree/index';
3 changes: 3 additions & 0 deletions packages/react-components/react-tree/library/src/Tree.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type {
TreeBaseProps,
TreeBaseState,
TreeCheckedChangeData,
TreeCheckedChangeEvent,
TreeContextValues,
Expand All @@ -19,5 +21,6 @@ export {
treeClassNames,
useTreeContextValues_unstable,
useTreeStyles_unstable,
useTreeBase_unstable,
useTree_unstable,
} from './components/Tree/index';
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type {
TreeItemPersonaLayoutBaseProps,
TreeItemPersonaLayoutBaseState,
TreeItemPersonaLayoutContextValues,
TreeItemPersonaLayoutProps,
TreeItemPersonaLayoutSlots,
Expand All @@ -9,5 +11,6 @@ export {
renderTreeItemPersonaLayout_unstable,
treeItemPersonaLayoutClassNames,
useTreeItemPersonaLayoutStyles_unstable,
useTreeItemPersonaLayoutBase_unstable,
useTreeItemPersonaLayout_unstable,
} from './components/TreeItemPersonaLayout/index';
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentProps, ComponentState, SelectionMode } from '@fluentui/react-utilities';
import type { ComponentProps, ComponentState, DistributiveOmit, SelectionMode } from '@fluentui/react-utilities';
import type {
TreeSlots,
TreeCheckedChangeData,
Expand Down Expand Up @@ -96,3 +96,13 @@ export type FlatTreeState = ComponentState<FlatTreeSlots> &
TreeContextValue & {
open: boolean;
};

/**
* FlatTree Props without design-only props.
*/
export type FlatTreeBaseProps = DistributiveOmit<FlatTreeProps, 'appearance' | 'size'>;

/**
* State used in rendering FlatTree, without design-only state.
*/
export type FlatTreeBaseState = DistributiveOmit<FlatTreeState, 'appearance' | 'size'>;
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
export { FlatTree } from './FlatTree';
export type { FlatTreeContextValues, FlatTreeProps, FlatTreeSlots, FlatTreeState } from './FlatTree.types';
export type {
FlatTreeBaseProps,
FlatTreeBaseState,
FlatTreeContextValues,
FlatTreeProps,
FlatTreeSlots,
FlatTreeState,
} from './FlatTree.types';
export type {
HeadlessFlatTree,
HeadlessFlatTreeItem,
HeadlessFlatTreeItemProps,
HeadlessFlatTreeOptions,
} from './useHeadlessFlatTree';
export { useHeadlessFlatTree_unstable } from './useHeadlessFlatTree';
export { useFlatTree_unstable } from './useFlatTree';
export { useFlatTreeBase_unstable, useFlatTree_unstable } from './useFlatTree';
export { flatTreeClassNames, useFlatTreeStyles_unstable } from './useFlatTreeStyles.styles';
export { useFlatTreeContextValues_unstable } from './useFlatTreeContextValues';
export { renderFlatTree_unstable } from './renderFlatTree';
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

import * as React from 'react';
import { useRootTree } from '../../hooks/useRootTree';
import { FlatTreeProps, FlatTreeState } from './FlatTree.types';
import { FlatTreeBaseProps, FlatTreeBaseState, FlatTreeProps, FlatTreeState } from './FlatTree.types';
import { useEventCallback, useMergedRefs } from '@fluentui/react-utilities';
import { useFlatTreeNavigation } from '../../hooks/useFlatTreeNavigation';
import { useSubtree } from '../../hooks/useSubtree';
import { ImmutableSet } from '../../utils/ImmutableSet';
import { ImmutableMap } from '../../utils/ImmutableMap';
import { SubtreeContext } from '../../contexts/subtreeContext';

export const useFlatTree_unstable: (props: FlatTreeProps, ref: React.Ref<HTMLElement>) => FlatTreeState = (
/**
* Create the base state required to render FlatTree, without design-only props.
*
* @param props - props from this instance of FlatTree (without appearance and size)
* @param ref - reference to root HTMLElement of FlatTree
*/
export const useFlatTreeBase_unstable: (props: FlatTreeBaseProps, ref: React.Ref<HTMLElement>) => FlatTreeBaseState = (
props,
ref,
) => {
Expand All @@ -20,30 +26,51 @@ export const useFlatTree_unstable: (props: FlatTreeProps, ref: React.Ref<HTMLEle
// as level 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 isRoot ? useRootFlatTree(props, ref) : useSubFlatTree(props, ref);
return isRoot ? useRootFlatTreeBase(props, ref) : (useSubFlatTree(props as FlatTreeProps, ref) as FlatTreeBaseState);
};

/**
* Create the state required to render FlatTree.
*
* The returned state can be modified with hooks such as useFlatTreeStyles_unstable,
* before being passed to renderFlatTree_unstable.
*
* @param props - props from this instance of FlatTree
* @param ref - reference to root HTMLElement of FlatTree
*/
export const useFlatTree_unstable: (props: FlatTreeProps, ref: React.Ref<HTMLElement>) => FlatTreeState = (
props,
ref,
) => {
'use no memo';

const { appearance = 'subtle', size = 'medium' } = props;
const baseState = useFlatTreeBase_unstable(props, ref);
return { ...baseState, appearance, size } as unknown as FlatTreeState;
};

function useRootFlatTree(props: FlatTreeProps, ref: React.Ref<HTMLElement>): FlatTreeState {
function useRootFlatTreeBase(props: FlatTreeBaseProps, ref: React.Ref<HTMLElement>): FlatTreeBaseState {
const navigation = useFlatTreeNavigation(props.navigationMode);

return Object.assign(
useRootTree(
{
...props,
onNavigation: useEventCallback((event, data) => {
props.onNavigation?.(event, data);
if (!event.isDefaultPrevented()) {
navigation.navigate(data);
}
}),
},
useMergedRefs(ref, navigation.rootRef),
),
const fullState = useRootTree(
{
treeType: 'flat',
forceUpdateRovingTabIndex: navigation.forceUpdateRovingTabIndex,
} as const,
...props,
onNavigation: useEventCallback((event, data) => {
props.onNavigation?.(event, data);
if (!event.isDefaultPrevented()) {
navigation.navigate(data);
}
}),
},
useMergedRefs(ref, navigation.rootRef),
);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { appearance: _appearance, size: _size, ...baseState } = fullState;
return Object.assign(baseState, {
treeType: 'flat',
forceUpdateRovingTabIndex: navigation.forceUpdateRovingTabIndex,
} as const) as unknown as FlatTreeBaseState;
}

function useSubFlatTree(props: FlatTreeProps, ref: React.Ref<HTMLElement>): FlatTreeState {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type * as React from 'react';
import type { PresenceMotionSlotProps } from '@fluentui/react-motion';
import type { ComponentProps, ComponentState, SelectionMode, Slot } from '@fluentui/react-utilities';
import type { ComponentProps, ComponentState, DistributiveOmit, SelectionMode, Slot } from '@fluentui/react-utilities';
import type { TreeContextValue, SubtreeContextValue } from '../../contexts';
import type { ArrowDown, ArrowLeft, ArrowRight, ArrowUp, End, Enter, Home } from '@fluentui/keyboard-keys';
import type { TreeItemValue } from '../TreeItem/TreeItem.types';
Expand Down Expand Up @@ -176,3 +176,13 @@ export type TreeProps = ComponentProps<TreeSlots> & {
export type TreeState = ComponentState<TreeSlots> & {
open: boolean;
} & (TreeContextValue | SubtreeContextValue);

/**
* Tree Props without design-only props.
*/
export type TreeBaseProps = DistributiveOmit<TreeProps, 'appearance' | 'size'>;

/**
* State used in rendering Tree, without design-only state.
*/
export type TreeBaseState = DistributiveOmit<TreeState, 'appearance' | 'size'>;
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { Tree } from './Tree';
export type {
TreeBaseProps,
TreeBaseState,
TreeCheckedChangeData,
TreeCheckedChangeEvent,
TreeContextValues,
Expand All @@ -14,7 +16,7 @@ export type {
TreeNavigationMode,
TreeNavigationDataParam,
} from './Tree.types';
export { useTree_unstable } from './useTree';
export { useTreeBase_unstable, useTree_unstable } from './useTree';
export { useTreeContextValues_unstable } from './useTreeContextValues';
export { treeClassNames, useTreeStyles_unstable } from './useTreeStyles.styles';
export { renderTree_unstable } from './renderTree';
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as React from 'react';
import { useEventCallback, useMergedRefs } from '@fluentui/react-utilities';
import type { TreeProps, TreeState } from './Tree.types';
import type { TreeBaseProps, TreeBaseState, TreeProps, TreeState } from './Tree.types';
import { createNextOpenItems, useControllableOpenItems } from '../../hooks/useControllableOpenItems';
import { createNextNestedCheckedItems, useNestedCheckedItems } from './useNestedControllableCheckedItems';
import { SubtreeContext } from '../../contexts/subtreeContext';
Expand All @@ -13,60 +13,87 @@ import { useTreeContext_unstable } from '../../contexts/treeContext';
import { ImmutableSet } from '../../utils/ImmutableSet';
import { ImmutableMap } from '../../utils/ImmutableMap';

export const useTree_unstable = (props: TreeProps, ref: React.Ref<HTMLElement>): TreeState => {
/**
* Create the base state required to render Tree, without design-only props.
*
* @param props - props from this instance of Tree (without appearance and size)
* @param ref - reference to root HTMLElement of Tree
*/
export const useTreeBase_unstable = (props: TreeBaseProps, ref: React.Ref<HTMLElement>): TreeBaseState => {
'use no memo';

const isRoot = React.useContext(SubtreeContext) === undefined;
// as level 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 isRoot ? useNestedRootTree(props, ref) : useNestedSubtree(props, ref);
return isRoot ? useNestedRootTreeBase(props, ref) : (useNestedSubtree(props as TreeProps, ref) as TreeBaseState);
};

function useNestedRootTree(props: TreeProps, ref: React.Ref<HTMLElement>): TreeState {
/**
* Create the 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
*/
export const useTree_unstable = (props: TreeProps, ref: React.Ref<HTMLElement>): TreeState => {
'use no memo';

const { appearance = 'subtle', size = 'medium' } = props;
const baseState = useTreeBase_unstable(props, ref);
if (baseState.contextType === 'root') {
return { ...baseState, appearance, size } as unknown as TreeState;
}
return baseState as unknown as TreeState;
};

function useNestedRootTreeBase(props: TreeBaseProps, ref: React.Ref<HTMLElement>): TreeBaseState {
'use no memo';

const [openItems, setOpenItems] = useControllableOpenItems(props);
const checkedItems = useNestedCheckedItems(props);
const navigation = useTreeNavigation(props.navigationMode);

return Object.assign(
useRootTree(
{
...props,
openItems,
checkedItems,
onOpenChange: useEventCallback((event, data) => {
const nextOpenItems = createNextOpenItems(data, openItems);
props.onOpenChange?.(event, {
...data,
openItems: ImmutableSet.dangerouslyGetInternalSet(nextOpenItems),
});
setOpenItems(nextOpenItems);
}),
onNavigation: useEventCallback((event, data) => {
props.onNavigation?.(event, data);
if (!event.isDefaultPrevented()) {
navigation.navigate(data, {
preventScroll: data.isScrollPrevented(),
});
}
}),
onCheckedChange: useEventCallback((event, data) => {
const nextCheckedItems = createNextNestedCheckedItems(data, checkedItems);
props.onCheckedChange?.(event, {
...data,
checkedItems: ImmutableMap.dangerouslyGetInternalMap(nextCheckedItems),
});
}),
},
useMergedRefs(ref, navigation.treeRef),
),
const fullState = useRootTree(
{
treeType: 'nested',
forceUpdateRovingTabIndex: navigation.forceUpdateRovingTabIndex,
} as const,
...props,
openItems,
checkedItems,
onOpenChange: useEventCallback((event, data) => {
const nextOpenItems = createNextOpenItems(data, openItems);
props.onOpenChange?.(event, {
...data,
openItems: ImmutableSet.dangerouslyGetInternalSet(nextOpenItems),
});
setOpenItems(nextOpenItems);
}),
onNavigation: useEventCallback((event, data) => {
props.onNavigation?.(event, data);
if (!event.isDefaultPrevented()) {
navigation.navigate(data, {
preventScroll: data.isScrollPrevented(),
});
}
}),
onCheckedChange: useEventCallback((event, data) => {
const nextCheckedItems = createNextNestedCheckedItems(data, checkedItems);
props.onCheckedChange?.(event, {
...data,
checkedItems: ImmutableMap.dangerouslyGetInternalMap(nextCheckedItems),
});
}),
},
useMergedRefs(ref, navigation.treeRef),
);

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { appearance: _appearance, size: _size, ...baseRootState } = fullState;
return Object.assign(baseRootState, {
treeType: 'nested',
forceUpdateRovingTabIndex: navigation.forceUpdateRovingTabIndex,
} as const) as unknown as TreeBaseState;
}

function useNestedSubtree(props: TreeProps, ref: React.Ref<HTMLElement>): TreeState {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
import type { ComponentProps, ComponentState, DistributiveOmit, Slot } from '@fluentui/react-utilities';
import type { AvatarContextValue, AvatarSize } from '@fluentui/react-avatar';
import { ButtonContextValue } from '@fluentui/react-button';
import { TreeItemLayoutSlots } from '../TreeItemLayout/TreeItemLayout.types';
Expand Down Expand Up @@ -35,3 +35,13 @@ export type TreeItemPersonaLayoutState = ComponentState<TreeItemPersonaLayoutSlo
avatarSize: AvatarSize;
buttonContextValue: ButtonContextValue;
};

/**
* TreeItemPersonaLayout Props without design-only props.
*/
export type TreeItemPersonaLayoutBaseProps = TreeItemPersonaLayoutProps;

/**
* State used in rendering TreeItemPersonaLayout, without design-only state.
*/
export type TreeItemPersonaLayoutBaseState = DistributiveOmit<TreeItemPersonaLayoutState, 'avatarSize'>;
Loading
Loading