228 lines
5.3 KiB
Markdown
228 lines
5.3 KiB
Markdown
<script setup>
|
|
import { computed, ref } from 'vue'
|
|
|
|
import Pill from '~/components/ui/Pill.vue';
|
|
import Pills from '~/components/ui/Pills.vue';
|
|
import Spacer from '~/components/ui/Spacer.vue';
|
|
import Layout from '~/components/ui/Layout.vue';
|
|
import Input from '~/components/ui/Input.vue';
|
|
|
|
const nullModel = ref({
|
|
current: [],
|
|
});
|
|
|
|
const staticModel = ref({
|
|
current: ["#Noise", "#FieldRecording", "#Experiment"],
|
|
});
|
|
|
|
const interactiveModel = ref({
|
|
current: ["#Noise", "#FieldRecording", "#Experiment"],
|
|
others: ["#Melody", "#Rhythm"],
|
|
});
|
|
|
|
const customModel = ref({
|
|
current: ["custom", "#FieldRecording", "#Experiment"],
|
|
others: ["#Melody", "#Rhythm"],
|
|
custom: ["custom"],
|
|
});
|
|
|
|
const search = ref()
|
|
|
|
const countryInput = ref("")
|
|
|
|
const inputModel = ref({ input: "Custom", isEditing: false })
|
|
|
|
const update = (e)=>{console.log(e)}
|
|
</script>
|
|
|
|
```ts
|
|
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:
|
|
|
|
- `current`: these pills 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.
|
|
- `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
|
|
|
|
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.
|
|
|
|
:::
|
|
|
|
## 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
|
|
|
|
```ts
|
|
const nullModel = ref({
|
|
current: []
|
|
});
|
|
```
|
|
|
|
```vue-html
|
|
<Pills v-model="nullModel" />
|
|
```
|
|
|
|
<Layout class="preview" style="padding:16px">
|
|
<Pills v-model="nullModel" />
|
|
</Layout>
|
|
|
|
## Predefined list of pills
|
|
|
|
```ts
|
|
const staticModel = ref({
|
|
current: ["#Noise", "#FieldRecording", "#Experiment"]
|
|
});
|
|
```
|
|
|
|
```vue-html
|
|
<Pills v-model="staticModel" label="Tags" />
|
|
```
|
|
|
|
<Layout class="preview" style="padding:16px">
|
|
<Pills v-model="staticModel" label="Tags" />
|
|
</Layout>
|
|
|
|
## Let users select and unselect pills
|
|
|
|
Select a set of pills from presets, and add and remove custom ones
|
|
|
|
```ts
|
|
const interactiveModel = ref({
|
|
current: ["#Noise", "#FieldRecording", "#Experiment"],
|
|
others: ["#Melody", "#Rhythm"]
|
|
});
|
|
```
|
|
|
|
```vue-html
|
|
<Pills v-model="interactiveModel" label="Tags" />
|
|
```
|
|
|
|
<Layout class="preview" style="padding:16px">
|
|
<Pills v-model="interactiveModel" label="Tags" />
|
|
</Layout>
|
|
|
|
## Let users add, remove and edit custom pills
|
|
|
|
Use [reactive](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#reactive-variables-with-ref) methods [such as `computed(...)`](https://vuejs.org/guide/essentials/computed.html) and `watch(...)` to query the model.
|
|
|
|
```ts
|
|
const customModel = ref({
|
|
current: ["custom", "#FieldRecording", "#Experiment"],
|
|
others: ["#Melody", "#Rhythm"],
|
|
custom: ["custom"]
|
|
});
|
|
```
|
|
|
|
```vue-html
|
|
<Pills v-model="customModel" label="Custom" />
|
|
```
|
|
|
|
<Layout class="preview" style="padding:16px">
|
|
<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>
|