mirror of
https://github.com/RocketChat/Rocket.Chat.git
synced 2025-12-27 22:40:49 +00:00
chore: Purge and rename removed v1 files (#37912)
Some checks failed
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (${{ matrix.type }}) (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (${{ matrix.type }}) (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release candidate cut / new-release (push) Has been cancelled
Some checks failed
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (${{ matrix.type }}) (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (${{ matrix.type }}) (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release candidate cut / new-release (push) Has been cancelled
This commit is contained in:
parent
62708dcd12
commit
a587ab378c
@ -1,8 +0,0 @@
|
||||
import { testCreateChannelModal } from './testCreateChannelModal';
|
||||
import CreateChannelModalComponent from '../../../sidebar/header/CreateChannel';
|
||||
|
||||
jest.mock('../../../lib/utils/goToRoomById', () => ({
|
||||
goToRoomById: jest.fn(),
|
||||
}));
|
||||
|
||||
testCreateChannelModal(CreateChannelModalComponent);
|
||||
@ -1,269 +0,0 @@
|
||||
import { mockAppRoot } from '@rocket.chat/mock-providers';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import type CreateChannelModal2Component from './CreateChannelModal';
|
||||
import { createFakeLicenseInfo } from '../../../../tests/mocks/data';
|
||||
import type CreateChannelModalComponent from '../../../sidebar/header/CreateChannel';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export function testCreateChannelModal(CreateChannelModal: typeof CreateChannelModalComponent | typeof CreateChannelModal2Component) {
|
||||
describe('CreateChannelModal', () => {
|
||||
describe('Encryption', () => {
|
||||
it('should render with encryption option disabled and set to off when E2E_Enable=false and E2E_Enabled_Default_PrivateRooms=false', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', false).withSetting('E2E_Enabled_Default_PrivateRooms', false).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option enabled and set to off when E2E_Enable=true and E2E_Enabled_Default_PrivateRooms=false', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', false).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option disabled and set to off when E2E_Enable=false and E2E_Enabled_Default_PrivateRooms=true', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', false).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option enabled and set to on when E2E_Enable=true and E2E_Enabled_Default_PrivateRooms=True', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('when Private goes ON → OFF: forces Encrypted OFF and disables it (E2E_Enable=true, E2E_Enabled_Default_PrivateRooms=true)', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
// initial: private=true, encrypted ON and enabled
|
||||
expect(priv).toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
|
||||
// Private ON -> OFF: encrypted must become OFF and disabled
|
||||
await userEvent.click(priv);
|
||||
expect(priv).not.toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('when Private goes OFF → ON: keeps Encrypted OFF but re-enables it (E2E_Enable=true, E2E_Enabled_Default_PrivateRooms=true)', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
// turn private OFF to simulate user path from non-private
|
||||
await userEvent.click(priv);
|
||||
expect(priv).not.toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
|
||||
// turn private back ON -> encrypted should remain OFF but become enabled
|
||||
await userEvent.click(priv);
|
||||
expect(priv).toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('private room: toggling Broadcast on/off does not change or disable Encrypted', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Broadcast') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
expect(priv).toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
expect(broadcast).not.toBeChecked();
|
||||
|
||||
// Broadcast: OFF -> ON (Encrypted unchanged + enabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
|
||||
// Broadcast: ON -> OFF (Encrypted unchanged + enabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
|
||||
// User can still toggle Encrypted freely while Broadcast is OFF
|
||||
await userEvent.click(encrypted);
|
||||
expect(encrypted).not.toBeChecked();
|
||||
|
||||
// User can still toggle Encrypted freely while Broadcast is ON
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('non-private room: Encrypted remains OFF and disabled regardless of Broadcast state', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Broadcast') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
// Switch to non-private
|
||||
await userEvent.click(priv);
|
||||
expect(priv).not.toBeChecked();
|
||||
|
||||
// Encrypted must be OFF + disabled (non-private cannot be encrypted)
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
|
||||
// Broadcast: OFF -> ON (Encrypted stays OFF + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
|
||||
// Broadcast: ON -> OFF (Encrypted still OFF + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Federation', () => {
|
||||
it('should render with federated option disabled when user lacks license module', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
expect(federated).toHaveAccessibleDescription('error-this-is-a-premium-feature');
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should render with federated option disabled if the feature is disabled for workspaces', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot()
|
||||
.withJohnDoe()
|
||||
.withSetting('Federation_Matrix_enabled', false)
|
||||
.withEndpoint(
|
||||
'GET',
|
||||
'/v1/licenses.info',
|
||||
jest.fn().mockImplementation(() => ({
|
||||
license: createFakeLicenseInfo({ activeModules: ['federation'] }),
|
||||
})),
|
||||
)
|
||||
.build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).toBeDisabled();
|
||||
expect(federated).toHaveAccessibleDescription('Federation_Matrix_Federated_Description_disabled');
|
||||
});
|
||||
|
||||
it('should render with federated option disabled when user lacks permission', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot()
|
||||
.withJohnDoe()
|
||||
.withSetting('Federation_Matrix_enabled', true)
|
||||
.withEndpoint(
|
||||
'GET',
|
||||
'/v1/licenses.info',
|
||||
jest.fn().mockImplementation(() => ({
|
||||
license: createFakeLicenseInfo({ activeModules: ['federation'] }),
|
||||
})),
|
||||
)
|
||||
.build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).toBeDisabled();
|
||||
expect(federated).toHaveAccessibleDescription('error-not-authorized-federation');
|
||||
});
|
||||
|
||||
it('should render with federated option enabled when user has license module, permission and feature enabled', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot()
|
||||
.withJohnDoe()
|
||||
.withSetting('Federation_Matrix_enabled', true)
|
||||
.withPermission('access-federation')
|
||||
.withEndpoint(
|
||||
'GET',
|
||||
'/v1/licenses.info',
|
||||
jest.fn().mockImplementation(() => ({
|
||||
license: createFakeLicenseInfo({ activeModules: ['federation'] }),
|
||||
})),
|
||||
)
|
||||
.build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).not.toBeDisabled();
|
||||
expect(federated).toHaveAccessibleDescription('Federation_Matrix_Federated_Description');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -27,10 +27,10 @@ import type { ReactElement } from 'react';
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
|
||||
import { goToRoomById } from '../../lib/utils/goToRoomById';
|
||||
import { useEncryptedRoomDescription } from '../../navbar/NavBarPagesGroup/actions/useEncryptedRoomDescription';
|
||||
import RoomAutoComplete from '../RoomAutoComplete';
|
||||
import UserAutoCompleteMultiple from '../UserAutoCompleteMultiple';
|
||||
import DefaultParentRoomField from './DefaultParentRoomField';
|
||||
import { useEncryptedRoomDescription } from '../../NavBarV2/NavBarPagesGroup/actions/useEncryptedRoomDescription';
|
||||
|
||||
type CreateDiscussionFormValues = {
|
||||
name: string;
|
||||
|
||||
1
apps/meteor/client/components/SidebarToggler/index.ts
Normal file
1
apps/meteor/client/components/SidebarToggler/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './SidebarToggler';
|
||||
@ -1 +0,0 @@
|
||||
export { default as SidebarTogglerV2 } from './SidebarToggler';
|
||||
4
apps/meteor/client/definitions/global.d.ts
vendored
4
apps/meteor/client/definitions/global.d.ts
vendored
@ -26,6 +26,7 @@ declare global {
|
||||
mozAudioContext?: AudioContext;
|
||||
/** @deprecated use `window.AudioContext` */
|
||||
webkitAudioContext?: AudioContext;
|
||||
opera?: string;
|
||||
}
|
||||
|
||||
interface Navigator {
|
||||
@ -57,6 +58,9 @@ declare global {
|
||||
onSuccess?: (stream: MediaStream) => void,
|
||||
onError?: (error: any) => void,
|
||||
) => void;
|
||||
userAgentData?: {
|
||||
mobile: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface RTCPeerConnection {
|
||||
|
||||
@ -2,57 +2,49 @@ import { mockAppRoot } from '@rocket.chat/mock-providers';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CreateTeamModal from './CreateTeamModal';
|
||||
import CreateTeamModalOld from '../../../sidebar/header/CreateTeam';
|
||||
import CreateChannelModal from './CreateChannelModal';
|
||||
import { createFakeLicenseInfo } from '../../../../tests/mocks/data';
|
||||
|
||||
jest.mock('../../../lib/utils/goToRoomById', () => ({
|
||||
goToRoomById: jest.fn(),
|
||||
}));
|
||||
|
||||
type CreateTeamModalComponentType = typeof CreateTeamModal | typeof CreateTeamModalOld;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
|
||||
describe.each([
|
||||
['CreateTeamModal', CreateTeamModalOld],
|
||||
['CreateTeamModal in NavbarV2', CreateTeamModal],
|
||||
] as const)(
|
||||
'%s',
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
(_name: string, CreateTeamModalComponent: CreateTeamModalComponentType) => {
|
||||
describe('CreateChannelModal', () => {
|
||||
describe('Encryption', () => {
|
||||
it('should render with encryption option disabled and set to off when E2E_Enable=false and E2E_Enabled_Default_PrivateRooms=false', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', false).withSetting('E2E_Enabled_Default_PrivateRooms', false).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option enabled and set to off when E2E_Enable=true and E2E_Enabled_Default_PrivateRooms=false', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', false).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option disabled and set to off when E2E_Enable=false and E2E_Enabled_Default_PrivateRooms=true', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', false).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
|
||||
expect(encrypted).not.toBeChecked();
|
||||
@ -60,26 +52,26 @@ describe.each([
|
||||
});
|
||||
|
||||
it('should render with encryption option enabled and set to on when E2E_Enable=true and E2E_Enabled_Default_PrivateRooms=True', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('when Private goes ON → OFF: forces Encrypted OFF and disables it (E2E_Enable=true, E2E_Enabled_Default_PrivateRooms=true)', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
// initial: private=true, encrypted ON and enabled
|
||||
expect(priv).toBeChecked();
|
||||
@ -94,14 +86,14 @@ describe.each([
|
||||
});
|
||||
|
||||
it('when Private goes OFF → ON: keeps Encrypted OFF but re-enables it (E2E_Enable=true, E2E_Enabled_Default_PrivateRooms=true)', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
// turn private OFF to simulate user path from non-private
|
||||
await userEvent.click(priv);
|
||||
@ -116,16 +108,16 @@ describe.each([
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('private team: toggling Broadcast on/off does not change or disable Encrypted', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
it('private room: toggling Broadcast on/off does not change or disable Encrypted', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Broadcast') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
expect(priv).toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
@ -155,16 +147,16 @@ describe.each([
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('non-private team: Encrypted remains OFF and disabled regardless of Broadcast state', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
it('non-private room: Encrypted remains OFF and disabled regardless of Broadcast state', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
const encrypted = screen.getByLabelText('Encrypted') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Broadcast') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Private') as HTMLInputElement;
|
||||
|
||||
// Switch to non-private
|
||||
await userEvent.click(priv);
|
||||
@ -186,73 +178,92 @@ describe.each([
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should disable and turn on ReadOnly toggle when Broadcast is ON and no set-readonly permission', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
describe('Federation', () => {
|
||||
it('should render with federated option disabled when user lacks license module', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(readOnly).not.toBeChecked();
|
||||
|
||||
// Broadcast: OFF -> ON (ReadOnly stays ON + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(readOnly).toBeChecked();
|
||||
expect(readOnly).toBeDisabled();
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
expect(federated).toHaveAccessibleDescription('error-this-is-a-premium-feature');
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should disable and turn on ReadOnly toggle when Broadcast is ON with set-readonly permission', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withPermission('set-readonly').build(),
|
||||
it('should render with federated option disabled if the feature is disabled for workspaces', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot()
|
||||
.withJohnDoe()
|
||||
.withSetting('Federation_Matrix_enabled', false)
|
||||
.withEndpoint(
|
||||
'GET',
|
||||
'/v1/licenses.info',
|
||||
jest.fn().mockImplementation(() => ({
|
||||
license: createFakeLicenseInfo({ activeModules: ['federation'] }),
|
||||
})),
|
||||
)
|
||||
.build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(readOnly).not.toBeChecked();
|
||||
|
||||
// Broadcast: OFF -> ON (ReadOnly stays ON + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(readOnly).toBeChecked();
|
||||
expect(readOnly).toBeDisabled();
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).toBeDisabled();
|
||||
expect(federated).toHaveAccessibleDescription('Federation_Matrix_Federated_Description_disabled');
|
||||
});
|
||||
|
||||
it('should disable and turn off ReadOnly toggle when Broadcast is OFF with no set-readonly permission', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().build(),
|
||||
it('should render with federated option disabled when user lacks permission', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot()
|
||||
.withJohnDoe()
|
||||
.withSetting('Federation_Matrix_enabled', true)
|
||||
.withEndpoint(
|
||||
'GET',
|
||||
'/v1/licenses.info',
|
||||
jest.fn().mockImplementation(() => ({
|
||||
license: createFakeLicenseInfo({ activeModules: ['federation'] }),
|
||||
})),
|
||||
)
|
||||
.build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(readOnly).not.toBeChecked();
|
||||
expect(readOnly).toBeDisabled();
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).toBeDisabled();
|
||||
expect(federated).toHaveAccessibleDescription('error-not-authorized-federation');
|
||||
});
|
||||
|
||||
it('should enable ReadOnly toggle when Broadcast is OFF with set-readonly permission', async () => {
|
||||
render(<CreateTeamModalComponent onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withPermission('set-readonly').build(),
|
||||
it('should render with federated option enabled when user has license module, permission and feature enabled', async () => {
|
||||
render(<CreateChannelModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot()
|
||||
.withJohnDoe()
|
||||
.withSetting('Federation_Matrix_enabled', true)
|
||||
.withPermission('access-federation')
|
||||
.withEndpoint(
|
||||
'GET',
|
||||
'/v1/licenses.info',
|
||||
jest.fn().mockImplementation(() => ({
|
||||
license: createFakeLicenseInfo({ activeModules: ['federation'] }),
|
||||
})),
|
||||
)
|
||||
.build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(readOnly).not.toBeChecked();
|
||||
expect(readOnly).toBeEnabled();
|
||||
const federated = screen.getByLabelText('Federation_Matrix_Federated');
|
||||
expect(federated).toBeInTheDocument();
|
||||
expect(federated).not.toBeChecked();
|
||||
expect(federated).not.toBeDisabled();
|
||||
expect(federated).toHaveAccessibleDescription('Federation_Matrix_Federated_Description');
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
@ -1,3 +1,4 @@
|
||||
import type { IRoom } from '@rocket.chat/core-typings';
|
||||
import {
|
||||
Box,
|
||||
Modal,
|
||||
@ -42,6 +43,7 @@ import { goToRoomById } from '../../../lib/utils/goToRoomById';
|
||||
|
||||
type CreateChannelModalProps = {
|
||||
teamId?: string;
|
||||
mainRoom?: IRoom;
|
||||
onClose: () => void;
|
||||
reload?: () => void;
|
||||
};
|
||||
@ -75,7 +77,7 @@ const getFederationHintKey = (federationModule: boolean, featureToggle: boolean,
|
||||
|
||||
const hasExternalMembers = (members: string[]): boolean => members.some((member) => member.startsWith('@'));
|
||||
|
||||
const CreateChannelModal = ({ teamId = '', onClose, reload }: CreateChannelModalProps) => {
|
||||
const CreateChannelModal = ({ teamId = '', mainRoom, onClose, reload }: CreateChannelModalProps) => {
|
||||
const t = useTranslation();
|
||||
const canSetReadOnly = usePermissionWithScopedRoles('set-readonly', ['owner']);
|
||||
const e2eEnabled = useSetting('E2E_Enable');
|
||||
@ -99,7 +101,7 @@ const CreateChannelModal = ({ teamId = '', onClose, reload }: CreateChannelModal
|
||||
|
||||
const dispatchToastMessage = useToastMessageDispatch();
|
||||
|
||||
const canOnlyCreateOneType = useCreateChannelTypePermission();
|
||||
const canOnlyCreateOneType = useCreateChannelTypePermission(mainRoom?._id);
|
||||
|
||||
const {
|
||||
register,
|
||||
@ -0,0 +1,247 @@
|
||||
import { mockAppRoot } from '@rocket.chat/mock-providers';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
|
||||
import CreateTeamModal from './CreateTeamModal';
|
||||
|
||||
jest.mock('../../../lib/utils/goToRoomById', () => ({
|
||||
goToRoomById: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('CreateTeamModal', () => {
|
||||
it('should render with encryption option disabled and set to off when E2E_Enable=false and E2E_Enabled_Default_PrivateRooms=false', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', false).withSetting('E2E_Enabled_Default_PrivateRooms', false).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option enabled and set to off when E2E_Enable=true and E2E_Enabled_Default_PrivateRooms=false', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', false).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option disabled and set to off when E2E_Enable=false and E2E_Enabled_Default_PrivateRooms=true', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', false).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
expect(encrypted).toBeInTheDocument();
|
||||
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should render with encryption option enabled and set to on when E2E_Enable=true and E2E_Enabled_Default_PrivateRooms=True', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('when Private goes ON → OFF: forces Encrypted OFF and disables it (E2E_Enable=true, E2E_Enabled_Default_PrivateRooms=true)', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
|
||||
// initial: private=true, encrypted ON and enabled
|
||||
expect(priv).toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
|
||||
// Private ON -> OFF: encrypted must become OFF and disabled
|
||||
await userEvent.click(priv);
|
||||
expect(priv).not.toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('when Private goes OFF → ON: keeps Encrypted OFF but re-enables it (E2E_Enable=true, E2E_Enabled_Default_PrivateRooms=true)', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
|
||||
// turn private OFF to simulate user path from non-private
|
||||
await userEvent.click(priv);
|
||||
expect(priv).not.toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
|
||||
// turn private back ON -> encrypted should remain OFF but become enabled
|
||||
await userEvent.click(priv);
|
||||
expect(priv).toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('private team: toggling Broadcast on/off does not change or disable Encrypted', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
|
||||
expect(priv).toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
expect(broadcast).not.toBeChecked();
|
||||
|
||||
// Broadcast: OFF -> ON (Encrypted unchanged + enabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
|
||||
// Broadcast: ON -> OFF (Encrypted unchanged + enabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(encrypted).toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
|
||||
// User can still toggle Encrypted freely while Broadcast is OFF
|
||||
await userEvent.click(encrypted);
|
||||
expect(encrypted).not.toBeChecked();
|
||||
|
||||
// User can still toggle Encrypted freely while Broadcast is ON
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeEnabled();
|
||||
});
|
||||
|
||||
it('non-private team: Encrypted remains OFF and disabled regardless of Broadcast state', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withSetting('E2E_Enable', true).withSetting('E2E_Enabled_Default_PrivateRooms', true).build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const encrypted = screen.getByLabelText('Teams_New_Encrypted_Label') as HTMLInputElement;
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const priv = screen.getByLabelText('Teams_New_Private_Label') as HTMLInputElement;
|
||||
|
||||
// Switch to non-private
|
||||
await userEvent.click(priv);
|
||||
expect(priv).not.toBeChecked();
|
||||
|
||||
// Encrypted must be OFF + disabled (non-private cannot be encrypted)
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
|
||||
// Broadcast: OFF -> ON (Encrypted stays OFF + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
|
||||
// Broadcast: ON -> OFF (Encrypted still OFF + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(encrypted).not.toBeChecked();
|
||||
expect(encrypted).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should disable and turn on ReadOnly toggle when Broadcast is ON and no set-readonly permission', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(readOnly).not.toBeChecked();
|
||||
|
||||
// Broadcast: OFF -> ON (ReadOnly stays ON + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(readOnly).toBeChecked();
|
||||
expect(readOnly).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should disable and turn on ReadOnly toggle when Broadcast is ON with set-readonly permission', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withPermission('set-readonly').build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(readOnly).not.toBeChecked();
|
||||
|
||||
// Broadcast: OFF -> ON (ReadOnly stays ON + disabled)
|
||||
await userEvent.click(broadcast);
|
||||
expect(broadcast).toBeChecked();
|
||||
expect(readOnly).toBeChecked();
|
||||
expect(readOnly).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should disable and turn off ReadOnly toggle when Broadcast is OFF with no set-readonly permission', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(readOnly).not.toBeChecked();
|
||||
expect(readOnly).toBeDisabled();
|
||||
});
|
||||
|
||||
it('should enable ReadOnly toggle when Broadcast is OFF with set-readonly permission', async () => {
|
||||
render(<CreateTeamModal onClose={() => null} />, {
|
||||
wrapper: mockAppRoot().withPermission('set-readonly').build(),
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByText('Advanced_settings'));
|
||||
|
||||
const broadcast = screen.getByLabelText('Teams_New_Broadcast_Label') as HTMLInputElement;
|
||||
const readOnly = screen.getByLabelText('Teams_New_Read_only_Label') as HTMLInputElement;
|
||||
|
||||
expect(broadcast).not.toBeChecked();
|
||||
expect(readOnly).not.toBeChecked();
|
||||
expect(readOnly).toBeEnabled();
|
||||
});
|
||||
});
|
||||
@ -2,23 +2,17 @@ import { mockAppRoot } from '@rocket.chat/mock-providers';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
|
||||
import { useEncryptedRoomDescription } from './useEncryptedRoomDescription';
|
||||
import { useEncryptedRoomDescription as useEncryptedRoomDescriptionOld } from '../../../sidebar/header/hooks/useEncryptedRoomDescription';
|
||||
|
||||
type Hook = typeof useEncryptedRoomDescription | typeof useEncryptedRoomDescriptionOld;
|
||||
|
||||
const wrapper = mockAppRoot();
|
||||
|
||||
describe.each([
|
||||
['useEncryptedRoomDescription in NavBarV2', useEncryptedRoomDescription],
|
||||
['useEncryptedRoomDescription', useEncryptedRoomDescriptionOld],
|
||||
] as const)('%s', (_name, useEncryptedRoomDescriptionHook: Hook) => {
|
||||
describe('useEncryptedRoomDescription', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe.each(['channel', 'team'] as const)('roomType=%s', (roomType) => {
|
||||
it('returns "Not_available_for_this_workspace" when E2E is disabled', () => {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescriptionHook(roomType), {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescription(roomType), {
|
||||
wrapper: wrapper.withSetting('E2E_Enable', false).build(),
|
||||
});
|
||||
const describe = result.current;
|
||||
@ -27,7 +21,7 @@ describe.each([
|
||||
});
|
||||
|
||||
it('returns "Encrypted_not_available" when room is not private and E2E is enabled', () => {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescriptionHook(roomType), {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescription(roomType), {
|
||||
wrapper: wrapper.withSetting('E2E_Enable', true).build(),
|
||||
});
|
||||
const describe = result.current;
|
||||
@ -36,7 +30,7 @@ describe.each([
|
||||
});
|
||||
|
||||
it('returns "Encrypted_messages" when private and encrypted are true and E2E is enabled', () => {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescriptionHook(roomType), {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescription(roomType), {
|
||||
wrapper: wrapper.withSetting('E2E_Enable', true).build(),
|
||||
});
|
||||
const describe = result.current;
|
||||
@ -45,7 +39,7 @@ describe.each([
|
||||
});
|
||||
|
||||
it('returns "Encrypted_messages_false" when private and encrypted are false and E2E is enabled', () => {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescriptionHook(roomType), {
|
||||
const { result } = renderHook(() => useEncryptedRoomDescription(roomType), {
|
||||
wrapper: wrapper.withSetting('E2E_Enable', true).build(),
|
||||
});
|
||||
const describe = result.current;
|
||||
@ -2,7 +2,7 @@ import type { GenericMenuItemProps } from '@rocket.chat/ui-client';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useCreateRoomModal } from './useCreateRoomModal';
|
||||
import MatrixFederationSearch from '../../../sidebarv2/header/MatrixFederationSearch';
|
||||
import MatrixFederationSearch from '../../../sidebar/header/MatrixFederationSearch';
|
||||
|
||||
export const useMatrixFederationItems = ({
|
||||
isMatrixEnabled,
|
||||
@ -2,7 +2,7 @@ import { NavBarGroup, NavBarSection } from '@rocket.chat/fuselage';
|
||||
import { useLayout } from '@rocket.chat/ui-contexts';
|
||||
|
||||
import NavBarPagesGroup from './NavBarPagesGroup';
|
||||
import { SidebarTogglerV2 } from '../components/SidebarTogglerV2';
|
||||
import SidebarToggler from '../components/SidebarToggler';
|
||||
|
||||
const NavBarPagesSection = () => {
|
||||
const { sidebar } = useLayout();
|
||||
@ -12,7 +12,7 @@ const NavBarPagesSection = () => {
|
||||
{sidebar.shouldToggle && (
|
||||
<>
|
||||
<NavBarGroup>
|
||||
<SidebarTogglerV2 />
|
||||
<SidebarToggler />
|
||||
</NavBarGroup>
|
||||
</>
|
||||
)}
|
||||
@ -6,8 +6,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import NavBarSearchItem from './NavBarSearchItem';
|
||||
import { RoomIcon } from '../../components/RoomIcon';
|
||||
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
|
||||
import SidebarItemBadges from '../../sidebarv2/badges/SidebarItemBadges';
|
||||
import { useUnreadDisplay } from '../../sidebarv2/hooks/useUnreadDisplay';
|
||||
import SidebarItemBadges from '../../sidebar/badges/SidebarItemBadges';
|
||||
import { useUnreadDisplay } from '../../sidebar/hooks/useUnreadDisplay';
|
||||
|
||||
type NavBarSearchItemWithDataProps = {
|
||||
room: SubscriptionWithRoom;
|
||||
@ -1,52 +1,41 @@
|
||||
import { IconButton, Sidebar } from '@rocket.chat/fuselage';
|
||||
import type { Keys as IconName } from '@rocket.chat/icons';
|
||||
import type { ReactElement } from 'react';
|
||||
import { IconButton, SidebarV2Item, SidebarV2ItemAvatarWrapper, SidebarV2ItemMenu, SidebarV2ItemTitle } from '@rocket.chat/fuselage';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
type CondensedProps = {
|
||||
title: ReactElement | string;
|
||||
titleIcon?: ReactElement;
|
||||
avatar: ReactElement | boolean;
|
||||
icon?: IconName;
|
||||
actions?: ReactElement;
|
||||
title: ReactNode;
|
||||
titleIcon?: ReactNode;
|
||||
avatar: ReactNode;
|
||||
icon?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
href?: string;
|
||||
unread?: boolean;
|
||||
menu?: () => ReactElement;
|
||||
menu?: () => ReactNode;
|
||||
menuOptions?: any;
|
||||
selected?: boolean;
|
||||
badges?: ReactElement;
|
||||
badges?: ReactNode;
|
||||
clickable?: boolean;
|
||||
};
|
||||
} & Omit<HTMLAttributes<HTMLAnchorElement>, 'is'>;
|
||||
|
||||
const Condensed = ({ icon, title = '', avatar, actions, href, unread, menu, badges, ...props }: CondensedProps) => {
|
||||
const Condensed = ({ icon, title, avatar, actions, unread, menu, badges, ...props }: CondensedProps) => {
|
||||
const [menuVisibility, setMenuVisibility] = useState(!!window.DISABLE_ANIMATION);
|
||||
|
||||
const handleFocus = () => setMenuVisibility(true);
|
||||
const handlePointerEnter = () => setMenuVisibility(true);
|
||||
|
||||
return (
|
||||
<Sidebar.Item {...props} {...({ href } as any)} clickable={!!href} onFocus={handleFocus} onPointerEnter={handlePointerEnter}>
|
||||
{avatar && <Sidebar.Item.Avatar>{avatar}</Sidebar.Item.Avatar>}
|
||||
<Sidebar.Item.Content>
|
||||
<Sidebar.Item.Wrapper>
|
||||
{icon}
|
||||
<Sidebar.Item.Title data-qa='sidebar-item-title' className={(unread && 'rcx-sidebar-item--highlighted') as string}>
|
||||
{title}
|
||||
</Sidebar.Item.Title>
|
||||
</Sidebar.Item.Wrapper>
|
||||
{badges && <Sidebar.Item.Badge>{badges}</Sidebar.Item.Badge>}
|
||||
{menu && (
|
||||
<Sidebar.Item.Menu>
|
||||
{menuVisibility ? menu() : <IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-item__menu icon='kebab' />}
|
||||
</Sidebar.Item.Menu>
|
||||
)}
|
||||
</Sidebar.Item.Content>
|
||||
{actions && (
|
||||
<Sidebar.Item.Container>
|
||||
<Sidebar.Item.Actions>{actions}</Sidebar.Item.Actions>
|
||||
</Sidebar.Item.Container>
|
||||
<SidebarV2Item {...props} onFocus={handleFocus} onPointerEnter={handlePointerEnter}>
|
||||
{avatar && <SidebarV2ItemAvatarWrapper>{avatar}</SidebarV2ItemAvatarWrapper>}
|
||||
{icon}
|
||||
<SidebarV2ItemTitle unread={unread}>{title}</SidebarV2ItemTitle>
|
||||
{badges}
|
||||
{actions}
|
||||
{menu && (
|
||||
<SidebarV2ItemMenu>
|
||||
{menuVisibility ? menu() : <IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-v2-item__menu icon='kebab' />}
|
||||
</SidebarV2ItemMenu>
|
||||
)}
|
||||
</Sidebar.Item>
|
||||
</SidebarV2Item>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -20,14 +20,7 @@ export default {
|
||||
const Template: StoryFn<typeof Extended> = (args) => (
|
||||
<Extended
|
||||
{...args}
|
||||
title={
|
||||
<Box display='flex' flexDirection='row' w='full' alignItems='center'>
|
||||
<Box flexGrow='1' withTruncatedText>
|
||||
John Doe
|
||||
</Box>
|
||||
<Box fontScale='micro'>15:38</Box>
|
||||
</Box>
|
||||
}
|
||||
title='John Doe'
|
||||
subtitle={
|
||||
<Box display='flex' flexDirection='row' w='full' alignItems='center'>
|
||||
<Box flexGrow='1' withTruncatedText>
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
import { Sidebar, IconButton } from '@rocket.chat/fuselage';
|
||||
import type { Keys as IconName } from '@rocket.chat/icons';
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
SidebarV2Item,
|
||||
SidebarV2ItemAvatarWrapper,
|
||||
SidebarV2ItemCol,
|
||||
SidebarV2ItemRow,
|
||||
SidebarV2ItemTitle,
|
||||
SidebarV2ItemTimestamp,
|
||||
SidebarV2ItemContent,
|
||||
SidebarV2ItemMenu,
|
||||
IconButton,
|
||||
} from '@rocket.chat/fuselage';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
import { useShortTimeAgo } from '../../hooks/useTimeAgo';
|
||||
|
||||
type ExtendedProps = {
|
||||
icon?: IconName;
|
||||
title?: ReactNode;
|
||||
icon?: ReactNode;
|
||||
title: ReactNode;
|
||||
avatar?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
href?: string;
|
||||
@ -20,11 +29,11 @@ type ExtendedProps = {
|
||||
menuOptions?: any;
|
||||
titleIcon?: ReactNode;
|
||||
threadUnread?: boolean;
|
||||
};
|
||||
} & Omit<HTMLAttributes<HTMLElement>, 'is'>;
|
||||
|
||||
const Extended = ({
|
||||
icon,
|
||||
title = '',
|
||||
title,
|
||||
avatar,
|
||||
actions,
|
||||
href,
|
||||
@ -46,44 +55,26 @@ const Extended = ({
|
||||
const handlePointerEnter = () => setMenuVisibility(true);
|
||||
|
||||
return (
|
||||
<Sidebar.Item
|
||||
selected={selected}
|
||||
highlighted={unread}
|
||||
{...props}
|
||||
{...({ href } as any)}
|
||||
clickable={!!href}
|
||||
onFocus={handleFocus}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
>
|
||||
{avatar && <Sidebar.Item.Avatar>{avatar}</Sidebar.Item.Avatar>}
|
||||
<Sidebar.Item.Content>
|
||||
<Sidebar.Item.Content>
|
||||
<Sidebar.Item.Wrapper>
|
||||
{icon}
|
||||
<Sidebar.Item.Title data-qa='sidebar-item-title' className={(unread && 'rcx-sidebar-item--highlighted') as string}>
|
||||
{title}
|
||||
</Sidebar.Item.Title>
|
||||
{time && <Sidebar.Item.Time>{formatDate(time)}</Sidebar.Item.Time>}
|
||||
</Sidebar.Item.Wrapper>
|
||||
</Sidebar.Item.Content>
|
||||
<Sidebar.Item.Content>
|
||||
<Sidebar.Item.Wrapper>
|
||||
<Sidebar.Item.Subtitle className={(unread && 'rcx-sidebar-item--highlighted') as string}>{subtitle}</Sidebar.Item.Subtitle>
|
||||
<Sidebar.Item.Badge>{badges}</Sidebar.Item.Badge>
|
||||
{menu && (
|
||||
<Sidebar.Item.Menu>
|
||||
{menuVisibility ? menu() : <IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-item__menu icon='kebab' />}
|
||||
</Sidebar.Item.Menu>
|
||||
)}
|
||||
</Sidebar.Item.Wrapper>
|
||||
</Sidebar.Item.Content>
|
||||
</Sidebar.Item.Content>
|
||||
{actions && (
|
||||
<Sidebar.Item.Container>
|
||||
<Sidebar.Item.Actions>{actions}</Sidebar.Item.Actions>
|
||||
</Sidebar.Item.Container>
|
||||
)}
|
||||
</Sidebar.Item>
|
||||
<SidebarV2Item href={href} selected={selected} {...props} onFocus={handleFocus} onPointerEnter={handlePointerEnter}>
|
||||
{avatar && <SidebarV2ItemAvatarWrapper>{avatar}</SidebarV2ItemAvatarWrapper>}
|
||||
<SidebarV2ItemCol>
|
||||
<SidebarV2ItemRow>
|
||||
{icon}
|
||||
<SidebarV2ItemTitle unread={unread}>{title}</SidebarV2ItemTitle>
|
||||
{time && <SidebarV2ItemTimestamp>{formatDate(time)}</SidebarV2ItemTimestamp>}
|
||||
</SidebarV2ItemRow>
|
||||
<SidebarV2ItemRow>
|
||||
<SidebarV2ItemContent unread={unread}>{subtitle}</SidebarV2ItemContent>
|
||||
{badges}
|
||||
{actions}
|
||||
{menu && (
|
||||
<SidebarV2ItemMenu>
|
||||
{menuVisibility ? menu() : <IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-v2-item__menu icon='kebab' />}
|
||||
</SidebarV2ItemMenu>
|
||||
)}
|
||||
</SidebarV2ItemRow>
|
||||
</SidebarV2ItemCol>
|
||||
</SidebarV2Item>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
import { Sidebar, IconButton } from '@rocket.chat/fuselage';
|
||||
import type { ReactNode } from 'react';
|
||||
import { IconButton, SidebarV2Item, SidebarV2ItemAvatarWrapper, SidebarV2ItemMenu, SidebarV2ItemTitle } from '@rocket.chat/fuselage';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
type MediumProps = {
|
||||
title: ReactNode;
|
||||
titleIcon?: ReactNode;
|
||||
avatar: ReactNode;
|
||||
icon?: string;
|
||||
icon?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
href?: string;
|
||||
unread?: boolean;
|
||||
@ -14,37 +14,27 @@ type MediumProps = {
|
||||
badges?: ReactNode;
|
||||
selected?: boolean;
|
||||
menuOptions?: any;
|
||||
};
|
||||
} & Omit<HTMLAttributes<HTMLElement>, 'is'>;
|
||||
|
||||
const Medium = ({ icon, title = '', avatar, actions, href, badges, unread, menu, ...props }: MediumProps) => {
|
||||
const Medium = ({ icon, title, avatar, actions, badges, unread, menu, ...props }: MediumProps) => {
|
||||
const [menuVisibility, setMenuVisibility] = useState(!!window.DISABLE_ANIMATION);
|
||||
|
||||
const handleFocus = () => setMenuVisibility(true);
|
||||
const handlePointerEnter = () => setMenuVisibility(true);
|
||||
|
||||
return (
|
||||
<Sidebar.Item {...props} href={href} clickable={!!href} onFocus={handleFocus} onPointerEnter={handlePointerEnter}>
|
||||
{avatar && <Sidebar.Item.Avatar>{avatar}</Sidebar.Item.Avatar>}
|
||||
<Sidebar.Item.Content>
|
||||
<Sidebar.Item.Wrapper>
|
||||
{icon}
|
||||
<Sidebar.Item.Title data-qa='sidebar-item-title' className={unread ? 'rcx-sidebar-item--highlighted' : undefined}>
|
||||
{title}
|
||||
</Sidebar.Item.Title>
|
||||
</Sidebar.Item.Wrapper>
|
||||
{badges && <Sidebar.Item.Badge>{badges}</Sidebar.Item.Badge>}
|
||||
{menu && (
|
||||
<Sidebar.Item.Menu>
|
||||
{menuVisibility ? menu() : <IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-item__menu icon='kebab' />}
|
||||
</Sidebar.Item.Menu>
|
||||
)}
|
||||
</Sidebar.Item.Content>
|
||||
{actions && (
|
||||
<Sidebar.Item.Container>
|
||||
<Sidebar.Item.Actions>{actions}</Sidebar.Item.Actions>
|
||||
</Sidebar.Item.Container>
|
||||
<SidebarV2Item {...props} onFocus={handleFocus} onPointerEnter={handlePointerEnter}>
|
||||
<SidebarV2ItemAvatarWrapper>{avatar}</SidebarV2ItemAvatarWrapper>
|
||||
{icon}
|
||||
<SidebarV2ItemTitle unread={unread}>{title}</SidebarV2ItemTitle>
|
||||
{badges}
|
||||
{actions}
|
||||
{menu && (
|
||||
<SidebarV2ItemMenu>
|
||||
{menuVisibility ? menu() : <IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-v2-item__menu icon='kebab' />}
|
||||
</SidebarV2ItemMenu>
|
||||
)}
|
||||
</Sidebar.Item>
|
||||
</SidebarV2Item>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,33 +1,32 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useResizeObserver } from '@rocket.chat/fuselage-hooks';
|
||||
import { VirtualizedScrollbars } from '@rocket.chat/ui-client';
|
||||
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
|
||||
import { useUserPreference, useUserId } from '@rocket.chat/ui-contexts';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Virtuoso } from 'react-virtuoso';
|
||||
import { GroupedVirtuoso } from 'react-virtuoso';
|
||||
|
||||
import RoomListCollapser from './RoomListCollapser';
|
||||
import RoomListRow from './RoomListRow';
|
||||
import RoomListRowWrapper from './RoomListRowWrapper';
|
||||
import RoomListWrapper from './RoomListWrapper';
|
||||
import { useOpenedRoom } from '../../lib/RoomManager';
|
||||
import { useAvatarTemplate } from '../hooks/useAvatarTemplate';
|
||||
import { useCollapsedGroups } from '../hooks/useCollapsedGroups';
|
||||
import { usePreventDefault } from '../hooks/usePreventDefault';
|
||||
import { useRoomList } from '../hooks/useRoomList';
|
||||
import { useShortcutOpenMenu } from '../hooks/useShortcutOpenMenu';
|
||||
import { useTemplateByViewMode } from '../hooks/useTemplateByViewMode';
|
||||
|
||||
const computeItemKey = (index: number, room: SubscriptionWithRoom): SubscriptionWithRoom['_id'] | number => room._id || index;
|
||||
|
||||
const RoomList = (): ReactElement => {
|
||||
const RoomList = () => {
|
||||
const { t } = useTranslation();
|
||||
const isAnonymous = !useUserId();
|
||||
const roomsList = useRoomList();
|
||||
|
||||
const { collapsedGroups, handleClick, handleKeyDown } = useCollapsedGroups();
|
||||
const { groupsCount, groupsList, roomList, groupedUnreadInfo } = useRoomList({ collapsedGroups });
|
||||
const avatarTemplate = useAvatarTemplate();
|
||||
const sideBarItemTemplate = useTemplateByViewMode();
|
||||
const { ref } = useResizeObserver({ debounceDelay: 100 });
|
||||
const { ref } = useResizeObserver<HTMLElement>({ debounceDelay: 100 });
|
||||
const openedRoom = useOpenedRoom() ?? '';
|
||||
const sidebarViewMode = useUserPreference<'extended' | 'medium' | 'condensed'>('sidebarViewMode') || 'extended';
|
||||
|
||||
@ -36,7 +35,7 @@ const RoomList = (): ReactElement => {
|
||||
() => ({
|
||||
extended,
|
||||
t,
|
||||
SideBarItemTemplate: sideBarItemTemplate,
|
||||
SidebarItemTemplate: sideBarItemTemplate,
|
||||
AvatarTemplate: avatarTemplate,
|
||||
openedRoom,
|
||||
sidebarViewMode,
|
||||
@ -48,92 +47,26 @@ const RoomList = (): ReactElement => {
|
||||
usePreventDefault(ref);
|
||||
useShortcutOpenMenu(ref);
|
||||
|
||||
const roomsListStyle = css`
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
|
||||
overflow-x: hidden;
|
||||
overflow-y: hidden;
|
||||
|
||||
flex: 1 1 auto;
|
||||
|
||||
height: 100%;
|
||||
|
||||
&--embedded {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
&__list:not(:last-child) {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
&__type {
|
||||
display: flex;
|
||||
|
||||
flex-direction: row;
|
||||
|
||||
padding: 0 var(--sidebar-default-padding) 1rem var(--sidebar-default-padding);
|
||||
|
||||
color: var(--rooms-list-title-color);
|
||||
|
||||
font-size: var(--rooms-list-title-text-size);
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&-text--livechat {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__empty-room {
|
||||
padding: 0 var(--sidebar-default-padding);
|
||||
|
||||
color: var(--rooms-list-empty-text-color);
|
||||
|
||||
font-size: var(--rooms-list-empty-text-size);
|
||||
}
|
||||
|
||||
&__toolbar-search {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
|
||||
overflow-y: scroll;
|
||||
|
||||
height: 100%;
|
||||
|
||||
background-color: var(--sidebar-background);
|
||||
|
||||
padding-block-start: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 400px) {
|
||||
padding: 0 calc(var(--sidebar-small-default-padding) - 4px);
|
||||
|
||||
&__type,
|
||||
&__empty-room {
|
||||
padding: 0 calc(var(--sidebar-small-default-padding) - 4px) 0.5rem calc(var(--sidebar-small-default-padding) - 4px);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
return (
|
||||
<Box className={[roomsListStyle, 'sidebar--custom-colors'].filter(Boolean)}>
|
||||
<Box h='full' w='full' ref={ref}>
|
||||
<VirtualizedScrollbars>
|
||||
<Virtuoso
|
||||
totalCount={roomsList.length}
|
||||
data={roomsList}
|
||||
components={{
|
||||
Item: RoomListRowWrapper,
|
||||
List: RoomListWrapper,
|
||||
}}
|
||||
computeItemKey={computeItemKey}
|
||||
itemContent={(_, data): ReactElement => <RoomListRow data={itemData} item={data} />}
|
||||
/>
|
||||
</VirtualizedScrollbars>
|
||||
</Box>
|
||||
<Box position='relative' overflow='hidden' height='full' ref={ref}>
|
||||
<VirtualizedScrollbars>
|
||||
<GroupedVirtuoso
|
||||
groupCounts={groupsCount}
|
||||
groupContent={(index) => (
|
||||
<RoomListCollapser
|
||||
collapsedGroups={collapsedGroups}
|
||||
onClick={() => handleClick(groupsList[index])}
|
||||
onKeyDown={(e) => handleKeyDown(e, groupsList[index])}
|
||||
groupTitle={groupsList[index]}
|
||||
unreadCount={groupedUnreadInfo[index]}
|
||||
/>
|
||||
)}
|
||||
{...(roomList.length > 0 && {
|
||||
itemContent: (index) => roomList[index] && <RoomListRow data={itemData} item={roomList[index]} />,
|
||||
})}
|
||||
components={{ Item: RoomListRowWrapper, List: RoomListWrapper }}
|
||||
/>
|
||||
</VirtualizedScrollbars>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,26 +1,27 @@
|
||||
import { SidebarSection } from '@rocket.chat/fuselage';
|
||||
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
|
||||
import { useVideoConfAcceptCall, useVideoConfRejectIncomingCall, useVideoConfIncomingCalls } from '@rocket.chat/ui-video-conf';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { ReactElement } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
import SideBarItemTemplateWithData from './SideBarItemTemplateWithData';
|
||||
import SidebarItemTemplateWithData from './SidebarItemTemplateWithData';
|
||||
import type { useAvatarTemplate } from '../hooks/useAvatarTemplate';
|
||||
import type { useTemplateByViewMode } from '../hooks/useTemplateByViewMode';
|
||||
|
||||
type RoomListRowProps = {
|
||||
extended: boolean;
|
||||
t: TFunction;
|
||||
SideBarItemTemplate: ReturnType<typeof useTemplateByViewMode>;
|
||||
AvatarTemplate: ReturnType<typeof useAvatarTemplate>;
|
||||
openedRoom: string;
|
||||
sidebarViewMode: 'extended' | 'condensed' | 'medium';
|
||||
isAnonymous: boolean;
|
||||
data: {
|
||||
extended: boolean;
|
||||
t: TFunction;
|
||||
SidebarItemTemplate: ReturnType<typeof useTemplateByViewMode>;
|
||||
AvatarTemplate: ReturnType<typeof useAvatarTemplate>;
|
||||
openedRoom: string;
|
||||
sidebarViewMode: 'extended' | 'condensed' | 'medium';
|
||||
isAnonymous: boolean;
|
||||
};
|
||||
item: SubscriptionWithRoom;
|
||||
};
|
||||
|
||||
const RoomListRow = ({ data, item }: { data: RoomListRowProps; item: SubscriptionWithRoom }): ReactElement => {
|
||||
const { extended, t, SideBarItemTemplate, AvatarTemplate, openedRoom, sidebarViewMode } = data;
|
||||
const RoomListRow = ({ data, item }: RoomListRowProps) => {
|
||||
const { extended, t, SidebarItemTemplate, AvatarTemplate, openedRoom, sidebarViewMode } = data;
|
||||
|
||||
const acceptCall = useVideoConfAcceptCall();
|
||||
const rejectCall = useVideoConfRejectIncomingCall();
|
||||
@ -36,22 +37,14 @@ const RoomListRow = ({ data, item }: { data: RoomListRowProps; item: Subscriptio
|
||||
[acceptCall, rejectCall, currentCall],
|
||||
);
|
||||
|
||||
if (typeof item === 'string') {
|
||||
return (
|
||||
<SidebarSection>
|
||||
<SidebarSection.Title>{t(item)}</SidebarSection.Title>
|
||||
</SidebarSection>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<SideBarItemTemplateWithData
|
||||
<SidebarItemTemplateWithData
|
||||
sidebarViewMode={sidebarViewMode}
|
||||
selected={item.rid === openedRoom}
|
||||
t={t}
|
||||
room={item}
|
||||
extended={extended}
|
||||
SideBarItemTemplate={SideBarItemTemplate}
|
||||
SidebarItemTemplate={SidebarItemTemplate}
|
||||
AvatarTemplate={AvatarTemplate}
|
||||
videoConfActions={videoConfActions}
|
||||
/>
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import type { HTMLAttributes, Ref } from 'react';
|
||||
import { SidebarV2ListItem } from '@rocket.chat/fuselage';
|
||||
import type { ForwardedRef, HTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
const RoomListRoomWrapper = forwardRef(function RoomListRoomWrapper(props: HTMLAttributes<HTMLDivElement>, ref: Ref<HTMLDivElement>) {
|
||||
return <div role='listitem' ref={ref} {...props} />;
|
||||
type RoomListRoomWrapperProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const RoomListRoomWrapper = forwardRef(function RoomListRoomWrapper(props: RoomListRoomWrapperProps, ref: ForwardedRef<HTMLDivElement>) {
|
||||
return <SidebarV2ListItem ref={ref} {...props} />;
|
||||
});
|
||||
|
||||
export default RoomListRoomWrapper;
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { useMergedRefs } from '@rocket.chat/fuselage-hooks';
|
||||
import type { HTMLAttributes, Ref } from 'react';
|
||||
import type { ForwardedRef, HTMLAttributes } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useSidebarListNavigation } from './useSidebarListNavigation';
|
||||
|
||||
const RoomListWrapper = forwardRef(function RoomListWrapper(props: HTMLAttributes<HTMLDivElement>, ref: Ref<HTMLDivElement>) {
|
||||
type RoomListWrapperProps = HTMLAttributes<HTMLDivElement>;
|
||||
|
||||
const RoomListWrapper = forwardRef(function RoomListWrapper(props: RoomListWrapperProps, ref: ForwardedRef<HTMLDivElement>) {
|
||||
const { t } = useTranslation();
|
||||
const { sidebarListRef } = useSidebarListNavigation();
|
||||
const mergedRefs = useMergedRefs(ref, sidebarListRef);
|
||||
|
||||
@ -1,226 +0,0 @@
|
||||
import type { IMessage } from '@rocket.chat/core-typings';
|
||||
import { isDirectMessageRoom, isMultipleDirectMessageRoom, isOmnichannelRoom, isVideoConfMessage } from '@rocket.chat/core-typings';
|
||||
import { Sidebar, SidebarItemAction, SidebarItemActions } from '@rocket.chat/fuselage';
|
||||
import type { SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
|
||||
import { useLayout } from '@rocket.chat/ui-contexts';
|
||||
import DOMPurify from 'dompurify';
|
||||
import type { TFunction } from 'i18next';
|
||||
import type { AllHTMLAttributes, ComponentType, ReactElement, ReactNode } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
|
||||
import { normalizeSidebarMessage } from './normalizeSidebarMessage';
|
||||
import { RoomIcon } from '../../components/RoomIcon';
|
||||
import { roomCoordinator } from '../../lib/rooms/roomCoordinator';
|
||||
import { isIOsDevice } from '../../lib/utils/isIOsDevice';
|
||||
import { useOmnichannelPriorities } from '../../views/omnichannel/hooks/useOmnichannelPriorities';
|
||||
import RoomMenu from '../RoomMenu';
|
||||
import SidebarItemBadges from '../badges/SidebarItemBadges';
|
||||
import type { useAvatarTemplate } from '../hooks/useAvatarTemplate';
|
||||
import { useUnreadDisplay } from '../hooks/useUnreadDisplay';
|
||||
|
||||
const getMessage = (room: SubscriptionWithRoom, lastMessage: IMessage | undefined, t: TFunction): string | undefined => {
|
||||
if (!lastMessage) {
|
||||
return t('No_messages_yet');
|
||||
}
|
||||
if (isVideoConfMessage(lastMessage)) {
|
||||
return t('Call_started');
|
||||
}
|
||||
if (!lastMessage.u) {
|
||||
return normalizeSidebarMessage(lastMessage, t);
|
||||
}
|
||||
if (lastMessage.u?.username === room.u?.username) {
|
||||
return `${t('You')}: ${normalizeSidebarMessage(lastMessage, t)}`;
|
||||
}
|
||||
if (isDirectMessageRoom(room) && !isMultipleDirectMessageRoom(room)) {
|
||||
return normalizeSidebarMessage(lastMessage, t);
|
||||
}
|
||||
return `${lastMessage.u.name || lastMessage.u.username}: ${normalizeSidebarMessage(lastMessage, t)}`;
|
||||
};
|
||||
|
||||
type RoomListRowProps = {
|
||||
extended: boolean;
|
||||
t: TFunction;
|
||||
SideBarItemTemplate: ComponentType<
|
||||
{
|
||||
icon: ReactNode;
|
||||
title: ReactNode;
|
||||
avatar: ReactNode;
|
||||
actions: unknown;
|
||||
href: string;
|
||||
time?: Date;
|
||||
menu?: () => ReactNode;
|
||||
menuOptions?: unknown;
|
||||
subtitle?: ReactNode;
|
||||
titleIcon?: string;
|
||||
badges?: ReactNode;
|
||||
threadUnread?: boolean;
|
||||
unread?: boolean;
|
||||
selected?: boolean;
|
||||
is?: string;
|
||||
} & AllHTMLAttributes<HTMLElement>
|
||||
>;
|
||||
AvatarTemplate: ReturnType<typeof useAvatarTemplate>;
|
||||
openedRoom?: string;
|
||||
// sidebarViewMode: 'extended';
|
||||
isAnonymous?: boolean;
|
||||
|
||||
room: SubscriptionWithRoom;
|
||||
id?: string;
|
||||
/* @deprecated */
|
||||
style?: AllHTMLAttributes<HTMLElement>['style'];
|
||||
|
||||
selected?: boolean;
|
||||
|
||||
sidebarViewMode?: unknown;
|
||||
videoConfActions?: {
|
||||
[action: string]: () => void;
|
||||
};
|
||||
};
|
||||
|
||||
function SideBarItemTemplateWithData({
|
||||
room,
|
||||
id,
|
||||
selected,
|
||||
style,
|
||||
extended,
|
||||
SideBarItemTemplate,
|
||||
AvatarTemplate,
|
||||
t,
|
||||
isAnonymous,
|
||||
videoConfActions,
|
||||
}: RoomListRowProps): ReactElement {
|
||||
const { sidebar } = useLayout();
|
||||
|
||||
const href = roomCoordinator.getRouteLink(room.t, room) || '';
|
||||
const title = roomCoordinator.getRoomName(room.t, room) || '';
|
||||
|
||||
const { lastMessage, unread = 0, alert, rid, t: type, cl } = room;
|
||||
|
||||
const { unreadCount, unreadTitle, showUnread, highlightUnread: highlighted } = useUnreadDisplay(room);
|
||||
|
||||
const icon = (
|
||||
// TODO: Remove icon='at'
|
||||
<Sidebar.Item.Icon highlighted={highlighted} icon='at'>
|
||||
<RoomIcon room={room} placement='sidebar' isIncomingCall={Boolean(videoConfActions)} />
|
||||
</Sidebar.Item.Icon>
|
||||
);
|
||||
|
||||
const actions = useMemo(
|
||||
() =>
|
||||
videoConfActions && (
|
||||
<SidebarItemActions>
|
||||
<SidebarItemAction onClick={videoConfActions.acceptCall} secondary success icon='phone' />
|
||||
<SidebarItemAction onClick={videoConfActions.rejectCall} secondary danger icon='phone-off' />
|
||||
</SidebarItemActions>
|
||||
),
|
||||
[videoConfActions],
|
||||
);
|
||||
|
||||
const isQueued = isOmnichannelRoom(room) && room.status === 'queued';
|
||||
const { enabled: isPriorityEnabled } = useOmnichannelPriorities();
|
||||
|
||||
const message = extended && getMessage(room, lastMessage, t);
|
||||
const subtitle = message ? (
|
||||
<span className='message-body--unstyled' dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(message) }} />
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<SideBarItemTemplate
|
||||
is='a'
|
||||
id={id}
|
||||
data-qa='sidebar-item'
|
||||
data-unread={highlighted}
|
||||
unread={highlighted}
|
||||
selected={selected}
|
||||
href={href}
|
||||
onClick={(): void => {
|
||||
!selected && sidebar.toggle();
|
||||
}}
|
||||
aria-label={showUnread ? t('__unreadTitle__from__roomTitle__', { unreadTitle, roomTitle: title }) : title}
|
||||
title={title}
|
||||
time={lastMessage?.ts}
|
||||
subtitle={subtitle}
|
||||
icon={icon}
|
||||
style={style}
|
||||
badges={<SidebarItemBadges room={room} roomTitle={title} />}
|
||||
avatar={AvatarTemplate && <AvatarTemplate {...room} />}
|
||||
actions={actions}
|
||||
menu={
|
||||
!isIOsDevice && !isAnonymous && (!isQueued || (isQueued && isPriorityEnabled))
|
||||
? (): ReactElement => (
|
||||
<RoomMenu
|
||||
alert={alert}
|
||||
threadUnread={unreadCount.threads > 0}
|
||||
rid={rid}
|
||||
unread={!!unread}
|
||||
roomOpen={selected}
|
||||
type={type}
|
||||
cl={cl}
|
||||
name={title}
|
||||
hideDefaultOptions={isQueued}
|
||||
/>
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function safeDateNotEqualCheck(a: Date | string | undefined, b: Date | string | undefined): boolean {
|
||||
if (!a || !b) {
|
||||
return a !== b;
|
||||
}
|
||||
return new Date(a).toISOString() !== new Date(b).toISOString();
|
||||
}
|
||||
|
||||
const keys: (keyof RoomListRowProps)[] = [
|
||||
'id',
|
||||
'style',
|
||||
'extended',
|
||||
'selected',
|
||||
'SideBarItemTemplate',
|
||||
'AvatarTemplate',
|
||||
't',
|
||||
'sidebarViewMode',
|
||||
'videoConfActions',
|
||||
];
|
||||
|
||||
// eslint-disable-next-line react/no-multi-comp
|
||||
export default memo(SideBarItemTemplateWithData, (prevProps, nextProps) => {
|
||||
if (keys.some((key) => prevProps[key] !== nextProps[key])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (prevProps.room === nextProps.room) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (prevProps.room._id !== nextProps.room._id) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.room._updatedAt?.toISOString() !== nextProps.room._updatedAt?.toISOString()) {
|
||||
return false;
|
||||
}
|
||||
if (safeDateNotEqualCheck(prevProps.room.lastMessage?._updatedAt, nextProps.room.lastMessage?._updatedAt)) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.room.alert !== nextProps.room.alert) {
|
||||
return false;
|
||||
}
|
||||
if (isOmnichannelRoom(prevProps.room) && isOmnichannelRoom(nextProps.room) && prevProps.room?.v?.status !== nextProps.room?.v?.status) {
|
||||
return false;
|
||||
}
|
||||
if (prevProps.room.teamMain !== nextProps.room.teamMain) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isOmnichannelRoom(prevProps.room) &&
|
||||
isOmnichannelRoom(nextProps.room) &&
|
||||
prevProps.room.priorityWeight !== nextProps.room.priorityWeight
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
@ -1,12 +1,13 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useFocusManager } from 'react-aria';
|
||||
|
||||
const isListItem = (node: EventTarget) => (node as HTMLElement).classList.contains('rcx-sidebar-item');
|
||||
const isListItemMenu = (node: EventTarget) => (node as HTMLElement).classList.contains('rcx-sidebar-item__menu');
|
||||
const isListItem = (node: EventTarget) => (node as HTMLElement).classList.contains('rcx-sidebar-v2-item');
|
||||
const isCollapseGroup = (node: EventTarget) => (node as HTMLElement).classList.contains('rcx-sidebar-v2-collapse-group__bar');
|
||||
const isListItemMenu = (node: EventTarget) => (node as HTMLElement).classList.contains('rcx-sidebar-v2-item__menu');
|
||||
|
||||
/**
|
||||
* Custom hook to provide the sidebar navigation by keyboard.
|
||||
* @param ref - A ref to the message list DOM element.
|
||||
* @returns ref - A ref to the message list DOM element.
|
||||
*/
|
||||
export const useSidebarListNavigation = () => {
|
||||
const sidebarListFocusManager = useFocusManager();
|
||||
@ -24,7 +25,7 @@ export const useSidebarListNavigation = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isListItem(e.target)) {
|
||||
if (!isListItem(e.target) && !isCollapseGroup(e.target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -34,26 +35,29 @@ export const useSidebarListNavigation = () => {
|
||||
|
||||
if (e.shiftKey) {
|
||||
sidebarListFocusManager?.focusPrevious({
|
||||
accept: (node) => !isListItem(node) && !isListItemMenu(node),
|
||||
accept: (node) => !isListItem(node) && !isListItemMenu(node) && !isCollapseGroup(node),
|
||||
});
|
||||
} else if (isListItemMenu(e.target)) {
|
||||
sidebarListFocusManager?.focusNext({
|
||||
accept: (node) => !isListItem(node) && !isListItemMenu(node),
|
||||
accept: (node) => !isListItem(node) && !isListItemMenu(node) && !isCollapseGroup(node),
|
||||
});
|
||||
} else {
|
||||
sidebarListFocusManager?.focusNext({
|
||||
accept: (node) => !isListItem(node),
|
||||
accept: (node) => !isListItem(node) && !isCollapseGroup(node),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.key === 'ArrowUp') {
|
||||
sidebarListFocusManager?.focusPrevious({ accept: (node) => isListItem(node) });
|
||||
sidebarListFocusManager?.focusPrevious({ accept: (node) => isListItem(node) || isCollapseGroup(node) });
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
sidebarListFocusManager?.focusNext({ accept: (node) => isListItem(node) });
|
||||
sidebarListFocusManager?.focusNext({ accept: (node) => isListItem(node) || isCollapseGroup(node) });
|
||||
}
|
||||
|
||||
lastItemFocused = document.activeElement as HTMLElement;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user