@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
192 lines • 12.7 kB
JavaScript
import { __rest } from "tslib";
// 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 { 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 { useUniqueId } from '../internal/hooks/use-unique-id';
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((_a, ref) => {
var _b;
var { 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 } = _a, props = __rest(_a, ["items", "variant", "loading", "loadingText", "disabled", "disabledReason", "expandableGroups", "children", "onItemClick", "onItemFollow", "customTriggerBuilder", "expandToViewport", "ariaLabel", "title", "description", "preferCenter", "mainAction", "showMainActionOnly", "__internalRootRef", "analyticsMetadataTransformer", "linkStyle", "fullWidth"]);
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 isMainAction = 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 = (isMainAction ? 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, isMainAction]);
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 = Object.assign(Object.assign({ 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', __nativeAttributes: {
'aria-haspopup': true,
} });
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: 'expand',
detail: {
expanded: `${!isOpen}`,
label: `.${analyticsSelectors['trigger-label']}`,
},
};
if (customTriggerBuilder) {
trigger = (React.createElement("div", Object.assign({ 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 (isMainAction) {
const { text, iconName, iconAlt, iconSvg, iconUrl, external, externalIconAriaLabel } = mainAction, mainActionProps = __rest(mainAction, ["text", "iconName", "iconAlt", "iconSvg", "iconUrl", "external", "externalIconAriaLabel"]);
const mainActionIconProps = external
? { iconName: 'external', iconAlign: 'right' }
: { iconName, iconAlt, iconSvg, iconUrl };
const mainActionAriaLabel = externalIconAriaLabel
? `${(_b = mainAction.ariaLabel) !== null && _b !== void 0 ? _b : mainAction.text} ${mainAction.externalIconAriaLabel}`
: mainAction.ariaLabel;
const hasNoText = !text;
const mainActionButton = (React.createElement(InternalButton, Object.assign({ 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" }), text));
trigger = (React.createElement("div", { role: "group", "aria-label": ariaLabel, className: styles['split-trigger-wrapper'] },
React.createElement("div", Object.assign({ className: clsx(styles['trigger-item'], styles['split-trigger'], styles[`variant-${variant}`], mainActionProps.disabled && styles.disabled, 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", Object.assign({ className: clsx(styles['trigger-item'], styles['dropdown-trigger'], isVisualRefresh && styles['visual-refresh'], styles[`variant-${variant}`], baseTriggerProps.disabled && styles.disabled, baseTriggerProps.loading && styles.loading) }, getAnalyticsMetadataAttribute(analyticsMetadata)),
React.createElement(InternalButton, Object.assign({ ref: triggerRef }, baseTriggerProps, { className: clsx(baseTriggerProps.className, {
[styles['main-action-trigger-full-width']]: canBeFullWidth,
}), __emitPerformanceMarks: false }), children)))));
}
else {
trigger = (React.createElement("div", Object.assign({ className: styles['dropdown-trigger'] }, getAnalyticsMetadataAttribute(analyticsMetadata)),
React.createElement(InternalButton, Object.assign({ 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", Object.assign({}, 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", 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 })))));
});
export default InternalButtonDropdown;
//# sourceMappingURL=internal.js.map