@genspectrum/dashboard-components 0.3.2 → 0.4.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.
Files changed (35) hide show
  1. package/custom-elements.json +45 -26
  2. package/dist/dashboard-components.js +518 -434
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +21 -12
  5. package/dist/style.css +150 -229
  6. package/package.json +3 -1
  7. package/src/preact/aggregatedData/aggregate.tsx +1 -1
  8. package/src/preact/components/SegmentSelector.tsx +0 -1
  9. package/src/preact/components/checkbox-selector.tsx +7 -9
  10. package/src/preact/components/dropdown.tsx +40 -0
  11. package/src/preact/components/info.stories.tsx +8 -8
  12. package/src/preact/components/info.tsx +38 -19
  13. package/src/preact/components/mutation-type-selector.tsx +0 -1
  14. package/src/preact/components/proportion-selector-dropdown.tsx +9 -18
  15. package/src/preact/components/tabs.tsx +12 -3
  16. package/src/preact/dateRangeSelector/computeInitialValues.spec.ts +99 -0
  17. package/src/preact/dateRangeSelector/computeInitialValues.ts +73 -0
  18. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +93 -4
  19. package/src/preact/dateRangeSelector/date-range-selector.tsx +49 -106
  20. package/src/preact/dateRangeSelector/selectableOptions.ts +79 -0
  21. package/src/preact/locationFilter/location-filter.tsx +1 -1
  22. package/src/preact/mutationComparison/mutation-comparison.tsx +3 -3
  23. package/src/preact/mutationFilter/mutation-filter.stories.tsx +3 -6
  24. package/src/preact/mutationFilter/mutation-filter.tsx +48 -54
  25. package/src/preact/mutations/mutations.tsx +3 -4
  26. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +3 -5
  27. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +3 -3
  28. package/src/preact/shared/floating-ui/hooks.ts +83 -0
  29. package/src/web-components/input/gs-date-range-selector.stories.ts +11 -5
  30. package/src/web-components/input/gs-date-range-selector.tsx +22 -5
  31. package/src/web-components/input/gs-location-filter.stories.ts +6 -7
  32. package/src/web-components/input/gs-location-filter.tsx +3 -2
  33. package/src/web-components/input/gs-mutation-filter.stories.ts +1 -8
  34. package/src/web-components/input/gs-mutation-filter.tsx +1 -9
  35. package/src/web-components/visualization/gs-prevalence-over-time.tsx +1 -1
@@ -2,14 +2,19 @@ import flatpickr from 'flatpickr';
2
2
  import 'flatpickr/dist/flatpickr.min.css';
3
3
  import { useEffect, useRef, useState } from 'preact/hooks';
4
4
 
5
+ import { computeInitialValues } from './computeInitialValues';
5
6
  import { toYYYYMMDD } from './dateConversion';
7
+ import {
8
+ type CustomSelectOption,
9
+ getDatesForSelectorValue,
10
+ getSelectableOptions,
11
+ type PresetOptionValues,
12
+ } from './selectableOptions';
6
13
  import { ErrorBoundary } from '../components/error-boundary';
7
14
  import { ResizeContainer } from '../components/resize-container';
8
15
  import { Select } from '../components/select';
9
16
  import type { ScaleType } from '../shared/charts/getYAxisScale';
10
17
 
11
- export type CustomSelectOption<CustomLabel extends string> = { label: CustomLabel; dateFrom: string; dateTo: string };
12
-
13
18
  export interface DateRangeSelectorProps<CustomLabel extends string> extends DateRangeSelectorPropsInner<CustomLabel> {
14
19
  width: string;
15
20
  }
@@ -18,35 +23,19 @@ export interface DateRangeSelectorPropsInner<CustomLabel extends string> {
18
23
  customSelectOptions: CustomSelectOption<CustomLabel>[];
19
24
  earliestDate?: string;
20
25
  initialValue?: PresetOptionValues | CustomLabel;
26
+ initialDateFrom?: string;
27
+ initialDateTo?: string;
21
28
  dateColumn: string;
22
29
  }
23
30
 
24
- export const PRESET_VALUE_CUSTOM = 'custom';
25
- export const PRESET_VALUE_ALL_TIMES = 'allTimes';
26
- export const PRESET_VALUE_LAST_2_WEEKS = 'last2Weeks';
27
- export const PRESET_VALUE_LAST_MONTH = 'lastMonth';
28
- export const PRESET_VALUE_LAST_2_MONTHS = 'last2Months';
29
- export const PRESET_VALUE_LAST_3_MONTHS = 'last3Months';
30
- export const PRESET_VALUE_LAST_6_MONTHS = 'last6Months';
31
-
32
- export const presets = {
33
- [PRESET_VALUE_CUSTOM]: { label: 'Custom' },
34
- [PRESET_VALUE_ALL_TIMES]: { label: 'All times' },
35
- [PRESET_VALUE_LAST_2_WEEKS]: { label: 'Last 2 weeks' },
36
- [PRESET_VALUE_LAST_MONTH]: { label: 'Last month' },
37
- [PRESET_VALUE_LAST_2_MONTHS]: { label: 'Last 2 months' },
38
- [PRESET_VALUE_LAST_3_MONTHS]: { label: 'Last 3 months' },
39
- [PRESET_VALUE_LAST_6_MONTHS]: { label: 'Last 6 months' },
40
- };
41
-
42
- export type PresetOptionValues = keyof typeof presets;
43
-
44
31
  export const DateRangeSelector = <CustomLabel extends string>({
45
32
  customSelectOptions,
46
33
  earliestDate = '1900-01-01',
47
34
  initialValue,
48
35
  width,
49
36
  dateColumn,
37
+ initialDateFrom,
38
+ initialDateTo,
50
39
  }: DateRangeSelectorProps<CustomLabel>) => {
51
40
  const size = { width, height: '3rem' };
52
41
 
@@ -58,6 +47,8 @@ export const DateRangeSelector = <CustomLabel extends string>({
58
47
  earliestDate={earliestDate}
59
48
  initialValue={initialValue}
60
49
  dateColumn={dateColumn}
50
+ initialDateFrom={initialDateFrom}
51
+ initialDateTo={initialDateTo}
61
52
  />
62
53
  </ResizeContainer>
63
54
  </ErrorBoundary>
@@ -69,24 +60,30 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
69
60
  earliestDate = '1900-01-01',
70
61
  initialValue,
71
62
  dateColumn,
63
+ initialDateFrom,
64
+ initialDateTo,
72
65
  }: DateRangeSelectorPropsInner<CustomLabel>) => {
66
+ const initialValues = computeInitialValues(
67
+ initialValue,
68
+ initialDateFrom,
69
+ initialDateTo,
70
+ earliestDate,
71
+ customSelectOptions,
72
+ );
73
+
73
74
  const fromDatePickerRef = useRef<HTMLInputElement>(null);
74
75
  const toDatePickerRef = useRef<HTMLInputElement>(null);
75
76
  const divRef = useRef<HTMLDivElement>(null);
76
77
  const [dateFromPicker, setDateFromPicker] = useState<flatpickr.Instance | null>(null);
77
78
  const [dateToPicker, setDateToPicker] = useState<flatpickr.Instance | null>(null);
78
79
 
79
- const selectableOptions = getSelectableOptions(customSelectOptions);
80
-
81
80
  const [selectedDateRange, setSelectedDateRange] = useState<CustomLabel | PresetOptionValues>(
82
- initialValue !== undefined && selectableOptions.some((option) => option.value === initialValue)
83
- ? initialValue
84
- : PRESET_VALUE_LAST_6_MONTHS,
81
+ initialValues.initialSelectedDateRange,
85
82
  );
86
83
 
87
84
  const [selectedDates, setSelectedDates] = useState<{ dateFrom: Date; dateTo: Date }>({
88
- dateFrom: getDatesForSelectorValue('last6Months', customSelectOptions, earliestDate).dateFrom,
89
- dateTo: getDatesForSelectorValue('last6Months', customSelectOptions, earliestDate).dateTo,
85
+ dateFrom: initialValues.initialSelectedDateFrom,
86
+ dateTo: initialValues.initialSelectedDateTo,
90
87
  });
91
88
 
92
89
  useEffect(() => {
@@ -182,11 +179,11 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
182
179
  };
183
180
 
184
181
  return (
185
- <div class='join w-full' ref={divRef}>
182
+ <div class='flex flex-wrap' ref={divRef}>
186
183
  <Select
187
- items={selectableOptions}
184
+ items={getSelectableOptions(customSelectOptions)}
188
185
  selected={selectedDateRange}
189
- selectStyle='select-bordered rounded-none join-item grow'
186
+ selectStyle='select-bordered rounded-none flex-grow w-40'
190
187
  onChange={(event: Event) => {
191
188
  event.preventDefault();
192
189
  const select = event.target as HTMLSelectElement;
@@ -194,80 +191,26 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
194
191
  onSelectChange(value as CustomLabel | PresetOptionValues);
195
192
  }}
196
193
  />
197
- <input
198
- class='input input-bordered rounded-none join-item grow'
199
- type='text'
200
- placeholder='Date from'
201
- ref={fromDatePickerRef}
202
- onChange={onChangeDateFrom}
203
- onBlur={onChangeDateFrom}
204
- />
205
- <input
206
- class='input input-bordered rounded-none join-item grow'
207
- type='text'
208
- placeholder='Date to'
209
- ref={toDatePickerRef}
210
- onChange={onChangeDateTo}
211
- onBlur={onChangeDateTo}
212
- />
194
+ <div className={'flex flex-wrap flex-grow'}>
195
+ <input
196
+ class='input input-bordered rounded-none flex-grow min-w-40'
197
+ type='text'
198
+ size={10}
199
+ placeholder='Date from'
200
+ ref={fromDatePickerRef}
201
+ onChange={onChangeDateFrom}
202
+ onBlur={onChangeDateFrom}
203
+ />
204
+ <input
205
+ class='input input-bordered rounded-none flex-grow min-w-40'
206
+ type='text'
207
+ size={10}
208
+ placeholder='Date to'
209
+ ref={toDatePickerRef}
210
+ onChange={onChangeDateTo}
211
+ onBlur={onChangeDateTo}
212
+ />
213
+ </div>
213
214
  </div>
214
215
  );
215
216
  };
216
-
217
- const getSelectableOptions = <Label extends string>(customSelectOptions: CustomSelectOption<Label>[]) => {
218
- const presetOptions = Object.entries(presets).map(([key, value]) => {
219
- return { label: value.label, value: key };
220
- });
221
-
222
- const customOptions = customSelectOptions.map((customSelectOption) => {
223
- return { label: customSelectOption.label, value: customSelectOption.label };
224
- });
225
-
226
- return [...presetOptions, ...customOptions];
227
- };
228
-
229
- const getDatesForSelectorValue = <Label extends string>(
230
- selectorValue: string,
231
- customSelectOptions: CustomSelectOption<Label>[],
232
- earliestDate: string,
233
- ) => {
234
- const today = new Date();
235
-
236
- const customSelectOption = customSelectOptions.find((option) => option.label === selectorValue);
237
- if (customSelectOption) {
238
- return { dateFrom: new Date(customSelectOption.dateFrom), dateTo: new Date(customSelectOption.dateTo) };
239
- }
240
-
241
- switch (selectorValue) {
242
- case PRESET_VALUE_LAST_2_WEEKS: {
243
- const twoWeeksAgo = new Date(today);
244
- twoWeeksAgo.setDate(today.getDate() - 14);
245
- return { dateFrom: twoWeeksAgo, dateTo: today };
246
- }
247
- case PRESET_VALUE_LAST_MONTH: {
248
- const lastMonth = new Date(today);
249
- lastMonth.setMonth(today.getMonth() - 1);
250
- return { dateFrom: lastMonth, dateTo: today };
251
- }
252
- case PRESET_VALUE_LAST_2_MONTHS: {
253
- const twoMonthsAgo = new Date(today);
254
- twoMonthsAgo.setMonth(today.getMonth() - 2);
255
- return { dateFrom: twoMonthsAgo, dateTo: today };
256
- }
257
- case PRESET_VALUE_LAST_3_MONTHS: {
258
- const threeMonthsAgo = new Date(today);
259
- threeMonthsAgo.setMonth(today.getMonth() - 3);
260
- return { dateFrom: threeMonthsAgo, dateTo: today };
261
- }
262
- case PRESET_VALUE_LAST_6_MONTHS: {
263
- const sixMonthsAgo = new Date(today);
264
- sixMonthsAgo.setMonth(today.getMonth() - 6);
265
- return { dateFrom: sixMonthsAgo, dateTo: today };
266
- }
267
- case PRESET_VALUE_ALL_TIMES: {
268
- return { dateFrom: new Date(earliestDate), dateTo: today };
269
- }
270
- default:
271
- return { dateFrom: today, dateTo: today };
272
- }
273
- };
@@ -0,0 +1,79 @@
1
+ export const PRESET_VALUE_CUSTOM = 'custom';
2
+ export const PRESET_VALUE_ALL_TIMES = 'allTimes';
3
+ export const PRESET_VALUE_LAST_2_WEEKS = 'last2Weeks';
4
+ export const PRESET_VALUE_LAST_MONTH = 'lastMonth';
5
+ export const PRESET_VALUE_LAST_2_MONTHS = 'last2Months';
6
+ export const PRESET_VALUE_LAST_3_MONTHS = 'last3Months';
7
+ export const PRESET_VALUE_LAST_6_MONTHS = 'last6Months';
8
+
9
+ export const presets = {
10
+ [PRESET_VALUE_CUSTOM]: { label: 'Custom' },
11
+ [PRESET_VALUE_ALL_TIMES]: { label: 'All times' },
12
+ [PRESET_VALUE_LAST_2_WEEKS]: { label: 'Last 2 weeks' },
13
+ [PRESET_VALUE_LAST_MONTH]: { label: 'Last month' },
14
+ [PRESET_VALUE_LAST_2_MONTHS]: { label: 'Last 2 months' },
15
+ [PRESET_VALUE_LAST_3_MONTHS]: { label: 'Last 3 months' },
16
+ [PRESET_VALUE_LAST_6_MONTHS]: { label: 'Last 6 months' },
17
+ };
18
+
19
+ export type PresetOptionValues = keyof typeof presets;
20
+
21
+ export type CustomSelectOption<CustomLabel extends string> = { label: CustomLabel; dateFrom: string; dateTo: string };
22
+
23
+ export const getSelectableOptions = <Label extends string>(customSelectOptions: CustomSelectOption<Label>[]) => {
24
+ const presetOptions = Object.entries(presets).map(([key, value]) => {
25
+ return { label: value.label, value: key };
26
+ });
27
+
28
+ const customOptions = customSelectOptions.map((customSelectOption) => {
29
+ return { label: customSelectOption.label, value: customSelectOption.label };
30
+ });
31
+
32
+ return [...presetOptions, ...customOptions];
33
+ };
34
+
35
+ export const getDatesForSelectorValue = <Label extends string>(
36
+ selectorValue: string,
37
+ customSelectOptions: CustomSelectOption<Label>[],
38
+ earliestDate: string,
39
+ ) => {
40
+ const today = new Date();
41
+
42
+ const customSelectOption = customSelectOptions.find((option) => option.label === selectorValue);
43
+ if (customSelectOption) {
44
+ return { dateFrom: new Date(customSelectOption.dateFrom), dateTo: new Date(customSelectOption.dateTo) };
45
+ }
46
+
47
+ switch (selectorValue) {
48
+ case PRESET_VALUE_LAST_2_WEEKS: {
49
+ const twoWeeksAgo = new Date(today);
50
+ twoWeeksAgo.setDate(today.getDate() - 14);
51
+ return { dateFrom: twoWeeksAgo, dateTo: today };
52
+ }
53
+ case PRESET_VALUE_LAST_MONTH: {
54
+ const lastMonth = new Date(today);
55
+ lastMonth.setMonth(today.getMonth() - 1);
56
+ return { dateFrom: lastMonth, dateTo: today };
57
+ }
58
+ case PRESET_VALUE_LAST_2_MONTHS: {
59
+ const twoMonthsAgo = new Date(today);
60
+ twoMonthsAgo.setMonth(today.getMonth() - 2);
61
+ return { dateFrom: twoMonthsAgo, dateTo: today };
62
+ }
63
+ case PRESET_VALUE_LAST_3_MONTHS: {
64
+ const threeMonthsAgo = new Date(today);
65
+ threeMonthsAgo.setMonth(today.getMonth() - 3);
66
+ return { dateFrom: threeMonthsAgo, dateTo: today };
67
+ }
68
+ case PRESET_VALUE_LAST_6_MONTHS: {
69
+ const sixMonthsAgo = new Date(today);
70
+ sixMonthsAgo.setMonth(today.getMonth() - 6);
71
+ return { dateFrom: sixMonthsAgo, dateTo: today };
72
+ }
73
+ case PRESET_VALUE_ALL_TIMES: {
74
+ return { dateFrom: new Date(earliestDate), dateTo: today };
75
+ }
76
+ default:
77
+ return { dateFrom: today, dateTo: today };
78
+ }
79
+ };
@@ -93,7 +93,7 @@ export const LocationFilterInner = ({ initialValue, fields }: LocationFilterInne
93
93
 
94
94
  const parseLocation = (location: string, fields: string[]) => {
95
95
  const fieldValues = location.split('/').map((part) => part.trim());
96
- return fieldValues.reduce((acc, fieldValue, i) => ({ ...acc, [fields[i]]: fieldValue }), {});
96
+ return fields.reduce((acc, field, i) => ({ ...acc, [field]: fieldValues[i] }), {});
97
97
  };
98
98
 
99
99
  const hasMatchingEntry = (data: Record<string, string>[] | null, eventDetail: Record<string, string>) => {
@@ -167,7 +167,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
167
167
  setProportionInterval,
168
168
  }) => {
169
169
  return (
170
- <div class='flex flex-row'>
170
+ <>
171
171
  <ProportionSelectorDropdown
172
172
  proportionInterval={proportionInterval}
173
173
  setMinProportion={(min) => setProportionInterval((prev) => ({ ...prev, min }))}
@@ -183,7 +183,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
183
183
  getData={() => getMutationComparisonTableData({ content: filteredData }, proportionInterval)}
184
184
  filename='mutation_comparison.csv'
185
185
  />
186
- <Info>Info for mutation comparison</Info>
187
- </div>
186
+ <Info height={'100px'}>Info for mutation comparison</Info>
187
+ </>
188
188
  );
189
189
  };
@@ -20,7 +20,6 @@ const meta: Meta<MutationFilterProps> = {
20
20
  },
21
21
  argTypes: {
22
22
  width: { control: 'text' },
23
- height: { control: 'text' },
24
23
  initialValue: {
25
24
  control: {
26
25
  type: 'object',
@@ -36,13 +35,12 @@ export const Default: StoryObj<MutationFilterProps> = {
36
35
  render: (args) => (
37
36
  <LapisUrlContext.Provider value={LAPIS_URL}>
38
37
  <ReferenceGenomeContext.Provider value={referenceGenome}>
39
- <MutationFilter width={args.width} height={args.height} initialValue={args.initialValue} />
38
+ <MutationFilter width={args.width} initialValue={args.initialValue} />
40
39
  </ReferenceGenomeContext.Provider>
41
40
  </LapisUrlContext.Provider>
42
41
  ),
43
42
  args: {
44
43
  width: '100%',
45
- height: '700px',
46
44
  },
47
45
  };
48
46
 
@@ -106,7 +104,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
106
104
  });
107
105
 
108
106
  await step('Remove the first mutation', async () => {
109
- const firstMutationDeleteButton = canvas.getAllByRole('button')[0];
107
+ const firstMutationDeleteButton = canvas.getAllByRole('button')[1];
110
108
  await waitFor(() => fireEvent.click(firstMutationDeleteButton));
111
109
 
112
110
  await expect(changedListenerMock).toHaveBeenCalledWith(
@@ -153,7 +151,7 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
153
151
  render: (args) => (
154
152
  <LapisUrlContext.Provider value={LAPIS_URL}>
155
153
  <ReferenceGenomeContext.Provider value={referenceGenome}>
156
- <MutationFilter initialValue={args.initialValue} width={args.width} height={args.height} />
154
+ <MutationFilter initialValue={args.initialValue} width={args.width} />
157
155
  </ReferenceGenomeContext.Provider>
158
156
  </LapisUrlContext.Provider>
159
157
  ),
@@ -165,7 +163,6 @@ export const WithInitialValue: StoryObj<MutationFilterProps> = {
165
163
  aminoAcidInsertions: ['ins_S:123:AAA'],
166
164
  },
167
165
  width: '100%',
168
- height: '700px',
169
166
  },
170
167
  play: async ({ canvasElement, step }) => {
171
168
  const { canvas, onBlurListenerMock } = await prepare(canvasElement, step);
@@ -7,7 +7,6 @@ import { type Deletion, type Insertion, type Mutation, type Substitution } from
7
7
  import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
8
8
  import { ErrorBoundary } from '../components/error-boundary';
9
9
  import Info from '../components/info';
10
- import { ResizeContainer } from '../components/resize-container';
11
10
  import { singleGraphColorRGBByName } from '../shared/charts/colors';
12
11
  import { DeleteIcon } from '../shared/icons/DeleteIcon';
13
12
 
@@ -17,7 +16,6 @@ export interface MutationFilterInnerProps {
17
16
 
18
17
  export interface MutationFilterProps extends MutationFilterInnerProps {
19
18
  width: string;
20
- height: string;
21
19
  }
22
20
 
23
21
  export type SelectedFilters = {
@@ -31,14 +29,12 @@ export type SelectedMutationFilterStrings = {
31
29
  [Key in keyof SelectedFilters]: string[];
32
30
  };
33
31
 
34
- export const MutationFilter: FunctionComponent<MutationFilterProps> = ({ initialValue, width, height }) => {
35
- const size = { height, width };
36
-
32
+ export const MutationFilter: FunctionComponent<MutationFilterProps> = ({ initialValue, width }) => {
37
33
  return (
38
- <ErrorBoundary size={size}>
39
- <ResizeContainer size={size}>
34
+ <ErrorBoundary size={{ height: '3.375rem', width }}>
35
+ <div style={width}>
40
36
  <MutationFilterInner initialValue={initialValue} />
41
- </ResizeContainer>
37
+ </div>
42
38
  </ErrorBoundary>
43
39
  );
44
40
  };
@@ -54,6 +50,9 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
54
50
 
55
51
  const handleSubmit = (event: Event) => {
56
52
  event.preventDefault();
53
+ if (inputValue === '') {
54
+ return;
55
+ }
57
56
 
58
57
  const parsedMutation = parseAndValidateMutation(inputValue, referenceGenome);
59
58
 
@@ -102,30 +101,33 @@ export const MutationFilterInner: FunctionComponent<MutationFilterInnerProps> =
102
101
  };
103
102
 
104
103
  return (
105
- <div class={`h-full w-full rounded-lg border border-gray-300 bg-white p-2 overflow-scroll`}>
106
- <div class='flex justify-between'>
104
+ <form className='w-full border boder-gray-300 rounded-md relative' onSubmit={handleSubmit} ref={formRef}>
105
+ <div className='absolute -top-3 -right-3'>
106
+ <Info height={'100px'}>Info for mutation filter</Info>
107
+ </div>
108
+ <div className='w-full flex p-2 flex-wrap items-center'>
107
109
  <SelectedMutationDisplay
108
110
  selectedFilters={selectedFilters}
109
111
  setSelectedFilters={setSelectedFilters}
110
112
  fireChangeEvent={fireChangeEvent}
111
113
  />
112
- <Info>Info for mutation filter</Info>
113
- </div>
114
-
115
- <form className='mt-2 w-full' onSubmit={handleSubmit} ref={formRef}>
116
- <label className={`input flex items-center gap-2 ${isError ? 'input-error' : 'input-bordered'}`}>
114
+ <div
115
+ className={`w-full flex border ${isError ? 'border-red-500' : 'border-gray-300'} border-solid m-2 text-sm focus-within:border-gray-400 `}
116
+ >
117
117
  <input
118
- className='grow min-w-0'
118
+ className='grow flex-1 p-1 border-none focus:outline-none focus:ring-0'
119
119
  type='text'
120
120
  value={inputValue}
121
121
  onInput={handleInputChange}
122
122
  placeholder={getPlaceholder(referenceGenome)}
123
123
  onBlur={handleOnBlur}
124
124
  />
125
- <button className='btn btn-sm'>+</button>
126
- </label>
127
- </form>
128
- </div>
125
+ <button type='submit' className='btn btn-xs m-1'>
126
+ +
127
+ </button>
128
+ </div>
129
+ </div>
130
+ </form>
129
131
  );
130
132
  };
131
133
 
@@ -193,44 +195,36 @@ const SelectedMutationDisplay: FunctionComponent<{
193
195
  };
194
196
 
195
197
  return (
196
- <ul class='flex flex-wrap'>
198
+ <>
197
199
  {selectedFilters.nucleotideMutations.map((mutation) => (
198
- <li key={mutation.toString()}>
199
- <SelectedNucleotideMutation
200
- mutation={mutation}
201
- onDelete={(mutation: Substitution | Deletion) =>
202
- onSelectedRemoved(mutation, 'nucleotideMutations')
203
- }
204
- />
205
- </li>
200
+ <SelectedNucleotideMutation
201
+ key={mutation.toString()}
202
+ mutation={mutation}
203
+ onDelete={(mutation: Substitution | Deletion) => onSelectedRemoved(mutation, 'nucleotideMutations')}
204
+ />
206
205
  ))}
207
206
  {selectedFilters.aminoAcidMutations.map((mutation) => (
208
- <li key={mutation.toString()}>
209
- <SelectedAminoAcidMutation
210
- mutation={mutation}
211
- onDelete={(mutation: Substitution | Deletion) =>
212
- onSelectedRemoved(mutation, 'aminoAcidMutations')
213
- }
214
- />
215
- </li>
207
+ <SelectedAminoAcidMutation
208
+ key={mutation.toString()}
209
+ mutation={mutation}
210
+ onDelete={(mutation: Substitution | Deletion) => onSelectedRemoved(mutation, 'aminoAcidMutations')}
211
+ />
216
212
  ))}
217
213
  {selectedFilters.nucleotideInsertions.map((insertion) => (
218
- <li key={insertion.toString()}>
219
- <SelectedNucleotideInsertion
220
- insertion={insertion}
221
- onDelete={(insertion) => onSelectedRemoved(insertion, 'nucleotideInsertions')}
222
- />
223
- </li>
214
+ <SelectedNucleotideInsertion
215
+ key={insertion.toString()}
216
+ insertion={insertion}
217
+ onDelete={(insertion) => onSelectedRemoved(insertion, 'nucleotideInsertions')}
218
+ />
224
219
  ))}
225
220
  {selectedFilters.aminoAcidInsertions.map((insertion) => (
226
- <li key={insertion.toString()}>
227
- <SelectedAminoAcidInsertion
228
- insertion={insertion}
229
- onDelete={(insertion: Insertion) => onSelectedRemoved(insertion, 'aminoAcidInsertions')}
230
- />
231
- </li>
221
+ <SelectedAminoAcidInsertion
222
+ key={insertion.toString()}
223
+ insertion={insertion}
224
+ onDelete={(insertion: Insertion) => onSelectedRemoved(insertion, 'aminoAcidInsertions')}
225
+ />
232
226
  ))}
233
- </ul>
227
+ </>
234
228
  );
235
229
  };
236
230
 
@@ -313,15 +307,15 @@ const SelectedFilter = <MutationType extends Mutation>({
313
307
  textColor,
314
308
  }: SelectedFilterProps<MutationType>) => {
315
309
  return (
316
- <div
317
- class='flex items-center flex-nowrap gap-1 rounded me-1 px-2.5 py-0.5 font-medium text-xs mb-1 min-w-max'
310
+ <span
311
+ class='inline-block mx-1 px-2 py-1 font-medium text-xs rounded-full'
318
312
  style={{ backgroundColor, color: textColor }}
319
313
  >
320
- <div className='whitespace-nowrap min-w-max'>{mutation.toString()}</div>
314
+ {mutation.toString()}
321
315
  <button type='button' onClick={() => onDelete(mutation)}>
322
316
  <DeleteIcon />
323
317
  </button>
324
- </div>
318
+ </span>
325
319
  );
326
320
  };
327
321
 
@@ -168,7 +168,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
168
168
  setProportionInterval,
169
169
  }) => {
170
170
  return (
171
- <div class='flex flex-row'>
171
+ <>
172
172
  <SegmentSelector displayedSegments={displayedSegments} setDisplayedSegments={setDisplayedSegments} />
173
173
  {activeTab === 'Table' && (
174
174
  <MutationTypeSelector
@@ -182,7 +182,6 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
182
182
  proportionInterval={proportionInterval}
183
183
  setMinProportion={(min) => setProportionInterval((prev) => ({ ...prev, min }))}
184
184
  setMaxProportion={(max) => setProportionInterval((prev) => ({ ...prev, max }))}
185
- openDirection={'left'}
186
185
  />
187
186
  <CsvDownloadButton
188
187
  className='mx-1 btn btn-xs'
@@ -205,7 +204,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
205
204
  filename='insertions.csv'
206
205
  />
207
206
  )}
208
- <Info>Info for mutations</Info>
209
- </div>
207
+ <Info height={'100px'}>Info for mutations</Info>
208
+ </>
210
209
  );
211
210
  };
@@ -208,7 +208,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
208
208
  granularity,
209
209
  }) => {
210
210
  return (
211
- <div class='flex'>
211
+ <>
212
212
  {activeTab !== 'Table' && (
213
213
  <ScalingSelector yAxisScaleType={yAxisScaleType} setYAxisScaleType={setYAxisScaleType} />
214
214
  )}
@@ -226,17 +226,15 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
226
226
  />
227
227
 
228
228
  <PrevalenceOverTimeInfo />
229
- </div>
229
+ </>
230
230
  );
231
231
  };
232
232
 
233
233
  const PrevalenceOverTimeInfo: FunctionComponent = () => {
234
234
  return (
235
- <Info size={{ width: '600px', height: '30vh' }}>
235
+ <Info height={'100px'}>
236
236
  <InfoHeadline1>Prevalence over time</InfoHeadline1>
237
237
  <InfoParagraph>Prevalence over time info.</InfoParagraph>
238
238
  </Info>
239
239
  );
240
240
  };
241
-
242
- export default PrevalenceOverTime;
@@ -161,16 +161,16 @@ const RelativeGrowthAdvantageToolbar: FunctionComponent<RelativeGrowthAdvantageT
161
161
  generationTime,
162
162
  }) => {
163
163
  return (
164
- <div class='flex'>
164
+ <>
165
165
  <ScalingSelector yAxisScaleType={yAxisScaleType} setYAxisScaleType={setYAxisScaleType} />
166
166
  <RelativeGrowthAdvantageInfo generationTime={generationTime} />
167
- </div>
167
+ </>
168
168
  );
169
169
  };
170
170
 
171
171
  const RelativeGrowthAdvantageInfo: FunctionComponent<{ generationTime: number }> = ({ generationTime }) => {
172
172
  return (
173
- <Info size={{ width: '600px', height: '30vh' }}>
173
+ <Info>
174
174
  <InfoHeadline1>Relative growth advantage</InfoHeadline1>
175
175
  <InfoParagraph>
176
176
  If variants spread pre-dominantly by local transmission across demographic groups, this estimate