mirror of
https://github.com/logseq/logseq.git
synced 2026-05-16 00:42:20 +00:00
Replace flat row flash with accent shimmer sweep and fix combo key press animation
- Row highlight on shortcut trigger now uses a horizontal gradient sweep (background-position animation) instead of a static background flash, providing a distinct visual language from the focus ring - Shimmer uses theme-aware accent color via color-mix on keymap page, with a neutral fallback in shui.css base styles - Guard against animation spam: clearTimeout+reset pattern prevents stale timeout accumulation during rapid key repeat; reflow only forced on first trigger, class stays applied until last trigger ends - Fix combo key press animation: animate the container (the keycap) instead of individual kbd elements, so translateY and box-shadow follow the container's border-radius correctly - Scope row shimmer to .shui-shortcut-row/.shortcut-row elements to prevent accidental shimmer on standalone badge containers - Respect prefers-reduced-motion for all new animations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
62
deps/shui/src/logseq/shui/shortcut.cljs
vendored
62
deps/shui/src/logseq/shui/shortcut.cljs
vendored
@@ -157,30 +157,58 @@
|
||||
%)))))))
|
||||
|
||||
(def ^:private press-animation-ms 160)
|
||||
(def ^:private row-shimmer-ms 750)
|
||||
|
||||
(defn- highlight-row!
|
||||
"Add or remove the row highlight class on the closest shortcut row ancestor."
|
||||
"Add or remove the row highlight class on the closest shortcut row ancestor.
|
||||
On first trigger, forces a reflow to start the CSS animation.
|
||||
On repeated triggers, the class stays applied (no reflow) and the
|
||||
removal timer is reset so it fires after the last trigger."
|
||||
[^js container add?]
|
||||
(when-let [^js row (or (.closest container ".shui-shortcut-row, .shortcut-row")
|
||||
(.-parentElement container))]
|
||||
(if add?
|
||||
(.add (.-classList row) "shui-shortcut-row--pressed")
|
||||
(.remove (.-classList row) "shui-shortcut-row--pressed"))))
|
||||
(let [already? (.contains (.-classList row) "shui-shortcut-row--pressed")]
|
||||
;; Cancel any pending removal
|
||||
(when-let [t (.-__sweepTimer row)]
|
||||
(js/clearTimeout t)
|
||||
(set! (.-__sweepTimer row) nil))
|
||||
;; Only force reflow on first trigger to start the animation
|
||||
(when-not already?
|
||||
(.add (.-classList row) "shui-shortcut-row--pressed"))
|
||||
;; Schedule removal after the last trigger
|
||||
(set! (.-__sweepTimer row)
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(.remove (.-classList row) "shui-shortcut-row--pressed")
|
||||
(set! (.-__sweepTimer row) nil))
|
||||
row-shimmer-ms)))
|
||||
(do
|
||||
(when-let [t (.-__sweepTimer row)]
|
||||
(js/clearTimeout t)
|
||||
(set! (.-__sweepTimer row) nil))
|
||||
(.remove (.-classList row) "shui-shortcut-row--pressed")))))
|
||||
|
||||
(defn- animate-element!
|
||||
"Add pressed class, optionally highlight row, then auto-reset after animation."
|
||||
"Add pressed class, optionally highlight row, then auto-reset after animation.
|
||||
Key badge uses a simple clearTimeout+reset pattern to avoid stale removals."
|
||||
[^js el ^js container highlight-row?]
|
||||
(.add (.-classList el) "shui-shortcut-key-pressed")
|
||||
(when highlight-row? (highlight-row! container true))
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(.remove (.-classList el) "shui-shortcut-key-pressed")
|
||||
(when highlight-row? (highlight-row! container false)))
|
||||
press-animation-ms))
|
||||
;; Clear any pending badge removal, then schedule a new one
|
||||
(when-let [t (.-__badgeTimer el)]
|
||||
(js/clearTimeout t))
|
||||
(set! (.-__badgeTimer el)
|
||||
(js/setTimeout
|
||||
(fn []
|
||||
(.remove (.-classList el) "shui-shortcut-key-pressed")
|
||||
(set! (.-__badgeTimer el) nil))
|
||||
press-animation-ms)))
|
||||
|
||||
(defn shortcut-press!
|
||||
"Central helper to trigger key press animation.
|
||||
Finds all nodes with matching data-shortcut-binding and animates individual keys.
|
||||
For combo keys, animates the container (the whole keycap depresses).
|
||||
For separate keys, animates individual kbd elements.
|
||||
Optionally highlights parent row.
|
||||
|
||||
Args:
|
||||
@@ -192,11 +220,15 @@
|
||||
selector (str "[data-shortcut-binding=\"" normalized "\"]")
|
||||
containers (.querySelectorAll js/document selector)]
|
||||
(doseq [^js container (array-seq containers)]
|
||||
(let [keys (.querySelectorAll container "kbd.shui-shortcut-key")]
|
||||
(if (> (.-length keys) 0)
|
||||
(doseq [^js key-el (array-seq keys)]
|
||||
(animate-element! key-el container highlight-row?))
|
||||
(animate-element! container container highlight-row?)))))))
|
||||
(if (.contains (.-classList container) "shui-shortcut-combo")
|
||||
;; Combo keys: animate the container as a unit (one keycap)
|
||||
(animate-element! container container highlight-row?)
|
||||
;; Separate keys: animate each kbd individually
|
||||
(let [keys (.querySelectorAll container "kbd.shui-shortcut-key")]
|
||||
(if (> (.-length keys) 0)
|
||||
(doseq [^js key-el (array-seq keys)]
|
||||
(animate-element! key-el container highlight-row?))
|
||||
(animate-element! container container highlight-row?))))))))
|
||||
|
||||
(rum/defc combo-keys
|
||||
"Renders combo keys (simultaneous key combinations) with separator."
|
||||
|
||||
@@ -279,6 +279,7 @@ div[data-radix-popper-content-wrapper] {
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
transition: transform 140ms ease-out, box-shadow 140ms ease-out;
|
||||
}
|
||||
|
||||
/* Glow effect for combo and separate keys */
|
||||
@@ -377,33 +378,52 @@ kbd.shui-shortcut-key,
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
|
||||
/* Key press animation */
|
||||
/* Key press animation — separate keys: animate individual kbd elements */
|
||||
kbd.shui-shortcut-key-pressed,
|
||||
.shui-shortcut-key-pressed {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Key press animation with glow - preserve glow effect */
|
||||
/* Combo keys: animate the container */
|
||||
.shui-shortcut-combo.shui-shortcut-glow.shui-shortcut-key-pressed {
|
||||
box-shadow: rgba(255, 255, 255, 0.15) 0px 2px 0px 0px inset, rgba(0, 0, 0, 0.15) 0px 0px 0px 0px inset, 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Separate keys: animate individual keys */
|
||||
.shui-shortcut-separate.shui-shortcut-glow kbd.shui-shortcut-key-pressed {
|
||||
box-shadow: rgba(255, 255, 255, 0.15) 0px 2px 0px 0px inset, rgba(0, 0, 0, 0.15) 0px 0px 0px 0px inset, 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Key press animation without glow */
|
||||
.shui-shortcut-combo:not(.shui-shortcut-glow) kbd.shui-shortcut-key-pressed,
|
||||
.shui-shortcut-separate:not(.shui-shortcut-glow) kbd.shui-shortcut-key-pressed {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Row highlight animation */
|
||||
.shui-shortcut-row--pressed {
|
||||
background-color: rgba(223, 239, 254, 0.1);
|
||||
transition: background-color 160ms ease-out;
|
||||
/* Key press animation — combo keys: animate the container (the whole keycap) */
|
||||
.shui-shortcut-combo.shui-shortcut-key-pressed {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.shui-shortcut-combo.shui-shortcut-glow.shui-shortcut-key-pressed {
|
||||
box-shadow: rgba(255, 255, 255, 0.15) 0px 2px 0px 0px inset, rgba(0, 0, 0, 0.15) 0px 0px 0px 0px inset, 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.shui-shortcut-combo:not(.shui-shortcut-glow).shui-shortcut-key-pressed {
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
/* Row highlight animation — accent band sweep (only on actual row elements) */
|
||||
.shui-shortcut-row.shui-shortcut-row--pressed,
|
||||
.shortcut-row.shui-shortcut-row--pressed {
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
transparent 35%,
|
||||
rgba(223, 239, 254, 0.08) 50%,
|
||||
transparent 65%,
|
||||
transparent 100%
|
||||
);
|
||||
background-size: 300% 100%;
|
||||
background-repeat: no-repeat;
|
||||
animation: shortcut-row-sweep 700ms ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes shortcut-row-sweep {
|
||||
from { background-position: -50% 0; }
|
||||
to { background-position: 150% 0; }
|
||||
}
|
||||
|
||||
/* Ensure consistent height for shortcut containers */
|
||||
@@ -417,14 +437,16 @@ kbd.shui-shortcut-key-pressed,
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
kbd.shui-shortcut-key,
|
||||
.shui-shortcut-key,
|
||||
.shui-shortcut-row--pressed {
|
||||
.shui-shortcut-combo {
|
||||
transition: none;
|
||||
transform: none;
|
||||
box-shadow: 0 0 0 transparent;
|
||||
}
|
||||
|
||||
.shui-shortcut-row--pressed {
|
||||
background-color: transparent;
|
||||
|
||||
.shui-shortcut-row.shui-shortcut-row--pressed,
|
||||
.shortcut-row.shui-shortcut-row--pressed {
|
||||
animation: none !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,9 +215,19 @@ button.shortcut-feedback-action {
|
||||
background-color: var(--lx-gray-05-alpha, var(--rx-gray-05-alpha));
|
||||
}
|
||||
|
||||
/* Shortcut keypress highlight — briefly flashes the row when the user presses a shortcut */
|
||||
/* Shortcut keypress shimmer — accent band sweeps left-to-right across the row */
|
||||
&.shui-shortcut-row--pressed {
|
||||
background-color: var(--lx-gray-05-alpha, var(--rx-gray-05-alpha));
|
||||
background-image: linear-gradient(
|
||||
90deg,
|
||||
transparent 0%,
|
||||
transparent 35%,
|
||||
color-mix(in srgb, var(--lx-accent-09, hsl(var(--primary))) 10%, transparent) 50%,
|
||||
transparent 65%,
|
||||
transparent 100%
|
||||
);
|
||||
background-size: 300% 100%;
|
||||
background-repeat: no-repeat;
|
||||
animation: shortcut-row-sweep 700ms ease-out forwards;
|
||||
}
|
||||
|
||||
/* Keyboard navigation focus ring — two levels above hover for clear distinction */
|
||||
@@ -289,6 +299,11 @@ button.shortcut-feedback-action {
|
||||
|
||||
}
|
||||
|
||||
@keyframes shortcut-row-sweep {
|
||||
from { background-position: -50% 0; }
|
||||
to { background-position: 150% 0; }
|
||||
}
|
||||
|
||||
/* === V3 SHORTCUT POPOVER === */
|
||||
.shortcut-popover {
|
||||
@apply flex flex-col;
|
||||
|
||||
Reference in New Issue
Block a user