aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-10-02 18:45:30 +0200
committerArmand Philippot <git@armandphilippot.com>2023-11-11 18:14:41 +0100
commitf914ff8376dd91c4f6f8ca149e1cb6becb622d88 (patch)
tree777dc0268eba86721878a715c68f0f09bedb4b18 /src/components/molecules
parentb52b8183ce299b5a2d3c3b2f4f8cb94bb443d746 (diff)
refactor(components): rewrite Link component
* rename `external` prop to `isExternal` * rename `download` prop to `isDownload` * rewrite CSS to reduce code length and complexity * move link styles in Sass placeholders to avoid repeats because of WordPress articles * move NavLink component to molecules
Diffstat (limited to 'src/components/molecules')
-rw-r--r--src/components/molecules/layout/branding.tsx3
-rw-r--r--src/components/molecules/layout/meta.tsx2
-rw-r--r--src/components/molecules/nav/index.ts1
-rw-r--r--src/components/molecules/nav/nav-link/index.ts1
-rw-r--r--src/components/molecules/nav/nav-link/nav-link.module.scss61
-rw-r--r--src/components/molecules/nav/nav-link/nav-link.stories.tsx79
-rw-r--r--src/components/molecules/nav/nav-link/nav-link.test.tsx28
-rw-r--r--src/components/molecules/nav/nav-link/nav-link.tsx44
-rw-r--r--src/components/molecules/nav/nav-list.module.scss10
-rw-r--r--src/components/molecules/nav/nav-list.tsx5
10 files changed, 229 insertions, 5 deletions
diff --git a/src/components/molecules/layout/branding.tsx b/src/components/molecules/layout/branding.tsx
index 981da74..dceee5e 100644
--- a/src/components/molecules/layout/branding.tsx
+++ b/src/components/molecules/layout/branding.tsx
@@ -1,8 +1,7 @@
-import Link from 'next/link';
import { type FC, useRef } from 'react';
import { useIntl } from 'react-intl';
import { useStyles } from '../../../utils/hooks';
-import { Heading } from '../../atoms';
+import { Heading, Link } from '../../atoms';
import { FlippingLogo, type FlippingLogoProps } from '../images';
import styles from './branding.module.scss';
diff --git a/src/components/molecules/layout/meta.tsx b/src/components/molecules/layout/meta.tsx
index 094c420..63909a4 100644
--- a/src/components/molecules/layout/meta.tsx
+++ b/src/components/molecules/layout/meta.tsx
@@ -336,7 +336,7 @@ export const Meta: FC<MetaProps> = ({
);
case 'website':
return typeof value === 'string' ? (
- <Link href={value} external={true}>
+ <Link href={value} isExternal>
{value}
</Link>
) : null;
diff --git a/src/components/molecules/nav/index.ts b/src/components/molecules/nav/index.ts
index 9c46050..fe7cd0b 100644
--- a/src/components/molecules/nav/index.ts
+++ b/src/components/molecules/nav/index.ts
@@ -1,3 +1,4 @@
export * from './breadcrumb';
+export * from './nav-link';
export * from './nav-list';
export * from './pagination';
diff --git a/src/components/molecules/nav/nav-link/index.ts b/src/components/molecules/nav/nav-link/index.ts
new file mode 100644
index 0000000..f1b68c2
--- /dev/null
+++ b/src/components/molecules/nav/nav-link/index.ts
@@ -0,0 +1 @@
+export * from './nav-link';
diff --git a/src/components/molecules/nav/nav-link/nav-link.module.scss b/src/components/molecules/nav/nav-link/nav-link.module.scss
new file mode 100644
index 0000000..8a7d371
--- /dev/null
+++ b/src/components/molecules/nav/nav-link/nav-link.module.scss
@@ -0,0 +1,61 @@
+@use "../../../../styles/abstracts/functions" as fun;
+@use "../../../../styles/abstracts/placeholders";
+
+.link {
+ --draw-border-thickness: #{fun.convert-px(4)};
+ --draw-border-color1: var(--color-primary-light);
+ --draw-border-color2: var(--color-primary-lighter);
+
+ display: flex;
+ flex-flow: row wrap;
+ place-items: center;
+ place-content: center;
+ row-gap: var(--spacing-2xs);
+ min-width: var(--link-min-width, fun.convert-px(80));
+ padding: var(--spacing-xs) var(--spacing-xs) var(--spacing-2xs);
+ background: none;
+ border-radius: 8%;
+ font-size: var(--font-size-sm);
+ font-variant: small-caps;
+ font-weight: 600;
+ text-align: center;
+ text-decoration: none;
+
+ &--inline {
+ width: fit-content;
+
+ .logo {
+ margin-right: var(--spacing-xs);
+ }
+ }
+
+ &--stack {
+ .logo {
+ flex: 0 0 100%;
+ display: flex;
+ place-content: center;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ @extend %draw-borders;
+ }
+
+ &:hover {
+ color: var(--color-primary-light);
+ }
+
+ &:focus {
+ color: var(--color-primary-light);
+ }
+
+ &:active {
+ --draw-border-color1: var(--color-primary-dark);
+ --draw-border-color2: var(--color-primary-light);
+
+ color: var(--color-primary-dark);
+
+ @extend %draw-borders;
+ }
+}
diff --git a/src/components/molecules/nav/nav-link/nav-link.stories.tsx b/src/components/molecules/nav/nav-link/nav-link.stories.tsx
new file mode 100644
index 0000000..4e8400f
--- /dev/null
+++ b/src/components/molecules/nav/nav-link/nav-link.stories.tsx
@@ -0,0 +1,79 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { Icon } from '../../../atoms';
+import { NavLink as NavLinkComponent } from './nav-link';
+
+/**
+ * NavLink - Storybook Meta
+ */
+export default {
+ title: 'Molecules/Nav/NavLink',
+ component: NavLinkComponent,
+ argTypes: {
+ href: {
+ control: {
+ type: 'text',
+ },
+ description: 'The link target.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ label: {
+ control: {
+ type: 'text',
+ },
+ description: 'The link label.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ logo: {
+ control: {
+ type: null,
+ },
+ description: 'The link logo.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta<typeof NavLinkComponent>;
+
+const Template: ComponentStory<typeof NavLinkComponent> = (args) => (
+ <div style={{ width: 'fit-content' }}>
+ <NavLinkComponent {...args} />
+ </div>
+);
+
+/**
+ * NavLink Stories - Default
+ */
+export const Default = Template.bind({});
+Default.args = {
+ href: '#',
+ label: 'A nav link',
+};
+
+/**
+ * NavLink Stories - StackWithLogo
+ */
+export const StackWithLogo = Template.bind({});
+StackWithLogo.args = {
+ href: '#example',
+ label: 'A nav link',
+ logo: <Icon aria-hidden shape="home" />,
+};
+
+/**
+ * NavLink Stories - InlineWithLogo
+ */
+export const InlineWithLogo = Template.bind({});
+InlineWithLogo.args = {
+ href: '#example',
+ isInline: true,
+ label: 'A nav link',
+ logo: <Icon aria-hidden shape="home" />,
+};
diff --git a/src/components/molecules/nav/nav-link/nav-link.test.tsx b/src/components/molecules/nav/nav-link/nav-link.test.tsx
new file mode 100644
index 0000000..aa9b557
--- /dev/null
+++ b/src/components/molecules/nav/nav-link/nav-link.test.tsx
@@ -0,0 +1,28 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+import { NavLink } from './nav-link';
+
+describe('NavLink', () => {
+ it('renders a link', () => {
+ const label = 'eius';
+ const target = '#harum';
+
+ render(<NavLink href={target} label={label} />);
+
+ expect(rtlScreen.getByRole('link', { name: label })).toHaveAttribute(
+ 'href',
+ target
+ );
+ });
+
+ it('can render a nav link with inlined contents', () => {
+ const label = 'eius';
+ const target = '#harum';
+
+ render(<NavLink href={target} isInline label={label} />);
+
+ expect(rtlScreen.getByRole('link', { name: label })).toHaveClass(
+ 'link--inline'
+ );
+ });
+});
diff --git a/src/components/molecules/nav/nav-link/nav-link.tsx b/src/components/molecules/nav/nav-link/nav-link.tsx
new file mode 100644
index 0000000..f9fc529
--- /dev/null
+++ b/src/components/molecules/nav/nav-link/nav-link.tsx
@@ -0,0 +1,44 @@
+import {
+ type ForwardRefRenderFunction,
+ forwardRef,
+ type ReactNode,
+} from 'react';
+import { Link, type LinkProps } from '../../../atoms';
+import styles from './nav-link.module.scss';
+
+export type NavLinkProps = Omit<LinkProps, 'children' | 'disableTransition'> & {
+ /**
+ * Should the logo and label be inlined?
+ *
+ * @default false
+ */
+ isInline?: boolean;
+ /**
+ * The link label.
+ */
+ label: string;
+ /**
+ * The link logo.
+ */
+ logo?: ReactNode;
+};
+
+const NavLinkWithRef: ForwardRefRenderFunction<
+ HTMLAnchorElement,
+ NavLinkProps
+> = ({ className = '', isInline = false, label, logo, ...props }, ref) => {
+ const linkClass = [
+ styles.link,
+ styles[isInline ? 'link--inline' : 'link--stack'],
+ className,
+ ].join(' ');
+
+ return (
+ <Link {...props} className={linkClass} disableTransition ref={ref}>
+ {logo ? <span className={styles.logo}>{logo}</span> : null}
+ {label}
+ </Link>
+ );
+};
+
+export const NavLink = forwardRef(NavLinkWithRef);
diff --git a/src/components/molecules/nav/nav-list.module.scss b/src/components/molecules/nav/nav-list.module.scss
index ff99581..316638e 100644
--- a/src/components/molecules/nav/nav-list.module.scss
+++ b/src/components/molecules/nav/nav-list.module.scss
@@ -1,4 +1,14 @@
.nav {
+ &--main {
+ width: fit-content;
+ }
+
+ &--main & {
+ &__item {
+ flex: 1;
+ }
+ }
+
&--footer & {
&__item:not(:first-child) {
&::before {
diff --git a/src/components/molecules/nav/nav-list.tsx b/src/components/molecules/nav/nav-list.tsx
index 55c2aa9..b3c7138 100644
--- a/src/components/molecules/nav/nav-list.tsx
+++ b/src/components/molecules/nav/nav-list.tsx
@@ -1,5 +1,6 @@
import type { FC, ReactNode } from 'react';
-import { Link, List, ListItem, Nav, NavLink, type NavProps } from '../../atoms';
+import { Link, List, ListItem, Nav, type NavProps } from '../../atoms';
+import { NavLink } from './nav-link';
import styles from './nav-list.module.scss';
export type NavItem = {
@@ -49,7 +50,7 @@ export const NavList: FC<NavListProps> = ({
...props
}) => {
const kindClass = `nav--${kind}`;
- const navClass = `${styles[kindClass]} ${className}`;
+ const navClass = `${styles.nav} ${styles[kindClass]} ${className}`;
/**
* Get the nav items.