fix: native button toggles, scroll enabled, remove whileHover interference
All checks were successful
Deploy to Coolify / deploy (push) Successful in 4s
All checks were successful
Deploy to Coolify / deploy (push) Successful in 4s
This commit is contained in:
@@ -64,7 +64,7 @@ export default function Home() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="relative w-screen overflow-hidden no-select"
|
className="relative w-screen overflow-hidden"
|
||||||
style={{ height: "100dvh", background: "var(--bg)" }}
|
style={{ height: "100dvh", background: "var(--bg)" }}
|
||||||
>
|
>
|
||||||
{/* Ambient orbs */}
|
{/* Ambient orbs */}
|
||||||
@@ -128,14 +128,13 @@ export default function Home() {
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
{/* Content area — fills remaining space */}
|
{/* Content area — fills remaining space */}
|
||||||
<div className="flex-1 min-h-0 overflow-hidden">
|
<div className="flex-1 min-h-0 overflow-y-auto">
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
|
|
||||||
{/* ═══════════════ HOME TAB ═══════════════ */}
|
{/* ═══════════════ HOME TAB ═══════════════ */}
|
||||||
{activeTab === "home" && (
|
{activeTab === "home" && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="home"
|
key="home"
|
||||||
className="h-full"
|
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
animate="visible"
|
animate="visible"
|
||||||
@@ -146,10 +145,9 @@ export default function Home() {
|
|||||||
Row 2: [Очиститель ×2] [Погода]
|
Row 2: [Очиститель ×2] [Погода]
|
||||||
*/}
|
*/}
|
||||||
<div
|
<div
|
||||||
className="h-full grid gap-3"
|
className="grid gap-3"
|
||||||
style={{
|
style={{
|
||||||
gridTemplateColumns: "1fr 1fr 1fr",
|
gridTemplateColumns: "1fr 1fr 1fr",
|
||||||
gridTemplateRows: "1fr 1fr",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Свет Гостиная */}
|
{/* Свет Гостиная */}
|
||||||
|
|||||||
@@ -18,165 +18,124 @@ const MODES = [
|
|||||||
{ id: "High", label: "Турбо", color: "#f59e0b" },
|
{ id: "High", label: "Турбо", color: "#f59e0b" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function AirPurifierCard({
|
export default function AirPurifierCard({ entityId, state, presetMode, onUpdate }: Props) {
|
||||||
entityId,
|
const [localOn, setLocalOn] = useState(state === "on");
|
||||||
state,
|
|
||||||
presetMode,
|
|
||||||
onUpdate,
|
|
||||||
}: Props) {
|
|
||||||
const isOn = state === "on";
|
|
||||||
const [currentMode, setCurrentMode] = useState(presetMode || "Auto");
|
const [currentMode, setCurrentMode] = useState(presetMode || "Auto");
|
||||||
const [pending, setPending] = useState(false);
|
const [pending, setPending] = useState(false);
|
||||||
|
|
||||||
const handleToggle = useCallback(async () => {
|
const handleToggle = useCallback(async () => {
|
||||||
if (pending) return;
|
if (pending) return;
|
||||||
|
const newState = !localOn;
|
||||||
|
setLocalOn(newState);
|
||||||
setPending(true);
|
setPending(true);
|
||||||
await toggleFan(entityId, !isOn);
|
try {
|
||||||
|
await toggleFan(entityId, newState);
|
||||||
onUpdate();
|
onUpdate();
|
||||||
|
} catch (e) {
|
||||||
|
setLocalOn(!newState);
|
||||||
|
}
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}, [entityId, isOn, pending, onUpdate]);
|
}, [entityId, localOn, pending, onUpdate]);
|
||||||
|
|
||||||
const handleMode = useCallback(
|
const handleMode = useCallback(async (mode: string) => {
|
||||||
async (mode: string) => {
|
|
||||||
setCurrentMode(mode);
|
setCurrentMode(mode);
|
||||||
await setFanPreset(entityId, mode);
|
await setFanPreset(entityId, mode);
|
||||||
onUpdate();
|
onUpdate();
|
||||||
},
|
}, [entityId, onUpdate]);
|
||||||
[entityId, onUpdate]
|
|
||||||
);
|
|
||||||
|
|
||||||
|
const isOn = localOn;
|
||||||
const activeMode = MODES.find((m) => m.id === currentMode) || MODES[0];
|
const activeMode = MODES.find((m) => m.id === currentMode) || MODES[0];
|
||||||
const accentColor = isOn ? activeMode.color : "rgba(255,255,255,0.3)";
|
const accentColor = isOn ? activeMode.color : "rgba(255,255,255,0.3)";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="glass-card p-6 h-full flex flex-col"
|
className="glass-card p-6 flex flex-col"
|
||||||
style={
|
style={{
|
||||||
isOn
|
minHeight: "140px",
|
||||||
? {
|
...(isOn ? {
|
||||||
background: `${activeMode.color}08`,
|
background: `${activeMode.color}08`,
|
||||||
border: `1px solid ${activeMode.color}25`,
|
border: `1px solid ${activeMode.color}25`,
|
||||||
boxShadow: `0 0 40px ${activeMode.color}10`,
|
boxShadow: `0 0 40px ${activeMode.color}10`,
|
||||||
}
|
} : {})
|
||||||
: {}
|
}}
|
||||||
}
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.35, delay: 0.14 }}
|
transition={{ duration: 0.35, delay: 0.14 }}
|
||||||
whileHover={{ scale: 1.01 }}
|
|
||||||
>
|
>
|
||||||
{/* Top row: icon + name + toggle */}
|
{/* Top row */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{/* Animated icon */}
|
<div className="w-16 h-16 rounded-2xl flex items-center justify-center flex-shrink-0"
|
||||||
<div
|
|
||||||
className="w-16 h-16 rounded-2xl flex items-center justify-center flex-shrink-0"
|
|
||||||
style={{
|
style={{
|
||||||
background: isOn ? `${activeMode.color}20` : "rgba(255,255,255,0.06)",
|
background: isOn ? `${activeMode.color}20` : "rgba(255,255,255,0.06)",
|
||||||
boxShadow: isOn ? `0 0 28px ${activeMode.color}40` : "none",
|
boxShadow: isOn ? `0 0 28px ${activeMode.color}40` : "none",
|
||||||
}}
|
}}>
|
||||||
>
|
<motion.div animate={isOn ? { rotate: 360 } : { rotate: 0 }}
|
||||||
<motion.div
|
transition={isOn ? { duration: 4, repeat: Infinity, ease: "linear" } : {}}>
|
||||||
animate={isOn ? { rotate: 360 } : { rotate: 0 }}
|
<Wind size={32} color={isOn ? activeMode.color : "rgba(255,255,255,0.3)"} strokeWidth={1.5} />
|
||||||
transition={
|
|
||||||
isOn ? { duration: 4, repeat: Infinity, ease: "linear" } : {}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Wind
|
|
||||||
size={32}
|
|
||||||
color={isOn ? activeMode.color : "rgba(255,255,255,0.3)"}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div
|
<div className="text-xl font-bold" style={{ color: "var(--text-primary)" }}>Очиститель</div>
|
||||||
className="text-xl font-bold truncate"
|
<div className="text-sm mt-0.5 font-medium" style={{ color: isOn ? activeMode.color : "var(--text-secondary)" }}>
|
||||||
style={{ color: "var(--text-primary)" }}
|
|
||||||
>
|
|
||||||
Очиститель воздуха
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="text-sm mt-0.5 font-medium"
|
|
||||||
style={{ color: isOn ? activeMode.color : "var(--text-secondary)" }}
|
|
||||||
>
|
|
||||||
{isOn ? activeMode.label : "Выключен"}
|
{isOn ? activeMode.label : "Выключен"}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Toggle */}
|
{/* Toggle — native button */}
|
||||||
<motion.div
|
<button
|
||||||
className={`toggle-track ${isOn ? "toggle-on" : ""}`}
|
|
||||||
style={{
|
|
||||||
background: isOn ? activeMode.color : "rgba(255,255,255,0.1)",
|
|
||||||
boxShadow: isOn ? `0 0 16px ${activeMode.color}60` : "none",
|
|
||||||
}}
|
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
whileTap={{ scale: 0.88 }}
|
style={{
|
||||||
|
width: "60px", height: "32px", borderRadius: "16px",
|
||||||
|
background: isOn ? activeMode.color : "rgba(255,255,255,0.12)",
|
||||||
|
boxShadow: isOn ? `0 0 16px ${activeMode.color}60` : "none",
|
||||||
|
border: "none", cursor: "pointer", position: "relative",
|
||||||
|
transition: "background 0.2s ease, box-shadow 0.2s ease",
|
||||||
|
flexShrink: 0,
|
||||||
|
WebkitTapHighlightColor: "transparent",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="toggle-thumb" />
|
<motion.div
|
||||||
</motion.div>
|
style={{
|
||||||
|
width: "24px", height: "24px", borderRadius: "12px",
|
||||||
|
background: "white", position: "absolute", top: "4px",
|
||||||
|
boxShadow: "0 2px 6px rgba(0,0,0,0.3)",
|
||||||
|
}}
|
||||||
|
animate={{ left: isOn ? "32px" : "4px" }}
|
||||||
|
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mode buttons */}
|
{/* Mode buttons */}
|
||||||
<AnimatePresence>
|
<div className="flex gap-3 mt-4">
|
||||||
{isOn && (
|
|
||||||
<motion.div
|
|
||||||
className="flex gap-3 mt-auto pt-4"
|
|
||||||
initial={{ opacity: 0, y: 8 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: 8 }}
|
|
||||||
transition={{ duration: 0.22 }}
|
|
||||||
>
|
|
||||||
{MODES.map((mode) => {
|
{MODES.map((mode) => {
|
||||||
const isActive = currentMode === mode.id;
|
const isActive = currentMode === mode.id && isOn;
|
||||||
return (
|
return (
|
||||||
<motion.button
|
<button
|
||||||
key={mode.id}
|
key={mode.id}
|
||||||
onClick={() => handleMode(mode.id)}
|
onClick={() => isOn && handleMode(mode.id)}
|
||||||
className="flex-1 py-2.5 rounded-2xl text-sm font-semibold"
|
className="flex-1 py-2.5 rounded-2xl text-sm font-semibold"
|
||||||
style={
|
style={isActive ? {
|
||||||
isActive
|
|
||||||
? {
|
|
||||||
background: `${mode.color}22`,
|
background: `${mode.color}22`,
|
||||||
border: `1.5px solid ${mode.color}60`,
|
border: `1.5px solid ${mode.color}60`,
|
||||||
color: mode.color,
|
color: mode.color,
|
||||||
boxShadow: `0 0 14px ${mode.color}30`,
|
boxShadow: `0 0 14px ${mode.color}30`,
|
||||||
}
|
cursor: "pointer",
|
||||||
: {
|
WebkitTapHighlightColor: "transparent",
|
||||||
|
} : {
|
||||||
background: "rgba(255,255,255,0.05)",
|
background: "rgba(255,255,255,0.05)",
|
||||||
border: "1px solid rgba(255,255,255,0.08)",
|
border: "1px solid rgba(255,255,255,0.08)",
|
||||||
color: "var(--text-secondary)",
|
color: isOn ? "var(--text-secondary)" : "rgba(255,255,255,0.15)",
|
||||||
}
|
cursor: isOn ? "pointer" : "default",
|
||||||
}
|
WebkitTapHighlightColor: "transparent",
|
||||||
whileTap={{ scale: 0.88 }}
|
|
||||||
>
|
|
||||||
{mode.label}
|
|
||||||
</motion.button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
|
|
||||||
{/* Offline state bottom fill */}
|
|
||||||
{!isOn && (
|
|
||||||
<div className="mt-auto flex gap-3 pt-4">
|
|
||||||
{MODES.map((mode) => (
|
|
||||||
<div
|
|
||||||
key={mode.id}
|
|
||||||
className="flex-1 py-2.5 rounded-2xl text-sm font-semibold text-center"
|
|
||||||
style={{
|
|
||||||
background: "rgba(255,255,255,0.03)",
|
|
||||||
border: "1px solid rgba(255,255,255,0.05)",
|
|
||||||
color: "rgba(255,255,255,0.15)",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{mode.label}
|
{mode.label}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,142 +15,116 @@ interface Props {
|
|||||||
onUpdate: () => void;
|
onUpdate: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function LightCard({
|
export default function LightCard({ entityId, name, state, brightness, showSlider = false, onUpdate }: Props) {
|
||||||
entityId,
|
// Optimistic local state — не зависим от HA mock
|
||||||
name,
|
const [localOn, setLocalOn] = useState(state === "on");
|
||||||
state,
|
|
||||||
brightness,
|
|
||||||
showSlider = false,
|
|
||||||
onUpdate,
|
|
||||||
}: Props) {
|
|
||||||
const isOn = state === "on";
|
|
||||||
const brightPct = getBrightnessPct(brightness);
|
const brightPct = getBrightnessPct(brightness);
|
||||||
const [localBrightness, setLocalBrightness] = useState(brightPct || 70);
|
const [localBrightness, setLocalBrightness] = useState(brightPct || 70);
|
||||||
const [pending, setPending] = useState(false);
|
const [pending, setPending] = useState(false);
|
||||||
|
|
||||||
const handleToggle = useCallback(async () => {
|
const handleToggle = useCallback(async () => {
|
||||||
if (pending) return;
|
if (pending) return;
|
||||||
|
const newState = !localOn;
|
||||||
|
setLocalOn(newState); // optimistic
|
||||||
setPending(true);
|
setPending(true);
|
||||||
await toggleLight(entityId, !isOn);
|
try {
|
||||||
|
await toggleLight(entityId, newState);
|
||||||
onUpdate();
|
onUpdate();
|
||||||
|
} catch (e) {
|
||||||
|
setLocalOn(!newState); // rollback on real error
|
||||||
|
}
|
||||||
setPending(false);
|
setPending(false);
|
||||||
}, [entityId, isOn, pending, onUpdate]);
|
}, [entityId, localOn, pending, onUpdate]);
|
||||||
|
|
||||||
const handleBrightnessChange = useCallback(
|
const handleBrightnessChange = useCallback(async (val: number) => {
|
||||||
async (val: number) => {
|
|
||||||
setLocalBrightness(val);
|
setLocalBrightness(val);
|
||||||
await setLightBrightness(entityId, pctToBrightness(val));
|
await setLightBrightness(entityId, pctToBrightness(val));
|
||||||
onUpdate();
|
onUpdate();
|
||||||
},
|
}, [entityId, onUpdate]);
|
||||||
[entityId, onUpdate]
|
|
||||||
);
|
const isOn = localOn;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="glass-card p-6 h-full flex flex-col justify-between"
|
className="glass-card p-6 flex flex-col justify-between"
|
||||||
style={
|
style={{
|
||||||
isOn
|
minHeight: "160px",
|
||||||
? {
|
...(isOn ? {
|
||||||
background: "rgba(245,158,11,0.07)",
|
background: "rgba(245,158,11,0.07)",
|
||||||
border: "1px solid rgba(245,158,11,0.22)",
|
border: "1px solid rgba(245,158,11,0.22)",
|
||||||
boxShadow: "0 0 40px rgba(245,158,11,0.08), inset 0 1px 0 rgba(245,158,11,0.1)",
|
boxShadow: "0 0 40px rgba(245,158,11,0.08), inset 0 1px 0 rgba(245,158,11,0.1)",
|
||||||
}
|
} : {})
|
||||||
: {}
|
}}
|
||||||
}
|
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.35 }}
|
transition={{ duration: 0.35 }}
|
||||||
whileHover={{ scale: 1.01 }}
|
|
||||||
>
|
>
|
||||||
{/* Top row: icon + toggle */}
|
{/* Top row: icon + toggle */}
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
{/* Icon */}
|
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-16 h-16 rounded-2xl flex items-center justify-center"
|
className="w-16 h-16 rounded-2xl flex items-center justify-center"
|
||||||
style={{
|
style={{
|
||||||
background: isOn
|
background: isOn ? "rgba(245,158,11,0.18)" : "rgba(255,255,255,0.06)",
|
||||||
? "rgba(245,158,11,0.18)"
|
|
||||||
: "rgba(255,255,255,0.06)",
|
|
||||||
boxShadow: isOn ? "0 0 28px rgba(245,158,11,0.35)" : "none",
|
boxShadow: isOn ? "0 0 28px rgba(245,158,11,0.35)" : "none",
|
||||||
}}
|
}}
|
||||||
animate={{ scale: isOn ? 1 : 0.97 }}
|
animate={{ scale: isOn ? 1 : 0.97 }}
|
||||||
transition={{ duration: 0.3 }}
|
|
||||||
>
|
>
|
||||||
<Lightbulb
|
<Lightbulb size={32} color={isOn ? "#f59e0b" : "rgba(255,255,255,0.35)"} fill={isOn ? "#f59e0b" : "none"} strokeWidth={isOn ? 1.5 : 2} />
|
||||||
size={32}
|
|
||||||
color={isOn ? "#f59e0b" : "rgba(255,255,255,0.35)"}
|
|
||||||
fill={isOn ? "#f59e0b" : "none"}
|
|
||||||
strokeWidth={isOn ? 1.5 : 2}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* Toggle */}
|
{/* Toggle switch */}
|
||||||
<motion.div
|
<button
|
||||||
className={`toggle-track ${isOn ? "toggle-on" : ""}`}
|
|
||||||
style={{
|
|
||||||
background: isOn
|
|
||||||
? "#f59e0b"
|
|
||||||
: "rgba(255,255,255,0.1)",
|
|
||||||
boxShadow: isOn ? "0 0 16px rgba(245,158,11,0.5)" : "none",
|
|
||||||
}}
|
|
||||||
onClick={handleToggle}
|
onClick={handleToggle}
|
||||||
whileTap={{ scale: 0.88 }}
|
style={{
|
||||||
|
width: "60px",
|
||||||
|
height: "32px",
|
||||||
|
borderRadius: "16px",
|
||||||
|
background: isOn ? "#f59e0b" : "rgba(255,255,255,0.12)",
|
||||||
|
boxShadow: isOn ? "0 0 16px rgba(245,158,11,0.5)" : "none",
|
||||||
|
border: "none",
|
||||||
|
cursor: "pointer",
|
||||||
|
position: "relative",
|
||||||
|
transition: "background 0.2s ease, box-shadow 0.2s ease",
|
||||||
|
flexShrink: 0,
|
||||||
|
WebkitTapHighlightColor: "transparent",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="toggle-thumb" />
|
<motion.div
|
||||||
</motion.div>
|
style={{
|
||||||
|
width: "24px",
|
||||||
|
height: "24px",
|
||||||
|
borderRadius: "12px",
|
||||||
|
background: "white",
|
||||||
|
position: "absolute",
|
||||||
|
top: "4px",
|
||||||
|
boxShadow: "0 2px 6px rgba(0,0,0,0.3)",
|
||||||
|
}}
|
||||||
|
animate={{ left: isOn ? "32px" : "4px" }}
|
||||||
|
transition={{ type: "spring", stiffness: 500, damping: 30 }}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Name + state */}
|
{/* Name + state */}
|
||||||
<div className="mt-auto">
|
<div className="mt-4">
|
||||||
<div
|
<div className="text-xl font-bold leading-tight" style={{ color: isOn ? "#f59e0b" : "var(--text-primary)" }}>
|
||||||
className="text-xl font-bold leading-tight"
|
|
||||||
style={{ color: isOn ? "#f59e0b" : "var(--text-primary)" }}
|
|
||||||
>
|
|
||||||
{name}
|
{name}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div className="text-sm mt-1 font-medium" style={{ color: isOn ? "rgba(245,158,11,0.7)" : "var(--text-secondary)" }}>
|
||||||
className="text-sm mt-1 font-medium"
|
|
||||||
style={{ color: isOn ? "rgba(245,158,11,0.7)" : "var(--text-secondary)" }}
|
|
||||||
>
|
|
||||||
{isOn ? (showSlider ? `Яркость ${localBrightness}%` : "Включён") : "Выключен"}
|
{isOn ? (showSlider ? `Яркость ${localBrightness}%` : "Включён") : "Выключен"}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Brightness slider */}
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{showSlider && isOn && (
|
{showSlider && isOn && (
|
||||||
<motion.div
|
<motion.div className="mt-4" initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: "auto" }} exit={{ opacity: 0, height: 0 }}>
|
||||||
className="mt-4"
|
|
||||||
initial={{ opacity: 0, height: 0 }}
|
|
||||||
animate={{ opacity: 1, height: "auto" }}
|
|
||||||
exit={{ opacity: 0, height: 0 }}
|
|
||||||
transition={{ duration: 0.25 }}
|
|
||||||
>
|
|
||||||
<div className="relative pt-1">
|
|
||||||
<div
|
|
||||||
className="absolute left-0 rounded-full pointer-events-none"
|
|
||||||
style={{
|
|
||||||
width: `${localBrightness}%`,
|
|
||||||
height: "6px",
|
|
||||||
top: "calc(50% + 4px)",
|
|
||||||
background: "linear-gradient(90deg, rgba(245,158,11,0.5), rgba(245,158,11,0.9))",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range" min={5} max={100} value={localBrightness}
|
||||||
min={5}
|
|
||||||
max={100}
|
|
||||||
value={localBrightness}
|
|
||||||
onChange={(e) => setLocalBrightness(parseInt(e.target.value))}
|
onChange={(e) => setLocalBrightness(parseInt(e.target.value))}
|
||||||
onMouseUp={(e) =>
|
onMouseUp={(e) => handleBrightnessChange(parseInt((e.target as HTMLInputElement).value))}
|
||||||
handleBrightnessChange(parseInt((e.target as HTMLInputElement).value))
|
onTouchEnd={(e) => handleBrightnessChange(parseInt((e.target as HTMLInputElement).value))}
|
||||||
}
|
className="w-full"
|
||||||
onTouchEnd={(e) =>
|
style={{ accentColor: "#f59e0b" }}
|
||||||
handleBrightnessChange(parseInt((e.target as HTMLInputElement).value))
|
|
||||||
}
|
|
||||||
className="w-full relative z-10"
|
|
||||||
style={{ background: "transparent" }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
Reference in New Issue
Block a user