@genspectrum/dashboard-components 0.1.1
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/LICENSE +661 -0
- package/README.md +109 -0
- package/custom-elements.json +1587 -0
- package/dist/dashboard-components.js +7322 -0
- package/dist/dashboard-components.js.map +1 -0
- package/dist/genspectrum-components.d.ts +298 -0
- package/dist/style.css +2930 -0
- package/package.json +109 -0
- package/src/constants.ts +6 -0
- package/src/index.ts +1 -0
- package/src/lapisApi/ReferenceGenome.ts +30 -0
- package/src/lapisApi/__mockData__/referenceGenome.json +58 -0
- package/src/lapisApi/lapisApi.ts +99 -0
- package/src/lapisApi/lapisTypes.ts +51 -0
- package/src/operator/Dataset.ts +3 -0
- package/src/operator/DivisionOperator.spec.ts +27 -0
- package/src/operator/DivisionOperator.ts +60 -0
- package/src/operator/FetchAggregatedOperator.ts +44 -0
- package/src/operator/FetchInsertionsOperator.ts +24 -0
- package/src/operator/FetchSubstitutionsOrDeletionsOperator.ts +49 -0
- package/src/operator/FillMissingOperator.spec.ts +26 -0
- package/src/operator/FillMissingOperator.ts +30 -0
- package/src/operator/GroupByAndSumOperator.spec.ts +26 -0
- package/src/operator/GroupByAndSumOperator.ts +26 -0
- package/src/operator/GroupByOperator.spec.ts +43 -0
- package/src/operator/GroupByOperator.ts +32 -0
- package/src/operator/MapOperator.spec.ts +13 -0
- package/src/operator/MapOperator.ts +16 -0
- package/src/operator/MockOperator.spec.ts +11 -0
- package/src/operator/MockOperator.ts +12 -0
- package/src/operator/Operator.ts +5 -0
- package/src/operator/SlidingOperator.spec.ts +52 -0
- package/src/operator/SlidingOperator.ts +23 -0
- package/src/operator/SortOperator.spec.ts +13 -0
- package/src/operator/SortOperator.ts +16 -0
- package/src/preact/LapisUrlContext.ts +3 -0
- package/src/preact/ReferenceGenomeContext.ts +5 -0
- package/src/preact/components/SegmentSelector.tsx +62 -0
- package/src/preact/components/chart.stories.tsx +42 -0
- package/src/preact/components/chart.tsx +32 -0
- package/src/preact/components/checkbox-selector.stories.tsx +56 -0
- package/src/preact/components/checkbox-selector.tsx +46 -0
- package/src/preact/components/confidence-interval-selector.tsx +45 -0
- package/src/preact/components/csv-download-button.stories.tsx +25 -0
- package/src/preact/components/csv-download-button.tsx +51 -0
- package/src/preact/components/error-display.stories.tsx +22 -0
- package/src/preact/components/error-display.tsx +5 -0
- package/src/preact/components/headline.stories.tsx +29 -0
- package/src/preact/components/headline.tsx +16 -0
- package/src/preact/components/info.stories.tsx +22 -0
- package/src/preact/components/info.tsx +16 -0
- package/src/preact/components/loading-display.stories.tsx +20 -0
- package/src/preact/components/loading-display.tsx +5 -0
- package/src/preact/components/min-max-percent-slider.css +40 -0
- package/src/preact/components/min-max-range-slider.tsx +95 -0
- package/src/preact/components/mutation-type-selector.tsx +30 -0
- package/src/preact/components/no-data-display.stories.tsx +20 -0
- package/src/preact/components/no-data-display.tsx +5 -0
- package/src/preact/components/percent-intput.tsx +49 -0
- package/src/preact/components/proportion-selector-dropdown.stories.tsx +66 -0
- package/src/preact/components/proportion-selector-dropdown.tsx +33 -0
- package/src/preact/components/proportion-selector.stories.tsx +81 -0
- package/src/preact/components/proportion-selector.tsx +43 -0
- package/src/preact/components/scaling-selector.stories.tsx +25 -0
- package/src/preact/components/scaling-selector.tsx +36 -0
- package/src/preact/components/select.stories.tsx +42 -0
- package/src/preact/components/select.tsx +21 -0
- package/src/preact/components/table.stories.tsx +24 -0
- package/src/preact/components/table.tsx +51 -0
- package/src/preact/components/tabs.stories.tsx +60 -0
- package/src/preact/components/tabs.tsx +49 -0
- package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +32 -0
- package/src/preact/dateRangeSelector/date-range-selector.tsx +228 -0
- package/src/preact/dateRangeSelector/dateConversion.ts +8 -0
- package/src/preact/locationFilter/__mockData__/aggregated.json +775 -0
- package/src/preact/locationFilter/fetchAutocompletionList.spec.ts +36 -0
- package/src/preact/locationFilter/fetchAutocompletionList.ts +43 -0
- package/src/preact/locationFilter/location-filter.stories.tsx +50 -0
- package/src/preact/locationFilter/location-filter.tsx +112 -0
- package/src/preact/mutationComparison/__mockData__/nucleotideMutationsOtherVariant.json +295 -0
- package/src/preact/mutationComparison/__mockData__/nucleotideMutationsSomeVariant.json +304 -0
- package/src/preact/mutationComparison/fetchMutationData.spec.ts +118 -0
- package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +125 -0
- package/src/preact/mutationComparison/getMutationComparisonTableData.ts +40 -0
- package/src/preact/mutationComparison/mutation-comparison-table.tsx +43 -0
- package/src/preact/mutationComparison/mutation-comparison-venn.tsx +122 -0
- package/src/preact/mutationComparison/mutation-comparison.stories.tsx +152 -0
- package/src/preact/mutationComparison/mutation-comparison.tsx +179 -0
- package/src/preact/mutationComparison/queryMutationData.ts +53 -0
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +164 -0
- package/src/preact/mutationFilter/mutation-filter.tsx +268 -0
- package/src/preact/mutationFilter/parseAndValidateMutation.ts +54 -0
- package/src/preact/mutationFilter/parseMutation.spec.ts +150 -0
- package/src/preact/mutationFilter/sequenceTypeFromSegment.spec.ts +66 -0
- package/src/preact/mutationFilter/sequenceTypeFromSegment.ts +20 -0
- package/src/preact/mutations/__mockData__/nucleotideInsertions.json +252 -0
- package/src/preact/mutations/__mockData__/nucleotideMutations.json +880 -0
- package/src/preact/mutations/getInsertionsTableData.spec.ts +36 -0
- package/src/preact/mutations/getInsertionsTableData.ts +10 -0
- package/src/preact/mutations/getMutationsGridData.spec.ts +135 -0
- package/src/preact/mutations/getMutationsGridData.ts +92 -0
- package/src/preact/mutations/getMutationsTableData.spec.ts +94 -0
- package/src/preact/mutations/getMutationsTableData.ts +17 -0
- package/src/preact/mutations/mutations-grid.tsx +84 -0
- package/src/preact/mutations/mutations-insertions-table.tsx +33 -0
- package/src/preact/mutations/mutations-table.tsx +47 -0
- package/src/preact/mutations/mutations.stories.tsx +95 -0
- package/src/preact/mutations/mutations.tsx +192 -0
- package/src/preact/mutations/queryMutations.ts +55 -0
- package/src/preact/prevalenceOverTime/__mockData__/denominator.json +1700 -0
- package/src/preact/prevalenceOverTime/__mockData__/denominatorOneVariant.json +608 -0
- package/src/preact/prevalenceOverTime/__mockData__/numeratorEG.json +1560 -0
- package/src/preact/prevalenceOverTime/__mockData__/numeratorJN1.json +592 -0
- package/src/preact/prevalenceOverTime/__mockData__/numeratorOneVariant.json +604 -0
- package/src/preact/prevalenceOverTime/getPrevalenceOverTimeTableData.spec.ts +67 -0
- package/src/preact/prevalenceOverTime/getPrevalenceOverTimeTableData.ts +18 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +105 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +86 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +141 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time-table.tsx +46 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +165 -0
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +202 -0
- package/src/preact/relativeGrowthAdvantage/__mockData__/denominator.json +376 -0
- package/src/preact/relativeGrowthAdvantage/__mockData__/numerator.json +332 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +138 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +71 -0
- package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +136 -0
- package/src/preact/shared/charts/LogitScale.ts +48 -0
- package/src/preact/shared/charts/colors.ts +26 -0
- package/src/preact/shared/charts/confideceInterval.ts +29 -0
- package/src/preact/shared/charts/getYAxisScale.ts +16 -0
- package/src/preact/shared/charts/scales.ts +16 -0
- package/src/preact/shared/icons/DeleteIcon.tsx +17 -0
- package/src/preact/shared/sort/sortInsertions.spec.ts +47 -0
- package/src/preact/shared/sort/sortInsertions.ts +21 -0
- package/src/preact/shared/sort/sortMutationPositions.spec.ts +31 -0
- package/src/preact/shared/sort/sortMutationPositions.ts +14 -0
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +47 -0
- package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +17 -0
- package/src/preact/shared/table/formatProportion.ts +3 -0
- package/src/preact/textInput/__mockData__/aggregated_hosts.json +24 -0
- package/src/preact/textInput/fetchAutocompleteList.ts +9 -0
- package/src/preact/textInput/text-input.stories.tsx +49 -0
- package/src/preact/textInput/text-input.tsx +73 -0
- package/src/preact/useQuery.ts +27 -0
- package/src/query/queryInsertions.ts +14 -0
- package/src/query/queryPrevalenceOverTime.ts +126 -0
- package/src/query/queryRelativeGrowthAdvantage.ts +131 -0
- package/src/query/querySubstitutionsOrDeletions.ts +19 -0
- package/src/styles/tailwind.css +3 -0
- package/src/styles/tailwind.d.ts +3 -0
- package/src/types.ts +23 -0
- package/src/utils/mutations.spec.ts +64 -0
- package/src/utils/mutations.ts +165 -0
- package/src/utils/temporal.spec.ts +97 -0
- package/src/utils/temporal.ts +348 -0
- package/src/utils/test-utils.ts +5 -0
- package/src/utils/type-utils.ts +15 -0
- package/src/utils/utils.spec.ts +16 -0
- package/src/utils/utils.ts +38 -0
- package/src/web-components/PreactLitAdapter.tsx +62 -0
- package/src/web-components/PreactLitAdapterWithGridJsStyles.tsx +12 -0
- package/src/web-components/app.ts +51 -0
- package/src/web-components/display/index.ts +4 -0
- package/src/web-components/display/mutation-comparison-component.stories.ts +138 -0
- package/src/web-components/display/mutation-comparison-component.tsx +31 -0
- package/src/web-components/display/mutations-component.stories.ts +107 -0
- package/src/web-components/display/mutations-component.tsx +27 -0
- package/src/web-components/display/prevalence-over-time-component.stories.ts +205 -0
- package/src/web-components/display/prevalence-over-time-component.tsx +46 -0
- package/src/web-components/display/relative-growth-advantage-component.stories.ts +89 -0
- package/src/web-components/display/relative-growth-advantage-component.tsx +37 -0
- package/src/web-components/index.ts +3 -0
- package/src/web-components/input/date-range-selector-component.stories.ts +53 -0
- package/src/web-components/input/date-range-selector-component.tsx +33 -0
- package/src/web-components/input/index.ts +4 -0
- package/src/web-components/input/location-filter-component.stories.ts +184 -0
- package/src/web-components/input/location-filter-component.tsx +68 -0
- package/src/web-components/input/location-filter.mdx +25 -0
- package/src/web-components/input/mutation-filter-component.stories.ts +97 -0
- package/src/web-components/input/mutation-filter-component.tsx +27 -0
- package/src/web-components/input/text-input-component.stories.ts +92 -0
- package/src/web-components/input/text-input-component.tsx +30 -0
- package/src/web-components/lapis-context.ts +3 -0
- package/src/web-components/reference-genome-context.ts +5 -0
- package/src/web-components/withinShadowRoot.story.ts +34 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { type Meta } from '@storybook/web-components';
|
|
3
|
+
|
|
4
|
+
import { CsvDownloadButton, type CsvDownloadButtonProps } from './csv-download-button';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<CsvDownloadButtonProps> = {
|
|
7
|
+
title: 'Component/CSV Download Button',
|
|
8
|
+
parameters: { fetchMock: {} },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export default meta;
|
|
12
|
+
|
|
13
|
+
export const DownloadButton: StoryObj = {
|
|
14
|
+
render: (args) => {
|
|
15
|
+
return <CsvDownloadButton label={args.label} filename={args.filename} getData={() => args.data} />;
|
|
16
|
+
},
|
|
17
|
+
args: {
|
|
18
|
+
data: [
|
|
19
|
+
{ field1: 'value1_1', fieldWithEmptyValues: null, field2: 5 },
|
|
20
|
+
{ field1: 'value2_1', fieldWithEmptyValues: undefined, field2: 42 },
|
|
21
|
+
],
|
|
22
|
+
label: 'My Download Button',
|
|
23
|
+
filename: 'myFile.csv',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
export interface CsvDownloadButtonProps {
|
|
4
|
+
label?: string;
|
|
5
|
+
filename?: string;
|
|
6
|
+
getData: () => Record<string, string | number | boolean | null>[];
|
|
7
|
+
className?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const CsvDownloadButton: FunctionComponent<CsvDownloadButtonProps> = ({
|
|
11
|
+
label = 'Download',
|
|
12
|
+
filename = 'data.csv',
|
|
13
|
+
getData,
|
|
14
|
+
className,
|
|
15
|
+
}) => {
|
|
16
|
+
const download = () => {
|
|
17
|
+
const content = getDownloadContent();
|
|
18
|
+
const blob = new Blob([content], { type: 'text/csv' });
|
|
19
|
+
const url = URL.createObjectURL(blob);
|
|
20
|
+
const a = document.createElement('a');
|
|
21
|
+
a.href = url;
|
|
22
|
+
a.download = filename;
|
|
23
|
+
a.click();
|
|
24
|
+
URL.revokeObjectURL(url);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getDownloadContent = () => {
|
|
28
|
+
const data = getData();
|
|
29
|
+
const keys = getDataKeys(data);
|
|
30
|
+
const header = `${keys.join(',')}\n`;
|
|
31
|
+
const rows = data.map((row) => keys.map((key) => row[key]).join(',')).join('\n');
|
|
32
|
+
return header + rows;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getDataKeys = (data: Record<string, string | number | boolean | null>[]) => {
|
|
36
|
+
const keysSet = data
|
|
37
|
+
.map((row) => Object.keys(row))
|
|
38
|
+
.reduce((accumulatedKeys, keys) => {
|
|
39
|
+
keys.forEach((key) => accumulatedKeys.add(key));
|
|
40
|
+
return accumulatedKeys;
|
|
41
|
+
}, new Set<string>());
|
|
42
|
+
|
|
43
|
+
return [...keysSet];
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<button className={className} onClick={download}>
|
|
48
|
+
{label}
|
|
49
|
+
</button>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import { ErrorDisplay } from './error-display';
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Component/Error',
|
|
8
|
+
component: ErrorDisplay,
|
|
9
|
+
parameters: { fetchMock: {} },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const ErrorStory: StoryObj = {
|
|
15
|
+
render: () => <ErrorDisplay error={new Error('some message')} />,
|
|
16
|
+
|
|
17
|
+
play: async ({ canvasElement }) => {
|
|
18
|
+
const canvas = within(canvasElement);
|
|
19
|
+
const error = canvas.getByText('Error: ', { exact: false });
|
|
20
|
+
await waitFor(() => expect(error).toBeInTheDocument());
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, within } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import Headline, { type HeadlineProps } from './headline';
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Headline> = {
|
|
7
|
+
title: 'Component/Headline',
|
|
8
|
+
component: Headline,
|
|
9
|
+
parameters: { fetchMock: {} },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const HeadlineStory: StoryObj<HeadlineProps> = {
|
|
15
|
+
render: (args) => (
|
|
16
|
+
<Headline {...args}>
|
|
17
|
+
<div class='flex justify-center px-4 py-16 bg-base-200'>Some Content</div>
|
|
18
|
+
</Headline>
|
|
19
|
+
),
|
|
20
|
+
args: {
|
|
21
|
+
heading: 'My Headline',
|
|
22
|
+
},
|
|
23
|
+
play: async ({ canvasElement }) => {
|
|
24
|
+
const canvas = within(canvasElement);
|
|
25
|
+
|
|
26
|
+
await expect(canvas.getByText('My Headline')).toBeInTheDocument();
|
|
27
|
+
await expect(canvas.getByText('Some Content')).toBeInTheDocument();
|
|
28
|
+
},
|
|
29
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
export interface HeadlineProps {
|
|
4
|
+
heading: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const Headline: FunctionComponent<HeadlineProps> = ({ heading, children }) => {
|
|
8
|
+
return (
|
|
9
|
+
<>
|
|
10
|
+
<h1>{heading}</h1>
|
|
11
|
+
{children}
|
|
12
|
+
</>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default Headline;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
|
|
3
|
+
import Info, { type InfoProps } from './info';
|
|
4
|
+
|
|
5
|
+
const meta: Meta<InfoProps> = {
|
|
6
|
+
title: 'Component/Info',
|
|
7
|
+
component: Info,
|
|
8
|
+
parameters: { fetchMock: {} },
|
|
9
|
+
args: {
|
|
10
|
+
content: 'This is a tooltip which shows some information.',
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
|
|
16
|
+
export const InfoStory: StoryObj<InfoProps> = {
|
|
17
|
+
render: (args) => (
|
|
18
|
+
<div class='flex justify-center px-4 py-16 bg-base-200'>
|
|
19
|
+
<Info {...args} />
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
export interface InfoProps {
|
|
4
|
+
content: string;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Info: FunctionComponent<InfoProps> = ({ content, className }) => {
|
|
9
|
+
return (
|
|
10
|
+
<div class={`${className} tooltip`} data-tip={content}>
|
|
11
|
+
<button class='btn btn-xs'>?</button>
|
|
12
|
+
</div>
|
|
13
|
+
);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default Info;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import { LoadingDisplay } from './loading-display';
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Component/Loading',
|
|
8
|
+
component: LoadingDisplay,
|
|
9
|
+
parameters: { fetchMock: {} },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const LoadingStory: StoryObj = {
|
|
15
|
+
play: async ({ canvasElement }) => {
|
|
16
|
+
const canvas = within(canvasElement);
|
|
17
|
+
const loading = canvas.getByText('Loading...');
|
|
18
|
+
await waitFor(() => expect(loading).toBeInTheDocument());
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
input[type=range]::-webkit-slider-thumb {
|
|
2
|
+
-webkit-appearance: none;
|
|
3
|
+
pointer-events: all;
|
|
4
|
+
width: 24px;
|
|
5
|
+
height: 24px;
|
|
6
|
+
background-color: #fff;
|
|
7
|
+
border-radius: 50%;
|
|
8
|
+
box-shadow: 0 0 0 1px #C6C6C6;
|
|
9
|
+
cursor: pointer;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
input[type=range]::-moz-range-thumb {
|
|
13
|
+
-webkit-appearance: none;
|
|
14
|
+
pointer-events: all;
|
|
15
|
+
width: 24px;
|
|
16
|
+
height: 24px;
|
|
17
|
+
background-color: #fff;
|
|
18
|
+
border-radius: 50%;
|
|
19
|
+
box-shadow: 0 0 0 1px #C6C6C6;
|
|
20
|
+
cursor: pointer;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
input[type=range]::-webkit-slider-thumb:hover {
|
|
24
|
+
background: #f7f7f7;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
input[type=range]::-webkit-slider-thumb:active {
|
|
28
|
+
box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
|
29
|
+
-webkit-box-shadow: inset 0 0 3px #387bbe, 0 0 9px #387bbe;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
input[type="range"] {
|
|
33
|
+
-webkit-appearance: none;
|
|
34
|
+
appearance: none;
|
|
35
|
+
height: 2px;
|
|
36
|
+
width: 100%;
|
|
37
|
+
position: absolute;
|
|
38
|
+
background-color: #C6C6C6;
|
|
39
|
+
pointer-events: none;
|
|
40
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
import { useState } from 'preact/hooks';
|
|
3
|
+
import { type ChangeEvent } from 'react';
|
|
4
|
+
import './min-max-percent-slider.css';
|
|
5
|
+
|
|
6
|
+
export interface MinMaxPercentSliderProps {
|
|
7
|
+
min: number;
|
|
8
|
+
max: number;
|
|
9
|
+
setMin: (min: number) => void;
|
|
10
|
+
setMax: (max: number) => void;
|
|
11
|
+
rangeMin?: number;
|
|
12
|
+
rangeMax?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const MinMaxRangeSlider: FunctionComponent<MinMaxPercentSliderProps> = ({
|
|
17
|
+
min,
|
|
18
|
+
max,
|
|
19
|
+
setMin,
|
|
20
|
+
setMax,
|
|
21
|
+
rangeMin = 0,
|
|
22
|
+
rangeMax = 100,
|
|
23
|
+
step = 0.1,
|
|
24
|
+
}) => {
|
|
25
|
+
const sliderColor = '#C6C6C6';
|
|
26
|
+
const rangeColor = '#387bbe';
|
|
27
|
+
|
|
28
|
+
const [zIndexTo, setZIndexTo] = useState(0);
|
|
29
|
+
|
|
30
|
+
const onMinChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
31
|
+
const input = event.target as HTMLInputElement;
|
|
32
|
+
const minValue = Number(input.value);
|
|
33
|
+
|
|
34
|
+
if (minValue > max) {
|
|
35
|
+
setMax(minValue);
|
|
36
|
+
setMin(minValue);
|
|
37
|
+
} else {
|
|
38
|
+
setMin(minValue);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const onMaxChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
43
|
+
const input = event.target as HTMLInputElement;
|
|
44
|
+
const maxValue = Number(input.value);
|
|
45
|
+
|
|
46
|
+
if (maxValue <= 0) {
|
|
47
|
+
setZIndexTo(2);
|
|
48
|
+
} else {
|
|
49
|
+
setZIndexTo(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (maxValue < min) {
|
|
53
|
+
setMin(maxValue);
|
|
54
|
+
setMax(maxValue);
|
|
55
|
+
} else {
|
|
56
|
+
setMax(maxValue);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const background = `
|
|
61
|
+
linear-gradient(
|
|
62
|
+
to right,
|
|
63
|
+
${sliderColor} 0%,
|
|
64
|
+
${sliderColor} ${min}%,
|
|
65
|
+
${rangeColor} ${min}%,
|
|
66
|
+
${rangeColor} ${max}%,
|
|
67
|
+
${sliderColor} ${max}%,
|
|
68
|
+
${sliderColor} 100%)
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div class='my-4 relative'>
|
|
73
|
+
<input
|
|
74
|
+
id='fromSlider'
|
|
75
|
+
type='range'
|
|
76
|
+
value={min}
|
|
77
|
+
onInput={onMinChange}
|
|
78
|
+
min={rangeMin}
|
|
79
|
+
max={rangeMax}
|
|
80
|
+
step={step}
|
|
81
|
+
style={{ background, zIndex: 1, height: 0 }}
|
|
82
|
+
/>
|
|
83
|
+
<input
|
|
84
|
+
id='toSlider'
|
|
85
|
+
type='range'
|
|
86
|
+
value={max}
|
|
87
|
+
min={rangeMin}
|
|
88
|
+
max={rangeMax}
|
|
89
|
+
step={step}
|
|
90
|
+
onInput={onMaxChange}
|
|
91
|
+
style={{ background, zIndex: zIndexTo }}
|
|
92
|
+
/>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact/compat';
|
|
2
|
+
|
|
3
|
+
import { type CheckboxItem, CheckboxSelector } from './checkbox-selector';
|
|
4
|
+
import type { SubstitutionOrDeletion } from '../../types';
|
|
5
|
+
|
|
6
|
+
export type DisplayedMutationType = CheckboxItem & {
|
|
7
|
+
type: SubstitutionOrDeletion;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type MutationTypeSelectorProps = {
|
|
11
|
+
displayedMutationTypes: DisplayedMutationType[];
|
|
12
|
+
setDisplayedMutationTypes: (mutationTypes: DisplayedMutationType[]) => void;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const MutationTypeSelector: FunctionComponent<MutationTypeSelectorProps> = ({
|
|
16
|
+
displayedMutationTypes,
|
|
17
|
+
setDisplayedMutationTypes,
|
|
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
|
+
return (
|
|
23
|
+
<CheckboxSelector
|
|
24
|
+
className='mx-1'
|
|
25
|
+
items={displayedMutationTypes}
|
|
26
|
+
label={mutationTypesSelectorLabel}
|
|
27
|
+
setItems={(items) => setDisplayedMutationTypes(items)}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, waitFor, within } from '@storybook/test';
|
|
3
|
+
|
|
4
|
+
import { NoDataDisplay } from './no-data-display';
|
|
5
|
+
|
|
6
|
+
const meta: Meta = {
|
|
7
|
+
title: 'Component/No data',
|
|
8
|
+
component: NoDataDisplay,
|
|
9
|
+
parameters: { fetchMock: {} },
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default meta;
|
|
13
|
+
|
|
14
|
+
export const NoDataStory: StoryObj = {
|
|
15
|
+
play: async ({ canvasElement }) => {
|
|
16
|
+
const canvas = within(canvasElement);
|
|
17
|
+
const noData = canvas.getByText('No data available.');
|
|
18
|
+
await waitFor(() => expect(noData).toBeInTheDocument());
|
|
19
|
+
},
|
|
20
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
import { useEffect, useState } from 'preact/hooks';
|
|
3
|
+
import { type ChangeEvent } from 'react';
|
|
4
|
+
|
|
5
|
+
export type PercentInputProps = {
|
|
6
|
+
percentage: number;
|
|
7
|
+
setPercentage: (percentage: number) => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const percentageInRange = (percentage: number) => {
|
|
11
|
+
return percentage <= 100 && percentage >= 0;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const PercentInput: FunctionComponent<PercentInputProps> = ({ percentage, setPercentage }) => {
|
|
15
|
+
const [internalPercentage, setInternalPercentage] = useState(percentage);
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setInternalPercentage(percentage);
|
|
19
|
+
}, [percentage]);
|
|
20
|
+
|
|
21
|
+
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
22
|
+
const input = event.target as HTMLInputElement;
|
|
23
|
+
const value = Number(input.value);
|
|
24
|
+
|
|
25
|
+
const inRange = percentageInRange(value);
|
|
26
|
+
|
|
27
|
+
if (inRange) {
|
|
28
|
+
setPercentage(value);
|
|
29
|
+
}
|
|
30
|
+
setInternalPercentage(value);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const isError = !percentageInRange(internalPercentage);
|
|
34
|
+
return (
|
|
35
|
+
<label className={`input input-bordered flex items-center gap-2 w-32 ${isError ? 'input-error' : ''}`}>
|
|
36
|
+
<input
|
|
37
|
+
type='number'
|
|
38
|
+
step={0.1}
|
|
39
|
+
min={0}
|
|
40
|
+
max={100}
|
|
41
|
+
value={internalPercentage}
|
|
42
|
+
onInput={handleInputChange}
|
|
43
|
+
lang='en'
|
|
44
|
+
className={`grow w-16`}
|
|
45
|
+
/>
|
|
46
|
+
%
|
|
47
|
+
</label>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, fn, userEvent, within } from '@storybook/test';
|
|
3
|
+
import { type FunctionComponent } from 'preact';
|
|
4
|
+
import { useState } from 'preact/hooks';
|
|
5
|
+
|
|
6
|
+
import { ProportionSelector } from './proportion-selector';
|
|
7
|
+
import { ProportionSelectorDropdown, type ProportionSelectorDropdownProps } from './proportion-selector-dropdown';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<ProportionSelectorDropdownProps> = {
|
|
10
|
+
title: 'Component/Proportion selector dropdown',
|
|
11
|
+
component: ProportionSelector,
|
|
12
|
+
parameters: { fetchMock: {} },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
const WrapperWithState: FunctionComponent<{
|
|
18
|
+
setMinProportion: (value: number) => void;
|
|
19
|
+
setMaxProportion: (value: number) => void;
|
|
20
|
+
}> = ({ setMinProportion, setMaxProportion }) => {
|
|
21
|
+
const [proportionInterval, setProportionInterval] = useState({ min: 0.05, max: 1 });
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<ProportionSelectorDropdown
|
|
25
|
+
proportionInterval={proportionInterval}
|
|
26
|
+
setMinProportion={(value: number) => {
|
|
27
|
+
setProportionInterval({ ...proportionInterval, min: value });
|
|
28
|
+
setMinProportion(value);
|
|
29
|
+
}}
|
|
30
|
+
setMaxProportion={(value: number) => {
|
|
31
|
+
setProportionInterval({ ...proportionInterval, max: value });
|
|
32
|
+
setMaxProportion(value);
|
|
33
|
+
}}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const ProportionSelectorStory: StoryObj<ProportionSelectorDropdownProps> = {
|
|
39
|
+
render: (args) => {
|
|
40
|
+
return <WrapperWithState setMinProportion={args.setMinProportion} setMaxProportion={args.setMaxProportion} />;
|
|
41
|
+
},
|
|
42
|
+
args: {
|
|
43
|
+
setMinProportion: fn(),
|
|
44
|
+
setMaxProportion: fn(),
|
|
45
|
+
},
|
|
46
|
+
play: async ({ canvasElement, step, args }) => {
|
|
47
|
+
const canvas = within(canvasElement);
|
|
48
|
+
|
|
49
|
+
await step('Expect initial proportion to show on the button', async () => {
|
|
50
|
+
const button = canvas.getByRole('button');
|
|
51
|
+
await expect(button).toHaveTextContent('Proportion 5.0% - 100.0%');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await step('Change min proportion and expect it to show on the button', async () => {
|
|
55
|
+
const button = canvas.getByRole('button');
|
|
56
|
+
await userEvent.click(button);
|
|
57
|
+
|
|
58
|
+
const minInput = canvas.getAllByLabelText('%')[0];
|
|
59
|
+
await userEvent.clear(minInput);
|
|
60
|
+
await userEvent.type(minInput, '10');
|
|
61
|
+
|
|
62
|
+
await expect(button).toHaveTextContent('Proportion 10.0% - 100.0%');
|
|
63
|
+
await expect(args.setMinProportion).toHaveBeenCalledWith(0.1);
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
import { ProportionSelector, type ProportionSelectorProps } from './proportion-selector';
|
|
4
|
+
|
|
5
|
+
export interface ProportionSelectorDropdownProps extends ProportionSelectorProps {
|
|
6
|
+
openDirection?: 'left' | 'right';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const ProportionSelectorDropdown: FunctionComponent<ProportionSelectorDropdownProps> = ({
|
|
10
|
+
proportionInterval,
|
|
11
|
+
setMinProportion,
|
|
12
|
+
setMaxProportion,
|
|
13
|
+
openDirection = 'right',
|
|
14
|
+
}) => {
|
|
15
|
+
const label = `${(proportionInterval.min * 100).toFixed(1)}% - ${(proportionInterval.max * 100).toFixed(1)}%`;
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div class={`dropdown ${openDirection === 'left' ? 'dropdown-end' : ''}`}>
|
|
19
|
+
<div tabIndex={0} role='button' class='btn btn-xs whitespace-nowrap'>
|
|
20
|
+
Proportion {label}
|
|
21
|
+
</div>
|
|
22
|
+
<ul tabIndex={0} class='p-2 shadow menu dropdown-content z-[1] bg-base-100 rounded-box w-72'>
|
|
23
|
+
<div class='mb-2 ml-2'>
|
|
24
|
+
<ProportionSelector
|
|
25
|
+
proportionInterval={proportionInterval}
|
|
26
|
+
setMinProportion={setMinProportion}
|
|
27
|
+
setMaxProportion={setMaxProportion}
|
|
28
|
+
/>
|
|
29
|
+
</div>
|
|
30
|
+
</ul>
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, fireEvent, fn, userEvent, waitFor, within } from '@storybook/test';
|
|
3
|
+
import { type FunctionComponent } from 'preact';
|
|
4
|
+
import { useState } from 'preact/hooks';
|
|
5
|
+
|
|
6
|
+
import { ProportionSelector, type ProportionSelectorProps } from './proportion-selector';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<ProportionSelectorProps> = {
|
|
9
|
+
title: 'Component/Proportion selector',
|
|
10
|
+
component: ProportionSelector,
|
|
11
|
+
parameters: { fetchMock: {} },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
|
|
16
|
+
const WrapperWithState: FunctionComponent<{
|
|
17
|
+
setMinProportion: (value: number) => void;
|
|
18
|
+
setMaxProportion: (value: number) => void;
|
|
19
|
+
}> = ({ setMinProportion, setMaxProportion }) => {
|
|
20
|
+
const [proportionInterval, setProportionInterval] = useState({ min: 0.05, max: 1 });
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<ProportionSelector
|
|
24
|
+
proportionInterval={proportionInterval}
|
|
25
|
+
setMinProportion={(value: number) => {
|
|
26
|
+
setProportionInterval((prev) => ({ ...prev, min: value }));
|
|
27
|
+
setMinProportion(value);
|
|
28
|
+
}}
|
|
29
|
+
setMaxProportion={(value: number) => {
|
|
30
|
+
setProportionInterval((prev) => ({ ...prev, max: value }));
|
|
31
|
+
setMaxProportion(value);
|
|
32
|
+
}}
|
|
33
|
+
/>
|
|
34
|
+
);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const ProportionSelectorStory: StoryObj<ProportionSelectorProps> = {
|
|
38
|
+
render: (args) => {
|
|
39
|
+
return <WrapperWithState setMinProportion={args.setMinProportion} setMaxProportion={args.setMaxProportion} />;
|
|
40
|
+
},
|
|
41
|
+
args: {
|
|
42
|
+
setMinProportion: fn(),
|
|
43
|
+
setMaxProportion: fn(),
|
|
44
|
+
},
|
|
45
|
+
play: async ({ canvasElement, step, args }) => {
|
|
46
|
+
const canvas = within(canvasElement);
|
|
47
|
+
|
|
48
|
+
await step('Expect initial min proportion to be 5% and max proportion 100%', async () => {
|
|
49
|
+
await expect(canvas.getAllByLabelText('%')[0]).toHaveValue(5);
|
|
50
|
+
await expect(canvas.getAllByLabelText('%')[1]).toHaveValue(100);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await step('Change min proportion to 10%', async () => {
|
|
54
|
+
const minInput = canvas.getAllByLabelText('%')[0];
|
|
55
|
+
await userEvent.clear(minInput);
|
|
56
|
+
await userEvent.type(minInput, '10');
|
|
57
|
+
await expect(args.setMinProportion).toHaveBeenCalledWith(0.1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await step('Change max proportion to 50%', async () => {
|
|
61
|
+
const maxInput = canvas.getAllByLabelText('%')[1];
|
|
62
|
+
await userEvent.clear(maxInput);
|
|
63
|
+
await userEvent.type(maxInput, '50');
|
|
64
|
+
await expect(args.setMaxProportion).toHaveBeenCalledWith(0.5);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
await step('Move min proportion silder to 20%', async () => {
|
|
68
|
+
const minSlider = canvas.getAllByRole('slider')[0];
|
|
69
|
+
await fireEvent.input(minSlider, { target: { value: '20' } });
|
|
70
|
+
await expect(args.setMinProportion).toHaveBeenCalledWith(0.2);
|
|
71
|
+
await waitFor(() => expect(canvas.getAllByLabelText('%')[0]).toHaveValue(20));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
await step('Move max proportion silder to 80%', async () => {
|
|
75
|
+
const maxSlider = canvas.getAllByRole('slider')[1];
|
|
76
|
+
await fireEvent.input(maxSlider, { target: { value: '80' } });
|
|
77
|
+
await expect(args.setMaxProportion).toHaveBeenCalledWith(0.8);
|
|
78
|
+
await waitFor(() => expect(canvas.getAllByLabelText('%')[1]).toHaveValue(80));
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
};
|