mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 15:25:44 +00:00
Add per-slider reset buttons via SliderControl component
This commit is contained in:
@@ -2,7 +2,7 @@ import { cn } from "@/lib/utils";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { getAssetPath, getRenderableAssetUrl } from "@/lib/assetPath";
|
||||
import { BUILT_IN_WALLPAPERS, WALLPAPER_PATHS, WALLPAPER_RELATIVE_PATHS } from "@/lib/wallpapers";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { SliderControl } from "./SliderControl";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -12,7 +12,7 @@ import { Trash2, Download, Crop, X, Bug, Upload, Star, Film, Image, Sparkles, Pa
|
||||
import { toast } from "sonner";
|
||||
import { useI18n, useScopedT } from "../../contexts/I18nContext";
|
||||
import type { ZoomDepth, CropRegion, AnnotationRegion, AnnotationType, PlaybackSpeed } from "./types";
|
||||
import { SPEED_OPTIONS } from "./types";
|
||||
import { SPEED_OPTIONS, DEFAULT_CURSOR_SIZE, DEFAULT_CURSOR_SMOOTHING, DEFAULT_CURSOR_MOTION_BLUR, DEFAULT_CURSOR_CLICK_BOUNCE, DEFAULT_ZOOM_MOTION_BLUR } from "./types";
|
||||
import { CropControl } from "./CropControl";
|
||||
import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp";
|
||||
import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel";
|
||||
@@ -457,34 +457,32 @@ export function SettingsPanel({
|
||||
/>
|
||||
</div>
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.backgroundBlur')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{backgroundBlur.toFixed(1)}px</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[backgroundBlur]}
|
||||
onValueChange={(values) => onBackgroundBlurChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.backgroundBlur')}
|
||||
value={backgroundBlur}
|
||||
defaultValue={0}
|
||||
min={0}
|
||||
max={8}
|
||||
step={0.25}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onBackgroundBlurChange?.(v)}
|
||||
formatValue={(v) => `${v.toFixed(1)}px`}
|
||||
parseInput={(t) => parseFloat(t.replace(/px$/, ''))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 mb-3">
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.zoomMotionBlur')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{zoomMotionBlur.toFixed(2)}×</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[zoomMotionBlur]}
|
||||
onValueChange={(values) => onZoomMotionBlurChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.zoomMotionBlur')}
|
||||
value={zoomMotionBlur}
|
||||
defaultValue={DEFAULT_ZOOM_MOTION_BLUR}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.05}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onZoomMotionBlurChange?.(v)}
|
||||
formatValue={(v) => `${v.toFixed(2)}×`}
|
||||
parseInput={(t) => parseFloat(t.replace(/×$/, ''))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -500,107 +498,100 @@ export function SettingsPanel({
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 mb-3">
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.cursorSize')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{cursorSize.toFixed(2)}×</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[cursorSize]}
|
||||
onValueChange={(values) => onCursorSizeChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.cursorSize')}
|
||||
value={cursorSize}
|
||||
defaultValue={DEFAULT_CURSOR_SIZE}
|
||||
min={0.5}
|
||||
max={10}
|
||||
step={0.05}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onCursorSizeChange?.(v)}
|
||||
formatValue={(v) => `${v.toFixed(2)}×`}
|
||||
parseInput={(t) => parseFloat(t.replace(/×$/, ''))}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.cursorSmoothing')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{cursorSmoothing <= 0 ? tSettings('effects.off') : cursorSmoothing.toFixed(2)}</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[cursorSmoothing]}
|
||||
onValueChange={(values) => onCursorSmoothingChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.cursorSmoothing')}
|
||||
value={cursorSmoothing}
|
||||
defaultValue={DEFAULT_CURSOR_SMOOTHING}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.01}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onCursorSmoothingChange?.(v)}
|
||||
formatValue={(v) => v <= 0 ? 'Off' : v.toFixed(2)}
|
||||
parseInput={(t) => parseFloat(t)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2 mb-3">
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.cursorMotionBlur')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{cursorMotionBlur.toFixed(2)}×</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[cursorMotionBlur]}
|
||||
onValueChange={(values) => onCursorMotionBlurChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.cursorMotionBlur')}
|
||||
value={cursorMotionBlur}
|
||||
defaultValue={DEFAULT_CURSOR_MOTION_BLUR}
|
||||
min={0}
|
||||
max={2}
|
||||
step={0.05}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onCursorMotionBlurChange?.(v)}
|
||||
formatValue={(v) => `${v.toFixed(2)}×`}
|
||||
parseInput={(t) => parseFloat(t.replace(/×$/, ''))}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.cursorClickBounce')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{cursorClickBounce.toFixed(2)}×</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[cursorClickBounce]}
|
||||
onValueChange={(values) => onCursorClickBounceChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.cursorClickBounce')}
|
||||
value={cursorClickBounce}
|
||||
defaultValue={DEFAULT_CURSOR_CLICK_BOUNCE}
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.05}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onCursorClickBounceChange?.(v)}
|
||||
formatValue={(v) => `${v.toFixed(2)}×`}
|
||||
parseInput={(t) => parseFloat(t.replace(/×$/, ''))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.shadow')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{Math.round(shadowIntensity * 100)}%</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[shadowIntensity]}
|
||||
onValueChange={(values) => onShadowChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.shadow')}
|
||||
value={shadowIntensity}
|
||||
defaultValue={0}
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onShadowChange?.(v)}
|
||||
formatValue={(v) => `${Math.round(v * 100)}%`}
|
||||
parseInput={(t) => parseFloat(t.replace(/%$/, '')) / 100}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.roundness')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{borderRadius}px</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[borderRadius]}
|
||||
onValueChange={(values) => onBorderRadiusChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.roundness')}
|
||||
value={borderRadius}
|
||||
defaultValue={12.5}
|
||||
min={0}
|
||||
max={25}
|
||||
step={0.5}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onBorderRadiusChange?.(v)}
|
||||
formatValue={(v) => `${v}px`}
|
||||
parseInput={(t) => parseFloat(t.replace(/px$/, ''))}
|
||||
/>
|
||||
</div>
|
||||
<div className="p-2 rounded-lg bg-white/5 border border-white/5">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{tSettings('effects.padding')}</div>
|
||||
<span className="text-[10px] text-slate-500 font-mono">{padding}%</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[padding]}
|
||||
onValueChange={(values) => onPaddingChange?.(values[0])}
|
||||
<SliderControl
|
||||
label={tSettings('effects.padding')}
|
||||
value={padding}
|
||||
defaultValue={50}
|
||||
min={0}
|
||||
max={100}
|
||||
step={1}
|
||||
className="w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
onChange={(v) => onPaddingChange?.(v)}
|
||||
formatValue={(v) => `${v}%`}
|
||||
parseInput={(t) => parseFloat(t.replace(/%$/, ''))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { RotateCcw } from "lucide-react";
|
||||
|
||||
interface SliderControlProps {
|
||||
label: string;
|
||||
value: number;
|
||||
defaultValue: number;
|
||||
min: number;
|
||||
max: number;
|
||||
step: number;
|
||||
onChange: (value: number) => void;
|
||||
formatValue: (value: number) => string;
|
||||
parseInput: (text: string) => number | null;
|
||||
accentColor?: "purple" | "blue";
|
||||
}
|
||||
|
||||
export function SliderControl({
|
||||
label,
|
||||
value,
|
||||
defaultValue,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
onChange,
|
||||
formatValue,
|
||||
parseInput,
|
||||
accentColor = "blue",
|
||||
}: SliderControlProps) {
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [editText, setEditText] = useState("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const isModified = value !== defaultValue;
|
||||
|
||||
useEffect(() => {
|
||||
if (editing && inputRef.current) {
|
||||
inputRef.current.focus();
|
||||
inputRef.current.select();
|
||||
}
|
||||
}, [editing]);
|
||||
|
||||
const commitEdit = () => {
|
||||
const parsed = parseInput(editText);
|
||||
if (parsed != null && !isNaN(parsed)) {
|
||||
onChange(Math.min(max, Math.max(min, parsed)));
|
||||
}
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="text-[10px] font-medium text-slate-300">{label}</div>
|
||||
{isModified && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onChange(defaultValue)}
|
||||
className="text-slate-500 hover:text-slate-300 transition-colors"
|
||||
title="Reset to default"
|
||||
>
|
||||
<RotateCcw className="w-2.5 h-2.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{editing ? (
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="text"
|
||||
value={editText}
|
||||
onChange={(e) => setEditText(e.target.value)}
|
||||
onBlur={commitEdit}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") commitEdit();
|
||||
if (e.key === "Escape") cancelEdit();
|
||||
}}
|
||||
className="w-14 text-[10px] text-right font-mono bg-white/10 border border-white/20 rounded px-1 py-0 text-slate-200 outline-none focus:border-white/40"
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="text-[10px] text-slate-500 font-mono cursor-text hover:text-slate-300 transition-colors"
|
||||
onClick={() => {
|
||||
setEditText(formatValue(value));
|
||||
setEditing(true);
|
||||
}}
|
||||
>
|
||||
{formatValue(value)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Slider
|
||||
value={[value]}
|
||||
onValueChange={(values) => onChange(values[0])}
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
className={
|
||||
accentColor === "purple"
|
||||
? "w-full [&_[role=slider]]:bg-[#8b5cf6] [&_[role=slider]]:border-[#8b5cf6] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
: "w-full [&_[role=slider]]:bg-[#2563EB] [&_[role=slider]]:border-[#2563EB] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3"
|
||||
}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user