From dfbb46386703919de1e7960a6b65de8bc9da009a Mon Sep 17 00:00:00 2001 From: Bernardo Sunderhus Date: Fri, 28 Jul 2023 09:20:23 +0000 Subject: [PATCH] bugfix: ensures controlled state works properly + makes AccordionItemValue generic + adds story for controlled state + simplifies access to requestToggle by context + reorganizes context into a separate folder --- ...-84a02e56-cdf6-4b86-9cf8-bc28925b6316.json | 7 ++ .../etc/react-accordion.api.md | 72 ++++++++++--------- .../components/Accordion/Accordion.test.tsx | 2 +- .../src/components/Accordion/Accordion.tsx | 7 +- .../components/Accordion/Accordion.types.ts | 34 ++++----- .../components/Accordion/AccordionContext.ts | 19 ----- .../src/components/Accordion/index.ts | 1 - .../components/Accordion/renderAccordion.tsx | 4 +- .../components/Accordion/useAccordion.test.ts | 20 +++--- .../src/components/Accordion/useAccordion.ts | 30 ++++---- .../Accordion/useAccordionContextValues.ts | 6 +- .../AccordionHeader/AccordionHeader.types.ts | 6 +- .../AccordionHeader/AccordionHeaderContext.ts | 16 ----- .../src/components/AccordionHeader/index.ts | 1 - .../AccordionHeader/renderAccordionHeader.tsx | 6 +- .../useAccordionHeader.test.tsx | 18 ++--- .../AccordionHeader/useAccordionHeader.tsx | 15 ++-- .../useAccordionHeaderContextValues.ts | 7 +- .../AccordionItem/AccordionItem.types.ts | 18 ++--- .../AccordionItem/AccordionItemContext.ts | 19 ----- .../src/components/AccordionItem/index.ts | 1 - .../AccordionItem/renderAccordionItem.tsx | 6 +- .../AccordionItem/useAccordionItem.ts | 15 +--- .../useAccordionItemContextValues.test.ts | 2 +- .../useAccordionItemContextValues.ts | 9 +-- .../AccordionPanel/AccordionPanel.test.tsx | 6 +- .../AccordionPanel/useAccordionPanel.ts | 4 +- .../react-accordion/src/contexts/accordion.ts | 40 +++++++++++ .../src/contexts/accordionHeader.ts | 28 ++++++++ .../src/contexts/accordionItem.ts | 23 ++++++ .../react-accordion/src/index.ts | 21 +++--- .../Accordion/AccordionControlled.stories.tsx | 46 ++++++++++++ .../stories/Accordion/index.stories.tsx | 1 + 33 files changed, 291 insertions(+), 219 deletions(-) create mode 100644 change/@fluentui-react-accordion-84a02e56-cdf6-4b86-9cf8-bc28925b6316.json delete mode 100644 packages/react-components/react-accordion/src/components/Accordion/AccordionContext.ts delete mode 100644 packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeaderContext.ts delete mode 100644 packages/react-components/react-accordion/src/components/AccordionItem/AccordionItemContext.ts create mode 100644 packages/react-components/react-accordion/src/contexts/accordion.ts create mode 100644 packages/react-components/react-accordion/src/contexts/accordionHeader.ts create mode 100644 packages/react-components/react-accordion/src/contexts/accordionItem.ts create mode 100644 packages/react-components/react-accordion/stories/Accordion/AccordionControlled.stories.tsx diff --git a/change/@fluentui-react-accordion-84a02e56-cdf6-4b86-9cf8-bc28925b6316.json b/change/@fluentui-react-accordion-84a02e56-cdf6-4b86-9cf8-bc28925b6316.json new file mode 100644 index 0000000000000..dd01d0e2847b1 --- /dev/null +++ b/change/@fluentui-react-accordion-84a02e56-cdf6-4b86-9cf8-bc28925b6316.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "feat: make AccordionItemValue generic", + "packageName": "@fluentui/react-accordion", + "email": "bernardo.sunderhus@gmail.com", + "dependentChangeType": "patch" +} diff --git a/packages/react-components/react-accordion/etc/react-accordion.api.md b/packages/react-components/react-accordion/etc/react-accordion.api.md index 712c9f5a97696..9b6a507f62919 100644 --- a/packages/react-components/react-accordion/etc/react-accordion.api.md +++ b/packages/react-components/react-accordion/etc/react-accordion.api.md @@ -19,15 +19,18 @@ import type { Slot } from '@fluentui/react-utilities'; import type { SlotClassNames } from '@fluentui/react-utilities'; // @public -export const Accordion: ForwardRefComponent; +export const Accordion: ForwardRefComponent & ((props: AccordionProps) => JSX.Element); // @public (undocumented) export const accordionClassNames: SlotClassNames; // @public (undocumented) -export type AccordionContextValue = Required> & Pick & { +export type AccordionContextValue = { openItems: AccordionItemValue[]; - requestToggle: (event: AccordionToggleEvent, data: AccordionToggleData) => void; + requestToggle: (data: AccordionRequestToggleData) => void; + collapsible: boolean; + multiple: boolean; + navigation: 'linear' | 'circular' | undefined; }; // @public (undocumented) @@ -42,12 +45,11 @@ export const AccordionHeader: ForwardRefComponent; export const accordionHeaderClassNames: SlotClassNames; // @public (undocumented) -export const AccordionHeaderContextProvider: React_2.Provider; - -// @public (undocumented) -export type AccordionHeaderContextValue = Required> & { +export type AccordionHeaderContextValue = { disabled: boolean; open: boolean; + expandIconPosition: AccordionHeaderExpandIconPosition; + size: AccordionHeaderSize; }; // @public (undocumented) @@ -65,6 +67,9 @@ export type AccordionHeaderProps = ComponentProps> size?: AccordionHeaderSize; }; +// @public (undocumented) +export const AccordionHeaderProvider: React_2.Provider; + // @public (undocumented) export type AccordionHeaderSize = 'small' | 'medium' | 'large' | 'extra-large'; @@ -89,24 +94,25 @@ export const AccordionItem: ForwardRefComponent; export const accordionItemClassNames: SlotClassNames; // @public (undocumented) -export type AccordionItemContextValue = Required> & { - onHeaderClick(ev: React_2.MouseEvent | React_2.KeyboardEvent): void; +export type AccordionItemContextValue = { open: boolean; + disabled: boolean; + value: Value; }; // @public (undocumented) -export type AccordionItemContextValues = { - accordionItem: AccordionItemContextValue; +export type AccordionItemContextValues = { + accordionItem: AccordionItemContextValue; }; // @public (undocumented) -export type AccordionItemProps = ComponentProps & { +export type AccordionItemProps = ComponentProps & { disabled?: boolean; - value: AccordionItemValue; + value: Value; }; // @public (undocumented) -export const AccordionItemProvider: React_2.Provider; +export const AccordionItemProvider: React_2.Provider>; // @public (undocumented) export type AccordionItemSlots = { @@ -114,7 +120,7 @@ export type AccordionItemSlots = { }; // @public (undocumented) -export type AccordionItemState = ComponentState & AccordionItemContextValue; +export type AccordionItemState = ComponentState & AccordionItemContextValue; // @public (undocumented) export type AccordionItemValue = unknown; @@ -139,17 +145,17 @@ export type AccordionPanelState = ComponentState & { }; // @public (undocumented) -export type AccordionProps = ComponentProps & { - defaultOpenItems?: AccordionItemValue | AccordionItemValue[]; +export type AccordionProps = ComponentProps & { + defaultOpenItems?: Value | Value[]; collapsible?: boolean; multiple?: boolean; navigation?: 'linear' | 'circular'; - onToggle?: AccordionToggleEventHandler; - openItems?: AccordionItemValue | AccordionItemValue[]; + onToggle?: AccordionToggleEventHandler; + openItems?: Value | Value[]; }; // @public (undocumented) -export const AccordionProvider: Provider & FC>; +export const AccordionProvider: Provider> & FC>>; // @public (undocumented) export type AccordionSlots = { @@ -157,18 +163,19 @@ export type AccordionSlots = { }; // @public (undocumented) -export type AccordionState = ComponentState & AccordionContextValue; +export type AccordionState = ComponentState & AccordionContextValue; // @public (undocumented) -export type AccordionToggleData = { - value: AccordionItemValue; +export type AccordionToggleData = { + value: Value; + openItems: Value[]; }; // @public (undocumented) export type AccordionToggleEvent = React_2.MouseEvent | React_2.KeyboardEvent; // @public (undocumented) -export type AccordionToggleEventHandler = (event: AccordionToggleEvent, data: AccordionToggleData) => void; +export type AccordionToggleEventHandler = (event: AccordionToggleEvent, data: AccordionToggleData) => void; // @public export const renderAccordion_unstable: (state: AccordionState, contextValues: AccordionContextValues) => JSX.Element; @@ -183,10 +190,10 @@ export const renderAccordionItem_unstable: (state: AccordionItemState, contextVa export const renderAccordionPanel_unstable: (state: AccordionPanelState) => JSX.Element | null; // @public -export const useAccordion_unstable: (props: AccordionProps, ref: React_2.Ref) => AccordionState; +export const useAccordion_unstable: (props: AccordionProps, ref: React_2.Ref) => AccordionState; // @public (undocumented) -export const useAccordionContext_unstable: (selector: ContextSelector) => T; +export const useAccordionContext_unstable: (selector: ContextSelector, T>) => T; // @public (undocumented) export function useAccordionContextValues_unstable(state: AccordionState): AccordionContextValues; @@ -195,12 +202,7 @@ export function useAccordionContextValues_unstable(state: AccordionState): Accor export const useAccordionHeader_unstable: (props: AccordionHeaderProps, ref: React_2.Ref) => AccordionHeaderState; // @public (undocumented) -export const useAccordionHeaderContext_unstable: () => { - open: boolean; - disabled: boolean; - size: string; - expandIconPosition: string; -}; +export const useAccordionHeaderContext_unstable: () => AccordionHeaderContextValue; // @public (undocumented) export function useAccordionHeaderContextValues_unstable(state: AccordionHeaderState): AccordionHeaderContextValues; @@ -212,13 +214,13 @@ export const useAccordionHeaderStyles_unstable: (state: AccordionHeaderState) => export const useAccordionItem_unstable: (props: AccordionItemProps, ref: React_2.Ref) => AccordionItemState; // @public (undocumented) -export const useAccordionItemContext_unstable: () => AccordionItemContextValue; +export const useAccordionItemContext_unstable: () => AccordionItemContextValue; // @public (undocumented) export function useAccordionItemContextValues_unstable(state: AccordionItemState): AccordionItemContextValues; // @public (undocumented) -export const useAccordionItemStyles_unstable: (state: AccordionItemState) => AccordionItemState; +export const useAccordionItemStyles_unstable: (state: AccordionItemState) => AccordionItemState; // @public export const useAccordionPanel_unstable: (props: AccordionPanelProps, ref: React_2.Ref) => AccordionPanelState; @@ -227,7 +229,7 @@ export const useAccordionPanel_unstable: (props: AccordionPanelProps, ref: React export const useAccordionPanelStyles_unstable: (state: AccordionPanelState) => AccordionPanelState; // @public (undocumented) -export const useAccordionStyles_unstable: (state: AccordionState) => AccordionState; +export const useAccordionStyles_unstable: (state: AccordionState) => AccordionState; // (No @packageDocumentation comment for this package) diff --git a/packages/react-components/react-accordion/src/components/Accordion/Accordion.test.tsx b/packages/react-components/react-accordion/src/components/Accordion/Accordion.test.tsx index def3024647095..1db0f7134b762 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/Accordion.test.tsx +++ b/packages/react-components/react-accordion/src/components/Accordion/Accordion.test.tsx @@ -8,7 +8,7 @@ describe('Accordion', () => { Component: Accordion, displayName: 'Accordion', // Accordion does not have own styles - disabledTests: ['make-styles-overrides-win'], + disabledTests: ['make-styles-overrides-win', 'consistent-callback-args'], }); /** diff --git a/packages/react-components/react-accordion/src/components/Accordion/Accordion.tsx b/packages/react-components/react-accordion/src/components/Accordion/Accordion.tsx index 0ca066ac271c2..5f1a7a555f4ae 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/Accordion.tsx +++ b/packages/react-components/react-accordion/src/components/Accordion/Accordion.tsx @@ -10,8 +10,8 @@ import type { ForwardRefComponent } from '@fluentui/react-utilities'; /** * Define a styled Accordion, using the `useAccordion_unstable` and `useAccordionStyles_unstable` hooks. */ -export const Accordion: ForwardRefComponent = React.forwardRef( - (props, ref) => { +export const Accordion: ForwardRefComponent & ((props: AccordionProps) => JSX.Element) = + React.forwardRef((props, ref) => { const state = useAccordion_unstable(props, ref); const contextValues = useAccordionContextValues_unstable(state); @@ -20,7 +20,6 @@ export const Accordion: ForwardRefComponent = React.forwardRef & ((props: AccordionProps) => JSX.Element); Accordion.displayName = 'Accordion'; diff --git a/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts b/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts index de93ff2ff5353..554016e527ba7 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts +++ b/packages/react-components/react-accordion/src/components/Accordion/Accordion.types.ts @@ -1,25 +1,16 @@ import * as React from 'react'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { AccordionContextValue } from '../../contexts/accordion'; import type { AccordionItemValue } from '../AccordionItem/AccordionItem.types'; export type AccordionIndex = number | number[]; export type AccordionToggleEvent = React.MouseEvent | React.KeyboardEvent; -export type AccordionToggleEventHandler = (event: AccordionToggleEvent, data: AccordionToggleData) => void; - -export type AccordionContextValue = Required> & - Pick & { - /** - * The list of opened panels by index - */ - openItems: AccordionItemValue[]; - /** - * Callback used by AccordionItem to request a change on it's own opened state - * Should be used to toggle AccordionItem - */ - requestToggle: (event: AccordionToggleEvent, data: AccordionToggleData) => void; - }; +export type AccordionToggleEventHandler = ( + event: AccordionToggleEvent, + data: AccordionToggleData, +) => void; export type AccordionContextValues = { accordion: AccordionContextValue; @@ -29,15 +20,16 @@ export type AccordionSlots = { root: NonNullable>; }; -export type AccordionToggleData = { - value: AccordionItemValue; +export type AccordionToggleData = { + value: Value; + openItems: Value[]; }; -export type AccordionProps = ComponentProps & { +export type AccordionProps = ComponentProps & { /** * Default value for the uncontrolled state of the panel. */ - defaultOpenItems?: AccordionItemValue | AccordionItemValue[]; + defaultOpenItems?: Value | Value[]; /** * Indicates if Accordion support multiple Panels closed at the same time. @@ -57,12 +49,12 @@ export type AccordionProps = ComponentProps & { /** * Callback to be called when the opened items change. */ - onToggle?: AccordionToggleEventHandler; + onToggle?: AccordionToggleEventHandler; /** * Controls the state of the panel. */ - openItems?: AccordionItemValue | AccordionItemValue[]; + openItems?: Value | Value[]; }; -export type AccordionState = ComponentState & AccordionContextValue; +export type AccordionState = ComponentState & AccordionContextValue; diff --git a/packages/react-components/react-accordion/src/components/Accordion/AccordionContext.ts b/packages/react-components/react-accordion/src/components/Accordion/AccordionContext.ts deleted file mode 100644 index 178082eedc527..0000000000000 --- a/packages/react-components/react-accordion/src/components/Accordion/AccordionContext.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { createContext, ContextSelector, useContextSelector } from '@fluentui/react-context-selector'; -import type { Context } from '@fluentui/react-context-selector'; -import type { AccordionContextValue } from './Accordion.types'; - -export const AccordionContext: Context = createContext( - undefined, -) as Context; - -const accordionContextDefaultValue: AccordionContextValue = { - openItems: [], - collapsible: false, - requestToggle() { - /* noop */ - }, -}; - -export const AccordionProvider = AccordionContext.Provider; -export const useAccordionContext_unstable = (selector: ContextSelector): T => - useContextSelector(AccordionContext, (ctx = accordionContextDefaultValue) => selector(ctx)); diff --git a/packages/react-components/react-accordion/src/components/Accordion/index.ts b/packages/react-components/react-accordion/src/components/Accordion/index.ts index 7a08828b1b164..c4669810c07b4 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/index.ts +++ b/packages/react-components/react-accordion/src/components/Accordion/index.ts @@ -4,4 +4,3 @@ export * from './renderAccordion'; export * from './useAccordion'; export * from './useAccordionStyles.styles'; export * from './useAccordionContextValues'; -export * from './AccordionContext'; diff --git a/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx b/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx index c2ee7bd559b1c..e4ecae8e54319 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx +++ b/packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx @@ -5,8 +5,8 @@ import { createElement } from '@fluentui/react-jsx-runtime'; import { getSlotsNext } from '@fluentui/react-utilities'; -import { AccordionContext } from './AccordionContext'; import type { AccordionState, AccordionSlots, AccordionContextValues } from './Accordion.types'; +import { AccordionProvider } from '../../contexts/accordion'; /** * Function that renders the final JSX of the component @@ -16,7 +16,7 @@ export const renderAccordion_unstable = (state: AccordionState, contextValues: A return ( - {slotProps.root.children} + {slotProps.root.children} ); }; diff --git a/packages/react-components/react-accordion/src/components/Accordion/useAccordion.test.ts b/packages/react-components/react-accordion/src/components/Accordion/useAccordion.test.ts index e5ec379d29f85..a6c08c9ef89ff 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/useAccordion.test.ts +++ b/packages/react-components/react-accordion/src/components/Accordion/useAccordion.test.ts @@ -10,7 +10,7 @@ describe('useAccordion_unstable', () => { expect(result.current.openItems.length).toEqual(1); expect(result.current.openItems.includes(0)).toBeTruthy(); - act(() => result.current.requestToggle(undefined!, { value: 1 })); + act(() => result.current.requestToggle({ value: 1, event: undefined! })); expect(result.current.openItems.length).toEqual(1); expect(result.current.openItems.includes(1)).toBeTruthy(); @@ -20,9 +20,9 @@ describe('useAccordion_unstable', () => { it('should only have zero open items before having any items', () => { const { result } = renderHook(() => useAccordion_unstable({ multiple: true }, React.createRef())); expect(result.current.openItems.length).toEqual(0); - act(() => result.current.requestToggle(undefined!, { value: 0 })); + act(() => result.current.requestToggle({ value: 0, event: undefined! })); expect(result.current.openItems.length).toEqual(1); - act(() => result.current.requestToggle(undefined!, { value: 0 })); + act(() => result.current.requestToggle({ value: 0, event: undefined! })); expect(result.current.openItems.length).toEqual(1); }); it('should not have less than 1 open item', () => { @@ -32,16 +32,16 @@ describe('useAccordion_unstable', () => { expect(result.current.openItems.length).toEqual(1); - act(() => result.current.requestToggle(undefined!, { value: 0 })); + act(() => result.current.requestToggle({ value: 0, event: undefined! })); expect(result.current.openItems.length).toEqual(1); expect(result.current.openItems.includes(0)).toBeTruthy(); }); it('should open multiple panels', () => { const { result } = renderHook(() => useAccordion_unstable({ multiple: true }, React.createRef())); expect(result.current.openItems.length).toEqual(0); - act(() => result.current.requestToggle(undefined!, { value: 0 })); + act(() => result.current.requestToggle({ value: 0, event: undefined! })); expect(result.current.openItems.includes(0)).toBeTruthy(); - act(() => result.current.requestToggle(undefined!, { value: 1 })); + act(() => result.current.requestToggle({ value: 1, event: undefined! })); expect(result.current.openItems.includes(1)).toBeTruthy(); expect(result.current.openItems.length).toEqual(2); }); @@ -50,18 +50,18 @@ describe('useAccordion_unstable', () => { it('should have zero panels opened', () => { const { result } = renderHook(() => useAccordion_unstable({ collapsible: true }, React.createRef())); expect(result.current.openItems.length).toEqual(0); - act(() => result.current.requestToggle(undefined!, { value: 0 })); + act(() => result.current.requestToggle({ value: 0, event: undefined! })); expect(result.current.openItems.length).toEqual(1); - act(() => result.current.requestToggle(undefined!, { value: 0 })); + act(() => result.current.requestToggle({ value: 0, event: undefined! })); expect(result.current.openItems.length).toEqual(0); }); it('should not open more than one panel', () => { const { result } = renderHook(() => useAccordion_unstable({ collapsible: true }, React.createRef())); expect(result.current.openItems.length).toEqual(0); - act(() => result.current.requestToggle(undefined!, { value: 0 })); + act(() => result.current.requestToggle({ value: 0, event: undefined! })); expect(result.current.openItems.length).toEqual(1); expect(result.current.openItems.includes(0)).toBeTruthy(); - act(() => result.current.requestToggle(undefined!, { value: 1 })); + act(() => result.current.requestToggle({ value: 1, event: undefined! })); expect(result.current.openItems.length).toEqual(1); expect(result.current.openItems.includes(1)).toBeTruthy(); expect(result.current.openItems.includes(0)).toBeFalsy(); diff --git a/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts b/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts index 0b06b206ac3df..df715ca5ba148 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts +++ b/packages/react-components/react-accordion/src/components/Accordion/useAccordion.ts @@ -1,15 +1,19 @@ import * as React from 'react'; import { getNativeElementProps, useControllableState, useEventCallback } from '@fluentui/react-utilities'; -import type { AccordionProps, AccordionState, AccordionToggleData, AccordionToggleEvent } from './Accordion.types'; +import type { AccordionProps, AccordionState } from './Accordion.types'; import type { AccordionItemValue } from '../AccordionItem/AccordionItem.types'; import { useArrowNavigationGroup } from '@fluentui/react-tabster'; +import { AccordionRequestToggleData } from '../../contexts/accordion'; /** * Returns the props and state required to render the component * @param props - Accordion properties * @param ref - reference to root HTMLElement of Accordion */ -export const useAccordion_unstable = (props: AccordionProps, ref: React.Ref): AccordionState => { +export const useAccordion_unstable = ( + props: AccordionProps, + ref: React.Ref, +): AccordionState => { const { openItems: controlledOpenItems, defaultOpenItems, @@ -29,13 +33,15 @@ export const useAccordion_unstable = (props: AccordionProps, ref: React.Ref { - onToggle?.(event, data); - setOpenItems(previousOpenItems => updateOpenItems(data.value, previousOpenItems, multiple, collapsible)); + const requestToggle = useEventCallback((data: AccordionRequestToggleData) => { + const nextOpenItems = updateOpenItems(data.value, openItems, multiple, collapsible); + onToggle?.(data.event, { value: data.value, openItems: nextOpenItems }); + setOpenItems(nextOpenItems); }); return { collapsible, + multiple, navigation, openItems, requestToggle, @@ -44,7 +50,7 @@ export const useAccordion_unstable = (props: AccordionProps, ref: React.Ref({ defaultOpenItems, multiple, -}: Pick): AccordionItemValue[] { +}: Pick, 'defaultOpenItems' | 'multiple'>): Value[] { if (defaultOpenItems !== undefined) { if (Array.isArray(defaultOpenItems)) { return multiple ? defaultOpenItems : [defaultOpenItems[0]]; @@ -73,9 +79,9 @@ function initializeUncontrolledOpenItems({ * @param multiple - if Accordion support multiple Panels opened at the same time * @param collapsible - if Accordion support multiple Panels closed at the same time */ -function updateOpenItems( - value: AccordionItemValue, - previousOpenItems: AccordionItemValue[], +function updateOpenItems( + value: Value, + previousOpenItems: Value[], multiple: boolean, collapsible: boolean, ) { @@ -96,7 +102,7 @@ function updateOpenItems( /** * Normalizes Accordion index into an array of indexes */ -function normalizeValues(index?: AccordionItemValue | AccordionItemValue[]): AccordionItemValue[] | undefined { +function normalizeValues(index?: Value | Value[]): Value[] | undefined { if (index === undefined) { return undefined; } diff --git a/packages/react-components/react-accordion/src/components/Accordion/useAccordionContextValues.ts b/packages/react-components/react-accordion/src/components/Accordion/useAccordionContextValues.ts index 82860a1018e3c..29feb91003d0c 100644 --- a/packages/react-components/react-accordion/src/components/Accordion/useAccordionContextValues.ts +++ b/packages/react-components/react-accordion/src/components/Accordion/useAccordionContextValues.ts @@ -1,7 +1,8 @@ -import type { AccordionContextValue, AccordionContextValues, AccordionState } from './Accordion.types'; +import type { AccordionContextValue } from '../../contexts/accordion'; +import type { AccordionContextValues, AccordionState } from './Accordion.types'; export function useAccordionContextValues_unstable(state: AccordionState): AccordionContextValues { - const { navigation, openItems, requestToggle, collapsible } = state; + const { navigation, openItems, requestToggle, multiple, collapsible } = state; // This context is created with "@fluentui/react-context-selector", these is no sense to memoize it const accordion: AccordionContextValue = { @@ -9,6 +10,7 @@ export function useAccordionContextValues_unstable(state: AccordionState): Accor openItems, requestToggle, collapsible, + multiple, }; return { accordion }; diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeader.types.ts b/packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeader.types.ts index 71adfe25acfa3..e4e49e2429192 100644 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeader.types.ts +++ b/packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeader.types.ts @@ -1,14 +1,10 @@ import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; import type { ARIAButtonSlotProps } from '@fluentui/react-aria'; +import type { AccordionHeaderContextValue } from '../../contexts/accordionHeader'; export type AccordionHeaderSize = 'small' | 'medium' | 'large' | 'extra-large'; export type AccordionHeaderExpandIconPosition = 'start' | 'end'; -export type AccordionHeaderContextValue = Required> & { - disabled: boolean; - open: boolean; -}; - export type AccordionHeaderContextValues = { accordionHeader: AccordionHeaderContextValue; }; diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeaderContext.ts b/packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeaderContext.ts deleted file mode 100644 index 174b35685eca1..0000000000000 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/AccordionHeaderContext.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as React from 'react'; -import type { AccordionHeaderContextValue } from './AccordionHeader.types'; - -export const AccordionHeaderContext = React.createContext(undefined); - -export const AccordionHeaderContextProvider = AccordionHeaderContext.Provider; - -const accordionHeaderContextDefaultValue = { - open: false, - disabled: false, - size: 'medium', - expandIconPosition: 'start', -}; - -export const useAccordionHeaderContext = () => - React.useContext(AccordionHeaderContext) ?? accordionHeaderContextDefaultValue; diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/index.ts b/packages/react-components/react-accordion/src/components/AccordionHeader/index.ts index 8af099f3da5b2..ce32bacc27b60 100644 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/index.ts +++ b/packages/react-components/react-accordion/src/components/AccordionHeader/index.ts @@ -4,4 +4,3 @@ export * from './renderAccordionHeader'; export * from './useAccordionHeader'; export * from './useAccordionHeaderContextValues'; export * from './useAccordionHeaderStyles.styles'; -export * from './AccordionHeaderContext'; diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/renderAccordionHeader.tsx b/packages/react-components/react-accordion/src/components/AccordionHeader/renderAccordionHeader.tsx index 95d69b89832cc..2a0c4f362a65a 100644 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/renderAccordionHeader.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionHeader/renderAccordionHeader.tsx @@ -4,8 +4,8 @@ import { createElement } from '@fluentui/react-jsx-runtime'; import { getSlotsNext } from '@fluentui/react-utilities'; -import { AccordionHeaderContext } from './AccordionHeaderContext'; import type { AccordionHeaderState, AccordionHeaderSlots, AccordionHeaderContextValues } from './AccordionHeader.types'; +import { AccordionHeaderProvider } from '../../contexts/accordionHeader'; /** * Function that renders the final JSX of the component @@ -17,7 +17,7 @@ export const renderAccordionHeader_unstable = ( const { slots, slotProps } = getSlotsNext(state); return ( - + {state.expandIconPosition === 'start' && slots.expandIcon && } @@ -26,6 +26,6 @@ export const renderAccordionHeader_unstable = ( {state.expandIconPosition === 'end' && slots.expandIcon && } - + ); }; diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.test.tsx b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.test.tsx index 9c54d3da7706b..5025534c3bfc3 100644 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.test.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.test.tsx @@ -2,34 +2,34 @@ import { renderHook } from '@testing-library/react-hooks'; import * as React from 'react'; import { useAccordionHeader_unstable } from './useAccordionHeader'; -import { AccordionContext } from '../Accordion/AccordionContext'; -import { AccordionItemContext } from '../AccordionItem/AccordionItemContext'; +import { AccordionProvider } from '../../contexts/accordion'; +import { AccordionItemProvider } from '../../contexts/accordionItem'; describe('useAccordionHeader_unstable', () => { it('should return button props as disabled even when it is not disabled (forceDisabled)', () => { const ref = React.createRef(); const wrapper: React.FC = ({ children }) => ( - { /* ... */ }, }} > - { - /* ... */ - }, + value: 1, }} > {children} - - + + ); const { result } = renderHook(() => useAccordionHeader_unstable({}, ref), { wrapper }); expect(result.current.button['aria-disabled']).toBeTruthy(); diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx index 06802efcdd3e7..d290bcd19ca80 100644 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeader.tsx @@ -5,12 +5,12 @@ import { resolveShorthand, useEventCallback, } from '@fluentui/react-utilities'; -import { useAccordionItemContext_unstable } from '../AccordionItem/index'; import { ARIAButtonSlotProps, useARIAButtonShorthand } from '@fluentui/react-aria'; import type { AccordionHeaderProps, AccordionHeaderState } from './AccordionHeader.types'; -import { useAccordionContext_unstable } from '../Accordion/AccordionContext'; +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'; /** * Returns the props and state required to render the component @@ -22,7 +22,8 @@ export const useAccordionHeader_unstable = ( ref: React.Ref, ): AccordionHeaderState => { const { as, icon, button, expandIcon, inline = false, size = 'medium', expandIconPosition = 'start' } = props; - const { onHeaderClick: onAccordionHeaderClick, disabled, open } = useAccordionItemContext_unstable(); + const { value, disabled, open } = useAccordionItemContext_unstable(); + const requestToggle = useAccordionContext_unstable(ctx => ctx.requestToggle); /** * force disabled state on button if accordion isn't collapsible @@ -77,12 +78,12 @@ export const useAccordionHeader_unstable = ( type: 'button', }, }), - onClick: useEventCallback(ev => { + onClick: useEventCallback(event => { if (isResolvedShorthand(button)) { - button.onClick?.(ev); + button.onClick?.(event); } - if (!ev.defaultPrevented) { - onAccordionHeaderClick(ev); + if (!event.defaultPrevented) { + requestToggle({ value, event }); } }), }, diff --git a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeaderContextValues.ts b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeaderContextValues.ts index 29b52665a873b..b8a6525811fc2 100644 --- a/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeaderContextValues.ts +++ b/packages/react-components/react-accordion/src/components/AccordionHeader/useAccordionHeaderContextValues.ts @@ -1,9 +1,6 @@ import * as React from 'react'; -import type { - AccordionHeaderContextValue, - AccordionHeaderState, - AccordionHeaderContextValues, -} from './AccordionHeader.types'; +import type { AccordionHeaderState, AccordionHeaderContextValues } from './AccordionHeader.types'; +import type { AccordionHeaderContextValue } from '../../contexts/accordionHeader'; export function useAccordionHeaderContextValues_unstable(state: AccordionHeaderState): AccordionHeaderContextValues { const { disabled, expandIconPosition, open, size } = state; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/AccordionItem.types.ts b/packages/react-components/react-accordion/src/components/AccordionItem/AccordionItem.types.ts index 19242f8b7902b..b995fcc1afd40 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/AccordionItem.types.ts +++ b/packages/react-components/react-accordion/src/components/AccordionItem/AccordionItem.types.ts @@ -1,20 +1,15 @@ -import * as React from 'react'; import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities'; +import { AccordionItemContextValue } from '../../contexts/accordionItem'; -export type AccordionItemContextValue = Required> & { - onHeaderClick(ev: React.MouseEvent | React.KeyboardEvent): void; - open: boolean; -}; - -export type AccordionItemContextValues = { - accordionItem: AccordionItemContextValue; +export type AccordionItemContextValues = { + accordionItem: AccordionItemContextValue; }; export type AccordionItemSlots = { root: NonNullable>; }; -export type AccordionItemProps = ComponentProps & { +export type AccordionItemProps = ComponentProps & { /** * Disables opening/closing of panel. */ @@ -22,9 +17,10 @@ export type AccordionItemProps = ComponentProps & { /** * Required value that identifies this item inside an Accordion component. */ - value: AccordionItemValue; + value: Value; }; export type AccordionItemValue = unknown; -export type AccordionItemState = ComponentState & AccordionItemContextValue; +export type AccordionItemState = ComponentState & + AccordionItemContextValue; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/AccordionItemContext.ts b/packages/react-components/react-accordion/src/components/AccordionItem/AccordionItemContext.ts deleted file mode 100644 index e7aae7fd9edf5..0000000000000 --- a/packages/react-components/react-accordion/src/components/AccordionItem/AccordionItemContext.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as React from 'react'; -import type { AccordionItemContextValue } from './AccordionItem.types'; - -export const AccordionItemContext = React.createContext( - undefined, -) as React.Context; - -const accordionItemContextDefaultValue: AccordionItemContextValue = { - onHeaderClick() { - /** */ - }, - open: false, - disabled: false, -}; - -export const AccordionItemProvider = AccordionItemContext.Provider; - -export const useAccordionItemContext_unstable = () => - React.useContext(AccordionItemContext) ?? accordionItemContextDefaultValue; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/index.ts b/packages/react-components/react-accordion/src/components/AccordionItem/index.ts index 301f2238c6d80..3e8670fc4e40a 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/index.ts +++ b/packages/react-components/react-accordion/src/components/AccordionItem/index.ts @@ -3,5 +3,4 @@ export * from './AccordionItem.types'; export * from './renderAccordionItem'; export * from './useAccordionItem'; export * from './useAccordionItemContextValues'; -export * from './AccordionItemContext'; export * from './useAccordionItemStyles.styles'; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx b/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx index 659b3851ab40b..c51adb18207ea 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionItem/renderAccordionItem.tsx @@ -4,8 +4,8 @@ import { createElement } from '@fluentui/react-jsx-runtime'; import { getSlotsNext } from '@fluentui/react-utilities'; -import { AccordionItemContext } from './AccordionItemContext'; import type { AccordionItemState, AccordionItemSlots, AccordionItemContextValues } from './AccordionItem.types'; +import { AccordionItemProvider } from '../../contexts/accordionItem'; /** * Function that renders the final JSX of the component @@ -15,9 +15,7 @@ export const renderAccordionItem_unstable = (state: AccordionItemState, contextV return ( - - {slotProps.root.children} - + {slotProps.root.children} ); }; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts index bf1141e0df65b..ace9aac02a53c 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts +++ b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItem.ts @@ -1,8 +1,7 @@ import * as React from 'react'; import { getNativeElementProps } from '@fluentui/react-utilities'; -import { useAccordionContext_unstable } from '../Accordion/AccordionContext'; +import { useAccordionContext_unstable } from '../../contexts/accordion'; import type { AccordionItemProps, AccordionItemState } from './AccordionItem.types'; -import type { AccordionToggleEvent } from '../Accordion/Accordion.types'; /** * Returns the props and state required to render the component @@ -15,23 +14,15 @@ export const useAccordionItem_unstable = ( ): AccordionItemState => { const { value, disabled = false } = props; - const requestToggle = useAccordionContext_unstable(ctx => ctx.requestToggle); const open = useAccordionContext_unstable(ctx => ctx.openItems.includes(value)); - const onAccordionHeaderClick = React.useCallback( - (ev: AccordionToggleEvent) => requestToggle(ev, { value }), - [requestToggle, value], - ); return { open, + value, disabled, - onHeaderClick: onAccordionHeaderClick, components: { root: 'div', }, - root: getNativeElementProps('div', { - ref: ref, - ...props, - }), + root: getNativeElementProps('div', { ref, ...props }), }; }; diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.test.ts b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.test.ts index f0539d013a830..d8507fe474225 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.test.ts +++ b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.test.ts @@ -15,6 +15,6 @@ describe('useAccordionContextValues_unstable', () => { expect(result.current.accordionItem).toBeDefined(); expect(result.current.accordionItem.disabled).toBe(false); expect(result.current.accordionItem.open).toBe(false); - expect(result.current.accordionItem.onHeaderClick).toBeInstanceOf(Function); + expect(result.current.accordionItem.value).toBe('foo'); }); }); diff --git a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.ts b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.ts index 723efaaba5866..bb56cbb56c0eb 100644 --- a/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.ts +++ b/packages/react-components/react-accordion/src/components/AccordionItem/useAccordionItemContextValues.ts @@ -1,11 +1,12 @@ import * as React from 'react'; -import type { AccordionItemContextValue, AccordionItemContextValues, AccordionItemState } from './AccordionItem.types'; +import type { AccordionItemContextValues, AccordionItemState } from './AccordionItem.types'; +import { AccordionItemContextValue } from '../../contexts/accordionItem'; export function useAccordionItemContextValues_unstable(state: AccordionItemState): AccordionItemContextValues { - const { disabled, onHeaderClick, open } = state; + const { disabled, open, value } = state; const accordionItem = React.useMemo( - () => ({ disabled, onHeaderClick, open }), - [disabled, onHeaderClick, open], + () => ({ disabled, open, value }), + [disabled, open, value], ); return { accordionItem }; diff --git a/packages/react-components/react-accordion/src/components/AccordionPanel/AccordionPanel.test.tsx b/packages/react-components/react-accordion/src/components/AccordionPanel/AccordionPanel.test.tsx index 90ecce32f3f97..fc1dd450a3b76 100644 --- a/packages/react-components/react-accordion/src/components/AccordionPanel/AccordionPanel.test.tsx +++ b/packages/react-components/react-accordion/src/components/AccordionPanel/AccordionPanel.test.tsx @@ -2,13 +2,13 @@ import * as React from 'react'; import { AccordionPanel } from './AccordionPanel'; import * as renderer from 'react-test-renderer'; import { isConformant } from '../../testing/isConformant'; -import { AccordionItemContext } from '../AccordionItem'; +import { AccordionItemProvider } from '../../contexts/accordionItem'; describe('AccordionPanel', () => { const Wrapper: React.FC = props => ( - undefined }}> + {props.children} - + ); isConformant({ diff --git a/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts b/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts index ec9d601e90079..95e36b002e1c4 100644 --- a/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts +++ b/packages/react-components/react-accordion/src/components/AccordionPanel/useAccordionPanel.ts @@ -1,9 +1,9 @@ import * as React from 'react'; import { getNativeElementProps } from '@fluentui/react-utilities'; import { useTabsterAttributes } from '@fluentui/react-tabster'; -import { useAccordionItemContext_unstable } from '../AccordionItem/index'; -import { useAccordionContext_unstable } from '../Accordion/AccordionContext'; +import { useAccordionContext_unstable } from '../../contexts/accordion'; import type { AccordionPanelProps, AccordionPanelState } from './AccordionPanel.types'; +import { useAccordionItemContext_unstable } from '../../contexts/accordionItem'; /** * Returns the props and state required to render the component diff --git a/packages/react-components/react-accordion/src/contexts/accordion.ts b/packages/react-components/react-accordion/src/contexts/accordion.ts new file mode 100644 index 0000000000000..73e8181a8602b --- /dev/null +++ b/packages/react-components/react-accordion/src/contexts/accordion.ts @@ -0,0 +1,40 @@ +import { createContext, ContextSelector, useContextSelector } from '@fluentui/react-context-selector'; +import type { Context } from '@fluentui/react-context-selector'; +import { AccordionItemValue } from '../AccordionItem'; +import { AccordionToggleData, AccordionToggleEvent } from '../Accordion'; + +export type AccordionRequestToggleData = { event: AccordionToggleEvent } & Pick< + AccordionToggleData, + 'value' +>; + +export type AccordionContextValue = { + /** + * The list of opened panels by index + */ + openItems: AccordionItemValue[]; + /** + * Callback used by AccordionItem to request a change on it's own opened state + * Should be used to toggle AccordionItem + */ + requestToggle: (data: AccordionRequestToggleData) => void; + collapsible: boolean; + multiple: boolean; + navigation: 'linear' | 'circular' | undefined; +}; + +const AccordionContext = createContext(undefined) as Context; + +const accordionContextDefaultValue: AccordionContextValue = { + openItems: [], + collapsible: false, + multiple: false, + navigation: undefined, + requestToggle() { + /* noop */ + }, +}; + +export const { Provider: AccordionProvider } = AccordionContext; +export const useAccordionContext_unstable = (selector: ContextSelector): T => + useContextSelector(AccordionContext, (ctx = accordionContextDefaultValue) => selector(ctx)); diff --git a/packages/react-components/react-accordion/src/contexts/accordionHeader.ts b/packages/react-components/react-accordion/src/contexts/accordionHeader.ts new file mode 100644 index 0000000000000..f087dba5de265 --- /dev/null +++ b/packages/react-components/react-accordion/src/contexts/accordionHeader.ts @@ -0,0 +1,28 @@ +import * as React from 'react'; +import type { + AccordionHeaderExpandIconPosition, + AccordionHeaderSize, +} from '../components/AccordionHeader/AccordionHeader.types'; + +export type AccordionHeaderContextValue = { + disabled: boolean; + open: boolean; + expandIconPosition: AccordionHeaderExpandIconPosition; + size: AccordionHeaderSize; +}; + +const AccordionHeaderContext = React.createContext( + undefined, +) as React.Context; + +const accordionHeaderContextDefaultValue = { + open: false, + disabled: false, + size: 'medium', + expandIconPosition: 'start', +}; + +export const { Provider: AccordionHeaderProvider } = AccordionHeaderContext; + +export const useAccordionHeaderContext_unstable = () => + React.useContext(AccordionHeaderContext) ?? accordionHeaderContextDefaultValue; diff --git a/packages/react-components/react-accordion/src/contexts/accordionItem.ts b/packages/react-components/react-accordion/src/contexts/accordionItem.ts new file mode 100644 index 0000000000000..153cda9090a2e --- /dev/null +++ b/packages/react-components/react-accordion/src/contexts/accordionItem.ts @@ -0,0 +1,23 @@ +import * as React from 'react'; +import { AccordionItemValue } from '../AccordionItem'; + +export type AccordionItemContextValue = { + open: boolean; + disabled: boolean; + value: Value; +}; + +const AccordionItemContext = React.createContext | undefined>( + undefined, +) as React.Context>; + +const accordionItemContextDefaultValue: AccordionItemContextValue = { + open: false, + disabled: false, + value: undefined, +}; + +export const { Provider: AccordionItemProvider } = AccordionItemContext; + +export const useAccordionItemContext_unstable = () => + React.useContext(AccordionItemContext) ?? accordionItemContextDefaultValue; diff --git a/packages/react-components/react-accordion/src/index.ts b/packages/react-components/react-accordion/src/index.ts index 79973432e04d8..6e6b481aa3260 100644 --- a/packages/react-components/react-accordion/src/index.ts +++ b/packages/react-components/react-accordion/src/index.ts @@ -1,15 +1,12 @@ export { Accordion, - AccordionProvider, accordionClassNames, renderAccordion_unstable, - useAccordionContext_unstable, useAccordionContextValues_unstable, useAccordionStyles_unstable, useAccordion_unstable, } from './Accordion'; export type { - AccordionContextValue, AccordionContextValues, AccordionIndex, AccordionProps, @@ -21,16 +18,13 @@ export type { } from './Accordion'; export { AccordionItem, - AccordionItemProvider, accordionItemClassNames, renderAccordionItem_unstable, useAccordionItemContextValues_unstable, - useAccordionItemContext_unstable, useAccordionItemStyles_unstable, useAccordionItem_unstable, } from './AccordionItem'; export type { - AccordionItemContextValue, AccordionItemContextValues, AccordionItemProps, AccordionItemSlots, @@ -40,15 +34,12 @@ export type { export { AccordionHeader, accordionHeaderClassNames, - AccordionHeaderContextProvider, renderAccordionHeader_unstable, useAccordionHeaderContextValues_unstable, useAccordionHeaderStyles_unstable, useAccordionHeader_unstable, - useAccordionHeaderContext as useAccordionHeaderContext_unstable, } from './AccordionHeader'; export type { - AccordionHeaderContextValue, AccordionHeaderContextValues, AccordionHeaderExpandIconPosition, AccordionHeaderProps, @@ -64,3 +55,15 @@ export { useAccordionPanel_unstable, } from './AccordionPanel'; export type { AccordionPanelProps, AccordionPanelSlots, AccordionPanelState } from './AccordionPanel'; + +export { AccordionProvider, useAccordionContext_unstable } from './contexts/accordion'; + +export type { AccordionContextValue } from './contexts/accordion'; + +export { AccordionItemProvider, useAccordionItemContext_unstable } from './contexts/accordionItem'; + +export type { AccordionItemContextValue } from './contexts/accordionItem'; + +export { AccordionHeaderProvider, useAccordionHeaderContext_unstable } from './contexts/accordionHeader'; + +export type { AccordionHeaderContextValue } from './contexts/accordionHeader'; diff --git a/packages/react-components/react-accordion/stories/Accordion/AccordionControlled.stories.tsx b/packages/react-components/react-accordion/stories/Accordion/AccordionControlled.stories.tsx new file mode 100644 index 0000000000000..c4917fe64f251 --- /dev/null +++ b/packages/react-components/react-accordion/stories/Accordion/AccordionControlled.stories.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { + Accordion, + AccordionHeader, + AccordionItem, + AccordionPanel, + AccordionToggleEventHandler, +} from '@fluentui/react-components'; + +export const Controlled = () => { + const [openItems, setOpenItems] = React.useState(['1']); + const handleToggle: AccordionToggleEventHandler = (event, data) => { + setOpenItems(data.openItems); + }; + return ( + + + Accordion Header 1 + +
Accordion Panel 1
+
+
+ + Accordion Header 2 + +
Accordion Panel 2
+
+
+ + Accordion Header 3 + +
Accordion Panel 3
+
+
+
+ ); +}; + +Controlled.parameters = { + docs: { + description: { + story: + 'An accordion can be controlled, to ensure `multiple` and `collapsible` you should use `openItems` provided through `onToggle` callback.', + }, + }, +}; diff --git a/packages/react-components/react-accordion/stories/Accordion/index.stories.tsx b/packages/react-components/react-accordion/stories/Accordion/index.stories.tsx index 15f994023d06a..a907bb76571f3 100644 --- a/packages/react-components/react-accordion/stories/Accordion/index.stories.tsx +++ b/packages/react-components/react-accordion/stories/Accordion/index.stories.tsx @@ -1,6 +1,7 @@ import { Accordion } from '@fluentui/react-components'; export { Default } from './AccordionDefault.stories'; export { Collapsible } from './AccordionCollapsible.stories'; +export { Controlled } from './AccordionControlled.stories'; export { Multiple } from './AccordionMultiple.stories'; export { Navigation } from './AccordionNavigation.stories'; export { OpenItems } from './AccordionOpenItems.stories';