Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 183 additions & 114 deletions src/components/atoms/link/Link.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,181 +1,250 @@
import { action } from '@storybook/addon-actions';
import type { Meta, StoryObj } from '@storybook/react';
import { fn } from '@storybook/test';
import Link from './Link';
import type { ReactNode } from 'react';
import { Link } from './Link';

const StoryCanvas = ({ children }: { children: ReactNode }) => (
<section className='mx-auto my-xl flex max-w-4xl flex-col gap-5 p-lg'>{children}</section>
);

const VariantRow = ({ children }: { children: ReactNode }) => (
<div className='flex flex-wrap items-center gap-4'>{children}</div>
);

/**
* ## Description
* Link renders navigation and action affordances with Stack-and-Flow typography, focus, and token styling.
*
* ## DESCRIPTION
* Link component for navigation and actions.
*
* ## DEPENDENCIES
* - Icon: Uses Icon component from `lucide-react` for icons.
* ## Dependencies
* Uses `lucide-react/dynamic` when the optional `icon` prop is provided.
*
* ## Usage Guide
* Use `regular` for inline navigation, `outlined` for secondary call-to-action links, and `button` when a link must visually match a primary action while preserving link semantics when `href` is present. When `href` is omitted, Link behaves as a local action control with button semantics and keyboard activation.
*/

const meta: Meta<typeof Link> = {
title: 'Atoms/Link',
component: Link,
parameters: {
layout: 'fullscreen',
docs: {
autodocs: true
}
},
tags: ['autodocs']
};

export default meta;

type Story = StoryObj<typeof Link>;

/**
* Shows the default inline link for body copy, release notes, and low-emphasis navigation.
*/
export const Default: Story = {
args: {
variant: 'regular',
href: '',
target: '_blank',
size: 'md',
children: 'Lorem ipsum',
className: '',
title: ''
}
children: 'Read the release notes',
href: 'https://github.com/egdev6'
},
render: (args) => (
<StoryCanvas>
<p className='fs-base text-text-light dark:text-text-dark'>
Follow the product update in <Link {...args} /> for migration notes and release context.
</p>
</StoryCanvas>
)
};

/**
* - Regular: Default link style.
* - ⚠️ This variant uses a transparent background by default. To ensure proper visibility, place it inside a container with a solid or plain background.
* Shows the three visual hierarchy levels: inline, secondary CTA, and primary CTA.
*/

export const Regular: Story = {
export const Variants: Story = {
render: () => (
<div className='flex gap-4 items-center'>
<Link size='sm' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
<Link size='md' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
<Link size='lg' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
</div>
<StoryCanvas>
<VariantRow>
<Link href='https://github.com/egdev6'>Inline link</Link>
<Link variant='outlined' href='https://github.com/egdev6'>
Secondary CTA
</Link>
<Link variant='button' href='https://github.com/egdev6' onClick={action('primary-link-click')}>
Primary CTA
</Link>
</VariantRow>
</StoryCanvas>
)
};

/**
* - Outlined: Link with outlined style.
* - ⚠️ This variant uses a transparent background by default. To ensure proper visibility, place it inside a container with a solid or plain background.
* Shows CTA-style links with their default glow and with glow disabled for quieter layouts.
* Regular inline links stay glowless regardless of this option.
*/

export const Outlined: Story = {
export const Shadow: Story = {
render: () => (
<div className='flex gap-4 items-center'>
<Link size='sm' variant='outlined' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
<Link size='md' variant='outlined' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
<Link size='lg' variant='outlined' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
</div>
<StoryCanvas>
<div className='grid gap-4'>
<VariantRow>
<Link variant='outlined' href='https://github.com/egdev6'>
Outlined with glow
</Link>
<Link variant='outlined' shadow={false} href='https://github.com/egdev6'>
Outlined without glow
</Link>
</VariantRow>
<VariantRow>
<Link variant='button' href='https://github.com/egdev6'>
Primary with glow
</Link>
<Link variant='button' shadow={false} href='https://github.com/egdev6'>
Primary without glow
</Link>
</VariantRow>
</div>
</StoryCanvas>
)
};

/**
* - Button: Link styled as a button.
* Shows how size changes typography and horizontal rhythm while preserving each variant treatment.
*/

export const Button: Story = {
export const Sizes: Story = {
render: () => (
<div className='flex gap-4 items-center'>
<Link size='sm' variant='button' onClick={fn()}>
Lorem Ipsum
</Link>
<Link size='md' variant='button' onClick={fn()}>
Lorem Ipsum
</Link>
<Link size='lg' variant='button' onClick={fn()}>
Lorem Ipsum
</Link>
</div>
<StoryCanvas>
<div className='grid gap-4'>
<VariantRow>
<Link size='sm' href='https://github.com/egdev6'>
Small inline
</Link>
<Link size='md' href='https://github.com/egdev6'>
Medium inline
</Link>
<Link size='lg' href='https://github.com/egdev6'>
Large inline
</Link>
</VariantRow>
<VariantRow>
<Link size='sm' variant='outlined' href='https://github.com/egdev6'>
Small outlined
</Link>
<Link size='md' variant='outlined' href='https://github.com/egdev6'>
Medium outlined
</Link>
<Link size='lg' variant='outlined' href='https://github.com/egdev6'>
Large outlined
</Link>
</VariantRow>
<VariantRow>
<Link size='sm' variant='button' href='https://github.com/egdev6'>
Small button
</Link>
<Link size='md' variant='button' href='https://github.com/egdev6'>
Medium button
</Link>
<Link size='lg' variant='button' href='https://github.com/egdev6'>
Large button
</Link>
</VariantRow>
</div>
</StoryCanvas>
)
};

/**
* - With Icon: Link with an icon.
* Shows icons inheriting current text color and keeping alignment consistent across variants.
*/

export const WithIcon: Story = {
render: () => (
<div className='flex gap-4 items-center'>
<Link size='md' icon='image' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
<Link size='md' variant='outlined' icon='image' href='https://github.com/egdev6'>
Lorem Ipsum
</Link>
<Link size='md' variant='button' icon='image' onClick={fn()}>
Lorem Ipsum
</Link>
</div>
<StoryCanvas>
<VariantRow>
<Link icon='external-link' href='https://github.com/egdev6'>
External link
</Link>
<Link variant='outlined' icon='arrow-right' href='https://github.com/egdev6'>
Continue
</Link>
<Link variant='button' icon='download' href='https://github.com/egdev6'>
Download
</Link>
</VariantRow>
</StoryCanvas>
)
};

/**
* - You can change the target of the link to open in a new tab or the same tab.
* - Use the `target` prop to specify the link behavior.
* Shows disabled links preserving their color treatment while reducing affordance through opacity and cursor state.
*/
export const Target: Story = {
export const Disabled: Story = {
render: () => (
<div className='flex gap-4 items-center'>
<Link size='md' href='https://github.com/egdev6' target='_blank' title='Open in new tab'>
Open in new tab
</Link>
<Link size='md' href='https://github.com/egdev6' target='_self' title='Open in same tab'>
Open in same tab
</Link>
<Link size='md' href='https://github.com/egdev6' target='_parent' title='Open in parent frame'>
Open in parent frame
</Link>
<Link size='md' href='https://github.com/egdev6' target='_top' title='Open in top frame'>
Open in top frame
</Link>
</div>
<StoryCanvas>
<VariantRow>
<Link disabled={true} href='https://github.com/egdev6'>
Inline disabled
</Link>
<Link disabled={true} variant='outlined' href='https://github.com/egdev6'>
Outlined disabled
</Link>
<Link disabled={true} variant='button' href='https://github.com/egdev6'>
Button disabled
</Link>
</VariantRow>
</StoryCanvas>
)
};

/**
* - Accessibility: The `title` prop is used to provide a tooltip or description for the link.
* - Use the `title` attribute to provide additional context for screen readers.
* Shows target values changing navigation behavior while preserving accessible link semantics.
*/
export const Target: Story = {
render: () => (
<StoryCanvas>
<VariantRow>
<Link href='https://github.com/egdev6' target='_blank' title='Open in new tab'>
New tab
</Link>
<Link href='https://github.com/egdev6' target='_self' title='Open in same tab'>
Same tab
</Link>
<Link href='https://github.com/egdev6' target='_parent' title='Open in parent frame'>
Parent frame
</Link>
<Link href='https://github.com/egdev6' target='_top' title='Open in top frame'>
Top frame
</Link>
</VariantRow>
</StoryCanvas>
)
};

/**
* Shows title-backed accessible labels when visible text alone is not enough.
*/
export const Accessibility: Story = {
render: () => (
<div className='flex gap-4 items-center'>
<Link size='md' href='https://github.com/egdev6' title='GitHub Profile'>
GitHub Profile
</Link>
</div>
<StoryCanvas>
<VariantRow>
<Link href='https://github.com/egdev6' title='Open the Stack and Flow GitHub profile'>
GitHub profile
</Link>
<Link variant='button' href='https://github.com/egdev6' title='Open documentation in a new tab'>
Documentation
</Link>
</VariantRow>
</StoryCanvas>
)
};

/**
* - Custom Class: You can add custom classes to the link for additional styling.
* - You need to override the default classes using `!important` to ensure your styles take precedence.
* Shows local action behavior when no `href` is provided; these controls expose button semantics and support pointer plus keyboard activation.
*/

export const CustomClass: Story = {
export const ActionSemantics: Story = {
render: () => (
<div className='flex gap-4 items-center'>
<Link size='md' href='https://github.com/egdev6' icon='arrow-right' className='!flex-row-reverse'>
Custom Class Link
</Link>
<Link
size='md'
variant='outlined'
href='https://github.com/egdev6'
className='!border-blue-dark !bg-blue-dark !text-white hover:!bg-blue hover:!border-blue hover:!shadow-blue-dark'
>
Custom Class Link
</Link>
</div>
<StoryCanvas>
<VariantRow>
<Link variant='button' onClick={action('local-link-action')}>
Run local action
</Link>
<Link variant='outlined' onClick={action('secondary-local-link-action')}>
Secondary action
</Link>
</VariantRow>
</StoryCanvas>
)
};
Loading
Loading