diff --git a/change/@fluentui-react-accordion-67ff3ccc-c9c6-47c5-9a09-68e6d9bfe110.json b/change/@fluentui-react-accordion-67ff3ccc-c9c6-47c5-9a09-68e6d9bfe110.json new file mode 100644 index 00000000000000..0ce2e69d7466cc --- /dev/null +++ b/change/@fluentui-react-accordion-67ff3ccc-c9c6-47c5-9a09-68e6d9bfe110.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: add collapse motion to Accordion", + "packageName": "@fluentui/react-accordion", + "email": "olkatruk@microsoft.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-accordion/library/etc/react-accordion.api.md b/packages/react-components/react-accordion/library/etc/react-accordion.api.md index 03feba29d36042..af9164de7a81a9 100644 --- a/packages/react-components/react-accordion/library/etc/react-accordion.api.md +++ b/packages/react-components/react-accordion/library/etc/react-accordion.api.md @@ -12,6 +12,7 @@ import type { ComponentState } from '@fluentui/react-utilities'; import { ContextSelector } from '@fluentui/react-context-selector'; import { FC } from 'react'; import type { ForwardRefComponent } from '@fluentui/react-utilities'; +import type { PresenceMotionSlotProps } from '@fluentui/react-motion'; import { Provider } from 'react'; import { ProviderProps } from 'react'; import * as React_2 from 'react'; @@ -130,7 +131,7 @@ export type AccordionItemValue = unknown; export const AccordionPanel: ForwardRefComponent; // @public (undocumented) -export const accordionPanelClassNames: SlotClassNames; +export const accordionPanelClassNames: SlotClassNames>; // @public (undocumented) export type AccordionPanelProps = ComponentProps; @@ -138,6 +139,7 @@ export type AccordionPanelProps = ComponentProps; // @public (undocumented) export type AccordionPanelSlots = { root: NonNullable>; + collapseMotion?: Slot; }; // @public (undocumented) @@ -188,7 +190,7 @@ export const renderAccordionHeader_unstable: (state: AccordionHeaderState, conte export const renderAccordionItem_unstable: (state: AccordionItemState, contextValues: AccordionItemContextValues) => JSX.Element; // @public -export const renderAccordionPanel_unstable: (state: AccordionPanelState) => JSX.Element | null; +export const renderAccordionPanel_unstable: (state: AccordionPanelState) => JSX.Element; // @public export const useAccordion_unstable: (props: AccordionProps, ref: React_2.Ref) => AccordionState; diff --git a/packages/react-components/react-accordion/library/package.json b/packages/react-components/react-accordion/library/package.json index 12776edfeeb821..d7121873c43989 100644 --- a/packages/react-components/react-accordion/library/package.json +++ b/packages/react-components/react-accordion/library/package.json @@ -37,6 +37,8 @@ "@fluentui/react-icons": "^2.0.245", "@fluentui/react-jsx-runtime": "^9.0.42", "@fluentui/react-shared-contexts": "^9.20.0", + "@fluentui/react-motion": "^9.3.0", + "@fluentui/react-motion-components-preview": "^0.1.1", "@fluentui/react-tabster": "^9.22.3", "@fluentui/react-theme": "^9.1.19", "@fluentui/react-utilities": "^9.18.13", diff --git a/packages/react-components/react-accordion/library/src/components/AccordionHeader/__snapshots__/AccordionHeader.test.tsx.snap b/packages/react-components/react-accordion/library/src/components/AccordionHeader/__snapshots__/AccordionHeader.test.tsx.snap index 316d8b894488ae..74e5a90ba34914 100644 --- a/packages/react-components/react-accordion/library/src/components/AccordionHeader/__snapshots__/AccordionHeader.test.tsx.snap +++ b/packages/react-components/react-accordion/library/src/components/AccordionHeader/__snapshots__/AccordionHeader.test.tsx.snap @@ -23,6 +23,7 @@ exports[`AccordionHeader renders a default state 1`] = ` style={ Object { "transform": "rotate(0deg)", + "transition": "transform 200ms ease-out", } } viewBox="0 0 20 20" diff --git a/packages/react-components/react-accordion/library/src/components/AccordionHeader/useAccordionHeader.tsx b/packages/react-components/react-accordion/library/src/components/AccordionHeader/useAccordionHeader.tsx index 2ef909071f1949..47da69cbce4fad 100644 --- a/packages/react-components/react-accordion/library/src/components/AccordionHeader/useAccordionHeader.tsx +++ b/packages/react-components/react-accordion/library/src/components/AccordionHeader/useAccordionHeader.tsx @@ -6,6 +6,7 @@ import { useAccordionContext_unstable } from '../../contexts/accordion'; import { ChevronRightRegular } from '@fluentui/react-icons'; import { useFluent_unstable as useFluent } from '@fluentui/react-shared-contexts'; import { useAccordionItemContext_unstable } from '../../contexts/accordionItem'; +import { motionTokens } from '@fluentui/react-motion'; /** * Returns the props and state required to render the component @@ -83,7 +84,14 @@ export const useAccordionHeader_unstable = ( expandIcon: slot.optional(expandIcon, { renderByDefault: true, defaultProps: { - children: , + children: ( + + ), 'aria-hidden': true, }, elementType: 'span', diff --git a/packages/react-components/react-accordion/library/src/components/AccordionPanel/AccordionPanel.types.ts b/packages/react-components/react-accordion/library/src/components/AccordionPanel/AccordionPanel.types.ts index b6d7da835e018d..aaeb8a48057716 100644 --- a/packages/react-components/react-accordion/library/src/components/AccordionPanel/AccordionPanel.types.ts +++ b/packages/react-components/react-accordion/library/src/components/AccordionPanel/AccordionPanel.types.ts @@ -1,7 +1,9 @@ +import type { PresenceMotionSlotProps } from '@fluentui/react-motion'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; export type AccordionPanelSlots = { root: NonNullable>; + collapseMotion?: Slot; }; export type AccordionPanelProps = ComponentProps; diff --git a/packages/react-components/react-accordion/library/src/components/AccordionPanel/renderAccordionPanel.tsx b/packages/react-components/react-accordion/library/src/components/AccordionPanel/renderAccordionPanel.tsx index b0018c7048dc20..b8be7ba675d5db 100644 --- a/packages/react-components/react-accordion/library/src/components/AccordionPanel/renderAccordionPanel.tsx +++ b/packages/react-components/react-accordion/library/src/components/AccordionPanel/renderAccordionPanel.tsx @@ -9,5 +9,11 @@ import type { AccordionPanelState, AccordionPanelSlots } from './AccordionPanel. */ export const renderAccordionPanel_unstable = (state: AccordionPanelState) => { assertSlots(state); - return state.open ? {state.root.children} : null; + return state.collapseMotion ? ( + + + + ) : ( + + ); }; diff --git a/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts b/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts index 12bbd7377a061c..808a7f62ec14f1 100644 --- a/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts +++ b/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanel.ts @@ -1,6 +1,8 @@ import * as React from 'react'; import { getIntrinsicElementProps, slot } from '@fluentui/react-utilities'; import { useTabsterAttributes } from '@fluentui/react-tabster'; +import { presenceMotionSlot, type PresenceMotionSlotProps } from '@fluentui/react-motion'; +import { Collapse } from '@fluentui/react-motion-components-preview'; import { useAccordionContext_unstable } from '../../contexts/accordion'; import type { AccordionPanelProps, AccordionPanelState } from './AccordionPanel.types'; import { useAccordionItemContext_unstable } from '../../contexts/accordionItem'; @@ -22,6 +24,11 @@ export const useAccordionPanel_unstable = ( open, components: { root: 'div', + // TODO: remove once React v18 slot API is modified + // This is a problem at the moment due to UnknownSlotProps assumption + // that `children` property is `ReactNode`, which in this case is not valid + // as PresenceComponentProps['children'] is `ReactElement` + collapseMotion: Collapse as React.FC, }, root: slot.always( getIntrinsicElementProps('div', { @@ -34,5 +41,12 @@ export const useAccordionPanel_unstable = ( }), { elementType: 'div' }, ), + collapseMotion: presenceMotionSlot(props.collapseMotion, { + elementType: Collapse, + defaultProps: { + visible: open, + unmountOnExit: true, + }, + }), }; }; diff --git a/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanelStyles.styles.ts b/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanelStyles.styles.ts index 6face280965aa1..65948e2d6ccb3e 100644 --- a/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanelStyles.styles.ts +++ b/packages/react-components/react-accordion/library/src/components/AccordionPanel/useAccordionPanelStyles.styles.ts @@ -4,7 +4,7 @@ import { tokens } from '@fluentui/react-theme'; import type { AccordionPanelSlots, AccordionPanelState } from './AccordionPanel.types'; -export const accordionPanelClassNames: SlotClassNames = { +export const accordionPanelClassNames: SlotClassNames> = { root: 'fui-AccordionPanel', };