@genspectrum/dashboard-components 0.1.4 → 0.2.0
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 +1021 -804
- package/dist/dashboard-components.js +647 -218
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +336 -126
- package/dist/style.css +214 -36
- package/package.json +4 -4
- package/src/preact/aggregatedData/aggregate.stories.tsx +2 -0
- package/src/preact/aggregatedData/aggregate.tsx +33 -28
- package/src/preact/components/error-boundary.stories.tsx +62 -0
- package/src/preact/components/error-boundary.tsx +31 -0
- package/src/preact/components/error-display.stories.tsx +24 -3
- package/src/preact/components/error-display.tsx +14 -1
- package/src/preact/components/headline.stories.tsx +19 -1
- package/src/preact/components/headline.tsx +9 -1
- package/src/preact/components/info.stories.tsx +24 -3
- package/src/preact/components/info.tsx +49 -5
- package/src/preact/components/loading-display.stories.tsx +6 -6
- package/src/preact/components/loading-display.tsx +1 -1
- package/src/preact/components/no-data-display.tsx +5 -1
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +17 -0
- package/src/preact/dateRangeSelector/date-range-selector.tsx +43 -15
- package/src/preact/locationFilter/location-filter.stories.tsx +23 -6
- package/src/preact/locationFilter/location-filter.tsx +29 -18
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +3 -0
- package/src/preact/mutationComparison/mutation-comparison.tsx +31 -27
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +17 -2
- package/src/preact/mutationFilter/mutation-filter.tsx +26 -8
- package/src/preact/mutations/mutations.stories.tsx +3 -0
- package/src/preact/mutations/mutations.tsx +32 -26
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +4 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +57 -31
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +3 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +89 -32
- package/src/preact/textInput/text-input.tsx +26 -3
- package/src/web-components/app.stories.ts +1 -2
- package/src/web-components/app.ts +4 -2
- package/src/web-components/index.ts +1 -1
- package/src/web-components/input/{date-range-selector-component.stories.ts → gs-date-range-selector.stories.ts} +35 -3
- package/src/web-components/input/gs-date-range-selector.tsx +110 -0
- package/src/web-components/input/{location-filter-component.stories.ts → gs-location-filter.stories.ts} +29 -4
- package/src/web-components/input/{location-filter-component.tsx → gs-location-filter.tsx} +12 -1
- package/src/web-components/input/{mutation-filter-component.stories.ts → gs-mutation-filter.stories.ts} +30 -4
- package/src/web-components/input/gs-mutation-filter.tsx +114 -0
- package/src/web-components/input/{text-input-component.stories.ts → gs-text-input.stories.ts} +42 -3
- package/src/web-components/input/gs-text-input.tsx +73 -0
- package/src/web-components/input/index.ts +4 -4
- package/src/web-components/visualization/data_visualization_statistical_analysis.mdx +26 -0
- package/src/web-components/{display/aggregate-component.stories.ts → visualization/gs-aggregate.stories.ts} +8 -6
- package/src/web-components/{display/aggregate-component.tsx → visualization/gs-aggregate.tsx} +16 -2
- package/src/web-components/{display/mutation-comparison-component.stories.ts → visualization/gs-mutation-comparison.stories.ts} +11 -9
- package/src/web-components/{display/mutation-comparison-component.tsx → visualization/gs-mutation-comparison.tsx} +8 -1
- package/src/web-components/{display/mutations-component.stories.ts → visualization/gs-mutations.stories.ts} +30 -11
- package/src/web-components/visualization/gs-mutations.tsx +94 -0
- package/src/web-components/{display/prevalence-over-time-component.stories.ts → visualization/gs-prevalence-over-time.stories.ts} +24 -1
- package/src/web-components/visualization/gs-prevalence-over-time.tsx +148 -0
- package/src/web-components/{display/relative-growth-advantage-component.stories.ts → visualization/gs-relative-growth-advantage.stories.ts} +21 -1
- package/src/web-components/visualization/gs-relative-growth-advantage.tsx +100 -0
- package/src/web-components/visualization/index.ts +5 -0
- package/src/web-components/display/index.ts +0 -5
- package/src/web-components/display/mutations-component.tsx +0 -40
- package/src/web-components/display/prevalence-over-time-component.tsx +0 -58
- package/src/web-components/display/relative-growth-advantage-component.tsx +0 -49
- package/src/web-components/input/date-range-selector-component.tsx +0 -46
- package/src/web-components/input/mutation-filter-component.tsx +0 -35
- package/src/web-components/input/text-input-component.tsx +0 -39
|
@@ -8,9 +8,10 @@ import {
|
|
|
8
8
|
} from '../../query/queryRelativeGrowthAdvantage';
|
|
9
9
|
import { type LapisFilter } from '../../types';
|
|
10
10
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
11
|
+
import { ErrorBoundary } from '../components/error-boundary';
|
|
11
12
|
import { ErrorDisplay } from '../components/error-display';
|
|
12
13
|
import Headline from '../components/headline';
|
|
13
|
-
import Info from '../components/info';
|
|
14
|
+
import Info, { InfoHeadline1, InfoHeadline2, InfoLink, InfoParagraph } from '../components/info';
|
|
14
15
|
import { LoadingDisplay } from '../components/loading-display';
|
|
15
16
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
16
17
|
import { ResizeContainer, type Size } from '../components/resize-container';
|
|
@@ -21,20 +22,49 @@ import { useQuery } from '../useQuery';
|
|
|
21
22
|
|
|
22
23
|
export type View = 'line';
|
|
23
24
|
|
|
24
|
-
export interface RelativeGrowthAdvantageProps {
|
|
25
|
+
export interface RelativeGrowthAdvantageProps extends RelativeGrowthAdvantagePropsInner {
|
|
26
|
+
size?: Size;
|
|
27
|
+
headline?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RelativeGrowthAdvantagePropsInner {
|
|
25
31
|
numerator: LapisFilter;
|
|
26
32
|
denominator: LapisFilter;
|
|
27
33
|
generationTime: number;
|
|
28
34
|
views: View[];
|
|
29
|
-
size?: Size;
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
38
|
+
views,
|
|
39
|
+
size,
|
|
40
|
+
numerator,
|
|
41
|
+
denominator,
|
|
42
|
+
generationTime,
|
|
43
|
+
headline = 'Relative growth advantage',
|
|
44
|
+
}) => {
|
|
45
|
+
const defaultSize = { height: '600px', width: '100%' };
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<ErrorBoundary size={size} defaultSize={defaultSize} headline={headline}>
|
|
49
|
+
<ResizeContainer size={size} defaultSize={defaultSize}>
|
|
50
|
+
<Headline heading={headline}>
|
|
51
|
+
<RelativeGrowthAdvantageInner
|
|
52
|
+
views={views}
|
|
53
|
+
numerator={numerator}
|
|
54
|
+
denominator={denominator}
|
|
55
|
+
generationTime={generationTime}
|
|
56
|
+
/>
|
|
57
|
+
</Headline>
|
|
58
|
+
</ResizeContainer>
|
|
59
|
+
</ErrorBoundary>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const RelativeGrowthAdvantageInner: FunctionComponent<RelativeGrowthAdvantageProps> = ({
|
|
33
64
|
numerator,
|
|
34
65
|
denominator,
|
|
35
66
|
generationTime,
|
|
36
67
|
views,
|
|
37
|
-
size,
|
|
38
68
|
}) => {
|
|
39
69
|
const lapis = useContext(LapisUrlContext);
|
|
40
70
|
const [yAxisScaleType, setYAxisScaleType] = useState<ScaleType>('linear');
|
|
@@ -44,42 +74,26 @@ export const RelativeGrowthAdvantage: FunctionComponent<RelativeGrowthAdvantageP
|
|
|
44
74
|
[lapis, numerator, denominator, generationTime, views],
|
|
45
75
|
);
|
|
46
76
|
|
|
47
|
-
const headline = 'Relative growth advantage';
|
|
48
77
|
if (isLoading) {
|
|
49
|
-
return
|
|
50
|
-
<Headline heading={headline}>
|
|
51
|
-
<LoadingDisplay />
|
|
52
|
-
</Headline>
|
|
53
|
-
);
|
|
78
|
+
return <LoadingDisplay />;
|
|
54
79
|
}
|
|
55
80
|
|
|
56
81
|
if (error !== null) {
|
|
57
|
-
return
|
|
58
|
-
<Headline heading={headline}>
|
|
59
|
-
<ErrorDisplay error={error} />
|
|
60
|
-
</Headline>
|
|
61
|
-
);
|
|
82
|
+
return <ErrorDisplay error={error} />;
|
|
62
83
|
}
|
|
63
84
|
|
|
64
85
|
if (data === null) {
|
|
65
|
-
return
|
|
66
|
-
<Headline heading={headline}>
|
|
67
|
-
<NoDataDisplay />
|
|
68
|
-
</Headline>
|
|
69
|
-
);
|
|
86
|
+
return <NoDataDisplay />;
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
return (
|
|
73
|
-
<
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
/>
|
|
81
|
-
</Headline>
|
|
82
|
-
</ResizeContainer>
|
|
90
|
+
<RelativeGrowthAdvantageTabs
|
|
91
|
+
data={data}
|
|
92
|
+
yAxisScaleType={yAxisScaleType}
|
|
93
|
+
setYAxisScaleType={setYAxisScaleType}
|
|
94
|
+
views={views}
|
|
95
|
+
generationTime={generationTime}
|
|
96
|
+
/>
|
|
83
97
|
);
|
|
84
98
|
};
|
|
85
99
|
|
|
@@ -88,6 +102,7 @@ type RelativeGrowthAdvantageTabsProps = {
|
|
|
88
102
|
yAxisScaleType: ScaleType;
|
|
89
103
|
setYAxisScaleType: (scaleType: ScaleType) => void;
|
|
90
104
|
views: View[];
|
|
105
|
+
generationTime: number;
|
|
91
106
|
};
|
|
92
107
|
|
|
93
108
|
const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabsProps> = ({
|
|
@@ -95,6 +110,7 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
|
|
|
95
110
|
yAxisScaleType,
|
|
96
111
|
setYAxisScaleType,
|
|
97
112
|
views,
|
|
113
|
+
generationTime,
|
|
98
114
|
}) => {
|
|
99
115
|
const getTab = (view: View) => {
|
|
100
116
|
switch (view) {
|
|
@@ -117,7 +133,11 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
|
|
|
117
133
|
|
|
118
134
|
const tabs = views.map((view) => getTab(view));
|
|
119
135
|
const toolbar = () => (
|
|
120
|
-
<RelativeGrowthAdvantageToolbar
|
|
136
|
+
<RelativeGrowthAdvantageToolbar
|
|
137
|
+
generationTime={generationTime}
|
|
138
|
+
yAxisScaleType={yAxisScaleType}
|
|
139
|
+
setYAxisScaleType={setYAxisScaleType}
|
|
140
|
+
/>
|
|
121
141
|
);
|
|
122
142
|
|
|
123
143
|
return <Tabs tabs={tabs} toolbar={toolbar} />;
|
|
@@ -126,16 +146,53 @@ const RelativeGrowthAdvantageTabs: FunctionComponent<RelativeGrowthAdvantageTabs
|
|
|
126
146
|
type RelativeGrowthAdvantageToolbarProps = {
|
|
127
147
|
yAxisScaleType: ScaleType;
|
|
128
148
|
setYAxisScaleType: (scaleType: ScaleType) => void;
|
|
149
|
+
generationTime: number;
|
|
129
150
|
};
|
|
130
151
|
|
|
131
152
|
const RelativeGrowthAdvantageToolbar: FunctionComponent<RelativeGrowthAdvantageToolbarProps> = ({
|
|
132
153
|
yAxisScaleType,
|
|
133
154
|
setYAxisScaleType,
|
|
155
|
+
generationTime,
|
|
134
156
|
}) => {
|
|
135
157
|
return (
|
|
136
158
|
<div class='flex'>
|
|
137
159
|
<ScalingSelector yAxisScaleType={yAxisScaleType} setYAxisScaleType={setYAxisScaleType} />
|
|
138
|
-
<
|
|
160
|
+
<RelativeGrowthAdvantageInfo generationTime={generationTime} />
|
|
139
161
|
</div>
|
|
140
162
|
);
|
|
141
163
|
};
|
|
164
|
+
|
|
165
|
+
const RelativeGrowthAdvantageInfo: FunctionComponent<{ generationTime: number }> = ({ generationTime }) => {
|
|
166
|
+
return (
|
|
167
|
+
<Info size={{ width: '600px', height: '30vh' }}>
|
|
168
|
+
<InfoHeadline1>Relative growth advantage</InfoHeadline1>
|
|
169
|
+
<InfoParagraph>
|
|
170
|
+
If variants spread pre-dominantly by local transmission across demographic groups, this estimate
|
|
171
|
+
reflects the relative viral intrinsic growth advantage of the focal variant in the selected country and
|
|
172
|
+
time frame. We report the relative growth advantage per {generationTime} days (in percentage; 0% means
|
|
173
|
+
equal growth). Importantly, the relative growth advantage estimate reflects the advantage compared to
|
|
174
|
+
the co-circulating variants. Thus, as new variants spread, the advantage of the focal variant may
|
|
175
|
+
decrease. Different mechanisms can alter the intrinsic growth rate, including an intrinsic transmission
|
|
176
|
+
advantage, immune evasion, and a prolonged infectious period. When absolute numbers of a variant are
|
|
177
|
+
low, the growth advantage may merely reflect the current importance of introductions from abroad or the
|
|
178
|
+
variant spreading in a particular demographic group. In this case, the estimate does not provide
|
|
179
|
+
information on any intrinsic fitness advantages.
|
|
180
|
+
</InfoParagraph>
|
|
181
|
+
<InfoParagraph>
|
|
182
|
+
Example: Assume that 10 infections from the focal variant and 100 infections from the co-circulating
|
|
183
|
+
variants occur today and that the focal variant has a relative growth advantage of 50%. Then, if the
|
|
184
|
+
number of new infections from the co-circulating variants remains at 100 in {generationTime} days from
|
|
185
|
+
today, we expect the number of new infections from the focal variant to be 15.
|
|
186
|
+
</InfoParagraph>
|
|
187
|
+
|
|
188
|
+
<InfoHeadline2>Reference</InfoHeadline2>
|
|
189
|
+
<InfoParagraph>
|
|
190
|
+
Chen, Chaoran, et al. "Quantification of the spread of SARS-CoV-2 variant B.1.1.7 in Switzerland."
|
|
191
|
+
Epidemics (2021); doi:{' '}
|
|
192
|
+
<InfoLink href='https://www.sciencedirect.com/science/article/pii/S1755436521000335?via=ihub'>
|
|
193
|
+
10.1016/j.epidem.2021.100480
|
|
194
|
+
</InfoLink>
|
|
195
|
+
</InfoParagraph>
|
|
196
|
+
</Info>
|
|
197
|
+
);
|
|
198
|
+
};
|
|
@@ -3,18 +3,41 @@ import { useContext, useRef } from 'preact/hooks';
|
|
|
3
3
|
|
|
4
4
|
import { fetchAutocompleteList } from './fetchAutocompleteList';
|
|
5
5
|
import { LapisUrlContext } from '../LapisUrlContext';
|
|
6
|
+
import { ErrorBoundary } from '../components/error-boundary';
|
|
6
7
|
import { ErrorDisplay } from '../components/error-display';
|
|
7
8
|
import { LoadingDisplay } from '../components/loading-display';
|
|
8
9
|
import { NoDataDisplay } from '../components/no-data-display';
|
|
10
|
+
import { ResizeContainer } from '../components/resize-container';
|
|
9
11
|
import { useQuery } from '../useQuery';
|
|
10
12
|
|
|
11
|
-
export interface
|
|
13
|
+
export interface TextInputInnerProps {
|
|
12
14
|
lapisField: string;
|
|
13
15
|
placeholderText?: string;
|
|
14
16
|
initialValue?: string;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
|
-
export
|
|
19
|
+
export interface TextInputProps extends TextInputInnerProps {
|
|
20
|
+
width?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TextInput: FunctionComponent<TextInputProps> = ({ width, lapisField, placeholderText, initialValue }) => {
|
|
24
|
+
const defaultSize = { width: '100%', height: '3rem' };
|
|
25
|
+
const size = width === undefined ? undefined : { width, height: defaultSize.height };
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<ErrorBoundary defaultSize={defaultSize} size={size}>
|
|
29
|
+
<ResizeContainer size={size} defaultSize={defaultSize}>
|
|
30
|
+
<TextInputInner lapisField={lapisField} placeholderText={placeholderText} initialValue={initialValue} />
|
|
31
|
+
</ResizeContainer>
|
|
32
|
+
</ErrorBoundary>
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const TextInputInner: FunctionComponent<TextInputInnerProps> = ({
|
|
37
|
+
lapisField,
|
|
38
|
+
placeholderText,
|
|
39
|
+
initialValue,
|
|
40
|
+
}) => {
|
|
18
41
|
const lapis = useContext(LapisUrlContext);
|
|
19
42
|
|
|
20
43
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
@@ -58,7 +81,7 @@ export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, place
|
|
|
58
81
|
<>
|
|
59
82
|
<input
|
|
60
83
|
type='text'
|
|
61
|
-
class='input input-bordered'
|
|
84
|
+
class='input input-bordered w-full'
|
|
62
85
|
placeholder={placeholderText !== undefined ? placeholderText : lapisField}
|
|
63
86
|
onInput={onInput}
|
|
64
87
|
ref={inputRef}
|
|
@@ -25,7 +25,6 @@ const meta: Meta = {
|
|
|
25
25
|
parameters: withComponentDocs({
|
|
26
26
|
fetchMock: {},
|
|
27
27
|
componentDocs: {
|
|
28
|
-
tag: 'gs-app',
|
|
29
28
|
opensShadowDom: false,
|
|
30
29
|
expectsChildren: true,
|
|
31
30
|
codeExample,
|
|
@@ -92,7 +91,7 @@ export const FailsToFetchReferenceGenome: StoryObj<{ lapis: string }> = {
|
|
|
92
91
|
const canvas = within(canvasElement);
|
|
93
92
|
|
|
94
93
|
await waitFor(() => {
|
|
95
|
-
expect(canvas.getByText('Error')).toBeVisible();
|
|
94
|
+
expect(canvas.getByText('Error', { exact: false })).toBeVisible();
|
|
96
95
|
});
|
|
97
96
|
},
|
|
98
97
|
};
|
|
@@ -55,8 +55,10 @@ export class App extends LitElement {
|
|
|
55
55
|
override render() {
|
|
56
56
|
return this.updateReferenceGenome.render({
|
|
57
57
|
complete: () => html` <slot></slot>`,
|
|
58
|
-
error: () =>
|
|
59
|
-
|
|
58
|
+
error: () =>
|
|
59
|
+
html` <div class="m-2 w-full alert alert-error">
|
|
60
|
+
Error: Cannot fetch reference genome. Is LAPIS available?
|
|
61
|
+
</div>`,
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
64
|
|
|
@@ -3,6 +3,7 @@ import { expect, waitFor } from '@storybook/test';
|
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
4
4
|
import { html } from 'lit';
|
|
5
5
|
|
|
6
|
+
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
6
7
|
import { LAPIS_URL } from '../../constants';
|
|
7
8
|
import {
|
|
8
9
|
type DateRangeSelectorProps,
|
|
@@ -14,20 +15,33 @@ import {
|
|
|
14
15
|
PRESET_VALUE_LAST_6_MONTHS,
|
|
15
16
|
PRESET_VALUE_LAST_MONTH,
|
|
16
17
|
} from '../../preact/dateRangeSelector/date-range-selector';
|
|
17
|
-
import './date-range-selector
|
|
18
|
+
import './gs-date-range-selector';
|
|
18
19
|
import '../app';
|
|
19
20
|
import { toYYYYMMDD } from '../../preact/dateRangeSelector/dateConversion';
|
|
20
21
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
21
22
|
|
|
23
|
+
const codeExample = String.raw`
|
|
24
|
+
<gs-date-range-selector
|
|
25
|
+
customSelectOptions='[{ "label": "Year 2021", "dateFrom": "2021-01-01", "dateTo": "2021-12-31" }]'
|
|
26
|
+
earliestDate="1970-01-01"
|
|
27
|
+
initialValue="${PRESET_VALUE_LAST_6_MONTHS}"
|
|
28
|
+
width="100%"
|
|
29
|
+
></gs-date-range-selector>`;
|
|
30
|
+
|
|
22
31
|
const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
23
32
|
title: 'Input/DateRangeSelector',
|
|
24
33
|
component: 'gs-date-range-selector',
|
|
25
|
-
parameters: {
|
|
34
|
+
parameters: withComponentDocs({
|
|
26
35
|
actions: {
|
|
27
36
|
handles: ['gs-date-range-changed'],
|
|
28
37
|
},
|
|
29
38
|
fetchMock: {},
|
|
30
|
-
|
|
39
|
+
componentDocs: {
|
|
40
|
+
opensShadowDom: true,
|
|
41
|
+
expectsChildren: false,
|
|
42
|
+
codeExample,
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
31
45
|
argTypes: {
|
|
32
46
|
initialValue: {
|
|
33
47
|
control: {
|
|
@@ -44,13 +58,30 @@ const meta: Meta<DateRangeSelectorProps<'CustomDateRange'>> = {
|
|
|
44
58
|
'CustomDateRange',
|
|
45
59
|
],
|
|
46
60
|
},
|
|
61
|
+
customSelectOptions: {
|
|
62
|
+
control: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
earliestDate: {
|
|
67
|
+
control: {
|
|
68
|
+
type: 'text',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
width: {
|
|
72
|
+
control: {
|
|
73
|
+
type: 'text',
|
|
74
|
+
},
|
|
75
|
+
},
|
|
47
76
|
},
|
|
48
77
|
args: {
|
|
49
78
|
customSelectOptions: [{ label: 'CustomDateRange', dateFrom: '2021-01-01', dateTo: '2021-12-31' }],
|
|
50
79
|
earliestDate: '1970-01-01',
|
|
51
80
|
initialValue: PRESET_VALUE_LAST_6_MONTHS,
|
|
81
|
+
width: '100%',
|
|
52
82
|
},
|
|
53
83
|
decorators: [withActions],
|
|
84
|
+
tags: ['autodocs'],
|
|
54
85
|
};
|
|
55
86
|
|
|
56
87
|
export default meta;
|
|
@@ -63,6 +94,7 @@ export const DateRangeSelectorStory: StoryObj<DateRangeSelectorProps<'CustomDate
|
|
|
63
94
|
.customSelectOptions=${args.customSelectOptions}
|
|
64
95
|
.earliestDate=${args.earliestDate}
|
|
65
96
|
.initialValue=${args.initialValue}
|
|
97
|
+
.width=${args.width}
|
|
66
98
|
></gs-date-range-selector>
|
|
67
99
|
</div>
|
|
68
100
|
</gs-app>`,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { customElement, property } from 'lit/decorators.js';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type CustomSelectOption,
|
|
5
|
+
DateRangeSelector,
|
|
6
|
+
type PresetOptionValues,
|
|
7
|
+
} from '../../preact/dateRangeSelector/date-range-selector';
|
|
8
|
+
import { type Equals, type Expect } from '../../utils/typeAssertions';
|
|
9
|
+
import { PreactLitAdapter } from '../PreactLitAdapter';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ## Context
|
|
13
|
+
* This component is a group of input fields designed to specify a date range. It consists of 3 fields:
|
|
14
|
+
*
|
|
15
|
+
* - a select field to choose a predefined date range,
|
|
16
|
+
* - an input field with an attached date picker for the start date,
|
|
17
|
+
* - an input field with an attached date picker for the end date.
|
|
18
|
+
*
|
|
19
|
+
* Setting a value in the select field will overwrite the previous values of the start and end date.
|
|
20
|
+
* Setting a value in either of the date pickers will set the select field to "custom",
|
|
21
|
+
* which represents an arbitrary date range.
|
|
22
|
+
*
|
|
23
|
+
* @fires {CustomEvent<{ dateFrom: string; dateTo: string; }>} gs-date-range-changed
|
|
24
|
+
* Fired when:
|
|
25
|
+
* - The select field is changed,
|
|
26
|
+
* - A date is selected in either of the date pickers,
|
|
27
|
+
* - A date was typed into either of the date input fields, and the input field loses focus ("on blur").
|
|
28
|
+
* Contains the dates in the format `YYYY-MM-DD`.
|
|
29
|
+
*/
|
|
30
|
+
@customElement('gs-date-range-selector')
|
|
31
|
+
export class DateRangeSelectorComponent extends PreactLitAdapter {
|
|
32
|
+
/**
|
|
33
|
+
* An array of custom options that the select field should provide,
|
|
34
|
+
* in addition to the predefined options.
|
|
35
|
+
* The `label` will be shown to the user, and it will be available as `initialValue`.
|
|
36
|
+
* The dates must be in the format `YYYY-MM-DD`.
|
|
37
|
+
*/
|
|
38
|
+
@property({ type: Array })
|
|
39
|
+
customSelectOptions: { label: string; dateFrom: string; dateTo: string }[] = [];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The `dateFrom` value to use in the `allTimes` preset in the format `YYYY-MM-DD`.
|
|
43
|
+
*/
|
|
44
|
+
@property({ type: String })
|
|
45
|
+
earliestDate: string | undefined = '1900-01-01';
|
|
46
|
+
|
|
47
|
+
// prettier-ignore
|
|
48
|
+
// The multiline union type must not start with `| 'custom'` - Storybook will list "" as the first type which is wrong
|
|
49
|
+
/**
|
|
50
|
+
* The initial value to use for this date range selector.
|
|
51
|
+
* Must be a valid label from the preset labels or a `label` given in the `customSelectOptions`.
|
|
52
|
+
*
|
|
53
|
+
* If the value is invalid, the component will default to `'last6Months'`.
|
|
54
|
+
*/
|
|
55
|
+
@property()
|
|
56
|
+
initialValue:
|
|
57
|
+
'custom'
|
|
58
|
+
| 'allTimes'
|
|
59
|
+
| 'last2Weeks'
|
|
60
|
+
| 'lastMonth'
|
|
61
|
+
| 'last2Months'
|
|
62
|
+
| 'last3Months'
|
|
63
|
+
| 'last6Months'
|
|
64
|
+
| string
|
|
65
|
+
| undefined = 'last6Months';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The width of the component.
|
|
69
|
+
*
|
|
70
|
+
* If not set, the component will take the full width of its container.
|
|
71
|
+
*
|
|
72
|
+
* The width should be a string with a unit in css style, e.g. '100%', '500px' or '50vw'.
|
|
73
|
+
* If the unit is %, the size will be relative to the container of the component.
|
|
74
|
+
*/
|
|
75
|
+
@property({ type: Object })
|
|
76
|
+
width: string | undefined = undefined;
|
|
77
|
+
|
|
78
|
+
override render() {
|
|
79
|
+
return (
|
|
80
|
+
<DateRangeSelector
|
|
81
|
+
customSelectOptions={this.customSelectOptions}
|
|
82
|
+
earliestDate={this.earliestDate}
|
|
83
|
+
initialValue={this.initialValue}
|
|
84
|
+
width={this.width}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
declare global {
|
|
91
|
+
interface HTMLElementTagNameMap {
|
|
92
|
+
'gs-date-range-selector': DateRangeSelectorComponent;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface HTMLElementEventMap {
|
|
96
|
+
'gs-date-range-changed': CustomEvent<{
|
|
97
|
+
dateFrom: string;
|
|
98
|
+
dateTo: string;
|
|
99
|
+
}>;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* eslint-disable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
104
|
+
type CustomSelectOptionsMatches = Expect<
|
|
105
|
+
Equals<typeof DateRangeSelectorComponent.prototype.customSelectOptions, CustomSelectOption<string>[]>
|
|
106
|
+
>;
|
|
107
|
+
type InitialValueMatches = Expect<
|
|
108
|
+
Equals<typeof DateRangeSelectorComponent.prototype.initialValue, PresetOptionValues | string | undefined>
|
|
109
|
+
>;
|
|
110
|
+
/* eslint-enable @typescript-eslint/no-unused-vars, no-unused-vars */
|
|
@@ -7,11 +7,18 @@ import { ifDefined } from 'lit/directives/if-defined.js';
|
|
|
7
7
|
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
8
8
|
import { AGGREGATED_ENDPOINT, LAPIS_URL } from '../../constants';
|
|
9
9
|
import '../app';
|
|
10
|
-
import './location-filter
|
|
10
|
+
import './gs-location-filter';
|
|
11
11
|
import data from '../../preact/locationFilter/__mockData__/aggregated.json';
|
|
12
12
|
import { type LocationFilterProps } from '../../preact/locationFilter/location-filter';
|
|
13
13
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
14
14
|
|
|
15
|
+
const codeExample = String.raw`
|
|
16
|
+
<gs-location-filter
|
|
17
|
+
fields="['region', 'country']"
|
|
18
|
+
value='Europe / Switzerland'
|
|
19
|
+
width="100%"
|
|
20
|
+
></gs-location-filter>`;
|
|
21
|
+
|
|
15
22
|
const meta: Meta = {
|
|
16
23
|
title: 'Input/Location filter',
|
|
17
24
|
component: 'gs-location-filter',
|
|
@@ -20,12 +27,28 @@ const meta: Meta = {
|
|
|
20
27
|
handles: ['gs-location-changed'],
|
|
21
28
|
},
|
|
22
29
|
componentDocs: {
|
|
23
|
-
tag: 'gs-location-filter',
|
|
24
30
|
opensShadowDom: true,
|
|
25
31
|
expectsChildren: false,
|
|
26
|
-
codeExample
|
|
32
|
+
codeExample,
|
|
27
33
|
},
|
|
28
34
|
}),
|
|
35
|
+
argTypes: {
|
|
36
|
+
fields: {
|
|
37
|
+
control: {
|
|
38
|
+
type: 'object',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
initialValue: {
|
|
42
|
+
control: {
|
|
43
|
+
type: 'text',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
width: {
|
|
47
|
+
control: {
|
|
48
|
+
type: 'text',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
29
52
|
decorators: [withActions],
|
|
30
53
|
tags: ['autodocs'],
|
|
31
54
|
};
|
|
@@ -39,6 +62,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
39
62
|
<gs-location-filter
|
|
40
63
|
.fields=${args.fields}
|
|
41
64
|
initialValue=${ifDefined(args.initialValue)}
|
|
65
|
+
.width=${args.width}
|
|
42
66
|
></gs-location-filter>
|
|
43
67
|
</div>
|
|
44
68
|
</gs-app>`;
|
|
@@ -46,6 +70,7 @@ const Template: StoryObj<LocationFilterProps> = {
|
|
|
46
70
|
args: {
|
|
47
71
|
fields: ['region', 'country', 'division', 'location'],
|
|
48
72
|
initialValue: '',
|
|
73
|
+
width: '100%',
|
|
49
74
|
},
|
|
50
75
|
};
|
|
51
76
|
|
|
@@ -119,7 +144,7 @@ export const FetchingLocationsFails: StoryObj<LocationFilterProps> = {
|
|
|
119
144
|
const canvas = await withinShadowRoot(canvasElement, 'gs-location-filter');
|
|
120
145
|
|
|
121
146
|
await waitFor(() =>
|
|
122
|
-
expect(canvas.getByText('
|
|
147
|
+
expect(canvas.getByText('Oops! Something went wrong.', { exact: false })).toBeInTheDocument(),
|
|
123
148
|
);
|
|
124
149
|
},
|
|
125
150
|
};
|
|
@@ -48,8 +48,19 @@ export class LocationFilterComponent extends PreactLitAdapter {
|
|
|
48
48
|
@property({ type: Array })
|
|
49
49
|
fields: string[] = [];
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The width of the component.
|
|
53
|
+
*
|
|
54
|
+
* If not set, the component will take the full width of its container.
|
|
55
|
+
*
|
|
56
|
+
* The width should be a string with a unit in css style, e.g. '100%', '500px' or '50vw'.
|
|
57
|
+
* If the unit is %, the size will be relative to the container of the component.
|
|
58
|
+
*/
|
|
59
|
+
@property({ type: Object })
|
|
60
|
+
width: string | undefined = undefined;
|
|
61
|
+
|
|
51
62
|
override render() {
|
|
52
|
-
return <LocationFilter initialValue={this.initialValue} fields={this.fields} />;
|
|
63
|
+
return <LocationFilter initialValue={this.initialValue} fields={this.fields} width={this.width} />;
|
|
53
64
|
}
|
|
54
65
|
}
|
|
55
66
|
|
|
@@ -3,22 +3,47 @@ import { expect, fn, userEvent, waitFor } from '@storybook/test';
|
|
|
3
3
|
import type { Meta, StoryObj } from '@storybook/web-components';
|
|
4
4
|
import { html } from 'lit';
|
|
5
5
|
|
|
6
|
+
import { withComponentDocs } from '../../../.storybook/ComponentDocsBlock';
|
|
6
7
|
import { LAPIS_URL } from '../../constants';
|
|
7
8
|
import '../app';
|
|
8
9
|
import { type MutationFilterProps } from '../../preact/mutationFilter/mutation-filter';
|
|
9
10
|
import { withinShadowRoot } from '../withinShadowRoot.story';
|
|
10
|
-
import './mutation-filter
|
|
11
|
+
import './gs-mutation-filter';
|
|
11
12
|
|
|
12
|
-
const
|
|
13
|
+
const codeExample = String.raw`
|
|
14
|
+
<gs-mutation-filter
|
|
15
|
+
initialValue='["A123T"]'
|
|
16
|
+
size='{ "width": "100%", "height": "6.5rem" }'
|
|
17
|
+
></gs-mutation-filter>`;
|
|
18
|
+
|
|
19
|
+
const meta: Meta<MutationFilterProps> = {
|
|
13
20
|
title: 'Input/Mutation filter',
|
|
14
21
|
component: 'gs-mutation-filter',
|
|
15
|
-
parameters: {
|
|
22
|
+
parameters: withComponentDocs({
|
|
16
23
|
actions: {
|
|
17
24
|
handles: ['gs-mutation-filter-changed', 'gs-mutation-filter-on-blur'],
|
|
18
25
|
},
|
|
19
26
|
fetchMock: {},
|
|
27
|
+
componentDocs: {
|
|
28
|
+
opensShadowDom: true,
|
|
29
|
+
expectsChildren: false,
|
|
30
|
+
codeExample,
|
|
31
|
+
},
|
|
32
|
+
}),
|
|
33
|
+
argTypes: {
|
|
34
|
+
initialValue: {
|
|
35
|
+
control: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
size: {
|
|
40
|
+
control: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
20
44
|
},
|
|
21
45
|
decorators: [withActions],
|
|
46
|
+
tags: ['autodocs'],
|
|
22
47
|
};
|
|
23
48
|
|
|
24
49
|
export default meta;
|
|
@@ -27,12 +52,13 @@ const Template: StoryObj<MutationFilterProps> = {
|
|
|
27
52
|
render: (args) => {
|
|
28
53
|
return html` <gs-app lapis="${LAPIS_URL}">
|
|
29
54
|
<div class="max-w-screen-lg">
|
|
30
|
-
<gs-mutation-filter .initialValue=${args.initialValue}></gs-mutation-filter>
|
|
55
|
+
<gs-mutation-filter .initialValue=${args.initialValue} .size=${args.size}></gs-mutation-filter>
|
|
31
56
|
</div>
|
|
32
57
|
</gs-app>`;
|
|
33
58
|
},
|
|
34
59
|
args: {
|
|
35
60
|
initialValue: [],
|
|
61
|
+
size: { width: '100%', height: '3rem' },
|
|
36
62
|
},
|
|
37
63
|
};
|
|
38
64
|
|