Compare commits

..

17 Commits

Author SHA1 Message Date
Hristo Terezov
2ab86cffb8 feat(draggable-panels): Enable touch-screen support
Adds touch-screen support for resizing filmstrip and chat panels to enable tablet and touch-laptop users to adjust panel widths. Previously, drag handles only worked with mouse hover, making panels non-resizable on touch devices.

Changes:
- Implement Pointer Events API for unified mouse/touch handling
- Add touch device detection with screen size threshold
- Make drag handles always visible on touch devices with padding for easier tapping
- Maintain identical visual layout between touch and non-touch versions

Touch devices with sufficiently large screens now have fully functional drag handles with appropriate hit targets while smaller devices remain disabled to preserve mobile UX.
2026-02-06 09:48:44 -06:00
Mihaela Dumitru
9ea9f4c899 [WIP] feat(ui) add semantic tokens (#16772) 2026-02-06 15:06:54 +02:00
Mihaela Dumitru
28c72bfa7f fix(toolbar): remove hang up from prejoin app 2026-02-06 12:48:29 +01:00
dependabot[bot]
24c78cf8ff chore(deps-dev): bump webpack from 5.95.0 to 5.105.0
Bumps [webpack](https://github.com/webpack/webpack) from 5.95.0 to 5.105.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Changelog](https://github.com/webpack/webpack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack/compare/v5.95.0...v5.105.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-version: 5.105.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-06 08:58:18 +01:00
Stephan Paternotte
147e64106d lang: Dutch translation updates.
* Update main-nl.json

Full update to latest state of main.json including Dutch translation updates.
Validated with JSONLint

* Fixes lint.

---------

Co-authored-by: Дамян Минков <damencho@jitsi.org>
2026-02-05 16:58:54 -06:00
Hristo Terezov
3d53f2e4c5 fix(PiP): Mac OS minimize not working
When we minimize the meeting window on Mac OS via the yellow button, 2 requests for PiP are triggered one after another because of the blur and the visibilitychange handlers. If we implement in Electron to focus the meeting window on the pipLeft event this will lead to the first request to exit triggering the pipLeft event after the second request is triggered which will actually bring the window back because we focus on pipLeft event. This behaviour breaks the ability to minimize a window.

The current fix will prevent us for sending 2 requests for PiP one after another by skipping the second unnecessary request.
2026-02-05 12:36:07 -06:00
damencho
e60bfc573a fix(muc_auth_ban): Adds checks for jaas. 2026-02-04 06:14:30 -06:00
damencho
796a7efa7f fix(tests): Make tests to go through pre-join automatically. 2026-02-03 17:58:28 -06:00
damencho
fb075c376d fix(prejoin): Handles different ways of disabling prejoin.
Now handles using `config.prejoinConfig.enabled=false` and `config.prejoinConfig={enabled:false}.

Fixes #16892.
2026-02-03 17:58:28 -06:00
Mihaela Dumitru
c457ed0d3c feat(external-api): fire participantMuted event for all mute state changes (#16893) 2026-02-03 10:59:28 +02:00
damencho
1efc5e40e1 fix(settings): Fixes rendering a tab when props change.
The Profile tab can display logged in state and auth id used for it. It does not re-render when this changes in the background.
2026-02-02 15:52:19 -06:00
Дамян Минков
dc84826d9c fix(muc_cleanup_backend_services): Stops timer before creating new one. (#16894)
* fix(muc_cleanup_backend_services): Stops timer before creating new one.

* squash: Adds some logs and skip if already being destroyed.
2026-02-02 10:09:26 -06:00
Horatiu Muresan
b4a64ebc44 fix(recording-button) Fix recording button tooltip (#16891) 2026-01-30 13:28:39 +02:00
Hristo Terezov
dbb3ccc274 feat(CustomPanel): Implement an customizable panel.
The panel will appear on the right side after the participant pane panel. Currently the panel is disabled by default and the components that  are rendered in the panel are empty (null).  The panel is easily customizable by adding some content in the CustomPanel component.
2026-01-29 14:59:06 -06:00
bgrozev
9a6ed65cb1 test: Retry some tests (#16888)
* chore: Update wdio to 9.23.2.

* test: Add a retry test property.

* test: Retry some of the flaky tests.
2026-01-29 13:58:57 -06:00
bgrozev
21ea67b29c fix: Only parse transcript for transcription-result messages. (#16885) 2026-01-28 16:23:12 -06:00
bgrozev
ab4be2366f test: Add test for jaas "async" transcriptions. (#16793)
* test: Add test for jaas "async" transcriptions.

* fix: Do not expect name in async transcription events.
2026-01-27 18:56:16 -06:00
167 changed files with 4585 additions and 1346 deletions

View File

@@ -45,7 +45,7 @@ body {
.jitsi-icon {
&-default svg {
fill: white;
fill: var(--icon-default-color, white);
}
}

View File

@@ -1,5 +1,5 @@
.always-on-top-toolbox {
background-color: $newToolbarBackgroundColor;
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
border-radius: 3px;
display: flex;
z-index: $toolbarZ;

View File

@@ -2,7 +2,7 @@
* Round badge.
*/
.badge-round {
background-color: #165ECC;
background-color: var(--toolbar-badge-background, #165ECC);
border-radius: 50%;
box-sizing: border-box;
color: #FFFFFF;
@@ -93,7 +93,7 @@
.toolbox-content-wrapper::after {
content: '';
background: $newToolbarBackgroundColor;
background: var(--toolbox-background-color, $newToolbarBackgroundColor);
padding-bottom: env(safe-area-inset-bottom, 0);
}

View File

@@ -60,7 +60,7 @@
}
#notification-participant-list {
background-color: $newToolbarBackgroundColor;
background-color: var(--toolbox-background-color, $newToolbarBackgroundColor);
border: 1px solid rgba(255, 255, 255, .4);
border-radius: 8px;
left: 0;

File diff suppressed because it is too large Load Diff

View File

@@ -227,6 +227,9 @@
"video_ssrc": "Video SSRC:",
"yes": "yes"
},
"customPanel": {
"close": "Close"
},
"dateUtils": {
"earlier": "Earlier",
"today": "Today",
@@ -1315,6 +1318,7 @@
"chat": "Open / Close chat",
"clap": "Clap",
"closeChat": "Close chat",
"closeCustomPanel": "Close",
"closeMoreActions": "Close more actions menu",
"closeParticipantsPane": "Close participants pane",
"closedCaptions": "Closed captions",
@@ -1420,9 +1424,11 @@
"chat": "Open / Close chat",
"clap": "Clap",
"closeChat": "Close chat",
"closeCustomPanel": "Close",
"closeParticipantsPane": "Close participants pane",
"closeReactionsMenu": "Close reactions menu",
"closedCaptions": "Closed captions",
"copilot": "Copilot",
"disableNoiseSuppression": "Disable extra noise suppression",
"disableReactionSounds": "You can disable reaction sounds for this meeting",
"documentClose": "Close shared document",

View File

@@ -240,13 +240,27 @@ function initCommands() {
APP.store.dispatch(muteAllParticipants(exclude, muteMediaType));
},
'mute-remote-participant': (participantId, mediaType) => {
if (!isLocalParticipantModerator(APP.store.getState())) {
logger.error('Missing moderator rights to mute remote participant');
const state = APP.store.getState();
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
const localParticipant = getLocalParticipant(state);
// Check if targeting the local participant
if (participantId === localParticipant?.id) {
if (muteMediaType === MEDIA_TYPE.AUDIO) {
APP.conference.toggleAudioMuted(false);
} else if (muteMediaType === MEDIA_TYPE.VIDEO) {
APP.conference.toggleVideoMuted(false, true);
}
return;
}
const muteMediaType = mediaType ? mediaType : MEDIA_TYPE.AUDIO;
if (!isLocalParticipantModerator(state)) {
logger.error('Missing moderator rights to mute remote participant');
return;
}
APP.store.dispatch(muteRemote(participantId, muteMediaType));
},
@@ -1417,17 +1431,15 @@ class API {
*
* @param {string} participantId - The ID of the participant.
* @param {boolean} isMuted - True if muted, false if unmuted.
* @param {string} mediaType - Media type that was muted ('audio', 'video', or 'desktop').
* @param {boolean} isSelfMuted - True if participant muted themselves, false if muted by moderator.
* @param {string} mediaType - Media type that was muted ('audio' or 'video').
* @returns {void}
*/
notifyParticipantMuted(participantId, isMuted, mediaType, isSelfMuted = true) {
notifyParticipantMuted(participantId, isMuted, mediaType) {
this._sendEvent({
name: 'participant-muted',
id: participantId,
isMuted,
mediaType,
isSelfMuted
mediaType
});
}

2109
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -164,12 +164,12 @@
"@types/w3c-image-capture": "1.0.6",
"@types/w3c-web-hid": "1.0.3",
"@types/zxcvbn": "4.4.1",
"@wdio/allure-reporter": "9.22.0",
"@wdio/cli": "9.22.0",
"@wdio/globals": "9.17.0",
"@wdio/junit-reporter": "9.21.0",
"@wdio/local-runner": "9.22.0",
"@wdio/mocha-framework": "9.22.0",
"@wdio/allure-reporter": "9.23.2",
"@wdio/cli": "9.23.2",
"@wdio/globals": "9.23.0",
"@wdio/junit-reporter": "9.23.2",
"@wdio/local-runner": "9.23.2",
"@wdio/mocha-framework": "9.23.2",
"babel-loader": "9.1.0",
"babel-plugin-optional-require": "0.3.1",
"circular-dependency-plugin": "5.2.0",
@@ -194,7 +194,7 @@
"typescript": "5.7.2",
"unorm": "1.6.0",
"webdriverio": "9.22.0",
"webpack": "5.95.0",
"webpack": "5.105.0",
"webpack-bundle-analyzer": "4.4.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "5.1.0"

View File

@@ -26,5 +26,6 @@ import '../face-landmarks/middleware';
import '../gifs/middleware';
import '../whiteboard/middleware.web';
import '../file-sharing/middleware.web';
import '../custom-panel/middleware.web';
import './middlewares.any';

View File

@@ -1,5 +1,6 @@
import '../base/devices/reducer';
import '../base/premeeting/reducer';
import '../custom-panel/reducer';
import '../base/tooltip/reducer';
import '../e2ee/reducer';
import '../face-landmarks/reducer';

View File

@@ -31,6 +31,7 @@ import { IUserInteractionState } from '../base/user-interaction/reducer';
import { IBreakoutRoomsState } from '../breakout-rooms/reducer';
import { ICalendarSyncState } from '../calendar-sync/reducer';
import { IChatState } from '../chat/reducer';
import { ICustomPanelState } from '../custom-panel/reducer';
import { IDeepLinkingState } from '../deep-linking/reducer';
import { IDropboxState } from '../dropbox/reducer';
import { IDynamicBrandingState } from '../dynamic-branding/reducer';
@@ -121,6 +122,7 @@ export interface IReduxState {
'features/calendar-sync': ICalendarSyncState;
'features/call-integration': ICallIntegrationState;
'features/chat': IChatState;
'features/custom-panel': ICustomPanelState;
'features/deep-linking': IDeepLinkingState;
'features/dropbox': IDropboxState;
'features/dynamic-branding': IDynamicBrandingState;

View File

@@ -387,7 +387,8 @@ export function setConfigFromURLParams(
// When not in an iframe, start without media if the pre-join page is not enabled.
if (!isEmbedded()
&& 'config.prejoinConfig.enabled' in params && config.prejoinConfig?.enabled === false) {
&& ('config.prejoinConfig' in params || 'config.prejoinConfig.enabled' in params)
&& config.prejoinConfig?.enabled === false) {
logger.warn('Using prejoinConfig.enabled config URL overwrite implies starting without media.');
config.disableInitialGUM = true;
}

View File

@@ -29,4 +29,3 @@ export function isIpadMobileBrowser() {
// @ts-ignore
return isIosMobileBrowser() && Platform.isPad;
}

View File

@@ -0,0 +1 @@
export * from './utils.any';

View File

@@ -0,0 +1,37 @@
import { MIN_FILMSTRIP_RESIZE_WIDTH } from '../../filmstrip/constants';
/**
* Detects if the current device has touch capability.
* This includes smartphones, tablets, and laptops with touch screens.
*
* @returns {boolean} True if the device supports touch events.
*/
export function isTouchDevice(): boolean {
// Check maxTouchPoints (most reliable for modern browsers)
if ('maxTouchPoints' in navigator) {
return navigator.maxTouchPoints > 0;
}
return false;
}
/**
* Determines if resize functionality should be enabled based on device capabilities
* and screen size. On touch devices, resize is only enabled for larger screens.
* On non-touch devices (desktop), resize is always enabled.
*
* @returns {boolean} True if resize functionality should be available to the user.
*/
export function shouldEnableResize(): boolean {
const hasTouch = isTouchDevice();
// On non-touch devices (desktop), always enable resize
if (!hasTouch) {
return true;
}
// On touch devices, only enable if screen is large enough.
return window?.innerWidth >= MIN_FILMSTRIP_RESIZE_WIDTH;
}
export * from './utils.any';

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles-icon lucide-sparkles"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg>

After

Width:  |  Height:  |  Size: 582 B

View File

@@ -1,3 +1,4 @@
import { default as IconAI } from './AI.svg';
import { default as IconRecordAccount } from './account-record.svg';
import { default as IconAddUser } from './add-user.svg';
import { default as IconArrowBack } from './arrow-back.svg';
@@ -112,6 +113,7 @@ import { default as IconYahoo } from './yahoo.svg';
*/
export const DEFAULT_ICON: Record<string, any> = {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,

View File

@@ -4,6 +4,7 @@ import { DEFAULT_ICON } from './constants';
const {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,
@@ -123,6 +124,7 @@ const {
export {
IconAddUser,
IconAI,
IconArrowBack,
IconArrowDown,
IconArrowDownLarge,

View File

@@ -56,9 +56,9 @@ const useStyles = makeStyles()(theme => {
label: {
...theme.typography.labelRegular,
alignItems: 'center',
background: theme.palette.ui04,
background: theme.palette.labelBackground,
borderRadius: '4px',
color: theme.palette.text01,
color: theme.palette.labelText,
display: 'flex',
margin: '0 2px',
padding: '6px',
@@ -72,11 +72,11 @@ const useStyles = makeStyles()(theme => {
cursor: 'pointer'
},
[COLORS.white]: {
background: theme.palette.ui09,
color: theme.palette.text04,
background: theme.palette.labelWhiteBackground,
color: theme.palette.labelWhiteText,
'& svg': {
fill: theme.palette.icon04
fill: theme.palette.labelWhiteIcon
}
},
[COLORS.green]: {

View File

@@ -1,4 +1,5 @@
import { IReduxState, IStore } from '../../app/types';
import { getLocalParticipant } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
/**
@@ -13,6 +14,13 @@ StateListenerRegistry.register(
if (muted !== previousMuted) {
APP.API.notifyAudioMutedStatusChanged(muted);
// Also fire the participantMuted event for consistency
const localParticipant = getLocalParticipant(store.getState());
if (localParticipant) {
APP.API.notifyParticipantMuted(localParticipant.id, muted, 'audio');
}
}
}
);

View File

@@ -84,7 +84,7 @@ const useStyles = makeStyles()(theme => {
...theme.typography.bodyLongBold,
borderRadius: theme.shape.borderRadius,
boxSizing: 'border-box',
color: theme.palette.text01,
color: theme.palette.actionButtonText,
cursor: 'pointer',
display: 'inline-block',
marginBottom: '16px',
@@ -95,20 +95,20 @@ const useStyles = makeStyles()(theme => {
border: 0,
'&.primary': {
background: theme.palette.action01,
color: theme.palette.text01,
background: theme.palette.prejoinActionButtonPrimary,
color: theme.palette.prejoinActionButtonPrimaryText,
'&:hover': {
backgroundColor: theme.palette.action01Hover
backgroundColor: theme.palette.prejoinActionButtonPrimaryHover
}
},
'&.secondary': {
background: theme.palette.action02,
color: theme.palette.text04,
background: theme.palette.prejoinActionButtonSecondary,
color: theme.palette.prejoinActionButtonSecondaryText,
'&:hover': {
backgroundColor: theme.palette.action02Hover
backgroundColor: theme.palette.prejoinActionButtonSecondaryHover
}
},
@@ -120,7 +120,7 @@ const useStyles = makeStyles()(theme => {
},
'&.disabled': {
background: theme.palette.disabled01,
background: theme.palette.prejoinActionButtonDisabled,
border: '1px solid #5E6D7A',
color: '#AFB6BC',
cursor: 'initial',

View File

@@ -109,7 +109,7 @@ const useStyles = makeStyles()(theme => {
position: 'absolute',
inset: '0 0 0 0',
display: 'flex',
backgroundColor: theme.palette.ui01,
backgroundColor: theme.palette.preMeetingBackground,
zIndex: 252,
'@media (max-width: 720px)': {
@@ -163,7 +163,7 @@ const useStyles = makeStyles()(theme => {
},
title: {
...theme.typography.heading4,
color: `${theme.palette.text01}!important`,
color: theme.palette.prejoinTitleText,
marginBottom: theme.spacing(3),
textAlign: 'center',
@@ -179,7 +179,7 @@ const useStyles = makeStyles()(theme => {
roomName: {
...theme.typography.heading5,
color: theme.palette.text01,
color: theme.palette.prejoinRoomNameText,
display: 'inline-block',
overflow: 'hidden',
textOverflow: 'ellipsis',

View File

@@ -6,7 +6,7 @@ const useStyles = makeStyles()(theme => {
return {
warning: {
bottom: 0,
color: theme.palette.text03,
color: theme.palette.prejoinRecordingWarningText,
display: 'flex',
justifyContent: 'center',
...theme.typography.bodyShortRegular,

View File

@@ -11,8 +11,8 @@ import { setUnsafeRoomConsent } from '../../actions.web';
const useStyles = makeStyles()(theme => {
return {
warning: {
backgroundColor: theme.palette.warning01,
color: theme.palette.text04,
backgroundColor: theme.palette.prejoinWarningBackground,
color: theme.palette.prejoinWarningText,
...theme.typography.bodyShortRegular,
padding: theme.spacing(3),
borderRadius: theme.shape.borderRadius,

View File

@@ -9,11 +9,11 @@ import { getSupportUrl } from '../../functions';
const useStyles = makeStyles()(theme => {
return {
dialog: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
backgroundColor: theme.palette.dialogBackground,
border: `1px solid ${theme.palette.inlineDialogBorder}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
color: theme.palette.text01,
color: theme.palette.dialogText,
...theme.typography.bodyShortRegular,
padding: `${theme.spacing(3)} 10`,
'& .retry-button': {

View File

@@ -2,6 +2,7 @@ import { batch } from 'react-redux';
import { IStore } from '../../app/types';
import { CHAT_SIZE } from '../../chat/constants';
import { getCustomPanelWidth } from '../../custom-panel/functions';
import { getParticipantsPaneWidth } from '../../participants-pane/functions';
import {
@@ -51,6 +52,7 @@ export function clientResized(clientWidth: number, clientHeight: number) {
}
availableWidth -= getParticipantsPaneWidth(state);
availableWidth -= getCustomPanelWidth(state);
reducedUIEnabled && dispatch(setReducedUI(availableWidth, clientHeight));
}

View File

@@ -22,11 +22,11 @@ interface IProps {
const useStyles = makeStyles()(theme => {
return {
container: {
backgroundColor: theme.palette.uiBackground,
backgroundColor: theme.palette.tooltipBackground,
borderRadius: '3px',
padding: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.text01,
color: theme.palette.tooltipText,
position: 'relative',
'&.mounting-animation': {

View File

@@ -51,7 +51,7 @@ import './subscriber.web';
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case TRACK_ADDED: {
const { local } = action.track;
const { local, jitsiTrack } = action.track;
// The devices list needs to be refreshed when no initial video permissions
// were granted and a local video track is added by umuting the video.
@@ -65,6 +65,16 @@ MiddlewareRegistry.register(store => next => action => {
if (participantId) {
logTracksForParticipant(store.getState()['features/base/tracks'], participantId, 'Track added');
// Fire participantMuted event for initial state of remote tracks
if (typeof action.track?.muted !== 'undefined' && jitsiTrack) {
const isVideoTrack = jitsiTrack.getType() !== MEDIA_TYPE.AUDIO;
const mediaType = isVideoTrack
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
: 'audio';
APP.API.notifyParticipantMuted(participantId, action.track.muted, mediaType);
}
}
return result;
@@ -119,6 +129,16 @@ MiddlewareRegistry.register(store => next => action => {
// TODO Remove the following calls to APP.UI once components interested
// in track mute changes are moved into React and/or redux.
const { jitsiTrack } = action.track;
const participantID = jitsiTrack.getParticipantId();
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
const local = jitsiTrack.isLocal();
// Get old muted state BEFORE updating
const tracks = store.getState()['features/base/tracks'];
const oldTrack = tracks.find((t: ITrack) => t.jitsiTrack === jitsiTrack);
const oldMutedState = oldTrack?.muted;
const result = next(action);
const state = store.getState();
@@ -126,11 +146,6 @@ MiddlewareRegistry.register(store => next => action => {
return result;
}
const { jitsiTrack } = action.track;
const participantID = jitsiTrack.getParticipantId();
const isVideoTrack = jitsiTrack.type !== MEDIA_TYPE.AUDIO;
const local = jitsiTrack.isLocal();
if (isVideoTrack) {
if (local && !(jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP)) {
APP.conference.setVideoMuteStatus();
@@ -144,12 +159,14 @@ MiddlewareRegistry.register(store => next => action => {
if (typeof action.track?.muted !== 'undefined' && participantID && !local) {
logTracksForParticipant(store.getState()['features/base/tracks'], participantID, 'Track updated');
// Notify external API when remote participant mutes/unmutes themselves
const mediaType = isVideoTrack
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
: 'audio';
// Fire participantMuted event only if muted state actually changed
if (oldMutedState !== action.track.muted) {
const mediaType = isVideoTrack
? (jitsiTrack.getVideoType() === VIDEO_TYPE.DESKTOP ? 'desktop' : 'video')
: 'audio';
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType, true);
APP.API.notifyParticipantMuted(participantID, action.track.muted, mediaType);
}
}
return result;

View File

@@ -4,7 +4,7 @@ import { isEqual, sortBy } from 'lodash-es';
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
import { getAutoPinSetting } from '../../video-layout/functions.any';
import { MEDIA_TYPE } from '../media/constants';
import { getScreenshareParticipantIds } from '../participants/functions';
import { getLocalParticipant, getScreenshareParticipantIds } from '../participants/functions';
import StateListenerRegistry from '../redux/StateListenerRegistry';
import { isLocalTrackMuted } from './functions';
@@ -47,6 +47,13 @@ StateListenerRegistry.register(
/* listener */ (muted, store, previousMuted) => {
if (muted !== previousMuted) {
APP.API.notifyVideoMutedStatusChanged(muted);
// Also fire the participantMuted event for consistency
const localParticipant = getLocalParticipant(store.getState());
if (localParticipant) {
APP.API.notifyParticipantMuted(localParticipant.id, muted, 'video');
}
}
}
);

View File

@@ -1,3 +1,4 @@
/* eslint-disable @stylistic/no-multi-spaces */
// Mapping between the token used and the color
export const colorMap = {
// ----- Surfaces -----
@@ -7,7 +8,7 @@ export const colorMap = {
// - JitsiMeetView.java
uiBackground: 'surface01',
// Container backgrounds
// Container backgrounds (legacy tokens)
ui01: 'surface02',
ui02: 'surface03',
ui03: 'ui02',
@@ -47,6 +48,426 @@ export const colorMap = {
// Focus
focus01: 'focus01',
// ----- Semantic Tokens (component-based, backwards compatible) -----
// Dialog/Modal Components
dialogBackground: 'surface02', // Main dialog background (same as ui01)
dialogOverlay: 'surface03', // Overlay/backdrop (same as ui02)
dialogBorder: 'ui02', // Dialog borders
dialogText: 'textColor01', // Primary dialog text (same as text01)
dialogSecondaryText: 'textColor02', // Secondary dialog text (same as text02)
// Large Video
largeVideoBackground: 'surface03', // Main video area background (same as ui02)
largeVideoPlaceholder: 'surface03', // Placeholder when no video (same as ui02)
// Filmstrip
filmstripBackground: 'surface03', // Filmstrip container background (same as ui02)
filmstripBackgroundHover: 'uiBackground', // Filmstrip background on hover/focus
filmstripDragHandle: 'icon02', // Filmstrip resize drag handle color
filmstripDragHandleHover: 'icon01', // Filmstrip resize drag handle hover color
thumbnailBackground: 'surface03', // Individual thumbnail background (same as ui02)
thumbnailBorder: 'ui03', // Thumbnail borders (same as ui03)
thumbnailHover: 'hover05', // Thumbnail hover state (same as action03Hover)
thumbnailTintBackground: 'uiBackground', // Thumbnail tint overlay background
thumbnailRaisedHandIcon: 'uiBackground', // Thumbnail raised hand indicator icon
thumbnailVideoBackground: 'uiBackground', // Thumbnail video/placeholder background
// Chat
chatBackground: 'surface02', // Chat panel background (same as ui01)
chatBackdrop: 'ui04', // Chat screen background (same as ui10)
chatEmptyText: 'ui03', // Empty component text
chatInputBackground: 'surface03', // Chat input field background (same as ui02)
chatInputBorder: 'surface03', // Chat input border (same as ui02)
chatLink: 'action01', // Chat link color (same as link01)
chatLobbyMessageBubble: 'support06', // Lobby message bubble background
chatLobbyMessageNotice: 'surface01', // Lobby message notice text
chatLobbyRecipientContainer: 'support06', // Lobby recipient container background
chatMessageLocal: 'surface05', // Local participant message bubble (same as ui04)
chatMessagePrivate: 'support05', // Private/DM message bubble
chatMessageRemote: 'surface03', // Remote participant message bubble (same as ui02)
chatMessageText: 'textColor01', // Chat message text
chatPrivateNotice: 'textColor02', // Private message notice text
chatRecipientCancelIcon: 'icon01', // Recipient cancel icon color
chatRecipientContainer: 'support05', // Recipient container background
chatRecipientText: 'textColor01', // Recipient text color
chatReplyIcon: 'icon01', // Reply icon color
chatSenderName: 'textColor02', // Sender display name color
chatTimestamp: 'ui03', // Chat timestamp text
// Toolbox/Toolbar
toolboxBackground: 'surface02', // Main toolbox background
drawerBackground: 'surface02', // Drawer/side panel background
toolboxIconHover: 'surface05', // Toolbox icon hover background
toolboxIconActive: 'ui02', // Toolbox icon active/pressed background
toolboxIconToggled: 'ui02', // Toolbox icon toggled background
toolbarButton: 'action01', // Toolbar button color
toolbarButtonHover: 'hover01', // Toolbar button hover (same as action01Hover)
toolbarButtonActive: 'active01', // Toolbar button active/pressed state
toolbarIcon: 'icon01', // Toolbar icon color
toolbarIconHover: 'icon01', // Toolbar icon hover state
toolbarIconActive: 'action01', // Toolbar icon active/toggled state
// Overflow Menu (More Actions)
overflowMenuBackground: 'surface02', // Overflow menu background
overflowMenuBorder: 'surface05', // Overflow menu border
overflowMenuItemText: 'text01', // Overflow menu item text
overflowMenuItemIcon: 'text01', // Overflow menu item icon
overflowMenuItemHover: 'surface03', // Overflow menu item hover background
overflowMenuItemDisabled: 'text03', // Overflow menu item disabled text/icon
overflowMenuSeparator: 'ui03', // Overflow menu group separator
// Participants Pane
participantsPaneBackground: 'surface02', // Participants list background
participantItemBackground: 'surface03', // Individual participant item background
participantItemHover: 'hover05', // Participant item hover
participantItemBorder: 'ui02', // Participant item border
participantCounterBadge: 'ui02', // Participant counter badge background
participantCounterText: 'text01', // Participant counter text
participantModeratorLabel: 'text03', // Moderator label text
participantSectionText: 'text02', // Section header/subtitle text
participantActionButton: 'action02', // Action button background
participantLinkText: 'link01', // Link text color
participantWarningText: 'warning02', // Warning text color
participantRaisedHandBadge: 'warning02', // Raised hand indicator background
participantRaisedHandIcon: 'icon04', // Raised hand icon color
// Lobby
lobbyBackground: 'surface02', // Lobby screen background (same as ui01)
lobbyPreviewBackground: 'surface03', // Video preview background (same as ui02)
// Speaker Stats
speakerStatsBackground: 'surface02', // Speaker stats panel background
speakerStatsRowBackground: 'ui02', // Individual stat row background
speakerStatsRowAlternate: 'ui03', // Alternate row background
speakerStatsBorder: 'surface03', // Speaker stats borders
speakerStatsHeaderBackground: 'ui09', // Header background
speakerStatsSearchBackground: 'field01', // Search input background
speakerStatsSearchBorder: 'ui05', // Search input border
speakerStatsSearchText: 'text01', // Search input text
speakerStatsSearchPlaceholder: 'text03', // Search placeholder
speakerStatsSearchIcon: 'icon03', // Search icon color
speakerStatsLabelText: 'text03', // Label text color
speakerStatsSuccessBar: 'success02', // Success/progress bar
speakerStatsAvatarLeft: 'surface05', // Avatar background for participants who left
// Pre-meeting/Prejoin
preMeetingBackground: 'surface02', // Pre-meeting screen container background
preMeetingPreview: 'ui01', // Video preview in pre-meeting
prejoinDialogBackground: 'uiBackground', // Prejoin dialog background
prejoinDialogDelimiter: 'ui03', // Prejoin dialog delimiter line
prejoinDialogDelimiterText: 'text01', // Prejoin dialog delimiter text
prejoinTitleText: 'text01', // Prejoin title text color
prejoinRoomNameText: 'text01', // Prejoin room name text color
prejoinWarningBackground: 'warning01', // Warning banner background
prejoinWarningText: 'text04', // Warning banner text
prejoinRecordingWarningText: 'text03', // Recording warning text
prejoinActionButtonPrimary: 'action01', // Primary action button
prejoinActionButtonPrimaryHover: 'action01Hover', // Primary button hover
prejoinActionButtonPrimaryText: 'text01', // Primary button text
prejoinActionButtonSecondary: 'action02', // Secondary action button
prejoinActionButtonSecondaryHover: 'action02Hover', // Secondary button hover
prejoinActionButtonSecondaryText: 'text04', // Secondary button text
prejoinActionButtonDanger: 'actionDanger', // Danger button (leave)
prejoinActionButtonDisabled: 'disabled01', // Disabled button
prejoinCountryPickerBackground: 'ui01', // Country picker background
prejoinCountryPickerBorder: 'ui03', // Country picker border
prejoinCountryPickerText: 'text01', // Country picker text
prejoinCountryRowBackground: 'action03', // Country row background
prejoinCountryRowHover: 'action03Hover', // Country row hover
prejoinDeviceStatusOk: 'success01', // Device status OK background
prejoinDeviceStatusWarning: 'warning01', // Device status warning background
prejoinDeviceStatusText: 'uiBackground', // Device status text
// Notifications
notificationBackground: 'ui04', // Notification background
notificationNormalIcon: 'action01', // Normal notification icon
notificationError: 'iconError', // Error notification icon
notificationSuccess: 'success01', // Success notification icon
notificationWarning: 'warning01', // Warning notification icon
notificationText: 'text04', // Notification text
notificationActionText: 'action01', // Notification action text
notificationErrorText: 'textError', // Error notification text
notificationActionFocus: 'action01', // Notification action focus outline
notificationCloseIcon: 'icon04', // Notification close icon
// Forms/Inputs
inputBackground: 'field01', // Input field background
inputBorder: 'surface03', // Input field border (same as ui02)
inputText: 'textColor01', // Input field text (same as text01)
inputPlaceholder: 'textColor02', // Input placeholder text (same as text02)
// Breakout Rooms
breakoutRoomBackground: 'ui01', // Breakout rooms panel background
breakoutRoomItemBackground: 'surface03', // Individual breakout room background
breakoutRoomArrowBackground: 'ui02', // Breakout room arrow container background
// Settings
settingsBackground: 'ui01', // Settings dialog background
settingsSectionBackground: 'ui02', // Settings section background
settingsTabText: 'text01', // Settings tab text
settingsShortcutKey: 'surface05', // Keyboard shortcut key background
settingsVideoPreviewBorder: 'action01Hover', // Video preview border (selected)
settingsErrorIcon: 'iconError', // Error icon color
// Visitors
visitorsCountBadge: 'warning02', // Visitors count badge background
visitorsCountText: 'uiBackground', // Visitors count badge text
visitorsCountIcon: 'icon04', // Visitors count icon
visitorsQueueBackground: 'ui01', // Visitors queue panel background
visitorsQueueText: 'text01', // Visitors queue text
visitorsArrowBackground: 'ui02', // Visitors arrow container background
// Welcome Page
welcomeBackground: 'surface01', // Welcome page background (same as uiBackground)
welcomeCard: 'ui01', // Welcome page tab bar background
welcomeTabActive: 'icon01', // Welcome page active tab icon
welcomeTabInactive: 'icon03', // Welcome page inactive tab icon
// ----- Form Components -----
// Input
inputLabel: 'text01', // Input field label text
inputFieldBackground: 'ui02', // Input field background color
inputFieldBorder: 'ui02', // Input field border color
inputFieldText: 'text01', // Input field text color
inputFieldPlaceholder: 'text02', // Input field placeholder text
inputFieldDisabled: 'text03', // Input field disabled text
inputFieldError: 'textError', // Input field error state
inputFieldFocus: 'focus01', // Input field focus outline
inputClearButton: 'transparent', // Input clear button background
inputBottomLabel: 'text02', // Input bottom label text
inputBottomLabelError: 'textError', // Input bottom label error text
// Select
selectLabel: 'text01', // Select label text
selectBackground: 'ui02', // Select background color
selectText: 'text01', // Select text color
selectDisabled: 'text03', // Select disabled text
selectError: 'textError', // Select error state
selectFocus: 'focus01', // Select focus outline
selectIcon: 'icon01', // Select dropdown icon (enabled)
selectIconDisabled: 'icon03', // Select dropdown icon (disabled)
selectBottomLabel: 'text02', // Select bottom label text
selectBottomLabelError: 'textError', // Select bottom label error text
// MultiSelect
multiSelectBackground: 'ui01', // MultiSelect dropdown background
multiSelectBorder: 'ui04', // MultiSelect dropdown border
multiSelectItemText: 'text01', // MultiSelect item text
multiSelectItemHover: 'ui02', // MultiSelect item hover background
multiSelectItemDisabled: 'text03', // MultiSelect disabled item text
// Checkbox
checkboxLabel: 'text01', // Checkbox label text
checkboxBorder: 'icon03', // Checkbox border color
checkboxChecked: 'action01', // Checkbox checked background
checkboxDisabledBackground: 'ui02', // Checkbox disabled background
checkboxDisabledBorder: 'surface05', // Checkbox disabled border
checkboxDisabledChecked: 'ui02', // Checkbox disabled checked background
checkboxIcon: 'icon01', // Checkbox check icon (enabled)
checkboxIconDisabled: 'icon03', // Checkbox check icon (disabled)
// Switch
switchBackground: 'ui01', // Switch background (unchecked)
switchBackgroundOn: 'action01', // Switch background (checked)
switchToggle: 'ui04', // Switch toggle circle
switchToggleDisabled: 'ui03', // Switch toggle circle (disabled)
switchFocus: 'focus01', // Switch focus outline
// Tabs
tabText: 'text02', // Tab text (unselected)
tabTextHover: 'text01', // Tab text (hover)
tabTextSelected: 'text01', // Tab text (selected)
tabTextDisabled: 'text03', // Tab text (disabled)
tabBorder: 'ui05', // Tab bottom border (unselected)
tabBorderHover: 'ui10', // Tab bottom border (hover)
tabBorderSelected: 'action01', // Tab bottom border (selected)
tabBorderDisabled: 'ui05', // Tab bottom border (disabled)
tabFocus: 'focus01', // Tab focus outline
tabBadgeBackground: 'warning01', // Tab count badge background
tabBadgeText: 'text04', // Tab count badge text
// ListItem
listItemText: 'text01', // List item text color
listItemBackground: 'ui01', // List item default background
listItemHover: 'surface03', // List item hover background
listItemHighlighted: 'surface03', // List item highlighted/active background
listItemBoxShadow: 'ui02', // List item actions box shadow color
// ClickableIcon
clickableIconBackground: 'transparent', // Clickable icon background
clickableIconHover: 'ui02', // Clickable icon hover background
clickableIconActive: 'ui03', // Clickable icon active/pressed background
clickableIconFocus: 'focus01', // Clickable icon focus outline
// Label
labelBackground: 'ui04', // Label default background
labelText: 'text01', // Label text color
labelWhiteBackground: 'ui08', // Label white variant background
labelWhiteText: 'text04', // Label white variant text
labelWhiteIcon: 'surface01', // Label white variant icon
// Tooltip
tooltipBackground: 'uiBackground', // Tooltip background color
tooltipText: 'text01', // Tooltip text color
// Polls
pollsBackground: 'surface03', // Poll container background
pollsTitle: 'text01', // Poll title text
pollsSubtitle: 'text02', // Poll subtitle/secondary text
pollsQuestion: 'text01', // Poll question text
pollsAnswer: 'text01', // Poll answer text
pollsBarBackground: 'ui03', // Poll results bar background
pollsBarPercentage: 'text01', // Poll results percentage text
pollsVotersBackground: 'ui03', // Poll voters list background
pollsVotersText: 'text01', // Poll voters list text
pollsSeparator: 'ui03', // Poll section separator
pollsSendLabel: 'text01', // Poll send button label
pollsSendDisabled: 'text03', // Poll send button disabled label
pollsPaneBackground: 'ui01', // Poll pane container background
pollsPaneBorder: 'ui05', // Poll pane border
pollsCreateBackground: 'uiBackground', // Poll create dialog background
pollsCreateBorder: 'ui06', // Poll create dialog border
// Video Quality / Slider
sliderKnob: 'text01', // Slider knob/thumb color
sliderTrack: 'text03', // Slider track color
sliderFocus: 'ui06', // Slider focus outline
videoQualityText: 'text01', // Video quality dialog text
videoQualityBackground: 'surface02', // Video quality dialog background
// Connection Indicator
connectionIndicatorLost: 'ui05', // Connection indicator lost status
connectionIndicatorOther: 'action01', // Connection indicator other status
// Device Selection
deviceSelectorBackground: 'ui01', // Device selector background
deviceSelectorText: 'text01', // Device selector text
deviceSelectorBorder: 'ui03', // Device selector border
deviceSelectorTextBackground: 'uiBackground', // Device selector text-only background
deviceSelectorVideoPreview: 'uiBackground', // Device selector video preview background
// Invite / Dial-in
dialInBackground: 'ui01', // Dial-in summary background
dialInText: 'text01', // Dial-in summary text
dialInSecondaryText: 'text02', // Dial-in summary secondary text
// Reactions
reactionsMenuBackground: 'ui01', // Reactions menu background
reactionsMenuBorder: 'ui02', // Reactions menu border
// Recording / Live Stream
recordingBackground: 'ui01', // Recording panel background
recordingText: 'text01', // Recording panel text
recordingHighlightButton: 'ui04', // Recording highlight button background
recordingHighlightButtonDisabled: 'text02', // Recording highlight button disabled background
recordingHighlightButtonIcon: 'ui02', // Recording highlight button icon color
recordingHighlightButtonIconDisabled: 'text03', // Recording highlight button disabled icon color
recordingNotificationText: 'surface01', // Recording notification text color
recordingNotificationAction: 'action01', // Recording notification action color
// Virtual Background
virtualBackgroundBackground: 'ui01', // Virtual background picker background
virtualBackgroundText: 'text01', // Virtual background picker text
virtualBackgroundBorder: 'ui03', // Virtual background item border
virtualBackgroundPreview: 'uiBackground', // Virtual background preview container
// Conference / Meeting
conferenceTimerText: 'text01', // Conference timer text
conferenceSubjectText: 'text01', // Conference subject text
conferenceNoticeBackground: 'uiBackground', // Conference notice background
conferenceNoticeText: 'text01', // Conference notice text
conferenceRaisedHandLabelText: 'uiBackground', // Raised hands count label text
conferenceRaisedHandLabelIcon: 'surface01', // Raised hands count label icon
// Subtitle Messages
subtitleMessageBackground: 'ui02', // Subtitle message background
subtitleMessageText: 'text01', // Subtitle message text
subtitleMessageSender: 'text02', // Subtitle message sender name
subtitleMessageTime: 'text03', // Subtitle message timestamp
// Language Selector
languageSelectorBackground: 'ui01', // Language selector background
languageSelectorText: 'text01', // Language selector text
languageSelectorHover: 'ui02', // Language selector item hover
// Video Menu
videoMenuBackground: 'ui01', // Video menu background
videoMenuBorder: 'ui02', // Video menu border
videoMenuText: 'text01', // Video menu text
videoMenuSliderBackground: 'ui03', // Video menu slider background
// File Sharing
fileSharingBackground: 'ui01', // File sharing panel background
fileSharingText: 'text01', // File sharing text
fileSharingEmptyText: 'text02', // File sharing empty state text
fileSharingEmptyIcon: 'icon03', // File sharing empty state icon
fileSharingItemBackground: 'surface03', // File sharing item background
fileSharingItemBorder: 'ui02', // File sharing item hover/border
// Gifs
gifsBackground: 'ui01', // GIFs panel background
gifsText: 'text01', // GIFs panel text
// Whiteboard
whiteboardBackground: 'ui03', // Whiteboard background
whiteboardText: 'text01', // Whiteboard panel text
// Salesforce
salesforceSearchBackground: 'field01', // Salesforce search input background
salesforceSearchBorder: 'ui05', // Salesforce search input border
salesforceSearchText: 'dialogText', // Salesforce search input text
salesforceSearchPlaceholder: 'text03', // Salesforce search placeholder
salesforceSearchIcon: 'text03', // Salesforce search icon
// Security Dialog
securityDialogBackground: 'ui01', // Security dialog background
securityDialogText: 'text01', // Security dialog text
securityDialogSecondaryText: 'text02', // Security dialog secondary text
securityDialogBorder: 'ui07', // Security dialog border color
// Deep Linking
deepLinkingBackground: 'ui01', // Deep linking page content pane background
deepLinkingBorder: 'ui03', // Deep linking page content pane border
deepLinkingText: 'text01', // Deep linking page text
deepLinkingSeparator: 'ui03', // Deep linking separator line
deepLinkingLabelText: 'text02', // Deep linking label text
deepLinkingLink: 'link01', // Deep linking link color
// Base React Components
baseReactBackground: 'ui01', // Base react component background
baseReactText: 'text01', // Base react component text
baseReactBorder: 'ui03', // Base react component border
// Inline Dialog
inlineDialogBackground: 'ui01', // Inline dialog background
inlineDialogText: 'text01', // Inline dialog text
inlineDialogBorder: 'ui02', // Inline dialog border
// Pre-meeting / Action Button
actionButtonBackground: 'ui01', // Action button background (different from main buttons)
actionButtonText: 'text01', // Action button text
actionButtonBorder: 'ui03', // Action button border
// Audio Route Picker
audioRoutePickerBackground: 'ui01', // Audio route picker background
audioRoutePickerText: 'text01', // Audio route picker text
audioRoutePickerBorder: 'ui03', // Audio route picker border
// Etherpad
etherpadBackground: 'ui01', // Etherpad panel background
etherpadText: 'text01', // Etherpad panel text
// Display Name
displayNameBackground: 'ui01', // Display name background
displayNameText: 'text01', // Display name text
// Car Mode
carModeBackground: 'ui01', // Car mode background
carModeText: 'text01', // Car mode text
carModeBorder: 'ui03', // Car mode border
// ----- Links -----
link01: 'action01',

View File

@@ -14,7 +14,7 @@ const useStyles = makeStyles()(theme => {
width: '100%',
height: '100%',
position: 'fixed',
color: theme.palette.text01,
color: theme.palette.dialogText,
...theme.typography.bodyLongRegular,
top: 0,
left: 0,
@@ -49,13 +49,13 @@ const useStyles = makeStyles()(theme => {
height: '100%',
top: 0,
left: 0,
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.dialogOverlay,
opacity: 0.75
},
modal: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui03}`,
backgroundColor: theme.palette.dialogBackground,
border: `1px solid ${theme.palette.dialogBorder}`,
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
borderRadius: `${theme.shape.borderRadius}px`,
display: 'flex',

View File

@@ -47,7 +47,7 @@ const useStyles = makeStyles()(theme => {
return {
formControl: {
...theme.typography.bodyLongRegular,
color: theme.palette.text01,
color: theme.palette.checkboxLabel,
display: 'inline-flex',
alignItems: 'center',
@@ -76,10 +76,10 @@ const useStyles = makeStyles()(theme => {
backgroundColor: 'transparent',
margin: '3px',
font: 'inherit',
color: theme.palette.icon03,
color: theme.palette.checkboxBorder,
width: '18px',
height: '18px',
border: `2px solid ${theme.palette.icon03}`,
border: `2px solid ${theme.palette.checkboxBorder}`,
borderRadius: '3px',
display: 'grid',
@@ -90,7 +90,7 @@ const useStyles = makeStyles()(theme => {
width: '18px',
height: '18px',
opacity: 0,
backgroundColor: theme.palette.action01,
backgroundColor: theme.palette.checkboxChecked,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
@@ -104,11 +104,11 @@ const useStyles = makeStyles()(theme => {
},
'&:disabled': {
backgroundColor: theme.palette.ui03,
borderColor: theme.palette.ui04,
backgroundColor: theme.palette.checkboxDisabledBackground,
borderColor: theme.palette.checkboxDisabledBorder,
'&::before': {
backgroundColor: theme.palette.ui04
backgroundColor: theme.palette.checkboxDisabledChecked
}
},
@@ -173,7 +173,7 @@ const Checkbox = ({
<Icon
aria-hidden = { true }
className = 'checkmark'
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
color = { disabled ? theme.palette.checkboxIconDisabled : theme.palette.checkboxIcon }
size = { 18 }
src = { IconCheck } />
</div>

View File

@@ -16,22 +16,22 @@ const useStyles = makeStyles()(theme => {
return {
button: {
padding: '2px',
backgroundColor: theme.palette.action03,
backgroundColor: theme.palette.clickableIconBackground,
border: 0,
outline: 0,
borderRadius: `${theme.shape.borderRadius}px`,
'&:hover': {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.clickableIconHover
},
'&.focus-visible': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.clickableIconFocus}`
},
'&:active': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.clickableIconActive
},
'&.is-mobile': {

View File

@@ -133,11 +133,11 @@ const MAX_HEIGHT = 400;
const useStyles = makeStyles()(theme => {
return {
contextMenu: {
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
backgroundColor: theme.palette.overflowMenuBackground,
border: `1px solid ${theme.palette.overflowMenuBorder}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
boxShadow: '0px 1px 2px rgba(41, 41, 41, 0.25)',
color: theme.palette.text01,
color: theme.palette.overflowMenuItemText,
...theme.typography.bodyShortRegular,
marginTop: '48px',
position: 'absolute',

View File

@@ -122,11 +122,11 @@ const useStyles = makeStyles()(theme => {
},
'&:hover': {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.overflowMenuItemHover
},
'&:active': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.overflowMenuItemHover
},
'&.focus-visible': {
@@ -137,7 +137,7 @@ const useStyles = makeStyles()(theme => {
selected: {
borderLeft: `3px solid ${theme.palette.action01Hover}`,
paddingLeft: '13px',
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.overflowMenuItemHover
},
contextMenuItemDisabled: {
@@ -146,19 +146,19 @@ const useStyles = makeStyles()(theme => {
contextMenuItemIconDisabled: {
'& svg': {
fill: `${theme.palette.text03} !important`
fill: `${theme.palette.overflowMenuItemDisabled} !important`
}
},
contextMenuItemLabelDisabled: {
color: theme.palette.text03,
color: theme.palette.overflowMenuItemDisabled,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.text03
fill: theme.palette.overflowMenuItemDisabled
}
},
@@ -168,13 +168,13 @@ const useStyles = makeStyles()(theme => {
contextMenuItemIcon: {
'& svg': {
fill: theme.palette.icon01
fill: theme.palette.overflowMenuItemIcon
}
},
text: {
...theme.typography.bodyShortRegular,
color: theme.palette.text01
color: theme.palette.overflowMenuItemText
},
drawerText: {

View File

@@ -30,7 +30,7 @@ const useStyles = makeStyles()(theme => {
},
'& + &:not(:empty)': {
borderTop: `1px solid ${theme.palette.ui03}`
borderTop: `1px solid ${theme.palette.overflowMenuSeparator}`
},
'&:first-of-type': {

View File

@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
},
title: {
color: theme.palette.text01,
color: theme.palette.dialogText,
...theme.typography.heading5,
margin: 0,
padding: 0

View File

@@ -42,7 +42,7 @@ const useStyles = makeStyles()(theme => {
flexDirection: 'column',
minWidth: '211px',
maxWidth: '100%',
borderRight: `1px solid ${theme.palette.ui03}`,
borderRight: `1px solid ${theme.palette.dialogBorder}`,
[`@media (max-width: ${MOBILE_BREAKPOINT}px)`]: {
width: '100%',
@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
title: {
...theme.typography.heading5,
color: `${theme.palette.text01} !important`,
color: `${theme.palette.dialogText} !important`,
margin: 0,
padding: 0
},
@@ -301,7 +301,7 @@ const DialogWithTabs = ({
}
return null;
}, [ selectedTabIndex, tabStates ]);
}, [ selectedTabIndex, tabStates, tabs ]);
const closeIcon = useMemo(() => (
<ClickableIcon

View File

@@ -49,7 +49,7 @@ const useStyles = makeStyles()(theme => {
},
label: {
color: theme.palette.text01,
color: theme.palette.inputLabel,
...theme.typography.bodyShortRegular,
marginBottom: theme.spacing(2),
@@ -64,9 +64,9 @@ const useStyles = makeStyles()(theme => {
},
input: {
backgroundColor: theme.palette.ui03,
background: theme.palette.ui03,
color: theme.palette.text01,
backgroundColor: theme.palette.inputFieldBackground,
background: theme.palette.inputFieldBackground,
color: theme.palette.inputFieldText,
...theme.typography.bodyShortRegular,
padding: '10px 16px',
borderRadius: theme.shape.borderRadius,
@@ -76,16 +76,16 @@ const useStyles = makeStyles()(theme => {
width: '100%',
'&::placeholder': {
color: theme.palette.text02
color: theme.palette.inputFieldPlaceholder
},
'&:focus': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldFocus}`
},
'&:disabled': {
color: theme.palette.text03
color: theme.palette.inputFieldDisabled
},
'&.is-mobile': {
@@ -99,7 +99,7 @@ const useStyles = makeStyles()(theme => {
},
'&.error': {
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
boxShadow: `0px 0px 0px 2px ${theme.palette.inputFieldError}`
},
'&.clearable-input': {
paddingRight: '46px'
@@ -131,7 +131,7 @@ const useStyles = makeStyles()(theme => {
right: '16px',
top: '10px',
cursor: 'pointer',
backgroundColor: theme.palette.action03,
backgroundColor: theme.palette.inputClearButton,
border: 0,
padding: 0
},
@@ -139,14 +139,14 @@ const useStyles = makeStyles()(theme => {
bottomLabel: {
marginTop: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.text02,
color: theme.palette.inputBottomLabel,
'&.is-mobile': {
...theme.typography.bodyShortRegular
},
'&.error': {
color: theme.palette.textError
color: theme.palette.inputBottomLabelError
}
}
};

View File

@@ -83,7 +83,7 @@ const useStyles = makeStyles()(theme => {
return {
container: {
alignItems: 'center',
color: theme.palette.text01,
color: theme.palette.listItemText,
display: 'flex',
...theme.typography.bodyShortBold,
margin: `0 -${participantsPaneTheme.panePadding}px`,
@@ -93,7 +93,7 @@ const useStyles = makeStyles()(theme => {
minHeight: '40px',
'&:hover, &:focus-within': {
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.listItemHover,
'& .indicators': {
display: 'none'
@@ -103,8 +103,8 @@ const useStyles = makeStyles()(theme => {
display: 'flex',
position: 'relative',
top: 'auto',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHover
}
},
@@ -115,14 +115,14 @@ const useStyles = makeStyles()(theme => {
},
highlighted: {
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.listItemHighlighted,
'& .actions': {
display: 'flex',
position: 'relative',
top: 'auto',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHighlighted
}
},
@@ -170,20 +170,20 @@ const useStyles = makeStyles()(theme => {
actionsContainer: {
position: 'absolute',
top: '-1000px',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHover
},
actionsPermanent: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui01}`,
backgroundColor: theme.palette.ui01
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBackground}`,
backgroundColor: theme.palette.listItemBackground
},
actionsVisible: {
display: 'flex',
boxShadow: `-15px 0px 10px -5px ${theme.palette.ui02}`,
backgroundColor: theme.palette.ui02
boxShadow: `-15px 0px 10px -5px ${theme.palette.listItemBoxShadow}`,
backgroundColor: theme.palette.listItemHighlighted
}
};
});

View File

@@ -38,8 +38,8 @@ const useStyles = makeStyles()(theme => {
},
marginTop: theme.spacing(2),
width: '100%',
backgroundColor: theme.palette.ui01,
border: `1px solid ${theme.palette.ui04}`,
backgroundColor: theme.palette.multiSelectBackground,
border: `1px solid ${theme.palette.multiSelectBorder}`,
borderRadius: `${Number(theme.shape.borderRadius)}px`,
...theme.typography.bodyShortRegular,
zIndex: 2,
@@ -57,7 +57,7 @@ const useStyles = makeStyles()(theme => {
inlineSize: 'calc(100% - 38px)',
overflowWrap: 'break-word',
marginLeft: theme.spacing(2),
color: theme.palette.text01,
color: theme.palette.multiSelectItemText,
'&.with-remove': {
// 60px because of the icon before the content and the remove button
inlineSize: 'calc(100% - 60px)',
@@ -76,15 +76,15 @@ const useStyles = makeStyles()(theme => {
cursor: 'pointer',
padding: `10px ${theme.spacing(3)}`,
'&:hover': {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.multiSelectItemHover
}
},
'&.disabled': {
cursor: 'not-allowed',
'&:hover': {
backgroundColor: theme.palette.ui01
backgroundColor: theme.palette.multiSelectBackground
},
color: theme.palette.text03
color: theme.palette.multiSelectItemDisabled
}
},
errorMessage: {

View File

@@ -70,7 +70,7 @@ const useStyles = makeStyles()(theme => {
},
label: {
color: theme.palette.text01,
color: theme.palette.selectLabel,
...theme.typography.bodyShortRegular,
marginBottom: theme.spacing(2),
@@ -84,11 +84,11 @@ const useStyles = makeStyles()(theme => {
},
select: {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.selectBackground,
borderRadius: `${theme.shape.borderRadius}px`,
width: '100%',
...theme.typography.bodyShortRegular,
color: theme.palette.text01,
color: theme.palette.selectText,
padding: '10px 16px',
paddingRight: '42px',
border: 0,
@@ -99,11 +99,11 @@ const useStyles = makeStyles()(theme => {
'&:focus': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.selectFocus}`
},
'&:disabled': {
color: theme.palette.text03
color: theme.palette.selectDisabled
},
'&.is-mobile': {
@@ -113,7 +113,7 @@ const useStyles = makeStyles()(theme => {
},
'&.error': {
boxShadow: `0px 0px 0px 2px ${theme.palette.textError}`
boxShadow: `0px 0px 0px 2px ${theme.palette.selectError}`
}
},
@@ -132,14 +132,14 @@ const useStyles = makeStyles()(theme => {
bottomLabel: {
marginTop: theme.spacing(2),
...theme.typography.labelRegular,
color: theme.palette.text02,
color: theme.palette.selectBottomLabel,
'&.is-mobile': {
...theme.typography.bodyShortRegular
},
'&.error': {
color: theme.palette.textError
color: theme.palette.selectBottomLabelError
}
}
};
@@ -180,7 +180,7 @@ const Select = ({
</select>
<Icon
className = { cx(classes.icon, isMobile && 'is-mobile') }
color = { disabled ? theme.palette.icon03 : theme.palette.icon01 }
color = { disabled ? theme.palette.selectIconDisabled : theme.palette.selectIcon }
size = { 22 }
src = { IconArrowDown } />
</div>

View File

@@ -18,7 +18,7 @@ const useStyles = makeStyles()(theme => {
return {
container: {
position: 'relative',
backgroundColor: theme.palette.ui05,
backgroundColor: theme.palette.switchBackground,
borderRadius: '12px',
width: '40px',
height: '24px',
@@ -29,11 +29,11 @@ const useStyles = makeStyles()(theme => {
display: 'inline-block',
'&.disabled': {
backgroundColor: theme.palette.ui05,
backgroundColor: theme.palette.switchBackground,
cursor: 'default',
'& .toggle': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.switchToggleDisabled
}
},
@@ -45,7 +45,7 @@ const useStyles = makeStyles()(theme => {
},
containerOn: {
backgroundColor: theme.palette.action01
backgroundColor: theme.palette.switchBackgroundOn
},
toggle: {
@@ -55,7 +55,7 @@ const useStyles = makeStyles()(theme => {
zIndex: 5,
top: '4px',
left: '4px',
backgroundColor: theme.palette.ui10,
backgroundColor: theme.palette.switchToggle,
borderRadius: '100%',
transition: '.3s',
@@ -87,7 +87,7 @@ const useStyles = makeStyles()(theme => {
'&.focus-visible + .toggle-checkbox-ring': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
boxShadow: `0px 0px 0px 2px ${theme.palette.switchFocus}`
}
},

View File

@@ -29,13 +29,13 @@ const useStyles = makeStyles()(theme => {
tab: {
...theme.typography.bodyShortBold,
color: theme.palette.text02,
color: theme.palette.tabText,
flex: 1,
padding: '14px',
background: 'none',
border: 0,
appearance: 'none',
borderBottom: `2px solid ${theme.palette.ui05}`,
borderBottom: `2px solid ${theme.palette.tabBorder}`,
transition: 'color, border-color 0.2s',
display: 'flex',
alignItems: 'center',
@@ -43,25 +43,25 @@ const useStyles = makeStyles()(theme => {
borderRadius: 0,
'&:hover': {
color: theme.palette.text01,
borderColor: theme.palette.ui10
color: theme.palette.tabTextHover,
borderColor: theme.palette.tabBorderHover
},
'&.focus-visible': {
outline: 0,
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`,
boxShadow: `0px 0px 0px 2px ${theme.palette.tabFocus}`,
border: 0,
color: theme.palette.text01
color: theme.palette.tabTextSelected
},
'&.selected': {
color: theme.palette.text01,
borderColor: theme.palette.action01
color: theme.palette.tabTextSelected,
borderColor: theme.palette.tabBorderSelected
},
'&:disabled': {
color: theme.palette.text03,
borderColor: theme.palette.ui05
color: theme.palette.tabTextDisabled,
borderColor: theme.palette.tabBorderDisabled
},
'&.is-mobile': {
@@ -72,9 +72,9 @@ const useStyles = makeStyles()(theme => {
badge: {
...theme.typography.labelBold,
alignItems: 'center',
backgroundColor: theme.palette.warning01,
backgroundColor: theme.palette.tabBadgeBackground,
borderRadius: theme.spacing(2),
color: theme.palette.text04,
color: theme.palette.tabBadgeText,
display: 'inline-flex',
height: theme.spacing(3),
justifyContent: 'center',

View File

@@ -11,6 +11,23 @@ export * from './constants.any';
*/
export const commonStyles = (theme: Theme) => {
return {
':root': {
// Inject semantic tokens as CSS custom properties for use in SCSS
'--toolbox-background-color': theme.palette.toolboxBackground,
'--drawer-background-color': theme.palette.drawerBackground,
'--toolbar-button-color': theme.palette.toolbarButton,
'--toolbar-button-hover-color': theme.palette.toolbarButtonHover,
'--toolbar-button-active-color': theme.palette.toolbarButtonActive,
'--toolbar-icon-color': theme.palette.toolbarIcon,
'--toolbar-icon-hover-color': theme.palette.toolbarIconHover,
'--toolbar-icon-active-color': theme.palette.toolbarIconActive,
'--overflow-menu-background-color': theme.palette.overflowMenuBackground,
'--overflow-menu-item-text-color': theme.palette.overflowMenuItemText,
'--overflow-menu-item-icon-color': theme.palette.overflowMenuItemIcon,
'--overflow-menu-item-hover-color': theme.palette.overflowMenuItemHover,
'--overflow-menu-item-disabled-color': theme.palette.overflowMenuItemDisabled
},
'.empty-list': {
listStyleType: 'none',
margin: 0,
@@ -39,7 +56,7 @@ export const commonStyles = (theme: Theme) => {
'.overflow-menu-item': {
alignItems: 'center',
color: theme.palette.text01,
color: theme.palette.overflowMenuItemText,
cursor: 'pointer',
display: 'flex',
fontSize: '0.875rem',
@@ -59,20 +76,20 @@ export const commonStyles = (theme: Theme) => {
'&.disabled': {
cursor: 'initial',
color: theme.palette.text03,
color: theme.palette.overflowMenuItemDisabled,
'&:hover': {
background: 'none'
},
'& svg': {
fill: theme.palette.text03
fill: theme.palette.overflowMenuItemDisabled
}
},
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
background: theme.palette.action02Hover
background: theme.palette.overflowMenuItemHover
},
'&.unclickable:hover': {
background: 'inherit'
@@ -100,14 +117,14 @@ export const commonStyles = (theme: Theme) => {
},
'& svg': {
fill: theme.palette.text01,
fill: theme.palette.overflowMenuItemIcon,
height: 20,
width: 20
}
},
'.prejoin-dialog': {
backgroundColor: theme.palette.uiBackground,
backgroundColor: theme.palette.prejoinDialogBackground,
boxShadow: '0px 2px 20px rgba(0, 0, 0, 0.5)',
borderRadius: theme.shape.borderRadius,
color: '#fff',
@@ -173,7 +190,7 @@ export const commonStyles = (theme: Theme) => {
},
'.prejoin-dialog-delimiter': {
background: theme.palette.ui03,
background: theme.palette.prejoinDialogDelimiter,
border: '0',
height: '1px',
margin: '0',
@@ -194,8 +211,8 @@ export const commonStyles = (theme: Theme) => {
},
'.prejoin-dialog-delimiter-txt': {
background: theme.palette.uiBackground,
color: theme.palette.text01,
background: theme.palette.prejoinDialogBackground,
color: theme.palette.prejoinDialogDelimiterText,
fontSize: '0.75rem',
textTransform: 'uppercase' as const,
padding: `0 ${theme.spacing(2)}`
@@ -219,11 +236,11 @@ export const commonStyles = (theme: Theme) => {
'@media (hover: hover) and (pointer: fine)': {
'&:hover': {
backgroundColor: theme.palette.ui04
backgroundColor: theme.palette.toolboxIconHover
},
'&:active': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.toolboxIconActive
}
},
[theme.breakpoints.down(320)]: {
@@ -232,7 +249,7 @@ export const commonStyles = (theme: Theme) => {
},
'&.toggled': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.toolboxIconToggled
},
'&.disabled': {
@@ -240,13 +257,13 @@ export const commonStyles = (theme: Theme) => {
backgroundColor: `${theme.palette.disabled01} !important`,
'& svg': {
fill: `${theme.palette.text03} !important`
fill: `${theme.palette.icon03} !important`
}
}
},
'.toolbox-button': {
color: theme.palette.text01,
color: theme.palette.toolbarIcon,
cursor: 'pointer',
display: 'inline-block',
lineHeight: '3rem',
@@ -254,7 +271,7 @@ export const commonStyles = (theme: Theme) => {
},
'.toolbox-content-items': {
background: theme.palette.ui01,
background: theme.palette.toolboxBackground,
borderRadius: 6,
margin: '0 auto',
padding: 6,

11
react/features/base/ui/types.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
import '@mui/material/styles';
import { IPalette, ITypography } from './types';
declare module '@mui/material/styles' {
interface Palette extends IPalette {}
interface PaletteOptions extends Partial<IPalette> {}
interface Typography extends ITypography {}
interface TypographyOptions extends Partial<ITypography> {}
}

View File

@@ -5,6 +5,7 @@ interface ITypographyType {
lineHeight: string;
}
/* eslint-disable typescript-sort-keys/interface */
export interface IPalette {
action01: string;
action01Active: string;
@@ -58,6 +59,324 @@ export interface IPalette {
uiBackground: string;
warning01: string;
warning02: string;
// Semantic tokens (component-based, more descriptive names)
breakoutRoomArrowBackground: string;
breakoutRoomBackground: string;
breakoutRoomItemBackground: string;
chatBackground: string;
chatBackdrop: string;
chatEmptyText: string;
chatInputBackground: string;
chatInputBorder: string;
chatLink: string;
chatLobbyMessageBubble: string;
chatLobbyMessageNotice: string;
chatLobbyRecipientContainer: string;
chatMessageLocal: string;
chatMessagePrivate: string;
chatMessageRemote: string;
chatMessageText: string;
chatPrivateNotice: string;
chatRecipientCancelIcon: string;
chatRecipientContainer: string;
chatRecipientText: string;
chatReplyIcon: string;
chatSenderName: string;
chatTimestamp: string;
dialogBackground: string;
dialogBorder: string;
dialogOverlay: string;
dialogSecondaryText: string;
dialogText: string;
drawerBackground: string;
filmstripBackground: string;
filmstripBackgroundHover: string;
filmstripDragHandle: string;
filmstripDragHandleHover: string;
inputBackground: string;
inputBorder: string;
inputPlaceholder: string;
inputText: string;
largeVideoBackground: string;
largeVideoPlaceholder: string;
lobbyBackground: string;
lobbyPreviewBackground: string;
notificationActionFocus: string;
notificationActionText: string;
notificationBackground: string;
notificationCloseIcon: string;
notificationError: string;
notificationErrorText: string;
notificationNormalIcon: string;
notificationSuccess: string;
notificationText: string;
notificationWarning: string;
overflowMenuBackground: string;
overflowMenuBorder: string;
overflowMenuItemDisabled: string;
overflowMenuItemHover: string;
overflowMenuItemIcon: string;
overflowMenuItemText: string;
overflowMenuSeparator: string;
participantActionButton: string;
participantCounterBadge: string;
participantCounterText: string;
participantItemBackground: string;
participantItemBorder: string;
participantItemHover: string;
participantLinkText: string;
participantModeratorLabel: string;
participantRaisedHandBadge: string;
participantRaisedHandIcon: string;
participantSectionText: string;
participantsPaneBackground: string;
participantWarningText: string;
preMeetingBackground: string;
preMeetingPreview: string;
prejoinActionButtonDanger: string;
prejoinActionButtonDisabled: string;
prejoinActionButtonPrimary: string;
prejoinActionButtonPrimaryHover: string;
prejoinActionButtonPrimaryText: string;
prejoinActionButtonSecondary: string;
prejoinActionButtonSecondaryHover: string;
prejoinActionButtonSecondaryText: string;
prejoinCountryPickerBackground: string;
prejoinCountryPickerBorder: string;
prejoinCountryPickerText: string;
prejoinCountryRowBackground: string;
prejoinCountryRowHover: string;
prejoinDeviceStatusOk: string;
prejoinDeviceStatusText: string;
prejoinDeviceStatusWarning: string;
prejoinDialogBackground: string;
prejoinDialogDelimiter: string;
prejoinDialogDelimiterText: string;
prejoinRecordingWarningText: string;
prejoinRoomNameText: string;
prejoinTitleText: string;
prejoinWarningBackground: string;
prejoinWarningText: string;
settingsBackground: string;
settingsErrorIcon: string;
settingsSectionBackground: string;
settingsShortcutKey: string;
settingsTabText: string;
settingsVideoPreviewBorder: string;
speakerStatsAvatarLeft: string;
speakerStatsBackground: string;
speakerStatsBorder: string;
speakerStatsHeaderBackground: string;
speakerStatsLabelText: string;
speakerStatsRowAlternate: string;
speakerStatsRowBackground: string;
speakerStatsSearchBackground: string;
speakerStatsSearchBorder: string;
speakerStatsSearchIcon: string;
speakerStatsSearchPlaceholder: string;
speakerStatsSearchText: string;
speakerStatsSuccessBar: string;
thumbnailBackground: string;
thumbnailBorder: string;
thumbnailHover: string;
thumbnailRaisedHandIcon: string;
thumbnailTintBackground: string;
thumbnailVideoBackground: string;
toolbarButton: string;
toolbarButtonActive: string;
toolbarButtonHover: string;
toolbarIcon: string;
toolbarIconActive: string;
toolbarIconHover: string;
toolboxBackground: string;
toolboxIconActive: string;
toolboxIconHover: string;
toolboxIconToggled: string;
visitorsArrowBackground: string;
visitorsCountBadge: string;
visitorsCountIcon: string;
visitorsCountText: string;
visitorsQueueBackground: string;
visitorsQueueText: string;
welcomeBackground: string;
welcomeCard: string;
welcomeTabActive: string;
welcomeTabInactive: string;
// Form components
actionButtonBackground: string;
actionButtonBorder: string;
actionButtonText: string;
audioRoutePickerBackground: string;
audioRoutePickerBorder: string;
audioRoutePickerText: string;
baseReactBackground: string;
baseReactBorder: string;
baseReactText: string;
carModeBackground: string;
carModeBorder: string;
carModeText: string;
checkboxBorder: string;
checkboxChecked: string;
checkboxDisabledBackground: string;
checkboxDisabledBorder: string;
checkboxDisabledChecked: string;
checkboxIcon: string;
checkboxIconDisabled: string;
checkboxLabel: string;
clickableIconActive: string;
clickableIconBackground: string;
clickableIconFocus: string;
clickableIconHover: string;
conferenceNoticeBackground: string;
conferenceNoticeText: string;
conferenceRaisedHandLabelIcon: string;
conferenceRaisedHandLabelText: string;
conferenceSubjectText: string;
conferenceTimerText: string;
connectionIndicatorLost: string;
connectionIndicatorOther: string;
deepLinkingBackground: string;
deepLinkingBorder: string;
deepLinkingLabelText: string;
deepLinkingLink: string;
deepLinkingSeparator: string;
deepLinkingText: string;
deviceSelectorBackground: string;
deviceSelectorBorder: string;
deviceSelectorText: string;
deviceSelectorTextBackground: string;
deviceSelectorVideoPreview: string;
dialInBackground: string;
dialInSecondaryText: string;
dialInText: string;
displayNameBackground: string;
displayNameText: string;
etherpadBackground: string;
etherpadText: string;
fileSharingBackground: string;
fileSharingEmptyIcon: string;
fileSharingEmptyText: string;
fileSharingItemBackground: string;
fileSharingItemBorder: string;
fileSharingText: string;
gifsBackground: string;
gifsText: string;
inlineDialogBackground: string;
inlineDialogBorder: string;
inlineDialogText: string;
inputBottomLabel: string;
inputBottomLabelError: string;
inputClearButton: string;
inputFieldBackground: string;
inputFieldBorder: string;
inputFieldDisabled: string;
inputFieldError: string;
inputFieldFocus: string;
inputFieldPlaceholder: string;
inputFieldText: string;
inputLabel: string;
labelBackground: string;
labelText: string;
labelWhiteBackground: string;
labelWhiteIcon: string;
labelWhiteText: string;
languageSelectorBackground: string;
languageSelectorHover: string;
languageSelectorText: string;
listItemBackground: string;
listItemBoxShadow: string;
listItemHighlighted: string;
listItemHover: string;
listItemText: string;
multiSelectBackground: string;
multiSelectBorder: string;
multiSelectItemDisabled: string;
multiSelectItemHover: string;
multiSelectItemText: string;
pollsAnswer: string;
pollsBackground: string;
pollsBarBackground: string;
pollsBarPercentage: string;
pollsCreateBackground: string;
pollsCreateBorder: string;
pollsPaneBackground: string;
pollsPaneBorder: string;
pollsQuestion: string;
pollsSendDisabled: string;
pollsSendLabel: string;
pollsSeparator: string;
pollsSubtitle: string;
pollsTitle: string;
pollsVotersBackground: string;
pollsVotersText: string;
reactionsMenuBackground: string;
reactionsMenuBorder: string;
recordingBackground: string;
recordingHighlightButton: string;
recordingHighlightButtonDisabled: string;
recordingHighlightButtonIcon: string;
recordingHighlightButtonIconDisabled: string;
recordingNotificationAction: string;
recordingNotificationText: string;
recordingText: string;
securityDialogBackground: string;
securityDialogBorder: string;
securityDialogSecondaryText: string;
securityDialogText: string;
selectBackground: string;
selectBottomLabel: string;
selectBottomLabelError: string;
selectDisabled: string;
selectError: string;
selectFocus: string;
selectIcon: string;
selectIconDisabled: string;
selectLabel: string;
selectText: string;
sliderFocus: string;
sliderKnob: string;
sliderTrack: string;
subtitleMessageBackground: string;
subtitleMessageSender: string;
subtitleMessageText: string;
subtitleMessageTime: string;
switchBackground: string;
switchBackgroundOn: string;
switchFocus: string;
switchToggle: string;
switchToggleDisabled: string;
tabBadgeBackground: string;
tabBadgeText: string;
tabBorder: string;
tabBorderDisabled: string;
tabBorderHover: string;
tabBorderSelected: string;
tabFocus: string;
tabText: string;
tabTextDisabled: string;
tabTextHover: string;
tabTextSelected: string;
tooltipBackground: string;
tooltipText: string;
videoMenuBackground: string;
videoMenuBorder: string;
videoMenuSliderBackground: string;
videoMenuText: string;
videoQualityBackground: string;
videoQualityText: string;
virtualBackgroundBackground: string;
virtualBackgroundBorder: string;
virtualBackgroundPreview: string;
virtualBackgroundText: string;
whiteboardBackground: string;
whiteboardText: string;
salesforceSearchBackground: string;
salesforceSearchBorder: string;
salesforceSearchIcon: string;
salesforceSearchPlaceholder: string;
salesforceSearchText: string;
}
export interface ITypography {

View File

@@ -11,13 +11,49 @@ import * as tokens from './tokens.json';
*/
export function createColorTokens(colorMap: Object): any {
const allTokens = merge({}, tokens, jitsiTokens);
const result: any = {};
return Object.entries(colorMap)
.reduce((result, [ token, value ]: [any, string]) => {
const color = allTokens[value as keyof typeof allTokens] || value;
// First pass: resolve tokens that reference allTokens directly
Object.entries(colorMap).forEach(([ token, value ]: [any, string]) => {
const color = allTokens[value as keyof typeof allTokens] || value;
return Object.assign(result, { [token]: color });
}, {});
result[token] = color;
});
// Second pass: resolve semantic tokens that reference other colorMap entries
// Recursively resolve until we get actual color values
const resolveToken = (value: string, depth = 0): string => {
// Prevent infinite loops
if (depth > 10) {
return value;
}
// If it's already a color (starts with # or rgb/rgba), return it
if (value.startsWith('#') || value.startsWith('rgb')) {
return value;
}
// Look up in the result map first (for colorMap token references)
if (result[value]) {
return resolveToken(result[value], depth + 1);
}
// Then look up in allTokens
const resolved = allTokens[value as keyof typeof allTokens];
if (resolved && resolved !== value && typeof resolved === 'string') {
return resolveToken(resolved, depth + 1);
}
return value;
};
// Third pass: recursively resolve all values
Object.entries(result).forEach(([ token, value ]) => {
result[token] = resolveToken(String(value));
});
return result;
}
/**

View File

@@ -4,6 +4,7 @@ import { connect, useSelector } from 'react-redux';
import { makeStyles } from 'tss-react/mui';
import { IReduxState } from '../../../app/types';
import { isTouchDevice, shouldEnableResize } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import { IconInfo, IconMessage, IconShareDoc, IconSubtitles } from '../../../base/icons/svg';
import { getLocalParticipant, getRemoteParticipants, isPrivateChatEnabledSelf } from '../../../base/participants/functions';
@@ -23,7 +24,16 @@ import {
setUserChatWidth,
toggleChat
} from '../../actions.web';
import { CHAT_SIZE, ChatTabs, OPTION_GROUPCHAT, SMALL_WIDTH_THRESHOLD } from '../../constants';
import {
CHAT_DRAG_HANDLE_HEIGHT,
CHAT_DRAG_HANDLE_OFFSET,
CHAT_DRAG_HANDLE_WIDTH,
CHAT_SIZE,
CHAT_TOUCH_HANDLE_SIZE,
ChatTabs,
OPTION_GROUPCHAT,
SMALL_WIDTH_THRESHOLD
} from '../../constants';
import { getChatMaxSize, getFocusedTab, isChatDisabled } from '../../functions';
import { IChatProps as AbstractProps } from '../../types';
@@ -104,10 +114,15 @@ interface IProps extends AbstractProps {
_width: number;
}
const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme, { _isResizing, width }) => {
const useStyles = makeStyles<{
_isResizing: boolean;
isTouch: boolean;
resizeEnabled: boolean;
width: number;
}>()((theme, { _isResizing, isTouch, resizeEnabled, width }) => {
return {
container: {
backgroundColor: theme.palette.ui01,
backgroundColor: theme.palette.chatBackground,
flexShrink: 0,
overflow: 'hidden',
position: 'relative',
@@ -115,11 +130,15 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
width: `${width}px`,
zIndex: 300,
'&:hover, &:focus-within': {
'& .dragHandleContainer': {
visibility: 'visible'
// On non-touch devices (desktop), show handle on hover
// On touch devices, handle is always visible if resize is enabled
...(!isTouch && {
'&:hover, &:focus-within': {
'& .dragHandleContainer': {
visibility: 'visible'
}
}
},
}),
'@media (max-width: 580px)': {
height: '100dvh',
@@ -146,7 +165,7 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
padding: `${theme.spacing(3)} ${theme.spacing(4)}`,
alignItems: 'center',
boxSizing: 'border-box',
color: theme.palette.text01,
color: theme.palette.chatMessageText,
...theme.typography.heading6,
lineHeight: 'unset',
fontWeight: theme.typography.heading6.fontWeight as any,
@@ -183,16 +202,23 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
dragHandleContainer: {
height: '100%',
width: '9px',
// Touch devices need larger hit target but positioned to not take extra space
width: isTouch ? `${CHAT_TOUCH_HANDLE_SIZE}px` : `${CHAT_DRAG_HANDLE_WIDTH}px`,
backgroundColor: 'transparent',
position: 'absolute',
cursor: 'col-resize',
display: 'flex',
display: resizeEnabled ? 'flex' : 'none', // Hide if resize not enabled
alignItems: 'center',
justifyContent: 'center',
visibility: 'hidden',
right: '4px',
// On touch devices, always visible if resize enabled. On desktop, hidden by default
visibility: (isTouch && resizeEnabled) ? 'visible' : 'hidden',
// Position touch handle centered on offset from edge, maintaining same gap as non-touch
right: isTouch
? `${CHAT_DRAG_HANDLE_OFFSET - Math.floor((CHAT_TOUCH_HANDLE_SIZE - CHAT_DRAG_HANDLE_WIDTH) / 2)}px`
: `${CHAT_DRAG_HANDLE_OFFSET}px`,
top: 0,
// Prevent touch scrolling while dragging
touchAction: 'none',
'&:hover': {
'& .dragHandle': {
@@ -210,10 +236,15 @@ const useStyles = makeStyles<{ _isResizing: boolean; width: number; }>()((theme,
},
dragHandle: {
// Keep the same visual appearance on all devices
backgroundColor: theme.palette.icon02,
height: '100px',
width: '3px',
borderRadius: '1px'
height: `${CHAT_DRAG_HANDLE_HEIGHT}px`,
width: `${CHAT_DRAG_HANDLE_WIDTH / 3}px`,
borderRadius: '1px',
// Make more visible when actively shown
...(isTouch && resizeEnabled && {
backgroundColor: theme.palette.icon01
})
},
privateMessageRecipientsList: {
@@ -246,7 +277,10 @@ const Chat = ({
return null;
}
const { classes, cx } = useStyles({ _isResizing, width: _width });
// Detect touch capability and screen size for resize functionality
const isTouch = isTouchDevice();
const resizeEnabled = shouldEnableResize();
const { classes, cx } = useStyles({ _isResizing, width: _width, isTouch, resizeEnabled });
const [ isMouseDown, setIsMouseDown ] = useState(false);
const [ mousePosition, setMousePosition ] = useState<number | null>(null);
const [ dragChatWidth, setDragChatWidth ] = useState<number | null>(null);
@@ -282,16 +316,21 @@ const Chat = ({
}, [ participants, defaultRemoteDisplayName, t, notifyTimestamp ]);
/**
* Handles mouse down on the drag handle.
* Handles pointer down on the drag handle.
* Supports both mouse and touch events via Pointer Events API.
*
* @param {MouseEvent} e - The mouse down event.
* @param {React.PointerEvent} e - The pointer down event.
* @returns {void}
*/
const onDragHandleMouseDown = useCallback((e: React.MouseEvent) => {
const onDragHandlePointerDown = useCallback((e: React.PointerEvent) => {
e.preventDefault();
e.stopPropagation();
// Store the initial mouse position and chat width
// Capture the pointer to ensure we receive all pointer events
// even if the pointer moves outside the element
(e.target as HTMLElement).setPointerCapture(e.pointerId);
// Store the initial pointer position and chat width
setIsMouseDown(true);
setMousePosition(e.clientX);
setDragChatWidth(_width);
@@ -299,7 +338,7 @@ const Chat = ({
// Indicate that resizing is in progress
dispatch(setChatIsResizing(true));
// Add visual feedback that we're dragging
// Add visual feedback that we're dragging (cursor for mouse, not visible on touch)
document.body.style.cursor = 'col-resize';
// Disable text selection during resize
@@ -307,11 +346,12 @@ const Chat = ({
}, [ _width, dispatch ]);
/**
* Drag handle mouse up handler.
* Drag handle pointer up handler.
* Supports both mouse and touch events via Pointer Events API.
*
* @returns {void}
*/
const onDragMouseUp = useCallback(() => {
const onDragPointerUp = useCallback(() => {
if (isMouseDown) {
setIsMouseDown(false);
dispatch(setChatIsResizing(false));
@@ -323,12 +363,13 @@ const Chat = ({
}, [ isMouseDown, dispatch ]);
/**
* Handles drag handle mouse move.
* Handles drag handle pointer move.
* Supports both mouse and touch events via Pointer Events API.
*
* @param {MouseEvent} e - The mousemove event.
* @param {PointerEvent} e - The pointermove event.
* @returns {void}
*/
const onChatResize = useCallback(throttle((e: MouseEvent) => {
const onChatResize = useCallback(throttle((e: PointerEvent) => {
if (isMouseDown && mousePosition !== null && dragChatWidth !== null) {
// For chat panel resizing on the left edge:
// - Dragging left (decreasing X coordinate) should make the panel wider
@@ -352,14 +393,14 @@ const Chat = ({
// Set up event listeners when component mounts
useEffect(() => {
document.addEventListener('mouseup', onDragMouseUp);
document.addEventListener('mousemove', onChatResize);
document.addEventListener('pointerup', onDragPointerUp);
document.addEventListener('pointermove', onChatResize);
return () => {
document.removeEventListener('mouseup', onDragMouseUp);
document.removeEventListener('mousemove', onChatResize);
document.removeEventListener('pointerup', onDragPointerUp);
document.removeEventListener('pointermove', onChatResize);
};
}, [ onDragMouseUp, onChatResize ]);
}, [ onDragPointerUp, onChatResize ]);
/**
* Sends a text message.
@@ -600,7 +641,7 @@ const Chat = ({
(isMouseDown || _isResizing) && 'visible',
'dragHandleContainer'
) }
onMouseDown = { onDragHandleMouseDown }>
onPointerDown = { onDragHandlePointerDown }>
<div className = { cx(classes.dragHandle, 'dragHandle') } />
</div>
</div> : null

View File

@@ -37,7 +37,7 @@ const styles = (_theme: Theme, { _chatWidth }: IProps) => {
}
},
chatDisabled: {
borderTop: `1px solid ${_theme.palette.ui02}`,
borderTop: `1px solid ${_theme.palette.chatInputBorder}`,
boxSizing: 'border-box' as const,
padding: _theme.spacing(4),
textAlign: 'center' as const,

View File

@@ -43,7 +43,7 @@ const useStyles = makeStyles()((theme: Theme) => {
chatMessage: {
display: 'inline-flex',
padding: '12px',
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.chatMessageRemote,
borderRadius: '4px 12px 12px 12px',
maxWidth: '100%',
marginTop: '4px',
@@ -66,21 +66,21 @@ const useStyles = makeStyles()((theme: Theme) => {
},
'&.privatemessage': {
backgroundColor: theme.palette.support05
backgroundColor: theme.palette.chatMessagePrivate
},
'&.local': {
backgroundColor: theme.palette.ui04,
backgroundColor: theme.palette.chatMessageLocal,
borderRadius: '12px 4px 12px 12px',
'&.privatemessage': {
backgroundColor: theme.palette.support05
backgroundColor: theme.palette.chatMessagePrivate
},
'&.local': {
backgroundColor: theme.palette.ui04,
backgroundColor: theme.palette.chatMessageLocal,
borderRadius: '12px 4px 12px 12px',
'&.privatemessage': {
backgroundColor: theme.palette.support05
backgroundColor: theme.palette.chatMessagePrivate
}
},
@@ -91,7 +91,7 @@ const useStyles = makeStyles()((theme: Theme) => {
},
'&.lobbymessage': {
backgroundColor: theme.palette.support05
backgroundColor: theme.palette.chatMessagePrivate
}
},
'&.error': {
@@ -100,7 +100,7 @@ const useStyles = makeStyles()((theme: Theme) => {
fontWeight: 100
},
'&.lobbymessage': {
backgroundColor: theme.palette.support05
backgroundColor: theme.palette.chatMessagePrivate
}
},
sideBySideContainer: {
@@ -146,7 +146,7 @@ const useStyles = makeStyles()((theme: Theme) => {
},
displayName: {
...theme.typography.labelBold,
color: theme.palette.text02,
color: theme.palette.chatSenderName,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
@@ -155,18 +155,18 @@ const useStyles = makeStyles()((theme: Theme) => {
},
userMessage: {
...theme.typography.bodyShortRegular,
color: theme.palette.text01,
color: theme.palette.chatMessageText,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word'
},
privateMessageNotice: {
...theme.typography.labelRegular,
color: theme.palette.text02,
color: theme.palette.chatPrivateNotice,
marginTop: theme.spacing(1)
},
timestamp: {
...theme.typography.labelRegular,
color: theme.palette.text03,
color: theme.palette.chatTimestamp,
marginTop: theme.spacing(1),
marginLeft: theme.spacing(1),
whiteSpace: 'nowrap',
@@ -174,12 +174,12 @@ const useStyles = makeStyles()((theme: Theme) => {
},
reactionsPopover: {
padding: theme.spacing(2),
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.chatInputBackground,
borderRadius: theme.shape.borderRadius,
maxWidth: '150px',
maxHeight: '400px',
overflowY: 'auto',
color: theme.palette.text01
color: theme.palette.chatMessageText
},
reactionItem: {
display: 'flex',

View File

@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
padding: '16px',
flex: 1,
boxSizing: 'border-box',
color: theme.palette.text01
color: theme.palette.chatMessageText
},
container: {
display: 'flex',
@@ -48,7 +48,7 @@ const useStyles = makeStyles()(theme => {
boxSizing: 'border-box',
flexDirection: 'column',
gap: '16px',
color: theme.palette.text01,
color: theme.palette.chatMessageText,
textAlign: 'center'
},
emptyIcon: {
@@ -62,7 +62,7 @@ const useStyles = makeStyles()(theme => {
},
emptyState: {
...theme.typography.bodyLongBold,
color: theme.palette.text02
color: theme.palette.chatSenderName
}
};
});
@@ -103,7 +103,7 @@ const ClosedCaptionsTab = ({
<div className = { classes.emptyContent }>
<Icon
className = { classes.emptyIcon }
color = { theme.palette.icon03 }
color = { theme.palette.chatEmptyText }
src = { IconSubtitles } />
<span className = { classes.emptyState }>
{ t('closedCaptionsTab.emptyState') }

View File

@@ -12,7 +12,7 @@ const useStyles = makeStyles()((theme: Theme) => {
display: 'flex',
flexDirection: 'row',
borderRadius: '4px',
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.chatInputBackground
},
emojiButton: {

View File

@@ -46,12 +46,12 @@ const useStyles = makeStyles()(theme => {
// Add background to button container to hide text underneath in chat context
'& > div:last-child': {
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.chatMessageRemote,
paddingLeft: theme.spacing(2)
},
'&:hover > div:last-child': {
backgroundColor: theme.palette.ui03
backgroundColor: theme.palette.chatInputBackground
}
},
@@ -66,7 +66,7 @@ const useStyles = makeStyles()(theme => {
deletedFileMessage: {
...theme.typography.bodyShortRegular,
fontStyle: 'italic',
color: theme.palette.text02,
color: theme.palette.fileSharingEmptyText,
padding: theme.spacing(1, 0)
}
};

View File

@@ -39,14 +39,14 @@ const useStyles = makeStyles()(theme => {
}
},
menuPanel: {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.chatInputBackground,
borderRadius: theme.shape.borderRadius,
boxShadow: theme.shadows[3],
overflow: 'hidden'
},
copiedMessage: {
position: 'fixed',
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.chatInputBackground,
color: 'white',
padding: '4px 8px',
borderRadius: '4px',

View File

@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
backgroundColor: theme.palette.support05,
borderRadius: theme.shape.borderRadius,
...theme.typography.bodyShortRegular,
color: theme.palette.text01
color: theme.palette.chatRecipientText
},
text: {

View File

@@ -22,7 +22,7 @@ interface IProps extends ISubtitle {
const useStyles = makeStyles()(theme => {
return {
messageContainer: {
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.subtitleMessageBackground,
borderRadius: '4px 12px 12px 12px',
padding: '12px',
maxWidth: '100%',
@@ -39,7 +39,7 @@ const useStyles = makeStyles()(theme => {
messageHeader: {
...theme.typography.labelBold,
color: theme.palette.text02,
color: theme.palette.subtitleMessageSender,
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
overflow: 'hidden',
@@ -49,14 +49,14 @@ const useStyles = makeStyles()(theme => {
messageText: {
...theme.typography.bodyShortRegular,
color: theme.palette.text01,
color: theme.palette.subtitleMessageText,
whiteSpace: 'pre-wrap',
wordBreak: 'break-word'
},
timestamp: {
...theme.typography.labelRegular,
color: theme.palette.text03,
color: theme.palette.subtitleMessageTime,
marginTop: theme.spacing(1)
},

View File

@@ -33,6 +33,23 @@ export const MESSAGE_TYPE_REMOTE = 'remote';
export const SMALL_WIDTH_THRESHOLD = 580;
/**
* Drag handle dimensions for resizable chat.
*/
export const CHAT_DRAG_HANDLE_WIDTH = 9;
export const CHAT_DRAG_HANDLE_HEIGHT = 100;
/**
* Touch target size for chat drag handle on touch devices.
* Provides adequate hit area (44px) for comfortable tapping.
*/
export const CHAT_TOUCH_HANDLE_SIZE = 44;
/**
* Offset from edge for positioning the chat drag handle.
*/
export const CHAT_DRAG_HANDLE_OFFSET = 4;
/**
* Lobby message type.

View File

@@ -14,6 +14,7 @@ import { setColorAlpha } from '../../../base/util/helpers';
import { openChat, setFocusedTab } from '../../../chat/actions.web';
import Chat from '../../../chat/components/web/Chat';
import { ChatTabs } from '../../../chat/constants';
import CustomPanel from '../../../custom-panel/components/web/CustomPanel';
import { isFileUploadingEnabled, processFiles } from '../../../file-sharing/functions.any';
import MainFilmstrip from '../../../filmstrip/components/web/MainFilmstrip';
import ScreenshareFilmstrip from '../../../filmstrip/components/web/ScreenshareFilmstrip';
@@ -326,6 +327,7 @@ class Conference extends AbstractConference<IProps, any> {
{ _showVisitorsQueue && <VisitorsQueue />}
</div>
<ParticipantsPane />
<CustomPanel />
<ReactionAnimations />
</div>
);

View File

@@ -7,7 +7,7 @@ const useStyles = makeStyles()(theme => {
return {
timer: {
...theme.typography.labelRegular,
color: theme.palette.text01,
color: theme.palette.conferenceTimerText,
padding: '6px 8px',
backgroundColor: 'rgba(0, 0, 0, 0.8)',
boxSizing: 'border-box',

View File

@@ -15,8 +15,8 @@ const useStyles = makeStyles()(theme => {
},
message: {
backgroundColor: theme.palette.uiBackground,
color: theme.palette.text01,
backgroundColor: theme.palette.conferenceNoticeBackground,
color: theme.palette.conferenceNoticeText,
padding: '3px',
borderRadius: '5px'
}

View File

@@ -13,7 +13,7 @@ const useStyles = makeStyles()(theme => {
return {
label: {
backgroundColor: theme.palette.warning02,
color: theme.palette.uiBackground
color: theme.palette.conferenceRaisedHandLabelText
}
};
});
@@ -35,7 +35,7 @@ const RaisedHandsCountLabel = () => {
accessibilityText = { t('raisedHandsLabel') }
className = { styles.label }
icon = { IconRaiseHand }
iconColor = { theme.palette.icon04 }
iconColor = { theme.palette.conferenceRaisedHandLabelIcon }
id = 'raisedHandsCountLabel'
onClick = { onClick }
text = { `${raisedHandsCount}` } />

View File

@@ -10,7 +10,7 @@ const useStyles = makeStyles()(theme => {
return {
container: {
...theme.typography.bodyLongRegular,
color: theme.palette.text01,
color: theme.palette.conferenceSubjectText,
padding: '2px 16px',
backgroundColor: 'rgba(0, 0, 0, 0.6)',
maxWidth: '324px',

View File

@@ -14,13 +14,13 @@ import Input from '../../../base/ui/components/web/Input';
const useStyles = makeStyles()(theme => {
return {
content: {
color: theme.palette.text01
color: theme.palette.dialogText
},
info: {
background: theme.palette.ui01,
background: theme.palette.dialogBackground,
...theme.typography.labelRegular,
color: theme.palette.text02,
color: theme.palette.dialogSecondaryText,
marginTop: theme.spacing(2)
},

View File

@@ -179,7 +179,7 @@ const styles = (theme: Theme) => {
},
'&.status-lost': {
backgroundColor: theme.palette.ui05
backgroundColor: theme.palette.connectionIndicatorLost
},
'&.status-other': {

View File

@@ -0,0 +1,14 @@
/**
* Action type to signal the closing of the custom panel.
*/
export const CUSTOM_PANEL_CLOSE = 'CUSTOM_PANEL_CLOSE';
/**
* Action type to signal the opening of the custom panel.
*/
export const CUSTOM_PANEL_OPEN = 'CUSTOM_PANEL_OPEN';
/**
* Action type to enable or disable the custom panel dynamically.
*/
export const SET_CUSTOM_PANEL_ENABLED = 'SET_CUSTOM_PANEL_ENABLED';

View File

@@ -0,0 +1,40 @@
import {
CUSTOM_PANEL_CLOSE,
CUSTOM_PANEL_OPEN,
SET_CUSTOM_PANEL_ENABLED
} from './actionTypes';
/**
* Action to close the custom panel.
*
* @returns {Object} The action object.
*/
export function close() {
return {
type: CUSTOM_PANEL_CLOSE
};
}
/**
* Action to open the custom panel.
*
* @returns {Object} The action object.
*/
export function open() {
return {
type: CUSTOM_PANEL_OPEN
};
}
/**
* Action to enable or disable the custom panel dynamically.
*
* @param {boolean} enabled - Whether the custom panel should be enabled.
* @returns {Object} The action object.
*/
export function setCustomPanelEnabled(enabled: boolean) {
return {
type: SET_CUSTOM_PANEL_ENABLED,
enabled
};
}

View File

@@ -0,0 +1,10 @@
/**
* Custom panel placeholder component.
* This file is overridden by jitsi-meet-branding at build time
* to provide the actual panel implementation with iframe content.
*
* @returns {null} This placeholder renders nothing.
*/
const CustomPanel = (): null => null;
export default CustomPanel;

View File

@@ -0,0 +1,10 @@
/**
* Custom panel button placeholder component.
* This file is overridden by jitsi-meet-branding at build time
* to provide the actual button implementation with custom icon.
*
* @returns {null} This placeholder renders nothing.
*/
const CustomPanelButton = (): null => null;
export default CustomPanelButton;

View File

@@ -0,0 +1,4 @@
/**
* Default width for the custom panel in pixels.
*/
export const DEFAULT_CUSTOM_PANEL_WIDTH = 315;

View File

@@ -0,0 +1,67 @@
import { IReduxState } from '../app/types';
import { DEFAULT_CUSTOM_PANEL_WIDTH } from './constants';
/**
* Returns whether the custom panel is enabled based on Redux state.
* The feature is disabled by default and can be enabled dynamically via console.
*
* @param {IReduxState} state - The Redux state.
* @returns {boolean} Whether the custom panel is enabled.
*/
export function isCustomPanelEnabled(state: IReduxState): boolean {
return Boolean(state['features/custom-panel']?.enabled);
}
/**
* Returns the custom panel URL.
* Override to provide the actual URL.
*
* @returns {string} The custom panel URL.
*/
export function getCustomPanelUrl(): string {
return '';
}
/**
* Returns the custom panel button icon.
* Override to provide the actual icon.
*
* @returns {Function | undefined} The icon component.
*/
export function getCustomPanelIcon(): Function | undefined {
return undefined;
}
/**
* Returns the configured panel width.
*
* @returns {number} The panel width in pixels.
*/
export function getCustomPanelConfiguredWidth(): number {
return DEFAULT_CUSTOM_PANEL_WIDTH;
}
/**
* Returns whether the custom panel is currently open.
*
* @param {IReduxState} state - The Redux state.
* @returns {boolean} Whether the custom panel is open.
*/
export function getCustomPanelOpen(state: IReduxState): boolean {
return Boolean(state['features/custom-panel']?.isOpen);
}
/**
* Returns the current panel width (0 if closed or disabled).
*
* @param {IReduxState} state - The Redux state.
* @returns {number} The panel width in pixels.
*/
export function getCustomPanelWidth(state: IReduxState): number {
if (!isCustomPanelEnabled(state)) {
return 0;
}
return getCustomPanelOpen(state) ? getCustomPanelConfiguredWidth() : 0;
}

View File

@@ -0,0 +1,29 @@
import { useSelector } from 'react-redux';
import CustomPanelButton from './components/web/CustomPanelButton';
import { isCustomPanelEnabled } from './functions';
/**
* Configuration for the custom panel toolbar button.
*/
const customPanel = {
key: 'custom-panel',
Content: CustomPanelButton,
group: 2
};
/**
* A hook that returns the custom panel button if the feature is enabled.
* Uses useSelector for reactive updates when the feature is toggled dynamically.
*
* @returns {Object | undefined} The button configuration or undefined if disabled.
*/
export function useCustomPanelButton() {
const enabled = useSelector(isCustomPanelEnabled);
if (enabled) {
return customPanel;
}
return undefined;
}

View File

@@ -0,0 +1,4 @@
/**
* Custom panel middleware placeholder.
* Override to add custom panel functionality.
*/

View File

@@ -0,0 +1,59 @@
import ReducerRegistry from '../base/redux/ReducerRegistry';
import {
CUSTOM_PANEL_CLOSE,
CUSTOM_PANEL_OPEN,
SET_CUSTOM_PANEL_ENABLED
} from './actionTypes';
/**
* The state of the custom panel feature.
*/
export interface ICustomPanelState {
/**
* Whether the custom panel feature is enabled.
* This can be toggled dynamically via console.
*/
enabled: boolean;
/**
* Whether the custom panel is currently open.
*/
isOpen: boolean;
}
const DEFAULT_STATE: ICustomPanelState = {
enabled: false,
isOpen: false
};
/**
* Listen for actions that mutate the custom panel state.
*/
ReducerRegistry.register(
'features/custom-panel', (state: ICustomPanelState = DEFAULT_STATE, action): ICustomPanelState => {
switch (action.type) {
case CUSTOM_PANEL_CLOSE:
return {
...state,
isOpen: false
};
case CUSTOM_PANEL_OPEN:
return {
...state,
isOpen: true
};
case SET_CUSTOM_PANEL_ENABLED:
return {
...state,
enabled: action.enabled
};
default:
return state;
}
}
);

View File

@@ -33,12 +33,12 @@ const useStyles = makeStyles()((theme: Theme) => {
contentPane: {
display: 'flex',
flexDirection: 'column',
background: theme.palette.ui01,
border: `1px solid ${theme.palette.ui03}`,
background: theme.palette.deepLinkingBackground,
border: `1px solid ${theme.palette.deepLinkingBorder}`,
padding: 40,
borderRadius: 16,
maxWidth: 410,
color: theme.palette.text01
color: theme.palette.deepLinkingText
},
logo: {
marginBottom: 32
@@ -66,14 +66,14 @@ const useStyles = makeStyles()((theme: Theme) => {
marginTop: 40,
height: 1,
maxWidth: 390,
background: theme.palette.ui03
background: theme.palette.deepLinkingSeparator
},
label: {
marginTop: 40,
...theme.typography.labelRegular,
color: theme.palette.text02,
color: theme.palette.deepLinkingLabelText,
'& a': {
color: theme.palette.link01
color: theme.palette.deepLinkingLink
}
}
};

View File

@@ -44,7 +44,7 @@ const useStyles = makeStyles()((theme: Theme) => {
flexDirection: 'column',
padding: `${PADDINGS.topBottom}px ${PADDINGS.leftRight}px`,
maxWidth: 410,
color: theme.palette.text01
color: theme.palette.deepLinkingText
},
launchingMeetingLabel: {
marginTop: 24,
@@ -89,7 +89,7 @@ const useStyles = makeStyles()((theme: Theme) => {
marginTop: '32px',
height: 1,
width: `calc(100% + ${2 * PADDINGS.leftRight}px)`,
background: theme.palette.ui03
background: theme.palette.deepLinkingSeparator
}
};
});

View File

@@ -26,7 +26,7 @@ const useStyles = makeStyles()(theme => {
flex: 1,
height: '4px',
borderRadius: '1px',
backgroundColor: theme.palette.ui04,
backgroundColor: theme.palette.labelBackground,
marginRight: theme.spacing(1),
'&:last-of-type': {

View File

@@ -20,7 +20,7 @@ const useStyles = makeStyles()(theme => {
label: {
...theme.typography.bodyShortRegular,
color: theme.palette.text01,
color: theme.palette.deviceSelectorText,
marginBottom: theme.spacing(2)
},

View File

@@ -56,11 +56,11 @@ const useStyles = makeStyles()(theme => {
width: '100%',
boxSizing: 'border-box',
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.uiBackground,
backgroundColor: theme.palette.deviceSelectorTextBackground,
padding: '10px 16px',
textAlign: 'center',
...theme.typography.bodyShortRegular,
border: `1px solid ${theme.palette.ui03}`
border: `1px solid ${theme.palette.deviceSelectorBorder}`
}
};
});

View File

@@ -32,7 +32,7 @@ const useStyles = makeStyles()(theme => {
borderRadius: '3px',
overflow: 'hidden',
marginBottom: theme.spacing(4),
backgroundColor: theme.palette.uiBackground
backgroundColor: theme.palette.deviceSelectorVideoPreview
},
video: {
@@ -42,7 +42,7 @@ const useStyles = makeStyles()(theme => {
},
errorText: {
color: theme.palette.text01,
color: theme.palette.deviceSelectorText,
left: 0,
position: 'absolute',
right: 0,

View File

@@ -31,7 +31,7 @@ const useStyles = makeStyles()(theme => {
ratingLabel: {
...theme.typography.bodyShortBold,
color: theme.palette.text01,
color: theme.palette.dialogText,
marginBottom: theme.spacing(2),
height: '20px'
},

View File

@@ -84,7 +84,7 @@ const useStyles = makeStyles()(theme => {
},
fileItem: {
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.fileSharingItemBackground,
borderRadius: theme.shape.borderRadius,
boxSizing: 'border-box',
display: 'flex',
@@ -106,7 +106,7 @@ const useStyles = makeStyles()(theme => {
},
'&:hover': {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.fileSharingItemBorder,
'& .actionIconVisibility': {
opacity: 1
@@ -186,7 +186,7 @@ const useStyles = makeStyles()(theme => {
},
progressBar: {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.fileSharingItemBorder,
borderRadius: theme.shape.borderRadius,
height: 4,
overflow: 'hidden',
@@ -275,7 +275,7 @@ const FileItem = ({
<>
<div className = { classes.fileIconContainer }>
<Icon
color = { BaseTheme.palette.icon01 }
color = { BaseTheme.palette.fileSharingText }
size = { iconSize }
src = { getFileIcon(file.fileType) } />
</div>
@@ -320,7 +320,7 @@ const FileItem = ({
onClick = { handleDownload }
type = 'button'>
<Icon
color = { BaseTheme.palette.icon01 }
color = { BaseTheme.palette.fileSharingText }
size = { 24 }
src = { IconDownload } />
</button>
@@ -335,7 +335,7 @@ const FileItem = ({
onClick = { handleRemove }
type = 'button'>
<Icon
color = { BaseTheme.palette.icon01 }
color = { BaseTheme.palette.fileSharingText }
size = { 24 }
src = { IconTrash } />
</button>

View File

@@ -32,8 +32,8 @@ const useStyles = makeStyles()(theme => {
},
dropZone: {
backgroundColor: theme.palette.ui02,
border: `2px dashed ${theme.palette.ui03}`,
backgroundColor: theme.palette.fileSharingItemBackground,
border: `2px dashed ${theme.palette.fileSharingItemBorder}`,
borderRadius: theme.shape.borderRadius,
bottom: 0,
left: 0,
@@ -44,7 +44,7 @@ const useStyles = makeStyles()(theme => {
zIndex: 0,
'&.dragging': {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.fileSharingItemBorder,
borderColor: theme.palette.action01,
opacity: 0.8,
zIndex: 2
@@ -85,7 +85,7 @@ const useStyles = makeStyles()(theme => {
noFilesText: {
...theme.typography.bodyLongBold,
color: theme.palette.text02,
color: theme.palette.fileSharingEmptyText,
padding: '0 24px',
textAlign: 'center'
},
@@ -189,7 +189,7 @@ const FileSharing = () => {
tabIndex = { 0 }>
<Icon
className = { classes.uploadIcon }
color = { BaseTheme.palette.icon03 }
color = { BaseTheme.palette.fileSharingEmptyIcon }
size = { 160 }
src = { IconCloudUpload } />
<span className = { classes.noFilesText }>

View File

@@ -10,7 +10,7 @@ import { withStyles } from 'tss-react/mui';
import { ACTION_SHORTCUT_TRIGGERED, createShortcutEvent, createToolbarEvent } from '../../../analytics/AnalyticsEvents';
import { sendAnalytics } from '../../../analytics/functions';
import { IReduxState, IStore } from '../../../app/types';
import { isMobileBrowser } from '../../../base/environment/utils';
import { isMobileBrowser, isTouchDevice, shouldEnableResize } from '../../../base/environment/utils';
import { translate } from '../../../base/i18n/functions';
import Icon from '../../../base/icons/components/Icon';
import { IconArrowDown, IconArrowUp } from '../../../base/icons/svg';
@@ -32,12 +32,17 @@ import {
import {
ASPECT_RATIO_BREAKPOINT,
DEFAULT_FILMSTRIP_WIDTH,
DRAG_HANDLE_HEIGHT,
DRAG_HANDLE_TOP_PANEL_HEIGHT,
DRAG_HANDLE_TOP_PANEL_WIDTH,
DRAG_HANDLE_WIDTH,
FILMSTRIP_TYPE,
MIN_STAGE_VIEW_HEIGHT,
MIN_STAGE_VIEW_WIDTH,
TILE_HORIZONTAL_MARGIN,
TILE_VERTICAL_MARGIN,
TOP_FILMSTRIP_HEIGHT
TOP_FILMSTRIP_HEIGHT,
TOUCH_DRAG_HANDLE_PADDING
} from '../../constants';
import {
getVerticalViewMaxWidth,
@@ -52,6 +57,21 @@ import ThumbnailWrapper from './ThumbnailWrapper';
const BACKGROUND_COLOR = 'rgba(51, 51, 51, .5)';
const TOUCH_DEVICE_PADDING = {
paddingLeft: `${TOUCH_DRAG_HANDLE_PADDING}px`,
paddingRight: `${TOUCH_DRAG_HANDLE_PADDING}px`,
paddingTop: 0,
paddingBottom: 0
};
const TOUCH_DEVICE_TOP_PANEL_PADDING = {
paddingLeft: 0,
paddingRight: 0,
paddingTop: `${TOUCH_DRAG_HANDLE_PADDING}px`,
paddingBottom: `${TOUCH_DRAG_HANDLE_PADDING}px`
};
const NON_TOUCH_DEVICE_PANEL = {
pading: 0
};
/**
* Creates the styles for the component.
@@ -61,6 +81,14 @@ const BACKGROUND_COLOR = 'rgba(51, 51, 51, .5)';
* @returns {Object}
*/
function styles(theme: Theme, props: IProps) {
const { _topPanelFilmstrip: isTopPanel } = props;
const _isTouchDevice = isTouchDevice();
const resizeEnabled = shouldEnableResize();
const handlePaddding = _isTouchDevice
? (isTopPanel ? TOUCH_DEVICE_TOP_PANEL_PADDING : TOUCH_DEVICE_PADDING)
: NON_TOUCH_DEVICE_PANEL;
const result = {
toggleFilmstripContainer: {
display: 'flex',
@@ -79,7 +107,7 @@ function styles(theme: Theme, props: IProps) {
zIndex: 1,
'&:hover, &:focus-within': {
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.filmstripBackground
}
},
@@ -122,23 +150,27 @@ function styles(theme: Theme, props: IProps) {
right: 0,
bottom: 0,
'&:hover, &:focus-within': {
'& .resizable-filmstrip': {
backgroundColor: BACKGROUND_COLOR
},
// On touch devices, handle is always visible via base styles, so no hover needed.
// On desktop, show handle on hover/focus.
...(!_isTouchDevice && {
'&:hover, &:focus-within': {
'& .resizable-filmstrip': {
backgroundColor: BACKGROUND_COLOR
},
'& .filmstrip-hover': {
backgroundColor: BACKGROUND_COLOR
},
'& .filmstrip-hover': {
backgroundColor: BACKGROUND_COLOR
},
'& .toggleFilmstripContainer': {
opacity: 1
},
'& .toggleFilmstripContainer': {
opacity: 1
},
'& .dragHandleContainer': {
visibility: 'visible' as const
'& .dragHandleContainer': {
visibility: 'visible' as const
}
}
},
}),
'.horizontal-filmstrip &.hidden': {
bottom: '-50px',
@@ -156,10 +188,10 @@ function styles(theme: Theme, props: IProps) {
},
filmstripBackground: {
backgroundColor: theme.palette.uiBackground,
backgroundColor: theme.palette.filmstripBackgroundHover,
'&:hover, &:focus-within': {
backgroundColor: theme.palette.uiBackground
backgroundColor: theme.palette.filmstripBackgroundHover
}
},
@@ -187,18 +219,26 @@ function styles(theme: Theme, props: IProps) {
dragHandleContainer: {
height: '100%',
width: '9px',
width: `${DRAG_HANDLE_WIDTH}px`,
backgroundColor: 'transparent',
position: 'relative' as const,
cursor: 'col-resize',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
visibility: 'hidden' as const,
// On touch devices, always visible if resize enabled. On desktop, hidden by default
visibility: (_isTouchDevice && resizeEnabled) ? 'visible' as const : 'hidden' as const,
marginLeft: 0,
marginTop: 0,
// Touch devices get padding for easier tapping
// Vertical filmstrip: left/right padding. Top panel: top/bottom padding.
...handlePaddding,
// Prevent touch scrolling while dragging
touchAction: 'none',
'&:hover': {
'& .dragHandle': {
backgroundColor: theme.palette.icon01
backgroundColor: theme.palette.filmstripDragHandleHover
}
},
@@ -206,27 +246,28 @@ function styles(theme: Theme, props: IProps) {
visibility: 'visible' as const,
'& .dragHandle': {
backgroundColor: theme.palette.icon01
backgroundColor: theme.palette.filmstripDragHandleHover
}
},
'&.top-panel': {
order: 2,
width: '100%',
height: '9px',
height: `${DRAG_HANDLE_WIDTH}px`,
cursor: 'row-resize',
'& .dragHandle': {
height: '3px',
width: '100px'
height: `${DRAG_HANDLE_TOP_PANEL_HEIGHT}px`,
width: `${DRAG_HANDLE_TOP_PANEL_WIDTH}px`
}
}
},
dragHandle: {
backgroundColor: theme.palette.icon02,
height: '100px',
width: '3px',
// Keep the same visual appearance on all devices
backgroundColor: theme.palette.filmstripDragHandle,
height: `${DRAG_HANDLE_HEIGHT}px`,
width: `${DRAG_HANDLE_WIDTH / 3}px`,
borderRadius: '1px'
}
};
@@ -313,6 +354,11 @@ export interface IProps extends WithTranslation {
*/
_isToolboxVisible: Boolean;
/**
* Whether the device has touch capability.
*/
_isTouchDevice?: boolean;
/**
* Whether or not the current layout is vertical filmstrip.
*/
@@ -358,6 +404,11 @@ export interface IProps extends WithTranslation {
*/
_resizableFilmstrip: boolean;
/**
* Whether resize functionality should be enabled based on device and screen size.
*/
_resizeEnabled?: boolean;
/**
* The number of rows in tile view.
*/
@@ -491,8 +542,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
this._onGridItemsRendered = this._onGridItemsRendered.bind(this);
this._onListItemsRendered = this._onListItemsRendered.bind(this);
this._onToggleButtonTouch = this._onToggleButtonTouch.bind(this);
this._onDragHandleMouseDown = this._onDragHandleMouseDown.bind(this);
this._onDragMouseUp = this._onDragMouseUp.bind(this);
this._onDragHandlePointerDown = this._onDragHandlePointerDown.bind(this);
this._onDragHandleClick = this._onDragHandleClick.bind(this);
this._onDragHandleTouchStart = this._onDragHandleTouchStart.bind(this);
this._onDragPointerUp = this._onDragPointerUp.bind(this);
this._onFilmstripResize = this._onFilmstripResize.bind(this);
this._throttledResize = throttle(
@@ -516,10 +569,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
handler: this._onShortcutToggleFilmstrip
}));
document.addEventListener('mouseup', this._onDragMouseUp);
document.addEventListener('pointerup', this._onDragPointerUp);
// @ts-ignore
document.addEventListener('mousemove', this._throttledResize);
document.addEventListener('pointermove', this._throttledResize);
}
/**
@@ -530,10 +583,10 @@ class Filmstrip extends PureComponent <IProps, IState> {
override componentWillUnmount() {
this.props.dispatch(unregisterShortcut('F'));
document.removeEventListener('mouseup', this._onDragMouseUp);
document.removeEventListener('pointerup', this._onDragPointerUp);
// @ts-ignore
document.removeEventListener('mousemove', this._throttledResize);
document.removeEventListener('pointermove', this._throttledResize);
}
/**
@@ -678,7 +731,9 @@ class Filmstrip extends PureComponent <IProps, IState> {
(isMouseDown || _alwaysShowResizeBar) && 'visible',
_topPanelFilmstrip && 'top-panel')
}
onMouseDown = { this._onDragHandleMouseDown }>
onClick = { this._onDragHandleClick }
onPointerDown = { this._onDragHandlePointerDown }
onTouchStart = { this._onDragHandleTouchStart }>
<div className = { clsx(classes.dragHandle, 'dragHandle') } />
</div>
{filmstrip}
@@ -691,14 +746,23 @@ class Filmstrip extends PureComponent <IProps, IState> {
}
/**
* Handles mouse down on the drag handle.
* Handles pointer down on the drag handle.
* Supports both mouse and touch events via Pointer Events API.
*
* @param {MouseEvent} e - The mouse down event.
* @param {React.PointerEvent} e - The pointer down event.
* @returns {void}
*/
_onDragHandleMouseDown(e: React.MouseEvent) {
_onDragHandlePointerDown(e: React.PointerEvent) {
const { _topPanelFilmstrip, _topPanelHeight, _verticalFilmstripWidth } = this.props;
// Prevent toolbar from appearing and stop event propagation
e.preventDefault();
e.stopPropagation();
// Capture the pointer to ensure we receive all pointer events
// even if the pointer moves outside the element
(e.target as HTMLElement).setPointerCapture(e.pointerId);
this.setState({
isMouseDown: true,
mousePosition: _topPanelFilmstrip ? e.clientY : e.clientX,
@@ -709,11 +773,33 @@ class Filmstrip extends PureComponent <IProps, IState> {
}
/**
* Drag handle mouse up handler.
* Prevents click events on drag handle from triggering toolbar.
*
* @param {React.MouseEvent} e - The click event.
* @returns {void}
*/
_onDragHandleClick(e: React.MouseEvent) {
e.preventDefault();
e.stopPropagation();
}
/**
* Prevents touch start events on drag handle from triggering toolbar.
*
* @param {React.TouchEvent} e - The touch start event.
* @returns {void}
*/
_onDragHandleTouchStart(e: React.TouchEvent) {
e.stopPropagation();
}
/**
* Drag handle pointer up handler.
* Supports both mouse and touch events via Pointer Events API.
*
* @returns {void}
*/
_onDragMouseUp() {
_onDragPointerUp() {
if (this.state.isMouseDown) {
this.setState({
isMouseDown: false
@@ -723,12 +809,13 @@ class Filmstrip extends PureComponent <IProps, IState> {
}
/**
* Handles drag handle mouse move.
* Handles drag handle pointer move.
* Supports both mouse and touch events via Pointer Events API.
*
* @param {MouseEvent} e - The mousemove event.
* @param {PointerEvent} e - The pointermove event.
* @returns {void}
*/
_onFilmstripResize(e: React.MouseEvent) {
_onFilmstripResize(e: PointerEvent) {
if (this.state.isMouseDown) {
const {
dispatch,
@@ -1163,4 +1250,4 @@ function _mapStateToProps(state: IReduxState, ownProps: any) {
};
}
export default withStyles(translate(connect(_mapStateToProps)(Filmstrip)), styles);
export default translate(connect(_mapStateToProps)(withStyles(Filmstrip, styles)));

View File

@@ -67,7 +67,7 @@ const RaisedHandIndicator = ({
<div className = { styles.raisedHandIndicator }>
<BaseIndicator
icon = { IconRaiseHand }
iconColor = { theme.palette.uiBackground }
iconColor = { theme.palette.thumbnailRaisedHandIcon }
iconSize = { iconSize }
tooltipKey = 'raisedHand'
tooltipPosition = { tooltipPosition } />

View File

@@ -305,7 +305,7 @@ const defaultStyles = (theme: Theme) => {
height: '100%',
width: '100%',
borderRadius: '4px',
backgroundColor: theme.palette.ui02
backgroundColor: theme.palette.thumbnailBackground
},
borderIndicator: {
@@ -341,7 +341,7 @@ const defaultStyles = (theme: Theme) => {
justifyContent: 'center',
alignItems: 'center',
overflow: 'hidden',
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.thumbnailBackground,
'& img': {
maxWidth: '100%',
@@ -356,7 +356,7 @@ const defaultStyles = (theme: Theme) => {
zIndex: 1,
width: '100%',
height: '100%',
backgroundColor: `${theme.palette.uiBackground}`,
backgroundColor: theme.palette.thumbnailTintBackground,
opacity: 0.8
},

View File

@@ -269,6 +269,19 @@ export const MIN_STAGE_VIEW_HEIGHT = 700;
*/
export const MIN_STAGE_VIEW_WIDTH = 800;
/**
* Drag handle dimensions for resizable filmstrip.
*/
export const DRAG_HANDLE_WIDTH = 9;
export const DRAG_HANDLE_HEIGHT = 100;
export const DRAG_HANDLE_TOP_PANEL_HEIGHT = 3;
export const DRAG_HANDLE_TOP_PANEL_WIDTH = 100;
/**
* Touch padding added to each side of drag handle for easier tapping on touch devices.
*/
export const TOUCH_DRAG_HANDLE_PADDING = 6;
/**
* Horizontal margin used for the vertical filmstrip.
*/
@@ -298,3 +311,9 @@ export const MAX_ACTIVE_PARTICIPANTS = 6;
* Top filmstrip default height.
*/
export const TOP_FILMSTRIP_HEIGHT = 180;
/**
* Minimum screen width needed for functional filmstrip resizing.
* Calculated as stage minimum + filmstrip minimum (800px + 120px = 920px).
*/
export const MIN_FILMSTRIP_RESIZE_WIDTH = MIN_STAGE_VIEW_WIDTH + DEFAULT_FILMSTRIP_WIDTH;

View File

@@ -2,7 +2,7 @@ import { Theme } from '@mui/material/styles';
import { IReduxState } from '../app/types';
import { IStateful } from '../base/app/types';
import { isMobileBrowser } from '../base/environment/utils';
import { isTouchDevice, shouldEnableResize } from '../base/environment/utils';
import { MEDIA_TYPE } from '../base/media/constants';
import {
getLocalParticipant,
@@ -30,6 +30,7 @@ import {
DEFAULT_LOCAL_TILE_ASPECT_RATIO,
DISPLAY_AVATAR,
DISPLAY_VIDEO,
DRAG_HANDLE_WIDTH,
FILMSTRIP_GRID_BREAKPOINT,
FILMSTRIP_TYPE,
INDICATORS_TOOLTIP_POSITION,
@@ -45,6 +46,7 @@ import {
TILE_VIEW_DEFAULT_NUMBER_OF_VISIBLE_TILES,
TILE_VIEW_GRID_HORIZONTAL_MARGIN,
TILE_VIEW_GRID_VERTICAL_MARGIN,
TOUCH_DRAG_HANDLE_PADDING,
VERTICAL_VIEW_HORIZONTAL_MARGIN
} from './constants';
@@ -621,6 +623,7 @@ export function getIndicatorsTooltipPosition(thumbnailType?: string) {
/**
* Returns whether or not the filmstrip is resizable.
* On touch devices, resize is only enabled for larger screens (tablets, not phones).
*
* @param {Object} state - Redux state.
* @returns {boolean}
@@ -629,7 +632,7 @@ export function isFilmstripResizable(state: IReduxState) {
const { filmstrip } = state['features/base/config'];
const _currentLayout = getCurrentLayout(state);
return !filmstrip?.disableResizable && !isMobileBrowser()
return !filmstrip?.disableResizable && shouldEnableResize()
&& (_currentLayout === LAYOUTS.VERTICAL_FILMSTRIP_VIEW || _currentLayout === LAYOUTS.STAGE_FILMSTRIP_VIEW);
}
@@ -662,8 +665,13 @@ export function getVerticalViewMaxWidth(state: IReduxState) {
// Adding 4px for the border-right and margin-right.
// On non-resizable filmstrip add 4px for the left margin and border.
// Also adding 7px for the scrollbar. Also adding 9px for the drag handle.
maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? 9 : 4);
// Also adding 7px for the scrollbar.
// Drag handle: DRAG_HANDLE_WIDTH + padding (TOUCH_DRAG_HANDLE_PADDING on each side for touch)
const dragHandleWidth = isTouchDevice()
? DRAG_HANDLE_WIDTH + (TOUCH_DRAG_HANDLE_PADDING * 2)
: DRAG_HANDLE_WIDTH;
maxWidth += (_verticalViewGrid ? 0 : 11) + (_resizableFilmstrip ? dragHandleWidth : 4);
return maxWidth;
}
@@ -840,5 +848,5 @@ export function isTopPanelEnabled(state: IReduxState) {
* @returns {string} The background color.
*/
export function getThumbnailBackgroundColor(theme: Theme): string {
return theme.palette.uiBackground;
return theme.palette.thumbnailVideoBackground;
}

View File

@@ -26,7 +26,7 @@ const useStyles = makeStyles()((theme: Theme) => {
marginTop: 32,
maxWidth: 310,
padding: '16px 12px',
background: theme.palette.ui02,
background: theme.palette.dialInBackground,
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
@@ -50,7 +50,7 @@ const useStyles = makeStyles()((theme: Theme) => {
separator: {
width: '100%',
height: 1,
background: theme.palette.ui04,
background: theme.palette.labelBackground,
marginBottom: 18
},
pinLabel: {

View File

@@ -94,7 +94,7 @@ const styles = (theme: Theme) => {
display: 'flex',
flexDirection: 'column' as const,
background: '#1E1E1E',
color: theme.palette.text01
color: theme.palette.dialInText
},
scrollable: {
height: '100dvh',

View File

@@ -11,7 +11,7 @@ const useStyles = makeStyles()(theme => {
overlayContainer: {
width: '100%',
height: '100%',
backgroundColor: theme.palette.ui02,
backgroundColor: theme.palette.largeVideoPlaceholder,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
@@ -31,13 +31,13 @@ const useStyles = makeStyles()(theme => {
height: '56px',
boxSizing: 'border-box',
border: '3px solid',
borderColor: theme.palette.text01,
borderColor: theme.palette.dialogText,
borderRadius: '6px'
},
laptopStand: {
width: '40px',
height: '4px',
backgroundColor: theme.palette.text01,
backgroundColor: theme.palette.dialogText,
boxSizing: 'border-box',
borderRadius: '6px',
marginTop: '4px'
@@ -49,7 +49,7 @@ const useStyles = makeStyles()(theme => {
lineHeight: '1.75rem',
marginTop: '24px',
letterSpacing: '-0.012em',
color: theme.palette.text01
color: theme.palette.dialogText
},
showSharing: {
fontStyle: 'normal',

View File

@@ -39,7 +39,7 @@ interface IProps extends INotificationProps {
const useStyles = makeStyles()((theme: Theme) => {
return {
container: {
backgroundColor: theme.palette.ui10,
backgroundColor: theme.palette.notificationBackground,
padding: '8px 16px 8px 20px',
display: 'flex',
position: 'relative' as const,
@@ -85,19 +85,19 @@ const useStyles = makeStyles()((theme: Theme) => {
borderRadius: '4px',
'&.normal': {
backgroundColor: theme.palette.action01
backgroundColor: theme.palette.notificationNormalIcon
},
'&.error': {
backgroundColor: theme.palette.iconError
backgroundColor: theme.palette.notificationError
},
'&.success': {
backgroundColor: theme.palette.success01
backgroundColor: theme.palette.notificationSuccess
},
'&.warning': {
backgroundColor: theme.palette.warning01
backgroundColor: theme.palette.notificationWarning
}
},
@@ -113,7 +113,7 @@ const useStyles = makeStyles()((theme: Theme) => {
display: 'flex',
flexDirection: 'column' as const,
justifyContent: 'space-between',
color: theme.palette.text04,
color: theme.palette.notificationText,
flex: 1,
margin: '0 8px',
@@ -150,7 +150,7 @@ const useStyles = makeStyles()((theme: Theme) => {
border: 0,
outline: 0,
backgroundColor: 'transparent',
color: theme.palette.action01,
color: theme.palette.notificationActionText,
...theme.typography.bodyShortBold,
marginRight: theme.spacing(3),
padding: 0,
@@ -161,11 +161,11 @@ const useStyles = makeStyles()((theme: Theme) => {
},
'&.destructive': {
color: theme.palette.textError
color: theme.palette.notificationErrorText
},
'&:focus-visible': {
outline: `2px solid ${theme.palette.action01}`,
outline: `2px solid ${theme.palette.notificationActionFocus}`,
outlineOffset: 2
}
},
@@ -203,10 +203,10 @@ const Notification = ({
);
const ICON_COLOR = {
error: theme.palette.iconError,
normal: theme.palette.action01,
success: theme.palette.success01,
warning: theme.palette.warning01
error: theme.palette.notificationError,
normal: theme.palette.notificationNormalIcon,
success: theme.palette.notificationSuccess,
warning: theme.palette.notificationWarning
};
const onDismiss = useCallback(() => {
@@ -359,10 +359,10 @@ const Notification = ({
))}
</div>
</div>
{ !disableClosing && (
{!disableClosing && (
<Icon
className = { classes.closeIcon }
color = { theme.palette.icon04 }
color = { theme.palette.notificationCloseIcon }
id = 'close-notification'
onClick = { onDismiss }
size = { 20 }

View File

@@ -95,7 +95,7 @@ const useStyles = makeStyles()(theme => {
},
arrowContainer: {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.breakoutRoomArrowBackground,
width: '24px',
height: '24px',
borderRadius: '6px',

View File

@@ -49,7 +49,7 @@ interface IProps {
const useStyles = makeStyles()(theme => {
return {
text: {
color: theme.palette.text02,
color: theme.palette.participantSectionText,
padding: '10px 16px',
height: '40px',
overflow: 'hidden',

View File

@@ -45,11 +45,11 @@ const useStyles = makeStyles()(theme => {
cursor: 'pointer',
padding: `${theme.spacing(1)} 0`,
...theme.typography.bodyShortBold,
color: theme.palette.text02,
color: theme.palette.participantSectionText,
flexShrink: 0
},
arrowContainer: {
backgroundColor: theme.palette.ui03,
backgroundColor: theme.palette.visitorsArrowBackground,
width: '24px',
height: '24px',
borderRadius: '6px',

View File

@@ -56,7 +56,7 @@ const useStyles = makeStyles()(theme => {
text: {
...theme.typography.bodyShortRegular,
color: theme.palette.text02,
color: theme.palette.participantSectionText,
padding: '10px 16px',
height: '40px',
overflow: 'hidden',

View File

@@ -24,7 +24,7 @@ const useStyles = makeStyles()(theme => {
},
drawerItem: {
alignItems: 'center',
color: theme.palette.text01,
color: theme.palette.participantCounterText,
display: 'flex',
padding: '12px 16px',
...theme.typography.bodyShortRegularLarge,
@@ -35,7 +35,7 @@ const useStyles = makeStyles()(theme => {
'&:hover': {
cursor: 'pointer',
background: theme.palette.action02
background: theme.palette.participantActionButton
}
},
icon: {
@@ -48,11 +48,11 @@ const useStyles = makeStyles()(theme => {
},
heading: {
...theme.typography.bodyShortBold,
color: theme.palette.text02
color: theme.palette.participantSectionText
},
link: {
...theme.typography.labelBold,
color: theme.palette.link01,
color: theme.palette.participantLinkText,
cursor: 'pointer'
}
};

View File

@@ -23,10 +23,10 @@ import MeetingParticipantItems from './MeetingParticipantItems';
const useStyles = makeStyles()(theme => {
return {
headingW: {
color: theme.palette.warning02
color: theme.palette.participantWarningText
},
heading: {
color: theme.palette.text02,
color: theme.palette.participantSectionText,
...theme.typography.bodyShortBold,
marginBottom: theme.spacing(3),

View File

@@ -110,7 +110,7 @@ const useStyles = makeStyles()(theme => {
moderatorLabel: {
...theme.typography.labelBold,
color: theme.palette.text03
color: theme.palette.participantModeratorLabel
},
avatar: {

Some files were not shown because too many files have changed in this diff Show More