Skip to content

Commit 8d352ac

Browse files
committed
Refactor toggle group
1 parent 668012e commit 8d352ac

File tree

4 files changed

+76
-144
lines changed

4 files changed

+76
-144
lines changed

docs/reference/generated/toggle-group.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
"props": {
55
"defaultValue": {
66
"type": "array",
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 ToggleGroup represented by an array of\nthe values of all pressed toggle buttons.\nThis is the uncontrolled counterpart of `value`."
88
},
99
"value": {
1010
"type": "array",
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 ToggleGroup represented by an array of\nthe values of all pressed toggle buttons\nThis is the controlled counterpart of `defaultValue`."
1212
},
1313
"onValueChange": {
1414
"type": "function(groupValue: Array<any>, event: Event) => void",
15+
"required": true,
1516
"description": "Callback fired when the pressed states of the ToggleGroup changes."
1617
},
1718
"toggleMultiple": {

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

Lines changed: 71 additions & 28 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

@@ -33,7 +33,7 @@ const ToggleGroup = React.forwardRef(function ToggleGroup(
3333
defaultValue: defaultValueProp,
3434
disabled: disabledProp = false,
3535
loop = true,
36-
onValueChange: onValueChangeProp,
36+
onValueChange,
3737
orientation = 'horizontal',
3838
toggleMultiple = false,
3939
value: valueProp,
@@ -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', props, {
84100
state,
85-
className,
101+
ref: forwardedRef,
102+
props: [
103+
{
104+
role: 'group',
105+
},
106+
otherProps,
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,9 +130,26 @@ 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 Omit<BaseUIComponentProps<'div', State>, 'defaultValue'> {
134+
/**
135+
* The open state of the ToggleGroup 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 ToggleGroup 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 ToggleGroup 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;
117153
/**
118154
* Whether the component should ignore user interaction.
119155
* @default false
@@ -122,13 +158,20 @@ namespace ToggleGroup {
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

@@ -148,7 +191,7 @@ ToggleGroup.propTypes /* remove-proptypes */ = {
148191
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
149192
/**
150193
* The open state of the ToggleGroup represented by an array of
151-
* the values of all pressed `<ToggleGroup.Item/>`s.
194+
* the values of all pressed toggle buttons.
152195
* This is the uncontrolled counterpart of `value`.
153196
*/
154197
defaultValue: PropTypes.array,
@@ -169,7 +212,7 @@ ToggleGroup.propTypes /* remove-proptypes */ = {
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.
171214
*/
172-
onValueChange: PropTypes.func,
215+
onValueChange: PropTypes.func.isRequired,
173216
/**
174217
* @default 'horizontal'
175218
*/
@@ -190,7 +233,7 @@ ToggleGroup.propTypes /* remove-proptypes */ = {
190233
toggleMultiple: PropTypes.bool,
191234
/**
192235
* The open state of the ToggleGroup represented by an array of
193-
* the values of all pressed `<ToggleGroup.Item/>`s
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)