diff --git a/packages/components/slider/src/Slider.test.tsx b/packages/components/slider/src/Slider.test.tsx index d524da684..76d668fec 100644 --- a/packages/components/slider/src/Slider.test.tsx +++ b/packages/components/slider/src/Slider.test.tsx @@ -13,6 +13,10 @@ describe('Slider', () => { * cf. https://github.com/radix-ui/primitives/blob/28bebf2c6992d056244845c898abeff45dec2871/packages/react/slider/src/Slider.tsx#L9C10-L9C17 */ mockResizeObserver() + + Object.defineProperty(HTMLSpanElement.prototype, 'setPointerCapture', { + value: vi.fn(), + }) }) beforeEach(() => vi.clearAllMocks()) @@ -87,4 +91,28 @@ describe('Slider', () => { */ expect(commit).toHaveBeenCalledWith([32]) }) + + it('should set data attribute based on event type', async () => { + const user = userEvent.setup() + + render( +
+ + + + +
+ ) + + const thumb = screen.getByRole('slider') + + await user.pointer({ keys: '[TouchA>]', target: thumb }) + expect(thumb).toHaveAttribute('data-interaction', 'pointerdown') + + await user.keyboard('{ArrowRight>}') + expect(thumb).toHaveAttribute('data-interaction', 'keydown') + + thumb.blur() + expect(thumb).toHaveAttribute('data-interaction', 'blur') + }) }) diff --git a/packages/components/slider/src/SliderThumb.styles.ts b/packages/components/slider/src/SliderThumb.styles.ts index 8eefe3d9d..daaf6f626 100644 --- a/packages/components/slider/src/SliderThumb.styles.ts +++ b/packages/components/slider/src/SliderThumb.styles.ts @@ -6,6 +6,7 @@ export const thumbVariants = cva( 'hover:ring-4 u-shadow-border-transition', 'outline-none', 'focus-visible:u-ring', + 'data-[interaction=pointerdown]:focus-visible:!ring-0', 'spark-disabled:hover:ring-0 spark-disabled:cursor-not-allowed', ], { diff --git a/packages/components/slider/src/SliderThumb.tsx b/packages/components/slider/src/SliderThumb.tsx index bd134a543..303491806 100644 --- a/packages/components/slider/src/SliderThumb.tsx +++ b/packages/components/slider/src/SliderThumb.tsx @@ -1,5 +1,5 @@ import * as RadixSlider from '@radix-ui/react-slider' -import { forwardRef } from 'react' +import { type FocusEvent, forwardRef, type KeyboardEvent, type PointerEvent, useRef } from 'react' import { useSliderContext } from './SliderContext' import { thumbVariants } from './SliderThumb.styles' @@ -12,14 +12,39 @@ export interface SliderThumbProps extends RadixSlider.SliderThumbProps { asChild?: boolean } -export const SliderThumb = forwardRef( - ({ asChild = false, className, ...rest }, ref) => { +export const SliderThumb = forwardRef( + ({ asChild = false, className, onPointerDown, onKeyDown, onBlur, ...rest }, forwardedRef) => { const { intent } = useSliderContext() + const innerRef = useRef(null) + const ref = forwardedRef || innerRef + + const setInteractionType = (e: KeyboardEvent | FocusEvent | PointerEvent) => { + /** + * Radix Slider implementation uses `.focus()` and thus prevent us to handle + * distinctively focus/focus-visible styles. So we use a `data-interaction` attribute to stay + * aware of the type of event, and adapt styles if needed. + */ + if (typeof ref === 'function' || !ref.current) return + ref.current.dataset.interaction = e.type + } + return ( ) => { + setInteractionType(e) + onPointerDown?.(e) + }} + onKeyDown={(e: KeyboardEvent) => { + setInteractionType(e) + onKeyDown?.(e) + }} + onBlur={(e: FocusEvent) => { + setInteractionType(e) + onBlur?.(e) + }} className={thumbVariants({ intent, className })} {...rest} />