@genspectrum/dashboard-components 0.1.3 → 0.1.4

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 (75) hide show
  1. package/custom-elements.json +311 -75
  2. package/dist/dashboard-components.js +622 -434
  3. package/dist/dashboard-components.js.map +1 -1
  4. package/dist/genspectrum-components.d.ts +229 -42
  5. package/dist/style.css +132 -139
  6. package/package.json +9 -5
  7. package/src/preact/aggregatedData/aggregate.stories.tsx +5 -5
  8. package/src/preact/aggregatedData/aggregate.tsx +8 -4
  9. package/src/preact/components/ReferenceGenomesAwaiter.tsx +25 -0
  10. package/src/preact/components/csv-download-button.tsx +8 -2
  11. package/src/preact/components/headline.tsx +16 -4
  12. package/src/preact/components/min-max-range-slider.tsx +4 -4
  13. package/src/preact/components/percent-intput.tsx +2 -3
  14. package/src/preact/components/resize-container.tsx +23 -0
  15. package/src/preact/components/table.tsx +1 -0
  16. package/src/preact/components/tabs.stories.tsx +2 -2
  17. package/src/preact/components/tabs.tsx +47 -24
  18. package/src/preact/dateRangeSelector/date-range-selector.stories.tsx +36 -4
  19. package/src/preact/dateRangeSelector/date-range-selector.tsx +57 -43
  20. package/src/preact/locationFilter/location-filter.tsx +2 -2
  21. package/src/preact/mutationComparison/getMutationComparisonTableData.spec.ts +5 -5
  22. package/src/preact/mutationComparison/getMutationComparisonTableData.ts +45 -10
  23. package/src/preact/mutationComparison/mutation-comparison-table.tsx +20 -22
  24. package/src/preact/mutationComparison/mutation-comparison-venn.tsx +6 -3
  25. package/src/preact/mutationComparison/mutation-comparison.stories.tsx +8 -1
  26. package/src/preact/mutationComparison/mutation-comparison.tsx +13 -4
  27. package/src/preact/mutationFilter/mutation-filter.stories.tsx +70 -31
  28. package/src/preact/mutationFilter/mutation-filter.tsx +62 -14
  29. package/src/preact/mutations/getInsertionsTableData.spec.ts +6 -4
  30. package/src/preact/mutations/getInsertionsTableData.ts +1 -1
  31. package/src/preact/mutations/getMutationsTableData.spec.ts +9 -19
  32. package/src/preact/mutations/getMutationsTableData.ts +1 -1
  33. package/src/preact/mutations/mutations-insertions-table.tsx +3 -1
  34. package/src/preact/mutations/mutations-table.tsx +3 -1
  35. package/src/preact/mutations/mutations.stories.tsx +8 -1
  36. package/src/preact/mutations/mutations.tsx +16 -5
  37. package/src/preact/prevalenceOverTime/prevalence-over-time-bar-chart.tsx +1 -0
  38. package/src/preact/prevalenceOverTime/prevalence-over-time-bubble-chart.tsx +1 -0
  39. package/src/preact/prevalenceOverTime/prevalence-over-time-line-chart.tsx +1 -0
  40. package/src/preact/prevalenceOverTime/prevalence-over-time.stories.tsx +4 -0
  41. package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +17 -9
  42. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage-chart.tsx +8 -5
  43. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.stories.tsx +12 -0
  44. package/src/preact/relativeGrowthAdvantage/relative-growth-advantage.tsx +13 -8
  45. package/src/preact/shared/sort/sortInsertions.spec.ts +11 -10
  46. package/src/preact/shared/sort/sortInsertions.ts +10 -17
  47. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.spec.ts +19 -10
  48. package/src/preact/shared/sort/sortSubstitutionsAndDeletions.ts +45 -12
  49. package/src/preact/textInput/text-input.stories.tsx +22 -1
  50. package/src/preact/textInput/text-input.tsx +3 -1
  51. package/src/utils/typeAssertions.spec.ts +31 -0
  52. package/src/utils/typeAssertions.ts +16 -0
  53. package/src/web-components/PreactLitAdapter.tsx +0 -1
  54. package/src/web-components/app.stories.ts +129 -0
  55. package/src/web-components/app.ts +27 -6
  56. package/src/web-components/display/aggregate-component.stories.ts +21 -11
  57. package/src/web-components/display/aggregate-component.tsx +12 -5
  58. package/src/web-components/display/mutation-comparison-component.stories.ts +29 -11
  59. package/src/web-components/display/mutation-comparison-component.tsx +72 -4
  60. package/src/web-components/display/mutations-component.stories.ts +14 -13
  61. package/src/web-components/display/mutations-component.tsx +14 -1
  62. package/src/web-components/display/prevalence-over-time-component.stories.ts +20 -18
  63. package/src/web-components/display/prevalence-over-time-component.tsx +12 -0
  64. package/src/web-components/display/relative-growth-advantage-component.stories.ts +11 -10
  65. package/src/web-components/display/relative-growth-advantage-component.tsx +12 -0
  66. package/src/web-components/input/date-range-selector-component.stories.ts +35 -8
  67. package/src/web-components/input/date-range-selector-component.tsx +18 -5
  68. package/src/web-components/input/location-filter-component.stories.ts +15 -4
  69. package/src/web-components/input/location-filter-component.tsx +2 -6
  70. package/src/web-components/input/mutation-filter-component.stories.ts +20 -9
  71. package/src/web-components/input/mutation-filter-component.tsx +10 -2
  72. package/src/web-components/input/text-input-component.stories.ts +13 -4
  73. package/src/web-components/input/text-input-component.tsx +11 -2
  74. package/src/web-components/display/aggregate-component.mdx +0 -25
  75. package/src/web-components/input/location-filter.mdx +0 -25
package/dist/style.css CHANGED
@@ -868,108 +868,6 @@ html {
868
868
  --bc: 27.8078% 0.029596 256.847952;
869
869
  }
870
870
 
871
- @media (prefers-color-scheme: dark) {
872
-
873
- :root {
874
- color-scheme: dark;
875
- --in: 72.06% 0.191 231.6;
876
- --su: 64.8% 0.150 160;
877
- --wa: 84.71% 0.199 83.87;
878
- --er: 71.76% 0.221 22.18;
879
- --pc: 13.138% 0.0392 275.75;
880
- --sc: 14.96% 0.052 342.55;
881
- --ac: 14.902% 0.0334 183.61;
882
- --inc: 0% 0 0;
883
- --suc: 0% 0 0;
884
- --wac: 0% 0 0;
885
- --erc: 0% 0 0;
886
- --rounded-box: 1rem;
887
- --rounded-btn: 0.5rem;
888
- --rounded-badge: 1.9rem;
889
- --animation-btn: 0.25s;
890
- --animation-input: .2s;
891
- --btn-focus-scale: 0.95;
892
- --border-btn: 1px;
893
- --tab-border: 1px;
894
- --tab-radius: 0.5rem;
895
- --p: 65.69% 0.196 275.75;
896
- --s: 74.8% 0.26 342.55;
897
- --a: 74.51% 0.167 183.61;
898
- --n: 31.3815% 0.021108 254.139175;
899
- --nc: 74.6477% 0.0216 264.435964;
900
- --b1: 25.3267% 0.015896 252.417568;
901
- --b2: 23.2607% 0.013807 253.100675;
902
- --b3: 21.1484% 0.01165 254.087939;
903
- --bc: 74.6477% 0.0216 264.435964;
904
- }
905
- }
906
-
907
- [data-theme=light] {
908
- color-scheme: light;
909
- --in: 72.06% 0.191 231.6;
910
- --su: 64.8% 0.150 160;
911
- --wa: 84.71% 0.199 83.87;
912
- --er: 71.76% 0.221 22.18;
913
- --pc: 89.824% 0.06192 275.75;
914
- --ac: 15.352% 0.0368 183.61;
915
- --inc: 0% 0 0;
916
- --suc: 0% 0 0;
917
- --wac: 0% 0 0;
918
- --erc: 0% 0 0;
919
- --rounded-box: 1rem;
920
- --rounded-btn: 0.5rem;
921
- --rounded-badge: 1.9rem;
922
- --animation-btn: 0.25s;
923
- --animation-input: .2s;
924
- --btn-focus-scale: 0.95;
925
- --border-btn: 1px;
926
- --tab-border: 1px;
927
- --tab-radius: 0.5rem;
928
- --p: 49.12% 0.3096 275.75;
929
- --s: 69.71% 0.329 342.55;
930
- --sc: 98.71% 0.0106 342.55;
931
- --a: 76.76% 0.184 183.61;
932
- --n: 32.1785% 0.02476 255.701624;
933
- --nc: 89.4994% 0.011585 252.096176;
934
- --b1: 100% 0 0;
935
- --b2: 96.1151% 0 0;
936
- --b3: 92.4169% 0.00108 197.137559;
937
- --bc: 27.8078% 0.029596 256.847952;
938
- }
939
-
940
- [data-theme=dark] {
941
- color-scheme: dark;
942
- --in: 72.06% 0.191 231.6;
943
- --su: 64.8% 0.150 160;
944
- --wa: 84.71% 0.199 83.87;
945
- --er: 71.76% 0.221 22.18;
946
- --pc: 13.138% 0.0392 275.75;
947
- --sc: 14.96% 0.052 342.55;
948
- --ac: 14.902% 0.0334 183.61;
949
- --inc: 0% 0 0;
950
- --suc: 0% 0 0;
951
- --wac: 0% 0 0;
952
- --erc: 0% 0 0;
953
- --rounded-box: 1rem;
954
- --rounded-btn: 0.5rem;
955
- --rounded-badge: 1.9rem;
956
- --animation-btn: 0.25s;
957
- --animation-input: .2s;
958
- --btn-focus-scale: 0.95;
959
- --border-btn: 1px;
960
- --tab-border: 1px;
961
- --tab-radius: 0.5rem;
962
- --p: 65.69% 0.196 275.75;
963
- --s: 74.8% 0.26 342.55;
964
- --a: 74.51% 0.167 183.61;
965
- --n: 31.3815% 0.021108 254.139175;
966
- --nc: 74.6477% 0.0216 264.435964;
967
- --b1: 25.3267% 0.015896 252.417568;
968
- --b2: 23.2607% 0.013807 253.100675;
969
- --b3: 21.1484% 0.01165 254.087939;
970
- --bc: 74.6477% 0.0216 264.435964;
971
- }
972
-
973
871
  *, ::before, ::after {
974
872
  --tw-border-spacing-x: 0;
975
873
  --tw-border-spacing-y: 0;
@@ -1077,6 +975,39 @@ html {
1077
975
  --tw-contain-paint: ;
1078
976
  --tw-contain-style: ;
1079
977
  }
978
+ .container {
979
+ width: 100%;
980
+ }
981
+ @media (min-width: 640px) {
982
+
983
+ .container {
984
+ max-width: 640px;
985
+ }
986
+ }
987
+ @media (min-width: 768px) {
988
+
989
+ .container {
990
+ max-width: 768px;
991
+ }
992
+ }
993
+ @media (min-width: 1024px) {
994
+
995
+ .container {
996
+ max-width: 1024px;
997
+ }
998
+ }
999
+ @media (min-width: 1280px) {
1000
+
1001
+ .container {
1002
+ max-width: 1280px;
1003
+ }
1004
+ }
1005
+ @media (min-width: 1536px) {
1006
+
1007
+ .container {
1008
+ max-width: 1536px;
1009
+ }
1010
+ }
1080
1011
  .avatar.placeholder > div {
1081
1012
  display: flex;
1082
1013
  align-items: center;
@@ -1668,19 +1599,6 @@ html {
1668
1599
  cursor: default;
1669
1600
  grid-column-start: span 9999;
1670
1601
  }
1671
- .tab-content {
1672
- grid-column-start: 1;
1673
- grid-column-end: span 9999;
1674
- grid-row-start: 2;
1675
- margin-top: calc(var(--tab-border) * -1);
1676
- display: none;
1677
- border-color: transparent;
1678
- border-width: var(--tab-border, 0);
1679
- }
1680
- :checked + .tab-content:nth-child(2),
1681
- .tab-active + .tab-content:nth-child(2) {
1682
- border-start-start-radius: 0px;
1683
- }
1684
1602
  input.tab:checked + .tab-content,
1685
1603
  .tab-active + .tab-content {
1686
1604
  display: block;
@@ -1828,6 +1746,14 @@ input.tab:checked + .tab-content,
1828
1746
  outline-offset: 2px;
1829
1747
  outline-color: var(--fallback-bc,oklch(var(--bc)/1));
1830
1748
  }
1749
+ .checkbox:disabled {
1750
+ border-width: 0px;
1751
+ cursor: not-allowed;
1752
+ border-color: transparent;
1753
+ --tw-bg-opacity: 1;
1754
+ background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
1755
+ opacity: 0.2;
1756
+ }
1831
1757
  .checkbox:checked,
1832
1758
  .checkbox[aria-checked="true"] {
1833
1759
  background-repeat: no-repeat;
@@ -1854,13 +1780,6 @@ input.tab:checked + .tab-content,
1854
1780
  linear-gradient(-90deg, transparent 80%, var(--chkbg) 80%),
1855
1781
  linear-gradient(0deg, var(--chkbg) 43%, var(--chkfg) 43%, var(--chkfg) 57%, var(--chkbg) 57%);
1856
1782
  }
1857
- .checkbox:disabled {
1858
- cursor: not-allowed;
1859
- border-color: transparent;
1860
- --tw-bg-opacity: 1;
1861
- background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
1862
- opacity: 0.2;
1863
- }
1864
1783
  @keyframes checkmark {
1865
1784
 
1866
1785
  0% {
@@ -1916,7 +1835,8 @@ input.tab:checked + .tab-content,
1916
1835
  border-color: var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)));
1917
1836
  outline-color: var(--fallback-er,oklch(var(--er)/1));
1918
1837
  }
1919
- .input-disabled,
1838
+ .input:has(> input[disabled]),
1839
+ .input-disabled,
1920
1840
  .input:disabled,
1921
1841
  .input[disabled] {
1922
1842
  cursor: not-allowed;
@@ -1926,16 +1846,20 @@ input.tab:checked + .tab-content,
1926
1846
  background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
1927
1847
  color: var(--fallback-bc,oklch(var(--bc)/0.4));
1928
1848
  }
1929
- .input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder {
1849
+ .input:has(> input[disabled])::-moz-placeholder, .input-disabled::-moz-placeholder, .input:disabled::-moz-placeholder, .input[disabled]::-moz-placeholder {
1930
1850
  color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
1931
1851
  --tw-placeholder-opacity: 0.2;
1932
1852
  }
1933
- .input-disabled::placeholder,
1853
+ .input:has(> input[disabled])::placeholder,
1854
+ .input-disabled::placeholder,
1934
1855
  .input:disabled::placeholder,
1935
1856
  .input[disabled]::placeholder {
1936
1857
  color: var(--fallback-bc,oklch(var(--bc)/var(--tw-placeholder-opacity)));
1937
1858
  --tw-placeholder-opacity: 0.2;
1938
1859
  }
1860
+ .input:has(> input[disabled]) > input[disabled] {
1861
+ cursor: not-allowed;
1862
+ }
1939
1863
  .input::-webkit-date-and-time-value {
1940
1864
  text-align: inherit;
1941
1865
  }
@@ -1962,6 +1886,13 @@ input.tab:checked + .tab-content,
1962
1886
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1963
1887
  mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1964
1888
  }
1889
+ .loading-spinner {
1890
+ -webkit-mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1891
+ mask-image: url("data:image/svg+xml,%3Csvg width='24' height='24' stroke='%23000' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cstyle%3E.spinner_V8m1%7Btransform-origin:center;animation:spinner_zKoa 2s linear infinite%7D.spinner_V8m1 circle%7Bstroke-linecap:round;animation:spinner_YpZS 1.5s ease-out infinite%7D%40keyframes spinner_zKoa%7B100%25%7Btransform:rotate(360deg)%7D%7D%40keyframes spinner_YpZS%7B0%25%7Bstroke-dasharray:0 150;stroke-dashoffset:0%7D47.5%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-16%7D95%25%2C100%25%7Bstroke-dasharray:42 150;stroke-dashoffset:-59%7D%7D%3C%2Fstyle%3E%3Cg class='spinner_V8m1'%3E%3Ccircle cx='12' cy='12' r='9.5' fill='none' stroke-width='3'%3E%3C%2Fcircle%3E%3C%2Fg%3E%3C%2Fsvg%3E");
1892
+ }
1893
+ .loading-md {
1894
+ width: 1.5rem;
1895
+ }
1965
1896
  :where(.menu li:empty) {
1966
1897
  --tw-bg-opacity: 1;
1967
1898
  background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
@@ -2718,12 +2649,6 @@ input.tab:checked + .tab-content,
2718
2649
  .z-\[1\] {
2719
2650
  z-index: 1;
2720
2651
  }
2721
- .col-\[9999\] {
2722
- grid-column: 9999;
2723
- }
2724
- .m-1 {
2725
- margin: 0.25rem;
2726
- }
2727
2652
  .m-2 {
2728
2653
  margin: 0.5rem;
2729
2654
  }
@@ -2769,8 +2694,11 @@ input.tab:checked + .tab-content,
2769
2694
  .grid {
2770
2695
  display: grid;
2771
2696
  }
2772
- .w-11\/12 {
2773
- width: 91.666667%;
2697
+ .hidden {
2698
+ display: none;
2699
+ }
2700
+ .h-full {
2701
+ height: 100%;
2774
2702
  }
2775
2703
  .w-16 {
2776
2704
  width: 4rem;
@@ -2793,6 +2721,9 @@ input.tab:checked + .tab-content,
2793
2721
  .max-w-screen-lg {
2794
2722
  max-width: 1024px;
2795
2723
  }
2724
+ .flex-1 {
2725
+ flex: 1 1 0%;
2726
+ }
2796
2727
  .grow {
2797
2728
  flex-grow: 1;
2798
2729
  }
@@ -2814,12 +2745,18 @@ input.tab:checked + .tab-content,
2814
2745
  .justify-center {
2815
2746
  justify-content: center;
2816
2747
  }
2748
+ .justify-between {
2749
+ justify-content: space-between;
2750
+ }
2817
2751
  .gap-1 {
2818
2752
  gap: 0.25rem;
2819
2753
  }
2820
2754
  .gap-2 {
2821
2755
  gap: 0.5rem;
2822
2756
  }
2757
+ .overflow-auto {
2758
+ overflow: auto;
2759
+ }
2823
2760
  .whitespace-nowrap {
2824
2761
  white-space: nowrap;
2825
2762
  }
@@ -2841,24 +2778,41 @@ input.tab:checked + .tab-content,
2841
2778
  .rounded-none {
2842
2779
  border-radius: 0px;
2843
2780
  }
2781
+ .rounded-b-md {
2782
+ border-bottom-right-radius: 0.375rem;
2783
+ border-bottom-left-radius: 0.375rem;
2784
+ }
2785
+ .rounded-tl-md {
2786
+ border-top-left-radius: 0.375rem;
2787
+ }
2788
+ .rounded-tr-md {
2789
+ border-top-right-radius: 0.375rem;
2790
+ }
2844
2791
  .border {
2845
2792
  border-width: 1px;
2846
2793
  }
2847
2794
  .border-2 {
2848
2795
  border-width: 2px;
2849
2796
  }
2850
- .border-base-300 {
2851
- --tw-border-opacity: 1;
2852
- border-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-border-opacity)));
2797
+ .border-b-2 {
2798
+ border-bottom-width: 2px;
2853
2799
  }
2854
2800
  .border-error {
2855
2801
  --tw-border-opacity: 1;
2856
2802
  border-color: var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)));
2857
2803
  }
2804
+ .border-gray-100 {
2805
+ --tw-border-opacity: 1;
2806
+ border-color: rgb(243 244 246 / var(--tw-border-opacity));
2807
+ }
2858
2808
  .border-gray-300 {
2859
2809
  --tw-border-opacity: 1;
2860
2810
  border-color: rgb(209 213 219 / var(--tw-border-opacity));
2861
2811
  }
2812
+ .border-gray-400 {
2813
+ --tw-border-opacity: 1;
2814
+ border-color: rgb(156 163 175 / var(--tw-border-opacity));
2815
+ }
2862
2816
  .bg-base-100 {
2863
2817
  --tw-bg-opacity: 1;
2864
2818
  background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
@@ -2871,9 +2825,6 @@ input.tab:checked + .tab-content,
2871
2825
  --tw-bg-opacity: 1;
2872
2826
  background-color: rgb(255 255 255 / var(--tw-bg-opacity));
2873
2827
  }
2874
- .p-1 {
2875
- padding: 0.25rem;
2876
- }
2877
2828
  .p-2 {
2878
2829
  padding: 0.5rem;
2879
2830
  }
@@ -2901,13 +2852,39 @@ input.tab:checked + .tab-content,
2901
2852
  padding-top: 4rem;
2902
2853
  padding-bottom: 4rem;
2903
2854
  }
2855
+ .py-2 {
2856
+ padding-top: 0.5rem;
2857
+ padding-bottom: 0.5rem;
2858
+ }
2859
+ .text-lg {
2860
+ font-size: 1.125rem;
2861
+ line-height: 1.75rem;
2862
+ }
2863
+ .text-sm {
2864
+ font-size: 0.875rem;
2865
+ line-height: 1.25rem;
2866
+ }
2867
+ .text-xl {
2868
+ font-size: 1.25rem;
2869
+ line-height: 1.75rem;
2870
+ }
2904
2871
  .text-xs {
2905
2872
  font-size: 0.75rem;
2906
2873
  line-height: 1rem;
2907
2874
  }
2875
+ .font-bold {
2876
+ font-weight: 700;
2877
+ }
2908
2878
  .font-medium {
2909
2879
  font-weight: 500;
2910
2880
  }
2881
+ .leading-5 {
2882
+ line-height: 1.25rem;
2883
+ }
2884
+ .text-gray-600 {
2885
+ --tw-text-opacity: 1;
2886
+ color: rgb(75 85 99 / var(--tw-text-opacity));
2887
+ }
2911
2888
  .shadow {
2912
2889
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
2913
2890
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -2915,6 +2892,22 @@ input.tab:checked + .tab-content,
2915
2892
  }
2916
2893
  .filter {
2917
2894
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
2895
+ }
2896
+ .transition-colors {
2897
+ transition-property: color, background-color, border-color, text-decoration-color, fill, stroke;
2898
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
2899
+ transition-duration: 150ms;
2900
+ }
2901
+ .duration-150 {
2902
+ transition-duration: 150ms;
2903
+ }
2904
+ .hover\:bg-gray-100:hover {
2905
+ --tw-bg-opacity: 1;
2906
+ background-color: rgb(243 244 246 / var(--tw-bg-opacity));
2907
+ }
2908
+ .hover\:text-gray-700:hover {
2909
+ --tw-text-opacity: 1;
2910
+ color: rgb(55 65 81 / var(--tw-text-opacity));
2918
2911
  }.flatpickr-calendar{background:transparent;opacity:0;display:none;text-align:center;visibility:hidden;padding:0;-webkit-animation:none;animation:none;direction:ltr;border:0;font-size:14px;line-height:24px;border-radius:5px;position:absolute;width:307.875px;-webkit-box-sizing:border-box;box-sizing:border-box;-ms-touch-action:manipulation;touch-action:manipulation;background:#fff;-webkit-box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08);box-shadow:1px 0 0 #e6e6e6,-1px 0 0 #e6e6e6,0 1px 0 #e6e6e6,0 -1px 0 #e6e6e6,0 3px 13px rgba(0,0,0,0.08)}.flatpickr-calendar.open,.flatpickr-calendar.inline{opacity:1;max-height:640px;visibility:visible}.flatpickr-calendar.open{display:inline-block;z-index:99999}.flatpickr-calendar.animate.open{-webkit-animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1);animation:fpFadeInDown 300ms cubic-bezier(.23,1,.32,1)}.flatpickr-calendar.inline{display:block;position:relative;top:2px}.flatpickr-calendar.static{position:absolute;top:calc(100% + 2px)}.flatpickr-calendar.static.open{z-index:999;display:block}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+1) .flatpickr-day.inRange:nth-child(7n+7){-webkit-box-shadow:none !important;box-shadow:none !important}.flatpickr-calendar.multiMonth .flatpickr-days .dayContainer:nth-child(n+2) .flatpickr-day.inRange:nth-child(7n+1){-webkit-box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6;box-shadow:-2px 0 0 #e6e6e6,5px 0 0 #e6e6e6}.flatpickr-calendar .hasWeeks .dayContainer,.flatpickr-calendar .hasTime .dayContainer{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.flatpickr-calendar .hasWeeks .dayContainer{border-left:0}.flatpickr-calendar.hasTime .flatpickr-time{height:40px;border-top:1px solid #e6e6e6}.flatpickr-calendar.noCalendar.hasTime .flatpickr-time{height:auto}.flatpickr-calendar:before,.flatpickr-calendar:after{position:absolute;display:block;pointer-events:none;border:solid transparent;content:'';height:0;width:0;left:22px}.flatpickr-calendar.rightMost:before,.flatpickr-calendar.arrowRight:before,.flatpickr-calendar.rightMost:after,.flatpickr-calendar.arrowRight:after{left:auto;right:22px}.flatpickr-calendar.arrowCenter:before,.flatpickr-calendar.arrowCenter:after{left:50%;right:50%}.flatpickr-calendar:before{border-width:5px;margin:0 -5px}.flatpickr-calendar:after{border-width:4px;margin:0 -4px}.flatpickr-calendar.arrowTop:before,.flatpickr-calendar.arrowTop:after{bottom:100%}.flatpickr-calendar.arrowTop:before{border-bottom-color:#e6e6e6}.flatpickr-calendar.arrowTop:after{border-bottom-color:#fff}.flatpickr-calendar.arrowBottom:before,.flatpickr-calendar.arrowBottom:after{top:100%}.flatpickr-calendar.arrowBottom:before{border-top-color:#e6e6e6}.flatpickr-calendar.arrowBottom:after{border-top-color:#fff}.flatpickr-calendar:focus{outline:0}.flatpickr-wrapper{position:relative;display:inline-block}.flatpickr-months{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.flatpickr-months .flatpickr-month{background:transparent;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9);height:34px;line-height:1;text-align:center;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;overflow:hidden;-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.flatpickr-months .flatpickr-prev-month,.flatpickr-months .flatpickr-next-month{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;text-decoration:none;cursor:pointer;position:absolute;top:0;height:34px;padding:10px;z-index:3;color:rgba(0,0,0,0.9);fill:rgba(0,0,0,0.9)}.flatpickr-months .flatpickr-prev-month.flatpickr-disabled,.flatpickr-months .flatpickr-next-month.flatpickr-disabled{display:none}.flatpickr-months .flatpickr-prev-month i,.flatpickr-months .flatpickr-next-month i{position:relative}.flatpickr-months .flatpickr-prev-month.flatpickr-prev-month,.flatpickr-months .flatpickr-next-month.flatpickr-prev-month{/*
2919
2912
  /*rtl:begin:ignore*/left:0/*
2920
2913
  /*rtl:end:ignore*/}/*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -35,9 +35,10 @@
35
35
  "lint:lit-analyzer": "lit-analyzer",
36
36
  "generate-manifest": "npx custom-elements-manifest analyze --litelement --globs src/web-components/**",
37
37
  "generate-manifest:watch": "npm run generate-manifest -- --watch",
38
- "format": "prettier \"**/*.{cjs,html,js,json,md,ts,tsx}\" --ignore-path ./.eslintignore --write",
38
+ "format": "prettier \"**/*.{cjs,html,js,json,md,ts,tsx}\" --write",
39
39
  "check-format": "prettier --check \"**/*.{ts,tsx,json,md,mdx,mjs,cjs}\"",
40
40
  "check-types": "tsc --noEmit",
41
+ "check-dependencies": "depcheck",
41
42
  "storybook": "storybook dev -p 6006",
42
43
  "storybook-preact": "storybook dev --port 6007 --config-dir .storybook-preact",
43
44
  "build-storybook": "storybook build",
@@ -55,8 +56,8 @@
55
56
  "lit"
56
57
  ],
57
58
  "dependencies": {
58
- "@floating-ui/dom": "^1.6.3",
59
59
  "@lit/context": "^1.1.1",
60
+ "@lit/reactive-element": "^2.0.4",
60
61
  "@lit/task": "^1.0.0",
61
62
  "chart.js": "^4.4.2",
62
63
  "chartjs-chart-error-bars": "^4.4.0",
@@ -71,15 +72,16 @@
71
72
  "devDependencies": {
72
73
  "@custom-elements-manifest/analyzer": "^0.9.4",
73
74
  "@playwright/test": "^1.43.1",
74
- "@preact/preset-vite": "^2.8.2",
75
+ "@storybook/addon-actions": "^8.0.9",
75
76
  "@storybook/addon-essentials": "^8.0.9",
76
77
  "@storybook/addon-interactions": "^8.0.9",
77
78
  "@storybook/addon-links": "^8.0.9",
78
- "@storybook/blocks": "^8.0.0",
79
+ "@storybook/blocks": "^8.0.10",
79
80
  "@storybook/preact": "^8.0.9",
80
81
  "@storybook/preact-vite": "^8.0.9",
81
82
  "@storybook/test": "^8.0.0",
82
83
  "@storybook/test-runner": "^0.17.0",
84
+ "@storybook/types": "^8.0.9",
83
85
  "@storybook/web-components": "^8.0.9",
84
86
  "@storybook/web-components-vite": "^8.0.9",
85
87
  "@types/node": "^20.12.7",
@@ -87,6 +89,7 @@
87
89
  "@typescript-eslint/parser": "^7.7.0",
88
90
  "autoprefixer": "^10.4.19",
89
91
  "daisyui": "^4.10.2",
92
+ "depcheck": "^1.4.7",
90
93
  "eslint": "^8.57.0",
91
94
  "eslint-config-preact": "^1.3.0",
92
95
  "eslint-plugin-import": "^2.29.1",
@@ -97,6 +100,7 @@
97
100
  "msw": "^2.2.14",
98
101
  "postcss": "^8.4.38",
99
102
  "prettier": "^3.2.5",
103
+ "react": "^18.3.1",
100
104
  "release-please": "^16.10.2",
101
105
  "storybook": "^8.0.9",
102
106
  "storybook-addon-fetch-mock": "^2.0.0",
@@ -10,6 +10,7 @@ const meta: Meta<AggregateProps> = {
10
10
  component: Aggregate,
11
11
  argTypes: {
12
12
  fields: [{ control: 'object' }],
13
+ size: [{ control: 'object' }],
13
14
  },
14
15
  parameters: {
15
16
  fetchMock: {
@@ -37,11 +38,9 @@ export default meta;
37
38
 
38
39
  export const Default: StoryObj<AggregateProps> = {
39
40
  render: (args) => (
40
- <div class='max-w-screen-lg'>
41
- <LapisUrlContext.Provider value={LAPIS_URL}>
42
- <Aggregate {...args} />
43
- </LapisUrlContext.Provider>
44
- </div>
41
+ <LapisUrlContext.Provider value={LAPIS_URL}>
42
+ <Aggregate {...args} />
43
+ </LapisUrlContext.Provider>
45
44
  ),
46
45
  args: {
47
46
  fields: ['division', 'host'],
@@ -49,5 +48,6 @@ export const Default: StoryObj<AggregateProps> = {
49
48
  filter: {
50
49
  country: 'USA',
51
50
  },
51
+ size: { width: '100%', height: '70vh' },
52
52
  },
53
53
  };
@@ -11,6 +11,7 @@ import Headline from '../components/headline';
11
11
  import Info from '../components/info';
12
12
  import { LoadingDisplay } from '../components/loading-display';
13
13
  import { NoDataDisplay } from '../components/no-data-display';
14
+ import { ResizeContainer, type Size } from '../components/resize-container';
14
15
  import Tabs from '../components/tabs';
15
16
  import { useQuery } from '../useQuery';
16
17
 
@@ -20,9 +21,10 @@ export interface AggregateProps {
20
21
  filter: LapisFilter;
21
22
  fields: string[];
22
23
  views: View[];
24
+ size?: Size;
23
25
  }
24
26
 
25
- export const Aggregate: FunctionComponent<AggregateProps> = ({ fields, views, filter }) => {
27
+ export const Aggregate: FunctionComponent<AggregateProps> = ({ fields, views, filter, size }) => {
26
28
  const lapis = useContext(LapisUrlContext);
27
29
 
28
30
  const { data, error, isLoading } = useQuery(async () => {
@@ -56,9 +58,11 @@ export const Aggregate: FunctionComponent<AggregateProps> = ({ fields, views, fi
56
58
  }
57
59
 
58
60
  return (
59
- <Headline heading={headline}>
60
- <AggregatedDataTabs data={data} views={views} fields={fields} />
61
- </Headline>
61
+ <ResizeContainer size={size} defaultSize={{ height: '700px', width: '100%' }}>
62
+ <Headline heading={headline}>
63
+ <AggregatedDataTabs data={data} views={views} fields={fields} />
64
+ </Headline>
65
+ </ResizeContainer>
62
66
  );
63
67
  };
64
68
 
@@ -0,0 +1,25 @@
1
+ import { type ComponentChildren, type FunctionalComponent } from 'preact';
2
+ import { useContext } from 'preact/hooks';
3
+
4
+ import { type ReferenceGenome } from '../../lapisApi/ReferenceGenome';
5
+ import { ReferenceGenomeContext } from '../ReferenceGenomeContext';
6
+
7
+ /**
8
+ * Sometimes the reference genome is not immediately available.
9
+ * This component will display a loading spinner until the reference genome is available.
10
+ * Child components can assume that the reference genome is available on the first render,
11
+ * which e.g. matters for initial values of `useState`.
12
+ */
13
+ export const ReferenceGenomesAwaiter: FunctionalComponent<{ children: ComponentChildren }> = ({ children }) => {
14
+ const referenceGenome = useContext(ReferenceGenomeContext);
15
+
16
+ if (isNotInitialized(referenceGenome)) {
17
+ return <div className='laoding loading-spinner loading-md'>Loading...</div>;
18
+ }
19
+
20
+ return <>{children}</>;
21
+ };
22
+
23
+ function isNotInitialized(referenceGenome: ReferenceGenome) {
24
+ return referenceGenome.nucleotideSequences.length === 0 && referenceGenome.genes.length === 0;
25
+ }
@@ -1,9 +1,15 @@
1
1
  import { type FunctionComponent } from 'preact';
2
2
 
3
+ type ToStringable = {
4
+ toString: () => string;
5
+ };
6
+
7
+ type DataValue = string | number | boolean | null | ToStringable;
8
+
3
9
  export interface CsvDownloadButtonProps {
4
10
  label?: string;
5
11
  filename?: string;
6
- getData: () => Record<string, string | number | boolean | null>[];
12
+ getData: () => Record<string, DataValue>[];
7
13
  className?: string;
8
14
  }
9
15
 
@@ -32,7 +38,7 @@ export const CsvDownloadButton: FunctionComponent<CsvDownloadButtonProps> = ({
32
38
  return header + rows;
33
39
  };
34
40
 
35
- const getDataKeys = (data: Record<string, string | number | boolean | null>[]) => {
41
+ const getDataKeys = (data: Record<string, DataValue>[]) => {
36
42
  const keysSet = data
37
43
  .map((row) => Object.keys(row))
38
44
  .reduce((accumulatedKeys, keys) => {
@@ -1,15 +1,27 @@
1
1
  import { type FunctionComponent } from 'preact';
2
+ import { useEffect, useRef, useState } from 'preact/hooks';
2
3
 
3
4
  export interface HeadlineProps {
4
5
  heading: string;
5
6
  }
6
7
 
7
8
  const Headline: FunctionComponent<HeadlineProps> = ({ heading, children }) => {
9
+ const ref = useRef<HTMLHeadingElement>(null);
10
+
11
+ const [h1Height, setH1Height] = useState('2rem');
12
+
13
+ useEffect(() => {
14
+ if (ref.current) {
15
+ const h1Height = ref.current.getBoundingClientRect().height;
16
+ setH1Height(`${h1Height}px`);
17
+ }
18
+ }, []);
19
+
8
20
  return (
9
- <>
10
- <h1>{heading}</h1>
11
- {children}
12
- </>
21
+ <div className='h-full w-full'>
22
+ <h1 ref={ref}>{heading}</h1>
23
+ <div style={{ height: `calc(100% - ${h1Height})` }}>{children}</div>
24
+ </div>
13
25
  );
14
26
  };
15
27
 
@@ -1,6 +1,6 @@
1
- import { type FunctionComponent } from 'preact';
1
+ import { type FunctionComponent, type JSX } from 'preact';
2
2
  import { useState } from 'preact/hooks';
3
- import { type ChangeEvent } from 'react';
3
+
4
4
  import './min-max-percent-slider.css';
5
5
 
6
6
  export interface MinMaxPercentSliderProps {
@@ -27,7 +27,7 @@ export const MinMaxRangeSlider: FunctionComponent<MinMaxPercentSliderProps> = ({
27
27
 
28
28
  const [zIndexTo, setZIndexTo] = useState(0);
29
29
 
30
- const onMinChange = (event: ChangeEvent<HTMLInputElement>) => {
30
+ const onMinChange = (event: JSX.TargetedInputEvent<HTMLInputElement>) => {
31
31
  const input = event.target as HTMLInputElement;
32
32
  const minValue = Number(input.value);
33
33
 
@@ -39,7 +39,7 @@ export const MinMaxRangeSlider: FunctionComponent<MinMaxPercentSliderProps> = ({
39
39
  }
40
40
  };
41
41
 
42
- const onMaxChange = (event: ChangeEvent<HTMLInputElement>) => {
42
+ const onMaxChange = (event: JSX.TargetedInputEvent<HTMLInputElement>) => {
43
43
  const input = event.target as HTMLInputElement;
44
44
  const maxValue = Number(input.value);
45
45
 
@@ -1,6 +1,5 @@
1
- import { type FunctionComponent } from 'preact';
1
+ import { type FunctionComponent, type JSX } from 'preact';
2
2
  import { useEffect, useState } from 'preact/hooks';
3
- import { type ChangeEvent } from 'react';
4
3
 
5
4
  export type PercentInputProps = {
6
5
  percentage: number;
@@ -18,7 +17,7 @@ export const PercentInput: FunctionComponent<PercentInputProps> = ({ percentage,
18
17
  setInternalPercentage(percentage);
19
18
  }, [percentage]);
20
19
 
21
- const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
20
+ const handleInputChange = (event: JSX.TargetedInputEvent<HTMLInputElement>) => {
22
21
  const input = event.target as HTMLInputElement;
23
22
  const value = Number(input.value);
24
23