mirror of
https://github.com/logseq/logseq.git
synced 2026-05-30 07:29:48 +00:00
feat: extend icon-picker topbar arrow-nav and unify focus styling
The icon-picker topbar now ropes the color swatch and trash button into the same arrow-key rove as the tabs (data-topbar-stop + the controller's :topbar region). Tabs auto-activate when arrow-roved over (e.detail = 0 discriminates programmatic clicks from real ones, so the on-change handler keeps focus inside the topbar instead of bouncing to search). Topbar focus styling is unified across both pickers so back / tabs / segments / color / trash all paint the same blue ring (the trio of browser-default outline, custom --lx-accent-09 outline, and shui's --ring box-shadow no longer disagrees). Trash and inactive tabs need opacity:1 on :focus-visible so the base opacity-60 doesn't dim the ring into a muted glow. Tile-to-tile transition no longer animates outline-color: animating it from `currentColor` (e.g. red on a tinted icon) briefly tinted the focus ring red before the accent color landed. Width and offset still glide so the ring grows/shrinks smoothly; color snaps instantly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -3833,7 +3833,7 @@
|
||||
[:span.absolute.hidden {:ref *el-ref}]))
|
||||
|
||||
(rum/defc color-picker
|
||||
[*color on-select! & {:keys [on-hover! on-hover-end!]}]
|
||||
[*color on-select! & {:keys [on-hover! on-hover-end! button-attrs]}]
|
||||
(let [;; Defensive: never let the CSS sentinel "inherit" leak into React state.
|
||||
initial-color (let [v @*color] (when (and v (not= v "inherit")) v))
|
||||
[color, set-color!] (rum/use-state initial-color)
|
||||
@@ -3900,8 +3900,9 @@
|
||||
[])
|
||||
|
||||
[:button.color-picker-trigger
|
||||
{:ref *el
|
||||
:on-click (fn [^js e] (shui/popup-show! (.-target e) content-fn {:content-props {:side "bottom" :side-offset 6}}))}
|
||||
(merge button-attrs
|
||||
{:ref *el
|
||||
:on-click (fn [^js e] (shui/popup-show! (.-target e) content-fn {:content-props {:side "bottom" :side-offset 6}}))})
|
||||
(if color
|
||||
[:span.color-picker-fill {:style {:background-color color}}]
|
||||
[:span.color-picker-empty
|
||||
@@ -4308,14 +4309,22 @@
|
||||
:*input-ref *input-ref
|
||||
:flat-items flat-items
|
||||
:sections sections
|
||||
:*virtuoso-ref *virtuoso-ref})
|
||||
:*virtuoso-ref *virtuoso-ref
|
||||
:topbar-selector ".cp__emoji-icon-picker .tabs-section [data-topbar-stop]"})
|
||||
(ui/tab-items
|
||||
{:tabs [[:all "All"] [:emoji "Emojis"] [:icon "Icons"] [:custom "Custom"]]
|
||||
:active @*tab
|
||||
:on-change (fn [id _e]
|
||||
:on-change (fn [id ^js e]
|
||||
(reset! *tab id)
|
||||
(reset! *focus-region :search)
|
||||
(reset! *highlighted-index nil))})
|
||||
(reset! *highlighted-index nil)
|
||||
;; Only return focus to search for genuine mouse
|
||||
;; clicks. Programmatic .click() from keyboard
|
||||
;; arrow-rove (handle-topbar-keys auto-activate)
|
||||
;; has e.detail = 0; real clicks are >= 1. Keeps
|
||||
;; arrow nav inside the topbar region.
|
||||
(when (and e (pos? (.-detail e)))
|
||||
(reset! *focus-region :search)))
|
||||
:button-attrs {:data-topbar-stop "tab"}})
|
||||
[:div.tab-actions
|
||||
;; color picker (always visible)
|
||||
(color-picker *color (fn [c]
|
||||
@@ -4338,10 +4347,12 @@
|
||||
:color c})))
|
||||
:on-hover-end! (when preview-target-db-id
|
||||
(fn []
|
||||
(state/set-state! :ui/icon-hover-preview nil))))
|
||||
(state/set-state! :ui/icon-hover-preview nil)))
|
||||
:button-attrs {:data-topbar-stop "color"})
|
||||
;; delete button
|
||||
(when del-btn?
|
||||
(shui/button {:variant :outline :size :sm :data-action "del"
|
||||
:data-topbar-stop "trash"
|
||||
:on-click #(on-chosen nil)}
|
||||
(shui/tabler-icon "trash" {:size 17})))]]
|
||||
|
||||
@@ -4368,11 +4379,11 @@
|
||||
(shui/popup-hide!)
|
||||
(reset-q!)))
|
||||
|
||||
;; Up Arrow / Shift+Tab: move to tab bar
|
||||
;; Up Arrow / Shift+Tab: move to topbar at the active tab
|
||||
(or (= code 38)
|
||||
(and (= code 9) (.-shiftKey e)))
|
||||
(do (util/stop e)
|
||||
(reset! *focus-region :tabs)
|
||||
(reset! *focus-region :topbar)
|
||||
(reset! *highlighted-index nil)
|
||||
(when-let [^js cnt (some-> (rum/deref *input-ref) (.closest ".cp__emoji-icon-picker"))]
|
||||
(when-let [active-tab (.querySelector cnt "[data-active='true'].tab-item")]
|
||||
|
||||
@@ -124,7 +124,15 @@
|
||||
|
||||
> button {
|
||||
@apply flex items-center justify-center rounded-lg active:opacity-70;
|
||||
transition: background-color 150ms, outline-color 150ms;
|
||||
/* Animate width + offset so the ring grows/shrinks smoothly when
|
||||
the highlight state changes. outline-color is intentionally NOT
|
||||
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. */
|
||||
transition: background-color 150ms,
|
||||
outline-width 150ms,
|
||||
outline-offset 150ms;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--rx-gray-05);
|
||||
@@ -162,6 +170,13 @@
|
||||
&[data-action=del] {
|
||||
@apply !w-6 !h-6 overflow-hidden rounded-md opacity-60
|
||||
hover:text-red-rx-09 hover:opacity-90;
|
||||
|
||||
/* Force opacity 1 on keyboard focus so the focus ring paints
|
||||
crisply; the base opacity-60 otherwise dims the outline into
|
||||
a muted glow. */
|
||||
&:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,8 +259,11 @@
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
/* Keyboard focus state */
|
||||
/* Keyboard focus state. Force opacity 1 so the focus ring on an
|
||||
inactive tab paints fully (the base rule sets opacity-60, which
|
||||
would otherwise dim the outline). */
|
||||
&:focus-visible {
|
||||
opacity: 1;
|
||||
outline: 2px solid var(--lx-accent-09);
|
||||
outline-offset: -2px;
|
||||
border-radius: 4px;
|
||||
@@ -708,9 +726,47 @@
|
||||
.ui__button[data-action=del] {
|
||||
@apply !w-6 !h-6 overflow-hidden rounded-md opacity-60;
|
||||
@apply hover:text-red-rx-09 hover:opacity-90;
|
||||
|
||||
/* Keyboard focus state. Force opacity 1 so the focus ring paints at
|
||||
full alpha; otherwise the button's base opacity-60 dims the outline
|
||||
and reads as a "muted glow" instead of a clear focus indicator. */
|
||||
&:focus-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Unified focus ring across the asset-picker topbar. Without this, three
|
||||
different mechanisms render three different blues: back button uses the
|
||||
browser-default outline (system blue), segments use our custom outline
|
||||
(--lx-accent-09), and the trash button uses shui's box-shadow ring
|
||||
(Tailwind --ring token). This rule overrides all three to match. */
|
||||
.asset-picker-topbar :is(
|
||||
.back-button,
|
||||
.segmented-control .segment,
|
||||
.ui__button[data-action=del]
|
||||
):focus-visible {
|
||||
outline: 2px solid var(--lx-accent-09);
|
||||
outline-offset: 2px;
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Same unified treatment for the icon-picker topbar. The color-picker
|
||||
trigger and the trash button each had their own focus stylings (browser
|
||||
default + shui's --ring respectively); pulling them onto --lx-accent-09
|
||||
so all topbar stops match. The .tab-item rule (above) already uses the
|
||||
same token; keep it as-is. */
|
||||
.cp__emoji-icon-picker .tabs-section :is(
|
||||
.color-picker-trigger,
|
||||
.ui__button[data-action=del]
|
||||
):focus-visible {
|
||||
outline: 2px solid var(--lx-accent-09);
|
||||
outline-offset: 2px;
|
||||
box-shadow: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Search input matching icon picker */
|
||||
.asset-picker-search {
|
||||
@apply py-1;
|
||||
|
||||
Reference in New Issue
Block a user