@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
122 lines • 8.98 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useRef } from 'react';
import clsx from 'clsx';
import { useComponentMetadata, useMergeRefs, useUniqueId, warnOnce, } from '@awsui/component-toolkit/internal';
import { getAnalyticsMetadataAttribute } from '@awsui/component-toolkit/internal/analytics-metadata';
import { ActionsWrapper } from '../alert/actions-wrapper';
import { InternalButton } from '../button/internal';
import InternalIcon from '../icon/internal';
import { DATA_ATTR_ANALYTICS_FLASHBAR, DATA_ATTR_ANALYTICS_SUPPRESS_FLOW_EVENTS, } from '../internal/analytics/selectors';
import { getVisualContextClassname } from '../internal/components/visual-context';
import { PACKAGE_VERSION } from '../internal/environment';
import { isDevelopment } from '../internal/is-development';
import { awsuiPluginsInternal } from '../internal/plugins/api';
import { createUseDiscoveredAction, createUseDiscoveredContent } from '../internal/plugins/helpers';
import useContainerWidth from '../internal/utils/use-container-width';
import InternalLiveRegion from '../live-region/internal';
import InternalSpinner from '../spinner/internal';
import { getDismissButtonStyles, getFlashStyles } from './style';
import analyticsSelectors from './analytics-metadata/styles.css.js';
import styles from './styles.css.js';
const ICON_TYPES = {
success: 'status-positive',
warning: 'status-warning',
info: 'status-info',
error: 'status-negative',
'in-progress': 'status-in-progress',
};
const useDiscoveredAction = createUseDiscoveredAction(awsuiPluginsInternal.flashbar.onActionRegistered);
const useDiscoveredContent = createUseDiscoveredContent('flash', awsuiPluginsInternal.flashContent);
function dismissButton(dismissLabel, onDismiss, style, type, ref, id, onDismissed, persistenceConfig) {
return (React.createElement("div", { className: styles['dismiss-button-wrapper'], ...getAnalyticsMetadataAttribute({ action: 'dismiss' }) },
React.createElement(InternalButton, { ref: ref, onClick: event => {
if (onDismiss) {
onDismiss(event);
}
if (onDismissed) {
onDismissed(id, persistenceConfig);
}
}, className: styles['dismiss-button'], variant: "flashbar-icon", iconName: "close", formAction: "none", ariaLabel: dismissLabel, style: getDismissButtonStyles(style, type) })));
}
export const focusFlashFocusableArea = (flash) => {
if (!flash) {
return;
}
const dismissButton = flash.querySelector(`.${styles['dismiss-button']}`);
const focusContainer = flash.querySelector(`.${styles['flash-focus-container']}`);
if (dismissButton) {
dismissButton.focus();
}
else if (focusContainer) {
focusContainer.focus();
}
};
export function focusFlashById(element, itemId) {
if (!element) {
return;
}
const flashElement = element.querySelector(`[data-itemid="${CSS.escape(itemId)}"]`);
if (!flashElement) {
return;
}
const focusContainer = flashElement.querySelector(`.${styles['flash-focus-container']}`);
focusContainer === null || focusContainer === void 0 ? void 0 : focusContainer.focus();
}
export const Flash = React.forwardRef(({ id, header, content, dismissible, dismissLabel, loading, action, buttonText, onButtonClick, onDismiss, className, transitionState, ariaRole, i18nStrings, type = 'info', analyticsMetadata, style, rootRef, onDismissed, persistenceConfig, ...props }, ref) => {
if (isDevelopment) {
if (buttonText && !onButtonClick) {
warnOnce('Flashbar', `You provided a \`buttonText\` prop without an \`onButtonClick\` handler. This will render a non-interactive action button.`);
}
if (dismissible && !onDismiss) {
warnOnce('Flashbar', `You have set the \`dismissible\` prop without an \`onDismiss\` handler. This will render a non-interactive dismiss button.`);
}
}
const [containerWidth, containerMeasureRef] = useContainerWidth();
const elementRef = useComponentMetadata('Flash', PACKAGE_VERSION, analyticsMetadata);
// Merge all refs including the rootRef if provided
const mergedRef = useMergeRefs(ref, rootRef, elementRef, containerMeasureRef);
const flashIconId = useUniqueId('flash-icon');
const flashMessageId = useUniqueId('flash-message');
const headerRefObject = useRef(null);
const contentRefObject = useRef(null);
const dismissButtonRefObject = useRef(null);
const { discoveredActions, headerRef: headerRefAction, contentRef: contentRefAction } = useDiscoveredAction(type);
const { initialHidden, headerReplacementType, contentReplacementType, headerRef: headerRefContent, contentRef: contentRefContent, replacementHeaderRef, replacementContentRef, } = useDiscoveredContent({ type, header, children: content });
const headerRef = useMergeRefs(headerRefAction, headerRefContent, headerRefObject);
const contentRef = useMergeRefs(contentRefAction, contentRefContent, contentRefObject);
const statusIconAriaLabel = props.statusIconAriaLabel ||
(i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings[`${loading || type === 'in-progress' ? 'inProgress' : type}IconAriaLabel`]);
const iconType = ICON_TYPES[type];
const icon = loading ? (React.createElement("span", { role: "img", "aria-label": statusIconAriaLabel },
React.createElement(InternalSpinner, null))) : (React.createElement(InternalIcon, { name: iconType, ariaLabel: statusIconAriaLabel }));
const effectiveType = loading ? 'info' : type;
const analyticsAttributes = { [DATA_ATTR_ANALYTICS_FLASHBAR]: effectiveType };
if (analyticsMetadata === null || analyticsMetadata === void 0 ? void 0 : analyticsMetadata.suppressFlowMetricEvents) {
analyticsAttributes[DATA_ATTR_ANALYTICS_SUPPRESS_FLOW_EVENTS] = 'true';
}
return (
// We're not using "polite" or "assertive" here, just turning default behavior off.
// eslint-disable-next-line @awsui/components-react/prefer-live-region
React.createElement("div", { ref: mergedRef, role: ariaRole, "aria-live": ariaRole ? 'off' : undefined, "data-itemid": id, className: clsx(styles.flash, styles[`flash-type-${effectiveType}`], className, transitionState && {
[styles.enter]: transitionState === 'enter',
[styles.entering]: transitionState === 'entering',
[styles.entered]: transitionState === 'entered',
[styles.exit]: transitionState === 'exit',
[styles.exiting]: transitionState === 'exiting',
[styles.exited]: transitionState === 'exited',
}, getVisualContextClassname(type === 'warning' && !loading ? 'flashbar-warning' : 'flashbar'), initialHidden && styles['initial-hidden']), style: getFlashStyles(style, effectiveType), ...analyticsAttributes },
React.createElement("div", { className: styles['flash-body'] },
React.createElement("div", { className: styles['flash-focus-container'], tabIndex: -1, role: "group", "aria-labelledby": `${flashIconId} ${flashMessageId}` },
React.createElement("div", { className: clsx(styles['flash-icon'], styles['flash-text']), id: flashIconId }, icon),
React.createElement("div", { className: clsx(styles['flash-message'], styles['flash-text']), id: flashMessageId },
React.createElement("div", { className: clsx(styles['flash-header'], headerReplacementType !== 'original' ? styles.hidden : analyticsSelectors['flash-header']), ref: headerRef }, header),
React.createElement("div", { className: clsx(styles['header-replacement'], headerReplacementType !== 'replaced' && styles.hidden), ref: replacementHeaderRef }),
React.createElement("div", { className: clsx(styles['flash-content'], contentReplacementType !== 'original' ? styles.hidden : analyticsSelectors['flash-header']), ref: contentRef }, content),
React.createElement("div", { className: clsx(styles['content-replacement'], contentReplacementType !== 'replaced' && styles.hidden), ref: replacementContentRef }))),
React.createElement(ActionsWrapper, { className: styles['action-button-wrapper'], testUtilClasses: { actionSlot: styles['action-slot'], actionButton: styles['action-button'] }, action: action, discoveredActions: discoveredActions, buttonText: buttonText, onButtonClick: onButtonClick, containerWidth: containerWidth, wrappedClass: styles['action-wrapped'] })),
dismissible &&
dismissButton(dismissLabel, onDismiss, style, effectiveType, dismissButtonRefObject, id, onDismissed, persistenceConfig),
ariaRole === 'status' && (React.createElement(InternalLiveRegion, { sources: [statusIconAriaLabel, headerRefObject, contentRefObject] }))));
});
//# sourceMappingURL=flash.js.map