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 keys = computed(() => Object.keys(props.options) as T[])
|
||||||
|
|
||||||
const model = defineModel<T>({ required: true })
|
const model = defineModel<T | undefined>({ required: true })
|
||||||
|
|
||||||
const index = computed({
|
const index = computed({
|
||||||
get () {
|
get () {
|
||||||
return keys.value.indexOf(model.value)
|
return model.value
|
||||||
|
? keys.value.indexOf(model.value)
|
||||||
|
: undefined
|
||||||
},
|
},
|
||||||
set (newIndex) {
|
set (newIndex) {
|
||||||
model.value = keys.value[newIndex]
|
model.value = newIndex
|
||||||
|
? keys.value[newIndex]
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -38,7 +42,8 @@ onMounted(() => {
|
||||||
:style="`
|
:style="`
|
||||||
--step-size: calc(100% / ${keys.length + 2});
|
--step-size: calc(100% / ${keys.length + 2});
|
||||||
--slider-width: calc(var(--step-size) * ${keys.length - 1} + 16px);
|
--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 -->
|
<!-- Label -->
|
||||||
|
@ -66,7 +71,7 @@ onMounted(() => {
|
||||||
v-for="key in keys"
|
v-for="key in keys"
|
||||||
:key="key"
|
:key="key"
|
||||||
:class="[$style.key, { [$style.current]: key === model } ]"
|
:class="[$style.key, { [$style.current]: key === model } ]"
|
||||||
style="flex-basis:var(--step-size)"
|
style="flex-basis: var(--step-size); padding-bottom: 8px;"
|
||||||
type="button"
|
type="button"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@click="() => { model = key; input.focus(); }"
|
@click="() => { model = key; input.focus(); }"
|
||||||
|
@ -74,7 +79,6 @@ onMounted(() => {
|
||||||
{{ key }}
|
{{ key }}
|
||||||
</button>
|
</button>
|
||||||
</Layout>
|
</Layout>
|
||||||
<Spacer size-8 />
|
|
||||||
|
|
||||||
<!-- Slider -->
|
<!-- Slider -->
|
||||||
|
|
||||||
|
@ -83,12 +87,15 @@ onMounted(() => {
|
||||||
ref="input"
|
ref="input"
|
||||||
v-model="index"
|
v-model="index"
|
||||||
type="range"
|
type="range"
|
||||||
style="width: var(--slider-width); opacity: .001;"
|
style="width: var(--slider-width); opacity: .001; cursor: pointer;"
|
||||||
:max="keys.length - 1"
|
:max="keys.length - 1"
|
||||||
:autofocus="autofocus || undefined"
|
:autofocus="autofocus || undefined"
|
||||||
>
|
>
|
||||||
<div :class="$style.range" />
|
<div :class="$style.range" />
|
||||||
<div :class="$style.pin" />
|
<div
|
||||||
|
v-if="model !== undefined"
|
||||||
|
:class="$style.pin"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<Spacer size-8 />
|
<Spacer size-8 />
|
||||||
|
|
||||||
|
@ -104,6 +111,7 @@ onMounted(() => {
|
||||||
><Markdown :md="options[key]" /></span>
|
><Markdown :md="options[key]" /></span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
v-if="model !== undefined"
|
||||||
style="position: absolute;"
|
style="position: absolute;"
|
||||||
:class="$style.description"
|
:class="$style.description"
|
||||||
><Markdown :md="options[model]" /></span>
|
><Markdown :md="options[model]" /></span>
|
||||||
|
@ -147,6 +155,7 @@ onMounted(() => {
|
||||||
top: 11px;
|
top: 11px;
|
||||||
transition: all .1s;
|
transition: all .1s;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
opacity: var(--slider-opacity);
|
||||||
}
|
}
|
||||||
input:focus~.range {
|
input:focus~.range {
|
||||||
// focused style
|
// focused style
|
||||||
|
|
|
@ -11,6 +11,8 @@ const options = {
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const option = ref<keyof typeof options>('pod')
|
const option = ref<keyof typeof options>('pod')
|
||||||
|
|
||||||
|
const optionWithUndefined = ref<keyof typeof options | undefined>(undefined)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
```ts
|
```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.
|
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)
|
- Define a possible values
|
||||||
- Select one value as active (model)
|
- Select zero or one values as active (`v-model`)
|
||||||
|
|
||||||
User interaction:
|
**User interaction**
|
||||||
|
|
||||||
- It mimics the functionality of a single `range` input:
|
- It mimics the functionality of a single `range` input:
|
||||||
- to be operated with arrow keys or mouse
|
- to be operated with arrow keys or mouse
|
||||||
- focus is indicated
|
- focus is indicated
|
||||||
- ticks are indicated
|
- ticks are indicated
|
||||||
|
|
||||||
Design:
|
**Design**
|
||||||
|
|
||||||
- A pin (same as in the toggle component)
|
- A pin (same as in the toggle component)
|
||||||
- a range (very faint)
|
- a range (very faint)
|
||||||
- Ticks
|
- Ticks?
|
||||||
- Constant dimensions, fitting the largest text box
|
- Constant dimensions, fitting the largest text box
|
||||||
|
|
||||||
- Not to be confused with a pearls navigation patterns (list of dots; indicates temporal range)
|
- Not to be confused with a pearls navigation patterns (list of dots; indicates temporal range)
|
||||||
|
|
Loading…
Reference in New Issue