@genspectrum/dashboard-components 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +51 -3
- package/dist/dashboard-components.js +359 -159
- package/dist/dashboard-components.js.map +1 -1
- package/dist/genspectrum-components.d.ts +3 -3
- package/dist/style.css +92 -8
- package/package.json +1 -1
- package/src/constants.ts +1 -0
- package/src/preact/aggregatedData/aggregate.tsx +1 -1
- package/src/preact/components/color-scale-selector-dropdown.stories.tsx +27 -0
- package/src/preact/components/color-scale-selector-dropdown.tsx +17 -0
- package/src/preact/components/color-scale-selector.stories.tsx +61 -0
- package/src/preact/components/color-scale-selector.tsx +79 -0
- package/src/preact/components/info.stories.tsx +37 -10
- package/src/preact/components/info.tsx +22 -43
- package/src/preact/components/min-max-range-slider.tsx +1 -1
- package/src/preact/components/tooltip.stories.tsx +12 -2
- package/src/preact/components/tooltip.tsx +38 -14
- package/src/preact/mutationComparison/mutation-comparison.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter-info.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter.stories.tsx +1 -1
- package/src/preact/mutationFilter/mutation-filter.tsx +2 -3
- package/src/preact/mutations/mutations.tsx +1 -1
- package/src/preact/mutationsOverTime/__mockData__/aggregated_byDay.json +38 -0
- package/src/preact/mutationsOverTime/__mockData__/aggregated_byWeek.json +122 -0
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_20_01_2024.json +6778 -0
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_21_01_2024.json +7129 -0
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_22_01_2024.json +4681 -0
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_23_01_2024.json +10738 -0
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_24_01_2024.json +11710 -0
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_25_01_2024.json +11557 -0
- package/src/preact/mutationsOverTime/__mockData__/aminoAcidMutations_26_01_2024.json +8596 -0
- package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week3_2024.json +8812 -0
- package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week4_2024.json +9730 -0
- package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week5_2024.json +9865 -0
- package/src/preact/mutationsOverTime/__mockData__/nucleotideMutations_week6_2024.json +11314 -0
- package/src/preact/mutationsOverTime/mutations-over-time-grid.tsx +83 -40
- package/src/preact/mutationsOverTime/mutations-over-time.tsx +50 -11
- package/src/preact/numberSequencesOverTime/number-sequences-over-time.tsx +1 -1
- package/src/preact/prevalenceOverTime/prevalence-over-time.tsx +1 -1
- package/src/preact/shared/charts/colors.ts +1 -1
- package/src/utils/temporal.spec.ts +3 -4
- package/src/utils/temporal.ts +9 -4
- package/src/web-components/visualization/gs-mutations-over-time.stories.ts +262 -2
- package/src/preact/shared/icons/DeleteIcon.tsx +0 -17
|
@@ -976,21 +976,21 @@ declare global {
|
|
|
976
976
|
|
|
977
977
|
declare global {
|
|
978
978
|
interface HTMLElementTagNameMap {
|
|
979
|
-
'gs-
|
|
979
|
+
'gs-relative-growth-advantage': RelativeGrowthAdvantageComponent;
|
|
980
980
|
}
|
|
981
981
|
}
|
|
982
982
|
|
|
983
983
|
|
|
984
984
|
declare global {
|
|
985
985
|
interface HTMLElementTagNameMap {
|
|
986
|
-
'gs-
|
|
986
|
+
'gs-aggregate-component': AggregateComponent;
|
|
987
987
|
}
|
|
988
988
|
}
|
|
989
989
|
|
|
990
990
|
|
|
991
991
|
declare global {
|
|
992
992
|
interface HTMLElementTagNameMap {
|
|
993
|
-
'gs-
|
|
993
|
+
'gs-prevalence-over-time': PrevalenceOverTimeComponent;
|
|
994
994
|
}
|
|
995
995
|
}
|
|
996
996
|
|
package/dist/style.css
CHANGED
|
@@ -1428,6 +1428,11 @@ html {
|
|
|
1428
1428
|
visibility: visible;
|
|
1429
1429
|
opacity: 1;
|
|
1430
1430
|
}
|
|
1431
|
+
.modal-action {
|
|
1432
|
+
display: flex;
|
|
1433
|
+
margin-top: 1.5rem;
|
|
1434
|
+
justify-content: flex-end;
|
|
1435
|
+
}
|
|
1431
1436
|
:root:has(:is(.modal-open, .modal:target, .modal-toggle:checked + .modal, .modal[open])) {
|
|
1432
1437
|
overflow: hidden;
|
|
1433
1438
|
scrollbar-gutter: stable;
|
|
@@ -1954,6 +1959,11 @@ input.tab:checked + .tab-content,
|
|
|
1954
1959
|
--tw-scale-y: 1;
|
|
1955
1960
|
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
1956
1961
|
}
|
|
1962
|
+
.modal-action > :not([hidden]) ~ :not([hidden]) {
|
|
1963
|
+
--tw-space-x-reverse: 0;
|
|
1964
|
+
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
|
|
1965
|
+
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
|
|
1966
|
+
}
|
|
1957
1967
|
@keyframes modal-pop {
|
|
1958
1968
|
|
|
1959
1969
|
0% {
|
|
@@ -2528,6 +2538,9 @@ input.tab:checked + .tab-content,
|
|
|
2528
2538
|
border-end-end-radius: inherit;
|
|
2529
2539
|
border-start-end-radius: inherit;
|
|
2530
2540
|
}
|
|
2541
|
+
.modal-bottom {
|
|
2542
|
+
place-items: end;
|
|
2543
|
+
}
|
|
2531
2544
|
.select-xs {
|
|
2532
2545
|
height: 1.5rem;
|
|
2533
2546
|
min-height: 1.5rem;
|
|
@@ -2767,6 +2780,9 @@ input.tab:checked + .tab-content,
|
|
|
2767
2780
|
.visible {
|
|
2768
2781
|
visibility: visible;
|
|
2769
2782
|
}
|
|
2783
|
+
.invisible {
|
|
2784
|
+
visibility: hidden;
|
|
2785
|
+
}
|
|
2770
2786
|
.static {
|
|
2771
2787
|
position: static;
|
|
2772
2788
|
}
|
|
@@ -2782,18 +2798,39 @@ input.tab:checked + .tab-content,
|
|
|
2782
2798
|
.-top-3 {
|
|
2783
2799
|
top: -0.75rem;
|
|
2784
2800
|
}
|
|
2801
|
+
.bottom-full {
|
|
2802
|
+
bottom: 100%;
|
|
2803
|
+
}
|
|
2785
2804
|
.left-0 {
|
|
2786
2805
|
left: 0px;
|
|
2787
2806
|
}
|
|
2807
|
+
.left-1\/2 {
|
|
2808
|
+
left: 50%;
|
|
2809
|
+
}
|
|
2810
|
+
.left-full {
|
|
2811
|
+
left: 100%;
|
|
2812
|
+
}
|
|
2813
|
+
.right-0 {
|
|
2814
|
+
right: 0px;
|
|
2815
|
+
}
|
|
2788
2816
|
.right-2 {
|
|
2789
2817
|
right: 0.5rem;
|
|
2790
2818
|
}
|
|
2819
|
+
.right-full {
|
|
2820
|
+
right: 100%;
|
|
2821
|
+
}
|
|
2791
2822
|
.top-0 {
|
|
2792
2823
|
top: 0px;
|
|
2793
2824
|
}
|
|
2825
|
+
.top-1\/2 {
|
|
2826
|
+
top: 50%;
|
|
2827
|
+
}
|
|
2794
2828
|
.top-2 {
|
|
2795
2829
|
top: 0.5rem;
|
|
2796
2830
|
}
|
|
2831
|
+
.top-full {
|
|
2832
|
+
top: 100%;
|
|
2833
|
+
}
|
|
2797
2834
|
.z-10 {
|
|
2798
2835
|
z-index: 10;
|
|
2799
2836
|
}
|
|
@@ -2810,6 +2847,10 @@ input.tab:checked + .tab-content,
|
|
|
2810
2847
|
margin-left: 0.25rem;
|
|
2811
2848
|
margin-right: 0.25rem;
|
|
2812
2849
|
}
|
|
2850
|
+
.mx-2 {
|
|
2851
|
+
margin-left: 0.5rem;
|
|
2852
|
+
margin-right: 0.5rem;
|
|
2853
|
+
}
|
|
2813
2854
|
.mx-auto {
|
|
2814
2855
|
margin-left: auto;
|
|
2815
2856
|
margin-right: auto;
|
|
@@ -2822,18 +2863,30 @@ input.tab:checked + .tab-content,
|
|
|
2822
2863
|
margin-top: 1rem;
|
|
2823
2864
|
margin-bottom: 1rem;
|
|
2824
2865
|
}
|
|
2866
|
+
.mb-1 {
|
|
2867
|
+
margin-bottom: 0.25rem;
|
|
2868
|
+
}
|
|
2825
2869
|
.mb-2 {
|
|
2826
2870
|
margin-bottom: 0.5rem;
|
|
2827
2871
|
}
|
|
2872
|
+
.ml-1 {
|
|
2873
|
+
margin-left: 0.25rem;
|
|
2874
|
+
}
|
|
2828
2875
|
.ml-2\.5 {
|
|
2829
2876
|
margin-left: 0.625rem;
|
|
2830
2877
|
}
|
|
2831
2878
|
.ml-3 {
|
|
2832
2879
|
margin-left: 0.75rem;
|
|
2833
2880
|
}
|
|
2881
|
+
.mr-1 {
|
|
2882
|
+
margin-right: 0.25rem;
|
|
2883
|
+
}
|
|
2834
2884
|
.mr-2 {
|
|
2835
2885
|
margin-right: 0.5rem;
|
|
2836
2886
|
}
|
|
2887
|
+
.mt-1 {
|
|
2888
|
+
margin-top: 0.25rem;
|
|
2889
|
+
}
|
|
2837
2890
|
.mt-4 {
|
|
2838
2891
|
margin-top: 1rem;
|
|
2839
2892
|
}
|
|
@@ -2855,9 +2908,15 @@ input.tab:checked + .tab-content,
|
|
|
2855
2908
|
.hidden {
|
|
2856
2909
|
display: none;
|
|
2857
2910
|
}
|
|
2911
|
+
.h-8 {
|
|
2912
|
+
height: 2rem;
|
|
2913
|
+
}
|
|
2858
2914
|
.h-full {
|
|
2859
2915
|
height: 100%;
|
|
2860
2916
|
}
|
|
2917
|
+
.w-10 {
|
|
2918
|
+
width: 2.5rem;
|
|
2919
|
+
}
|
|
2861
2920
|
.w-16 {
|
|
2862
2921
|
width: 4rem;
|
|
2863
2922
|
}
|
|
@@ -2892,6 +2951,14 @@ input.tab:checked + .tab-content,
|
|
|
2892
2951
|
.grow {
|
|
2893
2952
|
flex-grow: 1;
|
|
2894
2953
|
}
|
|
2954
|
+
.translate-x-\[-50\%\] {
|
|
2955
|
+
--tw-translate-x: -50%;
|
|
2956
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
2957
|
+
}
|
|
2958
|
+
.translate-y-\[-50\%\] {
|
|
2959
|
+
--tw-translate-y: -50%;
|
|
2960
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
2961
|
+
}
|
|
2895
2962
|
.resize {
|
|
2896
2963
|
resize: both;
|
|
2897
2964
|
}
|
|
@@ -2925,9 +2992,6 @@ input.tab:checked + .tab-content,
|
|
|
2925
2992
|
.overflow-auto {
|
|
2926
2993
|
overflow: auto;
|
|
2927
2994
|
}
|
|
2928
|
-
.overflow-y-auto {
|
|
2929
|
-
overflow-y: auto;
|
|
2930
|
-
}
|
|
2931
2995
|
.whitespace-nowrap {
|
|
2932
2996
|
white-space: nowrap;
|
|
2933
2997
|
}
|
|
@@ -2992,6 +3056,10 @@ input.tab:checked + .tab-content,
|
|
|
2992
3056
|
--tw-border-opacity: 1;
|
|
2993
3057
|
border-color: rgb(239 68 68 / var(--tw-border-opacity));
|
|
2994
3058
|
}
|
|
3059
|
+
.bg-red-200 {
|
|
3060
|
+
--tw-bg-opacity: 1;
|
|
3061
|
+
background-color: rgb(254 202 202 / var(--tw-bg-opacity));
|
|
3062
|
+
}
|
|
2995
3063
|
.bg-white {
|
|
2996
3064
|
--tw-bg-opacity: 1;
|
|
2997
3065
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
|
@@ -3090,9 +3158,6 @@ input.tab:checked + .tab-content,
|
|
|
3090
3158
|
.underline {
|
|
3091
3159
|
text-decoration-line: underline;
|
|
3092
3160
|
}
|
|
3093
|
-
.opacity-90 {
|
|
3094
|
-
opacity: 0.9;
|
|
3095
|
-
}
|
|
3096
3161
|
.shadow {
|
|
3097
3162
|
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
3098
3163
|
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
|
@@ -3118,6 +3183,25 @@ input.tab:checked + .tab-content,
|
|
|
3118
3183
|
.duration-150 {
|
|
3119
3184
|
transition-duration: 150ms;
|
|
3120
3185
|
}
|
|
3186
|
+
@media (min-width: 640px) {
|
|
3187
|
+
|
|
3188
|
+
.sm\:modal-middle {
|
|
3189
|
+
place-items: center;
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3192
|
+
.sm\:modal-middle :where(.modal-box) {
|
|
3193
|
+
width: 91.666667%;
|
|
3194
|
+
max-width: 32rem;
|
|
3195
|
+
--tw-translate-y: 0px;
|
|
3196
|
+
--tw-scale-x: .9;
|
|
3197
|
+
--tw-scale-y: .9;
|
|
3198
|
+
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
|
3199
|
+
border-top-left-radius: var(--rounded-box, 1rem);
|
|
3200
|
+
border-top-right-radius: var(--rounded-box, 1rem);
|
|
3201
|
+
border-bottom-right-radius: var(--rounded-box, 1rem);
|
|
3202
|
+
border-bottom-left-radius: var(--rounded-box, 1rem);
|
|
3203
|
+
}
|
|
3204
|
+
}
|
|
3121
3205
|
.focus-within\:border-gray-400:focus-within {
|
|
3122
3206
|
--tw-border-opacity: 1;
|
|
3123
3207
|
border-color: rgb(156 163 175 / var(--tw-border-opacity));
|
|
@@ -3154,8 +3238,8 @@ input.tab:checked + .tab-content,
|
|
|
3154
3238
|
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
3155
3239
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
|
3156
3240
|
}
|
|
3157
|
-
.peer:hover ~ .peer-hover\:
|
|
3158
|
-
|
|
3241
|
+
.peer:hover ~ .peer-hover\:visible {
|
|
3242
|
+
visibility: visible;
|
|
3159
3243
|
}.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{/*
|
|
3160
3244
|
/*rtl:begin:ignore*/left:0/*
|
|
3161
3245
|
/*rtl:end:ignore*/}/*
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -2,5 +2,6 @@ export const LAPIS_URL = 'https://lapis.cov-spectrum.org/open/v2';
|
|
|
2
2
|
|
|
3
3
|
export const AGGREGATED_ENDPOINT = `${LAPIS_URL}/sample/aggregated`;
|
|
4
4
|
export const NUCLEOTIDE_MUTATIONS_ENDPOINT = `${LAPIS_URL}/sample/nucleotideMutations`;
|
|
5
|
+
export const AMINO_ACID_MUTATIONS_ENDPOINT = `${LAPIS_URL}/sample/aminoAcidMutations`;
|
|
5
6
|
export const NUCLEOTIDE_INSERTIONS_ENDPOINT = `${LAPIS_URL}/sample/nucleotideInsertions`;
|
|
6
7
|
export const REFERENCE_GENOME_ENDPOINT = `${LAPIS_URL}/sample/referenceGenome`;
|
|
@@ -104,7 +104,7 @@ const Toolbar: FunctionComponent<ToolbarProps> = ({ data }) => {
|
|
|
104
104
|
return (
|
|
105
105
|
<div class='flex flex-row'>
|
|
106
106
|
<CsvDownloadButton className='mx-1 btn btn-xs' getData={() => data} filename='aggregate.csv' />
|
|
107
|
-
<Info
|
|
107
|
+
<Info>Info for aggregate</Info>
|
|
108
108
|
</div>
|
|
109
109
|
);
|
|
110
110
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { type FunctionComponent } from 'preact';
|
|
3
|
+
import { useState } from 'preact/hooks';
|
|
4
|
+
|
|
5
|
+
import { type ColorScale } from './color-scale-selector';
|
|
6
|
+
import { ColorScaleSelectorDropdown, type ColorScaleSelectorDropdownProps } from './color-scale-selector-dropdown';
|
|
7
|
+
import { ProportionSelector } from './proportion-selector';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<ColorScaleSelectorDropdownProps> = {
|
|
10
|
+
title: 'Component/Color scale selector dropdown',
|
|
11
|
+
component: ProportionSelector,
|
|
12
|
+
parameters: { fetchMock: {} },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default meta;
|
|
16
|
+
|
|
17
|
+
const WrapperWithState: FunctionComponent<{}> = () => {
|
|
18
|
+
const [colorScale, setColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
19
|
+
|
|
20
|
+
return <ColorScaleSelectorDropdown colorScale={colorScale} setColorScale={setColorScale} />;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const ColorScaleSelectorStory: StoryObj<ColorScaleSelectorDropdownProps> = {
|
|
24
|
+
render: () => {
|
|
25
|
+
return <WrapperWithState />;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
import { ColorScaleSelector, type ColorScaleSelectorProps } from './color-scale-selector';
|
|
4
|
+
import { Dropdown } from './dropdown';
|
|
5
|
+
|
|
6
|
+
export type ColorScaleSelectorDropdownProps = ColorScaleSelectorProps;
|
|
7
|
+
|
|
8
|
+
export const ColorScaleSelectorDropdown: FunctionComponent<ColorScaleSelectorDropdownProps> = ({
|
|
9
|
+
colorScale,
|
|
10
|
+
setColorScale,
|
|
11
|
+
}) => {
|
|
12
|
+
return (
|
|
13
|
+
<Dropdown buttonTitle={`Color scale`} placement={'bottom-start'}>
|
|
14
|
+
<ColorScaleSelector colorScale={colorScale} setColorScale={setColorScale} />
|
|
15
|
+
</Dropdown>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type Meta, type StoryObj } from '@storybook/preact';
|
|
2
|
+
import { expect, fireEvent, fn, waitFor, within } from '@storybook/test';
|
|
3
|
+
import { type FunctionComponent } from 'preact';
|
|
4
|
+
import { useState } from 'preact/hooks';
|
|
5
|
+
|
|
6
|
+
import { type ColorScale, ColorScaleSelector, type ColorScaleSelectorProps } from './color-scale-selector';
|
|
7
|
+
|
|
8
|
+
const meta: Meta<ColorScaleSelectorProps> = {
|
|
9
|
+
title: 'Component/Color scale selector',
|
|
10
|
+
component: ColorScaleSelector,
|
|
11
|
+
parameters: { fetchMock: {} },
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default meta;
|
|
15
|
+
|
|
16
|
+
const WrapperWithState: FunctionComponent<{
|
|
17
|
+
setColorScale: (colorScale: ColorScale) => void;
|
|
18
|
+
}> = ({ setColorScale }) => {
|
|
19
|
+
const [internalColorScale, setInternalColorScale] = useState<ColorScale>({ min: 0, max: 1, color: 'indigo' });
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ColorScaleSelector
|
|
23
|
+
colorScale={internalColorScale}
|
|
24
|
+
setColorScale={(colorScale) => {
|
|
25
|
+
setColorScale(colorScale);
|
|
26
|
+
setInternalColorScale(colorScale);
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const ColorScaleSelectorStory: StoryObj<ColorScaleSelectorProps> = {
|
|
33
|
+
render: (args) => {
|
|
34
|
+
return <WrapperWithState {...args} />;
|
|
35
|
+
},
|
|
36
|
+
args: {
|
|
37
|
+
setColorScale: fn(),
|
|
38
|
+
},
|
|
39
|
+
play: async ({ canvasElement, step, args }) => {
|
|
40
|
+
const canvas = within(canvasElement);
|
|
41
|
+
|
|
42
|
+
await step('Expect initial value to be 0 and value 100%', async () => {
|
|
43
|
+
await expect(canvas.getAllByText('%', { exact: false })[0]).toHaveTextContent('0%');
|
|
44
|
+
await expect(canvas.getAllByText('%', { exact: false })[1]).toHaveTextContent('100%');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
await step('Move min slider to 20%', async () => {
|
|
48
|
+
const minSlider = canvas.getAllByRole('slider')[0];
|
|
49
|
+
await fireEvent.input(minSlider, { target: { value: '20' } });
|
|
50
|
+
await expect(args.setColorScale).toHaveBeenCalledWith({ min: 0.2, max: 1, color: 'indigo' });
|
|
51
|
+
await waitFor(() => expect(canvas.getAllByText('%', { exact: false })[0]).toHaveTextContent('20%'));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
await step('Move max slider to 80%', async () => {
|
|
55
|
+
const maxSlider = canvas.getAllByRole('slider')[1];
|
|
56
|
+
await fireEvent.input(maxSlider, { target: { value: '80' } });
|
|
57
|
+
await expect(args.setColorScale).toHaveBeenCalledWith({ min: 0.2, max: 0.8, color: 'indigo' });
|
|
58
|
+
await waitFor(() => expect(canvas.getAllByText('%', { exact: false })[1]).toHaveTextContent('80%'));
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { type FunctionComponent } from 'preact';
|
|
2
|
+
|
|
3
|
+
import { MinMaxRangeSlider } from './min-max-range-slider';
|
|
4
|
+
import { type GraphColor, singleGraphColorRGBByName } from '../shared/charts/colors';
|
|
5
|
+
import { formatProportion } from '../shared/table/formatProportion';
|
|
6
|
+
|
|
7
|
+
export interface ColorScale {
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
color: GraphColor;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ColorScaleSelectorProps {
|
|
14
|
+
colorScale: ColorScale;
|
|
15
|
+
setColorScale: (colorScale: ColorScale) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const ColorScaleSelector: FunctionComponent<ColorScaleSelectorProps> = ({ colorScale, setColorScale }) => {
|
|
19
|
+
const colorDisplayCss = `w-10 h-8 border border-gray-200 mx-2 text-xs flex items-center justify-center`;
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<div className='flex items-center'>
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
backgroundColor: singleGraphColorRGBByName(colorScale.color, 0),
|
|
26
|
+
color: 'black',
|
|
27
|
+
}}
|
|
28
|
+
className={colorDisplayCss}
|
|
29
|
+
>
|
|
30
|
+
{formatProportion(colorScale.min, 0)}
|
|
31
|
+
</div>
|
|
32
|
+
<div className='w-64'>
|
|
33
|
+
<MinMaxRangeSlider
|
|
34
|
+
min={colorScale.min * 100}
|
|
35
|
+
max={colorScale.max * 100}
|
|
36
|
+
setMin={(percentage) => {
|
|
37
|
+
setColorScale({ ...colorScale, min: percentage / 100 });
|
|
38
|
+
}}
|
|
39
|
+
setMax={(percentage) => {
|
|
40
|
+
setColorScale({ ...colorScale, max: percentage / 100 });
|
|
41
|
+
}}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
<div
|
|
45
|
+
style={{
|
|
46
|
+
backgroundColor: singleGraphColorRGBByName(colorScale.color, 1),
|
|
47
|
+
color: 'white',
|
|
48
|
+
}}
|
|
49
|
+
className={colorDisplayCss}
|
|
50
|
+
>
|
|
51
|
+
{formatProportion(colorScale.max, 0)}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const getColorWithingScale = (value: number, colorScale: ColorScale) => {
|
|
58
|
+
if (colorScale.min === colorScale.max) {
|
|
59
|
+
return singleGraphColorRGBByName(colorScale.color, 0);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const colorRange = colorScale.max - colorScale.min;
|
|
63
|
+
|
|
64
|
+
const alpha = (value - colorScale.min) / colorRange;
|
|
65
|
+
|
|
66
|
+
return singleGraphColorRGBByName(colorScale.color, alpha);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const getTextColorForScale = (value: number, colorScale: ColorScale) => {
|
|
70
|
+
if (colorScale.min === colorScale.max) {
|
|
71
|
+
return 'black';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const colorRange = colorScale.max - colorScale.min;
|
|
75
|
+
|
|
76
|
+
const alpha = (value - colorScale.min) / colorRange;
|
|
77
|
+
|
|
78
|
+
return alpha <= 0.5 ? 'black' : 'white';
|
|
79
|
+
};
|
|
@@ -7,9 +7,6 @@ const meta: Meta<InfoProps> = {
|
|
|
7
7
|
title: 'Component/Info',
|
|
8
8
|
component: Info,
|
|
9
9
|
parameters: { fetchMock: {} },
|
|
10
|
-
args: {
|
|
11
|
-
height: '100px',
|
|
12
|
-
},
|
|
13
10
|
};
|
|
14
11
|
|
|
15
12
|
export default meta;
|
|
@@ -24,20 +21,50 @@ export const InfoStory: StoryObj<InfoProps> = {
|
|
|
24
21
|
),
|
|
25
22
|
};
|
|
26
23
|
|
|
27
|
-
export const
|
|
24
|
+
export const OpenInfo: StoryObj<InfoProps> = {
|
|
28
25
|
...InfoStory,
|
|
29
26
|
play: async ({ canvasElement }) => {
|
|
30
27
|
const canvas = within(canvasElement);
|
|
31
|
-
const openInfo = canvas.getByRole('button', { name: '?' });
|
|
32
28
|
|
|
33
|
-
await
|
|
29
|
+
await openInfo(canvas);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const ShowsAndClosesInfoOnClick: StoryObj<InfoProps> = {
|
|
34
|
+
...InfoStory,
|
|
35
|
+
play: async ({ canvasElement, step }) => {
|
|
36
|
+
const canvas = within(canvasElement);
|
|
34
37
|
|
|
35
|
-
await
|
|
38
|
+
await openInfo(canvas);
|
|
36
39
|
|
|
37
|
-
await
|
|
40
|
+
await step('Close the info dialog by clicking the close button', async () => {
|
|
41
|
+
const dialog = await waitFor(() => canvas.getByRole('dialog'));
|
|
42
|
+
const closeButton = within(dialog).getByRole('button', { name: 'Close' });
|
|
43
|
+
closeButton.click();
|
|
44
|
+
await waitFor(() => expect(canvas.queryByText(tooltipText, { exact: false })).not.toBeVisible());
|
|
45
|
+
});
|
|
38
46
|
|
|
39
|
-
await
|
|
47
|
+
await openInfo(canvas);
|
|
48
|
+
await step('Close the info dialog by clicking the x button', async () => {
|
|
49
|
+
const dialog = await waitFor(() => canvas.getByRole('dialog'));
|
|
50
|
+
const xButton = within(dialog).getByRole('button', { name: '✕' });
|
|
51
|
+
xButton.click();
|
|
52
|
+
await waitFor(() => expect(canvas.queryByText(tooltipText, { exact: false })).not.toBeVisible());
|
|
53
|
+
});
|
|
40
54
|
|
|
41
|
-
await
|
|
55
|
+
await openInfo(canvas);
|
|
56
|
+
await step('Close the info dialog by clicking outside', async () => {
|
|
57
|
+
const dialog = await waitFor(() => canvas.getByRole('dialog'));
|
|
58
|
+
const xButton = within(dialog).getByRole('button', { name: 'Helper to close when clicked outside' });
|
|
59
|
+
xButton.click();
|
|
60
|
+
await waitFor(() => expect(canvas.queryByText(tooltipText, { exact: false })).not.toBeVisible());
|
|
61
|
+
});
|
|
42
62
|
},
|
|
43
63
|
};
|
|
64
|
+
|
|
65
|
+
const openInfo = async (canvas: ReturnType<typeof within>) => {
|
|
66
|
+
const openInfo = canvas.getByRole('button', { name: '?' });
|
|
67
|
+
await waitFor(() => expect(openInfo).toBeInTheDocument());
|
|
68
|
+
await userEvent.click(openInfo);
|
|
69
|
+
await waitFor(() => expect(canvas.getByText(tooltipText, { exact: false })).toBeVisible());
|
|
70
|
+
};
|
|
@@ -1,57 +1,36 @@
|
|
|
1
|
-
import { offset, shift, size } from '@floating-ui/dom';
|
|
2
1
|
import { type FunctionComponent } from 'preact';
|
|
3
|
-
import { useRef
|
|
2
|
+
import { useRef } from 'preact/hooks';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
import { useCloseOnClickOutside, useCloseOnEsc, useFloatingUi } from '../shared/floating-ui/hooks';
|
|
4
|
+
export interface InfoProps {}
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const Info: FunctionComponent<InfoProps> = ({ children, height }) => {
|
|
13
|
-
const [showHelp, setShowHelp] = useState(false);
|
|
14
|
-
const referenceRef = useRef<HTMLButtonElement>(null);
|
|
15
|
-
const floatingRef = useRef<HTMLDivElement>(null);
|
|
16
|
-
|
|
17
|
-
useFloatingUi(referenceRef, floatingRef, [
|
|
18
|
-
offset(10),
|
|
19
|
-
shift(),
|
|
20
|
-
size({
|
|
21
|
-
apply() {
|
|
22
|
-
if (!floatingRef.current) {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
floatingRef.current.style.width = '100vw';
|
|
26
|
-
floatingRef.current.style.height = height ? height : '50vh';
|
|
27
|
-
},
|
|
28
|
-
}),
|
|
29
|
-
]);
|
|
6
|
+
const Info: FunctionComponent<InfoProps> = ({ children }) => {
|
|
7
|
+
const dialogRef = useRef<HTMLDialogElement>(null);
|
|
30
8
|
|
|
31
9
|
const toggleHelp = () => {
|
|
32
|
-
|
|
10
|
+
dialogRef.current?.showModal();
|
|
33
11
|
};
|
|
34
12
|
|
|
35
|
-
useCloseOnEsc(setShowHelp);
|
|
36
|
-
useCloseOnClickOutside(floatingRef, referenceRef, setShowHelp);
|
|
37
|
-
|
|
38
13
|
return (
|
|
39
14
|
<div className='relative'>
|
|
40
|
-
<button type='button' className='btn btn-xs' onClick={toggleHelp}
|
|
15
|
+
<button type='button' className='btn btn-xs' onClick={toggleHelp}>
|
|
41
16
|
?
|
|
42
17
|
</button>
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
18
|
+
<dialog ref={dialogRef} className={'modal modal-bottom sm:modal-middle'}>
|
|
19
|
+
<div className='modal-box'>
|
|
20
|
+
<form method='dialog'>
|
|
21
|
+
<button className='btn btn-sm btn-circle btn-ghost absolute right-2 top-2'>✕</button>
|
|
22
|
+
</form>
|
|
23
|
+
<div className={'flex flex-col'}>{children}</div>
|
|
24
|
+
<div className='modal-action'>
|
|
25
|
+
<form method='dialog'>
|
|
26
|
+
<button className={'float-right underline text-sm hover:text-blue-700 mr-2'}>Close</button>
|
|
27
|
+
</form>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<form method='dialog' className='modal-backdrop'>
|
|
31
|
+
<button>Helper to close when clicked outside</button>
|
|
32
|
+
</form>
|
|
33
|
+
</dialog>
|
|
55
34
|
</div>
|
|
56
35
|
);
|
|
57
36
|
};
|
|
@@ -7,6 +7,15 @@ const meta: Meta<TooltipProps> = {
|
|
|
7
7
|
title: 'Component/Tooltip',
|
|
8
8
|
component: Tooltip,
|
|
9
9
|
parameters: { fetchMock: {} },
|
|
10
|
+
argTypes: {
|
|
11
|
+
content: { control: 'text' },
|
|
12
|
+
position: {
|
|
13
|
+
control: {
|
|
14
|
+
type: 'radio',
|
|
15
|
+
},
|
|
16
|
+
options: ['top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'left', 'right'],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
10
19
|
};
|
|
11
20
|
|
|
12
21
|
export default meta;
|
|
@@ -17,12 +26,13 @@ export const TooltipStory: StoryObj<TooltipProps> = {
|
|
|
17
26
|
render: (args) => (
|
|
18
27
|
<div class='flex justify-center px-4 py-16'>
|
|
19
28
|
<Tooltip {...args}>
|
|
20
|
-
<div>Hover me</div>
|
|
29
|
+
<div className='bg-red-200'>Hover me</div>
|
|
21
30
|
</Tooltip>
|
|
22
31
|
</div>
|
|
23
32
|
),
|
|
24
33
|
args: {
|
|
25
34
|
content: tooltipContent,
|
|
35
|
+
position: 'bottom',
|
|
26
36
|
},
|
|
27
37
|
};
|
|
28
38
|
|
|
@@ -38,7 +48,7 @@ export const RendersStringContent: StoryObj<TooltipProps> = {
|
|
|
38
48
|
},
|
|
39
49
|
};
|
|
40
50
|
|
|
41
|
-
export const
|
|
51
|
+
export const RendersComponentContent: StoryObj<TooltipProps> = {
|
|
42
52
|
...TooltipStory,
|
|
43
53
|
args: {
|
|
44
54
|
content: <div>{tooltipContent}</div>,
|