funkwhale/front/ui-docs/components/ui/pills.md

6.8 KiB

import Pills from "~/components/ui/Pills.vue"

Pills

Show a dense list of pills representing tags, categories or options. Users can select a subset of given options and create new ones.

The model you provide will be mutated by this component:

  • currents: these items are currently selected
  • 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.

Each item has a label of type string as well as a type of either:

  • custom: the user can edit its label or
  • preset: the user cannot edit its label
type Item = { type: 'custom' | 'preset', label: string }
type Model = {
  currents: Item[],
  others?: Item[],
}

No pills

const nullModel = ref({
  currents: []
}) satisfies Model;
<Pills v-model="nullModel" />

Static list of pills

const staticModel = ref({
  currents: [
    { label: "#noise", type: 'preset' },
    { label: "#fieldRecording", type: 'preset' },
    { label: "#experiment", type: 'preset' }
  ]
} satisfies Model);
<Pills
  :get="(v) => { return }"
  :set="() => staticModel"
/>

Let users add, remove and edit custom pills

By adding custom options, you make the Pills instance interactive. Use reactive methods such as computed(...) and watch(...) to bind the model.

Note that this component will automatically add an empty pill to the end of the model because it made the implementation more straightforward. Use `filter(({ label }) => label !== '') to ignore it when reading the model.

Minimal example

const simpleCustomModel = ref({
  currents: [],
  others: []
})
<Pills
  :get="(v) => { simpleCustomModel = v }"
  :set="() => staticModel"
/>

Complex example

const customModel = ref({
  ...staticModel,
  others: [
    { label: "#MyTag1", type: 'custom' },
    { label: "#MyTag2", type: 'custom' }
  ]
} satisfies Model);
<Pills
  :get="(v) => { customModel = v }"
  :set="() => customModel"
  label="Custom Tags"
  cancel="Cancel"
/>

Bind data with an external sink

In the following example, others are shared among two Pills lists.

const sharedOthers = ref<Model['others']>(customModel.value.others)
const currentA = ref<Model['currents']>([{ label: 'A', type: 'preset' }])
const currentB = ref<Model['currents']>([])

const updateSharedOthers = (others: Item[]) => {
  sharedOthers.value
    = unionBy(sharedOthers.value, others, 'label')
    .filter(item =>
      [...currentA.value, ...currentB.value].every(({ label }) =>
        item.label !== label
    ))
}

<template v-for="_ in [1]" :key="[...sharedOthers].join(',')"

 Shared among A and B:
    {{ (sharedOthers || []).map(({ label }) => label).join(', ') }}
  

Bind data with an external source

You can use the same pattern to influence the model from an outside source:

const tags = ref<string[]>(['1', '2'])
const sharedTags = ref<string[]>(['3'])
const setTags = (v: string[]) => {
  sharedTags.value
    = [...tags.value, ...sharedTags.value].filter(tag => !(v.includes(tag)))
  tags.value
    = v
}

<Layout flex gap-8

<Button secondary @click="setTags([])"

Set tags=[]
Set tags=['1', '2', '3']
{{ tags.join(', ') }}
{{ sharedTags.join(', ') }}