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

117 lines 7.61 kB
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import React, { useEffect, useRef } from 'react'; import clsx from 'clsx'; import { getAnalyticsMetadataAttribute, } from '@awsui/component-toolkit/internal/analytics-metadata'; import InternalIcon from '../../icon/internal'; import { useDropdownContext } from '../../internal/components/dropdown/context'; import useHiddenDescription from '../../internal/hooks/use-hidden-description'; import { useVisualRefresh } from '../../internal/hooks/use-visual-mode'; import Tooltip from '../tooltip'; import { getMenuItemCheckboxProps, getMenuItemProps } from '../utils/menu-item'; import { isCheckboxItem, isLinkItem } from '../utils/utils'; import { getItemTarget } from '../utils/utils'; import analyticsLabels from '../analytics-metadata/styles.css.js'; import styles from './styles.css.js'; const ItemElement = ({ position = '1', item, disabled, onItemActivate, highlighted, highlightItem, showDivider, hasCategoryHeader, isKeyboardHighlighted = false, analyticsMetadataTransformer = (metadata) => metadata, variant = 'normal', linkStyle, }) => { const isLink = isLinkItem(item); const isCheckbox = isCheckboxItem(item); const onClick = (event) => { // Stop propagation to parent node and handle event exclusively in here. This ensures // that no group will interfere with the default behavior of links event.stopPropagation(); if (!isLink) { event.preventDefault(); } if (!disabled) { onItemActivate(item, event); } }; const onHover = () => { highlightItem(item); }; const isVisualRefresh = useVisualRefresh(); return (React.createElement("li", { className: clsx(styles['item-element'], styles[`variant-${variant}`], { [styles.highlighted]: highlighted, [styles.disabled]: disabled, [styles['has-category-header']]: hasCategoryHeader, [styles['has-checkmark']]: isCheckbox, [styles['show-divider']]: showDivider, [styles['is-focused']]: isKeyboardHighlighted, [styles['visual-refresh']]: isVisualRefresh, }), role: "presentation", "data-testid": item.id, "data-description": item.description, onClick: onClick, onMouseEnter: onHover, onTouchStart: onHover, ...getAnalyticsMetadataAttribute(disabled ? {} : analyticsMetadataTransformer({ action: 'click', detail: { position, id: item.id, label: `.${analyticsLabels['menu-item']}`, href: item.href || '', }, })) }, React.createElement(MenuItem, { item: item, disabled: disabled, highlighted: highlighted, linkStyle: linkStyle }))); }; function MenuItem({ item, disabled, highlighted, linkStyle }) { const menuItemRef = useRef(null); const isCheckbox = isCheckboxItem(item); const isCurrentBreadcrumb = !isCheckbox && item.isCurrentBreadcrumb; useEffect(() => { if (highlighted && menuItemRef.current) { menuItemRef.current.focus(); } }, [highlighted]); const isDisabledWithReason = disabled && item.disabledReason; const { targetProps, descriptionEl } = useHiddenDescription(item.disabledReason); const menuItemProps = { 'aria-label': item.ariaLabel, className: clsx(styles['menu-item'], analyticsLabels['menu-item'], linkStyle && styles['link-style'], linkStyle && highlighted && styles['link-style-highlighted'], isCurrentBreadcrumb && styles['current-breadcrumb']), 'aria-current': isCurrentBreadcrumb, lang: item.lang, ref: menuItemRef, // We are using the roving tabindex technique to manage the focus state of the dropdown. // The current element will always have tabindex=0 which means that it can be tabbed to, // while all other items have tabindex=-1 so we can focus them when necessary. tabIndex: highlighted ? 0 : -1, ...(isCheckbox ? getMenuItemCheckboxProps({ disabled, checked: item.checked }) : getMenuItemProps({ disabled })), ...(isDisabledWithReason ? targetProps : {}), }; const menuItem = isLinkItem(item) ? (React.createElement("a", { ...menuItemProps, href: !disabled ? item.href : undefined, download: !disabled && item.download ? item.download : undefined, target: getItemTarget(item), rel: item.external ? 'noopener noreferrer' : undefined }, React.createElement(MenuItemContent, { item: item, disabled: disabled, highlighted: highlighted }))) : (React.createElement("span", { ...menuItemProps }, React.createElement(MenuItemContent, { item: item, disabled: disabled, highlighted: highlighted }))); const { position } = useDropdownContext(); const tooltipPosition = position === 'bottom-left' || position === 'top-left' ? 'left' : 'right'; return isDisabledWithReason ? (React.createElement(Tooltip, { content: item.disabledReason, position: tooltipPosition, className: styles['item-tooltip-wrapper'] }, menuItem, descriptionEl)) : (menuItem); } const MenuItemContent = ({ item, disabled, highlighted, }) => { const hasIcon = !!(item.iconName || item.iconUrl || item.iconSvg); const hasExternal = isLinkItem(item) && item.external; const isCheckbox = isCheckboxItem(item); return (React.createElement(React.Fragment, null, isCheckbox && React.createElement(MenuItemCheckmark, { checked: item.checked, disabled: disabled }), hasIcon && (React.createElement(MenuItemIcon, { name: item.iconName, url: item.iconUrl, svg: item.iconSvg, alt: item.iconAlt, badge: item.badge })), React.createElement("div", { className: styles['content-wrapper'] }, React.createElement("div", { className: styles['main-row'] }, React.createElement("div", null, item.text, hasExternal && React.createElement(ExternalIcon, { disabled: disabled, ariaLabel: item.externalIconAriaLabel })), item.labelTag && (React.createElement("div", { className: clsx(styles['label-tag'], disabled && styles.disabled) }, item.labelTag))), item.secondaryText && (React.createElement("div", { className: clsx(styles['secondary-text'], highlighted && styles.highlighted, disabled && styles.disabled) }, item.secondaryText))))); }; const MenuItemIcon = (props) => (React.createElement("span", { className: styles.icon }, React.createElement(InternalIcon, { ...props }))); // Toggle has aria-hidden set because it's just used as a graphical element, // a11y attributes for the checkmark are communicated through the role and aria-checked state // of the menu element item. const MenuItemCheckmark = ({ disabled, checked }) => { const checkmark = React.createElement(InternalIcon, { variant: disabled ? 'disabled' : 'normal', name: "check" }); return (React.createElement("span", { className: clsx(styles.icon, styles.checkmark, { [styles.disabled]: disabled }), "aria-hidden": "true", style: { visibility: checked ? 'visible' : 'hidden' } }, checkmark)); }; const ExternalIcon = ({ disabled, ariaLabel }) => { const icon = React.createElement(InternalIcon, { variant: disabled ? 'disabled' : 'normal', name: "external" }); return (React.createElement("span", { className: styles['external-icon'], role: ariaLabel ? 'img' : undefined, "aria-label": ariaLabel }, icon)); }; export default ItemElement; //# sourceMappingURL=index.js.map