diff --git a/components/src/preact/components/error-display.tsx b/components/src/preact/components/error-display.tsx index 13b49d58..31ccfc72 100644 --- a/components/src/preact/components/error-display.tsx +++ b/components/src/preact/components/error-display.tsx @@ -2,6 +2,8 @@ import { type FunctionComponent } from 'preact'; import { useEffect, useRef } from 'preact/hooks'; import { type ZodError } from 'zod'; +import { InfoHeadline1, InfoParagraph } from './info'; +import { Modal, useModalRef } from './modal'; import { LapisError, UnknownLapisError } from '../../lapisApi/lapisApi'; export const GS_ERROR_EVENT_TYPE = 'gs-error'; @@ -46,7 +48,7 @@ export const ErrorDisplay: FunctionComponent = ({ error, rese console.error(error); const containerRef = useRef(null); - const ref = useRef(null); + const modalRef = useModalRef(); useEffect(() => { containerRef.current?.dispatchEvent(new ErrorEvent(error)); @@ -66,23 +68,16 @@ export const ErrorDisplay: FunctionComponent = ({ error, rese {details !== undefined && ( <> {' '} - - - - - + + {details.headline} + {details.message} + )} diff --git a/components/src/preact/components/info.tsx b/components/src/preact/components/info.tsx index f6f68754..d4dc7360 100644 --- a/components/src/preact/components/info.tsx +++ b/components/src/preact/components/info.tsx @@ -1,11 +1,12 @@ import { type FunctionComponent } from 'preact'; -import { useRef } from 'preact/hooks'; + +import { Modal, useModalRef } from './modal'; const Info: FunctionComponent = ({ children }) => { - const dialogRef = useRef(null); + const modalRef = useModalRef(); const toggleHelp = () => { - dialogRef.current?.showModal(); + modalRef.current?.showModal(); }; return ( @@ -13,22 +14,7 @@ const Info: FunctionComponent = ({ children }) => { - -
-
- -
-
{children}
-
-
- -
-
-
-
- -
-
+ {children} ); }; diff --git a/components/src/preact/components/modal.stories.tsx b/components/src/preact/components/modal.stories.tsx new file mode 100644 index 00000000..95c64724 --- /dev/null +++ b/components/src/preact/components/modal.stories.tsx @@ -0,0 +1,44 @@ +import { type Meta, type StoryObj } from '@storybook/preact'; +import { expect, waitFor, within } from '@storybook/test'; +import { type FunctionComponent } from 'preact'; + +import { Modal, type ModalProps, useModalRef } from './modal'; + +const meta: Meta = { + title: 'Component/Modal', + component: Modal, + parameters: { fetchMock: {} }, +}; + +export default meta; + +const WrapperWithButtonThatOpensTheModal: FunctionComponent = () => { + const modalRef = useModalRef(); + + return ( +
+ + +

Modal content

+
+
+ ); +}; + +export const ModalStory: StoryObj = { + render: () => { + return ; + }, + play: async ({ canvasElement, step }) => { + const canvas = within(canvasElement); + + await step('Open the modal', async () => { + const button = canvas.getByText('Open modal'); + button.click(); + + await waitFor(() => expect(canvas.getByText('Modal content')).toBeVisible()); + }); + }, +}; diff --git a/components/src/preact/components/modal.tsx b/components/src/preact/components/modal.tsx new file mode 100644 index 00000000..a12debfb --- /dev/null +++ b/components/src/preact/components/modal.tsx @@ -0,0 +1,31 @@ +import { type FunctionComponent, type Ref } from 'preact'; +import { useRef } from 'preact/hooks'; + +export type ModalProps = { + modalRef: Ref; +}; + +export function useModalRef() { + return useRef(null); +} + +export const Modal: FunctionComponent = ({ children, modalRef }) => { + return ( + +
+
+ +
+
{children}
+
+
+ +
+
+
+
+ +
+
+ ); +}; diff --git a/components/src/preact/map/sequences-by-location-map.tsx b/components/src/preact/map/sequences-by-location-map.tsx index b2a309b5..68a3e5dc 100644 --- a/components/src/preact/map/sequences-by-location-map.tsx +++ b/components/src/preact/map/sequences-by-location-map.tsx @@ -7,6 +7,7 @@ import { type GeoJsonFeatureProperties, type MapSource, useGeoJsonMap } from './ import { type AggregateData } from '../../query/queryAggregateData'; import { InfoHeadline1, InfoParagraph } from '../components/info'; import { LoadingDisplay } from '../components/loading-display'; +import { Modal, useModalRef } from '../components/modal'; import { formatProportion } from '../shared/table/formatProportion'; type FeatureData = { proportion: number; count: number }; @@ -135,48 +136,38 @@ const DataMatchInformation: FunctionComponent = ({ nullCount, hasTableView, }) => { - const dialogRef = useRef(null); + const modalRef = useModalRef(); const proportion = formatProportion(countOfMatchedLocationData / totalCount); return ( <> - -
- Sequences By Location - Map View - - The current filter has matched {totalCount.toLocaleString('en-us')} sequences. From these - sequences, we were able to match {countOfMatchedLocationData.toLocaleString('en-us')} ( - {proportion}) on locations on the map. - - - {unmatchedLocations.length > 0 && ( - <> - The following locations from the data could not be matched on the map:{' '} - {unmatchedLocations.map((it) => `"${it}"`).join(', ')}.{' '} - - )} - {nullCount > 0 && - `${nullCount.toLocaleString('en-us')} matching sequences have no location information. `} - {hasTableView && 'You can check the table view for more detailed information.'} - -
-
- -
-
-
-
- -
-
+ + Sequences By Location - Map View + + The current filter has matched {totalCount.toLocaleString('en-us')} sequences. From these sequences, + we were able to match {countOfMatchedLocationData.toLocaleString('en-us')} ({proportion}) on + locations on the map. + + + {unmatchedLocations.length > 0 && ( + <> + The following locations from the data could not be matched on the map:{' '} + {unmatchedLocations.map((it) => `"${it}"`).join(', ')}.{' '} + + )} + {nullCount > 0 && + `${nullCount.toLocaleString('en-us')} matching sequences have no location information. `} + {hasTableView && 'You can check the table view for more detailed information.'} + + ); };