@genspectrum/dashboard-components 0.6.11 → 0.6.12
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/dist/dashboard-components.js +1072 -853
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +7 -7
- package/dist/style.css +113 -0
- package/package.json +2 -2
- package/src/preact/components/checkbox-selector.stories.tsx +93 -11
- package/src/preact/components/checkbox-selector.tsx +19 -0
- package/src/preact/components/color-scale-selector-dropdown.tsx +5 -3
- package/src/preact/components/dropdown.tsx +3 -3
- package/src/preact/components/mutation-type-selector.stories.tsx +115 -0
- package/src/preact/components/mutation-type-selector.tsx +33 -8
- package/src/preact/components/percent-input.stories.tsx +93 -0
- package/src/preact/components/percent-intput.tsx +4 -0
- package/src/preact/components/proportion-selector-dropdown.stories.tsx +2 -2
- package/src/preact/components/proportion-selector-dropdown.tsx +9 -7
- package/src/preact/components/proportion-selector.stories.tsx +4 -4
- package/src/preact/components/proportion-selector.tsx +46 -12
- package/src/preact/components/segment-selector.stories.tsx +151 -0
- package/src/preact/components/{SegmentSelector.tsx → segment-selector.tsx} +29 -20
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +1 -1
- package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
- package/src/preact/mutationComparison/queryMutationData.ts +1 -1
- package/src/preact/mutations/mutations-grid.tsx +5 -1
- package/src/preact/mutations/mutations.tsx +1 -1
- package/src/preact/mutations/queryMutations.ts +1 -1
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +4 -4
- package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +3 -2
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -1
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +3 -2
- package/src/preact/useQuery.ts +1 -1
- package/src/query/queryMutationsOverTime.ts +3 -3
- package/src/utils/map2d.spec.ts +83 -22
- package/src/utils/map2d.ts +158 -0
- package/src/utils/Map2d.ts +0 -75
|
@@ -990,14 +990,14 @@ declare global {
|
|
|
990
990
|
|
|
991
991
|
declare global {
|
|
992
992
|
interface HTMLElementTagNameMap {
|
|
993
|
-
'gs-
|
|
993
|
+
'gs-number-sequences-over-time': NumberSequencesOverTimeComponent;
|
|
994
994
|
}
|
|
995
995
|
}
|
|
996
996
|
|
|
997
997
|
|
|
998
998
|
declare global {
|
|
999
999
|
interface HTMLElementTagNameMap {
|
|
1000
|
-
'gs-
|
|
1000
|
+
'gs-aggregate-component': AggregateComponent;
|
|
1001
1001
|
}
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
@@ -1041,21 +1041,21 @@ declare global {
|
|
|
1041
1041
|
|
|
1042
1042
|
declare global {
|
|
1043
1043
|
interface HTMLElementTagNameMap {
|
|
1044
|
-
'gs-
|
|
1044
|
+
'gs-lineage-filter': LineageFilterComponent;
|
|
1045
1045
|
}
|
|
1046
1046
|
interface HTMLElementEventMap {
|
|
1047
|
-
'gs-
|
|
1048
|
-
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
1047
|
+
'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
|
|
1049
1048
|
}
|
|
1050
1049
|
}
|
|
1051
1050
|
|
|
1052
1051
|
|
|
1053
1052
|
declare global {
|
|
1054
1053
|
interface HTMLElementTagNameMap {
|
|
1055
|
-
'gs-
|
|
1054
|
+
'gs-mutation-filter': MutationFilterComponent;
|
|
1056
1055
|
}
|
|
1057
1056
|
interface HTMLElementEventMap {
|
|
1058
|
-
'gs-
|
|
1057
|
+
'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
|
|
1058
|
+
'gs-mutation-filter-on-blur': CustomEvent<SelectedMutationFilterStrings>;
|
|
1059
1059
|
}
|
|
1060
1060
|
}
|
|
1061
1061
|
|
package/dist/style.css
CHANGED
|
@@ -983,6 +983,39 @@ html {
|
|
|
983
983
|
--tw-contain-paint: ;
|
|
984
984
|
--tw-contain-style: ;
|
|
985
985
|
}
|
|
986
|
+
.container {
|
|
987
|
+
width: 100%;
|
|
988
|
+
}
|
|
989
|
+
@media (min-width: 640px) {
|
|
990
|
+
|
|
991
|
+
.container {
|
|
992
|
+
max-width: 640px;
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
@media (min-width: 768px) {
|
|
996
|
+
|
|
997
|
+
.container {
|
|
998
|
+
max-width: 768px;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
@media (min-width: 1024px) {
|
|
1002
|
+
|
|
1003
|
+
.container {
|
|
1004
|
+
max-width: 1024px;
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
@media (min-width: 1280px) {
|
|
1008
|
+
|
|
1009
|
+
.container {
|
|
1010
|
+
max-width: 1280px;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
@media (min-width: 1536px) {
|
|
1014
|
+
|
|
1015
|
+
.container {
|
|
1016
|
+
max-width: 1536px;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
986
1019
|
.alert {
|
|
987
1020
|
display: grid;
|
|
988
1021
|
width: 100%;
|
|
@@ -1024,6 +1057,15 @@ html {
|
|
|
1024
1057
|
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
|
|
1025
1058
|
}
|
|
1026
1059
|
|
|
1060
|
+
.menu li > *:not(ul, .menu-title, details, .btn):active,
|
|
1061
|
+
.menu li > *:not(ul, .menu-title, details, .btn).active,
|
|
1062
|
+
.menu li > details > summary:active {
|
|
1063
|
+
--tw-bg-opacity: 1;
|
|
1064
|
+
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
|
|
1065
|
+
--tw-text-opacity: 1;
|
|
1066
|
+
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1027
1069
|
.tab:hover {
|
|
1028
1070
|
--tw-text-opacity: 1;
|
|
1029
1071
|
}
|
|
@@ -1117,6 +1159,25 @@ html {
|
|
|
1117
1159
|
container-type: inline-size;
|
|
1118
1160
|
grid-template-columns: auto 1fr;
|
|
1119
1161
|
}
|
|
1162
|
+
.divider {
|
|
1163
|
+
display: flex;
|
|
1164
|
+
flex-direction: row;
|
|
1165
|
+
align-items: center;
|
|
1166
|
+
align-self: stretch;
|
|
1167
|
+
margin-top: 1rem;
|
|
1168
|
+
margin-bottom: 1rem;
|
|
1169
|
+
height: 1rem;
|
|
1170
|
+
white-space: nowrap;
|
|
1171
|
+
}
|
|
1172
|
+
.divider:before,
|
|
1173
|
+
.divider:after {
|
|
1174
|
+
height: 0.125rem;
|
|
1175
|
+
width: 100%;
|
|
1176
|
+
flex-grow: 1;
|
|
1177
|
+
--tw-content: '';
|
|
1178
|
+
content: var(--tw-content);
|
|
1179
|
+
background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
|
|
1180
|
+
}
|
|
1120
1181
|
.dropdown {
|
|
1121
1182
|
position: relative;
|
|
1122
1183
|
display: inline-block;
|
|
@@ -1633,6 +1694,11 @@ input.tab:checked + .tab-content,
|
|
|
1633
1694
|
--alert-bg: var(--fallback-er,oklch(var(--er)/1));
|
|
1634
1695
|
--alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
|
|
1635
1696
|
}
|
|
1697
|
+
.btm-nav > *:where(.active) {
|
|
1698
|
+
border-top-width: 2px;
|
|
1699
|
+
--tw-bg-opacity: 1;
|
|
1700
|
+
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
|
1701
|
+
}
|
|
1636
1702
|
.btm-nav > *.disabled,
|
|
1637
1703
|
.btm-nav > *[disabled] {
|
|
1638
1704
|
pointer-events: none;
|
|
@@ -1785,6 +1851,9 @@ input.tab:checked + .tab-content,
|
|
|
1785
1851
|
background-position-y: 0;
|
|
1786
1852
|
}
|
|
1787
1853
|
}
|
|
1854
|
+
.divider:not(:empty) {
|
|
1855
|
+
gap: 1rem;
|
|
1856
|
+
}
|
|
1788
1857
|
.dropdown.dropdown-open .dropdown-content,
|
|
1789
1858
|
.dropdown:focus .dropdown-content,
|
|
1790
1859
|
.dropdown:focus-within .dropdown-content {
|
|
@@ -1900,6 +1969,14 @@ input.tab:checked + .tab-content,
|
|
|
1900
1969
|
outline: 2px solid transparent;
|
|
1901
1970
|
outline-offset: 2px;
|
|
1902
1971
|
}
|
|
1972
|
+
.menu li > *:not(ul, .menu-title, details, .btn):active,
|
|
1973
|
+
.menu li > *:not(ul, .menu-title, details, .btn).active,
|
|
1974
|
+
.menu li > details > summary:active {
|
|
1975
|
+
--tw-bg-opacity: 1;
|
|
1976
|
+
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
|
|
1977
|
+
--tw-text-opacity: 1;
|
|
1978
|
+
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
|
1979
|
+
}
|
|
1903
1980
|
.mockup-phone .display {
|
|
1904
1981
|
overflow: hidden;
|
|
1905
1982
|
border-radius: 40px;
|
|
@@ -2396,6 +2473,12 @@ input.tab:checked + .tab-content,
|
|
|
2396
2473
|
--tw-bg-opacity: 1;
|
|
2397
2474
|
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
|
2398
2475
|
}
|
|
2476
|
+
.table-zebra tr.active,
|
|
2477
|
+
.table-zebra tr.active:nth-child(even),
|
|
2478
|
+
.table-zebra-zebra tbody tr:nth-child(even) {
|
|
2479
|
+
--tw-bg-opacity: 1;
|
|
2480
|
+
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
|
|
2481
|
+
}
|
|
2399
2482
|
.table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) {
|
|
2400
2483
|
border-bottom-width: 1px;
|
|
2401
2484
|
--tw-border-opacity: 1;
|
|
@@ -2468,6 +2551,18 @@ input.tab:checked + .tab-content,
|
|
|
2468
2551
|
--togglehandleborder: 0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset,
|
|
2469
2552
|
var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset;
|
|
2470
2553
|
}
|
|
2554
|
+
.btm-nav-xs > *:where(.active) {
|
|
2555
|
+
border-top-width: 1px;
|
|
2556
|
+
}
|
|
2557
|
+
.btm-nav-sm > *:where(.active) {
|
|
2558
|
+
border-top-width: 2px;
|
|
2559
|
+
}
|
|
2560
|
+
.btm-nav-md > *:where(.active) {
|
|
2561
|
+
border-top-width: 2px;
|
|
2562
|
+
}
|
|
2563
|
+
.btm-nav-lg > *:where(.active) {
|
|
2564
|
+
border-top-width: 4px;
|
|
2565
|
+
}
|
|
2471
2566
|
.btn-xs {
|
|
2472
2567
|
height: 1.5rem;
|
|
2473
2568
|
min-height: 1.5rem;
|
|
@@ -2887,6 +2982,9 @@ input.tab:checked + .tab-content,
|
|
|
2887
2982
|
margin-top: 1rem;
|
|
2888
2983
|
margin-bottom: 1rem;
|
|
2889
2984
|
}
|
|
2985
|
+
.mb-0 {
|
|
2986
|
+
margin-bottom: 0px;
|
|
2987
|
+
}
|
|
2890
2988
|
.mb-1 {
|
|
2891
2989
|
margin-bottom: 0.25rem;
|
|
2892
2990
|
}
|
|
@@ -2911,6 +3009,9 @@ input.tab:checked + .tab-content,
|
|
|
2911
3009
|
.mr-2 {
|
|
2912
3010
|
margin-right: 0.5rem;
|
|
2913
3011
|
}
|
|
3012
|
+
.mt-0 {
|
|
3013
|
+
margin-top: 0px;
|
|
3014
|
+
}
|
|
2914
3015
|
.mt-0\.5 {
|
|
2915
3016
|
margin-top: 0.125rem;
|
|
2916
3017
|
}
|
|
@@ -2950,12 +3051,24 @@ input.tab:checked + .tab-content,
|
|
|
2950
3051
|
.w-16 {
|
|
2951
3052
|
width: 4rem;
|
|
2952
3053
|
}
|
|
3054
|
+
.w-20 {
|
|
3055
|
+
width: 5rem;
|
|
3056
|
+
}
|
|
3057
|
+
.w-24 {
|
|
3058
|
+
width: 6rem;
|
|
3059
|
+
}
|
|
2953
3060
|
.w-32 {
|
|
2954
3061
|
width: 8rem;
|
|
2955
3062
|
}
|
|
3063
|
+
.w-44 {
|
|
3064
|
+
width: 11rem;
|
|
3065
|
+
}
|
|
2956
3066
|
.w-64 {
|
|
2957
3067
|
width: 16rem;
|
|
2958
3068
|
}
|
|
3069
|
+
.w-\[6rem\] {
|
|
3070
|
+
width: 6rem;
|
|
3071
|
+
}
|
|
2959
3072
|
.w-\[7\.5rem\] {
|
|
2960
3073
|
width: 7.5rem;
|
|
2961
3074
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.12",
|
|
4
4
|
"description": "GenSpectrum web components for building dashboards",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"@storybook/types": "^8.0.9",
|
|
91
91
|
"@storybook/web-components": "^8.0.9",
|
|
92
92
|
"@storybook/web-components-vite": "^8.0.9",
|
|
93
|
-
"@types/node": "^
|
|
93
|
+
"@types/node": "^22.0.0",
|
|
94
94
|
"@types/object-hash": "^3.0.6",
|
|
95
95
|
"@typescript-eslint/eslint-plugin": "^7.14.1",
|
|
96
96
|
"@typescript-eslint/parser": "^7.14.1",
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
2
|
import { expect, fn, waitFor, within } from '@storybook/test';
|
|
3
|
+
import { type FunctionComponent } from 'preact';
|
|
4
|
+
import { useState } from 'preact/hooks';
|
|
3
5
|
|
|
4
6
|
import { type CheckboxItem, CheckboxSelector, type CheckboxSelectorProps } from './checkbox-selector';
|
|
5
7
|
|
|
@@ -14,12 +16,33 @@ const meta: Meta<CheckboxSelectorProps> = {
|
|
|
14
16
|
|
|
15
17
|
export default meta;
|
|
16
18
|
|
|
19
|
+
const WrapperWithState: FunctionComponent<CheckboxSelectorProps> = ({
|
|
20
|
+
items: initialItems,
|
|
21
|
+
label,
|
|
22
|
+
setItems: setItemsMock,
|
|
23
|
+
}) => {
|
|
24
|
+
const [items, setItems] = useState<CheckboxItem[]>(initialItems);
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className='w-32'>
|
|
28
|
+
<CheckboxSelector
|
|
29
|
+
items={items}
|
|
30
|
+
label={label}
|
|
31
|
+
setItems={(items: CheckboxItem[]) => {
|
|
32
|
+
setItemsMock(items);
|
|
33
|
+
setItems(items);
|
|
34
|
+
}}
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
17
40
|
export const CheckboxSelectorStory: StoryObj<CheckboxSelectorProps> = {
|
|
18
41
|
render: (args) => {
|
|
19
42
|
let wrapperStateItems = args.items;
|
|
20
43
|
|
|
21
44
|
return (
|
|
22
|
-
<
|
|
45
|
+
<WrapperWithState
|
|
23
46
|
items={wrapperStateItems}
|
|
24
47
|
label={args.label}
|
|
25
48
|
setItems={(items: CheckboxItem[]) => {
|
|
@@ -37,20 +60,79 @@ export const CheckboxSelectorStory: StoryObj<CheckboxSelectorProps> = {
|
|
|
37
60
|
label: 'Some label',
|
|
38
61
|
setItems: fn(),
|
|
39
62
|
},
|
|
40
|
-
play: async ({ canvasElement, args }) => {
|
|
63
|
+
play: async ({ canvasElement, args, step }) => {
|
|
41
64
|
const canvas = within(canvasElement);
|
|
42
65
|
|
|
43
|
-
const open = () => canvas.getByText('Some label'
|
|
66
|
+
const open = () => canvas.getByText('Some label');
|
|
67
|
+
const selectAll = () => canvas.getByText('Select all');
|
|
68
|
+
const selectNone = () => canvas.getByText('Select none');
|
|
69
|
+
const firstItem = () => canvas.getByLabelText('item1');
|
|
44
70
|
open().click();
|
|
45
71
|
|
|
46
|
-
|
|
47
|
-
|
|
72
|
+
await step('Select one item', async () => {
|
|
73
|
+
firstItem().click();
|
|
48
74
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
75
|
+
await waitFor(() =>
|
|
76
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
77
|
+
{ checked: true, label: 'item1' },
|
|
78
|
+
{ checked: false, label: 'item2' },
|
|
79
|
+
]),
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
await step('Select all items with one item already selected', async () => {
|
|
84
|
+
selectAll().click();
|
|
85
|
+
|
|
86
|
+
await waitFor(() =>
|
|
87
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
88
|
+
{ checked: true, label: 'item1' },
|
|
89
|
+
{ checked: true, label: 'item2' },
|
|
90
|
+
]),
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
await step('Deselect one item', async () => {
|
|
95
|
+
firstItem().click();
|
|
96
|
+
|
|
97
|
+
await waitFor(() =>
|
|
98
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
99
|
+
{ checked: false, label: 'item1' },
|
|
100
|
+
{ checked: true, label: 'item2' },
|
|
101
|
+
]),
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await step('Select none with one item already selected', async () => {
|
|
106
|
+
selectNone().click();
|
|
107
|
+
|
|
108
|
+
await waitFor(() =>
|
|
109
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
110
|
+
{ checked: false, label: 'item1' },
|
|
111
|
+
{ checked: false, label: 'item2' },
|
|
112
|
+
]),
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
await step('Select all items with none selected', async () => {
|
|
117
|
+
selectAll().click();
|
|
118
|
+
|
|
119
|
+
await waitFor(() =>
|
|
120
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
121
|
+
{ checked: true, label: 'item1' },
|
|
122
|
+
{ checked: true, label: 'item2' },
|
|
123
|
+
]),
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
await step('Select none with all items selected', async () => {
|
|
128
|
+
selectNone().click();
|
|
129
|
+
|
|
130
|
+
await waitFor(() =>
|
|
131
|
+
expect(args.setItems).toHaveBeenCalledWith([
|
|
132
|
+
{ checked: false, label: 'item1' },
|
|
133
|
+
{ checked: false, label: 'item2' },
|
|
134
|
+
]),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
55
137
|
},
|
|
56
138
|
};
|
|
@@ -18,6 +18,25 @@ export const CheckboxSelector = <Item extends CheckboxItem>({
|
|
|
18
18
|
}: CheckboxSelectorProps<Item>) => {
|
|
19
19
|
return (
|
|
20
20
|
<Dropdown buttonTitle={label} placement={'bottom-start'}>
|
|
21
|
+
<button
|
|
22
|
+
className='btn btn-xs btn-ghost'
|
|
23
|
+
onClick={() => {
|
|
24
|
+
const newItems = items.map((item) => ({ ...item, checked: true }));
|
|
25
|
+
setItems(newItems);
|
|
26
|
+
}}
|
|
27
|
+
>
|
|
28
|
+
Select all
|
|
29
|
+
</button>
|
|
30
|
+
<button
|
|
31
|
+
className='btn btn-xs btn-ghost'
|
|
32
|
+
onClick={() => {
|
|
33
|
+
const newItems = items.map((item) => ({ ...item, checked: false }));
|
|
34
|
+
setItems(newItems);
|
|
35
|
+
}}
|
|
36
|
+
>
|
|
37
|
+
Select none
|
|
38
|
+
</button>
|
|
39
|
+
<div className='divider mt-0 mb-0' />
|
|
21
40
|
<ul>
|
|
22
41
|
{items.map((item, index) => (
|
|
23
42
|
<li className='flex flex-row items-center' key={item.label}>
|
|
@@ -10,8 +10,10 @@ export const ColorScaleSelectorDropdown: FunctionComponent<ColorScaleSelectorDro
|
|
|
10
10
|
setColorScale,
|
|
11
11
|
}) => {
|
|
12
12
|
return (
|
|
13
|
-
<
|
|
14
|
-
<
|
|
15
|
-
|
|
13
|
+
<div className='w-20'>
|
|
14
|
+
<Dropdown buttonTitle={`Color scale`} placement={'bottom-start'}>
|
|
15
|
+
<ColorScaleSelector colorScale={colorScale} setColorScale={setColorScale} />
|
|
16
|
+
</Dropdown>
|
|
17
|
+
</div>
|
|
16
18
|
);
|
|
17
19
|
};
|
|
@@ -28,13 +28,13 @@ export const Dropdown: FunctionComponent<DropdownProps> = ({ children, buttonTit
|
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
|
|
32
|
-
<button type='button' className='btn btn-xs whitespace-nowrap' onClick={toggle} ref={referenceRef}>
|
|
31
|
+
<>
|
|
32
|
+
<button type='button' className='btn btn-xs whitespace-nowrap w-full' onClick={toggle} ref={referenceRef}>
|
|
33
33
|
{buttonTitle}
|
|
34
34
|
</button>
|
|
35
35
|
<div ref={floatingRef} className={`${dropdownClass} ${showContent ? '' : 'hidden'}`}>
|
|
36
36
|
{children}
|
|
37
37
|
</div>
|
|
38
|
-
|
|
38
|
+
</>
|
|
39
39
|
);
|
|
40
40
|
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, within } from '@storybook/test';
|
|
3
|
+
import { type FunctionComponent } from 'preact';
|
|
4
|
+
import { useState } from 'preact/hooks';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
type DisplayedMutationType,
|
|
8
|
+
MutationTypeSelector,
|
|
9
|
+
type MutationTypeSelectorProps,
|
|
10
|
+
} from './mutation-type-selector';
|
|
11
|
+
|
|
12
|
+
const meta: Meta<MutationTypeSelectorProps> = {
|
|
13
|
+
title: 'Component/Mutation type selector',
|
|
14
|
+
component: MutationTypeSelector,
|
|
15
|
+
parameters: { fetchMock: {} },
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
|
|
20
|
+
const WrapperWithState: FunctionComponent<{
|
|
21
|
+
displayedMutationTypes: DisplayedMutationType[];
|
|
22
|
+
}> = ({ displayedMutationTypes: initialMutationTypes }) => {
|
|
23
|
+
const [displayedMutationTypes, setDisplayedMutationTypes] = useState<DisplayedMutationType[]>(initialMutationTypes);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<MutationTypeSelector
|
|
27
|
+
displayedMutationTypes={displayedMutationTypes}
|
|
28
|
+
setDisplayedMutationTypes={(mutationTypes: DisplayedMutationType[]) => {
|
|
29
|
+
setDisplayedMutationTypes(mutationTypes);
|
|
30
|
+
}}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const MutationTypeSelectorStory: StoryObj<MutationTypeSelectorProps> = {
|
|
36
|
+
render: (args) => {
|
|
37
|
+
return <WrapperWithState {...args} />;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const AllMutationTypesSelected: StoryObj<MutationTypeSelectorProps> = {
|
|
42
|
+
...MutationTypeSelectorStory,
|
|
43
|
+
args: {
|
|
44
|
+
displayedMutationTypes: [
|
|
45
|
+
{
|
|
46
|
+
label: 'Substitution',
|
|
47
|
+
type: 'substitution',
|
|
48
|
+
checked: true,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: 'Deletion',
|
|
52
|
+
type: 'deletion',
|
|
53
|
+
checked: true,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
play: async ({ canvasElement, step }) => {
|
|
58
|
+
const canvas = within(canvasElement);
|
|
59
|
+
|
|
60
|
+
await step('Show short form of types as label', async () => {
|
|
61
|
+
await expect(canvas.getByText('Subst., Del.')).toBeInTheDocument();
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const NoMutationTypesSelected: StoryObj<MutationTypeSelectorProps> = {
|
|
67
|
+
...MutationTypeSelectorStory,
|
|
68
|
+
args: {
|
|
69
|
+
displayedMutationTypes: [
|
|
70
|
+
{
|
|
71
|
+
label: 'Substitution',
|
|
72
|
+
type: 'substitution',
|
|
73
|
+
checked: false,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
label: 'Deletion',
|
|
77
|
+
type: 'deletion',
|
|
78
|
+
checked: false,
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
},
|
|
82
|
+
play: async ({ canvasElement, step }) => {
|
|
83
|
+
const canvas = within(canvasElement);
|
|
84
|
+
|
|
85
|
+
await step("Show 'No types' as label", async () => {
|
|
86
|
+
await expect(canvas.getByText('No types')).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export const OneTypesSelected: StoryObj<MutationTypeSelectorProps> = {
|
|
92
|
+
...MutationTypeSelectorStory,
|
|
93
|
+
args: {
|
|
94
|
+
displayedMutationTypes: [
|
|
95
|
+
{
|
|
96
|
+
label: 'Substitution',
|
|
97
|
+
type: 'substitution',
|
|
98
|
+
checked: true,
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
label: 'Deletion',
|
|
102
|
+
type: 'deletion',
|
|
103
|
+
checked: false,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
play: async ({ canvasElement, step }) => {
|
|
108
|
+
const canvas = within(canvasElement);
|
|
109
|
+
|
|
110
|
+
await step('Show the selected type as label', async () => {
|
|
111
|
+
const substitutionElements = await canvas.getAllByText('Substitution');
|
|
112
|
+
await expect(substitutionElements.length).toBe(2);
|
|
113
|
+
});
|
|
114
|
+
},
|
|
115
|
+
};
|
|
@@ -16,14 +16,39 @@ export const MutationTypeSelector: FunctionComponent<MutationTypeSelectorProps>
|
|
|
16
16
|
displayedMutationTypes,
|
|
17
17
|
setDisplayedMutationTypes,
|
|
18
18
|
}) => {
|
|
19
|
-
const checkedLabels = displayedMutationTypes.filter((type) => type.checked).map((type) => type.label);
|
|
20
|
-
const mutationTypesSelectorLabel = `Types: ${checkedLabels.length > 0 ? checkedLabels.join(', ') : 'None'}`;
|
|
21
|
-
|
|
22
19
|
return (
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
<div className='w-[6rem]'>
|
|
21
|
+
<CheckboxSelector
|
|
22
|
+
items={displayedMutationTypes}
|
|
23
|
+
label={getMutationTypesSelectorLabel(displayedMutationTypes)}
|
|
24
|
+
setItems={(items) => setDisplayedMutationTypes(items)}
|
|
25
|
+
/>
|
|
26
|
+
</div>
|
|
28
27
|
);
|
|
29
28
|
};
|
|
29
|
+
|
|
30
|
+
const getMutationTypesSelectorLabel = (displayedMutationTypes: DisplayedMutationType[]) => {
|
|
31
|
+
const checkedLabels = displayedMutationTypes.filter((displayedMutationType) => displayedMutationType.checked);
|
|
32
|
+
|
|
33
|
+
if (checkedLabels.length === 0) {
|
|
34
|
+
return `No types`;
|
|
35
|
+
}
|
|
36
|
+
if (displayedMutationTypes.length === checkedLabels.length) {
|
|
37
|
+
return displayedMutationTypes
|
|
38
|
+
.map((type) => {
|
|
39
|
+
switch (type.type) {
|
|
40
|
+
case 'substitution':
|
|
41
|
+
return 'Subst.';
|
|
42
|
+
case 'deletion':
|
|
43
|
+
return 'Del.';
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
.join(', ');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return checkedLabels
|
|
50
|
+
.map((type) => {
|
|
51
|
+
return type.label;
|
|
52
|
+
})
|
|
53
|
+
.join(', ');
|
|
54
|
+
};
|