Spaces:
Running
Running
File size: 3,547 Bytes
f2f99a3 | 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 | import type { PresetName } from '@core/types.js';
const PRESET_MARKS: Array<{ value: number; label: string; emoji: string; preset: PresetName }> = [
{ value: 0.00, label: 'Light', emoji: '🌤️', preset: 'light' },
{ value: 0.33, label: 'Moderate', emoji: '⚡', preset: 'moderate' },
{ value: 0.67, label: 'Strong', emoji: '🛡️', preset: 'strong' },
{ value: 1.00, label: 'Fortress', emoji: '🏰', preset: 'fortress' },
];
interface StrengthSliderProps {
value: number;
onChange: (value: number, preset: PresetName) => void;
disabled?: boolean;
}
export default function StrengthSlider({ value, onChange, disabled }: StrengthSliderProps) {
const nearestPreset = PRESET_MARKS.reduce((best, mark) =>
Math.abs(mark.value - value) < Math.abs(best.value - value) ? mark : best
);
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-zinc-300">Strength</label>
<span className="text-xs tabular-nums text-zinc-500">
{nearestPreset.emoji} {nearestPreset.label}
</span>
</div>
<div className="relative">
<input
type="range"
min="0"
max="1"
step="0.01"
value={value}
disabled={disabled}
onChange={(e) => {
const v = parseFloat(e.target.value);
const snap = PRESET_MARKS.find((m) => Math.abs(m.value - v) < 0.04);
if (snap) {
onChange(snap.value, snap.preset);
} else {
// Snap to nearest preset (no custom mode)
const nearest = PRESET_MARKS.reduce((best, mark) =>
Math.abs(mark.value - v) < Math.abs(best.value - v) ? mark : best
);
onChange(nearest.value, nearest.preset);
}
}}
className="w-full h-1.5 rounded-full appearance-none cursor-pointer
bg-zinc-700 accent-blue-500
disabled:opacity-50 disabled:cursor-not-allowed
[&::-webkit-slider-thumb]:appearance-none
[&::-webkit-slider-thumb]:w-4
[&::-webkit-slider-thumb]:h-4
[&::-webkit-slider-thumb]:rounded-full
[&::-webkit-slider-thumb]:bg-blue-500
[&::-webkit-slider-thumb]:shadow-lg
[&::-webkit-slider-thumb]:shadow-blue-500/20
[&::-webkit-slider-thumb]:transition-transform
[&::-webkit-slider-thumb]:hover:scale-110"
/>
<div className="relative mt-2 h-5">
{PRESET_MARKS.map((mark, i) => {
const pct = mark.value * 100;
const isFirst = i === 0;
const isLast = i === PRESET_MARKS.length - 1;
const align = isFirst ? 'left-0' : isLast ? 'right-0' : '-translate-x-1/2';
return (
<button
key={mark.preset}
onClick={() => onChange(mark.value, mark.preset)}
disabled={disabled}
style={isFirst || isLast ? undefined : { left: `${pct}%` }}
className={`absolute whitespace-nowrap text-[10px] transition-colors ${align} ${
nearestPreset.preset === mark.preset
? 'text-blue-400 font-medium'
: 'text-zinc-600 hover:text-zinc-400'
} disabled:opacity-50`}
>
{mark.emoji} {mark.label}
</button>
);
})}
</div>
</div>
</div>
);
}
|