aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/components/atoms/links/nav-link.module.scss4
-rw-r--r--src/components/molecules/nav/nav.module.scss22
-rw-r--r--src/components/molecules/nav/nav.stories.tsx75
-rw-r--r--src/components/molecules/nav/nav.test.tsx28
-rw-r--r--src/components/molecules/nav/nav.tsx71
5 files changed, 197 insertions, 3 deletions
diff --git a/src/components/atoms/links/nav-link.module.scss b/src/components/atoms/links/nav-link.module.scss
index 24e8737..241c9c3 100644
--- a/src/components/atoms/links/nav-link.module.scss
+++ b/src/components/atoms/links/nav-link.module.scss
@@ -15,8 +15,7 @@
row-gap: var(--spacing-2xs);
min-width: var(--link-min-width, fun.convert-px(85));
padding: var(--spacing-xs);
- background: var(--color-bg);
- background-repeat: no-repeat;
+ background: inherit;
font-size: var(--font-size-sm);
font-variant: small-caps;
font-weight: 600;
@@ -25,7 +24,6 @@
@include mix.media("screen") {
@include mix.dimensions("md") {
- background-color: inherit;
border-radius: 8%;
}
}
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;