UNPKG

@carbon/ibm-products

Version:
253 lines (251 loc) 9.12 kB
/** * Copyright IBM Corp. 2020, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { __toESM } from "../../_virtual/_rolldown/runtime.js"; import { require_classnames } from "../../node_modules/classnames/index.js"; import { pkg } from "../../settings.js"; import { getDevtoolsProps } from "../../global/js/utils/devtools.js"; import { usePortalTarget } from "../../global/js/hooks/usePortalTarget.js"; import uuidv4 from "../../global/js/utils/uuidv4.js"; import React, { forwardRef, useEffect, useRef, useState } from "react"; import PropTypes from "prop-types"; import { Button, ComposedModal, FormGroup, Loading, ModalBody, ModalFooter, ModalHeader, PasswordInput, RadioButton, RadioButtonGroup, TextInput, unstable_FeatureFlags, usePrefix } from "@carbon/react"; import { CheckmarkFilled, ErrorFilled } from "@carbon/react/icons"; //#region src/components/ExportModal/ExportModal.tsx var import_classnames = /* @__PURE__ */ __toESM(require_classnames()); const componentName = "ExportModal"; const defaults = { inputType: "text", preformattedExtensions: Object.freeze([]), validExtensions: Object.freeze([]) }; /** * Modal dialog version of the export pattern */ const ExportModal = forwardRef(({ body, className, error, errorMessage, filename, hidePasswordLabel, inputLabel, inputType = "text", invalidInputText, loading, loadingMessage, onClose, onRequestSubmit, open, portalTarget: portalTargetIn, preformattedExtensions = defaults.preformattedExtensions, preformattedExtensionsLabel, primaryButtonText, secondaryButtonText, showPasswordLabel, successMessage, successful, title, triggerButtonRef, validExtensions = defaults.validExtensions, ...rest }, ref) => { const blockClass = `${pkg.prefix}--export-modal`; const [name, setName] = useState(""); const [dirtyInput, setDirtyInput] = useState(false); const [extension, setExtension] = useState(""); const renderPortalUse = usePortalTarget(portalTargetIn); const carbonPrefix = usePrefix(); useEffect(() => { setName(filename); if (preformattedExtensions && preformattedExtensions.length > 0 && preformattedExtensions[0]?.extension) setExtension(preformattedExtensions?.[0]?.extension); }, [ filename, preformattedExtensions, open ]); useEffect(() => { if (successful) document.querySelector(`.${blockClass} .${carbonPrefix}--modal-close-button button`)?.focus(); }, [ successful, blockClass, carbonPrefix ]); const onNameChangeHandler = (evt) => { setName(evt.target.value); }; const onExtensionChangeHandler = (value) => { setExtension(value); }; const onBlurHandler = () => { setDirtyInput(true); }; const onSubmitHandler = () => { const returnName = extension ? `${filename}.${extension.toLocaleLowerCase()}` : name; onRequestSubmit?.(returnName); }; const hasInvalidExtension = () => { if (!dirtyInput || !validExtensions || !validExtensions.length) return false; if (!name.includes(".")) return true; const ext = name.split(".").pop(); if (!validExtensions.includes(ext)) return true; return false; }; const internalId = useRef(uuidv4()); const primaryButtonDisabled = loading || !name || hasInvalidExtension(); const submitted = loading || error || successful; const commonInputProps = { id: `text-input--${internalId.current}`, value: name, onChange: onNameChangeHandler, labelText: inputLabel, invalid: hasInvalidExtension(), invalidText: invalidInputText, onBlur: onBlurHandler, ["data-modal-primary-focus"]: true }; return renderPortalUse(/* @__PURE__ */ React.createElement(unstable_FeatureFlags, { enableExperimentalFocusWrapWithoutSentinels: true }, /* @__PURE__ */ React.createElement(ComposedModal, { ...rest, className: (0, import_classnames.default)(blockClass, className), "aria-label": title, size: "sm", preventCloseOnClickOutside: true, launcherButtonRef: triggerButtonRef, open, ref, onClose, ...getDevtoolsProps(componentName) }, /* @__PURE__ */ React.createElement(ModalHeader, { className: `${blockClass}__header`, closeModal: onClose, title }), /* @__PURE__ */ React.createElement(ModalBody, { className: `${blockClass}__body-container` }, !submitted && /* @__PURE__ */ React.createElement(React.Fragment, null, body && /* @__PURE__ */ React.createElement("p", { className: `${blockClass}__body` }, body), preformattedExtensions.length ? /* @__PURE__ */ React.createElement(FormGroup, { legendText: preformattedExtensionsLabel }, /* @__PURE__ */ React.createElement(RadioButtonGroup, { orientation: "vertical", onChange: onExtensionChangeHandler, valueSelected: extension, name: "extensions", "aria-label": "extensions" }, preformattedExtensions.map((o) => /* @__PURE__ */ React.createElement(RadioButton, { key: o.extension, id: o.extension, value: o.extension, labelText: `${o.extension} (${o.description})`, "data-modal-primary-focus": true })))) : /* @__PURE__ */ React.createElement("div", { className: `${blockClass}__input-container` }, inputType === "text" ? /* @__PURE__ */ React.createElement(TextInput, commonInputProps) : /* @__PURE__ */ React.createElement(PasswordInput, { ...commonInputProps, showPasswordLabel, hidePasswordLabel, tooltipPosition: "left" }))), /* @__PURE__ */ React.createElement("div", { "aria-live": "polite", className: `${blockClass}__messaging` }, loading && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(Loading, { "aria-live": "off", description: "", small: true, withOverlay: false }), /* @__PURE__ */ React.createElement("p", null, loadingMessage)), successful && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(CheckmarkFilled, { size: 16, className: `${blockClass}__checkmark-icon` }), /* @__PURE__ */ React.createElement("p", null, successMessage)), error && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(ErrorFilled, { size: 16, className: `${blockClass}__error-icon` }), /* @__PURE__ */ React.createElement("p", null, errorMessage)))), !submitted && /* @__PURE__ */ React.createElement(ModalFooter, { className: `${blockClass}__footer` }, /* @__PURE__ */ React.createElement(Button, { type: "button", kind: "secondary", onClick: onClose }, secondaryButtonText), /* @__PURE__ */ React.createElement(Button, { type: "submit", kind: "primary", onClick: onSubmitHandler, disabled: primaryButtonDisabled }, primaryButtonText))))); }); ExportModal.propTypes = { /** * Body content for the modal */ /**@ts-ignore*/ body: PropTypes.string, /** * Optional class name */ className: PropTypes.string, /** * specify if an error occurred */ error: PropTypes.bool, /** * messaging to display in the event of an error */ errorMessage: PropTypes.string, /** * name of the file being exported */ filename: PropTypes.string.isRequired, /** * label text that's displayed when hovering over visibility toggler to hide key */ hidePasswordLabel: PropTypes.string, /** * label for the text input */ inputLabel: PropTypes.string, /** * specify the type of text input */ /**@ts-ignore */ inputType: PropTypes.oneOf(["text", "password"]), /** * text for an invalid input */ invalidInputText: PropTypes.string, /** * specify if the modal is in a loading state */ loading: PropTypes.bool, /** * message to display during the loading state */ loadingMessage: PropTypes.string, /** * Specify a handler for closing modal */ onClose: PropTypes.func, /** * Specify a handler for "submitting" modal. Returns the file name */ onRequestSubmit: PropTypes.func, /** * Specify whether the Modal is currently open */ open: PropTypes.bool, /** * The DOM node the tearsheet should be rendered within. Defaults to document.body. */ portalTarget: PropTypes.node, /** * Array of extensions to display as radio buttons */ /**@ts-ignore */ preformattedExtensions: PropTypes.arrayOf(PropTypes.shape({ extension: PropTypes.string, description: PropTypes.string })), /** * Label for the preformatted label form group */ preformattedExtensionsLabel: PropTypes.string, /** * Specify the text for the primary button */ primaryButtonText: PropTypes.string.isRequired, /** * Specify the text for the secondary button */ secondaryButtonText: PropTypes.string.isRequired, /** * label text that's displayed when hovering over visibility toggler to show key */ showPasswordLabel: PropTypes.string, /** * messaging to display if the export was successful */ successMessage: PropTypes.string, /** * specify if the export was successful */ successful: PropTypes.bool, /** * The text displayed at the top of the modal */ title: PropTypes.string.isRequired, /** * Sets the trigger button ref */ triggerButtonRef: PropTypes.any, /** * array of valid extensions the file can have */ /**@ts-ignore */ validExtensions: PropTypes.array }; ExportModal.displayName = componentName; //#endregion export { ExportModal };