From f914ff8376dd91c4f6f8ca149e1cb6becb622d88 Mon Sep 17 00:00:00 2001 From: Armand Philippot Date: Mon, 2 Oct 2023 18:45:30 +0200 Subject: 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 --- src/components/atoms/links/index.ts | 1 - src/components/atoms/links/link.module.scss | 220 ----------------------- src/components/atoms/links/link.stories.tsx | 180 ------------------- src/components/atoms/links/link.test.tsx | 10 -- src/components/atoms/links/link.tsx | 55 ------ src/components/atoms/links/link/index.ts | 1 + src/components/atoms/links/link/link.module.scss | 70 ++++++++ src/components/atoms/links/link/link.stories.tsx | 178 ++++++++++++++++++ src/components/atoms/links/link/link.test.tsx | 47 +++++ src/components/atoms/links/link/link.tsx | 90 ++++++++++ src/components/atoms/links/nav-link.module.scss | 45 ----- src/components/atoms/links/nav-link.stories.tsx | 55 ------ src/components/atoms/links/nav-link.test.tsx | 13 -- src/components/atoms/links/nav-link.tsx | 32 ---- 14 files changed, 386 insertions(+), 611 deletions(-) delete mode 100644 src/components/atoms/links/link.module.scss delete mode 100644 src/components/atoms/links/link.stories.tsx delete mode 100644 src/components/atoms/links/link.test.tsx delete mode 100644 src/components/atoms/links/link.tsx create mode 100644 src/components/atoms/links/link/index.ts create mode 100644 src/components/atoms/links/link/link.module.scss create mode 100644 src/components/atoms/links/link/link.stories.tsx create mode 100644 src/components/atoms/links/link/link.test.tsx create mode 100644 src/components/atoms/links/link/link.tsx delete mode 100644 src/components/atoms/links/nav-link.module.scss delete mode 100644 src/components/atoms/links/nav-link.stories.tsx delete mode 100644 src/components/atoms/links/nav-link.test.tsx delete mode 100644 src/components/atoms/links/nav-link.tsx (limited to 'src/components/atoms') diff --git a/src/components/atoms/links/index.ts b/src/components/atoms/links/index.ts index ad8824e..619f05c 100644 --- a/src/components/atoms/links/index.ts +++ b/src/components/atoms/links/index.ts @@ -1,4 +1,3 @@ export * from './link'; -export * from './nav-link'; export * from './sharing-link'; export * from './social-link'; diff --git a/src/components/atoms/links/link.module.scss b/src/components/atoms/links/link.module.scss deleted file mode 100644 index 4980a1c..0000000 --- a/src/components/atoms/links/link.module.scss +++ /dev/null @@ -1,220 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; -@use "../../../styles/abstracts/variables" as var; - -/* stylelint-disable no-descending-specificity */ -.link { - &[hreflang] { - &::after { - display: inline-block; - - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]"; - font-size: var(--font-size-sm); - } - } - - &--download { - &::after { - display: inline-block; - - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &[hreflang] { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - } - } - - &--external { - &::after { - display: inline-block; - - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &[hreflang] { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - } - } - - &--external#{&}--download { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - - &[hreflang] { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - } - } -} - -:global([data-theme="dark"]) { - :local { - .link { - &--download { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &[hreflang] { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - } - } - - &--external { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')); - } - - &[hreflang] { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')); - } - } - } - - &--external.link--download { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - - &[hreflang] { - &::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - - &:focus:not(:active)::after { - /* Prettier is removing spacing between content parts. */ - - /* prettier-ignore */ - content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('')) "\0000a0" url(fun.encode-svg('')); - } - } - } - } - } -} -/* stylelint-enable no-descending-specificity */ diff --git a/src/components/atoms/links/link.stories.tsx b/src/components/atoms/links/link.stories.tsx deleted file mode 100644 index 8351de7..0000000 --- a/src/components/atoms/links/link.stories.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { Link } from './link'; - -/** - * Link - Storybook Meta - */ -export default { - title: 'Atoms/Typography/Links', - component: Link, - argTypes: { - children: { - control: { - type: 'text', - }, - description: 'The link body.', - type: { - name: 'string', - required: true, - }, - }, - className: { - control: { - type: 'text', - }, - description: 'Set additional classnames.', - table: { - category: 'Styles', - }, - type: { - name: 'string', - required: false, - }, - }, - download: { - control: { - type: 'boolean', - }, - description: 'Determine if the link purpose is to download a file.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - external: { - control: { - type: 'boolean', - }, - description: 'Determine if the link is external of the current website.', - table: { - category: 'Options', - defaultValue: { summary: false }, - }, - type: { - name: 'boolean', - required: false, - }, - }, - href: { - control: { - type: 'text', - }, - description: 'The link target.', - type: { - name: 'string', - required: true, - }, - }, - lang: { - control: { - type: 'text', - }, - table: { - category: 'Options', - }, - description: 'The target language as code language.', - type: { - name: 'string', - required: false, - }, - }, - }, -} as ComponentMeta; - -const Template: ComponentStory = (args) => ; - -/** - * Links Stories - Default - */ -export const Default = Template.bind({}); -Default.args = { - children: 'A link', - href: '#', - download: false, - external: false, -}; - -/** - * Links Stories - Download - */ -export const Download = Template.bind({}); -Download.args = { - children: 'A link to a file', - href: '#', - download: true, - external: false, -}; - -/** - * Links Stories - DownloadWithLang - */ -export const DownloadWithLang = Template.bind({}); -DownloadWithLang.args = { - children: 'A link to a file', - href: '#', - download: true, - external: false, - lang: 'en', -}; - -/** - * Links Stories - External - */ -export const External = Template.bind({}); -External.args = { - children: 'A link', - href: '#', - download: false, - external: true, -}; - -/** - * Links Stories - External download - */ -export const ExternalDownload = Template.bind({}); -ExternalDownload.args = { - children: 'A link', - href: '#', - download: true, - external: true, -}; - -/** - * Links Stories - External With Lang - */ -export const ExternalWithLang = Template.bind({}); -ExternalWithLang.args = { - children: 'A link', - href: '#', - download: false, - external: true, - lang: 'en', -}; - -/** - * Links Stories - External download with lang - */ -export const ExternalDownloadWithLang = Template.bind({}); -ExternalDownloadWithLang.args = { - children: 'A link', - href: '#', - download: true, - external: true, - lang: 'en', -}; - -/** - * Links Stories - With Lang - */ -export const WithLang = Template.bind({}); -WithLang.args = { - children: 'A link', - href: '#', - download: false, - external: false, - lang: 'en', -}; diff --git a/src/components/atoms/links/link.test.tsx b/src/components/atoms/links/link.test.tsx deleted file mode 100644 index 9829c1b..0000000 --- a/src/components/atoms/links/link.test.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { Link } from './link'; - -describe('Link', () => { - it('render a link', () => { - render(A link); - expect(screen.getByRole('link')).toHaveTextContent('A link'); - }); -}); diff --git a/src/components/atoms/links/link.tsx b/src/components/atoms/links/link.tsx deleted file mode 100644 index 1765bb5..0000000 --- a/src/components/atoms/links/link.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import NextLink from 'next/link'; -import { AnchorHTMLAttributes, FC, ReactNode } from 'react'; -import styles from './link.module.scss'; - -export type LinkProps = AnchorHTMLAttributes & { - /** - * The link body. - */ - children: ReactNode; - /** - * True if it is a download link. Default: false. - */ - download?: boolean; - /** - * True if it is an external link. Default: false. - */ - external?: boolean; - /** - * The link target. - */ - href: string; - /** - * The link target code language. - */ - lang?: string; -}; - -/** - * Link Component - * - * Render a link. - */ -export const Link: FC = ({ - children, - className = '', - download = false, - external = false, - href, - lang, - ...props -}) => { - const downloadClass = download ? styles['link--download'] : ''; - const linkClass = `${styles.link} ${downloadClass} ${className}`; - const externalLinkClass = `${linkClass} ${styles['link--external']}`; - - return external ? ( - - {children} - - ) : ( - - {children} - - ); -}; diff --git a/src/components/atoms/links/link/index.ts b/src/components/atoms/links/link/index.ts new file mode 100644 index 0000000..e33728e --- /dev/null +++ b/src/components/atoms/links/link/index.ts @@ -0,0 +1 @@ +export * from './link'; diff --git a/src/components/atoms/links/link/link.module.scss b/src/components/atoms/links/link/link.module.scss new file mode 100644 index 0000000..8f94a54 --- /dev/null +++ b/src/components/atoms/links/link/link.module.scss @@ -0,0 +1,70 @@ +@use "../../../../styles/abstracts/placeholders"; + +.link { + color: var(--color-primary); + + &--regular { + @extend %link; + } + + &[hreflang], + &--download, + &--external { + @extend %link-with-icon; + } + + &[hreflang]:not(#{&}--download):not(#{&}--external) { + --is-icon-hidden: ""; + } + + &[hreflang] { + @extend %link-with-lang; + } + + &--download { + @extend %download-link; + } + + &--external { + @extend %external-link; + } + + &--download, + &--external { + &:not([hreflang]) { + --is-lang-hidden: ""; + } + } + + &--external#{&}--download { + @extend %external-download-link; + } +} + +:global([data-theme="light"]) { + :local { + .link { + &--download { + @extend %light-download-link; + } + + &--external { + @extend %light-external-link; + } + } + } +} + +:global([data-theme="dark"]) { + :local { + .link { + &--download { + @extend %dark-download-link; + } + + &--external { + @extend %dark-external-link; + } + } + } +} diff --git a/src/components/atoms/links/link/link.stories.tsx b/src/components/atoms/links/link/link.stories.tsx new file mode 100644 index 0000000..0a6a6c5 --- /dev/null +++ b/src/components/atoms/links/link/link.stories.tsx @@ -0,0 +1,178 @@ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; +import { Link } from './link'; + +/** + * Link - Storybook Meta + */ +export default { + title: 'Atoms/Links/Link', + component: Link, + argTypes: { + children: { + control: { + type: 'text', + }, + description: 'The link body.', + type: { + name: 'string', + required: true, + }, + }, + className: { + control: { + type: 'text', + }, + description: 'Set additional classnames.', + table: { + category: 'Styles', + }, + type: { + name: 'string', + required: false, + }, + }, + href: { + control: { + type: 'text', + }, + description: 'The link target.', + type: { + name: 'string', + required: true, + }, + }, + isDownload: { + control: { + type: 'boolean', + }, + description: 'Determine if the link purpose is to download a file.', + table: { + category: 'Options', + defaultValue: { summary: false }, + }, + type: { + name: 'boolean', + required: false, + }, + }, + isExternal: { + control: { + type: 'boolean', + }, + description: 'Determine if the link is external of the current website.', + table: { + category: 'Options', + defaultValue: { summary: false }, + }, + type: { + name: 'boolean', + required: false, + }, + }, + lang: { + control: { + type: 'text', + }, + table: { + category: 'Options', + }, + description: 'The target language as code language.', + type: { + name: 'string', + required: false, + }, + }, + }, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +/** + * Links Stories - Default + */ +export const Default = Template.bind({}); +Default.args = { + children: 'A link', + href: '#', +}; + +/** + * Links Stories - Download + */ +export const DownloadLink = Template.bind({}); +DownloadLink.args = { + children: 'A link to a file', + href: '#', + isDownload: true, + isExternal: false, +}; + +/** + * Links Stories - Download link with lang + */ +export const DownloadLinkWithLang = Template.bind({}); +DownloadLinkWithLang.args = { + children: 'A link to a file', + href: '#', + isDownload: true, + isExternal: false, + lang: 'en', +}; + +/** + * Links Stories - External + */ +export const ExternalLink = Template.bind({}); +ExternalLink.args = { + children: 'A link', + href: '#', + isDownload: false, + isExternal: true, +}; + +/** + * Links Stories - External download link + */ +export const ExternalDownload = Template.bind({}); +ExternalDownload.args = { + children: 'A link', + href: '#', + isDownload: true, + isExternal: true, +}; + +/** + * Links Stories - External link with Lang + */ +export const ExternalLinkWithLang = Template.bind({}); +ExternalLinkWithLang.args = { + children: 'A link', + href: '#', + isDownload: false, + isExternal: true, + lang: 'en', +}; + +/** + * Links Stories - External download with lang + */ +export const ExternalDownloadWithLang = Template.bind({}); +ExternalDownloadWithLang.args = { + children: 'A link', + href: '#', + isDownload: true, + isExternal: true, + lang: 'en', +}; + +/** + * Links Stories - With Lang + */ +export const LinkLang = Template.bind({}); +LinkLang.args = { + children: 'A link', + href: '#', + isDownload: false, + isExternal: false, + lang: 'en', +}; diff --git a/src/components/atoms/links/link/link.test.tsx b/src/components/atoms/links/link/link.test.tsx new file mode 100644 index 0000000..ad1951d --- /dev/null +++ b/src/components/atoms/links/link/link.test.tsx @@ -0,0 +1,47 @@ +import { describe, expect, it } from '@jest/globals'; +import { render, screen as rtlScreen } from '@testing-library/react'; +import { Link } from './link'; + +describe('Link', () => { + it('renders a link', () => { + const anchor = 'porro'; + const target = '/tempora'; + + render({anchor}); + + expect(rtlScreen.getByRole('link', { name: anchor })).toHaveAttribute( + 'href', + target + ); + }); + + it('can render an external link', () => { + const anchor = 'accusamus'; + const target = 'https://www.example.com'; + + render( + + {anchor} + + ); + + expect(rtlScreen.getByRole('link', { name: anchor })).toHaveClass( + 'link--external' + ); + }); + + it('can render a download link', () => { + const anchor = 'dolor'; + const target = '/officiis.pdf'; + + render( + + {anchor} + + ); + + expect(rtlScreen.getByRole('link', { name: anchor })).toHaveClass( + 'link--download' + ); + }); +}); diff --git a/src/components/atoms/links/link/link.tsx b/src/components/atoms/links/link/link.tsx new file mode 100644 index 0000000..e88cc7c --- /dev/null +++ b/src/components/atoms/links/link/link.tsx @@ -0,0 +1,90 @@ +import NextLink from 'next/link'; +import { + forwardRef, + type AnchorHTMLAttributes, + type ForwardRefRenderFunction, + type ReactNode, +} from 'react'; +import styles from './link.module.scss'; + +export type LinkProps = Omit< + AnchorHTMLAttributes, + 'children' | 'download' | 'hrefLang' | 'lang' +> & { + /** + * The link body. + */ + children: ReactNode; + /** + * Should we disable the default transition on links? + * + * @default false + */ + disableTransition?: boolean; + /** + * True if it is a download link. + * + * @default false + */ + isDownload?: boolean; + /** + * True if it is an external link. + * + * @default false + */ + isExternal?: boolean; + /** + * The link target. + */ + href: string; + /** + * The link target code language. + */ + lang?: string; +}; + +const LinkWithRef: ForwardRefRenderFunction = ( + { + children, + className = '', + disableTransition = false, + isDownload = false, + isExternal = false, + href, + lang, + rel = '', + ...props + }, + ref +) => { + const LinkComponent = isExternal ? 'a' : NextLink; + const linkClass = [ + styles.link, + styles[disableTransition ? '' : 'link--regular'], + styles[isDownload ? 'link--download' : ''], + styles[isExternal ? 'link--external' : ''], + className, + ].join(' '); + const linkRel = + isExternal && !rel.includes('external') ? `external ${rel}` : rel; + + return ( + + {children} + + ); +}; + +/** + * Link Component + * + * Render a link. + */ +export const Link = forwardRef(LinkWithRef); diff --git a/src/components/atoms/links/nav-link.module.scss b/src/components/atoms/links/nav-link.module.scss deleted file mode 100644 index e72885a..0000000 --- a/src/components/atoms/links/nav-link.module.scss +++ /dev/null @@ -1,45 +0,0 @@ -@use "../../../styles/abstracts/functions" as fun; -@use "../../../styles/abstracts/mixins" as mix; -@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: inline-flex; - flex-flow: column nowrap; - place-items: center; - place-content: center; - row-gap: var(--spacing-2xs); - min-width: var(--link-min-width, fun.convert-px(85)); - padding: var(--spacing-xs); - background: inherit; - font-size: var(--font-size-sm); - font-variant: small-caps; - font-weight: 600; - line-height: 1; - text-decoration: none; - - @include mix.media("screen") { - @include mix.dimensions("md") { - border-radius: 8%; - } - } - - &:hover, - &:focus { - @extend %draw-borders; - } - - &:focus { - color: var(--color-primary-light); - } - - &:active { - --draw-border-color1: var(--color-primary-dark); - --draw-border-color2: var(--color-primary-light); - - @extend %draw-borders; - } -} diff --git a/src/components/atoms/links/nav-link.stories.tsx b/src/components/atoms/links/nav-link.stories.tsx deleted file mode 100644 index 7fca926..0000000 --- a/src/components/atoms/links/nav-link.stories.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { ComponentMeta, ComponentStory } from '@storybook/react'; -import { NavLink as NavLinkComponent } from './nav-link'; - -/** - * NavLink - Storybook Meta - */ -export default { - title: 'Atoms/Typography/Links', - 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; - -const Template: ComponentStory = (args) => ( - -); - -/** - * Links Stories - Nav Link - */ -export const NavLink = Template.bind({}); -NavLink.args = { - href: '#', - label: 'A nav link', -}; diff --git a/src/components/atoms/links/nav-link.test.tsx b/src/components/atoms/links/nav-link.test.tsx deleted file mode 100644 index acf3225..0000000 --- a/src/components/atoms/links/nav-link.test.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { describe, expect, it } from '@jest/globals'; -import { render, screen } from '../../../../tests/utils'; -import { NavLink } from './nav-link'; - -describe('NavLink', () => { - it('renders a nav link to blog page', () => { - render(); - expect(screen.getByRole('link', { name: 'Blog' })).toHaveAttribute( - 'href', - '/blog' - ); - }); -}); diff --git a/src/components/atoms/links/nav-link.tsx b/src/components/atoms/links/nav-link.tsx deleted file mode 100644 index 109529c..0000000 --- a/src/components/atoms/links/nav-link.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import Link from 'next/link'; -import { FC, ReactNode } from 'react'; -import styles from './nav-link.module.scss'; - -export type NavLinkProps = { - /** - * Link target. - */ - href: string; - /** - * Link label. - */ - label: string; - /** - * Link logo. - */ - logo?: ReactNode; -}; - -/** - * NavLink component - * - * Render a navigation link. - */ -export const NavLink: FC = ({ href, label, logo }) => { - return ( - - {logo} - {label} - - ); -}; -- cgit v1.2.3