forked from PinSpace-Org/GistPin
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSlider.tsx
More file actions
153 lines (144 loc) · 4.48 KB
/
Copy pathSlider.tsx
File metadata and controls
153 lines (144 loc) · 4.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
'use client';
import { useId } from 'react';
interface SliderProps {
value: number;
onChange: (value: number) => void;
min?: number;
max?: number;
step?: number;
label?: string;
showValue?: boolean;
marks?: boolean;
vertical?: boolean;
disabled?: boolean;
}
export function Slider({
value,
onChange,
min = 0,
max = 100,
step = 1,
label,
showValue = true,
marks = false,
vertical = false,
disabled = false,
}: SliderProps) {
const id = useId();
const pct = ((value - min) / (max - min)) * 100;
const markValues: number[] = [];
if (marks) {
for (let v = min; v <= max; v += step * Math.ceil((max - min) / (step * 5))) {
markValues.push(v);
}
if (!markValues.includes(max)) markValues.push(max);
}
return (
<div className={`flex ${vertical ? 'flex-col-reverse items-center gap-2' : 'flex-col gap-1'} w-full`}>
{(label || showValue) && (
<div className="flex justify-between text-xs text-gray-600 dark:text-gray-400">
{label && <label htmlFor={id}>{label}</label>}
{showValue && <span>{value}</span>}
</div>
)}
<div className={`relative flex items-center ${vertical ? 'h-40 w-6 flex-col' : 'h-6 w-full'}`}>
<div className={`absolute rounded-full bg-gray-200 dark:bg-gray-700 ${vertical ? 'w-1.5 h-full' : 'h-1.5 w-full'}`} />
<div
className={`absolute rounded-full bg-brand ${vertical ? 'w-1.5 bottom-0' : 'h-1.5 left-0'}`}
style={vertical ? { height: `${pct}%` } : { width: `${pct}%` }}
/>
<input
id={id}
type="range"
min={min}
max={max}
step={step}
value={value}
disabled={disabled}
onChange={(e) => onChange(Number(e.target.value))}
className={[
'absolute appearance-none bg-transparent cursor-pointer',
'accent-brand focus:outline-none',
vertical ? 'h-full w-6 writing-mode-vertical' : 'w-full h-6',
disabled ? 'opacity-50 cursor-not-allowed' : '',
].join(' ')}
style={vertical ? { writingMode: 'vertical-lr', direction: 'rtl' } : undefined}
aria-valuemin={min}
aria-valuemax={max}
aria-valuenow={value}
/>
</div>
{marks && markValues.length > 0 && (
<div className={`flex ${vertical ? 'flex-col-reverse items-start' : 'justify-between'} text-xs text-gray-400`}>
{markValues.map((m) => <span key={m}>{m}</span>)}
</div>
)}
</div>
);
}
interface RangeSliderProps {
value: [number, number];
onChange: (value: [number, number]) => void;
min?: number;
max?: number;
step?: number;
label?: string;
disabled?: boolean;
}
export function RangeSlider({
value,
onChange,
min = 0,
max = 100,
step = 1,
label,
disabled = false,
}: RangeSliderProps) {
const [lo, hi] = value;
const loId = useId();
const hiId = useId();
const loPct = ((lo - min) / (max - min)) * 100;
const hiPct = ((hi - min) / (max - min)) * 100;
return (
<div className="flex flex-col gap-1 w-full">
{label && (
<div className="flex justify-between text-xs text-gray-600 dark:text-gray-400">
<span>{label}</span>
<span>{lo} – {hi}</span>
</div>
)}
<div className="relative flex items-center h-6 w-full">
<div className="absolute h-1.5 w-full rounded-full bg-gray-200 dark:bg-gray-700" />
<div
className="absolute h-1.5 rounded-full bg-brand"
style={{ left: `${loPct}%`, width: `${hiPct - loPct}%` }}
/>
<input
id={loId}
type="range"
min={min}
max={hi}
step={step}
value={lo}
disabled={disabled}
onChange={(e) => onChange([Math.min(Number(e.target.value), hi), hi])}
className="absolute w-full appearance-none bg-transparent accent-brand cursor-pointer disabled:opacity-50"
aria-label={label ? `${label} lower bound` : 'Lower bound'}
/>
<input
id={hiId}
type="range"
min={lo}
max={max}
step={step}
value={hi}
disabled={disabled}
onChange={(e) => onChange([lo, Math.max(Number(e.target.value), lo)])}
className="absolute w-full appearance-none bg-transparent accent-brand cursor-pointer disabled:opacity-50"
aria-label={label ? `${label} upper bound` : 'Upper bound'}
/>
</div>
</div>
);
}
export default Slider;