Slider.vue
2.94 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
<script setup>
/**
* Slider —— 受控滑块(v-model)。用于"拖到某个目标值"这类连续取值,
* 离散几选一仍然用 Select / SegmentedControl。
*
* 右侧的数值由页面用 `#value` 插槽给:滑块只认数字,"320 名 / 70%" 这种带单位和
* 换算的措辞归页面,`@drama/ui` 不认识业务也不认识 i18n(和 Pagination 的 summary 同一个道理)。
*/
defineProps({
/** 受控值。 */
modelValue: { type: Number, required: true },
min: { type: Number, default: 0 },
max: { type: Number, default: 100 },
step: { type: Number, default: 1 },
disabled: { type: Boolean, default: false },
})
/**
* 两个事件是分工的:拖动过程中一路发 `update:modelValue`(标签跟着走),
* 松手才发一次 `change`。要发请求的调用方监听 `change` —— 挂在 `update:modelValue` 上
* 会让拖一次滑块打出几十个请求。
*/
const emit = defineEmits(['update:modelValue', 'change'])
/**
* @param {Event} e
* @returns {void}
*/
function onInput(e) {
emit('update:modelValue', Number(/** @type {HTMLInputElement} */ (e.target).value))
}
/**
* @param {Event} e
* @returns {void}
*/
function onChange(e) {
emit('change', Number(/** @type {HTMLInputElement} */ (e.target).value))
}
</script>
<template>
<div class="drama-slider">
<input
class="drama-slider__input"
type="range"
:min="min"
:max="max"
:step="step"
:value="modelValue"
:disabled="disabled"
@input="onInput"
@change="onChange"
>
<span
v-if="$slots.value"
class="drama-slider__value"
><slot name="value" /></span>
</div>
</template>
<style scoped>
.drama-slider {
display: flex;
align-items: center;
gap: var(--drama-space-md);
height: var(--drama-control-height-sm);
}
.drama-slider__input {
flex: 1;
min-width: 96px;
appearance: none;
height: 4px;
border-radius: 2px;
background: var(--drama-color-border);
outline: none;
cursor: pointer;
}
.drama-slider__input:disabled {
opacity: 0.55;
cursor: not-allowed;
}
/* 两套厂商前缀写不进同一条规则,只能各写一遍。 */
.drama-slider__input::-webkit-slider-thumb {
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--drama-color-primary);
border: 2px solid var(--drama-color-surface);
box-shadow: 0 0 0 1px var(--drama-color-border);
cursor: pointer;
}
.drama-slider__input::-moz-range-thumb {
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--drama-color-primary);
border: 2px solid var(--drama-color-surface);
cursor: pointer;
}
.drama-slider__input:focus-visible {
box-shadow: 0 0 0 3px color-mix(in srgb, var(--drama-color-primary) 30%, transparent);
}
.drama-slider__value {
flex-shrink: 0;
min-width: 96px;
font-size: var(--drama-font-size-sm);
color: var(--drama-color-text-muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
</style>