aboutsummaryrefslogtreecommitdiffstats
path: root/src/components/molecules
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2022-04-01 19:03:42 +0200
committerArmand Philippot <git@armandphilippot.com>2022-04-01 22:58:18 +0200
commitd177e0c7c61845b516d4a361a21739bb6486b9b5 (patch)
tree3905aab133889d5d59f8116fbcf301930b858887 /src/components/molecules
parent163f9dc0fe436b708de4e59999e87005c6685a0f (diff)
chore: add a back to top component
Diffstat (limited to 'src/components/molecules')
-rw-r--r--src/components/molecules/buttons/back-to-top.module.scss49
-rw-r--r--src/components/molecules/buttons/back-to-top.stories.tsx41
-rw-r--r--src/components/molecules/buttons/back-to-top.test.tsx10
-rw-r--r--src/components/molecules/buttons/back-to-top.tsx40
4 files changed, 140 insertions, 0 deletions
diff --git a/src/components/molecules/buttons/back-to-top.module.scss b/src/components/molecules/buttons/back-to-top.module.scss
new file mode 100644
index 0000000..1abf1f6
--- /dev/null
+++ b/src/components/molecules/buttons/back-to-top.module.scss
@@ -0,0 +1,49 @@
+@use "@styles/abstracts/functions" as fun;
+
+.wrapper {
+ a {
+ svg {
+ width: 100%;
+ }
+
+ :global {
+ .arrow-head {
+ transform: translateY(30%) scale(1.1);
+ transition: all 0.45s ease-in-out 0s;
+ }
+
+ .arrow-bar {
+ opacity: 0;
+ transform: translateY(30%) scaleY(0);
+ transition: transform 0.4s ease-in-out 0s, opacity 0.1s linear 0.4s;
+ }
+ }
+
+ &:hover,
+ &:focus {
+ :global {
+ .arrow-head {
+ transform: translateY(0) scale(1);
+ }
+
+ .arrow-bar {
+ opacity: 1;
+ transform: translateY(0) scaleY(1);
+ transition: transform 0.45s ease-in-out 0s;
+ }
+ }
+
+ svg {
+ :global {
+ animation: pulse 1.2s ease-in-out 0.6s infinite;
+ }
+ }
+ }
+
+ &:active {
+ svg {
+ animation-play-state: paused;
+ }
+ }
+ }
+}
diff --git a/src/components/molecules/buttons/back-to-top.stories.tsx b/src/components/molecules/buttons/back-to-top.stories.tsx
new file mode 100644
index 0000000..f1a36e5
--- /dev/null
+++ b/src/components/molecules/buttons/back-to-top.stories.tsx
@@ -0,0 +1,41 @@
+import { ComponentMeta, ComponentStory } from '@storybook/react';
+import { IntlProvider } from 'react-intl';
+import BackToTopComponent from './back-to-top';
+
+export default {
+ title: 'Molecules/Buttons',
+ component: BackToTopComponent,
+ argTypes: {
+ additionalClasses: {
+ control: {
+ type: 'text',
+ },
+ description: 'Add additional classes to the button wrapper.',
+ type: {
+ name: 'string',
+ required: false,
+ },
+ },
+ target: {
+ control: {
+ type: 'text',
+ },
+ description: 'An element id (without hashtag) to use as anchor.',
+ type: {
+ name: 'string',
+ required: true,
+ },
+ },
+ },
+} as ComponentMeta<typeof BackToTopComponent>;
+
+const Template: ComponentStory<typeof BackToTopComponent> = (args) => (
+ <IntlProvider locale="en">
+ <BackToTopComponent {...args} />
+ </IntlProvider>
+);
+
+export const BackToTop = Template.bind({});
+BackToTop.args = {
+ target: 'top',
+};
diff --git a/src/components/molecules/buttons/back-to-top.test.tsx b/src/components/molecules/buttons/back-to-top.test.tsx
new file mode 100644
index 0000000..2b3a0a9
--- /dev/null
+++ b/src/components/molecules/buttons/back-to-top.test.tsx
@@ -0,0 +1,10 @@
+import { render, screen } from '@test-utils';
+import BackToTop from './back-to-top';
+
+describe('BackToTop', () => {
+ it('renders a BackToTop link', () => {
+ render(<BackToTop target="top" />);
+ expect(screen.getByRole('link')).toHaveAccessibleName('Back to top');
+ expect(screen.getByRole('link')).toHaveAttribute('href', '/#top');
+ });
+});
diff --git a/src/components/molecules/buttons/back-to-top.tsx b/src/components/molecules/buttons/back-to-top.tsx
new file mode 100644
index 0000000..f25d3e9
--- /dev/null
+++ b/src/components/molecules/buttons/back-to-top.tsx
@@ -0,0 +1,40 @@
+import ButtonLink from '@components/atoms/buttons/button-link';
+import Arrow from '@components/atoms/icons/arrow';
+import { FC } from 'react';
+import { useIntl } from 'react-intl';
+import styles from './back-to-top.module.scss';
+
+type BackToTopProps = {
+ /**
+ * Add additional classes to the button wrapper.
+ */
+ additionalClasses?: string;
+ /**
+ * An element id (without hashtag) to use as anchor.
+ */
+ target: string;
+};
+
+/**
+ * BackToTop component
+ *
+ * Render a back to top link.
+ */
+const BackToTop: FC<BackToTopProps> = ({ additionalClasses, target }) => {
+ const intl = useIntl();
+ const linkName = intl.formatMessage({
+ defaultMessage: 'Back to top',
+ description: 'BackToTop: link text',
+ id: 'm+SUSR',
+ });
+
+ return (
+ <div className={`${styles.wrapper} ${additionalClasses}`}>
+ <ButtonLink shape="square" target={`#${target}`} aria-label={linkName}>
+ <Arrow direction="top" />
+ </ButtonLink>
+ </div>
+ );
+};
+
+export default BackToTop;