UNPKG

@carbon/ibm-products

Version:

Carbon for IBM Products

299 lines (292 loc) 10.1 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. */ import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js'; import { unstable_FeatureFlags, ComposedModal, ModalHeader, ModalBody, FormGroup, RadioButtonGroup, RadioButton, TextInput, PasswordInput, Loading, ModalFooter, Button } from '@carbon/react'; import { CheckmarkFilled, ErrorFilled } from '@carbon/react/icons'; import React__default, { forwardRef, useState, useEffect, useRef } from 'react'; import PropTypes from '../../_virtual/index.js'; import cx from 'classnames'; import { getDevtoolsProps } from '../../global/js/utils/devtools.js'; import { pkg, carbon } from '../../settings.js'; import { usePortalTarget } from '../../global/js/hooks/usePortalTarget.js'; import uuidv4 from '../../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 */ let ExportModal = /*#__PURE__*/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 = `${pkg.prefix}--export-modal`; const [name, setName] = useState(''); const [dirtyInput, setDirtyInput] = useState(false); // by default (if it exists) use the first extension in the extension array const [extension, setExtension] = useState(''); const renderPortalUse = usePortalTarget(portalTargetIn); useEffect(() => { setName(filename); if (preformattedExtensions && preformattedExtensions.length > 0 && preformattedExtensions[0]?.extension) { setExtension(preformattedExtensions?.[0]?.extension); } }, [filename, preformattedExtensions, open]); useEffect(() => { if (successful) { const button = document.querySelector(`.${blockClass} .${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 = 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__default.createElement(unstable_FeatureFlags, { enableExperimentalFocusWrapWithoutSentinels: true }, /*#__PURE__*/React__default.createElement(ComposedModal, _extends({}, rest, { className: cx(blockClass, className), "aria-label": title, size: "sm", preventCloseOnClickOutside: true, launcherButtonRef: triggerButtonRef, open, ref, onClose }, getDevtoolsProps(componentName)), /*#__PURE__*/React__default.createElement(ModalHeader, { className: `${blockClass}__header`, closeModal: onClose, title: title }), /*#__PURE__*/React__default.createElement(ModalBody, { className: `${blockClass}__body-container` }, !submitted && /*#__PURE__*/React__default.createElement(React__default.Fragment, null, body && /*#__PURE__*/React__default.createElement("p", { className: `${blockClass}__body` }, body), preformattedExtensions.length ? /*#__PURE__*/React__default.createElement(FormGroup, { legendText: preformattedExtensionsLabel }, /*#__PURE__*/React__default.createElement(RadioButtonGroup, { orientation: "vertical", onChange: onExtensionChangeHandler, valueSelected: extension, name: "extensions" }, preformattedExtensions.map(o => /*#__PURE__*/React__default.createElement(RadioButton, { key: o.extension, id: o.extension, value: o.extension, labelText: `${o.extension} (${o.description})`, "data-modal-primary-focus": true })))) : /*#__PURE__*/React__default.createElement("div", { className: `${blockClass}__input-container` }, inputType === 'text' ? /*#__PURE__*/React__default.createElement(TextInput, commonInputProps) : /*#__PURE__*/React__default.createElement(PasswordInput, _extends({}, commonInputProps, { showPasswordLabel: showPasswordLabel, hidePasswordLabel: hidePasswordLabel, tooltipPosition: "left" })))), /*#__PURE__*/React__default.createElement("div", { "aria-live": "polite", className: `${blockClass}__messaging` }, loading && /*#__PURE__*/React__default.createElement(React__default.Fragment, null, _Loading || (_Loading = /*#__PURE__*/React__default.createElement(Loading, { "aria-live": "off", description: "", small: true, withOverlay: false })), /*#__PURE__*/React__default.createElement("p", null, loadingMessage)), successful && /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(CheckmarkFilled, { size: 16, className: `${blockClass}__checkmark-icon` }), /*#__PURE__*/React__default.createElement("p", null, successMessage)), error && /*#__PURE__*/React__default.createElement(React__default.Fragment, null, /*#__PURE__*/React__default.createElement(ErrorFilled, { size: 16, className: `${blockClass}__error-icon` }), /*#__PURE__*/React__default.createElement("p", null, errorMessage)))), !submitted && /*#__PURE__*/React__default.createElement(ModalFooter, { className: `${blockClass}__footer` }, /*#__PURE__*/React__default.createElement(Button, { type: "button", kind: "secondary", onClick: onClose }, secondaryButtonText), /*#__PURE__*/React__default.createElement(Button, { type: "submit", kind: "primary", onClick: onSubmitHandler, disabled: primaryButtonDisabled }, primaryButtonText))))); }); // Return a placeholder if not released and not enabled by feature flag ExportModal = pkg.checkComponentEnabled(ExportModal, componentName); 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; export { ExportModal };