aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules/layout
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/molecules/layout')
-rw-r--r--src/components/molecules/layout/branding.module.scss48
-rw-r--r--src/components/molecules/layout/branding.stories.tsx83
-rw-r--r--src/components/molecules/layout/branding.test.tsx61
-rw-r--r--src/components/molecules/layout/branding.tsx97
4 files changed, 289 insertions, 0 deletions
diff --git a/src/components/molecules/layout/branding.module.scss b/src/components/molecules/layout/branding.module.scss
new file mode 100644
index 0000000..aa18002
--- /dev/null
+++ b/src/components/molecules/layout/branding.module.scss
@@ -0,0 +1,48 @@
+@use "@styles/abstracts/functions" as fun;
+
+.wrapper {
+ display: grid;
+ grid-template-columns:
+ var(--logo-size, fun.convert-px(100))
+ minmax(0, 1fr);
+ grid-template-rows: 1fr min-content;
+ align-items: center;
+ column-gap: var(--spacing-sm);
+}
+
+.logo {
+ grid-row: span 2;
+}
+
+.title {
+ font-size: var(--font-size-2xl);
+}
+
+.baseline {
+ color: var(--color-fg-light);
+}
+
+.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/layout/branding.stories.tsx b/src/components/molecules/layout/branding.stories.tsx
new file mode 100644
index 0000000..726ba26
--- /dev/null
+++ b/src/components/molecules/layout/branding.stories.tsx
@@ -0,0 +1,83 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { IntlProvider } from 'react-intl';
+import BrandingComponent from './branding';
+
+export default {
+ title: 'Molecules/Layout',
+ component: BrandingComponent,
+ args: {
+ isHome: false,
+ },
+ argTypes: {
+ baseline: {
+ control: {
+ type: 'text',
+ },
+ description: 'The Branding baseline.',
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ isHome: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Use H1 if the current page is homepage.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ photo: {
+ control: {
+ type: 'text',
+ },
+ description: 'The Branding photo.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ title: {
+ control: {
+ type: 'text',
+ },
+ description: 'The Branding title.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ withLink: {
+ control: {
+ type: 'boolean',
+ },
+ description: 'Wraps the title with a link to homepage.',
+ table: {
+ category: 'Options',
+ defaultValue: { summary: false },
+ },
+ type: {
+ name: 'boolean',
+ required: false,
+ },
+ },
+ },
+} as ComponentMeta<typeof BrandingComponent>;
+
+const Template: ComponentStory<typeof BrandingComponent> = (args) => (
+ <IntlProvider locale="en">
+ <BrandingComponent {...args} />
+ </IntlProvider>
+);
+
+export const Branding = Template.bind({});
+Branding.args = {
+ title: 'Website title',
+ photo: 'http://placeimg.com/640/480',
+};
diff --git a/src/components/molecules/layout/branding.test.tsx b/src/components/molecules/layout/branding.test.tsx
new file mode 100644
index 0000000..4fe1e9a
--- /dev/null
+++ b/src/components/molecules/layout/branding.test.tsx
@@ -0,0 +1,61 @@
+import { render, screen } from '@test-utils';
+import Branding from './branding';
+
+describe('Branding', () => {
+ it('renders a photo', () => {
+ render(
+ <Branding
+ photo="http://placeimg.com/640/480/city"
+ title="Website title"
+ />
+ );
+ expect(
+ screen.getByRole('img', { name: 'Website title picture' })
+ ).toBeInTheDocument();
+ });
+
+ it('renders a logo', () => {
+ render(
+ <Branding photo="http://placeimg.com/640/480/city" title="Website name" />
+ );
+ expect(screen.getByTitle('Website name logo')).toBeInTheDocument();
+ });
+
+ it('renders a baseline', () => {
+ render(
+ <Branding
+ photo="http://placeimg.com/640/480"
+ title="Website title"
+ baseline="Website baseline"
+ />
+ );
+ expect(screen.getByText('Website baseline')).toBeInTheDocument();
+ });
+
+ it('renders a title wrapped with h1 element', () => {
+ render(
+ <Branding
+ photo="http://placeimg.com/640/480"
+ title="Website title"
+ isHome={true}
+ />
+ );
+ expect(
+ screen.getByRole('heading', { level: 1, name: 'Website title' })
+ ).toBeInTheDocument();
+ });
+
+ it('renders a title with h1 styles', () => {
+ render(
+ <Branding
+ photo="http://placeimg.com/640/480"
+ title="Website title"
+ isHome={false}
+ />
+ );
+ expect(
+ screen.queryByRole('heading', { level: 1, name: 'Website title' })
+ ).not.toBeInTheDocument();
+ expect(screen.getByText('Website title')).toHaveClass('heading--1');
+ });
+});
diff --git a/src/components/molecules/layout/branding.tsx b/src/components/molecules/layout/branding.tsx
new file mode 100644
index 0000000..efb2e34
--- /dev/null
+++ b/src/components/molecules/layout/branding.tsx
@@ -0,0 +1,97 @@
+import Heading from '@components/atoms/headings/heading';
+import Link from 'next/link';
+import { FC } from 'react';
+import { useIntl } from 'react-intl';
+import styles from './branding.module.scss';
+import FlippingLogo from './flipping-logo';
+
+type BrandingProps = {
+ /**
+ * The Branding baseline.
+ */
+ baseline?: string;
+ /**
+ * Use H1 if the current page is homepage. Default: false.
+ */
+ isHome?: boolean;
+ /**
+ * A photography URL.
+ */
+ photo: string;
+ /**
+ * The Branding title;
+ */
+ title: string;
+ /**
+ * Wraps the title with a link to homepage. Default: false.
+ */
+ withLink?: boolean;
+};
+
+/**
+ * Branding component
+ *
+ * Render the branding logo, title and optional baseline.
+ */
+const Branding: FC<BrandingProps> = ({
+ baseline,
+ isHome = false,
+ photo,
+ title,
+ withLink = false,
+}) => {
+ 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 }
+ );
+
+ return (
+ <div className={styles.wrapper}>
+ <FlippingLogo
+ additionalClasses={styles.logo}
+ altText={altText}
+ logoTitle={logoTitle}
+ photo={photo}
+ />
+ <Heading
+ isFake={!isHome}
+ level={1}
+ withMargin={false}
+ additionalClasses={styles.title}
+ >
+ {withLink ? (
+ <Link href="/">
+ <a className={styles.link}>{title}</a>
+ </Link>
+ ) : (
+ title
+ )}
+ </Heading>
+ {baseline && (
+ <Heading
+ isFake={true}
+ level={4}
+ withMargin={false}
+ additionalClasses={styles.baseline}
+ >
+ {baseline}
+ </Heading>
+ )}
+ </div>
+ );
+};
+
+export default Branding;