@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.
Files changed (65) hide show
  1. package/custom-elements.json +1021 -804
  2. package/dist/dashboard-components.js +647 -218
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +336 -126
  5. package/dist/style.css +214 -36
  6. package/package.json +4 -4
  7. package/src/preact/aggregatedData/aggregate.stories.tsx +2 -0
  8. package/src/preact/aggregatedData/aggregate.tsx +33 -28
  9. package/src/preact/components/error-boundary.stories.tsx +62 -0
  10. package/src/preact/components/error-boundary.tsx +31 -0
  11. package/src/preact/components/error-display.stories.tsx +24 -3
  12. package/src/preact/components/error-display.tsx +14 -1
  13. package/src/preact/components/headline.stories.tsx +19 -1
  14. package/src/preact/components/headline.tsx +9 -1
  15. package/src/preact/components/info.stories.tsx +24 -3
  16. package/src/preact/components/info.tsx +49 -5
  17. package/src/preact/components/loading-display.stories.tsx +6 -6
  18. package/src/preact/components/loading-display.tsx +1 -1
  19. package/src/preact/components/no-data-display.tsx +5 -1
  20. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +17 -0
  21. package/src/preact/dateRangeSelector/date-range-selector.tsx +43 -15
  22. package/src/preact/locationFilter/location-filter.stories.tsx +23 -6
  23. package/src/preact/locationFilter/location-filter.tsx +29 -18
  24. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +3 -0
  25. package/src/preact/mutationComparison/mutation-comparison.tsx +31 -27
  26. package/src/preact/mutationFilter/mutation-filter.stories.tsx +17 -2
  27. package/src/preact/mutationFilter/mutation-filter.tsx +26 -8
  28. package/src/preact/mutations/mutations.stories.tsx +3 -0
  29. package/src/preact/mutations/mutations.tsx +32 -26
  30. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +4 -0
  31. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +57 -31
  32. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +3 -0
  33. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +89 -32
  34. package/src/preact/textInput/text-input.tsx +26 -3
  35. package/src/web-components/app.stories.ts +1 -2
  36. package/src/web-components/app.ts +4 -2
  37. package/src/web-components/index.ts +1 -1
  38. package/src/web-components/input/{date-range-selector-component.stories.ts → gs-date-range-selector.stories.ts} +35 -3
  39. package/src/web-components/input/gs-date-range-selector.tsx +110 -0
  40. package/src/web-components/input/{location-filter-component.stories.ts → gs-location-filter.stories.ts} +29 -4
  41. package/src/web-components/input/{location-filter-component.tsx → gs-location-filter.tsx} +12 -1
  42. package/src/web-components/input/{mutation-filter-component.stories.ts → gs-mutation-filter.stories.ts} +30 -4
  43. package/src/web-components/input/gs-mutation-filter.tsx +114 -0
  44. package/src/web-components/input/{text-input-component.stories.ts → gs-text-input.stories.ts} +42 -3
  45. package/src/web-components/input/gs-text-input.tsx +73 -0
  46. package/src/web-components/input/index.ts +4 -4
  47. package/src/web-components/visualization/data_visualization_statistical_analysis.mdx +26 -0
  48. package/src/web-components/{display/aggregate-component.stories.ts → visualization/gs-aggregate.stories.ts} +8 -6
  49. package/src/web-components/{display/aggregate-component.tsx → visualization/gs-aggregate.tsx} +16 -2
  50. package/src/web-components/{display/mutation-comparison-component.stories.ts → visualization/gs-mutation-comparison.stories.ts} +11 -9
  51. package/src/web-components/{display/mutation-comparison-component.tsx → visualization/gs-mutation-comparison.tsx} +8 -1
  52. package/src/web-components/{display/mutations-component.stories.ts → visualization/gs-mutations.stories.ts} +30 -11
  53. package/src/web-components/visualization/gs-mutations.tsx +94 -0
  54. package/src/web-components/{display/prevalence-over-time-component.stories.ts → visualization/gs-prevalence-over-time.stories.ts} +24 -1
  55. package/src/web-components/visualization/gs-prevalence-over-time.tsx +148 -0
  56. package/src/web-components/{display/relative-growth-advantage-component.stories.ts → visualization/gs-relative-growth-advantage.stories.ts} +21 -1
  57. package/src/web-components/visualization/gs-relative-growth-advantage.tsx +100 -0
  58. package/src/web-components/visualization/index.ts +5 -0
  59. package/src/web-components/display/index.ts +0 -5
  60. package/src/web-components/display/mutations-component.tsx +0 -40
  61. package/src/web-components/display/prevalence-over-time-component.tsx +0 -58
  62. package/src/web-components/display/relative-growth-advantage-component.tsx +0 -49
  63. package/src/web-components/input/date-range-selector-component.tsx +0 -46
  64. package/src/web-components/input/mutation-filter-component.tsx +0 -35
  65. 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
- <ResizeContainer size={size} defaultSize={{ height: '700px', width: '100%' }}>
74
- <Headline heading={headline}>
75
- <RelativeGrowthAdvantageTabs
76
- data={data}
77
- yAxisScaleType={yAxisScaleType}
78
- setYAxisScaleType={setYAxisScaleType}
79
- views={views}
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 yAxisScaleType={yAxisScaleType} setYAxisScaleType={setYAxisScaleType} />
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
- <Info className='ml-1' content='Line chart' />
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 TextInputProps {
13
+ export interface TextInputInnerProps {
12
14
  lapisField: string;
13
15
  placeholderText?: string;
14
16
  initialValue?: string;
15
17
  }
16
18
 
17
- export const TextInput: FunctionComponent<TextInputProps> = ({ lapisField, placeholderText, initialValue }) => {
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: () => html`<p>Error</p>`, // TODO(#143): Add more advanced error handling
59
- pending: () => html` <p>Loading reference genomes...</p> `,
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
 
@@ -1,3 +1,3 @@
1
1
  export { App } from './app.js';
2
- export * from './display';
2
+ export * from './visualization';
3
3
  export * from './input';
@@ -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-component';
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-component';
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: `<gs-location-filter fields="['continent', 'country']" value='Europe / Switzerland'></gs-location-filter>`,
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('Bad Request: {"error":"no data"} ', { exact: false })).toBeInTheDocument(),
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-component';
11
+ import './gs-mutation-filter';
11
12
 
12
- const meta: Meta = {
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