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
<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>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
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
Component Props
Styles can be freely adjusted.
Color:
View example source code
vue
<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>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
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
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
<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>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
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
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