Add BDS link styles and update existing link styles

- Introduced new styles for BDS link icons, including hover and focus states.
- Updated existing link styles to exclude BDS links from certain color and hover effects.
- Ensured consistent styling across light and dark themes for BDS links.
- Refactored landing page link styles to accommodate new BDS link classes.
This commit is contained in:
akcodez
2025-12-02 10:21:52 -08:00
parent 5b73ccb8be
commit 2ff14e4224
11 changed files with 1736 additions and 34 deletions

View File

@@ -0,0 +1,395 @@
# Link Component
A comprehensive, accessible link component from the XRPL.org Brand Design System (BDS). Supports multiple variants, sizes, and automatic theme-aware color states with animated arrow icons.
## Table of Contents
- [Installation](#installation)
- [Basic Usage](#basic-usage)
- [Props API](#props-api)
- [Variants](#variants)
- [Sizes](#sizes)
- [Color States](#color-states)
- [Icon Animations](#icon-animations)
- [Accessibility](#accessibility)
- [Best Practices](#best-practices)
- [Examples](#examples)
- [Related Components](#related-components)
---
## Installation
```tsx
import { Link } from 'shared/components/Link';
// or
import { Link } from 'shared/components/Link/Link';
```
---
## Basic Usage
```tsx
// Internal link (default)
<Link href="/docs">View Documentation</Link>
// External link
<Link href="https://example.com" variant="external" target="_blank" rel="noopener noreferrer">
External Resource
</Link>
// Inline link (no icon)
<p>
Learn more about <Link href="/about" variant="inline">our mission</Link>.
</p>
```
---
## Props API
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `href` | `string` | **required** | The URL the link points to |
| `variant` | `'internal' \| 'external' \| 'inline'` | `'internal'` | Link variant determining icon and behavior |
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Size of the link text and icon |
| `icon` | `'arrow' \| 'external' \| null` | `null` | Override icon type (auto-determined by variant if `null`) |
| `disabled` | `boolean` | `false` | Disables the link, preventing navigation |
| `children` | `React.ReactNode` | **required** | Link text content |
| `className` | `string` | - | Additional CSS classes |
| `...rest` | `AnchorHTMLAttributes` | - | All standard anchor attributes (`target`, `rel`, etc.) |
---
## Variants
### Internal (Default)
For navigation within the same website. Displays a horizontal arrow (→) that animates to a chevron (>) on hover.
```tsx
<Link href="/docs" variant="internal">
Internal Documentation
</Link>
```
### External
For links to external websites. Displays a diagonal arrow with corner bracket (↗) that animates on hover. Always use with `target="_blank"` and `rel="noopener noreferrer"` for security.
```tsx
<Link
href="https://github.com/XRPLF"
variant="external"
target="_blank"
rel="noopener noreferrer"
>
GitHub Repository
</Link>
```
### Inline
For links embedded within body text. No icon is displayed, making the link flow naturally within paragraphs.
```tsx
<p>
The XRP Ledger is a decentralized blockchain. You can{" "}
<Link href="/docs" variant="inline">read the documentation</Link>{" "}
to learn more.
</p>
```
---
## Sizes
| Size | Font Size | Line Height | Icon Gap |
|------|-----------|-------------|----------|
| `small` | 14px | 1.5 | 6px |
| `medium` | 16px | 1.5 | 8px |
| `large` | 20px | 1.5 | 10px |
```tsx
<Link href="/docs" size="small">Small Link</Link>
<Link href="/docs" size="medium">Medium Link</Link>
<Link href="/docs" size="large">Large Link</Link>
```
---
## Color States
The Link component automatically handles color states based on the current theme. Colors are applied via CSS and follow the Figma design specifications.
### Light Mode
| State | Color Token | Hex Value | Additional Styles |
|-------|-------------|-----------|-------------------|
| **Enabled** | Green 400 | `#0DAA3E` | No underline |
| **Hover** | Green 500 | `#078139` | Underline, arrow animates |
| **Focus** | Green 500 | `#078139` | Underline, black outline |
| **Active** | Green 400 | `#0DAA3E` | Underline |
| **Visited** | Lilac 400 | `#7649E3` | No underline |
| **Disabled** | Gray 400 | `#A2A2A4` | No underline, no pointer |
### Dark Mode
| State | Color Token | Hex Value | Additional Styles |
|-------|-------------|-----------|-------------------|
| **Enabled** | Green 300 | `#21E46B` | No underline |
| **Hover** | Green 200 | `#70EE97` | Underline, arrow animates |
| **Focus** | Green 200 | `#70EE97` | Underline, white outline |
| **Active** | Green 300 | `#21E46B` | Underline |
| **Visited** | Lilac 300 | `#C0A7FF` | No underline |
| **Disabled** | Gray 500 | `#838386` | No underline, no pointer |
### Focus Outline
- **Light Mode**: 2px solid black (`#000000`)
- **Dark Mode**: 2px solid white (`#FFFFFF`)
---
## Icon Animations
Both internal and external arrow icons feature a smooth animation on hover/focus:
- **Animation Duration**: 150ms
- **Timing Function**: `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
### Internal Arrow Animation
The horizontal line of the arrow (→) shrinks from left to right, revealing a chevron shape (>).
### External Arrow Animation
The diagonal line of the external arrow (↗) scales down toward the top-right corner, leaving just the corner bracket.
### Disabled State
When disabled, the animation is disabled and the icon opacity is reduced to 50%.
---
## Accessibility
The Link component follows accessibility best practices:
### Keyboard Navigation
- Focusable via Tab key
- Activatable via Enter key
- Clear focus indicator with high-contrast outline
### ARIA Attributes
- `aria-disabled="true"` is applied when the link is disabled
- Icons are marked with `aria-hidden="true"` to prevent screen reader announcement
### Best Practices
1. **Use descriptive link text** - Avoid "click here" or "read more"
2. **External links** - Consider adding "(opens in new tab)" for screen readers
3. **Disabled state** - Provide context for why the link is disabled
```tsx
// Good - Descriptive link text
<Link href="/pricing">View pricing plans</Link>
// Bad - Non-descriptive
<Link href="/pricing">Click here</Link>
// External with screen reader context
<Link
href="https://example.com"
variant="external"
target="_blank"
rel="noopener noreferrer"
aria-label="GitHub Repository (opens in new tab)"
>
GitHub Repository
</Link>
```
---
## Best Practices
### Do's
1. **Use appropriate variants**
```tsx
// Internal navigation
<Link href="/about" variant="internal">About Us</Link>
// External sites
<Link href="https://github.com" variant="external" target="_blank" rel="noopener noreferrer">
GitHub
</Link>
// Within paragraphs
<p>Learn about <Link href="/xrp" variant="inline">XRP</Link> today.</p>
```
2. **Match size to context**
```tsx
// Navigation/CTA - use large
<Link href="/get-started" size="large">Get Started</Link>
// Body content - use medium
<Link href="/docs" size="medium">Documentation</Link>
// Footnotes/captions - use small
<Link href="/terms" size="small">Terms of Service</Link>
```
3. **Always use security attributes for external links**
```tsx
<Link
href="https://external-site.com"
variant="external"
target="_blank"
rel="noopener noreferrer"
>
External Site
</Link>
```
### Don'ts
1. **Don't use disabled for navigation prevention** - Use proper routing instead
```tsx
// Bad - Using disabled for auth gate
<Link href="/dashboard" disabled={!isAuthenticated}>Dashboard</Link>
// Good - Handle in onClick or router
<Link href="/dashboard" onClick={handleAuthCheck}>Dashboard</Link>
```
2. **Don't mix variants inappropriately**
```tsx
// Bad - External link with internal variant
<Link href="https://example.com" variant="internal">External Site</Link>
// Good
<Link href="https://example.com" variant="external">External Site</Link>
```
3. **Don't use inline variant for standalone links**
```tsx
// Bad - Standalone inline link
<Link href="/docs" variant="inline">Documentation</Link>
// Good - Use internal for standalone
<Link href="/docs" variant="internal">Documentation</Link>
```
---
## Examples
### Navigation Menu
```tsx
<nav className="d-flex flex-column gap-3">
<Link href="/docs" size="medium">Documentation</Link>
<Link href="/tutorials" size="medium">Tutorials</Link>
<Link href="/api" size="medium">API Reference</Link>
<Link
href="https://github.com/XRPLF"
variant="external"
size="medium"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</Link>
</nav>
```
### Call-to-Action Section
```tsx
<div className="d-flex flex-column gap-4">
<Link href="/get-started" size="large">
Get Started with XRPL
</Link>
<Link href="/use-cases" size="large">
Explore Use Cases
</Link>
</div>
```
### Rich Text Content
```tsx
<article>
<p>
The XRP Ledger (XRPL) is a decentralized, public blockchain led by a{" "}
<Link href="/community" variant="inline">global community</Link>{" "}
of businesses and developers. It supports a wide variety of{" "}
<Link href="/use-cases" variant="inline">use cases</Link>{" "}
including payments, tokenization, and DeFi.
</p>
<p>
To learn more, check out the{" "}
<Link href="/docs" variant="inline">official documentation</Link>{" "}
or visit the{" "}
<Link
href="https://github.com/XRPLF"
variant="inline"
target="_blank"
rel="noopener noreferrer"
>
GitHub repository
</Link>.
</p>
</article>
```
### Disabled State
```tsx
<div className="d-flex flex-column gap-3">
<Link href="/premium" size="medium" disabled>
Premium Features (Coming Soon)
</Link>
<span className="text-muted">This feature is not yet available.</span>
</div>
```
---
## Related Components
- **LinkArrow** - The animated arrow icon component used internally by Link
- **Button** - For actions that don't navigate (forms, modals, etc.)
---
## File Structure
```
shared/components/Link/
├── index.ts # Exports
├── Link.tsx # Main component
├── Link.md # This documentation
├── LinkArrow.tsx # Arrow icon component
├── _link.scss # Link styles
└── _link-icons.scss # Arrow icon styles & animations
```
---
## Changelog
### v1.0.0
- Initial release with internal, external, and inline variants
- Three size options (small, medium, large)
- Theme-aware color states (light/dark mode)
- Animated arrow icons
- Full accessibility support

View File

@@ -0,0 +1,168 @@
import React from 'react';
import clsx from 'clsx';
import { LinkArrow, LinkArrowVariant } from './LinkArrow';
export type LinkVariant = 'internal' | 'external' | 'inline';
export type LinkSize = 'small' | 'medium' | 'large';
export type LinkIconType = 'arrow' | 'external' | null;
export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
/**
* Link variant - internal, external, or inline
* @default 'internal'
*/
variant?: LinkVariant;
/**
* Size of the link
* @default 'medium'
*/
size?: LinkSize;
/**
* Icon type - arrow, external, or null
* If null, icon is determined by variant (internal/external)
* Arrow icons animate to chevron shape on hover
* @default null
*/
icon?: LinkIconType;
/**
* Disabled state - prevents navigation and applies disabled styles
* @default false
*/
disabled?: boolean;
/**
* Link URL (required)
*/
href: string;
/**
* Link text content
*/
children: React.ReactNode;
}
/**
* Link Component
*
* A comprehensive link component supporting multiple sizes, icon types, and states.
* Arrow icons animate to chevron shape on hover.
*
* Color states are handled automatically via CSS per theme:
*
* Light Mode:
* - Enabled: Green 400 (#0DAA3E)
* - Hover/Focus: Green 500 (#078139) + underline + arrow animates to chevron
* - Active: Green 400 (#0DAA3E) + underline
* - Visited: Lilac 400 (#7649E3)
* - Disabled: Gray 400 (#A2A2A4)
* - Focus outline: Black (#000000)
*
* Dark Mode:
* - Enabled: Green 300 (#21E46B)
* - Hover/Focus: Green 200 (#70EE97) + underline + arrow animates to chevron
* - Active: Green 300 (#21E46B) + underline
* - Visited: Lilac 300 (#C0A7FF)
* - Disabled: Gray 500 (#838386)
* - Focus outline: White (#FFFFFF)
*
* @see Link.md for full documentation
*
* @example
* ```tsx
* // Basic internal link (arrow animates to chevron on hover)
* <Link href="/docs" size="medium">
* View documentation
* </Link>
*
* // External link
* <Link href="https://example.com" variant="external" size="large">
* External resource
* </Link>
*
* // Disabled link
* <Link href="#" disabled>
* Coming soon
* </Link>
*
* // Inline link (no icon)
* <Link href="/docs" variant="inline">
* Learn more
* </Link>
* ```
*/
export const Link: React.FC<LinkProps> = ({
variant = 'internal',
size = 'medium',
icon = null,
disabled = false,
href,
children,
className,
onClick,
...rest
}) => {
// Determine icon type based on variant if not explicitly provided
const getIconType = (): LinkArrowVariant | null => {
if (icon === null) {
// Auto-determine icon based on variant
if (variant === 'external') {
return 'external';
}
if (variant === 'internal') {
return 'internal'; // Default to internal arrow for internal variant
}
return null; // Inline links have no icon
}
// Map icon prop to LinkArrow variant
if (icon === 'arrow') return 'internal';
if (icon === 'external') return 'external';
return null;
};
const iconType = getIconType();
const shouldShowIcon = variant !== 'inline' && iconType !== null;
const classes = clsx(
'bds-link',
`bds-link--${variant}`,
`bds-link--${size}`,
{
'bds-link--disabled': disabled,
},
className
);
const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {
if (disabled) {
e.preventDefault();
e.stopPropagation();
return;
}
onClick?.(e);
};
return (
<a
href={disabled ? '#' : href}
className={classes}
onClick={handleClick}
aria-disabled={disabled}
{...rest}
>
{children}
{shouldShowIcon && (
<LinkArrow
variant={iconType as LinkArrowVariant}
size={size}
disabled={disabled}
/>
)}
</a>
);
};
Link.displayName = 'Link';

View File

@@ -0,0 +1,165 @@
import React from 'react';
import clsx from 'clsx';
export type LinkArrowVariant = 'internal' | 'external';
export type LinkArrowSize = 'small' | 'medium' | 'large';
export interface LinkArrowProps extends React.SVGProps<SVGSVGElement> {
/**
* Arrow variant - internal (→) or external (↗)
* Both variants animate on hover (horizontal line shrinks to show chevron)
* @default 'internal'
*/
variant?: LinkArrowVariant;
/**
* Size of the arrow icon
* @default 'medium'
*/
size?: LinkArrowSize;
/**
* Color of the arrow (hex color or CSS color value)
* @default 'currentColor' (inherits from parent)
*/
color?: string;
/**
* Disabled state - reduces opacity and prevents hover animation
* @default false
*/
disabled?: boolean;
/**
* Additional CSS classes
*/
className?: string;
}
// Size mappings for internal arrow (viewBox 0 0 26 22)
const internalSizeMap: Record<LinkArrowSize, { width: number; height: number }> = {
small: { width: 15, height: 14 },
medium: { width: 17, height: 16 },
large: { width: 26, height: 22 },
};
// Size mappings for external arrow (viewBox 0 0 21 21, square aspect ratio)
const externalSizeMap: Record<LinkArrowSize, { width: number; height: number }> = {
small: { width: 14, height: 14 },
medium: { width: 16, height: 16 },
large: { width: 21, height: 21 },
};
/**
* LinkArrow Component
*
* A customizable SVG arrow icon for use in link components.
* Supports internal (→) and external (↗) variants with three size options.
* Both variants animate on hover - horizontal line shrinks to reveal chevron shape.
*
* @example
* ```tsx
* <LinkArrow variant="internal" size="medium" />
* <LinkArrow variant="external" size="large" color="#0DAA3E" />
* <LinkArrow variant="internal" size="small" disabled />
* ```
*/
export const LinkArrow: React.FC<LinkArrowProps> = ({
variant = 'internal',
size = 'medium',
color = 'currentColor',
disabled = false,
className,
...svgProps
}) => {
const dimensions = variant === 'external'
? externalSizeMap[size]
: internalSizeMap[size];
const classes = clsx(
'bds-link-icon',
`bds-link-icon--${variant}`,
`bds-link-icon--${size}`,
{
'bds-link-icon--disabled': disabled,
},
className
);
// Internal arrow (→) - horizontal arrow pointing right
// Horizontal line animates away on hover to show chevron
const renderInternalArrow = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={dimensions.width}
height={dimensions.height}
viewBox="0 0 26 22"
fill="none"
aria-hidden="true"
{...svgProps}
>
{/* Chevron part (static) */}
<path
d="M14.0019 1.00191L24.0015 11.0015L14.0019 21.001"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
/>
{/* Horizontal line (animates away on hover) */}
<path
d="M23.999 10.999H0"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
className="arrow-horizontal"
/>
</svg>
);
// External arrow (↗) - diagonal arrow with corner bracket
// Diagonal line animates away on hover, leaving just the chevron bracket
const renderExternalArrow = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={dimensions.width}
height={dimensions.height}
viewBox="0 0 21 21"
fill="none"
aria-hidden="true"
{...svgProps}
>
{/* Corner bracket - horizontal line (static) */}
<path
d="M4.0031 2L19 2"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
/>
{/* Corner bracket - vertical line (static) */}
<path
d="M19 2V17"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
/>
{/* Diagonal arrow line (animates away on hover) */}
<path
d="M18.9963 2L1 20"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
className="arrow-horizontal"
/>
</svg>
);
return (
<span className={classes}>
{variant === 'external' ? renderExternalArrow() : renderInternalArrow()}
</span>
);
};
LinkArrow.displayName = 'LinkArrow';

View File

@@ -0,0 +1,124 @@
// Link Arrow Icon Styles and Animations
// -----------------------------------------------------------------------------
// Styles for link arrow icons with hover animations that transform arrow to chevron
// Animation: 150ms with custom cubic-bezier per Figma specs
@import "../../../styles/_colors.scss";
// Animation timing per Figma
$bds-link-transition-duration: 150ms;
$bds-link-transition-timing: cubic-bezier(0.98, 0.12, 0.12, 0.98);
// Base styles for link icons
.bds-link-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
svg {
display: block;
path {
stroke: currentColor; // Inherits color from parent link
}
.arrow-horizontal {
transition: transform $bds-link-transition-duration $bds-link-transition-timing;
}
}
}
// Internal arrow: horizontal line shrinks from left toward right (toward the chevron)
.bds-link-icon--internal svg .arrow-horizontal {
transform-origin: right center;
}
// External arrow: diagonal line shrinks toward top-right corner (leaving just the bracket)
.bds-link-icon--external svg .arrow-horizontal {
transform-origin: 19px 2px; // Top-right corner where diagonal starts
}
// Hover state - shrink/hide the horizontal line to create chevron effect
// Applies to both internal and external icons when not disabled
// Internal: scale the horizontal line from right
a:hover .bds-link-icon--internal:not(.bds-link-icon--disabled) svg .arrow-horizontal,
a:focus .bds-link-icon--internal:not(.bds-link-icon--disabled) svg .arrow-horizontal {
transform: scaleX(0);
}
// External: scale the diagonal line toward the corner (uniform scale for diagonal)
a:hover .bds-link-icon--external:not(.bds-link-icon--disabled) svg .arrow-horizontal,
a:focus .bds-link-icon--external:not(.bds-link-icon--disabled) svg .arrow-horizontal {
transform: scale(0);
}
// Disabled state
.bds-link-icon--disabled {
opacity: 0.5;
cursor: not-allowed;
svg {
path {
// Disabled icons use gray color from theme
stroke: currentColor;
}
.arrow-horizontal {
transition: none; // Disable animation when disabled
}
}
}
// Theme-specific disabled colors
html.light,
.light {
.bds-link-icon--disabled svg path {
stroke: $gray-400;
}
}
html.dark,
.dark,
html:not(.light) {
.bds-link-icon--disabled svg path {
stroke: $gray-500;
}
}
// Size variants for internal arrows (wider aspect ratio)
.bds-link-icon--internal {
&.bds-link-icon--small {
width: 15px;
height: 14px;
}
&.bds-link-icon--medium {
width: 17px;
height: 16px;
}
&.bds-link-icon--large {
width: 26px;
height: 22px;
}
}
// Size variants for external arrows (square aspect ratio)
.bds-link-icon--external {
&.bds-link-icon--small {
width: 14px;
height: 14px;
}
&.bds-link-icon--medium {
width: 16px;
height: 16px;
}
&.bds-link-icon--large {
width: 21px;
height: 21px;
}
}

View File

@@ -0,0 +1,243 @@
// Link Component Styles
// -----------------------------------------------------------------------------
// Styles for the Link component with support for sizes, states, and themes
// Light mode colors per Figma: Enabled=green-400, Hover/Focus=green-500+underline,
// Active=green-400+underline, Visited=lilac-400, Disabled=gray-400
// Dark mode colors per Figma: Enabled=green-300, Hover/Focus=green-200+underline,
// Active=green-300+underline, Visited=lilac-300, Disabled=gray-500
@import "../../../styles/_colors.scss";
// Base link styles
.bds-link {
display: inline-flex;
align-items: center;
gap: 8px;
text-decoration: none;
transition: color 0.2s ease, text-decoration 0.2s ease;
cursor: pointer;
// Focus styles for accessibility (outline color set per theme below)
&:focus-visible {
outline: 2px solid $white; // Default to white (dark mode)
outline-offset: 2px;
}
// Icon spacing
.bds-link-icon {
margin-left: 0;
flex-shrink: 0;
}
}
// Size variants
.bds-link--small {
font-size: 14px;
line-height: 1.5;
gap: 6px;
}
.bds-link--medium {
font-size: 16px;
line-height: 1.5;
gap: 8px;
}
.bds-link--large {
font-size: 20px;
line-height: 1.5;
gap: 10px;
}
// Link color states (Light Mode - per Figma specs)
// Use element + class selector for higher specificity to override html.light a rules
a.bds-link,
.bds-link {
// Enabled state: Green 400
color: $green-400;
text-decoration: none;
// Hover state: Green 500 + underline
&:hover:not(.bds-link--disabled) {
color: $green-500;
text-decoration: underline;
}
// Focus state: Green 500 + underline
&:focus:not(.bds-link--disabled) {
color: $green-500;
text-decoration: underline;
}
// Active state: Green 400 + underline
&:active:not(.bds-link--disabled) {
color: $green-400;
text-decoration: underline;
}
// Visited state: Lilac 400 (purple)
&:visited:not(.bds-link--disabled) {
color: $purple;
}
}
// Light theme overrides - BDS links are excluded from general light theme rules
// so these rules will apply naturally without needing !important
html.light {
a.bds-link,
nav a.bds-link {
// Enabled state: Green 400
color: $green-400;
text-decoration: none;
// Focus outline: Black for light mode
&:focus-visible {
outline-color: $black;
}
// Hover state: Green 500 + underline
&:hover:not(.bds-link--disabled) {
color: $green-500;
text-decoration: underline;
}
// Focus state: Green 500 + underline
&:focus:not(.bds-link--disabled) {
color: $green-500;
text-decoration: underline;
}
// Active state: Green 400 + underline
&:active:not(.bds-link--disabled) {
color: $green-400;
text-decoration: underline;
}
// Visited state: Lilac 400 (purple)
&:visited:not(.bds-link--disabled) {
color: $purple;
}
// Disabled state - needs to be here for specificity
&.bds-link--disabled {
color: $gray-400;
cursor: not-allowed;
pointer-events: none;
text-decoration: none;
&:hover,
&:focus,
&:active,
&:visited {
color: $gray-400;
text-decoration: none;
}
}
}
}
// Dark theme styles (per Figma specs)
html.dark {
a.bds-link,
nav a.bds-link {
// Enabled state: Green 300
color: $green-300;
text-decoration: none;
// Focus outline: White for dark mode
&:focus-visible {
outline-color: $white;
}
// Hover state: Green 200 + underline
&:hover:not(.bds-link--disabled) {
color: $green-200;
text-decoration: underline;
}
// Focus state: Green 200 + underline
&:focus:not(.bds-link--disabled) {
color: $green-200;
text-decoration: underline;
}
// Active state: Green 300 + underline
&:active:not(.bds-link--disabled) {
color: $green-300;
text-decoration: underline;
}
// Visited state: Lilac 300
&:visited:not(.bds-link--disabled) {
color: $lilac-300;
}
// Disabled state - needs to be here for specificity
&.bds-link--disabled {
color: $gray-500;
cursor: not-allowed;
pointer-events: none;
text-decoration: none;
&:hover,
&:focus,
&:active,
&:visited {
color: $gray-500;
text-decoration: none;
}
}
}
}
// Disabled state (base/dark theme)
// Use element + class selector for higher specificity
a.bds-link.bds-link--disabled,
.bds-link.bds-link--disabled {
color: $gray-400;
cursor: not-allowed;
pointer-events: none;
text-decoration: none;
.bds-link-icon {
opacity: 0.5;
}
&:hover,
&:focus,
&:active,
&:visited {
color: $gray-400;
text-decoration: none;
}
}
// Dark theme adjustments for disabled (fallback for non-html.dark contexts)
html.dark,
.dark,
html:not(.light) {
a.bds-link.bds-link--disabled,
.bds-link.bds-link--disabled {
color: $gray-500;
&:hover,
&:focus,
&:active,
&:visited {
color: $gray-500;
}
}
}
// Inline variant (no icon spacing adjustment needed)
.bds-link--inline {
display: inline;
gap: 0;
.bds-link-icon {
display: none;
}
}
// Standalone variants (internal/external)
// These variants use icons, spacing is handled by gap property in .bds-link

View File

@@ -0,0 +1,5 @@
export { Link } from './Link';
export type { LinkProps, LinkVariant, LinkSize, LinkColor, LinkIconType } from './Link';
export { LinkArrow } from './LinkArrow';
export type { LinkArrowProps, LinkArrowVariant, LinkArrowSize } from './LinkArrow';