This document outlines the steps to create Slider
component styled withTailwind CSS and using some npm dependency libraries.
Node.js
and npm
installed on your machine.Tailwind CSS
installed in your project.CVA(class-variance-authority)
is a utility for managing CSS class names based on various conditions.clsx
is a tiny utility for constructing className strings conditionally.1// slider.helpers.ts
2import { cva } from "class-variance-authority";
3
4export const wrapperVariants = cva(
5 "w-full relative flex items-center overflow-hidden rounded-full",
6 {
7 variants: {
8 sliderSize: {
9 small: "h-4",
10 medium: "h-6",
11 large: "h-7",
12 },
13 },
14 },
15);
16
17export const barWrapperVariants = cva(
18 "rounded-full w-full bg-neutral-200 dark:bg-neutral-500 relative overflow-hidden",
19 {
20 variants: {
21 sliderSize: {
22 small: "h-1",
23 medium: "h-3",
24 large: "h-7",
25 },
26 },
27 },
28);
29
30export const colorVariants = cva("", {
31 variants: {
32 bgColor: {
33 primary: "bg-blue-500 data-[disabled=true]:bg-blue-300",
34 secondary: "bg-gray-500 data-[disabled=true]:bg-gray-300",
35 success: "bg-green-500 data-[disabled=true]:bg-green-300",
36 danger: "bg-red-500 data-[disabled=true]:bg-red-300",
37 warning: "bg-yellow-500 data-[disabled=true]:bg-yellow-200",
38 info: "bg-cyan-500 data-[disabled=true]:bg-cyan-200",
39 light: "bg-gray-400 data-[disabled=true]:bg-gray-300",
40 dark: "bg-black data-[disabled=true]:bg-gray-300",
41 },
42 },
43});
44
45export const thumbVariants = cva("absolute top-0", {
46 variants: {
47 sliderSize: {
48 small: "w-4 h-4 p-0.5 rounded-full",
49 medium: "w-6 h-6 p-0.5 rounded-full",
50 large: "w-7 h-7 p-1 !bg-none",
51 },
52 },
53});
54
55export const getSliderInitValue = (
56 value: number | undefined,
57 defaultValue: number | undefined,
58 min: number,
59) => {
60 if (value && !Number.isNaN(+value)) return value;
61 if (defaultValue && !Number.isNaN(+defaultValue)) return defaultValue;
62 return min;
63};
64
1// slider.component.tsx
2import clsx from "clsx";
3"use client";
4
5import { cn } from "@/lib/utils";
6import { useState } from "react";
7import type { VariantProps } from "class-variance-authority";
8import {
9 barWrapperVariants,
10 colorVariants,
11 getSliderInitValue,
12 thumbVariants,
13 wrapperVariants,
14} from "./slider.helpers";
15
16export interface SliderProps
17 extends React.InputHTMLAttributes<HTMLInputElement>,
18 VariantProps<typeof wrapperVariants>,
19 VariantProps<typeof colorVariants> {
20 value?: number;
21 defaultValue?: number;
22 min?: number;
23 max?: number;
24 className?: string;
25}
26
27const Slider: React.FC<SliderProps> = ({
28 disabled,
29 value,
30 defaultValue,
31 min = 0,
32 max = 100,
33 sliderSize = "medium",
34 bgColor = "primary",
35 className,
36 onChange,
37 ...props
38}) => {
39 const [innerValue, setInnerValue] = useState<number>(
40 getSliderInitValue(value, defaultValue, min),
41 );
42
43 const percent = Math.abs(innerValue - min) / Math.abs(max - min);
44
45 const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
46 const num = +e.target.value;
47 setInnerValue(Number.isNaN(num) ? min : num);
48 onChange?.(e);
49 };
50
51 return (
52 <div className={cn(wrapperVariants({ sliderSize }), className)}>
53 <div className={barWrapperVariants({ sliderSize })}>
54 <div
55 data-disabled={Boolean(disabled)}
56 style={{ width: `${percent * 100}%` }}
57 className={cn("absolute inset-0", colorVariants({ bgColor }))}
58 />
59 </div>
60 <div
61 data-disabled={Boolean(disabled)}
62 style={{
63 left: `calc(${percent} * (100% - ${sliderSize === "small" ? 1 : sliderSize === "medium" ? 1.5 : 1.75}rem))`,
64 }}
65 className={cn(
66 colorVariants({ bgColor }),
67 thumbVariants({ sliderSize }),
68 )}
69 >
70 {sliderSize === "large" && (
71 <>
72 <div className="absolute inset-y-0 right-0 w-1/2 bg-neutral-200 dark:bg-neutral-500" />
73 <div
74 data-disabled={Boolean(disabled)}
75 className={cn(
76 "absolute inset-0 rounded-r-full",
77 colorVariants({ bgColor }),
78 )}
79 />
80 </>
81 )}
82 <div className="absolute inset-1 rounded-full bg-white" />
83 </div>
84 <input
85 type="range"
86 value={value}
87 disabled={disabled}
88 min={min}
89 max={max}
90 onChange={handleChange}
91 className="absolute inset-0 opacity-0"
92 {...props}
93 />
94 </div>
95 );
96};
97
98export default Slider;
99