Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lauri/enhancement/363 redesign scrolltop component #389

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// import { CustomEditor } from '@/shared/ui/CustomEditor';
'use client';
import { ScrollTop } from '@/features/ScrollTop';
import {
NavMenuWithDropdowns,
NavMenuWithDropdownsProps,
Expand Down Expand Up @@ -243,6 +244,7 @@ const Page = () => {
This is the main content, adapting to both desktop and mobile devices. This is the
main content, adapting to both desktop and mobile devices
</p>
<ScrollTop />
</LayoutWithSidebars>
);
};
Expand Down
2 changes: 1 addition & 1 deletion frontend-next-migration/src/features/ScrollTop/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { ScrollTop } from './ui/ScrollTop';
export { ScrollTop } from './ui/v2/ScrollTop';
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ interface ScrollTopProps {
innerText?: string;
}

/**
* @deprecated use v2 instead
* (features/ScrollTop/ui/v2/ScrollTop)
*/
export const ScrollTop = memo(({ className = '', innerText }: ScrollTopProps) => {
const { t } = useClientTranslation('translation');

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.ScrollTop {
// _styles/themes/main.scss
--primary-color: #FFA100;
--white: #FAF9F6;
--black: #121212;
--border-desktop: 4px;
--border-mobile: 2px;
--h1-size-mobile: 32px;

color: var(--black);
border: var(--border-desktop) solid var(--black);
border-radius: var(--border-radius-custom);
background-color: var(--primary-color);
z-index: var(--navbar-z-index);
cursor: pointer;
width: 70px;
height: 70px;
padding:0 !important;
display: grid;
position: fixed;
bottom: 118px; // 32px + 70px + 16px + 70px
right: 32px;
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease-in-out, visibility 0.2s ease-in-out;
transform: rotate(90deg); // For making '<<' (\226A) point up
font-size: 70px !important;
@media (max-width:breakpoint(lg)) {
width:60px;
height:60px;
right: 16px;
bottom: 260px;
border-width: var(--border-mobile);
border-radius: calc(var(--border-radius-custom) * 1.5);
font: var(--font-xxl) !important;
}
}

.ScrollTop::before {
content:'\226A'; // Much less than -symbol ('<<')
}

.show {
opacity: 1;
visibility: visible;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import React from 'react';
import { ScrollTop } from './ScrollTop';

export default {
title: 'features/ScrollTopV2',
component: ScrollTop,
args: {
className: '',
children: 'UP',
},
} as ComponentMeta<typeof ScrollTop>;

const Template: ComponentStory<typeof ScrollTop> = (args) => {
return (
<>
<div
style={{
height: '2000px',
textAlign: 'center',
color: 'white',
fontSize: '36px',
paddingTop: '1rem',
}}
>
Scroll down to see the button
</div>
<ScrollTop {...args} />
<div
style={{
backgroundColor: '#FFA100',
color: 'white',
fontSize: '50px',
textAlign: 'center',
borderRadius: '15px',
opacity: '0.1',
position: 'fixed',
right: '32px',
bottom: '32px',
width: '70px',
height: '70px',
}}
>
&#9743;
</div>
</>
);
};

export const Default = Template.bind({});
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { render, screen, fireEvent } from '@testing-library/react';
import * as hooks from '@/shared/lib/hooks';
import * as i18n from '@/shared/i18n';
import { ScrollTop } from './ScrollTop';

// Mocking the hooks used in the ScrollTop component
jest.mock('@/shared/lib/hooks', () => ({
useCurrentYPosition: jest.fn(), // Mock the useCurrentYPosition hook
}));

jest.mock('@/shared/i18n', () => ({
useClientTranslation: jest.fn(), // Mock the useClientTranslation hook
}));

describe('ScrollTop', () => {
const mockScrollTo = jest.fn(); // Mock function to simulate scrolling
let originalScrollTo: typeof window.scrollTo; // Store original window.scrollTo function

beforeAll(() => {
// Before all tests, replace window.scrollTo with the mock function
originalScrollTo = window.scrollTo;
window.scrollTo = mockScrollTo;
});

afterAll(() => {
// Restore original window.scrollTo function after all tests
window.scrollTo = originalScrollTo;
});

beforeEach(() => {
// Clear all mocks before each test to ensure clean state
jest.clearAllMocks();
// Mock translation function to return the key as the translation
(i18n.useClientTranslation as jest.Mock).mockReturnValue({ t: (key: string) => key });
});

it('renders correctly with default props', () => {
// Mock the hook to simulate being at the top of the page
(hooks.useCurrentYPosition as jest.Mock).mockReturnValue(0);

render(<ScrollTop />); // Render the ScrollTop component

// Check if the button is in the document and has the correct text and class
const button = screen.getByTestId('scroll-to-top-btn');
expect(button).toBeInTheDocument();
expect(button).toHaveClass('ScrollTop');
});

it('shows button when scrolled down', () => {
// Mock the hook to simulate scrolling down the page
(hooks.useCurrentYPosition as jest.Mock).mockReturnValue(window.innerHeight / 4);
render(<ScrollTop />); // Render the component

// Verify that the button is visible
expect(screen.getByTestId('scroll-to-top-btn')).toHaveClass('show');
});

it('hides button when scrolled up', () => {
// Mock the hook to simulate being at the top of the page
(hooks.useCurrentYPosition as jest.Mock).mockReturnValue(0);
render(<ScrollTop />); // Render the component

// Verify that the button is hidden
expect(screen.getByTestId('scroll-to-top-btn')).not.toHaveClass('show');
});

it('scrolls to top when button is clicked', () => {
// Mock the hook to simulate scrolling down the page
(hooks.useCurrentYPosition as jest.Mock).mockReturnValue(window.innerHeight / 4);
render(<ScrollTop />); // Render the component

const button = screen.getByTestId('scroll-to-top-btn'); // Get the button element
fireEvent.click(button); // Simulate a click on the button

// Check if the mock scrollTo function was called with correct parameters
expect(mockScrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' });
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
'use client';
import { memo, useCallback, useEffect, useState } from 'react';
import { classNames } from '@/shared/lib/classNames/classNames';
import { Button, ButtonSize, ButtonTheme } from '@/shared/ui/Button/Button';
import { useCurrentYPosition } from '@/shared/lib/hooks';
import cls from './ScrollTop.module.scss';

interface ScrollTopProps {
className?: string;
}

export const ScrollTop = memo(({ className = '' }: ScrollTopProps) => {
const currentYPosition = useCurrentYPosition();

const handleButtonClick = useCallback(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, []);

const [showButton, setShowButton] = useState(false);

useEffect(() => {
if (currentYPosition > window.innerHeight / 6) {
setShowButton(true);
} else {
setShowButton(false);
}
}, [currentYPosition]);

return (
<Button
data-testid="scroll-to-top-btn"
size={ButtonSize.XXXL}
theme={ButtonTheme.OUTLINE}
className={classNames(cls.ScrollTop, { [cls.show]: showButton }, [className])}
onClick={handleButtonClick}
/>
);
});

ScrollTop.displayName = 'ScrollTop';