[UI] Full restyle of Slider component.

The component has been rewritten using only CSS 3 and no JS.
This commit is contained in:
Fabio Manganiello 2024-01-08 02:27:36 +01:00
parent b785609eda
commit 80c2f0d8dd
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
10 changed files with 128 additions and 96 deletions

View file

@ -67,7 +67,7 @@
<div class="col-9"> <div class="col-9">
<Slider :range="colorConverter.ranges.hue" :disabled="loading" <Slider :range="colorConverter.ranges.hue" :disabled="loading"
:value="animations.color_transition.hue_step" :value="animations.color_transition.hue_step"
@mouseup="animations.color_transition.hue_step = parseFloat($event.target.value)" /> @change="animations.color_transition.hue_step = parseFloat($event.target.value)" />
</div> </div>
</div> </div>
@ -78,7 +78,7 @@
<div class="col-9"> <div class="col-9">
<Slider :range="colorConverter.ranges.sat" :disabled="loading" <Slider :range="colorConverter.ranges.sat" :disabled="loading"
:value="animations.color_transition.sat_step" :value="animations.color_transition.sat_step"
@mouseup="animations.color_transition.sat_step = parseFloat($event.target.value)" /> @change="animations.color_transition.sat_step = parseFloat($event.target.value)" />
</div> </div>
</div> </div>
@ -89,7 +89,7 @@
<div class="col-9"> <div class="col-9">
<Slider :range="colorConverter.ranges.bri" :disabled="loading" <Slider :range="colorConverter.ranges.bri" :disabled="loading"
:value="animations.color_transition.bri_step" :value="animations.color_transition.bri_step"
@mouseup="animations.color_transition.bri_step = parseFloat($event.target.value)" /> @change="animations.color_transition.bri_step = parseFloat($event.target.value)" />
</div> </div>
</div> </div>

View file

@ -8,7 +8,7 @@
</div> </div>
<div class="col-11 control"> <div class="col-11 control">
<Slider :range="colorConverter.ranges.bri" :disabled="loading" :value="state.bri" <Slider :range="colorConverter.ranges.bri" :disabled="loading" :value="state.bri"
@mouseup.stop="$emit(light ? 'set-light' : 'set-group', {brightness: parseInt($event.target.value)})" /> @change.stop="$emit(light ? 'set-light' : 'set-group', {brightness: parseInt($event.target.value)})" />
</div> </div>
</div> </div>
@ -18,7 +18,7 @@
</div> </div>
<div class="col-11 control"> <div class="col-11 control">
<Slider :range="colorConverter.ranges.ct" :disabled="loading" :value="state.ct" <Slider :range="colorConverter.ranges.ct" :disabled="loading" :value="state.ct"
@mouseup.stop="$emit(light ? 'set-light' : 'set-group', {temperature: parseInt($event.target.value)})" /> @change.stop="$emit(light ? 'set-light' : 'set-group', {temperature: parseInt($event.target.value)})" />
</div> </div>
</div> </div>

View file

@ -6,7 +6,7 @@
</div> </div>
<div class="col-s-8 col-m-10 time-bar"> <div class="col-s-8 col-m-10 time-bar">
<Slider :value="elapsed" :range="[0, duration]" :disabled="!duration || status.state === 'stop'" <Slider :value="elapsed" :range="[0, duration]" :disabled="!duration || status.state === 'stop'"
@input="$emit('seek', $event.target.value)" /> @change="$emit('seek', $event.target.value)" />
</div> </div>
<div class="col-s-2 col-m-1 time"> <div class="col-s-2 col-m-1 time">
<span class="total-time" <span class="total-time"

View file

@ -12,7 +12,7 @@
<div class="col-11 volume-slider"> <div class="col-11 volume-slider">
<Slider :value="status.volume" :range="volumeRange" :disabled="status.volume == null" <Slider :value="status.volume" :range="volumeRange" :disabled="status.volume == null"
@input="$emit('set-volume', $event.target.value)" /> @change="$emit('set-volume', $event.target.value)" />
</div> </div>
</div> </div>
</template> </template>

View file

@ -15,13 +15,9 @@
:disabled="disabled" :disabled="disabled"
:value="value" :value="value"
ref="range" ref="range"
@input.stop="onUpdate" @input.stop="$emit('input', $event)"
@change.stop="onUpdate"> @change.stop="$emit('change', $event)">
<div class="track" :class="{'with-label': withLabel}">
<div class="track-inner" ref="track"></div>
</div>
<div class="thumb" ref="thumb"></div>
<span class="label" v-if="withLabel" v-text="value" ref="label"></span> <span class="label" v-if="withLabel" v-text="value" ref="label"></span>
</span> </span>
</label> </label>
@ -29,8 +25,7 @@
<script> <script>
export default { export default {
name: "Slider", emits: ['input', 'change'],
emits: ['input', 'change', 'mouseup', 'mousedown', 'touchstart', 'touchend', 'keyup', 'keydown'],
props: { props: {
value: { value: {
type: Number, type: Number,
@ -61,41 +56,13 @@ export default {
default: false, default: false,
} }
}, },
methods: {
onUpdate(event) {
this.update(event.target.value)
this.$emit(event.type, {
...event,
target: {
...event.target,
value: this.$refs.range.value,
}
})
},
update(value) {
const sliderWidth = this.$refs.range.clientWidth
const percent = (value - this.range[0]) / (this.range[1] - this.range[0])
const innerWidth = percent * sliderWidth
const thumb = this.$refs.thumb
thumb.style.left = `${innerWidth - thumb.clientWidth / 2}px`
this.$refs.thumb.style.transform = `translate(-${percent}%, -50%)`
this.$refs.track.style.width = `${innerWidth}px`
},
},
mounted() {
if (this.value != null)
this.update(this.value)
this.$watch(() => this.value, (newValue) => this.update(newValue))
},
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
$label-width: 3em; $label-width: 3em;
$thumb-height: 1em;
$slider-height: 0.5em;
.slider-wrapper { .slider-wrapper {
width: 100%; width: 100%;
@ -109,13 +76,114 @@ $label-width: 3em;
position: relative; position: relative;
} }
.slider { input.slider {
width: 100%; width: 100%;
background: none;
height: 1.5em;
position: relative;
border-radius: 0.5em;
cursor: pointer; cursor: pointer;
opacity: 0; outline: none;
overflow: hidden;
transition: all ease 100ms;
@include appearance(none);
&::-ms-tooltip { &:active {
display: none; filter: brightness(80%);
cursor: grabbing;
}
&:hover {
filter: saturate(130%);
}
&:disabled {
cursor: not-allowed;
opacity: 0.5;
filter: grayscale(1);
}
/* Chrome and friends */
&::-webkit-slider-runnable-track {
position: relative;
border-radius: $slider-height;
background: linear-gradient($slider-bg 0 0) scroll no-repeat center /
100% calc(#{$slider-height} + 1px);
}
&::-webkit-slider-runnable-track,
&::-webkit-slider-thumb {
-webkit-appearance: none;
transition: all ease 100ms;
height: $thumb-height;
}
&::-webkit-slider-thumb {
--clip-top: calc((#{$thumb-height} - #{$slider-height}) * 0.5);
--clip-bottom: calc(#{$thumb-height} - var(--clip-top));
--clip-further: calc(100% + 1px);
width: $thumb-height;
background: $slider-progress-bg;
box-shadow: calc(-100vmax - #{$thumb-height} + 2.5px) #{$slider-height} #{$slider-height} 100vmax #{$slider-progress-bg};
border-radius: $thumb-height;
cursor: grab;
&:hover {
filter: brightness(130%) blur(1px);
cursor: grab;
}
clip-path: polygon(
100% -1px,
#{$slider-height} -1px,
0 var(--clip-top),
-100vmax var(--clip-top),
-100vmax var(--clip-bottom),
0 var(--clip-bottom),
#{$slider-height} 100%,
var(--clip-further) var(--clip-further)
);
}
/* Firefox */
&::-moz-range-track {
background: $slider-bg;
position: relative;
height: $slider-height;
border-radius: 0.5em;
box-shadow: inset 1px 0px 3px 0 $slider-track-shadow;
}
&::-moz-range-thumb {
$thumb-height: 1.125em;
width: $thumb-height;
height: $thumb-height;
position: relative;
background: $slider-thumb-bg;
border-radius: 50%;
border: none;
cursor: grabbing;
transition: all ease 100ms;
@include appearance(none);
&:hover {
filter: brightness(130%) blur(1px);
cursor: grab;
}
&:disabled {
background: $slider-thumb-disabled-bg;
cursor: not-allowed;
}
}
&::-moz-range-progress {
width: 100%;
height: $slider-height;
cursor: pointer;
background: $slider-progress-bg;
border-radius: 0.5em 0 0 0.5em;
} }
} }
@ -123,10 +191,6 @@ $label-width: 3em;
width: 100%; width: 100%;
display: flex; display: flex;
&.with-label {
width: calc(100% - $label-width);
}
.left { .left {
text-align: left; text-align: left;
} }
@ -137,47 +201,15 @@ $label-width: 3em;
} }
} }
.track {
width: 100%;
height: 0.75em;
background: $slider-bg;
position: absolute;
top: 50%;
transform: translateY(-50%);
border-radius: 0.5em;
box-shadow: inset 1px 0px 3px 0 $slider-track-shadow;
pointer-events: none;
.track-inner {
width: 0;
height: 100%;
background: $slider-progress-bg;
border-radius: 0.5em 0 0 0.5em;
}
&.with-label {
width: calc(100% - $label-width);
}
}
.thumb {
width: 1.25em;
height: 1.25em;
background: $slider-thumb-bg;
position: absolute;
top: 50%;
left: 0;
transform: translate(0%, -50%);
border-radius: 50%;
box-shadow: 1px 0px 2px 0 $slider-thumb-shadow;
pointer-events: none;
}
.label { .label {
width: $label-width; width: $label-width;
position: relative; position: relative;
font-weight: normal; font-weight: normal;
text-align: center; text-align: center;
} }
.with-label {
width: calc(100% - $label-width);
}
} }
</style> </style>

View file

@ -148,7 +148,7 @@
<div class="value"> <div class="value">
<Slider :value="audioVolume" :range="[0, 100]" <Slider :value="audioVolume" :range="[0, 100]"
@input="onVolumeChange" /> @change="onVolumeChange" />
</div> </div>
</div> </div>

View file

@ -25,7 +25,7 @@
<div class="input" v-if="value?.min != null && value?.max != null"> <div class="input" v-if="value?.min != null && value?.max != null">
<div class="col-10"> <div class="col-10">
<Slider :range="[value.min, value.max]" with-range <Slider :range="[value.min, value.max]" with-range
:value="value.value" @input="setValue" /> :value="value.value" @change="setValue" />
</div> </div>
<div class="col-2 value"> <div class="col-2 value">
<input type="number" :value="value.value" @change="setValue"> <input type="number" :value="value.value" @change="setValue">

View file

@ -41,7 +41,7 @@
</div> </div>
<div class="input"> <div class="input">
<Slider :range="[value.brightness_min, value.brightness_max]" <Slider :range="[value.brightness_min, value.brightness_max]"
:value="value.brightness" @input="setLight({brightness: $event.target.value})" /> :value="value.brightness" @change="setLight({brightness: $event.target.value})" />
</div> </div>
</div> </div>
@ -51,7 +51,7 @@
</div> </div>
<div class="input"> <div class="input">
<Slider :range="[value.saturation_min, value.saturation_max]" <Slider :range="[value.saturation_min, value.saturation_max]"
:value="value.saturation" @input="setLight({saturation: $event.target.value})" /> :value="value.saturation" @change="setLight({saturation: $event.target.value})" />
</div> </div>
</div> </div>
@ -61,7 +61,7 @@
</div> </div>
<div class="input"> <div class="input">
<Slider :range="[value.temperature_min, value.temperature_max]" <Slider :range="[value.temperature_min, value.temperature_max]"
:value="value.temperature" @input="setLight({temperature: $event.target.value})"/> :value="value.temperature" @change="setLight({temperature: $event.target.value})"/>
</div> </div>
</div> </div>
</div> </div>

View file

@ -7,7 +7,7 @@
<div class="col-s-12 col-m-9 controls"> <div class="col-s-12 col-m-9 controls">
<div class="col-10 slider-container"> <div class="col-10 slider-container">
<Slider :range="[0, 100]" :value="config.volume.percent" <Slider :range="[0, 100]" :value="config.volume.percent"
@mouseup="$emit('volume-change', {host: server.name, client: id, volume: $event.target.value})" /> @change="$emit('volume-change', {host: server.name, client: id, volume: $event.target.value})" />
</div> </div>
<div class="col-2 switch pull-right"> <div class="col-2 switch pull-right">

View file

@ -7,7 +7,7 @@
<div :class="{'col-6': hasIcon, 'col-7': !hasIcon}" v-text="name" /> <div :class="{'col-6': hasIcon, 'col-7': !hasIcon}" v-text="name" />
<div class="col-5 slider-container"> <div class="col-5 slider-container">
<div class="slider"> <div class="slider">
<SliderElement :value="value" :range="[parseFloat(min), parseFloat(max)]" @mouseup="run" /> <SliderElement :value="value" :range="[parseFloat(min), parseFloat(max)]" @change="run" />
</div> </div>
</div> </div>
</div> </div>