@genspectrum/dashboard-components 0.6.6 → 0.6.8
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.
- package/custom-elements.json +16 -0
- package/dist/dashboard-components.js +226 -145
- package/dist/dashboard-components.js.map +1 -1
- package/dist/style.css +42 -6
- package/package.json +1 -1
- package/src/preact/aggregatedData/aggregate.tsx +1 -1
- package/src/preact/components/color-scale-selector-dropdown.stories.tsx +27 -0
- package/src/preact/components/color-scale-selector-dropdown.tsx +17 -0
- package/src/preact/components/color-scale-selector.stories.tsx +61 -0
- package/src/preact/components/color-scale-selector.tsx +79 -0
- package/src/preact/components/info.stories.tsx +37 -10
- package/src/preact/components/info.tsx +22 -43
- package/src/preact/components/min-max-range-slider.tsx +1 -1
- package/src/preact/components/tooltip.tsx +2 -2
- package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter-info.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +2 -3
- package/src/preact/mutations/mutations.tsx +1 -1
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +61 -35
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +34 -15
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
- package/src/preact/shared/charts/colors.ts +1 -1
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +1 -1
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +1 -1
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +8 -0
- package/src/preact/shared/icons/DeleteIcon.tsx +0 -17
package/dist/style.css
CHANGED
|
@@ -1428,6 +1428,11 @@ html {
|
|
|
1428
1428
|
visibility: visible;
|
|
1429
1429
|
opacity: 1;
|
|
1430
1430
|
}
|
|
1431
|
+
.modal-action {
|
|
1432
|
+
display: flex;
|
|
1433
|
+
margin-top: 1.5rem;
|
|
1434
|
+
justify-content: flex-end;
|
|
1435
|
+
}
|
|
1431
1436
|
:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) {
|
|
1432
1437
|
overflow: hidden;
|
|
1433
1438
|
scrollbar-gutter: stable;
|
|
@@ -1954,6 +1959,11 @@ input.tab:checked + .tab-content,
|
|
|
1954
1959
|
--tw-scale-y: 1;
|
|
1955
1960
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1956
1961
|
}
|
|
1962
|
+
.modal-action > :not([hidden]) ~ :not([hidden]) {
|
|
1963
|
+
--tw-space-x-reverse: 0;
|
|
1964
|
+
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
|
1965
|
+
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
|
1966
|
+
}
|
|
1957
1967
|
@keyframes modal-pop {
|
|
1958
1968
|
|
|
1959
1969
|
0% {
|
|
@@ -2528,6 +2538,9 @@ input.tab:checked + .tab-content,
|
|
|
2528
2538
|
border-end-end-radius: inherit;
|
|
2529
2539
|
border-start-end-radius: inherit;
|
|
2530
2540
|
}
|
|
2541
|
+
.modal-bottom {
|
|
2542
|
+
place-items: end;
|
|
2543
|
+
}
|
|
2531
2544
|
.select-xs {
|
|
2532
2545
|
height: 1.5rem;
|
|
2533
2546
|
min-height: 1.5rem;
|
|
@@ -2834,6 +2847,10 @@ input.tab:checked + .tab-content,
|
|
|
2834
2847
|
margin-left: 0.25rem;
|
|
2835
2848
|
margin-right: 0.25rem;
|
|
2836
2849
|
}
|
|
2850
|
+
.mx-2 {
|
|
2851
|
+
margin-left: 0.5rem;
|
|
2852
|
+
margin-right: 0.5rem;
|
|
2853
|
+
}
|
|
2837
2854
|
.mx-auto {
|
|
2838
2855
|
margin-left: auto;
|
|
2839
2856
|
margin-right: auto;
|
|
@@ -2891,9 +2908,15 @@ input.tab:checked + .tab-content,
|
|
|
2891
2908
|
.hidden {
|
|
2892
2909
|
display: none;
|
|
2893
2910
|
}
|
|
2911
|
+
.h-8 {
|
|
2912
|
+
height: 2rem;
|
|
2913
|
+
}
|
|
2894
2914
|
.h-full {
|
|
2895
2915
|
height: 100%;
|
|
2896
2916
|
}
|
|
2917
|
+
.w-10 {
|
|
2918
|
+
width: 2.5rem;
|
|
2919
|
+
}
|
|
2897
2920
|
.w-16 {
|
|
2898
2921
|
width: 4rem;
|
|
2899
2922
|
}
|
|
@@ -2969,9 +2992,6 @@ input.tab:checked + .tab-content,
|
|
|
2969
2992
|
.overflow-auto {
|
|
2970
2993
|
overflow: auto;
|
|
2971
2994
|
}
|
|
2972
|
-
.overflow-y-auto {
|
|
2973
|
-
overflow-y: auto;
|
|
2974
|
-
}
|
|
2975
2995
|
.whitespace-nowrap {
|
|
2976
2996
|
white-space: nowrap;
|
|
2977
2997
|
}
|
|
@@ -3138,9 +3158,6 @@ input.tab:checked + .tab-content,
|
|
|
3138
3158
|
.underline {
|
|
3139
3159
|
text-decoration-line: underline;
|
|
3140
3160
|
}
|
|
3141
|
-
.opacity-90 {
|
|
3142
|
-
opacity: 0.9;
|
|
3143
|
-
}
|
|
3144
3161
|
.shadow {
|
|
3145
3162
|
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
3146
3163
|
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
|
@@ -3166,6 +3183,25 @@ input.tab:checked + .tab-content,
|
|
|
3166
3183
|
.duration-150 {
|
|
3167
3184
|
transition-duration: 150ms;
|
|
3168
3185
|
}
|
|
3186
|
+
@media (min-width: 640px) {
|
|
3187
|
+
|
|
3188
|
+
.sm\:modal-middle {
|
|
3189
|
+
place-items: center;
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
.sm\:modal-middle :where(.modal-box) {
|
|
3193
|
+
width: 91.666667%;
|
|
3194
|
+
max-width: 32rem;
|
|
3195
|
+
--tw-translate-y: 0px;
|
|
3196
|
+
--tw-scale-x: .9;
|
|
3197
|
+
--tw-scale-y: .9;
|
|
3198
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
3199
|
+
border-top-left-radius: var(--rounded-box, 1rem);
|
|
3200
|
+
border-top-right-radius: var(--rounded-box, 1rem);
|
|
3201
|
+
border-bottom-right-radius: var(--rounded-box, 1rem);
|
|
3202
|
+
border-bottom-left-radius: var(--rounded-box, 1rem);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3169
3205
|
.focus-within\:border-gray-400:focus-within {
|
|
3170
3206
|
--tw-border-opacity: 1;
|
|
3171
3207
|
border-color: rgb(156 163 175 / var(--tw-border-opacity));
|
package/package.json
CHANGED
|
@@ -104,7 +104,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({ data }) => {
|
|
|
104
104
|
return (
|
|
105
105
|
<div class='flex flex-row'>
|
|
106
106
|
<CsvDownloadButton className='mx-1 btn btn-xs' getData={() => data} filename='aggregate.csv' />
|
|
107
|
-
<Info
|
|
107
|
+
<Info>Info for aggregate</Info>
|
|
108
108
|
</div>
|
|
109
109
|
);
|
|
110
110
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { type FunctionComponent } from 'preact';
|
|
3
|
+
import { useState } from 'preact/hooks';
|
|
4
|
+
|
|
5
|
+
import { type ColorScale } from './color-scale-selector';
|
|
6
|
+
import { ColorScaleSelectorDropdown, type ColorScaleSelectorDropdownProps } from './color-scale-selector-dropdown';
|
|
7
|
+
import { ProportionSelector } from './proportion-selector';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<ColorScaleSelectorDropdownProps> = {
|
|
10
|
+
title: 'Component/Color scale selector dropdown',
|
|
11
|
+
component: ProportionSelector,
|
|
12
|
+
parameters: { fetchMock: {} },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
const WrapperWithState: FunctionComponent<{}> = () => {
|
|
18
|
+
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
19
|
+
|
|
20
|
+
return <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ColorScaleSelectorStory: StoryObj<ColorScaleSelectorDropdownProps> = {
|
|
24
|
+
render: () => {
|
|
25
|
+
return <WrapperWithState />;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
import { ColorScaleSelector, type ColorScaleSelectorProps } from './color-scale-selector';
|
|
4
|
+
import { Dropdown } from './dropdown';
|
|
5
|
+
|
|
6
|
+
export type ColorScaleSelectorDropdownProps = ColorScaleSelectorProps;
|
|
7
|
+
|
|
8
|
+
export const ColorScaleSelectorDropdown: FunctionComponent<ColorScaleSelectorDropdownProps> = ({
|
|
9
|
+
colorScale,
|
|
10
|
+
setColorScale,
|
|
11
|
+
}) => {
|
|
12
|
+
return (
|
|
13
|
+
<Dropdown buttonTitle={`Color scale`} placement={'bottom-start'}>
|
|
14
|
+
<ColorScaleSelector colorScale={colorScale} setColorScale={setColorScale} />
|
|
15
|
+
</Dropdown>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';
|
|
3
|
+
import { type FunctionComponent } from 'preact';
|
|
4
|
+
import { useState } from 'preact/hooks';
|
|
5
|
+
|
|
6
|
+
import { type ColorScale, ColorScaleSelector, type ColorScaleSelectorProps } from './color-scale-selector';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<ColorScaleSelectorProps> = {
|
|
9
|
+
title: 'Component/Color scale selector',
|
|
10
|
+
component: ColorScaleSelector,
|
|
11
|
+
parameters: { fetchMock: {} },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
|
|
16
|
+
const WrapperWithState: FunctionComponent<{
|
|
17
|
+
setColorScale: (colorScale: ColorScale) => void;
|
|
18
|
+
}> = ({ setColorScale }) => {
|
|
19
|
+
const [internalColorScale, setInternalColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ColorScaleSelector
|
|
23
|
+
colorScale={internalColorScale}
|
|
24
|
+
setColorScale={(colorScale) => {
|
|
25
|
+
setColorScale(colorScale);
|
|
26
|
+
setInternalColorScale(colorScale);
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const ColorScaleSelectorStory: StoryObj<ColorScaleSelectorProps> = {
|
|
33
|
+
render: (args) => {
|
|
34
|
+
return <WrapperWithState {...args} />;
|
|
35
|
+
},
|
|
36
|
+
args: {
|
|
37
|
+
setColorScale: fn(),
|
|
38
|
+
},
|
|
39
|
+
play: async ({ canvasElement, step, args }) => {
|
|
40
|
+
const canvas = within(canvasElement);
|
|
41
|
+
|
|
42
|
+
await step('Expect initial value to be 0 and value 100%', async () => {
|
|
43
|
+
await expect(canvas.getAllByText('%', { exact: false })[0]).toHaveTextContent('0%');
|
|
44
|
+
await expect(canvas.getAllByText('%', { exact: false })[1]).toHaveTextContent('100%');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await step('Move min slider to 20%', async () => {
|
|
48
|
+
const minSlider = canvas.getAllByRole('slider')[0];
|
|
49
|
+
await fireEvent.input(minSlider, { target: { value: '20' } });
|
|
50
|
+
await expect(args.setColorScale).toHaveBeenCalledWith({ min: 0.2, max: 1, color: 'indigo' });
|
|
51
|
+
await waitFor(() => expect(canvas.getAllByText('%', { exact: false })[0]).toHaveTextContent('20%'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await step('Move max slider to 80%', async () => {
|
|
55
|
+
const maxSlider = canvas.getAllByRole('slider')[1];
|
|
56
|
+
await fireEvent.input(maxSlider, { target: { value: '80' } });
|
|
57
|
+
await expect(args.setColorScale).toHaveBeenCalledWith({ min: 0.2, max: 0.8, color: 'indigo' });
|
|
58
|
+
await waitFor(() => expect(canvas.getAllByText('%', { exact: false })[1]).toHaveTextContent('80%'));
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
import { MinMaxRangeSlider } from './min-max-range-slider';
|
|
4
|
+
import { type GraphColor, singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
5
|
+
import { formatProportion } from '../shared/table/formatProportion';
|
|
6
|
+
|
|
7
|
+
export interface ColorScale {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
color: GraphColor;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ColorScaleSelectorProps {
|
|
14
|
+
colorScale: ColorScale;
|
|
15
|
+
setColorScale: (colorScale: ColorScale) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ColorScaleSelector: FunctionComponent<ColorScaleSelectorProps> = ({ colorScale, setColorScale }) => {
|
|
19
|
+
const colorDisplayCss = `w-10 h-8 border border-gray-200 mx-2 text-xs flex items-center justify-center`;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className='flex items-center'>
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
backgroundColor: singleGraphColorRGBByName(colorScale.color, 0),
|
|
26
|
+
color: 'black',
|
|
27
|
+
}}
|
|
28
|
+
className={colorDisplayCss}
|
|
29
|
+
>
|
|
30
|
+
{formatProportion(colorScale.min, 0)}
|
|
31
|
+
</div>
|
|
32
|
+
<div className='w-64'>
|
|
33
|
+
<MinMaxRangeSlider
|
|
34
|
+
min={colorScale.min * 100}
|
|
35
|
+
max={colorScale.max * 100}
|
|
36
|
+
setMin={(percentage) => {
|
|
37
|
+
setColorScale({ ...colorScale, min: percentage / 100 });
|
|
38
|
+
}}
|
|
39
|
+
setMax={(percentage) => {
|
|
40
|
+
setColorScale({ ...colorScale, max: percentage / 100 });
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div
|
|
45
|
+
style={{
|
|
46
|
+
backgroundColor: singleGraphColorRGBByName(colorScale.color, 1),
|
|
47
|
+
color: 'white',
|
|
48
|
+
}}
|
|
49
|
+
className={colorDisplayCss}
|
|
50
|
+
>
|
|
51
|
+
{formatProportion(colorScale.max, 0)}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getColorWithingScale = (value: number, colorScale: ColorScale) => {
|
|
58
|
+
if (colorScale.min === colorScale.max) {
|
|
59
|
+
return singleGraphColorRGBByName(colorScale.color, 0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const colorRange = colorScale.max - colorScale.min;
|
|
63
|
+
|
|
64
|
+
const alpha = (value - colorScale.min) / colorRange;
|
|
65
|
+
|
|
66
|
+
return singleGraphColorRGBByName(colorScale.color, alpha);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const getTextColorForScale = (value: number, colorScale: ColorScale) => {
|
|
70
|
+
if (colorScale.min === colorScale.max) {
|
|
71
|
+
return 'black';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const colorRange = colorScale.max - colorScale.min;
|
|
75
|
+
|
|
76
|
+
const alpha = (value - colorScale.min) / colorRange;
|
|
77
|
+
|
|
78
|
+
return alpha <= 0.5 ? 'black' : 'white';
|
|
79
|
+
};
|
|
@@ -7,9 +7,6 @@ const meta: Meta<InfoProps> = {
|
|
|
7
7
|
title: 'Component/Info',
|
|
8
8
|
component: Info,
|
|
9
9
|
parameters: { fetchMock: {} },
|
|
10
|
-
args: {
|
|
11
|
-
height: '100px',
|
|
12
|
-
},
|
|
13
10
|
};
|
|
14
11
|
|
|
15
12
|
export default meta;
|
|
@@ -24,20 +21,50 @@ export const InfoStory: StoryObj<InfoProps> = {
|
|
|
24
21
|
),
|
|
25
22
|
};
|
|
26
23
|
|
|
27
|
-
export const
|
|
24
|
+
export const OpenInfo: StoryObj<InfoProps> = {
|
|
28
25
|
...InfoStory,
|
|
29
26
|
play: async ({ canvasElement }) => {
|
|
30
27
|
const canvas = within(canvasElement);
|
|
31
|
-
const openInfo = canvas.getByRole('button', { name: '?' });
|
|
32
28
|
|
|
33
|
-
await
|
|
29
|
+
await openInfo(canvas);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const ShowsAndClosesInfoOnClick: StoryObj<InfoProps> = {
|
|
34
|
+
...InfoStory,
|
|
35
|
+
play: async ({ canvasElement, step }) => {
|
|
36
|
+
const canvas = within(canvasElement);
|
|
34
37
|
|
|
35
|
-
await
|
|
38
|
+
await openInfo(canvas);
|
|
36
39
|
|
|
37
|
-
await
|
|
40
|
+
await step('Close the info dialog by clicking the close button', async () => {
|
|
41
|
+
const dialog = await waitFor(() => canvas.getByRole('dialog'));
|
|
42
|
+
const closeButton = within(dialog).getByRole('button', { name: 'Close' });
|
|
43
|
+
closeButton.click();
|
|
44
|
+
await waitFor(() => expect(canvas.queryByText(tooltipText, { exact: false })).not.toBeVisible());
|
|
45
|
+
});
|
|
38
46
|
|
|
39
|
-
await
|
|
47
|
+
await openInfo(canvas);
|
|
48
|
+
await step('Close the info dialog by clicking the x button', async () => {
|
|
49
|
+
const dialog = await waitFor(() => canvas.getByRole('dialog'));
|
|
50
|
+
const xButton = within(dialog).getByRole('button', { name: '✕' });
|
|
51
|
+
xButton.click();
|
|
52
|
+
await waitFor(() => expect(canvas.queryByText(tooltipText, { exact: false })).not.toBeVisible());
|
|
53
|
+
});
|
|
40
54
|
|
|
41
|
-
await
|
|
55
|
+
await openInfo(canvas);
|
|
56
|
+
await step('Close the info dialog by clicking outside', async () => {
|
|
57
|
+
const dialog = await waitFor(() => canvas.getByRole('dialog'));
|
|
58
|
+
const xButton = within(dialog).getByRole('button', { name: 'Helper to close when clicked outside' });
|
|
59
|
+
xButton.click();
|
|
60
|
+
await waitFor(() => expect(canvas.queryByText(tooltipText, { exact: false })).not.toBeVisible());
|
|
61
|
+
});
|
|
42
62
|
},
|
|
43
63
|
};
|
|
64
|
+
|
|
65
|
+
const openInfo = async (canvas: ReturnType<typeof within>) => {
|
|
66
|
+
const openInfo = canvas.getByRole('button', { name: '?' });
|
|
67
|
+
await waitFor(() => expect(openInfo).toBeInTheDocument());
|
|
68
|
+
await userEvent.click(openInfo);
|
|
69
|
+
await waitFor(() => expect(canvas.getByText(tooltipText, { exact: false })).toBeVisible());
|
|
70
|
+
};
|
|
@@ -1,57 +1,36 @@
|
|
|
1
|
-
import { offset, shift, size } from '@floating-ui/dom';
|
|
2
1
|
import { type FunctionComponent } from 'preact';
|
|
3
|
-
import { useRef
|
|
2
|
+
import { useRef } from 'preact/hooks';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
import { useCloseOnClickOutside, useCloseOnEsc, useFloatingUi } from '../shared/floating-ui/hooks';
|
|
4
|
+
export interface InfoProps {}
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const Info: FunctionComponent<InfoProps> = ({ children, height }) => {
|
|
13
|
-
const [showHelp, setShowHelp] = useState(false);
|
|
14
|
-
const referenceRef = useRef<HTMLButtonElement>(null);
|
|
15
|
-
const floatingRef = useRef<HTMLDivElement>(null);
|
|
16
|
-
|
|
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
|
-
]);
|
|
6
|
+
const Info: FunctionComponent<InfoProps> = ({ children }) => {
|
|
7
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
30
8
|
|
|
31
9
|
const toggleHelp = () => {
|
|
32
|
-
|
|
10
|
+
dialogRef.current?.showModal();
|
|
33
11
|
};
|
|
34
12
|
|
|
35
|
-
useCloseOnEsc(setShowHelp);
|
|
36
|
-
useCloseOnClickOutside(floatingRef, referenceRef, setShowHelp);
|
|
37
|
-
|
|
38
13
|
return (
|
|
39
14
|
<div className='relative'>
|
|
40
|
-
<button type='button' className='btn btn-xs' onClick={toggleHelp}
|
|
15
|
+
<button type='button' className='btn btn-xs' onClick={toggleHelp}>
|
|
41
16
|
?
|
|
42
17
|
</button>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
18
|
+
<dialog ref={dialogRef} className={'modal modal-bottom sm:modal-middle'}>
|
|
19
|
+
<div className='modal-box'>
|
|
20
|
+
<form method='dialog'>
|
|
21
|
+
<button className='btn btn-sm btn-circle btn-ghost absolute right-2 top-2'>✕</button>
|
|
22
|
+
</form>
|
|
23
|
+
<div className={'flex flex-col'}>{children}</div>
|
|
24
|
+
<div className='modal-action'>
|
|
25
|
+
<form method='dialog'>
|
|
26
|
+
<button className={'float-right underline text-sm hover:text-blue-700 mr-2'}>Close</button>
|
|
27
|
+
</form>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<form method='dialog' className='modal-backdrop'>
|
|
31
|
+
<button>Helper to close when clicked outside</button>
|
|
32
|
+
</form>
|
|
33
|
+
</dialog>
|
|
55
34
|
</div>
|
|
56
35
|
);
|
|
57
36
|
};
|
|
@@ -41,8 +41,8 @@ function getPositionCss(position?: TooltipPosition) {
|
|
|
41
41
|
|
|
42
42
|
const Tooltip: FunctionComponent<TooltipProps> = ({ children, content, position = 'bottom' }) => {
|
|
43
43
|
return (
|
|
44
|
-
<div className='relative'>
|
|
45
|
-
<div className='peer'>{children}</div>
|
|
44
|
+
<div className='relative w-full h-full'>
|
|
45
|
+
<div className='peer w-full h-full'>{children}</div>
|
|
46
46
|
<div
|
|
47
47
|
className={`absolute z-10 w-max bg-white p-4 border border-gray-200 rounded-md invisible peer-hover:visible ${getPositionCss(position)}`}
|
|
48
48
|
>
|
|
@@ -188,7 +188,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
188
188
|
getData={() => getMutationComparisonTableData({ content: filteredData }, proportionInterval)}
|
|
189
189
|
filename='mutation_comparison.csv'
|
|
190
190
|
/>
|
|
191
|
-
<Info
|
|
191
|
+
<Info>Info for mutation comparison</Info>
|
|
192
192
|
</>
|
|
193
193
|
);
|
|
194
194
|
};
|
|
@@ -9,7 +9,7 @@ export const MutationFilterInfo = () => {
|
|
|
9
9
|
|
|
10
10
|
const firstGene = referenceGenome.genes[0].name;
|
|
11
11
|
return (
|
|
12
|
-
<Info
|
|
12
|
+
<Info>
|
|
13
13
|
<InfoHeadline1> Mutation Filter</InfoHeadline1>
|
|
14
14
|
<InfoParagraph>This component allows you to filter for mutations at specific positions.</InfoParagraph>
|
|
15
15
|
|
|
@@ -104,7 +104,7 @@ export const FiresFilterChangedEvents: StoryObj<MutationFilterProps> = {
|
|
|
104
104
|
});
|
|
105
105
|
|
|
106
106
|
await step('Remove the first mutation', async () => {
|
|
107
|
-
const firstMutationDeleteButton = canvas.getAllByRole('button')[1];
|
|
107
|
+
const firstMutationDeleteButton = canvas.getAllByRole('button', { name: '✕' })[1];
|
|
108
108
|
await waitFor(() => fireEvent.click(firstMutationDeleteButton));
|
|
109
109
|
|
|
110
110
|
await expect(changedListenerMock).toHaveBeenCalledWith(
|
|
@@ -8,7 +8,6 @@ import { type Deletion, type Insertion, type Mutation, type Substitution } from
|
|
|
8
8
|
import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
|
|
9
9
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
10
10
|
import { singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
11
|
-
import { DeleteIcon } from '../shared/icons/DeleteIcon';
|
|
12
11
|
|
|
13
12
|
export interface MutationFilterInnerProps {
|
|
14
13
|
initialValue?: SelectedMutationFilterStrings | string[] | undefined;
|
|
@@ -312,8 +311,8 @@ const SelectedFilter = <MutationType extends Mutation>({
|
|
|
312
311
|
style={{ backgroundColor, color: textColor }}
|
|
313
312
|
>
|
|
314
313
|
{mutation.toString()}
|
|
315
|
-
<button type='button' onClick={() => onDelete(mutation)}>
|
|
316
|
-
|
|
314
|
+
<button className='ml-1' type='button' onClick={() => onDelete(mutation)}>
|
|
315
|
+
✕
|
|
317
316
|
</button>
|
|
318
317
|
</span>
|
|
319
318
|
);
|