refactor(ui): add Slider component

This commit is contained in:
upsiflu 2025-02-03 23:09:32 +01:00
parent a2d79aa6bc
commit 337373ff75
3 changed files with 167 additions and 0 deletions

View File

@ -0,0 +1,107 @@
<script setup lang="ts" generic="T extends string">
import { ref, computed, onMounted } from 'vue'
import Layout from "~/components/ui/Layout.vue"
import Spacer from "~/components/ui/Spacer.vue"
const props = defineProps<{
label?: string,
options: Record<T, string>,
autofocus?: true
}>();
const keys = computed(() => Object.keys(props.options) as T[])
const model = defineModel<T>({ required: true });
const index = computed({
get() {
return keys.value.indexOf(model.value)
},
set(newIndex) {
console.log("NEW", newIndex)
model.value = keys.value[newIndex]
console.log("=", keys.value[newIndex])
}
})
const input = ref()
onMounted(() => {
if (props.autofocus) input.value.focus();
})
</script>
<template>
<Layout stack no-gap :style="`
--step-size: calc(100% / ${keys.length + 2});
--slider-width: calc(var(--step-size) * ${keys.length - 1} + 16px);
--current-step: ${index};
`">
<!-- Label -->
<label v-if="$slots['label']" :class="$style.label">
<slot name="label" />
</label>
<label v-if="label" :class="$style.label">
{{ label }}
</label>
<!-- Model indicator -->
<Layout flex no-gap>
<button v-for="key in keys" :class="[$style.key, {[$style.current]: key===model}]"
style="flex-basis:var(--step-size)"
type="button"
@click="() => { model = key; input.focus(); }"
tabindex="-1"
>{{ key }}</button>
</Layout>
<Spacer size-8 />
<!-- Slider -->
<input ref="input" type="range"
style="width: var(--slider-width);"
:max="keys.length - 1"
v-model="index"
/>
<Spacer size-8/>
<!-- Description of current option -->
<span style="position: relative;">
<span style="display: inline-flex; margin-right: -100%; width: 100%; visibility: hidden;" >
<span v-for="key in keys" :class="$style.description" :style="`margin-right: -20%; --current-step: 0; color: magenta;`">{{ options[key] }}</span>
</span>
<span style="position: absolute;" :class="$style.description">{{ options[model] }}</span>
</span>
</Layout>
</template>
<style module lang="scss">
.label {
margin-top: -18px;
padding-bottom: 8px;
font-size: 14px;
font-weight: 600;
}
.key {
all: unset;
opacity: .7;
&.current {
opacity: 1;
}
}
.description {
font-size: 12px;
font-weight: 600;
width: 11rem;
overflow: visible;
transition: margin .2s;
--inset: calc(var(--step-size) * var(--current-step));
margin-left: var(--inset);
margin-right: calc(0px - var(--inset));
}
</style>

View File

@ -42,6 +42,7 @@ export default defineConfig({
],
},
{ text: 'Input', link: '/components/ui/input' },
{ text: 'Slider', link: '/components/ui/slider' },
{ text: 'Popover (Dropdown Menu)', link: '/components/ui/popover' },
{ text: 'Textarea', link: '/components/ui/textarea' },
{ text: 'Toggle', link: '/components/ui/toggle' },

View File

@ -0,0 +1,59 @@
<script setup lang="ts">
import { ref } from 'vue'
import Slider from '~/components/ui/Slider.vue'
const options = {
me: "Only I can find and edit this",
pod: "Me and other users on the instance can find and edit this",
everyone: "Everyone can find and edit this"
} as const
const option = ref<keyof typeof options>('me')
</script>
```ts
import Slider from "~/components/ui/Slider.vue";
```
# Slider
Let a user select a value along a gradient.
The model (required) is the current value.
```ts
const options = {
me: "Only I can find and edit this",
pod: "Me and other users on the instance can find and edit this",
everyone: "Everyone can find and edit this",
} as const;
const option = ref<keyof typeof options>("me");
```
```vue-html
<Slider :options="options" v-model="option" label="Privacy" />
```
<Slider :options="options" v-model="option" label="Privacy" />
Functionality:
- Define a list of `[value:string, description:markdown]` pairs (props)
- Select one value as active (model)
User interaction:
- It mimics the functionality of a single `range` input:
- to be operated with arrow keys or mouse
- focus is indicated
- ticks are indicated
Design:
- A pin (same as in the toggle component)
- a range (very faint)
- Ticks
- Not to be confused with a pearls navigation patterns (list of dots; indicates temporal range)