Skip to content

Commit 192666b

Browse files
authored
bugfix: ensures controlled state works properly + (#28665)
makes AccordionItemValue generic + adds story for controlled state + simplifies access to requestToggle by context + reorganizes context into a separate folder
1 parent 141424a commit 192666b

33 files changed

+291
-219
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "minor",
3+
"comment": "feat: make AccordionItemValue generic",
4+
"packageName": "@fluentui/react-accordion",
5+
"email": "[email protected]",
6+
"dependentChangeType": "patch"
7+
}

packages/react-components/react-accordion/etc/react-accordion.api.md

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@ import type { Slot } from '@fluentui/react-utilities';
1919
import type { SlotClassNames } from '@fluentui/react-utilities';
2020

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

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

2727
// @public (undocumented)
28-
export type AccordionContextValue = Required<Pick<AccordionProps, 'collapsible'>> & Pick<AccordionProps, 'navigation'> & {
28+
export type AccordionContextValue<Value = AccordionItemValue> = {
2929
openItems: AccordionItemValue[];
30-
requestToggle: (event: AccordionToggleEvent, data: AccordionToggleData) => void;
30+
requestToggle: (data: AccordionRequestToggleData<Value>) => void;
31+
collapsible: boolean;
32+
multiple: boolean;
33+
navigation: 'linear' | 'circular' | undefined;
3134
};
3235

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

4447
// @public (undocumented)
45-
export const AccordionHeaderContextProvider: React_2.Provider<AccordionHeaderContextValue | undefined>;
46-
47-
// @public (undocumented)
48-
export type AccordionHeaderContextValue = Required<Pick<AccordionHeaderProps, 'expandIconPosition' | 'size'>> & {
48+
export type AccordionHeaderContextValue = {
4949
disabled: boolean;
5050
open: boolean;
51+
expandIconPosition: AccordionHeaderExpandIconPosition;
52+
size: AccordionHeaderSize;
5153
};
5254

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

70+
// @public (undocumented)
71+
export const AccordionHeaderProvider: React_2.Provider<AccordionHeaderContextValue>;
72+
6873
// @public (undocumented)
6974
export type AccordionHeaderSize = 'small' | 'medium' | 'large' | 'extra-large';
7075

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

9196
// @public (undocumented)
92-
export type AccordionItemContextValue = Required<Pick<AccordionItemProps, 'disabled'>> & {
93-
onHeaderClick(ev: React_2.MouseEvent | React_2.KeyboardEvent): void;
97+
export type AccordionItemContextValue<Value = AccordionItemValue> = {
9498
open: boolean;
99+
disabled: boolean;
100+
value: Value;
95101
};
96102

97103
// @public (undocumented)
98-
export type AccordionItemContextValues = {
99-
accordionItem: AccordionItemContextValue;
104+
export type AccordionItemContextValues<Value = AccordionItemValue> = {
105+
accordionItem: AccordionItemContextValue<Value>;
100106
};
101107

102108
// @public (undocumented)
103-
export type AccordionItemProps = ComponentProps<AccordionItemSlots> & {
109+
export type AccordionItemProps<Value = AccordionItemValue> = ComponentProps<AccordionItemSlots> & {
104110
disabled?: boolean;
105-
value: AccordionItemValue;
111+
value: Value;
106112
};
107113

108114
// @public (undocumented)
109-
export const AccordionItemProvider: React_2.Provider<AccordionItemContextValue>;
115+
export const AccordionItemProvider: React_2.Provider<AccordionItemContextValue<unknown>>;
110116

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

116122
// @public (undocumented)
117-
export type AccordionItemState = ComponentState<AccordionItemSlots> & AccordionItemContextValue;
123+
export type AccordionItemState<Value = AccordionItemValue> = ComponentState<AccordionItemSlots> & AccordionItemContextValue<Value>;
118124

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

141147
// @public (undocumented)
142-
export type AccordionProps = ComponentProps<AccordionSlots> & {
143-
defaultOpenItems?: AccordionItemValue | AccordionItemValue[];
148+
export type AccordionProps<Value = AccordionItemValue> = ComponentProps<AccordionSlots> & {
149+
defaultOpenItems?: Value | Value[];
144150
collapsible?: boolean;
145151
multiple?: boolean;
146152
navigation?: 'linear' | 'circular';
147-
onToggle?: AccordionToggleEventHandler;
148-
openItems?: AccordionItemValue | AccordionItemValue[];
153+
onToggle?: AccordionToggleEventHandler<Value>;
154+
openItems?: Value | Value[];
149155
};
150156

151157
// @public (undocumented)
152-
export const AccordionProvider: Provider<AccordionContextValue> & FC<ProviderProps<AccordionContextValue>>;
158+
export const AccordionProvider: Provider<AccordionContextValue<unknown>> & FC<ProviderProps<AccordionContextValue<unknown>>>;
153159

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

159165
// @public (undocumented)
160-
export type AccordionState = ComponentState<AccordionSlots> & AccordionContextValue;
166+
export type AccordionState<Value = AccordionItemValue> = ComponentState<AccordionSlots> & AccordionContextValue<Value>;
161167

162168
// @public (undocumented)
163-
export type AccordionToggleData = {
164-
value: AccordionItemValue;
169+
export type AccordionToggleData<Value = AccordionItemValue> = {
170+
value: Value;
171+
openItems: Value[];
165172
};
166173

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

170177
// @public (undocumented)
171-
export type AccordionToggleEventHandler = (event: AccordionToggleEvent, data: AccordionToggleData) => void;
178+
export type AccordionToggleEventHandler<Value = AccordionItemValue> = (event: AccordionToggleEvent, data: AccordionToggleData<Value>) => void;
172179

173180
// @public
174181
export const renderAccordion_unstable: (state: AccordionState, contextValues: AccordionContextValues) => JSX.Element;
@@ -183,10 +190,10 @@ export const renderAccordionItem_unstable: (state: AccordionItemState, contextVa
183190
export const renderAccordionPanel_unstable: (state: AccordionPanelState) => JSX.Element | null;
184191

185192
// @public
186-
export const useAccordion_unstable: (props: AccordionProps, ref: React_2.Ref<HTMLElement>) => AccordionState;
193+
export const useAccordion_unstable: <Value = unknown>(props: AccordionProps<Value>, ref: React_2.Ref<HTMLElement>) => AccordionState<Value>;
187194

188195
// @public (undocumented)
189-
export const useAccordionContext_unstable: <T>(selector: ContextSelector<AccordionContextValue, T>) => T;
196+
export const useAccordionContext_unstable: <T>(selector: ContextSelector<AccordionContextValue<unknown>, T>) => T;
190197

191198
// @public (undocumented)
192199
export function useAccordionContextValues_unstable(state: AccordionState): AccordionContextValues;
@@ -195,12 +202,7 @@ export function useAccordionContextValues_unstable(state: AccordionState): Accor
195202
export const useAccordionHeader_unstable: (props: AccordionHeaderProps, ref: React_2.Ref<HTMLElement>) => AccordionHeaderState;
196203

197204
// @public (undocumented)
198-
export const useAccordionHeaderContext_unstable: () => {
199-
open: boolean;
200-
disabled: boolean;
201-
size: string;
202-
expandIconPosition: string;
203-
};
205+
export const useAccordionHeaderContext_unstable: () => AccordionHeaderContextValue;
204206

205207
// @public (undocumented)
206208
export function useAccordionHeaderContextValues_unstable(state: AccordionHeaderState): AccordionHeaderContextValues;
@@ -212,13 +214,13 @@ export const useAccordionHeaderStyles_unstable: (state: AccordionHeaderState) =>
212214
export const useAccordionItem_unstable: (props: AccordionItemProps, ref: React_2.Ref<HTMLElement>) => AccordionItemState;
213215

214216
// @public (undocumented)
215-
export const useAccordionItemContext_unstable: () => AccordionItemContextValue;
217+
export const useAccordionItemContext_unstable: () => AccordionItemContextValue<unknown>;
216218

217219
// @public (undocumented)
218220
export function useAccordionItemContextValues_unstable(state: AccordionItemState): AccordionItemContextValues;
219221

220222
// @public (undocumented)
221-
export const useAccordionItemStyles_unstable: (state: AccordionItemState) => AccordionItemState;
223+
export const useAccordionItemStyles_unstable: (state: AccordionItemState) => AccordionItemState<unknown>;
222224

223225
// @public
224226
export const useAccordionPanel_unstable: (props: AccordionPanelProps, ref: React_2.Ref<HTMLElement>) => AccordionPanelState;
@@ -227,7 +229,7 @@ export const useAccordionPanel_unstable: (props: AccordionPanelProps, ref: React
227229
export const useAccordionPanelStyles_unstable: (state: AccordionPanelState) => AccordionPanelState;
228230

229231
// @public (undocumented)
230-
export const useAccordionStyles_unstable: (state: AccordionState) => AccordionState;
232+
export const useAccordionStyles_unstable: (state: AccordionState) => AccordionState<unknown>;
231233

232234
// (No @packageDocumentation comment for this package)
233235

packages/react-components/react-accordion/src/components/Accordion/Accordion.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ describe('Accordion', () => {
88
Component: Accordion,
99
displayName: 'Accordion',
1010
// Accordion does not have own styles
11-
disabledTests: ['make-styles-overrides-win'],
11+
disabledTests: ['make-styles-overrides-win', 'consistent-callback-args'],
1212
});
1313

1414
/**

packages/react-components/react-accordion/src/components/Accordion/Accordion.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import type { ForwardRefComponent } from '@fluentui/react-utilities';
1010
/**
1111
* Define a styled Accordion, using the `useAccordion_unstable` and `useAccordionStyles_unstable` hooks.
1212
*/
13-
export const Accordion: ForwardRefComponent<AccordionProps> = React.forwardRef<HTMLDivElement, AccordionProps>(
14-
(props, ref) => {
13+
export const Accordion: ForwardRefComponent<AccordionProps> & (<TItem>(props: AccordionProps<TItem>) => JSX.Element) =
14+
React.forwardRef<HTMLDivElement, AccordionProps>((props, ref) => {
1515
const state = useAccordion_unstable(props, ref);
1616
const contextValues = useAccordionContextValues_unstable(state);
1717

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

2222
return renderAccordion_unstable(state, contextValues);
23-
},
24-
);
23+
}) as ForwardRefComponent<AccordionProps> & (<TItem>(props: AccordionProps<TItem>) => JSX.Element);
2524

2625
Accordion.displayName = 'Accordion';
Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
import * as React from 'react';
22
import type { ComponentProps, ComponentState, Slot } from '@fluentui/react-utilities';
3+
import { AccordionContextValue } from '../../contexts/accordion';
34
import type { AccordionItemValue } from '../AccordionItem/AccordionItem.types';
45

56
export type AccordionIndex = number | number[];
67

78
export type AccordionToggleEvent<E = HTMLElement> = React.MouseEvent<E> | React.KeyboardEvent<E>;
89

9-
export type AccordionToggleEventHandler = (event: AccordionToggleEvent, data: AccordionToggleData) => void;
10-
11-
export type AccordionContextValue = Required<Pick<AccordionProps, 'collapsible'>> &
12-
Pick<AccordionProps, 'navigation'> & {
13-
/**
14-
* The list of opened panels by index
15-
*/
16-
openItems: AccordionItemValue[];
17-
/**
18-
* Callback used by AccordionItem to request a change on it's own opened state
19-
* Should be used to toggle AccordionItem
20-
*/
21-
requestToggle: (event: AccordionToggleEvent, data: AccordionToggleData) => void;
22-
};
10+
export type AccordionToggleEventHandler<Value = AccordionItemValue> = (
11+
event: AccordionToggleEvent,
12+
data: AccordionToggleData<Value>,
13+
) => void;
2314

2415
export type AccordionContextValues = {
2516
accordion: AccordionContextValue;
@@ -29,15 +20,16 @@ export type AccordionSlots = {
2920
root: NonNullable<Slot<'div'>>;
3021
};
3122

32-
export type AccordionToggleData = {
33-
value: AccordionItemValue;
23+
export type AccordionToggleData<Value = AccordionItemValue> = {
24+
value: Value;
25+
openItems: Value[];
3426
};
3527

36-
export type AccordionProps = ComponentProps<AccordionSlots> & {
28+
export type AccordionProps<Value = AccordionItemValue> = ComponentProps<AccordionSlots> & {
3729
/**
3830
* Default value for the uncontrolled state of the panel.
3931
*/
40-
defaultOpenItems?: AccordionItemValue | AccordionItemValue[];
32+
defaultOpenItems?: Value | Value[];
4133

4234
/**
4335
* Indicates if Accordion support multiple Panels closed at the same time.
@@ -57,12 +49,12 @@ export type AccordionProps = ComponentProps<AccordionSlots> & {
5749
/**
5850
* Callback to be called when the opened items change.
5951
*/
60-
onToggle?: AccordionToggleEventHandler;
52+
onToggle?: AccordionToggleEventHandler<Value>;
6153

6254
/**
6355
* Controls the state of the panel.
6456
*/
65-
openItems?: AccordionItemValue | AccordionItemValue[];
57+
openItems?: Value | Value[];
6658
};
6759

68-
export type AccordionState = ComponentState<AccordionSlots> & AccordionContextValue;
60+
export type AccordionState<Value = AccordionItemValue> = ComponentState<AccordionSlots> & AccordionContextValue<Value>;

packages/react-components/react-accordion/src/components/Accordion/AccordionContext.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

packages/react-components/react-accordion/src/components/Accordion/index.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@ export * from './renderAccordion';
44
export * from './useAccordion';
55
export * from './useAccordionStyles.styles';
66
export * from './useAccordionContextValues';
7-
export * from './AccordionContext';

packages/react-components/react-accordion/src/components/Accordion/renderAccordion.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { createElement } from '@fluentui/react-jsx-runtime';
55

66
import { getSlotsNext } from '@fluentui/react-utilities';
77

8-
import { AccordionContext } from './AccordionContext';
98
import type { AccordionState, AccordionSlots, AccordionContextValues } from './Accordion.types';
9+
import { AccordionProvider } from '../../contexts/accordion';
1010

1111
/**
1212
* Function that renders the final JSX of the component
@@ -16,7 +16,7 @@ export const renderAccordion_unstable = (state: AccordionState, contextValues: A
1616

1717
return (
1818
<slots.root {...slotProps.root}>
19-
<AccordionContext.Provider value={contextValues.accordion}>{slotProps.root.children}</AccordionContext.Provider>
19+
<AccordionProvider value={contextValues.accordion}>{slotProps.root.children}</AccordionProvider>
2020
</slots.root>
2121
);
2222
};

0 commit comments

Comments
 (0)