Skip to content

Commit 59f23d0

Browse files
mj12albertatomiks
authored andcommitted
[Toggle][ToggleGroup] Use useRenderElement (mui#1707)
1 parent efd090a commit 59f23d0

File tree

7 files changed

+171
-309
lines changed

7 files changed

+171
-309
lines changed

docs/reference/generated/toggle-group.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
"props": {
55
"defaultValue": {
66
"type": "any[]",
7-
"description": "The open state of the ToggleGroup represented by an array of\nthe values of all pressed `<ToggleGroup.Item/>`s.\nThis is the uncontrolled counterpart of `value`."
7+
"description": "The open state of the toggle group represented by an array of\nthe values of all pressed toggle buttons.\nThis is the uncontrolled counterpart of `value`."
88
},
99
"value": {
1010
"type": "any[]",
11-
"description": "The open state of the ToggleGroup represented by an array of\nthe values of all pressed `<ToggleGroup.Item/>`s\nThis is the controlled counterpart of `defaultValue`."
11+
"description": "The open state of the toggle group represented by an array of\nthe values of all pressed toggle buttons.\nThis is the controlled counterpart of `defaultValue`."
1212
},
1313
"onValueChange": {
1414
"type": "((groupValue: any[], event: Event) => void)",
15-
"description": "Callback fired when the pressed states of the ToggleGroup changes."
15+
"description": "Callback fired when the pressed states of the toggle group changes."
1616
},
1717
"toggleMultiple": {
1818
"type": "boolean",
@@ -22,15 +22,15 @@
2222
"disabled": {
2323
"type": "boolean",
2424
"default": "false",
25-
"description": "Whether the component should ignore user interaction."
25+
"description": "Whether the toggle group should ignore user interaction."
2626
},
2727
"loop": {
2828
"type": "boolean",
2929
"default": "true",
3030
"description": "Whether to loop keyboard focus back to the first item\nwhen the end of the list is reached while using the arrow keys."
3131
},
3232
"orientation": {
33-
"type": "ToggleGroupOrientation",
33+
"type": "Orientation",
3434
"default": "'horizontal'"
3535
},
3636
"className": {

docs/reference/generated/toggle.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44
"props": {
55
"value": {
66
"type": "string",
7-
"description": "A unique string that identifies the component when used\ninside a ToggleGroup."
7+
"description": "A unique string that identifies the toggle when used\ninside a toggle group."
88
},
99
"defaultPressed": {
1010
"type": "boolean",
1111
"default": "false",
12-
"description": "The default pressed state. Use when the component is not controlled."
12+
"description": "Whether the toggle button is currently pressed.\nThis is the uncontrolled counterpart of `pressed`."
1313
},
1414
"pressed": {
1515
"type": "boolean",
16-
"description": "Whether the toggle button is currently active."
16+
"description": "Whether the toggle button is currently pressed.\nThis is the controlled counterpart of `defaultPressed`."
1717
},
1818
"onPressedChange": {
1919
"type": "((pressed: boolean, event: Event) => void)",

packages/react/src/toggle-group/ToggleGroup.tsx

Lines changed: 78 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
'use client';
22
import * as React from 'react';
33
import PropTypes from 'prop-types';
4-
import { NOOP } from '../utils/noop';
5-
import { useComponentRenderer } from '../utils/useComponentRenderer';
6-
import type { BaseUIComponentProps } from '../utils/types';
4+
import { useRenderElement } from '../utils/useRenderElement';
5+
import type { BaseUIComponentProps, Orientation } from '../utils/types';
76
import { CompositeRoot } from '../composite/root/CompositeRoot';
7+
import { useControlled } from '../utils/useControlled';
88
import { useDirection } from '../direction-provider/DirectionContext';
9+
import { useEventCallback } from '../utils/useEventCallback';
910
import { useToolbarRootContext } from '../toolbar/root/ToolbarRootContext';
10-
import { useToggleGroup } from './useToggleGroup';
1111
import { ToggleGroupContext } from './ToggleGroupContext';
1212
import { ToggleGroupDataAttributes } from './ToggleGroupDataAttributes';
1313

@@ -26,21 +26,21 @@ const customStyleHookMapping = {
2626
* Documentation: [Base UI Toggle Group](https://base-ui.com/react/components/toggle-group)
2727
*/
2828
const ToggleGroup = React.forwardRef(function ToggleGroup(
29-
props: ToggleGroup.Props,
29+
componentProps: ToggleGroup.Props,
3030
forwardedRef: React.ForwardedRef<HTMLDivElement>,
3131
) {
3232
const {
3333
defaultValue: defaultValueProp,
3434
disabled: disabledProp = false,
3535
loop = true,
36-
onValueChange: onValueChangeProp,
36+
onValueChange,
3737
orientation = 'horizontal',
3838
toggleMultiple = false,
3939
value: valueProp,
4040
className,
4141
render,
42-
...otherProps
43-
} = props;
42+
...elementProps
43+
} = componentProps;
4444

4545
const direction = useDirection();
4646

@@ -54,12 +54,31 @@ const ToggleGroup = React.forwardRef(function ToggleGroup(
5454
return undefined;
5555
}, [valueProp, defaultValueProp]);
5656

57-
const { getRootProps, disabled, setGroupValue, value } = useToggleGroup({
58-
value: valueProp,
59-
defaultValue,
60-
disabled: (toolbarContext?.disabled ?? false) || disabledProp,
61-
toggleMultiple,
62-
onValueChange: onValueChangeProp ?? NOOP,
57+
const disabled = (toolbarContext?.disabled ?? false) || disabledProp;
58+
59+
const [groupValue, setValueState] = useControlled({
60+
controlled: valueProp,
61+
default: defaultValue,
62+
name: 'ToggleGroup',
63+
state: 'value',
64+
});
65+
66+
const setGroupValue = useEventCallback((newValue: string, nextPressed: boolean, event: Event) => {
67+
let newGroupValue: any[] | undefined;
68+
if (toggleMultiple) {
69+
newGroupValue = groupValue.slice();
70+
if (nextPressed) {
71+
newGroupValue.push(newValue);
72+
} else {
73+
newGroupValue.splice(groupValue.indexOf(newValue), 1);
74+
}
75+
} else {
76+
newGroupValue = nextPressed ? [newValue] : [];
77+
}
78+
if (Array.isArray(newGroupValue)) {
79+
setValueState(newGroupValue);
80+
onValueChange?.(newGroupValue, event);
81+
}
6382
});
6483

6584
const state: ToggleGroup.State = React.useMemo(
@@ -72,19 +91,21 @@ const ToggleGroup = React.forwardRef(function ToggleGroup(
7291
disabled,
7392
orientation,
7493
setGroupValue,
75-
value,
94+
value: groupValue,
7695
}),
77-
[disabled, orientation, setGroupValue, value],
96+
[disabled, orientation, setGroupValue, groupValue],
7897
);
7998

80-
const { renderElement } = useComponentRenderer({
81-
propGetter: getRootProps,
82-
render: render ?? 'div',
83-
ref: forwardedRef,
99+
const renderElement = useRenderElement('div', componentProps, {
84100
state,
85-
className,
101+
ref: forwardedRef,
102+
props: [
103+
{
104+
role: 'group',
105+
},
106+
elementProps,
107+
],
86108
customStyleHookMapping,
87-
extraProps: otherProps,
88109
});
89110

90111
return (
@@ -100,8 +121,6 @@ const ToggleGroup = React.forwardRef(function ToggleGroup(
100121

101122
export { ToggleGroup };
102123

103-
export type ToggleGroupOrientation = 'horizontal' | 'vertical';
104-
105124
namespace ToggleGroup {
106125
export interface State {
107126
/**
@@ -111,24 +130,48 @@ namespace ToggleGroup {
111130
multiple: boolean;
112131
}
113132

114-
export interface Props
115-
extends Partial<useToggleGroup.Parameters>,
116-
Omit<BaseUIComponentProps<'div', State>, 'defaultValue'> {
133+
export interface Props extends BaseUIComponentProps<'div', State> {
117134
/**
118-
* Whether the component should ignore user interaction.
135+
* The open state of the toggle group represented by an array of
136+
* the values of all pressed toggle buttons.
137+
* This is the controlled counterpart of `defaultValue`.
138+
*/
139+
value?: readonly any[];
140+
/**
141+
* The open state of the toggle group represented by an array of
142+
* the values of all pressed toggle buttons.
143+
* This is the uncontrolled counterpart of `value`.
144+
*/
145+
defaultValue?: readonly any[];
146+
/**
147+
* Callback fired when the pressed states of the toggle group changes.
148+
*
149+
* @param {any[]} groupValue An array of the `value`s of all the pressed items.
150+
* @param {Event} event The corresponding event that initiated the change.
151+
*/
152+
onValueChange?: (groupValue: any[], event: Event) => void;
153+
/**
154+
* Whether the toggle group should ignore user interaction.
119155
* @default false
120156
*/
121157
disabled?: boolean;
122158
/**
123159
* @default 'horizontal'
124160
*/
125-
orientation?: ToggleGroupOrientation;
161+
orientation?: Orientation;
126162
/**
127163
* Whether to loop keyboard focus back to the first item
128164
* when the end of the list is reached while using the arrow keys.
129165
* @default true
130166
*/
131167
loop?: boolean;
168+
/**
169+
* When `false` only one item in the group can be pressed. If any item in
170+
* the group becomes pressed, the others will become unpressed.
171+
* When `true` multiple items can be pressed.
172+
* @default false
173+
*/
174+
toggleMultiple?: boolean;
132175
}
133176
}
134177

@@ -147,13 +190,13 @@ ToggleGroup.propTypes /* remove-proptypes */ = {
147190
*/
148191
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
149192
/**
150-
* The open state of the ToggleGroup represented by an array of
151-
* the values of all pressed `<ToggleGroup.Item/>`s.
193+
* The open state of the toggle group represented by an array of
194+
* the values of all pressed toggle buttons.
152195
* This is the uncontrolled counterpart of `value`.
153196
*/
154197
defaultValue: PropTypes.array,
155198
/**
156-
* Whether the component should ignore user interaction.
199+
* Whether the toggle group should ignore user interaction.
157200
* @default false
158201
*/
159202
disabled: PropTypes.bool,
@@ -164,7 +207,7 @@ ToggleGroup.propTypes /* remove-proptypes */ = {
164207
*/
165208
loop: PropTypes.bool,
166209
/**
167-
* Callback fired when the pressed states of the ToggleGroup changes.
210+
* Callback fired when the pressed states of the toggle group changes.
168211
*
169212
* @param {any[]} groupValue An array of the `value`s of all the pressed items.
170213
* @param {Event} event The corresponding event that initiated the change.
@@ -189,8 +232,8 @@ ToggleGroup.propTypes /* remove-proptypes */ = {
189232
*/
190233
toggleMultiple: PropTypes.bool,
191234
/**
192-
* The open state of the ToggleGroup represented by an array of
193-
* the values of all pressed `<ToggleGroup.Item/>`s
235+
* The open state of the toggle group represented by an array of
236+
* the values of all pressed toggle buttons.
194237
* This is the controlled counterpart of `defaultValue`.
195238
*/
196239
value: PropTypes.array,

packages/react/src/toggle-group/ToggleGroupContext.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
'use client';
22
import * as React from 'react';
3-
import type { ToggleGroupOrientation } from './ToggleGroup';
3+
import type { Orientation } from '../utils/types';
44

55
export interface ToggleGroupContext {
66
value: readonly any[];
77
setGroupValue: (newValue: string, nextPressed: boolean, event: Event) => void;
88
disabled: boolean;
9-
orientation: ToggleGroupOrientation;
9+
orientation: Orientation;
1010
}
1111

1212
export const ToggleGroupContext = React.createContext<ToggleGroupContext | undefined>(undefined);

packages/react/src/toggle-group/useToggleGroup.ts

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

0 commit comments

Comments
 (0)