Skip to content

Commit ea5a754

Browse files
authored
Merge pull request #33 from graphieros/slider-improvements
Slider improvements
2 parents f9de19d + cc819c4 commit ea5a754

12 files changed

+176
-115
lines changed

TestingArena/ArenaVueUiCandlestick.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const model = ref([
7171
{ key: 'style.layout.candle.widthRatio', def: 0.5, type: 'number', min: 0.1, max: 1, step: 0.1},
7272
{ key: 'style.zoom.show', def: true, type: 'checkbox'},
7373
{ key: 'style.zoom.color', def: '#CCCCCC', type: 'color'},
74+
{ key: 'style.zoom.highlightColor', def: '#4A4A4A', type: 'color' },
7475
{ key: 'style.zoom.fontSize', def: 14, type: 'number', min: 8, max: 42},
7576
{ key: 'style.zoom.useResetSlot', def: false, type: 'checkbox'},
7677
{ key: 'style.title.text', def: 'At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis', type: 'text'},

TestingArena/ArenaVueUiDonutEvolution.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ const model = ref([
9595
{ key: 'style.chart.zoom.show', def: true, type: 'checkbox'},
9696
{ key: 'style.chart.zoom.fontSize', def: 14, type: 'number', min: 8, max: 48},
9797
{ key: 'style.chart.zoom.color', def: '#CCCCCC', type: 'color'},
98+
{ key: 'style.chart.zoom.highlightColor', def: "#1A1A1A", type: 'color' },
9899
{ key: 'style.chart.zoom.useResetSlot', def: false, type: 'checkbox'}
99100
]);
100101

TestingArena/ArenaVueUiQuickChart.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ const model = ref([
107107
{ key: 'yAxisLabel', def: 'Lorem ipsum Y axis labellum'},
108108
{ key: 'zoomXy', def: true, type: 'checkbox'},
109109
{ key: 'zoomColor', def: '#CCCCCC', type: 'color'},
110+
{ key: 'zoomHighlightColor', def: '#1A1A1A', type: 'color'},
110111
{ key: 'zoomFontSize', def: 14, type: 'number', min: 8, max: 48},
111112
{ key: 'zoomUseResetSlot', def: false, type: 'checkbox'}
112113

TestingArena/ArenaVueUiXy.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const model = ref([
5353
{ key: 'chart.width', def: 1000, type: 'range', min: 300, max: 2000, label: 'width', category: 'general' },
5454
{ key: 'chart.zoom.show', def: true, type: 'checkbox', label: 'zoom', category: 'general' },
5555
{ key: 'chart.zoom.color', def: '#CCCCCC', type: 'color' },
56+
{ key: 'chart.zoom.highlightColor', def: '#4A4A4A', type: 'color' },
5657
{ key: 'chart.zoom.fontSize', def: 14, type: 'number', min: 8, max: 42},
5758
{ key: 'chart.zoom.useResetSlot', def: false, type: 'checkbox'},
5859

src/atoms/Slicer.vue

Lines changed: 155 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup>
2-
import { computed, ref } from "vue";
3-
import BaseIcon from "./BaseIcon.vue";
2+
import { ref, computed, watch } from 'vue';
3+
import BaseIcon from './BaseIcon.vue';
44
55
const props = defineProps({
66
background: {
@@ -9,7 +9,7 @@ const props = defineProps({
99
},
1010
fontSize: {
1111
type: Number,
12-
default: 14,
12+
default: 14
1313
},
1414
labelLeft: {
1515
type: String,
@@ -35,166 +35,208 @@ const props = defineProps({
3535
type: Number,
3636
default: 0
3737
},
38+
selectColor: {
39+
type: String,
40+
default: '#4A4A4A'
41+
},
3842
useResetSlot: {
3943
type: Boolean,
4044
default: false
4145
},
4246
valueStart: {
4347
type: [Number, String],
44-
default: 0,
48+
default: 0
4549
},
4650
valueEnd: {
4751
type: [Number, String],
4852
default: 0
4953
}
5054
});
5155
52-
const emit = defineEmits([
53-
'update:start',
54-
'update:end',
55-
'reset'
56-
]);
56+
const startValue = ref(props.min);
57+
const endValue = ref(props.max);
5758
58-
const REF_SLICER = ref(null);
59-
const REF_START = ref(null);
60-
const REF_END = ref(null);
59+
const emit = defineEmits(['update:start', 'update:end', 'reset']);
60+
61+
const highlightStyle = computed(() => {
62+
const range = props.max - props.min;
63+
const startPercent = ((startValue.value - props.min) / range) * 100;
64+
const endPercent = ((endValue.value - props.min) / range) * 100;
65+
return {
66+
left: `calc(${startPercent}% + 5px)`,
67+
width: `${endPercent - startPercent - 1}%`,
68+
background: props.selectColor
69+
};
70+
});
6171
6272
const slicerColor = computed(() => props.inputColor);
6373
const backgroundColor = computed(() => props.background);
74+
const selectColorOpaque = computed(() => `${props.selectColor}33`)
6475
6576
function reset() {
66-
emit('reset')
77+
emit('reset');
6778
}
6879
80+
function onStartInput() {
81+
if (Number(startValue.value) > Number(endValue.value) - 1) {
82+
startValue.value = Number(endValue.value) - 1;
83+
}
84+
emit('update:start', Number(startValue.value));
85+
}
86+
87+
function onEndInput() {
88+
if (Number(endValue.value) < Number(startValue.value) + 1) {
89+
endValue.value = Number(startValue.value) + 1;
90+
}
91+
emit('update:end', Number(endValue.value));
92+
}
93+
94+
watch(
95+
() => props.min,
96+
(newMin) => {
97+
if (Number(startValue.value) < Number(newMin)) {
98+
startValue.value = Number(newMin);
99+
}
100+
if (Number(endValue.value) < Number(newMin)) {
101+
endValue.value = Number(newMin);
102+
}
103+
}
104+
);
105+
106+
watch(
107+
() => props.max,
108+
(newMax) => {
109+
if (Number(startValue.value) > Number(newMax)) {
110+
startValue.value = Number(newMax);
111+
}
112+
if (Number(endValue.value) > Number(newMax)) {
113+
endValue.value = Number(newMax);
114+
}
115+
}
116+
);
69117
</script>
70118

71119
<template>
72-
<div class="vue-data-ui-slicer" data-html2canvas-ignore>
120+
<div>
73121
<div class="vue-data-ui-slicer-labels">
74-
<div class="vue-data-ui-slicer-label-left" :style="`font-size:${props.fontSize}px;color:${props.textColor}`">
122+
<div class="vue-data-ui-slicer-label-left"
123+
:style="{ fontSize: `${props.fontSize}px`, color: props.textColor }">
75124
{{ labelLeft }}
76125
</div>
77126
<div v-if="valueStart > 0 || valueEnd < max">
78-
<button v-if="!useResetSlot" data-cy-reset tabindex="0" role="button" class="vue-data-ui-refresh-button" @click="reset()">
79-
<BaseIcon name="refresh" :stroke="textColor"/>
127+
<button v-if="!useResetSlot" data-cy-reset tabindex="0" role="button" class="vue-data-ui-refresh-button"
128+
@click="reset">
129+
<BaseIcon name="refresh" :stroke="textColor" />
80130
</button>
81-
<slot v-else name="reset-action" v-bind="{ reset }"/>
131+
<slot v-else name="reset-action" :reset="reset" />
82132
</div>
83-
<div class="vue-data-ui-slicer-label-right" :style="`font-size:${props.fontSize}px;color:${props.textColor}`">
133+
<div class="vue-data-ui-slicer-label-right"
134+
:style="{ fontSize: `${props.fontSize}px`, color: props.textColor }">
84135
{{ labelRight }}
85136
</div>
86137
</div>
87-
<div class="vue-data-ui-slicer-knobs">
88-
<div ref="REF_SLICER" class="vue-data-ui-slicer-track"/>
89-
<input
90-
ref="REF_START"
91-
type="range"
92-
:value="valueStart"
93-
:style="`border:none !important;accent-color:${slicerColor}`"
94-
:min="min"
95-
:max="max"
96-
@input="emit('update:start', $event.target.value)"
97-
>
98-
<input
99-
ref="REF_END"
100-
type="range"
101-
:value="valueEnd"
102-
:style="`border:none !important;accent-color:${slicerColor}`"
103-
:min="min"
104-
:max="max"
105-
@input="emit('update:end', $event.target.value)"
106-
>
138+
<div class="double-range-slider">
139+
<div class="slider-track"></div>
140+
<div class="range-highlight" :style="highlightStyle"></div>
141+
<input type="range" :min="min" :max="max" v-model="startValue" @input="onStartInput" />
142+
<input type="range" :min="min" :max="max" v-model="endValue" @input="onEndInput" />
107143
</div>
108144
</div>
109145
</template>
110146

111-
<style scoped lang="scss">
112-
.vue-data-ui-slicer {
113-
position: relative;
114-
display: flex;
115-
flex-direction: column;
116-
gap: 12px;
117-
padding: 0 24px;
118-
margin-bottom: 24px;
119-
}
120147

121-
.vue-data-ui-slicer-knobs {
122-
position: relative;
148+
<style scoped lang="scss">
149+
.double-range-slider {
150+
position: relative !important;
123151
width: calc(100% - 48px);
152+
height: 40px;
124153
margin: 0 auto;
125-
height: 12px;
126154
}
127155
128-
input[type="range"]{
129-
-webkit-appearance: none;
130-
-moz-appearance: none;
131-
appearance: none;
132-
width: 100%;
133-
outline: none;
156+
input[type="range"] {
134157
position: absolute;
135-
margin: auto;
136-
top: 0;
137-
bottom: 0;
138158
left: 0;
139-
background-color: transparent;
140-
pointer-events: none;
141-
}
142-
143-
.vue-data-ui-slicer-track {
144159
width: 100%;
145-
height: 5px;
146-
position: absolute;
147-
margin: auto;
148-
top: 0;
149-
bottom: 0;
150-
border-radius: 5px;
151-
background: v-bind(slicerColor);
152-
}
153-
input[type="range"]::-webkit-slider-runnable-track{
154-
-webkit-appearance: none;
155-
height: 5px;
156-
}
157-
input[type="range"]::-moz-range-track{
158-
-moz-appearance: none;
159-
height: 5px;
160-
}
161-
input[type="range"]::-ms-track{
162160
appearance: none;
163-
height: 5px;
161+
background: transparent;
162+
pointer-events: none;
163+
z-index: 3;
164164
}
165-
input[type="range"]::-webkit-slider-thumb{
165+
166+
input[type="range"]::-webkit-slider-thumb {
166167
-webkit-appearance: none;
167-
height: 1.3em;
168-
width: 1.3em;
168+
pointer-events: auto;
169+
width: 20px;
170+
height: 20px;
169171
background-color: v-bind(slicerColor);
172+
border-radius: 50%;
170173
cursor: pointer;
171-
margin-top: -6px;
174+
position: relative;
175+
z-index: 2;
176+
outline: 2px solid v-bind(backgroundColor);
177+
transition: all 0.2s ease-in-out;
178+
&:active,
179+
&:hover {
180+
box-shadow: 0 0 0 10px v-bind(selectColorOpaque);
181+
background-color: v-bind(selectColor);
182+
}
183+
}
184+
185+
input[type="range"]::-moz-range-thumb {
172186
pointer-events: auto;
187+
width: 20px;
188+
height: 20px;
189+
background-color: v-bind(slicerColor);
173190
border-radius: 50%;
174-
border: 1px solid v-bind(backgroundColor);
175-
}
176-
input[type="range"]::-moz-range-thumb{
177-
-webkit-appearance: none;
178-
appearance: none;
179-
height: 1.3em;
180-
width: 1.3em;
181191
cursor: pointer;
182-
border-radius: 50%;
183-
background-color: v-bind(slicerColor);
184-
pointer-events: auto;
192+
position: relative;
193+
z-index: 2;
194+
outline: 2px solid v-bind(backgroundColor);
195+
transition: all 0.2s ease-in-out;
196+
&:active,
197+
&:hover {
198+
box-shadow: 0 0 0 10px v-bind(selectColorOpaque);
199+
background-color: v-bind(selectColor);
200+
}
185201
}
186-
input[type="range"]::-ms-thumb{
187-
appearance: none;
188-
height: 1.3em;
189-
width: 1.3em;
190-
cursor: pointer;
191-
border-radius: 50%;
192-
background-color: v-bind(slicerColor);
202+
203+
input[type="range"]::-ms-thumb {
193204
pointer-events: auto;
194-
}
195-
input[type="range"]:active::-webkit-slider-thumb{
205+
width: 20px;
206+
height: 20px;
196207
background-color: v-bind(slicerColor);
197-
border: 2px solid v-bind(backgroundColor);
208+
border-radius: 50%;
209+
cursor: pointer;
210+
position: relative;
211+
z-index: 2;
212+
outline: 2px solid v-bind(backgroundColor);
213+
transition: all 0.2s ease-in-out;
214+
&:active,
215+
&:hover {
216+
box-shadow: 0 0 0 10px v-bind(selectColorOpaque);
217+
background-color: v-bind(selectColor);
218+
}
219+
}
220+
221+
.slider-track {
222+
position: absolute;
223+
width: 99%;
224+
height: 8px;
225+
border-radius: 4px;
226+
background: #ddd;
227+
top: 8px;
228+
z-index: 1;
229+
left: 50%;
230+
transform: translateX(-50%);
231+
-webkit-transform: translateX(-50%);
232+
}
233+
234+
.range-highlight {
235+
position: absolute;
236+
height: 8px;
237+
top: 8px;
238+
z-index: 1;
239+
border-radius: 4px;
198240
}
199241
200242
.vue-data-ui-refresh-button {
@@ -204,11 +246,12 @@ input[type="range"]:active::-webkit-slider-thumb{
204246
height: 36px;
205247
width: 36px;
206248
display: flex;
207-
align-items:center;
208-
justify-content:center;
249+
align-items: center;
250+
justify-content: center;
209251
border-radius: 50%;
210252
cursor: pointer;
211253
transition: transform 0.2s ease-in-out;
254+
transform-origin: center;
212255
&:focus {
213256
outline: 1px solid v-bind(slicerColor);
214257
}
@@ -220,7 +263,7 @@ input[type="range"]:active::-webkit-slider-thumb{
220263
.vue-data-ui-slicer-labels {
221264
display: flex;
222265
flex-direction: row;
223-
align-items:center;
266+
align-items: center;
224267
padding: 0 24px;
225268
height: 40px;
226269
}
@@ -229,9 +272,11 @@ input[type="range"]:active::-webkit-slider-thumb{
229272
.vue-data-ui-slicer-label-right {
230273
width: 100%;
231274
}
275+
232276
.vue-data-ui-slicer-label-left {
233277
text-align: left;
234278
}
279+
235280
.vue-data-ui-slicer-label-right {
236281
text-align: right;
237282
}

0 commit comments

Comments
 (0)