Keyboard arrow through pins and notifications

This commit is contained in:
Jason Zimdars
2025-07-30 16:07:55 -05:00
parent d3287adba3
commit 6227cf9a8b
12 changed files with 118 additions and 55 deletions
+1 -1
View File
@@ -1 +1 @@
<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path d="m1.50197 7.275 5.764-5.765c.57744-.579297 1.51515-.580803 2.09445-.00337.07782.07757.14676.16355.20555.25637l4.10003 6.443c1.8579-.44233 3.8147-.15604 5.468.8.7059.4121.944 1.3184.5319 2.0243-.0641.1098-.142.2108-.2319.3007l-8.105 8.1c-.3257.3218-.7805.4772-1.235.422-.45519-.0584-.85766-.3243-1.09003-.72-.96021-1.6515-1.24675-3.6096-.8-5.467l-6.443-4.1c-.68979-.43852-.893482-1.3532-.45496-2.04299.05793-.09112.12565-.17562.20196-.25201z"/><path d="m16.54 14.428 5.94 5.94c.5856.5857.5856 1.5352 0 2.121-.5896.5764-1.5315.5764-2.121 0l-5.94-5.94c-.5755-.5959-.559-1.5455.0368-2.121.5813-.5614 1.5029-.5614 2.0842 0z"/></g></svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m22.5 7.3s.1.2.2.3c.4.7.2 1.6-.5 2l-6.4 4.1c.4 1.9.2 3.8-.8 5.5-.2.4-.6.7-1.1.7s-.9-.1-1.2-.4l-8.1-8.2s-.2-.2-.2-.3c-.4-.7-.2-1.6.5-2 1.7-1 3.6-1.2 5.5-.8l4-6.4s.1-.2.2-.3c.6-.6 1.5-.6 2.1 0z"/><path d="m9.5 14.4c.6.6.6 1.5 0 2.1l-5.9 5.9c-.6.6-1.5.6-2.1 0s-.6-1.5 0-2.1l5.9-5.9c.6-.6 1.5-.6 2.1 0z"/></svg>

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 376 B

+1 -1
View File
@@ -1 +1 @@
<svg enable-background="new 0 0 24 24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m22.5 20.4-5.4-5.4 3-3c.2-.1.3-.3.4-.5.7-1.2.3-2.7-.9-3.4-1.7-1-3.6-1.3-5.5-1l-3.7-5.9c-.1-.2-.2-.3-.3-.4-.5-.5-1.1-.7-1.8-.7s-1.3.2-1.7.7l-5.8 5.8c-.1.1-.2.2-.3.4-.7 1.2-.4 2.7.8 3.4l5.9 3.7c-.3 1.9.1 3.8 1 5.5.4.7 1.1 1.1 1.8 1.2h.3c.7 0 1.3-.3 1.8-.7l3-3 5.3 5.3c.6.6 1.5.6 2.1 0 .6-.5.6-1.4 0-2zm-11.9-1.7c-.1.1-.3.2-.4.1-.1 0-.3-.1-.4-.2-.8-1.4-1.1-3.1-.7-4.7l.2-.7-7-4.5c-.2-.1-.3-.4-.1-.6 0 0 0-.1.1-.1l5.7-5.8c.1-.1.3-.1.3-.1.1 0 .2 0 .3.1 0 0 0 0 .1.1l4.5 7 .7-.2c1.6-.4 3.3-.1 4.7.7.1.1.2.2.2.3v.4s0 .1-.1.1z"/></svg>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m.6 22.4c.6.6 1.5.6 2.1 0l5.3-5.3 3 3c.5.4 1.1.7 1.8.7h.3c.7-.1 1.4-.5 1.8-1.2.9-1.7 1.3-3.6 1-5.5l5.9-3.7c1.2-.7 1.5-2.2.8-3.4-.1-.2-.2-.3-.3-.4l-5.8-5.8c-.4-.5-1-.7-1.7-.7s-1.3.2-1.8.7c-.1 0-.2.2-.3.4l-3.7 5.9c-1.9-.3-3.8 0-5.5 1-1.2.7-1.6 2.2-.9 3.4.1.2.2.4.4.5l3 3-5.4 5.4c-.6.6-.6 1.5 0 2zm3.8-11.8c-.1 0-.1-.1-.1-.1v-.4c0-.1.1-.2.2-.3 1.4-.8 3.1-1.1 4.7-.7l.7.2 4.5-7h.1c.1-.2.2-.2.3-.2h.3l5.7 5.8c.2.2.1.5 0 .6l-7 4.5.2.7c.4 1.6.1 3.3-.7 4.7 0 .1-.3.2-.4.2 0 .1-.3 0-.4-.1z"/></svg>

Before

Width:  |  Height:  |  Size: 630 B

After

Width:  |  Height:  |  Size: 558 B

+70 -22
View File
@@ -76,6 +76,10 @@
display: none;
}
.tray__toggle-text kbd {
vertical-align: text-bottom;
}
@media (max-width: 799px) {
/* When collapsed on mobile, make it small */
.tray__dialog:not([open]) ~ & {
@@ -141,21 +145,17 @@
position: relative;
&[aria-selected]{
.card {
border-radius: 0.25ch;
outline: var(--focus-ring-size) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
}
.tray__dialog & {
font-size: 10px;
margin-block-end: var(--tray-item-margin);
transition: var(--tray-duration) var(--tray-timing-function);
transition-delay: var(--tray-item-delay);
transition-property: margin, scale;
z-index: var(--tray-item-z);
&:not(.tray__hat) {
z-index: var(--tray-item-z);
}
&:has(*:focus-visible) {
z-index: calc(var(--tray-item-z) + 1);
@@ -230,6 +230,10 @@
font-size: 12px;
}
}
.tray__dialog:not([open]) & {
pointer-events: none;
}
}
> *:is(:first-child, :last-child) {
@@ -244,17 +248,18 @@
display: none;
position: relative;
/* Red dot */
&:after {
background-color: oklch(var(--lch-red-medium));
block-size: 1ch;
border-radius: 50%;
box-shadow: 0 0 0 1px var(--tray-hat-bg);
content: "";
inline-size: 1ch;
inset: 25% auto auto 50%;
position: absolute;
translate: 25% -75%;
.btn:has(&) { /* Red dot */
&:after {
background-color: oklch(var(--lch-red-medium));
block-size: 1ch;
border-radius: 50%;
box-shadow: 0 0 0 1px var(--tray-hat-bg);
content: "";
inline-size: 1ch;
inset: 25% auto auto 50%;
position: absolute;
translate: 25% -75%;
}
}
}
@@ -320,7 +325,24 @@
position: relative;
[open] &[aria-selected] {
outline: 0;
z-index: calc(var(--tray-item-z) + 2);
.card__link {
border-radius: 0.25ch;
outline: var(--focus-ring-size) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
z-index: 1;
}
}
&:nth-child(1n + 11) { display: none; }
&:not([aria-selected]) .card__link:focus-visible,
.tray__dialog:not([open]) & .card__link:focus-visible {
--focus-ring-size: 0;
}
}
.tray__remove-pin-btn {
@@ -436,8 +458,34 @@
.tray--notifications {
inset-inline: auto var(--tray-margin);
.tray__item:nth-child(1n + 7) {
display: none;
.tray__item,
[data-navigable-list-target~=item] {
[open] &[aria-selected] {
outline: 0;
z-index: calc(var(--tray-item-z) + 2);
.card,
.tray__hat & .btn {
border-radius: 0.25ch;
outline: var(--focus-ring-size) solid var(--focus-ring-color);
outline-offset: var(--focus-ring-offset);
}
}
&:nth-child(1n + 7) {
display: none;
pointer-events: none;
visibility: hidden;
}
&:not([aria-selected]) .card:focus-visible,
.tray__dialog:not([open]) & .card:focus-visible {
--focus-ring-size: 0;
}
.btn:focus-visible {
outline: 0;
}
}
&:not(:has(.card--notification)) {
@@ -19,6 +19,7 @@ export default class extends Controller {
this.dialogTarget.show()
}
this.dialogTarget.setAttribute('aria-hidden', 'false')
this.dispatch("show")
}
toggle() {
@@ -7,7 +7,8 @@ export default class extends Controller {
reverseOrder: { type: Boolean, default: false },
selectionAttribute: { type: String, default: "aria-selected" },
focusOnSelection: { type: Boolean, default: true },
actionableItems: { type: Boolean, default: false }
actionableItems: { type: Boolean, default: false },
reverseNavigation: { type: Boolean, default: false }
}
connect() {
@@ -51,7 +52,9 @@ export default class extends Controller {
// Private
get #visibleItems() {
return this.itemTargets.filter(item => !item.hidden)
return this.itemTargets.filter(item => {
return item.checkVisibility() && !item.hidden
})
}
#selectPrevious() {
@@ -113,10 +116,12 @@ export default class extends Controller {
#keyHandlers = {
ArrowDown(event) {
this.#handleArrowKey(event, this.#selectNext.bind(this))
const selectMethod = this.reverseNavigationValue ? this.#selectPrevious.bind(this) : this.#selectNext.bind(this)
this.#handleArrowKey(event, selectMethod)
},
ArrowUp(event) {
this.#handleArrowKey(event, this.#selectPrevious.bind(this))
const selectMethod = this.reverseNavigationValue ? this.#selectNext.bind(this) : this.#selectPrevious.bind(this)
this.#handleArrowKey(event, selectMethod)
},
ArrowRight(event) {
this.#handleArrowKey(event, this.#selectNext.bind(this), false)
+1 -1
View File
@@ -1,6 +1,6 @@
<%= turbo_frame_tag @card, :assignment do %>
<%= tag.div class: "max-width", data: {
action: "turbo:before-cache@document->dialog#close keydown->navigable-list#navigate filter:changed->navigable-list#reset",
action: "turbo:before-cache@document->dialog#close dialog:show@document->navigable-list#reset keydown->navigable-list#navigate filter:changed->navigable-list#reset",
controller: "filter navigable-list",
dialog_target: "dialog",
navigable_list_focus_on_selection_value: false,
@@ -7,7 +7,7 @@
<dialog class="popup panel flex-column align-start gap-half fill-white shadow margin-block-double" data-dialog-target="dialog">
<%= tag.div class: "max-width full-width", data: {
action: "turbo:before-cache@document->dialog#close keydown->navigable-list#navigate filter:changed->navigable-list#reset",
action: "turbo:before-cache@document->dialog#close dialog:show@document->navigable-list#reset keydown->navigable-list#navigate filter:changed->navigable-list#reset",
controller: "filter navigable-list",
dialog_target: "dialog",
navigable_list_focus_on_selection_value: false,
+1 -1
View File
@@ -3,7 +3,7 @@
data-controller="filter navigable-list"
data-navigable-list-focus-on-selection-value="false"
data-navigable-list-actionable-items-value="true"
data-action="keydown->navigable-list#navigate filter:changed->navigable-list#reset"
data-action="keydown->navigable-list#navigate filter:changed->navigable-list#reset dialog:show@document->navigable-list#reset"
>
<strong class="popup__title txt-nowrap">Tag this…</strong>
+1 -1
View File
@@ -7,7 +7,7 @@
</button>
<%= tag.dialog class: "margin-block-start-half popup panel flex-column align-start gap-half fill-white shadow txt-small", data: {
action: "turbo:before-cache@document->dialog#close keydown->navigable-list#navigate",
action: "turbo:before-cache@document->dialog#close keydown->navigable-list#navigate dialog:show@document->navigable-list#reset",
aria: { label: "Sort by…", aria_description: "Sort by…" },
controller: "navigable-list",
dialog_target: "dialog",
+2 -2
View File
@@ -1,6 +1,6 @@
<div class="tray__item" id="<%= dom_id pin %>">
<div class="tray__item" id="<%= dom_id pin %>" data-navigable-list-target="item">
<%= render "cards/display/preview", card: pin.card %>
<%= button_to card_pin_path(pin.card), method: :delete, class: "tray__remove-pin-btn btn btn--circle borderless" do %>
<%= icon_tag "remove-med" %> <span class="for-screen-reader">Un-pin this card</span>
<%= icon_tag "pinned" %> <span class="for-screen-reader">Un-pin this card</span>
<% end %>
</div>
+7 -2
View File
@@ -1,8 +1,13 @@
<%= turbo_stream_from Current.user, :pins_tray %>
<section class="tray tray--pins" data-controller="dialog">
<%= tag.dialog id: "pin-tray", class: "tray__dialog",
data: { turbo_permanent: true, dialog_modal_value: false, dialog_target: "dialog", action: "keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside" } do %>
<%= tag.dialog id: "pin-tray", class: "tray__dialog", data: {
action: "keydown->navigable-list#navigate dialog:show@document->navigable-list#reset keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside",
controller: "navigable-list",
dialog_target: "dialog",
navigable_list_actionable_items_value: "true",
navigable_list_reverse_navigation_value: "true" },
turbo_permanent: true do %>
<%= turbo_frame_tag "pins", src: my_pins_path %>
<% end %>
+23 -19
View File
@@ -2,33 +2,37 @@
<section class="tray tray--notifications" data-controller="dialog badge" data-badge-unread-class="unread" data-action="turbo:render#update">
<dialog class="tray__dialog"
data-dialog-modal-value="false" data-dialog-target="dialog"
data-action="keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside">
data-controller="navigable-list"
data-dialog-target="dialog"
data-navigable-list-actionable-items-value="true"
data-navigable-list-reverse-navigation-value="true"
data-action="keydown->navigable-list#navigate dialog:show@document->navigable-list#reset turbo-visit->navigable-list#reset keydown.esc->dialog#close:stop click@document->dialog#closeOnClickOutside">
<%= turbo_frame_tag "notifications", src: tray_notifications_path, refresh: "morph" %>
<div class="tray__item tray__hat txt-x-small">
<%= link_to settings_notifications_path,
class: "btn borderless tray__notification-settings",
title: "Notification settings",
data: { action: "dialog#close" } do %>
<%= icon_tag "settings" %>
<span>Settings</span>
<% end %>
<div data-navigable-list-target="item">
<%= link_to settings_notifications_path,
class: "btn borderless tray__notification-settings",
title: "Notification settings",
data: { action: "dialog#close" } do %>
<%= icon_tag "settings" %>
<span>Settings</span>
<% end %>
</div>
<%= link_to notifications_path, class: "tray__new-notifications btn borderless flex-item-grow position-relative overflow-ellipsis", data: { action: "click->dialog#close" } do %>
<%= icon_tag "bell" %>
<span>See more new items</span>
<% end %>
<%= link_to notifications_path, class: "tray__old-notifications btn borderless flex-item-grow position-relative overflow-ellipsis", data: { action: "click->dialog#close" } do %>
<%= icon_tag "bell" %>
<span>See older items</span>
<% end %>
<div data-navigable-list-target="item">
<%= link_to notifications_path, class: "btn borderless flex-item-grow position-relative overflow-ellipsis", data: { action: "click->dialog#close" } do %>
<%= icon_tag "bell" %>
<span class="tray__new-notifications">See more new items</span>
<span class="tray__old-notifications">See older items</span>
<% end %>
</div>
<%= button_to read_all_notifications_path,
class: "btn borderless tray__clear-notifications",
title: "Mark all notifications as read",
data: { action: "dialog#close badge#clear", turbo_frame: "notifications" } do %>
data: { action: "dialog#close badge#clear", turbo_frame: "notifications" },
form: { data: { navigable_list_target: "item" } } do %>
<%= icon_tag "check" %>
<span>Clear all</span>
<% end %>