Skip to content

Commit

Permalink
Rename SegmentedControl props to match selection APIs (#7097)
Browse files Browse the repository at this point in the history
* Rename SegmentedControl props to match selection APIs

* fix chromatic stories

* ts strict

* Add AriaLabelingProps

* Pass value and defaultValue to RadioGroup

* fix merge conflicts
  • Loading branch information
devongovett committed Sep 26, 2024
1 parent 72be76a commit 0b314ea
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 46 deletions.
28 changes: 14 additions & 14 deletions packages/@react-spectrum/s2/chromatic/SegmentedControl.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ export default meta;

export const Example = (args: any) => (
<SegmentedControl {...args} styles={style({width: '[250px]'})}>
<SegmentedControlItem value="day">Day</SegmentedControlItem>
<SegmentedControlItem value="week">Week</SegmentedControlItem>
<SegmentedControlItem value="month">Month</SegmentedControlItem>
<SegmentedControlItem value="year">Year</SegmentedControlItem>
<SegmentedControlItem id="day">Day</SegmentedControlItem>
<SegmentedControlItem id="week">Week</SegmentedControlItem>
<SegmentedControlItem id="month">Month</SegmentedControlItem>
<SegmentedControlItem id="year">Year</SegmentedControlItem>
</SegmentedControl>
);

Expand All @@ -45,9 +45,9 @@ Example.args = {

export const WithIcons = (args: any) => (
<SegmentedControl {...args} styles={style({width: '[400px]'})}>
<SegmentedControlItem value="unordered"><ListBulleted /><Text>Unordered</Text></SegmentedControlItem>
<SegmentedControlItem value="ordered"><ListNumbered /><Text>Ordered</Text></SegmentedControlItem>
<SegmentedControlItem value="task list"><ListMultiSelect /><Text>Task List</Text></SegmentedControlItem>
<SegmentedControlItem id="unordered"><ListBulleted /><Text>Unordered</Text></SegmentedControlItem>
<SegmentedControlItem id="ordered"><ListNumbered /><Text>Ordered</Text></SegmentedControlItem>
<SegmentedControlItem id="task list"><ListMultiSelect /><Text>Task List</Text></SegmentedControlItem>
</SegmentedControl>
);

Expand All @@ -57,9 +57,9 @@ WithIcons.args = {

export const OnlyIcons = (args: any) => (
<SegmentedControl styles={style({maxWidth: 'fit'})} {...args}>
<SegmentedControlItem value="align bottom"><AlignBottom /></SegmentedControlItem>
<SegmentedControlItem value="align center"><AlignCenter /></SegmentedControlItem>
<SegmentedControlItem value="align left"><AlignLeft /></SegmentedControlItem>
<SegmentedControlItem id="align bottom"><AlignBottom /></SegmentedControlItem>
<SegmentedControlItem id="align center"><AlignCenter /></SegmentedControlItem>
<SegmentedControlItem id="align left"><AlignLeft /></SegmentedControlItem>
</SegmentedControl>
);

Expand All @@ -69,10 +69,10 @@ OnlyIcons.args = {

export const CustomWidth = (args: any) => (
<SegmentedControl {...args} styles={style({width: '[400px]'})}>
<SegmentedControlItem value="overview">Overview</SegmentedControlItem>
<SegmentedControlItem value="specs">Specs</SegmentedControlItem>
<SegmentedControlItem value="guidelines">Guidelines</SegmentedControlItem>
<SegmentedControlItem value="accessibility">Accessibility</SegmentedControlItem>
<SegmentedControlItem id="overview">Overview</SegmentedControlItem>
<SegmentedControlItem id="specs">Specs</SegmentedControlItem>
<SegmentedControlItem id="guidelines">Guidelines</SegmentedControlItem>
<SegmentedControlItem id="accessibility">Accessibility</SegmentedControlItem>
</SegmentedControl>
);

Expand Down
46 changes: 28 additions & 18 deletions packages/@react-spectrum/s2/src/SegmentedControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
* governing permissions and limitations under the License.
*/

import {AriaLabelingProps, DOMRef, DOMRefValue, FocusableRef} from '@react-types/shared';
import {centerBaseline} from './CenterBaseline';
import {ContextValue, DEFAULT_SLOT, Provider, TextContext as RACTextContext, Radio, RadioGroup, RadioGroupProps, RadioGroupStateContext, RadioProps} from 'react-aria-components';
import {ContextValue, DEFAULT_SLOT, Provider, TextContext as RACTextContext, Radio, RadioGroup, RadioGroupStateContext, SlotProps} from 'react-aria-components';
import {createContext, forwardRef, ReactNode, RefObject, useCallback, useContext, useRef} from 'react';
import {DOMRef, DOMRefValue, FocusableRef} from '@react-types/shared';
import {focusRing, size, style} from '../style' with {type: 'macro'};
import {getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
import {IconContext} from './Icon';
Expand All @@ -23,7 +23,7 @@ import {useDOMRef, useFocusableRef} from '@react-spectrum/utils';
import {useLayoutEffect} from '@react-aria/utils';
import {useSpectrumContextProps} from './useSpectrumContextProps';

export interface SegmentedControlProps extends Omit<RadioGroupProps, 'isReadOnly' | 'name' | 'isRequired' | 'isInvalid' | 'validate' | 'validationBehavior' | 'children' | 'className' | 'style' | 'aria-label' | 'orientation'>, StyleProps{
export interface SegmentedControlProps extends AriaLabelingProps, StyleProps, SlotProps {
/**
* The content to display in the segmented control.
*/
Expand All @@ -32,16 +32,22 @@ export interface SegmentedControlProps extends Omit<RadioGroupProps, 'isReadOnly
* Whether the segmented control is disabled.
*/
isDisabled?: boolean,
/**
* Defines a string value that labels the current element.
*/
'aria-label': string
/** The id of the currently selected item (controlled). */
selectedKey?: string | null,
/** The id of the initial selected item (uncontrolled). */
defaultSelectedKey?: string,
/** Handler that is called when the selection changes. */
onSelectionChange?: (id: string) => void
}
export interface SegmentedControlItemProps extends Omit<RadioProps, 'children' | 'className' | 'style' | 'onHoverStart' | 'onHoverEnd' | 'onHoverChange'>, StyleProps {
export interface SegmentedControlItemProps extends AriaLabelingProps, StyleProps {
/**
* The content to display in the control item.
* The content to display in the segmented control item.
*/
children: ReactNode
children: ReactNode,
/** The id of the item, matching the value used in SegmentedControl's `selectedKey` prop. */
id: string,
/** Whether the item is disabled or not. */
isDisabled?: boolean
}

export const SegmentedControlContext = createContext<ContextValue<SegmentedControlProps, DOMRefValue<HTMLDivElement>>>(null);
Expand Down Expand Up @@ -135,8 +141,9 @@ const InternalSegmentedControlContext = createContext<InternalSegmentedControlCo
function SegmentedControl(props: SegmentedControlProps, ref: DOMRef<HTMLDivElement>) {
[props, ref] = useSpectrumContextProps(props, ref, SegmentedControlContext);
let {
defaultValue,
value
defaultSelectedKey,
selectedKey,
onSelectionChange
} = props;
let domRef = useDOMRef(ref);

Expand All @@ -148,21 +155,23 @@ function SegmentedControl(props: SegmentedControlProps, ref: DOMRef<HTMLDivEleme
prevRef.current = currentSelectedRef?.current.getBoundingClientRect();
}

if (props.onChange) {
props.onChange(value);
if (onSelectionChange) {
onSelectionChange(value);
}
};

return (
<RadioGroup
{...props}
value={selectedKey}
defaultValue={defaultSelectedKey}
ref={domRef}
orientation="horizontal"
style={props.UNSAFE_style}
onChange={onChange}
className={(props.UNSAFE_className || '') + segmentedControl({size: 'M'}, props.styles)}
aria-label={props['aria-label']}>
<DefaultSelectionTracker defaultValue={defaultValue} value={value} prevRef={prevRef} currentSelectedRef={currentSelectedRef}>
<DefaultSelectionTracker defaultValue={defaultSelectedKey} value={selectedKey} prevRef={prevRef} currentSelectedRef={currentSelectedRef}>
{props.children}
</DefaultSelectionTracker>
</RadioGroup>
Expand Down Expand Up @@ -197,15 +206,15 @@ function SegmentedControlItem(props: SegmentedControlItemProps, ref: FocusableRe
let divRef = useRef<HTMLDivElement>(null);
let {register, prevRef, currentSelectedRef} = useContext(InternalSegmentedControlContext);
let state = useContext(RadioGroupStateContext);
let isSelected = props.value === state?.selectedValue;
let isSelected = props.id === state?.selectedValue;
// do not apply animation if a user has the prefers-reduced-motion setting
let isReduced = false;
if (window?.matchMedia) {
isReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
}

useLayoutEffect(() => {
register?.(props.value);
register?.(props.id);
}, []);

useLayoutEffect(() => {
Expand All @@ -231,7 +240,8 @@ function SegmentedControlItem(props: SegmentedControlItemProps, ref: FocusableRe

return (
<Radio
{...props}
{...props}
value={props.id}
ref={domRef}
inputRef={inputRef}
style={props.UNSAFE_style}
Expand Down
28 changes: 14 additions & 14 deletions packages/@react-spectrum/s2/stories/SegmentedControl.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ export default meta;

export const Example = (args: any) => (
<SegmentedControl {...args} styles={style({width: '[250px]'})}>
<SegmentedControlItem value="day">Day</SegmentedControlItem>
<SegmentedControlItem value="week">Week</SegmentedControlItem>
<SegmentedControlItem value="month">Month</SegmentedControlItem>
<SegmentedControlItem value="year">Year</SegmentedControlItem>
<SegmentedControlItem id="day">Day</SegmentedControlItem>
<SegmentedControlItem id="week">Week</SegmentedControlItem>
<SegmentedControlItem id="month">Month</SegmentedControlItem>
<SegmentedControlItem id="year">Year</SegmentedControlItem>
</SegmentedControl>
);

Expand All @@ -47,9 +47,9 @@ Example.args = {

export const WithIcons = (args: any) => (
<SegmentedControl {...args} styles={style({width: '[400px]'})}>
<SegmentedControlItem value="unordered"><ListBulleted /><Text>Unordered</Text></SegmentedControlItem>
<SegmentedControlItem value="ordered"><ListNumbered /><Text>Ordered</Text></SegmentedControlItem>
<SegmentedControlItem value="task list"><ListMultiSelect /><Text>Task List</Text></SegmentedControlItem>
<SegmentedControlItem id="unordered"><ListBulleted /><Text>Unordered</Text></SegmentedControlItem>
<SegmentedControlItem id="ordered"><ListNumbered /><Text>Ordered</Text></SegmentedControlItem>
<SegmentedControlItem id="task list"><ListMultiSelect /><Text>Task List</Text></SegmentedControlItem>
</SegmentedControl>
);

Expand All @@ -59,9 +59,9 @@ WithIcons.args = {

export const OnlyIcons = (args: any) => (
<SegmentedControl {...args}>
<SegmentedControlItem value="align bottom"><AlignBottom /></SegmentedControlItem>
<SegmentedControlItem value="align center"><AlignCenter /></SegmentedControlItem>
<SegmentedControlItem value="align left"><AlignLeft /></SegmentedControlItem>
<SegmentedControlItem id="align bottom"><AlignBottom /></SegmentedControlItem>
<SegmentedControlItem id="align center"><AlignCenter /></SegmentedControlItem>
<SegmentedControlItem id="align left"><AlignLeft /></SegmentedControlItem>
</SegmentedControl>
);

Expand All @@ -71,10 +71,10 @@ OnlyIcons.args = {

export const CustomWidth = (args: any) => (
<SegmentedControl {...args} styles={style({width: '[400px]'})}>
<SegmentedControlItem value="overview">Overview</SegmentedControlItem>
<SegmentedControlItem value="specs">Specs</SegmentedControlItem>
<SegmentedControlItem value="guidelines">Guidelines</SegmentedControlItem>
<SegmentedControlItem value="accessibility">Accessibility</SegmentedControlItem>
<SegmentedControlItem id="overview">Overview</SegmentedControlItem>
<SegmentedControlItem id="specs">Specs</SegmentedControlItem>
<SegmentedControlItem id="guidelines">Guidelines</SegmentedControlItem>
<SegmentedControlItem id="accessibility">Accessibility</SegmentedControlItem>
</SegmentedControl>
);

Expand Down

1 comment on commit 0b314ea

@rspbot
Copy link

@rspbot rspbot commented on 0b314ea Sep 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.