Skip to content

Commit

Permalink
feat(react-tree): creates SubtreeContext to avoid spreading root cont…
Browse files Browse the repository at this point in the history
…ext (#29194)

* feat: creates SubtreeContext

* chore: review comments
  • Loading branch information
bsunderhus authored Sep 29, 2023
1 parent bfa0ec8 commit b7c4075
Show file tree
Hide file tree
Showing 22 changed files with 252 additions and 91 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: export SubtreeContext from @fluentui/react-tree",
"packageName": "@fluentui/react-components",
"email": "bernardo.sunderhus@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: creates SubtreeContext",
"packageName": "@fluentui/react-tree",
"email": "bernardo.sunderhus@gmail.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ import { Subtitle2 } from '@fluentui/react-text';
import { subtitle2ClassNames } from '@fluentui/react-text';
import { Subtitle2Stronger } from '@fluentui/react-text';
import { subtitle2StrongerClassNames } from '@fluentui/react-text';
import { SubtreeContextValue } from '@fluentui/react-tree';
import { Switch } from '@fluentui/react-switch';
import { switchClassNames } from '@fluentui/react-switch';
import { SwitchOnChangeData } from '@fluentui/react-switch';
Expand Down Expand Up @@ -1108,6 +1109,7 @@ import { useSpinner_unstable } from '@fluentui/react-spinner';
import { useSpinnerStyles_unstable } from '@fluentui/react-spinner';
import { useSplitButton_unstable } from '@fluentui/react-button';
import { useSplitButtonStyles_unstable } from '@fluentui/react-button';
import { useSubtreeContext_unstable } from '@fluentui/react-tree';
import { useSwitch_unstable } from '@fluentui/react-switch';
import { useSwitchStyles_unstable } from '@fluentui/react-switch';
import { useTab_unstable } from '@fluentui/react-tabs';
Expand Down Expand Up @@ -2557,6 +2559,8 @@ export { Subtitle2Stronger }

export { subtitle2StrongerClassNames }

export { SubtreeContextValue }

export { Switch }

export { switchClassNames }
Expand Down Expand Up @@ -3403,6 +3407,8 @@ export { useSplitButton_unstable }

export { useSplitButtonStyles_unstable }

export { useSubtreeContext_unstable }

export { useSwitch_unstable }

export { useSwitchStyles_unstable }
Expand Down
2 changes: 2 additions & 0 deletions packages/react-components/react-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1254,6 +1254,7 @@ export {
useHeadlessFlatTree_unstable,
useTreeContextValues_unstable,
useTreeContext_unstable,
useSubtreeContext_unstable,
useTreeItemContextValues_unstable,
useTreeItemContext_unstable,
useTreeItemLayoutStyles_unstable,
Expand All @@ -1279,6 +1280,7 @@ export type {
TreeCheckedChangeData,
TreeCheckedChangeEvent,
TreeContextValue,
SubtreeContextValue,
TreeContextValues,
TreeItemContextValue,
TreeItemLayoutProps,
Expand Down
50 changes: 32 additions & 18 deletions packages/react-components/react-tree/etc/react-tree.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,8 @@ import { ContextSelector } from '@fluentui/react-context-selector';
import type { End } from '@fluentui/keyboard-keys';
import type { Enter } from '@fluentui/keyboard-keys';
import type { ExtractSlotProps } from '@fluentui/react-utilities';
import { FC } from 'react';
import { ForwardRefComponent } from '@fluentui/react-utilities';
import type { Home } from '@fluentui/keyboard-keys';
import { Provider } from 'react';
import { ProviderProps } from 'react';
import { Radio } from '@fluentui/react-radio';
import { RadioProps } from '@fluentui/react-radio';
import * as React_2 from 'react';
Expand All @@ -46,7 +43,7 @@ export type FlattenTreeItem<Props extends TreeItemProps> = Omit<Props, 'subtree'
export const FlatTree: ForwardRefComponent<FlatTreeProps>;

// @public (undocumented)
export const flatTreeClassNames: SlotClassNames<TreeSlots>;
export const flatTreeClassNames: SlotClassNames<FlatTreeSlots>;

// @public
export const FlatTreeItem: ForwardRefComponent<FlatTreeItemProps>;
Expand All @@ -71,6 +68,14 @@ export type FlatTreeProps = ComponentProps<TreeSlots> & {
onCheckedChange?(event: TreeCheckedChangeEvent, data: TreeCheckedChangeData): void;
};

// @public (undocumented)
export type FlatTreeSlots = TreeSlots;

// @public (undocumented)
export type FlatTreeState = ComponentState<FlatTreeSlots> & TreeContextValue & {
open: boolean;
};

// @public
export type HeadlessFlatTree<Props extends HeadlessFlatTreeItemProps> = {
getTreeProps(): Required<Pick<FlatTreeProps, 'openItems' | 'onOpenChange' | 'onNavigation' | 'checkedItems' | 'onCheckedChange'>> & {
Expand All @@ -95,7 +100,7 @@ export type HeadlessFlatTreeOptions = Pick<FlatTreeProps, 'onOpenChange' | 'onNa
};

// @public (undocumented)
export const renderFlatTree_unstable: (state: TreeState, contextValues: TreeContextValues) => JSX.Element;
export const renderFlatTree_unstable: (state: FlatTreeState, contextValues: FlatTreeContextValues) => JSX.Element;

// @public (undocumented)
export const renderTree_unstable: (state: TreeState, contextValues: TreeContextValues) => JSX.Element;
Expand All @@ -109,6 +114,12 @@ export const renderTreeItemLayout_unstable: (state: TreeItemLayoutState) => JSX.
// @public
export const renderTreeItemPersonaLayout_unstable: (state: TreeItemPersonaLayoutState, contextValues: TreeItemPersonaLayoutContextValues) => JSX.Element;

// @public (undocumented)
export type SubtreeContextValue = {
contextType: 'subtree';
level: number;
};

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

Expand All @@ -135,8 +146,9 @@ export const treeClassNames: SlotClassNames<TreeSlots>;

// @public (undocumented)
export type TreeContextValue = {
treeType: 'nested' | 'flat';
contextType?: 'root';
level: number;
treeType: 'nested' | 'flat';
selectionMode: 'none' | SelectionMode_2;
appearance: 'subtle' | 'subtle-alpha' | 'transparent';
size: 'small' | 'medium';
Expand All @@ -147,7 +159,7 @@ export type TreeContextValue = {

// @public (undocumented)
export type TreeContextValues = {
tree: TreeContextValue;
tree: TreeContextValue | SubtreeContextValue;
};

// @public
Expand Down Expand Up @@ -352,37 +364,39 @@ export type TreeProps = ComponentProps<TreeSlots> & {
};

// @public (undocumented)
export const TreeProvider: Provider<TreeContextValue | undefined> & FC<ProviderProps<TreeContextValue | undefined>>;
export const TreeProvider: {
(props: React_2.ProviderProps<TreeContextValue | SubtreeContextValue>): JSX.Element;
displayName: string;
};

// @public (undocumented)
export type TreeSelectionValue = MultiSelectValue | SingleSelectValue;

// @public (undocumented)
type TreeSlots = {
export type TreeSlots = {
root: Slot<'div'>;
};
export { TreeSlots as FlatTreeSlots }
export { TreeSlots }

// @public
type TreeState = ComponentState<TreeSlots> & TreeContextValue & {
export type TreeState = ComponentState<TreeSlots> & {
open: boolean;
};
export { TreeState as FlatTreeState }
export { TreeState }
} & (TreeContextValue | SubtreeContextValue);

// @public (undocumented)
export const useFlatTree_unstable: (props: FlatTreeProps, ref: React_2.Ref<HTMLElement>) => TreeState;
export const useFlatTree_unstable: (props: FlatTreeProps, ref: React_2.Ref<HTMLElement>) => FlatTreeState;

// @public (undocumented)
export const useFlatTreeContextValues_unstable: (state: TreeState) => TreeContextValues;
export const useFlatTreeContextValues_unstable: (state: FlatTreeState) => FlatTreeContextValues;

// @public (undocumented)
export const useFlatTreeStyles_unstable: (state: TreeState) => TreeState;
export const useFlatTreeStyles_unstable: (state: FlatTreeState) => FlatTreeState;

// @public
export function useHeadlessFlatTree_unstable<Props extends HeadlessTreeItemProps>(props: Props[], options?: HeadlessFlatTreeOptions): HeadlessFlatTree<Props>;

// @public (undocumented)
export const useSubtreeContext_unstable: () => SubtreeContextValue;

// @public (undocumented)
export const useTree_unstable: (props: TreeProps, ref: React_2.Ref<HTMLElement>) => TreeState;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { ComponentProps, SelectionMode } from '@fluentui/react-utilities';
import type { ComponentProps, ComponentState, SelectionMode } from '@fluentui/react-utilities';
import type {
TreeSlots,
TreeState,
TreeCheckedChangeData,
TreeCheckedChangeEvent,
TreeNavigationData_unstable,
Expand All @@ -11,8 +10,13 @@ import type {
TreeSelectionValue,
} from '../Tree/index';
import type { TreeItemValue } from '../TreeItem/index';
import type { TreeContextValue } from '../../contexts';

export { TreeSlots as FlatTreeSlots, TreeState as FlatTreeState };
export type FlatTreeSlots = TreeSlots;

export type FlatTreeContextValues = {
tree: TreeContextValue;
};

export type FlatTreeProps = ComponentProps<TreeSlots> & {
/**
Expand Down Expand Up @@ -80,3 +84,8 @@ export type FlatTreeProps = ComponentProps<TreeSlots> & {
*/
onCheckedChange?(event: TreeCheckedChangeEvent, data: TreeCheckedChangeData): void;
};

export type FlatTreeState = ComponentState<FlatTreeSlots> &
TreeContextValue & {
open: boolean;
};
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TreeContextValues, renderTree_unstable } from '../../Tree';
import type { FlatTreeState } from './FlatTree.types';
import { renderTree_unstable } from '../../Tree';
import type { FlatTreeContextValues, FlatTreeState } from './FlatTree.types';

export const renderFlatTree_unstable: (state: FlatTreeState, contextValues: TreeContextValues) => JSX.Element =
export const renderFlatTree_unstable: (state: FlatTreeState, contextValues: FlatTreeContextValues) => JSX.Element =
renderTree_unstable;
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import * as React from 'react';
import { useRootTree } from '../../hooks/useRootTree';
import { FlatTreeProps, FlatTreeSlots, FlatTreeState } from './FlatTree.types';
import { FlatTreeProps, FlatTreeState } from './FlatTree.types';
import { useFlatTreeNavigation } from './useFlatTreeNavigation';
import { HTMLElementWalker, createHTMLElementWalker } from '../../utils/createHTMLElementWalker';
import { useFluent_unstable } from '@fluentui/react-shared-contexts';
import { treeItemFilter } from '../../utils/treeItemFilter';
import { ExtractSlotProps, slot, useEventCallback, useMergedRefs } from '@fluentui/react-utilities';
import { slot, useEventCallback, useMergedRefs } from '@fluentui/react-utilities';
import type { TreeNavigationData_unstable, TreeNavigationEvent_unstable } from '../Tree/Tree.types';
import { useTreeContext_unstable } from '../../contexts/treeContext';
import { useSubtree } from '../../hooks/useSubtree';
import { ImmutableSet } from '../../utils/ImmutableSet';
import { ImmutableMap } from '../../utils/ImmutableMap';

export const useFlatTree_unstable: (props: FlatTreeProps, ref: React.Ref<HTMLElement>) => FlatTreeState = (
props,
Expand Down Expand Up @@ -62,9 +64,23 @@ function useSubFlatTree(props: FlatTreeProps, ref: React.Ref<HTMLElement>): Flat
}
return {
...useSubtree(props, ref),
// ------ defaultTreeContextValue
level: 0,
contextType: 'root',
treeType: 'nested',
selectionMode: 'none',
openItems: ImmutableSet.empty,
checkedItems: ImmutableMap.empty,
requestTreeResponse: noop,
appearance: 'subtle',
size: 'medium',
// ------ defaultTreeContextValue
open: false,
treeType: 'flat',
components: { root: React.Fragment },
root: slot.always<ExtractSlotProps<FlatTreeSlots['root']>>(undefined, { elementType: React.Fragment }),
root: slot.always(props, { elementType: React.Fragment }),
};
}

function noop() {
/* do nothing */
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
import { TreeContextValues, useTreeContextValues_unstable } from '../../Tree';
import type { FlatTreeState } from './FlatTree.types';
import type { TreeContextValue } from '../../contexts';
import type { FlatTreeContextValues, FlatTreeState } from './FlatTree.types';

export const useFlatTreeContextValues_unstable: (state: FlatTreeState) => TreeContextValues =
useTreeContextValues_unstable;
export const useFlatTreeContextValues_unstable = (state: FlatTreeState): FlatTreeContextValues => {
const {
openItems,
level,
contextType,
treeType,
checkedItems,
selectionMode,
appearance,
size,
requestTreeResponse,
} = state;
/**
* This context is created with "@fluentui/react-context-selector",
* there is no sense to memoize it
*/
const tree: TreeContextValue = {
treeType,
size,
openItems,
appearance,
checkedItems,
selectionMode,
contextType,
level,
requestTreeResponse,
};

return { tree };
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type * as React from 'react';
import type { ComponentProps, ComponentState, SelectionMode, Slot } from '@fluentui/react-utilities';
import type { TreeContextValue } from '../../contexts';
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';
import { CheckboxProps } from '@fluentui/react-checkbox';
Expand Down Expand Up @@ -68,7 +68,7 @@ export type TreeCheckedChangeData = {
export type TreeCheckedChangeEvent = TreeCheckedChangeData['event'];

export type TreeContextValues = {
tree: TreeContextValue;
tree: TreeContextValue | SubtreeContextValue;
};

export type TreeProps = ComponentProps<TreeSlots> & {
Expand Down Expand Up @@ -146,7 +146,6 @@ export type TreeProps = ComponentProps<TreeSlots> & {
/**
* State used in rendering Tree
*/
export type TreeState = ComponentState<TreeSlots> &
TreeContextValue & {
open: boolean;
};
export type TreeState = ComponentState<TreeSlots> & {
open: boolean;
} & (TreeContextValue | SubtreeContextValue);
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/** @jsxRuntime automatic */
/** @jsxImportSource @fluentui/react-jsx-runtime */
import { assertSlots } from '@fluentui/react-utilities';
import { TreeProvider } from '../../contexts';
import type { TreeContextValues, TreeSlots, TreeState } from '../Tree/Tree.types';
import { TreeProvider } from '../TreeProvider';

export const renderTree_unstable = (state: TreeState, contextValues: TreeContextValues) => {
assertSlots<TreeSlots>(state);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
} from './Tree.types';
import { createNextOpenItems, useControllableOpenItems } from '../../hooks/useControllableOpenItems';
import { createNextNestedCheckedItems, useNestedCheckedItems } from './useNestedControllableCheckedItems';
import { useTreeContext_unstable } from '../../contexts/treeContext';
import { useSubtreeContext_unstable } from '../../contexts/subtreeContext';
import { useRootTree } from '../../hooks/useRootTree';
import { useSubtree } from '../../hooks/useSubtree';
import { HTMLElementWalker, createHTMLElementWalker } from '../../utils/createHTMLElementWalker';
Expand All @@ -21,11 +21,11 @@ import { useTreeNavigation } from './useTreeNavigation';
import { useFluent_unstable } from '@fluentui/react-shared-contexts';

export const useTree_unstable = (props: TreeProps, ref: React.Ref<HTMLElement>): TreeState => {
const isSubtree = useTreeContext_unstable(ctx => ctx.level > 0);
// as isSubTree is static, this doesn't break rule of hooks
const { level } = useSubtreeContext_unstable();
// 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 isSubtree ? { ...useSubtree(props, ref), treeType: 'nested' } : useNestedRootTree(props, ref);
return level > 0 ? useSubtree(props, ref) : useNestedRootTree(props, ref);
};

function useNestedRootTree(props: TreeProps, ref: React.Ref<HTMLElement>): TreeState {
Expand Down
Loading

0 comments on commit b7c4075

Please sign in to comment.