Merge branch 'master' into MM-50966-in-product-expansion
Этот коммит содержится в:
@@ -26,6 +26,7 @@ import {
|
||||
CreateSubscriptionRequest,
|
||||
Feedback,
|
||||
WorkspaceDeletionRequest,
|
||||
NewsletterRequestBody,
|
||||
} from '@mattermost/types/cloud';
|
||||
import {
|
||||
SelfHostedSignupForm,
|
||||
@@ -3894,6 +3895,7 @@ export default class Client4 {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
confirmSelfHostedExpansion = (setupIntentId: string, expandRequest: SelfHostedExpansionRequest) => {
|
||||
return this.doFetch<SelfHostedSignupSuccessResponse>(
|
||||
`${this.getHostedCustomerRoute()}/confirm?expand=true`,
|
||||
@@ -3901,6 +3903,13 @@ export default class Client4 {
|
||||
);
|
||||
}
|
||||
|
||||
subscribeToNewsletter = (newletterRequestBody: NewsletterRequestBody) => {
|
||||
return this.doFetch<StatusOK>(
|
||||
`${this.getHostedCustomerRoute()}/subscribe-newsletter`,
|
||||
{method: 'post', body: JSON.stringify(newletterRequestBody)},
|
||||
);
|
||||
};
|
||||
|
||||
createPaymentMethod = async () => {
|
||||
return this.doFetch(
|
||||
`${this.getCloudRoute()}/payment`,
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {useEffect, useState} from 'react';
|
||||
|
||||
export const useFollowElementDimensions = (elementId: string): DOMRectReadOnly => {
|
||||
const [dimensions, setDimensions] = useState(new DOMRect());
|
||||
|
||||
useEffect(() => {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) {
|
||||
return undefined;
|
||||
}
|
||||
const observer = new ResizeObserver((entries) => {
|
||||
if (entries.length > 0) {
|
||||
setDimensions(entries[0].contentRect);
|
||||
}
|
||||
});
|
||||
observer.observe(element);
|
||||
|
||||
return () => {
|
||||
observer.unobserve(element);
|
||||
};
|
||||
}, [elementId]);
|
||||
|
||||
return dimensions;
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/GenericModal/FooterPagination should render default 1`] = `
|
||||
<div
|
||||
className="footer-pagination"
|
||||
>
|
||||
<div
|
||||
className="footer-pagination__legend"
|
||||
/>
|
||||
<div
|
||||
className="footer-pagination__button-container"
|
||||
>
|
||||
<button
|
||||
className="footer-pagination__button-container__button disabled"
|
||||
disabled={true}
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
<ChevronLeftIcon
|
||||
size={16}
|
||||
/>
|
||||
<span>
|
||||
Previous
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="footer-pagination__button-container__button disabled"
|
||||
disabled={true}
|
||||
onClick={[MockFunction]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Next
|
||||
</span>
|
||||
<ChevronRightIcon
|
||||
size={16}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
@import '../../../../../channels/src/sass/utils/mixins';
|
||||
|
||||
.footer-pagination {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
&__legend {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
color: rgba(var(--center-channel-color-rgb), 0.64);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
}
|
||||
|
||||
&__button-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&__button {
|
||||
@include tertiary-button;
|
||||
@include button-small;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
> :not(:first-child) {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
|
||||
import {FooterPagination} from './';
|
||||
|
||||
describe('components/GenericModal/FooterPagination', () => {
|
||||
const baseProps = {
|
||||
page: 0,
|
||||
total: 0,
|
||||
itemsPerPage: 0,
|
||||
onNextPage: jest.fn(),
|
||||
onPreviousPage: jest.fn(),
|
||||
};
|
||||
|
||||
test('should render default', () => {
|
||||
const wrapper = shallow(
|
||||
<FooterPagination {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should render pagination legend', () => {
|
||||
const wrapper = shallow(
|
||||
<FooterPagination
|
||||
{...baseProps}
|
||||
page={0}
|
||||
total={17}
|
||||
itemsPerPage={10}
|
||||
/>,
|
||||
);
|
||||
|
||||
const legend = wrapper.find('.footer-pagination__legend');
|
||||
|
||||
expect(legend.length).toEqual(1);
|
||||
expect(legend.at(0).text()).toEqual('Showing 1-10 of 17');
|
||||
});
|
||||
|
||||
test('should render pagination buttons', () => {
|
||||
const wrapper = shallow(
|
||||
<FooterPagination
|
||||
{...baseProps}
|
||||
page={1}
|
||||
total={30}
|
||||
itemsPerPage={10}
|
||||
/>,
|
||||
);
|
||||
|
||||
const buttons = wrapper.find('.footer-pagination__button-container__button');
|
||||
|
||||
expect(buttons.length).toEqual(2);
|
||||
expect(buttons.at(0).text()).toEqual('<ChevronLeftIcon />Previous');
|
||||
expect(buttons.at(1).text()).toEqual('Next<ChevronRightIcon />');
|
||||
});
|
||||
|
||||
test('should handle pagination buttons', async () => {
|
||||
const onPreviousPage = jest.fn();
|
||||
const onNextPage = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
<FooterPagination
|
||||
page={1}
|
||||
total={30}
|
||||
itemsPerPage={10}
|
||||
onPreviousPage={onPreviousPage}
|
||||
onNextPage={onNextPage}
|
||||
/>,
|
||||
);
|
||||
|
||||
const buttons = wrapper.find('.footer-pagination__button-container__button');
|
||||
const prevButton = buttons.at(0);
|
||||
const nextButton = buttons.at(1);
|
||||
|
||||
expect(prevButton.hasClass('disabled')).toBeFalsy();
|
||||
expect(nextButton.hasClass('disabled')).toBeFalsy();
|
||||
|
||||
nextButton.simulate('click');
|
||||
|
||||
expect(onNextPage).toHaveBeenCalledTimes(1);
|
||||
|
||||
prevButton.simulate('click');
|
||||
|
||||
expect(onPreviousPage).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
|
||||
import {ChevronLeftIcon, ChevronRightIcon} from '@mattermost/compass-icons/components';
|
||||
|
||||
import './footer_pagination.scss';
|
||||
|
||||
const BUTTON_ICON_SIZE = 16;
|
||||
|
||||
type FooterPaginationProps = {
|
||||
page: number;
|
||||
total: number;
|
||||
itemsPerPage: number;
|
||||
onNextPage: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
onPreviousPage: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
|
||||
};
|
||||
|
||||
export const FooterPagination = ({
|
||||
page,
|
||||
total,
|
||||
itemsPerPage,
|
||||
onNextPage,
|
||||
onPreviousPage,
|
||||
}: FooterPaginationProps) => {
|
||||
const {formatMessage} = useIntl();
|
||||
|
||||
const startCount = page * itemsPerPage;
|
||||
const endCount = Math.min(startCount + itemsPerPage, total);
|
||||
const totalPages = Math.trunc((total - 1) / itemsPerPage);
|
||||
|
||||
const prevDisabled = page <= 0;
|
||||
const nextDisabled = page >= totalPages;
|
||||
|
||||
return (
|
||||
<div className='footer-pagination'>
|
||||
<div className='footer-pagination__legend'>
|
||||
{Boolean(total) && (
|
||||
formatMessage(
|
||||
{
|
||||
id: 'footer_pagination.count',
|
||||
defaultMessage: 'Showing {startCount, number}-{endCount, number} of {total, number}',
|
||||
},
|
||||
{
|
||||
startCount: startCount + 1,
|
||||
endCount,
|
||||
total,
|
||||
},
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
<div className='footer-pagination__button-container'>
|
||||
<button
|
||||
type='button'
|
||||
className={classNames(
|
||||
'footer-pagination__button-container__button',
|
||||
{disabled: prevDisabled},
|
||||
)}
|
||||
onClick={onPreviousPage}
|
||||
disabled={prevDisabled}
|
||||
>
|
||||
<ChevronLeftIcon size={BUTTON_ICON_SIZE}/>
|
||||
<span>
|
||||
{formatMessage({
|
||||
id: 'footer_pagination.prev',
|
||||
defaultMessage: 'Previous',
|
||||
})}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
className={classNames(
|
||||
'footer-pagination__button-container__button',
|
||||
{disabled: nextDisabled},
|
||||
)}
|
||||
onClick={onNextPage}
|
||||
disabled={nextDisabled}
|
||||
>
|
||||
<span>
|
||||
{formatMessage({
|
||||
id: 'footer_pagination.next',
|
||||
defaultMessage: 'Next',
|
||||
})}
|
||||
</span>
|
||||
<ChevronRightIcon size={BUTTON_ICON_SIZE}/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export * from './footer_pagination';
|
||||
@@ -34,13 +34,17 @@ export type Props = {
|
||||
enforceFocus?: boolean;
|
||||
container?: React.ReactNode | React.ReactNodeArray;
|
||||
ariaLabel?: string;
|
||||
errorText?: string;
|
||||
errorText?: string | React.ReactNode;
|
||||
compassDesign?: boolean;
|
||||
backdrop?: boolean;
|
||||
backdropClassName?: string;
|
||||
tabIndex?: number;
|
||||
children: React.ReactNode;
|
||||
keyboardEscape?: boolean;
|
||||
headerInput?: React.ReactNode;
|
||||
bodyPadding?: boolean;
|
||||
footerContent?: React.ReactNode;
|
||||
footerDivider?: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -56,6 +60,7 @@ export class GenericModal extends React.PureComponent<Props, State> {
|
||||
autoCloseOnConfirmButton: true,
|
||||
enforceFocus: true,
|
||||
keyboardEscape: true,
|
||||
bodyPadding: true,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
@@ -195,7 +200,12 @@ export class GenericModal extends React.PureComponent<Props, State> {
|
||||
className='GenericModal__wrapper-enter-key-press-catcher'
|
||||
>
|
||||
<Modal.Header closeButton={true}>
|
||||
{this.props.compassDesign && headerText}
|
||||
{this.props.compassDesign && (
|
||||
<>
|
||||
{headerText}
|
||||
{this.props.headerInput}
|
||||
</>
|
||||
)}
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{this.props.compassDesign ? (
|
||||
@@ -208,14 +218,22 @@ export class GenericModal extends React.PureComponent<Props, State> {
|
||||
) : (
|
||||
headerText
|
||||
)}
|
||||
<div className='GenericModal__body'>
|
||||
<div className={classNames('GenericModal__body', {padding: this.props.bodyPadding})}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</Modal.Body>
|
||||
{(cancelButton || confirmButton) && <Modal.Footer>
|
||||
{cancelButton}
|
||||
{confirmButton}
|
||||
</Modal.Footer>}
|
||||
{(cancelButton || confirmButton || this.props.footerContent) && (
|
||||
<Modal.Footer className={classNames({divider: this.props.footerDivider})}>
|
||||
{(cancelButton || confirmButton) ? (
|
||||
<>
|
||||
{cancelButton}
|
||||
{confirmButton}
|
||||
</>
|
||||
) : (
|
||||
this.props.footerContent
|
||||
)}
|
||||
</Modal.Footer>
|
||||
)}
|
||||
</div>
|
||||
</FocusTrap>
|
||||
</Modal>
|
||||
|
||||
@@ -7,6 +7,7 @@ export type {CircleSkeletonLoaderProps, RectangleSkeletonLoaderProps} from './sk
|
||||
export type {Props as FocusTrapProps} from './focus_trap';
|
||||
|
||||
// components
|
||||
export * from './generic_modal/footer_content';
|
||||
export {GenericModal} from './generic_modal/generic_modal';
|
||||
export {CircleSkeletonLoader, RectangleSkeletonLoader} from './skeleton_loader';
|
||||
export * from './tour_tip';
|
||||
@@ -16,3 +17,4 @@ export {FocusTrap} from './focus_trap';
|
||||
// hooks
|
||||
export * from './common/hooks/useMeasurePunchouts';
|
||||
export {useElementAvailable} from './common/hooks/useElementAvailable';
|
||||
export {useFollowElementDimensions} from './common/hooks/useFollowElementDimensions';
|
||||
|
||||
@@ -363,7 +363,7 @@
|
||||
|
||||
&__backdrop {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
|
||||
@@ -90,15 +90,10 @@ export const TourTip = ({
|
||||
}: Props) => {
|
||||
const FIRST_STEP_INDEX = 0;
|
||||
const triggerRef = useRef(null);
|
||||
const [useBackdrop, setUseBackdrop] = useState(!hideBackdrop);
|
||||
const onJump = (event: React.MouseEvent, jumpToStep: number) => {
|
||||
handleJump?.(event, jumpToStep);
|
||||
};
|
||||
|
||||
useClickOutsideRef(triggerRef, () => {
|
||||
setUseBackdrop(false);
|
||||
});
|
||||
|
||||
// This needs to be changed if root-portal node isn't available to maybe body
|
||||
const rootPortal = document.getElementById('root-portal');
|
||||
|
||||
@@ -214,7 +209,7 @@ export const TourTip = ({
|
||||
>
|
||||
<PulsatingDot/>
|
||||
</div>
|
||||
{useBackdrop && <TourTipBackdrop
|
||||
<TourTipBackdrop
|
||||
show={show}
|
||||
onDismiss={handleDismiss}
|
||||
onPunchOut={handlePunchOut}
|
||||
@@ -222,7 +217,7 @@ export const TourTip = ({
|
||||
overlayPunchOut={overlayPunchOut}
|
||||
appendTo={rootPortal!}
|
||||
transparent={hideBackdrop}
|
||||
/>}
|
||||
/>
|
||||
{show && (
|
||||
<Tippy
|
||||
showOnCreate={show}
|
||||
|
||||
@@ -81,4 +81,3 @@ export const TourTipBackdrop = ({
|
||||
</TourTipRootPortal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -220,6 +220,11 @@ export interface CreateSubscriptionRequest {
|
||||
internal_purchase_order?: string;
|
||||
}
|
||||
|
||||
export interface NewsletterRequestBody {
|
||||
email: string;
|
||||
subscribed_content: string;
|
||||
}
|
||||
|
||||
export const areShippingDetailsValid = (address: Address | null | undefined): boolean => {
|
||||
if (!address) {
|
||||
return false;
|
||||
|
||||
@@ -116,6 +116,7 @@ export type ClientConfig = {
|
||||
ExperimentalViewArchivedChannels: string;
|
||||
FileLevel: string;
|
||||
FeatureFlagAppsEnabled: string;
|
||||
FeatureFlagAppsSidebarCategory: string;
|
||||
FeatureFlagBoardsProduct: string;
|
||||
FeatureFlagCallsEnabled: string;
|
||||
FeatureFlagGraphQL: string;
|
||||
|
||||
Ссылка в новой задаче
Block a user