From d17d894f398650209c0ddd29502308de8c07bd93 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Wed, 27 Sep 2023 18:43:25 +0200 Subject: feat(components): add Article, Aside, Footer, Header, Main & Nav Some components have been renamed to be able to create Footer, Header and Nav. --- src/components/molecules/layout/page-footer.tsx | 25 ++--- src/components/molecules/layout/page-header.tsx | 6 +- src/components/molecules/nav/index.ts | 2 +- src/components/molecules/nav/nav-list.module.scss | 21 +++++ src/components/molecules/nav/nav-list.stories.tsx | 106 ++++++++++++++++++++++ src/components/molecules/nav/nav-list.test.tsx | 28 ++++++ src/components/molecules/nav/nav-list.tsx | 75 +++++++++++++++ src/components/molecules/nav/nav.module.scss | 21 ----- src/components/molecules/nav/nav.stories.tsx | 104 --------------------- src/components/molecules/nav/nav.test.tsx | 28 ------ src/components/molecules/nav/nav.tsx | 82 ----------------- 11 files changed, 244 insertions(+), 254 deletions(-) create mode 100644 src/components/molecules/nav/nav-list.module.scss create mode 100644 src/components/molecules/nav/nav-list.stories.tsx create mode 100644 src/components/molecules/nav/nav-list.test.tsx create mode 100644 src/components/molecules/nav/nav-list.tsx delete mode 100644 src/components/molecules/nav/nav.module.scss delete mode 100644 src/components/molecules/nav/nav.stories.tsx delete mode 100644 src/components/molecules/nav/nav.test.tsx delete mode 100644 src/components/molecules/nav/nav.tsx (limited to 'src/components/molecules') diff --git a/src/components/molecules/layout/page-footer.tsx b/src/components/molecules/layout/page-footer.tsx index 786fca0..5f3b176 100644 --- a/src/components/molecules/layout/page-footer.tsx +++ b/src/components/molecules/layout/page-footer.tsx @@ -1,11 +1,8 @@ -import { FC } from 'react'; -import { Meta, MetaData } from './meta'; +import type { FC } from 'react'; +import { Footer, type FooterProps } from '../../atoms'; +import { Meta, type MetaData } from './meta'; -export type PageFooterProps = { - /** - * Set additional classnames to the footer element. - */ - className?: string; +export type PageFooterProps = Omit & { /** * The footer metadata. */ @@ -15,12 +12,10 @@ export type PageFooterProps = { /** * PageFooter component * - * Render a footer element to display page meta. + * Render a footer to display page meta. */ -export const PageFooter: FC = ({ meta, ...props }) => { - return ( -
- {meta && } -
- ); -}; +export const PageFooter: FC = ({ meta, ...props }) => ( +
+ {meta ? : null} +
+); diff --git a/src/components/molecules/layout/page-header.tsx b/src/components/molecules/layout/page-header.tsx index 04f2966..92650c5 100644 --- a/src/components/molecules/layout/page-header.tsx +++ b/src/components/molecules/layout/page-header.tsx @@ -1,5 +1,5 @@ import type { FC, ReactNode } from 'react'; -import { Heading } from '../../atoms'; +import { Header, Heading } from '../../atoms'; import { Meta, type MetaData } from './meta'; import styles from './page-header.module.scss'; @@ -50,7 +50,7 @@ export const PageHeader: FC = ({ }; return ( -
+
{title} @@ -67,6 +67,6 @@ export const PageHeader: FC = ({ ) : null} {intro ? getIntro() : null}
-
+
); }; diff --git a/src/components/molecules/nav/index.ts b/src/components/molecules/nav/index.ts index 68efe4e..9c46050 100644 --- a/src/components/molecules/nav/index.ts +++ b/src/components/molecules/nav/index.ts @@ -1,3 +1,3 @@ export * from './breadcrumb'; -export * from './nav'; +export * from './nav-list'; export * from './pagination'; diff --git a/src/components/molecules/nav/nav-list.module.scss b/src/components/molecules/nav/nav-list.module.scss new file mode 100644 index 0000000..a6d43bc --- /dev/null +++ b/src/components/molecules/nav/nav-list.module.scss @@ -0,0 +1,21 @@ +@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-list.stories.tsx b/src/components/molecules/nav/nav-list.stories.tsx new file mode 100644 index 0000000..110a6ca --- /dev/null +++ b/src/components/molecules/nav/nav-list.stories.tsx @@ -0,0 +1,106 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Envelop, Home } from '../../atoms'; +import { NavList, type NavItem } from './nav-list'; + +/** + * Nav - Storybook Meta + */ +export default { + title: 'Molecules/Navigation/Nav', + component: NavList, + argTypes: { + 'aria-label': { + control: { + type: 'text', + }, + description: 'An accessible name for the navigation.', + table: { + category: 'Accessibility', + }, + type: { + name: 'string', + required: false, + }, + }, + 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, + }, + }, + listClassName: { + control: { + type: 'text', + }, + description: 'Set additional classnames to the navigation list.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +const MainNavItems: NavItem[] = [ + { id: 'homeLink', href: '/', label: 'Home', logo: }, + { id: 'contactLink', href: '/contact', label: 'Contact', logo: }, +]; + +const FooterNavItems: NavItem[] = [ + { id: 'contactLink', href: '/contact', label: 'Contact' }, + { id: 'legalLink', href: '/legal-notice', label: 'Legal notice' }, +]; + +/** + * Nav Stories - Main navigation + */ +export const MainNav = Template.bind({}); +MainNav.args = { + items: MainNavItems, + kind: 'main', +}; + +/** + * Nav Stories - Footer navigation + */ +export const FooterNav = Template.bind({}); +FooterNav.args = { + items: FooterNavItems, + kind: 'footer', +}; diff --git a/src/components/molecules/nav/nav-list.test.tsx b/src/components/molecules/nav/nav-list.test.tsx new file mode 100644 index 0000000..58437cb --- /dev/null +++ b/src/components/molecules/nav/nav-list.test.tsx @@ -0,0 +1,28 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '../../../../tests/utils'; +import { Envelop, Home } from '../../atoms'; +import { NavList, type NavItem } from './nav-list'; + +const navItems: NavItem[] = [ + { id: 'homeLink', href: '/', label: 'Home', logo: }, + { id: 'contactLink', href: '/contact', label: 'Contact', logo: }, +]; + +describe('Nav', () => { + it('renders a main navigation', () => { + render(); + expect(rtlScreen.getByRole('navigation')).toHaveClass('nav--main'); + }); + + it('renders a footer navigation', () => { + render(); + expect(rtlScreen.getByRole('navigation')).toHaveClass('nav--footer'); + }); + + it('renders navigation links', () => { + render(); + expect( + rtlScreen.getByRole('link', { name: navItems[0].label }) + ).toHaveAttribute('href', navItems[0].href); + }); +}); diff --git a/src/components/molecules/nav/nav-list.tsx b/src/components/molecules/nav/nav-list.tsx new file mode 100644 index 0000000..59556ce --- /dev/null +++ b/src/components/molecules/nav/nav-list.tsx @@ -0,0 +1,75 @@ +import type { FC, ReactNode } from 'react'; +import { Link, Nav, NavLink, type NavProps } from '../../atoms'; +import styles from './nav-list.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 NavListProps = Omit & { + /** + * The navigation items. + */ + items: NavItem[]; + /** + * The navigation kind. + */ + kind: 'main' | 'footer'; + /** + * Set additional classnames to the navigation list. + */ + listClassName?: string; +}; + +/** + * Nav component + * + * Render the nav links. + */ +export const NavList: FC = ({ + className = '', + items, + kind, + listClassName = '', + ...props +}) => { + const kindClass = `nav--${kind}`; + const navClass = `${styles[kindClass]} ${className}`; + const listClass = `${styles.nav__list} ${listClassName}`; + + /** + * Get the nav items. + * @returns {JSX.Element[]} An array of nav items. + */ + const getItems = (): JSX.Element[] => + items.map(({ id, href, label, logo }) => ( +
  • + {kind === 'main' ? ( + + ) : ( + {label} + )} +
  • + )); + + return ( + + ); +}; diff --git a/src/components/molecules/nav/nav.module.scss b/src/components/molecules/nav/nav.module.scss deleted file mode 100644 index a6d43bc..0000000 --- a/src/components/molecules/nav/nav.module.scss +++ /dev/null @@ -1,21 +0,0 @@ -@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 deleted file mode 100644 index d343528..0000000 --- a/src/components/molecules/nav/nav.stories.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Envelop, Home } from '../../atoms'; -import { Nav, type NavItem } from './nav'; - -/** - * Nav - Storybook Meta - */ -export default { - title: 'Molecules/Navigation/Nav', - component: Nav, - argTypes: { - 'aria-label': { - control: { - type: 'text', - }, - description: 'An accessible name for the navigation.', - table: { - category: 'Accessibility', - }, - type: { - name: 'string', - required: false, - }, - }, - 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, - }, - }, - listClassName: { - control: { - type: 'text', - }, - description: 'Set additional classnames to the navigation list.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) =>