diff --git a/src/main/frontend/components/icon.cljs b/src/main/frontend/components/icon.cljs index 9c1a5dcd01..25cd1a1009 100644 --- a/src/main/frontend/components/icon.cljs +++ b/src/main/frontend/components/icon.cljs @@ -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"))] diff --git a/src/main/frontend/components/icon.css b/src/main/frontend/components/icon.css index 9d84ae25ac..03f8cfec7c 100644 --- a/src/main/frontend/components/icon.css +++ b/src/main/frontend/components/icon.css @@ -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 {