diff --git a/change/@fluentui-react-carousel-base-hooks.json b/change/@fluentui-react-carousel-base-hooks.json new file mode 100644 index 00000000000000..ed97610d1210ba --- /dev/null +++ b/change/@fluentui-react-carousel-base-hooks.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add useCarouselBase_unstable, useCarouselNavBase_unstable, and useCarouselNavContainerBase_unstable hooks", + "packageName": "@fluentui/react-carousel", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-table-base-hooks.json b/change/@fluentui-react-table-base-hooks.json new file mode 100644 index 00000000000000..5d9cf95fb26576 --- /dev/null +++ b/change/@fluentui-react-table-base-hooks.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add base hooks for Table, TableCell, TableRow, TableCellLayout, DataGrid, DataGridRow", + "packageName": "@fluentui/react-table", + "email": "dmytrokirpa@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/change/@fluentui-react-tree-base-hooks.json b/change/@fluentui-react-tree-base-hooks.json new file mode 100644 index 00000000000000..9708352d4c4316 --- /dev/null +++ b/change/@fluentui-react-tree-base-hooks.json @@ -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" +} diff --git a/packages/react-components/react-carousel/library/src/Carousel.ts b/packages/react-components/react-carousel/library/src/Carousel.ts index f1005db6a3980a..e9d2abb500dc8e 100644 --- a/packages/react-components/react-carousel/library/src/Carousel.ts +++ b/packages/react-components/react-carousel/library/src/Carousel.ts @@ -1,5 +1,7 @@ export type { CarouselAnnouncerFunction, + CarouselBaseProps, + CarouselBaseState, CarouselMotion, CarouselProps, CarouselSlots, @@ -12,6 +14,7 @@ export { Carousel, carouselClassNames, renderCarousel_unstable, + useCarouselBase_unstable, useCarouselStyles_unstable, useCarousel_unstable, } from './components/Carousel/index'; diff --git a/packages/react-components/react-carousel/library/src/CarouselNav.ts b/packages/react-components/react-carousel/library/src/CarouselNav.ts index d3cbd1c0c90d99..e3f363944efb0b 100644 --- a/packages/react-components/react-carousel/library/src/CarouselNav.ts +++ b/packages/react-components/react-carousel/library/src/CarouselNav.ts @@ -1,4 +1,6 @@ export type { + CarouselNavBaseProps, + CarouselNavBaseState, CarouselNavContextValue, CarouselNavProps, CarouselNavSlots, @@ -9,6 +11,7 @@ export { CarouselNav, carouselNavClassNames, renderCarouselNav_unstable, + useCarouselNavBase_unstable, useCarouselNavStyles_unstable, useCarouselNav_unstable, } from './components/CarouselNav/index'; diff --git a/packages/react-components/react-carousel/library/src/CarouselNavContainer.ts b/packages/react-components/react-carousel/library/src/CarouselNavContainer.ts index 760338c73194bf..b42e88e0b2bdce 100644 --- a/packages/react-components/react-carousel/library/src/CarouselNavContainer.ts +++ b/packages/react-components/react-carousel/library/src/CarouselNavContainer.ts @@ -1,4 +1,6 @@ export type { + CarouselNavContainerBaseProps, + CarouselNavContainerBaseState, CarouselNavContainerProps, CarouselNavContainerSlots, CarouselNavContainerState, @@ -7,6 +9,7 @@ export { CarouselNavContainer, carouselNavContainerClassNames, renderCarouselNavContainer_unstable, + useCarouselNavContainerBase_unstable, useCarouselNavContainerStyles_unstable, useCarouselNavContainer_unstable, } from './components/CarouselNavContainer/index'; diff --git a/packages/react-components/react-carousel/library/src/components/Carousel/Carousel.types.ts b/packages/react-components/react-carousel/library/src/components/Carousel/Carousel.types.ts index 7c513d93d322b9..a39dd941745afc 100644 --- a/packages/react-components/react-carousel/library/src/components/Carousel/Carousel.types.ts +++ b/packages/react-components/react-carousel/library/src/components/Carousel/Carousel.types.ts @@ -1,4 +1,4 @@ -import type { ComponentProps, ComponentState, EventHandler, Slot } from '@fluentui/react-utilities'; +import type { ComponentProps, ComponentState, DistributiveOmit, EventHandler, Slot } from '@fluentui/react-utilities'; import type { CarouselAppearance, CarouselContextValue, CarouselIndexChangeData } from '../CarouselContext.types'; export type CarouselSlots = { @@ -108,6 +108,16 @@ export type CarouselProps = ComponentProps & { */ export type CarouselState = ComponentState & CarouselContextValue; +/** + * Carousel Props without design-only props. + */ +export type CarouselBaseProps = DistributiveOmit; + +/** + * State used in rendering Carousel, without design-only state. + */ +export type CarouselBaseState = DistributiveOmit; + export interface CarouselVisibilityEventDetail { isVisible: boolean; } diff --git a/packages/react-components/react-carousel/library/src/components/Carousel/index.ts b/packages/react-components/react-carousel/library/src/components/Carousel/index.ts index 56347a4734d57a..f02093daafc6d6 100644 --- a/packages/react-components/react-carousel/library/src/components/Carousel/index.ts +++ b/packages/react-components/react-carousel/library/src/components/Carousel/index.ts @@ -1,6 +1,8 @@ export { Carousel } from './Carousel'; export type { CarouselAnnouncerFunction, + CarouselBaseProps, + CarouselBaseState, CarouselMotion, CarouselProps, CarouselSlots, @@ -10,5 +12,5 @@ export type { CarouselVisibilityEventDetail, } from './Carousel.types'; export { renderCarousel_unstable } from './renderCarousel'; -export { useCarousel_unstable } from './useCarousel'; +export { useCarouselBase_unstable, useCarousel_unstable } from './useCarousel'; export { carouselClassNames, useCarouselStyles_unstable } from './useCarouselStyles.styles'; diff --git a/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts b/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts index fd9c435c4ea709..8d7304c12a2fa1 100644 --- a/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts +++ b/packages/react-components/react-carousel/library/src/components/Carousel/useCarousel.ts @@ -10,21 +10,18 @@ import { } from '@fluentui/react-utilities'; import * as React from 'react'; -import type { CarouselProps, CarouselState } from './Carousel.types'; +import type { CarouselBaseProps, CarouselBaseState, CarouselProps, CarouselState } from './Carousel.types'; import type { CarouselContextValue } from '../CarouselContext.types'; import { useEmblaCarousel } from '../useEmblaCarousel'; import { useAnnounce } from '@fluentui/react-shared-contexts'; /** - * Create the state required to render Carousel. - * - * The returned state can be modified with hooks such as useCarouselStyles_unstable, - * before being passed to renderCarousel_unstable. + * Create the base state required to render Carousel, without design-only props. * - * @param props - props from this instance of Carousel + * @param props - props from this instance of Carousel (without appearance) * @param ref - reference to root HTMLDivElement of Carousel */ -export function useCarousel_unstable(props: CarouselProps, ref: React.Ref): CarouselState { +export function useCarouselBase_unstable(props: CarouselBaseProps, ref: React.Ref): CarouselBaseState { 'use no memo'; const { @@ -37,7 +34,6 @@ export function useCarousel_unstable(props: CarouselProps, ref: React.Ref): CarouselState { + 'use no memo'; + + const { appearance = 'flat' } = props; + + return { + ...useCarouselBase_unstable(props, ref), + appearance, + }; +} diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.types.ts b/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.types.ts index 53717fcb2a8d67..d72bcb09818380 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.types.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselNav/CarouselNav.types.ts @@ -1,4 +1,4 @@ -import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { ComponentProps, ComponentState, DistributiveOmit, Slot } from '@fluentui/react-utilities'; import * as React from 'react'; export type CarouselNavSlots = { @@ -33,3 +33,13 @@ export type CarouselNavProps = Omit>, ' } & Partial>; export type CarouselNavContextValue = Pick; + +/** + * CarouselNav Props without design-only props. + */ +export type CarouselNavBaseProps = DistributiveOmit; + +/** + * State used in rendering CarouselNav, without design-only state. + */ +export type CarouselNavBaseState = DistributiveOmit; diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNav/index.ts b/packages/react-components/react-carousel/library/src/components/CarouselNav/index.ts index 65d30987547c40..b1c72d356a1029 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNav/index.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselNav/index.ts @@ -1,5 +1,7 @@ export { CarouselNav } from './CarouselNav'; export type { + CarouselNavBaseProps, + CarouselNavBaseState, CarouselNavContextValue, CarouselNavProps, CarouselNavSlots, @@ -7,5 +9,5 @@ export type { NavButtonRenderFunction, } from './CarouselNav.types'; export { renderCarouselNav_unstable } from './renderCarouselNav'; -export { useCarouselNav_unstable } from './useCarouselNav'; +export { useCarouselNavBase_unstable, useCarouselNav_unstable } from './useCarouselNav'; export { carouselNavClassNames, useCarouselNavStyles_unstable } from './useCarouselNavStyles.styles'; diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNav.ts b/packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNav.ts index 32a6c64f4bc78a..e630155bd4069f 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNav.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselNav/useCarouselNav.ts @@ -5,21 +5,24 @@ import { getIntrinsicElementProps, slot, useIsomorphicLayoutEffect } from '@flue import * as React from 'react'; import { useCarouselContext_unstable as useCarouselContext } from '../CarouselContext'; -import type { CarouselNavProps, CarouselNavState } from './CarouselNav.types'; +import type { + CarouselNavBaseProps, + CarouselNavBaseState, + CarouselNavProps, + CarouselNavState, +} from './CarouselNav.types'; import { useControllableState } from '@fluentui/react-utilities'; /** - * Create the state required to render CarouselNav. - * - * The returned state can be modified with hooks such as useCarouselNavStyles_unstable, - * before being passed to renderCarouselNav_unstable. + * Create the base state required to render CarouselNav, without design-only props. * - * @param props - props from this instance of CarouselNav + * @param props - props from this instance of CarouselNav (without appearance) * @param ref - reference to root HTMLDivElement of CarouselNav */ -export const useCarouselNav_unstable = (props: CarouselNavProps, ref: React.Ref): CarouselNavState => { - const { appearance } = props; - +export const useCarouselNavBase_unstable = ( + props: CarouselNavBaseProps, + ref: React.Ref, +): CarouselNavBaseState => { const focusableGroupAttr = useArrowNavigationGroup({ circular: false, axis: 'horizontal', @@ -44,7 +47,6 @@ export const useCarouselNav_unstable = (props: CarouselNavProps, ref: React.Ref< return { totalSlides, - appearance, renderNavButton: props.children, components: { root: 'div', @@ -61,3 +63,21 @@ export const useCarouselNav_unstable = (props: CarouselNavProps, ref: React.Ref< ), }; }; + +/** + * Create the state required to render CarouselNav. + * + * The returned state can be modified with hooks such as useCarouselNavStyles_unstable, + * before being passed to renderCarouselNav_unstable. + * + * @param props - props from this instance of CarouselNav + * @param ref - reference to root HTMLDivElement of CarouselNav + */ +export const useCarouselNav_unstable = (props: CarouselNavProps, ref: React.Ref): CarouselNavState => { + const { appearance } = props; + + return { + ...useCarouselNavBase_unstable(props, ref), + appearance, + }; +}; diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/CarouselNavContainer.types.ts b/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/CarouselNavContainer.types.ts index dea6429b3998b6..f1bca3293b02e2 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/CarouselNavContainer.types.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/CarouselNavContainer.types.ts @@ -1,4 +1,4 @@ -import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import type { ComponentProps, ComponentState, DistributiveOmit, Slot } from '@fluentui/react-utilities'; import { CarouselAutoplayButton } from '../CarouselAutoplayButton/CarouselAutoplayButton'; import { CarouselButtonProps } from '../CarouselButton/CarouselButton.types'; import { TooltipProps } from '@fluentui/react-tooltip'; @@ -39,3 +39,13 @@ export type CarouselNavContainerProps = ComponentProps & Pick; + +/** + * CarouselNavContainer Props without design-only props. + */ +export type CarouselNavContainerBaseProps = DistributiveOmit; + +/** + * State used in rendering CarouselNavContainer, without design-only state. + */ +export type CarouselNavContainerBaseState = DistributiveOmit; diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/index.ts b/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/index.ts index 70c01028eb1adc..ca73a3e3ea4975 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/index.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/index.ts @@ -1,11 +1,13 @@ export { CarouselNavContainer } from './CarouselNavContainer'; export type { + CarouselNavContainerBaseProps, + CarouselNavContainerBaseState, CarouselNavContainerProps, CarouselNavContainerSlots, CarouselNavContainerState, } from './CarouselNavContainer.types'; export { renderCarouselNavContainer_unstable } from './renderCarouselNavContainer'; -export { useCarouselNavContainer_unstable } from './useCarouselNavContainer'; +export { useCarouselNavContainerBase_unstable, useCarouselNavContainer_unstable } from './useCarouselNavContainer'; export { carouselNavContainerClassNames, useCarouselNavContainerStyles_unstable, diff --git a/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/useCarouselNavContainer.ts b/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/useCarouselNavContainer.ts index 1d58f5cb48dc2d..d2164c5b7ef3b0 100644 --- a/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/useCarouselNavContainer.ts +++ b/packages/react-components/react-carousel/library/src/components/CarouselNavContainer/useCarouselNavContainer.ts @@ -1,24 +1,25 @@ import * as React from 'react'; import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; -import type { CarouselNavContainerProps, CarouselNavContainerState } from './CarouselNavContainer.types'; +import type { + CarouselNavContainerBaseProps, + CarouselNavContainerBaseState, + CarouselNavContainerProps, + CarouselNavContainerState, +} from './CarouselNavContainer.types'; import { CarouselAutoplayButton } from '../CarouselAutoplayButton/CarouselAutoplayButton'; import { CarouselButton } from '../CarouselButton/CarouselButton'; import { Tooltip } from '@fluentui/react-tooltip'; /** - * Create the state required to render CarouselNavContainer. - * - * The returned state can be modified with hooks such as useCarouselNavContainerStyles_unstable, - * before being passed to renderCarouselNavContainer_unstable. + * Create the base state required to render CarouselNavContainer, without design-only props. * - * @param props - props from this instance of CarouselNavContainer + * @param props - props from this instance of CarouselNavContainer (without layout) * @param ref - reference to root HTMLDivElement of CarouselNavContainer */ -export const useCarouselNavContainer_unstable = ( - props: CarouselNavContainerProps, +export const useCarouselNavContainerBase_unstable = ( + props: CarouselNavContainerBaseProps, ref: React.Ref, -): CarouselNavContainerState => { - const { layout } = props; +): CarouselNavContainerBaseState => { const next: CarouselNavContainerState['next'] = slot.optional(props.next, { defaultProps: { navType: 'next', @@ -59,7 +60,6 @@ export const useCarouselNavContainer_unstable = ( }); return { - layout, components: { root: 'div', next: CarouselButton, @@ -84,3 +84,24 @@ export const useCarouselNavContainer_unstable = ( autoplayTooltip, }; }; + +/** + * Create the state required to render CarouselNavContainer. + * + * The returned state can be modified with hooks such as useCarouselNavContainerStyles_unstable, + * before being passed to renderCarouselNavContainer_unstable. + * + * @param props - props from this instance of CarouselNavContainer + * @param ref - reference to root HTMLDivElement of CarouselNavContainer + */ +export const useCarouselNavContainer_unstable = ( + props: CarouselNavContainerProps, + ref: React.Ref, +): CarouselNavContainerState => { + const { layout } = props; + + return { + ...useCarouselNavContainerBase_unstable(props, ref), + layout, + }; +}; diff --git a/packages/react-components/react-carousel/library/src/index.ts b/packages/react-components/react-carousel/library/src/index.ts index 06da5fc8598daf..6ffb83aca71e4c 100644 --- a/packages/react-components/react-carousel/library/src/index.ts +++ b/packages/react-components/react-carousel/library/src/index.ts @@ -6,11 +6,19 @@ export { useCarouselButtonStyles_unstable, useCarouselButton_unstable, } from './CarouselButton'; -export type { CarouselNavProps, CarouselNavSlots, CarouselNavState, NavButtonRenderFunction } from './CarouselNav'; +export type { + CarouselNavBaseProps, + CarouselNavBaseState, + CarouselNavProps, + CarouselNavSlots, + CarouselNavState, + NavButtonRenderFunction, +} from './CarouselNav'; export { CarouselNav, carouselNavClassNames, renderCarouselNav_unstable, + useCarouselNavBase_unstable, useCarouselNavStyles_unstable, useCarouselNav_unstable, } from './CarouselNav'; @@ -22,11 +30,19 @@ export { useCarouselNavButtonStyles_unstable, useCarouselNavButton_unstable, } from './CarouselNavButton'; -export type { CarouselProps, CarouselSlots, CarouselState, CarouselAnnouncerFunction } from './Carousel'; +export type { + CarouselAnnouncerFunction, + CarouselBaseProps, + CarouselBaseState, + CarouselProps, + CarouselSlots, + CarouselState, +} from './Carousel'; export { Carousel, carouselClassNames, renderCarousel_unstable, + useCarouselBase_unstable, useCarouselStyles_unstable, useCarousel_unstable, } from './Carousel'; @@ -71,6 +87,8 @@ export { } from './CarouselSlider'; export type { CarouselSliderProps, CarouselSliderSlots, CarouselSliderState } from './CarouselSlider'; export type { + CarouselNavContainerBaseProps, + CarouselNavContainerBaseState, CarouselNavContainerProps, CarouselNavContainerSlots, CarouselNavContainerState, @@ -79,6 +97,7 @@ export { CarouselNavContainer, carouselNavContainerClassNames, renderCarouselNavContainer_unstable, + useCarouselNavContainerBase_unstable, useCarouselNavContainerStyles_unstable, useCarouselNavContainer_unstable, } from './CarouselNavContainer'; diff --git a/packages/react-components/react-tree/library/src/FlatTree.ts b/packages/react-components/react-tree/library/src/FlatTree.ts index 86e84032cec936..32cf1b6ca41e05 100644 --- a/packages/react-components/react-tree/library/src/FlatTree.ts +++ b/packages/react-components/react-tree/library/src/FlatTree.ts @@ -1,4 +1,6 @@ export type { + FlatTreeBaseProps, + FlatTreeBaseState, FlatTreeContextValues, FlatTreeProps, FlatTreeSlots, @@ -14,6 +16,7 @@ export { renderFlatTree_unstable, useFlatTreeContextValues_unstable, useFlatTreeStyles_unstable, + useFlatTreeBase_unstable, useFlatTree_unstable, useHeadlessFlatTree_unstable, } from './components/FlatTree/index'; diff --git a/packages/react-components/react-tree/library/src/Tree.ts b/packages/react-components/react-tree/library/src/Tree.ts index 6ca6c39fdd94f1..2e3f1aa3202b58 100644 --- a/packages/react-components/react-tree/library/src/Tree.ts +++ b/packages/react-components/react-tree/library/src/Tree.ts @@ -1,4 +1,6 @@ export type { + TreeBaseProps, + TreeBaseState, TreeCheckedChangeData, TreeCheckedChangeEvent, TreeContextValues, @@ -19,5 +21,6 @@ export { treeClassNames, useTreeContextValues_unstable, useTreeStyles_unstable, + useTreeBase_unstable, useTree_unstable, } from './components/Tree/index'; diff --git a/packages/react-components/react-tree/library/src/TreeItemPersonaLayout.ts b/packages/react-components/react-tree/library/src/TreeItemPersonaLayout.ts index 2a095e3e49b420..cbd3fde9c7dcbe 100644 --- a/packages/react-components/react-tree/library/src/TreeItemPersonaLayout.ts +++ b/packages/react-components/react-tree/library/src/TreeItemPersonaLayout.ts @@ -1,4 +1,6 @@ export type { + TreeItemPersonaLayoutBaseProps, + TreeItemPersonaLayoutBaseState, TreeItemPersonaLayoutContextValues, TreeItemPersonaLayoutProps, TreeItemPersonaLayoutSlots, @@ -9,5 +11,6 @@ export { renderTreeItemPersonaLayout_unstable, treeItemPersonaLayoutClassNames, useTreeItemPersonaLayoutStyles_unstable, + useTreeItemPersonaLayoutBase_unstable, useTreeItemPersonaLayout_unstable, } from './components/TreeItemPersonaLayout/index'; diff --git a/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.types.ts b/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.types.ts index 51ca908f5c16a4..721a68c8258653 100644 --- a/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.types.ts +++ b/packages/react-components/react-tree/library/src/components/FlatTree/FlatTree.types.ts @@ -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, @@ -96,3 +96,13 @@ export type FlatTreeState = ComponentState & TreeContextValue & { open: boolean; }; + +/** + * FlatTree Props without design-only props. + */ +export type FlatTreeBaseProps = DistributiveOmit; + +/** + * State used in rendering FlatTree, without design-only state. + */ +export type FlatTreeBaseState = DistributiveOmit; diff --git a/packages/react-components/react-tree/library/src/components/FlatTree/index.ts b/packages/react-components/react-tree/library/src/components/FlatTree/index.ts index 97a53b747a7a85..ad149a9ff2a7c4 100644 --- a/packages/react-components/react-tree/library/src/components/FlatTree/index.ts +++ b/packages/react-components/react-tree/library/src/components/FlatTree/index.ts @@ -1,5 +1,12 @@ 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, @@ -7,7 +14,7 @@ export type { 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'; diff --git a/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTree.ts b/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTree.ts index c6250bfd8a38ca..d02c5d839cd3fc 100644 --- a/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTree.ts +++ b/packages/react-components/react-tree/library/src/components/FlatTree/useFlatTree.ts @@ -2,7 +2,7 @@ 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'; @@ -10,7 +10,13 @@ import { ImmutableSet } from '../../utils/ImmutableSet'; import { ImmutableMap } from '../../utils/ImmutableMap'; import { SubtreeContext } from '../../contexts/subtreeContext'; -export const useFlatTree_unstable: (props: FlatTreeProps, ref: React.Ref) => 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) => FlatTreeBaseState = ( props, ref, ) => { @@ -20,30 +26,51 @@ export const useFlatTree_unstable: (props: FlatTreeProps, ref: React.Ref) => 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): FlatTreeState { +function useRootFlatTreeBase(props: FlatTreeBaseProps, ref: React.Ref): 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): FlatTreeState { diff --git a/packages/react-components/react-tree/library/src/components/Tree/Tree.types.ts b/packages/react-components/react-tree/library/src/components/Tree/Tree.types.ts index cc27b3b0a8493f..6c2564e58849eb 100644 --- a/packages/react-components/react-tree/library/src/components/Tree/Tree.types.ts +++ b/packages/react-components/react-tree/library/src/components/Tree/Tree.types.ts @@ -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'; @@ -176,3 +176,13 @@ export type TreeProps = ComponentProps & { export type TreeState = ComponentState & { open: boolean; } & (TreeContextValue | SubtreeContextValue); + +/** + * Tree Props without design-only props. + */ +export type TreeBaseProps = DistributiveOmit; + +/** + * State used in rendering Tree, without design-only state. + */ +export type TreeBaseState = DistributiveOmit; diff --git a/packages/react-components/react-tree/library/src/components/Tree/index.ts b/packages/react-components/react-tree/library/src/components/Tree/index.ts index 7ae08eee163aac..c17a7516c7619b 100644 --- a/packages/react-components/react-tree/library/src/components/Tree/index.ts +++ b/packages/react-components/react-tree/library/src/components/Tree/index.ts @@ -1,5 +1,7 @@ export { Tree } from './Tree'; export type { + TreeBaseProps, + TreeBaseState, TreeCheckedChangeData, TreeCheckedChangeEvent, TreeContextValues, @@ -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'; diff --git a/packages/react-components/react-tree/library/src/components/Tree/useTree.ts b/packages/react-components/react-tree/library/src/components/Tree/useTree.ts index 006e651ce374b4..b13bf381b33933 100644 --- a/packages/react-components/react-tree/library/src/components/Tree/useTree.ts +++ b/packages/react-components/react-tree/library/src/components/Tree/useTree.ts @@ -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'; @@ -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): 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): 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): 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): 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): 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): TreeState { diff --git a/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/TreeItemPersonaLayout.types.ts b/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/TreeItemPersonaLayout.types.ts index 926bba37b24912..051e4c4372cdc3 100644 --- a/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/TreeItemPersonaLayout.types.ts +++ b/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/TreeItemPersonaLayout.types.ts @@ -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'; @@ -35,3 +35,13 @@ export type TreeItemPersonaLayoutState = ComponentState; diff --git a/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/index.ts b/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/index.ts index 035dfda3a845fe..6bb44fd58c8e66 100644 --- a/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/index.ts +++ b/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/index.ts @@ -1,12 +1,14 @@ export { TreeItemPersonaLayout } from './TreeItemPersonaLayout'; export type { + TreeItemPersonaLayoutBaseProps, + TreeItemPersonaLayoutBaseState, TreeItemPersonaLayoutContextValues, TreeItemPersonaLayoutProps, TreeItemPersonaLayoutSlots, TreeItemPersonaLayoutState, } from './TreeItemPersonaLayout.types'; export { renderTreeItemPersonaLayout_unstable } from './renderTreeItemPersonaLayout'; -export { useTreeItemPersonaLayout_unstable } from './useTreeItemPersonaLayout'; +export { useTreeItemPersonaLayoutBase_unstable, useTreeItemPersonaLayout_unstable } from './useTreeItemPersonaLayout'; export { treeItemPersonaLayoutClassNames, useTreeItemPersonaLayoutStyles_unstable, diff --git a/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/useTreeItemPersonaLayout.ts b/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/useTreeItemPersonaLayout.ts index c116be71f11d99..1010cb7dc2dff2 100644 --- a/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/useTreeItemPersonaLayout.ts +++ b/packages/react-components/react-tree/library/src/components/TreeItemPersonaLayout/useTreeItemPersonaLayout.ts @@ -1,7 +1,12 @@ 'use client'; import * as React from 'react'; -import type { TreeItemPersonaLayoutProps, TreeItemPersonaLayoutState } from './TreeItemPersonaLayout.types'; +import type { + TreeItemPersonaLayoutBaseProps, + TreeItemPersonaLayoutBaseState, + TreeItemPersonaLayoutProps, + TreeItemPersonaLayoutState, +} from './TreeItemPersonaLayout.types'; import { slot } from '@fluentui/react-utilities'; import { useTreeContext_unstable } from '../../contexts'; import { treeAvatarSize } from '../../utils/tokens'; @@ -10,18 +15,15 @@ import { Checkbox, CheckboxProps } from '@fluentui/react-checkbox'; import { Radio, RadioProps } from '@fluentui/react-radio'; /** - * Create the state required to render TreeItemPersonaLayout. - * - * The returned state can be modified with hooks such as useTreeItemPersonaLayoutStyles_unstable, - * before being passed to renderTreeItemPersonaLayout_unstable. + * Create the base state required to render TreeItemPersonaLayout, without design-only props. * * @param props - props from this instance of TreeItemPersonaLayout * @param ref - reference to root HTMLElement of TreeItemPersonaLayout */ -export const useTreeItemPersonaLayout_unstable = ( - props: TreeItemPersonaLayoutProps, +export const useTreeItemPersonaLayoutBase_unstable = ( + props: TreeItemPersonaLayoutBaseProps, ref: React.Ref, -): TreeItemPersonaLayoutState => { +): TreeItemPersonaLayoutBaseState => { const { media, children, main, description } = props; const treeItemLayoutState = useTreeItemLayout_unstable( @@ -33,7 +35,6 @@ export const useTreeItemPersonaLayout_unstable = ( ref, ); - const size = useTreeContext_unstable(ctx => ctx.size); const selectionMode = useTreeContext_unstable(ctx => ctx.selectionMode); return { @@ -49,9 +50,28 @@ export const useTreeItemPersonaLayout_unstable = ( // Casting here to a union between checkbox and radio selector: (selectionMode === 'multiselect' ? Checkbox : Radio) as React.ElementType, }, - avatarSize: treeAvatarSize[size], main: slot.always(main, { defaultProps: { children }, elementType: 'div' }), media: slot.always(media, { elementType: 'div' }), description: slot.optional(description, { elementType: 'div' }), }; }; + +/** + * Create the state required to render TreeItemPersonaLayout. + * + * The returned state can be modified with hooks such as useTreeItemPersonaLayoutStyles_unstable, + * before being passed to renderTreeItemPersonaLayout_unstable. + * + * @param props - props from this instance of TreeItemPersonaLayout + * @param ref - reference to root HTMLElement of TreeItemPersonaLayout + */ +export const useTreeItemPersonaLayout_unstable = ( + props: TreeItemPersonaLayoutProps, + ref: React.Ref, +): TreeItemPersonaLayoutState => { + const size = useTreeContext_unstable(ctx => ctx.size); + return { + ...useTreeItemPersonaLayoutBase_unstable(props, ref), + avatarSize: treeAvatarSize[size], + }; +}; diff --git a/packages/react-components/react-tree/library/src/index.ts b/packages/react-components/react-tree/library/src/index.ts index 114aa2c1627cdc..4230f6f414ae30 100644 --- a/packages/react-components/react-tree/library/src/index.ts +++ b/packages/react-components/react-tree/library/src/index.ts @@ -1,6 +1,7 @@ export { Tree, treeClassNames, + useTreeBase_unstable, useTree_unstable, useTreeStyles_unstable, useTreeContextValues_unstable, @@ -8,6 +9,8 @@ export { } from './Tree'; export type { + TreeBaseProps, + TreeBaseState, TreeSlots, TreeProps, TreeState, @@ -26,13 +29,14 @@ export type { export { FlatTree, flatTreeClassNames, + useFlatTreeBase_unstable, useFlatTree_unstable, useFlatTreeStyles_unstable, useFlatTreeContextValues_unstable, renderFlatTree_unstable, } from './FlatTree'; -export type { FlatTreeSlots, FlatTreeProps, FlatTreeState } from './FlatTree'; +export type { FlatTreeBaseProps, FlatTreeBaseState, FlatTreeSlots, FlatTreeProps, FlatTreeState } from './FlatTree'; export { TreeProvider, TreeRootReset } from './components/TreeProvider'; @@ -90,9 +94,12 @@ export { treeItemPersonaLayoutClassNames, renderTreeItemPersonaLayout_unstable, useTreeItemPersonaLayoutStyles_unstable, + useTreeItemPersonaLayoutBase_unstable, useTreeItemPersonaLayout_unstable, } from './TreeItemPersonaLayout'; export type { + TreeItemPersonaLayoutBaseProps, + TreeItemPersonaLayoutBaseState, TreeItemPersonaLayoutProps, TreeItemPersonaLayoutSlots, TreeItemPersonaLayoutState,