150 lines
3.4 KiB
Vue
150 lines
3.4 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref, watch, watchEffect } from 'vue'
|
||
|
||
import { color } from '~/composables/color';
|
||
|
||
import Pill from './Pill.vue'
|
||
import Layout from './Layout.vue';
|
||
import Spacer from './Spacer.vue';
|
||
|
||
const props = defineProps<{
|
||
icon?: string,
|
||
placeholder?: string,
|
||
label?: string,
|
||
customOptions?: true,
|
||
|
||
autofocus?: boolean,
|
||
}>();
|
||
|
||
const customOptions = ref<string[]>([]);
|
||
|
||
const model = defineModel<{
|
||
current : string[],
|
||
others?: string[]
|
||
}>({required:true})
|
||
|
||
const whenInteractive = (then:() => void) => {
|
||
if(!model.value.others) return; then();
|
||
}
|
||
|
||
const selectedLabel = ref("+");
|
||
whenInteractive(()=>
|
||
watchEffect(() => {
|
||
if (!model.value.others) return
|
||
const newLabel = selectedLabel.value;
|
||
console.log("newLabel", newLabel);
|
||
selectedLabel.value = '+'
|
||
if (!newLabel || newLabel==='+') return;
|
||
if (!model.value.current.includes(newLabel) ){
|
||
model.value.current.push(newLabel);
|
||
model.value.others = model.value.others.filter(value=>value!==newLabel)
|
||
}
|
||
})
|
||
)
|
||
const deselectPill = (value:string) =>
|
||
whenInteractive(()=>
|
||
model.value =
|
||
{...model.value,
|
||
current : model.value.current.filter(v=>v!==value),
|
||
others : [value, ...(model.value.others || [])]
|
||
}
|
||
)
|
||
|
||
</script>
|
||
|
||
<template>
|
||
<Layout stack no-gap label :class="$style.pills" for="dropdown">
|
||
|
||
<!-- Label -->
|
||
<span v-if="$slots['label']" :class="$style.label">
|
||
<slot name="label" />
|
||
</span>
|
||
<span v-if="props.label" :class="$style.label">
|
||
{{ props.label }}
|
||
</span>
|
||
|
||
<!-- List of Pills -->
|
||
|
||
<Layout flex gap-8
|
||
v-bind="color({}, ['solid', 'default', 'raised'])()"
|
||
:class="$style.list"
|
||
>
|
||
<Pill outline raised
|
||
v-for="value in model.current"
|
||
v-if="!model.others"
|
||
:class="$style.pill"
|
||
>
|
||
<span>{{ value }}</span>
|
||
</Pill>
|
||
<Pill outline raised
|
||
no-underline @click="deselectPill(value)"
|
||
v-for="value in model.current"
|
||
v-if="model.others"
|
||
:class="$style.pill"
|
||
>
|
||
<span :class="$style['pill-content']">{{ value }}</span> ×
|
||
</Pill>
|
||
|
||
<!-- Add more pills -->
|
||
|
||
<select
|
||
v-if="model.others"
|
||
name="dropdown"
|
||
v-model="selectedLabel"
|
||
:class="$style.dropdown"
|
||
@change="e => e.target.value='+'"
|
||
>
|
||
<option value="+"></option>
|
||
<option v-for="value in model.others" :value="value">
|
||
{{ value }}
|
||
</option>
|
||
</select>
|
||
</Layout>
|
||
</Layout>
|
||
</template>
|
||
|
||
<style module lang="scss">
|
||
.pills {
|
||
>.label {
|
||
margin-top: -18px;
|
||
padding-bottom: 8px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
>.list {
|
||
padding:4px;
|
||
border-radius: 24px;
|
||
|
||
min-height: 48px;
|
||
min-width: 160px;
|
||
|
||
>:is(:hover, :focus-visible) .pill-content {
|
||
text-decoration: line-through !important;
|
||
}
|
||
> .pill {
|
||
margin: 4px;
|
||
padding: 2px;
|
||
}
|
||
>.dropdown{
|
||
border-radius: 15px;
|
||
padding: 2px 11.25px;
|
||
flex-grow: 1;
|
||
text-align: right;
|
||
background: transparent;
|
||
appearance: auto;
|
||
margin-right: 12px;
|
||
|
||
// From vitepress default, needed for app
|
||
border: 0;
|
||
color: inherit;
|
||
}
|
||
}
|
||
&:hover>.list {
|
||
box-shadow: inset 0 0 0 4px var(--border-color)
|
||
}
|
||
:has(>.dropdown:focus) {
|
||
box-shadow: inset 0 0 0 4px var(--focus-ring-color)
|
||
}
|
||
}
|
||
</style>
|