@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
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Fragment, type FunctionComponent } from 'preact';
|
|
1
|
+
import { Fragment, type FunctionComponent, type RefObject } from 'preact';
|
|
2
|
+
import { useEffect, useRef, useState } from 'preact/hooks';
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
type MutationOverTimeDataGroupedByMutation,
|
|
@@ -6,22 +7,29 @@ import {
|
|
|
6
7
|
} from '../../query/queryMutationsOverTime';
|
|
7
8
|
import { type Deletion, type Substitution } from '../../utils/mutations';
|
|
8
9
|
import { compareTemporal, type Temporal, YearMonthDay } from '../../utils/temporal';
|
|
10
|
+
import { type ColorScale, getColorWithingScale, getTextColorForScale } from '../components/color-scale-selector';
|
|
9
11
|
import Tooltip, { type TooltipPosition } from '../components/tooltip';
|
|
10
|
-
import {
|
|
12
|
+
import { sortSubstitutionsAndDeletions } from '../shared/sort/sortSubstitutionsAndDeletions';
|
|
11
13
|
import { formatProportion } from '../shared/table/formatProportion';
|
|
12
14
|
|
|
13
15
|
export interface MutationsOverTimeGridProps {
|
|
14
16
|
data: MutationOverTimeDataGroupedByMutation;
|
|
17
|
+
colorScale: ColorScale;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
const MAX_NUMBER_OF_GRID_ROWS = 100;
|
|
21
|
+
const MUTATION_CELL_WIDTH_REM = 8;
|
|
18
22
|
|
|
19
|
-
const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({ data }) => {
|
|
20
|
-
const allMutations = data.getFirstAxisKeys();
|
|
23
|
+
const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({ data, colorScale }) => {
|
|
24
|
+
const allMutations = data.getFirstAxisKeys().sort(sortSubstitutionsAndDeletions);
|
|
21
25
|
const shownMutations = allMutations.slice(0, MAX_NUMBER_OF_GRID_ROWS);
|
|
22
26
|
|
|
23
27
|
const dates = data.getSecondAxisKeys().sort((a, b) => compareTemporal(a, b));
|
|
24
28
|
|
|
29
|
+
const [showProportionText, setShowProportionText] = useState(false);
|
|
30
|
+
const gridRef = useRef<HTMLDivElement>(null);
|
|
31
|
+
useShowProportion(gridRef, dates.length, setShowProportionText);
|
|
32
|
+
|
|
25
33
|
return (
|
|
26
34
|
<>
|
|
27
35
|
{allMutations.length > MAX_NUMBER_OF_GRID_ROWS && (
|
|
@@ -31,10 +39,11 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
31
39
|
</div>
|
|
32
40
|
)}
|
|
33
41
|
<div
|
|
42
|
+
ref={gridRef}
|
|
34
43
|
style={{
|
|
35
44
|
display: 'grid',
|
|
36
45
|
gridTemplateRows: `repeat(${shownMutations.length}, 24px)`,
|
|
37
|
-
gridTemplateColumns:
|
|
46
|
+
gridTemplateColumns: `${MUTATION_CELL_WIDTH_REM}rem repeat(${dates.length}, minmax(0.05rem, 1fr))`,
|
|
38
47
|
}}
|
|
39
48
|
>
|
|
40
49
|
{shownMutations.map((mutation, rowIndex) => {
|
|
@@ -64,6 +73,8 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
64
73
|
date={date}
|
|
65
74
|
mutation={mutation}
|
|
66
75
|
tooltipPosition={tooltipPosition}
|
|
76
|
+
showProportionText={showProportionText}
|
|
77
|
+
colorScale={colorScale}
|
|
67
78
|
/>
|
|
68
79
|
</div>
|
|
69
80
|
);
|
|
@@ -76,6 +87,33 @@ const MutationsOverTimeGrid: FunctionComponent<MutationsOverTimeGridProps> = ({
|
|
|
76
87
|
);
|
|
77
88
|
};
|
|
78
89
|
|
|
90
|
+
function useShowProportion(
|
|
91
|
+
gridRef: RefObject<HTMLDivElement>,
|
|
92
|
+
girdColumns: number,
|
|
93
|
+
setShowProportionText: (value: ((prevState: boolean) => boolean) | boolean) => void,
|
|
94
|
+
) {
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
const checkWidth = () => {
|
|
97
|
+
if (gridRef.current) {
|
|
98
|
+
const width = gridRef.current.getBoundingClientRect().width;
|
|
99
|
+
const widthPerDate = (width - remToPx(MUTATION_CELL_WIDTH_REM)) / girdColumns;
|
|
100
|
+
const maxWidthProportionText = 28;
|
|
101
|
+
|
|
102
|
+
setShowProportionText(widthPerDate > maxWidthProportionText);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
checkWidth();
|
|
107
|
+
window.addEventListener('resize', checkWidth);
|
|
108
|
+
|
|
109
|
+
return () => {
|
|
110
|
+
window.removeEventListener('resize', checkWidth);
|
|
111
|
+
};
|
|
112
|
+
}, [girdColumns, gridRef, setShowProportionText]);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const remToPx = (rem: number) => rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
|
|
116
|
+
|
|
79
117
|
function getTooltipPosition(rowIndex: number, rows: number, columnIndex: number, columns: number) {
|
|
80
118
|
const tooltipX = rowIndex < rows / 2 ? 'bottom' : 'top';
|
|
81
119
|
const tooltipY = columnIndex < columns / 2 ? 'start' : 'end';
|
|
@@ -87,12 +125,15 @@ const ProportionCell: FunctionComponent<{
|
|
|
87
125
|
date: Temporal;
|
|
88
126
|
mutation: Substitution | Deletion;
|
|
89
127
|
tooltipPosition: TooltipPosition;
|
|
90
|
-
|
|
128
|
+
showProportionText: boolean;
|
|
129
|
+
colorScale: ColorScale;
|
|
130
|
+
}> = ({ value, mutation, date, tooltipPosition, showProportionText, colorScale }) => {
|
|
91
131
|
const tooltipContent = (
|
|
92
132
|
<div>
|
|
93
133
|
<p>
|
|
94
|
-
<span className='font-bold'>{date.englishName()}</span>
|
|
134
|
+
<span className='font-bold'>{date.englishName()}</span>
|
|
95
135
|
</p>
|
|
136
|
+
<p>({timeIntervalDisplay(date)})</p>
|
|
96
137
|
<p>{mutation.code}</p>
|
|
97
138
|
<p>Proportion: {formatProportion(value.proportion)}</p>
|
|
98
139
|
<p>Count: {value.count}</p>
|
|
@@ -100,21 +141,19 @@ const ProportionCell: FunctionComponent<{
|
|
|
100
141
|
);
|
|
101
142
|
|
|
102
143
|
return (
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
<
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</div>
|
|
117
|
-
</>
|
|
144
|
+
<div className={'py-1 w-full h-full'}>
|
|
145
|
+
<Tooltip content={tooltipContent} position={tooltipPosition}>
|
|
146
|
+
<div
|
|
147
|
+
style={{
|
|
148
|
+
backgroundColor: getColorWithingScale(value.proportion, colorScale),
|
|
149
|
+
color: getTextColorForScale(value.proportion, colorScale),
|
|
150
|
+
}}
|
|
151
|
+
className={`w-full h-full text-center hover:font-bold text-xs group`}
|
|
152
|
+
>
|
|
153
|
+
{showProportionText ? formatProportion(value.proportion, 0) : undefined}
|
|
154
|
+
</div>
|
|
155
|
+
</Tooltip>
|
|
156
|
+
</div>
|
|
118
157
|
);
|
|
119
158
|
};
|
|
120
159
|
|
|
@@ -126,19 +165,6 @@ const timeIntervalDisplay = (date: Temporal) => {
|
|
|
126
165
|
return `${date.firstDay.toString()} - ${date.lastDay.toString()}`;
|
|
127
166
|
};
|
|
128
167
|
|
|
129
|
-
const backgroundColor = (proportion: number) => {
|
|
130
|
-
// TODO(#353): Make minAlpha and maxAlpha configurable
|
|
131
|
-
const minAlpha = 0.0;
|
|
132
|
-
const maxAlpha = 1;
|
|
133
|
-
|
|
134
|
-
const alpha = minAlpha + (maxAlpha - minAlpha) * proportion;
|
|
135
|
-
return singleGraphColorRGBByName('indigo', alpha);
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const textColor = (proportion: number) => {
|
|
139
|
-
return proportion > 0.5 ? 'white' : 'black';
|
|
140
|
-
};
|
|
141
|
-
|
|
142
168
|
const MutationCell: FunctionComponent<{ mutation: Substitution | Deletion }> = ({ mutation }) => {
|
|
143
169
|
return <div className='text-center'>{mutation.toString()}</div>;
|
|
144
170
|
};
|
|
@@ -11,6 +11,8 @@ import { type LapisFilter, type SequenceType, type TemporalGranularity } from '.
|
|
|
11
11
|
import { compareTemporal } from '../../utils/temporal';
|
|
12
12
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
13
13
|
import { type DisplayedSegment, SegmentSelector, useDisplayedSegments } from '../components/SegmentSelector';
|
|
14
|
+
import { type ColorScale } from '../components/color-scale-selector';
|
|
15
|
+
import { ColorScaleSelectorDropdown } from '../components/color-scale-selector-dropdown';
|
|
14
16
|
import { CsvDownloadButton } from '../components/csv-download-button';
|
|
15
17
|
import { ErrorBoundary } from '../components/error-boundary';
|
|
16
18
|
import { ErrorDisplay } from '../components/error-display';
|
|
@@ -22,6 +24,7 @@ import type { ProportionInterval } from '../components/proportion-selector';
|
|
|
22
24
|
import { ProportionSelectorDropdown } from '../components/proportion-selector-dropdown';
|
|
23
25
|
import { ResizeContainer } from '../components/resize-container';
|
|
24
26
|
import Tabs from '../components/tabs';
|
|
27
|
+
import { sortSubstitutionsAndDeletions } from '../shared/sort/sortSubstitutionsAndDeletions';
|
|
25
28
|
import { useQuery } from '../useQuery';
|
|
26
29
|
|
|
27
30
|
export type View = 'grid';
|
|
@@ -90,6 +93,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
90
93
|
views,
|
|
91
94
|
}) => {
|
|
92
95
|
const [proportionInterval, setProportionInterval] = useState({ min: 0.05, max: 0.9 });
|
|
96
|
+
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
93
97
|
|
|
94
98
|
const [displayedSegments, setDisplayedSegments] = useDisplayedSegments(sequenceType);
|
|
95
99
|
const [displayedMutationTypes, setDisplayedMutationTypes] = useState<DisplayedMutationType[]>([
|
|
@@ -113,15 +117,16 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
113
117
|
case 'grid':
|
|
114
118
|
return {
|
|
115
119
|
title: 'Grid',
|
|
116
|
-
content: <MutationsOverTimeGrid data={filteredData} />,
|
|
120
|
+
content: <MutationsOverTimeGrid data={filteredData} colorScale={colorScale} />,
|
|
117
121
|
};
|
|
118
122
|
}
|
|
119
123
|
};
|
|
120
124
|
|
|
121
125
|
const tabs = views.map((view) => getTab(view));
|
|
122
126
|
|
|
123
|
-
const toolbar = () => (
|
|
127
|
+
const toolbar = (activeTab: string) => (
|
|
124
128
|
<Toolbar
|
|
129
|
+
activeTab={activeTab}
|
|
125
130
|
displayedSegments={displayedSegments}
|
|
126
131
|
setDisplayedSegments={setDisplayedSegments}
|
|
127
132
|
displayedMutationTypes={displayedMutationTypes}
|
|
@@ -129,6 +134,8 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
129
134
|
proportionInterval={proportionInterval}
|
|
130
135
|
setProportionInterval={setProportionInterval}
|
|
131
136
|
filteredData={filteredData}
|
|
137
|
+
colorScale={colorScale}
|
|
138
|
+
setColorScale={setColorScale}
|
|
132
139
|
/>
|
|
133
140
|
);
|
|
134
141
|
|
|
@@ -136,6 +143,7 @@ const MutationsOverTimeTabs: FunctionComponent<MutationOverTimeTabsProps> = ({
|
|
|
136
143
|
};
|
|
137
144
|
|
|
138
145
|
type ToolbarProps = {
|
|
146
|
+
activeTab: string;
|
|
139
147
|
displayedSegments: DisplayedSegment[];
|
|
140
148
|
setDisplayedSegments: (segments: DisplayedSegment[]) => void;
|
|
141
149
|
displayedMutationTypes: DisplayedMutationType[];
|
|
@@ -143,9 +151,12 @@ type ToolbarProps = {
|
|
|
143
151
|
proportionInterval: ProportionInterval;
|
|
144
152
|
setProportionInterval: Dispatch<StateUpdater<ProportionInterval>>;
|
|
145
153
|
filteredData: MutationOverTimeDataGroupedByMutation;
|
|
154
|
+
colorScale: ColorScale;
|
|
155
|
+
setColorScale: Dispatch<StateUpdater<ColorScale>>;
|
|
146
156
|
};
|
|
147
157
|
|
|
148
158
|
const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
159
|
+
activeTab,
|
|
149
160
|
displayedSegments,
|
|
150
161
|
setDisplayedSegments,
|
|
151
162
|
displayedMutationTypes,
|
|
@@ -153,9 +164,14 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
153
164
|
proportionInterval,
|
|
154
165
|
setProportionInterval,
|
|
155
166
|
filteredData,
|
|
167
|
+
colorScale,
|
|
168
|
+
setColorScale,
|
|
156
169
|
}) => {
|
|
157
170
|
return (
|
|
158
171
|
<>
|
|
172
|
+
{activeTab === 'Grid' && (
|
|
173
|
+
<ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />
|
|
174
|
+
)}
|
|
159
175
|
<SegmentSelector displayedSegments={displayedSegments} setDisplayedSegments={setDisplayedSegments} />
|
|
160
176
|
<MutationTypeSelector
|
|
161
177
|
setDisplayedMutationTypes={setDisplayedMutationTypes}
|
|
@@ -171,7 +187,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
171
187
|
getData={() => getDownloadData(filteredData)}
|
|
172
188
|
filename='mutations_over_time.csv'
|
|
173
189
|
/>
|
|
174
|
-
<Info
|
|
190
|
+
<Info>Info for mutations over time</Info>
|
|
175
191
|
</>
|
|
176
192
|
);
|
|
177
193
|
};
|
|
@@ -179,16 +195,19 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
179
195
|
function getDownloadData(filteredData: MutationOverTimeDataGroupedByMutation) {
|
|
180
196
|
const dates = filteredData.getSecondAxisKeys().sort((a, b) => compareTemporal(a, b));
|
|
181
197
|
|
|
182
|
-
return filteredData
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
return filteredData
|
|
199
|
+
.getFirstAxisKeys()
|
|
200
|
+
.sort(sortSubstitutionsAndDeletions)
|
|
201
|
+
.map((mutation) => {
|
|
202
|
+
return dates.reduce(
|
|
203
|
+
(accumulated, date) => {
|
|
204
|
+
const proportion = filteredData.get(mutation, date)?.proportion ?? 0;
|
|
205
|
+
return {
|
|
206
|
+
...accumulated,
|
|
207
|
+
[date.toString()]: proportion,
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
{ mutation: mutation.toString() },
|
|
211
|
+
);
|
|
212
|
+
});
|
|
194
213
|
}
|
|
@@ -156,7 +156,7 @@ const Toolbar = ({ activeTab, data, granularity, yAxisScaleType, setYAxisScaleTy
|
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
const NumberSequencesOverTimeInfo = () => (
|
|
159
|
-
<Info
|
|
159
|
+
<Info>
|
|
160
160
|
<InfoHeadline1>Number of sequences over time</InfoHeadline1>
|
|
161
161
|
<InfoParagraph>
|
|
162
162
|
<a href='https://github.com/GenSpectrum/dashboard-components/issues/315'>TODO</a>
|
|
@@ -236,7 +236,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({
|
|
|
236
236
|
|
|
237
237
|
const PrevalenceOverTimeInfo: FunctionComponent = () => {
|
|
238
238
|
return (
|
|
239
|
-
<Info
|
|
239
|
+
<Info>
|
|
240
240
|
<InfoHeadline1>Prevalence over time</InfoHeadline1>
|
|
241
241
|
<InfoParagraph>Prevalence over time info.</InfoParagraph>
|
|
242
242
|
</Info>
|
|
@@ -12,7 +12,7 @@ const ColorsRGB = {
|
|
|
12
12
|
purple: [170, 68, 153],
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
type GraphColor = keyof typeof ColorsRGB;
|
|
15
|
+
export type GraphColor = keyof typeof ColorsRGB;
|
|
16
16
|
|
|
17
17
|
export const singleGraphColorRGBAById = (id: number, alpha = 1) => {
|
|
18
18
|
const keys = Object.keys(ColorsRGB) as GraphColor[];
|
|
@@ -32,7 +32,7 @@ describe('sortSubstitutionsAndDeletions with no segments', () => {
|
|
|
32
32
|
describe('sortSubstitutionsAndDeletions with segments', () => {
|
|
33
33
|
test('should sort for segment first', () => {
|
|
34
34
|
const a = new Substitution('AA1', 'A', 'B', 123);
|
|
35
|
-
const b = new Substitution('BB1', 'A', 'B',
|
|
35
|
+
const b = new Substitution('BB1', 'A', 'B', 123);
|
|
36
36
|
|
|
37
37
|
expect(sortSubstitutionsAndDeletions(a, b)).toBeLessThan(0);
|
|
38
38
|
expect(sortSubstitutionsAndDeletions(b, a)).toBeGreaterThan(0);
|
|
@@ -2,7 +2,7 @@ import { Deletion, type Substitution } from '../../../utils/mutations';
|
|
|
2
2
|
|
|
3
3
|
export const sortSubstitutionsAndDeletions = (a: Substitution | Deletion, b: Substitution | Deletion) => {
|
|
4
4
|
if (a.segment !== b.segment) {
|
|
5
|
-
compareSegments(a.segment, b.segment);
|
|
5
|
+
return compareSegments(a.segment, b.segment);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
if (a.position !== b.position) {
|
|
@@ -475,3 +475,11 @@ export const AminoAcidMutationsByDay: StoryObj<Required<MutationsOverTimeProps>>
|
|
|
475
475
|
},
|
|
476
476
|
},
|
|
477
477
|
};
|
|
478
|
+
|
|
479
|
+
export const HideProportionOnSmallScreen: StoryObj<Required<MutationsOverTimeProps>> = {
|
|
480
|
+
...ByMonth,
|
|
481
|
+
args: {
|
|
482
|
+
...ByMonth.args,
|
|
483
|
+
width: '300px',
|
|
484
|
+
},
|
|
485
|
+
};
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { FunctionComponent } from 'preact';
|
|
2
|
-
|
|
3
|
-
export const DeleteIcon: FunctionComponent = () => {
|
|
4
|
-
return (
|
|
5
|
-
<svg
|
|
6
|
-
fill='currentColor'
|
|
7
|
-
stroke-width='0'
|
|
8
|
-
xmlns='http://www.w3.org/2000/svg'
|
|
9
|
-
viewBox='0 0 16 16'
|
|
10
|
-
style='overflow: visible; color: currentcolor;'
|
|
11
|
-
height='1em'
|
|
12
|
-
width='1em'
|
|
13
|
-
>
|
|
14
|
-
<path d='M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z' />
|
|
15
|
-
</svg>
|
|
16
|
-
);
|
|
17
|
-
};
|