@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
199 lines • 12.4 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useEffect, useImperativeHandle, useRef } from 'react';
import clsx from 'clsx';
import { useUniqueId, warnOnce } from '@awsui/component-toolkit/internal';
import { getAnalyticsMetadataAttribute } from '@awsui/component-toolkit/internal/analytics-metadata';
import InternalBox from '../box/internal';
import { InternalButton } from '../button/internal';
import { useFunnel } from '../internal/analytics/hooks/use-funnel.js';
import { getBaseProps } from '../internal/base-component';
import Dropdown from '../internal/components/dropdown';
import OptionsList from '../internal/components/options-list';
import { useMobile } from '../internal/hooks/use-mobile';
import { useVisualRefresh } from '../internal/hooks/use-visual-mode/index.js';
import { isDevelopment } from '../internal/is-development';
import { spinWhenOpen } from '../internal/styles/motion/utils';
import { checkSafeUrl } from '../internal/utils/check-safe-url';
import ItemsList from './items-list';
import { useButtonDropdown } from './utils/use-button-dropdown';
import { isLinkItem } from './utils/utils.js';
import analyticsSelectors from './analytics-metadata/styles.css.js';
import styles from './styles.css.js';
const InternalButtonDropdown = React.forwardRef(({ items, variant = 'normal', loading = false, loadingText, disabled = false, disabledReason, expandableGroups = false, children, onItemClick, onItemFollow, customTriggerBuilder, expandToViewport, ariaLabel, title, description, preferCenter, mainAction, showMainActionOnly, __internalRootRef, analyticsMetadataTransformer, linkStyle, fullWidth, position, nativeMainActionAttributes, nativeTriggerAttributes, ...props }, ref) => {
var _a;
const isInRestrictedView = useMobile();
const dropdownId = useUniqueId('dropdown');
for (const item of items) {
if (isLinkItem(item)) {
checkSafeUrl('ButtonDropdown', item.href);
}
}
if (mainAction) {
checkSafeUrl('ButtonDropdown', mainAction.href);
}
if (isDevelopment) {
if (mainAction && variant !== 'primary' && variant !== 'normal') {
warnOnce('ButtonDropdown', 'Main action is only supported for "primary" and "normal" component variant.');
}
}
const hasMainAction = mainAction && (variant === 'primary' || variant === 'normal');
const isVisualRefresh = useVisualRefresh();
const { isOpen, targetItem, isHighlighted, isKeyboardHighlight, isExpanded, highlightItem, onKeyDown, onKeyUp, onItemActivate, onGroupToggle, toggleDropdown, closeDropdown, setIsUsingMouse, } = useButtonDropdown({
items,
onItemClick,
onItemFollow,
// Scroll is unnecessary when moving focus back to the dropdown trigger.
onReturnFocus: () => { var _a; return (_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true }); },
expandToViewport,
hasExpandableGroups: expandableGroups,
isInRestrictedView,
});
const handleMouseEvent = () => {
setIsUsingMouse(true);
};
const baseProps = getBaseProps(props);
const mainActionRef = useRef(null);
const triggerRef = useRef(null);
useImperativeHandle(ref, () => ({
focus(...args) {
var _a;
(_a = (hasMainAction ? mainActionRef : triggerRef).current) === null || _a === void 0 ? void 0 : _a.focus(...args);
},
focusDropdownTrigger(...args) {
var _a;
(_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.focus(...args);
},
}), [mainActionRef, triggerRef, hasMainAction]);
const clickHandler = () => {
if (!loading && !disabled) {
// Prevent moving highlight on mobiles to avoid disabled state reason popup if defined.
toggleDropdown({ moveHighlightOnOpen: !isInRestrictedView });
}
};
const canBeOpened = !loading && !disabled;
const canBeFullWidth = !!fullWidth && (variant === 'primary' || variant === 'normal');
const triggerVariant = variant === 'navigation' ? undefined : variant === 'inline-icon' ? 'inline-icon' : variant;
const iconProps = variant === 'icon' || variant === 'inline-icon'
? {
iconName: 'ellipsis',
}
: {
iconName: 'caret-down-filled',
iconAlign: 'right',
__iconClass: spinWhenOpen(styles, 'rotate', canBeOpened && isOpen),
};
const baseTriggerProps = {
className: clsx(styles['trigger-button'], styles['test-utils-button-trigger'], analyticsSelectors['trigger-label']),
...iconProps,
variant: triggerVariant,
loading,
loadingText,
disabled,
disabledReason,
onClick: (event) => {
event.preventDefault();
clickHandler();
},
ariaLabel,
ariaExpanded: canBeOpened && isOpen,
formAction: 'none',
nativeButtonAttributes: {
'aria-haspopup': true,
...nativeTriggerAttributes,
},
};
const triggerId = useUniqueId('awsui-button-dropdown__trigger');
const triggerHasBadge = () => {
const flatItems = items.flatMap(item => {
if ('items' in item) {
return item.items;
}
return item;
});
return (variant === 'icon' &&
!!(flatItems === null || flatItems === void 0 ? void 0 : flatItems.find(item => {
if ('badge' in item) {
return item.badge;
}
})));
};
let trigger = null;
const analyticsMetadata = disabled
? {}
: {
action: !isOpen ? 'expand' : 'collapse',
detail: {
label: `.${analyticsSelectors['trigger-label']}`,
},
};
if (customTriggerBuilder) {
trigger = (React.createElement("div", { className: styles['dropdown-trigger'], ...getAnalyticsMetadataAttribute(analyticsMetadata) }, customTriggerBuilder({
testUtilsClass: styles['test-utils-button-trigger'],
ariaExpanded: canBeOpened && isOpen,
onClick: clickHandler,
triggerRef,
ariaLabel,
disabled,
disabledReason,
isOpen,
})));
}
else if (hasMainAction) {
const { text, iconName, iconAlt, iconSvg, iconUrl, external, externalIconAriaLabel, ...mainActionProps } = mainAction;
const mainActionIconProps = external
? { iconName: 'external', iconAlign: 'right', target: '_blank', rel: 'noopener noreferrer' }
: { iconName, iconAlt, iconSvg, iconUrl };
const mainActionAriaLabel = externalIconAriaLabel
? `${(_a = mainAction.ariaLabel) !== null && _a !== void 0 ? _a : mainAction.text} ${mainAction.externalIconAriaLabel}`
: mainAction.ariaLabel;
const hasNoText = !text;
const mainActionButton = (React.createElement(InternalButton, { ref: mainActionRef, ...mainActionProps, ...mainActionIconProps, fullWidth: canBeFullWidth, className: clsx(styles['trigger-button'], hasNoText && styles['has-no-text'], isVisualRefresh && styles['visual-refresh'], canBeFullWidth && styles['main-action-full-width']), variant: variant, ariaLabel: mainActionAriaLabel, formAction: "none", nativeAnchorAttributes: nativeMainActionAttributes === null || nativeMainActionAttributes === void 0 ? void 0 : nativeMainActionAttributes.anchor, nativeButtonAttributes: nativeMainActionAttributes === null || nativeMainActionAttributes === void 0 ? void 0 : nativeMainActionAttributes.button }, text));
trigger = (React.createElement("div", { role: "group", "aria-label": ariaLabel, className: styles['split-trigger-wrapper'] },
React.createElement("div", { className: clsx(styles['trigger-item'], styles['split-trigger'], styles[`variant-${variant}`], mainActionProps.loading && styles.loading),
// Close dropdown upon main action click unless event is cancelled.
onClick: closeDropdown,
// Prevent keyboard events from propagation to the button dropdown handler.
onKeyDown: e => e.stopPropagation(), onKeyUp: e => e.stopPropagation(), ...getAnalyticsMetadataAttribute({
action: 'click',
detail: {
label: `.${analyticsSelectors['main-action-label']}`,
},
}) }, mainActionButton),
!showMainActionOnly && (React.createElement("div", { className: clsx(styles['trigger-item'], styles['dropdown-trigger'], isVisualRefresh && styles['visual-refresh'], styles[`variant-${variant}`], baseTriggerProps.loading && styles.loading), ...getAnalyticsMetadataAttribute(analyticsMetadata) },
React.createElement(InternalButton, { ref: triggerRef, ...baseTriggerProps, className: clsx(baseTriggerProps.className, {
[styles['main-action-trigger-full-width']]: canBeFullWidth,
}), __emitPerformanceMarks: false }, children)))));
}
else {
trigger = (React.createElement("div", { className: styles['dropdown-trigger'], ...getAnalyticsMetadataAttribute(analyticsMetadata) },
React.createElement(InternalButton, { ref: triggerRef, id: triggerId, ...baseTriggerProps, className: clsx(baseTriggerProps.className, {
[styles['full-width']]: canBeFullWidth,
[styles.loading]: canBeFullWidth && !!loading,
}), badge: triggerHasBadge(), fullWidth: fullWidth }, children)));
}
const hasHeader = title || description;
const headerId = useUniqueId('awsui-button-dropdown__header');
const shouldLabelWithTrigger = !ariaLabel && !mainAction && variant !== 'icon' && variant !== 'inline-icon';
const { loadingButtonCount } = useFunnel();
useEffect(() => {
if (loading) {
loadingButtonCount.current++;
return () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
loadingButtonCount.current--;
};
}
}, [loading, loadingButtonCount]);
return (React.createElement("div", { ...baseProps, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onMouseDown: handleMouseEvent, onMouseMove: handleMouseEvent, className: clsx(styles['button-dropdown'], styles[`variant-${variant}`], canBeFullWidth && styles['full-width'], baseProps.className), "aria-owns": expandToViewport && isOpen ? dropdownId : undefined, ref: __internalRootRef },
React.createElement(Dropdown, { open: canBeOpened && isOpen, stretchWidth: false, stretchTriggerHeight: variant === 'navigation', expandToViewport: expandToViewport, preferCenter: preferCenter, onDropdownClose: () => toggleDropdown(), trigger: trigger, dropdownId: dropdownId },
hasHeader && (React.createElement("div", { className: styles.header, id: headerId },
title && (React.createElement("div", { className: styles.title },
React.createElement(InternalBox, { fontSize: "heading-s", fontWeight: "bold", color: "inherit", tagOverride: "h2", margin: { vertical: 'n', horizontal: 'n' } }, title))),
description && (React.createElement(InternalBox, { fontSize: "body-s" },
React.createElement("span", { className: styles.description }, description))))),
React.createElement(OptionsList, { open: canBeOpened && isOpen, position: "static", role: "menu", tagOverride: "ul", decreaseBlockMargin: true, ariaLabel: ariaLabel, ariaLabelledby: hasHeader ? headerId : shouldLabelWithTrigger ? triggerId : undefined, statusType: "finished" },
React.createElement(ItemsList, { items: items, onItemActivate: onItemActivate, onGroupToggle: onGroupToggle, hasExpandableGroups: expandableGroups, targetItem: targetItem, isHighlighted: isHighlighted, isKeyboardHighlight: isKeyboardHighlight, isExpanded: isExpanded, lastInDropdown: true, highlightItem: highlightItem, expandToViewport: expandToViewport, variant: variant, analyticsMetadataTransformer: analyticsMetadataTransformer, linkStyle: linkStyle, position: position })))));
});
export default InternalButtonDropdown;
//# sourceMappingURL=internal.js.map