From efe59ff35f72566fc422abc97050157e9b20e445 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 9 Dec 2025 15:10:42 +0000 Subject: [PATCH] Improve icon rendering in iconized context menu (#31458) * Fix composer button visibility in contrast colour mode Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Simplify Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshots Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update screenshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Improve icon rendering in iconized context menu Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Iterate Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Add test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Delint Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --- res/css/_components.pcss | 3 - res/css/structures/_SpacePanel.pcss | 40 ------- res/css/structures/_UserMenu.pcss | 41 +------ .../context_menus/_DeviceContextMenu.pcss | 4 - .../context_menus/_IconizedContextMenu.pcss | 72 +++-------- .../context_menus/_MessageContextMenu.pcss | 113 ------------------ .../_RoomGeneralContextMenu.pcss | 31 ----- .../_RoomNotificationContextMenu.pcss | 12 -- res/css/views/dialogs/_SpotlightDialog.pcss | 33 ++--- res/css/views/right_panel/_ThreadPanel.pcss | 8 -- res/css/views/rooms/_LegacyRoomList.pcss | 22 ---- .../views/rooms/_LegacyRoomListHeader.pcss | 19 --- res/css/views/rooms/_RoomTile.pcss | 26 ++-- res/img/element-icons/notifications.svg | 6 +- res/img/element-icons/room/invite.svg | 2 +- .../roomlist/notifications-default.svg | 6 +- .../roomlist/notifications-dm.svg | 2 +- .../roomlist/notifications-off.svg | 10 +- src/components/structures/SpaceRoomView.tsx | 10 +- src/components/structures/UserMenu.tsx | 29 +++-- .../context_menus/DeveloperToolsOption.tsx | 3 +- .../views/context_menus/DeviceContextMenu.tsx | 9 +- .../context_menus/IconizedContextMenu.tsx | 37 ++---- .../context_menus/MessageContextMenu.tsx | 75 +++++++----- .../context_menus/RoomGeneralContextMenu.tsx | 28 +++-- .../RoomNotificationContextMenu.tsx | 12 +- .../views/context_menus/SpaceContextMenu.tsx | 29 +++-- .../context_menus/ThreadListContextMenu.tsx | 6 +- .../spotlight/RoomResultContextMenus.tsx | 36 ++++-- src/components/views/rooms/LegacyRoomList.tsx | 26 ++-- .../views/rooms/LegacyRoomListHeader.tsx | 23 ++-- src/components/views/rooms/RoomTile.tsx | 11 +- src/components/views/spaces/SpacePanel.tsx | 1 - .../context_menus/DeviceContextMenu-test.tsx | 39 ++++++ .../DeviceContextMenu-test.tsx.snap | 85 +++++++++++++ .../RoomGeneralContextMenu-test.tsx.snap | 34 +++++- .../SpaceContextMenu-test.tsx.snap | 61 ++++++++-- 37 files changed, 440 insertions(+), 564 deletions(-) delete mode 100644 res/css/views/context_menus/_MessageContextMenu.pcss delete mode 100644 res/css/views/context_menus/_RoomGeneralContextMenu.pcss delete mode 100644 res/css/views/context_menus/_RoomNotificationContextMenu.pcss create mode 100644 test/unit-tests/components/views/context_menus/DeviceContextMenu-test.tsx create mode 100644 test/unit-tests/components/views/context_menus/__snapshots__/DeviceContextMenu-test.tsx.snap diff --git a/res/css/_components.pcss b/res/css/_components.pcss index 66eed09083..21e39a1584 100644 --- a/res/css/_components.pcss +++ b/res/css/_components.pcss @@ -120,9 +120,6 @@ @import "./views/context_menus/_DeviceContextMenu.pcss"; @import "./views/context_menus/_IconizedContextMenu.pcss"; @import "./views/context_menus/_LegacyCallContextMenu.pcss"; -@import "./views/context_menus/_MessageContextMenu.pcss"; -@import "./views/context_menus/_RoomGeneralContextMenu.pcss"; -@import "./views/context_menus/_RoomNotificationContextMenu.pcss"; @import "./views/dialogs/_AddExistingToSpaceDialog.pcss"; @import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss"; @import "./views/dialogs/_BugReportDialog.pcss"; diff --git a/res/css/structures/_SpacePanel.pcss b/res/css/structures/_SpacePanel.pcss index eb28db7307..177a0752ed 100644 --- a/res/css/structures/_SpacePanel.pcss +++ b/res/css/structures/_SpacePanel.pcss @@ -428,46 +428,6 @@ Please see LICENSE files in the repository root for full details. white-space: nowrap; } - .mx_SpacePanel_iconHome::before { - mask-image: url("@vector-im/compound-design-tokens/icons/home-solid.svg"); - } - - .mx_SpacePanel_iconInvite::before { - mask-image: url("$(res)/img/element-icons/room/invite.svg"); - } - - .mx_SpacePanel_iconSettings::before { - mask-image: url("@vector-im/compound-design-tokens/icons/settings-solid.svg"); - } - - .mx_SpacePanel_iconLeave::before { - mask-image: url("@vector-im/compound-design-tokens/icons/leave.svg"); - } - - .mx_SpacePanel_iconMembers::before { - mask-image: url("@vector-im/compound-design-tokens/icons/user-profile-solid.svg"); - } - - .mx_SpacePanel_iconPlus::before { - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); - } - - .mx_SpacePanel_iconExplore::before { - mask-image: url("@vector-im/compound-design-tokens/icons/search.svg"); - } - - .mx_SpacePanel_iconPreferences::before { - mask-image: url("@vector-im/compound-design-tokens/icons/preferences.svg"); - } - - .mx_SpacePanel_noIcon { - display: none; - - & + .mx_IconizedContextMenu_label { - padding-left: 5px !important; /* override default iconized label style to align with header */ - } - } - .mx_SpacePanel_contextMenu_separatorLabel { color: $tertiary-content; font-size: $font-10px; diff --git a/res/css/structures/_UserMenu.pcss b/res/css/structures/_UserMenu.pcss index 2f4099c893..42754a56a2 100644 --- a/res/css/structures/_UserMenu.pcss +++ b/res/css/structures/_UserMenu.pcss @@ -116,48 +116,11 @@ Please see LICENSE files in the repository root for full details. } } - .mx_IconizedContextMenu_icon { - width: 16px; - height: 16px; - display: block; - - &::before { - content: ""; - width: 16px; - height: 16px; - display: block; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background: $icon-button-color; - } - } - - .mx_UserMenu_iconHome::before { - mask-image: url("@vector-im/compound-design-tokens/icons/home-solid.svg"); - } - - .mx_UserMenu_iconBell::before { - mask-image: url("$(res)/img/element-icons/notifications.svg"); - } - - .mx_UserMenu_iconLock::before { - mask-image: url("@vector-im/compound-design-tokens/icons/lock-solid.svg"); - } - - .mx_UserMenu_iconSettings::before { - mask-image: url("@vector-im/compound-design-tokens/icons/settings-solid.svg"); + .mx_IconizedContextMenu_icon svg { + color: $icon-button-color; } .mx_UserMenu_iconMessage::before { mask-image: url("$(res)/img/element-icons/feedback.svg"); } - - .mx_UserMenu_iconSignOut::before { - mask-image: url("@vector-im/compound-design-tokens/icons/leave.svg"); - } - - .mx_UserMenu_iconQr::before { - mask-image: url("@vector-im/compound-design-tokens/icons/qr-code.svg"); - } } diff --git a/res/css/views/context_menus/_DeviceContextMenu.pcss b/res/css/views/context_menus/_DeviceContextMenu.pcss index d70b5eb043..565b2dfd19 100644 --- a/res/css/views/context_menus/_DeviceContextMenu.pcss +++ b/res/css/views/context_menus/_DeviceContextMenu.pcss @@ -9,10 +9,6 @@ Please see LICENSE files in the repository root for full details. .mx_DeviceContextMenu { max-width: 252px; - .mx_DeviceContextMenu_device_icon { - display: none; - } - .mx_IconizedContextMenu_label { padding-left: 0 !important; } diff --git a/res/css/views/context_menus/_IconizedContextMenu.pcss b/res/css/views/context_menus/_IconizedContextMenu.pcss index 5db7e65073..ca6ada17f4 100644 --- a/res/css/views/context_menus/_IconizedContextMenu.pcss +++ b/res/css/views/context_menus/_IconizedContextMenu.pcss @@ -68,19 +68,6 @@ Please see LICENSE files in the repository root for full details. cursor: not-allowed; } - img, - svg, - .mx_IconizedContextMenu_icon { - /* icons */ - width: 16px; - min-width: 16px; - max-width: 16px; - - & + .mx_IconizedContextMenu_label { - padding-left: 14px; - } - } - span.mx_IconizedContextMenu_label { /* labels */ width: 100%; @@ -92,27 +79,23 @@ Please see LICENSE files in the repository root for full details. white-space: nowrap; } + svg { + width: 16px; + height: 16px; + display: block; + flex-shrink: 0; + + & + .mx_IconizedContextMenu_label { + padding-left: 14px; + } + } + .mx_BetaCard_betaPill { margin-left: 16px; } } } - .mx_IconizedContextMenu_icon { - position: relative; - - &::before { - content: ""; - width: inherit; - height: inherit; - position: absolute; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background-color: var(--cpd-color-icon-primary); - } - } - .mx_IconizedContextMenu_optionList_red { .mx_IconizedContextMenu_item { color: $alert !important; @@ -121,10 +104,6 @@ Please see LICENSE files in the repository root for full details. svg { color: var(--cpd-color-icon-critical-primary); } - - .mx_IconizedContextMenu_icon::before { - background-color: var(--cpd-color-icon-critical-primary); - } } .mx_IconizedContextMenu_option_red { @@ -133,24 +112,16 @@ Please see LICENSE files in the repository root for full details. svg { color: $alert; } - - .mx_IconizedContextMenu_icon::before { - background-color: $alert; - } } .mx_IconizedContextMenu_active { &.mx_IconizedContextMenu_item, .mx_IconizedContextMenu_item { color: $accent !important; - } - svg { - color: $accent; - } - - .mx_IconizedContextMenu_icon::before { - background-color: $accent; + svg { + color: $accent; + } } } @@ -160,24 +131,11 @@ Please see LICENSE files in the repository root for full details. } } - .mx_IconizedContextMenu_checked, - .mx_IconizedContextMenu_unchecked { + svg.mx_IconizedContextMenu_checked { margin-left: 16px; margin-right: -5px; } - .mx_IconizedContextMenu_developerTools::before { - mask-image: url("@vector-im/compound-design-tokens/icons/labs.svg"); - } - - .mx_IconizedContextMenu_checked::before { - mask-image: url("@vector-im/compound-design-tokens/icons/check.svg"); - } - - .mx_IconizedContextMenu_unchecked::before { - content: unset; - } - .mx_IconizedContextMenu_sublabel { margin-left: 20px; color: $tertiary-content; diff --git a/res/css/views/context_menus/_MessageContextMenu.pcss b/res/css/views/context_menus/_MessageContextMenu.pcss deleted file mode 100644 index 20a0886225..0000000000 --- a/res/css/views/context_menus/_MessageContextMenu.pcss +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2024 New Vector Ltd. -Copyright 2021 Michael Weimann -Copyright 2015, 2016 OpenMarket Ltd - -SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial -Please see LICENSE files in the repository root for full details. -*/ - -.mx_MessageContextMenu { - .mx_IconizedContextMenu_icon { - width: 16px; - height: 16px; - display: block; - - &::before { - content: ""; - width: 16px; - height: 16px; - display: block; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - } - } - - .mx_MessageContextMenu_iconCollapse::before { - mask-image: url("@vector-im/compound-design-tokens/icons/chevron-up.svg"); - } - - .mx_MessageContextMenu_iconReport::before { - mask-image: url("@vector-im/compound-design-tokens/icons/error-solid.svg"); - } - - .mx_MessageContextMenu_iconLink::before { - mask-image: url("@vector-im/compound-design-tokens/icons/link.svg"); - } - - .mx_MessageContextMenu_iconPermalink::before { - mask-image: url("@vector-im/compound-design-tokens/icons/share.svg"); - } - - .mx_MessageContextMenu_iconUnhidePreview::before { - mask-image: url("@vector-im/compound-design-tokens/icons/visibility-on.svg"); - } - - .mx_MessageContextMenu_iconOpenInMapSite::before { - mask-image: url("@vector-im/compound-design-tokens/icons/pop-out.svg"); - } - - .mx_MessageContextMenu_iconEndPoll::before { - mask-image: url("@vector-im/compound-design-tokens/icons/check.svg"); - } - - .mx_MessageContextMenu_iconForward::before { - mask-image: url("@vector-im/compound-design-tokens/icons/forward.svg"); - } - - .mx_MessageContextMenu_iconRedact::before { - mask-image: url("@vector-im/compound-design-tokens/icons/delete.svg"); - } - - .mx_MessageContextMenu_iconResend::before { - mask-image: url("@vector-im/compound-design-tokens/icons/restart.svg"); - } - - .mx_MessageContextMenu_iconSource::before { - mask-image: url("@vector-im/compound-design-tokens/icons/inline-code.svg"); - } - - .mx_MessageContextMenu_iconQuote::before { - mask-image: url("@vector-im/compound-design-tokens/icons/quote.svg"); - } - - .mx_MessageContextMenu_iconPin::before { - mask-image: url("@vector-im/compound-design-tokens/icons/pin.svg"); - } - - .mx_MessageContextMenu_iconUnpin::before { - mask-image: url("@vector-im/compound-design-tokens/icons/unpin.svg"); - } - - .mx_MessageContextMenu_iconCopy::before { - height: 16px; - mask-image: url($copy-button-url); - position: relative; - width: 16px; - } - - .mx_MessageContextMenu_iconEdit::before { - mask-image: url("@vector-im/compound-design-tokens/icons/edit.svg"); - } - - .mx_MessageContextMenu_iconReply::before { - mask-image: url("@vector-im/compound-design-tokens/icons/reply.svg"); - } - - .mx_MessageContextMenu_iconReplyInThread::before { - mask-image: url("@vector-im/compound-design-tokens/icons/threads.svg"); - } - - .mx_MessageContextMenu_iconReact::before { - mask-image: url("@vector-im/compound-design-tokens/icons/reaction-add.svg"); - } - - .mx_MessageContextMenu_iconViewInRoom::before { - mask-image: url("$(res)/img/element-icons/view-in-room.svg"); - } - - .mx_MessageContextMenu_jumpToEvent::before { - mask-image: url("$(res)/img/element-icons/child-relationship.svg"); - } -} diff --git a/res/css/views/context_menus/_RoomGeneralContextMenu.pcss b/res/css/views/context_menus/_RoomGeneralContextMenu.pcss deleted file mode 100644 index d0786972c1..0000000000 --- a/res/css/views/context_menus/_RoomGeneralContextMenu.pcss +++ /dev/null @@ -1,31 +0,0 @@ -.mx_RoomGeneralContextMenu_iconStar::before { - mask-image: url("@vector-im/compound-design-tokens/icons/favourite-solid.svg"); -} - -.mx_RoomGeneralContextMenu_iconArrowDown::before { - mask-image: url("@vector-im/compound-design-tokens/icons/arrow-down.svg"); -} - -.mx_RoomGeneralContextMenu_iconMarkAsRead::before { - mask-image: url("@vector-im/compound-design-tokens/icons/mark-as-read.svg"); -} - -.mx_RoomGeneralContextMenu_iconMarkAsUnread::before { - mask-image: url("@vector-im/compound-design-tokens/icons/mark-as-unread.svg"); -} - -.mx_RoomGeneralContextMenu_iconSettings::before { - mask-image: url("@vector-im/compound-design-tokens/icons/settings-solid.svg"); -} - -.mx_RoomGeneralContextMenu_iconCopyLink::before { - mask-image: url("@vector-im/compound-design-tokens/icons/link.svg"); -} - -.mx_RoomGeneralContextMenu_iconInvite::before { - mask-image: url("$(res)/img/element-icons/room/invite.svg"); -} - -.mx_RoomGeneralContextMenu_iconSignOut::before { - mask-image: url("@vector-im/compound-design-tokens/icons/leave.svg"); -} diff --git a/res/css/views/context_menus/_RoomNotificationContextMenu.pcss b/res/css/views/context_menus/_RoomNotificationContextMenu.pcss deleted file mode 100644 index baf440fd43..0000000000 --- a/res/css/views/context_menus/_RoomNotificationContextMenu.pcss +++ /dev/null @@ -1,12 +0,0 @@ -.mx_RoomNotificationContextMenu_iconBell::before { - mask-image: url("$(res)/img/element-icons/notifications.svg"); -} -.mx_RoomNotificationContextMenu_iconBellDot::before { - mask-image: url("$(res)/img/element-icons/roomlist/notifications-default.svg"); -} -.mx_RoomNotificationContextMenu_iconBellMentions::before { - mask-image: url("$(res)/img/element-icons/roomlist/notifications-dm.svg"); -} -.mx_RoomNotificationContextMenu_iconBellCrossed::before { - mask-image: url("$(res)/img/element-icons/roomlist/notifications-off.svg"); -} diff --git a/res/css/views/dialogs/_SpotlightDialog.pcss b/res/css/views/dialogs/_SpotlightDialog.pcss index 8c0fd6a4b8..19a40d24a5 100644 --- a/res/css/views/dialogs/_SpotlightDialog.pcss +++ b/res/css/views/dialogs/_SpotlightDialog.pcss @@ -340,37 +340,28 @@ Please see LICENSE files in the repository root for full details. .mx_SpotlightDialog_option--menu, .mx_SpotlightDialog_option--notifications { - width: 20px; - min-width: 20px; - height: 20px; + width: 16px; + height: 16px; + padding: var(--cpd-space-0-5x); + flex-shrink: 0; margin-top: auto; margin-bottom: auto; position: relative; display: none; - &::before { - top: 2px; - left: 2px; - content: ""; - width: 16px; - height: 16px; - position: absolute; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background: $tertiary-content; + svg { + width: inherit; + height: inherit; + display: block; + color: $tertiary-content; } - &:hover::before, - &:focus-visible::before { - background-color: $secondary-content; + &:hover svg, + &:focus-visible svg { + color: $secondary-content; } } - .mx_SpotlightDialog_option--menu::before { - mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg"); - } - &:hover, &[aria-selected="true"] { .mx_SpotlightDialog_option--menu, diff --git a/res/css/views/right_panel/_ThreadPanel.pcss b/res/css/views/right_panel/_ThreadPanel.pcss index 2fe1a86908..2a282388eb 100644 --- a/res/css/views/right_panel/_ThreadPanel.pcss +++ b/res/css/views/right_panel/_ThreadPanel.pcss @@ -167,14 +167,6 @@ Please see LICENSE files in the repository root for full details. } } -.mx_ThreadPanel_viewInRoom::before { - mask-image: url("$(res)/img/element-icons/view-in-room.svg"); -} - -.mx_ThreadPanel_copyLinkToThread::before { - mask-image: url("@vector-im/compound-design-tokens/icons/link.svg"); -} - .mx_ContextualMenu_wrapper { .mx_ThreadPanel_Header_FilterOptionItem { display: flex; diff --git a/res/css/views/rooms/_LegacyRoomList.pcss b/res/css/views/rooms/_LegacyRoomList.pcss index 7b290e8f65..d890b561ec 100644 --- a/res/css/views/rooms/_LegacyRoomList.pcss +++ b/res/css/views/rooms/_LegacyRoomList.pcss @@ -9,25 +9,3 @@ Please see LICENSE files in the repository root for full details. .mx_LegacyRoomList { padding-right: 7px; /* width of the scrollbar, to line things up */ } - -.mx_LegacyRoomList_iconPlus::before { - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); -} -.mx_LegacyRoomList_iconNewRoom::before { - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); -} -.mx_LegacyRoomList_iconNewVideoRoom::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); -} -.mx_LegacyRoomList_iconAddExistingRoom::before { - mask-image: url("@vector-im/compound-design-tokens/icons/room.svg"); -} -.mx_LegacyRoomList_iconExplore::before { - mask-image: url("@vector-im/compound-design-tokens/icons/search.svg"); -} -.mx_LegacyRoomList_iconStartChat::before { - mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg"); -} -.mx_LegacyRoomList_iconInvite::before { - mask-image: url("@vector-im/compound-design-tokens/icons/share.svg"); -} diff --git a/res/css/views/rooms/_LegacyRoomListHeader.pcss b/res/css/views/rooms/_LegacyRoomListHeader.pcss index f008b1fbfc..fd6d7b3b29 100644 --- a/res/css/views/rooms/_LegacyRoomListHeader.pcss +++ b/res/css/views/rooms/_LegacyRoomListHeader.pcss @@ -87,22 +87,3 @@ Please see LICENSE files in the repository root for full details. } } } - -.mx_LegacyRoomListHeader_iconInvite::before { - mask-image: url("$(res)/img/element-icons/room/invite.svg"); -} -.mx_LegacyRoomListHeader_iconStartChat::before { - mask-image: url("@vector-im/compound-design-tokens/icons/user-add-solid.svg"); -} -.mx_LegacyRoomListHeader_iconNewRoom::before { - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); -} -.mx_LegacyRoomListHeader_iconNewVideoRoom::before { - mask-image: url("$(res)/img/element-icons/roomlist/hash-video.svg"); -} -.mx_LegacyRoomListHeader_iconExplore::before { - mask-image: url("@vector-im/compound-design-tokens/icons/search.svg"); -} -.mx_LegacyRoomListHeader_iconPlus::before { - mask-image: url("@vector-im/compound-design-tokens/icons/plus.svg"); -} diff --git a/res/css/views/rooms/_RoomTile.pcss b/res/css/views/rooms/_RoomTile.pcss index d525f73e46..babb70aec2 100644 --- a/res/css/views/rooms/_RoomTile.pcss +++ b/res/css/views/rooms/_RoomTile.pcss @@ -105,35 +105,23 @@ Please see LICENSE files in the repository root for full details. /* The context menu buttons are hidden by default */ .mx_RoomTile_menuButton, .mx_RoomTile_notificationsButton { - width: 20px; - min-width: 20px; /* yay flex */ - height: 20px; + width: 16px; + height: 16px; + padding: var(--cpd-space-0-5x); + flex-shrink: 0; margin-top: auto; margin-bottom: auto; position: relative; display: none; svg { - width: 16px; - height: 16px; - padding: var(--cpd-space-0-5x); + width: inherit; + height: inherit; + display: block; color: var(--cpd-color-icon-primary); } } - .mx_RoomTile_notificationsButton::before { - top: 2px; - left: 2px; - content: ""; - width: 16px; - height: 16px; - position: absolute; - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - background: var(--cpd-color-icon-primary); - } - /* If the room has an overriden notification setting then we always show the notifications menu button */ .mx_RoomTile_notificationsButton.mx_RoomTile_notificationsButton_show { display: block; diff --git a/res/img/element-icons/notifications.svg b/res/img/element-icons/notifications.svg index 7002782129..7709c673f1 100644 --- a/res/img/element-icons/notifications.svg +++ b/res/img/element-icons/notifications.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/res/img/element-icons/room/invite.svg b/res/img/element-icons/room/invite.svg index d2ecb837b2..2819553ab3 100644 --- a/res/img/element-icons/room/invite.svg +++ b/res/img/element-icons/room/invite.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/element-icons/roomlist/notifications-default.svg b/res/img/element-icons/roomlist/notifications-default.svg index 59743f5d67..c3af0ef809 100644 --- a/res/img/element-icons/roomlist/notifications-default.svg +++ b/res/img/element-icons/roomlist/notifications-default.svg @@ -1,5 +1,5 @@ - - - + + + diff --git a/res/img/element-icons/roomlist/notifications-dm.svg b/res/img/element-icons/roomlist/notifications-dm.svg index e0bd435240..9259c4d880 100644 --- a/res/img/element-icons/roomlist/notifications-dm.svg +++ b/res/img/element-icons/roomlist/notifications-dm.svg @@ -1,3 +1,3 @@ - + diff --git a/res/img/element-icons/roomlist/notifications-off.svg b/res/img/element-icons/roomlist/notifications-off.svg index c848471f63..bbc2dcfeb9 100644 --- a/res/img/element-icons/roomlist/notifications-off.svg +++ b/res/img/element-icons/roomlist/notifications-off.svg @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/src/components/structures/SpaceRoomView.tsx b/src/components/structures/SpaceRoomView.tsx index f9210ec96b..c1758a87c7 100644 --- a/src/components/structures/SpaceRoomView.tsx +++ b/src/components/structures/SpaceRoomView.tsx @@ -10,6 +10,7 @@ import { EventType, RoomType, JoinRule, Preset, type Room, RoomEvent } from "mat import { KnownMembership } from "matrix-js-sdk/src/types"; import { logger } from "matrix-js-sdk/src/logger"; import React, { type JSX, useCallback, useContext, useRef, useState } from "react"; +import { PlusIcon, RoomIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import MatrixClientContext from "../../contexts/MatrixClientContext"; import createRoom, { type IOpts } from "../../createRoom"; @@ -66,6 +67,7 @@ import MainSplit from "./MainSplit"; import RightPanel from "./RightPanel"; import SpaceHierarchy, { showRoom } from "./SpaceHierarchy"; import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; +import { Icon as HashVideoIcon } from "../../../res/img/element-icons/roomlist/hash-video.svg"; interface IProps { space: Room; @@ -117,7 +119,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => { <> } onClick={async (e): Promise => { e.preventDefault(); e.stopPropagation(); @@ -132,7 +134,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => { {videoRoomsEnabled && ( } onClick={async (e): Promise => { e.preventDefault(); e.stopPropagation(); @@ -157,7 +159,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => { )} } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -168,7 +170,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => { {canCreateSpace && ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 67af61f7ac..8c96fab279 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -8,6 +8,13 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX, createRef, type ReactNode } from "react"; import { type Room } from "matrix-js-sdk/src/matrix"; +import { + HomeSolidIcon, + LockSolidIcon, + QrCodeIcon, + SettingsSolidIcon, + LeaveIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; import { MatrixClientPeg } from "../../MatrixClientPeg"; import defaultDispatcher from "../../dispatcher/dispatcher"; @@ -42,7 +49,9 @@ import PosthogTrackers from "../../PosthogTrackers"; import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload"; import { SDKContext } from "../../contexts/SDKContext"; import { shouldShowFeedback } from "../../utils/Feedback"; -import DarkLightModeSvg from "../../../res/img/element-icons/roomlist/dark-light-mode.svg"; +import { Icon as DarkLightModeSvg } from "../../../res/img/element-icons/roomlist/dark-light-mode.svg"; +import { Icon as NotificationsIcon } from "../../../res/img/element-icons/notifications.svg"; +import { Icon as FeedbackIcon } from "../../../res/img/element-icons/feedback.svg"; interface IProps { isPanelCollapsed: boolean; @@ -297,7 +306,7 @@ export default class UserMenu extends React.Component { if (this.hasHomePage) { homeButton = ( } label={_t("common|home")} onClick={this.onHomeClick} /> @@ -308,7 +317,7 @@ export default class UserMenu extends React.Component { if (shouldShowFeedback()) { feedbackButton = ( } label={_t("common|feedback")} onClick={this.onProvideFeedback} /> @@ -317,7 +326,7 @@ export default class UserMenu extends React.Component { const linkNewDeviceButton = ( } label={_t("user_menu|link_new_device")} onClick={(e) => this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })} /> @@ -328,24 +337,24 @@ export default class UserMenu extends React.Component { {homeButton} {linkNewDeviceButton} } label={_t("notifications|enable_prompt_toast_title")} onClick={(e) => this.onSettingsOpen(e, UserTab.Notifications)} /> } label={_t("room_settings|security|title")} onClick={(e) => this.onSettingsOpen(e, UserTab.Security)} /> } label={_t("user_menu|settings")} onClick={(e) => this.onSettingsOpen(e)} /> {feedbackButton} } label={_t("action|sign_out")} onClick={this.onSignOutClick} /> @@ -357,7 +366,7 @@ export default class UserMenu extends React.Component { {homeButton} } label={_t("common|settings")} onClick={(e) => this.onSettingsOpen(e)} /> @@ -398,7 +407,7 @@ export default class UserMenu extends React.Component { : _t("user_menu|switch_theme_dark") } > - + {topSection} diff --git a/src/components/views/context_menus/DeveloperToolsOption.tsx b/src/components/views/context_menus/DeveloperToolsOption.tsx index 53a5d7283d..b1b8a5b136 100644 --- a/src/components/views/context_menus/DeveloperToolsOption.tsx +++ b/src/components/views/context_menus/DeveloperToolsOption.tsx @@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details. */ import React from "react"; +import { LabsIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import Modal from "../../../Modal"; import DevtoolsDialog from "../dialogs/DevtoolsDialog"; @@ -32,7 +33,7 @@ export const DeveloperToolsOption: React.FC = ({ onFinished, roomId }) => onFinished(); }} label={_t("devtools|title")} - iconClassName="mx_IconizedContextMenu_developerTools" + icon={} /> ); }; diff --git a/src/components/views/context_menus/DeviceContextMenu.tsx b/src/components/views/context_menus/DeviceContextMenu.tsx index b6646c05ec..7560b4a7d5 100644 --- a/src/components/views/context_menus/DeviceContextMenu.tsx +++ b/src/components/views/context_menus/DeviceContextMenu.tsx @@ -26,14 +26,7 @@ interface IDeviceContextMenuDeviceProps { } const DeviceContextMenuDevice: React.FC = ({ label, selected, onClick }) => { - return ( - - ); + return ; }; interface IDeviceContextMenuSectionProps { diff --git a/src/components/views/context_menus/IconizedContextMenu.tsx b/src/components/views/context_menus/IconizedContextMenu.tsx index 4ca35f2cc7..b7d07cae3f 100644 --- a/src/components/views/context_menus/IconizedContextMenu.tsx +++ b/src/components/views/context_menus/IconizedContextMenu.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX, type ReactNode } from "react"; import classNames from "classnames"; +import { CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import ContextMenu, { ChevronFace, @@ -33,26 +34,19 @@ interface IOptionListProps { interface IOptionProps extends React.ComponentProps { icon?: ReactNode; - iconClassName?: string; isDestructive?: boolean; } interface ICheckboxProps extends React.ComponentProps { - iconClassName: string; + icon?: ReactNode; words?: boolean; } interface IRadioProps extends React.ComponentProps { - iconClassName?: string; + icon?: ReactNode; } -export const IconizedContextMenuRadio: React.FC = ({ - label, - iconClassName, - active, - className, - ...props -}) => { +export const IconizedContextMenuRadio: React.FC = ({ label, icon, active, className, ...props }) => { return ( = ({ active={active} label={label} > - {iconClassName && } + {icon} {label} - {active && } + {active && } ); }; export const IconizedContextMenuCheckbox: React.FC = ({ label, - iconClassName, + icon, active, className, words, ...props }) => { - let marker: JSX.Element; + let marker: JSX.Element | undefined; if (words) { marker = ( {active ? _t("common|on") : _t("common|off")} ); - } else { - marker = ( - - ); + } else if (active) { + marker = ; } return ( @@ -104,7 +91,7 @@ export const IconizedContextMenuCheckbox: React.FC = ({ active={active} label={label} > - + {icon} {label} {marker} @@ -114,7 +101,6 @@ export const IconizedContextMenuCheckbox: React.FC = ({ export const IconizedContextMenuOption: React.FC = ({ label, className, - iconClassName, icon, children, isDestructive, @@ -130,7 +116,6 @@ export const IconizedContextMenuOption: React.FC = ({ })} label={label} > - {iconClassName && } {icon} {label} {children} diff --git a/src/components/views/context_menus/MessageContextMenu.tsx b/src/components/views/context_menus/MessageContextMenu.tsx index 6d13f1b2e8..4f1a6a962a 100644 --- a/src/components/views/context_menus/MessageContextMenu.tsx +++ b/src/components/views/context_menus/MessageContextMenu.tsx @@ -20,6 +20,27 @@ import { Thread, M_POLL_START, } from "matrix-js-sdk/src/matrix"; +import { + CheckIcon, + ChevronUpIcon, + EditIcon, + ErrorSolidIcon, + InlineCodeIcon, + LinkIcon, + PinIcon, + QuoteIcon, + ReactionAddIcon, + ReplyIcon, + RestartIcon, + ThreadsIcon, + UnpinIcon, + DeleteIcon, + ForwardIcon, + PopOutIcon, + VisibilityOnIcon, + ShareIcon, + CopyIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import dis from "../../../dispatcher/dispatcher"; @@ -53,6 +74,8 @@ import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadP import { CardContext } from "../right_panel/context"; import PinningUtils from "../../../utils/PinningUtils"; import PosthogTrackers from "../../../PosthogTrackers.ts"; +import { Icon as ViewInRoomIcon } from "../../../../res/img/element-icons/view-in-room.svg"; +import { Icon as ChildRelationshipIcon } from "../../../../res/img/element-icons/child-relationship.svg"; interface IReplyInThreadButton { mxEvent: MatrixEvent; @@ -86,13 +109,7 @@ const ReplyInThreadButton: React.FC = ({ mxEvent, closeMen closeMenu(); }; - return ( - - ); + return } label={_t("action|reply_in_thread")} onClick={onClick} />; }; interface IProps extends MenuProps { @@ -413,7 +430,7 @@ export default class MessageContextMenu extends React.Component if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) { resendReactionsButton = ( } label={_t("timeline|context_menu|resent_unsent_reactions", { unsentCount: unsentReactionsCount })} onClick={this.onResendReactionsClick} /> @@ -424,7 +441,7 @@ export default class MessageContextMenu extends React.Component if (isSent && this.state.canRedact) { redactButton = ( } label={_t("action|remove")} onClick={this.onRedactClick} /> @@ -437,7 +454,7 @@ export default class MessageContextMenu extends React.Component const mapSiteLink = createMapSiteLinkFromEvent(shareableLocationEvent); openInMapSiteButton = ( } onClick={null} label={_t("timeline|context_menu|open_in_osm")} element="a" @@ -455,7 +472,7 @@ export default class MessageContextMenu extends React.Component if (contentActionable && forwardableEvent) { forwardButton = ( } label={_t("action|forward")} onClick={this.onForwardClick(forwardableEvent)} /> @@ -465,7 +482,7 @@ export default class MessageContextMenu extends React.Component // This is specifically not behind the developerMode flag to give people insight into the Matrix const viewSourceButton = ( } label={_t("timeline|context_menu|view_source")} onClick={this.onViewSourceClick} /> @@ -475,7 +492,7 @@ export default class MessageContextMenu extends React.Component if (eventTileOps?.isWidgetHidden()) { unhidePreviewButton = ( } label={_t("timeline|context_menu|show_url_preview")} onClick={this.onUnhidePreviewClick} /> @@ -486,7 +503,7 @@ export default class MessageContextMenu extends React.Component if (permalink) { permalinkButton = ( } onClick={this.onShareClick} label={_t("action|share")} element="a" @@ -506,7 +523,7 @@ export default class MessageContextMenu extends React.Component if (this.canEndPoll(mxEvent)) { endPollButton = ( } label={_t("poll|end_title")} onClick={this.onEndPollClick} /> @@ -521,7 +538,7 @@ export default class MessageContextMenu extends React.Component ) { externalURLButton = ( } onClick={this.closeMenu} label={_t("timeline|context_menu|external_url")} element="a" @@ -541,7 +558,7 @@ export default class MessageContextMenu extends React.Component if (collapseReplyChain) { collapseReplyChainButton = ( } label={_t("timeline|context_menu|collapse_reply_thread")} onClick={this.onCollapseReplyChainClick} /> @@ -553,7 +570,7 @@ export default class MessageContextMenu extends React.Component if (relatedEventId && SettingsStore.getValue("developerMode")) { jumpToRelatedEventButton = ( } label={_t("timeline|context_menu|view_related_event")} onClick={() => this.onJumpToRelatedEventClick(relatedEventId)} /> @@ -564,7 +581,7 @@ export default class MessageContextMenu extends React.Component if (mxEvent.getSender() !== me) { reportEventButton = ( } label={_t("timeline|context_menu|report")} onClick={this.onReportEventClick} /> @@ -575,7 +592,7 @@ export default class MessageContextMenu extends React.Component if (link) { copyLinkButton = ( } onClick={this.onCopyLinkClick} label={_t("action|copy_link")} element="a" @@ -597,7 +614,7 @@ export default class MessageContextMenu extends React.Component if (rightClick && selectedText) { copyButton = ( } label={_t("action|copy")} triggerOnMouseDown={true} // We use onMouseDown so that the selection isn't cleared when we click onClick={this.onCopyClick} @@ -609,7 +626,7 @@ export default class MessageContextMenu extends React.Component if (rightClick && selectedText && selectedText.trim().length > 0 && this.isSelectionWithinSingleTextBody()) { quoteButton = ( } label={_t("action|quote")} triggerOnMouseDown={true} onClick={this.onQuoteClick} @@ -620,11 +637,7 @@ export default class MessageContextMenu extends React.Component let editButton: JSX.Element | undefined; if (rightClick && canEditContent(cli, mxEvent)) { editButton = ( - + } label={_t("action|edit")} onClick={this.onEditClick} /> ); } @@ -632,7 +645,7 @@ export default class MessageContextMenu extends React.Component if (rightClick && contentActionable && canSendMessages) { replyButton = ( } label={_t("action|reply")} onClick={this.onReplyClick} /> @@ -654,7 +667,7 @@ export default class MessageContextMenu extends React.Component if (rightClick && contentActionable && canReact) { reactButton = ( } label={_t("action|react")} onClick={this.onReactClick} inputRef={this.reactButtonRef} @@ -667,7 +680,7 @@ export default class MessageContextMenu extends React.Component const isPinned = PinningUtils.isPinned(MatrixClientPeg.safeGet(), this.props.mxEvent); pinButton = ( : } label={isPinned ? _t("action|unpin") : _t("action|pin")} onClick={() => this.onPinClick(isPinned)} /> @@ -678,7 +691,7 @@ export default class MessageContextMenu extends React.Component if (isThreadRootEvent) { viewInRoomButton = ( } label={_t("timeline|mab|view_in_room")} onClick={this.viewInRoom} /> diff --git a/src/components/views/context_menus/RoomGeneralContextMenu.tsx b/src/components/views/context_menus/RoomGeneralContextMenu.tsx index 7c21d09853..ac06958c71 100644 --- a/src/components/views/context_menus/RoomGeneralContextMenu.tsx +++ b/src/components/views/context_menus/RoomGeneralContextMenu.tsx @@ -9,6 +9,15 @@ Please see LICENSE files in the repository root for full details. import { logger } from "matrix-js-sdk/src/logger"; import { type Room } from "matrix-js-sdk/src/matrix"; import React, { type JSX, useContext } from "react"; +import { + FavouriteSolidIcon, + LinkIcon, + SettingsSolidIcon, + ArrowDownIcon, + MarkAsReadIcon, + MarkAsUnreadIcon, + LeaveIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts"; import RoomListActions from "../../../actions/RoomListActions"; @@ -34,6 +43,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent import { UIComponent } from "../../../settings/UIFeature"; import { DeveloperToolsOption } from "./DeveloperToolsOption"; import { useSettingValue } from "../../../hooks/useSettings"; +import { Icon as InviteIcon } from "../../../../res/img/element-icons/room/invite.svg"; export interface RoomGeneralContextMenuProps extends IContextMenuProps { room: Room; @@ -153,7 +163,7 @@ export const RoomGeneralContextMenu: React.FC = ({ onClick={wrapHandler((ev) => onTagRoom(ev, DefaultTagID.Favourite), onPostFavoriteClick, true)} active={isFavorite} label={isFavorite ? _t("room|context_menu|unfavourite") : _t("room|context_menu|favourite")} - iconClassName="mx_RoomGeneralContextMenu_iconStar" + icon={} /> ); @@ -163,7 +173,7 @@ export const RoomGeneralContextMenu: React.FC = ({ onClick={wrapHandler((ev) => onTagRoom(ev, DefaultTagID.LowPriority), onPostLowPriorityClick, true)} active={isLowPriority} label={_t("room|context_menu|low_priority")} - iconClassName="mx_RoomGeneralContextMenu_iconArrowDown" + icon={} /> ); @@ -180,7 +190,7 @@ export const RoomGeneralContextMenu: React.FC = ({ onPostInviteClick, )} label={_t("action|invite")} - iconClassName="mx_RoomGeneralContextMenu_iconInvite" + icon={} /> ); } @@ -198,7 +208,7 @@ export const RoomGeneralContextMenu: React.FC = ({ onPostCopyLinkClick, )} label={_t("room|context_menu|copy_link")} - iconClassName="mx_RoomGeneralContextMenu_iconCopyLink" + icon={} /> ); } @@ -214,7 +224,7 @@ export const RoomGeneralContextMenu: React.FC = ({ onPostSettingsClick, )} label={_t("common|settings")} - iconClassName="mx_RoomGeneralContextMenu_iconSettings" + icon={} /> ); @@ -222,7 +232,7 @@ export const RoomGeneralContextMenu: React.FC = ({ if (roomTags.includes(DefaultTagID.Archived)) { leaveOption = ( } label={_t("room|context_menu|forget")} className="mx_IconizedContextMenu_option_red" onClick={wrapHandler( @@ -248,7 +258,7 @@ export const RoomGeneralContextMenu: React.FC = ({ )} label={_t("action|leave")} className="mx_IconizedContextMenu_option_red" - iconClassName="mx_RoomGeneralContextMenu_iconSignOut" + icon={} /> ); } @@ -263,7 +273,7 @@ export const RoomGeneralContextMenu: React.FC = ({ onFinished?.(); }, onPostMarkAsReadClick)} label={_t("room|context_menu|mark_read")} - iconClassName="mx_RoomGeneralContextMenu_iconMarkAsRead" + icon={} /> ); } else if (!roomTags.includes(DefaultTagID.Archived)) { @@ -274,7 +284,7 @@ export const RoomGeneralContextMenu: React.FC = ({ onFinished?.(); }, onPostMarkAsUnreadClick)} label={_t("room|context_menu|mark_unread")} - iconClassName="mx_RoomGeneralContextMenu_iconMarkAsUnread" + icon={} /> ); } else { diff --git a/src/components/views/context_menus/RoomNotificationContextMenu.tsx b/src/components/views/context_menus/RoomNotificationContextMenu.tsx index 9844f27695..9ad381736a 100644 --- a/src/components/views/context_menus/RoomNotificationContextMenu.tsx +++ b/src/components/views/context_menus/RoomNotificationContextMenu.tsx @@ -20,6 +20,10 @@ import IconizedContextMenu, { IconizedContextMenuRadio, } from "../context_menus/IconizedContextMenu"; import { type ButtonEvent } from "../elements/AccessibleButton"; +import { Icon as NotificationsIcon } from "../../../../res/img/element-icons/notifications.svg"; +import { Icon as NotificationsDefaultIcon } from "../../../../res/img/element-icons/roomlist/notifications-default.svg"; +import { Icon as NotificationsDmIcon } from "../../../../res/img/element-icons/roomlist/notifications-dm.svg"; +import { Icon as NotificationsOffIcon } from "../../../../res/img/element-icons/roomlist/notifications-off.svg"; interface IProps extends IContextMenuProps { room: Room; @@ -46,7 +50,7 @@ export const RoomNotificationContextMenu: React.FC = ({ room, onFinished } onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessages))} /> ); @@ -55,7 +59,7 @@ export const RoomNotificationContextMenu: React.FC = ({ room, onFinished } onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessagesLoud))} /> ); @@ -64,7 +68,7 @@ export const RoomNotificationContextMenu: React.FC = ({ room, onFinished } onClick={wrapHandler(() => setNotificationState(RoomNotifState.MentionsOnly))} /> ); @@ -73,7 +77,7 @@ export const RoomNotificationContextMenu: React.FC = ({ room, onFinished } onClick={wrapHandler(() => setNotificationState(RoomNotifState.Mute))} /> ); diff --git a/src/components/views/context_menus/SpaceContextMenu.tsx b/src/components/views/context_menus/SpaceContextMenu.tsx index eab9c1d011..45a901157c 100644 --- a/src/components/views/context_menus/SpaceContextMenu.tsx +++ b/src/components/views/context_menus/SpaceContextMenu.tsx @@ -8,6 +8,14 @@ Please see LICENSE files in the repository root for full details. import React, { type JSX, useContext } from "react"; import { type Room, EventType, RoomType } from "matrix-js-sdk/src/matrix"; +import { + HomeSolidIcon, + PlusIcon, + SettingsSolidIcon, + LeaveIcon, + SearchIcon, + PreferencesIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; import { type IProps as IContextMenuProps } from "../../structures/ContextMenu"; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu"; @@ -32,6 +40,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent import { UIComponent } from "../../../settings/UIFeature"; import PosthogTrackers from "../../../PosthogTrackers"; import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { Icon as InviteIcon } from "../../../../res/img/element-icons/room/invite.svg"; interface IProps extends IContextMenuProps { space?: Room; @@ -60,7 +69,7 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... } label={_t("action|invite")} onClick={onInviteClick} /> @@ -81,7 +90,7 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... settingsOption = ( } label={_t("common|settings")} onClick={onSettingsClick} /> @@ -98,7 +107,7 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... leaveOption = ( } className="mx_IconizedContextMenu_option_red" label={_t("space|leave_dialog_action")} onClick={onLeaveClick} @@ -123,7 +132,7 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... devtoolsOption = ( } label={_t("space|context_menu|devtools_open_timeline")} onClick={onViewTimelineClick} /> @@ -170,7 +179,7 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... {canAddRooms && ( } label={_t("common|room")} onClick={onNewRoomClick} /> @@ -178,7 +187,7 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... {canAddVideoRooms && ( } label={_t("common|video_room")} onClick={onNewVideoRoomClick} > @@ -188,7 +197,7 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... {canAddSubSpaces && ( } label={_t("common|space")} onClick={onNewSubspaceClick} > @@ -234,18 +243,18 @@ const SpaceContextMenu: React.FC = ({ space, hideHeader, onFinished, ... {!hideHeader &&
{space.name}
} } label={_t("space|context_menu|home")} onClick={onHomeClick} /> {inviteOption} } label={canAddRooms ? _t("space|context_menu|manage_and_explore") : _t("space|context_menu|explore")} onClick={onExploreRoomsClick} /> } label={_t("common|preferences")} onClick={onPreferencesClick} /> diff --git a/src/components/views/context_menus/ThreadListContextMenu.tsx b/src/components/views/context_menus/ThreadListContextMenu.tsx index ef32d62282..11292f2a4f 100644 --- a/src/components/views/context_menus/ThreadListContextMenu.tsx +++ b/src/components/views/context_menus/ThreadListContextMenu.tsx @@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details. import React, { useCallback, useEffect } from "react"; import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; +import { LinkIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { type ButtonEvent } from "../elements/AccessibleButton"; import dis from "../../../dispatcher/dispatcher"; @@ -20,6 +21,7 @@ import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOpti import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; +import { Icon as ViewInRoomIcon } from "../../../../res/img/element-icons/view-in-room.svg"; export interface ThreadListContextMenuProps { mxEvent: MatrixEvent; @@ -102,7 +104,7 @@ const ThreadListContextMenu: React.FC = ({ viewInRoom(e)} label={_t("timeline|mab|view_in_room")} - iconClassName="mx_ThreadPanel_viewInRoom" + icon={} /> )} {permalinkCreator && ( @@ -110,7 +112,7 @@ const ThreadListContextMenu: React.FC = ({ data-testid="copy-thread-link" onClick={(e) => copyLinkToThread(e)} label={_t("timeline|mab|copy_link_thread")} - iconClassName="mx_ThreadPanel_copyLinkToThread" + icon={} /> )} diff --git a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx index f7fd5c6cb5..8bf5c1e22b 100644 --- a/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx +++ b/src/components/views/dialogs/spotlight/RoomResultContextMenus.tsx @@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com Please see LICENSE files in the repository root for full details. */ -import classNames from "classnames"; import { type Room } from "matrix-js-sdk/src/matrix"; -import React, { type JSX, Fragment, useState } from "react"; +import React, { type JSX, Fragment, useState, type ReactNode } from "react"; +import { OverflowHorizontalIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { ContextMenuTooltipButton } from "../../../../accessibility/context_menu/ContextMenuTooltipButton"; import { useNotificationState } from "../../../../hooks/useRoomNotificationState"; @@ -21,11 +21,25 @@ import { type ButtonEvent } from "../../elements/AccessibleButton"; import { contextMenuBelow } from "../../rooms/RoomTile"; import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents"; import { UIComponent } from "../../../../settings/UIFeature"; +import { Icon as NotificationsIcon } from "../../../../../res/img/element-icons/notifications.svg"; +import { Icon as NotificationsDefaultIcon } from "../../../../../res/img/element-icons/roomlist/notifications-default.svg"; +import { Icon as NotificationsDmIcon } from "../../../../../res/img/element-icons/roomlist/notifications-dm.svg"; +import { Icon as NotificationsOffIcon } from "../../../../../res/img/element-icons/roomlist/notifications-off.svg"; interface Props { room: Room; } +export function getNotificationIcon(state: RoomNotifState): ReactNode { + const icons: Record = { + [RoomNotifState.AllMessages]: , + [RoomNotifState.AllMessagesLoud]: , + [RoomNotifState.MentionsOnly]: , + [RoomNotifState.Mute]: , + }; + return icons[state]; +} + export function RoomResultContextMenus({ room }: Props): JSX.Element { const [notificationState] = useNotificationState(room); @@ -64,14 +78,6 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element { ); } - const notificationMenuClasses = classNames("mx_SpotlightDialog_option--notifications", { - // Show bell icon for the default case too. - mx_RoomNotificationContextMenu_iconBell: notificationState === RoomNotifState.AllMessages, - mx_RoomNotificationContextMenu_iconBellDot: notificationState === RoomNotifState.AllMessagesLoud, - mx_RoomNotificationContextMenu_iconBellMentions: notificationState === RoomNotifState.MentionsOnly, - mx_RoomNotificationContextMenu_iconBellCrossed: notificationState === RoomNotifState.Mute, - }); - return ( {shouldShowComponent(UIComponent.RoomOptionsMenu) && ( @@ -86,11 +92,13 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element { }} title={room.isSpaceRoom() ? _t("space|context_menu|options") : _t("room|context_menu|title")} isExpanded={generalMenuPosition !== null} - /> + > + + )} {!room.isSpaceRoom() && ( { ev.preventDefault(); ev.stopPropagation(); @@ -100,7 +108,9 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element { }} title={_t("room_list|notification_options")} isExpanded={notificationMenuPosition !== null} - /> + > + {getNotificationIcon(notificationState!)} + )} {generalMenu} {notificationMenu} diff --git a/src/components/views/rooms/LegacyRoomList.tsx b/src/components/views/rooms/LegacyRoomList.tsx index 6be226a172..95b62cf4c9 100644 --- a/src/components/views/rooms/LegacyRoomList.tsx +++ b/src/components/views/rooms/LegacyRoomList.tsx @@ -8,6 +8,13 @@ Please see LICENSE files in the repository root for full details. import { EventType, type Room, RoomType } from "matrix-js-sdk/src/matrix"; import React, { type JSX, type ComponentType, createRef, type ReactComponentElement, type SyntheticEvent } from "react"; +import { + PlusIcon, + UserAddSolidIcon, + RoomIcon, + SearchIcon, + ShareIcon, +} from "@vector-im/compound-design-tokens/assets/web/icons"; import { type IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex.tsx"; import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx"; @@ -67,6 +74,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager.ts"; import AccessibleButton from "../elements/AccessibleButton.tsx"; import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation.ts"; import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx"; +import { Icon as HashVideoIcon } from "../../../../res/img/element-icons/roomlist/hash-video.svg"; interface IProps { onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; @@ -142,7 +150,7 @@ const DmAuxButton: React.FC = ({ tabIndex, dispatcher = default {showCreateRooms && ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -158,7 +166,7 @@ const DmAuxButton: React.FC = ({ tabIndex, dispatcher = default {showInviteUsers && ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -230,7 +238,7 @@ const UntaggedAuxButton: React.FC = ({ tabIndex }) => { } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -247,7 +255,7 @@ const UntaggedAuxButton: React.FC = ({ tabIndex }) => { <> } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -261,7 +269,7 @@ const UntaggedAuxButton: React.FC = ({ tabIndex }) => { {videoRoomsEnabled && ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -279,7 +287,7 @@ const UntaggedAuxButton: React.FC = ({ tabIndex }) => { )} } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -300,7 +308,7 @@ const UntaggedAuxButton: React.FC = ({ tabIndex }) => { <> } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -312,7 +320,7 @@ const UntaggedAuxButton: React.FC = ({ tabIndex }) => { {videoRoomsEnabled && ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -333,7 +341,7 @@ const UntaggedAuxButton: React.FC = ({ tabIndex }) => { {showExploreRooms ? ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); diff --git a/src/components/views/rooms/LegacyRoomListHeader.tsx b/src/components/views/rooms/LegacyRoomListHeader.tsx index 719831d865..4e3329f59d 100644 --- a/src/components/views/rooms/LegacyRoomListHeader.tsx +++ b/src/components/views/rooms/LegacyRoomListHeader.tsx @@ -9,6 +9,7 @@ Please see LICENSE files in the repository root for full details. import { ClientEvent, EventType, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix"; import React, { type JSX, useContext, useEffect, useState } from "react"; import { Tooltip } from "@vector-im/compound-web"; +import { PlusIcon, UserAddSolidIcon, SearchIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import MatrixClientContext from "../../../contexts/MatrixClientContext"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; @@ -51,6 +52,8 @@ import IconizedContextMenu, { import SpaceContextMenu from "../context_menus/SpaceContextMenu"; import InlineSpinner from "../elements/InlineSpinner"; import { HomeButtonContextMenu } from "../spaces/SpacePanel"; +import { Icon as InviteIcon } from "../../../../res/img/element-icons/room/invite.svg"; +import { Icon as HashVideoIcon } from "../../../../res/img/element-icons/roomlist/hash-video.svg"; const contextMenuBelow = (elementRect: DOMRect): MenuProps => { // align the context menu's icons with the icon which opened the context menu @@ -178,7 +181,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { inviteOption = ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -194,7 +197,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { newRoomOptions = ( <> } label={_t("action|new_room")} onClick={(e) => { e.preventDefault(); @@ -206,7 +209,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { /> {videoRoomsEnabled && ( } label={_t("action|new_video_room")} onClick={(e) => { e.preventDefault(); @@ -236,7 +239,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { {newRoomOptions} } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -251,7 +254,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { /> } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -264,7 +267,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { {canCreateSpaces && ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -289,7 +292,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { <> } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -300,7 +303,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { /> } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -312,7 +315,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { {videoRoomsEnabled && ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); @@ -333,7 +336,7 @@ const LegacyRoomListHeader: React.FC = ({ onVisibilityChange }) => { joinRoomOpt = ( } onClick={(e) => { e.preventDefault(); e.stopPropagation(); diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 3e07ed5a9a..8d019a705f 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -45,6 +45,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent import { UIComponent } from "../../../settings/UIFeature"; import { isKnockDenied } from "../../../utils/membership"; import SettingsStore from "../../../settings/SettingsStore"; +import { getNotificationIcon } from "../dialogs/spotlight/RoomResultContextMenus.tsx"; interface Props { room: Room; @@ -293,12 +294,6 @@ class RoomTile extends React.PureComponent { const state = this.roomProps.notificationVolume; const classes = classNames("mx_RoomTile_notificationsButton", { - // Show bell icon for the default case too. - mx_RoomNotificationContextMenu_iconBell: state === RoomNotifState.AllMessages, - mx_RoomNotificationContextMenu_iconBellDot: state === RoomNotifState.AllMessagesLoud, - mx_RoomNotificationContextMenu_iconBellMentions: state === RoomNotifState.MentionsOnly, - mx_RoomNotificationContextMenu_iconBellCrossed: state === RoomNotifState.Mute, - // Only show the icon by default if the room is overridden to muted. // TODO: [FTUE Notifications] Probably need to detect global mute state mx_RoomTile_notificationsButton_show: state === RoomNotifState.Mute, @@ -312,7 +307,9 @@ class RoomTile extends React.PureComponent { title={_t("room_list|notification_options")} isExpanded={!!this.state.notificationsMenuPosition} tabIndex={isActive ? 0 : -1} - /> + > + {getNotificationIcon(state!)} + {this.state.notificationsMenuPosition && ( {_t("common|home")}} { diff --git a/test/unit-tests/components/views/context_menus/DeviceContextMenu-test.tsx b/test/unit-tests/components/views/context_menus/DeviceContextMenu-test.tsx new file mode 100644 index 0000000000..af0ca451ec --- /dev/null +++ b/test/unit-tests/components/views/context_menus/DeviceContextMenu-test.tsx @@ -0,0 +1,39 @@ +/* +Copyright 2025 Element Creations Ltd. + +SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial +Please see LICENSE files in the repository root for full details. +*/ + +import { render } from "jest-matrix-react"; +import React from "react"; + +import { clearAllModals } from "../../../../test-utils"; +import DeviceContextMenu from "../../../../../src/components/views/context_menus/DeviceContextMenu.tsx"; +import MediaDeviceHandler from "../../../../../src/MediaDeviceHandler.ts"; + +describe("DeviceContextMenu", () => { + afterEach(async () => { + await clearAllModals(); + }); + + it("renders a menu with the selected device checked", async () => { + jest.spyOn(MediaDeviceHandler, "getDevices").mockResolvedValue({ + videoinput: [ + { deviceId: "A", label: "Camera 1" } as MediaDeviceInfo, + { deviceId: "B", label: "Camera 2" } as MediaDeviceInfo, + { deviceId: "C", label: "Camera 3" } as MediaDeviceInfo, + ], + audioinput: [], + audiooutput: [], + }); + jest.spyOn(MediaDeviceHandler, "getDevice").mockReturnValue("B"); + + const { container, findByLabelText } = render( + , + ); + + await expect(findByLabelText("Camera 2")).resolves.toBeChecked(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/test/unit-tests/components/views/context_menus/__snapshots__/DeviceContextMenu-test.tsx.snap b/test/unit-tests/components/views/context_menus/__snapshots__/DeviceContextMenu-test.tsx.snap new file mode 100644 index 0000000000..2702c8620a --- /dev/null +++ b/test/unit-tests/components/views/context_menus/__snapshots__/DeviceContextMenu-test.tsx.snap @@ -0,0 +1,85 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`DeviceContextMenu renders a menu with the selected device checked 1`] = ` +
+
+
+ +
+
+`; diff --git a/test/unit-tests/components/views/context_menus/__snapshots__/RoomGeneralContextMenu-test.tsx.snap b/test/unit-tests/components/views/context_menus/__snapshots__/RoomGeneralContextMenu-test.tsx.snap index 98a6dead82..5eb947b1ff 100644 --- a/test/unit-tests/components/views/context_menus/__snapshots__/RoomGeneralContextMenu-test.tsx.snap +++ b/test/unit-tests/components/views/context_menus/__snapshots__/RoomGeneralContextMenu-test.tsx.snap @@ -32,9 +32,20 @@ exports[`RoomGeneralContextMenu renders an empty context menu for archived rooms role="menuitem" tabindex="-1" > - + + + + @@ -80,9 +91,20 @@ exports[`RoomGeneralContextMenu renders the default context menu 1`] = ` role="menuitem" tabindex="-1" > - + + + + diff --git a/test/unit-tests/components/views/context_menus/__snapshots__/SpaceContextMenu-test.tsx.snap b/test/unit-tests/components/views/context_menus/__snapshots__/SpaceContextMenu-test.tsx.snap index cb994cf77a..a364662232 100644 --- a/test/unit-tests/components/views/context_menus/__snapshots__/SpaceContextMenu-test.tsx.snap +++ b/test/unit-tests/components/views/context_menus/__snapshots__/SpaceContextMenu-test.tsx.snap @@ -34,9 +34,17 @@ exports[` renders menu correctly 1`] = ` role="menuitem" tabindex="0" > -  +  +  +   @@ -49,9 +57,17 @@ exports[` renders menu correctly 1`] = ` role="menuitem" tabindex="-1" > -  +  +  +   @@ -64,9 +80,19 @@ exports[` renders menu correctly 1`] = ` role="menuitem" tabindex="-1" > -  +  +  +   @@ -80,9 +106,20 @@ exports[` renders menu correctly 1`] = ` role="menuitem" tabindex="-1" > -  +  +  +  +