aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms
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/atoms
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/atoms')
-rw-r--r--src/components/atoms/links/index.ts1
-rw-r--r--src/components/atoms/links/link.module.scss220
-rw-r--r--src/components/atoms/links/link.test.tsx10
-rw-r--r--src/components/atoms/links/link.tsx55
-rw-r--r--src/components/atoms/links/link/index.ts1
-rw-r--r--src/components/atoms/links/link/link.module.scss70
-rw-r--r--src/components/atoms/links/link/link.stories.tsx (renamed from src/components/atoms/links/link.stories.tsx)84
-rw-r--r--src/components/atoms/links/link/link.test.tsx47
-rw-r--r--src/components/atoms/links/link/link.tsx90
-rw-r--r--src/components/atoms/links/nav-link.module.scss45
-rw-r--r--src/components/atoms/links/nav-link.stories.tsx55
-rw-r--r--src/components/atoms/links/nav-link.test.tsx13
-rw-r--r--src/components/atoms/links/nav-link.tsx32
13 files changed, 249 insertions, 474 deletions
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('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_white}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
-
- &[hreflang] {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_white}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
- }
- }
-
- &--external {
- &::after {
- display: inline-block;
-
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &[hreflang] {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
- }
- }
-
- &--external#{&}--download {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_white}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &[hreflang] {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$light-theme_white}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$light-theme_white}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$light-theme_white}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
- }
- }
-}
-
-:global([data-theme="dark"]) {
- :local {
- .link {
- &--download {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_black}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
-
- &[hreflang] {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_black}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>'));
- }
- }
- }
-
- &--external {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_black}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &[hreflang] {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_black}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
- }
- }
-
- &--external.link--download {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_black}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_black}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &[hreflang] {
- &::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_blue}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_blue}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_blue}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
-
- &:focus:not(:active)::after {
- /* Prettier is removing spacing between content parts. */
-
- /* prettier-ignore */
- content: "\0000a0[" attr(hreflang) "]\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="m49 80.048-28.445-30.77 19.32 4.095V5.06h18.252v48.313l21.318-4.095z"/><path fill="#{var.$dark-theme_black}" d="M0 67.57v27.37h100V67.57H87.973v15.344H12.027V67.569z"/></svg>')) "\0000a0" url(fun.encode-svg('<svg width="13" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><path fill="#{var.$dark-theme_black}" d="M100 0 59.543 5.887l20.8 6.523-51.134 51.134 7.249 7.248L87.59 19.66l6.522 20.798z"/><path fill="#{var.$dark-theme_black}" d="M4 10a4 4 0 0 0-4 4v82a4 4 0 0 0 4 4h82a4 4 0 0 0 4-4V62.314h-8V92H8V18h29.686v-8z"/></svg>'));
- }
- }
- }
- }
- }
-}
-/* stylelint-enable no-descending-specificity */
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(<Link href="#">A link</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<HTMLAnchorElement> & {
- /**
- * 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<LinkProps> = ({
- 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 ? (
- <a {...props} className={externalLinkClass} href={href} hrefLang={lang}>
- {children}
- </a>
- ) : (
- <NextLink {...props} className={linkClass} href={href} hrefLang={lang}>
- {children}
- </NextLink>
- );
-};
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.stories.tsx b/src/components/atoms/links/link/link.stories.tsx
index 8351de7..0a6a6c5 100644
--- a/src/components/atoms/links/link.stories.tsx
+++ b/src/components/atoms/links/link/link.stories.tsx
@@ -1,11 +1,11 @@
-import { ComponentMeta, ComponentStory } from '@storybook/react';
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
import { Link } from './link';
/**
* Link - Storybook Meta
*/
export default {
- title: 'Atoms/Typography/Links',
+ title: 'Atoms/Links/Link',
component: Link,
argTypes: {
children: {
@@ -31,7 +31,17 @@ export default {
required: false,
},
},
- download: {
+ href: {
+ control: {
+ type: 'text',
+ },
+ description: 'The link target.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ isDownload: {
control: {
type: 'boolean',
},
@@ -45,7 +55,7 @@ export default {
required: false,
},
},
- external: {
+ isExternal: {
control: {
type: 'boolean',
},
@@ -59,16 +69,6 @@ export default {
required: false,
},
},
- href: {
- control: {
- type: 'text',
- },
- description: 'The link target.',
- type: {
- name: 'string',
- required: true,
- },
- },
lang: {
control: {
type: 'text',
@@ -94,64 +94,62 @@ 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 = {
+export const DownloadLink = Template.bind({});
+DownloadLink.args = {
children: 'A link to a file',
href: '#',
- download: true,
- external: false,
+ isDownload: true,
+ isExternal: false,
};
/**
- * Links Stories - DownloadWithLang
+ * Links Stories - Download link with lang
*/
-export const DownloadWithLang = Template.bind({});
-DownloadWithLang.args = {
+export const DownloadLinkWithLang = Template.bind({});
+DownloadLinkWithLang.args = {
children: 'A link to a file',
href: '#',
- download: true,
- external: false,
+ isDownload: true,
+ isExternal: false,
lang: 'en',
};
/**
* Links Stories - External
*/
-export const External = Template.bind({});
-External.args = {
+export const ExternalLink = Template.bind({});
+ExternalLink.args = {
children: 'A link',
href: '#',
- download: false,
- external: true,
+ isDownload: false,
+ isExternal: true,
};
/**
- * Links Stories - External download
+ * Links Stories - External download link
*/
export const ExternalDownload = Template.bind({});
ExternalDownload.args = {
children: 'A link',
href: '#',
- download: true,
- external: true,
+ isDownload: true,
+ isExternal: true,
};
/**
- * Links Stories - External With Lang
+ * Links Stories - External link with Lang
*/
-export const ExternalWithLang = Template.bind({});
-ExternalWithLang.args = {
+export const ExternalLinkWithLang = Template.bind({});
+ExternalLinkWithLang.args = {
children: 'A link',
href: '#',
- download: false,
- external: true,
+ isDownload: false,
+ isExternal: true,
lang: 'en',
};
@@ -162,19 +160,19 @@ export const ExternalDownloadWithLang = Template.bind({});
ExternalDownloadWithLang.args = {
children: 'A link',
href: '#',
- download: true,
- external: true,
+ isDownload: true,
+ isExternal: true,
lang: 'en',
};
/**
* Links Stories - With Lang
*/
-export const WithLang = Template.bind({});
-WithLang.args = {
+export const LinkLang = Template.bind({});
+LinkLang.args = {
children: 'A link',
href: '#',
- download: false,
- external: false,
+ 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);
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<typeof NavLinkComponent>;
-
-const Template: ComponentStory<typeof NavLinkComponent> = (args) => (
- <NavLinkComponent {...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(<NavLink href="/blog" label="Blog" />);
- 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<NavLinkProps> = ({ href, label, logo }) => {
- return (
- <Link className={styles.link} href={href}>
- {logo}
- {label}
- </Link>
- );
-};