From e5d5a4290cad30d0258d2636be4b39a2ed479968 Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Sun, 26 Jul 2026 11:59:41 +0000 Subject: [PATCH 1/3] fix(mobile): close sidebar overlay when a link item is selected --- frappe/public/js/frappe/ui/sidebar/sidebar_item.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js index 2eaa94e75055..8d4d7b4da346 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js @@ -116,6 +116,14 @@ frappe.ui.sidebar_item.TypeLink = class SidebarItem { }) ); $(this.container).append(this.wrapper); + + if (this.path) { + this.wrapper.find(".item-anchor").on("click", () => { + if (frappe.is_mobile()) { + frappe.app.sidebar.close(); + } + }); + } } set_suffix() { if (this.item.suffix) { From e8d000997299d4a6af9680ab1c7cfdaade46636e Mon Sep 17 00:00:00 2001 From: KerollesFathy Date: Sun, 26 Jul 2026 18:33:32 +0000 Subject: [PATCH 2/3] fix(sidebar): close mobile sidebar on link click, fix duplicate click bindings Add setup_click() to TypeLink, called from make(), to close the mobile sidebar when a link is tapped. Remove the extra setup_click() calls in TypeSidebarItemGroup and TypeButton constructors, make() now calls it for them automatically, so the old explicit call was running their click handler twice. --- .../js/frappe/ui/sidebar/sidebar_item.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js index 8d4d7b4da346..72b872cd318f 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar_item.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar_item.js @@ -116,14 +116,16 @@ frappe.ui.sidebar_item.TypeLink = class SidebarItem { }) ); $(this.container).append(this.wrapper); + this.setup_click(); + } - if (this.path) { - this.wrapper.find(".item-anchor").on("click", () => { - if (frappe.is_mobile()) { - frappe.app.sidebar.close(); - } - }); - } + setup_click() { + if (!this.path) return; + this.wrapper.find(".item-anchor").on("click", () => { + if (frappe.is_mobile()) { + frappe.app.sidebar.close(); + } + }); } set_suffix() { if (this.item.suffix) { @@ -300,9 +302,9 @@ frappe.ui.sidebar_item.TypeSidebarItemGroup = class SpacerItem extends ( constructor(item, items) { super(item); this.title = frappe.app.sidebar.workspace_title; - this.setup_click(); } + // overrides TypeLink.setup_click(), invoked once via the base class's make() setup_click() { const me = this; this.wrapper.on("click", function () { @@ -352,9 +354,9 @@ frappe.ui.sidebar_item.TypeButton = class SidebarButton extends frappe.ui.sideba this.item.id && this.wrapper.attr("id", this.item.id); this.item.class && this.wrapper.attr("class", this.item.class); this.wrapper.attr("title", this.item.label); - this.setup_click(); } + // overrides TypeLink.setup_click(), invoked once via the base class's make() setup_click() { const me = this; if (this.item.onClick) { From e5a19c4d5c56ce3dc209d1da941e940ec774c7e0 Mon Sep 17 00:00:00 2001 From: sokumon Date: Mon, 27 Jul 2026 03:08:53 +0530 Subject: [PATCH 3/3] fix: notification dot styles --- frappe/desk/page/desktop/desktop.html | 1 + .../frappe/ui/notifications/notifications.js | 34 ++++------- frappe/public/js/frappe/ui/sidebar/sidebar.js | 2 +- .../js/frappe/ui/sidebar/workspace_dock.js | 14 ++--- frappe/public/scss/desk/notification.scss | 56 ++++++++----------- frappe/public/scss/desk/workspace_dock.scss | 11 +--- 6 files changed, 39 insertions(+), 79 deletions(-) diff --git a/frappe/desk/page/desktop/desktop.html b/frappe/desk/page/desktop/desktop.html index cea2cdef60f9..f9a82768734e 100644 --- a/frappe/desk/page/desktop/desktop.html +++ b/frappe/desk/page/desktop/desktop.html @@ -32,6 +32,7 @@ > +
0) { - $suffix + $count .text(count > 99 ? "99+" : count) .attr("aria-label", __("{0} unread notifications", [count])) .removeClass("hidden"); } else { - $suffix.removeAttr("aria-label").addClass("hidden"); + $count.removeAttr("aria-label").addClass("hidden"); } } @@ -418,14 +405,12 @@ class NotificationsView extends BaseNotificationsView { setup_notification_listeners() { frappe.realtime.on("notification", () => { this.settings.seen = 0; - this.toggle_notification_icon(false); this.update_count_badge(this.unread_count + 1); this.update_dropdown(); }); frappe.realtime.on("indicator_hide", () => { this.settings.seen = 1; - this.toggle_notification_icon(true); }); this.parent.on("show.bs.dropdown", () => { @@ -448,8 +433,9 @@ class NotificationsView extends BaseNotificationsView { } this.toggle_seen(true); - if (this.get_bell_icons().hasClass("indicator")) { - this.toggle_notification_icon(true); + // opening the panel counts as seeing what's in it -- let the other sessions know + if (this.settings?.seen == 0) { + this.settings.seen = 1; frappe.call( "frappe.desk.doctype.notification_log.notification_log.trigger_indicator_hide" ); diff --git a/frappe/public/js/frappe/ui/sidebar/sidebar.js b/frappe/public/js/frappe/ui/sidebar/sidebar.js index 9f2a16eae30b..bc06848a38d9 100644 --- a/frappe/public/js/frappe/ui/sidebar/sidebar.js +++ b/frappe/public/js/frappe/ui/sidebar/sidebar.js @@ -727,7 +727,7 @@ frappe.ui.Sidebar = class Sidebar { standard: true, type: "Button", class: "sidebar-notification hidden", - suffix: "", + suffix: "", onClick: () => { const $dropdown = this.wrapper.find(".dropdown-notifications"); $dropdown.toggleClass("hidden"); diff --git a/frappe/public/js/frappe/ui/sidebar/workspace_dock.js b/frappe/public/js/frappe/ui/sidebar/workspace_dock.js index 83c640127fc9..ae25d64e4629 100644 --- a/frappe/public/js/frappe/ui/sidebar/workspace_dock.js +++ b/frappe/public/js/frappe/ui/sidebar/workspace_dock.js @@ -86,11 +86,11 @@ frappe.ui.WorkspaceDock = class WorkspaceDock { name: "notifications", icon: "bell", label: __("Notifications"), - // the Notifications view keeps the unread count and unseen dot in sync off these - // classes (see notifications.js), and toggles the same dropdown the sidebar bell does. + // the Notifications view keeps the unread count in sync off these classes (see + // notifications.js), and toggles the same dropdown the sidebar bell does. css_class: "sidebar-notification", condition: () => frappe.boot.desk_settings.notifications, - badge: ``, + badge: ``, on_click: () => this.toggle_notifications(), setup: ($item) => { // seed the badge from boot; the Notifications view keeps it live from here on @@ -98,12 +98,6 @@ frappe.ui.WorkspaceDock = class WorkspaceDock { $item, frappe.boot.notification_unread_count || 0 ); - if ( - frappe.boot.notification_settings && - frappe.boot.notification_settings.seen == 0 - ) { - $item.find(".sidebar-item-icon").addClass("indicator blue"); - } }, }, ]; @@ -147,7 +141,7 @@ frappe.ui.WorkspaceDock = class WorkspaceDock { // The dock shows unread as a small dot, not a number -- just toggle it on whether any exist. sync_notification_count($bell, count) { - $bell.find(".sidebar-notification-count").toggleClass("hidden", count <= 0); + $bell.find(".notification-count").toggleClass("hidden", count <= 0); } // Toggle the shared notifications panel (lives in the sidebar), mirroring the sidebar bell. diff --git a/frappe/public/scss/desk/notification.scss b/frappe/public/scss/desk/notification.scss index 73b00ced9734..3c18a047ab83 100644 --- a/frappe/public/scss/desk/notification.scss +++ b/frappe/public/scss/desk/notification.scss @@ -35,38 +35,10 @@ display: none; } -.sidebar-notification .sidebar-item-icon.indicator, -.desktop-notification-icon.indicator { - position: relative; - - &::before { - position: absolute; - left: auto; - height: 6px; - width: 6px; - margin: 0; - z-index: 1; - } -} - -.desktop-notification-icon.indicator::before { - top: 0; - right: 0; -} - -.sidebar-notification .sidebar-item-icon.indicator::before { - top: 5px; - right: 6px; - height: 5.5px; - width: 5.5px; - transition: opacity 0.2s ease-in-out; -} - -.sidebar-notification:hover .sidebar-item-icon.indicator::before { - opacity: 0; -} - -.sidebar-notification-count { +// The unread count is the only unread affordance a bell gets. Where the bell has a label (the +// sidebar) it reads as a pill next to it; on icon-only bells (desktop navbar, workspace dock) there +// is no room for a number, so the same element is drawn as a dot on the icon's corner. +.notification-count { padding: 1px 7px; background-color: var(--bg-gray-100); color: var(--text-muted); @@ -76,8 +48,24 @@ border-radius: 10px; } -.sidebar-notification .standard-sidebar-item .item-anchor { - overflow: visible; +.desktop-notification-icon { + position: relative; + + .notification-count { + position: absolute; + top: 0; + right: 0; + width: 8px; + height: 8px; + padding: 0; + border-radius: 50%; + border: 1px solid var(--bg-color); + background-color: var(--surface-blue-5); + // dot only — hide the count text the shared notifications view sets on it + font-size: 0; + color: transparent; + overflow: hidden; + } } .mark-read { diff --git a/frappe/public/scss/desk/workspace_dock.scss b/frappe/public/scss/desk/workspace_dock.scss index fb9e3e581311..235488001054 100644 --- a/frappe/public/scss/desk/workspace_dock.scss +++ b/frappe/public/scss/desk/workspace_dock.scss @@ -170,7 +170,7 @@ } // unread indicator — a small dot on the bell's top-right corner (mirrors dot) - .sidebar-notification-count { + .notification-count { position: absolute; top: 5px; right: 5px; @@ -189,15 +189,6 @@ display: none; } } - - // re-place the shared unseen dot on the bell's top-right corner for the 16px icon - .sidebar-item-icon.indicator::before { - top: -1px; - right: -1px; - height: 8px; - width: 8px; - border-radius: 50%; - } } // Search / notification shortcuts are not workspaces — mirror :