diff --git a/webapp/channels/package.json b/webapp/channels/package.json index 2f4eb18a22..f63b532c8f 100644 --- a/webapp/channels/package.json +++ b/webapp/channels/package.json @@ -13,9 +13,10 @@ "@mattermost/compass-components": "^0.2.12", "@mattermost/compass-icons": "0.1.34", "@mattermost/types": "*", - "@mui/base": "5.0.0-alpha.116", - "@mui/material": "5.11.7", - "@mui/styled-engine-sc": "5.11.0", + "@mui/base": "5.0.0-alpha.127", + "@mui/material": "5.11.16", + "@mui/system": "5.11.16", + "@mui/styled-engine-sc": "5.11.11", "@stripe/react-stripe-js": "1.13.0", "@stripe/stripe-js": "1.41.0", "@tippyjs/react": "4.2.6", diff --git a/webapp/channels/src/components/admin_console/billing/delete_workspace/delete_workspace_modal.tsx b/webapp/channels/src/components/admin_console/billing/delete_workspace/delete_workspace_modal.tsx index 485dfad01d..1b57ecfc79 100644 --- a/webapp/channels/src/components/admin_console/billing/delete_workspace/delete_workspace_modal.tsx +++ b/webapp/channels/src/components/admin_console/billing/delete_workspace/delete_workspace_modal.tsx @@ -7,7 +7,7 @@ import {FormattedMessage} from 'react-intl'; import {useDispatch, useSelector} from 'react-redux'; -import {GenericModal} from '@mattermost/components'; +import {LegacyGenericModal} from '@mattermost/components'; import LaptopAlertSVG from 'components/common/svg_images_components/laptop_alert_svg'; import {closeModal, openModal} from 'actions/views/modals'; @@ -180,7 +180,7 @@ export default function DeleteWorkspaceModal(props: Props) { } return ( - @@ -249,6 +249,6 @@ export default function DeleteWorkspaceModal(props: Props) { /> - + ); } diff --git a/webapp/channels/src/components/air_gapped_self_hosted_purchase_modal/index.tsx b/webapp/channels/src/components/air_gapped_self_hosted_purchase_modal/index.tsx index 530902c3bb..97b6d9222c 100644 --- a/webapp/channels/src/components/air_gapped_self_hosted_purchase_modal/index.tsx +++ b/webapp/channels/src/components/air_gapped_self_hosted_purchase_modal/index.tsx @@ -4,7 +4,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; -import {GenericModal} from '@mattermost/components'; +import {LegacyGenericModal} from '@mattermost/components'; import {CloudLinks} from 'utils/constants'; import CreditCardSvg from 'components/common/svg_images_components/credit_card_svg'; import {useControlAirGappedSelfHostedPurchaseModal} from 'components/common/hooks/useControlModal'; @@ -15,7 +15,7 @@ export default function AirGappedSelfHostedPurhcaseModal() { const {close} = useControlAirGappedSelfHostedPurchaseModal(); return ( - {CloudLinks.SELF_HOSTED_PRICING} - + ); } diff --git a/webapp/channels/src/components/drafts/draft_actions/__snapshots__/delete_draft_modal.test.tsx.snap b/webapp/channels/src/components/drafts/draft_actions/__snapshots__/delete_draft_modal.test.tsx.snap index bcffa0da8f..26ffba2022 100644 --- a/webapp/channels/src/components/drafts/draft_actions/__snapshots__/delete_draft_modal.test.tsx.snap +++ b/webapp/channels/src/components/drafts/draft_actions/__snapshots__/delete_draft_modal.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`components/drafts/draft_actions/delete_draft_modal should have called onConfirm 1`] = ` - - + `; exports[`components/drafts/draft_actions/delete_draft_modal should have called onExited 1`] = ` - - + `; exports[`components/drafts/draft_actions/delete_draft_modal should match snapshot 1`] = ` diff --git a/webapp/channels/src/components/drafts/draft_actions/__snapshots__/send_draft_modal.test.tsx.snap b/webapp/channels/src/components/drafts/draft_actions/__snapshots__/send_draft_modal.test.tsx.snap index 548c04c4c5..cf89abf778 100644 --- a/webapp/channels/src/components/drafts/draft_actions/__snapshots__/send_draft_modal.test.tsx.snap +++ b/webapp/channels/src/components/drafts/draft_actions/__snapshots__/send_draft_modal.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`components/drafts/draft_actions/send_draft_modal should have called onConfirm 1`] = ` - - + `; exports[`components/drafts/draft_actions/send_draft_modal should have called onExited 1`] = ` - - + `; exports[`components/drafts/draft_actions/send_draft_modal should match snapshot 1`] = ` diff --git a/webapp/channels/src/components/feedback_modal/feedback.tsx b/webapp/channels/src/components/feedback_modal/feedback.tsx index 3f612017be..8b6c64aebe 100644 --- a/webapp/channels/src/components/feedback_modal/feedback.tsx +++ b/webapp/channels/src/components/feedback_modal/feedback.tsx @@ -6,7 +6,7 @@ import React, {useState} from 'react'; import {injectIntl, WrappedComponentProps} from 'react-intl'; import {useDispatch} from 'react-redux'; -import {GenericModal} from '@mattermost/components'; +import {LegacyGenericModal} from '@mattermost/components'; import {Feedback} from '@mattermost/types/cloud'; import {closeModal} from 'actions/views/modals'; import RadioButtonGroup from 'components/common/radio_group'; @@ -59,7 +59,7 @@ function FeedbackModal(props: Props) { }; return ( - > } - + ); } diff --git a/webapp/channels/src/components/generic_modal.tsx b/webapp/channels/src/components/generic_modal.tsx index 2e43ab311a..5ea190f2f5 100644 --- a/webapp/channels/src/components/generic_modal.tsx +++ b/webapp/channels/src/components/generic_modal.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {GenericModal} from '@mattermost/components'; +import {LegacyGenericModal} from '@mattermost/components'; import {t} from 'utils/i18n'; @@ -15,4 +15,4 @@ t('footer_pagination.count'); t('footer_pagination.prev'); t('footer_pagination.next'); -export default GenericModal; +export default LegacyGenericModal; diff --git a/webapp/channels/src/components/new_channel_modal/__snapshots__/new_channel_modal.test.tsx.snap b/webapp/channels/src/components/new_channel_modal/__snapshots__/new_channel_modal.test.tsx.snap deleted file mode 100644 index a681256d49..0000000000 --- a/webapp/channels/src/components/new_channel_modal/__snapshots__/new_channel_modal.test.tsx.snap +++ /dev/null @@ -1,96 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`components/new_channel_modal should match snapshot 1`] = ` - - - - - - - - - - This will be displayed when browsing for channels. - - - - - -`; diff --git a/webapp/channels/src/components/new_channel_modal/new_channel_modal.test.tsx b/webapp/channels/src/components/new_channel_modal/new_channel_modal.test.tsx index 6afbef1e5a..1d52d83ad6 100644 --- a/webapp/channels/src/components/new_channel_modal/new_channel_modal.test.tsx +++ b/webapp/channels/src/components/new_channel_modal/new_channel_modal.test.tsx @@ -3,26 +3,19 @@ import React from 'react'; import {act} from 'react-dom/test-utils'; -import {shallow} from 'enzyme'; -import {mountWithIntl} from 'tests/helpers/intl-test-helper'; - -import GenericModal from 'components/generic_modal'; -import Input from 'components/widgets/inputs/input/input'; -import URLInput from 'components/widgets/inputs/url_input/url_input'; -import PublicPrivateSelector from 'components/widgets/public-private-selector/public-private-selector'; - -import Constants, {suitePluginIds} from 'utils/constants'; +import {suitePluginIds} from 'utils/constants'; import {cleanUpUrlable} from 'utils/url'; import {GlobalState} from 'types/store'; import Permissions from 'mattermost-redux/constants/permissions'; import {createChannel} from 'mattermost-redux/actions/channels'; -import {ChannelType} from '@mattermost/types/channels'; - jest.mock('mattermost-redux/actions/channels'); import NewChannelModal from './new_channel_modal'; +import {render, screen, waitFor} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import {renderWithIntl} from 'tests/react_testing_utils'; const mockDispatch = jest.fn(); let mockState: GlobalState; @@ -113,313 +106,324 @@ describe('components/new_channel_modal', () => { } as unknown as GlobalState; }); - test('should match snapshot', () => { - expect( - shallow( - , - ), - ).toMatchSnapshot(); + test('should match component state with given props', () => { + render(); + + const heading = screen.getByRole('heading'); + expect(heading).toBeInTheDocument(); + expect(heading).toHaveAttribute('id', 'genericModalLabel'); + expect(heading.parentElement).toHaveClass('GenericModal__header'); + expect(heading).toHaveTextContent('Create a new channel'); + + const channelNameHeading = screen.getByText('Channel name'); + expect(channelNameHeading).toBeInTheDocument(); + expect(channelNameHeading).toHaveClass('Input_legend Input_legend___focus'); + + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); + expect(channelNameInput).toHaveAttribute('type', 'text'); + expect(channelNameInput).toHaveAttribute('name', 'new-channel-modal-name'); + expect(channelNameInput).toHaveAttribute('id', 'input_new-channel-modal-name'); + expect(channelNameInput).toHaveClass('Input form-control medium new-channel-modal-name-input Input__focus'); + + const editUrl = screen.getByText('Edit'); + expect(editUrl).toBeInTheDocument(); + expect(editUrl).toHaveClass('url-input-button-label'); + expect(editUrl.parentElement).toHaveClass('url-input-button'); + + const publicChannelSvg = screen.getByLabelText('Globe Circle Solid Icon'); + expect(publicChannelSvg).toBeInTheDocument(); + + const publicChannelHeading = screen.getByText('Public Channel'); + expect(publicChannelHeading).toBeInTheDocument(); + expect(publicChannelHeading.nextSibling).toHaveTextContent('Anyone can join'); + + const privateChannelSvg = screen.getByLabelText('Lock Circle Solid Icon'); + expect(privateChannelSvg).toBeInTheDocument(); + + const privateChannelHeading = screen.getByText('Private Channel'); + expect(privateChannelHeading).toBeInTheDocument(); + expect(privateChannelHeading.nextSibling).toHaveTextContent('Only invited members'); + + const purposeTextArea = screen.getByPlaceholderText('Enter a purpose for this channel (optional)'); + expect(purposeTextArea).toBeInTheDocument(); + expect(purposeTextArea).toHaveClass('new-channel-modal-purpose-textarea'); + + const purposeDesc = screen.getByText('This will be displayed when browsing for channels.'); + expect(purposeDesc).toBeInTheDocument(); + + const cancelButton = screen.getByText('Cancel'); + expect(cancelButton).toBeInTheDocument(); + expect(cancelButton).toHaveClass('GenericModal__button cancel'); + + const createChannelButton = screen.getByText('Create channel'); + expect(createChannelButton).toBeInTheDocument(); + expect(createChannelButton).toHaveClass('GenericModal__button confirm'); + expect(createChannelButton).toBeDisabled(); }); test('should handle display name change', () => { const value = 'Channel name'; - const mockChangeEvent = { - preventDefault: jest.fn(), - target: { - value, - }, - } as unknown as React.ChangeEvent; - const wrapper = shallow( + render( , ); // Change display name - let input = wrapper.find(Input).first(); - input.props().onChange!(mockChangeEvent); + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); + + userEvent.type(channelNameInput, value); // Display name should have been updated - input = wrapper.find(Input).first(); - expect(input.props().value).toEqual(value); + expect(channelNameInput).toHaveAttribute('value', value); // URL should have been changed according to display name - const urlInput = wrapper.find(URLInput); - expect(urlInput.props().pathInfo).toEqual(cleanUpUrlable(value)); + const urlInputLabel = screen.getByTestId('urlInputLabel'); + expect(urlInputLabel).toHaveTextContent(cleanUpUrlable(value)); }); - test('should handle url change', () => { + test('should handle url change', async () => { const value = 'Channel name'; - const mockInputChangeEvent = { - preventDefault: jest.fn(), - target: { - value, - }, - } as unknown as React.ChangeEvent; - const mockInputChangeUpdatedEvent = { - preventDefault: jest.fn(), - target: { - value: `${value} updated`, - }, - } as unknown as React.ChangeEvent; const url = 'channel-name-new'; - const mockURLInputChangeEvent = { - preventDefault: jest.fn(), - target: { - value: url, - }, - } as unknown as React.ChangeEvent; - const wrapper = shallow( + render( , ); // Change display name - let input = wrapper.find(Input).first(); - input.props().onChange!(mockInputChangeEvent); + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); - // URL should have been changed according to display name - let urlInput = wrapper.find(URLInput); - expect(urlInput.props().pathInfo).toEqual(cleanUpUrlable(value)); + userEvent.type(channelNameInput, value); + const urlInputLabel = screen.getByTestId('urlInputLabel'); + expect(urlInputLabel).toHaveTextContent(cleanUpUrlable(value)); // Change URL - urlInput.props().onChange!(mockURLInputChangeEvent); + const editUrl = screen.getByText('Edit'); + expect(editUrl).toBeInTheDocument(); + + userEvent.click(editUrl); + + const editUrlInput = screen.getByTestId('channelURLInput'); + userEvent.clear(editUrlInput); + userEvent.type(editUrlInput, url); + + const doneButton = screen.getByText('Done'); + await waitFor(() => + userEvent.click(doneButton), + ); // URL should have been updated - urlInput = wrapper.find(URLInput); - expect(urlInput.props().pathInfo).toEqual(url); + expect(screen.getByText(url, {exact: false})).toBeInTheDocument(); // Change display name again - input = wrapper.find(Input).first(); - input.props().onChange!(mockInputChangeUpdatedEvent); + userEvent.type(channelNameInput, `${value} updated`); // URL should NOT be updated - urlInput = wrapper.find(URLInput); - expect(urlInput.props().pathInfo).toEqual(url); + expect(screen.getByText(url, {exact: false})).toBeInTheDocument(); }); test('should handle type changes', () => { - const typePublic = Constants.OPEN_CHANNEL as ChannelType; - const typePrivate = Constants.PRIVATE_CHANNEL as ChannelType; - - const wrapper = shallow( + render( , ); // Change type to private - let selector = wrapper.find(PublicPrivateSelector); - selector.props().onChange!(typePrivate); + const privateChannel = screen.getByText('Private Channel'); + expect(privateChannel).toBeInTheDocument(); + + userEvent.click(privateChannel); // Type should have been updated to private - selector = wrapper.find(PublicPrivateSelector); - expect(selector.props().selected).toEqual(typePrivate); + expect(privateChannel.parentElement?.nextSibling?.firstChild).toHaveAttribute('aria-label', 'Check Circle Icon'); - // Change type to private - selector = wrapper.find(PublicPrivateSelector); - selector.props().onChange!(typePublic); + // Change type to public + const publicChannel = screen.getByText('Public Channel'); + expect(publicChannel).toBeInTheDocument(); + + userEvent.click(publicChannel); // Type should have been updated to public - selector = wrapper.find(PublicPrivateSelector); - expect(selector.props().selected).toEqual(typePublic); + expect(publicChannel.parentElement?.nextSibling?.firstChild).toHaveAttribute('aria-label', 'Check Circle Icon'); }); test('should handle purpose changes', () => { const value = 'Purpose'; - const mockChangeEvent = { - preventDefault: jest.fn(), - target: { - value, - }, - } as unknown as React.ChangeEvent; - const wrapper = shallow( + render( , ); // Change purpose - let textarea = wrapper.find('textarea'); - textarea.props().onChange!(mockChangeEvent); + const ChannelPurposeTextArea = screen.getByPlaceholderText('Enter a purpose for this channel (optional)'); + expect(ChannelPurposeTextArea).toBeInTheDocument(); + + userEvent.click(ChannelPurposeTextArea); + userEvent.type(ChannelPurposeTextArea, value); // Purpose should have been updated - textarea = wrapper.find('textarea'); - expect(textarea.props().value).toEqual(value); + expect(ChannelPurposeTextArea).toHaveValue(value); }); test('should enable confirm button when having valid display name, url and type', () => { - const mockChangeEvent = { - preventDefault: jest.fn(), - target: { - value: 'Channel name', - }, - } as unknown as React.ChangeEvent; - - const wrapper = shallow( + render( , ); // Confirm button should be disabled - let genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(true); + const createChannelButton = screen.getByText('Create channel'); + expect(createChannelButton).toBeInTheDocument(); + expect(createChannelButton).toBeDisabled(); // Change display name - const input = wrapper.find(Input).first(); - input.props().onChange!(mockChangeEvent); + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); + + userEvent.type(channelNameInput, 'Channel name'); // Change type to private - const selector = wrapper.find(PublicPrivateSelector); - selector.props().onChange!(Constants.PRIVATE_CHANNEL as ChannelType); + const privateChannel = screen.getByText('Private Channel'); + expect(privateChannel).toBeInTheDocument(); + + userEvent.click(privateChannel); // Confirm button should be enabled - genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(false); + expect(createChannelButton).toBeEnabled(); }); test('should disable confirm button when display name in error', () => { - const mockChangeEvent = { - preventDefault: jest.fn(), - target: { - value: 'Channel name', - }, - } as unknown as React.ChangeEvent; - const mockChangeInvalidEvent = { - preventDefault: jest.fn(), - target: { - value: '', - }, - } as unknown as React.ChangeEvent; - - const wrapper = shallow( + render( , ); // Change display name - let input = wrapper.find(Input).first(); - input.props().onChange!(mockChangeEvent); + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); + + userEvent.type(channelNameInput, 'Channel name'); // Change type to private - const selector = wrapper.find(PublicPrivateSelector); - selector.props().onChange!(Constants.PRIVATE_CHANNEL as ChannelType); + const privateChannel = screen.getByText('Private Channel'); + expect(privateChannel).toBeInTheDocument(); + + userEvent.click(privateChannel); // Confirm button should be enabled - let genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(false); + const createChannelButton = screen.getByText('Create channel'); + expect(createChannelButton).toBeEnabled(); // Change display name to invalid - input = wrapper.find(Input).first(); - input.props().onChange!(mockChangeInvalidEvent); + userEvent.clear(channelNameInput); + userEvent.type(channelNameInput, ''); // Confirm button should be disabled - genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(true); + expect(createChannelButton).toBeDisabled(); }); test('should disable confirm button when url in error', () => { - const mockChangeEvent = { - preventDefault: jest.fn(), - target: { - value: 'Channel name', - }, - } as unknown as React.ChangeEvent; - const mockChangeURLInvalidEvent = { - preventDefault: jest.fn(), - target: { - value: 'c-', - }, - } as unknown as React.ChangeEvent; - - const wrapper = shallow( + render( , ); // Change display name - const input = wrapper.find(Input).first(); - input.props().onChange!(mockChangeEvent); + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); + + userEvent.type(channelNameInput, 'Channel name'); // Change type to private - const selector = wrapper.find(PublicPrivateSelector); - selector.props().onChange!(Constants.PRIVATE_CHANNEL as ChannelType); + const privateChannel = screen.getByText('Private Channel'); + expect(privateChannel).toBeInTheDocument(); + + userEvent.click(privateChannel); // Confirm button should be enabled - let genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(false); + const createChannelButton = screen.getByText('Create channel'); + expect(createChannelButton).toBeEnabled(); // Change url to invalid - const urlInput = wrapper.find(URLInput); - urlInput.props().onChange!(mockChangeURLInvalidEvent); + const editUrl = screen.getByText('Edit'); + expect(editUrl).toBeInTheDocument(); + + userEvent.click(editUrl); + + const editUrlInput = screen.getByTestId('channelURLInput'); + userEvent.clear(editUrlInput); + userEvent.type(editUrlInput, 'c-'); // Confirm button should be disabled - genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(true); + expect(createChannelButton).toBeDisabled(); }); test('should disable confirm button when server error', async () => { - const mockChangeEvent = { - preventDefault: jest.fn(), - target: { - value: 'Channel name', - }, - } as unknown as React.ChangeEvent; - - const wrapper = shallow( + render( , ); // Confirm button should be disabled - let genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(true); + const createChannelButton = screen.getByText('Create channel'); + expect(createChannelButton).toBeDisabled(); // Change display name - const input = wrapper.find(Input).first(); - input.props().onChange!(mockChangeEvent); + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); + + userEvent.type(channelNameInput, 'Channel name'); // Change type to private - const selector = wrapper.find(PublicPrivateSelector); - selector.props().onChange!(Constants.PRIVATE_CHANNEL as ChannelType); + const privateChannel = screen.getByText('Private Channel'); + expect(privateChannel).toBeInTheDocument(); + + userEvent.click(privateChannel); // Confirm button should be enabled - genericModal = wrapper.find(GenericModal); - expect(genericModal.props().isConfirmDisabled).toEqual(false); + expect(createChannelButton).toBeEnabled(); // Submit - await genericModal.props().handleConfirm!(); + await act(async () => userEvent.click(createChannelButton)); - genericModal = wrapper.find(GenericModal); - expect(genericModal.props().errorText).toEqual('Something went wrong. Please try again.'); - expect(genericModal.props().isConfirmDisabled).toEqual(true); + const serverError = screen.getByText('Something went wrong. Please try again.'); + expect(serverError).toBeInTheDocument(); + expect(createChannelButton).toBeDisabled(); }); test('should request team creation on submit', async () => { const name = 'Channel name'; - const mockChangeEvent = { - preventDefault: jest.fn(), - target: { - value: name, - }, - } as unknown as React.ChangeEvent; - const wrapper = mountWithIntl( + renderWithIntl( , ); - const genericModal = wrapper.find('GenericModal'); - const displayName = genericModal.find('.new-channel-modal-name-input'); - const confirmButton = genericModal.find('button[type=\'submit\']'); - // Confirm button should be disabled - expect((confirmButton.instance() as unknown as HTMLButtonElement).disabled).toEqual(true); + const createChannelButton = screen.getByText('Create channel'); + expect(createChannelButton).toBeDisabled(); // Enter data - displayName.simulate('change', mockChangeEvent); + + const channelNameInput = screen.getByPlaceholderText('Enter a name for your new channel'); + expect(channelNameInput).toBeInTheDocument(); + expect(channelNameInput).toHaveAttribute('value', ''); + + userEvent.type(channelNameInput, name); // Display name should be updated - expect((displayName.instance() as unknown as HTMLInputElement).value).toEqual(name); + expect(channelNameInput).toHaveValue(name); // Confirm button should be enabled - expect((confirmButton.instance() as unknown as HTMLButtonElement).disabled).toEqual(false); + expect(createChannelButton).toBeEnabled(); // Submit await act(async () => { - confirmButton.simulate('click'); + userEvent.click(createChannelButton); }); // Request should be sent diff --git a/webapp/channels/src/components/product_notices_modal/__snapshots__/product_notices.test.tsx.snap b/webapp/channels/src/components/product_notices_modal/__snapshots__/product_notices.test.tsx.snap index 5252df431b..9efd1f6b6e 100644 --- a/webapp/channels/src/components/product_notices_modal/__snapshots__/product_notices.test.tsx.snap +++ b/webapp/channels/src/components/product_notices_modal/__snapshots__/product_notices.test.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ProductNoticesModal Match snapshot for single notice 1`] = ` - - + `; exports[`ProductNoticesModal Match snapshot for user notice 1`] = ` - - + `; exports[`ProductNoticesModal Should match snapshot for system admin notice 1`] = ` - - + `; exports[`ProductNoticesModal Should match snapshot when there are no notices 1`] = `""`; diff --git a/webapp/channels/src/components/purchase_in_progress_modal/index.tsx b/webapp/channels/src/components/purchase_in_progress_modal/index.tsx index 2e0483a401..ac1aaa9eb2 100644 --- a/webapp/channels/src/components/purchase_in_progress_modal/index.tsx +++ b/webapp/channels/src/components/purchase_in_progress_modal/index.tsx @@ -5,7 +5,7 @@ import React from 'react'; import {useSelector} from 'react-redux'; import {FormattedMessage} from 'react-intl'; -import {GenericModal, GenericModalProps} from '@mattermost/components'; +import {LegacyGenericModal, LegacyGenericModalProps} from '@mattermost/components'; import {getCurrentUser} from 'mattermost-redux/selectors/entities/common'; import {getUserByEmail} from 'mattermost-redux/selectors/entities/users'; @@ -48,7 +48,7 @@ export default function PurchaseInProgressModal(props: Props) { /> ); let actionToTake; - const genericModalProps: Partial = {}; + const genericModalProps: Partial = {}; if (sameUserAlreadyPurchasing) { description = ( } - + ); } diff --git a/webapp/channels/src/components/screening_in_progress_modal/index.tsx b/webapp/channels/src/components/screening_in_progress_modal/index.tsx index bc3dd30147..4a07d69475 100644 --- a/webapp/channels/src/components/screening_in_progress_modal/index.tsx +++ b/webapp/channels/src/components/screening_in_progress_modal/index.tsx @@ -4,7 +4,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; -import {GenericModal} from '@mattermost/components'; +import {LegacyGenericModal} from '@mattermost/components'; import AccessDeniedHappySvg from 'components/common/svg_images_components/access_denied_happy_svg'; import {useControlScreeningInProgressModal} from 'components/common/hooks/useControlModal'; @@ -14,7 +14,7 @@ export default function ScreeningInProgressModal() { const {close} = useControlScreeningInProgressModal(); return ( - - + ); } diff --git a/webapp/channels/src/components/threading/global_threads/mark_all_threads_as_read_modal/mark_all_threads_as_read_modal.tsx b/webapp/channels/src/components/threading/global_threads/mark_all_threads_as_read_modal/mark_all_threads_as_read_modal.tsx index 07d9d063c3..c261add247 100644 --- a/webapp/channels/src/components/threading/global_threads/mark_all_threads_as_read_modal/mark_all_threads_as_read_modal.tsx +++ b/webapp/channels/src/components/threading/global_threads/mark_all_threads_as_read_modal/mark_all_threads_as_read_modal.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import {useIntl} from 'react-intl'; -import {GenericModal} from '@mattermost/components'; +import {LegacyGenericModal} from '@mattermost/components'; import './mark_all_threads_as_read_modal.scss'; @@ -20,7 +20,7 @@ function MarkAllThreadsAsReadModal({ const {formatMessage} = useIntl(); return ( - - + ); } diff --git a/webapp/channels/src/components/widgets/inputs/url_input/url_input.tsx b/webapp/channels/src/components/widgets/inputs/url_input/url_input.tsx index e11a81cc46..c93a95cd6e 100644 --- a/webapp/channels/src/components/widgets/inputs/url_input/url_input.tsx +++ b/webapp/channels/src/components/widgets/inputs/url_input/url_input.tsx @@ -79,7 +79,10 @@ function UrlInput({ }; const urlInputLabel = ( - + {formatMessage({id: 'url_input.label.url', defaultMessage: 'URL: '})} {isShortenedURL ? getShortenedURL(fullURL, shortenLength) : fullURL} @@ -105,6 +108,7 @@ function UrlInput({ )} {(editing || hasError) && ( =16.0.0" } }, + "channels/node_modules/@mui/base": { + "version": "5.0.0-alpha.127", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.127.tgz", + "integrity": "sha512-FoRQd0IOH9MnfyL5yXssyQRnC4vXI+1bwkU1idr+wNkP1ZfxE+JsThHcfl1dy5azLssVUGTtQFD9edQLdbyJog==", + "dependencies": { + "@babel/runtime": "^7.21.0", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.12.0", + "@popperjs/core": "^2.11.7", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "channels/node_modules/@mui/base/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "channels/node_modules/@testing-library/user-event": { "version": "12.1.4", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.1.4.tgz", @@ -6510,9 +6556,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -9809,15 +9855,15 @@ "dev": true }, "node_modules/@mui/base": { - "version": "5.0.0-alpha.116", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.116.tgz", - "integrity": "sha512-VwhifWdrfHc4/ZdqRZ4Gf+7P39sovNN24By1YVZdvJ9fvp0Sr8sNftGUCjYXXz+xCXVBQDXvhfxMwZrj2MvJvA==", + "version": "5.0.0-alpha.124", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.124.tgz", + "integrity": "sha512-I6M+FrjRCybQCr8I8JTu6L2MkUobSQFgNIpOJyDNKL5zq/73LvZIQXvsKumAzthVGvI1PYaarM9vGDrDYbumKA==", "dependencies": { - "@babel/runtime": "^7.20.7", + "@babel/runtime": "^7.21.0", "@emotion/is-prop-valid": "^1.2.0", "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "@popperjs/core": "^2.11.6", + "@mui/utils": "^5.11.13", + "@popperjs/core": "^2.11.7", "clsx": "^1.2.1", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -9854,28 +9900,28 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", - "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.3.tgz", + "integrity": "sha512-yiJZ+knaknPHuRKhRk4L6XiwppwkAahVal3LuYpvBH7GkA2g+D9WLEXOEnNYtVFUggyKf6fWGLGnx0iqzkU5YA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" } }, "node_modules/@mui/material": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", - "integrity": "sha512-wDv7Pc6kMe9jeWkmCLt4JChd1lPc2u23JQHpB35L2VwQowpNFoDfIwqi0sYCnZTMKlRc7lza8LqwSwHl2G52Rw==", + "version": "5.11.16", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.16.tgz", + "integrity": "sha512-++glQqbZ3rMzOWB77yOvqRG+k8+scYTUKVWZpWff+GWsf6L10g9L2wgRhhAS8bDLuxCbXZlPNbSZowXDDw6z6Q==", "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/base": "5.0.0-alpha.116", - "@mui/core-downloads-tracker": "^5.11.7", - "@mui/system": "^5.11.7", + "@babel/runtime": "^7.21.0", + "@mui/base": "5.0.0-alpha.124", + "@mui/core-downloads-tracker": "^5.11.16", + "@mui/system": "^5.11.16", "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", + "@mui/utils": "^5.11.13", "@types/react-transition-group": "^4.4.5", "clsx": "^1.2.1", - "csstype": "^3.1.1", + "csstype": "^3.1.2", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -9912,12 +9958,12 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/@mui/private-theming": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", - "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.12.3.tgz", + "integrity": "sha512-o1e7Z1Bp27n4x2iUHhegV4/Jp6H3T6iBKHJdLivS5GbwsuAE/5l4SnZ+7+K+e5u9TuhwcAKZLkjvqzkDe8zqfA==", "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/utils": "^5.11.7", + "@babel/runtime": "^7.21.0", + "@mui/utils": "^5.12.3", "prop-types": "^15.8.1" }, "engines": { @@ -9938,13 +9984,13 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", - "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.12.3.tgz", + "integrity": "sha512-AhZtiRyT8Bjr7fufxE/mLS+QJ3LxwX1kghIcM2B2dvJzSSg9rnIuXDXM959QfUVIM3C8U4x3mgVoPFMQJvc4/g==", "dependencies": { - "@babel/runtime": "^7.20.6", - "@emotion/cache": "^11.10.5", - "csstype": "^3.1.1", + "@babel/runtime": "^7.21.0", + "@emotion/cache": "^11.10.8", + "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "engines": { @@ -9969,11 +10015,11 @@ } }, "node_modules/@mui/styled-engine-sc": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.11.0.tgz", - "integrity": "sha512-U8cA7DHB3fnLpDVM5qd/9FGnw5y+LrmwIXQ9Lzk3HYkF903tSMx9jXIbiz1vltpOfZTKX/Rn02m/nkH4OF6Pcg==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.11.11.tgz", + "integrity": "sha512-6+HsfcKHlhjQklDoEup7Itl+Xgn+BCsqEpIdIIhlxED4YlOZ38xghxIKrx78XFZznTorbhAspUgDDKIaB5vDMg==", "dependencies": { - "@babel/runtime": "^7.20.6", + "@babel/runtime": "^7.21.0", "prop-types": "^15.8.1" }, "engines": { @@ -9994,15 +10040,15 @@ } }, "node_modules/@mui/styled-engine/node_modules/@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "version": "11.10.8", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.8.tgz", + "integrity": "sha512-5fyqGHi51LU95o7qQ/vD1jyvC4uCY5GcBT+UgP4LHdpO9jPDlXqhrRr9/wCKmfoAvh5G/F7aOh4MwQa+8uEqhA==", "dependencies": { "@emotion/memoize": "^0.8.0", "@emotion/sheet": "^1.2.1", "@emotion/utils": "^1.2.0", "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "stylis": "4.1.4" } }, "node_modules/@mui/styled-engine/node_modules/@emotion/sheet": { @@ -10015,18 +10061,23 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" }, + "node_modules/@mui/styled-engine/node_modules/stylis": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.4.tgz", + "integrity": "sha512-USf5pszRYwuE6hg9by0OkKChkQYEXfkeTtm0xKw+jqQhwyjCVLdYyMBK7R+n7dhzsblAWJnGxju4vxq5eH20GQ==" + }, "node_modules/@mui/system": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", - "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "version": "5.11.16", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.16.tgz", + "integrity": "sha512-JY7CNm7ik2Gr4kQpz1+C9N/f4ET3QjVBo/iaHcmlSOgjdxnOzFbv+vCdb1DMzBGew+UbqckppZpZwbgbrBE2Rw==", "dependencies": { - "@babel/runtime": "^7.20.7", - "@mui/private-theming": "^5.11.7", - "@mui/styled-engine": "^5.11.0", + "@babel/runtime": "^7.21.0", + "@mui/private-theming": "^5.11.13", + "@mui/styled-engine": "^5.11.16", "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", + "@mui/utils": "^5.11.13", "clsx": "^1.2.1", - "csstype": "^3.1.1", + "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "engines": { @@ -10055,9 +10106,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", + "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==", "peerDependencies": { "@types/react": "*" }, @@ -10068,11 +10119,11 @@ } }, "node_modules/@mui/utils": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", - "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.12.3.tgz", + "integrity": "sha512-D/Z4Ub3MRl7HiUccid7sQYclTr24TqUAQFFlxHQF8FR177BrCTQ0JJZom7EqYjZCdXhwnSkOj2ph685MSKNtIA==", "dependencies": { - "@babel/runtime": "^7.20.7", + "@babel/runtime": "^7.21.0", "@types/prop-types": "^15.7.5", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.8.1", @@ -10352,9 +10403,9 @@ "dev": true }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -16093,9 +16144,9 @@ "dev": true }, "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/currently-unhandled": { "version": "0.4.1", @@ -33485,6 +33536,9 @@ "platform/components": { "name": "@mattermost/components", "version": "7.11.0", + "dependencies": { + "color-blend": "4.0.0" + }, "devDependencies": { "@babel/cli": "^7.17.6", "@babel/core": "^7.17.7", @@ -33521,8 +33575,10 @@ }, "peerDependencies": { "@babel/runtime-corejs3": "^7.17.8", - "@mui/base": "5.0.0-alpha.116", - "@mui/material": "5.11.7", + "@mui/base": "5.0.0-alpha.127", + "@mui/material": "5.11.16", + "@mui/styled-engine-sc": "5.11.11", + "@mui/system": "5.11.16", "@tippyjs/react": "^4.2.6", "classnames": "^2.3.1", "lodash": "^4.17.21", @@ -33826,6 +33882,14 @@ "styled-components": ">= 2" } }, + "platform/components/node_modules/color-blend": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/color-blend/-/color-blend-4.0.0.tgz", + "integrity": "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw==", + "engines": { + "node": ">=10.0.0" + } + }, "platform/components/node_modules/css-loader": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", @@ -37206,9 +37270,9 @@ } }, "@babel/runtime": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.13.tgz", - "integrity": "sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==", + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.5.tgz", + "integrity": "sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==", "requires": { "regenerator-runtime": "^0.13.11" }, @@ -39921,6 +39985,7 @@ "babel-loader": "^8.2.3", "babel-plugin-formatjs": "10.3.14", "babel-plugin-styled-components": "^2.0.6", + "color-blend": "4.0.0", "css-loader": "^6.7.1", "rollup": "^2.75.7", "rollup-plugin-auto-external": "^2.0.0", @@ -40166,6 +40231,11 @@ "picomatch": "^2.3.0" } }, + "color-blend": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/color-blend/-/color-blend-4.0.0.tgz", + "integrity": "sha512-fYODTHhI/NG+B5GnzvuL3kiFrK/UnkUezWFTgEPBTY5V+kpyfAn95Vn9sJeeCX6omrCOdxnqCL3CvH+6sXtIbw==" + }, "css-loader": { "version": "6.7.3", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", @@ -40220,15 +40290,15 @@ "dev": true }, "@mui/base": { - "version": "5.0.0-alpha.116", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.116.tgz", - "integrity": "sha512-VwhifWdrfHc4/ZdqRZ4Gf+7P39sovNN24By1YVZdvJ9fvp0Sr8sNftGUCjYXXz+xCXVBQDXvhfxMwZrj2MvJvA==", + "version": "5.0.0-alpha.124", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.124.tgz", + "integrity": "sha512-I6M+FrjRCybQCr8I8JTu6L2MkUobSQFgNIpOJyDNKL5zq/73LvZIQXvsKumAzthVGvI1PYaarM9vGDrDYbumKA==", "requires": { - "@babel/runtime": "^7.20.7", + "@babel/runtime": "^7.21.0", "@emotion/is-prop-valid": "^1.2.0", "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", - "@popperjs/core": "^2.11.6", + "@mui/utils": "^5.11.13", + "@popperjs/core": "^2.11.7", "clsx": "^1.2.1", "prop-types": "^15.8.1", "react-is": "^18.2.0" @@ -40250,24 +40320,24 @@ } }, "@mui/core-downloads-tracker": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.11.7.tgz", - "integrity": "sha512-lZgX7XQTk0zVcpwEa80r+T4y09dosnUxWvFPSikU/2Hh5wnyNOek8WfJwGCNsaRiXJHMi5eHY+z8oku4u5lgNw==" + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.3.tgz", + "integrity": "sha512-yiJZ+knaknPHuRKhRk4L6XiwppwkAahVal3LuYpvBH7GkA2g+D9WLEXOEnNYtVFUggyKf6fWGLGnx0iqzkU5YA==" }, "@mui/material": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.7.tgz", - "integrity": "sha512-wDv7Pc6kMe9jeWkmCLt4JChd1lPc2u23JQHpB35L2VwQowpNFoDfIwqi0sYCnZTMKlRc7lza8LqwSwHl2G52Rw==", + "version": "5.11.16", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.11.16.tgz", + "integrity": "sha512-++glQqbZ3rMzOWB77yOvqRG+k8+scYTUKVWZpWff+GWsf6L10g9L2wgRhhAS8bDLuxCbXZlPNbSZowXDDw6z6Q==", "requires": { - "@babel/runtime": "^7.20.7", - "@mui/base": "5.0.0-alpha.116", - "@mui/core-downloads-tracker": "^5.11.7", - "@mui/system": "^5.11.7", + "@babel/runtime": "^7.21.0", + "@mui/base": "5.0.0-alpha.124", + "@mui/core-downloads-tracker": "^5.11.16", + "@mui/system": "^5.11.16", "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", + "@mui/utils": "^5.11.13", "@types/react-transition-group": "^4.4.5", "clsx": "^1.2.1", - "csstype": "^3.1.1", + "csstype": "^3.1.2", "prop-types": "^15.8.1", "react-is": "^18.2.0", "react-transition-group": "^4.4.5" @@ -40281,36 +40351,36 @@ } }, "@mui/private-theming": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.11.7.tgz", - "integrity": "sha512-XzRTSZdc8bhuUdjablTNv3kFkZ/XIMlKkOqqJCU0G8W3tWGXpau2DXkafPd1ddjPhF9zF3qLKNGgKCChYItjgA==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.12.3.tgz", + "integrity": "sha512-o1e7Z1Bp27n4x2iUHhegV4/Jp6H3T6iBKHJdLivS5GbwsuAE/5l4SnZ+7+K+e5u9TuhwcAKZLkjvqzkDe8zqfA==", "requires": { - "@babel/runtime": "^7.20.7", - "@mui/utils": "^5.11.7", + "@babel/runtime": "^7.21.0", + "@mui/utils": "^5.12.3", "prop-types": "^15.8.1" } }, "@mui/styled-engine": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.11.0.tgz", - "integrity": "sha512-AF06K60Zc58qf0f7X+Y/QjaHaZq16znliLnGc9iVrV/+s8Ln/FCoeNuFvhlCbZZQ5WQcJvcy59zp0nXrklGGPQ==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.12.3.tgz", + "integrity": "sha512-AhZtiRyT8Bjr7fufxE/mLS+QJ3LxwX1kghIcM2B2dvJzSSg9rnIuXDXM959QfUVIM3C8U4x3mgVoPFMQJvc4/g==", "requires": { - "@babel/runtime": "^7.20.6", - "@emotion/cache": "^11.10.5", - "csstype": "^3.1.1", + "@babel/runtime": "^7.21.0", + "@emotion/cache": "^11.10.8", + "csstype": "^3.1.2", "prop-types": "^15.8.1" }, "dependencies": { "@emotion/cache": { - "version": "11.10.5", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.5.tgz", - "integrity": "sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==", + "version": "11.10.8", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.8.tgz", + "integrity": "sha512-5fyqGHi51LU95o7qQ/vD1jyvC4uCY5GcBT+UgP4LHdpO9jPDlXqhrRr9/wCKmfoAvh5G/F7aOh4MwQa+8uEqhA==", "requires": { "@emotion/memoize": "^0.8.0", "@emotion/sheet": "^1.2.1", "@emotion/utils": "^1.2.0", "@emotion/weak-memoize": "^0.3.0", - "stylis": "4.1.3" + "stylis": "4.1.4" } }, "@emotion/sheet": { @@ -40322,44 +40392,49 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + }, + "stylis": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.1.4.tgz", + "integrity": "sha512-USf5pszRYwuE6hg9by0OkKChkQYEXfkeTtm0xKw+jqQhwyjCVLdYyMBK7R+n7dhzsblAWJnGxju4vxq5eH20GQ==" } } }, "@mui/styled-engine-sc": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.11.0.tgz", - "integrity": "sha512-U8cA7DHB3fnLpDVM5qd/9FGnw5y+LrmwIXQ9Lzk3HYkF903tSMx9jXIbiz1vltpOfZTKX/Rn02m/nkH4OF6Pcg==", + "version": "5.11.11", + "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.11.11.tgz", + "integrity": "sha512-6+HsfcKHlhjQklDoEup7Itl+Xgn+BCsqEpIdIIhlxED4YlOZ38xghxIKrx78XFZznTorbhAspUgDDKIaB5vDMg==", "requires": { - "@babel/runtime": "^7.20.6", + "@babel/runtime": "^7.21.0", "prop-types": "^15.8.1" } }, "@mui/system": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.7.tgz", - "integrity": "sha512-uGB6hBxGlAdlmbLdTtUZYNPXkgQGGnKxHdkRATqsu7UlCxNsc/yS5NCEWy/3c4pnelD1LDLD39WrntP9mwhfkQ==", + "version": "5.11.16", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.11.16.tgz", + "integrity": "sha512-JY7CNm7ik2Gr4kQpz1+C9N/f4ET3QjVBo/iaHcmlSOgjdxnOzFbv+vCdb1DMzBGew+UbqckppZpZwbgbrBE2Rw==", "requires": { - "@babel/runtime": "^7.20.7", - "@mui/private-theming": "^5.11.7", - "@mui/styled-engine": "^5.11.0", + "@babel/runtime": "^7.21.0", + "@mui/private-theming": "^5.11.13", + "@mui/styled-engine": "^5.11.16", "@mui/types": "^7.2.3", - "@mui/utils": "^5.11.7", + "@mui/utils": "^5.11.13", "clsx": "^1.2.1", - "csstype": "^3.1.1", + "csstype": "^3.1.2", "prop-types": "^15.8.1" } }, "@mui/types": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.3.tgz", - "integrity": "sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==" + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.4.tgz", + "integrity": "sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==" }, "@mui/utils": { - "version": "5.11.7", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.11.7.tgz", - "integrity": "sha512-8uyNDeVHZA804Ego20Erv8TpxlbqTe/EbhTI2H1UYr4/RiIbBprat8W4Qqr2UQIsC/b3DLz+0RQ6R/E5BxEcLA==", + "version": "5.12.3", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.12.3.tgz", + "integrity": "sha512-D/Z4Ub3MRl7HiUccid7sQYclTr24TqUAQFFlxHQF8FR177BrCTQ0JJZom7EqYjZCdXhwnSkOj2ph685MSKNtIA==", "requires": { - "@babel/runtime": "^7.20.7", + "@babel/runtime": "^7.21.0", "@types/prop-types": "^15.7.5", "@types/react-is": "^16.7.1 || ^17.0.0", "prop-types": "^15.8.1", @@ -40545,9 +40620,9 @@ "dev": true }, "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + "version": "2.11.7", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.7.tgz", + "integrity": "sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==" }, "@react-dnd/asap": { "version": "4.0.1", @@ -47840,9 +47915,9 @@ } }, "csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "currently-unhandled": { "version": "0.4.1", @@ -54105,9 +54180,10 @@ "@mattermost/compass-components": "^0.2.12", "@mattermost/compass-icons": "0.1.34", "@mattermost/types": "*", - "@mui/base": "5.0.0-alpha.116", - "@mui/material": "5.11.7", - "@mui/styled-engine-sc": "5.11.0", + "@mui/base": "5.0.0-alpha.127", + "@mui/material": "5.11.16", + "@mui/styled-engine-sc": "5.11.11", + "@mui/system": "5.11.16", "@redux-devtools/extension": "3.2.3", "@stripe/react-stripe-js": "1.13.0", "@stripe/stripe-js": "1.41.0", @@ -54288,6 +54364,14 @@ "zen-observable": "0.9.0" }, "dependencies": { + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -54303,6 +54387,28 @@ "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", "dev": true }, + "@mui/base": { + "version": "5.0.0-alpha.127", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.127.tgz", + "integrity": "sha512-FoRQd0IOH9MnfyL5yXssyQRnC4vXI+1bwkU1idr+wNkP1ZfxE+JsThHcfl1dy5azLssVUGTtQFD9edQLdbyJog==", + "requires": { + "@babel/runtime": "^7.21.0", + "@emotion/is-prop-valid": "^1.2.0", + "@mui/types": "^7.2.4", + "@mui/utils": "^5.12.0", + "@popperjs/core": "^2.11.7", + "clsx": "^1.2.1", + "prop-types": "^15.8.1", + "react-is": "^18.2.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "@testing-library/user-event": { "version": "12.1.4", "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.1.4.tgz", diff --git a/webapp/platform/components/package.json b/webapp/platform/components/package.json index ab6d836b5b..0675cc5a53 100644 --- a/webapp/platform/components/package.json +++ b/webapp/platform/components/package.json @@ -45,8 +45,10 @@ }, "peerDependencies": { "@babel/runtime-corejs3": "^7.17.8", - "@mui/base": "5.0.0-alpha.116", - "@mui/material": "5.11.7", + "@mui/base": "5.0.0-alpha.127", + "@mui/material": "5.11.16", + "@mui/styled-engine-sc": "5.11.11", + "@mui/system": "5.11.16", "@tippyjs/react": "^4.2.6", "classnames": "^2.3.1", "lodash": "^4.17.21", @@ -57,5 +59,8 @@ "shallow-equals": "^1.0.0", "styled-components": "^5.3.5", "tippy.js": "^6.3.7" + }, + "dependencies": { + "color-blend": "4.0.0" } } diff --git a/webapp/platform/components/src/common/constants/theme.ts b/webapp/platform/components/src/common/constants/theme.ts new file mode 100644 index 0000000000..84a5e6a258 --- /dev/null +++ b/webapp/platform/components/src/common/constants/theme.ts @@ -0,0 +1,139 @@ +import {Theme, ThemeKey} from '@mattermost/types/theme'; + +export const THEMES: Record = { + denim: { + type: 'Denim', + sidebarBg: '#1e325c', + sidebarText: '#ffffff', + sidebarUnreadText: '#ffffff', + sidebarTextHoverBg: '#28427b', + sidebarTextActiveBorder: '#5d89ea', + sidebarTextActiveColor: '#ffffff', + sidebarHeaderBg: '#192a4d', + sidebarHeaderTextColor: '#ffffff', + sidebarTeamBarBg: '#14213e', + onlineIndicator: '#3db887', + awayIndicator: '#ffbc1f', + dndIndicator: '#d24b4e', + mentionBg: '#ffffff', + mentionColor: '#1e325c', + centerChannelBg: '#ffffff', + centerChannelColor: '#3f4350', + newMessageSeparator: '#cc8f00', + linkColor: '#386fe5', + buttonBg: '#1c58d9', + buttonColor: '#ffffff', + errorTextColor: '#d24b4e', + mentionHighlightBg: '#ffd470', + mentionHighlightLink: '#1b1d22', + codeTheme: 'github', + }, + sapphire: { + type: 'Sapphire', + sidebarBg: '#174ab5', + sidebarText: '#ffffff', + sidebarUnreadText: '#ffffff', + sidebarTextHoverBg: '#2a58ba', + sidebarTextActiveBorder: '#57b5f0', + sidebarTextActiveColor: '#ffffff', + sidebarHeaderBg: '#1542a2', + sidebarHeaderTextColor: '#ffffff', + sidebarTeamBarBg: '#133a91', + onlineIndicator: '#3db887', + awayIndicator: '#ffbc1f', + dndIndicator: '#d24b4e', + mentionBg: '#ffffff', + mentionColor: '#174ab5', + centerChannelBg: '#ffffff', + centerChannelColor: '#3f4350', + newMessageSeparator: '#15b7b7', + linkColor: '#1c58d9', + buttonBg: '#1c58d9', + buttonColor: '#ffffff', + errorTextColor: '#d24b4e', + mentionHighlightBg: '#7ff0f0', + mentionHighlightLink: '#0d6e6e', + codeTheme: 'github', + }, + quartz: { + type: 'Quartz', + sidebarBg: '#f4f4f6', + sidebarText: '#090a0b', + sidebarUnreadText: '#2d3039', + sidebarTextHoverBg: '#ebebed', + sidebarTextActiveBorder: '#32a4ec', + sidebarTextActiveColor: '#2d3039', + sidebarHeaderBg: '#e8e9ed', + sidebarHeaderTextColor: '#2d3039', + sidebarTeamBarBg: '#dddfe4', + onlineIndicator: '#3db887', + awayIndicator: '#f5ab07', + dndIndicator: '#d24b4e', + mentionBg: '#1c58d9', + mentionColor: '#ffffff', + centerChannelBg: '#ffffff', + centerChannelColor: '#3f4350', + newMessageSeparator: '#15b7b7', + linkColor: '#1c58d9', + buttonBg: '#1c58d9', + buttonColor: '#ffffff', + errorTextColor: '#d24b4e', + mentionHighlightBg: '#7ff0f0', + mentionHighlightLink: '#0d6e6e', + codeTheme: 'github', + }, + indigo: { + type: 'Indigo', + sidebarBg: '#0f1a2e', + sidebarText: '#ffffff', + sidebarUnreadText: '#ffffff', + sidebarTextHoverBg: '#222c3f', + sidebarTextActiveBorder: '#1279ba', + sidebarTextActiveColor: '#ffffff', + sidebarHeaderBg: '#152231', + sidebarHeaderTextColor: '#dddfe4', + sidebarTeamBarBg: '#05080f', + onlineIndicator: '#3db887', + awayIndicator: '#f5ab00', + dndIndicator: '#d24b4e', + mentionBg: '#1c58d9', + mentionColor: '#ffffff', + centerChannelBg: '#0a111f', + centerChannelColor: '#dddfe4', + newMessageSeparator: '#81a3ef', + linkColor: '#5d89ea', + buttonBg: '#386fe5', + buttonColor: '#ffffff', + errorTextColor: '#d24b4e', + mentionHighlightBg: '#133a91', + mentionHighlightLink: '#a4f4f4', + codeTheme: 'solarized-dark', + }, + onyx: { + type: 'Onyx', + sidebarBg: '#121317', + sidebarText: '#ffffff', + sidebarUnreadText: '#ffffff', + sidebarTextHoverBg: '#25262a', + sidebarTextActiveBorder: '#1592e0', + sidebarTextActiveColor: '#ffffff', + sidebarHeaderBg: '#1b1d22', + sidebarHeaderTextColor: '#dddfe4', + sidebarTeamBarBg: '#000000', + onlineIndicator: '#3db887', + awayIndicator: '#f5ab00', + dndIndicator: '#d24b4e', + mentionBg: '#1c58d9', + mentionColor: '#ffffff', + centerChannelBg: '#090a0b', + centerChannelColor: '#dddfe4', + newMessageSeparator: '#1adbdb', + linkColor: '#5d89ea', + buttonBg: '#386fe5', + buttonColor: '#ffffff', + errorTextColor: '#da6c6e', + mentionHighlightBg: '#0d6e6e', + mentionHighlightLink: '#a4f4f4', + codeTheme: 'monokai', + }, +}; diff --git a/webapp/platform/components/src/compass/components/base_modal/base_modal.tsx b/webapp/platform/components/src/compass/components/base_modal/base_modal.tsx new file mode 100644 index 0000000000..4e31b69874 --- /dev/null +++ b/webapp/platform/components/src/compass/components/base_modal/base_modal.tsx @@ -0,0 +1,59 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {memo} from 'react'; +import {styled} from '@mui/material/styles'; +import Dialog from '@mui/material/Dialog'; +import MUIPaper, {PaperProps} from '@mui/material/Paper'; +import Slide from '@mui/material/Slide'; +import {TransitionProps} from '@mui/material/transitions'; + +const Transition = React.forwardRef(( + {children, ...props}: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref, +) => { + return ( + + {children} + + ); +}); + +const Paper = styled(MUIPaper)(() => ({ + border: '1px solid rgba(var(--center-channel-color-rgb), 0.16)', + borderRadius: 12, + backgroundColor: 'var(--center-channel-bg)', + boxShadow: '0 20px 32px 0 rgba(0, 0, 0, 0.12)', + minWidth: 600, +})); + +type ModalProps = { + children: React.ReactNode | React.ReactNodeArray; + isOpen: boolean; + dialogClassName?: string; + dialogId?: string; + onClose?: () => void; +} + +const BaseModal = ({children, isOpen, onClose, dialogClassName = '', dialogId = ''}: ModalProps) => ( + {children} + +); + +export default BaseModal; diff --git a/webapp/platform/components/src/compass/components/base_modal/modal_title.tsx b/webapp/platform/components/src/compass/components/base_modal/modal_title.tsx new file mode 100644 index 0000000000..4539671a32 --- /dev/null +++ b/webapp/platform/components/src/compass/components/base_modal/modal_title.tsx @@ -0,0 +1,71 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {styled} from '@mui/material/styles'; +import MUIDialogTitle, {DialogTitleProps as MUIDialogTitleProps} from '@mui/material/DialogTitle'; + +import {CloseIcon} from '@mattermost/compass-icons/components'; + +import Icon_button from '../icon_button/icon_button'; + +type DialogTitleProps = MUIDialogTitleProps & { hasCloseButton: boolean }; + +const StyledModalTitle = styled(MUIDialogTitle, { + shouldForwardProp: (prop) => prop !== 'hasCloseButton', +})(({hasCloseButton}) => ({ + display: 'grid', + gridTemplateColumns: '1fr max-content max-content', + gap: 12, + fontFamily: 'Metropolis', + fontStyle: 'normal', + fontWeight: 600, + fontSize: 22, + lineHeight: '28px', + color: 'var(--center-channel-color)', + alignItems: 'center', + padding: hasCloseButton ? '22px 26px 24px 32px' : '28px 32px 24px', +})); + +const StyledModalTitleSection = styled('div')(() => ({ + padding: '0 32px 24px 32px', + borderBottom: '1px solid rgba(var(--center-channel-text-rgb), 0.12)', +})); + +type ModalTitleProps = { + title: string; + rightSection?: React.ReactNode | React.ReactNode[]; + children?: React.ReactNode | React.ReactNode[]; + onClose?: React.MouseEventHandler; +} + +const ModalTitle = ({title, onClose, children, rightSection = null}: ModalTitleProps) => { + const hasCloseButton = Boolean(onClose); + + return ( + <> + + {title} + {rightSection} + {hasCloseButton && ( + + )} + + {children && ( + + {children} + + )} + > + ); +}; + +export default ModalTitle; diff --git a/webapp/platform/components/src/compass/components/button/button.overrides.ts b/webapp/platform/components/src/compass/components/button/button.overrides.ts new file mode 100644 index 0000000000..6e79c6231e --- /dev/null +++ b/webapp/platform/components/src/compass/components/button/button.overrides.ts @@ -0,0 +1,173 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {alpha, ComponentsProps, ComponentsVariants, emphasize, Theme} from '@mui/material'; +import {ComponentsOverrides} from '@mui/material/styles/overrides'; + +import {blend} from '../../utils/color'; + +const componentName = 'MuiButton'; + +declare module '@mui/material/Button' { + interface ButtonPropsSizeOverrides { + 'x-small': true; + } + + interface ButtonPropsVariantOverrides { + quaternary: true; + } +} + +const defaultProps: ComponentsProps[typeof componentName] = { + disableElevation: true, + disableRipple: true, + disableTouchRipple: true, + disableFocusRipple: true, +}; + +const getFocusStyles = (color: string) => ({ + '&:not(.Mui-disabled)': { + '&:focus': { + boxShadow: `inset 0 0 0 2px ${emphasize(color, 0.05)}`, + }, + + '&:focus:not(:focus-visible)': { + boxShadow: 'none', + }, + + '&:focus:focus-visible': { + boxShadow: `inset 0 0 0 2px ${emphasize(color, 0.05)}`, + }, + }, +}); + +const styleOverrides: ComponentsOverrides[typeof componentName] = { + containedPrimary: ({theme, ownerState}) => ({ + backgroundColor: ownerState.inverted ? theme.palette.text.primary : theme.palette.primary.main, + color: ownerState.inverted ? theme.palette.primary.main : theme.palette.primary.contrastText, + + '&:hover': { + backgroundColor: ownerState.inverted ? blend(theme.palette.text.primary, alpha(theme.palette.primary.main, 0.08)) : emphasize(theme.palette.primary.main, 0.1), + }, + '&:active': { + backgroundColor: ownerState.inverted ? blend(theme.palette.text.primary, alpha(theme.palette.primary.main, 0.16)) : emphasize(theme.palette.primary.main, 0.2), + }, + + ...getFocusStyles(theme.palette.primary.main), + }), + containedError: ({theme}) => ({ + '&:hover': { + backgroundColor: emphasize(theme.palette.error.dark, 0.1), + }, + '&:active': { + backgroundColor: emphasize(theme.palette.error.dark, 0.2), + }, + + ...getFocusStyles(theme.palette.error.main), + }), + outlinedPrimary: ({theme}) => ({ + backgroundColor: alpha(theme.palette.primary.main, 0), + + '&:hover': { + backgroundColor: alpha(theme.palette.primary.main, 0.08), + }, + + '&:active': { + backgroundColor: alpha(theme.palette.primary.main, 0.16), + }, + + ...getFocusStyles(theme.palette.primary.main), + }), + outlinedError: ({theme}) => ({ + backgroundColor: alpha(theme.palette.error.main, 0), + + '&:hover': { + backgroundColor: alpha(theme.palette.error.main, 0.08), + }, + + '&:active': { + backgroundColor: alpha(theme.palette.error.main, 0.16), + }, + + ...getFocusStyles(theme.palette.error.main), + }), + textPrimary: ({theme}) => ({ + backgroundColor: alpha(theme.palette.primary.main, 0.08), + + '&:hover': { + backgroundColor: alpha(theme.palette.primary.main, 0.12), + }, + + '&:active': { + backgroundColor: alpha(theme.palette.primary.main, 0.16), + }, + + ...getFocusStyles(theme.palette.primary.main), + }), + textError: ({theme, ownerState}) => ({ + backgroundColor: ownerState.disabled ? theme.palette.action.disabledBackground : alpha(theme.palette.error.main, 0.12), + + '&:hover': { + backgroundColor: alpha(theme.palette.error.main, 0.12), + }, + + '&:active': { + backgroundColor: alpha(theme.palette.error.main, 0.16), + }, + + ...getFocusStyles(theme.palette.error.main), + }), +}; + +const variants: ComponentsVariants[typeof componentName] = [ + { + props: {size: 'x-small'}, + style: ({theme}) => ({ + padding: '0.4rem 1rem', + ...theme.typography.b50, + margin: 0, + textTransform: 'none', + }), + }, + { + props: {size: 'small'}, + style: ({theme}) => ({ + padding: '0.8rem 1.6rem', + ...theme.typography.b75, + margin: 0, + textTransform: 'none', + }), + }, + { + props: {size: 'medium'}, + style: ({theme}) => ({ + padding: '1rem 2rem', + ...theme.typography.b100, + margin: 0, + textTransform: 'none', + }), + }, + { + props: {size: 'large'}, + style: ({theme}) => ({ + padding: '1.2rem 2.4rem', + ...theme.typography.b200, + margin: 0, + textTransform: 'none', + }), + }, + { + props: {variant: 'quaternary'}, + style: () => ({ + backgroundColor: 'red', + }), + }, +]; + +const buttonOverrides = { + variants, + defaultProps, + styleOverrides, +}; + +export default buttonOverrides; diff --git a/webapp/platform/components/src/compass/components/button/button.tsx b/webapp/platform/components/src/compass/components/button/button.tsx new file mode 100644 index 0000000000..86658eb197 --- /dev/null +++ b/webapp/platform/components/src/compass/components/button/button.tsx @@ -0,0 +1,39 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import MuiButton, {ButtonProps as MuiButtonProps} from '@mui/material/Button'; + +type ButtonVariant = 'primary' | 'secondary' | 'tertiary'; + +const variantMap: Record> = { + primary: 'contained', + secondary: 'outlined', + tertiary: 'text', +}; + +type ExcludedMuiProps = + | 'sx' + | 'classes' + | 'variant' + | 'disableElevation' + | 'disableRipple' + | 'disableFocusRipple'; + +export type ButtonProps = Omit & { + destructive?: boolean; + disabled?: boolean; + inverted?: boolean; + variant?: ButtonVariant; + children: React.ReactNode | React.ReactNode[]; +} + +export const Button = ({variant = 'primary', destructive, ...rest}: ButtonProps) => ( + +); + +export default Button; diff --git a/webapp/platform/components/src/compass/components/generic_modal/generic_modal.tsx b/webapp/platform/components/src/compass/components/generic_modal/generic_modal.tsx new file mode 100644 index 0000000000..1289daa4eb --- /dev/null +++ b/webapp/platform/components/src/compass/components/generic_modal/generic_modal.tsx @@ -0,0 +1,80 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {memo} from 'react'; +import {styled} from '@mui/material/styles'; +import Button from '@mui/material/Button'; +import MUIPaper, {PaperProps} from '@mui/material/Paper'; +import DialogActions from '@mui/material/DialogActions'; +import Slide from '@mui/material/Slide'; +import {TransitionProps} from '@mui/material/transitions'; +import BaseModal from "../base_modal/base_modal"; + +const Transition = React.forwardRef(( + {children, ...props}: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref, +) => { + return ( + + {children} + + ); +}); + +const Paper = styled(MUIPaper)(() => ({ + border: '1px solid rgba(var(--center-channel-color-rgb), 0.16)', + borderRadius: 12, + backgroundColor: 'var(--center-channel-bg)', + boxShadow: '0 20px 32px 0 rgba(0, 0, 0, 0.12)', + minWidth: 600, +})); + +type ModalProps = { + children: React.ReactNode | React.ReactNodeArray; + isOpen: boolean; + dialogClassName?: string; + dialogId?: string; + onClose?: () => void; + onConfirm?: () => void; + onCancel?: () => void; +} + +const GenericModal = ({children, isOpen, onClose, onConfirm, onCancel, dialogClassName = '', dialogId = ''}: ModalProps) => { + const hasActions = Boolean(onConfirm || onCancel); + + const handleConfirmAction = () => { + onConfirm?.(); + onClose?.(); + }; + + const handleCancelAction = () => { + onCancel?.(); + onClose?.(); + }; + + return ( + + {children} + {hasActions && ( + + {onCancel && {'Cancel'}} + {onConfirm && {'Confirm'}} + + )} + + ); +}; + +export default memo(GenericModal); diff --git a/webapp/platform/components/src/compass/components/icon_button/icon_button.overrides.ts b/webapp/platform/components/src/compass/components/icon_button/icon_button.overrides.ts new file mode 100644 index 0000000000..4ff1e38628 --- /dev/null +++ b/webapp/platform/components/src/compass/components/icon_button/icon_button.overrides.ts @@ -0,0 +1,206 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {alpha, ComponentsProps, emphasize, Theme} from '@mui/material'; +import type {ComponentsVariants} from '@mui/material'; +import type {ComponentsOverrides} from '@mui/material/styles/overrides'; + +const componentName = 'MuiIconButton'; + +declare module '@mui/material/IconButton' { + interface IconButtonPropsSizeOverrides { + 'x-small': true; + } + interface IconButtonPropsOverrides { + destructive: boolean; + compact: boolean; + } +} + +const defaultProps: ComponentsProps[typeof componentName] = { + disableRipple: true, + disableTouchRipple: true, + disableFocusRipple: true, +}; + +export const getFocusStyles = (color: string) => ({ + '&:not(.Mui-disabled)': { + '&:focus': { + boxShadow: `inset 0 0 0 2px ${emphasize(color, 0.05)}`, + }, + + '&:focus:not(:focus-visible)': { + boxShadow: 'none', + }, + + '&:focus:focus-visible': { + boxShadow: `inset 0 0 0 2px ${emphasize(color, 0.05)}`, + }, + }, +}); + +const styleOverrides: ComponentsOverrides[typeof componentName] = { + root: ({theme, ownerState}) => ({ + backgroundColor: 'transparent', + color: ownerState.color === 'error' ? theme.palette.error.main : alpha(theme.palette.text.primary, 0.56), + borderRadius: 4, + margin: 0, + + '&:hover': { + backgroundColor: alpha(ownerState.color === 'error' ? theme.palette.error.main : theme.palette.text.primary, 0.08), + color: ownerState.color === 'error' ? theme.palette.error.main : alpha(theme.palette.text.primary, 0.72), + }, + + '&:active': { + backgroundColor: alpha(ownerState.color === 'error' ? theme.palette.error.main : theme.palette.primary.main, 0.16), + color: ownerState.color === 'error' ? theme.palette.error.main : theme.palette.primary.main, + }, + + svg: { + margin: 0, + fill: 'currentColor', + }, + + ...getFocusStyles(ownerState.color === 'error' ? theme.palette.error.main : theme.palette.primary.main), + + ...(ownerState.color !== 'error' && { + '&.toggled:not(.inverted):not(.Mui-disabled)': { + backgroundColor: theme.palette.primary.main, + color: theme.palette.primary.contrastText, + + '&:hover': { + backgroundColor: alpha(theme.palette.primary.main, 0.92), + color: theme.palette.primary.contrastText, + }, + + '&:active': { + backgroundColor: alpha(theme.palette.primary.main, 0.16), + color: theme.palette.primary.main, + }, + }, + + '&.inverted:not(.Mui-disabled)': { + backgroundColor: 'transparent', + color: alpha(theme.palette.text.primary, 0.56), + + '&:hover': { + backgroundColor: alpha(theme.palette.text.primary, 0.08), + color: theme.palette.text.primary, + }, + + '&:active': { + backgroundColor: alpha(theme.palette.text.primary, 0.16), + color: theme.palette.text.primary, + }, + + '&.toggled': { + backgroundColor: theme.palette.text.primary, + color: alpha(theme.palette.background.default, 0.56), + + '&:hover': { + backgroundColor: alpha(theme.palette.text.primary, 0.92), + color: alpha(theme.palette.background.default, 0.56), + }, + + '&:active': { + backgroundColor: alpha(theme.palette.text.primary, 0.16), + color: theme.palette.text.primary, + }, + }, + }, + }), + + transition: theme.transitions.create(['background-color', 'color'], {duration: theme.transitions.duration.shorter}), + }), +}; + +const variants: ComponentsVariants[typeof componentName] = [ + { + props: {size: 'x-small'}, + style: ({theme}) => ({ + padding: theme.spacing(0.5), + + svg: { + width: '12px', + height: '12px', + }, + + ...theme.typography.b75, + lineHeight: 0, + fontWeight: 600, + + '.MuiGrid-container': { + gap: theme.spacing(0.25), + maxHeight: theme.spacing(1.5), + }, + }), + }, + { + props: {size: 'small'}, + style: ({theme}) => ({ + padding: theme.spacing(0.75), + + svg: { + width: '16px', + height: '16px', + }, + + ...theme.typography.b100, + lineHeight: 0, + fontWeight: 600, + + '.MuiGrid-container': { + gap: theme.spacing(0.5), + maxHeight: theme.spacing(2), + }, + }), + }, + { + props: {size: 'medium'}, + style: ({theme}) => ({ + padding: theme.spacing(1), + + svg: { + width: '20px', + height: '20px', + }, + + ...theme.typography.b200, + lineHeight: 0, + fontWeight: 600, + + '.MuiGrid-container': { + gap: theme.spacing(0.75), + maxHeight: theme.spacing(2.5), + }, + }), + }, + { + props: {size: 'large'}, + style: ({theme}) => ({ + padding: theme.spacing(1), + + svg: { + width: '28px', + height: '28px', + }, + + ...theme.typography.b300, + lineHeight: 0, + fontWeight: 600, + + '.MuiGrid-container': { + gap: theme.spacing(0.75), + maxHeight: theme.spacing(3.5), + }, + }), + }, +]; + +const overrides = { + variants, + defaultProps, + styleOverrides, +}; + +export default overrides; diff --git a/webapp/platform/components/src/compass/components/icon_button/icon_button.tsx b/webapp/platform/components/src/compass/components/icon_button/icon_button.tsx new file mode 100644 index 0000000000..6c07ed3103 --- /dev/null +++ b/webapp/platform/components/src/compass/components/icon_button/icon_button.tsx @@ -0,0 +1,51 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Grid} from '@mui/material'; +import classNames from 'classnames'; +import React from 'react'; +import MuiIconButton, {IconButtonProps as MuiIconButtonProps} from '@mui/material/IconButton'; + +type ExcludedMuiProps = 'sx' | 'disableFocusRipple' | 'disableRipple' | 'color' | 'classes'; + +type CustomProps = { + IconComponent: React.FC; + compact?: boolean; + toggled?: boolean; + inverted?: boolean; + destructive?: boolean; + label?: string; +} + +type IconButtonProps = Omit & CustomProps; + +const IconButton = ({IconComponent, label, destructive = false, compact = false, toggled = false, inverted = false, ...props}: IconButtonProps) => { + return ( + + + + + + {label && ( + + {label} + + )} + + + ); +}; + +export default IconButton; diff --git a/webapp/platform/components/src/compass/components/index.ts b/webapp/platform/components/src/compass/components/index.ts new file mode 100644 index 0000000000..523da76dae --- /dev/null +++ b/webapp/platform/components/src/compass/components/index.ts @@ -0,0 +1,16 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import Typography from './typography/typography'; +import Button, {ButtonProps} from './button/button'; +import IconButton from './icon_button/icon_button'; + +export { + IconButton, + Button, + Typography, +}; + +export type { + ButtonProps, +}; diff --git a/webapp/platform/components/src/compass/components/typography/typography.overrides.ts b/webapp/platform/components/src/compass/components/typography/typography.overrides.ts new file mode 100644 index 0000000000..8771c6a35d --- /dev/null +++ b/webapp/platform/components/src/compass/components/typography/typography.overrides.ts @@ -0,0 +1,285 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {TypographyOptions} from '@mui/material/styles/createTypography'; + +import {getFontMargin} from '../../utils/font'; + +declare module '@mui/material/styles' { + interface TypographyVariants { + + // body text variants + b25: React.CSSProperties; + b50: React.CSSProperties; + b75: React.CSSProperties; + b100: React.CSSProperties; + b200: React.CSSProperties; + b300: React.CSSProperties; + + // heading variants + h25: React.CSSProperties; + h50: React.CSSProperties; + h75: React.CSSProperties; + h100: React.CSSProperties; + h200: React.CSSProperties; + h300: React.CSSProperties; + h400: React.CSSProperties; + h500: React.CSSProperties; + h600: React.CSSProperties; + h700: React.CSSProperties; + h800: React.CSSProperties; + h900: React.CSSProperties; + h1000: React.CSSProperties; + } + + // allow configuration using `createTheme` + interface TypographyVariantsOptions { + + // body text style properties + b25?: React.CSSProperties; + b50?: React.CSSProperties; + b75?: React.CSSProperties; + b100?: React.CSSProperties; + b200?: React.CSSProperties; + b300?: React.CSSProperties; + + // heading style properties + h25?: React.CSSProperties; + h50?: React.CSSProperties; + h75?: React.CSSProperties; + h100?: React.CSSProperties; + h200?: React.CSSProperties; + h300?: React.CSSProperties; + h400?: React.CSSProperties; + h500?: React.CSSProperties; + h600?: React.CSSProperties; + h700?: React.CSSProperties; + h800?: React.CSSProperties; + h900?: React.CSSProperties; + h1000?: React.CSSProperties; + } +} + +// Update the Typography's variant prop options +declare module '@mui/material/Typography' { + interface TypographyPropsVariantOverrides { + + // enable our custom body text variants + b25: true; + b50: true; + b75: true; + b100: true; + b200: true; + b300: true; + + // enable our custom heading variants + h25: true; + h50: true; + h75: true; + h100: true; + h200: true; + h300: true; + h400: true; + h500: true; + h600: true; + h700: true; + h800: true; + h900: true; + h1000: true; + + // disable the MUI variants + h1: false; + h2: false; + h3: false; + h4: false; + h5: false; + h6: false; + body1: false; + body2: false; + button: false; + caption: false; + inherit: false; + overline: false; + subtitle1: false; + subtitle2: false; + } +} + +const baseBodyStyles = { + fontFamily: 'Open Sans, sans-serif', + fontStyle: 'normal', + fontWeight: 400, +}; + +const b25 = { + fontSize: '1rem', + lineHeight: '1.6rem', + marginTop: getFontMargin(10, 0.75), + marginBottom: getFontMargin(10, 0.75), + ...baseBodyStyles, +}; +const b50 = { + fontSize: '1.1rem', + lineHeight: '1.6rem', + marginTop: getFontMargin(11, 0.75), + marginBottom: getFontMargin(11, 0.75), + ...baseBodyStyles, +}; +const b75 = { + fontSize: '1.2rem', + lineHeight: '1.6rem', + marginTop: getFontMargin(12, 0.75), + marginBottom: getFontMargin(12, 0.75), + ...baseBodyStyles, +}; +const b100 = { + fontSize: '1.4rem', + lineHeight: '2rem', + marginTop: getFontMargin(14, 0.75), + marginBottom: getFontMargin(14, 0.75), + ...baseBodyStyles, +}; +const b200 = { + fontSize: '1.6rem', + lineHeight: '2.4rem', + marginTop: getFontMargin(16, 0.75), + marginBottom: getFontMargin(16, 0.75), + ...baseBodyStyles, +}; +const b300 = { + fontSize: '1.8rem', + lineHeight: '2.8rem', + marginTop: getFontMargin(18, 0.75), + marginBottom: getFontMargin(18, 0.75), + ...baseBodyStyles, +}; + +const baseHeadingStyles = { + fontFamily: 'Metropolis, sans-serif', + fontStyle: 'normal', + fontWeight: 600, + + '&:first-of-type': { + marginTop: 0, + }, +}; + +const h25 = { + fontSize: '1rem', + lineHeight: '1.6rem', + marginTop: getFontMargin(10, 8 / 9), + marginBottom: getFontMargin(10, 0.5), + ...baseHeadingStyles, + fontFamily: 'Open Sans, sans-serif', +}; +const h50 = { + fontSize: '1.1rem', + lineHeight: '1.6rem', + marginTop: getFontMargin(11, 8 / 9), + marginBottom: getFontMargin(11, 0.5), + ...baseHeadingStyles, + fontFamily: 'Open Sans, sans-serif', +}; +const h75 = { + fontSize: '1.2rem', + lineHeight: '1.6rem', + marginTop: getFontMargin(12, 8 / 9), + marginBottom: getFontMargin(12, 0.5), + ...baseHeadingStyles, + fontFamily: 'Open Sans, sans-serif', +}; +const h100 = { + fontSize: '1.4rem', + lineHeight: '2rem', + marginTop: getFontMargin(14, 8 / 9), + marginBottom: getFontMargin(14, 0.5), + ...baseHeadingStyles, + fontFamily: 'Open Sans, sans-serif', +}; +const h200 = { + fontSize: '1.6rem', + lineHeight: '2.4rem', + marginTop: getFontMargin(16, 8 / 9), + marginBottom: getFontMargin(16, 0.5), + ...baseHeadingStyles, +}; +const h300 = { + fontSize: '1.8rem', + lineHeight: '2.4rem', + marginTop: getFontMargin(18, 8 / 9), + marginBottom: getFontMargin(18, 0.5), + ...baseHeadingStyles, +}; +const h400 = { + fontSize: '2rem', + lineHeight: '2.8rem', + marginTop: getFontMargin(18, 8 / 9), + marginBottom: getFontMargin(18, 0.5), + ...baseHeadingStyles, +}; +const h500 = { + fontSize: '2.2rem', + lineHeight: '2.8rem', + marginTop: getFontMargin(22, 8 / 9), + marginBottom: getFontMargin(22, 0.5), + ...baseHeadingStyles, +}; +const h600 = { + fontSize: '2.5rem', + lineHeight: '3rem', + marginTop: getFontMargin(25, 8 / 9), + marginBottom: getFontMargin(25, 0.5), + ...baseHeadingStyles, +}; +const h700 = { + fontSize: '2.8rem', + lineHeight: '3.6rem', + marginTop: getFontMargin(28, 8 / 9), + marginBottom: getFontMargin(28, 0.5), + ...baseHeadingStyles, +}; +const h800 = { + fontSize: '3.2rem', + lineHeight: '4rem', + marginTop: getFontMargin(32, 8 / 9), + marginBottom: getFontMargin(32, 0.5), + ...baseHeadingStyles, +}; +const h900 = { + fontSize: '3.6rem', + lineHeight: '4.4rem', + marginTop: getFontMargin(36, 8 / 9), + marginBottom: getFontMargin(36, 0.5), + ...baseHeadingStyles, +}; +const h1000 = { + fontSize: '4rem', + lineHeight: '4.8rem', + marginTop: getFontMargin(40, 8 / 9), + marginBottom: getFontMargin(40, 0.5), + ...baseHeadingStyles, +}; + +const typographyOverrides: TypographyOptions = { + b25, + b50, + b75, + b100, + b200, + b300, + h25, + h50, + h75, + h100, + h200, + h300, + h400, + h500, + h600, + h700, + h800, + h900, + h1000, +}; + +export default typographyOverrides; diff --git a/webapp/platform/components/src/compass/components/typography/typography.tsx b/webapp/platform/components/src/compass/components/typography/typography.tsx new file mode 100644 index 0000000000..f79c3c3e3c --- /dev/null +++ b/webapp/platform/components/src/compass/components/typography/typography.tsx @@ -0,0 +1,44 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {Typography as MuiTypography, TypographyProps as MuiTypographyProps} from '@mui/material'; + +type Props = Omit & { + gutterTop?: boolean; +} + +const Typography = ({variant = 'b100', ...props}: Props) => { + const variantMap = { + b25: 'p', + b50: 'p', + b75: 'p', + b100: 'p', + b200: 'p', + b300: 'p', + + h25: 'h6', + h50: 'h6', + h75: 'h6', + h100: 'h5', + h200: 'h5', + h300: 'h4', + h400: 'h4', + h500: 'h3', + h600: 'h3', + h700: 'h2', + h800: 'h2', + h900: 'h1', + h1000: 'h1', + }; + + return ( + + ); +}; + +export default Typography; diff --git a/webapp/platform/components/src/compass/index.ts b/webapp/platform/components/src/compass/index.ts new file mode 100644 index 0000000000..3c8b39496c --- /dev/null +++ b/webapp/platform/components/src/compass/index.ts @@ -0,0 +1,10 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +export * from './components'; + +import ThemeProvider from './theme_provider/theme_provider'; + +export { + ThemeProvider, +}; diff --git a/webapp/platform/components/src/compass/theme_provider/overrides.ts b/webapp/platform/components/src/compass/theme_provider/overrides.ts new file mode 100644 index 0000000000..935e543fd0 --- /dev/null +++ b/webapp/platform/components/src/compass/theme_provider/overrides.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {ThemeOptions} from '@mui/material/styles/createTheme'; + +import MuiButton from '../components/button/button.overrides'; +import MuiIconButton from '../components/icon_button/icon_button.overrides'; +import typographyOverrides from '../components/typography/typography.overrides'; + +const components: ThemeOptions['components'] = { + MuiButton, + MuiIconButton, +}; + +const overrides: ThemeOptions = { + components, + typography: { + ...typographyOverrides, + htmlFontSize: 10, + }, +}; + +export default overrides; diff --git a/webapp/platform/components/src/compass/theme_provider/theme_provider.tsx b/webapp/platform/components/src/compass/theme_provider/theme_provider.tsx new file mode 100644 index 0000000000..38394d6a80 --- /dev/null +++ b/webapp/platform/components/src/compass/theme_provider/theme_provider.tsx @@ -0,0 +1,31 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {createTheme, ThemeProvider as MUIThemeProvider} from '@mui/material/styles'; +import {Theme} from "@mattermost/types/theme"; + +import {createMUIThemeFromMMTheme} from './themes'; + +import {THEMES} from '../../common/constants/theme'; +import overrides from './overrides'; + +type Props = { + theme: Theme; +} + +const ThemeProvider = ({theme = THEMES.onyx}: Props) => { + const MUITheme = createMUIThemeFromMMTheme(theme); + const combinedTheme = createTheme({ + ...MUITheme, + ...overrides, + }); + + return ( + + ); +}; + +export default ThemeProvider; diff --git a/webapp/platform/components/src/compass/theme_provider/themes.ts b/webapp/platform/components/src/compass/theme_provider/themes.ts new file mode 100644 index 0000000000..cd65e90482 --- /dev/null +++ b/webapp/platform/components/src/compass/theme_provider/themes.ts @@ -0,0 +1,47 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {alpha} from '@mui/material'; +import {createTheme} from '@mui/material/styles'; +import {Theme} from '@mattermost/types/theme'; + +import {THEMES} from '../../common/constants/theme'; + +declare module '@mui/material/styles' { + interface Palette { + mention?: Palette['primary']; + } + interface PaletteOptions { + mention?: PaletteOptions['primary']; + } + + interface PaletteColor { + darker?: string; + } + interface SimplePaletteColorOptions { + darker?: string; + } +} + +export const createMUIThemeFromMMTheme = (theme: Theme) => createTheme({ + palette: { + primary: {main: theme.buttonBg}, + secondary: {main: theme.linkColor}, + error: {main: theme.dndIndicator}, + warning: {main: theme.awayIndicator}, + info: {main: theme.mentionHighlightBg}, + success: {main: theme.onlineIndicator}, + text: { + primary: theme.centerChannelColor, + }, + background: { + default: theme.centerChannelBg, + }, + action: { + disabled: alpha(theme.centerChannelColor, 0.32), + disabledBackground: alpha(theme.centerChannelColor, 0.08), + }, + tonalOffset: 0.05, + }, +}); + diff --git a/webapp/platform/components/src/compass/utils/color.ts b/webapp/platform/components/src/compass/utils/color.ts new file mode 100644 index 0000000000..bec38ea3a6 --- /dev/null +++ b/webapp/platform/components/src/compass/utils/color.ts @@ -0,0 +1,32 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {decomposeColor, hslToRgb} from '@mui/material'; +import {normal} from 'color-blend'; + +export const decomposeColorToRgb = (color: string) => { + if (color.startsWith('hsl')) { + return decomposeColor(hslToRgb(color)); + } + + return decomposeColor(color); +}; + +export const blend = (background: string, foreground: string) => { + const bg = decomposeColorToRgb(background); + const fg = decomposeColorToRgb(foreground); + + const blended = normal({ + r: bg.values[0], + g: bg.values[1], + b: bg.values[2], + a: bg.values[3] || 1, + }, { + r: fg.values[0], + g: fg.values[1], + b: fg.values[2], + a: fg.values[3] || 1, + }); + + return `rgba(${blended.r}, ${blended.g}, ${blended.b}, ${blended.a})`; +}; diff --git a/webapp/platform/components/src/compass/utils/font.ts b/webapp/platform/components/src/compass/utils/font.ts new file mode 100644 index 0000000000..e2dc37c64c --- /dev/null +++ b/webapp/platform/components/src/compass/utils/font.ts @@ -0,0 +1,5 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +export const getFontMargin = (fontSize: number, multiplier: number): number => + Math.max(Math.round((fontSize * multiplier) / 4) * 4, 8); diff --git a/webapp/platform/components/src/index.tsx b/webapp/platform/components/src/index.tsx index 2ead4f1994..925665a131 100644 --- a/webapp/platform/components/src/index.tsx +++ b/webapp/platform/components/src/index.tsx @@ -2,13 +2,14 @@ // See LICENSE.txt for license information. // type -export type {Props as GenericModalProps} from './generic_modal/generic_modal'; +export type {Props as LegacyGenericModalProps} from './legacy_generic_modal/legacy_generic_modal'; export type {CircleSkeletonLoaderProps, RectangleSkeletonLoaderProps} from './skeleton_loader'; export type {Props as FocusTrapProps} from './focus_trap'; // components -export * from './generic_modal/footer_content'; -export {GenericModal} from './generic_modal/generic_modal'; +export * from './compass'; +export * from './legacy_generic_modal/footer_content'; +export {LegacyGenericModal} from './legacy_generic_modal/legacy_generic_modal'; export {CircleSkeletonLoader, RectangleSkeletonLoader} from './skeleton_loader'; export * from './tour_tip'; export * from './pulsating_dot'; diff --git a/webapp/platform/components/src/generic_modal/footer_content/__snapshots__/footer_pagination.test.tsx.snap b/webapp/platform/components/src/legacy_generic_modal/footer_content/__snapshots__/footer_pagination.test.tsx.snap similarity index 100% rename from webapp/platform/components/src/generic_modal/footer_content/__snapshots__/footer_pagination.test.tsx.snap rename to webapp/platform/components/src/legacy_generic_modal/footer_content/__snapshots__/footer_pagination.test.tsx.snap diff --git a/webapp/platform/components/src/generic_modal/footer_content/footer_pagination.scss b/webapp/platform/components/src/legacy_generic_modal/footer_content/footer_pagination.scss similarity index 100% rename from webapp/platform/components/src/generic_modal/footer_content/footer_pagination.scss rename to webapp/platform/components/src/legacy_generic_modal/footer_content/footer_pagination.scss diff --git a/webapp/platform/components/src/generic_modal/footer_content/footer_pagination.test.tsx b/webapp/platform/components/src/legacy_generic_modal/footer_content/footer_pagination.test.tsx similarity index 100% rename from webapp/platform/components/src/generic_modal/footer_content/footer_pagination.test.tsx rename to webapp/platform/components/src/legacy_generic_modal/footer_content/footer_pagination.test.tsx diff --git a/webapp/platform/components/src/generic_modal/footer_content/footer_pagination.tsx b/webapp/platform/components/src/legacy_generic_modal/footer_content/footer_pagination.tsx similarity index 100% rename from webapp/platform/components/src/generic_modal/footer_content/footer_pagination.tsx rename to webapp/platform/components/src/legacy_generic_modal/footer_content/footer_pagination.tsx diff --git a/webapp/platform/components/src/generic_modal/footer_content/index.ts b/webapp/platform/components/src/legacy_generic_modal/footer_content/index.ts similarity index 100% rename from webapp/platform/components/src/generic_modal/footer_content/index.ts rename to webapp/platform/components/src/legacy_generic_modal/footer_content/index.ts diff --git a/webapp/platform/components/src/generic_modal/generic_modal.tsx b/webapp/platform/components/src/legacy_generic_modal/legacy_generic_modal.tsx similarity index 99% rename from webapp/platform/components/src/generic_modal/generic_modal.tsx rename to webapp/platform/components/src/legacy_generic_modal/legacy_generic_modal.tsx index 68272eb8f4..640c44c501 100644 --- a/webapp/platform/components/src/generic_modal/generic_modal.tsx +++ b/webapp/platform/components/src/legacy_generic_modal/legacy_generic_modal.tsx @@ -52,7 +52,7 @@ type State = { isFocalTrapActive: boolean; } -export class GenericModal extends React.PureComponent { +export class LegacyGenericModal extends React.PureComponent { static defaultProps: Partial = { show: true, id: 'genericModal', diff --git a/webapp/platform/types/src/theme.ts b/webapp/platform/types/src/theme.ts new file mode 100644 index 0000000000..f21cd5efaf --- /dev/null +++ b/webapp/platform/types/src/theme.ts @@ -0,0 +1,33 @@ +export type ThemeKey = 'denim' | 'sapphire' | 'quartz' | 'indigo' | 'onyx'; + +export type LegacyThemeType = 'Mattermost' | 'Organization' | 'Mattermost Dark' | 'Windows Dark'; + +export type ThemeType = 'Denim' | 'Sapphire' | 'Quartz' | 'Indigo' | 'Onyx'; + +export type Theme = { + type?: ThemeType | 'custom'; + sidebarBg: string; + sidebarText: string; + sidebarUnreadText: string; + sidebarTextHoverBg: string; + sidebarTextActiveBorder: string; + sidebarTextActiveColor: string; + sidebarHeaderBg: string; + sidebarTeamBarBg: string; + sidebarHeaderTextColor: string; + onlineIndicator: string; + awayIndicator: string; + dndIndicator: string; + mentionBg: string; + mentionColor: string; + centerChannelBg: string; + centerChannelColor: string; + newMessageSeparator: string; + linkColor: string; + buttonBg: string; + buttonColor: string; + errorTextColor: string; + mentionHighlightBg: string; + mentionHighlightLink: string; + codeTheme: string; +};