"use client"; import { type ChangeEventHandler, type HTMLAttributes, type ReactElement, type ReactNode, type Ref, useEffect, useState, } from "react"; import { useUserInteractionMode } from "../interaction/UserInteractionModeProvider.js"; import { type TooltipProps } from "../tooltip/Tooltip.js"; import { type LabelRequiredForA11y } from "../types.js"; import { SliderValueTooltip } from "./SliderValueTooltip.js"; import { sliderThumb, sliderThumbInput } from "./sliderStyles.js"; import { type SliderValueOptions } from "./useSlider.js"; const noop = (): void => { // do nothing }; /** * @since 6.0.0 */ export type SliderTooltipVisibility = "auto" | "hover" | "always"; /** * @since 2.5.0 */ export interface SliderThumbPresentation { /** * Boolean if the slider should act as a discrete slider which will render a * tooltip above the thumb while dragging to visualize the current value for * the slider. * * @defaultValue `false` */ discrete?: boolean; /** * A function that is used to help with accessibility by creating a better * value string if just a number isn't representative enough of your range. * * Example: * * ```tsx * const [value, controls] = useSlider(0, { * // format to be `$100`, etc * getValueText: value => `$${value}`, * }); * * return ; * ``` * * @defaultValue `() => ""` */ getValueText?: (value: number) => string; /** * Set this to `true` if the slider's thumb position should only update when * the user has dragged to the next value instead of with the mouse. * * @see {@link marks} * @defaultValue `!!marks` */ disableSmoothDragging?: boolean; /** * The discrete slider's value tooltip will only become visible when: * * - `"auto"` - the user is dragging with touch/mouse or focused with a keyboard * - `"hover"` - the behavior of `"auto"` plus while hovering the thumb with a mouse * - `"always"` - ... always * * This only applies when the {@link discrete} prop is `true`. * * @defaultValue `"auto"` */ tooltipVisibility?: SliderTooltipVisibility; } /** * @since 6.0.0 */ export interface ConfigurableSliderThumbProps extends Omit< HTMLAttributes, "onChange" > { name?: string; } /** * @internal * @since 2.5.0 * @since 6.0.0 Internal only component. */ export interface SliderThumbProps extends ConfigurableSliderThumbProps, Required, Required { ref?: Ref; id: string; value: number; index: 1 | 2; active: boolean; animate: boolean; disabled: boolean; vertical: boolean; onChange: ChangeEventHandler; tooltipProps?: Partial; getTooltipProps: ( value: number, isFirstThumb: boolean ) => Partial | undefined; getTooltipChildren: (value: number, isFirstThumb: boolean) => ReactNode; } /** * **Client Component** * * @internal * @since 2.5.0 * @since 6.0.0 Internal only component. */ export function SliderThumb( props: LabelRequiredForA11y ): ReactElement { const { ref, id, min, max, name, value, onChange, index, getValueText, step, active, animate, disabled, vertical, discrete, tabIndex = disabled ? -1 : 0, className, onFocus = noop, onKeyDown = noop, onMouseEnter = noop, onMouseLeave = noop, tooltipProps, getTooltipProps, getTooltipChildren, disableSmoothDragging, tooltipVisibility = "auto", ...remaining } = props; const { "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy } = props; const isFirstThumb = index === 1; const mode = useUserInteractionMode(); const keyboard = mode === "keyboard"; const touch = mode === "touch"; const [mouseVisible, setMouseVisible] = useState(false); const [keyboardVisible, setKeyboardVisible] = useState(false); useEffect(() => { if (disabled || !discrete) { setKeyboardVisible(false); return; } // if the mode changes away from keyboard, need to disable the keyboard // state setKeyboardVisible((prevVisible) => prevVisible && mode === "keyboard"); }, [disabled, discrete, mode]); useEffect(() => { if (disabled || !discrete) { setMouseVisible(false); return; } setMouseVisible((prevVisible) => prevVisible && mode !== "touch"); }, [disabled, discrete, mode]); useEffect(() => { if (!keyboardVisible) { return; } const callback = (): void => { setKeyboardVisible(false); }; window.addEventListener("blur", callback, true); return () => { window.removeEventListener("blur", callback); }; }, [keyboardVisible]); const classNameOptions = { index, active, animate: !disableSmoothDragging && animate, disabled, vertical, className, } as const; return ( <> {disabled && ( )} { onFocus(event); if (discrete && keyboard) { setKeyboardVisible(true); } }} onKeyDown={(event) => { onKeyDown(event); // this allows the tooltip to be visible when switching from mouse to // keyboard if (discrete && event.key !== "Tab") { setKeyboardVisible(true); } }} onMouseEnter={(event) => { onMouseEnter(event); if (discrete && tooltipVisibility === "hover" && !touch) { setMouseVisible(true); } }} onMouseLeave={(event) => { onMouseLeave(event); if (discrete && tooltipVisibility === "hover" && !touch) { setMouseVisible(false); } }} /> {discrete && ( {getTooltipChildren(value, isFirstThumb)} )} ); }