feat: Add SliderFill component and improve default SliderOutput formatting#10021
feat: Add SliderFill component and improve default SliderOutput formatting#10021devongovett wants to merge 5 commits intomainfrom
Conversation
# Conflicts: # packages/react-aria-components/exports/index.ts
|
Build successful! 🎉 |
|
Build successful! 🎉 |
## API Changes
react-aria-components/react-aria-components:ColorSliderState ColorSliderState {
decrementThumb: (number, number) => void
defaultValues: Array<number>
focusedThumb: number | undefined
getDisplayColor: () => Color
- getFormattedValue: (number) => string
+ getFormattedValue: (number | Array<number>) => string
getPercentValue: (number) => number
getThumbMaxValue: (number) => number
getThumbMinValue: (number) => number
getThumbPercent: (number) => number
getThumbValueLabel: (number) => string
getValuePercent: (number) => number
incrementThumb: (number, number) => void
isDisabled: boolean
isDragging: boolean
isThumbDragging: (number) => boolean
isThumbEditable: (number) => boolean
orientation: Orientation
pageSize: number
setFocusedThumb: (number | undefined) => void
setThumbDragging: (number, boolean) => void
setThumbEditable: (number, boolean) => void
setThumbPercent: (number, number) => void
setThumbValue: (number, number) => void
setValue: (string | Color) => void
step: number
value: Color
values: Array<number>
}/react-aria-components:SliderState SliderState {
decrementThumb: (number, number) => void
defaultValues: Array<number>
focusedThumb: number | undefined
- getFormattedValue: (number) => string
+ getFormattedValue: (number | Array<number>) => string
getPercentValue: (number) => number
getThumbMaxValue: (number) => number
getThumbMinValue: (number) => number
getThumbPercent: (number) => number
getThumbValueLabel: (number) => string
getValuePercent: (number) => number
incrementThumb: (number, number) => void
isDisabled: boolean
isThumbDragging: (number) => boolean
isThumbEditable: (number) => boolean
orientation: Orientation
pageSize: number
setFocusedThumb: (number | undefined) => void
setThumbDragging: (number, boolean) => void
setThumbEditable: (number, boolean) => void
setThumbPercent: (number, number) => void
setThumbValue: (number, number) => void
step: number
values: Array<number>
}/react-aria-components:SliderFill+SliderFill {
+ children?: ChildrenOrFunction<SliderFillRenderProps>
+ className?: ClassNameOrFunction<SliderFillRenderProps> = 'react-aria-SliderFill'
+ offset?: number = 0
+ onHoverChange?: (boolean) => void
+ onHoverEnd?: (HoverEvent) => void
+ onHoverStart?: (HoverEvent) => void
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, SliderFillRenderProps>
+ style?: StyleOrFunction<SliderFillRenderProps>
+}/react-aria-components:SliderFillContext+SliderFillContext {
+ UNTYPED
+}/react-aria-components:SliderFillProps+SliderFillProps {
+ children?: ChildrenOrFunction<SliderFillRenderProps>
+ className?: ClassNameOrFunction<SliderFillRenderProps> = 'react-aria-SliderFill'
+ offset?: number = 0
+ onHoverChange?: (boolean) => void
+ onHoverEnd?: (HoverEvent) => void
+ onHoverStart?: (HoverEvent) => void
+ render?: DOMRenderFunction<keyof React.JSX.IntrinsicElements, SliderFillRenderProps>
+ style?: StyleOrFunction<SliderFillRenderProps>
+}/react-aria-components:SliderFillRenderProps+SliderFillRenderProps {
+ isDisabled: boolean
+ isHovered: boolean
+ orientation: Orientation
+ state: SliderState
+}@react-stately/color/@react-stately/color:ColorSliderState ColorSliderState {
decrementThumb: (number, number) => void
defaultValues: Array<number>
focusedThumb: number | undefined
getDisplayColor: () => Color
- getFormattedValue: (number) => string
+ getFormattedValue: (number | Array<number>) => string
getPercentValue: (number) => number
getThumbMaxValue: (number) => number
getThumbMinValue: (number) => number
getThumbPercent: (number) => number
getThumbValueLabel: (number) => string
getValuePercent: (number) => number
incrementThumb: (number, number) => void
isDisabled: boolean
isDragging: boolean
isThumbDragging: (number) => boolean
isThumbEditable: (number) => boolean
orientation: Orientation
pageSize: number
setFocusedThumb: (number | undefined) => void
setThumbDragging: (number, boolean) => void
setThumbEditable: (number, boolean) => void
setThumbPercent: (number, number) => void
setThumbValue: (number, number) => void
setValue: (string | Color) => void
step: number
value: Color
values: Array<number>
}@react-stately/slider/@react-stately/slider:SliderState SliderState {
decrementThumb: (number, number) => void
defaultValues: Array<number>
focusedThumb: number | undefined
- getFormattedValue: (number) => string
+ getFormattedValue: (number | Array<number>) => string
getPercentValue: (number) => number
getThumbMaxValue: (number) => number
getThumbMinValue: (number) => number
getThumbPercent: (number) => number
getThumbValueLabel: (number) => string
getValuePercent: (number) => number
incrementThumb: (number, number) => void
isDisabled: boolean
isThumbDragging: (number) => boolean
isThumbEditable: (number) => boolean
orientation: Orientation
pageSize: number
setFocusedThumb: (number | undefined) => void
setThumbDragging: (number, boolean) => void
setThumbEditable: (number, boolean) => void
setThumbPercent: (number, number) => void
setThumbValue: (number, number) => void
step: number
values: Array<number>
} |
There was a problem hiding this comment.
Pull request overview
This PR improves the Slider authoring experience in React Aria Components by introducing a dedicated <SliderFill> component for rendering the selected range, and by updating <SliderOutput>’s default formatting to better handle multi-thumb sliders (range + list formatting). It also updates starter implementations, Spectrum S2 usage, docs, and tests to adopt the new APIs.
Changes:
- Added a new
SliderFillcomponent (and exports) to render the selected range with built-in orientation/offset handling. - Updated default
SliderOutputrendering to usestate.getFormattedValue()with improved formatting for 1, 2, and 3+ thumb values. - Refactored starter sliders, Spectrum S2 sliders, docs, and tests to use
SliderFilland the new default output behavior.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| starters/tailwind/src/Slider.tsx | Replaces manual fill sizing with <SliderFill> and uses default <SliderOutput> formatting; adds fillOffset wrapper prop. |
| starters/docs/src/Slider.tsx | Updates docs starter slider to use <SliderFill> and default <SliderOutput>; adds fillOffset wrapper prop. |
| starters/docs/src/Slider.css | Migrates styling hooks from .fill to .react-aria-SliderFill to match the new component. |
| packages/react-stately/src/slider/useSliderState.ts | Enhances getFormattedValue to support single values, ranges, and lists (via formatRange and Intl.ListFormat). |
| packages/react-stately/src/color/useColorSliderState.ts | Supplies a real numberFormatter to useSliderState and overrides getFormattedValue for color channel formatting. |
| packages/react-aria-components/test/Slider.test.js | Updates output expectations and adds coverage for SliderFill rendering/style behavior. |
| packages/react-aria-components/src/Slider.tsx | Adds SliderFill component, context, and updates SliderOutput default content to state.getFormattedValue(). |
| packages/react-aria-components/exports/Slider.ts | Exports SliderFill/SliderFillContext and related types. |
| packages/react-aria-components/exports/index.ts | Re-exports SliderFill and its types from the package entry. |
| packages/dev/s2-docs/pages/react-aria/Slider.mdx | Updates docs examples/anatomy/prop tables to include SliderFill and starter fillOffset. |
| packages/@react-spectrum/s2/src/Slider.tsx | Replaces custom filled track logic with <SliderFill> and adopts new output formatting. |
| packages/@react-spectrum/s2/src/RangeSlider.tsx | Replaces custom filled track logic with <SliderFill> for range sliders. |
| thumbLabels?: string[]; | ||
| /** | ||
| * The offset from which to start the fill. | ||
| * @default 0 |
| thumbLabels?: string[]; | ||
| /** | ||
| * The offset from which to start the fill. | ||
| * @default 0 |
| export interface SliderFillProps extends HoverEvents, RenderProps<SliderFillRenderProps>, GlobalDOMAttributes<HTMLDivElement> { | ||
| /** | ||
| * The offset from which to start the fill. | ||
| * @default 0 |
| defaultStyle: state.orientation === 'vertical' | ||
| ? { | ||
| position: 'absolute', | ||
| bottom: `${startPercent}%`, | ||
| height: `${sizePercent}%`, | ||
| width: '100%' | ||
| } | ||
| : { | ||
| position: 'absolute', | ||
| insetInlineStart: `${startPercent}%`, | ||
| width: `${sizePercent}%`, | ||
| height: '100%' | ||
| }, |
| let {onHoverStart, onHoverEnd, onHoverChange, ...otherProps} = props; | ||
| let {hoverProps, isHovered} = useHover({onHoverStart, onHoverEnd, onHoverChange}); | ||
|
|
||
| let offset = props.offset != null ? clamp(props.offset, state.getThumbMinValue(0), state.getThumbMaxValue(0)) : state.getThumbMinValue(0); |
There was a problem hiding this comment.
Thoughts on multiple SliderFills that allow you to specify start and end thumbs?
|
Half related, do we want a similar component for ProgressBar and Meter? I know those might be more complicated because they could be round and sliders are more likely to be a straight bar. Not in this PR, just a general thought. |
This improves the RAC Slider API to make it easier to render a slider with a fill. Previously this had to be implemented manually using the render props. There is some complexity to this to handle horizontal and vertical orientation, fill offset, etc. This PR adds a
<SliderFill>component with some default styles that handle these calculations for you. When there is a single SliderThumb, the fill is rendered from the start (or a customoffsetif one is provided). When there are multiple thumbs, the fill is shown between the first and last.Also updated the default behavior for the
<SliderOutput>component to handle formatting for ranges (when there are two thumbs), and lists when there are three or more.