feat(ui-components) Add Dialog Component (#12260)
This commit is contained in:
parent
0d917df1fb
commit
bfa88f13dc
|
@ -3,9 +3,9 @@
|
||||||
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
import { AtlasKitThemeProvider } from '@atlaskit/theme';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { DialogContainer } from '../../base/dialog';
|
|
||||||
import GlobalStyles from '../../base/ui/components/GlobalStyles';
|
import GlobalStyles from '../../base/ui/components/GlobalStyles';
|
||||||
import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider.web';
|
import JitsiThemeProvider from '../../base/ui/components/JitsiThemeProvider.web';
|
||||||
|
import DialogContainer from '../../base/ui/components/web/DialogContainer';
|
||||||
import { ChromeExtensionBanner } from '../../chrome-extension-banner';
|
import { ChromeExtensionBanner } from '../../chrome-extension-banner';
|
||||||
|
|
||||||
import { AbstractApp } from './AbstractApp';
|
import { AbstractApp } from './AbstractApp';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
import type { Dispatch } from 'redux';
|
import { IStore } from '../../app/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HIDE_DIALOG,
|
HIDE_DIALOG,
|
||||||
|
@ -22,7 +22,7 @@ import { isDialogOpen } from './functions';
|
||||||
* component: (React.Component | undefined)
|
* component: (React.Component | undefined)
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function hideDialog(component: ?Object) {
|
export function hideDialog(component?: ComponentType) {
|
||||||
return {
|
return {
|
||||||
type: HIDE_DIALOG,
|
type: HIDE_DIALOG,
|
||||||
component
|
component
|
||||||
|
@ -54,7 +54,7 @@ export function hideSheet() {
|
||||||
* componentProps: (Object | undefined)
|
* componentProps: (Object | undefined)
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function openDialog(component: Object, componentProps: ?Object) {
|
export function openDialog(component: ComponentType, componentProps?: Object) {
|
||||||
return {
|
return {
|
||||||
type: OPEN_DIALOG,
|
type: OPEN_DIALOG,
|
||||||
component,
|
component,
|
||||||
|
@ -74,7 +74,7 @@ export function openDialog(component: Object, componentProps: ?Object) {
|
||||||
* componentProps: (Object | undefined)
|
* componentProps: (Object | undefined)
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function openSheet(component: Object, componentProps: ?Object) {
|
export function openSheet(component: ComponentType, componentProps?: Object) {
|
||||||
return {
|
return {
|
||||||
type: OPEN_SHEET,
|
type: OPEN_SHEET,
|
||||||
component,
|
component,
|
||||||
|
@ -92,8 +92,8 @@ export function openSheet(component: Object, componentProps: ?Object) {
|
||||||
* specified {@code component}.
|
* specified {@code component}.
|
||||||
* @returns {Function}
|
* @returns {Function}
|
||||||
*/
|
*/
|
||||||
export function toggleDialog(component: Object, componentProps: ?Object) {
|
export function toggleDialog(component: ComponentType, componentProps?: Object) {
|
||||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
return (dispatch: IStore['dispatch'], getState: IStore['getState']) => {
|
||||||
if (isDialogOpen(getState, component)) {
|
if (isDialogOpen(getState, component)) {
|
||||||
dispatch(hideDialog(component));
|
dispatch(hideDialog(component));
|
||||||
} else {
|
} else {
|
|
@ -1,34 +1,33 @@
|
||||||
/* @flow */
|
import React, { Component, ComponentType } from 'react';
|
||||||
|
|
||||||
import React, { Component } from 'react';
|
import { IState } from '../../../app/types';
|
||||||
|
import { ReactionEmojiProps } from '../../../reactions/constants';
|
||||||
import { type ReactionEmojiProps } from '../../../reactions/constants';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of {@link DialogContainer}.
|
* The type of the React {@code Component} props of {@link DialogContainer}.
|
||||||
*/
|
*/
|
||||||
type Props = {
|
interface Props {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The component to render.
|
* The component to render.
|
||||||
*/
|
*/
|
||||||
_component: Function,
|
_component: ComponentType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The props to pass to the component that will be rendered.
|
* The props to pass to the component that will be rendered.
|
||||||
*/
|
*/
|
||||||
_componentProps: Object,
|
_componentProps: Object;
|
||||||
|
|
||||||
/**
|
|
||||||
* True if the UI is in a compact state where we don't show dialogs.
|
|
||||||
*/
|
|
||||||
_reducedUI: boolean,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of reactions to be displayed.
|
* Array of reactions to be displayed.
|
||||||
*/
|
*/
|
||||||
_reactionsQueue: Array<ReactionEmojiProps>
|
_reactionsQueue: Array<ReactionEmojiProps>;
|
||||||
};
|
|
||||||
|
/**
|
||||||
|
* True if the UI is in a compact state where we don't show dialogs.
|
||||||
|
*/
|
||||||
|
_reducedUI: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a DialogContainer responsible for showing all dialogs.
|
* Implements a DialogContainer responsible for showing all dialogs.
|
||||||
|
@ -61,7 +60,7 @@ export default class AbstractDialogContainer extends Component<Props> {
|
||||||
* @private
|
* @private
|
||||||
* @returns {Props}
|
* @returns {Props}
|
||||||
*/
|
*/
|
||||||
export function abstractMapStateToProps(state: Object): $Shape<Props> {
|
export function abstractMapStateToProps(state: IState) {
|
||||||
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
||||||
const { reducedUI } = state['features/base/responsive-ui'];
|
const { reducedUI } = state['features/base/responsive-ui'];
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import { ModalTransition } from '@atlaskit/modal-dialog';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { connect } from '../../../redux';
|
|
||||||
import AbstractDialogContainer, {
|
|
||||||
abstractMapStateToProps
|
|
||||||
} from '../AbstractDialogContainer';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Implements a DialogContainer responsible for showing all dialogs. Necessary
|
|
||||||
* for supporting @atlaskit's modal animations.
|
|
||||||
*
|
|
||||||
* @augments AbstractDialogContainer
|
|
||||||
*/
|
|
||||||
class DialogContainer extends AbstractDialogContainer {
|
|
||||||
/**
|
|
||||||
* Implements React's {@link Component#render()}.
|
|
||||||
*
|
|
||||||
* @inheritdoc
|
|
||||||
* @returns {ReactElement}
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<ModalTransition>
|
|
||||||
{ this._renderDialogContent() }
|
|
||||||
</ModalTransition>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(abstractMapStateToProps)(DialogContainer);
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
export { default as AbstractDialogTab } from './AbstractDialogTab';
|
export { default as AbstractDialogTab } from './AbstractDialogTab';
|
||||||
export type { Props as AbstractDialogTabProps } from './AbstractDialogTab';
|
export type { Props as AbstractDialogTabProps } from './AbstractDialogTab';
|
||||||
export { default as Dialog } from './Dialog';
|
export { default as Dialog } from './Dialog-old';
|
||||||
export { default as DialogContainer } from './DialogContainer';
|
|
||||||
export { default as DialogWithTabs } from './DialogWithTabs';
|
export { default as DialogWithTabs } from './DialogWithTabs';
|
||||||
export { default as StatelessDialog } from './StatelessDialog';
|
export { default as StatelessDialog } from './StatelessDialog';
|
||||||
|
export { default as DialogContainer } from '../../../ui/components/web/DialogContainer';
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
/* @flow */
|
import { ComponentType } from 'react';
|
||||||
|
|
||||||
|
import { IState } from '../../app/types';
|
||||||
|
import { IStateful } from '../app/types';
|
||||||
|
// eslint-disable-next-line lines-around-comment
|
||||||
|
// @ts-ignore
|
||||||
import { ColorSchemeRegistry } from '../color-scheme';
|
import { ColorSchemeRegistry } from '../color-scheme';
|
||||||
import { toState } from '../redux';
|
import { toState } from '../redux/functions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if any {@code Dialog} is currently open.
|
* Checks if any {@code Dialog} is currently open.
|
||||||
*
|
*
|
||||||
* @param {Function|Object} stateful - The redux store, the redux
|
* @param {IStateful} stateful - The redux store, the redux
|
||||||
* {@code getState} function, or the redux state itself.
|
* {@code getState} function, or the redux state itself.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isAnyDialogOpen(stateful: Function) {
|
export function isAnyDialogOpen(stateful: IStateful) {
|
||||||
return Boolean(toState(stateful)['features/base/dialog'].component);
|
return Boolean(toState(stateful)['features/base/dialog'].component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,25 +22,25 @@ export function isAnyDialogOpen(stateful: Function) {
|
||||||
* Checks if a {@code Dialog} with a specific {@code component} is currently
|
* Checks if a {@code Dialog} with a specific {@code component} is currently
|
||||||
* open.
|
* open.
|
||||||
*
|
*
|
||||||
* @param {Function|Object} stateful - The redux store, the redux
|
* @param {IStateful} stateful - The redux store, the redux
|
||||||
* {@code getState} function, or the redux state itself.
|
* {@code getState} function, or the redux state itself.
|
||||||
* @param {React.Component} component - The {@code component} of a
|
* @param {React.Component} component - The {@code component} of a
|
||||||
* {@code Dialog} to be checked.
|
* {@code Dialog} to be checked.
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export function isDialogOpen(stateful: Function | Object, component: Object) {
|
export function isDialogOpen(stateful: IStateful, component: ComponentType) {
|
||||||
return toState(stateful)['features/base/dialog'].component === component;
|
return toState(stateful)['features/base/dialog'].component === component;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maps part of the Redux state to the props of any Dialog based component.
|
* Maps part of the Redux state to the props of any Dialog based component.
|
||||||
*
|
*
|
||||||
* @param {Object} state - The Redux state.
|
* @param {IState} state - The Redux state.
|
||||||
* @returns {{
|
* @returns {{
|
||||||
* _dialogStyles: StyleType
|
* _dialogStyles: StyleType
|
||||||
* }}
|
* }}
|
||||||
*/
|
*/
|
||||||
export function _abstractMapStateToProps(state: Object): Object {
|
export function _abstractMapStateToProps(state: IState) {
|
||||||
return {
|
return {
|
||||||
_dialogStyles: ColorSchemeRegistry.get(state, 'Dialog')
|
_dialogStyles: ColorSchemeRegistry.get(state, 'Dialog')
|
||||||
};
|
};
|
|
@ -24,6 +24,11 @@ const useStyles = makeStyles()((theme: Theme) => {
|
||||||
backgroundColor: theme.palette.ui02
|
backgroundColor: theme.palette.ui02
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'&:focus': {
|
||||||
|
outline: 0,
|
||||||
|
boxShadow: `0px 0px 0px 2px ${theme.palette.focus01}`
|
||||||
|
},
|
||||||
|
|
||||||
'&:active': {
|
'&:active': {
|
||||||
backgroundColor: theme.palette.ui03
|
backgroundColor: theme.palette.ui03
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,250 @@
|
||||||
|
import { Theme } from '@mui/material';
|
||||||
|
import React, { ReactElement, useCallback, useContext, useEffect } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { keyframes } from 'tss-react';
|
||||||
|
import { makeStyles } from 'tss-react/mui';
|
||||||
|
|
||||||
|
import { IconClose } from '../../../icons/svg';
|
||||||
|
import { withPixelLineHeight } from '../../../styles/functions.web';
|
||||||
|
|
||||||
|
import Button from './Button';
|
||||||
|
import ClickableIcon from './ClickableIcon';
|
||||||
|
import { DialogTransitionContext } from './DialogTransition';
|
||||||
|
|
||||||
|
|
||||||
|
const useStyles = makeStyles()((theme: Theme) => {
|
||||||
|
return {
|
||||||
|
container: {
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
position: 'fixed',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
animation: `${keyframes`
|
||||||
|
0% {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
`} 0.2s forwards ease-out`,
|
||||||
|
|
||||||
|
'&.unmount': {
|
||||||
|
animation: `${keyframes`
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
`} 0.15s forwards ease-in`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
backdrop: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
backgroundColor: theme.palette.ui02,
|
||||||
|
opacity: 0.75
|
||||||
|
},
|
||||||
|
|
||||||
|
modal: {
|
||||||
|
zIndex: 1,
|
||||||
|
backgroundColor: theme.palette.ui01,
|
||||||
|
border: `1px solid ${theme.palette.ui03}`,
|
||||||
|
boxShadow: '0px 4px 25px 4px rgba(20, 20, 20, 0.6)',
|
||||||
|
borderRadius: `${theme.shape.borderRadius}px`,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
height: 'auto',
|
||||||
|
minHeight: '200px',
|
||||||
|
maxHeight: '560px',
|
||||||
|
marginTop: '64px',
|
||||||
|
animation: `${keyframes`
|
||||||
|
0% {
|
||||||
|
margin-top: 85px
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
margin-top: 64px
|
||||||
|
}
|
||||||
|
`} 0.2s forwards ease-out`,
|
||||||
|
|
||||||
|
'&.medium': {
|
||||||
|
width: '400px'
|
||||||
|
},
|
||||||
|
|
||||||
|
'&.large': {
|
||||||
|
width: '664px'
|
||||||
|
},
|
||||||
|
|
||||||
|
'&.unmount': {
|
||||||
|
animation: `${keyframes`
|
||||||
|
0% {
|
||||||
|
margin-top: 64px
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
margin-top: 40px
|
||||||
|
}
|
||||||
|
`} 0.15s forwards ease-in`
|
||||||
|
},
|
||||||
|
|
||||||
|
'@media (max-width: 448px)': {
|
||||||
|
width: '100% !important',
|
||||||
|
maxHeight: 'initial',
|
||||||
|
height: '100%',
|
||||||
|
margin: 0,
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
animation: `${keyframes`
|
||||||
|
0% {
|
||||||
|
margin-top: 15px
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
margin-top: 0
|
||||||
|
}
|
||||||
|
`} 0.2s forwards ease-out`,
|
||||||
|
|
||||||
|
'&.unmount': {
|
||||||
|
animation: `${keyframes`
|
||||||
|
0% {
|
||||||
|
margin-top: 0
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
margin-top: 15px
|
||||||
|
}
|
||||||
|
`} 0.15s forwards ease-in`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
header: {
|
||||||
|
width: '100%',
|
||||||
|
padding: '24px',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'flex-start',
|
||||||
|
justifyContent: 'space-between'
|
||||||
|
},
|
||||||
|
|
||||||
|
title: {
|
||||||
|
color: theme.palette.text01,
|
||||||
|
...withPixelLineHeight(theme.typography.heading5),
|
||||||
|
margin: 0,
|
||||||
|
padding: 0
|
||||||
|
},
|
||||||
|
|
||||||
|
content: {
|
||||||
|
height: 'auto',
|
||||||
|
overflowY: 'auto',
|
||||||
|
width: '100%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
padding: '0 24px',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
|
||||||
|
'@media (max-width: 448px)': {
|
||||||
|
height: '100%'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
footer: {
|
||||||
|
width: '100%',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
padding: '24px',
|
||||||
|
|
||||||
|
'& button:last-child': {
|
||||||
|
marginLeft: '16px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
interface DialogProps {
|
||||||
|
cancelKey?: string;
|
||||||
|
children?: ReactElement | ReactElement[];
|
||||||
|
description?: string;
|
||||||
|
ok?: {
|
||||||
|
disabled?: boolean;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
onCancel: () => void;
|
||||||
|
onSubmit?: () => void;
|
||||||
|
size?: 'large' | 'medium';
|
||||||
|
title?: string;
|
||||||
|
titleKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Dialog = ({
|
||||||
|
title,
|
||||||
|
titleKey,
|
||||||
|
description,
|
||||||
|
size = 'medium',
|
||||||
|
onCancel,
|
||||||
|
children,
|
||||||
|
ok,
|
||||||
|
cancelKey,
|
||||||
|
onSubmit
|
||||||
|
}: DialogProps) => {
|
||||||
|
const { classes, cx } = useStyles();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { isUnmounting } = useContext(DialogTransitionContext);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className = { cx(classes.container, isUnmounting && 'unmount') }>
|
||||||
|
<div
|
||||||
|
className = { classes.backdrop }
|
||||||
|
onClick = { onCancel } />
|
||||||
|
<div
|
||||||
|
aria-describedby = { description }
|
||||||
|
aria-labelledby = { title ?? t(titleKey ?? '') }
|
||||||
|
aria-modal = { true }
|
||||||
|
className = { cx(classes.modal, isUnmounting && 'unmount', size) }
|
||||||
|
role = 'dialog'>
|
||||||
|
<div className = { classes.header }>
|
||||||
|
<p className = { classes.title }>{title ?? t(titleKey ?? '')}</p>
|
||||||
|
<ClickableIcon
|
||||||
|
accessibilityLabel = { t('dialog.close') }
|
||||||
|
icon = { IconClose }
|
||||||
|
onClick = { onCancel } />
|
||||||
|
</div>
|
||||||
|
<div className = { classes.content }>{children}</div>
|
||||||
|
<div className = { classes.footer }>
|
||||||
|
{cancelKey && <Button
|
||||||
|
accessibilityLabel = { t(cancelKey) }
|
||||||
|
labelKey = { cancelKey }
|
||||||
|
onClick = { onCancel }
|
||||||
|
type = 'tertiary' />}
|
||||||
|
{ok && <Button
|
||||||
|
accessibilityLabel = { t(ok.key) }
|
||||||
|
disabled = { ok.disabled }
|
||||||
|
labelKey = { ok.key }
|
||||||
|
onClick = { onSubmit } />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Dialog;
|
|
@ -0,0 +1,138 @@
|
||||||
|
import { ModalTransition } from '@atlaskit/modal-dialog';
|
||||||
|
import React, { Component, ComponentType } from 'react';
|
||||||
|
|
||||||
|
import { IState } from '../../../../app/types';
|
||||||
|
import KeyboardShortcutsDialog from '../../../../keyboard-shortcuts/components/web/KeyboardShortcutsDialog';
|
||||||
|
import { ReactionEmojiProps } from '../../../../reactions/constants';
|
||||||
|
import { connect } from '../../../redux/functions';
|
||||||
|
|
||||||
|
import DialogTransition from './DialogTransition';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component to render.
|
||||||
|
*/
|
||||||
|
_component: ComponentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The props to pass to the component that will be rendered.
|
||||||
|
*/
|
||||||
|
_componentProps: Object;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of reactions to be displayed.
|
||||||
|
*/
|
||||||
|
_reactionsQueue: Array<ReactionEmojiProps>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if the UI is in a compact state where we don't show dialogs.
|
||||||
|
*/
|
||||||
|
_reducedUI: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function is necessary while the transition from @atlaskit dialog to our component is ongoing.
|
||||||
|
const isNewDialog = (component: any) => {
|
||||||
|
const list = [ KeyboardShortcutsDialog ];
|
||||||
|
|
||||||
|
return Boolean(list.find(comp => comp === component));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Needed for the transition to our component.
|
||||||
|
type State = {
|
||||||
|
isNewDialog: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements a DialogContainer responsible for showing all dialogs. Necessary
|
||||||
|
* for supporting @atlaskit's modal animations.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class DialogContainer extends Component<Props, State> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a new {@code DialogContainer} instance.
|
||||||
|
*
|
||||||
|
* @param {Props} props - The React {@code Component} props to initialize
|
||||||
|
* the new {@code DialogContainer} instance with.
|
||||||
|
*/
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isNewDialog: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check which Dialog container to render.
|
||||||
|
* Needed during transition from atlaskit.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
componentDidUpdate(prevProps: Props) {
|
||||||
|
if (this.props._component && prevProps._component !== this.props._component) {
|
||||||
|
// eslint-disable-next-line react/no-did-update-set-state
|
||||||
|
this.setState({
|
||||||
|
isNewDialog: isNewDialog(this.props._component)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the dialog to be displayed.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {ReactElement|null}
|
||||||
|
*/
|
||||||
|
_renderDialogContent() {
|
||||||
|
const {
|
||||||
|
_component: component,
|
||||||
|
_reducedUI: reducedUI
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
component && !reducedUI
|
||||||
|
? React.createElement(component, this.props._componentProps)
|
||||||
|
: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements React's {@link Component#render()}.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @returns {ReactElement}
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
return this.state.isNewDialog ? (
|
||||||
|
<DialogTransition>
|
||||||
|
{this._renderDialogContent()}
|
||||||
|
</DialogTransition>
|
||||||
|
) : (
|
||||||
|
<ModalTransition>
|
||||||
|
{ this._renderDialogContent() }
|
||||||
|
</ModalTransition>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps (parts of) the redux state to the associated
|
||||||
|
* {@code AbstractDialogContainer}'s props.
|
||||||
|
*
|
||||||
|
* @param {Object} state - The redux state.
|
||||||
|
* @private
|
||||||
|
* @returns {Props}
|
||||||
|
*/
|
||||||
|
function mapStateToProps(state: IState) {
|
||||||
|
const stateFeaturesBaseDialog = state['features/base/dialog'];
|
||||||
|
const { reducedUI } = state['features/base/responsive-ui'];
|
||||||
|
|
||||||
|
return {
|
||||||
|
_component: stateFeaturesBaseDialog.component,
|
||||||
|
_componentProps: stateFeaturesBaseDialog.componentProps,
|
||||||
|
_reducedUI: reducedUI
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(DialogContainer);
|
|
@ -0,0 +1,28 @@
|
||||||
|
import React, { ReactElement, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const DialogTransitionContext = React.createContext({ isUnmounting: false });
|
||||||
|
|
||||||
|
const DialogTransition = ({ children }: { children: ReactElement | null; }) => {
|
||||||
|
const [ childrenToRender, setChildrenToRender ] = useState(children);
|
||||||
|
const [ isUnmounting, setIsUnmounting ] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (children === null) {
|
||||||
|
setIsUnmounting(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setChildrenToRender(children);
|
||||||
|
setIsUnmounting(false);
|
||||||
|
}, 150);
|
||||||
|
} else {
|
||||||
|
setChildrenToRender(children);
|
||||||
|
}
|
||||||
|
}, [ children ]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogTransitionContext.Provider value = {{ isUnmounting }}>
|
||||||
|
{childrenToRender}
|
||||||
|
</DialogTransitionContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DialogTransition;
|
|
@ -3,10 +3,12 @@ import { withStyles } from '@mui/styles';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import { WithTranslation } from 'react-i18next';
|
import { WithTranslation } from 'react-i18next';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
// @ts-ignore
|
import { IStore } from '../../../app/types';
|
||||||
import { Dialog } from '../../../base/dialog';
|
import { hideDialog } from '../../../base/dialog/actions';
|
||||||
import { translate } from '../../../base/i18n/functions';
|
import { translate } from '../../../base/i18n/functions';
|
||||||
|
import Dialog from '../../../base/ui/components/web/Dialog';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of the React {@code Component} props of
|
* The type of the React {@code Component} props of
|
||||||
|
@ -14,6 +16,11 @@ import { translate } from '../../../base/i18n/functions';
|
||||||
*/
|
*/
|
||||||
interface Props extends WithTranslation {
|
interface Props extends WithTranslation {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches close dialog.
|
||||||
|
*/
|
||||||
|
_onCloseDialog: () => void;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object containing the CSS classes.
|
* An object containing the CSS classes.
|
||||||
*/
|
*/
|
||||||
|
@ -73,10 +80,8 @@ class KeyboardShortcutsDialog extends Component<Props> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
cancelKey = { 'dialog.close' }
|
onCancel = { this.props._onCloseDialog }
|
||||||
submitDisabled = { true }
|
titleKey = 'keyboardShortcuts.keyboardShortcuts'>
|
||||||
titleKey = 'keyboardShortcuts.keyboardShortcuts'
|
|
||||||
width = 'small'>
|
|
||||||
<div
|
<div
|
||||||
id = 'keyboard-shortcuts'>
|
id = 'keyboard-shortcuts'>
|
||||||
<ul
|
<ul
|
||||||
|
@ -125,4 +130,16 @@ class KeyboardShortcutsDialog extends Component<Props> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default translate(withStyles(styles)(KeyboardShortcutsDialog));
|
/**
|
||||||
|
* Function that maps parts of Redux actions into component props.
|
||||||
|
*
|
||||||
|
* @param {Object} dispatch - Redux dispatch.
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
function mapDispatchToProps(dispatch: IStore['dispatch']) {
|
||||||
|
return {
|
||||||
|
_onCloseDialog: () => dispatch(hideDialog())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(translate(withStyles(styles)(KeyboardShortcutsDialog)));
|
||||||
|
|
Loading…
Reference in New Issue