feat(ui): [WIP] Pills list
This commit is contained in:
parent
713c2fe34f
commit
9b02f1840e
|
@ -8,6 +8,12 @@ import Input from './Input.vue'
|
||||||
import Popover from './Popover.vue'
|
import Popover from './Popover.vue'
|
||||||
import PopoverItem from './popover/PopoverItem.vue'
|
import PopoverItem from './popover/PopoverItem.vue'
|
||||||
|
|
||||||
|
/* Event */
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
changed: []
|
||||||
|
}>()
|
||||||
|
|
||||||
/* Model */
|
/* Model */
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
@ -51,6 +57,8 @@ watch(isEditing, (isTrue, wasTrue) => {
|
||||||
model.value.current = { ...matchInOthers }
|
model.value.current = { ...matchInOthers }
|
||||||
}
|
}
|
||||||
model.value.others = model.value.others.filter(({ label }) => label !== model.value?.current.label)
|
model.value.others = model.value.others.filter(({ label }) => label !== model.value?.current.label)
|
||||||
|
|
||||||
|
emit('changed')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -249,7 +257,7 @@ const current = computed(() => (
|
||||||
<!-- Preset content -->
|
<!-- Preset content -->
|
||||||
<div :class="$style['pill-content']">
|
<div :class="$style['pill-content']">
|
||||||
<slot />
|
<slot />
|
||||||
{{ model?.current?.label }}
|
{{ model?.current?.label }} ​
|
||||||
<Popover
|
<Popover
|
||||||
v-if="model"
|
v-if="model"
|
||||||
v-model="isEditing"
|
v-model="isEditing"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watchEffect, watch, computed } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
|
|
||||||
import { color } from '~/composables/color'
|
import { color } from '~/composables/color'
|
||||||
|
|
||||||
|
@ -13,17 +13,13 @@ const props = defineProps<{
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const model = defineModel<{
|
const model = defineModel<{
|
||||||
current: string[],
|
currents: Item[],
|
||||||
others?: string[],
|
others?: Item[],
|
||||||
custom?: string[],
|
|
||||||
}>({ required: true })
|
}>({ required: true })
|
||||||
|
|
||||||
const whenInteractive = (then:() => void) => {
|
type Item = { type: 'custom' | 'preset', label: string }
|
||||||
if (!model.value.others) return; then()
|
|
||||||
}
|
|
||||||
|
|
||||||
const editingValue = ref('')
|
// Manually trigger rerendering
|
||||||
const additionalValue = ref('')
|
|
||||||
const componentKey = ref(0)
|
const componentKey = ref(0)
|
||||||
const forceRerender = () => componentKey.value++
|
const forceRerender = () => componentKey.value++
|
||||||
|
|
||||||
|
@ -31,99 +27,51 @@ const isStatic = computed(() =>
|
||||||
!model.value.others
|
!model.value.others
|
||||||
)
|
)
|
||||||
|
|
||||||
const selectedLabel = ref('+')
|
const emptyItem = {
|
||||||
|
label: '', type: 'custom'
|
||||||
|
} as const
|
||||||
|
|
||||||
// Dropdown changed -> select label
|
const pills = computed({
|
||||||
|
|
||||||
whenInteractive(() =>
|
get: () => {
|
||||||
watchEffect(() => {
|
console.log("OTHERS", model.value.others)
|
||||||
if (!model.value.others) return
|
return [...model.value.currents, { ...emptyItem }].map(
|
||||||
const newLabel = selectedLabel.value
|
(item) => ({ current: { ...item }, others: model.value.others ? model.value.others.map(item => ({ ...item })) : [] })
|
||||||
selectedLabel.value = '+'
|
)
|
||||||
if (!newLabel || newLabel === '+') return
|
},
|
||||||
if (!model.value.current.includes(newLabel)) {
|
set: (pills) => {
|
||||||
model.value.current.push(newLabel)
|
console.log("SETTING PILLS", pills)
|
||||||
model.value.others = model.value.others.filter(value => value !== newLabel)
|
model.value.currents = pills
|
||||||
|
.filter(({ current }) => current.label !== '')
|
||||||
|
.map(({ current }) => ({ ...current }))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
|
||||||
|
|
||||||
// Pill clicked --> edit or unselect label
|
const changed = (index: number, pill: (typeof pills.value)[number]) => {
|
||||||
|
/*reduce<U>(
|
||||||
|
callbackfn: (previousValue: U, currentValue: Item, currentIndex: number, array: Item[]) => U,
|
||||||
|
initialValue: U): U */
|
||||||
|
|
||||||
const pillClicked = (value: string) => {
|
console.log("NEW: #", index, "=", pills.value[index].current)
|
||||||
model.value.custom?.includes(value)
|
console.log("OLD: #", index, "=", pill.current)
|
||||||
? edit(value)
|
// model.value.currents.push({ ...emptyItem })
|
||||||
: unselect(value)
|
// console.log(model.value.currents.length)
|
||||||
}
|
|
||||||
|
|
||||||
const edit = (value: string) => {
|
model.value.currents[index] = { ...pills.value[index].current }
|
||||||
editingValue.value = value
|
model.value.others = { ...pills.value[index].others }
|
||||||
}
|
|
||||||
|
|
||||||
const unselect = (value: string) => {
|
|
||||||
model.value = {
|
|
||||||
...model.value,
|
|
||||||
current: model.value.current.filter(v => v !== value),
|
|
||||||
others: [value, ...(model.value.others || [])]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Editing value changed --> remove, add or replace a label
|
|
||||||
|
|
||||||
const remove = (value: string) => {
|
|
||||||
model.value = {
|
|
||||||
...model.value,
|
|
||||||
current: model.value.current.filter(v => v !== value),
|
|
||||||
custom: model.value.custom?.filter(v => v !== value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const add = (value: string) => {
|
|
||||||
if (model.value.current.includes(value)) return
|
|
||||||
model.value = {
|
|
||||||
...model.value,
|
|
||||||
current: [...model.value.current, value],
|
|
||||||
custom: [...(model.value.custom || []), value]
|
|
||||||
}
|
|
||||||
additionalValue.value = ''
|
|
||||||
|
|
||||||
// We have to force rerender because else, Vue keeps the previous additionalValue for the new "additionalValue" input pill :-(
|
|
||||||
forceRerender()
|
forceRerender()
|
||||||
}
|
}
|
||||||
|
|
||||||
const replace = (value: string) => {
|
// Add empty current item if none is inside
|
||||||
model.value = {
|
watch( model, () => {
|
||||||
...model.value,
|
console.log("MODEL CHANGED", model.value)
|
||||||
current: model.value.current.map(v => v === value ? editingValue.value : v),
|
|
||||||
custom: model.value.custom?.map(v => v === value ? editingValue.value : v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(editingValue, (newValue, oldValue) => {
|
|
||||||
if (oldValue === '') return
|
|
||||||
if (newValue === '') {
|
|
||||||
remove(oldValue)
|
|
||||||
} else {
|
|
||||||
replace(oldValue)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(additionalValue, (newValue, oldValue) => {
|
// Remove duplicates on each CONFIRM
|
||||||
if (newValue !== '') {
|
|
||||||
additionalValue.value = ''
|
|
||||||
add(newValue)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Remove duplicates
|
// const unique = (list:Item[]) =>
|
||||||
|
// list.reduce((acc, a) => ([a, ...acc]), [])
|
||||||
const unique = (a:string[]) => [...new Set(a)]
|
|
||||||
|
|
||||||
watch(model, () => {
|
|
||||||
model.value.current = unique(model.value.current)
|
|
||||||
model.value.others = model.value.others ? unique(model.value.others) : undefined
|
|
||||||
model.value.custom = model.value.custom ? unique(model.value.custom) : undefined
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -146,75 +94,33 @@ watch(model, () => {
|
||||||
:class="$style.label"
|
:class="$style.label"
|
||||||
>
|
>
|
||||||
{{ props.label }}
|
{{ props.label }}
|
||||||
|
<div>PILLS {{ pills.map(({ current })=>current.label) }}</div>
|
||||||
|
<div>MODEL {{ model.currents.map(({ label })=>label) }}</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- List of Pills -->
|
<!-- List of Pills -->
|
||||||
|
|
||||||
<Layout
|
<Layout
|
||||||
flex
|
flex
|
||||||
no-gap
|
gap-4
|
||||||
v-bind="color({}, ['solid', 'default', 'secondary'])()"
|
v-bind="color({}, ['solid', 'default', 'secondary'])()"
|
||||||
:class="$style.list"
|
:class="$style.list"
|
||||||
>
|
>
|
||||||
<!-- Add predefined or previously unselected pill -->
|
|
||||||
|
|
||||||
<select
|
|
||||||
v-if="model.others"
|
|
||||||
v-model="selectedLabel"
|
|
||||||
name="dropdown"
|
|
||||||
:class="$style.dropdown"
|
|
||||||
@change="e => { (e.target as HTMLInputElement).value='+' }"
|
|
||||||
>
|
|
||||||
<option value="+" />
|
|
||||||
<option
|
|
||||||
v-for="value in model.others"
|
|
||||||
:key="value"
|
|
||||||
:value="value"
|
|
||||||
>
|
|
||||||
{{ value }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<template
|
|
||||||
v-for="value in model.current"
|
|
||||||
:key="value"
|
|
||||||
>
|
|
||||||
<!-- List of current pills -->
|
|
||||||
|
|
||||||
<Pill
|
<Pill
|
||||||
v-if="value !== editingValue"
|
v-for="(pill, index) in pills"
|
||||||
|
:key="index*1000+componentKey"
|
||||||
|
v-model="pills[index]"
|
||||||
outline
|
outline
|
||||||
raised
|
|
||||||
no-underline
|
no-underline
|
||||||
:class="[$style.pill, $style[isStatic ? 'static' : model.custom?.includes(value) ? 'custom' : 'preset']]"
|
:class="[$style.pill, $style[isStatic ? 'static' : pill.current.label === '' ? 'empty' : pill.current.type === 'custom' ? 'custom' : 'preset']]"
|
||||||
@click="!isStatic && pillClicked(value)"
|
@changed="() => { console.log('CCCCCC', index); changed(index, pill) }"
|
||||||
>
|
>
|
||||||
<span :class="$style['pill-content']">{{ value }}</span>
|
<span
|
||||||
|
v-if="isStatic"
|
||||||
|
:class="$style['pill-content']"
|
||||||
|
>{{ pill.current.label }}</span>
|
||||||
|
{{ `${index} ${componentKey}` }}
|
||||||
</Pill>
|
</Pill>
|
||||||
|
|
||||||
<Pill
|
|
||||||
v-if="value === editingValue"
|
|
||||||
v-model="editingValue"
|
|
||||||
outline
|
|
||||||
raised
|
|
||||||
no-underline
|
|
||||||
autofocus
|
|
||||||
:class="[$style.pill, $style.custom]"
|
|
||||||
@click="!isStatic && pillClicked(value)"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Empty pill to add custom label -->
|
|
||||||
|
|
||||||
<!-- TODO: Add error state (or mitigation) if new label is already in `custom[]` -->
|
|
||||||
|
|
||||||
<Pill
|
|
||||||
v-if="model.custom"
|
|
||||||
:key="componentKey"
|
|
||||||
v-model="additionalValue"
|
|
||||||
solid
|
|
||||||
no-underline
|
|
||||||
style="margin-right: 40px; height:32px; flex-grow: 1;"
|
|
||||||
/>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
</Layout>
|
</Layout>
|
||||||
</template>
|
</template>
|
||||||
|
@ -233,47 +139,14 @@ watch(model, () => {
|
||||||
// Compensation for round shapes -> https://en.wikipedia.org/wiki/Overshoot_(typography)
|
// Compensation for round shapes -> https://en.wikipedia.org/wiki/Overshoot_(typography)
|
||||||
margin: 0 -4px;
|
margin: 0 -4px;
|
||||||
|
|
||||||
padding:4px;
|
// padding: 4px;
|
||||||
border-radius: 22px;
|
border-radius: 22px;
|
||||||
|
|
||||||
min-height: 48px;
|
gap: 8px;
|
||||||
min-width: 160px;
|
|
||||||
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
> .pill {
|
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
&.static {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
&.preset {
|
|
||||||
&:is(:hover, :focus-visible) .pill-content {
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
.pill-content::after{
|
|
||||||
content:'×';
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.custom {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
.empty {
|
||||||
>.dropdown{
|
flex-grow: 1;
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
|
|
||||||
border-radius: 15px;
|
|
||||||
padding: 2px 11.25px;
|
|
||||||
text-align: right;
|
|
||||||
background: transparent;
|
|
||||||
appearance: auto;
|
|
||||||
margin-right: 12px;
|
|
||||||
|
|
||||||
// From vitepress default, needed for app
|
|
||||||
border: 0;
|
|
||||||
color: inherit;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:hover:has(select)>.list {
|
&:hover:has(select)>.list {
|
||||||
|
|
|
@ -1,38 +1,41 @@
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
import Pill from '~/components/ui/Pill.vue';
|
|
||||||
import Pills from '~/components/ui/Pills.vue';
|
import Pills from '~/components/ui/Pills.vue';
|
||||||
import Spacer from '~/components/ui/Spacer.vue';
|
|
||||||
import Layout from '~/components/ui/Layout.vue';
|
type Item = { type: 'custom' | 'preset', label: string }
|
||||||
import Input from '~/components/ui/Input.vue';
|
type Model = {
|
||||||
|
currents: Item[],
|
||||||
|
others?: Item[],
|
||||||
|
}
|
||||||
|
|
||||||
const nullModel = ref({
|
const nullModel = ref({
|
||||||
current: [],
|
currents: []
|
||||||
});
|
} satisfies Model)
|
||||||
|
|
||||||
const staticModel = ref({
|
const staticModel = ref({
|
||||||
current: ["#Noise", "#FieldRecording", "#Experiment"],
|
currents: [
|
||||||
});
|
{ label: "#Noise", type: 'preset' },
|
||||||
|
{ label: "#FieldRecording", type: 'preset' },
|
||||||
|
{ label: "#Experiment", type: 'preset' }
|
||||||
|
]
|
||||||
|
} satisfies Model);
|
||||||
|
|
||||||
const interactiveModel = ref({
|
const interactiveModel = ref({
|
||||||
current: ["#Noise", "#FieldRecording", "#Experiment"],
|
...staticModel.value,
|
||||||
others: ["#Melody", "#Rhythm"],
|
others: [
|
||||||
});
|
{ label: "#Melody", type: 'preset' },
|
||||||
|
{ label: "#Rhythm", type: 'preset' }
|
||||||
|
]
|
||||||
|
} satisfies Model);
|
||||||
|
|
||||||
const customModel = ref({
|
const customModel = ref({
|
||||||
current: ["custom", "#FieldRecording", "#Experiment"],
|
...staticModel.value,
|
||||||
others: ["#Melody", "#Rhythm"],
|
others: [
|
||||||
custom: ["custom"],
|
{ label: "#MyTag1", type: 'custom' },
|
||||||
});
|
{ label: "#MyTag2", type: 'custom' }
|
||||||
|
]
|
||||||
const search = ref()
|
} satisfies Model);
|
||||||
|
|
||||||
const countryInput = ref("")
|
|
||||||
|
|
||||||
const inputModel = ref({ input: "Custom", isEditing: false })
|
|
||||||
|
|
||||||
const update = (e)=>{console.log(e)}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
@ -46,133 +49,66 @@ Users can select a subset of given options and create new ones.
|
||||||
|
|
||||||
The model you provide will be mutated by this component:
|
The model you provide will be mutated by this component:
|
||||||
|
|
||||||
- `current`: these pills are currently selected
|
- `currents`: these items are currently selected
|
||||||
- `others`: these pills are currently not selected (but can be selected by the user). This prop is optional. By adding it, you allow users to change the selection.
|
- `others`: these items are currently not selected (but can be selected by the user). This prop is optional. By adding it, you allow users to change the selection.
|
||||||
- `custom`: these pills were created by the user. This prop is optional. Users can edit, add and remove any pill defined in this array. Note that the `custom` array should only contain pills that are either in `current` or in `others`.
|
|
||||||
|
|
||||||
::: warning
|
Each item has a `label` of type `string` as well as a `type` of either:
|
||||||
|
|
||||||
If you place custom pills into `others`, the user will be able to select, edit and delete them but not to deselect them. If there is a use case for this, we have to design a good UX for deselecting custom pills.
|
- `custom`: the user can edit its label or
|
||||||
|
- `preset`: the user cannot edit its label
|
||||||
:::
|
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
<label for="selectTag">
|
|
||||||
|
|
||||||
<b>Select tag</b>
|
|
||||||
|
|
||||||
<Layout flex gap-12 style="background:transparent;padding:12px; outline-inset: -4px; border-radius: 24px;">
|
|
||||||
|
|
||||||
<Pill>
|
|
||||||
#Pell
|
|
||||||
</Pill>
|
|
||||||
|
|
||||||
<input autocomplete="off" style="flex-grow: 1; min-width: 44px; flex-basis: 44px; padding: 12px 22px; margin: -12px; border-radius: inherit; outline: 1px solid red;" value="pill"></input>
|
|
||||||
|
|
||||||
<Pill>
|
|
||||||
VeryLongPill
|
|
||||||
</Pill>
|
|
||||||
|
|
||||||
<Pill>
|
|
||||||
VeryLongEvenLongerPill
|
|
||||||
</Pill>
|
|
||||||
|
|
||||||
<Pill v-model="inputModel"/>
|
|
||||||
|
|
||||||
<input id="selectTag" size="50" list="tags" autocomplete="off" style="flex-grow: 1; min-width: 44px; flex-basis: 44px; padding: 12px 22px; margin: -12px; border-radius: inherit; outline: 1px solid red;" @input="update">
|
|
||||||
</input>
|
|
||||||
|
|
||||||
<!-- https://www.sitepoint.com/html5-datalist-autocomplete/ -->
|
|
||||||
|
|
||||||
<datalist id="tags">
|
|
||||||
<option>Russia</option>
|
|
||||||
<option>Germany</option>
|
|
||||||
<option>UnitedKingdom</option>
|
|
||||||
</datalist>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
*:has(> input){
|
|
||||||
outline: 4px solid transparent;
|
|
||||||
}
|
|
||||||
*:has(> input:focus){
|
|
||||||
outline-color:var(--focus-ring-color)
|
|
||||||
}
|
|
||||||
input:focus+datalist {
|
|
||||||
position: absolute;
|
|
||||||
max-height: 20em;
|
|
||||||
border: 0 none;
|
|
||||||
overflow-x: hidden;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
datalist option {
|
|
||||||
font-size: 0.8em;
|
|
||||||
padding: 0.3em 1em;
|
|
||||||
background-color: #ccc;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
datalist option:hover, datalist option:focus {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #036;
|
|
||||||
outline: 0 none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
</label>
|
|
||||||
|
|
||||||
## No pills
|
## No pills
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const nullModel = ref({
|
const nullModel = ref({
|
||||||
current: []
|
currents: []
|
||||||
});
|
}) as { currents: Item[] };
|
||||||
```
|
```
|
||||||
|
|
||||||
```vue-html
|
```vue-html
|
||||||
<Pills v-model="nullModel" />
|
<Pills v-model="nullModel" />
|
||||||
```
|
```
|
||||||
|
|
||||||
<Layout class="preview" style="padding:16px">
|
<Pills v-model="nullModel" />
|
||||||
<Pills v-model="nullModel" />
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
## Predefined list of pills
|
## Predefined list of pills
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const staticModel = ref({
|
const staticModel = ref({
|
||||||
current: ["#Noise", "#FieldRecording", "#Experiment"]
|
currents: [
|
||||||
|
{ label: "#Noise", type: 'preset' },
|
||||||
|
{ label: "#FieldRecording", type: 'preset' },
|
||||||
|
{ label: "#Experiment", type: 'preset' }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```vue-html
|
```vue-html
|
||||||
<Pills v-model="staticModel" label="Tags" />
|
<Pills v-model="staticModel" label="Static Tags" />
|
||||||
```
|
```
|
||||||
|
|
||||||
<Layout class="preview" style="padding:16px">
|
<Pills v-model="staticModel" label="Static Tags" />
|
||||||
<Pills v-model="staticModel" label="Tags" />
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
## Let users select and unselect pills
|
## Let users select and unselect pills
|
||||||
|
|
||||||
Select a set of pills from presets, and add and remove custom ones
|
Select a set of pills from presets, and add and remove custom ones
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
|
||||||
const interactiveModel = ref({
|
const interactiveModel = ref({
|
||||||
current: ["#Noise", "#FieldRecording", "#Experiment"],
|
...staticModel,
|
||||||
others: ["#Melody", "#Rhythm"]
|
others: [
|
||||||
|
{ label: "#Melody", type: 'preset' },
|
||||||
|
{ label: "#Rhythm", type: 'preset' }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```vue-html
|
```vue-html
|
||||||
<Pills v-model="interactiveModel" label="Tags" />
|
<Pills v-model="interactiveModel" label="Interactive Tags" />
|
||||||
```
|
```
|
||||||
|
|
||||||
<Layout class="preview" style="padding:16px">
|
<Pills v-model="interactiveModel" label="Interactive Tags" />
|
||||||
<Pills v-model="interactiveModel" label="Tags" />
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
## Let users add, remove and edit custom pills
|
## Let users add, remove and edit custom pills
|
||||||
|
|
||||||
|
@ -180,48 +116,16 @@ Use [reactive](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#r
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
const customModel = ref({
|
const customModel = ref({
|
||||||
current: ["custom", "#FieldRecording", "#Experiment"],
|
...staticModel,
|
||||||
others: ["#Melody", "#Rhythm"],
|
others: [
|
||||||
custom: ["custom"]
|
{ label: "#MyTag1", type: 'custom' },
|
||||||
|
{ label: "#MyTag2", type: 'custom' }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
```vue-html
|
```vue-html
|
||||||
<Pills v-model="customModel" label="Custom" />
|
<Pills v-model="customModel" label="Custom Tags" />
|
||||||
```
|
```
|
||||||
|
|
||||||
<Layout class="preview" style="padding:16px">
|
<Pills v-model="customModel" label="Custom Tags" />
|
||||||
<Pills v-model="customModel" label="Custom" />
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
## Combine Pills with other input fields
|
|
||||||
|
|
||||||
<Spacer />
|
|
||||||
<Layout form flex>
|
|
||||||
<Input
|
|
||||||
v-model="search"
|
|
||||||
label="Search"
|
|
||||||
style="max-width: 150px;"
|
|
||||||
/>
|
|
||||||
<Pills
|
|
||||||
v-model="customModel"
|
|
||||||
label="Filter by tags"
|
|
||||||
style="max-width: 250px;"
|
|
||||||
/>
|
|
||||||
<Layout stack noGap label>
|
|
||||||
<span class="label"> Ordering </span>
|
|
||||||
<select>
|
|
||||||
<option
|
|
||||||
v-for="key in ['by date', 'by duration']"
|
|
||||||
:value="key"
|
|
||||||
>
|
|
||||||
key
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</Layout>
|
|
||||||
<Input
|
|
||||||
v-model="search"
|
|
||||||
label="Option"
|
|
||||||
style="max-width: 50px;"
|
|
||||||
/>
|
|
||||||
</Layout>
|
|
||||||
|
|
Loading…
Reference in New Issue