@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
155 lines • 8.43 kB
JavaScript
import { __rest } from "tslib";
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useContext, useRef } from 'react';
import clsx from 'clsx';
import { useInternalI18n } from '../i18n/context';
import InternalIcon from '../icon/internal';
import { FunnelMetrics } from '../internal/analytics';
import { useFunnel, useFunnelStep, useFunnelSubStep } from '../internal/analytics/hooks/use-funnel';
import { DATA_ATTR_FUNNEL_VALUE, getFunnelValueSelector, getSubStepAllSelector, getTextFromSelector, } from '../internal/analytics/selectors';
import { getBaseProps } from '../internal/base-component';
import { InfoLinkLabelContext } from '../internal/context/info-link-label-context';
import { LinkDefaultVariantContext } from '../internal/context/link-default-variant-context';
import { useSingleTabStopNavigation } from '../internal/context/single-tab-stop-navigation-context';
import { fireCancelableEvent, fireNonCancelableEvent, isPlainLeftClick } from '../internal/events';
import useForwardFocus from '../internal/hooks/forward-focus';
import { useMergeRefs } from '../internal/hooks/use-merge-refs';
import { useUniqueId } from '../internal/hooks/use-unique-id';
import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
import { KeyCode } from '../internal/keycode';
import { checkSafeUrl } from '../internal/utils/check-safe-url';
import styles from './styles.css.js';
const InternalLink = React.forwardRef((_a, ref) => {
var { variant: providedVariant, fontSize = 'body-m', color = 'normal', external = false, target, href, rel, ariaLabel, externalIconAriaLabel, onFollow, onClick, children, __internalRootRef = null } = _a, props = __rest(_a, ["variant", "fontSize", "color", "external", "target", "href", "rel", "ariaLabel", "externalIconAriaLabel", "onFollow", "onClick", "children", "__internalRootRef"]);
checkSafeUrl('Link', href);
const isButton = !href;
const { defaultVariant } = useContext(LinkDefaultVariantContext);
const variant = providedVariant || defaultVariant;
const specialStyles = ['top-navigation', 'link', 'recovery'];
const hasSpecialStyle = specialStyles.indexOf(variant) > -1;
const i18n = useInternalI18n('link');
const baseProps = getBaseProps(props);
const anchorTarget = target !== null && target !== void 0 ? target : (external ? '_blank' : undefined);
const anchorRel = rel !== null && rel !== void 0 ? rel : (anchorTarget === '_blank' ? 'noopener noreferrer' : undefined);
const uniqueId = useUniqueId('link');
const linkId = useUniqueId('link-self');
const infoId = useUniqueId('link-info');
const infoLinkLabelFromContext = useContext(InfoLinkLabelContext);
const { funnelIdentifier, funnelInteractionId } = useFunnel();
const { stepIdentifier, stepNumber, stepNameSelector } = useFunnelStep();
const { subStepIdentifier, subStepSelector, subStepNameSelector } = useFunnelSubStep();
const fireFunnelEvent = (funnelInteractionId) => {
if (variant === 'info') {
const stepName = getTextFromSelector(stepNameSelector);
const subStepName = getTextFromSelector(subStepNameSelector);
FunnelMetrics.helpPanelInteracted({
funnelIdentifier,
funnelInteractionId,
stepIdentifier,
stepNumber,
stepName,
subStepIdentifier,
stepNameSelector,
subStepSelector,
subStepName,
subStepNameSelector,
elementSelector: getFunnelValueSelector(uniqueId),
subStepAllSelector: getSubStepAllSelector(),
});
}
else if (external) {
const stepName = getTextFromSelector(stepNameSelector);
const subStepName = getTextFromSelector(subStepNameSelector);
FunnelMetrics.externalLinkInteracted({
funnelIdentifier,
funnelInteractionId,
stepIdentifier,
stepNumber,
stepName,
stepNameSelector,
subStepIdentifier,
subStepSelector,
subStepName,
subStepNameSelector,
elementSelector: getFunnelValueSelector(uniqueId),
subStepAllSelector: getSubStepAllSelector(),
});
}
};
const fireFollowEvent = (event) => {
if (funnelInteractionId) {
fireFunnelEvent(funnelInteractionId);
}
fireCancelableEvent(onFollow, { href, external, target: anchorTarget }, event);
};
const fireClickEvent = (event) => {
const { altKey, ctrlKey, metaKey, shiftKey } = event;
const button = 'button' in event ? event.button : 0;
// make onClick non-cancelable to prevent it from being used to block full page reload
// for navigation use `onFollow` event instead
fireNonCancelableEvent(onClick, { altKey, button, ctrlKey, metaKey, shiftKey });
};
const handleLinkClick = (event) => {
if (isPlainLeftClick(event)) {
fireFollowEvent(event);
}
fireClickEvent(event);
};
const handleButtonClick = (event) => {
fireFollowEvent(event);
fireClickEvent(event);
};
const handleButtonKeyDown = (event) => {
if (event.keyCode === KeyCode.space || event.keyCode === KeyCode.enter) {
event.preventDefault();
fireFollowEvent(event);
fireClickEvent(event);
}
};
const linkRef = useRef(null);
const isVisualRefresh = useVisualRefresh();
useForwardFocus(ref, linkRef);
// Visual refresh should only add styles to buttons that don't already have unique styles (e.g. primary/secondary variants)
const applyButtonStyles = isButton && isVisualRefresh && !hasSpecialStyle;
const sharedProps = Object.assign(Object.assign({ id: linkId }, baseProps), {
// https://github.com/microsoft/TypeScript/issues/36659
ref: useMergeRefs(linkRef, __internalRootRef), className: clsx(styles.link, baseProps.className, applyButtonStyles ? styles.button : null, styles[getVariantStyle(variant)], styles[getFontSizeStyle(variant, fontSize)], styles[getColorStyle(variant, color)]), 'aria-label': ariaLabel, 'aria-labelledby': undefined, [DATA_ATTR_FUNNEL_VALUE]: uniqueId });
if (variant === 'info' && infoLinkLabelFromContext && !ariaLabel) {
sharedProps['aria-labelledby'] = `${sharedProps.id} ${infoId} ${infoLinkLabelFromContext}`;
}
const renderedExternalIconAriaLabel = i18n('externalIconAriaLabel', externalIconAriaLabel);
const content = (React.createElement(React.Fragment, null,
children,
external && (React.createElement("span", { className: styles['icon-wrapper'] },
"\u00A0",
React.createElement("span", { className: styles.icon, "aria-label": renderedExternalIconAriaLabel, role: renderedExternalIconAriaLabel ? 'img' : undefined },
React.createElement(InternalIcon, { name: "external", size: "inherit" })))),
variant === 'info' && (React.createElement("span", { hidden: true, id: infoId }, ":"))));
const { tabIndex } = useSingleTabStopNavigation(linkRef, { tabIndex: isButton ? 0 : undefined });
if (isButton) {
return (React.createElement("a", Object.assign({}, sharedProps, { role: "button", tabIndex: tabIndex, onKeyDown: handleButtonKeyDown, onClick: handleButtonClick }), content));
}
return (
// we dynamically set proper rel in the code above
// eslint-disable-next-line react/jsx-no-target-blank
React.createElement("a", Object.assign({}, sharedProps, { tabIndex: tabIndex, target: anchorTarget, rel: anchorRel, href: href, onClick: handleLinkClick }), content));
});
function getVariantStyle(variant) {
return `variant-${variant.replace(/^awsui-/, '')}`;
}
function getFontSizeStyle(variant, fontSize) {
switch (variant) {
case 'info':
return 'font-size-body-s';
case 'awsui-value-large':
return 'font-size-display-l';
default:
return `font-size-${fontSize}`;
}
}
function getColorStyle(variant, color) {
return `color-${variant === 'info' ? 'normal' : color}`;
}
export default InternalLink;
//# sourceMappingURL=internal.js.map