diff options
Diffstat (limited to 'src/components/molecules')
| -rw-r--r-- | src/components/molecules/nav/nav.module.scss | 22 | ||||
| -rw-r--r-- | src/components/molecules/nav/nav.stories.tsx | 75 | ||||
| -rw-r--r-- | src/components/molecules/nav/nav.test.tsx | 28 | ||||
| -rw-r--r-- | src/components/molecules/nav/nav.tsx | 71 | 
4 files changed, 196 insertions, 0 deletions
| diff --git a/src/components/molecules/nav/nav.module.scss b/src/components/molecules/nav/nav.module.scss new file mode 100644 index 0000000..9c0f6de --- /dev/null +++ b/src/components/molecules/nav/nav.module.scss @@ -0,0 +1,22 @@ +@use "@styles/abstracts/mixins" as mix; +@use "@styles/abstracts/placeholders"; + +.nav { +  &__list { +    @extend %reset-list; + +    display: flex; +    flex-flow: row wrap; +    gap: var(--spacing-2xs); +    align-items: center; +  } + +  &--footer & { +    &__item:not(:first-child) { +      &::before { +        content: "\2022"; +        margin-right: var(--spacing-2xs); +      } +    } +  } +} diff --git a/src/components/molecules/nav/nav.stories.tsx b/src/components/molecules/nav/nav.stories.tsx new file mode 100644 index 0000000..9975bbd --- /dev/null +++ b/src/components/molecules/nav/nav.stories.tsx @@ -0,0 +1,75 @@ +import Envelop from '@components/atoms/icons/envelop'; +import Home from '@components/atoms/icons/home'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import { IntlProvider } from 'react-intl'; +import NavComponent, { type NavItem } from './nav'; + +const MainNavItems: NavItem[] = [ +  { id: 'homeLink', href: '/', label: 'Home', logo: <Home /> }, +  { id: 'contactLink', href: '/contact', label: 'Contact', logo: <Envelop /> }, +]; + +const FooterNavItems: NavItem[] = [ +  { id: 'contactLink', href: '/contact', label: 'Contact' }, +  { id: 'legalLink', href: '/legal-notice', label: 'Legal notice' }, +]; + +export default { +  title: 'Molecules/Nav', +  component: NavComponent, +  argTypes: { +    className: { +      control: { +        type: 'text', +      }, +      description: 'Set additional classnames to the navigation wrapper.', +      table: { +        category: 'Styles', +      }, +      type: { +        name: 'string', +        required: false, +      }, +    }, +    items: { +      control: { +        type: null, +      }, +      description: 'The nav items.', +      type: { +        name: 'other', +        required: true, +        value: '', +      }, +    }, +    kind: { +      control: { +        type: 'select', +      }, +      description: 'The navigation kind.', +      options: ['main', 'footer'], +      type: { +        name: 'string', +        required: true, +      }, +    }, +  }, +} as ComponentMeta<typeof NavComponent>; + +const Template: ComponentStory<typeof NavComponent> = (args) => ( +  <IntlProvider locale="en"> +    <NavComponent {...args} /> +  </IntlProvider> +); + +export const MainNav = Template.bind({}); +MainNav.args = { +  items: MainNavItems, +  kind: 'main', +}; + +export const FooterNav = Template.bind({}); +FooterNav.args = { +  items: FooterNavItems, +  kind: 'footer', +}; diff --git a/src/components/molecules/nav/nav.test.tsx b/src/components/molecules/nav/nav.test.tsx new file mode 100644 index 0000000..183ca0b --- /dev/null +++ b/src/components/molecules/nav/nav.test.tsx @@ -0,0 +1,28 @@ +import Envelop from '@components/atoms/icons/envelop'; +import Home from '@components/atoms/icons/home'; +import { render, screen } from '@test-utils'; +import Nav, { type NavItem } from './nav'; + +const navItems: NavItem[] = [ +  { id: 'homeLink', href: '/', label: 'Home', logo: <Home /> }, +  { id: 'contactLink', href: '/contact', label: 'Contact', logo: <Envelop /> }, +]; + +describe('Nav', () => { +  it('renders a main navigation', () => { +    render(<Nav kind="main" items={navItems} />); +    expect(screen.getByRole('navigation')).toHaveClass('nav--main'); +  }); + +  it('renders a footer navigation', () => { +    render(<Nav kind="footer" items={navItems} />); +    expect(screen.getByRole('navigation')).toHaveClass('nav--footer'); +  }); + +  it('renders navigation links', () => { +    render(<Nav kind="main" items={navItems} />); +    expect( +      screen.getByRole('link', { name: navItems[0].label }) +    ).toHaveAttribute('href', navItems[0].href); +  }); +}); diff --git a/src/components/molecules/nav/nav.tsx b/src/components/molecules/nav/nav.tsx new file mode 100644 index 0000000..42e3843 --- /dev/null +++ b/src/components/molecules/nav/nav.tsx @@ -0,0 +1,71 @@ +import Link from '@components/atoms/links/link'; +import NavLink from '@components/atoms/links/nav-link'; +import { ReactNode, VFC } from 'react'; +import styles from './nav.module.scss'; + +export type NavItem = { +  /** +   * The item id. +   */ +  id: string; +  /** +   * The item link. +   */ +  href: string; +  /** +   * The item name. +   */ +  label: string; +  /** +   * The item logo. +   */ +  logo?: ReactNode; +}; + +export type NavProps = { +  /** +   * Set additional classnames to the navigation wrapper. +   */ +  className?: string; +  /** +   * The navigation items. +   */ +  items: NavItem[]; +  /** +   * The navigation kind. +   */ +  kind: 'main' | 'footer'; +}; + +/** + * Nav component + * + * Render the nav links. + */ +const Nav: VFC<NavProps> = ({ className = '', items, kind }) => { +  const kindClass = `nav--${kind}`; + +  /** +   * Get the nav items. +   * @returns {JSX.Element[]} An array of nav items. +   */ +  const getItems = (): JSX.Element[] => { +    return items.map(({ id, href, label, logo }) => ( +      <li key={id} className={styles.nav__item}> +        {kind === 'main' ? ( +          <NavLink href={href} label={label} logo={logo} /> +        ) : ( +          <Link href={href}>{label}</Link> +        )} +      </li> +    )); +  }; + +  return ( +    <nav className={`${styles[kindClass]} ${className}`}> +      <ul className={styles.nav__list}>{getItems()}</ul> +    </nav> +  ); +}; + +export default Nav; | 
