diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-07 18:44:14 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:14:41 +0100 |
| commit | d75b9a1e150ab211c1052fb49bede9bd16320aca (patch) | |
| tree | e5bb221d2b8dc83151697fe646e9194f921b5807 /src/components/molecules/layout | |
| parent | 12a03a9a72f7895d571dbaeeb245d92aa277a610 (diff) | |
feat(components): add a generic Flip component
The flipping animation is used at several places so it makes sense to
use a single component to handle the animation. It will avoid styles
duplication.
Diffstat (limited to 'src/components/molecules/layout')
| -rw-r--r-- | src/components/molecules/layout/branding.module.scss | 32 | ||||
| -rw-r--r-- | src/components/molecules/layout/branding.stories.tsx | 26 | ||||
| -rw-r--r-- | src/components/molecules/layout/branding.test.tsx | 75 | ||||
| -rw-r--r-- | src/components/molecules/layout/branding.tsx | 54 |
4 files changed, 132 insertions, 55 deletions
diff --git a/src/components/molecules/layout/branding.module.scss b/src/components/molecules/layout/branding.module.scss index 4d9e32c..bacf381 100644 --- a/src/components/molecules/layout/branding.module.scss +++ b/src/components/molecules/layout/branding.module.scss @@ -42,7 +42,7 @@ @include mix.media("screen") { @include mix.dimensions("2xs") { grid-template-columns: - var(--logo-size, fun.convert-px(100)) + var(--logo-size) minmax(0, 1fr); grid-template-rows: 1fr min-content; align-items: center; @@ -55,6 +55,8 @@ .logo { grid-row: span 2; margin-bottom: var(--spacing-sm); + border-radius: 50%; + animation: flip-logo 9s ease-in 0s 1; @include mix.media("screen") { @include mix.dimensions("2xs") { @@ -103,3 +105,31 @@ } } } + +.flip { + width: var(--logo-size); + height: var(--logo-size); + border: fun.convert-px(2) solid var(--color-primary-dark); + border-radius: 50%; + box-shadow: + fun.convert-px(1) fun.convert-px(2) fun.convert-px(1) 0 + var(--color-shadow-light), + fun.convert-px(2) fun.convert-px(3) fun.convert-px(3) 0 + var(--color-shadow-light); + + > * { + padding: fun.convert-px(2); + border-radius: 50%; + } +} + +@keyframes flip-logo { + 0%, + 90% { + transform: rotateY(180deg); + } + + 100% { + transform: rotateY(0deg); + } +} diff --git a/src/components/molecules/layout/branding.stories.tsx b/src/components/molecules/layout/branding.stories.tsx index 04844e2..7ff88c9 100644 --- a/src/components/molecules/layout/branding.stories.tsx +++ b/src/components/molecules/layout/branding.stories.tsx @@ -1,4 +1,6 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import NextImage from 'next/image'; +import { Logo } from '../../atoms'; import { Branding } from './branding'; /** @@ -82,8 +84,16 @@ const Template: ComponentStory<typeof Branding> = (args) => ( */ export const Default = Template.bind({}); Default.args = { + logo: <Logo heading="A logo example" />, + photo: ( + <NextImage + alt="A photo example" + height={200} + src="https://picsum.photos/200" + width={200} + /> + ), title: 'Website title', - photo: 'http://placeimg.com/640/480', }; /** @@ -91,7 +101,15 @@ Default.args = { */ export const WithBaseline = Template.bind({}); WithBaseline.args = { - title: 'Website title', baseline: 'Maiores corporis qui', - photo: 'http://placeimg.com/640/480', + logo: <Logo heading="A logo example" />, + photo: ( + <NextImage + alt="A photo example" + height={200} + src="https://picsum.photos/200" + width={200} + /> + ), + title: 'Website title', }; diff --git a/src/components/molecules/layout/branding.test.tsx b/src/components/molecules/layout/branding.test.tsx index 4b76446..cfb55c5 100644 --- a/src/components/molecules/layout/branding.test.tsx +++ b/src/components/molecules/layout/branding.test.tsx @@ -1,62 +1,109 @@ import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; +import NextImage from 'next/image'; +import { render, screen as rtlScreen } from '../../../../tests/utils'; +import { Logo } from '../../atoms'; import { Branding } from './branding'; describe('Branding', () => { it('renders a photo', () => { + const altText = 'A photo example'; + render( <Branding - photo="http://placeimg.com/640/480/city" + logo={<Logo />} + photo={ + <NextImage + alt="A photo example" + height={200} + src="https://picsum.photos/200" + width={200} + /> + } title="Website title" /> ); - expect( - screen.getByRole('img', { name: 'Website title picture' }) - ).toBeInTheDocument(); + expect(rtlScreen.getByRole('img', { name: altText })).toBeInTheDocument(); }); it('renders a logo', () => { + const logoHeading = 'sed enim voluptatem'; + render( - <Branding photo="http://placeimg.com/640/480/city" title="Website name" /> + <Branding + logo={<Logo heading={logoHeading} />} + photo={ + <NextImage + alt="A photo example" + height={200} + src="https://picsum.photos/200" + width={200} + /> + } + title="Website name" + /> ); - expect(screen.getByTitle('Website name logo')).toBeInTheDocument(); + expect(rtlScreen.getByTitle(logoHeading)).toBeInTheDocument(); }); it('renders a baseline', () => { render( <Branding - photo="http://placeimg.com/640/480" + logo={<Logo />} + photo={ + <NextImage + alt="A photo example" + height={200} + src="https://picsum.photos/200" + width={200} + /> + } title="Website title" baseline="Website baseline" /> ); - expect(screen.getByText('Website baseline')).toBeInTheDocument(); + expect(rtlScreen.getByText('Website baseline')).toBeInTheDocument(); }); it('renders a title wrapped with h1 element', () => { render( <Branding - photo="http://placeimg.com/640/480" + logo={<Logo />} + photo={ + <NextImage + alt="A photo example" + height={200} + src="https://picsum.photos/200" + width={200} + /> + } title="Website title" isHome={true} /> ); expect( - screen.getByRole('heading', { level: 1, name: 'Website title' }) + rtlScreen.getByRole('heading', { level: 1, name: 'Website title' }) ).toBeInTheDocument(); }); it('renders a title with h1 styles', () => { render( <Branding - photo="http://placeimg.com/640/480" + logo={<Logo />} + photo={ + <NextImage + alt="A photo example" + height={200} + src="https://picsum.photos/200" + width={200} + /> + } title="Website title" isHome={false} /> ); expect( - screen.queryByRole('heading', { level: 1, name: 'Website title' }) + rtlScreen.queryByRole('heading', { level: 1, name: 'Website title' }) ).not.toBeInTheDocument(); - expect(screen.getByText('Website title')).toHaveClass('heading--1'); + expect(rtlScreen.getByText('Website title')).toHaveClass('heading--1'); }); }); diff --git a/src/components/molecules/layout/branding.tsx b/src/components/molecules/layout/branding.tsx index dceee5e..c3d3b7c 100644 --- a/src/components/molecules/layout/branding.tsx +++ b/src/components/molecules/layout/branding.tsx @@ -1,11 +1,9 @@ -import { type FC, useRef } from 'react'; -import { useIntl } from 'react-intl'; +import { type FC, useRef, type ReactNode } from 'react'; import { useStyles } from '../../../utils/hooks'; -import { Heading, Link } from '../../atoms'; -import { FlippingLogo, type FlippingLogoProps } from '../images'; +import { Flip, FlipSide, Heading, Link } from '../../atoms'; import styles from './branding.module.scss'; -export type BrandingProps = Pick<FlippingLogoProps, 'photo'> & { +export type BrandingProps = { /** * The Branding baseline. */ @@ -15,6 +13,14 @@ export type BrandingProps = Pick<FlippingLogoProps, 'photo'> & { */ isHome?: boolean; /** + * The website logo. + */ + logo: ReactNode; + /** + * Your photo. + */ + photo: ReactNode; + /** * The Branding title; */ title: string; @@ -32,31 +38,14 @@ export type BrandingProps = Pick<FlippingLogoProps, 'photo'> & { export const Branding: FC<BrandingProps> = ({ baseline, isHome = false, + logo, photo, title, withLink = false, ...props }) => { const baselineRef = useRef<HTMLParagraphElement>(null); - const logoRef = useRef<HTMLDivElement>(null); const titleRef = useRef<HTMLHeadingElement | HTMLParagraphElement>(null); - const intl = useIntl(); - const altText = intl.formatMessage( - { - defaultMessage: '{website} picture', - description: 'Branding: photo alternative text', - id: 'dDK5oc', - }, - { website: title } - ); - const logoTitle = intl.formatMessage( - { - defaultMessage: '{website} logo', - description: 'Branding: logo title', - id: 'x55qsD', - }, - { website: title } - ); useStyles({ property: '--typing-animation', @@ -69,22 +58,15 @@ export const Branding: FC<BrandingProps> = ({ 'hide-text 4.25s linear 0s 1, blink 0.8s ease-in-out 4.25s 2, typing 3.8s linear 4.25s 1', target: baselineRef, }); - useStyles({ - property: 'animation', - styles: 'flip-logo 9s ease-in 0s 1', - target: logoRef, - }); return ( <div className={styles.wrapper}> - <FlippingLogo - {...props} - altText={altText} - className={styles.logo} - logoTitle={logoTitle} - photo={photo} - ref={logoRef} - /> + <Flip {...props} className={styles.logo}> + <FlipSide className={styles.flip}>{photo}</FlipSide> + <FlipSide className={styles.flip} isBack> + {logo} + </FlipSide> + </Flip> <Heading className={styles.title} isFake={!isHome} |
