diff --git a/Group.svg b/Group.svg new file mode 100644 index 0000000000000000000000000000000000000000..6c9f4a0e0768cd405fe0060c439a9f15a9766563 --- /dev/null +++ b/Group.svg @@ -0,0 +1,4 @@ +<svg width="18" height="24" viewBox="0 0 18 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M9 0C4.575 0 0 3.375 0 9C0 14.325 8.1 22.65 8.475 23.025C8.625 23.175 8.775 23.25 9 23.25C9.225 23.25 9.375 23.175 9.525 23.025C9.9 22.65 18 14.4 18 9C18 3.375 13.425 0 9 0ZM9 12C7.35 12 6 10.65 6 9C6 7.35 7.35 6 9 6C10.65 6 12 7.35 12 9C12 10.65 10.65 12 9 12Z" fill="#E17221"/> +<circle cx="9.0002" cy="8.99922" r="4.8" fill="#E17221"/> +</svg> diff --git a/src/assets/vectors/icons/Group.svg b/src/assets/vectors/icons/Group.svg new file mode 100644 index 0000000000000000000000000000000000000000..6d6e49d3b0756b321ce2ee1e317b6452f6cafc59 --- /dev/null +++ b/src/assets/vectors/icons/Group.svg @@ -0,0 +1,5 @@ +<svg width="18" height="24" viewBox="0 0 18 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M9 0C4.575 0 0 3.375 0 9C0 14.325 8.1 22.65 8.475 23.025C8.625 23.175 8.775 23.25 9 23.25C9.225 23.25 9.375 23.175 9.525 23.025C9.9 22.65 18 14.4 18 9C18 3.375 13.425 0 9 0ZM9 12C7.35 12 6 10.65 6 9C6 7.35 7.35 6 9 6C10.65 6 12 7.35 12 9C12 10.65 10.65 12 9 12Z" fill="#E17221"/> +<circle cx="9.0002" cy="8.99922" r="4.8" fill="#E17221"/> +<path d="M8.846 15V7.344L7.22 8.322V7.332L8.846 6.36H9.74V15H8.846Z" fill="white"/> +</svg> diff --git a/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/ChemicalsAccordion.component.test.tsx b/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/ChemicalsAccordion.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c8a4826a1446507681ca673059c8e1f5c24d2b6b --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/ChemicalsAccordion.component.test.tsx @@ -0,0 +1,41 @@ +import { render, screen } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { chemicalsFixture } from '@/models/fixtures/chemicalsFixture'; +import { Accordion } from '@/shared/Accordion'; +import { ChemicalsAccordion } from './ChemicalsAccordion.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <Accordion> + <ChemicalsAccordion /> + </Accordion> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('DrugsAccordion - component', () => { + it('should display drugs number after succesfull chemicals search', () => { + renderComponent({ + chemicals: { data: chemicalsFixture, loading: 'succeeded', error: { name: '', message: '' } }, + }); + expect(screen.getByText('Chemicals (2)')).toBeInTheDocument(); + }); + it('should display loading indicator while waiting for chemicals search response', () => { + renderComponent({ + chemicals: { data: [], loading: 'pending', error: { name: '', message: '' } }, + }); + expect(screen.getByText('Chemicals (Loading...)')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/ChemicalsAccordion.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/ChemicalsAccordion.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..46ee6f7865e555e2d825ba238941400d09887788 --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/ChemicalsAccordion.component.tsx @@ -0,0 +1,23 @@ +import { + numberOfChemicalsSelector, + loadingChemicalsStatusSelector, +} from '@/redux/chemicals/chemicals.selectors'; +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { AccordionItem, AccordionItemHeading, AccordionItemButton } from '@/shared/Accordion'; + +export const ChemicalsAccordion = (): JSX.Element => { + const chemicalsNumber = useAppSelector(numberOfChemicalsSelector); + const drugsState = useAppSelector(loadingChemicalsStatusSelector); + + return ( + <AccordionItem> + <AccordionItemHeading> + <AccordionItemButton variant="non-expandable"> + Chemicals + {drugsState === 'pending' && ' (Loading...)'} + {drugsState === 'succeeded' && ` (${chemicalsNumber})`} + </AccordionItemButton> + </AccordionItemHeading> + </AccordionItem> + ); +}; diff --git a/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/index.ts b/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..9fc44924d2be7c38dcb00d5a0a5289b7bf237a1a --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion/index.ts @@ -0,0 +1 @@ +export { ChemicalsAccordion } from './ChemicalsAccordion.component'; diff --git a/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/MirnaAccordion.component.test.tsx b/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/MirnaAccordion.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..b3b10bf68e3f0478792e5e0add36aa5559fb70b6 --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/MirnaAccordion.component.test.tsx @@ -0,0 +1,41 @@ +import { render, screen } from '@testing-library/react'; +import { StoreType } from '@/redux/store'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { mirnasFixture } from '@/models/fixtures/mirnasFixture'; +import { Accordion } from '@/shared/Accordion'; +import { MirnaAccordion } from './MirnaAccordion.component'; + +const renderComponent = (initialStoreState: InitialStoreState = {}): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <Accordion> + <MirnaAccordion /> + </Accordion> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('DrugsAccordion - component', () => { + it('should display drugs number after succesfull chemicals search', () => { + renderComponent({ + mirnas: { data: mirnasFixture, loading: 'succeeded', error: { name: '', message: '' } }, + }); + expect(screen.getByText('MiRNA (2)')).toBeInTheDocument(); + }); + it('should display loading indicator while waiting for chemicals search response', () => { + renderComponent({ + mirnas: { data: [], loading: 'pending', error: { name: '', message: '' } }, + }); + expect(screen.getByText('MiRNA (Loading...)')).toBeInTheDocument(); + }); +}); diff --git a/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/MirnaAccordion.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/MirnaAccordion.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e23bf05659a15086e88b15bdeeb3e3e83b0bcd6e --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/MirnaAccordion.component.tsx @@ -0,0 +1,23 @@ +import { useAppSelector } from '@/redux/hooks/useAppSelector'; +import { + numberOfMirnasSelector, + loadingMirnasStatusSelector, +} from '@/redux/mirnas/mirnas.selectors'; +import { AccordionItem, AccordionItemHeading, AccordionItemButton } from '@/shared/Accordion'; + +export const MirnaAccordion = (): JSX.Element => { + const mirnaNumber = useAppSelector(numberOfMirnasSelector); + const mirnaState = useAppSelector(loadingMirnasStatusSelector); + + return ( + <AccordionItem> + <AccordionItemHeading> + <AccordionItemButton variant="non-expandable"> + MiRNA + {mirnaState === 'pending' && ' (Loading...)'} + {mirnaState === 'succeeded' && ` (${mirnaNumber})`} + </AccordionItemButton> + </AccordionItemHeading> + </AccordionItem> + ); +}; diff --git a/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/index.ts b/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..69f2736c465dba1da3c0737fa07a3a8ac5268b8c --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/MirnaAccordion/index.ts @@ -0,0 +1 @@ +export { MirnaAccordion } from './MirnaAccordion.component'; diff --git a/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsList.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsList.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2678ebb1ba45b1e314797e321038f52d1b031ce5 --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsList.component.tsx @@ -0,0 +1,14 @@ +import { PinItem } from './PinsList.types'; +import { PinsListItem } from './PinsListItem'; + +interface PinsListProps { + pinsList: PinItem[]; +} + +export const PinsList = ({ pinsList }: PinsListProps): JSX.Element => ( + <ul className="px-6 py-2"> + {pinsList.map(pin => ( + <PinsListItem key={pin.name} name={pin.name} /> + ))} + </ul> +); diff --git a/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsList.types.tsx b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsList.types.tsx new file mode 100644 index 0000000000000000000000000000000000000000..ce02ca44317ef35bcaa08a1f816c2b4157a4d91b --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsList.types.tsx @@ -0,0 +1,3 @@ +export type PinItem = { + name: string; +}; diff --git a/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsListItem/PinsListItem.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsListItem/PinsListItem.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..c6eba919fdc7c3dc7375bc62874cea9da02d8668 --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsListItem/PinsListItem.component.tsx @@ -0,0 +1,13 @@ +import { Icon } from '@/shared/Icon'; + +interface PinsListItemProps { + name: string; +} + +export const PinsListItem = ({ name }: PinsListItemProps): JSX.Element => ( + <div className="flex flex-row justify-between pt-4"> + <Icon name="pin" className="mr-2 shrink-0" /> + <p className="w-full text-left">{name}</p> + <Icon name="chevron-right" className="h-6 w-6 shrink-0" /> + </div> +); diff --git a/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsListItem/index.ts b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsListItem/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..89b9aebca900ed5e4282564c6b0221ff861325d5 --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/PinsListItem/index.ts @@ -0,0 +1 @@ +export { PinsListItem } from './PinsListItem.component'; diff --git a/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/index.ts b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..c9d388b05a673ede7c8f42b9611fabba0eae00d7 --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/Results/PinsList/index.ts @@ -0,0 +1 @@ +export { PinsList } from './PinsList.component'; diff --git a/src/components/Map/Drawer/SearchDrawerContent/Results/Results.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/Results/Results.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..89b9cd601d70e334669de335f5abb0acf793976a --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/Results/Results.component.tsx @@ -0,0 +1,27 @@ +import { DrawerHeadingBackwardButton } from '@/shared/DrawerHeadingBackwardButton'; +import { PinsList } from './PinsList'; +import { PinItem } from './PinsList/PinsList.types'; + +const PINS_LIST: PinItem[] = [ + { name: 'Glyceraldehyde-3-phosphate dehydrogenase' }, + { name: 'D-3-phosphoglycerate dehydrogenase' }, + { name: 'Glutathione reductase, mitochondrial' }, + { name: 'NADH dehydrogenase [ubiquinone] iron-sulfur protein 8, mitochondrial' }, +]; + +export const Results = (): JSX.Element => { + const drugName = 'Aspirin'; + + const navigateToGroupedSearchResults = (): void => {}; + + return ( + <div> + <DrawerHeadingBackwardButton + title="Drugs" + value={drugName} + backwardFunction={navigateToGroupedSearchResults} + /> + <PinsList pinsList={PINS_LIST} /> + </div> + ); +}; diff --git a/src/components/Map/Drawer/SearchDrawerContent/Results/index.ts b/src/components/Map/Drawer/SearchDrawerContent/Results/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..0b2da8f8d99a9b80340d6ba81a92990adb10029d --- /dev/null +++ b/src/components/Map/Drawer/SearchDrawerContent/Results/index.ts @@ -0,0 +1 @@ +export { Results } from './Results.component'; diff --git a/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx b/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx index ea53111ce0ea851cfee87d48aeb9fbeb075ed64b..7f0cc18beaeafb98364d31dedab045ab00c4d7d1 100644 --- a/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx +++ b/src/components/Map/Drawer/SearchDrawerContent/SearchDrawerContent.component.tsx @@ -1,5 +1,7 @@ import { BioEntitiesAccordion } from '@/components/Map/Drawer/SearchDrawerContent/BioEntitiesAccordion'; import { DrugsAccordion } from '@/components/Map/Drawer/SearchDrawerContent/DrugsAccordion'; +import { ChemicalsAccordion } from '@/components/Map/Drawer/SearchDrawerContent/ChemicalsAccordion'; +import { MirnaAccordion } from '@/components/Map/Drawer/SearchDrawerContent/MirnaAccordion'; import { closeDrawer } from '@/redux/drawer/drawer.slice'; import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; import { IconButton } from '@/shared/IconButton'; @@ -33,6 +35,8 @@ export const SearchDrawerContent = (): JSX.Element => { <Accordion allowZeroExpanded> <BioEntitiesAccordion /> <DrugsAccordion /> + <ChemicalsAccordion /> + <MirnaAccordion /> </Accordion> </div> </div> diff --git a/src/constants/common.ts b/src/constants/common.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee434dc7c1ebfa6b43b4f2b6004ef5d042a41861 --- /dev/null +++ b/src/constants/common.ts @@ -0,0 +1 @@ +export const SIZE_OF_EMPTY_ARRAY = 0; diff --git a/src/redux/chemicals/chemicals.selectors.ts b/src/redux/chemicals/chemicals.selectors.ts index b6c7cb1ccdcc7bbacce65c6f539d5499ab66e8a0..0fef7a3275e97b1a501223003e06e4cfe05f3f23 100644 --- a/src/redux/chemicals/chemicals.selectors.ts +++ b/src/redux/chemicals/chemicals.selectors.ts @@ -1,3 +1,4 @@ +import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { rootSelector } from '@/redux/root/root.selectors'; import { createSelector } from '@reduxjs/toolkit'; @@ -7,3 +8,7 @@ export const loadingChemicalsStatusSelector = createSelector( chemicalsSelector, state => state.loading, ); + +export const numberOfChemicalsSelector = createSelector(chemicalsSelector, state => + state.data ? state.data.length : SIZE_OF_EMPTY_ARRAY, +); diff --git a/src/redux/drugs/drugs.selectors.ts b/src/redux/drugs/drugs.selectors.ts index cca30ff272f61d3ae75d24ed4a21786ffe37e28b..7a1d3eacacbf0fee4cd7093f4c0255f4c910af51 100644 --- a/src/redux/drugs/drugs.selectors.ts +++ b/src/redux/drugs/drugs.selectors.ts @@ -1,8 +1,7 @@ +import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { rootSelector } from '@/redux/root/root.selectors'; import { createSelector } from '@reduxjs/toolkit'; -const SIZE_OF_EMPTY_ARRAY = 0; - export const drugsSelector = createSelector(rootSelector, state => state.drugs); export const loadingDrugsStatusSelector = createSelector(drugsSelector, state => state.loading); diff --git a/src/redux/mirnas/mirnas.selectors.ts b/src/redux/mirnas/mirnas.selectors.ts index 5344f0370c031a7a21d9e9b673d7a7d95f1d1208..51ac4dea078d807985a399bede164e6782e7ae9b 100644 --- a/src/redux/mirnas/mirnas.selectors.ts +++ b/src/redux/mirnas/mirnas.selectors.ts @@ -1,6 +1,10 @@ +import { SIZE_OF_EMPTY_ARRAY } from '@/constants/common'; import { rootSelector } from '@/redux/root/root.selectors'; import { createSelector } from '@reduxjs/toolkit'; export const mirnasSelector = createSelector(rootSelector, state => state.mirnas); export const loadingMirnasStatusSelector = createSelector(mirnasSelector, state => state.loading); +export const numberOfMirnasSelector = createSelector(mirnasSelector, state => + state.data ? state.data.length : SIZE_OF_EMPTY_ARRAY, +); diff --git a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx index dffa5b9f6dc8ab7b44c80fd2ec1f350977327357..09603e0609e575f13e43dd5d4facdd18eb6763cf 100644 --- a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx +++ b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.component.tsx @@ -1,23 +1,13 @@ -/* eslint-disable react/no-multi-comp */ -import { Icon } from '@/shared/Icon'; import { AccordionItemButton as AIB } from 'react-accessible-accordion'; import './AccordionItemButton.style.css'; +import { Variant } from './AccordionItemButton.types'; +import { getIcon } from './AccordionItemButton.utils'; -type Variant = 'expandable' | 'non-expandable'; interface AccordionItemButtonProps { children: React.ReactNode; variant?: Variant; } -const getIcon = (variant: Variant): JSX.Element => { - const variantsIcons: Record<Variant, JSX.Element> = { - expandable: <Icon name="chevron-down" className="arrow-button h-6 w-6 fill-font-500" />, - 'non-expandable': <Icon name="chevron-right" className="h-6 w-6 fill-font-500" />, - }; - - return variantsIcons[variant]; -}; - export const AccordionItemButton = ({ children, variant = 'expandable', @@ -27,7 +17,6 @@ export const AccordionItemButton = ({ return ( <AIB className="accordion-button flex flex-row flex-nowrap justify-between"> {children} - {/* <Icon name="chevron-down" className="arrow-button h-6 w-6 fill-font-500" /> */} {ButtonIcon} </AIB> ); diff --git a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.types.ts b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.types.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae2054d23759807ce59d282febcc2c5a091ef045 --- /dev/null +++ b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.types.ts @@ -0,0 +1 @@ +export type Variant = 'expandable' | 'non-expandable'; diff --git a/src/shared/Accordion/AccordionItemButton/AccordionItemButton.utils.tsx b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.utils.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2d597a92b01655e0b73d72516d30717caa3c75c1 --- /dev/null +++ b/src/shared/Accordion/AccordionItemButton/AccordionItemButton.utils.tsx @@ -0,0 +1,11 @@ +import { Icon } from '@/shared/Icon'; +import { Variant } from './AccordionItemButton.types'; + +export const getIcon = (variant: Variant): JSX.Element => { + const variantsIcons: Record<Variant, JSX.Element> = { + expandable: <Icon name="chevron-down" className="arrow-button h-6 w-6 fill-font-500" />, + 'non-expandable': <Icon name="chevron-right" className="h-6 w-6 fill-font-500" />, + }; + + return variantsIcons[variant]; +}; diff --git a/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.component.test.tsx b/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.component.test.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e35e20491cc5d1f1b2e16ed4bd12b0866ad41df0 --- /dev/null +++ b/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.component.test.tsx @@ -0,0 +1,72 @@ +import { StoreType } from '@/redux/store'; +import { + InitialStoreState, + getReduxWrapperWithStore, +} from '@/utils/testing/getReduxWrapperWithStore'; +import { render, screen } from '@testing-library/react'; +import { DrawerHeadingBackwardButton } from './DrawerHeadingBackwardButton.component'; + +const backwardFunction = jest.fn(); + +const renderComponent = ( + title: string, + value: string, + initialStoreState: InitialStoreState = {}, +): { store: StoreType } => { + const { Wrapper, store } = getReduxWrapperWithStore(initialStoreState); + + return ( + render( + <Wrapper> + <DrawerHeadingBackwardButton + title={title} + value={value} + backwardFunction={backwardFunction} + /> + </Wrapper>, + ), + { + store, + } + ); +}; + +describe('DrawerHeadingBackwardButton - component', () => { + beforeEach(() => { + backwardFunction.mockReset(); + }); + + it('should render passed values', () => { + renderComponent('Title', 'value'); + + expect(screen.getByRole('back-button')).toBeInTheDocument(); + expect(screen.getByText('Title:')).toBeInTheDocument(); + expect(screen.getByText('value')).toBeInTheDocument(); + expect(screen.getByRole('close-drawer-button')).toBeInTheDocument(); + }); + + it('should call backward function on back button click', () => { + renderComponent('Title', 'value'); + + const backButton = screen.getByRole('back-button'); + backButton.click(); + + expect(backwardFunction).toBeCalled(); + }); + + it('should call class drawer on close button click', () => { + const { store } = renderComponent('Title', 'value', { + drawer: { + isOpen: true, + drawerName: 'search', + searchDrawerState: { currentStep: 0, selectedValue: { name: '', valueType: 'none' } }, + }, + }); + expect(store.getState().drawer.isOpen).toBe(true); + + const closeButton = screen.getByRole('close-drawer-button'); + closeButton.click(); + + expect(store.getState().drawer.isOpen).toBe(false); + }); +}); diff --git a/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.component.tsx b/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.component.tsx new file mode 100644 index 0000000000000000000000000000000000000000..4e39e76ac513958f91f90b29693bc88eb1f50d9e --- /dev/null +++ b/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.component.tsx @@ -0,0 +1,51 @@ +import { useAppDispatch } from '@/redux/hooks/useAppDispatch'; +import { closeDrawer } from '@/redux/drawer/drawer.slice'; +import { IconButton } from '@/shared/IconButton'; +import { BACK_BUTTON_ROLE, CLOSE_BUTTON_ROLE } from './DrawerHeadingBackwardButton.constants'; + +export interface DrawerHeadingBackwardButtonProps { + title: string; + value: string; + backwardFunction: () => void; +} + +export const DrawerHeadingBackwardButton = ({ + backwardFunction, + title, + value, +}: DrawerHeadingBackwardButtonProps): JSX.Element => { + const dispatch = useAppDispatch(); + + const handleCloseDrawer = (): void => { + dispatch(closeDrawer()); + }; + + const onBackwardClick = (): void => { + backwardFunction(); + }; + + return ( + <div className="flex items-center justify-between border-b border-b-divide pl-4 pr-5"> + <div className="flex flex-row flex-nowrap items-center"> + <IconButton + className="h-6 w-6 bg-white-pearl" + icon="chevron-left" + classNameIcon="fill-font-500 h-6 w-6" + onClick={onBackwardClick} + role={BACK_BUTTON_ROLE} + /> + <div className="ml-2 py-8 text-xl"> + <span className="font-normal">{title}: </span> + <span className="font-semibold">{value}</span> + </div> + </div> + <IconButton + className="bg-white-pearl" + classNameIcon="fill-font-500" + icon="close" + role={CLOSE_BUTTON_ROLE} + onClick={handleCloseDrawer} + /> + </div> + ); +}; diff --git a/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.constants.tsx b/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.constants.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8422870530be28c4877b289d32e73754160af761 --- /dev/null +++ b/src/shared/DrawerHeadingBackwardButton/DrawerHeadingBackwardButton.constants.tsx @@ -0,0 +1,2 @@ +export const CLOSE_BUTTON_ROLE = 'close-drawer-button'; +export const BACK_BUTTON_ROLE = 'back-button'; diff --git a/src/shared/DrawerHeadingBackwardButton/index.ts b/src/shared/DrawerHeadingBackwardButton/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b3fb46743bc8c6aa582eb014c30eef2d0eb3ef88 --- /dev/null +++ b/src/shared/DrawerHeadingBackwardButton/index.ts @@ -0,0 +1 @@ +export { DrawerHeadingBackwardButton } from './DrawerHeadingBackwardButton.component'; diff --git a/src/shared/Icon/Icon.component.tsx b/src/shared/Icon/Icon.component.tsx index b23a3ad43cb93631acb252e238ae2531626c3ff3..b683ed15a8d2d574411618f5f9b8486c41b8319f 100644 --- a/src/shared/Icon/Icon.component.tsx +++ b/src/shared/Icon/Icon.component.tsx @@ -12,6 +12,7 @@ import { PageIcon } from '@/shared/Icon/Icons/PageIcon'; import { PluginIcon } from '@/shared/Icon/Icons/PluginIcon'; import { PlusIcon } from '@/shared/Icon/Icons/PlusIcon'; import { CloseIcon } from '@/shared/Icon/Icons/CloseIcon'; +import { Pin } from '@/shared/Icon/Icons/Pin'; import type { IconTypes } from '@/types/iconTypes'; @@ -25,6 +26,7 @@ const icons = { 'chevron-left': ChevronLeftIcon, 'chevron-up': ChevronUpIcon, 'chevron-down': ChevronDownIcon, + pin: Pin, plus: PlusIcon, arrow: ArrowIcon, dots: DotsIcon, diff --git a/src/shared/Icon/Icons/Pin.tsx b/src/shared/Icon/Icons/Pin.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f0ee2edd45997180603d7ecedbab5fb24c78daaa --- /dev/null +++ b/src/shared/Icon/Icons/Pin.tsx @@ -0,0 +1,20 @@ +interface PinOrangeProps { + className: string; +} + +export const Pin = ({ className }: PinOrangeProps): JSX.Element => ( + <svg + width="18" + height="24" + viewBox="0 0 18 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + className={className} + > + <path + d="M9 0C4.575 0 0 3.375 0 9C0 14.325 8.1 22.65 8.475 23.025C8.625 23.175 8.775 23.25 9 23.25C9.225 23.25 9.375 23.175 9.525 23.025C9.9 22.65 18 14.4 18 9C18 3.375 13.425 0 9 0ZM9 12C7.35 12 6 10.65 6 9C6 7.35 7.35 6 9 6C10.65 6 12 7.35 12 9C12 10.65 10.65 12 9 12Z" + fill="currentColor" + /> + <circle cx="9.0002" cy="8.99922" r="4.8" fill="currentColor" /> + </svg> +); diff --git a/src/types/iconTypes.ts b/src/types/iconTypes.ts index 9b14f66b477ef940782e3c94b8d1079b8683af89..c37714cb64db3ae771784cd0978c5858bfdf5536 100644 --- a/src/types/iconTypes.ts +++ b/src/types/iconTypes.ts @@ -12,4 +12,5 @@ export type IconTypes = | 'legend' | 'page' | 'plugin' - | 'close'; + | 'close' + | 'pin'; diff --git a/src/utils/testing/getReduxWrapperWithStore.tsx b/src/utils/testing/getReduxWrapperWithStore.tsx index d13687fff071c2babbc3e643f27ef9936cd19cc1..b698584c6db44d64dd07f29a3447cfcd8ead2de3 100644 --- a/src/utils/testing/getReduxWrapperWithStore.tsx +++ b/src/utils/testing/getReduxWrapperWithStore.tsx @@ -1,19 +1,12 @@ import bioEntityContentsReducer from '@/redux/bioEntityContents/bioEntityContents.slice'; -import type { BioEntityContentsState } from '@/redux/bioEntityContents/bioEntityContents.types'; import chemicalsReducer from '@/redux/chemicals/chemicals.slice'; -import type { ChemicalsState } from '@/redux/chemicals/chemicals.types'; import drawerReducer from '@/redux/drawer/drawer.slice'; -import type { DrawerState } from '@/redux/drawer/drawer.types'; import drugsReducer from '@/redux/drugs/drugs.slice'; -import type { DrugsState } from '@/redux/drugs/drugs.types'; import mirnasReducer from '@/redux/mirnas/mirnas.slice'; -import type { MirnasState } from '@/redux/mirnas/mirnas.types'; import modelsReducer from '@/redux/models/models.slice'; import projectReducer from '@/redux/project/project.slice'; -import type { ProjectState } from '@/redux/project/project.types'; import searchReducer from '@/redux/search/search.slice'; -import type { SearchState } from '@/redux/search/search.types'; -import { store } from '@/redux/store'; +import { RootState, StoreType } from '@/redux/store'; import { configureStore } from '@reduxjs/toolkit'; import { Provider } from 'react-redux'; @@ -21,19 +14,11 @@ interface WrapperProps { children: React.ReactNode; } -export type InitialStoreState = { - search?: SearchState; - project?: ProjectState; - drugs?: DrugsState; - mirnas?: MirnasState; - chemicals?: ChemicalsState; - bioEntityContents?: BioEntityContentsState; - drawer?: DrawerState; -}; +export type InitialStoreState = Partial<RootState>; type GetReduxWrapperUsingSliceReducer = (initialState?: InitialStoreState) => { Wrapper: ({ children }: WrapperProps) => JSX.Element; - store: typeof store; + store: StoreType; }; export const getReduxWrapperWithStore: GetReduxWrapperUsingSliceReducer = (