mirror of
https://github.com/logseq/logseq.git
synced 2026-05-29 15:09:41 +00:00
feat: diagonal color wave across icon-picker grid
When the user hovers or commits a swatch, the icons in the picker grid transition to the new color in a staggered wave from top-left to bottom-right rather than snapping in lockstep — borrowed from Linear's icon picker. Mechanically: each cell renderer (icon-cp / emoji-cp / text-cp / avatar-cp) stamps its grid position as inline `--r` / `--c` custom properties, sourced from `pane-section`'s render loop. The existing `--ls-color-icon-preset` cascade already carries the chosen color down to the cells via currentColor inheritance; we add `color 320ms cubic-bezier(.4,0,.2,1)` to the per-button transition with `transition-delay: calc(var(--c) * 22ms + var(--r) * 36ms)`, so each cell starts its glide on its own clock. Two design decisions worth flagging: - **Hover preview drives the wave too**, not just commit. CSS transitions hold the old value during their `delay` window and retarget cleanly when the property changes mid-flight, so rapid cursor sweeps gracefully chase without flicker — far cells just hold steady until the user settles. - **Material's "standard" easing** (`cubic-bezier(0.4, 0, 0.2, 1)`) over `ease-out`: each cell pauses, glides through the middle, and settles. The pause makes the stagger legible — neighbors are visibly "starting" while predecessors are mid-glide, which is what reads as a wave rather than a snap-and-fade. `prefers-reduced-motion: reduce` strips the color transition so the grid snaps instantly for users who opt out of animation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1353,7 +1353,7 @@
|
||||
[:div.its.icons-row items])
|
||||
|
||||
(rum/defc icon-cp < rum/static
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id]}]
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id wave]}]
|
||||
(let [icon-id (get-in icon-item [:data :value])
|
||||
icon-name (or (:label icon-item) icon-id)
|
||||
color (get-in icon-item [:data :color])
|
||||
@@ -1367,6 +1367,7 @@
|
||||
:class (cond
|
||||
(= my-id highlighted-id) "is-highlighted"
|
||||
(= my-id ghost-highlighted-id) "is-ghost-highlighted")
|
||||
:style (when wave {"--r" (:r wave) "--c" (:c wave)})
|
||||
:title icon-name
|
||||
:on-click (fn [e]
|
||||
(on-chosen e (cond-> {:type :tabler-icon
|
||||
@@ -1383,7 +1384,7 @@
|
||||
(ui/icon icon-id' {:size 24}))]))
|
||||
|
||||
(rum/defc emoji-cp < rum/static
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id]}]
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id wave]}]
|
||||
(let [emoji-id (get-in icon-item [:data :value])
|
||||
emoji-name (or (:label icon-item) emoji-id)
|
||||
my-id (:id icon-item)]
|
||||
@@ -1394,6 +1395,7 @@
|
||||
:class (cond
|
||||
(= my-id highlighted-id) "is-highlighted"
|
||||
(= my-id ghost-highlighted-id) "is-ghost-highlighted")
|
||||
:style (when wave {"--r" (:r wave) "--c" (:c wave)})
|
||||
:title emoji-name
|
||||
:on-click (fn [e]
|
||||
(on-chosen e {:type :emoji
|
||||
@@ -1408,7 +1410,7 @@
|
||||
:style {:line-height 1}}]]))
|
||||
|
||||
(rum/defc text-cp < rum/static
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id]}]
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id wave]}]
|
||||
(let [text-value (get-in icon-item [:data :value])
|
||||
text-color (get-in icon-item [:data :color])
|
||||
my-id (:id icon-item)
|
||||
@@ -1422,6 +1424,7 @@
|
||||
:class (cond
|
||||
(= my-id highlighted-id) "is-highlighted"
|
||||
(= my-id ghost-highlighted-id) "is-ghost-highlighted")
|
||||
:style (when wave {"--r" (:r wave) "--c" (:c wave)})
|
||||
:title text-value
|
||||
:on-click (fn [e]
|
||||
(on-chosen e {:type :text
|
||||
@@ -1435,7 +1438,7 @@
|
||||
display-text]))
|
||||
|
||||
(rum/defc avatar-cp < rum/static
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id]}]
|
||||
[icon-item {:keys [on-chosen hover highlighted-id ghost-highlighted-id wave]}]
|
||||
(let [avatar-value (get-in icon-item [:data :value])
|
||||
backgroundColor (or (get-in icon-item [:data :backgroundColor])
|
||||
(colors/variable :gray :09))
|
||||
@@ -1449,6 +1452,7 @@
|
||||
{:tabIndex "-1"
|
||||
:data-item-id my-id
|
||||
:title avatar-value
|
||||
:style (when wave {"--r" (:r wave) "--c" (:c wave)})
|
||||
:class (str "p-0 border-0 bg-transparent cursor-pointer"
|
||||
(cond
|
||||
(= my-id highlighted-id) " is-highlighted"
|
||||
@@ -1571,12 +1575,18 @@
|
||||
(catch js/Error e
|
||||
(js/console.error e)
|
||||
nil))]
|
||||
(mapv #(render-fn % opts) icons))))}
|
||||
(vec (map-indexed
|
||||
(fn [c-idx item]
|
||||
(render-fn item (assoc opts :wave {:r idx :c c-idx})))
|
||||
icons)))))}
|
||||
|
||||
searching?
|
||||
(assoc :custom-scroll-parent (some-> (rum/deref *el-ref) (.closest ".bd-scroll"))))))
|
||||
[:div.its
|
||||
(map #(render-fn % opts) icon-items)]))]))
|
||||
(map-indexed
|
||||
(fn [i item]
|
||||
(render-fn item (assoc opts :wave {:r (quot i 9) :c (mod i 9)})))
|
||||
icon-items)]))]))
|
||||
|
||||
(rum/defc emojis-cp < rum/static
|
||||
[emojis* opts]
|
||||
@@ -3975,7 +3985,11 @@
|
||||
:on-select! on-select!
|
||||
:on-hover! on-hover!
|
||||
:on-hover-end! on-hover-end!}))]
|
||||
;; Display effect — fires for hover and committed color. Updates CSS var only.
|
||||
;; Display effect on the picker root — fires for both hover and committed
|
||||
;; color. Combined with the per-cell `--r`/`--c` `transition-delay` in CSS,
|
||||
;; every change to the var (hover preview OR commit) plays the diagonal
|
||||
;; wave across the grid. Rapid hover sweeps gracefully retarget mid-flight
|
||||
;; because each cell's delay holds its current value until activation.
|
||||
(hooks/use-effect!
|
||||
(fn []
|
||||
(when-let [^js picker (some-> (rum/deref *el) (.closest ".cp__emoji-icon-picker"))]
|
||||
|
||||
@@ -129,10 +129,24 @@
|
||||
transitioned — animating it from `currentColor` (the icon's
|
||||
tint, e.g. red) would briefly tint the ring red before the
|
||||
accent color landed. With color set instantly by the highlight
|
||||
rules, the ring just grows in the right color from t=0. */
|
||||
rules, the ring just grows in the right color from t=0.
|
||||
|
||||
`color` is also transitioned, with a per-cell delay computed
|
||||
from the inline --r / --c custom properties. When the user
|
||||
commits a new icon color, --ls-color-icon-preset on the picker
|
||||
root changes, and each cell's `color` (inherited via
|
||||
currentColor) glides into the new value at its own delay,
|
||||
producing a diagonal "wave" across the grid (top-left first,
|
||||
bottom-right last). Hover preview is intentionally NOT routed
|
||||
through the CSS var (it goes to the trigger and page header
|
||||
via :ui/icon-hover-preview state) so the wave has a real
|
||||
old→new delta to interpolate at commit time. */
|
||||
transition: background-color 150ms,
|
||||
outline-width 150ms,
|
||||
outline-offset 150ms;
|
||||
outline-offset 150ms,
|
||||
color 320ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-delay: 0ms, 0ms, 0ms,
|
||||
calc(var(--c, 0) * 22ms + var(--r, 0) * 36ms);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--rx-gray-05);
|
||||
@@ -151,6 +165,17 @@
|
||||
.virtuoso-item-list {
|
||||
@apply pt-1 pb-4 px-2;
|
||||
}
|
||||
|
||||
/* Honor reduced-motion: drop the staggered color transition so the
|
||||
grid snaps to the new color rather than animating across cells. */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
> .its > button,
|
||||
.icons-row > button {
|
||||
transition: background-color 150ms,
|
||||
outline-width 150ms,
|
||||
outline-offset 150ms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icons .ui__icon {
|
||||
|
||||
Reference in New Issue
Block a user