diff --git a/src/components/SkipNav/skip-nav.stories.tsx b/src/components/SkipNav/skip-nav.stories.tsx new file mode 100644 index 0000000000..6e1452a2bc --- /dev/null +++ b/src/components/SkipNav/skip-nav.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { SkipNav } from '~/src/index'; + +const meta: Meta = { + title: 'Components (Draft)/Skip Navigation', + tags: ['autodocs'], + component: SkipNav, + argTypes: { + label: { control: 'text' }, + href: { control: 'text' }, + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + render: (properties) => , + args: { + href: '#main', + label: 'Skip to main content', + }, +}; + +export const CustomLabel: Story = { + name: 'Custom label', + render: (properties) => , + args: { + href: '#main', + label: 'Skip to page content', + }, +}; diff --git a/src/components/SkipNav/skip-nav.test.tsx b/src/components/SkipNav/skip-nav.test.tsx new file mode 100644 index 0000000000..58ab691aba --- /dev/null +++ b/src/components/SkipNav/skip-nav.test.tsx @@ -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('', () => { + beforeEach(() => { + CfpbButton.init(); + }); + + it('renders the default link text', () => { + render(); + expect(screen.getByText('Skip to main content')).toBeInTheDocument(); + }); + + it('renders a custom label when provided', () => { + render(); + expect(screen.getByText('Skip to page content')).toBeInTheDocument(); + }); + + it('passes the default href and className to cfpb-button', async () => { + render(); + 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(); + 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(); + expect(screen.getByTestId('skip-nav')).toBeInTheDocument(); + expect(screen.getByText('Skip to main content')).toHaveClass('skip-nav__link'); + }); +}); diff --git a/src/components/SkipNav/skip-nav.tsx b/src/components/SkipNav/skip-nav.tsx index 7bb3dd5d7a..729303656a 100644 --- a/src/components/SkipNav/skip-nav.tsx +++ b/src/components/SkipNav/skip-nav.tsx @@ -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 ( -
- - {text} - +
+ + {label} +
); } diff --git a/src/types/cfpb-design-system.d.ts b/src/types/cfpb-design-system.d.ts index d36ea7233f..3403d3dfc5 100644 --- a/src/types/cfpb-design-system.d.ts +++ b/src/types/cfpb-design-system.d.ts @@ -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;