mirror of
https://github.com/luxfhe/example-redact.git
synced 2026-10-30 18:13:20 +00:00
98 lines
3.9 KiB
TypeScript
98 lines
3.9 KiB
TypeScript
"use client";
|
|
|
|
import * as React from "react";
|
|
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
import { cn } from "~~/lib/utils";
|
|
|
|
export interface SliderProps extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {
|
|
/** Show percentage markers */
|
|
showMarkers?: boolean;
|
|
/** Show "MAX" button */
|
|
showMaxButton?: boolean;
|
|
/** Max value */
|
|
max?: number;
|
|
/** On max button click */
|
|
// onMaxClick?: () => void;
|
|
/** Callback when the value changes */
|
|
onValueChange?: (value: number[]) => void;
|
|
}
|
|
|
|
const Slider = React.forwardRef<React.ComponentRef<typeof SliderPrimitive.Root>, SliderProps>(
|
|
({ className, showMarkers = true, showMaxButton = true, max = 100, onValueChange, ...props }, ref) => {
|
|
// State for the slider value (supports both manual movement & marker clicks)
|
|
const [internalValue, setInternalValue] = React.useState([props.value ? props.value[0] : 0]);
|
|
|
|
// Update value when clicking on a marker
|
|
const handleMarkerClick = (percentage: number) => {
|
|
const newValue = (percentage / 100) * max;
|
|
setInternalValue([newValue]); // Updates internal state
|
|
onValueChange?.([newValue]); // Calls external change handler
|
|
};
|
|
|
|
return (
|
|
<div className={cn("relative w-full space-y-2", className)}>
|
|
{/* Percentage markers with vertical lines */}
|
|
{showMarkers && (
|
|
<div className="relative flex justify-between text-xs text-gray-500 font-medium px-0">
|
|
{[0, 25, 50, 75, 100].map(percentage => (
|
|
<div
|
|
key={percentage}
|
|
className="flex flex-col items-center cursor-pointer hover:text-blue-600"
|
|
onClick={() => handleMarkerClick(percentage)}
|
|
>
|
|
<span>{percentage}%</span>
|
|
<div className="w-[1px] h-3 bg-gray-500"></div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="relative flex items-center">
|
|
{/* Slider Track */}
|
|
<SliderPrimitive.Root
|
|
ref={ref}
|
|
className={cn(
|
|
"relative flex w-full touch-none select-none items-center border-none",
|
|
props.disabled && "opacity-50 cursor-not-allowed"
|
|
)}
|
|
max={max}
|
|
value={internalValue.filter((v): v is number => v !== undefined)}
|
|
onValueChange={(val: number[]) => {
|
|
setInternalValue(val);
|
|
if (val[0] !== undefined) {
|
|
onValueChange?.(val); // Only call if value is defined
|
|
}
|
|
}}
|
|
{...props}
|
|
>
|
|
<SliderPrimitive.Track className="relative h-3 w-full rounded-full bg-[#005bb5] shadow-inner data-[disabled]:bg-gray-300">
|
|
<SliderPrimitive.Range className="absolute h-full rounded-full border-2 border-[#005bb5] bg-[#0073E6] data-[disabled]:bg-gray-400 data-[disabled]:border-gray-400" />
|
|
</SliderPrimitive.Track>
|
|
|
|
{/* Thumb */}
|
|
<SliderPrimitive.Thumb className="block h-6 w-6 rounded-full bg-[#0057FF] border-2 border-[#0057FF] shadow-md focus:outline-hidden focus:ring-2 focus:ring-blue-400 data-[disabled]:bg-gray-400 data-[disabled]:border-gray-400" />
|
|
</SliderPrimitive.Root>
|
|
|
|
{/* Max Button - Adjusted position */}
|
|
{showMaxButton && (
|
|
<button
|
|
onClick={() => handleMarkerClick(100)}
|
|
className="absolute right-[-55px] top-1/2 -translate-y-1/2 transform text-xs font-semibold text-[#0057FF] font-semibold bg-[#F0F4FF] shadow-md rounded-full px-3 py-1 shadow-md"
|
|
style={{
|
|
marginLeft: "12px", // Small offset to avoid overlapping
|
|
minWidth: "45px", // Ensures size consistency
|
|
height: "24px", // Matches thumb height
|
|
}}
|
|
>
|
|
MAX
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
);
|
|
|
|
Slider.displayName = SliderPrimitive.Root.displayName;
|
|
export { Slider };
|