UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

299 lines (292 loc) 9.96 kB
/** * Copyright IBM Corp. 2020, 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ 'use strict'; var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js'); var react = require('@carbon/react'); var icons = require('@carbon/react/icons'); var React = require('react'); var index = require('../../_virtual/index.js'); var cx = require('classnames'); var devtools = require('../../global/js/utils/devtools.js'); var settings = require('../../settings.js'); var usePortalTarget = require('../../global/js/hooks/usePortalTarget.js'); var uuidv4 = require('../../global/js/utils/uuidv4.js'); var _Loading; const componentName = 'ExportModal'; // Default values for props const defaults = { preformattedExtensions: Object.freeze([]), validExtensions: Object.freeze([]) }; /** * Modal dialog version of the export pattern */ exports.ExportModal = /*#__PURE__*/React.forwardRef((_ref, ref) => { let { // The component props, in alphabetical order (for consistency). 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, // Collect any other property values passed in. ...rest } = _ref; const blockClass = `${settings.pkg.prefix}--export-modal`; const [name, setName] = React.useState(''); const [dirtyInput, setDirtyInput] = React.useState(false); // by default (if it exists) use the first extension in the extension array const [extension, setExtension] = React.useState(''); const renderPortalUse = usePortalTarget.usePortalTarget(portalTargetIn); React.useEffect(() => { setName(filename); if (preformattedExtensions && preformattedExtensions.length > 0 && preformattedExtensions[0]?.extension) { setExtension(preformattedExtensions?.[0]?.extension); } }, [filename, preformattedExtensions, open]); React.useEffect(() => { if (successful) { const button = document.querySelector(`.${blockClass} .${settings.carbon.prefix}--modal-close-button button`); button?.focus(); } }, [successful, blockClass]); 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 = React.useRef(uuidv4.default()); 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(react.unstable_FeatureFlags, { enableExperimentalFocusWrapWithoutSentinels: true }, /*#__PURE__*/React.createElement(react.ComposedModal, _rollupPluginBabelHelpers.extends({}, rest, { className: cx(blockClass, className), "aria-label": title, size: "sm", preventCloseOnClickOutside: true, launcherButtonRef: triggerButtonRef, open, ref, onClose }, devtools.getDevtoolsProps(componentName)), /*#__PURE__*/React.createElement(react.ModalHeader, { className: `${blockClass}__header`, closeModal: onClose, title: title }), /*#__PURE__*/React.createElement(react.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(react.FormGroup, { legendText: preformattedExtensionsLabel }, /*#__PURE__*/React.createElement(react.RadioButtonGroup, { orientation: "vertical", onChange: onExtensionChangeHandler, valueSelected: extension, name: "extensions" }, preformattedExtensions.map(o => /*#__PURE__*/React.createElement(react.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(react.TextInput, commonInputProps) : /*#__PURE__*/React.createElement(react.PasswordInput, _rollupPluginBabelHelpers.extends({}, commonInputProps, { showPasswordLabel: showPasswordLabel, hidePasswordLabel: hidePasswordLabel, tooltipPosition: "left" })))), /*#__PURE__*/React.createElement("div", { "aria-live": "polite", className: `${blockClass}__messaging` }, loading && /*#__PURE__*/React.createElement(React.Fragment, null, _Loading || (_Loading = /*#__PURE__*/React.createElement(react.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(icons.CheckmarkFilled, { size: 16, className: `${blockClass}__checkmark-icon` }), /*#__PURE__*/React.createElement("p", null, successMessage)), error && /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(icons.ErrorFilled, { size: 16, className: `${blockClass}__error-icon` }), /*#__PURE__*/React.createElement("p", null, errorMessage)))), !submitted && /*#__PURE__*/React.createElement(react.ModalFooter, { className: `${blockClass}__footer` }, /*#__PURE__*/React.createElement(react.Button, { type: "button", kind: "secondary", onClick: onClose }, secondaryButtonText), /*#__PURE__*/React.createElement(react.Button, { type: "submit", kind: "primary", onClick: onSubmitHandler, disabled: primaryButtonDisabled }, primaryButtonText))))); }); // Return a placeholder if not released and not enabled by feature flag exports.ExportModal = settings.pkg.checkComponentEnabled(exports.ExportModal, componentName); exports.ExportModal.propTypes = { /** * Body content for the modal */ /**@ts-ignore*/ body: index.default.string, /** * Optional class name */ className: index.default.string, /** * specify if an error occurred */ error: index.default.bool, /** * messaging to display in the event of an error */ errorMessage: index.default.string, /** * name of the file being exported */ filename: index.default.string.isRequired, /** * label text that's displayed when hovering over visibility toggler to hide key */ hidePasswordLabel: index.default.string, /** * label for the text input */ inputLabel: index.default.string, /** * specify the type of text input */ /**@ts-ignore */ inputType: index.default.oneOf(['text', 'password']), /** * text for an invalid input */ invalidInputText: index.default.string, /** * specify if the modal is in a loading state */ loading: index.default.bool, /** * message to display during the loading state */ loadingMessage: index.default.string, /** * Specify a handler for closing modal */ onClose: index.default.func, /** * Specify a handler for "submitting" modal. Returns the file name */ onRequestSubmit: index.default.func, /** * Specify whether the Modal is currently open */ open: index.default.bool, /** * The DOM node the tearsheet should be rendered within. Defaults to document.body. */ portalTarget: index.default.node, /** * Array of extensions to display as radio buttons */ /**@ts-ignore */ preformattedExtensions: index.default.arrayOf(index.default.shape({ extension: index.default.string, description: index.default.string })), /** * Label for the preformatted label form group */ preformattedExtensionsLabel: index.default.string, /** * Specify the text for the primary button */ primaryButtonText: index.default.string.isRequired, /** * Specify the text for the secondary button */ secondaryButtonText: index.default.string.isRequired, /** * label text that's displayed when hovering over visibility toggler to show key */ showPasswordLabel: index.default.string, /** * messaging to display if the export was successful */ successMessage: index.default.string, /** * specify if the export was successful */ successful: index.default.bool, /** * The text displayed at the top of the modal */ title: index.default.string.isRequired, /** * Sets the trigger button ref */ triggerButtonRef: index.default.any, /** * array of valid extensions the file can have */ /**@ts-ignore */ validExtensions: index.default.array }; exports.ExportModal.displayName = componentName;