UNPKG

@sap-ux/ui-components

Version:

SAP UI Components Library

287 lines 10.7 kB
import React from 'react'; import { Dialog as BaseDialog, DialogFooter } from '@fluentui/react'; import { UIDefaultButton } from '../UIButton/index.js'; import { deepMerge } from '../../utilities/DeepMerge.js'; import '../../styles/_shadows.scss'; export const DIALOG_MAX_HEIGHT_OFFSET = 32; export const DIALOG_STYLES = { background: 'var(--vscode-editorWidget-background)', boxShadow: 'var(--ui-box-shadow-medium)', borderColor: 'var(--vscode-editorWidget-border)', borderRadius: 'var(--vscode-cornerRadius-small, 4px)', vPadding: 20, vPaddingHalf: 10, hPadding: 45, title: { color: 'var(--vscode-panelTitle-activeForeground)' }, contentText: { fontSize: 13, color: 'var(--vscode-foreground)' }, modalOverlay: { background: 'var(--vscode-editor-background)', opacity: 0.8 } }; export var UIDialogScrollArea; (function (UIDialogScrollArea) { // Header and footer freezed and content is scrollable UIDialogScrollArea["Content"] = "Content"; // Whole dialog is scrollable - default FluentUI behaviour, but it is buggy with large content UIDialogScrollArea["Dialog"] = "Dialog"; })(UIDialogScrollArea || (UIDialogScrollArea = {})); /** * UIDialog component. * based on https://developer.microsoft.com/en-us/fluentui#/controls/web/dialog * and https://app.abstract.com/projects/42046ab5-889d-4a59-88e6-1089e98cba67/branches/master/commits/f1fcd77447bb2b32aab3498311c1c39517e47e49/files/1455D538-559B-48EF-8D9F-1C2884ED540B/layers/AFDAAFD3-BFA6-4E96-BB6D-057DD13753E5 * * @exports * @class UIDialog * @extends {React.Component<IDialogProps, {}>} */ export class UIDialog extends React.Component { /** * Initializes component properties. * * @param {DialogProps} props */ constructor(props) { super(props); this.onResize = this.onResize.bind(this); this.onModalLayerMount = this.onModalLayerMount.bind(this); this.onModalLayerUnmount = this.onModalLayerUnmount.bind(this); this.attachResize = this.attachResize.bind(this); this.detachResize = this.detachResize.bind(this); this.onHeaderMouseDown = this.onHeaderMouseDown.bind(this); this.state = { resizeMaxHeight: this.getResizeMaxHeight(), isMounted: false }; } /** * Called when component is rerendered. * * @param {Readonly<DialogProps>} prevProps */ componentDidUpdate(prevProps) { const { scrollArea } = this.props; if (prevProps.scrollArea !== scrollArea) { if (scrollArea === UIDialogScrollArea.Content) { this.attachResize(); } else { this.detachResize(); } } } /** * Method handles modal dialog mount event. */ onModalLayerMount() { this.attachResize(); this.setState({ isMounted: true }); } /** * Method handles modal dialog unmount event. */ onModalLayerUnmount() { this.detachResize(); this.setState({ isMounted: false }); } /** * Method attaches to window resize event. */ attachResize() { // Window resize handled window.addEventListener('resize', this.onResize); this.onResize(); } /** * Method detaches from window resize event. */ detachResize() { window.removeEventListener('resize', this.onResize); } /** * Method handles window resizer event to update calculation for content scrollarea size. */ onResize() { this.setState({ resizeMaxHeight: this.getResizeMaxHeight() }); } /** * Method returns maximal height for dialog content "scrollArea=Content" mode. * * @returns {number} Max size of dialog. */ getResizeMaxHeight() { return window.innerHeight - DIALOG_MAX_HEIGHT_OFFSET; } /** * Method returns footer element depending in component props. * Three states. * 1. Accept/Decline buttons. * 2. Custom content of footer. * 3. No footer. * * @returns {JSX.Element | null} Footer element to render. */ getFooter() { let element; const { acceptButtonText, cancelButtonText, onAccept, onCancel, acceptButtonId, cancelButtonId, footer } = this.props; const dialogFooterProps = { styles: { actionsRight: { width: '100%', textAlign: 'center', justifyContent: 'center', margin: '0px' }, actions: { margin: `${DIALOG_STYLES.vPaddingHalf}px 0 ${DIALOG_STYLES.vPadding}px`, lineHeight: 'auto' }, action: { margin: '0px 5px' } } }; if (footer) { element = React.createElement(DialogFooter, { ...dialogFooterProps }, footer); } else if (onAccept || onCancel) { element = (React.createElement(DialogFooter, { ...dialogFooterProps }, onAccept ? (React.createElement(UIDefaultButton, { onClick: onAccept, primary: true, style: { height: '26px' }, id: acceptButtonId }, acceptButtonText)) : (''), onCancel ? (React.createElement(UIDefaultButton, { onClick: onCancel, style: { height: '26px' }, id: cancelButtonId }, cancelButtonText)) : (''))); } return element; } /** * Method handles mousedown event for dialog header area. * Method added to handle scenario when we open dropdown menu and move dialog. * Code in method sets focus to focuszone's placeholder - it triggers close for opened dropdown menu. * * @param {React.MouseEvent} event Mousedown event */ onHeaderMouseDown(event) { if (!this.props.modalProps?.dragOptions) { // No need to handle non draggable return; } const dialogFocusZone = event.target.closest('.ms-Dialog-main'); if (dialogFocusZone?.firstChild) { const focusPlaceholder = dialogFocusZone.firstChild; focusPlaceholder.focus(); } } /** * @returns {JSX.Element} */ render() { const { acceptButtonText, cancelButtonText, onAccept, onCancel, acceptButtonId, cancelButtonId, footer, closeButtonVisible, multiLineTitle, scrollArea = UIDialogScrollArea.Content, children, ...rest } = this.props; const dialogProps = { minWidth: '460px', modalProps: { layerProps: { onLayerDidMount: this.onModalLayerMount, onLayerWillUnmount: this.onModalLayerUnmount }, overlay: { styles: { root: { background: DIALOG_STYLES.modalOverlay.background, opacity: DIALOG_STYLES.modalOverlay.opacity } } } }, styles: { root: { opacity: !this.props.isOpenAnimated || this.state.isMounted ? undefined : 0 }, main: { backgroundColor: DIALOG_STYLES.background, border: `1px solid ${DIALOG_STYLES.borderColor}`, boxShadow: DIALOG_STYLES.boxShadow, borderRadius: DIALOG_STYLES.borderRadius, minHeight: 100, ...(scrollArea === UIDialogScrollArea.Content && { overflow: 'hidden', '.ms-Modal-scrollableContent': { overflow: 'hidden', height: '100%' } }) } }, dialogContentProps: { title: this.props.title, titleProps: { className: 'ui-medium-header', style: { padding: `${DIALOG_STYLES.vPadding}px 0 5px`, textAlign: 'center', color: DIALOG_STYLES.title.color, ...(!multiLineTitle && { whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', padding: `${DIALOG_STYLES.vPadding}px ${DIALOG_STYLES.hPadding}px 5px` }) }, onMouseDown: this.onHeaderMouseDown }, styles: { button: { display: !closeButtonVisible ? 'none' : undefined }, inner: { padding: '0', ...(scrollArea === UIDialogScrollArea.Content && { height: '100%', overflow: 'hidden', display: 'flex', flexDirection: 'column' }) }, subText: { margin: '0', lineHeight: 18, ...DIALOG_STYLES.contentText }, content: { ...DIALOG_STYLES.contentText, ...(scrollArea === UIDialogScrollArea.Content && { height: '100%', display: 'flex', flexDirection: 'column', maxHeight: this.state.resizeMaxHeight }) }, innerContent: { padding: `${DIALOG_STYLES.vPaddingHalf}px ${DIALOG_STYLES.hPadding}px`, boxSizing: 'border-box', ...(scrollArea === UIDialogScrollArea.Content && { height: '100%', overflow: 'auto', boxSizing: 'border-box' }) } } } }; const props = deepMerge(dialogProps, rest); return (React.createElement(BaseDialog, { ...props }, children, this.getFooter())); } } // Default values for public component properties UIDialog.defaultProps = { isOpenAnimated: true }; //# sourceMappingURL=UIDialog.js.map