Skip to content
Open
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
33 changes: 33 additions & 0 deletions src/components/SkipNav/skip-nav.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { SkipNav } from '~/src/index';

const meta: Meta<typeof SkipNav> = {
title: 'Components (Draft)/Skip Navigation',
tags: ['autodocs'],
component: SkipNav,
argTypes: {
label: { control: 'text' },
href: { control: 'text' },
},
};

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {
render: (properties) => <SkipNav {...properties} />,
args: {
href: '#main',
label: 'Skip to main content',
},
};

export const CustomLabel: Story = {
name: 'Custom label',
render: (properties) => <SkipNav {...properties} />,
args: {
href: '#main',
label: 'Skip to page content',
},
};
46 changes: 46 additions & 0 deletions src/components/SkipNav/skip-nav.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { CfpbButton } from '@cfpb/cfpb-design-system';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import SkipNav from './skip-nav';

describe('<SkipNav />', () => {
beforeEach(() => {
CfpbButton.init();
});

it('renders the default link text', () => {
render(<SkipNav />);
expect(screen.getByText('Skip to main content')).toBeInTheDocument();
});

it('renders a custom label when provided', () => {
render(<SkipNav label='Skip to page content' />);
expect(screen.getByText('Skip to page content')).toBeInTheDocument();
});

it('passes the default href and className to cfpb-button', async () => {
render(<SkipNav />);
const link = screen.getByText('Skip to main content');

expect(link).toHaveClass('skip-nav__link');

await new Promise((resolve) => requestAnimationFrame(resolve));

expect(link).toHaveProperty('href', '#main');
});

it('passes a custom href to cfpb-button', async () => {
render(<SkipNav href='#content' />);
const link = screen.getByText('Skip to main content');

await new Promise((resolve) => requestAnimationFrame(resolve));

expect(link).toHaveProperty('href', '#content');
});

it('wraps the web component in a skip-nav container', () => {
render(<SkipNav />);
expect(screen.getByTestId('skip-nav')).toBeInTheDocument();
expect(screen.getByText('Skip to main content')).toHaveClass('skip-nav__link');
});
});
27 changes: 21 additions & 6 deletions src/components/SkipNav/skip-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
import { CfpbButton } from '@cfpb/cfpb-design-system';
import type { JSXElement } from '../../types/jsx-element';
import './skip-nav.scss';

CfpbButton.init();

export interface SkipNavProperties {
href?: string;
label?: string;
}

/**
* A SkipNav (skip navigation) link is an accessibility feature, usually hidden
* until focused, placed at the top of a webpage to allow keyboard and screen
* reader users to bypass repetitive navigation menus and jump directly to the
* main content. It acts as an anchor link, enhancing user experience and
* efficiency.
*/
export default function SkipNav({
href = '#main',
text = 'Skip to main content',
}): JSXElement {
label = 'Skip to main content',
}: SkipNavProperties): JSXElement {
return (
<div className='skip-nav'>
<a className='skip-nav__link a-btn' href={href}>
{text}
</a>
<div className='skip-nav' data-testid='skip-nav'>
<cfpb-button href={href} className='skip-nav__link'>
{label}
</cfpb-button>
</div>
);
}
5 changes: 5 additions & 0 deletions src/types/cfpb-design-system.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
declare module '@cfpb/cfpb-design-system' {
export class CfpbButton extends HTMLElement {
static init(): void;
href?: string;
}

export class CfpbTagline extends HTMLElement {
static init(): void;
isLarge: boolean;
Expand Down
Loading