@genspectrum/dashboard-components 0.6.11 → 0.6.13
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 +1079 -873
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +3 -3
- package/dist/style.css +117 -24
- 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/loading-display.tsx +8 -1
- 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,21 +990,21 @@ 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-mutations-over-time-component': MutationsOverTimeComponent;
|
|
1001
1001
|
}
|
|
1002
1002
|
}
|
|
1003
1003
|
|
|
1004
1004
|
|
|
1005
1005
|
declare global {
|
|
1006
1006
|
interface HTMLElementTagNameMap {
|
|
1007
|
-
'gs-
|
|
1007
|
+
'gs-aggregate-component': AggregateComponent;
|
|
1008
1008
|
}
|
|
1009
1009
|
}
|
|
1010
1010
|
|
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;
|
|
@@ -2135,30 +2212,6 @@ input.tab:checked + .tab-content,
|
|
|
2135
2212
|
background-position: calc(0% + 12px) calc(1px + 50%),
|
|
2136
2213
|
calc(0% + 16px) calc(1px + 50%);
|
|
2137
2214
|
}
|
|
2138
|
-
.skeleton {
|
|
2139
|
-
border-radius: var(--rounded-box, 1rem);
|
|
2140
|
-
--tw-bg-opacity: 1;
|
|
2141
|
-
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
|
|
2142
|
-
will-change: background-position;
|
|
2143
|
-
animation: skeleton 1.8s ease-in-out infinite;
|
|
2144
|
-
background-image: linear-gradient(
|
|
2145
|
-
105deg,
|
|
2146
|
-
transparent 0%,
|
|
2147
|
-
transparent 40%,
|
|
2148
|
-
var(--fallback-b1,oklch(var(--b1)/1)) 50%,
|
|
2149
|
-
transparent 60%,
|
|
2150
|
-
transparent 100%
|
|
2151
|
-
);
|
|
2152
|
-
background-size: 200% auto;
|
|
2153
|
-
background-repeat: no-repeat;
|
|
2154
|
-
background-position-x: -50%;
|
|
2155
|
-
}
|
|
2156
|
-
@media (prefers-reduced-motion) {
|
|
2157
|
-
|
|
2158
|
-
.skeleton {
|
|
2159
|
-
animation-duration: 15s;
|
|
2160
|
-
}
|
|
2161
|
-
}
|
|
2162
2215
|
@keyframes skeleton {
|
|
2163
2216
|
|
|
2164
2217
|
from {
|
|
@@ -2396,6 +2449,12 @@ input.tab:checked + .tab-content,
|
|
|
2396
2449
|
--tw-bg-opacity: 1;
|
|
2397
2450
|
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
|
2398
2451
|
}
|
|
2452
|
+
.table-zebra tr.active,
|
|
2453
|
+
.table-zebra tr.active:nth-child(even),
|
|
2454
|
+
.table-zebra-zebra tbody tr:nth-child(even) {
|
|
2455
|
+
--tw-bg-opacity: 1;
|
|
2456
|
+
background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
|
|
2457
|
+
}
|
|
2399
2458
|
.table :where(thead tr, tbody tr:not(:last-child), tbody tr:first-child:last-child) {
|
|
2400
2459
|
border-bottom-width: 1px;
|
|
2401
2460
|
--tw-border-opacity: 1;
|
|
@@ -2468,6 +2527,18 @@ input.tab:checked + .tab-content,
|
|
|
2468
2527
|
--togglehandleborder: 0 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset,
|
|
2469
2528
|
var(--handleoffsetcalculator) 0 0 3px var(--fallback-bc,oklch(var(--bc)/1)) inset;
|
|
2470
2529
|
}
|
|
2530
|
+
.btm-nav-xs > *:where(.active) {
|
|
2531
|
+
border-top-width: 1px;
|
|
2532
|
+
}
|
|
2533
|
+
.btm-nav-sm > *:where(.active) {
|
|
2534
|
+
border-top-width: 2px;
|
|
2535
|
+
}
|
|
2536
|
+
.btm-nav-md > *:where(.active) {
|
|
2537
|
+
border-top-width: 2px;
|
|
2538
|
+
}
|
|
2539
|
+
.btm-nav-lg > *:where(.active) {
|
|
2540
|
+
border-top-width: 4px;
|
|
2541
|
+
}
|
|
2471
2542
|
.btn-xs {
|
|
2472
2543
|
height: 1.5rem;
|
|
2473
2544
|
min-height: 1.5rem;
|
|
@@ -2887,6 +2958,9 @@ input.tab:checked + .tab-content,
|
|
|
2887
2958
|
margin-top: 1rem;
|
|
2888
2959
|
margin-bottom: 1rem;
|
|
2889
2960
|
}
|
|
2961
|
+
.mb-0 {
|
|
2962
|
+
margin-bottom: 0px;
|
|
2963
|
+
}
|
|
2890
2964
|
.mb-1 {
|
|
2891
2965
|
margin-bottom: 0.25rem;
|
|
2892
2966
|
}
|
|
@@ -2911,6 +2985,9 @@ input.tab:checked + .tab-content,
|
|
|
2911
2985
|
.mr-2 {
|
|
2912
2986
|
margin-right: 0.5rem;
|
|
2913
2987
|
}
|
|
2988
|
+
.mt-0 {
|
|
2989
|
+
margin-top: 0px;
|
|
2990
|
+
}
|
|
2914
2991
|
.mt-0\.5 {
|
|
2915
2992
|
margin-top: 0.125rem;
|
|
2916
2993
|
}
|
|
@@ -2950,12 +3027,24 @@ input.tab:checked + .tab-content,
|
|
|
2950
3027
|
.w-16 {
|
|
2951
3028
|
width: 4rem;
|
|
2952
3029
|
}
|
|
3030
|
+
.w-20 {
|
|
3031
|
+
width: 5rem;
|
|
3032
|
+
}
|
|
3033
|
+
.w-24 {
|
|
3034
|
+
width: 6rem;
|
|
3035
|
+
}
|
|
2953
3036
|
.w-32 {
|
|
2954
3037
|
width: 8rem;
|
|
2955
3038
|
}
|
|
3039
|
+
.w-44 {
|
|
3040
|
+
width: 11rem;
|
|
3041
|
+
}
|
|
2956
3042
|
.w-64 {
|
|
2957
3043
|
width: 16rem;
|
|
2958
3044
|
}
|
|
3045
|
+
.w-\[6rem\] {
|
|
3046
|
+
width: 6rem;
|
|
3047
|
+
}
|
|
2959
3048
|
.w-\[7\.5rem\] {
|
|
2960
3049
|
width: 7.5rem;
|
|
2961
3050
|
}
|
|
@@ -3193,6 +3282,10 @@ input.tab:checked + .tab-content,
|
|
|
3193
3282
|
--tw-text-opacity: 1;
|
|
3194
3283
|
color: rgb(75 85 99 / var(--tw-text-opacity));
|
|
3195
3284
|
}
|
|
3285
|
+
.text-neutral-500 {
|
|
3286
|
+
--tw-text-opacity: 1;
|
|
3287
|
+
color: rgb(115 115 115 / var(--tw-text-opacity));
|
|
3288
|
+
}
|
|
3196
3289
|
.text-red-700 {
|
|
3197
3290
|
--tw-text-opacity: 1;
|
|
3198
3291
|
color: rgb(185 28 28 / var(--tw-text-opacity));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@genspectrum/dashboard-components",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.13",
|
|
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
|
};
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { type FunctionComponent } from 'preact';
|
|
2
2
|
|
|
3
3
|
export const LoadingDisplay: FunctionComponent = () => {
|
|
4
|
-
return
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
aria-label={'Loading'}
|
|
7
|
+
className='h-full w-full border-2 border-gray-100 rounded-md flex justify-center items-center'
|
|
8
|
+
>
|
|
9
|
+
<div className='loading loading-spinner loading-md text-neutral-500' />
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
5
12
|
};
|
|
@@ -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
|
+
};
|