Skip to content
Snippets Groups Projects
Commit 7c4a8216 authored by Adrian Orłów's avatar Adrian Orłów
Browse files

feat: add modal

parent e82dbe4c
No related branches found
No related tags found
2 merge requests!223reset the pin numbers before search results are fetch (so the results will be...,!74feat: add modal
Pipeline #82721 passed
Showing
with 227 additions and 6 deletions
import { MODAL_INITIAL_STATE } from '@/redux/modal/modal.constants';
import { modalSelector } from '@/redux/modal/modal.selector';
import { StoreType } from '@/redux/store';
import {
InitialStoreState,
getReduxWrapperWithStore,
} from '@/utils/testing/getReduxWrapperWithStore';
import { render, screen } from '@testing-library/react';
import { Modal } from './Modal.component';
const renderComponent = (initialStore?: InitialStoreState): { store: StoreType } => {
const { Wrapper, store } = getReduxWrapperWithStore(initialStore);
return (
render(
<Wrapper>
<Modal />
</Wrapper>,
),
{
store,
}
);
};
describe('Modal - Component', () => {
describe('when modal is hidden', () => {
beforeEach(() => {
renderComponent({
modal: {
...MODAL_INITIAL_STATE,
isOpen: false,
modalTitle: 'Modal Hidden Title',
},
});
});
it('should modal have hidden class', () => {
const modalElement = screen.getByRole('modal');
expect(modalElement).toBeInTheDocument();
expect(modalElement).toHaveClass('hidden');
});
});
describe('when modal is shown', () => {
let store: StoreType;
beforeEach(() => {
const { store: newStore } = renderComponent({
modal: {
...MODAL_INITIAL_STATE,
isOpen: true,
modalTitle: 'Modal Opened Title',
},
});
store = newStore;
});
it('should modal NOT have hidden class', () => {
const modalElement = screen.getByRole('modal');
expect(modalElement).toBeInTheDocument();
expect(modalElement).not.toHaveClass('hidden');
});
it('shows modal title', () => {
expect(screen.getByText('Modal Opened Title', { exact: false })).toBeInTheDocument();
});
it('shows modal close button', () => {
expect(screen.getByLabelText('close button')).toBeInTheDocument();
});
it('closes modal on close button click', () => {
const closeButton = screen.getByLabelText('close button');
closeButton.click();
const { isOpen } = modalSelector(store.getState());
expect(isOpen).toBeFalsy();
});
});
});
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { useAppSelector } from '@/redux/hooks/useAppSelector';
import { modalSelector } from '@/redux/modal/modal.selector';
import { closeModal } from '@/redux/modal/modal.slice';
import { Icon } from '@/shared/Icon';
import { twMerge } from 'tailwind-merge';
import { MODAL_ROLE } from './Modal.constants';
import { OverviewImagesModal } from './OverviewImagesModal';
export const Modal = (): React.ReactNode => {
const dispatch = useAppDispatch();
const { isOpen, modalName, modalTitle } = useAppSelector(modalSelector);
const handleCloseModal = (): void => {
dispatch(closeModal());
};
return (
<div
className={twMerge(
'absolute left-0 top-0 z-10 h-full w-full bg-cetacean-blue/[.48]',
isOpen ? '' : 'hidden',
)}
role={MODAL_ROLE}
>
<div className="flex h-full w-full items-center justify-center">
<div className="flex flex-col overflow-hidden rounded-lg ">
<div className="flex items-center justify-between bg-white p-[24px] text-xl">
<div>{modalTitle}</div>
<button type="button" onClick={handleCloseModal} aria-label="close button">
<Icon name="close" className="fill-font-500" />
</button>
</div>
<div>{isOpen && modalName === 'overview-images' && <OverviewImagesModal />}</div>
</div>
</div>
</div>
);
};
export const MODAL_ROLE = 'modal';
import * as React from 'react';
export const OverviewImagesModal: React.FC = () => {
return <div className="h-[200px] w-[500px] bg-white " />;
};
export { OverviewImagesModal } from './OverviewImagesModal.component';
export { Modal } from './Modal.component';
import { Drawer } from '@/components/Map/Drawer'; import { Drawer } from '@/components/Map/Drawer';
import { MapViewer } from './MapViewer/MapViewer.component';
import { MapAdditionalOptions } from './MapAdditionalOptions'; import { MapAdditionalOptions } from './MapAdditionalOptions';
import { MapViewer } from './MapViewer/MapViewer.component';
export const Map = (): JSX.Element => ( export const Map = (): JSX.Element => (
<div className="relative z-0 h-screen w-full bg-black" data-testid="map-container"> <div className="relative z-0 h-screen w-full bg-black" data-testid="map-container">
......
import { useAppDispatch } from '@/redux/hooks/useAppDispatch';
import { openOverviewImagesModal } from '@/redux/modal/modal.slice';
import { Button } from '@/shared/Button';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import { BackgroundSelector } from './BackgroundsSelector'; import { BackgroundSelector } from './BackgroundsSelector';
// top-[calc(64px+40px+24px)] -> TOP_BAR_HEIGHT+MAP_NAVIGATION_HEIGHT+DISTANCE_FROM_MAP_NAVIGATION // top-[calc(64px+40px+24px)] -> TOP_BAR_HEIGHT+MAP_NAVIGATION_HEIGHT+DISTANCE_FROM_MAP_NAVIGATION
export const MapAdditionalOptions = (): JSX.Element => ( export const MapAdditionalOptions = (): JSX.Element => {
<div className={twMerge('absolute right-6 top-[calc(64px+40px+24px)] z-10')}> const dispatch = useAppDispatch();
<BackgroundSelector />
</div> const handleBrowseOverviewImagesClick = (): void => {
); dispatch(openOverviewImagesModal());
};
return (
<div className={twMerge('absolute right-6 top-[calc(64px+40px+24px)] z-10 flex')}>
<Button className="mr-4" onClick={handleBrowseOverviewImagesClick}>
Browse overview images
</Button>
<BackgroundSelector />
</div>
);
};
...@@ -4,6 +4,7 @@ import { manrope } from '@/constants/font'; ...@@ -4,6 +4,7 @@ import { manrope } from '@/constants/font';
import { useReduxBusQueryManager } from '@/utils/query-manager/useReduxBusQueryManager'; import { useReduxBusQueryManager } from '@/utils/query-manager/useReduxBusQueryManager';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
import { useInitializeStore } from '../../utils/initialize/useInitializeStore'; import { useInitializeStore } from '../../utils/initialize/useInitializeStore';
import { Modal } from '../FunctionalArea/Modal';
export const MinervaSPA = (): JSX.Element => { export const MinervaSPA = (): JSX.Element => {
useInitializeStore(); useInitializeStore();
...@@ -13,6 +14,7 @@ export const MinervaSPA = (): JSX.Element => { ...@@ -13,6 +14,7 @@ export const MinervaSPA = (): JSX.Element => {
<div className={twMerge('relative', manrope.variable)}> <div className={twMerge('relative', manrope.variable)}>
<FunctionalArea /> <FunctionalArea />
<Map /> <Map />
<Modal />
</div> </div>
); );
}; };
import { ModalState } from './modal.types';
export const MODAL_INITIAL_STATE: ModalState = {
isOpen: false,
modalName: 'none',
modalTitle: '',
};
import { ModalState } from './modal.types';
export const MODAL_INITIAL_STATE_MOCK: ModalState = {
isOpen: false,
modalName: 'none',
modalTitle: '',
};
import { ModalName } from '@/types/modal';
import { PayloadAction } from '@reduxjs/toolkit';
import { ModalState } from './modal.types';
export const openModalReducer = (state: ModalState, action: PayloadAction<ModalName>): void => {
state.isOpen = true;
state.modalName = action.payload;
};
export const closeModalReducer = (state: ModalState): void => {
state.isOpen = false;
state.modalName = 'none';
};
export const openOverviewImagesModalReducer = (state: ModalState): void => {
state.isOpen = true;
state.modalName = 'overview-images';
state.modalTitle = 'Overview images';
};
import { createSelector } from '@reduxjs/toolkit';
import { rootSelector } from '../root/root.selectors';
export const modalSelector = createSelector(rootSelector, state => state.modal);
export const isModalOpenSelector = createSelector(modalSelector, state => state.isOpen);
import { createSlice } from '@reduxjs/toolkit';
import { MODAL_INITIAL_STATE } from './modal.constants';
import {
closeModalReducer,
openModalReducer,
openOverviewImagesModalReducer,
} from './modal.reducers';
const modalSlice = createSlice({
name: 'modal',
initialState: MODAL_INITIAL_STATE,
reducers: {
openModal: openModalReducer,
closeModal: closeModalReducer,
openOverviewImagesModal: openOverviewImagesModalReducer,
},
});
export const { openModal, closeModal, openOverviewImagesModal } = modalSlice.actions;
export default modalSlice.reducer;
import { ModalName } from '@/types/modal';
export interface ModalState {
isOpen: boolean;
modalName: ModalName;
modalTitle: string;
}
...@@ -4,6 +4,7 @@ import { CHEMICALS_INITIAL_STATE_MOCK } from '../chemicals/chemicals.mock'; ...@@ -4,6 +4,7 @@ import { CHEMICALS_INITIAL_STATE_MOCK } from '../chemicals/chemicals.mock';
import { initialStateFixture as drawerInitialStateMock } from '../drawer/drawerFixture'; import { initialStateFixture as drawerInitialStateMock } from '../drawer/drawerFixture';
import { DRUGS_INITIAL_STATE_MOCK } from '../drugs/drugs.mock'; import { DRUGS_INITIAL_STATE_MOCK } from '../drugs/drugs.mock';
import { initialMapStateFixture } from '../map/map.fixtures'; import { initialMapStateFixture } from '../map/map.fixtures';
import { MODAL_INITIAL_STATE_MOCK } from '../modal/modal.mock';
import { MODELS_INITIAL_STATE_MOCK } from '../models/models.mock'; import { MODELS_INITIAL_STATE_MOCK } from '../models/models.mock';
import { OVERLAYS_INITIAL_STATE_MOCK } from '../overlays/overlays.mock'; import { OVERLAYS_INITIAL_STATE_MOCK } from '../overlays/overlays.mock';
import { PROJECT_STATE_INITIAL_MOCK } from '../project/project.mock'; import { PROJECT_STATE_INITIAL_MOCK } from '../project/project.mock';
...@@ -23,4 +24,5 @@ export const INITIAL_STORE_STATE_MOCK: RootState = { ...@@ -23,4 +24,5 @@ export const INITIAL_STORE_STATE_MOCK: RootState = {
map: initialMapStateFixture, map: initialMapStateFixture,
overlays: OVERLAYS_INITIAL_STATE_MOCK, overlays: OVERLAYS_INITIAL_STATE_MOCK,
reactions: REACTIONS_STATE_INITIAL_MOCK, reactions: REACTIONS_STATE_INITIAL_MOCK,
modal: MODAL_INITIAL_STATE_MOCK,
}; };
...@@ -4,6 +4,7 @@ import chemicalsReducer from '@/redux/chemicals/chemicals.slice'; ...@@ -4,6 +4,7 @@ import chemicalsReducer from '@/redux/chemicals/chemicals.slice';
import drawerReducer from '@/redux/drawer/drawer.slice'; import drawerReducer from '@/redux/drawer/drawer.slice';
import drugsReducer from '@/redux/drugs/drugs.slice'; import drugsReducer from '@/redux/drugs/drugs.slice';
import mapReducer from '@/redux/map/map.slice'; import mapReducer from '@/redux/map/map.slice';
import modalReducer from '@/redux/modal/modal.slice';
import modelsReducer from '@/redux/models/models.slice'; import modelsReducer from '@/redux/models/models.slice';
import overlaysReducer from '@/redux/overlays/overlays.slice'; import overlaysReducer from '@/redux/overlays/overlays.slice';
import projectReducer from '@/redux/project/project.slice'; import projectReducer from '@/redux/project/project.slice';
...@@ -25,6 +26,7 @@ export const reducers = { ...@@ -25,6 +26,7 @@ export const reducers = {
chemicals: chemicalsReducer, chemicals: chemicalsReducer,
bioEntity: bioEntityReducer, bioEntity: bioEntityReducer,
drawer: drawerReducer, drawer: drawerReducer,
modal: modalReducer,
map: mapReducer, map: mapReducer,
backgrounds: backgroundsReducer, backgrounds: backgroundsReducer,
overlays: overlaysReducer, overlays: overlaysReducer,
......
export type ModalName = 'none' | 'overview-images';
...@@ -28,6 +28,7 @@ const config: Config = { ...@@ -28,6 +28,7 @@ const config: Config = {
orange: '#f48c40', orange: '#f48c40',
purple: '#6400e3', purple: '#6400e3',
pink: '#f1009f', pink: '#f1009f',
'cetacean-blue': '#070130',
}, },
height: { height: {
'calc-drawer': 'calc(100% - 104px)', 'calc-drawer': 'calc(100% - 104px)',
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment