feat(ui): editable list of pills
This commit is contained in:
parent
89d8ee5b9e
commit
a068792306
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<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<{
|
const emit = defineEmits<{
|
||||||
click: [event: MouseEvent]
|
click: [event: MouseEvent]
|
||||||
|
@ -7,13 +7,14 @@ const emit = defineEmits<{
|
||||||
const handleClick = (event: MouseEvent) => {
|
const handleClick = (event: MouseEvent) => {
|
||||||
emit('click', event)
|
emit('click', event)
|
||||||
}
|
}
|
||||||
const props = defineProps<PastelProps | ColorProps>()
|
const props = defineProps<{ noUnderline?:true } & (PastelProps | ColorProps) & VariantProps>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
class="funkwhale pill outline"
|
class="funkwhale pill"
|
||||||
v-bind="color(props, ['interactive'])()"
|
:class="props.noUnderline && 'no-underline'"
|
||||||
|
v-bind="color(props, ['interactive', 'outline'])()"
|
||||||
@click.stop="handleClick"
|
@click.stop="handleClick"
|
||||||
>
|
>
|
||||||
<div v-if="!!$slots.image" class="pill-image">
|
<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;
|
font-size: small;
|
||||||
|
|
||||||
border-radius: 100vh;
|
border-radius: 100vh;
|
||||||
margin: 0 0.5ch;
|
|
||||||
|
|
||||||
> .pill-content {
|
> .pill-content {
|
||||||
padding: 0.5em 0.75em;
|
padding: 0.5em 0.75em;
|
||||||
|
@ -45,7 +44,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover:not(.no-underline) {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,7 @@ export default defineConfig({
|
||||||
{ text: 'Loader', link: '/components/ui/loader' },
|
{ text: 'Loader', link: '/components/ui/loader' },
|
||||||
{ text: 'Modal', link: '/components/ui/modal' },
|
{ text: 'Modal', link: '/components/ui/modal' },
|
||||||
{ text: 'Pill', link: '/components/ui/pill' },
|
{ 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 |
|
| `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
|
||||||
|
|
||||||
Primary pills convey **positive** information.
|
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