diff options
| author | Armand Philippot <git@armandphilippot.com> | 2023-10-02 18:45:30 +0200 |
|---|---|---|
| committer | Armand Philippot <git@armandphilippot.com> | 2023-11-11 18:14:41 +0100 |
| commit | f914ff8376dd91c4f6f8ca149e1cb6becb622d88 (patch) | |
| tree | 777dc0268eba86721878a715c68f0f09bedb4b18 /src/components/atoms/links/link | |
| parent | b52b8183ce299b5a2d3c3b2f4f8cb94bb443d746 (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/atoms/links/link')
| -rw-r--r-- | src/components/atoms/links/link/index.ts | 1 | ||||
| -rw-r--r-- | src/components/atoms/links/link/link.module.scss | 70 | ||||
| -rw-r--r-- | src/components/atoms/links/link/link.stories.tsx | 178 | ||||
| -rw-r--r-- | src/components/atoms/links/link/link.test.tsx | 47 | ||||
| -rw-r--r-- | src/components/atoms/links/link/link.tsx | 90 |
5 files changed, 386 insertions, 0 deletions
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<typeof Link>; + +const Template: ComponentStory<typeof Link> = (args) => <Link {...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(<Link href={target}>{anchor}</Link>); + + 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( + <Link href={target} isExternal> + {anchor} + </Link> + ); + + expect(rtlScreen.getByRole('link', { name: anchor })).toHaveClass( + 'link--external' + ); + }); + + it('can render a download link', () => { + const anchor = 'dolor'; + const target = '/officiis.pdf'; + + render( + <Link href={target} isDownload> + {anchor} + </Link> + ); + + 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<HTMLAnchorElement>, + '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<HTMLAnchorElement, LinkProps> = ( + { + 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 ( + <LinkComponent + {...props} + className={linkClass} + href={href} + hrefLang={lang} + ref={ref} + rel={linkRel} + > + {children} + </LinkComponent> + ); +}; + +/** + * Link Component + * + * Render a link. + */ +export const Link = forwardRef(LinkWithRef); |
