feat(ui): editable list of pills
This commit is contained in:
parent
89d8ee5b9e
commit
a068792306
|
@ -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">
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
Loading…
Reference in New Issue