UNPKG

@octopusdeploy/design-system-components

Version:
216 lines (215 loc) 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Dialog = Dialog; const jsx_runtime_1 = require("react/jsx-runtime"); const css_1 = require("@emotion/css"); const design_system_icons_1 = require("@octopusdeploy/design-system-icons"); const design_system_tokens_1 = require("@octopusdeploy/design-system-tokens"); const react_1 = require("react"); const Button_1 = require("../Button"); const ErrorSummary_1 = require("../ErrorSummary"); const DialogBase_1 = require("./DialogBase"); const DialogMainPanel_1 = require("./DialogMainPanel"); const DialogStepNavigationFooter_1 = require("./DialogStepNavigationFooter"); const dialogBreakpoints_styles_1 = require("./dialogBreakpoints.styles"); const useDialogSteps_1 = require("./useDialogSteps"); function isMultiStepProps(props) { return "steps" in props && Array.isArray(props.steps); } /** * A standard modal dialog with a title, optional dismiss button, scrollable content area, * optional error panel, primary/secondary footer actions, optional animated side panel, * and optional multi-step navigation. * * @remarks Do not implement directly in Octopus Server code. Use Workflows instead. See https://docs.octopushq.com/docs/frontend/workflows */ function Dialog(props) { const { width, open, onClose, onClosed, title, errors, showDismissButton = true, sidePanel, defaultSidePanelOpen = true } = props; const multiStep = isMultiStepProps(props); const { currentStepIndex, totalSteps, isFirstStep, isLastStep, isNavigating, goToNext, goToPrevious, reset } = (0, useDialogSteps_1.useDialogSteps)(multiStep ? props.steps.map((s) => ({ onBeforeNext: s.onBeforeNext, onBeforeBack: s.onBeforeBack, onEnter: s.onEnter })) : []); // extraLarge → undefined: clamps here so an `any`-typed caller can't bypass the type-level rejection of sidePanel at extraLarge. const sidePanelWidth = (0, dialogBreakpoints_styles_1.toSidePanelWidth)(width); // Determine initial side panel state (avoids flash during close animation). const initialSidePanelContent = sidePanelWidth ? ((multiStep && props.steps.length > 0 ? props.steps[0].sidePanel : undefined) ?? sidePanel) : undefined; const initialHasSidePanel = !!initialSidePanelContent; // Determine content and side panel based on single vs multi-step. const currentStep = multiStep ? props.steps[currentStepIndex] : undefined; const content = currentStep ? currentStep.content : props.children; const sidePanelContent = sidePanelWidth ? (currentStep?.sidePanel ?? sidePanel) : undefined; const hasSidePanel = !!sidePanelContent; const [sidePanelOpen, setSidePanelOpen] = (0, react_1.useState)(defaultSidePanelOpen && hasSidePanel); (0, react_1.useEffect)(() => { if (open) { reset(); setSidePanelOpen(defaultSidePanelOpen && initialHasSidePanel); } }, [open, reset, defaultSidePanelOpen, initialHasSidePanel]); const showDismiss = !hasSidePanel && showDismissButton; const progressTracker = multiStep ? currentStep?.progressTracker : props.progressTracker; // Build the footer actions const actionsEl = multiStep ? ((0, jsx_runtime_1.jsx)(DialogStepNavigationFooter_1.DialogStepNavigationFooter, { totalSteps: totalSteps, currentStepIndex: currentStepIndex, isFirstStep: isFirstStep, isLastStep: isLastStep, isNavigating: isNavigating, primaryImportance: "primary", completeButtonLabel: props.completeButtonLabel, backButtonDisabled: props.backButtonDisabled, nextButtonDisabled: props.nextButtonDisabled, onClose: onClose, onBack: () => { void goToPrevious(); }, onNext: () => { void goToNext(); }, onComplete: props.onComplete })) : ((0, jsx_runtime_1.jsxs)("footer", { className: dialogStyles.actions, children: [showDismissButton && (0, jsx_runtime_1.jsx)(Button_1.Button, { importance: "quiet", label: "Cancel", onClick: onClose }), props.secondaryAction && (0, jsx_runtime_1.jsx)(Button_1.Button, { ...props.secondaryAction, importance: props.secondaryAction.importance ?? "secondary" }), (0, jsx_runtime_1.jsx)(Button_1.Button, { ...props.primaryAction, importance: props.primaryAction.importance ?? "primary" })] })); return ((0, jsx_runtime_1.jsx)(DialogBase_1.DialogBase, { open: open, onClose: onClose, onClosed: onClosed, accessibleName: title, autoFocusFirstElement: false, closeOnBackdropClick: showDismissButton, children: (0, jsx_runtime_1.jsx)("div", { className: (0, css_1.cx)(dialogStyles.outer, sidePanelWidth && sidePanelOpen ? outerSidePanelOpenStyles[sidePanelWidth] : null), children: (0, jsx_runtime_1.jsxs)("div", { className: (0, css_1.cx)(dialogStyles.container, sidePanelWidth ? containerStyles[sidePanelWidth] : null, sidePanelWidth && sidePanelOpen ? containerSidePanelOpenStyles[sidePanelWidth] : null), children: [(0, jsx_runtime_1.jsx)(DialogMainPanel_1.DialogMainPanel, { isLoading: props.isLoading, width: width, hasSidePanel: hasSidePanel, title: title, headerAction: hasSidePanel ? { type: "toggleSidePanel", onOpen: () => setSidePanelOpen(true), isOpen: sidePanelOpen } : showDismiss ? { type: "dismiss", onClose } : undefined, progressTracker: progressTracker, errorPanel: errors && errors.length > 0 ? errors.map((err, index) => (0, jsx_runtime_1.jsx)(ErrorSummary_1.ErrorSummary, { error: err }, index)) : undefined, footer: actionsEl, children: content }), sidePanelWidth && ((0, jsx_runtime_1.jsx)("aside", { className: (0, css_1.cx)(dialogStyles.sidePanelContainer, sidePanelWrapperStyles[sidePanelWidth]), "aria-hidden": !sidePanelOpen, ...(!sidePanelOpen && { inert: "" }), children: (0, jsx_runtime_1.jsxs)("div", { className: dialogStyles.sidePanel, children: [hasSidePanel && ((0, jsx_runtime_1.jsx)("div", { className: (0, css_1.cx)(toggleButtonStyles[sidePanelWidth], dialogStyles.toggleInSidePanel, !sidePanelOpen && dialogStyles.toggleInSidePanelClosed, !sidePanelOpen && dialogStyles.toggleHidden), children: (0, jsx_runtime_1.jsx)(Button_1.Button, { importance: "ghost", accessibleName: "Close side panel", onClick: () => { setSidePanelOpen(false); }, size: "small", icon: (0, jsx_runtime_1.jsx)(design_system_icons_1.ChevronsLeftIcon, { size: 24 }) }) })), (0, jsx_runtime_1.jsx)("div", { className: (0, css_1.cx)(dialogStyles.sidePanelContent, { [dialogStyles.sidePanelContentClosed]: !sidePanelOpen }), children: sidePanelContent })] }) }))] }) }) })); } //TODO replace with tokens const borderRadiusTransitionDurationMs = 150; // When opening: animate the border radius to 0 over 150ms with no delay, // so the corners are square before the panel finishes sliding in. const outerSidePanelOpenProps = { borderTopRightRadius: 0, borderBottomRightRadius: 0, transition: `border-top-right-radius ${borderRadiusTransitionDurationMs}ms ease 0ms, border-bottom-right-radius ${borderRadiusTransitionDurationMs}ms ease 0ms`, }; const outerSidePanelOpenStyles = { small: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.small})`]: outerSidePanelOpenProps }), medium: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.medium})`]: outerSidePanelOpenProps }), large: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.large})`]: outerSidePanelOpenProps }), }; const containerStyles = { small: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.small})`]: { gridTemplateColumns: `${design_system_tokens_1.dialog.mainPanel.width.small} 0px`, }, }), medium: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.medium})`]: { gridTemplateColumns: `${design_system_tokens_1.dialog.mainPanel.width.medium} 0px`, }, }), large: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.large})`]: { gridTemplateColumns: `${design_system_tokens_1.dialog.mainPanel.width.large} 0px`, }, }), }; const containerSidePanelOpenStyles = { small: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.small})`]: { gridTemplateColumns: `${design_system_tokens_1.dialog.mainPanel.width.small} ${design_system_tokens_1.dialog.sidePanel.width}`, }, }), medium: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.medium})`]: { gridTemplateColumns: `${design_system_tokens_1.dialog.mainPanel.width.medium} ${design_system_tokens_1.dialog.sidePanel.width}`, }, }), large: (0, css_1.css)({ [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.large})`]: { gridTemplateColumns: `${design_system_tokens_1.dialog.mainPanel.width.large} ${design_system_tokens_1.dialog.sidePanel.width}`, }, }), }; const sidePanelWrapperStyles = { small: (0, css_1.css)({ [`@media (max-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.small})`]: { display: "none" } }), medium: (0, css_1.css)({ [`@media (max-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.medium})`]: { display: "none" } }), large: (0, css_1.css)({ [`@media (max-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.large})`]: { display: "none" } }), }; const toggleButtonBaseProps = { display: "none", flexShrink: 0 }; const toggleButtonStyles = { small: (0, css_1.css)({ ...toggleButtonBaseProps, [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.small})`]: { display: "block" } }), medium: (0, css_1.css)({ ...toggleButtonBaseProps, [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.medium})`]: { display: "block" } }), large: (0, css_1.css)({ ...toggleButtonBaseProps, [`@media (min-width: ${dialogBreakpoints_styles_1.dialogSidePanelBreakpoint.large})`]: { display: "block" } }), }; const dialogStyles = { outer: (0, css_1.css)({ overflow: "hidden", borderRadius: design_system_tokens_1.borderRadius.large, maxHeight: design_system_tokens_1.dialog.maxHeight, display: "flex", flexDirection: "column", transition: `border-top-right-radius ${borderRadiusTransitionDurationMs}ms ease 150ms, border-bottom-right-radius ${borderRadiusTransitionDurationMs}ms ease 150ms`, }), container: (0, css_1.css)({ display: "grid", minHeight: 0, transition: `grid-template-columns ${design_system_tokens_1.dialog.sidePanel.slideDuration} ease-in-out ${design_system_tokens_1.dialog.sidePanel.fadeDuration}`, }), actions: (0, css_1.css)({ flexShrink: 0, display: "flex", flexDirection: "row", justifyContent: "flex-end", alignItems: "center", gap: design_system_tokens_1.space[8], padding: `${design_system_tokens_1.space[8]} ${design_system_tokens_1.space[24]} ${design_system_tokens_1.space[24]} ${design_system_tokens_1.space[24]}`, }), sidePanelContainer: (0, css_1.css)({ background: design_system_tokens_1.themeTokens.color.dialog.background.secondary.default, display: "flex", minWidth: 0, overflow: "hidden", position: "relative", borderBottomRightRadius: design_system_tokens_1.borderRadius.large, borderTopRightRadius: design_system_tokens_1.borderRadius.large, }), sidePanel: (0, css_1.css)({ display: "flex", flexDirection: "column", alignItems: "center", boxSizing: "border-box", padding: `${design_system_tokens_1.space[72]} ${design_system_tokens_1.space[32]}`, position: "relative", width: design_system_tokens_1.dialog.sidePanel.width, flex: "0 0 auto", }), sidePanelContent: (0, css_1.css)({ font: design_system_tokens_1.text.body.regular.large, color: design_system_tokens_1.themeTokens.color.text.primary, transition: `opacity ${design_system_tokens_1.dialog.sidePanel.fadeDuration} ease ${design_system_tokens_1.dialog.sidePanel.slideDuration}`, display: "flex", flexDirection: "column", alignItems: "flex-start", gap: design_system_tokens_1.space[24], width: "100%", "& img": { display: "block", width: "100%", height: "auto", }, // This will handle some basic typography styles for the content to prevent inconsistent styling. // TODO: should replace with a more robust typography system/layout system. "& p": { margin: 0, font: design_system_tokens_1.text.body.regular.large, textBox: "trim-both text", }, "& h3": { font: design_system_tokens_1.text.heading.small, margin: 0, textBox: "trim-both text", }, "& ul": { marginTop: 0, marginBottom: 0, font: design_system_tokens_1.text.body.regular.large, textBox: "trim-both text", }, "& p + h3, & p + div": { marginTop: design_system_tokens_1.space[12], }, }), sidePanelContentClosed: (0, css_1.css)({ opacity: 0, pointerEvents: "none", transition: `opacity ${design_system_tokens_1.dialog.sidePanel.fadeDuration} ease 0ms`, }), toggleInSidePanel: (0, css_1.css)({ position: "absolute", top: design_system_tokens_1.space[16], right: design_system_tokens_1.space[16], zIndex: 1, transition: `opacity ${design_system_tokens_1.dialog.sidePanel.fadeDuration} ease ${design_system_tokens_1.dialog.sidePanel.slideDuration}`, }), toggleInSidePanelClosed: (0, css_1.css)({ transition: `opacity ${design_system_tokens_1.dialog.sidePanel.fadeDuration} ease 0ms`, }), toggleHidden: (0, css_1.css)({ opacity: 0, pointerEvents: "none", }), };