Merge pull request #808 from basecamp/update-badge-counts

Update badge counts clientside
This commit is contained in:
Jason Zimdars
2025-07-25 07:23:35 -05:00
committed by GitHub
4 changed files with 72 additions and 29 deletions
+4 -4
View File
@@ -28,8 +28,8 @@ module NotificationsHelper
tag.div id: dom_id(notification), class: "tray__item" do
concat(
link_to(notification,
class: [ "card card--notification", { "card--closed": notification.card.closed? } ],
data: { turbo_frame: "_top" },
class: [ "card card--notification", { "card--closed": notification.card.closed? }, { "unread": !notification.read? } ],
data: { turbo_frame: "_top", badge_target: "unread", action: "badge#update" },
style: { "--card-color:": notification.card.color },
&)
)
@@ -42,7 +42,7 @@ module NotificationsHelper
method: :delete,
class: "card__notification-unread-indicator btn btn--circle borderless",
title: "Mark as unread",
data: { action: "form#submit:stop", form_target: "submit" },
data: { action: "form#submit:stop badge#update:stop", form_target: "submit" },
form: { data: { controller: "form" } } do
concat(icon_tag("unseen"))
concat(tag.span("Mark as unread", class: "for-screen-reader"))
@@ -51,7 +51,7 @@ module NotificationsHelper
button_to read_notification_path(notification),
class: "card__notification-unread-indicator btn btn--circle borderless",
title: "Mark as read",
data: { action: "form#submit:stop", form_target: "submit" },
data: { action: "form#submit:stop badge#update:stop", form_target: "submit" },
form: { data: { controller: "form" } } do
concat(icon_tag("remove-med"))
concat(tag.span("Mark as read", class: "for-screen-reader"))
@@ -0,0 +1,41 @@
import { Controller } from "@hotwired/stimulus"
import { onNextEventLoopTick } from "helpers/timing_helpers"
export default class extends Controller {
static targets = [ "unread" ]
static classes = [ "unread" ]
connect() {
onNextEventLoopTick(() => this.update())
}
update() {
onNextEventLoopTick(() => {
if (this.#available) {
const unreadCount = this.#unreadCount
if (unreadCount > 0) {
navigator.setAppBadge(unreadCount)
} else {
navigator.clearAppBadge()
}
}
})
}
clear() {
onNextEventLoopTick(() => {
if (this.#available) {
navigator.clearAppBadge()
}
})
}
get #unreadCount() {
return this.unreadTargets.filter(unreadTarget => unreadTarget.classList.contains(this.unreadClass)).length
}
get #available() {
return "setAppBadge" in navigator
}
}
+3 -3
View File
@@ -1,6 +1,6 @@
<%= turbo_stream_from Current.user, :notifications %>
<section class="tray tray--notifications" data-controller="dialog">
<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">
@@ -10,7 +10,7 @@
<%= link_to settings_notifications_path,
class: "btn borderless tray__notification-settings",
title: "Notification settings",
data: { action: "click->dialog#close" } do %>
data: { action: "dialog#close" } do %>
<%= icon_tag "settings" %>
<span class="translucent">Settings</span>
<% end %>
@@ -28,7 +28,7 @@
<%= button_to read_all_notifications_path,
class: "btn borderless tray__clear-notifications",
title: "Mark all notifications as read",
data: { action: "click->dialog#close", turbo_frame: "notifications" } do %>
data: { action: "dialog#close badge#clear", turbo_frame: "notifications" } do %>
<%= icon_tag "check" %>
<span class="translucent">Clear all</span>
<% end %>
+24 -22
View File
@@ -17,29 +17,31 @@
</nav>
<% end %>
<section class="notifications-list panel center borderless unpad flex flex-column gap-half">
<% if @unread.any? %>
<div class="flex align-center justify-space-between margin-block-start margin-block-end-half">
<h2 class="txt-medium txt-uppercase txt-alert">New for you</h2>
<%= button_to "Mark all as read", read_all_notifications_path, class: "btn txt-small", form: { data: { turbo: false } } %>
<div data-controller="badge" data-badge-unread-class="unread">
<section class="notifications-list panel center borderless unpad flex flex-column gap-half">
<% if @unread.any? %>
<div class="flex align-center justify-space-between margin-block-start margin-block-end-half">
<h2 class="txt-medium txt-uppercase txt-alert">New for you</h2>
<%= button_to "Mark all as read", read_all_notifications_path, class: "btn txt-small", form: { data: { turbo: false } }, data: { action: "badge#clear" } %>
</div>
<% else %>
<div class="notifications-list__empty-message pad border-radius border translucent" style="--border-style: dashed; --border-color: var(--color-ink); --border-radius: 0.2em;">
<strong>Nothing new for you.</strong>
</div>
<% end %>
<div id="notifications_list" contents>
<%= render partial: "notifications/notification", collection: @unread, cached: true %>
</div>
<% else %>
<div class="notifications-list__empty-message pad border-radius border translucent" style="--border-style: dashed; --border-color: var(--color-ink); --border-radius: 0.2em;">
<strong>Nothing new for you.</strong>
</section>
<section class="notifications-list notifications-list--read panel center borderless unpad flex flex-column gap-half margin-block-start">
<h2 class="txt-medium margin-block-start-double margin-block-end-half txt-uppercase translucent">Previously seen</h2>
<div id="notifications_list_read" contents>
<%= render partial: "notifications/notification", collection: @page.records, cached: true %>
</div>
<% end %>
<div id="notifications_list" contents>
<%= render partial: "notifications/notification", collection: @unread, cached: true %>
</div>
</section>
<section class="notifications-list notifications-list--read panel center borderless unpad flex flex-column gap-half margin-block-start">
<h2 class="txt-medium margin-block-start-double margin-block-end-half txt-uppercase translucent">Previously seen</h2>
<div id="notifications_list_read" contents>
<%= render partial: "notifications/notification", collection: @page.records, cached: true %>
</div>
</section>
</section>
</div>
<%= notifications_next_page_link(@page) if @page.records.any? %>