diff options
Diffstat (limited to 'src/components/atoms/links')
| -rw-r--r-- | src/components/atoms/links/link.module.scss | 220 | ||||
| -rw-r--r-- | src/components/atoms/links/link.stories.tsx | 180 | ||||
| -rw-r--r-- | src/components/atoms/links/link.test.tsx | 9 | ||||
| -rw-r--r-- | src/components/atoms/links/link.tsx | 67 | ||||
| -rw-r--r-- | src/components/atoms/links/nav-link.module.scss | 46 | ||||
| -rw-r--r-- | src/components/atoms/links/nav-link.stories.tsx | 55 | ||||
| -rw-r--r-- | src/components/atoms/links/nav-link.test.tsx | 12 | ||||
| -rw-r--r-- | src/components/atoms/links/nav-link.tsx | 36 | ||||
| -rw-r--r-- | src/components/atoms/links/sharing-link.module.scss | 157 | ||||
| -rw-r--r-- | src/components/atoms/links/sharing-link.stories.tsx | 98 | ||||
| -rw-r--r-- | src/components/atoms/links/sharing-link.test.tsx | 46 | ||||
| -rw-r--r-- | src/components/atoms/links/sharing-link.tsx | 48 | ||||
| -rw-r--r-- | src/components/atoms/links/social-link.module.scss | 43 | ||||
| -rw-r--r-- | src/components/atoms/links/social-link.stories.tsx | 73 | ||||
| -rw-r--r-- | src/components/atoms/links/social-link.test.tsx | 15 | ||||
| -rw-r--r-- | src/components/atoms/links/social-link.tsx | 53 |
16 files changed, 1158 insertions, 0 deletions
diff --git a/src/components/atoms/links/link.module.scss b/src/components/atoms/links/link.module.scss new file mode 100644 index 0000000..bb5775f --- /dev/null +++ b/src/components/atoms/links/link.module.scss @@ -0,0 +1,220 @@ +@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.stories.tsx b/src/components/atoms/links/link.stories.tsx new file mode 100644 index 0000000..4baabe5 --- /dev/null +++ b/src/components/atoms/links/link.stories.tsx @@ -0,0 +1,180 @@ +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<typeof Link>; + +const Template: ComponentStory<typeof Link> = (args) => <Link {...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 new file mode 100644 index 0000000..54e2414 --- /dev/null +++ b/src/components/atoms/links/link.test.tsx @@ -0,0 +1,9 @@ +import { render, screen } from '@test-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 new file mode 100644 index 0000000..c8ba273 --- /dev/null +++ b/src/components/atoms/links/link.tsx @@ -0,0 +1,67 @@ +import NextLink from 'next/link'; +import { FC, ReactNode } from 'react'; +import styles from './link.module.scss'; + +export type LinkProps = { + /** + * The link body. + */ + children: ReactNode; + /** + * Set additional classnames to the link. + */ + className?: string; + /** + * 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. + */ +const Link: FC<LinkProps> = ({ + children, + className = '', + download = false, + external = false, + href, + lang, +}) => { + const downloadClass = download ? styles['link--download'] : ''; + + return external ? ( + <a + href={href} + hrefLang={lang} + className={`${styles.link} ${styles['link--external']} ${downloadClass} ${className}`} + > + {children} + </a> + ) : ( + <NextLink href={href}> + <a + hrefLang={lang} + className={`${styles.link} ${downloadClass} ${className}`} + > + {children} + </a> + </NextLink> + ); +}; + +export default Link; diff --git a/src/components/atoms/links/nav-link.module.scss b/src/components/atoms/links/nav-link.module.scss new file mode 100644 index 0000000..241c9c3 --- /dev/null +++ b/src/components/atoms/links/nav-link.module.scss @@ -0,0 +1,46 @@ +@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); + --icon-size: #{fun.convert-px(30)}; + + 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 new file mode 100644 index 0000000..7f7a334 --- /dev/null +++ b/src/components/atoms/links/nav-link.stories.tsx @@ -0,0 +1,55 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import 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 new file mode 100644 index 0000000..7750cee --- /dev/null +++ b/src/components/atoms/links/nav-link.test.tsx @@ -0,0 +1,12 @@ +import { render, screen } from '@test-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 new file mode 100644 index 0000000..7c6fede --- /dev/null +++ b/src/components/atoms/links/nav-link.tsx @@ -0,0 +1,36 @@ +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. + */ +const NavLink: FC<NavLinkProps> = ({ href, label, logo }) => { + return ( + <Link href={href}> + <a className={styles.link}> + {logo} + {label} + </a> + </Link> + ); +}; + +export default NavLink; diff --git a/src/components/atoms/links/sharing-link.module.scss b/src/components/atoms/links/sharing-link.module.scss new file mode 100644 index 0000000..26ca737 --- /dev/null +++ b/src/components/atoms/links/sharing-link.module.scss @@ -0,0 +1,157 @@ +@use "@styles/abstracts/functions" as fun; + +.link { + display: inline-flex; + align-items: center; + padding: var(--spacing-2xs) var(--spacing-xs); + border-radius: fun.convert-px(3); + + &:hover, + &:focus { + transform: translateX(#{fun.convert-px(-3)}) + translateY(#{fun.convert-px(-3)}); + } + + &:active { + transform: translateX(#{fun.convert-px(2)}) translateY(#{fun.convert-px(2)}); + } + + &::before { + content: ""; + display: block; + width: fun.convert-px(30); + height: fun.convert-px(30); + background-repeat: no-repeat; + filter: drop-shadow( + #{fun.convert-px(1)} #{fun.convert-px(1)} #{fun.convert-px(1)} hsl(0, 0%, 0%) + ); + } + + &--diaspora { + background: hsl(0, 0%, 13%); + box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(0, 0%, 3%); + + &:hover, + &:focus { + box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 hsl(0, 0%, 3%); + } + + &:active { + box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 hsl(0, 0%, 3%); + } + + &::before { + background-image: url(fun.encode-svg( + '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path style="fill:#ffffff;" d="M15.257 21.928l-2.33-3.255c-.622-.87-1.128-1.549-1.155-1.55-.027 0-1.007 1.317-2.317 3.115-1.248 1.713-2.28 3.115-2.292 3.115-.035 0-4.5-3.145-4.51-3.178-.006-.016 1.003-1.497 2.242-3.292 1.239-1.794 2.252-3.29 2.252-3.325 0-.056-.401-.197-3.55-1.247a1604.93 1604.93 0 01-3.593-1.2c-.033-.013.153-.635.79-2.648.46-1.446.845-2.642.857-2.656.013-.015 1.71.528 3.772 1.207 2.062.678 3.766 1.233 3.787 1.233.021 0 .045-.032.053-.07.008-.039.026-1.794.04-3.902.013-2.107.036-3.848.05-3.87.02-.03.599-.038 2.725-.038 1.485 0 2.716.01 2.735.023.023.016.064 1.175.132 3.776.112 4.273.115 4.33.183 4.33.026 0 1.66-.547 3.631-1.216 1.97-.668 3.593-1.204 3.605-1.191.04.045 1.656 5.307 1.636 5.327-.011.01-1.656.574-3.655 1.252-2.75.932-3.638 1.244-3.645 1.284-.006.029.94 1.442 2.143 3.202 1.184 1.733 2.148 3.164 2.143 3.18-.012.036-4.442 3.299-4.48 3.299-.015 0-.577-.767-1.249-1.705z"/></svg>' + )); + } + } + + &--email { + background: hsl(0, 0%, 44%); + box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(0, 0%, 34%); + + &:hover, + &:focus { + box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 hsl(0, 0%, 34%); + } + + &:active { + box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 hsl(0, 0%, 34%); + } + + &::before { + background-image: url(fun.encode-svg( + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M15.909 12.123L24 17.238V6.792zM0 6.792v10.446l8.091-5.115zM22.5 3.75h-21c-.748 0-1.343.558-1.455 1.276L12 12.904l11.955-7.877c-.112-.718-.706-1.276-1.455-1.276zm-7.965 9.279l-2.123 1.398a.75.75 0 01-.825 0l-2.122-1.4-9.417 5.957c.116.712.707 1.266 1.452 1.266h21c.746 0 1.337-.553 1.452-1.266z"/></svg>' + )); + } + } + + &--facebook { + background: hsl(214, 89%, 52%); + box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(214, 89%, 42%); + + &:hover, + &:focus { + box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 + hsl(214, 89%, 42%); + } + + &:active { + box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 + hsl(214, 89%, 42%); + } + + &::before { + background-image: url(fun.encode-svg( + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>' + )); + } + } + + &--journal-du-hacker { + background: hsl(210, 24%, 51%); + box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(210, 24%, 41%); + + &:hover, + &:focus { + box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 + hsl(210, 24%, 41%); + } + + &:active { + box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 + hsl(210, 24%, 41%); + } + + &::before { + background-image: url(fun.encode-svg( + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M17.822 23.297a6.644 6.644 0 00-.654.032c-1.104.1-2.451-.378-3.244-1.15a3.223 3.223 0 01-.52-.739c-.209-.425-.22-.489-.211-1.178a8.174 8.174 0 01.19-1.585c.243-1.151.155-1.449-.514-1.737-.4-.172-.632-.135-1 .16-.268.215-.28.463-.07 1.532.298 1.526.286 2.238-.05 2.907-.28.56-.443.703-1.287 1.133-1.005.513-1.461.638-2.332.638-.73 0-1.014-.082-1.276-.366-.134-.145-.148-.2-.085-.32.099-.184.329-.3.959-.488.277-.082.604-.236.727-.341.123-.105.329-.265.457-.354.32-.222.562-.761.563-1.254 0-.331-.188-1.034-.45-1.676-.138-.338-.38.085-.38.666 0 .434-.673 1.569-.93 1.569-.048 0-.288.101-.532.225-.43.219-.47.225-1.31.225-.815 0-.889-.011-1.235-.194-.42-.22-.902-.694-1.094-1.073a2.752 2.752 0 00-.227-.377c-.083-.102-.08-.143.018-.293.206-.314.473-.317 1.186-.011.583.25 1.22.215 1.582-.086.168-.139.325-.697.342-1.217.02-.598-.049-.66-.596-.528-.86.206-1.762-.084-2.76-.887-.916-.739-1.362-.845-2.241-.538-.262.092-.51.153-.552.137-.042-.016-.134-.136-.204-.268-.118-.218-.12-.252-.02-.403.156-.24.714-.573 1.185-.708.297-.086.588-.11 1.076-.09.655.026.687.035 1.567.458.54.259.99.43 1.127.43.27 0 1.014-.37 1.159-.577.167-.238.124-.34-.322-.776-1.19-1.16-1.943-2.608-2.24-4.31-.124-.702-.14-1.888-.035-2.483.116-.656.677-2.273.915-2.64.385-.59 1.823-1.965 2.585-2.469C9.187.905 11.43.395 13.715.785c2.457.42 4.507 1.61 5.849 3.394 1.062 1.414 1.554 2.859 1.553 4.57 0 1.778-.497 3.238-1.599 4.693a6.207 6.207 0 00-.34.476c0 .013.205.12.456.238.737.345 1.169.844 1.726 1.994.256.527.531 1.031.613 1.12.225.247.614.42 1.099.49.588.085.804.178.9.388.109.24-.111.55-.402.563-.11.005-.394.033-.63.062-.887.107-1.851-.251-2.416-.898-.17-.193-.503-.616-.74-.939-.455-.616-.818-.922-1.054-.888-.117.017-.14.066-.127.28.008.142.068.34.133.438.09.137.127.412.161 1.196.05 1.153.147 1.458.55 1.726.306.204.552.198 1.11-.025.581-.233.923-.238 1.159-.018.243.227.2.637-.11 1.026-.33.419-1.338.899-2.001.954-1.194.1-2.371-.602-2.828-1.686-.062-.147-.197-.61-.301-1.03-.12-.486-.221-.762-.28-.762-.109 0-.263.401-.27.705-.003.12-.056.417-.118.657-.328 1.282.307 2.309 1.66 2.684.657.182.808.299.808.623 0 .319-.165.494-.454.481z"/></svg>' + )); + } + } + + &--linkedin { + background: hsl(210, 90%, 40%); + box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(210, 90%, 30%); + + &:hover, + &:focus { + box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 + hsl(210, 90%, 30%); + } + + &:active { + box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 + hsl(210, 90%, 30%); + } + + &::before { + background-image: url(fun.encode-svg( + '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path style="fill:#ffffff;" d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>' + )); + } + } + + &--twitter { + background: hsl(203, 89%, 53%); + box-shadow: #{fun.convert-px(3)} #{fun.convert-px(3)} 0 0 hsl(203, 89%, 43%); + + &:hover, + &:focus { + box-shadow: #{fun.convert-px(6)} #{fun.convert-px(6)} 0 0 + hsl(203, 89%, 43%); + } + + &:active { + box-shadow: #{fun.convert-px(1)} #{fun.convert-px(1)} 0 0 + hsl(203, 89%, 43%); + } + + &::before { + background-image: url(fun.encode-svg( + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path style="fill:#ffffff;" d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>' + )); + } + } +} diff --git a/src/components/atoms/links/sharing-link.stories.tsx b/src/components/atoms/links/sharing-link.stories.tsx new file mode 100644 index 0000000..e6bd11b --- /dev/null +++ b/src/components/atoms/links/sharing-link.stories.tsx @@ -0,0 +1,98 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import SharingLinkComponent from './sharing-link'; + +/** + * SharingLink - Storybook Meta + */ +export default { + title: 'Atoms/Buttons/Sharing', + component: SharingLinkComponent, + argTypes: { + medium: { + control: { + type: 'select', + }, + description: 'The sharing medium.', + options: [ + 'diaspora', + 'email', + 'facebook', + 'journal-du-hacker', + 'linkedin', + 'twitter', + ], + type: { + name: 'string', + required: true, + }, + }, + url: { + control: { + type: 'text', + }, + description: 'The sharing url.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta<typeof SharingLinkComponent>; + +const Template: ComponentStory<typeof SharingLinkComponent> = (args) => ( + <SharingLinkComponent {...args} /> +); + +/** + * Sharing Link Stories - Diaspora + */ +export const Diaspora = Template.bind({}); +Diaspora.args = { + medium: 'diaspora', + url: '#', +}; + +/** + * Sharing Link Stories - Email + */ +export const Email = Template.bind({}); +Email.args = { + medium: 'email', + url: '#', +}; + +/** + * Sharing Link Stories - Facebook + */ +export const Facebook = Template.bind({}); +Facebook.args = { + medium: 'facebook', + url: '#', +}; + +/** + * Sharing Link Stories - Journal du Hacker + */ +export const JournalDuHacker = Template.bind({}); +JournalDuHacker.args = { + medium: 'journal-du-hacker', + url: '#', +}; + +/** + * Sharing Link Stories - LinkedIn + */ +export const LinkedIn = Template.bind({}); +LinkedIn.args = { + medium: 'linkedin', + url: '#', +}; + +/** + * Sharing Link Stories - Twitter + */ +export const Twitter = Template.bind({}); +Twitter.args = { + medium: 'twitter', + url: '#', +}; diff --git a/src/components/atoms/links/sharing-link.test.tsx b/src/components/atoms/links/sharing-link.test.tsx new file mode 100644 index 0000000..e4c849c --- /dev/null +++ b/src/components/atoms/links/sharing-link.test.tsx @@ -0,0 +1,46 @@ +import { render, screen } from '@test-utils'; +import SharingLink from './sharing-link'; + +describe('SharingLink', () => { + it('render a Diaspora sharing link', () => { + render(<SharingLink medium="diaspora" url="#" />); + expect(screen.getByRole('link', { name: 'Share on diaspora' })).toHaveClass( + 'link--diaspora' + ); + }); + + it('render an Email sharing link', () => { + render(<SharingLink medium="email" url="#" />); + expect(screen.getByRole('link', { name: 'Share on email' })).toHaveClass( + 'link--email' + ); + }); + + it('render a Facebook sharing link', () => { + render(<SharingLink medium="facebook" url="#" />); + expect(screen.getByRole('link', { name: 'Share on facebook' })).toHaveClass( + 'link--facebook' + ); + }); + + it('render a Journal du Hacker sharing link', () => { + render(<SharingLink medium="journal-du-hacker" url="#" />); + expect( + screen.getByRole('link', { name: 'Share on journal-du-hacker' }) + ).toHaveClass('link--journal-du-hacker'); + }); + + it('render a LinkedIn sharing link', () => { + render(<SharingLink medium="linkedin" url="#" />); + expect(screen.getByRole('link', { name: 'Share on linkedin' })).toHaveClass( + 'link--linkedin' + ); + }); + + it('render a Twitter sharing link', () => { + render(<SharingLink medium="twitter" url="#" />); + expect(screen.getByRole('link', { name: 'Share on twitter' })).toHaveClass( + 'link--twitter' + ); + }); +}); diff --git a/src/components/atoms/links/sharing-link.tsx b/src/components/atoms/links/sharing-link.tsx new file mode 100644 index 0000000..ca53ef9 --- /dev/null +++ b/src/components/atoms/links/sharing-link.tsx @@ -0,0 +1,48 @@ +import { FC } from 'react'; +import { useIntl } from 'react-intl'; +import styles from './sharing-link.module.scss'; + +export type SharingMedium = + | 'diaspora' + | 'email' + | 'facebook' + | 'journal-du-hacker' + | 'linkedin' + | 'twitter'; + +export type SharingLinkProps = { + /** + * The sharing medium id. + */ + medium: SharingMedium; + /** + * The sharing url. + */ + url: string; +}; + +/** + * SharingLink component + * + * Render a sharing link. + */ +const SharingLink: FC<SharingLinkProps> = ({ medium, url }) => { + const intl = useIntl(); + const text = intl.formatMessage( + { + defaultMessage: 'Share on {name}', + description: 'Sharing: share on social network text', + id: 'ureXFw', + }, + { name: medium } + ); + const mediumClass = `link--${medium}`; + + return ( + <a href={url} className={`${styles.link} ${styles[mediumClass]}`}> + <span className="screen-reader-text">{text}</span> + </a> + ); +}; + +export default SharingLink; diff --git a/src/components/atoms/links/social-link.module.scss b/src/components/atoms/links/social-link.module.scss new file mode 100644 index 0000000..02fc61c --- /dev/null +++ b/src/components/atoms/links/social-link.module.scss @@ -0,0 +1,43 @@ +@use "@styles/abstracts/functions" as fun; + +.link { + display: flex; + width: var(--link-size, #{fun.convert-px(60)}); + height: var(--link-size, #{fun.convert-px(60)}); + box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) + var(--color-shadow), + fun.convert-px(1) fun.convert-px(2) fun.convert-px(2) fun.convert-px(-1) + var(--color-shadow), + fun.convert-px(3) fun.convert-px(4) fun.convert-px(4) fun.convert-px(-3) + var(--color-shadow), + 0 0 0 0 var(--color-shadow); + transition: all 0.25s linear 0s; + + &:hover, + &:focus { + box-shadow: fun.convert-px(1) fun.convert-px(1) fun.convert-px(1) + var(--color-shadow), + fun.convert-px(1) fun.convert-px(1) fun.convert-px(2) fun.convert-px(-1) + var(--color-shadow-light), + fun.convert-px(3) fun.convert-px(3) fun.convert-px(4) fun.convert-px(-4) + var(--color-shadow-light), + fun.convert-px(6) fun.convert-px(6) fun.convert-px(10) fun.convert-px(-3) + var(--color-shadow); + transform: scale(1.15); + } + + &:focus { + outline: var(--color-primary) dashed fun.convert-px(2); + } + + &:active { + box-shadow: 0 0 0 0 var(--color-shadow); + outline: none; + transform: scale(0.9); + } +} + +.icon { + max-width: 100%; + max-height: 100%; +} diff --git a/src/components/atoms/links/social-link.stories.tsx b/src/components/atoms/links/social-link.stories.tsx new file mode 100644 index 0000000..977ae6b --- /dev/null +++ b/src/components/atoms/links/social-link.stories.tsx @@ -0,0 +1,73 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; +import SocialLink from './social-link'; + +/** + * SocialLink - Storybook Meta + */ +export default { + title: 'Atoms/Buttons/Social', + component: SocialLink, + argTypes: { + name: { + control: { + type: 'select', + }, + description: 'Social website name.', + options: ['Github', 'Gitlab', 'LinkedIn', 'Twitter'], + type: { + name: 'string', + required: true, + }, + }, + url: { + control: { + type: null, + }, + description: 'Social profile url.', + type: { + name: 'string', + required: true, + }, + }, + }, +} as ComponentMeta<typeof SocialLink>; + +const Template: ComponentStory<typeof SocialLink> = (args) => ( + <SocialLink {...args} /> +); + +/** + * Social Link Stories - Github + */ +export const Github = Template.bind({}); +Github.args = { + name: 'Github', + url: '#', +}; + +/** + * Social Link Stories - Gitlab + */ +export const Gitlab = Template.bind({}); +Gitlab.args = { + name: 'Gitlab', + url: '#', +}; + +/** + * Social Link Stories - LinkedIn + */ +export const LinkedIn = Template.bind({}); +LinkedIn.args = { + name: 'LinkedIn', + url: '#', +}; + +/** + * Social Link Stories - Twitter + */ +export const Twitter = Template.bind({}); +Twitter.args = { + name: 'Twitter', + url: '#', +}; diff --git a/src/components/atoms/links/social-link.test.tsx b/src/components/atoms/links/social-link.test.tsx new file mode 100644 index 0000000..f49fb5a --- /dev/null +++ b/src/components/atoms/links/social-link.test.tsx @@ -0,0 +1,15 @@ +import { render, screen } from '@test-utils'; +import SocialLink from './social-link'; + +/** + * Next.js mock images to use next/image component. So for now, I need to mock + * the svg files manually. + */ +jest.mock('@assets/images/social-media/github.svg', () => 'svg-file'); + +describe('SocialLink', () => { + it('render a social link', () => { + render(<SocialLink name="Github" url="#" />); + expect(screen.getByRole('link')).toHaveAccessibleName('Github'); + }); +}); diff --git a/src/components/atoms/links/social-link.tsx b/src/components/atoms/links/social-link.tsx new file mode 100644 index 0000000..464bc60 --- /dev/null +++ b/src/components/atoms/links/social-link.tsx @@ -0,0 +1,53 @@ +import GithubIcon from '@assets/images/social-media/github.svg'; +import GitlabIcon from '@assets/images/social-media/gitlab.svg'; +import LinkedInIcon from '@assets/images/social-media/linkedin.svg'; +import TwitterIcon from '@assets/images/social-media/twitter.svg'; +import { FC } from 'react'; +import styles from './social-link.module.scss'; + +export type SocialWebsite = 'Github' | 'Gitlab' | 'LinkedIn' | 'Twitter'; + +export type SocialLinkProps = { + /** + * The social website name. + */ + name: SocialWebsite; + /** + * The social profile url. + */ + url: string; +}; + +/** + * SocialLink component + * + * Render a social icon link. + */ +const SocialLink: FC<SocialLinkProps> = ({ name, url }) => { + /** + * Retrieve a social link icon by id. + * @param {string} id - The social website id. + */ + const getIcon = (id: string) => { + switch (id) { + case 'Github': + return <GithubIcon className={styles.icon} aria-hidden="true" />; + case 'Gitlab': + return <GitlabIcon className={styles.icon} aria-hidden="true" />; + case 'LinkedIn': + return <LinkedInIcon className={styles.icon} aria-hidden="true" />; + case 'Twitter': + return <TwitterIcon className={styles.icon} aria-hidden="true" />; + default: + break; + } + }; + + return ( + <a href={url} className={styles.link} aria-label={name}> + {getIcon(name)} + </a> + ); +}; + +export default SocialLink; |
