import React from "react";
import UI from "../ui";
import type { LinkProps } from "./link.types";
import { useDisabledState } from "../../hooks/use-disabled-state";
/**
* Link - A semantic, accessible anchor component with enhanced security and styling.
*
* The Link component renders accessible `` elements with automatic security
* attributes for external links, customizable styling variants, and full WCAG 2.1
* AA compliance. It supports traditional text links, button-styled links, and
* programmatic focus management via ref forwarding.
*
* ## Features
*
* - ๐ **Automatic Security**: External links get `rel="noopener noreferrer"`
* - โฟ **WCAG 2.1 AA Compliant**: Accessible focus indicators and semantic HTML
* - ๐จ **Flexible Styling**: Text links, button links, and pill variants
* - โก **Performance**: Optional prefetch hints for faster navigation
* - ๐ฏ **Ref Forwarding**: Direct DOM access for focus management and scroll
* - ๐งช **Type-Safe**: Full TypeScript support with comprehensive prop types
*
* ## Accessibility
*
* - โ
Semantic `` element for proper keyboard navigation
* - โ
Focus indicators meet WCAG 2.4.7 (3:1 contrast ratio)
* - โ
Screen readers announce link purpose and destination
* - โ
External links include security attributes automatically
* - โ
Supports `aria-label` for icon-only or ambiguous links
* - โ
Ref forwarding enables skip-link patterns
*
* @example
* // Basic internal link
* About Us
*
* @example
* // External link with automatic security
*
* Visit Example
*
*
* @example
* // Button-styled call-to-action link
*
* Get Started
*
*
* @example
* // Icon-only link with accessible label
*
*
*
*
* @example
* // Analytics tracking with onClick (includes keyboard users)
* trackEvent('link_click', { href: '/products' })}
* >
* Browse Products
*
*
* @example
* // Skip link with ref forwarding for focus management
* const mainRef = useRef(null);
*
*
* Skip to main content
*
*
* @example
* // Custom styled link with CSS variables
*
* Browse Products
*
*
* @example
* // โ
GOOD: Descriptive link text
*
* Read installation guide
*
*
* @example
* // โ BAD: Generic link text (poor for screen readers)
*
* Click here
*
*
* @see {@link LinkProps} for complete prop documentation
*/
export const Link = React.forwardRef(
(
{
href,
target,
rel,
children,
styles,
prefetch = false,
btnStyle,
onClick,
onPointerDown,
disabled,
className,
...props
},
ref
) => {
// Anchor elements have no native `disabled` attribute; the library's
// convention is aria-disabled via the shared hook so the link stays in
// tab order (WCAG 2.1.1) and screen readers announce the state.
const { disabledProps, handlers } = useDisabledState(
disabled,
{
handlers: { onClick, onPointerDown },
className,
}
);
/**
* Compute the final `rel` attribute value with security defaults.
*
* For external links (target="_blank"), we merge user-provided `rel` values
* with security defaults `noopener noreferrer` to prevent:
* - window.opener exploitation (noopener)
* - Referrer header leakage (noreferrer)
*
* If prefetch is enabled, we also add the `prefetch` hint.
*/
const computedRel = React.useMemo(() => {
if (target === "_blank") {
// Start with security defaults
const securityTokens = new Set(["noopener", "noreferrer"]);
// Add prefetch if enabled
if (prefetch) {
securityTokens.add("prefetch");
}
// Merge with user-provided rel tokens (if any)
if (rel) {
rel.split(/\s+/).forEach((token) => {
if (token) securityTokens.add(token);
});
}
return Array.from(securityTokens).join(" ");
}
// For non-external links, use provided rel as-is
return rel;
}, [target, rel, prefetch]);
// Disabled links shouldn't navigate on keyboard activation either; the
// hook already blocks onClick, but removing href closes the loop for
// screen readers and prevents URL flashes on activation.
const safeHref = disabled ? undefined : href;
// Only emit the disabled attributes when actually disabled โ default
// `aria-disabled="false"` adds noise to the DOM and invalidates
// snapshot tests of callers that render Links (breadcrumbs, nav, etc).
const disabledAttrs = disabled
? { ...disabledProps }
: { className: className || undefined };
return (
{children}
);
}
);
export const IconLink = React.forwardRef(
({ href, icon, ...props }, ref) => {
return (
{icon}
);
}
);
export const LinkButton = React.forwardRef(
({ href, children, ...props }, ref) => {
return (
{children}
);
}
);
Link.displayName = "Link";
IconLink.displayName = "IconLink";
LinkButton.displayName = "LinkButton";
// Compound component pattern - attach subcomponents to Link with proper typing
export interface LinkComponent
extends React.ForwardRefExoticComponent<
LinkProps & React.RefAttributes
> {
LinkButton: typeof LinkButton;
IconLink: typeof IconLink;
}
// Attach subcomponents to Link using Object.assign for better type inference
const LinkWithSubcomponents = Object.assign(Link, {
LinkButton,
IconLink,
});
export default LinkWithSubcomponents as LinkComponent;