import { Meta } from "@storybook/addon-docs/blocks";

<Meta title="FP.REACT Components/Links/Readme" />

# Link Component

A semantic, accessible anchor component with enhanced security for external
links, customizable styling variants, and full WCAG 2.1 AA compliance. The Link
component wraps native `<a>` elements with automatic security attributes,
flexible styling, and programmatic focus management.

## Summary

The `Link` component provides a type-safe, accessible way to create hyperlinks
with built-in security for external URLs, button-styled variants, and
performance optimizations. It automatically adds `rel="noopener noreferrer"` to
external links, supports ref forwarding for focus management, and offers
flexible styling through CSS custom properties.

**Latest Version:** v1.0.0+

## Features

- 🔒 **Automatic Security** - External links (`target="_blank"`) get
  `rel="noopener noreferrer"` automatically
- ♿ **WCAG 2.1 AA Compliant** - Accessible focus indicators, semantic HTML, and
  screen reader support
- 🎨 **Flexible Styling** - Text links, button-styled links, and pill variants
  via CSS custom properties
- ⚡ **Performance Optimized** - useMemo for rel computation, ref forwarding,
  and minimal re-renders
- 🎯 **Ref Forwarding** - Direct DOM access for skip links, focus management,
  and scroll positioning
- 🔧 **Type-Safe** - Comprehensive TypeScript types with JSDoc documentation
- 🧪 **100% Tested** - Complete test coverage with accessibility validation via
  axe-core
- 📦 **Zero Dependencies** - Only relies on React and the UI component

## Accessibility

- ✅ Semantic `<a>` element for proper navigation and screen reader
  announcements
- ✅ Focus indicators meet WCAG 2.4.7 (3:1 contrast ratio minimum)
- ✅ `:focus-visible` support for better UX (keyboard vs mouse differentiation)
- ✅ External links include automatic security attributes
- ✅ Supports `aria-label` for icon-only or ambiguous links
- ✅ Ref forwarding enables skip-link patterns and programmatic focus
- ✅ No keyboard traps - standard tab navigation works as expected
- ✅ Screen readers announce link purpose and destination

**Accessibility Rating:** ✅ A (Excellent) - WCAG 2.1 Level AA

## Props

```ts
type LinkProps = {
  /** The URL that the hyperlink points to (relative or absolute) */
  href?: string;

  /** Where to display the linked URL (_self, _blank, _parent, _top) */
  target?: string;

  /** Relationship between current document and linked URL */
  rel?: string;

  /** Content to display inside the link */
  children: React.ReactNode;

  /** Inline CSS styles (can override CSS custom properties) */
  styles?: React.CSSProperties;

  /** Hints to browser to prefetch the resource (added to rel for target="_blank") */
  prefetch?: boolean;

  /** Applies button-like styling to the link */
  btnStyle?: string;

  /** Event handler called when link is clicked or activated (RECOMMENDED for analytics) */
  onClick?: (event: React.MouseEvent<HTMLAnchorElement>) => void;

  /** Event handler for pointer down events (mouse, touch, pen) - does NOT fire for keyboard */
  onPointerDown?: (event: React.PointerEvent<HTMLAnchorElement>) => void;
} & React.ComponentProps<typeof UI> &
  React.ComponentPropsWithoutRef<"a">;
```

### Default Values

| Prop       | Default     | Description                       |
| ---------- | ----------- | --------------------------------- |
| `href`     | `undefined` | Required for valid links          |
| `target`   | `undefined` | Default browser behavior (\_self) |
| `rel`      | `undefined` | Auto-computed for external links  |
| `prefetch` | `false`     | No prefetch by default            |
| `btnStyle` | `undefined` | Standard link styling (no button) |

## Usage Examples

### Basic Link

Simple internal link with descriptive text:

```tsx
import { Link } from "@fpkit/acss";

function Navigation() {
  return <Link href="/about">About Us</Link>;
}
```

### External Link with Automatic Security

External links automatically include `rel="noopener noreferrer"`:

```tsx
import { Link } from "@fpkit/acss";

function ExternalResource() {
  return (
    <Link href="https://example.com" target="_blank">
      Visit Example.com
    </Link>
  );
}

// Rendered HTML:
// <a href="https://example.com" target="_blank" rel="noopener noreferrer">
//   Visit Example.com
// </a>
```

### External Link with Custom rel Attributes

Custom `rel` values are **merged** with security defaults (not replaced):

```tsx
import { Link } from "@fpkit/acss";

function ExternalNoFollow() {
  return (
    <Link href="https://example.com" target="_blank" rel="nofollow author">
      External Resource
    </Link>
  );
}

// Rendered HTML includes: noopener, noreferrer, nofollow, author
```

### External Link with Prefetch

Performance optimization for anticipated navigation:

```tsx
import { Link } from "@fpkit/acss";

function NextPage() {
  return (
    <Link href="https://example.com/next" target="_blank" prefetch>
      Next Page
    </Link>
  );
}

// Rendered HTML:
// <a ... rel="noopener noreferrer prefetch">
```

### Button-Styled Link

Use `<b>` wrapper for button styling (maintains semantic `<a>`):

```tsx
import { Link } from "@fpkit/acss";

function CallToAction() {
  return (
    <Link href="/signup">
      <b>Sign Up Now</b>
    </Link>
  );
}
```

### Pill-Styled Link

Use `<i>` wrapper for fully rounded pill styling:

```tsx
import { Link } from "@fpkit/acss";

function PillButton() {
  return (
    <Link href="/get-started">
      <i>Get Started</i>
    </Link>
  );
}
```

### Button-Styled with data-btn Attribute

Alternative method using `btnStyle` prop:

```tsx
import { Link } from "@fpkit/acss";

function ActionButton() {
  return (
    <Link href="/action" btnStyle="primary">
      Take Action
    </Link>
  );
}
```

### Custom Styling with CSS Variables

Override default styles using CSS custom properties:

```tsx
import { Link } from "@fpkit/acss";

function CustomLink() {
  return (
    <Link
      href="/products"
      styles={{
        "--link-color": "#d63384",
        "--link-decoration": "underline",
        "--link-weight": "600",
      }}
    >
      Featured Products
    </Link>
  );
}
```

### Icon-Only Link with Accessible Label

Critical: Icon-only links **must** have `aria-label`:

```tsx
import { Link } from "@fpkit/acss";
import { SettingsIcon } from "../icons";

function SettingsLink() {
  return (
    <Link href="/settings" aria-label="Open settings">
      <SettingsIcon aria-hidden="true" />
    </Link>
  );
}
```

### Skip Link with Ref Forwarding

Enable keyboard users to skip navigation:

```tsx
import { Link } from "@fpkit/acss";
import { useRef, useEffect } from "react";

function SkipNavigation() {
  const mainRef = useRef<HTMLAnchorElement>(null);

  // Focus skip link on page load for keyboard users
  useEffect(() => {
    const handleKeyDown = (e: KeyboardEvent) => {
      if (e.key === "Tab" && !e.shiftKey) {
        mainRef.current?.focus();
      }
    };

    window.addEventListener("keydown", handleKeyDown, { once: true });
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, []);

  return (
    <Link
      ref={mainRef}
      href="#main-content"
      styles={{ "--link-color": "#fff" }}
    >
      Skip to main content
    </Link>
  );
}
```

### Email and Phone Links

Support for non-HTTP URL schemes:

```tsx
import { Link } from "@fpkit/acss";

function ContactLinks() {
  return (
    <>
      <Link href="mailto:hello@example.com">Email Us</Link>
      <Link href="tel:+1234567890">Call: +1 (234) 567-890</Link>
    </>
  );
}
```

### Link with Event Tracking (onClick - Recommended)

**Recommended**: Use `onClick` for analytics and tracking - it captures ALL
activation methods including keyboard:

```tsx
import { Link } from "@fpkit/acss";

function TrackedLink() {
  const handleLinkClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
    // Track analytics event - captures mouse, touch, AND keyboard activation
    console.log("Link clicked:", e.currentTarget.href);
    // Optional: prevent default and handle navigation manually
    // e.preventDefault();
  };

  return (
    <Link href="/products" onClick={handleLinkClick}>
      Browse Products
    </Link>
  );
}
```

### Link with Pointer-Specific Tracking (onPointerDown)

Use `onPointerDown` when you need pointer-specific data (mouse vs touch vs pen):

```tsx
import { Link } from "@fpkit/acss";

function PointerTrackedLink() {
  const handlePointerDown = (e: React.PointerEvent<HTMLAnchorElement>) => {
    // Track pointer type: mouse, touch, or pen
    console.log("Pointer type:", e.pointerType);
    console.log("Pointer ID:", e.pointerId);
  };

  return (
    <Link href="/products" onPointerDown={handlePointerDown}>
      Browse Products
    </Link>
  );
}
```

⚠️ **Accessibility Note**: `onPointerDown` does NOT fire for keyboard activation
(Enter key). If you need to track keyboard users, use `onClick` instead.

### Link with Both Event Handlers

Use both handlers together for comprehensive tracking:

```tsx
import { Link } from "@fpkit/acss";

function ComprehensiveTracking() {
  const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
    // Captures ALL activations (mouse, touch, keyboard)
    console.log("Link activated:", e.currentTarget.href);
  };

  const handlePointerDown = (e: React.PointerEvent<HTMLAnchorElement>) => {
    // Captures pointer-specific data
    console.log("Pointer type:", e.pointerType);
  };

  return (
    <Link
      href="/products"
      onClick={handleClick}
      onPointerDown={handlePointerDown}
    >
      Browse Products
    </Link>
  );
}
```

### Anchor Link for Same-Page Navigation

Jump to sections within the same page:

```tsx
import { Link } from "@fpkit/acss";

function TableOfContents() {
  return (
    <nav>
      <Link href="#introduction">Introduction</Link>
      <Link href="#features">Features</Link>
      <Link href="#examples">Examples</Link>
    </nav>
  );
}
```

### Link with Additional Attributes

Spread all native `<a>` attributes:

```tsx
import { Link } from "@fpkit/acss";

function DetailedLink() {
  return (
    <Link
      href="/download"
      download
      title="Download the user guide PDF"
      aria-describedby="download-description"
    >
      Download Guide
    </Link>
  );
}
```

## Styling

The component uses SCSS with CSS custom properties for theming.

### CSS Custom Properties

```css
a[href] {
  /* Color & Typography */
  --link-color: #085ab7;
  --link-weight: 400;
  --link-fs: 1rem;

  /* Text Decoration */
  --link-decoration: none;
  --link-decoration-offset: 0.09375rem; /* 1.5px */
  --link-decoration-thickness: 0.1875rem; /* 3px */
  --link-skip-ink: auto;

  /* Background & Border */
  --link-bg: transparent;
  --link-radius: 0.25rem;

  /* Focus Indicator (WCAG 2.4.7) */
  --link-focus-color: currentColor;
  --link-focus-width: 0.125rem; /* 2px */
  --link-focus-offset: 0.125rem; /* 2px */
  --link-focus-style: solid;

  /* Button Variant (when using <b> or <i>) */
  --link-button-color: var(--link-color);
  --link-border-width: 0.125rem; /* 2px */
  --link-border-color: currentColor;
  --link-border-style: solid;
}
```

### Theming Example

Create a custom theme by overriding CSS variables:

```css
/* Dark theme example */
.dark-theme a[href] {
  --link-color: #66b3ff;
  --link-focus-color: #fff;
  --link-decoration: underline;
}

/* Brand color override */
.brand-links a[href] {
  --link-color: #ff6b6b;
  --link-weight: 600;
  --link-decoration-thickness: 0.25rem;
}
```

### Button Variant Styling

Button-styled links (via `<b>`, `<i>`, or `data-btn`) automatically apply:

- Inline-flex display with centered content
- Padding based on font size
- Border outline with hover effects
- Scale transition on interaction

```scss
// Applied when link contains <b>, <i>, or has data-btn attribute
a[href]:has(> b),
a[href][data-btn],
a[href]:has(> i) {
  display: inline-flex;
  align-items: center;
  padding-inline: var(--link-fs);
  padding-block: calc(var(--link-fs) - 0.4rem);
  outline: var(--link-border-width) var(--link-border-color) var(--link-border-style);
}
```

## Security Considerations

### Automatic Security for External Links

The Link component **automatically protects** against common vulnerabilities
when opening links in new tabs:

#### window.opener Exploit Prevention

When `target="_blank"` is used without `rel="noopener"`, the opened page can
access the opener window via `window.opener` and potentially navigate it to a
malicious URL.

```tsx
// ✅ SAFE: Automatic protection
<Link href="https://example.com" target="_blank">
  External Link
</Link>
// Renders: <a ... rel="noopener noreferrer">

// ❌ UNSAFE: Raw anchor without protection
<a href="https://example.com" target="_blank">
  External Link
</a>
// Vulnerable to window.opener attacks!
```

#### Referrer Header Privacy

The `noreferrer` attribute prevents the browser from sending the `Referer` HTTP
header, protecting user privacy by not disclosing the source page URL.

### Custom rel Merging

User-provided `rel` values are **merged** with security defaults, not replaced:

```tsx
// User wants: nofollow author
<Link href="https://example.com" target="_blank" rel="nofollow author">
  External
</Link>

// Component renders: noopener noreferrer nofollow author
// Security + custom values = secure AND functional
```

## Best Practices

### Accessibility

1. **Use Descriptive Link Text**

   ```tsx
   // ✅ GOOD: Descriptive text makes sense out of context
   <Link href="/docs/installation">Read installation guide</Link>

   // ❌ BAD: Generic text is meaningless for screen readers
   <Link href="/docs/installation">Click here</Link>
   ```

2. **Provide aria-label for Icon-Only Links**

   ```tsx
   // ✅ GOOD: Screen readers can announce the link purpose
   <Link href="/settings" aria-label="Open settings">
     <SettingsIcon aria-hidden="true" />
   </Link>

   // ❌ BAD: No accessible name for screen readers
   <Link href="/settings">
     <SettingsIcon />
   </Link>
   ```

3. **Don't Rely Only on Color**

   ```tsx
   // ✅ GOOD: Underline + color provides multiple indicators
   <Link href="/important" styles={{ '--link-decoration': 'underline' }}>
     Important Notice
   </Link>

   // ⚠️ OKAY: Color alone may fail WCAG 1.4.1 (Use of Color)
   <Link href="/important" styles={{ '--link-color': 'red' }}>
     Important Notice
   </Link>
   ```

4. **Ensure Sufficient Focus Indicators**

   The component meets WCAG 2.4.7 by default, but custom styles should maintain
   contrast:

   ```tsx
   // ✅ GOOD: Maintains visible focus indicator
   <Link
     href="/test"
     styles={{
       '--link-focus-width': '0.1875rem', // 3px
       '--link-focus-color': '#000',
     }}
   >
     Test
   </Link>

   // ❌ BAD: Removes focus indicator (WCAG violation)
   <Link href="/test" styles={{ outline: 'none' }}>
     Test
   </Link>
   ```

### Event Handlers

1. **Use onClick for Analytics and Tracking**

   ```tsx
   // ✅ GOOD: onClick tracks all activation methods (mouse, touch, keyboard)
   <Link
     href="/products"
     onClick={(e) => trackEvent('link_click', { href: '/products' })}
   >
     Products
   </Link>

   // ❌ BAD: onPointerDown misses keyboard users
   <Link
     href="/products"
     onPointerDown={(e) => trackEvent('link_click', { href: '/products' })}
   >
     Products
   </Link>
   ```

2. **Use onPointerDown for Pointer-Specific Interactions**

   ```tsx
   // ✅ GOOD: onPointerDown for distinguishing input types
   <Link
     href="/drawing"
     onPointerDown={(e) => {
       if (e.pointerType === "pen") {
         enablePenMode();
       }
     }}
   >
     Drawing Tool
   </Link>
   ```

3. **Combine Both Handlers When Needed**

   ```tsx
   // ✅ GOOD: Use both for comprehensive tracking
   <Link
     href="/products"
     onClick={(e) => trackAllUsers(e)}
     onPointerDown={(e) => trackPointerType(e.pointerType)}
   >
     Products
   </Link>
   ```

4. **Memoize Event Handlers**

   ```tsx
   import { useCallback } from "react";

   // ✅ GOOD: Memoized callback prevents re-renders
   const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement>) => {
     trackEvent("link_click", { href: e.currentTarget.href });
   }, []);

   return (
     <Link href="/test" onClick={handleClick}>
       Test
     </Link>
   );
   ```

### Performance

1. **Use prefetch Judiciously**

   ```tsx
   // ✅ GOOD: Prefetch likely next navigation
   <Link href="/checkout" target="_blank" prefetch>
     Proceed to Checkout
   </Link>;

   // ❌ BAD: Prefetching too many resources wastes bandwidth
   {
     links.map((link) => (
       <Link key={link.id} href={link.url} prefetch>
         {link.title}
       </Link>
     ));
   }
   ```

### Styling

1. **Use rem Units for Accessibility**

   ```tsx
   // ✅ GOOD: rem units scale with user font size preferences
   <Link styles={{ '--link-fs': '1.125rem' }}>Large Link</Link>

   // ❌ BAD: px units ignore user preferences
   <Link styles={{ '--link-fs': '18px' }}>Large Link</Link>
   ```

2. **Maintain Semantic HTML**

   ```tsx
   // ✅ GOOD: Button-styled link maintains <a> semantics
   <Link href="/signup"><b>Sign Up</b></Link>

   // ❌ BAD: Don't use buttons for navigation
   <button onClick={() => navigate('/signup')}>Sign Up</button>
   ```

### Security

1. **Trust Automatic Security**

   ```tsx
   // ✅ GOOD: Let component handle security
   <Link href="https://example.com" target="_blank">External</Link>

   // ⚠️ UNNECESSARY: Component already adds these
   <Link href="https://example.com" target="_blank" rel="noopener noreferrer">
     External
   </Link>
   ```

2. **Validate User-Provided URLs**

   ```tsx
   import { Link } from "@fpkit/acss";

   function UserSubmittedLink({ url }: { url: string }) {
     // ✅ GOOD: Validate/sanitize user input
     const isValidUrl = url.startsWith("http://") || url.startsWith("https://");

     if (!isValidUrl) {
       return <span>Invalid URL</span>;
     }

     return (
       <Link href={url} target="_blank">
         Visit
       </Link>
     );
   }
   ```

## Technical Details

### Component Architecture

- **Functional Component** with `React.forwardRef` for ref support
- **Performance Optimized** with `React.useMemo` for rel computation
- **Type-Safe** with extracted TypeScript definitions in `link.types.ts`
- **Accessible** by leveraging semantic HTML5 `<a>` element
- **Composable** via UI component for polymorphic flexibility

### Browser Support

Works in all modern browsers supporting:

- React 18+
- CSS Custom Properties (IE 11+ with fallbacks)
- `:focus-visible` pseudo-class (progressive enhancement)
- `rel="noopener noreferrer"` (all modern browsers)

For older browsers, consider polyfills or fallback styling.

### Ref Type

```ts
const linkRef = useRef<HTMLAnchorElement>(null);
```

The forwarded ref is typed as `HTMLAnchorElement`, providing full DOM API
access:

- `linkRef.current?.focus()` - Programmatic focus
- `linkRef.current?.blur()` - Remove focus
- `linkRef.current?.click()` - Trigger click
- `linkRef.current?.href` - Read/write href
- `linkRef.current?.scrollIntoView()` - Scroll to link

## Testing

### Automated Testing

The component includes comprehensive tests covering:

```bash
npm test -- link.test.tsx
```

Tests include:

- ✅ Basic rendering and prop handling
- ✅ Security (rel attribute merging)
- ✅ External link protection
- ✅ Prefetch behavior
- ✅ Button styling variants
- ✅ Event handlers
- ✅ Ref forwarding
- ✅ Accessibility with axe-core
- ✅ Keyboard navigation
- ✅ URL scheme support (mailto, tel, etc.)
- ✅ Edge cases and performance

### Manual Testing Checklist

#### Keyboard Navigation

- [ ] Tab to link - receives visible focus indicator
- [ ] Focus indicator has 3:1 contrast minimum
- [ ] Enter key activates link (default browser behavior)
- [ ] No keyboard traps

#### Screen Reader Testing (NVDA/VoiceOver/JAWS)

- [ ] Announces as "link" role
- [ ] Announces link text or aria-label
- [ ] External links announce "opens in new window" (browser default)
- [ ] Icon-only links have accessible names

#### Visual Testing

- [ ] Focus indicator visible with sufficient contrast
- [ ] Works at 200% browser zoom
- [ ] Text reflows at 320px viewport width
- [ ] Hover state is visually distinct
- [ ] Button-styled links look clickable

#### Functional Testing

- [ ] Internal links navigate correctly
- [ ] External links open in new tab (when target="\_blank")
- [ ] mailto: and tel: links trigger correct apps
- [ ] Anchor links scroll to target element
- [ ] Event handlers fire as expected

## Related Components

- **Button** - For actions that don't navigate (use button, not link)
- **Breadcrumbs** - For navigation trails using links
- **Navigation** - For site navigation menus

## Resources

- [MDN: The Anchor Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a)
- [WCAG 2.1 Guidelines](https://www.w3.org/WAI/WCAG21/quickref/)
- [WebAIM: Links and Hypertext](https://webaim.org/techniques/hypertext/)
- [rel=noopener Security](https://mathiasbynens.github.io/rel-noopener/)
- [ARIA Authoring Practices: Link](https://www.w3.org/WAI/ARIA/apg/patterns/link/)

## Migration Guide

### From Raw Anchor Elements

```tsx
// Before: Raw <a> tag
<a href="https://example.com" target="_blank" rel="noopener noreferrer">
  External Link
</a>

// After: Link component (automatic security)
<Link href="https://example.com" target="_blank">
  External Link
</Link>
```

### From Previous Link Version (if applicable)

If upgrading from an older Link component that overwrote `rel` values:

```tsx
// Before: Custom rel values were lost
<Link href="https://example.com" target="_blank" rel="nofollow">
  External
</Link>
// Old behavior: rel="noopener noreferrer" (nofollow was lost!)

// After: Custom rel values are merged
<Link href="https://example.com" target="_blank" rel="nofollow">
  External
</Link>
// New behavior: rel="noopener noreferrer nofollow" (merged!)
```

## Changelog

### v1.0.0 (Latest)

- ✨ Initial release with comprehensive features
- ✨ Automatic security for external links (rel merging)
- ✨ React.forwardRef for ref forwarding and focus management
- ✨ useMemo optimization for rel computation
- ✨ Button and pill styling variants via wrappers
- ✨ WCAG 2.1 AA compliant focus indicators
- ✨ :focus-visible support for better UX
- ✨ Comprehensive TypeScript types in separate file
- ✨ 100+ test scenarios with axe-core accessibility validation
- ✨ Complete Storybook documentation with interactive examples
- 📝 Extensive JSDoc documentation
- ♿ Accessibility rating: A (Excellent)

---

**Need Help?** Check the
[Storybook examples](./?path=/docs/fp-react-components-links--docs) or review
the [component tests](./link.test.tsx) for usage patterns.
