From 08b6e61fdffada1813ed97c29ebb03a279af7e7d Mon Sep 17 00:00:00 2001 From: Alexander Katrukhin Date: Tue, 23 Jul 2024 17:57:43 -0400 Subject: [PATCH 1/2] feat: add collapse motion for Accordion - Updated dependencies in `package.json` - Enhanced `useAccordionHeader.tsx` for collapse motion - Added `AccordionMotion.ts` - Updated types and rendering logic in Accordion components - Styled `useAccordionPanelStyles.styles.ts` for animations - Created stories custom motion Accordion --- ...dion-67ff3ccc-c9c6-47c5-9a09-68e6d9bfe110.json | 7 +++++++ .../library/etc/react-accordion.api.md | 6 ++++-- .../react-accordion/library/package.json | 2 ++ .../__snapshots__/AccordionHeader.test.tsx.snap | 1 + .../AccordionHeader/useAccordionHeader.tsx | 10 +++++++++- .../AccordionPanel/AccordionPanel.types.ts | 2 ++ .../AccordionPanel/renderAccordionPanel.tsx | 8 +++++++- .../AccordionPanel/useAccordionPanel.ts | 15 ++++++++++++++- .../useAccordionPanelStyles.styles.ts | 2 +- 9 files changed, 47 insertions(+), 6 deletions(-) create mode 100644 change/@fluentui-react-accordion-67ff3ccc-c9c6-47c5-9a09-68e6d9bfe110.json 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 0000000000000..0ce2e69d7466c --- /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 03feba29d3604..af9164de7a81a 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 12776edfeeb82..d7121873c4398 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 316d8b894488a..74e5a90ba3491 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 2ef909071f194..47da69cbce4fa 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 b6d7da835e018..aaeb8a4805771 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 b0018c7048dc2..b8be7ba675d5d 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 12bbd7377a061..adbe7178ecd51 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,17 +24,28 @@ 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', { // FIXME: // `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement` // but since it would be a breaking change to fix it, we are casting ref to it's proper type - ref: ref as React.Ref, ...props, ...(navigation && focusableProps), }), { 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 6face280965aa..65948e2d6ccb3 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', }; From aac32549e99a4cd86d6fd685a7952cd7db0e526a Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Mon, 29 Jul 2024 12:08:28 +0200 Subject: [PATCH 2/2] restore ref --- .../library/src/components/AccordionPanel/useAccordionPanel.ts | 1 + 1 file changed, 1 insertion(+) 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 adbe7178ecd51..808a7f62ec14f 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 @@ -35,6 +35,7 @@ export const useAccordionPanel_unstable = ( // FIXME: // `ref` is wrongly assigned to be `HTMLElement` instead of `HTMLDivElement` // but since it would be a breaking change to fix it, we are casting ref to it's proper type + ref: ref as React.Ref, ...props, ...(navigation && focusableProps), }),