aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/atoms/links/social-link
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/atoms/links/social-link')
-rw-r--r--src/components/atoms/links/social-link/index.ts1
-rw-r--r--src/components/atoms/links/social-link/social-link.module.scss38
-rw-r--r--src/components/atoms/links/social-link/social-link.stories.tsx66
-rw-r--r--src/components/atoms/links/social-link/social-link.test.tsx62
-rw-r--r--src/components/atoms/links/social-link/social-link.tsx65
5 files changed, 232 insertions, 0 deletions
diff --git a/src/components/atoms/links/social-link/index.ts b/src/components/atoms/links/social-link/index.ts
new file mode 100644
index 0000000..9ac1b3b
--- /dev/null
+++ b/src/components/atoms/links/social-link/index.ts
@@ -0,0 +1 @@
+export * from './social-link';
diff --git a/src/components/atoms/links/social-link/social-link.module.scss b/src/components/atoms/links/social-link/social-link.module.scss
new file mode 100644
index 0000000..1aeab91
--- /dev/null
+++ b/src/components/atoms/links/social-link/social-link.module.scss
@@ -0,0 +1,38 @@
+@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);
+ }
+}
diff --git a/src/components/atoms/links/social-link/social-link.stories.tsx b/src/components/atoms/links/social-link/social-link.stories.tsx
new file mode 100644
index 0000000..cfb4ac7
--- /dev/null
+++ b/src/components/atoms/links/social-link/social-link.stories.tsx
@@ -0,0 +1,66 @@
+import type { ComponentMeta, ComponentStory } from '@storybook/react';
+import { SocialLink } from './social-link';
+
+/**
+ * SocialLink - Storybook Meta
+ */
+export default {
+ title: 'Atoms/Links/Social',
+ component: SocialLink,
+ argTypes: {
+ 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 = {
+ icon: 'Github',
+ label: 'Github profile',
+ url: '#',
+};
+
+/**
+ * Social Link Stories - Gitlab
+ */
+export const Gitlab = Template.bind({});
+Gitlab.args = {
+ icon: 'Gitlab',
+ label: 'Gitlab profile',
+ url: '#',
+};
+
+/**
+ * Social Link Stories - LinkedIn
+ */
+export const LinkedIn = Template.bind({});
+LinkedIn.args = {
+ icon: 'LinkedIn',
+ label: 'LinkedIn profile',
+ url: '#',
+};
+
+/**
+ * Social Link Stories - Twitter
+ */
+export const Twitter = Template.bind({});
+Twitter.args = {
+ icon: 'Twitter',
+ label: 'Twitter profile',
+ url: '#',
+};
diff --git a/src/components/atoms/links/social-link/social-link.test.tsx b/src/components/atoms/links/social-link/social-link.test.tsx
new file mode 100644
index 0000000..9129c27
--- /dev/null
+++ b/src/components/atoms/links/social-link/social-link.test.tsx
@@ -0,0 +1,62 @@
+import { describe, expect, it } from '@jest/globals';
+import { render, screen as rtlScreen } from '@testing-library/react';
+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');
+jest.mock('@assets/images/social-media/gitlab.svg', () => 'svg-file');
+jest.mock('@assets/images/social-media/linkedin.svg', () => 'svg-file');
+jest.mock('@assets/images/social-media/twitter.svg', () => 'svg-file');
+
+describe('SocialLink', () => {
+ it('render a Github social link', () => {
+ const label = 'sed ea impedit';
+ const target = '/voluptas';
+
+ render(<SocialLink icon="Github" label={label} url={target} />);
+
+ expect(rtlScreen.getByRole('link', { name: label })).toHaveAttribute(
+ 'href',
+ target
+ );
+ });
+
+ it('render a Gitlab social link', () => {
+ const label = 'rerum velit asperiores';
+ const target = '/enim';
+
+ render(<SocialLink icon="Gitlab" label={label} url={target} />);
+
+ expect(rtlScreen.getByRole('link', { name: label })).toHaveAttribute(
+ 'href',
+ target
+ );
+ });
+
+ it('render a LinkedIn social link', () => {
+ const label = 'in dolores sed';
+ const target = '/ut';
+
+ render(<SocialLink icon="LinkedIn" label={label} url={target} />);
+
+ expect(rtlScreen.getByRole('link', { name: label })).toHaveAttribute(
+ 'href',
+ target
+ );
+ });
+
+ it('render a Twitter social link', () => {
+ const label = 'voluptatibus temporibus expedita';
+ const target = '/magni';
+
+ render(<SocialLink icon="Twitter" label={label} url={target} />);
+
+ expect(rtlScreen.getByRole('link', { name: label })).toHaveAttribute(
+ 'href',
+ target
+ );
+ });
+});
diff --git a/src/components/atoms/links/social-link/social-link.tsx b/src/components/atoms/links/social-link/social-link.tsx
new file mode 100644
index 0000000..1da1e7d
--- /dev/null
+++ b/src/components/atoms/links/social-link/social-link.tsx
@@ -0,0 +1,65 @@
+import type { AnchorHTMLAttributes, FC } from 'react';
+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 styles from './social-link.module.scss';
+
+export type SocialWebsite = 'Github' | 'Gitlab' | 'LinkedIn' | 'Twitter';
+
+export type SocialLinkProps = Omit<
+ AnchorHTMLAttributes<HTMLAnchorElement>,
+ 'aria-label' | 'children' | 'href'
+> & {
+ /**
+ * The social link icon.
+ */
+ icon: SocialWebsite;
+ /**
+ * An accessible label for the link.
+ */
+ label: string;
+ /**
+ * The social profile url.
+ */
+ url: string;
+};
+
+/**
+ * SocialLink component
+ *
+ * Render a social icon link.
+ */
+export const SocialLink: FC<SocialLinkProps> = ({
+ className = '',
+ icon,
+ label,
+ url,
+ ...props
+}) => {
+ const linkClass = `${styles.link} ${className}`;
+
+ /**
+ * Retrieve a social link icon by id.
+ * @param {string} id - The social website id.
+ */
+ const getIcon = (id: string) => {
+ switch (id) {
+ case 'Github':
+ return <GithubIcon aria-hidden className={styles.icon} />;
+ case 'Gitlab':
+ return <GitlabIcon aria-hidden className={styles.icon} />;
+ case 'LinkedIn':
+ return <LinkedInIcon aria-hidden className={styles.icon} />;
+ case 'Twitter':
+ default:
+ return <TwitterIcon aria-hidden className={styles.icon} />;
+ }
+ };
+
+ return (
+ <a {...props} aria-label={label} className={linkClass} href={url}>
+ {getIcon(icon)}
+ </a>
+ );
+};