feat(ui): slider now supports mixed/undefined state
This commit is contained in:
parent
0c0fd24ebd
commit
1948d9455b
|
@ -13,14 +13,18 @@ const props = defineProps<{
|
|||
|
||||
const keys = computed(() => Object.keys(props.options) as T[])
|
||||
|
||||
const model = defineModel<T>({ required: true })
|
||||
const model = defineModel<T | undefined>({ required: true })
|
||||
|
||||
const index = computed({
|
||||
get () {
|
||||
return keys.value.indexOf(model.value)
|
||||
return model.value
|
||||
? keys.value.indexOf(model.value)
|
||||
: undefined
|
||||
},
|
||||
set (newIndex) {
|
||||
model.value = keys.value[newIndex]
|
||||
model.value = newIndex
|
||||
? keys.value[newIndex]
|
||||
: undefined
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -38,7 +42,8 @@ onMounted(() => {
|
|||
:style="`
|
||||
--step-size: calc(100% / ${keys.length + 2});
|
||||
--slider-width: calc(var(--step-size) * ${keys.length - 1} + 16px);
|
||||
--current-step: ${index};
|
||||
--slider-opacity: ${ index === undefined ? .5 : 1 };
|
||||
--current-step: ${ index === undefined ? keys.length - 1 : index };
|
||||
`"
|
||||
>
|
||||
<!-- Label -->
|
||||
|
@ -65,8 +70,8 @@ onMounted(() => {
|
|||
<button
|
||||
v-for="key in keys"
|
||||
:key="key"
|
||||
:class="[$style.key, {[$style.current]: key===model}]"
|
||||
style="flex-basis:var(--step-size)"
|
||||
:class="[$style.key, { [$style.current]: key === model } ]"
|
||||
style="flex-basis: var(--step-size); padding-bottom: 8px;"
|
||||
type="button"
|
||||
tabindex="-1"
|
||||
@click="() => { model = key; input.focus(); }"
|
||||
|
@ -74,7 +79,6 @@ onMounted(() => {
|
|||
{{ key }}
|
||||
</button>
|
||||
</Layout>
|
||||
<Spacer size-8 />
|
||||
|
||||
<!-- Slider -->
|
||||
|
||||
|
@ -83,12 +87,15 @@ onMounted(() => {
|
|||
ref="input"
|
||||
v-model="index"
|
||||
type="range"
|
||||
style="width: var(--slider-width); opacity: .001;"
|
||||
style="width: var(--slider-width); opacity: .001; cursor: pointer;"
|
||||
:max="keys.length - 1"
|
||||
:autofocus="autofocus || undefined"
|
||||
>
|
||||
<div :class="$style.range" />
|
||||
<div :class="$style.pin" />
|
||||
<div
|
||||
v-if="model !== undefined"
|
||||
:class="$style.pin"
|
||||
/>
|
||||
</span>
|
||||
<Spacer size-8 />
|
||||
|
||||
|
@ -104,6 +111,7 @@ onMounted(() => {
|
|||
><Markdown :md="options[key]" /></span>
|
||||
</span>
|
||||
<span
|
||||
v-if="model !== undefined"
|
||||
style="position: absolute;"
|
||||
:class="$style.description"
|
||||
><Markdown :md="options[model]" /></span>
|
||||
|
@ -147,6 +155,7 @@ onMounted(() => {
|
|||
top: 11px;
|
||||
transition: all .1s;
|
||||
pointer-events: none;
|
||||
opacity: var(--slider-opacity);
|
||||
}
|
||||
input:focus~.range {
|
||||
// focused style
|
||||
|
|
|
@ -11,6 +11,8 @@ const options = {
|
|||
} as const
|
||||
|
||||
const option = ref<keyof typeof options>('pod')
|
||||
|
||||
const optionWithUndefined = ref<keyof typeof options | undefined>(undefined)
|
||||
</script>
|
||||
|
||||
```ts
|
||||
|
@ -49,25 +51,50 @@ You can either specify the `label` prop or add custom Html into the `#label` slo
|
|||
|
||||
Add the prop `autofocus` to focus the slider as soon as it renders. Make sure to only autofocus one element per page.
|
||||
|
||||
## Undefined state
|
||||
|
||||
If you want to aggregate potentially mixed states, or start with no initial selection,
|
||||
you can set v-model to `undefined`.
|
||||
|
||||
```ts
|
||||
const optionWithUndefined = ref<keyof typeof options | undefined>(undefined)
|
||||
```
|
||||
|
||||
```vue-html
|
||||
<Slider
|
||||
v-model="optionWithUndefined"
|
||||
label="Privacy level?"
|
||||
:options="options"
|
||||
/>
|
||||
```
|
||||
|
||||
<Spacer />
|
||||
|
||||
<Slider
|
||||
v-model="optionWithUndefined"
|
||||
label="Privacy level?"
|
||||
:options="options"
|
||||
/>
|
||||
|
||||
---
|
||||
|
||||
Functionality:
|
||||
**Functionality**
|
||||
|
||||
- Define a list of `[value:string, description:markdown]` pairs (props)
|
||||
- Select one value as active (model)
|
||||
- Define a possible values
|
||||
- Select zero or one values as active (`v-model`)
|
||||
|
||||
User interaction:
|
||||
**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:
|
||||
**Design**
|
||||
|
||||
- A pin (same as in the toggle component)
|
||||
- a range (very faint)
|
||||
- Ticks
|
||||
- Ticks?
|
||||
- Constant dimensions, fitting the largest text box
|
||||
|
||||
- Not to be confused with a pearls navigation patterns (list of dots; indicates temporal range)
|
||||
|
|
Loading…
Reference in New Issue