@mskcc/carbon-react
Version:
Carbon react components for the MSKCC DSM
666 lines (647 loc) • 22 kB
JavaScript
/**
* MSKCC 2021, 2024
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var PropTypes = require('prop-types');
var React = require('react');
var deprecate = require('../../prop-types/deprecate.js');
var cx = require('classnames');
var Button = require('../Button/Button.js');
require('../Button/Button.Skeleton.js');
var useIsomorphicEffect = require('../../internal/useIsomorphicEffect.js');
var useNoInteractiveChildren = require('../../internal/useNoInteractiveChildren.js');
var usePrefix = require('../../internal/usePrefix.js');
var useId = require('../../internal/useId.js');
var MskIcon = require('../Icon/MskIcon.js');
var match = require('../../internal/keyboard/match.js');
var keys = require('../../internal/keyboard/keys.js');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx);
/**
* Conditionally call a callback when the escape key is pressed
* @param {node} ref - ref of the container element to scope the functionality to
* @param {func} callback - function to be called
* @param {bool} override - escape hatch to conditionally call the callback
*/
function useEscapeToClose(ref, callback) {
let override = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
const handleKeyDown = event => {
// The callback should only be called when focus is on or within the container
const elementContainsFocus = ref.current && document.activeElement === ref.current || ref.current.contains(document.activeElement);
if (match.matches(event, [keys.Escape]) && override && elementContainsFocus) {
callback(event);
}
};
useIsomorphicEffect["default"](() => {
document.addEventListener('keydown', handleKeyDown, false);
return () => document.removeEventListener('keydown', handleKeyDown, false);
});
}
function NotificationActionButton(_ref) {
let {
children,
className: customClassName,
onClick,
...rest
} = _ref;
const prefix = usePrefix.usePrefix();
const className = cx__default["default"](customClassName, {
[`${prefix}--actionable-notification__action-button`]: true
});
return /*#__PURE__*/React__default["default"].createElement(Button["default"], _rollupPluginBabelHelpers["extends"]({
className: className,
kind: "tertiary",
onClick: onClick
}, rest), children);
}
NotificationActionButton.propTypes = {
/**
* Specify the content of the notification action button.
*/
children: PropTypes__default["default"].node,
/**
* Specify an optional className to be applied to the notification action button
*/
className: PropTypes__default["default"].string,
/**
* Specify if the visual treatment of the button should be for an inline notification
*/
inline: PropTypes__default["default"].bool,
/**
* Optionally specify a click handler for the notification action button.
*/
onClick: PropTypes__default["default"].func
};
/**
* NotificationButton
* ==================
*/
function NotificationButton(_ref2) {
let {
'aria-label': ariaLabel,
// @ts-expect-error: deprecated prop
ariaLabel: deprecatedAriaLabel,
className,
type,
// renderIcon: IconTag,
name = 'clear',
notificationType,
...rest
} = _ref2;
const prefix = usePrefix.usePrefix();
const buttonClassName = cx__default["default"](className, {
[`${prefix}--${notificationType}-notification__close-button`]: notificationType
});
const iconClassName = cx__default["default"]({
[`${prefix}--${notificationType}-notification__close-icon`]: notificationType
});
return /*#__PURE__*/React__default["default"].createElement("button", _rollupPluginBabelHelpers["extends"]({}, rest, {
// eslint-disable-next-line react/button-has-type
type: type,
"aria-label": deprecatedAriaLabel || ariaLabel,
title: deprecatedAriaLabel || ariaLabel,
className: buttonClassName
}), /*#__PURE__*/React__default["default"].createElement(MskIcon.Icon, {
className: iconClassName,
icon: name
}));
}
NotificationButton.propTypes = {
/**
* Specify a label to be read by screen readers on the container node
*/
['aria-label']: PropTypes__default["default"].string,
/**
* Deprecated, please use `aria-label` instead.
* Specify a label to be read by screen readers on the container note.
*/
ariaLabel: deprecate["default"](PropTypes__default["default"].string, 'This prop syntax has been deprecated. Please use the new `aria-label`.'),
/**
* Specify an optional className to be applied to the notification button
*/
className: PropTypes__default["default"].string,
/**
* Specify an optional icon for the Button through a string,
* if something but regular "close" icon is desirable
*/
name: PropTypes__default["default"].string,
/**
* Specify the notification type
*/
notificationType: PropTypes__default["default"].oneOf(['toast', 'inline', 'actionable']),
// /**
// * Optional prop to allow overriding the icon rendering.
// * Can be a React component class
// */
// renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Optional prop to specify the type of the Button
*/
type: PropTypes__default["default"].string
};
NotificationButton.defaultProps = {
['aria-label']: 'close notification',
notificationType: 'toast',
type: 'button'
};
/**
* NotificationIcon
* ================
*/
function NotificationIcon(_ref3) {
let {
kind,
notificationType
} = _ref3;
const prefix = usePrefix.usePrefix();
if (!kind) {
return null;
}
function switchIcon() {
switch (kind) {
case 'error':
return 'error';
case 'success':
return 'check_circle';
case 'warning':
return 'warning';
case 'info':
default:
return 'info';
}
}
return /*#__PURE__*/React__default["default"].createElement(MskIcon.Icon, {
icon: switchIcon(),
className: `${prefix}--${notificationType}-notification__icon`
});
}
NotificationIcon.propTypes = {
iconDescription: PropTypes__default["default"].string.isRequired,
kind: PropTypes__default["default"].oneOf(['error', 'success', 'warning', 'info']).isRequired,
notificationType: PropTypes__default["default"].oneOf(['inline', 'toast']).isRequired
};
/**
* ToastNotification
* =================
*/
function ToastNotification(_ref4) {
let {
['aria-label']: ariaLabel,
// @ts-expect-error: deprecated prop
ariaLabel: deprecatedAriaLabel,
role,
onClose,
onCloseButtonClick,
statusIconDescription,
className,
children,
kind,
lowContrast,
hideCloseButton,
timeout,
title,
caption,
subtitle,
...rest
} = _ref4;
const [isOpen, setIsOpen] = React.useState(true);
const prefix = usePrefix.usePrefix();
const containerClassName = cx__default["default"](className, {
[`${prefix}--toast-notification`]: true,
[`${prefix}--toast-notification--low-contrast`]: lowContrast,
[`${prefix}--toast-notification--${kind}`]: kind
});
const contentRef = React.useRef(null);
useNoInteractiveChildren.useNoInteractiveChildren(contentRef);
const handleClose = evt => {
if (!onClose || onClose(evt) !== false) {
setIsOpen(false);
}
};
const ref = React.useRef(null);
function handleCloseButtonClick(event) {
onCloseButtonClick(event);
handleClose(event);
}
const savedOnClose = React.useRef(onClose);
React.useEffect(() => {
savedOnClose.current = onClose;
});
React.useEffect(() => {
if (!timeout) {
return;
}
const timeoutId = window.setTimeout(event => {
setIsOpen(false);
if (savedOnClose.current) {
savedOnClose.current(event);
}
}, timeout);
return () => {
window.clearTimeout(timeoutId);
};
}, [timeout]);
if (!isOpen) {
return null;
}
return /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({
ref: ref
}, rest, {
role: role,
className: containerClassName
}), /*#__PURE__*/React__default["default"].createElement(NotificationIcon, {
notificationType: "toast",
kind: kind,
iconDescription: statusIconDescription || `${kind} icon`
}), /*#__PURE__*/React__default["default"].createElement("div", {
ref: contentRef,
className: `${prefix}--toast-notification__details`
}, title && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--toast-notification__title`
}, title), subtitle && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--toast-notification__subtitle`
}, subtitle), caption && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--toast-notification__caption`
}, caption), children), !hideCloseButton && /*#__PURE__*/React__default["default"].createElement(NotificationButton, {
notificationType: "toast",
onClick: handleCloseButtonClick,
"aria-hidden": "true",
"aria-label": deprecatedAriaLabel || ariaLabel,
tabIndex: -1
}));
}
ToastNotification.propTypes = {
/**
* Provide a description for "close" icon button that can be read by screen readers
*/
['aria-label']: PropTypes__default["default"].string,
/**
* Deprecated, please use `aria-label` instead.
* Provide a description for "close" icon button that can be read by screen readers
*/
ariaLabel: deprecate["default"](PropTypes__default["default"].string, 'This prop syntax has been deprecated. Please use the new `aria-label`.'),
/**
* Specify the caption
*/
caption: PropTypes__default["default"].string,
/**
* Specify the content
*/
children: PropTypes__default["default"].node,
/**
* Specify an optional className to be applied to the notification box
*/
className: PropTypes__default["default"].string,
/**
* Specify the close button should be disabled, or not
*/
hideCloseButton: PropTypes__default["default"].bool,
/**
* Specify what state the notification represents
*/
kind: PropTypes__default["default"].oneOf(['error', 'info', 'success', 'warning']),
/**
* Specify whether you are using the low contrast variant of the ToastNotification.
*/
lowContrast: PropTypes__default["default"].bool,
/**
* Provide a function that is called when menu is closed
*/
onClose: PropTypes__default["default"].func,
/**
* Provide a function that is called when the close button is clicked
*/
onCloseButtonClick: PropTypes__default["default"].func,
/**
* By default, this value is "status". You can also provide an alternate
* role if it makes sense from the accessibility-side
*/
role: PropTypes__default["default"].oneOf(['alert', 'log', 'status']),
/**
* Provide a description for "status" icon that can be read by screen readers
*/
statusIconDescription: PropTypes__default["default"].string,
/**
* Specify the subtitle
*/
subtitle: PropTypes__default["default"].string,
/**
* Specify an optional duration the notification should be closed in
*/
timeout: PropTypes__default["default"].number,
/**
* Specify the title
*/
title: PropTypes__default["default"].string
};
ToastNotification.defaultProps = {
kind: 'error',
role: 'status',
onCloseButtonClick: () => {},
hideCloseButton: false,
timeout: 0
};
/**
* InlineNotification
* ==================
*/
function InlineNotification(_ref5) {
let {
['aria-label']: ariaLabel,
children,
title,
subtitle,
role,
onClose,
onCloseButtonClick,
statusIconDescription,
className,
kind,
lowContrast,
hideCloseButton,
...rest
} = _ref5;
const [isOpen, setIsOpen] = React.useState(true);
const prefix = usePrefix.usePrefix();
const containerClassName = cx__default["default"](className, {
[`${prefix}--inline-notification`]: true,
[`${prefix}--inline-notification--low-contrast`]: lowContrast,
[`${prefix}--inline-notification--${kind}`]: kind,
[`${prefix}--inline-notification--hide-close-button`]: hideCloseButton
});
const contentRef = React.useRef(null);
useNoInteractiveChildren.useNoInteractiveChildren(contentRef);
const handleClose = evt => {
if (!onClose || onClose(evt) !== false) {
setIsOpen(false);
}
};
const ref = React.useRef(null);
function handleCloseButtonClick(event) {
onCloseButtonClick(event);
handleClose(event);
}
if (!isOpen) {
return null;
}
return /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({
ref: ref
}, rest, {
role: role,
className: containerClassName
}), /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--inline-notification__details`
}, /*#__PURE__*/React__default["default"].createElement(NotificationIcon, {
notificationType: "inline",
kind: kind,
iconDescription: statusIconDescription || `${kind} icon`
}), /*#__PURE__*/React__default["default"].createElement("div", {
ref: contentRef,
className: `${prefix}--inline-notification__text-wrapper`
}, title && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--inline-notification__title`
}, title), subtitle && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--inline-notification__subtitle`
}, subtitle), children)), !hideCloseButton && /*#__PURE__*/React__default["default"].createElement(NotificationButton, {
notificationType: "inline",
onClick: handleCloseButtonClick,
"aria-hidden": "true",
"aria-label": ariaLabel,
tabIndex: -1
}));
}
InlineNotification.propTypes = {
/**
* Provide a description for "close" icon button that can be read by screen readers
*/
['aria-label']: PropTypes__default["default"].string,
/**
* Specify the content
*/
children: PropTypes__default["default"].node,
/**
* Specify an optional className to be applied to the notification box
*/
className: PropTypes__default["default"].string,
/**
* Specify the close button should be disabled, or not
*/
hideCloseButton: PropTypes__default["default"].bool,
/**
* Specify what state the notification represents
*/
kind: PropTypes__default["default"].oneOf(['error', 'info', 'success', 'warning']),
/**
* Specify whether you are using the low contrast variant of the InlineNotification.
*/
lowContrast: PropTypes__default["default"].bool,
/**
* Provide a function that is called when menu is closed
*/
onClose: PropTypes__default["default"].func,
/**
* Provide a function that is called when the close button is clicked
*/
onCloseButtonClick: PropTypes__default["default"].func,
/**
* By default, this value is "status". You can also provide an alternate
* role if it makes sense from the accessibility-side.
*/
role: PropTypes__default["default"].oneOf(['alert', 'log', 'status']),
/**
* Provide a description for "status" icon that can be read by screen readers
*/
statusIconDescription: PropTypes__default["default"].string,
/**
* Specify the subtitle
*/
subtitle: PropTypes__default["default"].string,
/**
* Specify the title
*/
title: PropTypes__default["default"].string
};
InlineNotification.defaultProps = {
kind: 'error',
role: 'status',
onCloseButtonClick: () => {},
hideCloseButton: false
};
/**
* ActionableNotification
* ======================
*/
function ActionableNotification(_ref6) {
let {
actionButtonLabel,
['aria-label']: ariaLabel,
// @ts-expect-error: deprecated prop
ariaLabel: deprecatedAriaLabel,
children,
role,
onActionButtonClick,
onClose,
onCloseButtonClick,
statusIconDescription,
className,
inline,
kind,
lowContrast,
hideCloseButton,
hasFocus,
closeOnEscape,
title,
subtitle,
...rest
} = _ref6;
const [isOpen, setIsOpen] = React.useState(true);
const prefix = usePrefix.usePrefix();
const id = useId.useId('actionable-notification');
const containerClassName = cx__default["default"](className, {
[`${prefix}--actionable-notification`]: true,
[`${prefix}--actionable-notification--toast`]: !inline,
[`${prefix}--actionable-notification--low-contrast`]: lowContrast,
[`${prefix}--actionable-notification--${kind}`]: kind,
[`${prefix}--actionable-notification--hide-close-button`]: hideCloseButton
});
const ref = React.useRef(null);
useIsomorphicEffect["default"](() => {
if (ref.current && hasFocus) {
ref.current.focus();
}
});
const handleClose = evt => {
if (!onClose || onClose(evt) !== false) {
setIsOpen(false);
}
};
useEscapeToClose(ref, handleCloseButtonClick, closeOnEscape);
function handleCloseButtonClick(event) {
onCloseButtonClick(event);
handleClose(event);
}
if (!isOpen) {
return null;
}
return /*#__PURE__*/React__default["default"].createElement("div", _rollupPluginBabelHelpers["extends"]({}, rest, {
ref: ref,
role: role,
className: containerClassName,
"aria-labelledby": title ? id : undefined
}), /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--actionable-notification__details`
}, /*#__PURE__*/React__default["default"].createElement(NotificationIcon, {
notificationType: inline ? 'inline' : 'toast',
kind: kind,
iconDescription: statusIconDescription || `${kind} icon`
}), /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--actionable-notification__text-wrapper`
}, /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--actionable-notification__content`
}, title && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--actionable-notification__title`,
id: id
}, title), subtitle && /*#__PURE__*/React__default["default"].createElement("div", {
className: `${prefix}--actionable-notification__subtitle`
}, subtitle), children))), actionButtonLabel && /*#__PURE__*/React__default["default"].createElement(NotificationActionButton, {
onClick: onActionButtonClick
}, actionButtonLabel), !hideCloseButton && /*#__PURE__*/React__default["default"].createElement(NotificationButton, {
"aria-label": deprecatedAriaLabel || ariaLabel,
notificationType: "actionable",
onClick: handleCloseButtonClick
}));
}
ActionableNotification.propTypes = {
/**
* Pass in the action button label that will be rendered within the ActionableNotification.
*/
actionButtonLabel: PropTypes__default["default"].string,
/**
* Provide a description for "close" icon button that can be read by screen readers
*/
['aria-label']: PropTypes__default["default"].string,
/**
* Deprecated, please use `aria-label` instead.
* Provide a description for "close" icon button that can be read by screen readers
*/
ariaLabel: deprecate["default"](PropTypes__default["default"].string, 'This prop syntax has been deprecated. Please use the new `aria-label`.'),
/**
* Specify the content
*/
children: PropTypes__default["default"].node,
/**
* Specify an optional className to be applied to the notification box
*/
className: PropTypes__default["default"].string,
/**
* Specify if pressing the escape key should close notifications
*/
closeOnEscape: PropTypes__default["default"].bool,
/**
* Specify if focus should be moved to the component when the notification contains actions
*/
hasFocus: PropTypes__default["default"].bool,
/**
* Specify the close button should be disabled, or not
*/
hideCloseButton: PropTypes__default["default"].bool,
/*
* Specify if the notification should have inline styling applied instead of toast
*/
inline: PropTypes__default["default"].bool,
/**
* Specify what state the notification represents
*/
kind: PropTypes__default["default"].oneOf(['error', 'info', 'success', 'warning']).isRequired,
/**
* Specify whether you are using the low contrast variant of the ActionableNotification.
*/
lowContrast: PropTypes__default["default"].bool,
/**
* Provide a function that is called when the action is clicked
*/
onActionButtonClick: PropTypes__default["default"].func,
/**
* Provide a function that is called when menu is closed
*/
onClose: PropTypes__default["default"].func,
/**
* Provide a function that is called when the close button is clicked
*/
onCloseButtonClick: PropTypes__default["default"].func,
/**
* By default, this value is "alertdialog". You can also provide an alternate
* role if it makes sense from the accessibility-side.
*/
role: PropTypes__default["default"].string,
/**
* Provide a description for "status" icon that can be read by screen readers
*/
statusIconDescription: PropTypes__default["default"].string,
/**
* Specify the subtitle
*/
subtitle: PropTypes__default["default"].node,
/**
* Specify the title
*/
title: PropTypes__default["default"].string
};
ActionableNotification.defaultProps = {
kind: 'error',
role: 'alertdialog',
onCloseButtonClick: () => {},
hideCloseButton: false,
hasFocus: true,
closeOnEscape: true,
inline: false
};
exports.ActionableNotification = ActionableNotification;
exports.InlineNotification = InlineNotification;
exports.NotificationActionButton = NotificationActionButton;
exports.NotificationButton = NotificationButton;
exports.ToastNotification = ToastNotification;