Stubborn Slider slider
A slider that never compromises when disabled. (。-`ω´-)
Usage Examples
Basic Usage
When the state is disabled, the more you drag the handle, the longer and tighter it gets. ᕕ( ゚ ∀。)ᕗ
Current Value: 50
View example source code
vue
<!-- #region main-code -->
<template>
<div class="w-full flex flex-col gap-4">
<base-checkbox
v-model="disabled"
class="w-full border rounded p-4"
:label="t('disabled')"
/>
<div class="flex flex-col flex-1 justify-center">
{{ t('currentValue') }} {{ Math.floor(value) }}
<slider-stubborn
v-model="value"
:disabled="disabled"
:max-thumb-length="thumbLength"
class="w-full"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { useWindowSize } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseCheckbox from '../../base-checkbox.vue'
import SliderStubborn from '../slider-stubborn.vue'
const { width, height } = useWindowSize()
const { t } = useI18n()
const disabled = ref(false)
const value = ref(50)
const thumbLength = computed(() =>
Math.min(width.value, height.value) / 3,
)
</script>
<!-- #endregion main-code -->
<i18n lang="json">
{
"zh-hant": {
"currentValue": "目前數值:",
"disabled": "停用"
},
"en": {
"currentValue": "Current Value:",
"disabled": "Disabled"
}
}
</i18n>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
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
Component Props
Styles can be freely adjusted.
Color:
View example source code
vue
<!-- #region main-code -->
<template>
<div class="w-full flex flex-col gap-10 py-10">
<div class="mb-10 flex items-center gap-1">
<base-input
v-model.number="thumbSize"
type="range"
:label="`${t('size')}: ${thumbSize}`"
class="flex-1"
:min="10"
:step="1"
:max="80"
/>
<div class="flex-1">
<div class="text-sm font-bold">
{{ t('color') }}
</div>
<input
v-model="thumbColor"
type="color"
class="h-[40px] w-full"
>
</div>
</div>
<slider-stubborn
v-model="value"
disabled
:max-thumb-length="thumbMaxLength / 4"
:thumb-color="thumbColor"
:thumb-size="thumbSize"
class="z-[999] w-full"
/>
</div>
</template>
<script setup lang="ts">
import { useWindowSize } from '@vueuse/core'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseInput from '../../../components/base-input.vue'
import SliderStubborn from '../slider-stubborn.vue'
const { width, height } = useWindowSize()
const { t } = useI18n()
const thumbSize = ref(40)
const thumbColor = ref('#FF639B')
const value = ref(50)
const thumbMaxLength = computed(() =>
Math.min(width.value, height.value),
)
</script>
<!-- #endregion main-code -->
<i18n lang="json">
{
"zh-hant": {
"size": "尺寸:",
"color": "顏色:"
},
"en": {
"size": "Size:",
"color": "Color:"
}
}
</i18n>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
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
Plan Selection
Disable the slider for a specific range to emphasize the disabled effect.
Select Your Plan
Basic Fishbowl
1 Just Right
Premium Fishbowl
3~6 Full
Recommended Plan
Luxury Ocean
Choose Your Own
Select Fish Count: 5
View example source code
vue
<!-- #region main-code -->
<template>
<div class="w-full flex flex-col gap-4 py-10">
<div class="flex flex-col flex-1 justify-center gap-4">
<div class="text-lg font-bold opacity-90">
{{ t('selectPlan') }}
</div>
<div class="mb-10 flex gap-4">
<div
class="card transform rounded-lg from-gray-400 to-gray-500 bg-gradient-to-bl p-1 shadow-md transition-all hover:shadow-lg hover:-translate-y-0.5"
:class="{ ' border-[0.3rem]': plan === 'basic' }"
@click="plan = 'basic'"
>
<div class="text-lg font-bold md:text-2xl">
{{ t('basicFishbowl') }}
</div>
<div class="mt-1 text-xs opacity-90 md:text-sm">
{{ t('basicFishbowlDescription') }}
</div>
</div>
<div
class="card transform border-2 border-indigo-200 rounded-lg from-blue-400 to-indigo-500 bg-gradient-to-bl p-1 shadow-lg transition-all hover:shadow-xl hover:-translate-y-1"
:class="{ ' border-[0.3rem]': plan === 'premium' }"
@click="plan = 'premium'"
>
<div class="text-lg font-bold md:text-2xl">
{{ t('premiumFishbowl') }}
</div>
<div class="mt-1 text-xs opacity-90 md:text-sm">
{{ t('premiumFishbowlDescription') }}
</div>
<div
class="absolute right-0 top-0 translate-x-2 transform rounded-bl-lg rounded-tr-lg bg-yellow-400 px-2 py-1 shadow-sm -translate-y-2"
>
<div class="text-xs text-red-900 font-semibold md:text-sm">
{{ t('recommendedPlan') }}
</div>
</div>
</div>
<div
class="card transform border-2 border-pink-200 rounded-lg from-purple-500 to-pink-500 bg-gradient-to-bl p-1 shadow-xl transition-all hover:shadow-2xl hover:-translate-y-1.5"
:class="{ ' border-[0.3rem]': plan === 'luxury' }"
@click="plan = 'luxury'"
>
<div class="text-lg font-bold md:text-2xl">
{{ t('luxuryOcean') }}
</div>
<div class="mt-1 text-xs opacity-90 md:text-sm">
{{ t('luxuryOceanDescription') }}
</div>
</div>
</div>
<div class="text-lg font-bold opacity-90">
{{ t('selectFishCount') }} {{ Math.floor(sliderValue) }}
</div>
<slider-stubborn
v-model="sliderValue"
v-bind="disabledParams"
:min="0"
:max="10"
:step="0.1"
:max-thumb-length="thumbLength"
:thumb-size="40"
class="w-full py-4"
/>
</div>
</div>
</template>
<script setup lang="ts">
import type { ComponentProps } from 'vue-component-type-helpers'
import { useWindowSize } from '@vueuse/core'
import { pipe } from 'remeda'
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import SliderStubborn from '../slider-stubborn.vue'
type Props = ComponentProps<typeof SliderStubborn>
type Plan = 'basic' | 'premium' | 'luxury'
const { t } = useI18n()
const { width, height } = useWindowSize()
const plan = ref<Plan>('premium')
const sliderValue = ref(5)
const planRangleMap: Record<Plan, [number, number]> = {
basic: [1, 1],
premium: [3, 6],
luxury: [-1, 11],
}
const disabledParams = computed<
Pick<Props, 'minDisabled' | 'maxDisabled'>
>(() => pipe(
planRangleMap[plan.value],
([min, max]) => ({
minDisabled: min,
maxDisabled: max,
}),
))
const thumbLength = computed(() =>
Math.min(width.value, height.value) / 3,
)
</script>
<style lang="sass" scoped>
.card
display: flex
flex-direction: column
justify-content: center
align-items: center
aspect-ratio: 1 / 1.3
flex: 1
transition-duration: 200ms
color: white
cursor: pointer
</style>
<!-- #endregion main-code -->
<i18n lang="json">
{
"zh-hant": {
"selectPlan": "選擇喜歡的方案",
"basicFishbowl": "基本魚缸",
"basicFishbowlDescription": "1 隻剛剛好",
"premiumFishbowl": "高級池塘",
"premiumFishbowlDescription": "3~6 隻吃飽飽",
"recommendedPlan": "推薦方案",
"luxuryOcean": "尊爵大海",
"luxuryOceanDescription": "任你選!",
"selectFishCount": "可選鱈魚數:"
},
"en": {
"selectPlan": "Select Your Plan",
"basicFishbowl": "Basic Fishbowl",
"basicFishbowlDescription": "1 Just Right",
"premiumFishbowl": "Premium Fishbowl",
"premiumFishbowlDescription": "3~6 Full",
"recommendedPlan": "Recommended Plan",
"luxuryOcean": "Luxury Ocean",
"luxuryOceanDescription": "Choose Your Own",
"selectFishCount": "Select Fish Count:"
}
}
</i18n>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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
How It Works
Uses an SVG path to create the stretching and bending elastic effect.
For a detailed explanation, see this article.
Warning! Σ(ˊДˋ;)
Do not set overflow to hidden, otherwise the handle will be clipped when it stretches.
Source Code
API
Props
interface Props {
modelValue: number;
disabled?: boolean;
/** 小於此數值也會有 disabled 效果 */
minDisabled?: number;
/** 大於此數值也會有 disabled 效果 */
maxDisabled?: number;
min?: number;
max?: number;
step?: number;
/** 握把被拉長的最大長度 */
maxThumbLength?: number;
thumbSize?: number;
thumbColor?: string;
trackClass?: string;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Emits
const emit = defineEmits<{
'update:modelValue': [value: Props['modelValue']];
}>()1
2
3
2
3