@genspectrum/dashboard-components 0.4.0 → 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.
@@ -1,7 +1,9 @@
1
- import { autoUpdate, computePosition, offset, shift, size } from '@floating-ui/dom';
1
+ import { offset, shift, size } from '@floating-ui/dom';
2
2
  import { type FunctionComponent } from 'preact';
3
- import { useEffect, useRef, useState } from 'preact/hooks';
4
- import { type MutableRefObject } from 'react';
3
+ import { useRef, useState } from 'preact/hooks';
4
+
5
+ import { dropdownClass } from './dropdown';
6
+ import { useCloseOnClickOutside, useCloseOnEsc, useFloatingUi } from '../shared/floating-ui/hooks';
5
7
 
6
8
  export interface InfoProps {
7
9
  height?: string;
@@ -12,7 +14,19 @@ const Info: FunctionComponent<InfoProps> = ({ children, height }) => {
12
14
  const referenceRef = useRef<HTMLButtonElement>(null);
13
15
  const floatingRef = useRef<HTMLDivElement>(null);
14
16
 
15
- useFloatingUi(referenceRef, floatingRef, height, showHelp);
17
+ useFloatingUi(referenceRef, floatingRef, [
18
+ offset(10),
19
+ shift(),
20
+ size({
21
+ apply() {
22
+ if (!floatingRef.current) {
23
+ return;
24
+ }
25
+ floatingRef.current.style.width = '100vw';
26
+ floatingRef.current.style.height = height ? height : '50vh';
27
+ },
28
+ }),
29
+ ]);
16
30
 
17
31
  const toggleHelp = () => {
18
32
  setShowHelp(!showHelp);
@@ -22,14 +36,13 @@ const Info: FunctionComponent<InfoProps> = ({ children, height }) => {
22
36
  useCloseOnClickOutside(floatingRef, referenceRef, setShowHelp);
23
37
 
24
38
  return (
25
- <div className='relative z-10'>
39
+ <div className='relative'>
26
40
  <button type='button' className='btn btn-xs' onClick={toggleHelp} ref={referenceRef}>
27
41
  ?
28
42
  </button>
29
43
  <div
30
44
  ref={floatingRef}
31
- className='bg-white p-2 border border-gray-100 shadow-lg rounded overflow-y-auto opacity-90'
32
- style={{ position: 'absolute', zIndex: 10, display: showHelp ? '' : 'none' }}
45
+ className={`${dropdownClass} overflow-y-auto opacity-90 ${showHelp ? '' : 'hidden'}`}
33
46
  >
34
47
  <div className={'flex flex-col'}>{children}</div>
35
48
  <button
@@ -43,93 +56,6 @@ const Info: FunctionComponent<InfoProps> = ({ children, height }) => {
43
56
  );
44
57
  };
45
58
 
46
- function useFloatingUi(
47
- referenceRef: MutableRefObject<HTMLButtonElement | null>,
48
- floatingRef: MutableRefObject<HTMLDivElement | null>,
49
- height: string | undefined,
50
- showHelp: boolean,
51
- ) {
52
- const cleanupRef = useRef<Function | null>(null);
53
-
54
- useEffect(() => {
55
- if (!referenceRef.current || !floatingRef.current) {
56
- return;
57
- }
58
-
59
- const { current: reference } = referenceRef;
60
- const { current: floating } = floatingRef;
61
-
62
- const update = () => {
63
- computePosition(reference, floating, {
64
- middleware: [
65
- offset(10),
66
- shift(),
67
- size({
68
- apply({}) {
69
- floating.style.width = '100vw';
70
- floating.style.height = height ? height : '50vh';
71
- },
72
- }),
73
- ],
74
- }).then(({ x, y }) => {
75
- floating.style.left = `${x}px`;
76
- floating.style.top = `${y}px`;
77
- });
78
- };
79
-
80
- update();
81
- cleanupRef.current = autoUpdate(reference, floating, update);
82
-
83
- return () => {
84
- if (cleanupRef.current) {
85
- cleanupRef.current();
86
- }
87
- };
88
- }, [showHelp, height, referenceRef, floatingRef]);
89
- }
90
-
91
- function useCloseOnClickOutside(
92
- floatingRef: MutableRefObject<HTMLDivElement | null>,
93
- referenceRef: MutableRefObject<HTMLButtonElement | null>,
94
- setShowHelp: (value: ((prevState: boolean) => boolean) | boolean) => void,
95
- ) {
96
- useEffect(() => {
97
- const handleClickOutside = (event: MouseEvent) => {
98
- const path = event.composedPath();
99
- if (
100
- floatingRef.current &&
101
- !path.includes(floatingRef.current) &&
102
- referenceRef.current &&
103
- !path.includes(referenceRef.current)
104
- ) {
105
- setShowHelp(false);
106
- }
107
- };
108
-
109
- document.addEventListener('mousedown', handleClickOutside);
110
-
111
- return () => {
112
- document.removeEventListener('mousedown', handleClickOutside);
113
- };
114
- }, [floatingRef, referenceRef, setShowHelp]);
115
- }
116
-
117
- function useCloseOnEsc(setShowHelp: (value: ((prevState: boolean) => boolean) | boolean) => void) {
118
- useEffect(() => {
119
- const handleKeyDown = (event: KeyboardEvent) => {
120
- if (event.key === 'Escape') {
121
- setShowHelp(false);
122
- }
123
- };
124
-
125
- document.addEventListener('keydown', handleKeyDown);
126
-
127
- return () => {
128
- document.removeEventListener('keydown', handleKeyDown);
129
- };
130
- }, [setShowHelp]);
131
- }
132
-
133
59
  export const InfoHeadline1: FunctionComponent = ({ children }) => {
134
60
  return <h1 className='text-lg font-bold'>{children}</h1>;
135
61
  };
@@ -21,7 +21,6 @@ export const MutationTypeSelector: FunctionComponent<MutationTypeSelectorProps>
21
21
 
22
22
  return (
23
23
  <CheckboxSelector
24
- className='mx-1'
25
24
  items={displayedMutationTypes}
26
25
  label={mutationTypesSelectorLabel}
27
26
  setItems={(items) => setDisplayedMutationTypes(items)}
@@ -1,33 +1,24 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
 
3
+ import { Dropdown } from './dropdown';
3
4
  import { ProportionSelector, type ProportionSelectorProps } from './proportion-selector';
4
5
 
5
- export interface ProportionSelectorDropdownProps extends ProportionSelectorProps {
6
- openDirection?: 'left' | 'right';
7
- }
6
+ export type ProportionSelectorDropdownProps = ProportionSelectorProps;
8
7
 
9
8
  export const ProportionSelectorDropdown: FunctionComponent<ProportionSelectorDropdownProps> = ({
10
9
  proportionInterval,
11
10
  setMinProportion,
12
11
  setMaxProportion,
13
- openDirection = 'right',
14
12
  }) => {
15
13
  const label = `${(proportionInterval.min * 100).toFixed(1)}% - ${(proportionInterval.max * 100).toFixed(1)}%`;
16
14
 
17
15
  return (
18
- <div class={`dropdown ${openDirection === 'left' ? 'dropdown-end' : ''}`}>
19
- <div tabIndex={0} role='button' class='btn btn-xs whitespace-nowrap'>
20
- Proportion {label}
21
- </div>
22
- <ul tabIndex={0} class='p-2 shadow menu dropdown-content z-[1] bg-base-100 rounded-box w-72'>
23
- <div class='mb-2 ml-2'>
24
- <ProportionSelector
25
- proportionInterval={proportionInterval}
26
- setMinProportion={setMinProportion}
27
- setMaxProportion={setMaxProportion}
28
- />
29
- </div>
30
- </ul>
31
- </div>
16
+ <Dropdown buttonTitle={`Proportion ${label}`} placement={'bottom-start'}>
17
+ <ProportionSelector
18
+ proportionInterval={proportionInterval}
19
+ setMinProportion={setMinProportion}
20
+ setMaxProportion={setMaxProportion}
21
+ />
22
+ </Dropdown>
32
23
  );
33
24
  };
@@ -17,11 +17,20 @@ const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
17
17
  const [heightOfTabs, setHeightOfTabs] = useState('3rem');
18
18
  const tabRef = useRef<HTMLDivElement>(null);
19
19
 
20
- useEffect(() => {
20
+ const updateHeightOfTabs = () => {
21
21
  if (tabRef.current) {
22
22
  const heightOfTabs = tabRef.current.getBoundingClientRect().height;
23
23
  setHeightOfTabs(`${heightOfTabs}px`);
24
24
  }
25
+ };
26
+
27
+ useEffect(() => {
28
+ updateHeightOfTabs();
29
+
30
+ window.addEventListener('resize', updateHeightOfTabs);
31
+ return () => {
32
+ window.removeEventListener('resize', updateHeightOfTabs);
33
+ };
25
34
  }, []);
26
35
 
27
36
  const tabElements = (
@@ -51,9 +60,9 @@ const Tabs: FunctionComponent<ComponentTabsProps> = ({ tabs, toolbar }) => {
51
60
 
52
61
  return (
53
62
  <div className='h-full w-full'>
54
- <div ref={tabRef} className='flex flex-row justify-between'>
63
+ <div ref={tabRef} className='flex flex-row justify-between flex-wrap'>
55
64
  {tabElements}
56
- {toolbar && <div className='py-2'>{toolbarElement}</div>}
65
+ {toolbar && <div className='py-2 flex flex-wrap gap-y-1'>{toolbarElement}</div>}
57
66
  </div>
58
67
  <div
59
68
  className={`p-2 border-2 border-gray-100 rounded-b-md rounded-tr-md ${activeTab === tabs[0].title ? '' : 'rounded-tl-md'}`}
@@ -179,11 +179,11 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
179
179
  };
180
180
 
181
181
  return (
182
- <div class='join w-full' ref={divRef}>
182
+ <div class='flex flex-wrap' ref={divRef}>
183
183
  <Select
184
184
  items={getSelectableOptions(customSelectOptions)}
185
185
  selected={selectedDateRange}
186
- selectStyle='select-bordered rounded-none join-item grow'
186
+ selectStyle='select-bordered rounded-none flex-grow w-40'
187
187
  onChange={(event: Event) => {
188
188
  event.preventDefault();
189
189
  const select = event.target as HTMLSelectElement;
@@ -191,22 +191,26 @@ export const DateRangeSelectorInner = <CustomLabel extends string>({
191
191
  onSelectChange(value as CustomLabel | PresetOptionValues);
192
192
  }}
193
193
  />
194
- <input
195
- class='input input-bordered rounded-none join-item grow'
196
- type='text'
197
- placeholder='Date from'
198
- ref={fromDatePickerRef}
199
- onChange={onChangeDateFrom}
200
- onBlur={onChangeDateFrom}
201
- />
202
- <input
203
- class='input input-bordered rounded-none join-item grow'
204
- type='text'
205
- placeholder='Date to'
206
- ref={toDatePickerRef}
207
- onChange={onChangeDateTo}
208
- onBlur={onChangeDateTo}
209
- />
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>
210
214
  </div>
211
215
  );
212
216
  };
@@ -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 }))}
@@ -184,6 +184,6 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
184
184
  filename='mutation_comparison.csv'
185
185
  />
186
186
  <Info height={'100px'}>Info for mutation comparison</Info>
187
- </div>
187
+ </>
188
188
  );
189
189
  };
@@ -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'
@@ -206,6 +205,6 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
206
205
  />
207
206
  )}
208
207
  <Info height={'100px'}>Info for mutations</Info>
209
- </div>
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,11 +226,11 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
226
226
  />
227
227
 
228
228
  <PrevalenceOverTimeInfo />
229
- </div>
229
+ </>
230
230
  );
231
231
  };
232
232
 
233
- const PrevalenceOverTimeInfo: FunctionComponent = ({}) => {
233
+ const PrevalenceOverTimeInfo: FunctionComponent = () => {
234
234
  return (
235
235
  <Info height={'100px'}>
236
236
  <InfoHeadline1>Prevalence over time</InfoHeadline1>
@@ -238,5 +238,3 @@ const PrevalenceOverTimeInfo: FunctionComponent = ({}) => {
238
238
  </Info>
239
239
  );
240
240
  };
241
-
242
- export default PrevalenceOverTime;
@@ -161,10 +161,10 @@ 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
 
@@ -0,0 +1,83 @@
1
+ import { autoUpdate, computePosition, type Middleware } from '@floating-ui/dom';
2
+ import type { Placement } from '@floating-ui/utils';
3
+ import { useEffect, useRef } from 'preact/hooks';
4
+ import type { MutableRefObject } from 'react';
5
+
6
+ export function useFloatingUi(
7
+ referenceRef: MutableRefObject<HTMLElement | null>,
8
+ floatingRef: MutableRefObject<HTMLElement | null>,
9
+ middleware?: Array<Middleware | null | undefined | false>,
10
+ placement?: Placement,
11
+ ) {
12
+ const cleanupRef = useRef<Function | null>(null);
13
+
14
+ useEffect(() => {
15
+ if (!referenceRef.current || !floatingRef.current) {
16
+ return;
17
+ }
18
+
19
+ const { current: reference } = referenceRef;
20
+ const { current: floating } = floatingRef;
21
+
22
+ const update = () => {
23
+ computePosition(reference, floating, {
24
+ placement,
25
+ middleware,
26
+ }).then(({ x, y }) => {
27
+ floating.style.left = `${x}px`;
28
+ floating.style.top = `${y}px`;
29
+ });
30
+ };
31
+
32
+ update();
33
+ cleanupRef.current = autoUpdate(reference, floating, update);
34
+
35
+ return () => {
36
+ if (cleanupRef.current) {
37
+ cleanupRef.current();
38
+ }
39
+ };
40
+ }, [placement, middleware, referenceRef, floatingRef]);
41
+ }
42
+
43
+ export function useCloseOnClickOutside(
44
+ floatingRef: MutableRefObject<HTMLElement | null>,
45
+ referenceRef: MutableRefObject<HTMLElement | null>,
46
+ setShowContent: (value: ((prevState: boolean) => boolean) | boolean) => void,
47
+ ) {
48
+ useEffect(() => {
49
+ const handleClickOutside = (event: MouseEvent) => {
50
+ const path = event.composedPath();
51
+ if (
52
+ floatingRef.current &&
53
+ !path.includes(floatingRef.current) &&
54
+ referenceRef.current &&
55
+ !path.includes(referenceRef.current)
56
+ ) {
57
+ setShowContent(false);
58
+ }
59
+ };
60
+
61
+ document.addEventListener('mousedown', handleClickOutside);
62
+
63
+ return () => {
64
+ document.removeEventListener('mousedown', handleClickOutside);
65
+ };
66
+ }, [floatingRef, referenceRef, setShowContent]);
67
+ }
68
+
69
+ export function useCloseOnEsc(setShowHelp: (value: ((prevState: boolean) => boolean) | boolean) => void) {
70
+ useEffect(() => {
71
+ const handleKeyDown = (event: KeyboardEvent) => {
72
+ if (event.key === 'Escape') {
73
+ setShowHelp(false);
74
+ }
75
+ };
76
+
77
+ document.addEventListener('keydown', handleKeyDown);
78
+
79
+ return () => {
80
+ document.removeEventListener('keydown', handleKeyDown);
81
+ };
82
+ }, [setShowHelp]);
83
+ }
@@ -1,6 +1,6 @@
1
1
  import { customElement, property } from 'lit/decorators.js';
2
2
 
3
- import PrevalenceOverTime, { type PrevalenceOverTimeProps } from '../../preact/prevalenceOverTime/prevalence-over-time';
3
+ import { PrevalenceOverTime, type PrevalenceOverTimeProps } from '../../preact/prevalenceOverTime/prevalence-over-time';
4
4
  import { type Equals, type Expect } from '../../utils/typeAssertions';
5
5
  import { PreactLitAdapterWithGridJsStyles } from '../PreactLitAdapterWithGridJsStyles';
6
6