@genspectrum/dashboard-components 1.8.2 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,7 +9,8 @@ import { type Deletion, type Substitution } from '../../utils/mutations';
9
9
  import { type Temporal } from '../../utils/temporalClass';
10
10
  import { AnnotatedMutation } from '../components/annotated-mutation';
11
11
  import { type ColorScale, getColorWithinScale, getTextColorForScale } from '../components/color-scale-selector';
12
- import Tooltip, { type TooltipPosition } from '../components/tooltip';
12
+ import PortalTooltip from '../components/portal-tooltip';
13
+ import { type TooltipPosition } from '../components/tooltip';
13
14
  import { formatProportion } from '../shared/table/formatProportion';
14
15
  import { type PageSizes, Pagination } from '../shared/tanstackTable/pagination';
15
16
  import { usePageSizeContext } from '../shared/tanstackTable/pagination-context';
@@ -28,6 +29,7 @@ export interface MutationsOverTimeGridProps {
28
29
  colorScale: ColorScale;
29
30
  sequenceType: SequenceType;
30
31
  pageSizes: PageSizes;
32
+ tooltipPortalTarget: HTMLElement | null;
31
33
  }
32
34
 
33
35
  type RowType = { mutation: Substitution | Deletion; values: (MutationOverTimeMutationValue | undefined)[] };
@@ -37,6 +39,7 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
37
39
  colorScale,
38
40
  sequenceType,
39
41
  pageSizes,
42
+ tooltipPortalTarget,
40
43
  }) => {
41
44
  const tableData = useMemo(() => {
42
45
  const allMutations = data.getFirstAxisKeys();
@@ -91,6 +94,7 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
91
94
  numberOfColumns,
92
95
  )}
93
96
  colorScale={colorScale}
97
+ tooltipPortalTarget={tooltipPortalTarget}
94
98
  />
95
99
  </div>
96
100
  );
@@ -99,7 +103,7 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
99
103
  });
100
104
 
101
105
  return [mutationHeader, ...dateHeaders];
102
- }, [colorScale, data, sequenceType]);
106
+ }, [colorScale, data, sequenceType, tooltipPortalTarget]);
103
107
 
104
108
  const { pageSize } = usePageSizeContext();
105
109
  const table = usePreactTable({
@@ -165,10 +169,10 @@ function styleGridHeader(columnIndex: number, numDateColumns: number) {
165
169
  return { className: 'invisible @[6rem]:visible' };
166
170
  }
167
171
 
168
- function getTooltipPosition(rowIndex: number, rows: number, columnIndex: number, columns: number) {
172
+ function getTooltipPosition(rowIndex: number, rows: number, columnIndex: number, columns: number): TooltipPosition {
169
173
  const tooltipX = rowIndex < rows / 2 || rowIndex < 6 ? 'bottom' : 'top';
170
174
  const tooltipY = columnIndex < columns / 2 ? 'start' : 'end';
171
- return `${tooltipX}-${tooltipY}` as const;
175
+ return `${tooltipX}-${tooltipY}`;
172
176
  }
173
177
 
174
178
  const ProportionCell: FunctionComponent<{
@@ -177,14 +181,16 @@ const ProportionCell: FunctionComponent<{
177
181
  mutation: Substitution | Deletion;
178
182
  tooltipPosition: TooltipPosition;
179
183
  colorScale: ColorScale;
180
- }> = ({ value, mutation, date, tooltipPosition, colorScale }) => {
184
+ tooltipPortalTarget: HTMLElement | null;
185
+ }> = ({ value, mutation, date, tooltipPosition, colorScale, tooltipPortalTarget }) => {
181
186
  const proportion = value?.type === 'belowThreshold' ? undefined : value?.proportion;
182
187
 
183
188
  return (
184
189
  <div className={'py-1 w-full h-full'}>
185
- <Tooltip
190
+ <PortalTooltip
186
191
  content={<MutationsOverTimeGridTooltip mutation={mutation} date={date} value={value} />}
187
192
  position={tooltipPosition}
193
+ portalTarget={tooltipPortalTarget}
188
194
  >
189
195
  <div
190
196
  style={{
@@ -201,7 +207,7 @@ const ProportionCell: FunctionComponent<{
201
207
  </span>
202
208
  )}
203
209
  </div>
204
- </Tooltip>
210
+ </PortalTooltip>
205
211
  </div>
206
212
  );
207
213
  };
@@ -1,5 +1,5 @@
1
1
  import { type FunctionComponent } from 'preact';
2
- import { type Dispatch, type StateUpdater, useMemo, useState, useEffect } from 'preact/hooks';
2
+ import { type Dispatch, type StateUpdater, useMemo, useState, useEffect, useLayoutEffect, useRef } from 'preact/hooks';
3
3
  import z from 'zod';
4
4
 
5
5
  // @ts-expect-error -- uses subpath imports and vite worker import
@@ -142,6 +142,12 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
142
142
  overallMutationData,
143
143
  }) => {
144
144
  const tabsRef = useDispatchFinishedLoadingEvent();
145
+ const tooltipPortalTargetRef = useRef<HTMLDivElement>(null);
146
+ const [tooltipPortalTarget, setTooltipPortalTarget] = useState<HTMLDivElement | null>(null);
147
+
148
+ useLayoutEffect(() => {
149
+ setTooltipPortalTarget(tooltipPortalTargetRef.current);
150
+ }, []);
145
151
 
146
152
  const [mutationFilterValue, setMutationFilterValue] = useState<MutationFilter>({
147
153
  textFilter: '',
@@ -198,6 +204,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
198
204
  colorScale={colorScale}
199
205
  sequenceType={originalComponentProps.sequenceType}
200
206
  pageSizes={originalComponentProps.pageSizes}
207
+ tooltipPortalTarget={tooltipPortalTarget}
201
208
  />
202
209
  ),
203
210
  };
@@ -227,9 +234,11 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
227
234
  );
228
235
 
229
236
  return (
230
- <PageSizeContextProvider pageSizes={originalComponentProps.pageSizes}>
231
- <Tabs ref={tabsRef} tabs={tabs} toolbar={toolbar} />
232
- </PageSizeContextProvider>
237
+ <div ref={tooltipPortalTargetRef}>
238
+ <PageSizeContextProvider pageSizes={originalComponentProps.pageSizes}>
239
+ <Tabs ref={tabsRef} tabs={tabs} toolbar={toolbar} />
240
+ </PageSizeContextProvider>
241
+ </div>
233
242
  );
234
243
  };
235
244
 
@@ -100,7 +100,9 @@ const TextSelector = ({
100
100
  return (
101
101
  <p>
102
102
  <span>{item.value}</span>
103
- {!hideCounts && <span className='ml-2 text-gray-500'>({item.count})</span>}
103
+ {!hideCounts && (
104
+ <span className='ml-2 text-gray-500'>({item.count.toLocaleString('en-US')})</span>
105
+ )}
104
106
  </p>
105
107
  );
106
108
  }}
@@ -1,5 +1,5 @@
1
1
  import { type FunctionComponent } from 'preact';
2
- import { type Dispatch, type StateUpdater, useMemo, useState } from 'preact/hooks';
2
+ import { type Dispatch, type StateUpdater, useMemo, useState, useRef } from 'preact/hooks';
3
3
  import z from 'zod';
4
4
 
5
5
  import { computeWastewaterMutationsOverTimeDataPerLocation } from './computeWastewaterMutationsOverTimeDataPerLocation';
@@ -150,6 +150,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
150
150
  originalComponentProps,
151
151
  }) => {
152
152
  const tabsRef = useDispatchFinishedLoadingEvent();
153
+ const tooltipPortalTargetRef = useRef<HTMLDivElement>(null);
153
154
 
154
155
  const [mutationFilterValue, setMutationFilterValue] = useState<MutationFilter>({
155
156
  textFilter: '',
@@ -176,6 +177,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
176
177
  colorScale={colorScale}
177
178
  pageSizes={originalComponentProps.pageSizes}
178
179
  sequenceType={originalComponentProps.sequenceType}
180
+ tooltipPortalTarget={tooltipPortalTargetRef.current}
179
181
  />
180
182
  ),
181
183
  })),
@@ -204,9 +206,11 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
204
206
  );
205
207
 
206
208
  return (
207
- <PageSizeContextProvider pageSizes={originalComponentProps.pageSizes}>
208
- <Tabs ref={tabsRef} tabs={tabs} toolbar={toolbar} />
209
- </PageSizeContextProvider>
209
+ <div ref={tooltipPortalTargetRef}>
210
+ <PageSizeContextProvider pageSizes={originalComponentProps.pageSizes}>
211
+ <Tabs ref={tabsRef} tabs={tabs} toolbar={toolbar} />
212
+ </PageSizeContextProvider>
213
+ </div>
210
214
  );
211
215
  };
212
216
 
@@ -229,7 +229,7 @@ export const FiresEvent: StoryObj<Required<LineageFilterProps>> = {
229
229
 
230
230
  await step('Enter a valid lineage value', async () => {
231
231
  await userEvent.type(inputField(), 'B.1.1.7*');
232
- await userEvent.click(canvas.getByRole('option', { name: 'B.1.1.7*(677146)' }));
232
+ await userEvent.click(canvas.getByRole('option', { name: 'B.1.1.7*(677,146)' }));
233
233
 
234
234
  await waitFor(() => {
235
235
  return expect(listenerMock.mock.calls.at(-1)![0].detail).toStrictEqual({
@@ -162,7 +162,7 @@ export const FiresEvents: StoryObj<Required<TextFilterProps>> = {
162
162
  await step('Remove initial value', async () => {
163
163
  await userEvent.click(canvas.getByRole('button', { name: 'clear selection' }));
164
164
 
165
- await expect(listenerMock).toHaveBeenLastCalledWith(
165
+ await expect(listenerMock).toHaveBeenCalledWith(
166
166
  expect.objectContaining({
167
167
  detail: {
168
168
  host: undefined,