Skip to content

Commit

Permalink
bugfix: ensures controlled state works properly + (microsoft#28665)
Browse files Browse the repository at this point in the history
makes AccordionItemValue generic +
adds story for controlled state +
simplifies access to requestToggle by context +
reorganizes context into a separate folder
  • Loading branch information
bsunderhus authored Aug 3, 2023
1 parent 141424a commit 192666b
Show file tree
Hide file tree
Showing 33 changed files with 291 additions and 219 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "feat: make AccordionItemValue generic",
"packageName": "@fluentui/react-accordion",
"email": "[email protected]",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,18 @@ import type { Slot } from '@fluentui/react-utilities';
import type { SlotClassNames } from '@fluentui/react-utilities';

// @public
export const Accordion: ForwardRefComponent<AccordionProps>;
export const Accordion: ForwardRefComponent<AccordionProps> & (<TItem>(props: AccordionProps<TItem>) => JSX.Element);

// @public (undocumented)
export const accordionClassNames: SlotClassNames<AccordionSlots>;

// @public (undocumented)
export type AccordionContextValue = Required<Pick<AccordionProps, 'collapsible'>> & Pick<AccordionProps, 'navigation'> & {
export type AccordionContextValue<Value = AccordionItemValue> = {
openItems: AccordionItemValue[];
requestToggle: (event: AccordionToggleEvent, data: AccordionToggleData) => void;
requestToggle: (data: AccordionRequestToggleData<Value>) => void;
collapsible: boolean;
multiple: boolean;
navigation: 'linear' | 'circular' | undefined;
};

// @public (undocumented)
Expand All @@ -42,12 +45,11 @@ export const AccordionHeader: ForwardRefComponent<AccordionHeaderProps>;
export const accordionHeaderClassNames: SlotClassNames<AccordionHeaderSlots>;

// @public (undocumented)
export const AccordionHeaderContextProvider: React_2.Provider<AccordionHeaderContextValue | undefined>;

// @public (undocumented)
export type AccordionHeaderContextValue = Required<Pick<AccordionHeaderProps, 'expandIconPosition' | 'size'>> & {
export type AccordionHeaderContextValue = {
disabled: boolean;
open: boolean;
expandIconPosition: AccordionHeaderExpandIconPosition;
size: AccordionHeaderSize;
};

// @public (undocumented)
Expand All @@ -65,6 +67,9 @@ export type AccordionHeaderProps = ComponentProps<Partial<AccordionHeaderSlots>>
size?: AccordionHeaderSize;
};

// @public (undocumented)
export const AccordionHeaderProvider: React_2.Provider<AccordionHeaderContextValue>;

// @public (undocumented)
export type AccordionHeaderSize = 'small' | 'medium' | 'large' | 'extra-large';

Expand All @@ -89,32 +94,33 @@ export const AccordionItem: ForwardRefComponent<AccordionItemProps>;
export const accordionItemClassNames: SlotClassNames<AccordionItemSlots>;

// @public (undocumented)
export type AccordionItemContextValue = Required<Pick<AccordionItemProps, 'disabled'>> & {
onHeaderClick(ev: React_2.MouseEvent | React_2.KeyboardEvent): void;
export type AccordionItemContextValue<Value = AccordionItemValue> = {
open: boolean;
disabled: boolean;
value: Value;
};

// @public (undocumented)
export type AccordionItemContextValues = {
accordionItem: AccordionItemContextValue;
export type AccordionItemContextValues<Value = AccordionItemValue> = {
accordionItem: AccordionItemContextValue<Value>;
};

// @public (undocumented)
export type AccordionItemProps = ComponentProps<AccordionItemSlots> & {
export type AccordionItemProps<Value = AccordionItemValue> = ComponentProps<AccordionItemSlots> & {
disabled?: boolean;
value: AccordionItemValue;
value: Value;
};

// @public (undocumented)
export const AccordionItemProvider: React_2.Provider<AccordionItemContextValue>;
export const AccordionItemProvider: React_2.Provider<AccordionItemContextValue<unknown>>;

// @public (undocumented)
export type AccordionItemSlots = {
root: NonNullable<Slot<'div'>>;
};

// @public (undocumented)
export type AccordionItemState = ComponentState<AccordionItemSlots> & AccordionItemContextValue;
export type AccordionItemState<Value = AccordionItemValue> = ComponentState<AccordionItemSlots> & AccordionItemContextValue<Value>;

// @public (undocumented)
export type AccordionItemValue = unknown;
Expand All @@ -139,36 +145,37 @@ export type AccordionPanelState = ComponentState<AccordionPanelSlots> & {
};

// @public (undocumented)
export type AccordionProps = ComponentProps<AccordionSlots> & {
defaultOpenItems?: AccordionItemValue | AccordionItemValue[];
export type AccordionProps<Value = AccordionItemValue> = ComponentProps<AccordionSlots> & {
defaultOpenItems?: Value | Value[];
collapsible?: boolean;
multiple?: boolean;
navigation?: 'linear' | 'circular';
onToggle?: AccordionToggleEventHandler;
openItems?: AccordionItemValue | AccordionItemValue[];
onToggle?: AccordionToggleEventHandler<Value>;
openItems?: Value | Value[];
};

// @public (undocumented)
export const AccordionProvider: Provider<AccordionContextValue> & FC<ProviderProps<AccordionContextValue>>;
export const AccordionProvider: Provider<AccordionContextValue<unknown>> & FC<ProviderProps<AccordionContextValue<unknown>>>;

// @public (undocumented)
export type AccordionSlots = {
root: NonNullable<Slot<'div'>>;
};

// @public (undocumented)
export type AccordionState = ComponentState<AccordionSlots> & AccordionContextValue;
export type AccordionState<Value = AccordionItemValue> = ComponentState<AccordionSlots> & AccordionContextValue<Value>;

// @public (undocumented)
export type AccordionToggleData = {
value: AccordionItemValue;
export type AccordionToggleData<Value = AccordionItemValue> = {
value: Value;
openItems: Value[];
};

// @public (undocumented)
export type AccordionToggleEvent<E = HTMLElement> = React_2.MouseEvent<E> | React_2.KeyboardEvent<E>;

// @public (undocumented)
export type AccordionToggleEventHandler = (event: AccordionToggleEvent, data: AccordionToggleData) => void;
export type AccordionToggleEventHandler<Value = AccordionItemValue> = (event: AccordionToggleEvent, data: AccordionToggleData<Value>) => void;

// @public
export const renderAccordion_unstable: (state: AccordionState, contextValues: AccordionContextValues) => JSX.Element;
Expand All @@ -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<HTMLElement>) => AccordionState;
export const useAccordion_unstable: <Value = unknown>(props: AccordionProps<Value>, ref: React_2.Ref<HTMLElement>) => AccordionState<Value>;

// @public (undocumented)
export const useAccordionContext_unstable: <T>(selector: ContextSelector<AccordionContextValue, T>) => T;
export const useAccordionContext_unstable: <T>(selector: ContextSelector<AccordionContextValue<unknown>, T>) => T;

// @public (undocumented)
export function useAccordionContextValues_unstable(state: AccordionState): AccordionContextValues;
Expand All @@ -195,12 +202,7 @@ export function useAccordionContextValues_unstable(state: AccordionState): Accor
export const useAccordionHeader_unstable: (props: AccordionHeaderProps, ref: React_2.Ref<HTMLElement>) => 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;
Expand All @@ -212,13 +214,13 @@ export const useAccordionHeaderStyles_unstable: (state: AccordionHeaderState) =>
export const useAccordionItem_unstable: (props: AccordionItemProps, ref: React_2.Ref<HTMLElement>) => AccordionItemState;

// @public (undocumented)
export const useAccordionItemContext_unstable: () => AccordionItemContextValue;
export const useAccordionItemContext_unstable: () => AccordionItemContextValue<unknown>;

// @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<unknown>;

// @public
export const useAccordionPanel_unstable: (props: AccordionPanelProps, ref: React_2.Ref<HTMLElement>) => AccordionPanelState;
Expand All @@ -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<unknown>;

// (No @packageDocumentation comment for this package)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AccordionProps> = React.forwardRef<HTMLDivElement, AccordionProps>(
(props, ref) => {
export const Accordion: ForwardRefComponent<AccordionProps> & (<TItem>(props: AccordionProps<TItem>) => JSX.Element) =
React.forwardRef<HTMLDivElement, AccordionProps>((props, ref) => {
const state = useAccordion_unstable(props, ref);
const contextValues = useAccordionContextValues_unstable(state);

Expand All @@ -20,7 +20,6 @@ export const Accordion: ForwardRefComponent<AccordionProps> = React.forwardRef<H
useCustomStyleHook_unstable('useAccordionStyles_unstable')(state);

return renderAccordion_unstable(state, contextValues);
},
);
}) as ForwardRefComponent<AccordionProps> & (<TItem>(props: AccordionProps<TItem>) => JSX.Element);

Accordion.displayName = 'Accordion';
Original file line number Diff line number Diff line change
@@ -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<E = HTMLElement> = React.MouseEvent<E> | React.KeyboardEvent<E>;

export type AccordionToggleEventHandler = (event: AccordionToggleEvent, data: AccordionToggleData) => void;

export type AccordionContextValue = Required<Pick<AccordionProps, 'collapsible'>> &
Pick<AccordionProps, 'navigation'> & {
/**
* 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<Value = AccordionItemValue> = (
event: AccordionToggleEvent,
data: AccordionToggleData<Value>,
) => void;

export type AccordionContextValues = {
accordion: AccordionContextValue;
Expand All @@ -29,15 +20,16 @@ export type AccordionSlots = {
root: NonNullable<Slot<'div'>>;
};

export type AccordionToggleData = {
value: AccordionItemValue;
export type AccordionToggleData<Value = AccordionItemValue> = {
value: Value;
openItems: Value[];
};

export type AccordionProps = ComponentProps<AccordionSlots> & {
export type AccordionProps<Value = AccordionItemValue> = ComponentProps<AccordionSlots> & {
/**
* 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.
Expand All @@ -57,12 +49,12 @@ export type AccordionProps = ComponentProps<AccordionSlots> & {
/**
* Callback to be called when the opened items change.
*/
onToggle?: AccordionToggleEventHandler;
onToggle?: AccordionToggleEventHandler<Value>;

/**
* Controls the state of the panel.
*/
openItems?: AccordionItemValue | AccordionItemValue[];
openItems?: Value | Value[];
};

export type AccordionState = ComponentState<AccordionSlots> & AccordionContextValue;
export type AccordionState<Value = AccordionItemValue> = ComponentState<AccordionSlots> & AccordionContextValue<Value>;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,3 @@ export * from './renderAccordion';
export * from './useAccordion';
export * from './useAccordionStyles.styles';
export * from './useAccordionContextValues';
export * from './AccordionContext';
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,7 +16,7 @@ export const renderAccordion_unstable = (state: AccordionState, contextValues: A

return (
<slots.root {...slotProps.root}>
<AccordionContext.Provider value={contextValues.accordion}>{slotProps.root.children}</AccordionContext.Provider>
<AccordionProvider value={contextValues.accordion}>{slotProps.root.children}</AccordionProvider>
</slots.root>
);
};
Loading

0 comments on commit 192666b

Please sign in to comment.