@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.
Files changed (34) hide show
  1. package/dist/dashboard-components.js +1072 -853
  2. package/dist/dashboard-components.js.map +1 -1
  3. package/dist/genspectrum-components.d.ts +7 -7
  4. package/dist/style.css +113 -0
  5. package/package.json +2 -2
  6. package/src/preact/components/checkbox-selector.stories.tsx +93 -11
  7. package/src/preact/components/checkbox-selector.tsx +19 -0
  8. package/src/preact/components/color-scale-selector-dropdown.tsx +5 -3
  9. package/src/preact/components/dropdown.tsx +3 -3
  10. package/src/preact/components/mutation-type-selector.stories.tsx +115 -0
  11. package/src/preact/components/mutation-type-selector.tsx +33 -8
  12. package/src/preact/components/percent-input.stories.tsx +93 -0
  13. package/src/preact/components/percent-intput.tsx +4 -0
  14. package/src/preact/components/proportion-selector-dropdown.stories.tsx +2 -2
  15. package/src/preact/components/proportion-selector-dropdown.tsx +9 -7
  16. package/src/preact/components/proportion-selector.stories.tsx +4 -4
  17. package/src/preact/components/proportion-selector.tsx +46 -12
  18. package/src/preact/components/segment-selector.stories.tsx +151 -0
  19. package/src/preact/components/{SegmentSelector.tsx → segment-selector.tsx} +29 -20
  20. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +1 -1
  21. package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
  22. package/src/preact/mutationComparison/queryMutationData.ts +1 -1
  23. package/src/preact/mutations/mutations-grid.tsx +5 -1
  24. package/src/preact/mutations/mutations.tsx +1 -1
  25. package/src/preact/mutations/queryMutations.ts +1 -1
  26. package/src/preact/mutationsOverTime/getFilteredMutationsOverTime.spec.ts +4 -4
  27. package/src/preact/mutationsOverTime/getFilteredMutationsOverTimeData.ts +3 -2
  28. package/src/preact/mutationsOverTime/mutations-over-time.tsx +1 -1
  29. package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +3 -2
  30. package/src/preact/useQuery.ts +1 -1
  31. package/src/query/queryMutationsOverTime.ts +3 -3
  32. package/src/utils/map2d.spec.ts +83 -22
  33. package/src/utils/map2d.ts +158 -0
  34. 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-aggregate-component': AggregateComponent;
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-number-sequences-over-time': NumberSequencesOverTimeComponent;
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-mutation-filter': MutationFilterComponent;
1044
+ 'gs-lineage-filter': LineageFilterComponent;
1045
1045
  }
1046
1046
  interface HTMLElementEventMap {
1047
- 'gs-mutation-filter-changed': CustomEvent<SelectedMutationFilterStrings>;
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-lineage-filter': LineageFilterComponent;
1054
+ 'gs-mutation-filter': MutationFilterComponent;
1056
1055
  }
1057
1056
  interface HTMLElementEventMap {
1058
- 'gs-lineage-filter-changed': CustomEvent<Record<string, string>>;
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.11",
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": "^20.12.7",
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
- <CheckboxSelector
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', { exact: false });
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
- const item1 = canvas.getByLabelText('item1', { exact: false });
47
- item1.click();
72
+ await step('Select one item', async () => {
73
+ firstItem().click();
48
74
 
49
- await waitFor(() =>
50
- expect(args.setItems).toHaveBeenCalledWith([
51
- { checked: true, label: 'item1' },
52
- { checked: false, label: 'item2' },
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
- <Dropdown buttonTitle={`Color scale`} placement={'bottom-start'}>
14
- <ColorScaleSelector colorScale={colorScale} setColorScale={setColorScale} />
15
- </Dropdown>
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
- <div>
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
- </div>
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
- <CheckboxSelector
24
- items={displayedMutationTypes}
25
- label={mutationTypesSelectorLabel}
26
- setItems={(items) => setDisplayedMutationTypes(items)}
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
+ };