Migrate Toggle to Compass Switch (#39038)
* Migrate Toggle component to Compass Switch adapter Rewrite components/toggle.tsx to wrap @mattermost/compass-ui Switch with prop mapping and sibling labels for onText/offText. Update Jest tests and snapshots for switch/checkbox semantics (role=switch, checked state). Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Place Toggle onText/offText on Compass Switch label Pass state text through Switch children so Public/Private sits left of the control and remains clickable for accessibility. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Use medium Compass Switch for plugin enable control PluginEnableButton Toggle size btn-sm → btn-md (Compass Switch medium). Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Stop forwarding legacy btn-toggle classes onto Compass Switch Toggle is a Compass Switch adapter; call sites still passed toggleClassName='btn-toggle-primary', which painted the old gray rectangular track chrome from _toggle.scss behind the Switch. Remove the prop, drop those call-site classNames, and delete the unused legacy toggle stylesheet. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix post priority Switch double-toggle in menu items Clicking the Compass Switch inside ToggleItem fired both Switch onChange and MenuItem onClick for the same gesture, so the handler ran twice and the control appeared stuck. Stop click propagation on the switch and add a unit test covering switch and row activation. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Use Compass Switch settings-row for channel settings toggles Replace custom __text + Toggle header rows with Switch children + secondaryLabel for configuration, discoverable, and shared-channels sections. Drop obsolete header SCSS; keep existing testids/behavior. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Migrate Admin Console LineSwitch rows to Compass Switch Replace Toggle-based LineSwitch with AdminSettingsSwitch (children + secondaryLabel), covering group mentions, team modes, and channel modes. Public/Private uses state text as the primary label; domain input stays as a footer under the switch. Delete unused LineSwitch styles and update Cypress selectors for role=switch inputs. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Migrate remaining Toggle call sites to Compass Switch Replace production Toggle adapters with direct @mattermost/compass-ui Switch usage across new channel modal, post priority, recaps, plugin enable, user properties, auto-translation, IP filtering, and content flagging reviewers. Delete the unused Toggle component and its tests. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Document medium Switch size for Admin Console Public/Private row AdminSettingsSwitch intentionally uses Compass medium (14px) + semiBold so short state labels like Public/Private match Channel Settings and do not inherit the old Toggle btn-lg → large (16px) default. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix Team Modes separator when Sync Group Members is on Omit the Sync row section separator only when no later row is visible. ABAC still renders disabled while synced, so last must not track isSynced alone. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Use static Public/Private Switch title in Channel Management Stop swapping Switch children between "Public" and "Private". Keep a static "Public channel or private channel" label with help text as secondaryLabel; checked remains public and unchecked private. Update Cypress assertions and channel_modes snapshots accordingly. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Show current Public/Private mode on channel Switch label Use a dynamic primary label (Public channel / Private channel) so checked state is scannable without a segmented control. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Update Cypress for dynamic Public/Private Switch labels Assert Public channel when checked and Private channel when unchecked. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix plugin enable Switch alignment next to actions menu Zero Bootstrap label margin-bottom and Switch width:100% on the PluginMetadataPanel enable control so it lines up with the ⋯ button. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Use segmented Public/Private control in Channel Management Replace the ambiguous Switch for channel privacy with a temporary AdminSettingsSegmentedRow (Public | Private) until Compass ships a segmented control. Update Cypress selectors accordingly. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix IP Filtering enable Switch not revealing edit config Use event.checked for the Compass Switch, skip the filterToggle sync effect while ipFilters is still null so an empty [] does not race the fetch, and show EditSection whenever filters are loaded and the toggle is on (do not wait on currentUsersIP). Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Increase Public/Private segmented control border radii Round the track to 8px and active segments to 4px for a softer Channel Management Public/Private control. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Increase Public/Private segmented control track padding Bump &__segments padding from 2px to 4px. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Soften Public/Private segmented control border Lower track border opacity from 0.16 to 0.08. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Darken Public/Private segmented control track background Raise &__segments background opacity from 0.04 to 0.08. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Address review comments on Switch a11y and formatting Align LDAP Cypress private-toggle assertion indentation, add aria-labels on IP Filtering and Auto-translation switches, and fix AdminSettingsSwitch import order for eslint import/order. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix eslint failures blocking Web App CI check-lint Add blank line before comment in channel_modes and reorder imports in post_priority_picker_item.test to satisfy import/order. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Align en catalogue isPublicDescr with segmented control copy Update admin.channel_settings.channel_details.isPublicDescr to say "Use this control" instead of "Use this switch", matching channel_modes. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix Switch migration CI failures in unit and Playwright tests Update recap configuration queries to role=switch, refresh channel_details snapshots for the Public/Private segmented control, and assert/set Compass Switch via aria-checked check/uncheck in Playwright helpers and related specs. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Stabilize Switch snapshots and fix Playwright check interception Mock Compass Switch CSS-module styles in Jest so snapshots no longer embed install-specific content hashes, and use force:true on Switch check/uncheck so the wrapping label does not intercept pointer events. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Update Channel Management Public/Private docs for segmented control Replace toggle wording with select Public or Private, and refresh the System Console screenshot to match AdminSettingsSegmentedRow. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Revert Public/Private docs wording; keep screenshot refresh only The step text can stay as toggle between Public or Private; only the stale screenshot needed updating for the segmented control. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Refresh Sync Group Members channel docs screenshot for Compass Switch Replace the stale Channel Management flip-toggle image with an updated System Console capture showing Sync Group Members enabled. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Refresh Anyone can join this team docs screenshot for Compass Switch Replace the stale Team Management flip-toggle image with an updated System Console capture showing Anyone can join enabled. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Refresh specific email domains team docs screenshot for Compass Switch Replace the stale Team Management flip-toggle image with an updated System Console capture showing email-domain restriction enabled. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Refresh Public/Private channel docs screenshot for segmented control Replace the Channel Management Public/Private docs image with an updated System Console capture showing the Public|Private segmented row. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Refresh Sync Group Members team docs screenshot for Compass Switch Replace the stale Team Management flip-toggle image with an updated System Console capture showing Sync Group Members enabled. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix Playwright Switch assertions for join/leave and scheduled recaps Assert join/leave defaults with toBeChecked instead of the legacy active CSS class, and pause/resume scheduled recaps via click plus polled expect helpers so async-controlled Compass Switches can settle. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Bump @mattermost/compass-ui to 0.1.0-alpha.15 Upgrade the Compass UI dependency used by Switch and related admin console controls. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Restore currentUsersIP gate for IP Filtering EditSection Keep the Toggle→Switch migration scoped to the adapter swap. Showing EditSection still requires the user's IP to be loaded, matching prior behavior. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Fix plugin install Cypress asserts for Compass Switch aria-checked Plugin enable controls use Switch (aria-checked), not segmented buttons (aria-pressed). Update MM-T5714/MM-T5715 assertions accordingly. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> * Revert IP Filtering filterToggle sync to master behavior The ipFilters === null early return and consolidated map were outside the Toggle→Switch swap scope and mildly changed load-time behavior. Co-authored-by: Matthew Birtch <mattbirtch@gmail.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
No files matched your search
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 118 KiB |
@@ -14,12 +14,10 @@ export function enableGroupMention(groupName: string, groupID: string, userEmail
|
||||
}
|
||||
cy.get('#group_profile').scrollIntoView().wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Click the allow reference button
|
||||
cy.findByTestId('allowReferenceSwitch').then((el) => {
|
||||
const button = el.find('button');
|
||||
const classAttribute = button[0]?.getAttribute('class') ?? '';
|
||||
if (!classAttribute.includes('active')) {
|
||||
button[0].click();
|
||||
// # Enable group mentions if not already on
|
||||
cy.findByTestId('allowReferenceSwitch-button').then(($input) => {
|
||||
if (!$input.prop('checked')) {
|
||||
cy.wrap($input).click({force: true});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -131,9 +131,8 @@ context('ldap', () => {
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// # Turn on sync group members
|
||||
cy.findByTestId('syncGroupSwitch').
|
||||
cy.findByTestId('syncGroupSwitch-button').
|
||||
scrollIntoView().
|
||||
findByRole('button').
|
||||
click({force: true});
|
||||
|
||||
// # Add board group to team
|
||||
@@ -211,17 +210,21 @@ context('ldap', () => {
|
||||
cy.wait(TIMEOUTS.TWO_SEC);
|
||||
|
||||
// Make it private and then cancel
|
||||
cy.findByTestId('allow-all-toggle').click();
|
||||
cy.findByTestId('allow-all-toggle-private').click({force: true});
|
||||
cy.get('#cancelButtonSettings').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
cy.visit(`/admin_console/user_management/channels/${testChannel.id}`);
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Channel Configuration');
|
||||
|
||||
// * Ensure it still public
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Public');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'false');
|
||||
|
||||
// Make it private and save
|
||||
cy.findByTestId('allow-all-toggle').click();
|
||||
cy.findByTestId('allow-all-toggle-private').click({force: true});
|
||||
cy.get('#saveSetting').should('be.enabled').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
|
||||
@@ -230,7 +233,11 @@ context('ldap', () => {
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Channel Configuration');
|
||||
|
||||
// * Ensure it is Private
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Private');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'false');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'true');
|
||||
|
||||
// # Go to team page to look for this channel in public channel directory
|
||||
cy.visit(`/${testTeam.name}`);
|
||||
@@ -256,7 +263,7 @@ context('ldap', () => {
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Channel Configuration');
|
||||
|
||||
// Make it public and then cancel
|
||||
cy.findByTestId('allow-all-toggle').click();
|
||||
cy.findByTestId('allow-all-toggle-public').click({force: true});
|
||||
cy.get('#cancelButtonSettings').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Mattermost Channels');
|
||||
@@ -268,7 +275,11 @@ context('ldap', () => {
|
||||
|
||||
// Make it public and save
|
||||
// * Ensure it still showing the channel as private
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Private').click();
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-public').click({force: true});
|
||||
cy.get('#saveSetting').should('be.enabled').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Mattermost Channels');
|
||||
@@ -277,8 +288,12 @@ context('ldap', () => {
|
||||
cy.visit(`/admin_console/user_management/channels/${privateChannel.id}`);
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Channel Configuration');
|
||||
|
||||
// * Ensure it still showing the channel as private
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Public');
|
||||
// * Ensure it is showing the channel as public
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'false');
|
||||
|
||||
// # Ensure the last message in the message says that it was converted to a public channel
|
||||
cy.visit(`/${testTeam.name}/channels/${privateChannel.name}`);
|
||||
@@ -303,7 +318,8 @@ context('ldap', () => {
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Channel Configuration');
|
||||
|
||||
// * Ensure the toggle to private/public is disabled
|
||||
cy.findByTestId('allow-all-toggle-button').should('be.disabled');
|
||||
cy.findByTestId('allow-all-toggle-public').should('be.disabled');
|
||||
cy.findByTestId('allow-all-toggle-private').should('be.disabled');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ describe('LDAP guest', () => {
|
||||
cy.get('.admin-console__header', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible').and('have.text', 'Team Configuration');
|
||||
|
||||
// # Turn on sync group members
|
||||
cy.findByTestId('syncGroupSwitch').scrollIntoView().click();
|
||||
cy.findByTestId('syncGroupSwitch-button').scrollIntoView().click({force: true});
|
||||
|
||||
// # Add board group to team
|
||||
cy.get('#addGroupsToTeamToggle').scrollIntoView().click();
|
||||
|
||||
@@ -35,7 +35,7 @@ describe('LDAP Group Sync - Test channel public/private toggle', () => {
|
||||
assert(channel.type === 'O');
|
||||
cy.visit(`/admin_console/user_management/channels/${channel.id}`);
|
||||
cy.get('#channel_profile').contains(channel.display_name);
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(1).click();
|
||||
cy.findByTestId('allow-all-toggle-private').click({force: true});
|
||||
cy.get('#saveSetting').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
return cy.apiGetChannel(channel.id);
|
||||
@@ -43,7 +43,7 @@ describe('LDAP Group Sync - Test channel public/private toggle', () => {
|
||||
assert(channel.type === 'P');
|
||||
cy.visit(`/admin_console/user_management/channels/${channel.id}`);
|
||||
cy.get('#channel_profile').contains(channel.display_name);
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(1).click();
|
||||
cy.findByTestId('allow-all-toggle-public').click({force: true});
|
||||
cy.get('#saveSetting').click();
|
||||
cy.get('#confirmModalButton').click();
|
||||
return cy.apiGetChannel(channel.id);
|
||||
@@ -57,13 +57,20 @@ describe('LDAP Group Sync - Test channel public/private toggle', () => {
|
||||
assert(channel.type === 'O');
|
||||
cy.visit(`/admin_console/user_management/channels/${channel.id}`);
|
||||
cy.get('#channel_profile').contains(channel.display_name);
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(0).click();
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(0).click();
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(1).contains('Public');
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(1).click();
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(0).click();
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(0).click();
|
||||
cy.get('#channel_manage .group-teams-and-channels--body').find('button').eq(1).contains('Private');
|
||||
cy.findByTestId('syncGroupSwitch-button').click({force: true});
|
||||
cy.findByTestId('syncGroupSwitch-button').click({force: true});
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-private').click({force: true});
|
||||
cy.findByTestId('syncGroupSwitch-button').click({force: true});
|
||||
cy.findByTestId('syncGroupSwitch-button').click({force: true});
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'false');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'true');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,13 +80,14 @@ describe('LDAP Group Sync - Test channel public/private toggle', () => {
|
||||
cy.visit(`/admin_console/user_management/channels/${id}`);
|
||||
cy.get('#channel_profile').contains('Town Square');
|
||||
cy.get('#channel_manage').scrollIntoView().should('be.visible').within(() => {
|
||||
cy.get('.line-switch').first().within(() => {
|
||||
cy.findByTestId('syncGroupSwitch').within(() => {
|
||||
cy.findByText('Sync Group Members').should('be.visible');
|
||||
cy.findByTestId('syncGroupSwitch-button').should('be.disabled');
|
||||
});
|
||||
cy.get('.line-switch').last().within(() => {
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
cy.findByTestId('allow-all-toggle-button').should('be.disabled');
|
||||
cy.findByTestId('allow-all-toggle-public').should('be.disabled');
|
||||
cy.findByTestId('allow-all-toggle-private').should('be.disabled');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -187,33 +187,29 @@ describe('System Console', () => {
|
||||
// # Login as sysadmin and navigate to board group page
|
||||
navigateToGroup(groupID);
|
||||
|
||||
// # Click the allow reference button
|
||||
cy.findByTestId('allowReferenceSwitch').then((el) => {
|
||||
el.find('button').click();
|
||||
// # Enable group mentions
|
||||
cy.findByTestId('allowReferenceSwitch-button').click({force: true});
|
||||
|
||||
// # Give the group a custom name different from its DisplayName attribute
|
||||
cy.get('#groupMention').find('input').clear().type(groupName);
|
||||
// # Give the group a custom name different from its DisplayName attribute
|
||||
cy.get('#groupMention').find('input').clear().type(groupName);
|
||||
|
||||
// # Click save button
|
||||
saveConfig();
|
||||
// # Click save button
|
||||
saveConfig();
|
||||
|
||||
// * Assert that the group mention works as expected since the group is enabled and sysadmin always has permission to mention
|
||||
assertGroupMentionEnabled(groupName);
|
||||
// * Assert that the group mention works as expected since the group is enabled and sysadmin always has permission to mention
|
||||
assertGroupMentionEnabled(groupName);
|
||||
|
||||
// # Login as sysadmin and navigate to board group page
|
||||
navigateToGroup(groupID);
|
||||
// # Login as sysadmin and navigate to board group page
|
||||
navigateToGroup(groupID);
|
||||
|
||||
// # Click the allow reference button
|
||||
cy.findByTestId('allowReferenceSwitch').then((elSwitch) => {
|
||||
elSwitch.find('button').click();
|
||||
// # Disable group mentions
|
||||
cy.findByTestId('allowReferenceSwitch-button').click({force: true});
|
||||
|
||||
// # Click save button
|
||||
saveConfig();
|
||||
// # Click save button
|
||||
saveConfig();
|
||||
|
||||
// * Assert that the group mention does not do anything since the group is disabled even though sysadmin has permission to mention
|
||||
assertGroupMentionDisabled(groupName);
|
||||
});
|
||||
});
|
||||
// * Assert that the group mention does not do anything since the group is disabled even though sysadmin has permission to mention
|
||||
assertGroupMentionDisabled(groupName);
|
||||
});
|
||||
|
||||
it('MM-23937 - Can restrict users from mentioning a group through the use_group_mentions permission', () => {
|
||||
|
||||
@@ -49,9 +49,8 @@ describe('Channel members test', () => {
|
||||
cy.get('#channelMembers').scrollIntoView().should('be.visible');
|
||||
|
||||
// # Click the sync group members switch
|
||||
cy.findByTestId('syncGroupSwitch').
|
||||
cy.findByTestId('syncGroupSwitch-button').
|
||||
scrollIntoView().
|
||||
findByRole('button').
|
||||
click({force: true});
|
||||
|
||||
// * Assert that the members block is no longer visible
|
||||
|
||||
@@ -182,27 +182,39 @@ describe('MM-23102 - Channel Moderation - Higher Scoped Scheme', () => {
|
||||
// # Visit Channel page and Search for the channel.
|
||||
// * Ensure message is disabled and manage members for members is disabled
|
||||
visitChannelConfigPage(testChannel);
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Public');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'false');
|
||||
cy.findByTestId('admin-channel_settings-channel_moderation-manageMembers-disabledMember').
|
||||
should('have.text', `Manage members for members are disabled in ${teamOverrideSchemeName} Team Scheme.`);
|
||||
cy.findByTestId(checkboxesTitleToIdMap.MANAGE_MEMBERS_MEMBERS).should('be.disabled');
|
||||
|
||||
// # Turn channel into a private channel
|
||||
cy.findByTestId('allow-all-toggle').click();
|
||||
cy.findByTestId('allow-all-toggle-private').click({force: true});
|
||||
saveConfigForChannel(testChannel.display_name, true);
|
||||
|
||||
// * Ensure it is private and no error message is shown and that manage members for members is not disabled
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Private');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'false');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('admin-channel_settings-channel_moderation-manageMembers-disabledMember').
|
||||
should('not.have.text', `Manage members for members are disabled in ${teamOverrideSchemeName} Team Scheme.`);
|
||||
cy.findByTestId(checkboxesTitleToIdMap.MANAGE_MEMBERS_MEMBERS).should('not.be.disabled');
|
||||
|
||||
// # Turn channel back to public channel
|
||||
cy.findByTestId('allow-all-toggle').click();
|
||||
cy.findByTestId('allow-all-toggle-public').click({force: true});
|
||||
saveConfigForChannel(testChannel.display_name, true);
|
||||
|
||||
// * ensure it got reverted back to a Public channel
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Public');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'false');
|
||||
});
|
||||
|
||||
it('MM-T1579 Manage Members removed for Private Channels / Permissions inherited when channel converted from Public to Private', () => {
|
||||
@@ -220,27 +232,39 @@ describe('MM-23102 - Channel Moderation - Higher Scoped Scheme', () => {
|
||||
visitChannelConfigPage(testChannel);
|
||||
|
||||
// * Ensure that error message is not displayed and manage members for members is not disabled
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Public');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'false');
|
||||
cy.findByTestId('admin-channel_settings-channel_moderation-manageMembers-disabledMember').
|
||||
should('not.have.text', `Manage members for members are disabled in ${teamOverrideSchemeName} Team Scheme.`);
|
||||
cy.findByTestId(checkboxesTitleToIdMap.MANAGE_MEMBERS_MEMBERS).should('not.be.disabled');
|
||||
|
||||
// # Turn it into a private channel
|
||||
cy.findByTestId('allow-all-toggle').click();
|
||||
cy.findByTestId('allow-all-toggle-private').click({force: true});
|
||||
saveConfigForChannel(testChannel.display_name, true);
|
||||
|
||||
// * Ensure it is a private channel and that a message is disabled and also manage members for members is disabled
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Private');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'false');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('admin-channel_settings-channel_moderation-manageMembers-disabledMember').
|
||||
should('have.text', `Manage members for members are disabled in ${teamOverrideSchemeName} Team Scheme.`);
|
||||
cy.findByTestId(checkboxesTitleToIdMap.MANAGE_MEMBERS_MEMBERS).should('be.disabled');
|
||||
|
||||
// # Turn channel back to public channel
|
||||
cy.findByTestId('allow-all-toggle').click();
|
||||
cy.findByTestId('allow-all-toggle-public').click({force: true});
|
||||
saveConfigForChannel(testChannel.display_name, true);
|
||||
|
||||
// * Ensure it got reset back to a public channel
|
||||
cy.findByTestId('allow-all-toggle').should('has.have.text', 'Public');
|
||||
cy.findByTestId('allow-all-toggle').within(() => {
|
||||
cy.findByText('Public channel or private channel').should('be.visible');
|
||||
});
|
||||
cy.findByTestId('allow-all-toggle-public').should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId('allow-all-toggle-private').should('have.attr', 'aria-pressed', 'false');
|
||||
});
|
||||
|
||||
it('MM-T1581 Check if user is allowed to Edit or Delete their own posts on a Read-Only channel', () => {
|
||||
|
||||
@@ -57,9 +57,8 @@ describe('Team members test', () => {
|
||||
cy.get('#teamMembers').scrollIntoView().should('be.visible');
|
||||
|
||||
// # Click the sync group members switch
|
||||
cy.findByTestId('syncGroupSwitch').
|
||||
cy.findByTestId('syncGroupSwitch-button').
|
||||
scrollIntoView().
|
||||
findByRole('button').
|
||||
click({force: true});
|
||||
|
||||
// * Assert that the members block is no longer visible
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('Plugins Management', () => {
|
||||
// * Verify the enable toggle reflects the running state
|
||||
cy.findByTestId('plugin-metadata-panel', {timeout: TIMEOUTS.ONE_MIN}).should('be.visible');
|
||||
cy.findByTestId(pluginEnableToggleTestId(demoPlugin.id)).
|
||||
should('have.attr', 'aria-pressed', 'true').
|
||||
should('have.attr', 'aria-checked', 'true').
|
||||
and('have.attr', 'aria-label', 'Disable plugin');
|
||||
|
||||
// # Disable the plugin from the settings page
|
||||
@@ -115,7 +115,7 @@ describe('Plugins Management', () => {
|
||||
// * Verify it disables in place, without needing Save
|
||||
cy.findByTestId('plugin-metadata-status').should('contain.text', 'Not running');
|
||||
cy.findByTestId(pluginEnableToggleTestId(demoPlugin.id)).
|
||||
should('have.attr', 'aria-pressed', 'false').
|
||||
should('have.attr', 'aria-checked', 'false').
|
||||
and('have.attr', 'aria-label', 'Enable plugin');
|
||||
|
||||
// # Re-enable the plugin from the settings page
|
||||
@@ -151,7 +151,7 @@ describe('Plugins Management', () => {
|
||||
|
||||
// * Verify the action is blocked with the expected warning, and the plugin stays enabled
|
||||
cy.findByText('Please save unsaved changes first').should('be.visible');
|
||||
cy.findByTestId(pluginEnableToggleTestId(demoPlugin.id)).should('have.attr', 'aria-pressed', 'true');
|
||||
cy.findByTestId(pluginEnableToggleTestId(demoPlugin.id)).should('have.attr', 'aria-checked', 'true');
|
||||
|
||||
// # Attempt to uninstall the plugin while unsaved changes exist
|
||||
cy.get(`#plugin-actions-menu-button-${demoPlugin.id}`).click();
|
||||
|
||||
@@ -61,33 +61,29 @@ export default class ConfigurationSettings {
|
||||
|
||||
async enableChannelBanner() {
|
||||
const toggleButton = this.container.getByTestId('channelBannerToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (!classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
if (!(await toggleButton.isChecked())) {
|
||||
await toggleButton.check({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
async disableChannelBanner() {
|
||||
const toggleButton = this.container.getByTestId('channelBannerToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
if (await toggleButton.isChecked()) {
|
||||
await toggleButton.uncheck({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
async enableChannelAutotranslation() {
|
||||
const toggleButton = this.container.getByTestId('channelTranslationToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (!classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
if (!(await toggleButton.isChecked())) {
|
||||
await toggleButton.check({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
async disableChannelAutotranslation() {
|
||||
const toggleButton = this.container.getByTestId('channelTranslationToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
if (await toggleButton.isChecked()) {
|
||||
await toggleButton.uncheck({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,17 +106,15 @@ export default class ConfigurationSettings {
|
||||
|
||||
async enableJoinLeaveMessages() {
|
||||
const toggleButton = this.container.getByTestId('channelJoinLeaveMessagesToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (!classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
if (!(await toggleButton.isChecked())) {
|
||||
await toggleButton.check({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
async disableJoinLeaveMessages() {
|
||||
const toggleButton = this.container.getByTestId('channelJoinLeaveMessagesToggle-button');
|
||||
const classes = await toggleButton.getAttribute('class');
|
||||
if (classes?.includes('active')) {
|
||||
await toggleButton.click();
|
||||
if (await toggleButton.isChecked()) {
|
||||
await toggleButton.uncheck({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,19 +194,15 @@ export default class ConfigurationSettings {
|
||||
|
||||
async enableShareWithWorkspaces() {
|
||||
const toggle = this.shareWithWorkspacesToggle;
|
||||
const ariaPressed = await toggle.getAttribute('aria-pressed');
|
||||
const classes = await toggle.getAttribute('class');
|
||||
if (ariaPressed !== 'true' && !classes?.includes('active')) {
|
||||
await toggle.click();
|
||||
if (!(await toggle.isChecked())) {
|
||||
await toggle.check({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
async disableShareWithWorkspaces() {
|
||||
const toggle = this.shareWithWorkspacesToggle;
|
||||
const ariaPressed = await toggle.getAttribute('aria-pressed');
|
||||
const classes = await toggle.getAttribute('class');
|
||||
if (ariaPressed === 'true' || classes?.includes('active')) {
|
||||
await toggle.click();
|
||||
if (await toggle.isChecked()) {
|
||||
await toggle.uncheck({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class Localization {
|
||||
this.header = container.getByText('Localization', {exact: true});
|
||||
this.featureDiscoveryBlock = container.getByText('Remove language barriers with auto-translation');
|
||||
this.autoTranslationSection = container.getByTestId('autotranslation-section-header');
|
||||
this.autoTranslationToggle = container.getByTestId('autotranslation-section-toggle').locator('button');
|
||||
this.autoTranslationToggle = container.getByTestId('autotranslation-section-toggle').getByRole('switch');
|
||||
this.providerDropdown = container.getByTestId('Providerdropdown');
|
||||
this.mattermostAgentsInactiveNotice = container.getByText(
|
||||
'LLMs must first be configured in the Agents plugin.',
|
||||
@@ -56,7 +56,7 @@ export default class Localization {
|
||||
async turnOnAutoTranslation() {
|
||||
const on = await this.isToggleOn();
|
||||
if (!on) {
|
||||
await this.autoTranslationToggle.click();
|
||||
await this.autoTranslationToggle.check({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@ class CreateRecapModal {
|
||||
async enableRunOnce() {
|
||||
const runOnce = this.container.locator('#run-once-toggle');
|
||||
await expect(runOnce).toBeVisible();
|
||||
if ((await runOnce.getAttribute('aria-pressed')) !== 'true') {
|
||||
await runOnce.click();
|
||||
if (!(await runOnce.isChecked())) {
|
||||
await runOnce.check({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ class ScheduledRecapItem {
|
||||
) {
|
||||
this.menuButton = container.getByRole('button', {name: /Options for /});
|
||||
this.schedulePattern = container.locator('.schedule-pattern');
|
||||
this.toggleButton = container.locator('.scheduled-recap-toggle button');
|
||||
this.toggleButton = container.locator('.scheduled-recap-toggle').getByRole('switch');
|
||||
}
|
||||
|
||||
async toBeVisible() {
|
||||
@@ -253,24 +253,27 @@ class ScheduledRecapItem {
|
||||
}
|
||||
|
||||
async expectActive() {
|
||||
await expect(this.toggleButton).toHaveAttribute('aria-pressed', 'true');
|
||||
await expect(this.toggleButton).toBeChecked();
|
||||
await expect(this.toggleButton).toHaveAccessibleName('Active - click to pause');
|
||||
}
|
||||
|
||||
async expectPaused() {
|
||||
await expect(this.toggleButton).toHaveAttribute('aria-pressed', 'false');
|
||||
await expect(this.toggleButton).not.toBeChecked();
|
||||
await expect(this.toggleButton).toHaveAccessibleName('Paused - click to resume');
|
||||
}
|
||||
|
||||
async pause() {
|
||||
await this.expectActive();
|
||||
await this.toggleButton.click();
|
||||
// Click instead of uncheck: the Switch is controlled by async Redux/API
|
||||
// updates, so Playwright's checked-state verification fails before the
|
||||
// store refreshes. expectPaused() polls until aria-checked flips.
|
||||
await this.toggleButton.click({force: true});
|
||||
await this.expectPaused();
|
||||
}
|
||||
|
||||
async resume() {
|
||||
await this.expectPaused();
|
||||
await this.toggleButton.click();
|
||||
await this.toggleButton.click({force: true});
|
||||
await this.expectActive();
|
||||
}
|
||||
|
||||
|
||||
@@ -164,10 +164,7 @@ test.describe('Channel attribute banner composition', {tag: ['@channel_attribute
|
||||
const settings = await channelsPage.openChannelSettings();
|
||||
const configuration = await settings.openConfigurationTab();
|
||||
|
||||
await expect(configuration.container.getByTestId('channelBannerToggle-button')).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'true',
|
||||
);
|
||||
await expect(configuration.container.getByTestId('channelBannerToggle-button')).toBeChecked();
|
||||
await expect(configuration.bannerTextEditor).toBeVisible();
|
||||
|
||||
// * Opening the tab changes nothing, so it must not offer to save
|
||||
|
||||
@@ -255,10 +255,7 @@ test.describe('Channel attribute banner settings', {tag: ['@channel_attributes']
|
||||
// # Switch the banner off
|
||||
let settings = await channelsPage.openChannelSettings();
|
||||
let configuration = await settings.openConfigurationTab();
|
||||
await expect(configuration.container.getByTestId('channelBannerToggle-button')).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'true',
|
||||
);
|
||||
await expect(configuration.container.getByTestId('channelBannerToggle-button')).toBeChecked();
|
||||
await configuration.disableChannelBanner();
|
||||
await configuration.save();
|
||||
await settings.close();
|
||||
@@ -270,10 +267,7 @@ test.describe('Channel attribute banner settings', {tag: ['@channel_attributes']
|
||||
// # Switch it back on
|
||||
settings = await channelsPage.openChannelSettings();
|
||||
configuration = await settings.openConfigurationTab();
|
||||
await expect(configuration.container.getByTestId('channelBannerToggle-button')).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'false',
|
||||
);
|
||||
await expect(configuration.container.getByTestId('channelBannerToggle-button')).not.toBeChecked();
|
||||
await configuration.enableChannelBanner();
|
||||
await configuration.save();
|
||||
await settings.close();
|
||||
|
||||
@@ -81,7 +81,7 @@ test.describe('Channel Classification - New channel creation', () => {
|
||||
|
||||
// Enable classification toggle
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
// Create button should be disabled (no classification level selected, no banner text)
|
||||
await expect(newChannelModal.createButton).toBeDisabled();
|
||||
@@ -98,7 +98,7 @@ test.describe('Channel Classification - New channel creation', () => {
|
||||
|
||||
// Enable classification toggle
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
// Open the classification dropdown
|
||||
const dropdownContainer = channelsPage.page.getByTestId('channelClassificationLevel');
|
||||
@@ -126,7 +126,7 @@ test.describe('Channel Classification - New channel creation', () => {
|
||||
|
||||
// Enable classification toggle
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
// Select a classification level
|
||||
const dropdownContainer = channelsPage.page.getByTestId('channelClassificationLevel');
|
||||
@@ -164,7 +164,7 @@ test.describe('Channel Classification - New channel creation', () => {
|
||||
|
||||
// Enable classification toggle
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
// Select the classification level
|
||||
const dropdownContainer = channelsPage.page.getByTestId('channelClassificationLevel');
|
||||
@@ -220,13 +220,12 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
await expect(classificationToggle).toBeVisible();
|
||||
|
||||
// Toggle it on
|
||||
const classes = await classificationToggle.getAttribute('class');
|
||||
if (!classes?.includes('active')) {
|
||||
await classificationToggle.click();
|
||||
if (!(await classificationToggle.isChecked())) {
|
||||
await classificationToggle.check({force: true});
|
||||
}
|
||||
|
||||
// Toggle should now be active
|
||||
await expect(classificationToggle).toHaveClass(/active/);
|
||||
await expect(classificationToggle).toBeChecked();
|
||||
});
|
||||
|
||||
test('Classification level can be set once toggle is enabled', async ({pw}) => {
|
||||
@@ -246,7 +245,7 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
|
||||
// Enable classification toggle
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
// Classification level dropdown should be visible
|
||||
const dropdownContainer = channelsPage.page.getByTestId('channelClassificationLevel');
|
||||
@@ -282,7 +281,7 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
|
||||
// Enable classification and select a level
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
const dropdownContainer = channelsPage.page.getByTestId('channelClassificationLevel');
|
||||
await dropdownContainer.click();
|
||||
@@ -295,7 +294,7 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
// The channel banner toggle should now be forced active and disabled
|
||||
const bannerToggle = channelsPage.page.getByTestId('channelBannerToggle-button');
|
||||
await expect(bannerToggle).toBeVisible();
|
||||
await expect(bannerToggle).toHaveClass(/active/);
|
||||
await expect(bannerToggle).toBeChecked();
|
||||
await expect(bannerToggle).toBeDisabled();
|
||||
|
||||
// Banner color input should be locked to the classification color
|
||||
@@ -324,7 +323,7 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
const configurationTab = await channelSettingsModal.openConfigurationTab();
|
||||
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
const dropdownContainer = channelsPage.page.getByTestId('channelClassificationLevel');
|
||||
await dropdownContainer.click();
|
||||
@@ -337,7 +336,7 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
// longer auto-enables banner_info.enabled when classification is on, so the banner
|
||||
// section body (and its color input) is hidden after toggling classification off
|
||||
// until a manual banner is enabled.
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.uncheck({force: true});
|
||||
await configurationTab.enableChannelBanner();
|
||||
const customColor = 'aa00aa';
|
||||
await configurationTab.setChannelBannerBackgroundColor(customColor);
|
||||
@@ -350,7 +349,7 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
expect(persisted.banner_info?.background_color?.toLowerCase().replace('#', '')).toBe(customColor);
|
||||
|
||||
// Symptom 2 guard: re-enable classification → Save button reappears (panel transitions out of 'saved')
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
const saveButton = channelsPage.page.getByTestId('SaveChangesPanel__save-btn');
|
||||
await expect(saveButton).toBeVisible();
|
||||
await expect(saveButton).toBeEnabled();
|
||||
@@ -375,7 +374,7 @@ test.describe('Channel Classification - Existing channel settings', () => {
|
||||
|
||||
// Enable classification and select a level
|
||||
const classificationToggle = channelsPage.page.getByTestId('channelClassificationToggle-button');
|
||||
await classificationToggle.click();
|
||||
await classificationToggle.check({force: true});
|
||||
|
||||
const dropdownContainer = channelsPage.page.getByTestId('channelClassificationLevel');
|
||||
await dropdownContainer.click();
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
* the Configuration tab toggle in Channel Settings.
|
||||
*
|
||||
* The toggle controls `DisableJoinLeaveMessages` on the channel:
|
||||
* - Toggle ON (active class) → messages shown (DisableJoinLeaveMessages = false)
|
||||
* - Toggle OFF (no active class) → messages hidden (DisableJoinLeaveMessages = true)
|
||||
* - Toggle ON (aria-checked=true) → messages shown (DisableJoinLeaveMessages = false)
|
||||
* - Toggle OFF (aria-checked=false) → messages hidden (DisableJoinLeaveMessages = true)
|
||||
*
|
||||
* Join system posts are generated server-side when a user is added to a
|
||||
* channel, so tests use `adminClient.addToChannel()` to produce them.
|
||||
@@ -45,9 +45,8 @@ test.describe('Channel Settings Modal - Join/Leave System Messages', () => {
|
||||
// * Toggle is visible
|
||||
await expect(configSettings.joinLeaveMessagesToggle).toBeVisible();
|
||||
|
||||
// * Toggle defaults to ON (active class present = messages shown)
|
||||
const classes = await configSettings.joinLeaveMessagesToggle.getAttribute('class');
|
||||
expect(classes).toContain('active');
|
||||
// * Toggle defaults to ON (messages shown)
|
||||
await expect(configSettings.joinLeaveMessagesToggle).toBeChecked();
|
||||
|
||||
await channelSettings.close();
|
||||
});
|
||||
|
||||
@@ -199,7 +199,7 @@ test.describe('Shared channel configuration', () => {
|
||||
|
||||
await expect(configurationTab.shareWithWorkspacesToggle).toBeVisible();
|
||||
// When sharing is disabled and no workspaces are configured, the toggle is simply off.
|
||||
await expect(configurationTab.shareWithWorkspacesToggle).toHaveAttribute('aria-pressed', 'false');
|
||||
await expect(configurationTab.shareWithWorkspacesToggle).not.toBeChecked();
|
||||
await channelSettingsModal.close();
|
||||
});
|
||||
|
||||
@@ -311,7 +311,7 @@ test.describe('Shared channel configuration', () => {
|
||||
await channelsPage.toBeVisible();
|
||||
channelSettingsModal = await channelsPage.openChannelSettings();
|
||||
configurationTab = await channelSettingsModal.openConfigurationTab();
|
||||
await expect(configurationTab.shareWithWorkspacesToggle).toHaveAttribute('aria-pressed', 'true');
|
||||
await expect(configurationTab.shareWithWorkspacesToggle).toBeChecked();
|
||||
await channelSettingsModal.close();
|
||||
|
||||
// Disable sharing via API (uninvite workspaces) to avoid async UI race conditions.
|
||||
@@ -331,7 +331,7 @@ test.describe('Shared channel configuration', () => {
|
||||
await channelsPage.toBeVisible();
|
||||
channelSettingsModal = await channelsPage.openChannelSettings();
|
||||
configurationTab = await channelSettingsModal.openConfigurationTab();
|
||||
await expect(configurationTab.shareWithWorkspacesToggle).toHaveAttribute('aria-pressed', 'false');
|
||||
await expect(configurationTab.shareWithWorkspacesToggle).not.toBeChecked();
|
||||
await channelSettingsModal.close();
|
||||
});
|
||||
|
||||
|
||||
@@ -110,9 +110,8 @@ test.describe('ABAC Policies - Channel Integration', () => {
|
||||
const abacToggle = systemConsolePage.page.locator('[data-testid="policy-enforce-toggle-button"]');
|
||||
await abacToggle.waitFor({state: 'visible', timeout: 5000});
|
||||
|
||||
const isEnabled = await abacToggle.getAttribute('aria-pressed');
|
||||
if (isEnabled !== 'true') {
|
||||
await abacToggle.click();
|
||||
if (!(await abacToggle.isChecked())) {
|
||||
await abacToggle.check({force: true});
|
||||
}
|
||||
await systemConsolePage.page.waitForTimeout(500);
|
||||
|
||||
|
||||
@@ -102,9 +102,10 @@ test.describe('ABAC - Team Membership console', {tag: ['@abac', '@team_membershi
|
||||
async function setToggle(page: Page, on: boolean): Promise<void> {
|
||||
const toggle = page.locator('[data-testid="policy-enforce-toggle-button"]');
|
||||
await toggle.waitFor({state: 'visible', timeout: 10000});
|
||||
const pressed = (await toggle.getAttribute('aria-pressed')) === 'true';
|
||||
if (pressed !== on) {
|
||||
await toggle.click();
|
||||
if (on) {
|
||||
await toggle.check({force: true});
|
||||
} else {
|
||||
await toggle.uncheck({force: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,10 +286,7 @@ test.describe('ABAC - Team Membership console', {tag: ['@abac', '@team_membershi
|
||||
await expect(disconnectModal).not.toBeVisible({timeout: 5000});
|
||||
|
||||
// * Custom rule still governs, so enforcement stays on
|
||||
await expect(page.locator('[data-testid="policy-enforce-toggle-button"]')).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'true',
|
||||
);
|
||||
await expect(page.locator('[data-testid="policy-enforce-toggle-button"]')).toBeChecked();
|
||||
|
||||
// * Save goes straight through: removal is not new criteria, so no apply modal
|
||||
await page.getByRole('button', {name: 'Save'}).click();
|
||||
|
||||
@@ -29,6 +29,8 @@ const config = {
|
||||
'^.+\\.(jpg|jpeg|png|apng|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
|
||||
'<rootDir>/src/tests/image_url_mock.json',
|
||||
'^.+\\.(css|less|scss)$': 'identity-obj-proxy',
|
||||
// Stabilize Compass Switch CSS-module hashes across environments.
|
||||
'Switch\\.module\\.scss\\.(js|cjs)$': '<rootDir>/src/tests/mocks/compass_switch_styles.js',
|
||||
'^.*i18n.*\\.(json)$': '<rootDir>/src/tests/i18n_mock.json',
|
||||
},
|
||||
moduleDirectories: ['node_modules'],
|
||||
|
||||
@@ -189,7 +189,7 @@ describe('TeamReviewersSection', () => {
|
||||
expect(teamNameCells[1]).toHaveTextContent('Team Two');
|
||||
});
|
||||
|
||||
const toggle = screen.getByRole('button', {name: /enable or disable content reviewers for team Team One/i});
|
||||
const toggle = screen.getByRole('switch', {name: /enable or disable content reviewers for team Team One/i});
|
||||
await userEvent.click(toggle);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
@@ -207,8 +207,8 @@ describe('TeamReviewersSection', () => {
|
||||
expect(mockSearchTeams).toHaveBeenCalledWith('', {page: 0, per_page: 10});
|
||||
});
|
||||
|
||||
expect(await screen.findByRole('button', {name: 'Enable or disable content reviewers for team Team One'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Enable or disable content reviewers for team Team Two'})).toBeInTheDocument();
|
||||
expect(await screen.findByRole('switch', {name: 'Enable or disable content reviewers for team Team One'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {name: 'Enable or disable content reviewers for team Team Two'})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('should handle toggle functionality with existing settings', async () => {
|
||||
@@ -241,7 +241,7 @@ describe('TeamReviewersSection', () => {
|
||||
expect(teamNameCells[1]).toHaveTextContent('Team Two');
|
||||
});
|
||||
|
||||
const toggle = screen.getByRole('button', {name: /enable or disable content reviewers for team Team One/i});
|
||||
const toggle = screen.getByRole('switch', {name: /enable or disable content reviewers for team Team One/i});
|
||||
await userEvent.click(toggle);
|
||||
|
||||
expect(onChange).toHaveBeenCalledWith({
|
||||
@@ -395,7 +395,7 @@ describe('TeamReviewersSection', () => {
|
||||
expect(teamNameCells[1]).toHaveTextContent('Team Two');
|
||||
});
|
||||
|
||||
const toggle = screen.getByRole('button', {name: /enable or disable content reviewers for team Team One/i});
|
||||
const toggle = screen.getByRole('switch', {name: /enable or disable content reviewers for team Team One/i});
|
||||
|
||||
// First click - enable
|
||||
await userEvent.click(toggle);
|
||||
@@ -423,7 +423,7 @@ describe('TeamReviewersSection', () => {
|
||||
expect(mockSearchTeams).toHaveBeenCalledWith('', {page: 0, per_page: 10});
|
||||
});
|
||||
|
||||
const updatedToggle = screen.getAllByRole('button', {name: /enable or disable content reviewers for team Team One/i})[0];
|
||||
const updatedToggle = screen.getAllByRole('switch', {name: /enable or disable content reviewers for team Team One/i})[0];
|
||||
|
||||
// Second click - disable
|
||||
await userEvent.click(updatedToggle);
|
||||
|
||||
@@ -5,13 +5,13 @@ import React, {useCallback, useEffect, useMemo, useState, type JSX} from 'react'
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import type {TeamReviewerSetting} from '@mattermost/types/config';
|
||||
import type {Team, TeamSearchOpts} from '@mattermost/types/teams';
|
||||
|
||||
import {searchTeams} from 'mattermost-redux/actions/teams';
|
||||
|
||||
import DataGrid from 'components/admin_console/data_grid/data_grid';
|
||||
import Toggle from 'components/toggle';
|
||||
import {TeamIcon} from 'components/widgets/team_icon/team_icon';
|
||||
|
||||
import * as Utils from 'utils/utils';
|
||||
@@ -151,13 +151,15 @@ export default function TeamReviewers({teamReviewersSetting, onChange, disabled}
|
||||
/>
|
||||
),
|
||||
enabled: (
|
||||
<Toggle
|
||||
<Switch
|
||||
id={`team_content_reviewer_toggle_${team.id}`}
|
||||
ariaLabel={intl.formatMessage({id: 'admin.dataSpillage.reviewerSettings.toggle', defaultMessage: 'Enable or disable content reviewers for team {teamName}'}, {teamName: team.display_name})}
|
||||
size='btn-md'
|
||||
onToggle={getHandleToggle(team.id)}
|
||||
toggled={teamReviewersSetting[team.id]?.Enabled || false}
|
||||
size='medium'
|
||||
onChange={getHandleToggle(team.id)}
|
||||
checked={teamReviewersSetting[team.id]?.Enabled || false}
|
||||
disabled={disabled}
|
||||
data-testid={`team_content_reviewer_toggle_${team.id}-button`}
|
||||
tabIndex={0}
|
||||
aria-label={intl.formatMessage({id: 'admin.dataSpillage.reviewerSettings.toggle', defaultMessage: 'Enable or disable content reviewers for team {teamName}'}, {teamName: team.display_name})}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -28,11 +28,13 @@ describe('components/admin_console/custom_plugin_settings/PluginEnableButton', (
|
||||
);
|
||||
|
||||
expect(screen.getByText('Disabled')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Enable plugin'}));
|
||||
const enableSwitch = screen.getByRole('switch', {name: 'Enable plugin'});
|
||||
expect(enableSwitch.closest('label')?.className).toMatch(/switch--size-medium/);
|
||||
await userEvent.click(enableSwitch);
|
||||
|
||||
expect(enablePlugin).toHaveBeenCalledWith('com.mattermost.calls');
|
||||
expect(disablePlugin).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole('button', {name: 'Enable plugin'}).closest('.col-sm-offset-4')).toBeNull();
|
||||
expect(screen.getByRole('switch', {name: 'Enable plugin'}).closest('.col-sm-offset-4')).toBeNull();
|
||||
});
|
||||
|
||||
it('disables the plugin when clicked while enabled', async () => {
|
||||
@@ -50,11 +52,11 @@ describe('components/admin_console/custom_plugin_settings/PluginEnableButton', (
|
||||
);
|
||||
|
||||
expect(screen.getByText('Enabled')).toBeInTheDocument();
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Disable plugin'}));
|
||||
await userEvent.click(screen.getByRole('switch', {name: 'Disable plugin'}));
|
||||
|
||||
expect(disablePlugin).toHaveBeenCalledWith('com.mattermost.calls');
|
||||
expect(enablePlugin).not.toHaveBeenCalled();
|
||||
expect(screen.getByRole('button', {name: 'Disable plugin'})).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.getByRole('switch', {name: 'Disable plugin'})).toBeChecked();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Plugin actions'}));
|
||||
expect(screen.getByRole('menuitem', {name: 'Uninstall plugin'})).toBeInTheDocument();
|
||||
@@ -107,11 +109,11 @@ describe('components/admin_console/custom_plugin_settings/PluginEnableButton', (
|
||||
/>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Enable plugin'}));
|
||||
await userEvent.click(screen.getByRole('switch', {name: 'Enable plugin'}));
|
||||
|
||||
expect(screen.getByText('Enabling')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('loadingSpinner')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Enable plugin'})).toBeDisabled();
|
||||
expect(screen.getByRole('switch', {name: 'Enable plugin'})).toBeDisabled();
|
||||
expect(screen.getByRole('button', {name: 'Plugin actions'})).toBeDisabled();
|
||||
expect(screen.queryByText('Enabled')).not.toBeInTheDocument();
|
||||
|
||||
@@ -120,7 +122,7 @@ describe('components/admin_console/custom_plugin_settings/PluginEnableButton', (
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Enabling')).not.toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByRole('button', {name: 'Enable plugin'})).toBeEnabled();
|
||||
expect(screen.getByRole('switch', {name: 'Enable plugin'})).toBeEnabled();
|
||||
expect(screen.getByText('Disabled')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -138,7 +140,7 @@ describe('components/admin_console/custom_plugin_settings/PluginEnableButton', (
|
||||
/>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Enable plugin'}));
|
||||
await userEvent.click(screen.getByRole('switch', {name: 'Enable plugin'}));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Unable to enable plugin')).toBeInTheDocument();
|
||||
@@ -160,7 +162,7 @@ describe('components/admin_console/custom_plugin_settings/PluginEnableButton', (
|
||||
/>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Enable plugin'}));
|
||||
await userEvent.click(screen.getByRole('switch', {name: 'Enable plugin'}));
|
||||
|
||||
expect(screen.getByText('Please save unsaved changes first')).toBeInTheDocument();
|
||||
expect(enablePlugin).not.toHaveBeenCalled();
|
||||
|
||||
@@ -8,13 +8,13 @@ import {bindActionCreators} from 'redux';
|
||||
import type {Dispatch} from 'redux';
|
||||
|
||||
import {DotsHorizontalIcon, OpenInNewIcon, TrashCanOutlineIcon} from '@mattermost/compass-icons/components';
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
|
||||
import {disablePlugin, enablePlugin, removePlugin} from 'mattermost-redux/actions/admin';
|
||||
import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
|
||||
import ConfirmModal from 'components/confirm_modal';
|
||||
import * as Menu from 'components/menu';
|
||||
import Toggle from 'components/toggle';
|
||||
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
@@ -195,14 +195,15 @@ export function PluginEnableButton({actions, disabled, homepageUrl, id, saveNeed
|
||||
defaultMessage={pluginEnabled ? 'Enabled' : 'Disabled'}
|
||||
/>
|
||||
)}
|
||||
<Toggle
|
||||
size='btn-sm'
|
||||
disabled={actionsDisabled}
|
||||
toggled={pluginEnabled}
|
||||
<Switch
|
||||
id={id}
|
||||
ariaLabel={toggleAriaLabel}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
onToggle={handleTogglePlugin}
|
||||
size='medium'
|
||||
disabled={actionsDisabled}
|
||||
checked={pluginEnabled}
|
||||
onChange={handleTogglePlugin}
|
||||
data-testid={`${id}-button`}
|
||||
tabIndex={0}
|
||||
aria-label={toggleAriaLabel}
|
||||
/>
|
||||
</div>
|
||||
<Menu.Container
|
||||
|
||||
@@ -98,7 +98,7 @@ describe('custom plugin sections and settings', () => {
|
||||
|
||||
const expectPluginActionsInMetadataPanel = () => {
|
||||
const wrapper = document.querySelector('.PluginMetadataPanel__actionsPanel');
|
||||
const toggleButton = screen.queryByRole('button', {name: 'Enable plugin'}) || screen.getByRole('button', {name: 'Disable plugin'});
|
||||
const toggleButton = screen.queryByRole('switch', {name: 'Enable plugin'}) || screen.getByRole('switch', {name: 'Disable plugin'});
|
||||
expect(wrapper).toContainElement(toggleButton);
|
||||
expect(wrapper).toContainElement(screen.getByRole('button', {name: 'Plugin actions'}));
|
||||
};
|
||||
@@ -112,7 +112,7 @@ describe('custom plugin sections and settings', () => {
|
||||
{...baseState});
|
||||
|
||||
expectPluginPageTitle('testplugin', 'testplugin');
|
||||
expect(screen.getByRole('button', {name: 'Disable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {name: 'Disable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Plugin actions'})).toBeInTheDocument();
|
||||
expectPluginActionsInMetadataPanel();
|
||||
const runningBadge = screen.getByTestId('plugin-metadata-status');
|
||||
@@ -564,7 +564,7 @@ describe('custom plugin sections and settings', () => {
|
||||
{...state});
|
||||
|
||||
expectPluginPageTitle('testplugin', 'testplugin');
|
||||
expect(screen.getByRole('button', {name: 'Enable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {name: 'Enable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Plugin actions'})).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('PluginSettings.PluginStates.testplugin.Enabletrue')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('PluginSettings.PluginStates.testplugin.Enablefalse')).not.toBeInTheDocument();
|
||||
@@ -653,7 +653,7 @@ describe('custom plugin sections and settings', () => {
|
||||
{...state});
|
||||
|
||||
expectPluginPageTitle('testplugin', 'testplugin');
|
||||
expect(screen.getByRole('button', {name: 'Enable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {name: 'Enable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Plugin actions'})).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('PluginSettings.PluginStates.testplugin.Enabletrue')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('PluginSettings.PluginStates.testplugin.Enablefalse')).not.toBeInTheDocument();
|
||||
@@ -708,7 +708,7 @@ describe('custom plugin sections and settings', () => {
|
||||
state,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('button', {name: 'Enable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {name: 'Enable plugin'})).toBeInTheDocument();
|
||||
|
||||
updateStoreState({
|
||||
entities: {
|
||||
@@ -742,8 +742,8 @@ describe('custom plugin sections and settings', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByRole('button', {name: 'Enable plugin'})).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Disable plugin'})).toBeInTheDocument();
|
||||
expect(screen.queryByRole('switch', {name: 'Enable plugin'})).not.toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {name: 'Disable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Plugin actions'})).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('PluginSettings.PluginStates.testplugin.Enabletrue')).not.toBeInTheDocument();
|
||||
});
|
||||
@@ -1001,11 +1001,11 @@ describe('custom plugin sections and settings', () => {
|
||||
{...state});
|
||||
|
||||
expectPluginPageTitle('testplugin', 'testplugin');
|
||||
expect(screen.getByRole('button', {name: 'Disable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {name: 'Disable plugin'})).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {name: 'Plugin actions'})).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('PluginSettings.PluginStates.testplugin.Enabletrue')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('PluginSettings.PluginStates.testplugin.Enablefalse')).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', {name: 'Enable plugin'})).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('switch', {name: 'Enable plugin'})).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('In order to view and configure plugin settings, enable the plugin.')).not.toBeInTheDocument();
|
||||
expect(screen.getByText('Custom Component Section 1')).toBeInTheDocument();
|
||||
expect(screen.getByText('Custom Component Section 2')).toBeInTheDocument();
|
||||
|
||||
@@ -94,46 +94,45 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Enable Group Mentions
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
Enable Group Mentions
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
When enabled, this group can be mentioned in other channels and teams. This may result in the group member list being visible to all users.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -505,46 +504,45 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Enable Group Mentions
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
Enable Group Mentions
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
When enabled, this group can be mentioned in other channels and teams. This may result in the group member list being visible to all users.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -914,46 +912,45 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Enable Group Mentions
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
Enable Group Mentions
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
When enabled, this group can be mentioned in other channels and teams. This may result in the group member list being visible to all users.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1323,46 +1320,45 @@ exports[`components/admin_console/group_settings/group_details/GroupDetails shou
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Enable Group Mentions
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
Enable Group Mentions
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
When enabled, this group can be mentioned in other channels and teams. This may result in the group member list being visible to all users.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,46 +62,45 @@ exports[`components/admin_console/group_settings/group_details/GroupProfileAndSe
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Enable Group Mentions
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
Enable Group Mentions
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
When enabled, this group can be mentioned in other channels and teams. This may result in the group member list being visible to all users.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -171,46 +170,46 @@ exports[`components/admin_console/group_settings/group_details/GroupProfileAndSe
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowReferenceSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Enable Group Mentions
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowReferenceSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="true"
|
||||
class="btn btn-lg btn-toggle active"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
Enable Group Mentions
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
When enabled, this group can be mentioned in other channels and teams. This may result in the group member list being visible to all users.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="true"
|
||||
checked=""
|
||||
class="switch__input"
|
||||
data-testid="allowReferenceSwitch-button"
|
||||
id="allowReferenceSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import React from 'react';
|
||||
import {FormattedMessage, defineMessage} from 'react-intl';
|
||||
|
||||
import GroupProfile from 'components/admin_console/group_settings/group_details/group_profile';
|
||||
import LineSwitch from 'components/admin_console/team_channel_settings/line_switch';
|
||||
import AdminSettingsSwitch from 'components/admin_console/team_channel_settings/admin_settings_switch';
|
||||
import AdminPanel from 'components/widgets/admin_console/admin_panel';
|
||||
|
||||
type GroupSettingsToggleProps = {
|
||||
@@ -21,31 +21,29 @@ const GroupSettingsToggle = ({
|
||||
onToggle,
|
||||
isDisabled,
|
||||
}: GroupSettingsToggleProps) => (
|
||||
<LineSwitch
|
||||
<AdminSettingsSwitch
|
||||
id={'allowReferenceSwitch'}
|
||||
disabled={isDisabled || isDefault}
|
||||
toggled={allowReference}
|
||||
checked={allowReference}
|
||||
last={true}
|
||||
onToggle={() => {
|
||||
onChange={() => {
|
||||
if (isDefault) {
|
||||
return;
|
||||
}
|
||||
onToggle(!allowReference);
|
||||
}}
|
||||
singleLine={false}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.groupDetailsToggle'
|
||||
defaultMessage='Enable Group Mentions'
|
||||
/>
|
||||
}
|
||||
subTitle={
|
||||
secondaryLabel={
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.groupDetailsToggleDescr'
|
||||
defaultMessage='When enabled, this group can be mentioned in other channels and teams. This may result in the group member list being visible to all users.'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.groupDetailsToggle'
|
||||
defaultMessage='Enable Group Mentions'
|
||||
/>
|
||||
</AdminSettingsSwitch>
|
||||
);
|
||||
|
||||
type GroupProfileAndSettingsProps = {
|
||||
|
||||
@@ -31,7 +31,7 @@ describe('EnableSectionContent', () => {
|
||||
expect(screen.getByText('Enable IP Filtering')).toBeInTheDocument();
|
||||
expect(screen.getByText('Limit access to your workspace by IP address.')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('filterToggle-button')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {pressed: true})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: true})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('clicking the toggle calls setFilterToggle', async () => {
|
||||
@@ -47,6 +47,19 @@ describe('EnableSectionContent', () => {
|
||||
expect(setFilterToggle).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test('clicking the toggle when off calls setFilterToggle with true', async () => {
|
||||
renderWithContext(
|
||||
<EnableSectionContent
|
||||
{...baseProps}
|
||||
filterToggle={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByTestId('filterToggle-button'));
|
||||
|
||||
expect(setFilterToggle).toHaveBeenCalledWith(true);
|
||||
});
|
||||
|
||||
test('renders the component, with toggle not pressed if filterToggle is false', () => {
|
||||
renderWithContext(
|
||||
<EnableSectionContent
|
||||
@@ -58,7 +71,7 @@ describe('EnableSectionContent', () => {
|
||||
expect(screen.getByText('Enable IP Filtering')).toBeInTheDocument();
|
||||
expect(screen.getByText('Limit access to your workspace by IP address.')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('filterToggle-button')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {pressed: false})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: false})).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
import React from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
|
||||
import ExternalLink from 'components/external_link';
|
||||
import Toggle from 'components/toggle';
|
||||
|
||||
type Props = {
|
||||
filterToggle: boolean;
|
||||
@@ -40,13 +41,15 @@ const EnableSectionContent: React.FC<Props> = ({filterToggle, setFilterToggle})
|
||||
</div>
|
||||
</div>
|
||||
<div className='SwitchSelector'>
|
||||
<Toggle
|
||||
size={'btn-md'}
|
||||
id={'filterToggle'}
|
||||
<Switch
|
||||
id='filterToggle'
|
||||
size='medium'
|
||||
disabled={false}
|
||||
onToggle={() => setFilterToggle(!filterToggle)}
|
||||
toggled={filterToggle}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
onChange={(e) => setFilterToggle(e.target.checked)}
|
||||
checked={filterToggle}
|
||||
data-testid='filterToggle-button'
|
||||
tabIndex={0}
|
||||
aria-label={formatMessage({id: 'admin.ip_filtering.enable_ip_filtering', defaultMessage: 'Enable IP Filtering'})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -64,9 +64,18 @@
|
||||
}
|
||||
|
||||
.SwitchSelector {
|
||||
display: flex;
|
||||
width: 32px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
|
||||
// Compass Switch root is a <label>; Bootstrap form labels
|
||||
// add margin-bottom which misaligns / can clip hit targets.
|
||||
> label {
|
||||
width: auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,13 +97,13 @@ describe('IPFiltering', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('filterToggle-button')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {pressed: true})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: true})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByTestId('filterToggle-button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', {pressed: false})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: false})).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -189,13 +189,13 @@ describe('IPFiltering', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('filterToggle-button')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {pressed: true})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: true})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByTestId('filterToggle-button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', {pressed: false})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: false})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
@@ -251,14 +251,14 @@ describe('IPFiltering', () => {
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('filterToggle-button')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', {pressed: true})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: true})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Use fireEvent.click here because userEvent doesn't work well with fake timers
|
||||
fireEvent.click(screen.getByTestId('filterToggle-button'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('button', {pressed: false})).toBeInTheDocument();
|
||||
expect(screen.getByRole('switch', {checked: false})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo, useState} from 'react';
|
||||
import {defineMessage, defineMessages, FormattedMessage} from 'react-intl';
|
||||
import {defineMessage, defineMessages, FormattedMessage, useIntl} from 'react-intl';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import type {AutoTranslationSettings} from '@mattermost/types/config';
|
||||
|
||||
import BooleanSetting from 'components/admin_console/boolean_setting';
|
||||
@@ -17,7 +18,6 @@ import {
|
||||
} from 'components/admin_console/system_properties/controls';
|
||||
import TextSetting from 'components/admin_console/text_setting';
|
||||
import useGetAgentsBridgeEnabled from 'components/common/hooks/useGetAgentsBridgeEnabled';
|
||||
import Toggle from 'components/toggle';
|
||||
import BetaTag from 'components/widgets/tag/beta_tag';
|
||||
|
||||
import * as I18n from 'i18n/i18n';
|
||||
@@ -47,6 +47,7 @@ const messages = defineMessages({
|
||||
export const searchableStrings: SearchableStrings = Object.values(messages);
|
||||
|
||||
export default function AutoTranslation(props: SystemConsoleCustomSettingsComponentProps) {
|
||||
const {formatMessage} = useIntl();
|
||||
const {available: isAgentsBridgeEnabled, reason: agentsBridgeUnavailableReason} = useGetAgentsBridgeEnabled();
|
||||
const [autoTranslationSettings, setAutoTranslationSettings] = useState<AutoTranslationSettings>(() => {
|
||||
const settings = props.value as AutoTranslationSettings;
|
||||
@@ -173,14 +174,15 @@ export default function AutoTranslation(props: SystemConsoleCustomSettingsCompon
|
||||
<span style={{marginRight: '12px'}}>
|
||||
{autoTranslationSettings.Enable ? on : off}
|
||||
</span>
|
||||
<Toggle
|
||||
size='btn-md'
|
||||
<Switch
|
||||
id='Enable'
|
||||
size='medium'
|
||||
disabled={props.disabled || props.setByEnv}
|
||||
toggled={autoTranslationSettings.Enable}
|
||||
id={'Enable'}
|
||||
checked={autoTranslationSettings.Enable}
|
||||
onChange={handleToggle}
|
||||
data-testid='Enable-button'
|
||||
tabIndex={-1}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
onToggle={handleToggle}
|
||||
aria-label={formatMessage(messages.enableAutoTranslationTitle)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -123,11 +123,6 @@
|
||||
border: none;
|
||||
margin: 0;
|
||||
gap: 8px;
|
||||
|
||||
.btn-toggle,
|
||||
.btn-toggle-primary {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__enable {
|
||||
@@ -138,6 +133,14 @@
|
||||
gap: 12px;
|
||||
line-height: 20px;
|
||||
|
||||
// Compass Switch root is a <label>. Bootstrap form labels add
|
||||
// margin-bottom, and Switch defaults to width: 100%, which throws off
|
||||
// vertical alignment next to the actions menu button.
|
||||
> label {
|
||||
width: auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.LoadingSpinner .spinner {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ describe('UserPropertyDotMenu', () => {
|
||||
|
||||
const editableItem = screen.getByRole('menuitemcheckbox', {name: /Editable by users/});
|
||||
expect(editableItem).toHaveAttribute('aria-checked', 'false');
|
||||
expect(within(editableItem).getByRole('button')).toBeEnabled();
|
||||
expect(within(editableItem).getByRole('switch')).toBeEnabled();
|
||||
expect(screen.queryByText('Synced attributes are managed by AD/LDAP or SAML')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -305,7 +305,7 @@ describe('UserPropertyDotMenu', () => {
|
||||
|
||||
const editableItem = screen.getByRole('menuitemcheckbox', {name: /Editable by users/});
|
||||
expect(editableItem).toHaveAttribute('aria-checked', 'false');
|
||||
expect(within(editableItem).getByRole('button')).toBeDisabled();
|
||||
expect(within(editableItem).getByRole('switch')).toBeDisabled();
|
||||
expect(screen.getByText('Synced attributes are managed by AD/LDAP or SAML')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -347,7 +347,7 @@ describe('UserPropertyDotMenu', () => {
|
||||
|
||||
const editableItem = screen.getByRole('menuitemcheckbox', {name: /Editable by users/});
|
||||
expect(editableItem).toHaveAttribute('aria-checked', 'false');
|
||||
expect(within(editableItem).getByRole('button')).toBeDisabled();
|
||||
expect(within(editableItem).getByRole('switch')).toBeDisabled();
|
||||
expect(screen.getByText('Synced attributes are managed by AD/LDAP or SAML')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -369,7 +369,7 @@ describe('UserPropertyDotMenu', () => {
|
||||
|
||||
const editableItem = screen.getByRole('menuitemcheckbox', {name: /Editable by users/});
|
||||
expect(editableItem).toHaveAttribute('aria-checked', 'false');
|
||||
expect(within(editableItem).getByRole('button')).toBeDisabled();
|
||||
expect(within(editableItem).getByRole('switch')).toBeDisabled();
|
||||
expect(screen.getByText('Synced attributes are managed by AD/LDAP or SAML')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -390,7 +390,7 @@ describe('UserPropertyDotMenu', () => {
|
||||
|
||||
const editableItem = screen.getByRole('menuitemcheckbox', {name: /Editable by users/});
|
||||
expect(editableItem).toHaveAttribute('aria-checked', 'false');
|
||||
expect(within(editableItem).getByRole('button')).toBeDisabled();
|
||||
expect(within(editableItem).getByRole('switch')).toBeDisabled();
|
||||
expect(screen.getByText('This attribute is managed by an integration')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('Link attribute to AD/LDAP')).toBeInTheDocument();
|
||||
@@ -438,7 +438,7 @@ describe('UserPropertyDotMenu', () => {
|
||||
|
||||
// everything else stays editable
|
||||
expect(screen.getByRole('menuitem', {name: /Visibility/})).not.toHaveAttribute('aria-disabled', 'true');
|
||||
expect(within(screen.getByRole('menuitemcheckbox', {name: /Editable by users/})).getByRole('button')).toBeEnabled();
|
||||
expect(within(screen.getByRole('menuitemcheckbox', {name: /Editable by users/})).getByRole('switch')).toBeEnabled();
|
||||
expect(screen.getByRole('menuitem', {name: /Duplicate attribute/})).not.toHaveAttribute('aria-disabled', 'true');
|
||||
expect(screen.getByRole('menuitem', {name: /Delete attribute/})).not.toHaveAttribute('aria-disabled', 'true');
|
||||
});
|
||||
|
||||
@@ -7,13 +7,13 @@ import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import {CheckIcon, ChevronRightIcon, DotsHorizontalIcon, EyeOutlineIcon, FormatListNumberedIcon, LockOutlineIcon, PencilOutlineIcon, SyncIcon, TrashCanOutlineIcon, ContentCopyIcon} from '@mattermost/compass-icons/components';
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import type {FieldVisibility} from '@mattermost/types/properties';
|
||||
import type {UserPropertyField} from '@mattermost/types/properties_user';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
import * as Menu from 'components/menu';
|
||||
import Toggle from 'components/toggle';
|
||||
|
||||
import {ModalIdentifiers} from 'utils/constants';
|
||||
import {slugifyForCEL} from 'utils/properties';
|
||||
@@ -401,14 +401,21 @@ const DotMenu = ({
|
||||
/>
|
||||
)}
|
||||
trailingElements={(
|
||||
<Toggle
|
||||
size='btn-sm'
|
||||
disabled={isManagedExternally}
|
||||
onToggle={handleEditableByUsersToggle}
|
||||
toggled={isEditableByUsers}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
tabIndex={-1}
|
||||
/>
|
||||
<span
|
||||
onClick={isManagedExternally ? undefined : (event: React.MouseEvent) => {
|
||||
// Compass Switch fires onChange on its own click; without
|
||||
// stopping propagation Menu.Item onClick also runs.
|
||||
event.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
size='small'
|
||||
disabled={isManagedExternally}
|
||||
onChange={handleEditableByUsersToggle}
|
||||
checked={isEditableByUsers}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
{field.create_at !== 0 && ([
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
.AdminSettingsSegmentedRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 24px;
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--center-channel-color);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
&__segments {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
padding: 4px;
|
||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.08);
|
||||
border-radius: 8px;
|
||||
background: rgba(var(--center-channel-color-rgb), 0.08);
|
||||
}
|
||||
|
||||
&__segment {
|
||||
padding: 4px 12px;
|
||||
border: 0;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.72);
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
|
||||
&:hover:not(:disabled):not(.AdminSettingsSegmentedRow__segment--active) {
|
||||
background: rgba(var(--center-channel-color-rgb), 0.06);
|
||||
color: var(--center-channel-color);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&--active {
|
||||
background: var(--center-channel-bg);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
color: var(--center-channel-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, {type JSX, type ReactNode} from 'react';
|
||||
|
||||
import './admin_settings_segmented_row.scss';
|
||||
|
||||
export type AdminSettingsSegment = {
|
||||
id: string;
|
||||
label: ReactNode;
|
||||
selected: boolean;
|
||||
onSelect: () => void;
|
||||
testId?: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
children: ReactNode;
|
||||
secondaryLabel: ReactNode;
|
||||
segments: AdminSettingsSegment[];
|
||||
last?: boolean;
|
||||
disabled?: boolean;
|
||||
footer?: ReactNode;
|
||||
groupAriaLabel?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Admin Console settings row with a temporary segmented control.
|
||||
* Use for mutually exclusive named modes (e.g. Public / Private) until
|
||||
* Compass ships a segmented control primitive.
|
||||
*/
|
||||
const AdminSettingsSegmentedRow = ({
|
||||
id,
|
||||
children,
|
||||
secondaryLabel,
|
||||
segments,
|
||||
last,
|
||||
disabled,
|
||||
footer,
|
||||
groupAriaLabel,
|
||||
}: Props): JSX.Element => (
|
||||
<>
|
||||
<div
|
||||
className='AdminSettingsSegmentedRow'
|
||||
data-testid={id}
|
||||
>
|
||||
<div className='AdminSettingsSegmentedRow__text'>
|
||||
<div className='AdminSettingsSegmentedRow__title'>
|
||||
{children}
|
||||
</div>
|
||||
<div className='AdminSettingsSegmentedRow__description'>
|
||||
{secondaryLabel}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className='AdminSettingsSegmentedRow__segments'
|
||||
role='group'
|
||||
aria-label={groupAriaLabel}
|
||||
>
|
||||
{segments.map((segment) => (
|
||||
<button
|
||||
key={segment.id}
|
||||
type='button'
|
||||
id={`${id}-${segment.id}`}
|
||||
data-testid={segment.testId ?? `${id}-${segment.id}`}
|
||||
className={classNames('AdminSettingsSegmentedRow__segment', {
|
||||
'AdminSettingsSegmentedRow__segment--active': segment.selected,
|
||||
})}
|
||||
aria-pressed={segment.selected}
|
||||
disabled={disabled}
|
||||
onClick={() => {
|
||||
if (!segment.selected) {
|
||||
segment.onSelect();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{segment.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{footer}
|
||||
{!last && (
|
||||
<div className='section-separator'>
|
||||
<hr className='separator__hr'/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
export default AdminSettingsSegmentedRow;
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {type JSX, type ReactNode} from 'react';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
children: ReactNode;
|
||||
secondaryLabel: ReactNode;
|
||||
checked: boolean;
|
||||
onChange: () => void;
|
||||
last?: boolean;
|
||||
disabled?: boolean;
|
||||
|
||||
/** Extra content rendered below the switch row (e.g. domain input). */
|
||||
footer?: ReactNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Admin Console settings row: Compass Switch with primary + secondary labels,
|
||||
* optional footer, and the legacy section separator between rows.
|
||||
*
|
||||
* Uses `size="medium"` + `semiBold` to match Channel Settings rows. The old
|
||||
* Toggle adapter defaulted to `btn-lg` → Compass `large` (16px label).
|
||||
*
|
||||
* Preserves both `data-testid={id}` (wrapper, for Cypress) and
|
||||
* `data-testid={`${id}-button`}` (on the switch input, for unit tests).
|
||||
*/
|
||||
const AdminSettingsSwitch = ({
|
||||
id,
|
||||
children,
|
||||
secondaryLabel,
|
||||
checked,
|
||||
onChange,
|
||||
last,
|
||||
disabled,
|
||||
footer,
|
||||
}: Props): JSX.Element => (
|
||||
<>
|
||||
<div data-testid={id}>
|
||||
<Switch
|
||||
id={id}
|
||||
size='medium'
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
onChange={onChange}
|
||||
data-testid={`${id}-button`}
|
||||
tabIndex={0}
|
||||
semiBold={true}
|
||||
secondaryLabel={secondaryLabel}
|
||||
>
|
||||
{children}
|
||||
</Switch>
|
||||
</div>
|
||||
{footer}
|
||||
{!last && (
|
||||
<div className='section-separator'>
|
||||
<hr className='separator__hr'/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
export default AdminSettingsSwitch;
|
||||
@@ -26,63 +26,60 @@ exports[`admin_console/team_channel_settings/channel/ChannelModes should match s
|
||||
<div
|
||||
class="group-teams-and-channels--body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="AdminSettingsSegmentedRow"
|
||||
data-testid="allow-all-toggle"
|
||||
>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
class="AdminSettingsSegmentedRow__text"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<div
|
||||
class="AdminSettingsSegmentedRow__title"
|
||||
>
|
||||
Public channel or private channel
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allow-all-toggle"
|
||||
class="AdminSettingsSegmentedRow__description"
|
||||
>
|
||||
<button
|
||||
aria-pressed="true"
|
||||
class="btn btn-lg btn-toggle active"
|
||||
data-testid="allow-all-toggle-button"
|
||||
id="allow-all-toggle"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
<div
|
||||
class="bg-text on"
|
||||
>
|
||||
Public
|
||||
</div>
|
||||
</button>
|
||||
Select Public for a channel any user can find and join.
|
||||
<br />
|
||||
Select Private to require channel invitations to join.
|
||||
<br />
|
||||
Use this control to change this channel from public to private or from private to public.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
aria-label="Public channel or private channel"
|
||||
class="AdminSettingsSegmentedRow__segments"
|
||||
role="group"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
<button
|
||||
aria-pressed="true"
|
||||
class="AdminSettingsSegmentedRow__segment AdminSettingsSegmentedRow__segment--active"
|
||||
data-testid="allow-all-toggle-public"
|
||||
id="allow-all-toggle-public"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
>
|
||||
Select Public for a channel any user can find and join.
|
||||
<br />
|
||||
Select Private to require channel invitations to join.
|
||||
<br />
|
||||
Use this switch to change this channel from public to private or from private to public.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
Public
|
||||
</button>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="AdminSettingsSegmentedRow__segment"
|
||||
data-testid="allow-all-toggle-private"
|
||||
id="allow-all-toggle-private"
|
||||
type="button"
|
||||
>
|
||||
Private
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -115,41 +112,23 @@ exports[`admin_console/team_channel_settings/channel/ChannelModes should match s
|
||||
<div
|
||||
class="group-teams-and-channels--body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="syncGroupSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="syncGroupSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Sync Group Members
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="syncGroupSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="syncGroupSwitch-button"
|
||||
id="syncGroupSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
Sync Group Members
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
When enabled, adding and removing users from groups will add or remove them from this channel. The only way of inviting members to this channel is by adding the groups they belong to.
|
||||
<a
|
||||
@@ -160,74 +139,88 @@ exports[`admin_console/team_channel_settings/channel/ChannelModes should match s
|
||||
>
|
||||
Learn More
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="syncGroupSwitch-button"
|
||||
id="syncGroupSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="AdminSettingsSegmentedRow"
|
||||
data-testid="allow-all-toggle"
|
||||
>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
class="AdminSettingsSegmentedRow__text"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<div
|
||||
class="AdminSettingsSegmentedRow__title"
|
||||
>
|
||||
Public channel or private channel
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allow-all-toggle"
|
||||
class="AdminSettingsSegmentedRow__description"
|
||||
>
|
||||
<button
|
||||
aria-pressed="true"
|
||||
class="btn btn-lg btn-toggle active"
|
||||
data-testid="allow-all-toggle-button"
|
||||
id="allow-all-toggle"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
<div
|
||||
class="bg-text on"
|
||||
>
|
||||
Public
|
||||
</div>
|
||||
</button>
|
||||
Select Public for a channel any user can find and join.
|
||||
<br />
|
||||
Select Private to require channel invitations to join.
|
||||
<br />
|
||||
Use this control to change this channel from public to private or from private to public.
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
aria-label="Public channel or private channel"
|
||||
class="AdminSettingsSegmentedRow__segments"
|
||||
role="group"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
<button
|
||||
aria-pressed="true"
|
||||
class="AdminSettingsSegmentedRow__segment AdminSettingsSegmentedRow__segment--active"
|
||||
data-testid="allow-all-toggle-public"
|
||||
id="allow-all-toggle-public"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding "
|
||||
>
|
||||
Select Public for a channel any user can find and join.
|
||||
<br />
|
||||
Select Private to require channel invitations to join.
|
||||
<br />
|
||||
Use this switch to change this channel from public to private or from private to public.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
Public
|
||||
</button>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="AdminSettingsSegmentedRow__segment"
|
||||
data-testid="allow-all-toggle-private"
|
||||
id="allow-all-toggle-private"
|
||||
type="button"
|
||||
>
|
||||
Private
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {type JSX} from 'react';
|
||||
import {FormattedMessage, defineMessage} from 'react-intl';
|
||||
import {FormattedMessage, defineMessage, useIntl} from 'react-intl';
|
||||
|
||||
import ExternalLink from 'components/external_link';
|
||||
import AdminPanel from 'components/widgets/admin_console/admin_panel';
|
||||
|
||||
import LineSwitch from '../../line_switch';
|
||||
import AdminSettingsSegmentedRow from '../../admin_settings_segmented_row';
|
||||
import AdminSettingsSwitch from '../../admin_settings_switch';
|
||||
|
||||
interface Props {
|
||||
isPublic: boolean;
|
||||
@@ -24,24 +25,18 @@ interface Props {
|
||||
const SyncGroupsToggle = (props: Props): JSX.Element => {
|
||||
const {isPublic, isSynced, isDefault, onToggle, isDisabled, policyEnforced} = props;
|
||||
return (
|
||||
<LineSwitch
|
||||
<AdminSettingsSwitch
|
||||
id='syncGroupSwitch'
|
||||
disabled={isDisabled || isDefault || policyEnforced}
|
||||
toggled={isSynced}
|
||||
checked={isSynced}
|
||||
last={isSynced}
|
||||
onToggle={() => {
|
||||
onChange={() => {
|
||||
if (isDefault) {
|
||||
return;
|
||||
}
|
||||
onToggle(!isSynced, isPublic, policyEnforced);
|
||||
}}
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.syncGroupMembers'
|
||||
defaultMessage='Sync Group Members'
|
||||
/>
|
||||
)}
|
||||
subTitle={(
|
||||
secondaryLabel={(
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.syncGroupMembersDescr'
|
||||
defaultMessage='When enabled, adding and removing users from groups will add or remove them from this channel. The only way of inviting members to this channel is by adding the groups they belong to. <link>Learn More</link>'
|
||||
@@ -57,34 +52,41 @@ const SyncGroupsToggle = (props: Props): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.syncGroupMembers'
|
||||
defaultMessage='Sync Group Members'
|
||||
/>
|
||||
</AdminSettingsSwitch>
|
||||
);
|
||||
};
|
||||
|
||||
const AllowAllToggle = (props: Props): JSX.Element | null => {
|
||||
const {isPublic, isSynced, isDefault, onToggle, isDisabled, policyEnforced} = props;
|
||||
const {formatMessage} = useIntl();
|
||||
if (isSynced) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mutual exclusive modes — temporary segmented control until Compass has one.
|
||||
const disabled = isDisabled || isDefault || policyEnforced;
|
||||
const setPublic = (nextPublic: boolean) => {
|
||||
if (isDefault || nextPublic === isPublic) {
|
||||
return;
|
||||
}
|
||||
onToggle(isSynced, nextPublic, policyEnforced);
|
||||
};
|
||||
|
||||
return (
|
||||
<LineSwitch
|
||||
<AdminSettingsSegmentedRow
|
||||
id='allow-all-toggle'
|
||||
disabled={isDisabled || isDefault || policyEnforced}
|
||||
toggled={isPublic}
|
||||
disabled={disabled}
|
||||
last={false}
|
||||
onToggle={() => {
|
||||
if (isDefault) {
|
||||
return;
|
||||
}
|
||||
onToggle(isSynced, !isPublic, policyEnforced);
|
||||
}}
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.isPublic'
|
||||
defaultMessage='Public channel or private channel'
|
||||
/>
|
||||
)}
|
||||
subTitle={isDefault ? (
|
||||
groupAriaLabel={formatMessage({
|
||||
id: 'admin.channel_settings.channel_details.isPublic',
|
||||
defaultMessage: 'Public channel or private channel',
|
||||
})}
|
||||
secondaryLabel={isDefault ? (
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.isDefaultDescr'
|
||||
defaultMessage='This default channel cannot be converted into a private channel.'
|
||||
@@ -92,24 +94,42 @@ const AllowAllToggle = (props: Props): JSX.Element | null => {
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.isPublicDescr'
|
||||
defaultMessage='Select Public for a channel any user can find and join. {br}Select Private to require channel invitations to join. {br}Use this switch to change this channel from public to private or from private to public.'
|
||||
defaultMessage='Select Public for a channel any user can find and join. {br}Select Private to require channel invitations to join. {br}Use this control to change this channel from public to private or from private to public.'
|
||||
values={{br: (<br/>)}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
onText={(
|
||||
<FormattedMessage
|
||||
id='channel_toggle_button.public'
|
||||
defaultMessage='Public'
|
||||
/>
|
||||
)}
|
||||
offText={(
|
||||
<FormattedMessage
|
||||
id='channel_toggle_button.private'
|
||||
defaultMessage='Private'
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
segments={[
|
||||
{
|
||||
id: 'public',
|
||||
testId: 'allow-all-toggle-public',
|
||||
selected: isPublic,
|
||||
onSelect: () => setPublic(true),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='channel_toggle_button.public'
|
||||
defaultMessage='Public'
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'private',
|
||||
testId: 'allow-all-toggle-private',
|
||||
selected: !isPublic,
|
||||
onSelect: () => setPublic(false),
|
||||
label: (
|
||||
<FormattedMessage
|
||||
id='channel_toggle_button.private'
|
||||
defaultMessage='Private'
|
||||
/>
|
||||
),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.isPublic'
|
||||
defaultMessage='Public channel or private channel'
|
||||
/>
|
||||
</AdminSettingsSegmentedRow>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -119,23 +139,23 @@ const PolicyEnforceToggle = (props: Props): JSX.Element | null => {
|
||||
return null;
|
||||
}
|
||||
|
||||
let subTitle: JSX.Element;
|
||||
let secondaryLabel: JSX.Element;
|
||||
if (isDefault) {
|
||||
subTitle = (
|
||||
secondaryLabel = (
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.default_channel_not_supported'
|
||||
defaultMessage='The default channel cannot have a membership policy.'
|
||||
/>
|
||||
);
|
||||
} else if (isPublic) {
|
||||
subTitle = (
|
||||
secondaryLabel = (
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.attribute_based_description_public'
|
||||
defaultMessage='Recommend this channel to users whose attributes match the rules and (optionally) auto-add them. Anyone can still join freely.'
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
subTitle = (
|
||||
secondaryLabel = (
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.attribute_based_description'
|
||||
defaultMessage='Restrict which users can be invited to this channel based on their user attributes and values. Only people who match the specified conditions will be allowed to be selected and added to this channel.'
|
||||
@@ -144,30 +164,29 @@ const PolicyEnforceToggle = (props: Props): JSX.Element | null => {
|
||||
}
|
||||
|
||||
return (
|
||||
<LineSwitch
|
||||
<AdminSettingsSwitch
|
||||
id='policy-enforce-toggle'
|
||||
|
||||
// Keep the visual disabled state aligned with the click guard in
|
||||
// onToggle (which short-circuits when isDefault is true). Without
|
||||
// onChange (which short-circuits when isDefault is true). Without
|
||||
// including isDefault here the toggle would look enabled but do
|
||||
// nothing on the default channel.
|
||||
disabled={isDisabled || isSynced || isDefault || !policyEnforcedToggleAvailable}
|
||||
toggled={policyEnforced}
|
||||
checked={policyEnforced}
|
||||
last={true}
|
||||
onToggle={() => {
|
||||
onChange={() => {
|
||||
if (isDefault || !policyEnforcedToggleAvailable) {
|
||||
return;
|
||||
}
|
||||
onToggle(isSynced, isPublic, !policyEnforced);
|
||||
}}
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.policy_enforced_title'
|
||||
defaultMessage='Enable attribute based channel access'
|
||||
/>
|
||||
)}
|
||||
subTitle={subTitle}
|
||||
/>
|
||||
secondaryLabel={secondaryLabel}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.channel_settings.channel_details.policy_enforced_title'
|
||||
defaultMessage='Enable attribute based channel access'
|
||||
/>
|
||||
</AdminSettingsSwitch>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -216,4 +235,3 @@ export const ChannelModes = (props: Props): JSX.Element => {
|
||||
</AdminPanel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {type JSX} from 'react';
|
||||
|
||||
import Toggle from 'components/toggle';
|
||||
|
||||
type Props = {
|
||||
id: string;
|
||||
title: JSX.Element;
|
||||
toggled: boolean;
|
||||
subTitle: JSX.Element;
|
||||
onToggle: () => void;
|
||||
last?: boolean;
|
||||
disabled?: boolean;
|
||||
singleLine?: boolean;
|
||||
children?: JSX.Element;
|
||||
offText?: JSX.Element;
|
||||
onText?: JSX.Element;
|
||||
};
|
||||
|
||||
const LineSwitch = (props: Props): JSX.Element => {
|
||||
const {title, subTitle, singleLine, toggled, onToggle, children, offText, onText, disabled, last, id} = props;
|
||||
return (<div>
|
||||
<div className='line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center'>
|
||||
<label className='line-switch__label'>{title}</label>
|
||||
<div
|
||||
data-testid={id}
|
||||
className='line-switch__toggle'
|
||||
>
|
||||
<Toggle
|
||||
id={id}
|
||||
disabled={disabled}
|
||||
onToggle={onToggle}
|
||||
toggled={toggled}
|
||||
onText={onText}
|
||||
offText={offText}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col-sm-10'>
|
||||
<div className={`help-text-small help-text-no-padding ${singleLine ? 'help-text-single-line' : ''}`}>{subTitle}</div>
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
{!last && <div className='section-separator'><hr className='separator__hr'/></div>}
|
||||
</div>);
|
||||
};
|
||||
|
||||
export default LineSwitch;
|
||||
@@ -61,106 +61,104 @@ exports[`admin_console/team_channel_settings/team/TeamDetails should match snaps
|
||||
<div
|
||||
class="group-teams-and-channels--body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowAllToggleSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowAllToggleSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Anyone can join this team
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowAllToggleSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowAllToggleSwitch-button"
|
||||
id="allowAllToggleSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding help-text-single-line"
|
||||
Anyone can join this team
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
This team can be discovered allowing anyone with an account to join this team.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
Only specific email domains can join this team
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowedDomainsToggleSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowedDomainsToggleSwitch-button"
|
||||
id="allowedDomainsToggleSwitch"
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowAllToggleSwitch-button"
|
||||
id="allowAllToggleSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding help-text-single-line"
|
||||
>
|
||||
Users can only join the team if their email matches one of the specified domains
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="help-text csvDomains"
|
||||
>
|
||||
Comma Separated Email Domain List
|
||||
</div>
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="mattermost.com"
|
||||
type="text"
|
||||
value=""
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-testid="allowedDomainsToggleSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowedDomainsToggleSwitch"
|
||||
>
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
Only specific email domains can join this team
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
Users can only join the team if their email matches one of the specified domains
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowedDomainsToggleSwitch-button"
|
||||
id="allowedDomainsToggleSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="help-text csvDomains"
|
||||
>
|
||||
Comma Separated Email Domain List
|
||||
</div>
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="mattermost.com"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,106 +26,105 @@ exports[`admin_console/team_channel_settings/team/TeamModes should match snapsho
|
||||
<div
|
||||
class="group-teams-and-channels--body"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
<div
|
||||
data-testid="allowAllToggleSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowAllToggleSwitch"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
Anyone can join this team
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowAllToggleSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="false"
|
||||
class="btn btn-lg btn-toggle"
|
||||
data-testid="allowAllToggleSwitch-button"
|
||||
id="allowAllToggleSwitch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding help-text-single-line"
|
||||
Anyone can join this team
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
This team can be discovered allowing anyone with an account to join this team.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="line-switch d-flex flex-sm-column flex-md-row align-items-sm-start align-items-center"
|
||||
>
|
||||
<label
|
||||
class="line-switch__label"
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
Only specific email domains can join this team
|
||||
</label>
|
||||
<div
|
||||
class="line-switch__toggle"
|
||||
data-testid="allowedDomainsToggleSwitch"
|
||||
>
|
||||
<button
|
||||
aria-pressed="true"
|
||||
class="btn btn-lg btn-toggle active"
|
||||
data-testid="allowedDomainsToggleSwitch-button"
|
||||
id="allowedDomainsToggleSwitch"
|
||||
<input
|
||||
aria-checked="false"
|
||||
class="switch__input"
|
||||
data-testid="allowAllToggleSwitch-button"
|
||||
id="allowAllToggleSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="handle"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div
|
||||
class="col-sm-10"
|
||||
>
|
||||
<div
|
||||
class="help-text-small help-text-no-padding help-text-single-line"
|
||||
>
|
||||
Users can only join the team if their email matches one of the specified domains
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="help-text csvDomains"
|
||||
>
|
||||
Comma Separated Email Domain List
|
||||
</div>
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="mattermost.com"
|
||||
type="text"
|
||||
value=""
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="section-separator"
|
||||
>
|
||||
<hr
|
||||
class="separator__hr"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
data-testid="allowedDomainsToggleSwitch"
|
||||
>
|
||||
<label
|
||||
class="switch switch--size-medium switch--semi-bold switch--with-secondary"
|
||||
for="allowedDomainsToggleSwitch"
|
||||
>
|
||||
<span
|
||||
class="switch__labels"
|
||||
>
|
||||
<span
|
||||
class="switch__label"
|
||||
>
|
||||
Only specific email domains can join this team
|
||||
</span>
|
||||
<span
|
||||
class="switch__secondary-label"
|
||||
>
|
||||
Users can only join the team if their email matches one of the specified domains
|
||||
</span>
|
||||
</span>
|
||||
<span
|
||||
class="switch__track"
|
||||
>
|
||||
<input
|
||||
aria-checked="true"
|
||||
checked=""
|
||||
class="switch__input"
|
||||
data-testid="allowedDomainsToggleSwitch-button"
|
||||
id="allowedDomainsToggleSwitch"
|
||||
role="switch"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
/>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
class="switch__knob"
|
||||
/>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
class="help-text csvDomains"
|
||||
>
|
||||
Comma Separated Email Domain List
|
||||
</div>
|
||||
<input
|
||||
class="form-control"
|
||||
placeholder="mattermost.com"
|
||||
type="text"
|
||||
value=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -542,7 +542,7 @@ describe('admin_console/team_channel_settings/team/TeamDetails', () => {
|
||||
|
||||
// Removing the last policy drops the enforce toggle on its own.
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('policy-enforce-toggle-button')).toHaveAttribute('aria-pressed', 'false');
|
||||
expect(screen.getByTestId('policy-enforce-toggle-button')).not.toBeChecked();
|
||||
});
|
||||
|
||||
// Save goes straight through — no spurious "Apply membership policy" modal.
|
||||
@@ -646,7 +646,7 @@ describe('admin_console/team_channel_settings/team/TeamDetails', () => {
|
||||
await userEvent.click(document.getElementById('confirmModalButton')!);
|
||||
|
||||
// Custom rule still governs, so enforcement stays on.
|
||||
expect(screen.getByTestId('policy-enforce-toggle-button')).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.getByTestId('policy-enforce-toggle-button')).toBeChecked();
|
||||
|
||||
// Removal is not new criteria: the unedited custom rule must not trigger the apply-count modal.
|
||||
await userEvent.click(screen.getByText('Save'));
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {renderWithContext} from 'tests/react_testing_utils';
|
||||
import {renderWithContext, screen} from 'tests/react_testing_utils';
|
||||
|
||||
import {TeamModes} from './team_modes';
|
||||
|
||||
@@ -20,4 +20,44 @@ describe('admin_console/team_channel_settings/team/TeamModes', () => {
|
||||
);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('keeps separator under Sync Group Members when synced and ABAC row follows', () => {
|
||||
renderWithContext(
|
||||
<TeamModes
|
||||
onToggle={jest.fn()}
|
||||
syncChecked={true}
|
||||
allAllowedChecked={false}
|
||||
allowedDomains={''}
|
||||
allowedDomainsChecked={false}
|
||||
isLicensedForLDAPGroups={true}
|
||||
abacSupported={true}
|
||||
policyEnforced={false}
|
||||
policyEnforcedToggleAvailable={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('policy-enforce-toggle')).toBeInTheDocument();
|
||||
|
||||
const syncRow = screen.getByTestId('syncGroupSwitch');
|
||||
expect(syncRow.nextElementSibling).toHaveClass('section-separator');
|
||||
});
|
||||
|
||||
test('omits separator under Sync Group Members when synced and no following row', () => {
|
||||
renderWithContext(
|
||||
<TeamModes
|
||||
onToggle={jest.fn()}
|
||||
syncChecked={true}
|
||||
allAllowedChecked={false}
|
||||
allowedDomains={''}
|
||||
allowedDomainsChecked={false}
|
||||
isLicensedForLDAPGroups={true}
|
||||
abacSupported={false}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.queryByTestId('policy-enforce-toggle')).not.toBeInTheDocument();
|
||||
|
||||
const syncRow = screen.getByTestId('syncGroupSwitch');
|
||||
expect(syncRow.nextElementSibling).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -7,7 +7,7 @@ import {FormattedMessage, defineMessage} from 'react-intl';
|
||||
import ExternalLink from 'components/external_link';
|
||||
import AdminPanel from 'components/widgets/admin_console/admin_panel';
|
||||
|
||||
import LineSwitch from '../../line_switch';
|
||||
import AdminSettingsSwitch from '../../admin_settings_switch';
|
||||
|
||||
type Props = {
|
||||
syncChecked: boolean;
|
||||
@@ -18,20 +18,14 @@ type Props = {
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
const SyncGroupsToggle = ({syncChecked, allAllowedChecked, allowedDomainsChecked, allowedDomains, onToggle, isDisabled}: Props) => (
|
||||
<LineSwitch
|
||||
const SyncGroupsToggle = ({syncChecked, allAllowedChecked, allowedDomainsChecked, allowedDomains, onToggle, isDisabled, last}: Props & {last?: boolean}) => (
|
||||
<AdminSettingsSwitch
|
||||
id='syncGroupSwitch'
|
||||
disabled={isDisabled}
|
||||
toggled={syncChecked}
|
||||
last={syncChecked}
|
||||
onToggle={() => onToggle(!syncChecked, allAllowedChecked, allowedDomainsChecked, allowedDomains)}
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.syncGroupMembers'
|
||||
defaultMessage='Sync Group Members'
|
||||
/>
|
||||
)}
|
||||
subTitle={(
|
||||
checked={syncChecked}
|
||||
last={last}
|
||||
onChange={() => onToggle(!syncChecked, allAllowedChecked, allowedDomainsChecked, allowedDomains)}
|
||||
secondaryLabel={(
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.syncGroupMembersDescr'
|
||||
defaultMessage='When enabled, adding and removing users from groups will add or remove them from this team. The only way of inviting members to this team is by adding the groups they belong to. <link>Learn More</link>'
|
||||
@@ -47,69 +41,74 @@ const SyncGroupsToggle = ({syncChecked, allAllowedChecked, allowedDomainsChecked
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>);
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.syncGroupMembers'
|
||||
defaultMessage='Sync Group Members'
|
||||
/>
|
||||
</AdminSettingsSwitch>
|
||||
);
|
||||
|
||||
const AllowAllToggle = ({syncChecked, allAllowedChecked, allowedDomainsChecked, allowedDomains, onToggle, isDisabled}: Props) =>
|
||||
(syncChecked ? null : (
|
||||
<LineSwitch
|
||||
<AdminSettingsSwitch
|
||||
id='allowAllToggleSwitch'
|
||||
disabled={isDisabled}
|
||||
toggled={allAllowedChecked}
|
||||
singleLine={true}
|
||||
onToggle={() => onToggle(syncChecked, !allAllowedChecked, allowedDomainsChecked, allowedDomains)}
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.anyoneCanJoin'
|
||||
defaultMessage='Anyone can join this team'
|
||||
/>
|
||||
)}
|
||||
subTitle={(
|
||||
checked={allAllowedChecked}
|
||||
onChange={() => onToggle(syncChecked, !allAllowedChecked, allowedDomainsChecked, allowedDomains)}
|
||||
secondaryLabel={(
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.anyoneCanJoinDescr'
|
||||
defaultMessage='This team can be discovered allowing anyone with an account to join this team.'
|
||||
/>
|
||||
)}
|
||||
/>));
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.anyoneCanJoin'
|
||||
defaultMessage='Anyone can join this team'
|
||||
/>
|
||||
</AdminSettingsSwitch>
|
||||
));
|
||||
|
||||
const AllowedDomainsToggle = ({syncChecked, allAllowedChecked, allowedDomainsChecked, allowedDomains, onToggle, isDisabled, last = true}: Props & {last?: boolean}) =>
|
||||
(syncChecked ? null : (
|
||||
<LineSwitch
|
||||
<AdminSettingsSwitch
|
||||
id='allowedDomainsToggleSwitch'
|
||||
disabled={isDisabled}
|
||||
toggled={allowedDomainsChecked}
|
||||
checked={allowedDomainsChecked}
|
||||
last={last}
|
||||
onToggle={() => onToggle(syncChecked, allAllowedChecked, !allowedDomainsChecked, allowedDomains)}
|
||||
singleLine={true}
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.specificDomains'
|
||||
defaultMessage='Only specific email domains can join this team'
|
||||
/>
|
||||
)}
|
||||
subTitle={(
|
||||
onChange={() => onToggle(syncChecked, allAllowedChecked, !allowedDomainsChecked, allowedDomains)}
|
||||
secondaryLabel={(
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.specificDomainsDescr'
|
||||
defaultMessage='Users can only join the team if their email matches one of the specified domains'
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<>
|
||||
<div className='help-text csvDomains'>
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.csvDomains'
|
||||
defaultMessage='Comma Separated Email Domain List'
|
||||
footer={(
|
||||
<>
|
||||
<div className='help-text csvDomains'>
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.csvDomains'
|
||||
defaultMessage='Comma Separated Email Domain List'
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
value={allowedDomains}
|
||||
placeholder='mattermost.com'
|
||||
className='form-control'
|
||||
onChange={(e) => onToggle(syncChecked, allAllowedChecked, allowedDomainsChecked, e.currentTarget.value)}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type='text'
|
||||
value={allowedDomains}
|
||||
placeholder='mattermost.com'
|
||||
className='form-control'
|
||||
onChange={(e) => onToggle(syncChecked, allAllowedChecked, allowedDomainsChecked, e.currentTarget.value)}
|
||||
disabled={isDisabled}
|
||||
/>
|
||||
</>
|
||||
</LineSwitch>));
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.specificDomains'
|
||||
defaultMessage='Only specific email domains can join this team'
|
||||
/>
|
||||
</AdminSettingsSwitch>
|
||||
));
|
||||
|
||||
type PolicyEnforceToggleProps = {
|
||||
syncChecked: boolean;
|
||||
@@ -122,27 +121,21 @@ type PolicyEnforceToggleProps = {
|
||||
};
|
||||
|
||||
const PolicyEnforceToggle = ({syncChecked, policyEnforced, policyEnforcedToggleAvailable, onToggle, isDisabled}: PolicyEnforceToggleProps) => (
|
||||
<LineSwitch
|
||||
<AdminSettingsSwitch
|
||||
id='policy-enforce-toggle'
|
||||
disabled={isDisabled || syncChecked || !policyEnforcedToggleAvailable}
|
||||
toggled={policyEnforced}
|
||||
checked={policyEnforced}
|
||||
|
||||
// The email-domains row renders after this one unless group sync hides it,
|
||||
// in which case this is the last row.
|
||||
// Email-domains (and anyone-can-join) hide when synced; this row stays
|
||||
// visible (disabled) and is then the last row.
|
||||
last={syncChecked}
|
||||
onToggle={() => {
|
||||
onChange={() => {
|
||||
if (syncChecked || !policyEnforcedToggleAvailable) {
|
||||
return;
|
||||
}
|
||||
onToggle?.(!policyEnforced);
|
||||
}}
|
||||
title={(
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.policy_enforced_title'
|
||||
defaultMessage='Manage membership with attribute based membership policies'
|
||||
/>
|
||||
)}
|
||||
subTitle={syncChecked ? (
|
||||
secondaryLabel={syncChecked ? (
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.policy_enforced_group_synced'
|
||||
defaultMessage='Group synced teams cannot use a membership policy. Disable group sync to manage access by attributes.'
|
||||
@@ -153,7 +146,13 @@ const PolicyEnforceToggle = ({syncChecked, policyEnforced, policyEnforcedToggleA
|
||||
defaultMessage='Restrict which users can be added to this team based on their user attributes and values. Only people who match the specified conditions will be allowed to be selected and added to this team.'
|
||||
/>
|
||||
)}
|
||||
/>);
|
||||
>
|
||||
<FormattedMessage
|
||||
id='admin.team_settings.team_details.policy_enforced_title'
|
||||
defaultMessage='Manage membership with attribute based membership policies'
|
||||
/>
|
||||
</AdminSettingsSwitch>
|
||||
);
|
||||
|
||||
type TeamModesProps = Props & {
|
||||
isLicensedForLDAPGroups?: boolean;
|
||||
@@ -163,16 +162,32 @@ type TeamModesProps = Props & {
|
||||
onPolicyEnforcedToggle?: (policyEnforced: boolean) => void;
|
||||
};
|
||||
|
||||
export const TeamModes = ({allAllowedChecked, syncChecked, allowedDomains, allowedDomainsChecked, onToggle, isDisabled, isLicensedForLDAPGroups, abacSupported, policyEnforced, policyEnforcedToggleAvailable, onPolicyEnforcedToggle}: TeamModesProps) => (
|
||||
<AdminPanel
|
||||
id='team_manage'
|
||||
title={defineMessage({id: 'admin.team_settings.team_detail.manageTitle', defaultMessage: 'Team Management'})}
|
||||
subtitle={defineMessage({id: 'admin.team_settings.team_detail.manageDescription', defaultMessage: 'Choose between inviting members manually or syncing members automatically from groups.'})}
|
||||
>
|
||||
<div className='group-teams-and-channels'>
|
||||
<div className='group-teams-and-channels--body'>
|
||||
{isLicensedForLDAPGroups && !policyEnforced &&
|
||||
<SyncGroupsToggle
|
||||
export const TeamModes = ({allAllowedChecked, syncChecked, allowedDomains, allowedDomainsChecked, onToggle, isDisabled, isLicensedForLDAPGroups, abacSupported, policyEnforced, policyEnforcedToggleAvailable, onPolicyEnforcedToggle}: TeamModesProps) => {
|
||||
// Omit the Sync row separator only when nothing else in this panel is
|
||||
// visible. ABAC still renders (disabled) while synced, so sync-on alone
|
||||
// must not set last.
|
||||
const syncIsLastVisible = Boolean(syncChecked && !abacSupported);
|
||||
|
||||
return (
|
||||
<AdminPanel
|
||||
id='team_manage'
|
||||
title={defineMessage({id: 'admin.team_settings.team_detail.manageTitle', defaultMessage: 'Team Management'})}
|
||||
subtitle={defineMessage({id: 'admin.team_settings.team_detail.manageDescription', defaultMessage: 'Choose between inviting members manually or syncing members automatically from groups.'})}
|
||||
>
|
||||
<div className='group-teams-and-channels'>
|
||||
<div className='group-teams-and-channels--body'>
|
||||
{isLicensedForLDAPGroups && !policyEnforced &&
|
||||
<SyncGroupsToggle
|
||||
allAllowedChecked={allAllowedChecked}
|
||||
allowedDomainsChecked={allowedDomainsChecked}
|
||||
allowedDomains={allowedDomains}
|
||||
syncChecked={syncChecked}
|
||||
onToggle={onToggle}
|
||||
isDisabled={isDisabled}
|
||||
last={syncIsLastVisible}
|
||||
/>
|
||||
}
|
||||
<AllowAllToggle
|
||||
allAllowedChecked={allAllowedChecked}
|
||||
allowedDomainsChecked={allowedDomainsChecked}
|
||||
allowedDomains={allowedDomains}
|
||||
@@ -180,32 +195,25 @@ export const TeamModes = ({allAllowedChecked, syncChecked, allowedDomains, allow
|
||||
onToggle={onToggle}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
}
|
||||
<AllowAllToggle
|
||||
allAllowedChecked={allAllowedChecked}
|
||||
allowedDomainsChecked={allowedDomainsChecked}
|
||||
allowedDomains={allowedDomains}
|
||||
syncChecked={syncChecked}
|
||||
onToggle={onToggle}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
{abacSupported &&
|
||||
<PolicyEnforceToggle
|
||||
{abacSupported &&
|
||||
<PolicyEnforceToggle
|
||||
syncChecked={syncChecked}
|
||||
policyEnforced={Boolean(policyEnforced)}
|
||||
policyEnforcedToggleAvailable={Boolean(policyEnforcedToggleAvailable)}
|
||||
onToggle={onPolicyEnforcedToggle}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
}
|
||||
<AllowedDomainsToggle
|
||||
allAllowedChecked={allAllowedChecked}
|
||||
allowedDomainsChecked={allowedDomainsChecked}
|
||||
allowedDomains={allowedDomains}
|
||||
syncChecked={syncChecked}
|
||||
policyEnforced={Boolean(policyEnforced)}
|
||||
policyEnforcedToggleAvailable={Boolean(policyEnforcedToggleAvailable)}
|
||||
onToggle={onPolicyEnforcedToggle}
|
||||
onToggle={onToggle}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
}
|
||||
<AllowedDomainsToggle
|
||||
allAllowedChecked={allAllowedChecked}
|
||||
allowedDomainsChecked={allowedDomainsChecked}
|
||||
allowedDomains={allowedDomains}
|
||||
syncChecked={syncChecked}
|
||||
onToggle={onToggle}
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AdminPanel>);
|
||||
</AdminPanel>
|
||||
);
|
||||
};
|
||||
@@ -24,29 +24,6 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.channel_banner_header, .channel_translation_header, .channel_join_leave_messages_header, .channel_shared_with_workspaces_header {
|
||||
display: flex;
|
||||
|
||||
.channel_banner_header__text, .channel_translation_header__text, .channel_join_leave_messages_header__text, .channel_shared_with_workspaces_header__text {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
span {
|
||||
&.heading {
|
||||
color: rgb(var(--center-channel-color-rgb));
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&.subheading {
|
||||
color: rgba(var(--center-channel-color-rgb), 0.75);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel_banner_section_body, .channel_shared_with_workspaces_section_body {
|
||||
@include animations.fade-in;
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
// Check that the toggle is not enabled
|
||||
const toggle = screen.getByTestId('channelBannerToggle-button');
|
||||
expect(toggle).toBeInTheDocument();
|
||||
expect(toggle).not.toHaveClass('active');
|
||||
expect(toggle).not.toBeChecked();
|
||||
|
||||
// Banner text and color inputs should not be visible when banner is disabled
|
||||
expect(screen.queryByTestId('channel_banner_banner_text_textbox')).not.toBeInTheDocument();
|
||||
@@ -155,7 +155,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
// Check that the toggle is enabled
|
||||
const toggle = screen.getByTestId('channelBannerToggle-button');
|
||||
expect(toggle).toBeInTheDocument();
|
||||
expect(toggle).not.toHaveClass('active');
|
||||
expect(toggle).not.toBeChecked();
|
||||
|
||||
// Click the toggle to enable the banner
|
||||
await userEvent.click(screen.getByTestId('channelBannerToggle-button'));
|
||||
@@ -175,7 +175,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
// Check that the toggle is enabled
|
||||
const toggle = screen.getByTestId('channelBannerToggle-button');
|
||||
expect(toggle).toBeInTheDocument();
|
||||
expect(toggle).toHaveClass('active');
|
||||
expect(toggle).toBeChecked();
|
||||
|
||||
// Banner text and color inputs should be visible when banner is enabled
|
||||
expect(screen.getByTestId('channel_banner_banner_text_textbox')).toBeInTheDocument();
|
||||
@@ -576,7 +576,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
|
||||
const toggle = screen.getByTestId('shareChannelWithWorkspacesToggle-button');
|
||||
await userEvent.click(toggle);
|
||||
expect(toggle).toHaveClass('active');
|
||||
expect(toggle).toBeChecked();
|
||||
expect(screen.getByText('Add workspace')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByRole('button', {name: 'Save'}));
|
||||
@@ -589,7 +589,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
expect(screen.queryByRole('button', {name: 'Save'})).not.toBeInTheDocument();
|
||||
}, {timeout: 2000});
|
||||
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toBeChecked();
|
||||
expect(screen.queryByText('Add workspace')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -623,7 +623,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
expect(screen.queryByRole('button', {name: 'Save'})).not.toBeInTheDocument();
|
||||
}, {timeout: 2000});
|
||||
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toBeChecked();
|
||||
|
||||
await userEvent.click(screen.getByTestId('shareChannelWithWorkspacesToggle-button'));
|
||||
await userEvent.click(screen.getByRole('button', {name: /Add workspace/i}));
|
||||
@@ -704,11 +704,11 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).toBeChecked();
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByTestId('shareChannelWithWorkspacesToggle-button'));
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toBeChecked();
|
||||
|
||||
remotes = [{...remote}];
|
||||
getRemotesForChannel.mockImplementation(() => remotes);
|
||||
@@ -720,7 +720,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toBeChecked();
|
||||
expect(screen.getByRole('button', {name: 'Save'})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -916,7 +916,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).toBeChecked();
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByTestId('shareChannelWithWorkspacesToggle-button'));
|
||||
@@ -937,7 +937,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
expect(screen.queryByRole('button', {name: 'Save'})).not.toBeInTheDocument();
|
||||
}, {timeout: 2000});
|
||||
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).not.toBeChecked();
|
||||
expect(screen.queryByText('Add workspace')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
@@ -970,7 +970,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).toHaveClass('active');
|
||||
expect(screen.getByTestId('shareChannelWithWorkspacesToggle-button')).toBeChecked();
|
||||
});
|
||||
|
||||
await userEvent.click(screen.getByTestId('shareChannelWithWorkspacesToggle-button'));
|
||||
@@ -1476,7 +1476,7 @@ describe('ChannelSettingsConfigurationTab', () => {
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByRole('button', {name: 'Reset'})).not.toBeInTheDocument();
|
||||
});
|
||||
expect(toggle).toHaveClass('active');
|
||||
expect(toggle).toBeChecked();
|
||||
});
|
||||
|
||||
it('shows an error in the SaveChangesPanel when patchPropertyValues rejects', async () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import type {Channel, ChannelBanner} from '@mattermost/types/channels';
|
||||
import type {ServerError} from '@mattermost/types/errors';
|
||||
|
||||
@@ -36,7 +37,6 @@ import ConfirmModal from 'components/confirm_modal';
|
||||
import DropdownInput from 'components/dropdown_input';
|
||||
import type {ValueType} from 'components/dropdown_input';
|
||||
import SectionNotice from 'components/section_notice';
|
||||
import Toggle from 'components/toggle';
|
||||
import AdvancedTextbox from 'components/widgets/advanced_textbox/advanced_textbox';
|
||||
import type {SaveChangesPanelState} from 'components/widgets/modals/components/save_changes_panel';
|
||||
import SaveChangesPanel from 'components/widgets/modals/components/save_changes_panel';
|
||||
@@ -894,41 +894,24 @@ function ChannelSettingsConfigurationTab({
|
||||
|
||||
{canManageClassification && (
|
||||
<>
|
||||
<div className='channel_banner_header'>
|
||||
<div className='channel_banner_header__text'>
|
||||
<label
|
||||
className='Input_legend'
|
||||
htmlFor='channelClassificationToggle'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channel_settings.classification.title'
|
||||
defaultMessage='Classification'
|
||||
/>
|
||||
</label>
|
||||
<label
|
||||
className='Input_subheading'
|
||||
htmlFor='channelClassificationToggle'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channel_settings.classification.description'
|
||||
defaultMessage='When enabled, a classification level can be set for the channel with configurable indicators.'
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='channel_banner_header__toggle'>
|
||||
<Toggle
|
||||
id='channelClassificationToggle'
|
||||
ariaLabel={formatMessage({id: 'channel_settings.classification.title', defaultMessage: 'Classification'})}
|
||||
size='btn-md'
|
||||
disabled={false}
|
||||
onToggle={handleClassificationToggle}
|
||||
toggled={classificationEnabled}
|
||||
tabIndex={0}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id='channelClassificationToggle'
|
||||
size='medium'
|
||||
checked={classificationEnabled}
|
||||
onChange={handleClassificationToggle}
|
||||
data-testid='channelClassificationToggle-button'
|
||||
tabIndex={0}
|
||||
semiBold={true}
|
||||
secondaryLabel={formatMessage({
|
||||
id: 'channel_settings.classification.description',
|
||||
defaultMessage: 'When enabled, a classification level can be set for the channel with configurable indicators.',
|
||||
})}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='channel_settings.classification.title'
|
||||
defaultMessage='Classification'
|
||||
/>
|
||||
</Switch>
|
||||
|
||||
{classificationEnabled && (
|
||||
<div className='channel_banner_section_body'>
|
||||
@@ -977,35 +960,19 @@ function ChannelSettingsConfigurationTab({
|
||||
|
||||
{canManageBanner && (
|
||||
<>
|
||||
<div className='channel_banner_header'>
|
||||
<div className='channel_banner_header__text'>
|
||||
<label
|
||||
className='Input_legend'
|
||||
aria-label={bannerHeading}
|
||||
>
|
||||
{bannerHeading}
|
||||
</label>
|
||||
<label
|
||||
className='Input_subheading'
|
||||
aria-label={bannerHeading}
|
||||
>
|
||||
{bannerSubHeading}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='channel_banner_header__toggle'>
|
||||
<Toggle
|
||||
id='channelBannerToggle'
|
||||
ariaLabel={bannerHeading}
|
||||
size='btn-md'
|
||||
disabled={bannerLockedByClassification || bannerRequiredByAttribute}
|
||||
onToggle={handleBannerToggle}
|
||||
toggled={bannerSectionOn}
|
||||
tabIndex={0}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id='channelBannerToggle'
|
||||
size='medium'
|
||||
checked={bannerSectionOn}
|
||||
disabled={bannerLockedByClassification || bannerRequiredByAttribute}
|
||||
onChange={handleBannerToggle}
|
||||
data-testid='channelBannerToggle-button'
|
||||
tabIndex={0}
|
||||
semiBold={true}
|
||||
secondaryLabel={bannerSubHeading}
|
||||
>
|
||||
{bannerHeading}
|
||||
</Switch>
|
||||
|
||||
{
|
||||
bannerSectionOn &&
|
||||
@@ -1107,35 +1074,18 @@ function ChannelSettingsConfigurationTab({
|
||||
)}
|
||||
|
||||
{canManageJoinLeaveMessages && (
|
||||
<div className='channel_join_leave_messages_header'>
|
||||
<div className='channel_join_leave_messages_header__text'>
|
||||
<label
|
||||
className='Input_legend'
|
||||
aria-label={joinLeaveMessagesHeading}
|
||||
>
|
||||
{joinLeaveMessagesHeading}
|
||||
</label>
|
||||
<label
|
||||
className='Input_subheading'
|
||||
aria-label={joinLeaveMessagesSubHeading}
|
||||
>
|
||||
{joinLeaveMessagesSubHeading}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='channel_join_leave_messages_header__toggle'>
|
||||
<Toggle
|
||||
id='channelJoinLeaveMessagesToggle'
|
||||
ariaLabel={joinLeaveMessagesHeading}
|
||||
size='btn-md'
|
||||
disabled={false}
|
||||
onToggle={handleJoinLeaveMessagesToggle}
|
||||
toggled={!disableJoinLeaveMessages}
|
||||
tabIndex={0}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id='channelJoinLeaveMessagesToggle'
|
||||
size='medium'
|
||||
checked={!disableJoinLeaveMessages}
|
||||
onChange={handleJoinLeaveMessagesToggle}
|
||||
data-testid='channelJoinLeaveMessagesToggle-button'
|
||||
tabIndex={0}
|
||||
semiBold={true}
|
||||
secondaryLabel={joinLeaveMessagesSubHeading}
|
||||
>
|
||||
{joinLeaveMessagesHeading}
|
||||
</Switch>
|
||||
)}
|
||||
|
||||
{(canManageSharedChannels || canManageClassification || canManageBanner || canManageJoinLeaveMessages) && canManageChannelTranslation && (
|
||||
@@ -1143,35 +1093,18 @@ function ChannelSettingsConfigurationTab({
|
||||
)}
|
||||
|
||||
{canManageChannelTranslation && (
|
||||
<div className='channel_translation_header'>
|
||||
<div className='channel_translation_header__text'>
|
||||
<label
|
||||
className='Input_legend'
|
||||
aria-label={autoTranslationHeading}
|
||||
>
|
||||
{autoTranslationHeading}
|
||||
</label>
|
||||
<label
|
||||
className='Input_subheading'
|
||||
aria-label={autoTranslationSubHeading}
|
||||
>
|
||||
{autoTranslationSubHeading}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className='channel_translation_header__toggle'>
|
||||
<Toggle
|
||||
id='channelTranslationToggle'
|
||||
ariaLabel={autoTranslationHeading}
|
||||
size='btn-md'
|
||||
disabled={false}
|
||||
onToggle={handleAutoTranslationToggle}
|
||||
toggled={isChannelAutotranslated}
|
||||
tabIndex={0}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id='channelTranslationToggle'
|
||||
size='medium'
|
||||
checked={isChannelAutotranslated}
|
||||
onChange={handleAutoTranslationToggle}
|
||||
data-testid='channelTranslationToggle-button'
|
||||
tabIndex={0}
|
||||
semiBold={true}
|
||||
secondaryLabel={autoTranslationSubHeading}
|
||||
>
|
||||
{autoTranslationHeading}
|
||||
</Switch>
|
||||
)}
|
||||
|
||||
{showSaveChangesPanel && (
|
||||
|
||||
@@ -747,7 +747,7 @@ describe('ChannelSettingsInfoTab', () => {
|
||||
stateWithDiscoverableFlag,
|
||||
);
|
||||
expect(screen.getByTestId('channel-settings-discoverable-toggle')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('channel-settings-discoverable-toggle')).toHaveAttribute('aria-pressed', 'false');
|
||||
expect(screen.getByTestId('channel-settings-discoverable-toggle')).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('reflects the channel\'s existing discoverable value on mount', () => {
|
||||
@@ -759,7 +759,7 @@ describe('ChannelSettingsInfoTab', () => {
|
||||
/>,
|
||||
stateWithDiscoverableFlag,
|
||||
);
|
||||
expect(screen.getByTestId('channel-settings-discoverable-toggle')).toHaveAttribute('aria-pressed', 'true');
|
||||
expect(screen.getByTestId('channel-settings-discoverable-toggle')).toBeChecked();
|
||||
});
|
||||
|
||||
it('disables the toggle when the user lacks manage_private_channel_discoverability', () => {
|
||||
|
||||
@@ -5,6 +5,7 @@ import React, {useCallback, useState, useEffect, useMemo, useRef} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import type {Channel, ChannelType} from '@mattermost/types/channels';
|
||||
import type {ServerError} from '@mattermost/types/errors';
|
||||
|
||||
@@ -21,7 +22,6 @@ import ChannelInfoAttributes from 'components/channel_attributes/channel_info_at
|
||||
import type {ChannelInfoAttributesHandle} from 'components/channel_attributes/channel_info_attributes';
|
||||
import ChannelNameFormField from 'components/channel_name_form_field/channel_name_form_field';
|
||||
import type {TextboxElement} from 'components/textbox';
|
||||
import Toggle from 'components/toggle';
|
||||
import AdvancedTextbox from 'components/widgets/advanced_textbox/advanced_textbox';
|
||||
import SaveChangesPanel, {type SaveChangesPanelState} from 'components/widgets/modals/components/save_changes_panel';
|
||||
import PublicPrivateSelector from 'components/widgets/public-private-selector/public-private-selector';
|
||||
@@ -569,35 +569,19 @@ function ChannelSettingsInfoTab({
|
||||
still visible (transparency) but not editable. */}
|
||||
{showDiscoverableToggle && (
|
||||
<div className='ChannelSettingsModal__discoverableSection'>
|
||||
<div className='channel_banner_header'>
|
||||
<div className='channel_banner_header__text'>
|
||||
<label
|
||||
className='Input_legend'
|
||||
aria-label={discoverableTitle}
|
||||
>
|
||||
{discoverableTitle}
|
||||
</label>
|
||||
<label
|
||||
className='Input_subheading'
|
||||
aria-label={discoverableDescription}
|
||||
>
|
||||
{discoverableDescription}
|
||||
</label>
|
||||
</div>
|
||||
<div className='channel_banner_header__toggle'>
|
||||
<Toggle
|
||||
id='channel-settings-discoverable-toggle'
|
||||
overrideTestId={true}
|
||||
ariaLabel={discoverableTitle}
|
||||
size='btn-md'
|
||||
disabled={!canManageDiscoverability}
|
||||
onToggle={handleDiscoverableToggle}
|
||||
toggled={discoverable}
|
||||
tabIndex={0}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id='channel-settings-discoverable-toggle'
|
||||
size='medium'
|
||||
checked={discoverable}
|
||||
disabled={!canManageDiscoverability}
|
||||
onChange={handleDiscoverableToggle}
|
||||
data-testid='channel-settings-discoverable-toggle'
|
||||
tabIndex={0}
|
||||
semiBold={true}
|
||||
secondaryLabel={discoverableDescription}
|
||||
>
|
||||
{discoverableTitle}
|
||||
</Switch>
|
||||
{!canManageDiscoverability && (
|
||||
<p className='ChannelSettingsModal__discoverableReadOnly'>
|
||||
{formatMessage({
|
||||
|
||||
@@ -132,24 +132,12 @@
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
// Discoverable Private Channels — same header/toggle layout as Channel Banner
|
||||
// in the Configuration tab (no extra fields when toggled on).
|
||||
// Discoverable Private Channels — Compass Switch settings-row (label + secondary).
|
||||
&__discoverableSection {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-top: 24px;
|
||||
gap: 8px;
|
||||
|
||||
.channel_banner_header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.channel_banner_header__text {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__discoverableReadOnly {
|
||||
|
||||
@@ -71,7 +71,7 @@ describe('ShareChannelWithWorkspaces', () => {
|
||||
expect(screen.getByText('Share with connected workspaces')).toBeInTheDocument();
|
||||
expect(screen.getByText('Collaborate with trusted organizations in this channel.')).toBeInTheDocument();
|
||||
const toggle = screen.getByTestId('shareChannelWithWorkspacesToggle-button');
|
||||
expect(toggle).not.toHaveClass('active');
|
||||
expect(toggle).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -83,7 +83,7 @@ describe('ShareChannelWithWorkspaces', () => {
|
||||
});
|
||||
|
||||
const toggle = screen.getByTestId('shareChannelWithWorkspacesToggle-button');
|
||||
expect(toggle).toHaveClass('active');
|
||||
expect(toggle).toBeChecked();
|
||||
expect(screen.getByText('Nebula Networks')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
import React, {useCallback, useEffect, useMemo, useState} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import {WithTooltip} from '@mattermost/shared/components/tooltip';
|
||||
import type {RemoteCluster} from '@mattermost/types/remote_clusters';
|
||||
import type {RemoteClusterInfo} from '@mattermost/types/shared_channels';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
|
||||
import Toggle from 'components/toggle';
|
||||
|
||||
import AddWorkspaceDropdown, {type RemoteToAdd} from './add_workspace_dropdown';
|
||||
import type {WorkspaceWithStatus} from './types';
|
||||
import WorkspaceList from './workspace_list';
|
||||
@@ -112,47 +111,33 @@ export default function ShareChannelWithWorkspaces({
|
||||
|
||||
return (
|
||||
<div className='channel_shared_with_workspaces_container'>
|
||||
<div className='channel_shared_with_workspaces_header'>
|
||||
<div className='channel_shared_with_workspaces_header__text'>
|
||||
<label
|
||||
className='Input_legend'
|
||||
aria-label={heading}
|
||||
<WithTooltip
|
||||
title={formatMessage({
|
||||
id: 'channel_settings.share_channel_with_workspaces.disable_toggle_tooltip',
|
||||
defaultMessage: 'No connected workspaces are available',
|
||||
})}
|
||||
hint={formatMessage({
|
||||
id: 'channel_settings.share_channel_with_workspaces.disable_toggle_tooltip_hint',
|
||||
defaultMessage: 'Contact your system admin to add one.',
|
||||
})}
|
||||
disabled={hasAvailableWorkspaces}
|
||||
>
|
||||
<div>
|
||||
<Switch
|
||||
id='shareChannelWithWorkspacesToggle'
|
||||
size='medium'
|
||||
checked={enabled && hasAvailableWorkspaces}
|
||||
disabled={!hasAvailableWorkspaces}
|
||||
onChange={handleToggle}
|
||||
data-testid='shareChannelWithWorkspacesToggle-button'
|
||||
tabIndex={hasAvailableWorkspaces ? 0 : -1}
|
||||
semiBold={true}
|
||||
secondaryLabel={subheading}
|
||||
>
|
||||
{heading}
|
||||
</label>
|
||||
<label
|
||||
className='Input_subheading'
|
||||
aria-label={subheading}
|
||||
>
|
||||
{subheading}
|
||||
</label>
|
||||
</Switch>
|
||||
</div>
|
||||
<WithTooltip
|
||||
title={formatMessage({
|
||||
id: 'channel_settings.share_channel_with_workspaces.disable_toggle_tooltip',
|
||||
defaultMessage: 'No connected workspaces are available',
|
||||
})}
|
||||
hint={formatMessage({
|
||||
id: 'channel_settings.share_channel_with_workspaces.disable_toggle_tooltip_hint',
|
||||
defaultMessage: 'Contact your system admin to add one.',
|
||||
})}
|
||||
disabled={hasAvailableWorkspaces}
|
||||
>
|
||||
<div className='channel_shared_with_workspaces_header__toggle'>
|
||||
|
||||
<Toggle
|
||||
id='shareChannelWithWorkspacesToggle'
|
||||
ariaLabel={heading}
|
||||
size='btn-md'
|
||||
onToggle={handleToggle}
|
||||
toggled={enabled && hasAvailableWorkspaces}
|
||||
disabled={!hasAvailableWorkspaces}
|
||||
tabIndex={hasAvailableWorkspaces ? 0 : -1}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
/>
|
||||
</div>
|
||||
</WithTooltip>
|
||||
</div>
|
||||
</WithTooltip>
|
||||
|
||||
{!hasAvailableWorkspaces && (
|
||||
<div className='channel_shared_with_workspaces_section_body'>
|
||||
|
||||
@@ -247,7 +247,7 @@ describe('RecapConfiguration', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const toggle = screen.getByRole('button', {name: /run once/i});
|
||||
const toggle = screen.getByRole('switch', {name: /run once/i});
|
||||
expect(toggle).toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -259,7 +259,7 @@ describe('RecapConfiguration', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const toggle = screen.getByRole('button', {name: /run once/i});
|
||||
const toggle = screen.getByRole('switch', {name: /run once/i});
|
||||
expect(toggle).not.toBeDisabled();
|
||||
});
|
||||
|
||||
@@ -272,7 +272,7 @@ describe('RecapConfiguration', () => {
|
||||
/>,
|
||||
);
|
||||
|
||||
const toggle = screen.getByRole('button', {name: /run once/i});
|
||||
const toggle = screen.getByRole('switch', {name: /run once/i});
|
||||
expect(toggle).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,10 +5,10 @@ import React, {useCallback, useState} from 'react';
|
||||
import {useIntl, FormattedMessage} from 'react-intl';
|
||||
|
||||
import {ProductChannelsIcon, LightningBoltOutlineIcon, CheckCircleIcon} from '@mattermost/compass-icons/components';
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import {WithTooltip} from '@mattermost/shared/components/tooltip';
|
||||
import type {Channel} from '@mattermost/types/channels';
|
||||
|
||||
import Toggle from 'components/toggle';
|
||||
const RECAP_NAME_MAX_LENGTH = 100;
|
||||
|
||||
type Props = {
|
||||
@@ -53,13 +53,14 @@ const RecapConfiguration = ({
|
||||
|
||||
const runOnceToggle = (
|
||||
<div className='run-once-toggle'>
|
||||
<Toggle
|
||||
<Switch
|
||||
id='run-once-toggle'
|
||||
toggled={runOnce}
|
||||
onToggle={() => setRunOnce(!runOnce)}
|
||||
size='btn-sm'
|
||||
toggleClassName='btn-toggle-primary'
|
||||
size='small'
|
||||
checked={runOnce}
|
||||
onChange={() => setRunOnce(!runOnce)}
|
||||
disabled={runOnceDisabled}
|
||||
data-testid='run-once-toggle-button'
|
||||
tabIndex={0}
|
||||
/>
|
||||
<label
|
||||
htmlFor='run-once-toggle'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
@use 'components/toggle';
|
||||
|
||||
.InviteAs {
|
||||
&__label {
|
||||
color: var(--center-channel-color);
|
||||
|
||||
@@ -63,21 +63,9 @@
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
// Discoverable toggle — same header/toggle layout as Channel Settings Info
|
||||
// and Channel Banner in Channel Settings Configuration.
|
||||
// Discoverable — Compass Switch settings-row (label + secondary).
|
||||
.new-channel-modal-discoverable {
|
||||
margin-top: 24px;
|
||||
|
||||
.channel_banner_header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.channel_banner_header__text {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-channel-modal-classification {
|
||||
|
||||
@@ -6,6 +6,7 @@ import React, {useCallback, useMemo, useRef, useState} from 'react';
|
||||
import {FormattedMessage, useIntl} from 'react-intl';
|
||||
import {shallowEqual, useDispatch, useSelector} from 'react-redux';
|
||||
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import {GenericModal} from '@mattermost/components';
|
||||
import {WithTooltip} from '@mattermost/shared/components/tooltip';
|
||||
import type {Board} from '@mattermost/types/boards';
|
||||
@@ -44,7 +45,6 @@ import useClassificationMarkings from 'components/common/hooks/useClassification
|
||||
import DropdownInput from 'components/dropdown_input';
|
||||
import type {ValueType} from 'components/dropdown_input';
|
||||
import type {TextboxElement} from 'components/textbox';
|
||||
import Toggle from 'components/toggle';
|
||||
import AdvancedTextbox from 'components/widgets/advanced_textbox/advanced_textbox';
|
||||
import Input from 'components/widgets/inputs/input/input';
|
||||
import LoadingSpinner from 'components/widgets/loading/loading_spinner';
|
||||
@@ -602,34 +602,18 @@ const NewChannelModal = () => {
|
||||
className='new-channel-modal-discoverable'
|
||||
data-testid='new-channel-discoverable-section'
|
||||
>
|
||||
<div className='channel_banner_header'>
|
||||
<div className='channel_banner_header__text'>
|
||||
<label
|
||||
className='Input_legend'
|
||||
aria-label={discoverableTitle}
|
||||
>
|
||||
{discoverableTitle}
|
||||
</label>
|
||||
<label
|
||||
className='Input_subheading'
|
||||
aria-label={discoverableDescription}
|
||||
>
|
||||
{discoverableDescription}
|
||||
</label>
|
||||
</div>
|
||||
<div className='channel_banner_header__toggle'>
|
||||
<Toggle
|
||||
id='newChannelDiscoverableToggle'
|
||||
overrideTestId={true}
|
||||
ariaLabel={discoverableTitle}
|
||||
size='btn-md'
|
||||
toggled={discoverable}
|
||||
onToggle={() => setDiscoverable((v) => !v)}
|
||||
tabIndex={0}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Switch
|
||||
id='newChannelDiscoverableToggle'
|
||||
size='medium'
|
||||
checked={discoverable}
|
||||
onChange={() => setDiscoverable((v) => !v)}
|
||||
data-testid='newChannelDiscoverableToggle'
|
||||
tabIndex={0}
|
||||
semiBold={true}
|
||||
secondaryLabel={discoverableDescription}
|
||||
>
|
||||
{discoverableTitle}
|
||||
</Switch>
|
||||
</div>
|
||||
)}
|
||||
{showDefaultCategorySelector && (
|
||||
@@ -701,13 +685,14 @@ const NewChannelModal = () => {
|
||||
/>
|
||||
</h4>
|
||||
</div>
|
||||
<Toggle
|
||||
<Switch
|
||||
id='channelClassificationToggle'
|
||||
toggled={classificationEnabled}
|
||||
onToggle={() => setClassificationEnabled(!classificationEnabled)}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
size='btn-md'
|
||||
ariaLabel={formatMessage({id: 'channel_modal.classification.toggle_label', defaultMessage: 'Channel classification'})}
|
||||
size='medium'
|
||||
checked={classificationEnabled}
|
||||
onChange={() => setClassificationEnabled(!classificationEnabled)}
|
||||
data-testid='channelClassificationToggle-button'
|
||||
tabIndex={0}
|
||||
aria-label={formatMessage({id: 'channel_modal.classification.toggle_label', defaultMessage: 'Channel classification'})}
|
||||
/>
|
||||
</div>
|
||||
<p className='new-channel-modal-classification__description'>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import {WithTestMenuContext} from 'components/menu/menu_context_test';
|
||||
|
||||
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
|
||||
|
||||
import {ToggleItem} from './post_priority_picker_item';
|
||||
|
||||
function renderToggleItem(props: Partial<React.ComponentProps<typeof ToggleItem>> = {}) {
|
||||
const onClick = jest.fn();
|
||||
renderWithContext(
|
||||
<WithTestMenuContext>
|
||||
<ToggleItem
|
||||
ariaLabel='Request acknowledgement'
|
||||
description='An acknowledgement button will appear with your message'
|
||||
disabled={false}
|
||||
icon={<span>{'icon'}</span>}
|
||||
onClick={onClick}
|
||||
text='Request acknowledgement'
|
||||
toggled={false}
|
||||
{...props}
|
||||
/>
|
||||
</WithTestMenuContext>,
|
||||
);
|
||||
return {onClick};
|
||||
}
|
||||
|
||||
describe('ToggleItem', () => {
|
||||
it('calls onClick once when the switch is clicked', async () => {
|
||||
const {onClick} = renderToggleItem();
|
||||
|
||||
await userEvent.click(screen.getByRole('switch', {name: 'Request acknowledgement'}));
|
||||
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('calls onClick once when the menu row is clicked', async () => {
|
||||
const {onClick} = renderToggleItem();
|
||||
|
||||
await userEvent.click(screen.getByRole('menuitemcheckbox', {name: 'Request acknowledgement'}));
|
||||
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -6,9 +6,9 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {CheckIcon, AlertOutlineIcon, AlertCircleOutlineIcon, MessageTextOutlineIcon, CheckCircleOutlineIcon, BellRingOutlineIcon} from '@mattermost/compass-icons/components';
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
|
||||
import {MenuItem} from 'components/menu/menu_item';
|
||||
import Toggle from 'components/toggle';
|
||||
|
||||
type ToggleProps = {
|
||||
ariaLabel?: string;
|
||||
@@ -108,6 +108,12 @@ function ToggleItem({
|
||||
text,
|
||||
toggled,
|
||||
}: ToggleProps) {
|
||||
// Compass Switch fires onChange on its own click; without stopping
|
||||
// propagation the MenuItem onClick also runs and the toggle nets out.
|
||||
const handleSwitchClick = (event: React.MouseEvent) => {
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
return (
|
||||
<Wrapper
|
||||
onClick={disabled ? undefined : onClick}
|
||||
@@ -117,17 +123,18 @@ function ToggleItem({
|
||||
role='menuitemcheckbox'
|
||||
aria-checked={toggled}
|
||||
aria-label={ariaLabel}
|
||||
trailingElements={<>
|
||||
<Toggle
|
||||
ariaLabel={ariaLabel}
|
||||
size='btn-sm'
|
||||
disabled={disabled}
|
||||
onToggle={onClick}
|
||||
toggled={toggled}
|
||||
toggleClassName='btn-toggle-primary'
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</>}
|
||||
trailingElements={
|
||||
<span onClick={disabled ? undefined : handleSwitchClick}>
|
||||
<Switch
|
||||
size='small'
|
||||
disabled={disabled}
|
||||
onChange={onClick}
|
||||
checked={toggled}
|
||||
tabIndex={-1}
|
||||
aria-label={ariaLabel}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
labels={<>
|
||||
<div>
|
||||
{text}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {useIntl, FormattedMessage} from 'react-intl';
|
||||
import {useDispatch} from 'react-redux';
|
||||
|
||||
import {DotsHorizontalIcon, PencilOutlineIcon, TrashCanOutlineIcon} from '@mattermost/compass-icons/components';
|
||||
import {Switch} from '@mattermost/compass-ui/components/switch';
|
||||
import type {ScheduledRecap} from '@mattermost/types/recaps';
|
||||
|
||||
import {pauseScheduledRecap, resumeScheduledRecap, deleteScheduledRecap} from 'mattermost-redux/actions/recaps';
|
||||
@@ -13,7 +14,6 @@ import type {ActionResult} from 'mattermost-redux/types/actions';
|
||||
|
||||
import ConfirmModal from 'components/confirm_modal';
|
||||
import * as Menu from 'components/menu';
|
||||
import Toggle from 'components/toggle';
|
||||
|
||||
import {useScheduleDisplay} from './schedule_display';
|
||||
|
||||
@@ -97,14 +97,15 @@ const ScheduledRecapItem = ({scheduledRecap, onEdit}: Props) => {
|
||||
</div>
|
||||
|
||||
<div className='scheduled-recap-toggle'>
|
||||
<Toggle
|
||||
<Switch
|
||||
id={`toggle-${scheduledRecap.id}`}
|
||||
toggled={scheduledRecap.enabled}
|
||||
onToggle={handleToggle}
|
||||
size='small'
|
||||
checked={scheduledRecap.enabled}
|
||||
onChange={handleToggle}
|
||||
disabled={isToggling}
|
||||
size='btn-sm'
|
||||
toggleClassName='btn-toggle-primary'
|
||||
ariaLabel={scheduledRecap.enabled ?
|
||||
data-testid={`toggle-${scheduledRecap.id}-button`}
|
||||
tabIndex={0}
|
||||
aria-label={scheduledRecap.enabled ?
|
||||
formatMessage({id: 'recaps.scheduled.toggle.active', defaultMessage: 'Active - click to pause'}) :
|
||||
formatMessage({id: 'recaps.scheduled.toggle.paused', defaultMessage: 'Paused - click to resume'})
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
onToggle: () => void;
|
||||
toggled?: boolean;
|
||||
disabled?: boolean;
|
||||
onText?: React.ReactNode;
|
||||
offText?: React.ReactNode;
|
||||
id?: string;
|
||||
overrideTestId?: boolean;
|
||||
size?: 'btn-lg' | 'btn-md' | 'btn-sm';
|
||||
toggleClassName?: string;
|
||||
ariaLabel?: string;
|
||||
tabIndex?: number;
|
||||
};
|
||||
|
||||
const Toggle: React.FC<Props> = (props: Props) => {
|
||||
const {
|
||||
onToggle,
|
||||
toggled,
|
||||
disabled,
|
||||
onText,
|
||||
offText,
|
||||
id,
|
||||
overrideTestId,
|
||||
ariaLabel,
|
||||
size = 'btn-lg',
|
||||
toggleClassName = 'btn-toggle',
|
||||
tabIndex = 0,
|
||||
} = props;
|
||||
let dataTestId = `${id}-button`;
|
||||
if (overrideTestId) {
|
||||
dataTestId = id || '';
|
||||
}
|
||||
|
||||
const className = classNames(
|
||||
'btn',
|
||||
size,
|
||||
toggleClassName,
|
||||
{
|
||||
active: toggled,
|
||||
disabled,
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<button
|
||||
aria-label={ariaLabel}
|
||||
data-testid={dataTestId}
|
||||
id={id}
|
||||
type='button'
|
||||
onClick={onToggle}
|
||||
className={className}
|
||||
aria-pressed={toggled ? 'true' : 'false'}
|
||||
disabled={disabled}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<div className='handle'/>
|
||||
{text(toggled, onText, offText)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
function text(toggled?: boolean, onText?: React.ReactNode, offText?: React.ReactNode): React.ReactNode | null {
|
||||
if ((toggled && !onText) || (!toggled && !offText)) {
|
||||
return null;
|
||||
}
|
||||
return (<div className={`bg-text ${toggled ? 'on' : 'off'}`}>{toggled ? onText : offText}</div>);
|
||||
}
|
||||
|
||||
export default Toggle;
|
||||
@@ -3148,7 +3148,7 @@
|
||||
"description": "Title of the toggle on the Channel Management panel that switches the channel between public and private."
|
||||
},
|
||||
"admin.channel_settings.channel_details.isPublicDescr": {
|
||||
"defaultMessage": "Select Public for a channel any user can find and join. {br}Select Private to require channel invitations to join. {br}Use this switch to change this channel from public to private or from private to public.",
|
||||
"defaultMessage": "Select Public for a channel any user can find and join. {br}Select Private to require channel invitations to join. {br}Use this control to change this channel from public to private or from private to public.",
|
||||
"description": "Description of the public/private toggle on the System Console channel configuration page; {br} placeholders insert line breaks."
|
||||
},
|
||||
"admin.channel_settings.channel_details.policy_enforced_title": {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.line-switch {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.line-switch__label {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -46,8 +46,6 @@
|
||||
@use 'sidebar-card';
|
||||
@use 'sidebar-header-dropdown-button';
|
||||
@use 'single_image_view';
|
||||
@use 'toggle';
|
||||
@use 'team-channel-settings';
|
||||
@use 'infinite-scroll';
|
||||
@use 'line-switch';
|
||||
@use 'apps-modal';
|
||||
@@ -1,159 +0,0 @@
|
||||
@use "utils/variables";
|
||||
|
||||
@use 'sass:color';
|
||||
|
||||
// Based on https://codepen.io/aanjulena/pen/ZLZjzV
|
||||
// Colors
|
||||
$brand-primary: #2a9fd8;
|
||||
variables.$gray: #6b7381;
|
||||
$gray-light: color.adjust(variables.$gray, $lightness: 15%);
|
||||
$gray-lighter: color.adjust(variables.$gray, $lightness: 30%);
|
||||
|
||||
// Button Colors
|
||||
$btn-default-color: variables.$gray;
|
||||
$btn-default-bg: $gray-lighter;
|
||||
|
||||
// Toggle Sizes
|
||||
$toggle-default-size: 1.5rem;
|
||||
$toggle-default-margin: 4rem;
|
||||
$toggle-default-font-size: 0.75rem;
|
||||
|
||||
// Mixin for Switch Colors
|
||||
// Variables: $color, $bg, $active-bg
|
||||
@mixin toggle-color($color: $btn-default-color, $bg: $btn-default-bg, $active-bg: $brand-primary) {
|
||||
background: $bg;
|
||||
color: $color;
|
||||
|
||||
&.active {
|
||||
background-color: $active-bg;
|
||||
}
|
||||
}
|
||||
|
||||
// Mixin for Default Switch Styles
|
||||
// Variables: $size, $margin, $color, $bg, $active-bg, $font-size
|
||||
@mixin toggle-mixin($size: $toggle-default-size, $margin: $toggle-default-margin, $font-size: $toggle-default-font-size) {
|
||||
$handlePosition: calc($size / 7);
|
||||
$handleSize: calc($size - (2 * $handlePosition));
|
||||
|
||||
position: relative;
|
||||
min-width: calc($size * 2);
|
||||
height: $size;
|
||||
// color: $color;
|
||||
// background: $bg;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: $size;
|
||||
float: right;
|
||||
|
||||
&:focus,
|
||||
&.focus {
|
||||
&,
|
||||
&.active {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
> .handle {
|
||||
position: absolute;
|
||||
top: $handlePosition;
|
||||
left: $handlePosition;
|
||||
width: $handleSize;
|
||||
height: $handleSize;
|
||||
border-radius: 1rem;
|
||||
background: #fff;
|
||||
transition: left 0.25s;
|
||||
}
|
||||
|
||||
&.active {
|
||||
transition: background-color 0.25s;
|
||||
|
||||
> .handle {
|
||||
right: $handlePosition;
|
||||
left: inherit;
|
||||
transition: left 0.25s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply Mixin to different sizes & colors
|
||||
.btn-toggle {
|
||||
@include toggle-color;
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.btn-lg {
|
||||
@include toggle-mixin($size: 2.8rem, $font-size: 1rem, $margin: 5rem);
|
||||
}
|
||||
|
||||
&.btn-md {
|
||||
@include toggle-mixin($size: 2.2rem, $font-size: 0.8rem, $margin: 3rem);
|
||||
}
|
||||
|
||||
&.btn-sm {
|
||||
@include toggle-mixin($font-size: 0.55rem, $margin: 0.5rem);
|
||||
}
|
||||
|
||||
&.btn-xs {
|
||||
@include toggle-mixin($size:1rem,$margin:0);
|
||||
}
|
||||
|
||||
.bg-text {
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
|
||||
&.off {
|
||||
padding: 0 1.6rem 0 3.4rem;
|
||||
}
|
||||
|
||||
&.on {
|
||||
padding: 0 3.4rem 0 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-toggle-primary {
|
||||
@include toggle-color(
|
||||
$color: rgb(var(--button-color-rgb)),
|
||||
$bg: rgba(var(--center-channel-color-rgb), 0.32),
|
||||
$active-bg: rgb(var(--button-bg-rgb)),
|
||||
);
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.btn-lg {
|
||||
@include toggle-mixin($size: 2.8rem, $font-size: 1rem, $margin: 5rem);
|
||||
}
|
||||
|
||||
&.btn-md {
|
||||
@include toggle-mixin($size: 2.2rem, $font-size: 0.8rem, $margin: 3rem);
|
||||
}
|
||||
|
||||
&.btn-sm {
|
||||
@include toggle-mixin($font-size: 0.55rem, $margin: 0.5rem);
|
||||
}
|
||||
|
||||
&.btn-xs {
|
||||
@include toggle-mixin($size:1rem,$margin:0);
|
||||
}
|
||||
|
||||
.bg-text {
|
||||
color: #fff;
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
|
||||
&.off {
|
||||
padding: 0 1.6rem 0 3.4rem;
|
||||
}
|
||||
|
||||
&.on {
|
||||
padding: 0 3.4rem 0 1.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,10 +352,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.line-switch__toggle {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.backstage-list__item {
|
||||
.item-actions,
|
||||
.actions {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
/**
|
||||
* Stable Compass Switch CSS-module class names for Jest snapshots.
|
||||
* The published `@mattermost/compass-ui` Switch.module.scss.js embeds
|
||||
* content-hash suffixes that can differ across installs of the same version,
|
||||
* which makes DOM snapshots flake across environments.
|
||||
*/
|
||||
const styles = {
|
||||
switch: 'switch',
|
||||
switch__input: 'switch__input',
|
||||
'switch__secondary-label': 'switch__secondary-label',
|
||||
switch__labels: 'switch__labels',
|
||||
switch__label: 'switch__label',
|
||||
switch__track: 'switch__track',
|
||||
switch__knob: 'switch__knob',
|
||||
'switch--size-small': 'switch--size-small',
|
||||
'switch--size-medium': 'switch--size-medium',
|
||||
'switch--size-large': 'switch--size-large',
|
||||
'switch--semi-bold': 'switch--semi-bold',
|
||||
'switch--with-secondary': 'switch--with-secondary',
|
||||
};
|
||||
|
||||
module.exports = styles;
|
||||
module.exports.default = styles;
|
||||