feat(ui): implement smooth Apple-style spring animation for toggle switch
Motivation: The previous toggle switch animation was abrupt and rigid. The clickable area also spanned the entire row width, which led to unintentional clicks. Implementation: - CSS: Replaced static transitions with cubic-bezier spring physics, hardware-accelerated transforms (translate), and pressed-state squish feedback. - JS: Wrapped the switch in a constrained flex container, replacing the full-width <label> wrapper to fix the clickable area.
This commit is contained in:
@@ -473,59 +473,81 @@
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
SWITCH / TOGGLE
|
||||
SWITCH / TOGGLE — Ultra-smooth Apple-style spring animation
|
||||
============================================================ */
|
||||
.llm-pm-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 42px;
|
||||
height: 22px;
|
||||
width: 44px;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.llm-pm-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.llm-pm-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
transition: var(--glass-transition);
|
||||
border-radius: 22px;
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
inset: 0;
|
||||
border-radius: 24px;
|
||||
border: 1.5px solid rgba(255, 255, 255, 0.1);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.35);
|
||||
transition:
|
||||
background-color 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
border-color 0.35s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
box-shadow 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
will-change: background-color;
|
||||
}
|
||||
|
||||
/* The thumb (white circle) — spring physics */
|
||||
.llm-pm-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
position: absolute;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
background-color: var(--glass-text-secondary);
|
||||
transition: var(--glass-transition);
|
||||
top: 50%;
|
||||
transform: translateY(-50%) translateX(0px);
|
||||
background: rgba(200, 200, 210, 0.9);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4), 0 0 0 0.5px rgba(0, 0, 0, 0.15);
|
||||
/* cubic-bezier(0.34, 1.56, 0.64, 1): fast shoot, slight overshoot, then settle — the "Apple spring" */
|
||||
transition:
|
||||
transform 0.38s cubic-bezier(0.34, 1.56, 0.64, 1),
|
||||
width 0.18s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
background 0.25s ease;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* Checked (ON) state */
|
||||
.llm-pm-switch input:checked+.llm-pm-slider {
|
||||
background: linear-gradient(135deg, var(--glass-accent), #8b5cf6);
|
||||
background-color: var(--glass-accent);
|
||||
border-color: transparent;
|
||||
box-shadow: 0 0 12px var(--glass-accent-glow),
|
||||
box-shadow:
|
||||
0 0 0 3px rgba(99, 102, 241, 0.18),
|
||||
0 0 14px rgba(99, 102, 241, 0.4),
|
||||
inset 0 1px 2px rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.llm-pm-switch input:checked+.llm-pm-slider:before {
|
||||
transform: translateX(20px);
|
||||
background-color: white;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
|
||||
transform: translateY(-50%) translateX(20px);
|
||||
background: white;
|
||||
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
/* Press feedback — thumb squishes horizontally on mousedown */
|
||||
.llm-pm-switch:active .llm-pm-slider:before {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
.llm-pm-switch:active input:checked+.llm-pm-slider:before {
|
||||
transform: translateY(-50%) translateX(18px);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
@@ -911,7 +911,7 @@ class ProviderManager {
|
||||
// Build DOM
|
||||
const fields = [
|
||||
$el("div.llm-pm-field", [
|
||||
$el("label", [
|
||||
$el("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: "0.8em", fontWeight: "600", color: "var(--glass-text-secondary)", textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: "8px" } }, [
|
||||
$el("span", t("provider_name")),
|
||||
$el("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, [
|
||||
$el("span", { style: { fontSize: "0.8em", fontWeight: "normal" } }, t("enable_nodes")),
|
||||
|
||||
Reference in New Issue
Block a user