feat(ui): editable list of pills

This commit is contained in:
upsiflu 2025-01-04 12:01:02 +01:00
parent 89d8ee5b9e
commit a068792306
6 changed files with 159 additions and 6 deletions

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { type ColorProps, type PastelProps, color } from '~/composables/color'
import { type ColorProps, type PastelProps, type VariantProps, color } from '~/composables/color'
const emit = defineEmits<{
click: [event: MouseEvent]
@ -7,13 +7,14 @@ const emit = defineEmits<{
const handleClick = (event: MouseEvent) => {
emit('click', event)
}
const props = defineProps<PastelProps | ColorProps>()
const props = defineProps<{ noUnderline?:true } & (PastelProps | ColorProps) & VariantProps>()
</script>
<template>
<button
class="funkwhale pill outline"
v-bind="color(props, ['interactive'])()"
class="funkwhale pill"
:class="props.noUnderline && 'no-underline'"
v-bind="color(props, ['interactive', 'outline'])()"
@click.stop="handleClick"
>
<div v-if="!!$slots.image" class="pill-image">

View File

@ -0,0 +1,123 @@
<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 './layout/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
style="padding:4px; border-radius: 18px;"
v-bind="color({}, ['solid', 'default', 'raised'])()"
:class="$style.list"
>
<Pill solid v-for="value in model.current" v-if="!model.others">
<span>{{ value }}</span>
</Pill>
<Pill solid no-underline @click="deselectPill(value)" v-for="value in model.current" v-if="model.others">
<span :class="$style['pill-content']">{{ value }}</span>   ×
</Pill>
<Spacer h grow />
<!-- 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>
.pills {
>.label {
margin-top: -18px;
padding-bottom: 8px;
font-size:14px;
font-weight:600;
}
>.list{
>:is(:hover, :focus-visible) .pill-content {
text-decoration: line-through !important;
}
>.dropdown{
border-radius: 15px;
padding: 2px 11.25px;
width:30px;
}
>.dropdown:focus-visible {
outline: 3px solid var(--fw-secondary);
outline-offset: 2px;
}
}
}
</style>

View File

@ -16,7 +16,6 @@
font-size: small;
border-radius: 100vh;
margin: 0 0.5ch;
> .pill-content {
padding: 0.5em 0.75em;
@ -45,7 +44,7 @@
}
}
&:hover {
&:hover:not(.no-underline) {
text-decoration: underline;
}

View File

@ -60,6 +60,7 @@ export default defineConfig({
{ text: 'Loader', link: '/components/ui/loader' },
{ text: 'Modal', link: '/components/ui/modal' },
{ text: 'Pill', link: '/components/ui/pill' },
{ text: 'List of pills', link: '/components/ui/pills' },
],
},
],

View File

@ -12,6 +12,8 @@ You can add text to pills by adding it between the `<Pill>` tags.
| ------- | ----------------------------------------------------------------------------------------------- | --------- | ----------- | ---------------------- |
| `color` | `primary` \| `secondary` \| `destructive` \| `blue` \| `red` \| `purple` \| `green` \| `yellow` | No | `secondary` | Renders a colored pill |
-> [Let the user create lists of pills](./pills)
### Primary
Primary pills convey **positive** information.

View File

@ -0,0 +1,27 @@
<script setup>
import { computed, ref } from 'vue'
import Pills from '~/components/ui/Pills.vue';
import Layout from '~/components/ui/Layout.vue';
const staticModel = ref({
current: ["Noise", "Field Recording", "Experiment"]
});
const interactiveModel = ref({
current: ["Noise", "Field Recording", "Experiment"],
others: ["Melody", "Rhythm"]
});
</script>
# Pills
<Layout class="preview" style="padding:16px">
<Pills v-model="staticModel" label="Tags" />
</Layout>
Select a set of pills from presets, and add and remove custom ones
<Layout class="preview" style="padding:16px">
<Pills v-model="interactiveModel" label="Tags" />
</Layout>