UNPKG

@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
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