@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
package/dist/style.css CHANGED
@@ -1008,6 +1008,35 @@ html {
1008
1008
  max-width: 1536px;
1009
1009
  }
1010
1010
  }
1011
+ .alert {
1012
+ display: grid;
1013
+ width: 100%;
1014
+ grid-auto-flow: row;
1015
+ align-content: flex-start;
1016
+ align-items: center;
1017
+ justify-items: center;
1018
+ gap: 1rem;
1019
+ text-align: center;
1020
+ border-radius: var(--rounded-box, 1rem);
1021
+ border-width: 1px;
1022
+ --tw-border-opacity: 1;
1023
+ border-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
1024
+ padding: 1rem;
1025
+ --tw-text-opacity: 1;
1026
+ color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
1027
+ --alert-bg: var(--fallback-b2,oklch(var(--b2)/1));
1028
+ --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
1029
+ background-color: var(--alert-bg);
1030
+ }
1031
+ @media (min-width: 640px) {
1032
+
1033
+ .alert {
1034
+ grid-auto-flow: column;
1035
+ grid-template-columns: auto minmax(auto,1fr);
1036
+ justify-items: start;
1037
+ text-align: start;
1038
+ }
1039
+ }
1011
1040
  .avatar.placeholder > div {
1012
1041
  display: flex;
1013
1042
  align-items: center;
@@ -1065,7 +1094,6 @@ html {
1065
1094
  transition-duration: 200ms;
1066
1095
  transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
1067
1096
  border-width: var(--border-btn, 1px);
1068
- animation: button-pop var(--animation-btn, 0.25s) ease-out;
1069
1097
  transition-property: color, background-color, border-color, opacity, box-shadow, transform;
1070
1098
  --tw-text-opacity: 1;
1071
1099
  color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
@@ -1517,28 +1545,13 @@ html {
1517
1545
  .select[multiple] {
1518
1546
  height: auto;
1519
1547
  }
1520
- .stack {
1548
+ .steps {
1521
1549
  display: inline-grid;
1522
- place-items: center;
1523
- align-items: flex-end;
1524
- }
1525
- .stack > * {
1526
- grid-column-start: 1;
1527
- grid-row-start: 1;
1528
- transform: translateY(10%) scale(0.9);
1529
- z-index: 1;
1530
- width: 100%;
1531
- opacity: 0.6;
1532
- }
1533
- .stack > *:nth-child(2) {
1534
- transform: translateY(5%) scale(0.95);
1535
- z-index: 2;
1536
- opacity: 0.8;
1537
- }
1538
- .stack > *:nth-child(1) {
1539
- transform: translateY(0) scale(1);
1540
- z-index: 3;
1541
- opacity: 1;
1550
+ grid-auto-flow: column;
1551
+ overflow: hidden;
1552
+ overflow-x: auto;
1553
+ counter-reset: step;
1554
+ grid-auto-columns: 1fr;
1542
1555
  }
1543
1556
  .steps .step {
1544
1557
  display: grid;
@@ -1554,8 +1567,7 @@ html {
1554
1567
  display: grid;
1555
1568
  align-items: flex-end;
1556
1569
  }
1557
- .tabs-lifted:has(.tab-content[class^="rounded-"]) .tab:first-child:not(.tab-active),
1558
- .tabs-lifted:has(.tab-content[class*=" rounded-"]) .tab:first-child:not(.tab-active) {
1570
+ .tabs-lifted:has(.tab-content[class^="rounded-"]) .tab:first-child:not(:is(.tab-active, [aria-selected="true"])), .tabs-lifted:has(.tab-content[class*=" rounded-"]) .tab:first-child:not(:is(.tab-active, [aria-selected="true"])) {
1559
1571
  border-bottom-color: transparent;
1560
1572
  }
1561
1573
  .tab {
@@ -1600,7 +1612,7 @@ html {
1600
1612
  grid-column-start: span 9999;
1601
1613
  }
1602
1614
  input.tab:checked + .tab-content,
1603
- .tab-active + .tab-content {
1615
+ :is(.tab-active, [aria-selected="true"]) + .tab-content {
1604
1616
  display: block;
1605
1617
  }
1606
1618
  .table {
@@ -1632,6 +1644,13 @@ input.tab:checked + .tab-content,
1632
1644
  --tw-bg-opacity: 1;
1633
1645
  background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
1634
1646
  }
1647
+ .alert-error {
1648
+ border-color: var(--fallback-er,oklch(var(--er)/0.2));
1649
+ --tw-text-opacity: 1;
1650
+ color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
1651
+ --alert-bg: var(--fallback-er,oklch(var(--er)/1));
1652
+ --alert-bg-mix: var(--fallback-b1,oklch(var(--b1)/1));
1653
+ }
1635
1654
  .btm-nav > *.disabled,
1636
1655
  .btm-nav > *[disabled] {
1637
1656
  pointer-events: none;
@@ -1645,6 +1664,12 @@ input.tab:checked + .tab-content,
1645
1664
  font-size: 1rem;
1646
1665
  line-height: 1.5rem;
1647
1666
  }
1667
+ @media (prefers-reduced-motion: no-preference) {
1668
+
1669
+ .btn {
1670
+ animation: button-pop var(--animation-btn, 0.25s) ease-out;
1671
+ }
1672
+ }
1648
1673
  .btn:active:hover,
1649
1674
  .btn:active:focus {
1650
1675
  animation: button-pop 0s ease-out;
@@ -2174,6 +2199,30 @@ input.tab:checked + .tab-content,
2174
2199
  background-position: calc(0% + 12px) calc(1px + 50%),
2175
2200
  calc(0% + 16px) calc(1px + 50%);
2176
2201
  }
2202
+ .skeleton {
2203
+ border-radius: var(--rounded-box, 1rem);
2204
+ --tw-bg-opacity: 1;
2205
+ background-color: var(--fallback-b3,oklch(var(--b3)/var(--tw-bg-opacity)));
2206
+ will-change: background-position;
2207
+ animation: skeleton 1.8s ease-in-out infinite;
2208
+ background-image: linear-gradient(
2209
+ 105deg,
2210
+ transparent 0%,
2211
+ transparent 40%,
2212
+ var(--fallback-b1,oklch(var(--b1)/1)) 50%,
2213
+ transparent 60%,
2214
+ transparent 100%
2215
+ );
2216
+ background-size: 200% auto;
2217
+ background-repeat: no-repeat;
2218
+ background-position-x: -50%;
2219
+ }
2220
+ @media (prefers-reduced-motion) {
2221
+
2222
+ .skeleton {
2223
+ animation-duration: 15s;
2224
+ }
2225
+ }
2177
2226
  @keyframes skeleton {
2178
2227
 
2179
2228
  from {
@@ -2222,12 +2271,79 @@ input.tab:checked + .tab-content,
2222
2271
  .steps .step[data-content]:after {
2223
2272
  content: attr(data-content);
2224
2273
  }
2274
+ .steps .step-neutral + .step-neutral:before,
2275
+ .steps .step-neutral:after {
2276
+ --tw-bg-opacity: 1;
2277
+ background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
2278
+ --tw-text-opacity: 1;
2279
+ color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
2280
+ }
2281
+ .steps .step-primary + .step-primary:before,
2282
+ .steps .step-primary:after {
2283
+ --tw-bg-opacity: 1;
2284
+ background-color: var(--fallback-p,oklch(var(--p)/var(--tw-bg-opacity)));
2285
+ --tw-text-opacity: 1;
2286
+ color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
2287
+ }
2288
+ .steps .step-secondary + .step-secondary:before,
2289
+ .steps .step-secondary:after {
2290
+ --tw-bg-opacity: 1;
2291
+ background-color: var(--fallback-s,oklch(var(--s)/var(--tw-bg-opacity)));
2292
+ --tw-text-opacity: 1;
2293
+ color: var(--fallback-sc,oklch(var(--sc)/var(--tw-text-opacity)));
2294
+ }
2295
+ .steps .step-accent + .step-accent:before,
2296
+ .steps .step-accent:after {
2297
+ --tw-bg-opacity: 1;
2298
+ background-color: var(--fallback-a,oklch(var(--a)/var(--tw-bg-opacity)));
2299
+ --tw-text-opacity: 1;
2300
+ color: var(--fallback-ac,oklch(var(--ac)/var(--tw-text-opacity)));
2301
+ }
2302
+ .steps .step-info + .step-info:before {
2303
+ --tw-bg-opacity: 1;
2304
+ background-color: var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));
2305
+ }
2306
+ .steps .step-info:after {
2307
+ --tw-bg-opacity: 1;
2308
+ background-color: var(--fallback-in,oklch(var(--in)/var(--tw-bg-opacity)));
2309
+ --tw-text-opacity: 1;
2310
+ color: var(--fallback-inc,oklch(var(--inc)/var(--tw-text-opacity)));
2311
+ }
2312
+ .steps .step-success + .step-success:before {
2313
+ --tw-bg-opacity: 1;
2314
+ background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
2315
+ }
2316
+ .steps .step-success:after {
2317
+ --tw-bg-opacity: 1;
2318
+ background-color: var(--fallback-su,oklch(var(--su)/var(--tw-bg-opacity)));
2319
+ --tw-text-opacity: 1;
2320
+ color: var(--fallback-suc,oklch(var(--suc)/var(--tw-text-opacity)));
2321
+ }
2322
+ .steps .step-warning + .step-warning:before {
2323
+ --tw-bg-opacity: 1;
2324
+ background-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));
2325
+ }
2326
+ .steps .step-warning:after {
2327
+ --tw-bg-opacity: 1;
2328
+ background-color: var(--fallback-wa,oklch(var(--wa)/var(--tw-bg-opacity)));
2329
+ --tw-text-opacity: 1;
2330
+ color: var(--fallback-wac,oklch(var(--wac)/var(--tw-text-opacity)));
2331
+ }
2332
+ .steps .step-error + .step-error:before {
2333
+ --tw-bg-opacity: 1;
2334
+ background-color: var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));
2335
+ }
2336
+ .steps .step-error:after {
2337
+ --tw-bg-opacity: 1;
2338
+ background-color: var(--fallback-er,oklch(var(--er)/var(--tw-bg-opacity)));
2339
+ --tw-text-opacity: 1;
2340
+ color: var(--fallback-erc,oklch(var(--erc)/var(--tw-text-opacity)));
2341
+ }
2225
2342
  .tabs-lifted > .tab:focus-visible {
2226
2343
  border-end-end-radius: 0;
2227
2344
  border-end-start-radius: 0;
2228
2345
  }
2229
- .tab.tab-active:not(.tab-disabled):not([disabled]),
2230
- .tab:is(input:checked) {
2346
+ .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tab:is(input:checked) {
2231
2347
  border-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-border-opacity)));
2232
2348
  --tw-border-opacity: 1;
2233
2349
  --tw-text-opacity: 1;
@@ -2262,8 +2378,7 @@ input.tab:checked + .tab-content,
2262
2378
  padding-inline-end: var(--tab-padding, 1rem);
2263
2379
  padding-top: var(--tab-border, 1px);
2264
2380
  }
2265
- .tabs-lifted > .tab.tab-active:not(.tab-disabled):not([disabled]),
2266
- .tabs-lifted > .tab:is(input:checked) {
2381
+ .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]), .tabs-lifted > .tab:is(input:checked) {
2267
2382
  background-color: var(--tab-bg);
2268
2383
  border-width: var(--tab-border, 1px) var(--tab-border, 1px) 0 var(--tab-border, 1px);
2269
2384
  border-inline-start-color: var(--tab-border-color);
@@ -2274,7 +2389,7 @@ input.tab:checked + .tab-content,
2274
2389
  padding-bottom: var(--tab-border, 1px);
2275
2390
  padding-top: 0;
2276
2391
  }
2277
- .tabs-lifted > .tab.tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked):before {
2392
+ .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked):before {
2278
2393
  z-index: 1;
2279
2394
  content: "";
2280
2395
  display: block;
@@ -2303,26 +2418,26 @@ input.tab:checked + .tab-content,
2303
2418
  );
2304
2419
  background-image: var(--radius-start), var(--radius-end);
2305
2420
  }
2306
- .tabs-lifted > .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted > .tab:is(input:checked):first-child:before {
2421
+ .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, .tabs-lifted > .tab:is(input:checked):first-child:before {
2307
2422
  background-image: var(--radius-end);
2308
2423
  background-position: top right;
2309
2424
  }
2310
- [dir="rtl"] .tabs-lifted > .tab.tab-active:not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):first-child:before {
2425
+ [dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):first-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):first-child:before {
2311
2426
  background-image: var(--radius-start);
2312
2427
  background-position: top left;
2313
2428
  }
2314
- .tabs-lifted > .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted > .tab:is(input:checked):last-child:before {
2429
+ .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, .tabs-lifted > .tab:is(input:checked):last-child:before {
2315
2430
  background-image: var(--radius-start);
2316
2431
  background-position: top left;
2317
2432
  }
2318
- [dir="rtl"] .tabs-lifted > .tab.tab-active:not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):last-child:before {
2433
+ [dir="rtl"] .tabs-lifted > .tab:is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):last-child:before, [dir="rtl"] .tabs-lifted > .tab:is(input:checked):last-child:before {
2319
2434
  background-image: var(--radius-end);
2320
2435
  background-position: top right;
2321
2436
  }
2322
2437
  .tabs-lifted
2323
- > .tab-active:not(.tab-disabled):not([disabled])
2438
+ > :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled])
2324
2439
  + .tabs-lifted
2325
- .tab-active:not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before {
2440
+ :is(.tab-active, [aria-selected="true"]):not(.tab-disabled):not([disabled]):before, .tabs-lifted > .tab:is(input:checked) + .tabs-lifted .tab:is(input:checked):before {
2326
2441
  background-image: var(--radius-end);
2327
2442
  background-position: top right;
2328
2443
  }
@@ -2643,9 +2758,21 @@ input.tab:checked + .tab-content,
2643
2758
  .static {
2644
2759
  position: static;
2645
2760
  }
2761
+ .absolute {
2762
+ position: absolute;
2763
+ }
2646
2764
  .relative {
2647
2765
  position: relative;
2648
2766
  }
2767
+ .right-6 {
2768
+ right: 1.5rem;
2769
+ }
2770
+ .top-8 {
2771
+ top: 2rem;
2772
+ }
2773
+ .z-50 {
2774
+ z-index: 50;
2775
+ }
2649
2776
  .z-\[1\] {
2650
2777
  z-index: 1;
2651
2778
  }
@@ -2682,6 +2809,9 @@ input.tab:checked + .tab-content,
2682
2809
  .mt-2 {
2683
2810
  margin-top: 0.5rem;
2684
2811
  }
2812
+ .mt-4 {
2813
+ margin-top: 1rem;
2814
+ }
2685
2815
  .inline {
2686
2816
  display: inline;
2687
2817
  }
@@ -2718,6 +2848,10 @@ input.tab:checked + .tab-content,
2718
2848
  .min-w-0 {
2719
2849
  min-width: 0px;
2720
2850
  }
2851
+ .min-w-max {
2852
+ min-width: -moz-max-content;
2853
+ min-width: max-content;
2854
+ }
2721
2855
  .max-w-screen-lg {
2722
2856
  max-width: 1024px;
2723
2857
  }
@@ -2742,6 +2876,9 @@ input.tab:checked + .tab-content,
2742
2876
  .items-center {
2743
2877
  align-items: center;
2744
2878
  }
2879
+ .justify-end {
2880
+ justify-content: flex-end;
2881
+ }
2745
2882
  .justify-center {
2746
2883
  justify-content: center;
2747
2884
  }
@@ -2757,6 +2894,9 @@ input.tab:checked + .tab-content,
2757
2894
  .overflow-auto {
2758
2895
  overflow: auto;
2759
2896
  }
2897
+ .overflow-scroll {
2898
+ overflow: scroll;
2899
+ }
2760
2900
  .whitespace-nowrap {
2761
2901
  white-space: nowrap;
2762
2902
  }
@@ -2775,6 +2915,9 @@ input.tab:checked + .tab-content,
2775
2915
  .rounded-lg {
2776
2916
  border-radius: 0.5rem;
2777
2917
  }
2918
+ .rounded-md {
2919
+ border-radius: 0.375rem;
2920
+ }
2778
2921
  .rounded-none {
2779
2922
  border-radius: 0px;
2780
2923
  }
@@ -2797,6 +2940,10 @@ input.tab:checked + .tab-content,
2797
2940
  .border-b-2 {
2798
2941
  border-bottom-width: 2px;
2799
2942
  }
2943
+ .border-black {
2944
+ --tw-border-opacity: 1;
2945
+ border-color: rgb(0 0 0 / var(--tw-border-opacity));
2946
+ }
2800
2947
  .border-error {
2801
2948
  --tw-border-opacity: 1;
2802
2949
  border-color: var(--fallback-er,oklch(var(--er)/var(--tw-border-opacity)));
@@ -2856,6 +3003,13 @@ input.tab:checked + .tab-content,
2856
3003
  padding-top: 0.5rem;
2857
3004
  padding-bottom: 0.5rem;
2858
3005
  }
3006
+ .text-justify {
3007
+ text-align: justify;
3008
+ }
3009
+ .text-base {
3010
+ font-size: 1rem;
3011
+ line-height: 1.5rem;
3012
+ }
2859
3013
  .text-lg {
2860
3014
  font-size: 1.125rem;
2861
3015
  line-height: 1.75rem;
@@ -2881,15 +3035,35 @@ input.tab:checked + .tab-content,
2881
3035
  .leading-5 {
2882
3036
  line-height: 1.25rem;
2883
3037
  }
3038
+ .text-blue-600 {
3039
+ --tw-text-opacity: 1;
3040
+ color: rgb(37 99 235 / var(--tw-text-opacity));
3041
+ }
2884
3042
  .text-gray-600 {
2885
3043
  --tw-text-opacity: 1;
2886
3044
  color: rgb(75 85 99 / var(--tw-text-opacity));
2887
3045
  }
3046
+ .text-red-700 {
3047
+ --tw-text-opacity: 1;
3048
+ color: rgb(185 28 28 / var(--tw-text-opacity));
3049
+ }
3050
+ .underline {
3051
+ text-decoration-line: underline;
3052
+ }
2888
3053
  .shadow {
2889
3054
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
2890
3055
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
2891
3056
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
2892
3057
  }
3058
+ .shadow-lg {
3059
+ --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
3060
+ --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);
3061
+ box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
3062
+ }
3063
+ .blur {
3064
+ --tw-blur: blur(8px);
3065
+ 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);
3066
+ }
2893
3067
  .filter {
2894
3068
  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
3069
  }
@@ -2905,6 +3079,10 @@ input.tab:checked + .tab-content,
2905
3079
  --tw-bg-opacity: 1;
2906
3080
  background-color: rgb(243 244 246 / var(--tw-bg-opacity));
2907
3081
  }
3082
+ .hover\:text-blue-800:hover {
3083
+ --tw-text-opacity: 1;
3084
+ color: rgb(30 64 175 / var(--tw-text-opacity));
3085
+ }
2908
3086
  .hover\:text-gray-700:hover {
2909
3087
  --tw-text-opacity: 1;
2910
3088
  color: rgb(55 65 81 / var(--tw-text-opacity));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@genspectrum/dashboard-components",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "GenSpectrum web components for building dashboards",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -35,7 +35,7 @@
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}\" --write",
38
+ "format": "prettier \"**/*.{ts,tsx,json,md,mdx,mjs,cjs}\" --write",
39
39
  "check-format": "prettier --check \"**/*.{ts,tsx,json,md,mdx,mjs,cjs}\"",
40
40
  "check-types": "tsc --noEmit",
41
41
  "check-dependencies": "depcheck",
@@ -70,7 +70,7 @@
70
70
  "zod": "^3.23.0"
71
71
  },
72
72
  "devDependencies": {
73
- "@custom-elements-manifest/analyzer": "^0.9.4",
73
+ "@custom-elements-manifest/analyzer": "^0.10.2",
74
74
  "@playwright/test": "^1.43.1",
75
75
  "@storybook/addon-actions": "^8.0.9",
76
76
  "@storybook/addon-essentials": "^8.0.9",
@@ -80,7 +80,7 @@
80
80
  "@storybook/preact": "^8.0.9",
81
81
  "@storybook/preact-vite": "^8.0.9",
82
82
  "@storybook/test": "^8.0.0",
83
- "@storybook/test-runner": "^0.17.0",
83
+ "@storybook/test-runner": "^0.18.0",
84
84
  "@storybook/types": "^8.0.9",
85
85
  "@storybook/web-components": "^8.0.9",
86
86
  "@storybook/web-components-vite": "^8.0.9",
@@ -11,6 +11,7 @@ const meta: Meta<AggregateProps> = {
11
11
  argTypes: {
12
12
  fields: [{ control: 'object' }],
13
13
  size: [{ control: 'object' }],
14
+ headline: { control: 'text' },
14
15
  },
15
16
  parameters: {
16
17
  fetchMock: {
@@ -49,5 +50,6 @@ export const Default: StoryObj<AggregateProps> = {
49
50
  country: 'USA',
50
51
  },
51
52
  size: { width: '100%', height: '70vh' },
53
+ headline: 'Aggregate',
52
54
  },
53
55
  };
@@ -6,6 +6,7 @@ import { type AggregateData, queryAggregateData } from '../../query/queryAggrega
6
6
  import { type LapisFilter } from '../../types';
7
7
  import { LapisUrlContext } from '../LapisUrlContext';
8
8
  import { CsvDownloadButton } from '../components/csv-download-button';
9
+ import { ErrorBoundary } from '../components/error-boundary';
9
10
  import { ErrorDisplay } from '../components/error-display';
10
11
  import Headline from '../components/headline';
11
12
  import Info from '../components/info';
@@ -17,53 +18,57 @@ import { useQuery } from '../useQuery';
17
18
 
18
19
  export type View = 'table';
19
20
 
20
- export interface AggregateProps {
21
+ export type AggregateProps = {
22
+ size?: Size;
23
+ headline?: string;
24
+ } & AggregateInnerProps;
25
+
26
+ export interface AggregateInnerProps {
21
27
  filter: LapisFilter;
22
28
  fields: string[];
23
29
  views: View[];
24
- size?: Size;
25
30
  }
26
31
 
27
- export const Aggregate: FunctionComponent<AggregateProps> = ({ fields, views, filter, size }) => {
32
+ export const Aggregate: FunctionComponent<AggregateProps> = ({
33
+ views,
34
+ size,
35
+ headline = 'Mutations',
36
+ filter,
37
+ fields,
38
+ }) => {
39
+ const defaultSize = { height: '600px', width: '100%' };
40
+
41
+ return (
42
+ <ErrorBoundary size={size} defaultSize={defaultSize} headline={headline}>
43
+ <ResizeContainer size={size} defaultSize={defaultSize}>
44
+ <Headline heading={headline}>
45
+ <AggregateInner fields={fields} filter={filter} views={views} />
46
+ </Headline>
47
+ </ResizeContainer>
48
+ </ErrorBoundary>
49
+ );
50
+ };
51
+
52
+ export const AggregateInner: FunctionComponent<AggregateInnerProps> = ({ fields, views, filter }) => {
28
53
  const lapis = useContext(LapisUrlContext);
29
54
 
30
55
  const { data, error, isLoading } = useQuery(async () => {
31
56
  return queryAggregateData(filter, fields, lapis);
32
57
  }, [filter, fields, lapis]);
33
58
 
34
- const headline = 'Aggregate';
35
-
36
59
  if (isLoading) {
37
- return (
38
- <Headline heading={headline}>
39
- <LoadingDisplay />
40
- </Headline>
41
- );
60
+ return <LoadingDisplay />;
42
61
  }
43
62
 
44
63
  if (error !== null) {
45
- return (
46
- <Headline heading={headline}>
47
- <ErrorDisplay error={error} />
48
- </Headline>
49
- );
64
+ return <ErrorDisplay error={error} />;
50
65
  }
51
66
 
52
67
  if (data === null) {
53
- return (
54
- <Headline heading={headline}>
55
- <NoDataDisplay />
56
- </Headline>
57
- );
68
+ return <NoDataDisplay />;
58
69
  }
59
70
 
60
- return (
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>
66
- );
71
+ return <AggregatedDataTabs data={data} views={views} fields={fields} />;
67
72
  };
68
73
 
69
74
  type AggregatedDataTabsProps = {
@@ -96,7 +101,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({ data }) => {
96
101
  return (
97
102
  <div class='flex flex-row'>
98
103
  <CsvDownloadButton className='mx-1 btn btn-xs' getData={() => data} filename='aggregate.csv' />
99
- <Info className='mx-1' content='Info for aggregate' />
104
+ <Info>Info for aggregate</Info>
100
105
  </div>
101
106
  );
102
107
  };
@@ -0,0 +1,62 @@
1
+ import { type Meta, type StoryObj } from '@storybook/preact';
2
+ import { expect, waitFor, within } from '@storybook/test';
3
+
4
+ import { ErrorBoundary } from './error-boundary';
5
+
6
+ const meta: Meta = {
7
+ title: 'Component/Error boundary',
8
+ component: ErrorBoundary,
9
+ parameters: { fetchMock: {} },
10
+ argTypes: {
11
+ size: { control: 'object' },
12
+ defaultSize: { control: 'object' },
13
+ headline: { control: 'text' },
14
+ },
15
+ };
16
+
17
+ export default meta;
18
+
19
+ export const ErrorBoundaryWithoutErrorStory: StoryObj = {
20
+ render: (args) => (
21
+ <ErrorBoundary size={args.size} defaultSize={args.defaultSize} headline={args.headline}>
22
+ <div>Some content</div>
23
+ </ErrorBoundary>
24
+ ),
25
+ args: {
26
+ size: { height: '600px', width: '100%' },
27
+ defaultSize: { height: '600px', width: '100%' },
28
+ headline: 'Some headline',
29
+ },
30
+
31
+ play: async ({ canvasElement }) => {
32
+ const canvas = within(canvasElement);
33
+ const content = canvas.getByText('Some content', { exact: false });
34
+ await waitFor(() => expect(content).toBeInTheDocument());
35
+ await waitFor(() => expect(canvas.queryByText('Some headline')).not.toBeInTheDocument());
36
+ },
37
+ };
38
+
39
+ export const ErrorBoundaryWithErrorStory: StoryObj = {
40
+ render: (args) => (
41
+ <ErrorBoundary size={args.size} defaultSize={args.defaultSize} headline={args.headline}>
42
+ <ContentThatThrowsError />
43
+ </ErrorBoundary>
44
+ ),
45
+ args: {
46
+ size: { height: '600px', width: '100%' },
47
+ defaultSize: { height: '600px', width: '100%' },
48
+ headline: 'Some headline',
49
+ },
50
+
51
+ play: async ({ canvasElement }) => {
52
+ const canvas = within(canvasElement);
53
+ const content = canvas.queryByText('Some content.', { exact: false });
54
+ await waitFor(() => expect(content).not.toBeInTheDocument());
55
+ await waitFor(() => expect(canvas.getByText('Some headline')).toBeInTheDocument());
56
+ await waitFor(() => expect(canvas.getByText('Error')).toBeInTheDocument());
57
+ },
58
+ };
59
+
60
+ const ContentThatThrowsError = () => {
61
+ throw new Error('Some error');
62
+ };
@@ -0,0 +1,31 @@
1
+ import type { FunctionComponent } from 'preact';
2
+ import { useErrorBoundary } from 'preact/hooks';
3
+
4
+ import { ErrorDisplay } from './error-display';
5
+ import { ResizeContainer, type Size } from './resize-container';
6
+ import Headline from '../components/headline';
7
+
8
+ export const ErrorBoundary: FunctionComponent<{ size?: Size; defaultSize: Size; headline?: string }> = ({
9
+ size,
10
+ defaultSize,
11
+ headline,
12
+ children,
13
+ }) => {
14
+ const [internalError] = useErrorBoundary();
15
+
16
+ if (internalError) {
17
+ console.error(internalError);
18
+ }
19
+
20
+ if (internalError) {
21
+ return (
22
+ <ResizeContainer defaultSize={defaultSize} size={size}>
23
+ <Headline heading={headline}>
24
+ <ErrorDisplay error={internalError} />
25
+ </Headline>
26
+ </ResizeContainer>
27
+ );
28
+ }
29
+
30
+ return <>{children}</>;
31
+ };