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>
This commit is contained in:
Michael Telatynski
2025-12-09 15:10:42 +00:00
committed by GitHub
parent 4fda167c11
commit efe59ff35f
37 changed files with 440 additions and 564 deletions
-3
View File
@@ -120,9 +120,6 @@
@import "./views/context_menus/_DeviceContextMenu.pcss"; @import "./views/context_menus/_DeviceContextMenu.pcss";
@import "./views/context_menus/_IconizedContextMenu.pcss"; @import "./views/context_menus/_IconizedContextMenu.pcss";
@import "./views/context_menus/_LegacyCallContextMenu.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/_AddExistingToSpaceDialog.pcss";
@import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss"; @import "./views/dialogs/_AnalyticsLearnMoreDialog.pcss";
@import "./views/dialogs/_BugReportDialog.pcss"; @import "./views/dialogs/_BugReportDialog.pcss";
-40
View File
@@ -428,46 +428,6 @@ Please see LICENSE files in the repository root for full details.
white-space: nowrap; 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 { .mx_SpacePanel_contextMenu_separatorLabel {
color: $tertiary-content; color: $tertiary-content;
font-size: $font-10px; font-size: $font-10px;
+2 -39
View File
@@ -116,48 +116,11 @@ Please see LICENSE files in the repository root for full details.
} }
} }
.mx_IconizedContextMenu_icon { .mx_IconizedContextMenu_icon svg {
width: 16px; color: $icon-button-color;
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_UserMenu_iconMessage::before { .mx_UserMenu_iconMessage::before {
mask-image: url("$(res)/img/element-icons/feedback.svg"); 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");
}
} }
@@ -9,10 +9,6 @@ Please see LICENSE files in the repository root for full details.
.mx_DeviceContextMenu { .mx_DeviceContextMenu {
max-width: 252px; max-width: 252px;
.mx_DeviceContextMenu_device_icon {
display: none;
}
.mx_IconizedContextMenu_label { .mx_IconizedContextMenu_label {
padding-left: 0 !important; padding-left: 0 !important;
} }
@@ -68,19 +68,6 @@ Please see LICENSE files in the repository root for full details.
cursor: not-allowed; 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 { span.mx_IconizedContextMenu_label {
/* labels */ /* labels */
width: 100%; width: 100%;
@@ -92,27 +79,23 @@ Please see LICENSE files in the repository root for full details.
white-space: nowrap; white-space: nowrap;
} }
svg {
width: 16px;
height: 16px;
display: block;
flex-shrink: 0;
& + .mx_IconizedContextMenu_label {
padding-left: 14px;
}
}
.mx_BetaCard_betaPill { .mx_BetaCard_betaPill {
margin-left: 16px; 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_optionList_red {
.mx_IconizedContextMenu_item { .mx_IconizedContextMenu_item {
color: $alert !important; color: $alert !important;
@@ -121,10 +104,6 @@ Please see LICENSE files in the repository root for full details.
svg { svg {
color: var(--cpd-color-icon-critical-primary); color: var(--cpd-color-icon-critical-primary);
} }
.mx_IconizedContextMenu_icon::before {
background-color: var(--cpd-color-icon-critical-primary);
}
} }
.mx_IconizedContextMenu_option_red { .mx_IconizedContextMenu_option_red {
@@ -133,24 +112,16 @@ Please see LICENSE files in the repository root for full details.
svg { svg {
color: $alert; color: $alert;
} }
.mx_IconizedContextMenu_icon::before {
background-color: $alert;
}
} }
.mx_IconizedContextMenu_active { .mx_IconizedContextMenu_active {
&.mx_IconizedContextMenu_item, &.mx_IconizedContextMenu_item,
.mx_IconizedContextMenu_item { .mx_IconizedContextMenu_item {
color: $accent !important; color: $accent !important;
}
svg { svg {
color: $accent; color: $accent;
} }
.mx_IconizedContextMenu_icon::before {
background-color: $accent;
} }
} }
@@ -160,24 +131,11 @@ Please see LICENSE files in the repository root for full details.
} }
} }
.mx_IconizedContextMenu_checked, svg.mx_IconizedContextMenu_checked {
.mx_IconizedContextMenu_unchecked {
margin-left: 16px; margin-left: 16px;
margin-right: -5px; 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 { .mx_IconizedContextMenu_sublabel {
margin-left: 20px; margin-left: 20px;
color: $tertiary-content; color: $tertiary-content;
@@ -1,113 +0,0 @@
/*
Copyright 2024 New Vector Ltd.
Copyright 2021 Michael Weimann <mail@michael-weimann.eu>
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");
}
}
@@ -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");
}
@@ -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");
}
+12 -21
View File
@@ -340,37 +340,28 @@ Please see LICENSE files in the repository root for full details.
.mx_SpotlightDialog_option--menu, .mx_SpotlightDialog_option--menu,
.mx_SpotlightDialog_option--notifications { .mx_SpotlightDialog_option--notifications {
width: 20px; width: 16px;
min-width: 20px; height: 16px;
height: 20px; padding: var(--cpd-space-0-5x);
flex-shrink: 0;
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
position: relative; position: relative;
display: none; display: none;
&::before { svg {
top: 2px; width: inherit;
left: 2px; height: inherit;
content: ""; display: block;
width: 16px; color: $tertiary-content;
height: 16px;
position: absolute;
mask-position: center;
mask-size: contain;
mask-repeat: no-repeat;
background: $tertiary-content;
} }
&:hover::before, &:hover svg,
&:focus-visible::before { &:focus-visible svg {
background-color: $secondary-content; color: $secondary-content;
} }
} }
.mx_SpotlightDialog_option--menu::before {
mask-image: url("@vector-im/compound-design-tokens/icons/overflow-horizontal.svg");
}
&:hover, &:hover,
&[aria-selected="true"] { &[aria-selected="true"] {
.mx_SpotlightDialog_option--menu, .mx_SpotlightDialog_option--menu,
@@ -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_ContextualMenu_wrapper {
.mx_ThreadPanel_Header_FilterOptionItem { .mx_ThreadPanel_Header_FilterOptionItem {
display: flex; display: flex;
-22
View File
@@ -9,25 +9,3 @@ Please see LICENSE files in the repository root for full details.
.mx_LegacyRoomList { .mx_LegacyRoomList {
padding-right: 7px; /* width of the scrollbar, to line things up */ 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");
}
@@ -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");
}
+7 -19
View File
@@ -105,35 +105,23 @@ Please see LICENSE files in the repository root for full details.
/* The context menu buttons are hidden by default */ /* The context menu buttons are hidden by default */
.mx_RoomTile_menuButton, .mx_RoomTile_menuButton,
.mx_RoomTile_notificationsButton { .mx_RoomTile_notificationsButton {
width: 20px; width: 16px;
min-width: 20px; /* yay flex */ height: 16px;
height: 20px; padding: var(--cpd-space-0-5x);
flex-shrink: 0;
margin-top: auto; margin-top: auto;
margin-bottom: auto; margin-bottom: auto;
position: relative; position: relative;
display: none; display: none;
svg { svg {
width: 16px; width: inherit;
height: 16px; height: inherit;
padding: var(--cpd-space-0-5x); display: block;
color: var(--cpd-color-icon-primary); 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 */ /* If the room has an overriden notification setting then we always show the notifications menu button */
.mx_RoomTile_notificationsButton.mx_RoomTile_notificationsButton_show { .mx_RoomTile_notificationsButton.mx_RoomTile_notificationsButton_show {
display: block; display: block;
+3 -3
View File
@@ -1,5 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2375 19.5C10.1625 19.7 10.125 19.9 10.125 20.125C10.125 21.1625 10.9625 22 12 22C13.0375 22 13.875 21.1625 13.875 20.125C13.875 19.9 13.825 19.7 13.7625 19.5H10.2375Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M10.2375 19.5C10.1625 19.7 10.125 19.9 10.125 20.125C10.125 21.1625 10.9625 22 12 22C13.0375 22 13.875 21.1625 13.875 20.125C13.875 19.9 13.825 19.7 13.7625 19.5H10.2375Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.6125 16.0625C18.8625 15.625 18.25 14.8875 18.25 13.75V9.5C18.25 6.05 15.3 3.25 12 3.25C8.7 3.25 5.75 6.05 5.75 9.5V13.75C5.75 14.875 5.125 15.625 4.3875 16.0625C3.8875 16.3625 3.25 16.775 3.25 17.275C3.25 17.85 3.6125 18.2375 4.3625 18.2375H12H19.6375C20.3875 18.2375 20.75 17.8375 20.75 17.275C20.75 16.775 20.125 16.3625 19.6125 16.0625Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M19.6125 16.0625C18.8625 15.625 18.25 14.8875 18.25 13.75V9.5C18.25 6.05 15.3 3.25 12 3.25C8.7 3.25 5.75 6.05 5.75 9.5V13.75C5.75 14.875 5.125 15.625 4.3875 16.0625C3.8875 16.3625 3.25 16.775 3.25 17.275C3.25 17.85 3.6125 18.2375 4.3625 18.2375H12H19.6375C20.3875 18.2375 20.75 17.8375 20.75 17.275C20.75 16.775 20.125 16.3625 19.6125 16.0625Z" fill="currentColor"/>
<path d="M12 4.5C12.6904 4.5 13.25 3.94036 13.25 3.25C13.25 2.55964 12.6904 2 12 2C11.3096 2 10.75 2.55964 10.75 3.25C10.75 3.94036 11.3096 4.5 12 4.5Z" fill="black"/> <path d="M12 4.5C12.6904 4.5 13.25 3.94036 13.25 3.25C13.25 2.55964 12.6904 2 12 2C11.3096 2 10.75 2.55964 10.75 3.25C10.75 3.94036 11.3096 4.5 12 4.5Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 916 B

After

Width:  |  Height:  |  Size: 937 B

+1 -1
View File
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="-0.4 1 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="24" height="24" viewBox="-0.4 1 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.1001 9C18.7779 9 18.5168 8.73883 18.5168 8.41667V6.08333H16.1834C15.8613 6.08333 15.6001 5.82217 15.6001 5.5C15.6001 5.17783 15.8613 4.91667 16.1834 4.91667H18.5168V2.58333C18.5168 2.26117 18.7779 2 19.1001 2C19.4223 2 19.6834 2.26117 19.6834 2.58333V4.91667H22.0168C22.3389 4.91667 22.6001 5.17783 22.6001 5.5C22.6001 5.82217 22.3389 6.08333 22.0168 6.08333H19.6834V8.41667C19.6834 8.73883 19.4223 9 19.1001 9ZM19.6001 11C20.0669 11 20.5212 10.9467 20.9574 10.8458C21.1161 11.5383 21.2 12.2594 21.2 13C21.2 16.1409 19.6917 18.9294 17.3598 20.6808V20.6807C16.0014 21.7011 14.3635 22.3695 12.5815 22.5505C12.2588 22.5832 11.9314 22.6 11.6 22.6C6.29807 22.6 2 18.302 2 13C2 7.69809 6.29807 3.40002 11.6 3.40002C12.3407 3.40002 13.0618 3.48391 13.7543 3.64268C13.6534 4.07884 13.6001 4.53319 13.6001 5C13.6001 8.31371 16.2864 11 19.6001 11ZM11.5999 20.68C13.6754 20.68 15.5585 19.8567 16.9407 18.5189C16.0859 16.4086 14.0167 14.92 11.5998 14.92C9.18298 14.92 7.11378 16.4086 6.25901 18.5189C7.64115 19.8567 9.52436 20.68 11.5999 20.68ZM11.7426 7.41172C10.3168 7.54168 9.2 8.74043 9.2 10.2C9.2 11.7464 10.4536 13 12 13C13.0308 13 13.9315 12.443 14.4176 11.6135C13.0673 10.6058 12.0929 9.12248 11.7426 7.41172Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M19.1001 9C18.7779 9 18.5168 8.73883 18.5168 8.41667V6.08333H16.1834C15.8613 6.08333 15.6001 5.82217 15.6001 5.5C15.6001 5.17783 15.8613 4.91667 16.1834 4.91667H18.5168V2.58333C18.5168 2.26117 18.7779 2 19.1001 2C19.4223 2 19.6834 2.26117 19.6834 2.58333V4.91667H22.0168C22.3389 4.91667 22.6001 5.17783 22.6001 5.5C22.6001 5.82217 22.3389 6.08333 22.0168 6.08333H19.6834V8.41667C19.6834 8.73883 19.4223 9 19.1001 9ZM19.6001 11C20.0669 11 20.5212 10.9467 20.9574 10.8458C21.1161 11.5383 21.2 12.2594 21.2 13C21.2 16.1409 19.6917 18.9294 17.3598 20.6808V20.6807C16.0014 21.7011 14.3635 22.3695 12.5815 22.5505C12.2588 22.5832 11.9314 22.6 11.6 22.6C6.29807 22.6 2 18.302 2 13C2 7.69809 6.29807 3.40002 11.6 3.40002C12.3407 3.40002 13.0618 3.48391 13.7543 3.64268C13.6534 4.07884 13.6001 4.53319 13.6001 5C13.6001 8.31371 16.2864 11 19.6001 11ZM11.5999 20.68C13.6754 20.68 15.5585 19.8567 16.9407 18.5189C16.0859 16.4086 14.0167 14.92 11.5998 14.92C9.18298 14.92 7.11378 16.4086 6.25901 18.5189C7.64115 19.8567 9.52436 20.68 11.5999 20.68ZM11.7426 7.41172C10.3168 7.54168 9.2 8.74043 9.2 10.2C9.2 11.7464 10.4536 13 12 13C13.0308 13 13.9315 12.443 14.4176 11.6135C13.0673 10.6058 12.0929 9.12248 11.7426 7.41172Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

@@ -1,5 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.59 15C7.53 15.16 7.5 15.32 7.5 15.5C7.5 16.33 8.17 17 9 17C9.83 17 10.5 16.33 10.5 15.5C10.5 15.32 10.46 15.16 10.41 15H7.59Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.59 15C7.53 15.16 7.5 15.32 7.5 15.5C7.5 16.33 8.17 17 9 17C9.83 17 10.5 16.33 10.5 15.5C10.5 15.32 10.46 15.16 10.41 15H7.59Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14 10.4C14 11.31 14.49 11.9 15.09 12.25C15.5 12.49 16 12.82 16 13.22C16 13.67 15.71 13.99 15.11 13.99H9H2.89C2.29 13.99 2 13.68 2 13.22C2 12.82 2.51 12.49 2.91 12.25C3.5 11.9 4 11.3 4 10.4V7C4 4.24 6.36 2 9 2C11.64 2 14 4.24 14 7V10.4ZM11.2402 6.1226C11.4223 5.9515 11.7099 5.96101 11.8824 6.14162C12.0358 6.31272 12.0358 6.56938 11.9016 6.74048L8.92075 10.685L8.892 10.723C8.62358 11.0462 8.13469 11.0938 7.80875 10.8276C7.7798 10.8132 7.75632 10.7881 7.73417 10.7643C7.72698 10.7566 7.71994 10.749 7.71289 10.7421L6.13071 8.99336C5.93899 8.76522 5.95816 8.42302 6.18823 8.2329C6.37995 8.0618 6.65795 8.0618 6.85926 8.19488L8.21138 9.012L11.2402 6.1226Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M14 10.4C14 11.31 14.49 11.9 15.09 12.25C15.5 12.49 16 12.82 16 13.22C16 13.67 15.71 13.99 15.11 13.99H9H2.89C2.29 13.99 2 13.68 2 13.22C2 12.82 2.51 12.49 2.91 12.25C3.5 11.9 4 11.3 4 10.4V7C4 4.24 6.36 2 9 2C11.64 2 14 4.24 14 7V10.4ZM11.2402 6.1226C11.4223 5.9515 11.7099 5.96101 11.8824 6.14162C12.0358 6.31272 12.0358 6.56938 11.9016 6.74048L8.92075 10.685L8.892 10.723C8.62358 11.0462 8.13469 11.0938 7.80875 10.8276C7.7798 10.8132 7.75632 10.7881 7.73417 10.7643C7.72698 10.7566 7.71994 10.749 7.71289 10.7421L6.13071 8.99336C5.93899 8.76522 5.95816 8.42302 6.18823 8.2329C6.37995 8.0618 6.65795 8.0618 6.85926 8.19488L8.21138 9.012L11.2402 6.1226Z" fill="currentColor"/>
<path d="M9 3C9.55228 3 10 2.55228 10 2C10 1.44772 9.55228 1 9 1C8.44772 1 8 1.44772 8 2C8 2.55228 8.44772 3 9 3Z" fill="black"/> <path d="M9 3C9.55228 3 10 2.55228 10 2C10 1.44772 9.55228 1 9 1C8.44772 1 8 1.44772 8 2C8 2.55228 8.44772 3 9 3Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -1,3 +1,3 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13 7C14.6569 7 16 5.65685 16 4C16 2.34315 14.6569 1 13 1C11.3431 1 10 2.34315 10 4C10 5.65685 11.3431 7 13 7ZM8.9677 2C6.34078 2.01817 4 4.25115 4 6.99989V10.3999C4 11.2999 3.5 11.8999 2.91 12.2499C2.51 12.4899 2 12.8199 2 13.2199C2 13.6799 2.29 13.9899 2.89 13.9899H9H15.11C15.71 13.9899 16 13.6699 16 13.2199C16 12.8199 15.5 12.4899 15.09 12.2499C14.49 11.8999 14 11.3099 14 10.3999V8.38838C13.6784 8.46136 13.3437 8.49989 13 8.49989C10.5147 8.49989 8.5 6.48517 8.5 3.99989C8.5 3.28151 8.66833 2.60245 8.9677 2ZM7.59 15C7.53 15.16 7.5 15.32 7.5 15.5C7.5 16.33 8.17 17 9 17C9.83 17 10.5 16.33 10.5 15.5C10.5 15.32 10.46 15.16 10.41 15H7.59Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M13 7C14.6569 7 16 5.65685 16 4C16 2.34315 14.6569 1 13 1C11.3431 1 10 2.34315 10 4C10 5.65685 11.3431 7 13 7ZM8.9677 2C6.34078 2.01817 4 4.25115 4 6.99989V10.3999C4 11.2999 3.5 11.8999 2.91 12.2499C2.51 12.4899 2 12.8199 2 13.2199C2 13.6799 2.29 13.9899 2.89 13.9899H9H15.11C15.71 13.9899 16 13.6699 16 13.2199C16 12.8199 15.5 12.4899 15.09 12.2499C14.49 11.8999 14 11.3099 14 10.3999V8.38838C13.6784 8.46136 13.3437 8.49989 13 8.49989C10.5147 8.49989 8.5 6.48517 8.5 3.99989C8.5 3.28151 8.66833 2.60245 8.9677 2ZM7.59 15C7.53 15.16 7.5 15.32 7.5 15.5C7.5 16.33 8.17 17 9 17C9.83 17 10.5 16.33 10.5 15.5C10.5 15.32 10.46 15.16 10.41 15H7.59Z" fill="currentColor"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 811 B

After

Width:  |  Height:  |  Size: 818 B

@@ -1,7 +1,7 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.59 15C7.53 15.16 7.5 15.32 7.5 15.5C7.5 16.33 8.17 17 9 17C9.83 17 10.5 16.33 10.5 15.5C10.5 15.32 10.46 15.16 10.41 15H7.59Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M7.59 15C7.53 15.16 7.5 15.32 7.5 15.5C7.5 16.33 8.17 17 9 17C9.83 17 10.5 16.33 10.5 15.5C10.5 15.32 10.46 15.16 10.41 15H7.59Z" fill="currentColor"/>
<path d="M9 3C9.55228 3 10 2.55228 10 2C10 1.44772 9.55228 1 9 1C8.44772 1 8 1.44772 8 2C8 2.55228 8.44772 3 9 3Z" fill="black"/> <path d="M9 3C9.55228 3 10 2.55228 10 2C10 1.44772 9.55228 1 9 1C8.44772 1 8 1.44772 8 2C8 2.55228 8.44772 3 9 3Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.08 10.9698C14.03 10.7998 14 10.6098 14 10.4098V7.00977C14 4.24977 11.64 2.00977 8.99998 2.00977C7.93998 2.00977 6.92998 2.37977 6.09998 2.98977L14.08 10.9698Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M14.08 10.9698C14.03 10.7998 14 10.6098 14 10.4098V7.00977C14 4.24977 11.64 2.00977 8.99998 2.00977C7.93998 2.00977 6.92998 2.37977 6.09998 2.98977L14.08 10.9698Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.38 5.14978C4.14 5.72978 4 6.34978 4 6.99978V10.3998C4 11.2998 3.5 11.8998 2.91 12.2498C2.51 12.4898 2 12.8198 2 13.2198C2 13.6798 2.29 13.9898 2.89 13.9898H9H13.22L4.38 5.14978Z" fill="black"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M4.38 5.14978C4.14 5.72978 4 6.34978 4 6.99978V10.3998C4 11.2998 3.5 11.8998 2.91 12.2498C2.51 12.4898 2 12.8198 2 13.2198C2 13.6798 2.29 13.9898 2.89 13.9898H9H13.22L4.38 5.14978Z" fill="currentColor"/>
<path d="M1 1L17 17" stroke="black" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/> <path d="M1 1L17 17" stroke="currentColor" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1004 B

After

Width:  |  Height:  |  Size: 1.0 KiB

+6 -4
View File
@@ -10,6 +10,7 @@ import { EventType, RoomType, JoinRule, Preset, type Room, RoomEvent } from "mat
import { KnownMembership } from "matrix-js-sdk/src/types"; import { KnownMembership } from "matrix-js-sdk/src/types";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import React, { type JSX, useCallback, useContext, useRef, useState } from "react"; 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 MatrixClientContext from "../../contexts/MatrixClientContext";
import createRoom, { type IOpts } from "../../createRoom"; import createRoom, { type IOpts } from "../../createRoom";
@@ -66,6 +67,7 @@ import MainSplit from "./MainSplit";
import RightPanel from "./RightPanel"; import RightPanel from "./RightPanel";
import SpaceHierarchy, { showRoom } from "./SpaceHierarchy"; import SpaceHierarchy, { showRoom } from "./SpaceHierarchy";
import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks"; import { type RoomPermalinkCreator } from "../../utils/permalinks/Permalinks";
import { Icon as HashVideoIcon } from "../../../res/img/element-icons/roomlist/hash-video.svg";
interface IProps { interface IProps {
space: Room; space: Room;
@@ -117,7 +119,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
<> <>
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_room")} label={_t("action|new_room")}
iconClassName="mx_LegacyRoomList_iconNewRoom" icon={<PlusIcon />}
onClick={async (e): Promise<void> => { onClick={async (e): Promise<void> => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -132,7 +134,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
{videoRoomsEnabled && ( {videoRoomsEnabled && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_video_room")} label={_t("action|new_video_room")}
iconClassName="mx_LegacyRoomList_iconNewVideoRoom" icon={<HashVideoIcon />}
onClick={async (e): Promise<void> => { onClick={async (e): Promise<void> => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -157,7 +159,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
)} )}
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|add_existing_room")} label={_t("action|add_existing_room")}
iconClassName="mx_LegacyRoomList_iconAddExistingRoom" icon={<RoomIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -168,7 +170,7 @@ const SpaceLandingAddButton: React.FC<{ space: Room }> = ({ space }) => {
{canCreateSpace && ( {canCreateSpace && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("room_list|add_space_label")} label={_t("room_list|add_space_label")}
iconClassName="mx_LegacyRoomList_iconPlus" icon={<PlusIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
+19 -10
View File
@@ -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 React, { type JSX, createRef, type ReactNode } from "react";
import { type Room } from "matrix-js-sdk/src/matrix"; 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 { MatrixClientPeg } from "../../MatrixClientPeg";
import defaultDispatcher from "../../dispatcher/dispatcher"; import defaultDispatcher from "../../dispatcher/dispatcher";
@@ -42,7 +49,9 @@ import PosthogTrackers from "../../PosthogTrackers";
import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload"; import { type ViewHomePagePayload } from "../../dispatcher/payloads/ViewHomePagePayload";
import { SDKContext } from "../../contexts/SDKContext"; import { SDKContext } from "../../contexts/SDKContext";
import { shouldShowFeedback } from "../../utils/Feedback"; 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 { interface IProps {
isPanelCollapsed: boolean; isPanelCollapsed: boolean;
@@ -297,7 +306,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
if (this.hasHomePage) { if (this.hasHomePage) {
homeButton = ( homeButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconHome" icon={<HomeSolidIcon />}
label={_t("common|home")} label={_t("common|home")}
onClick={this.onHomeClick} onClick={this.onHomeClick}
/> />
@@ -308,7 +317,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
if (shouldShowFeedback()) { if (shouldShowFeedback()) {
feedbackButton = ( feedbackButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconMessage" icon={<FeedbackIcon />}
label={_t("common|feedback")} label={_t("common|feedback")}
onClick={this.onProvideFeedback} onClick={this.onProvideFeedback}
/> />
@@ -317,7 +326,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
const linkNewDeviceButton = ( const linkNewDeviceButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconQr" icon={<QrCodeIcon />}
label={_t("user_menu|link_new_device")} label={_t("user_menu|link_new_device")}
onClick={(e) => this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })} onClick={(e) => this.onSettingsOpen(e, UserTab.SessionManager, { showMsc4108QrCode: true })}
/> />
@@ -328,24 +337,24 @@ export default class UserMenu extends React.Component<IProps, IState> {
{homeButton} {homeButton}
{linkNewDeviceButton} {linkNewDeviceButton}
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconBell" icon={<NotificationsIcon />}
label={_t("notifications|enable_prompt_toast_title")} label={_t("notifications|enable_prompt_toast_title")}
onClick={(e) => this.onSettingsOpen(e, UserTab.Notifications)} onClick={(e) => this.onSettingsOpen(e, UserTab.Notifications)}
/> />
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconLock" icon={<LockSolidIcon />}
label={_t("room_settings|security|title")} label={_t("room_settings|security|title")}
onClick={(e) => this.onSettingsOpen(e, UserTab.Security)} onClick={(e) => this.onSettingsOpen(e, UserTab.Security)}
/> />
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconSettings" icon={<SettingsSolidIcon />}
label={_t("user_menu|settings")} label={_t("user_menu|settings")}
onClick={(e) => this.onSettingsOpen(e)} onClick={(e) => this.onSettingsOpen(e)}
/> />
{feedbackButton} {feedbackButton}
<IconizedContextMenuOption <IconizedContextMenuOption
className="mx_IconizedContextMenu_option_red" className="mx_IconizedContextMenu_option_red"
iconClassName="mx_UserMenu_iconSignOut" icon={<LeaveIcon />}
label={_t("action|sign_out")} label={_t("action|sign_out")}
onClick={this.onSignOutClick} onClick={this.onSignOutClick}
/> />
@@ -357,7 +366,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
<IconizedContextMenuOptionList> <IconizedContextMenuOptionList>
{homeButton} {homeButton}
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_UserMenu_iconSettings" icon={<SettingsSolidIcon />}
label={_t("common|settings")} label={_t("common|settings")}
onClick={(e) => this.onSettingsOpen(e)} onClick={(e) => this.onSettingsOpen(e)}
/> />
@@ -398,7 +407,7 @@ export default class UserMenu extends React.Component<IProps, IState> {
: _t("user_menu|switch_theme_dark") : _t("user_menu|switch_theme_dark")
} }
> >
<img src={DarkLightModeSvg} role="presentation" alt="" width={16} /> <DarkLightModeSvg width="16px" height="16px" />
</RovingAccessibleButton> </RovingAccessibleButton>
</div> </div>
{topSection} {topSection}
@@ -7,6 +7,7 @@ Please see LICENSE files in the repository root for full details.
*/ */
import React from "react"; import React from "react";
import { LabsIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import Modal from "../../../Modal"; import Modal from "../../../Modal";
import DevtoolsDialog from "../dialogs/DevtoolsDialog"; import DevtoolsDialog from "../dialogs/DevtoolsDialog";
@@ -32,7 +33,7 @@ export const DeveloperToolsOption: React.FC<Props> = ({ onFinished, roomId }) =>
onFinished(); onFinished();
}} }}
label={_t("devtools|title")} label={_t("devtools|title")}
iconClassName="mx_IconizedContextMenu_developerTools" icon={<LabsIcon />}
/> />
); );
}; };
@@ -26,14 +26,7 @@ interface IDeviceContextMenuDeviceProps {
} }
const DeviceContextMenuDevice: React.FC<IDeviceContextMenuDeviceProps> = ({ label, selected, onClick }) => { const DeviceContextMenuDevice: React.FC<IDeviceContextMenuDeviceProps> = ({ label, selected, onClick }) => {
return ( return <IconizedContextMenuRadio label={label} active={selected} onClick={onClick} />;
<IconizedContextMenuRadio
iconClassName="mx_DeviceContextMenu_device_icon"
label={label}
active={selected}
onClick={onClick}
/>
);
}; };
interface IDeviceContextMenuSectionProps { interface IDeviceContextMenuSectionProps {
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, type ReactNode } from "react"; import React, { type JSX, type ReactNode } from "react";
import classNames from "classnames"; import classNames from "classnames";
import { CheckIcon } from "@vector-im/compound-design-tokens/assets/web/icons";
import ContextMenu, { import ContextMenu, {
ChevronFace, ChevronFace,
@@ -33,26 +34,19 @@ interface IOptionListProps {
interface IOptionProps extends React.ComponentProps<typeof MenuItem> { interface IOptionProps extends React.ComponentProps<typeof MenuItem> {
icon?: ReactNode; icon?: ReactNode;
iconClassName?: string;
isDestructive?: boolean; isDestructive?: boolean;
} }
interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> { interface ICheckboxProps extends React.ComponentProps<typeof MenuItemCheckbox> {
iconClassName: string; icon?: ReactNode;
words?: boolean; words?: boolean;
} }
interface IRadioProps extends React.ComponentProps<typeof MenuItemRadio> { interface IRadioProps extends React.ComponentProps<typeof MenuItemRadio> {
iconClassName?: string; icon?: ReactNode;
} }
export const IconizedContextMenuRadio: React.FC<IRadioProps> = ({ export const IconizedContextMenuRadio: React.FC<IRadioProps> = ({ label, icon, active, className, ...props }) => {
label,
iconClassName,
active,
className,
...props
}) => {
return ( return (
<MenuItemRadio <MenuItemRadio
{...props} {...props}
@@ -63,35 +57,28 @@ export const IconizedContextMenuRadio: React.FC<IRadioProps> = ({
active={active} active={active}
label={label} label={label}
> >
{iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />} {icon}
<span className="mx_IconizedContextMenu_label">{label}</span> <span className="mx_IconizedContextMenu_label">{label}</span>
{active && <span className="mx_IconizedContextMenu_icon mx_IconizedContextMenu_checked" />} {active && <CheckIcon className="mx_IconizedContextMenu_checked" />}
</MenuItemRadio> </MenuItemRadio>
); );
}; };
export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
label, label,
iconClassName, icon,
active, active,
className, className,
words, words,
...props ...props
}) => { }) => {
let marker: JSX.Element; let marker: JSX.Element | undefined;
if (words) { if (words) {
marker = ( marker = (
<span className="mx_IconizedContextMenu_activeText">{active ? _t("common|on") : _t("common|off")}</span> <span className="mx_IconizedContextMenu_activeText">{active ? _t("common|on") : _t("common|off")}</span>
); );
} else { } else if (active) {
marker = ( marker = <CheckIcon className="mx_IconizedContextMenu_checked" />;
<span
className={classNames("mx_IconizedContextMenu_icon", {
mx_IconizedContextMenu_checked: active,
mx_IconizedContextMenu_unchecked: !active,
})}
/>
);
} }
return ( return (
@@ -104,7 +91,7 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
active={active} active={active}
label={label} label={label}
> >
<span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} /> {icon}
<span className="mx_IconizedContextMenu_label">{label}</span> <span className="mx_IconizedContextMenu_label">{label}</span>
{marker} {marker}
</MenuItemCheckbox> </MenuItemCheckbox>
@@ -114,7 +101,6 @@ export const IconizedContextMenuCheckbox: React.FC<ICheckboxProps> = ({
export const IconizedContextMenuOption: React.FC<IOptionProps> = ({ export const IconizedContextMenuOption: React.FC<IOptionProps> = ({
label, label,
className, className,
iconClassName,
icon, icon,
children, children,
isDestructive, isDestructive,
@@ -130,7 +116,6 @@ export const IconizedContextMenuOption: React.FC<IOptionProps> = ({
})} })}
label={label} label={label}
> >
{iconClassName && <span className={classNames("mx_IconizedContextMenu_icon", iconClassName)} />}
{icon} {icon}
<span className="mx_IconizedContextMenu_label">{label}</span> <span className="mx_IconizedContextMenu_label">{label}</span>
{children} {children}
@@ -20,6 +20,27 @@ import {
Thread, Thread,
M_POLL_START, M_POLL_START,
} from "matrix-js-sdk/src/matrix"; } 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 { MatrixClientPeg } from "../../../MatrixClientPeg";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@@ -53,6 +74,8 @@ import { type ShowThreadPayload } from "../../../dispatcher/payloads/ShowThreadP
import { CardContext } from "../right_panel/context"; import { CardContext } from "../right_panel/context";
import PinningUtils from "../../../utils/PinningUtils"; import PinningUtils from "../../../utils/PinningUtils";
import PosthogTrackers from "../../../PosthogTrackers.ts"; 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 { interface IReplyInThreadButton {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@@ -86,13 +109,7 @@ const ReplyInThreadButton: React.FC<IReplyInThreadButton> = ({ mxEvent, closeMen
closeMenu(); closeMenu();
}; };
return ( return <IconizedContextMenuOption icon={<ThreadsIcon />} label={_t("action|reply_in_thread")} onClick={onClick} />;
<IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconReplyInThread"
label={_t("action|reply_in_thread")}
onClick={onClick}
/>
);
}; };
interface IProps extends MenuProps { interface IProps extends MenuProps {
@@ -413,7 +430,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) { if (!mxEvent.isRedacted() && unsentReactionsCount !== 0) {
resendReactionsButton = ( resendReactionsButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconResend" icon={<RestartIcon />}
label={_t("timeline|context_menu|resent_unsent_reactions", { unsentCount: unsentReactionsCount })} label={_t("timeline|context_menu|resent_unsent_reactions", { unsentCount: unsentReactionsCount })}
onClick={this.onResendReactionsClick} onClick={this.onResendReactionsClick}
/> />
@@ -424,7 +441,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (isSent && this.state.canRedact) { if (isSent && this.state.canRedact) {
redactButton = ( redactButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconRedact" icon={<DeleteIcon />}
label={_t("action|remove")} label={_t("action|remove")}
onClick={this.onRedactClick} onClick={this.onRedactClick}
/> />
@@ -437,7 +454,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
const mapSiteLink = createMapSiteLinkFromEvent(shareableLocationEvent); const mapSiteLink = createMapSiteLinkFromEvent(shareableLocationEvent);
openInMapSiteButton = ( openInMapSiteButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconOpenInMapSite" icon={<PopOutIcon />}
onClick={null} onClick={null}
label={_t("timeline|context_menu|open_in_osm")} label={_t("timeline|context_menu|open_in_osm")}
element="a" element="a"
@@ -455,7 +472,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (contentActionable && forwardableEvent) { if (contentActionable && forwardableEvent) {
forwardButton = ( forwardButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconForward" icon={<ForwardIcon />}
label={_t("action|forward")} label={_t("action|forward")}
onClick={this.onForwardClick(forwardableEvent)} onClick={this.onForwardClick(forwardableEvent)}
/> />
@@ -465,7 +482,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
// This is specifically not behind the developerMode flag to give people insight into the Matrix // This is specifically not behind the developerMode flag to give people insight into the Matrix
const viewSourceButton = ( const viewSourceButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconSource" icon={<InlineCodeIcon />}
label={_t("timeline|context_menu|view_source")} label={_t("timeline|context_menu|view_source")}
onClick={this.onViewSourceClick} onClick={this.onViewSourceClick}
/> />
@@ -475,7 +492,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (eventTileOps?.isWidgetHidden()) { if (eventTileOps?.isWidgetHidden()) {
unhidePreviewButton = ( unhidePreviewButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconUnhidePreview" icon={<VisibilityOnIcon />}
label={_t("timeline|context_menu|show_url_preview")} label={_t("timeline|context_menu|show_url_preview")}
onClick={this.onUnhidePreviewClick} onClick={this.onUnhidePreviewClick}
/> />
@@ -486,7 +503,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (permalink) { if (permalink) {
permalinkButton = ( permalinkButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconPermalink" icon={<ShareIcon />}
onClick={this.onShareClick} onClick={this.onShareClick}
label={_t("action|share")} label={_t("action|share")}
element="a" element="a"
@@ -506,7 +523,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (this.canEndPoll(mxEvent)) { if (this.canEndPoll(mxEvent)) {
endPollButton = ( endPollButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconEndPoll" icon={<CheckIcon />}
label={_t("poll|end_title")} label={_t("poll|end_title")}
onClick={this.onEndPollClick} onClick={this.onEndPollClick}
/> />
@@ -521,7 +538,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
) { ) {
externalURLButton = ( externalURLButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconLink" icon={<LinkIcon />}
onClick={this.closeMenu} onClick={this.closeMenu}
label={_t("timeline|context_menu|external_url")} label={_t("timeline|context_menu|external_url")}
element="a" element="a"
@@ -541,7 +558,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (collapseReplyChain) { if (collapseReplyChain) {
collapseReplyChainButton = ( collapseReplyChainButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconCollapse" icon={<ChevronUpIcon />}
label={_t("timeline|context_menu|collapse_reply_thread")} label={_t("timeline|context_menu|collapse_reply_thread")}
onClick={this.onCollapseReplyChainClick} onClick={this.onCollapseReplyChainClick}
/> />
@@ -553,7 +570,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (relatedEventId && SettingsStore.getValue("developerMode")) { if (relatedEventId && SettingsStore.getValue("developerMode")) {
jumpToRelatedEventButton = ( jumpToRelatedEventButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_jumpToEvent" icon={<ChildRelationshipIcon />}
label={_t("timeline|context_menu|view_related_event")} label={_t("timeline|context_menu|view_related_event")}
onClick={() => this.onJumpToRelatedEventClick(relatedEventId)} onClick={() => this.onJumpToRelatedEventClick(relatedEventId)}
/> />
@@ -564,7 +581,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (mxEvent.getSender() !== me) { if (mxEvent.getSender() !== me) {
reportEventButton = ( reportEventButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconReport" icon={<ErrorSolidIcon />}
label={_t("timeline|context_menu|report")} label={_t("timeline|context_menu|report")}
onClick={this.onReportEventClick} onClick={this.onReportEventClick}
/> />
@@ -575,7 +592,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (link) { if (link) {
copyLinkButton = ( copyLinkButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconCopy" icon={<CopyIcon />}
onClick={this.onCopyLinkClick} onClick={this.onCopyLinkClick}
label={_t("action|copy_link")} label={_t("action|copy_link")}
element="a" element="a"
@@ -597,7 +614,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (rightClick && selectedText) { if (rightClick && selectedText) {
copyButton = ( copyButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconCopy" icon={<CopyIcon />}
label={_t("action|copy")} label={_t("action|copy")}
triggerOnMouseDown={true} // We use onMouseDown so that the selection isn't cleared when we click triggerOnMouseDown={true} // We use onMouseDown so that the selection isn't cleared when we click
onClick={this.onCopyClick} onClick={this.onCopyClick}
@@ -609,7 +626,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (rightClick && selectedText && selectedText.trim().length > 0 && this.isSelectionWithinSingleTextBody()) { if (rightClick && selectedText && selectedText.trim().length > 0 && this.isSelectionWithinSingleTextBody()) {
quoteButton = ( quoteButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconQuote" icon={<QuoteIcon />}
label={_t("action|quote")} label={_t("action|quote")}
triggerOnMouseDown={true} triggerOnMouseDown={true}
onClick={this.onQuoteClick} onClick={this.onQuoteClick}
@@ -620,11 +637,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
let editButton: JSX.Element | undefined; let editButton: JSX.Element | undefined;
if (rightClick && canEditContent(cli, mxEvent)) { if (rightClick && canEditContent(cli, mxEvent)) {
editButton = ( editButton = (
<IconizedContextMenuOption <IconizedContextMenuOption icon={<EditIcon />} label={_t("action|edit")} onClick={this.onEditClick} />
iconClassName="mx_MessageContextMenu_iconEdit"
label={_t("action|edit")}
onClick={this.onEditClick}
/>
); );
} }
@@ -632,7 +645,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (rightClick && contentActionable && canSendMessages) { if (rightClick && contentActionable && canSendMessages) {
replyButton = ( replyButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconReply" icon={<ReplyIcon />}
label={_t("action|reply")} label={_t("action|reply")}
onClick={this.onReplyClick} onClick={this.onReplyClick}
/> />
@@ -654,7 +667,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (rightClick && contentActionable && canReact) { if (rightClick && contentActionable && canReact) {
reactButton = ( reactButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconReact" icon={<ReactionAddIcon />}
label={_t("action|react")} label={_t("action|react")}
onClick={this.onReactClick} onClick={this.onReactClick}
inputRef={this.reactButtonRef} inputRef={this.reactButtonRef}
@@ -667,7 +680,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
const isPinned = PinningUtils.isPinned(MatrixClientPeg.safeGet(), this.props.mxEvent); const isPinned = PinningUtils.isPinned(MatrixClientPeg.safeGet(), this.props.mxEvent);
pinButton = ( pinButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName={isPinned ? "mx_MessageContextMenu_iconUnpin" : "mx_MessageContextMenu_iconPin"} icon={isPinned ? <UnpinIcon /> : <PinIcon />}
label={isPinned ? _t("action|unpin") : _t("action|pin")} label={isPinned ? _t("action|unpin") : _t("action|pin")}
onClick={() => this.onPinClick(isPinned)} onClick={() => this.onPinClick(isPinned)}
/> />
@@ -678,7 +691,7 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
if (isThreadRootEvent) { if (isThreadRootEvent) {
viewInRoomButton = ( viewInRoomButton = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_MessageContextMenu_iconViewInRoom" icon={<ViewInRoomIcon />}
label={_t("timeline|mab|view_in_room")} label={_t("timeline|mab|view_in_room")}
onClick={this.viewInRoom} onClick={this.viewInRoom}
/> />
@@ -9,6 +9,15 @@ Please see LICENSE files in the repository root for full details.
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { type Room } from "matrix-js-sdk/src/matrix"; import { type Room } from "matrix-js-sdk/src/matrix";
import React, { type JSX, useContext } from "react"; 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 { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import RoomListActions from "../../../actions/RoomListActions"; import RoomListActions from "../../../actions/RoomListActions";
@@ -34,6 +43,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent
import { UIComponent } from "../../../settings/UIFeature"; import { UIComponent } from "../../../settings/UIFeature";
import { DeveloperToolsOption } from "./DeveloperToolsOption"; import { DeveloperToolsOption } from "./DeveloperToolsOption";
import { useSettingValue } from "../../../hooks/useSettings"; import { useSettingValue } from "../../../hooks/useSettings";
import { Icon as InviteIcon } from "../../../../res/img/element-icons/room/invite.svg";
export interface RoomGeneralContextMenuProps extends IContextMenuProps { export interface RoomGeneralContextMenuProps extends IContextMenuProps {
room: Room; room: Room;
@@ -153,7 +163,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onClick={wrapHandler((ev) => onTagRoom(ev, DefaultTagID.Favourite), onPostFavoriteClick, true)} onClick={wrapHandler((ev) => onTagRoom(ev, DefaultTagID.Favourite), onPostFavoriteClick, true)}
active={isFavorite} active={isFavorite}
label={isFavorite ? _t("room|context_menu|unfavourite") : _t("room|context_menu|favourite")} label={isFavorite ? _t("room|context_menu|unfavourite") : _t("room|context_menu|favourite")}
iconClassName="mx_RoomGeneralContextMenu_iconStar" icon={<FavouriteSolidIcon />}
/> />
); );
@@ -163,7 +173,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onClick={wrapHandler((ev) => onTagRoom(ev, DefaultTagID.LowPriority), onPostLowPriorityClick, true)} onClick={wrapHandler((ev) => onTagRoom(ev, DefaultTagID.LowPriority), onPostLowPriorityClick, true)}
active={isLowPriority} active={isLowPriority}
label={_t("room|context_menu|low_priority")} label={_t("room|context_menu|low_priority")}
iconClassName="mx_RoomGeneralContextMenu_iconArrowDown" icon={<ArrowDownIcon />}
/> />
); );
@@ -180,7 +190,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onPostInviteClick, onPostInviteClick,
)} )}
label={_t("action|invite")} label={_t("action|invite")}
iconClassName="mx_RoomGeneralContextMenu_iconInvite" icon={<InviteIcon />}
/> />
); );
} }
@@ -198,7 +208,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onPostCopyLinkClick, onPostCopyLinkClick,
)} )}
label={_t("room|context_menu|copy_link")} label={_t("room|context_menu|copy_link")}
iconClassName="mx_RoomGeneralContextMenu_iconCopyLink" icon={<LinkIcon />}
/> />
); );
} }
@@ -214,7 +224,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onPostSettingsClick, onPostSettingsClick,
)} )}
label={_t("common|settings")} label={_t("common|settings")}
iconClassName="mx_RoomGeneralContextMenu_iconSettings" icon={<SettingsSolidIcon />}
/> />
); );
@@ -222,7 +232,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
if (roomTags.includes(DefaultTagID.Archived)) { if (roomTags.includes(DefaultTagID.Archived)) {
leaveOption = ( leaveOption = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_RoomGeneralContextMenu_iconSignOut" icon={<LeaveIcon />}
label={_t("room|context_menu|forget")} label={_t("room|context_menu|forget")}
className="mx_IconizedContextMenu_option_red" className="mx_IconizedContextMenu_option_red"
onClick={wrapHandler( onClick={wrapHandler(
@@ -248,7 +258,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
)} )}
label={_t("action|leave")} label={_t("action|leave")}
className="mx_IconizedContextMenu_option_red" className="mx_IconizedContextMenu_option_red"
iconClassName="mx_RoomGeneralContextMenu_iconSignOut" icon={<LeaveIcon />}
/> />
); );
} }
@@ -263,7 +273,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onFinished?.(); onFinished?.();
}, onPostMarkAsReadClick)} }, onPostMarkAsReadClick)}
label={_t("room|context_menu|mark_read")} label={_t("room|context_menu|mark_read")}
iconClassName="mx_RoomGeneralContextMenu_iconMarkAsRead" icon={<MarkAsReadIcon />}
/> />
); );
} else if (!roomTags.includes(DefaultTagID.Archived)) { } else if (!roomTags.includes(DefaultTagID.Archived)) {
@@ -274,7 +284,7 @@ export const RoomGeneralContextMenu: React.FC<RoomGeneralContextMenuProps> = ({
onFinished?.(); onFinished?.();
}, onPostMarkAsUnreadClick)} }, onPostMarkAsUnreadClick)}
label={_t("room|context_menu|mark_unread")} label={_t("room|context_menu|mark_unread")}
iconClassName="mx_RoomGeneralContextMenu_iconMarkAsUnread" icon={<MarkAsUnreadIcon />}
/> />
); );
} else { } else {
@@ -20,6 +20,10 @@ import IconizedContextMenu, {
IconizedContextMenuRadio, IconizedContextMenuRadio,
} from "../context_menus/IconizedContextMenu"; } from "../context_menus/IconizedContextMenu";
import { type ButtonEvent } from "../elements/AccessibleButton"; 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 { interface IProps extends IContextMenuProps {
room: Room; room: Room;
@@ -46,7 +50,7 @@ export const RoomNotificationContextMenu: React.FC<IProps> = ({ room, onFinished
<IconizedContextMenuRadio <IconizedContextMenuRadio
label={_t("room|context_menu|notifications_default")} label={_t("room|context_menu|notifications_default")}
active={notificationState === RoomNotifState.AllMessages} active={notificationState === RoomNotifState.AllMessages}
iconClassName="mx_RoomNotificationContextMenu_iconBell" icon={<NotificationsIcon />}
onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessages))} onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessages))}
/> />
); );
@@ -55,7 +59,7 @@ export const RoomNotificationContextMenu: React.FC<IProps> = ({ room, onFinished
<IconizedContextMenuRadio <IconizedContextMenuRadio
label={_t("notifications|all_messages")} label={_t("notifications|all_messages")}
active={notificationState === RoomNotifState.AllMessagesLoud} active={notificationState === RoomNotifState.AllMessagesLoud}
iconClassName="mx_RoomNotificationContextMenu_iconBellDot" icon={<NotificationsDefaultIcon />}
onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessagesLoud))} onClick={wrapHandler(() => setNotificationState(RoomNotifState.AllMessagesLoud))}
/> />
); );
@@ -64,7 +68,7 @@ export const RoomNotificationContextMenu: React.FC<IProps> = ({ room, onFinished
<IconizedContextMenuRadio <IconizedContextMenuRadio
label={_t("notifications|mentions_keywords")} label={_t("notifications|mentions_keywords")}
active={notificationState === RoomNotifState.MentionsOnly} active={notificationState === RoomNotifState.MentionsOnly}
iconClassName="mx_RoomNotificationContextMenu_iconBellMentions" icon={<NotificationsDmIcon />}
onClick={wrapHandler(() => setNotificationState(RoomNotifState.MentionsOnly))} onClick={wrapHandler(() => setNotificationState(RoomNotifState.MentionsOnly))}
/> />
); );
@@ -73,7 +77,7 @@ export const RoomNotificationContextMenu: React.FC<IProps> = ({ room, onFinished
<IconizedContextMenuRadio <IconizedContextMenuRadio
label={_t("room|context_menu|notifications_mute")} label={_t("room|context_menu|notifications_mute")}
active={notificationState === RoomNotifState.Mute} active={notificationState === RoomNotifState.Mute}
iconClassName="mx_RoomNotificationContextMenu_iconBellCrossed" icon={<NotificationsOffIcon />}
onClick={wrapHandler(() => setNotificationState(RoomNotifState.Mute))} onClick={wrapHandler(() => setNotificationState(RoomNotifState.Mute))}
/> />
); );
@@ -8,6 +8,14 @@ Please see LICENSE files in the repository root for full details.
import React, { type JSX, useContext } from "react"; import React, { type JSX, useContext } from "react";
import { type Room, EventType, RoomType } from "matrix-js-sdk/src/matrix"; 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 { type IProps as IContextMenuProps } from "../../structures/ContextMenu";
import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu"; import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOptionList } from "./IconizedContextMenu";
@@ -32,6 +40,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent
import { UIComponent } from "../../../settings/UIFeature"; import { UIComponent } from "../../../settings/UIFeature";
import PosthogTrackers from "../../../PosthogTrackers"; import PosthogTrackers from "../../../PosthogTrackers";
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Icon as InviteIcon } from "../../../../res/img/element-icons/room/invite.svg";
interface IProps extends IContextMenuProps { interface IProps extends IContextMenuProps {
space?: Room; space?: Room;
@@ -60,7 +69,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
<IconizedContextMenuOption <IconizedContextMenuOption
data-testid="invite-option" data-testid="invite-option"
className="mx_SpacePanel_contextMenu_inviteButton" className="mx_SpacePanel_contextMenu_inviteButton"
iconClassName="mx_SpacePanel_iconInvite" icon={<InviteIcon />}
label={_t("action|invite")} label={_t("action|invite")}
onClick={onInviteClick} onClick={onInviteClick}
/> />
@@ -81,7 +90,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
settingsOption = ( settingsOption = (
<IconizedContextMenuOption <IconizedContextMenuOption
data-testid="settings-option" data-testid="settings-option"
iconClassName="mx_SpacePanel_iconSettings" icon={<SettingsSolidIcon />}
label={_t("common|settings")} label={_t("common|settings")}
onClick={onSettingsClick} onClick={onSettingsClick}
/> />
@@ -98,7 +107,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
leaveOption = ( leaveOption = (
<IconizedContextMenuOption <IconizedContextMenuOption
data-testid="leave-option" data-testid="leave-option"
iconClassName="mx_SpacePanel_iconLeave" icon={<LeaveIcon />}
className="mx_IconizedContextMenu_option_red" className="mx_IconizedContextMenu_option_red"
label={_t("space|leave_dialog_action")} label={_t("space|leave_dialog_action")}
onClick={onLeaveClick} onClick={onLeaveClick}
@@ -123,7 +132,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
devtoolsOption = ( devtoolsOption = (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconSettings" icon={<SettingsSolidIcon />}
label={_t("space|context_menu|devtools_open_timeline")} label={_t("space|context_menu|devtools_open_timeline")}
onClick={onViewTimelineClick} onClick={onViewTimelineClick}
/> />
@@ -170,7 +179,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
{canAddRooms && ( {canAddRooms && (
<IconizedContextMenuOption <IconizedContextMenuOption
data-testid="new-room-option" data-testid="new-room-option"
iconClassName="mx_SpacePanel_iconPlus" icon={<PlusIcon />}
label={_t("common|room")} label={_t("common|room")}
onClick={onNewRoomClick} onClick={onNewRoomClick}
/> />
@@ -178,7 +187,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
{canAddVideoRooms && ( {canAddVideoRooms && (
<IconizedContextMenuOption <IconizedContextMenuOption
data-testid="new-video-room-option" data-testid="new-video-room-option"
iconClassName="mx_SpacePanel_iconPlus" icon={<PlusIcon />}
label={_t("common|video_room")} label={_t("common|video_room")}
onClick={onNewVideoRoomClick} onClick={onNewVideoRoomClick}
> >
@@ -188,7 +197,7 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
{canAddSubSpaces && ( {canAddSubSpaces && (
<IconizedContextMenuOption <IconizedContextMenuOption
data-testid="new-subspace-option" data-testid="new-subspace-option"
iconClassName="mx_SpacePanel_iconPlus" icon={<PlusIcon />}
label={_t("common|space")} label={_t("common|space")}
onClick={onNewSubspaceClick} onClick={onNewSubspaceClick}
> >
@@ -234,18 +243,18 @@ const SpaceContextMenu: React.FC<IProps> = ({ space, hideHeader, onFinished, ...
{!hideHeader && <div className="mx_SpacePanel_contextMenu_header">{space.name}</div>} {!hideHeader && <div className="mx_SpacePanel_contextMenu_header">{space.name}</div>}
<IconizedContextMenuOptionList first> <IconizedContextMenuOptionList first>
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconHome" icon={<HomeSolidIcon />}
label={_t("space|context_menu|home")} label={_t("space|context_menu|home")}
onClick={onHomeClick} onClick={onHomeClick}
/> />
{inviteOption} {inviteOption}
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconExplore" icon={<SearchIcon />}
label={canAddRooms ? _t("space|context_menu|manage_and_explore") : _t("space|context_menu|explore")} label={canAddRooms ? _t("space|context_menu|manage_and_explore") : _t("space|context_menu|explore")}
onClick={onExploreRoomsClick} onClick={onExploreRoomsClick}
/> />
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_SpacePanel_iconPreferences" icon={<PreferencesIcon />}
label={_t("common|preferences")} label={_t("common|preferences")}
onClick={onPreferencesClick} onClick={onPreferencesClick}
/> />
@@ -8,6 +8,7 @@ Please see LICENSE files in the repository root for full details.
import React, { useCallback, useEffect } from "react"; import React, { useCallback, useEffect } from "react";
import { type MatrixEvent } from "matrix-js-sdk/src/matrix"; 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 { type ButtonEvent } from "../elements/AccessibleButton";
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
@@ -20,6 +21,7 @@ import IconizedContextMenu, { IconizedContextMenuOption, IconizedContextMenuOpti
import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import { WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore";
import { MatrixClientPeg } from "../../../MatrixClientPeg"; import { MatrixClientPeg } from "../../../MatrixClientPeg";
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload"; import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
import { Icon as ViewInRoomIcon } from "../../../../res/img/element-icons/view-in-room.svg";
export interface ThreadListContextMenuProps { export interface ThreadListContextMenuProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@@ -102,7 +104,7 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
<IconizedContextMenuOption <IconizedContextMenuOption
onClick={(e) => viewInRoom(e)} onClick={(e) => viewInRoom(e)}
label={_t("timeline|mab|view_in_room")} label={_t("timeline|mab|view_in_room")}
iconClassName="mx_ThreadPanel_viewInRoom" icon={<ViewInRoomIcon />}
/> />
)} )}
{permalinkCreator && ( {permalinkCreator && (
@@ -110,7 +112,7 @@ const ThreadListContextMenu: React.FC<ThreadListContextMenuProps> = ({
data-testid="copy-thread-link" data-testid="copy-thread-link"
onClick={(e) => copyLinkToThread(e)} onClick={(e) => copyLinkToThread(e)}
label={_t("timeline|mab|copy_link_thread")} label={_t("timeline|mab|copy_link_thread")}
iconClassName="mx_ThreadPanel_copyLinkToThread" icon={<LinkIcon />}
/> />
)} )}
</IconizedContextMenuOptionList> </IconizedContextMenuOptionList>
@@ -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. 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 { 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 { ContextMenuTooltipButton } from "../../../../accessibility/context_menu/ContextMenuTooltipButton";
import { useNotificationState } from "../../../../hooks/useRoomNotificationState"; import { useNotificationState } from "../../../../hooks/useRoomNotificationState";
@@ -21,11 +21,25 @@ import { type ButtonEvent } from "../../elements/AccessibleButton";
import { contextMenuBelow } from "../../rooms/RoomTile"; import { contextMenuBelow } from "../../rooms/RoomTile";
import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents"; import { shouldShowComponent } from "../../../../customisations/helpers/UIComponents";
import { UIComponent } from "../../../../settings/UIFeature"; 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 { interface Props {
room: Room; room: Room;
} }
export function getNotificationIcon(state: RoomNotifState): ReactNode {
const icons: Record<RoomNotifState, JSX.Element> = {
[RoomNotifState.AllMessages]: <NotificationsIcon />,
[RoomNotifState.AllMessagesLoud]: <NotificationsDefaultIcon />,
[RoomNotifState.MentionsOnly]: <NotificationsDmIcon />,
[RoomNotifState.Mute]: <NotificationsOffIcon />,
};
return icons[state];
}
export function RoomResultContextMenus({ room }: Props): JSX.Element { export function RoomResultContextMenus({ room }: Props): JSX.Element {
const [notificationState] = useNotificationState(room); 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 ( return (
<Fragment> <Fragment>
{shouldShowComponent(UIComponent.RoomOptionsMenu) && ( {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")} title={room.isSpaceRoom() ? _t("space|context_menu|options") : _t("room|context_menu|title")}
isExpanded={generalMenuPosition !== null} isExpanded={generalMenuPosition !== null}
/> >
<OverflowHorizontalIcon />
</ContextMenuTooltipButton>
)} )}
{!room.isSpaceRoom() && ( {!room.isSpaceRoom() && (
<ContextMenuTooltipButton <ContextMenuTooltipButton
className={notificationMenuClasses} className="mx_SpotlightDialog_option--notifications"
onClick={(ev: ButtonEvent) => { onClick={(ev: ButtonEvent) => {
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
@@ -100,7 +108,9 @@ export function RoomResultContextMenus({ room }: Props): JSX.Element {
}} }}
title={_t("room_list|notification_options")} title={_t("room_list|notification_options")}
isExpanded={notificationMenuPosition !== null} isExpanded={notificationMenuPosition !== null}
/> >
{getNotificationIcon(notificationState!)}
</ContextMenuTooltipButton>
)} )}
{generalMenu} {generalMenu}
{notificationMenu} {notificationMenu}
+17 -9
View File
@@ -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 { EventType, type Room, RoomType } from "matrix-js-sdk/src/matrix";
import React, { type JSX, type ComponentType, createRef, type ReactComponentElement, type SyntheticEvent } from "react"; 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 { type IState as IRovingTabIndexState, RovingTabIndexProvider } from "../../../accessibility/RovingTabIndex.tsx";
import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx"; import MatrixClientContext from "../../../contexts/MatrixClientContext.tsx";
@@ -67,6 +74,7 @@ import { getKeyBindingsManager } from "../../../KeyBindingsManager.ts";
import AccessibleButton from "../elements/AccessibleButton.tsx"; import AccessibleButton from "../elements/AccessibleButton.tsx";
import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation.ts"; import { Landmark, LandmarkNavigation } from "../../../accessibility/LandmarkNavigation.ts";
import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx"; import LegacyCallHandler, { LegacyCallHandlerEvent } from "../../../LegacyCallHandler.tsx";
import { Icon as HashVideoIcon } from "../../../../res/img/element-icons/roomlist/hash-video.svg";
interface IProps { interface IProps {
onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void; onKeyDown: (ev: React.KeyboardEvent, state: IRovingTabIndexState) => void;
@@ -142,7 +150,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
{showCreateRooms && ( {showCreateRooms && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|start_new_chat")} label={_t("action|start_new_chat")}
iconClassName="mx_LegacyRoomList_iconStartChat" icon={<UserAddSolidIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -158,7 +166,7 @@ const DmAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex, dispatcher = default
{showInviteUsers && ( {showInviteUsers && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|invite_to_space")} label={_t("action|invite_to_space")}
iconClassName="mx_LegacyRoomList_iconInvite" icon={<ShareIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -230,7 +238,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
<IconizedContextMenuOptionList first> <IconizedContextMenuOptionList first>
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|explore_rooms")} label={_t("action|explore_rooms")}
iconClassName="mx_LegacyRoomList_iconExplore" icon={<SearchIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -247,7 +255,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
<> <>
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_room")} label={_t("action|new_room")}
iconClassName="mx_LegacyRoomList_iconNewRoom" icon={<PlusIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -261,7 +269,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
{videoRoomsEnabled && ( {videoRoomsEnabled && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_video_room")} label={_t("action|new_video_room")}
iconClassName="mx_LegacyRoomList_iconNewVideoRoom" icon={<HashVideoIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -279,7 +287,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
)} )}
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|add_existing_room")} label={_t("action|add_existing_room")}
iconClassName="mx_LegacyRoomList_iconAddExistingRoom" icon={<RoomIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -300,7 +308,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
<> <>
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_room")} label={_t("action|new_room")}
iconClassName="mx_LegacyRoomList_iconNewRoom" icon={<PlusIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -312,7 +320,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
{videoRoomsEnabled && ( {videoRoomsEnabled && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_video_room")} label={_t("action|new_video_room")}
iconClassName="mx_LegacyRoomList_iconNewVideoRoom" icon={<HashVideoIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -333,7 +341,7 @@ const UntaggedAuxButton: React.FC<IAuxButtonProps> = ({ tabIndex }) => {
{showExploreRooms ? ( {showExploreRooms ? (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|explore_public_rooms")} label={_t("action|explore_public_rooms")}
iconClassName="mx_LegacyRoomList_iconExplore" icon={<SearchIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -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 { ClientEvent, EventType, type Room, RoomEvent, RoomType } from "matrix-js-sdk/src/matrix";
import React, { type JSX, useContext, useEffect, useState } from "react"; import React, { type JSX, useContext, useEffect, useState } from "react";
import { Tooltip } from "@vector-im/compound-web"; 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 MatrixClientContext from "../../../contexts/MatrixClientContext";
import { shouldShowComponent } from "../../../customisations/helpers/UIComponents"; import { shouldShowComponent } from "../../../customisations/helpers/UIComponents";
@@ -51,6 +52,8 @@ import IconizedContextMenu, {
import SpaceContextMenu from "../context_menus/SpaceContextMenu"; import SpaceContextMenu from "../context_menus/SpaceContextMenu";
import InlineSpinner from "../elements/InlineSpinner"; import InlineSpinner from "../elements/InlineSpinner";
import { HomeButtonContextMenu } from "../spaces/SpacePanel"; 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 => { const contextMenuBelow = (elementRect: DOMRect): MenuProps => {
// align the context menu's icons with the icon which opened the context menu // align the context menu's icons with the icon which opened the context menu
@@ -178,7 +181,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
inviteOption = ( inviteOption = (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|invite")} label={_t("action|invite")}
iconClassName="mx_LegacyRoomListHeader_iconInvite" icon={<InviteIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -194,7 +197,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
newRoomOptions = ( newRoomOptions = (
<> <>
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_LegacyRoomListHeader_iconNewRoom" icon={<PlusIcon />}
label={_t("action|new_room")} label={_t("action|new_room")}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@@ -206,7 +209,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
/> />
{videoRoomsEnabled && ( {videoRoomsEnabled && (
<IconizedContextMenuOption <IconizedContextMenuOption
iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom" icon={<HashVideoIcon />}
label={_t("action|new_video_room")} label={_t("action|new_video_room")}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
@@ -236,7 +239,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
{newRoomOptions} {newRoomOptions}
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|explore_rooms")} label={_t("action|explore_rooms")}
iconClassName="mx_LegacyRoomListHeader_iconExplore" icon={<SearchIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -251,7 +254,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
/> />
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|add_existing_room")} label={_t("action|add_existing_room")}
iconClassName="mx_LegacyRoomListHeader_iconPlus" icon={<PlusIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -264,7 +267,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
{canCreateSpaces && ( {canCreateSpaces && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("room_list|add_space_label")} label={_t("room_list|add_space_label")}
iconClassName="mx_LegacyRoomListHeader_iconPlus" icon={<PlusIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -289,7 +292,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
<> <>
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|start_new_chat")} label={_t("action|start_new_chat")}
iconClassName="mx_LegacyRoomListHeader_iconStartChat" icon={<UserAddSolidIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -300,7 +303,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
/> />
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_room")} label={_t("action|new_room")}
iconClassName="mx_LegacyRoomListHeader_iconNewRoom" icon={<PlusIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -312,7 +315,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
{videoRoomsEnabled && ( {videoRoomsEnabled && (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("action|new_video_room")} label={_t("action|new_video_room")}
iconClassName="mx_LegacyRoomListHeader_iconNewVideoRoom" icon={<HashVideoIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -333,7 +336,7 @@ const LegacyRoomListHeader: React.FC<IProps> = ({ onVisibilityChange }) => {
joinRoomOpt = ( joinRoomOpt = (
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("room_list|join_public_room_label")} label={_t("room_list|join_public_room_label")}
iconClassName="mx_LegacyRoomListHeader_iconExplore" icon={<SearchIcon />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
+4 -7
View File
@@ -45,6 +45,7 @@ import { shouldShowComponent } from "../../../customisations/helpers/UIComponent
import { UIComponent } from "../../../settings/UIFeature"; import { UIComponent } from "../../../settings/UIFeature";
import { isKnockDenied } from "../../../utils/membership"; import { isKnockDenied } from "../../../utils/membership";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { getNotificationIcon } from "../dialogs/spotlight/RoomResultContextMenus.tsx";
interface Props { interface Props {
room: Room; room: Room;
@@ -293,12 +294,6 @@ class RoomTile extends React.PureComponent<Props, State> {
const state = this.roomProps.notificationVolume; const state = this.roomProps.notificationVolume;
const classes = classNames("mx_RoomTile_notificationsButton", { 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. // Only show the icon by default if the room is overridden to muted.
// TODO: [FTUE Notifications] Probably need to detect global mute state // TODO: [FTUE Notifications] Probably need to detect global mute state
mx_RoomTile_notificationsButton_show: state === RoomNotifState.Mute, mx_RoomTile_notificationsButton_show: state === RoomNotifState.Mute,
@@ -312,7 +307,9 @@ class RoomTile extends React.PureComponent<Props, State> {
title={_t("room_list|notification_options")} title={_t("room_list|notification_options")}
isExpanded={!!this.state.notificationsMenuPosition} isExpanded={!!this.state.notificationsMenuPosition}
tabIndex={isActive ? 0 : -1} tabIndex={isActive ? 0 : -1}
/> >
{getNotificationIcon(state!)}
</ContextMenuTooltipButton>
{this.state.notificationsMenuPosition && ( {this.state.notificationsMenuPosition && (
<RoomNotificationContextMenu <RoomNotificationContextMenu
{...contextMenuBelow(this.state.notificationsMenuPosition)} {...contextMenuBelow(this.state.notificationsMenuPosition)}
@@ -99,7 +99,6 @@ export const HomeButtonContextMenu: React.FC<ComponentProps<typeof SpaceContextM
{!hideHeader && <div className="mx_SpacePanel_contextMenu_header">{_t("common|home")}</div>} {!hideHeader && <div className="mx_SpacePanel_contextMenu_header">{_t("common|home")}</div>}
<IconizedContextMenuOptionList first> <IconizedContextMenuOptionList first>
<IconizedContextMenuCheckbox <IconizedContextMenuCheckbox
iconClassName="mx_SpacePanel_noIcon"
label={_t("settings|sidebar|metaspaces_home_all_rooms")} label={_t("settings|sidebar|metaspaces_home_all_rooms")}
active={allRoomsInHome} active={allRoomsInHome}
onClick={() => { onClick={() => {
@@ -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(
<DeviceContextMenu deviceKinds={["videoinput"]} onFinished={jest.fn()} mountAsChild />,
);
await expect(findByLabelText("Camera 2")).resolves.toBeChecked();
expect(container).toMatchSnapshot();
});
});
@@ -0,0 +1,85 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`DeviceContextMenu renders a menu with the selected device checked 1`] = `
<div>
<div
class="mx_ContextualMenu_wrapper"
>
<div
class="mx_ContextualMenu_background"
/>
<div
class="mx_ContextualMenu"
role="menu"
>
<ul
class="mx_IconizedContextMenu mx_DeviceContextMenu mx_IconizedContextMenu_compact"
role="none"
>
<div
class="mx_IconizedContextMenu_optionList mx_IconizedContextMenu_optionList_notFirst"
>
<div>
<span
class="mx_IconizedContextMenu_optionList_label"
>
Cameras
</span>
</div>
<div
aria-checked="false"
aria-label="Camera 1"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitemradio"
tabindex="0"
>
<span
class="mx_IconizedContextMenu_label"
>
Camera 1
</span>
</div>
<div
aria-checked="true"
aria-label="Camera 2"
class="mx_AccessibleButton mx_IconizedContextMenu_item mx_IconizedContextMenu_active"
role="menuitemradio"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_label"
>
Camera 2
</span>
<svg
class="mx_IconizedContextMenu_checked"
fill="currentColor"
height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.55 17.575q-.2 0-.375-.062a.9.9 0 0 1-.325-.213L4.55 13q-.274-.274-.262-.713.012-.437.287-.712a.95.95 0 0 1 .7-.275q.425 0 .7.275L9.55 15.15l8.475-8.475q.274-.275.713-.275.437 0 .712.275.275.274.275.713 0 .437-.275.712l-9.2 9.2q-.15.15-.325.212a1.1 1.1 0 0 1-.375.063"
/>
</svg>
</div>
<div
aria-checked="false"
aria-label="Camera 3"
class="mx_AccessibleButton mx_IconizedContextMenu_item"
role="menuitemradio"
tabindex="-1"
>
<span
class="mx_IconizedContextMenu_label"
>
Camera 3
</span>
</div>
</div>
</ul>
</div>
</div>
</div>
`;
@@ -32,9 +32,20 @@ exports[`RoomGeneralContextMenu renders an empty context menu for archived rooms
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
<span <svg
class="mx_IconizedContextMenu_icon mx_RoomGeneralContextMenu_iconSignOut" fill="currentColor"
/> height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span <span
class="mx_IconizedContextMenu_label" class="mx_IconizedContextMenu_label"
> >
@@ -80,9 +91,20 @@ exports[`RoomGeneralContextMenu renders the default context menu 1`] = `
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
<span <svg
class="mx_IconizedContextMenu_icon mx_RoomGeneralContextMenu_iconSignOut" fill="currentColor"
/> height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span <span
class="mx_IconizedContextMenu_label" class="mx_IconizedContextMenu_label"
> >
@@ -34,9 +34,17 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
role="menuitem" role="menuitem"
tabindex="0" tabindex="0"
> >
<span <svg
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconHome" fill="currentColor"
/> height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="m12.971 3.54 7 3.889A2 2 0 0 1 21 9.177V19a2 2 0 0 1-2 2h-4v-9H9v9H5a2 2 0 0 1-2-2V9.177a2 2 0 0 1 1.029-1.748l7-3.89a2 2 0 0 1 1.942 0"
/>
</svg>
<span <span
class="mx_IconizedContextMenu_label" class="mx_IconizedContextMenu_label"
> >
@@ -49,9 +57,17 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
<span <svg
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconExplore" fill="currentColor"
/> height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M15.05 16.463a7.5 7.5 0 1 1 1.414-1.414l3.243 3.244a1 1 0 0 1-1.414 1.414zM16 10.5a5.5 5.5 0 1 0-11 0 5.5 5.5 0 0 0 11 0"
/>
</svg>
<span <span
class="mx_IconizedContextMenu_label" class="mx_IconizedContextMenu_label"
> >
@@ -64,9 +80,19 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
<span <svg
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconPreferences" fill="currentColor"
/> height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
clip-rule="evenodd"
d="M6.5 2h11a4.5 4.5 0 1 1 0 9h-11a4.5 4.5 0 0 1 0-9m0 2h7.258A4.5 4.5 0 0 0 13 6.5c0 .925.28 1.785.758 2.5H6.5a2.5 2.5 0 0 1 0-5M15 6.5a2.5 2.5 0 1 1 5 0 2.5 2.5 0 0 1-5 0m-13 11A4.5 4.5 0 0 1 6.5 13h11a4.5 4.5 0 1 1 0 9h-11q-.233 0-.46-.023A4.5 4.5 0 0 1 2 17.5m8.242-2.5H17.5a2.5 2.5 0 0 1 0 5h-7.258A4.5 4.5 0 0 0 11 17.5c0-.925-.28-1.785-.758-2.5M6.5 15a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5"
fill-rule="evenodd"
/>
</svg>
<span <span
class="mx_IconizedContextMenu_label" class="mx_IconizedContextMenu_label"
> >
@@ -80,9 +106,20 @@ exports[`<SpaceContextMenu /> renders menu correctly 1`] = `
role="menuitem" role="menuitem"
tabindex="-1" tabindex="-1"
> >
<span <svg
class="mx_IconizedContextMenu_icon mx_SpacePanel_iconLeave" fill="currentColor"
/> height="1em"
viewBox="0 0 24 24"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M14 13q.424 0 .713-.287A.97.97 0 0 0 15 12a.97.97 0 0 0-.287-.713A.97.97 0 0 0 14 11a.97.97 0 0 0-.713.287A.97.97 0 0 0 13 12q0 .424.287.713.288.287.713.287"
/>
<path
d="M10.385 21.788A1 1 0 0 1 10 21V3a1.003 1.003 0 0 1 1.242-.97l8 2A1 1 0 0 1 20 5v14a1 1 0 0 1-.758.97l-8 2a1 1 0 0 1-.857-.182M18 5.781l-6-1.5v15.438l6-1.5zM9 6H7v12h2v2H7a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2z"
/>
</svg>
<span <span
class="mx_IconizedContextMenu_label" class="mx_IconizedContextMenu_label"
> >