From 891441a76173c708c6604fa203b175aefa222333 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 9 Oct 2023 16:31:00 +0200 Subject: refactor(components): rewrite Branding component The component should only be responsible of the layout for the logo, the name and the optional baseline. Also, the homepage url could be different from `/` so the consumer should give the right url. --- .../molecules/branding/branding.module.scss | 80 +++++++++++++++++++ .../molecules/branding/branding.stories.tsx | 92 ++++++++++++++++++++++ .../molecules/branding/branding.test.tsx | 77 ++++++++++++++++++ src/components/molecules/branding/branding.tsx | 57 ++++++++++++++ src/components/molecules/branding/index.ts | 1 + 5 files changed, 307 insertions(+) create mode 100644 src/components/molecules/branding/branding.module.scss create mode 100644 src/components/molecules/branding/branding.stories.tsx create mode 100644 src/components/molecules/branding/branding.test.tsx create mode 100644 src/components/molecules/branding/branding.tsx create mode 100644 src/components/molecules/branding/index.ts (limited to 'src/components/molecules/branding') diff --git a/src/components/molecules/branding/branding.module.scss b/src/components/molecules/branding/branding.module.scss new file mode 100644 index 0000000..2f35fd7 --- /dev/null +++ b/src/components/molecules/branding/branding.module.scss @@ -0,0 +1,80 @@ +@use "../../../styles/abstracts/functions" as fun; +@use "../../../styles/abstracts/mixins" as mix; + +.wrapper { + display: grid; + grid-template-columns: minmax(0, 1fr); + justify-items: center; + width: 100%; + text-align: center; + + @include mix.media("screen") { + @include mix.dimensions("2xs") { + grid-template-columns: + auto + minmax(0, 1fr); + align-items: center; + justify-items: left; + column-gap: var(--spacing-sm); + width: unset; + } + } + + > *:first-child { + max-width: fun.convert-px(200); + max-height: fun.convert-px(200); + margin-bottom: var(--spacing-2xs); + + @include mix.media("screen") { + @include mix.dimensions("2xs") { + margin-bottom: 0; + } + } + } + + > *:nth-child(2) { + margin-block: var(--spacing-2xs); + } + + > *:nth-child(3) { + margin-block: 0 var(--spacing-xs); + } + + > *:first-child, + > *:first-child:nth-last-child(2) + * { + grid-row: span 2; + } + + > *:first-child:nth-last-child(3) + * { + align-self: self-end; + } + + > *:first-child:nth-last-child(3) ~ *:last-child { + align-self: self-start; + } +} + +.link { + background: linear-gradient( + to top, + var(--color-primary-light) fun.convert-px(5), + transparent fun.convert-px(5) + ) + left / 0 100% no-repeat; + text-decoration: none; + transition: all 0.6s ease-out 0s; + + &:hover, + &:focus { + background-size: 100% 100%; + } + + &:focus { + color: var(--color-primary-light); + } + + &:active { + background-size: 0 100%; + color: var(--color-primary-dark); + } +} diff --git a/src/components/molecules/branding/branding.stories.tsx b/src/components/molecules/branding/branding.stories.tsx new file mode 100644 index 0000000..c2f216a --- /dev/null +++ b/src/components/molecules/branding/branding.stories.tsx @@ -0,0 +1,92 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import NextImage from 'next/image'; +import { Heading } from '../../atoms'; +import { Branding } from './branding'; + +/** + * Branding - Storybook Meta + */ +export default { + title: 'Molecules/Branding', + component: Branding, + args: {}, + argTypes: { + baseline: { + control: { + type: 'object', + }, + description: 'The brand baseline.', + type: { + name: 'function', + required: false, + }, + }, + logo: { + control: { + type: 'object', + }, + description: 'The brand logo.', + type: { + name: 'function', + required: true, + }, + }, + name: { + control: { + type: 'object', + }, + description: 'The brand name.', + type: { + name: 'function', + required: true, + }, + }, + url: { + control: { + type: 'string', + }, + description: 'The homepage url.', + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +/** + * Branding Stories - Logo and title + */ +export const LogoAndTitle = Template.bind({}); +LogoAndTitle.args = { + logo: ( + + ), + name: Your brand name, +}; + +/** + * Branding Stories - Logo, title and baseline + */ +export const LogoTitleAndBaseline = Template.bind({}); +LogoTitleAndBaseline.args = { + baseline:
Your brand baseline if any
, + logo: ( + + ), + name: Your brand name, +}; diff --git a/src/components/molecules/branding/branding.test.tsx b/src/components/molecules/branding/branding.test.tsx new file mode 100644 index 0000000..7f41098 --- /dev/null +++ b/src/components/molecules/branding/branding.test.tsx @@ -0,0 +1,77 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import NextImage from 'next/image'; +import { Branding } from './branding'; + +describe('Branding', () => { + it('renders the brand logo and name', () => { + const altText = 'dolorem aut ullam'; + const name = 'ducimus quo enim'; + + render( + + } + name={
{name}
} + /> + ); + + expect(rtlScreen.getByRole('img', { name: altText })).toBeInTheDocument(); + expect(rtlScreen.getByText(name)).toBeInTheDocument(); + }); + + it('can render the brand logo, name and baseline', () => { + const altText = 'dolorem aut ullam'; + const name = 'ducimus quo enim'; + const baseline = 'ab consequatur est'; + + render( + {baseline}} + logo={ + + } + name={
{name}
} + /> + ); + + expect(rtlScreen.getByRole('img', { name: altText })).toBeInTheDocument(); + expect(rtlScreen.getByText(name)).toBeInTheDocument(); + expect(rtlScreen.getByText(baseline)).toBeInTheDocument(); + }); + + it('can render the brand name wrapped in a link', () => { + const altText = 'dolorem aut ullam'; + const name = 'ducimus quo enim'; + const url = '/velit'; + + render( + + } + name={
{name}
} + url={url} + /> + ); + + expect(rtlScreen.getByRole('img', { name: altText })).toBeInTheDocument(); + expect(rtlScreen.getByRole('link', { name })).toHaveAttribute('href', url); + }); +}); diff --git a/src/components/molecules/branding/branding.tsx b/src/components/molecules/branding/branding.tsx new file mode 100644 index 0000000..bb88a04 --- /dev/null +++ b/src/components/molecules/branding/branding.tsx @@ -0,0 +1,57 @@ +import { + type HTMLAttributes, + type ForwardRefRenderFunction, + forwardRef, + type ReactElement, +} from 'react'; +import styles from './branding.module.scss'; +import { Link } from 'src/components/atoms'; + +export type BrandingProps = Omit, 'children'> & { + /** + * The brand baseline. + */ + baseline?: ReactElement | null; + /** + * The brand logo. + * + * The logo size should not exceed ~200px. + */ + logo: ReactElement; + /** + * The brand name. + */ + name: ReactElement; + /** + * The homepage url if you want to wrap the name with a link. + */ + url?: string; +}; + +const BrandingWithRef: ForwardRefRenderFunction< + HTMLDivElement, + BrandingProps +> = ({ className = '', baseline, logo, name, url, ...props }, ref) => { + const wrapperClass = `${styles.wrapper} ${className}`; + + return ( +
+ {logo} + {url ? ( + + {name} + + ) : ( + name + )} + {baseline} +
+ ); +}; + +/** + * Branding component + * + * Render the branding logo, title and optional baseline. + */ +export const Branding = forwardRef(BrandingWithRef); diff --git a/src/components/molecules/branding/index.ts b/src/components/molecules/branding/index.ts new file mode 100644 index 0000000..5cf12ed --- /dev/null +++ b/src/components/molecules/branding/index.ts @@ -0,0 +1 @@ +export * from './branding'; -- cgit v1.2.3