diff --git a/front/src/components/ui/Pill.vue b/front/src/components/ui/Pill.vue index 9bf4ad51b..9eced4b68 100644 --- a/front/src/components/ui/Pill.vue +++ b/front/src/components/ui/Pill.vue @@ -8,6 +8,12 @@ import Input from './Input.vue' import Popover from './Popover.vue' import PopoverItem from './popover/PopoverItem.vue' +/* Event */ + +const emit = defineEmits<{ + changed: [] +}>() + /* Model */ const props = defineProps<{ @@ -51,6 +57,8 @@ watch(isEditing, (isTrue, wasTrue) => { model.value.current = { ...matchInOthers } } model.value.others = model.value.others.filter(({ label }) => label !== model.value?.current.label) + + emit('changed') } }) @@ -249,7 +257,7 @@ const current = computed(() => (
- {{ model?.current?.label }} + {{ model?.current?.label }} ​ -import { ref, watchEffect, watch, computed } from 'vue' +import { ref, computed, watch } from 'vue' import { color } from '~/composables/color' @@ -13,17 +13,13 @@ const props = defineProps<{ }>() const model = defineModel<{ - current: string[], - others?: string[], - custom?: string[], + currents: Item[], + others?: Item[], }>({ required: true }) -const whenInteractive = (then:() => void) => { - if (!model.value.others) return; then() -} +type Item = { type: 'custom' | 'preset', label: string } -const editingValue = ref('') -const additionalValue = ref('') +// Manually trigger rerendering const componentKey = ref(0) const forceRerender = () => componentKey.value++ @@ -31,99 +27,51 @@ const isStatic = computed(() => !model.value.others ) -const selectedLabel = ref('+') +const emptyItem = { + label: '', type: 'custom' +} as const -// Dropdown changed -> select label +const pills = computed({ -whenInteractive(() => - watchEffect(() => { - if (!model.value.others) return - const newLabel = selectedLabel.value - 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) - } - }) -) - -// Pill clicked --> edit or unselect label - -const pillClicked = (value: string) => { - model.value.custom?.includes(value) - ? edit(value) - : unselect(value) -} - -const edit = (value: string) => { - editingValue.value = value -} - -const unselect = (value: string) => { - model.value = { - ...model.value, - current: model.value.current.filter(v => v !== value), - others: [value, ...(model.value.others || [])] + get: () => { + console.log("OTHERS", model.value.others) + return [...model.value.currents, { ...emptyItem }].map( + (item) => ({ current: { ...item }, others: model.value.others ? model.value.others.map(item => ({ ...item })) : [] }) + ) + }, + set: (pills) => { + console.log("SETTING PILLS", pills) + model.value.currents = pills + .filter(({ current }) => current.label !== '') + .map(({ current }) => ({ ...current })) } -} +}) -// Editing value changed --> remove, add or replace a label +const changed = (index: number, pill: (typeof pills.value)[number]) => { + /*reduce( + callbackfn: (previousValue: U, currentValue: Item, currentIndex: number, array: Item[]) => U, + initialValue: U): U */ -const remove = (value: string) => { - model.value = { - ...model.value, - current: model.value.current.filter(v => v !== value), - custom: model.value.custom?.filter(v => v !== value) - } -} + console.log("NEW: #", index, "=", pills.value[index].current) + console.log("OLD: #", index, "=", pill.current) + // model.value.currents.push({ ...emptyItem }) + // console.log(model.value.currents.length) -const add = (value: string) => { - if (model.value.current.includes(value)) return - model.value = { - ...model.value, - current: [...model.value.current, value], - custom: [...(model.value.custom || []), value] - } - additionalValue.value = '' + model.value.currents[index] = { ...pills.value[index].current } + model.value.others = { ...pills.value[index].others } - // We have to force rerender because else, Vue keeps the previous additionalValue for the new "additionalValue" input pill :-( forceRerender() } -const replace = (value: string) => { - model.value = { - ...model.value, - current: model.value.current.map(v => v === value ? editingValue.value : v), - custom: model.value.custom?.map(v => v === value ? editingValue.value : v) - } -} - -watch(editingValue, (newValue, oldValue) => { - if (oldValue === '') return - if (newValue === '') { - remove(oldValue) - } else { - replace(oldValue) - } +// Add empty current item if none is inside +watch( model, () => { + console.log("MODEL CHANGED", model.value) }) -watch(additionalValue, (newValue, oldValue) => { - if (newValue !== '') { - additionalValue.value = '' - add(newValue) - } -}) +// Remove duplicates on each CONFIRM -// Remove duplicates - -const unique = (a:string[]) => [...new Set(a)] - -watch(model, () => { - model.value.current = unique(model.value.current) - model.value.others = model.value.others ? unique(model.value.others) : undefined - model.value.custom = model.value.custom ? unique(model.value.custom) : undefined -}) +// const unique = (list:Item[]) => +// list.reduce((acc, a) => ([a, ...acc]), []) @@ -233,47 +139,14 @@ watch(model, () => { // Compensation for round shapes -> https://en.wikipedia.org/wiki/Overshoot_(typography) margin: 0 -4px; - padding:4px; + // padding: 4px; border-radius: 22px; - min-height: 48px; - min-width: 160px; + gap: 8px; + padding: 2px; - gap: 4px; - - > .pill { - padding: 2px; - &.static { - text-decoration: none; - } - &.preset { - &:is(:hover, :focus-visible) .pill-content { - text-decoration: line-through; - } - .pill-content::after{ - content:'×'; - margin-left: 8px; - } - } - &.custom { - text-decoration: none; - } - - } - >.dropdown{ - position: absolute; - inset: 0; - - border-radius: 15px; - padding: 2px 11.25px; - text-align: right; - background: transparent; - appearance: auto; - margin-right: 12px; - - // From vitepress default, needed for app - border: 0; - color: inherit; + .empty { + flex-grow: 1; } } &:hover:has(select)>.list { diff --git a/front/ui-docs/components/ui/pills.md b/front/ui-docs/components/ui/pills.md index 63cd8ce89..d6966545b 100644 --- a/front/ui-docs/components/ui/pills.md +++ b/front/ui-docs/components/ui/pills.md @@ -1,38 +1,41 @@ - ```ts @@ -46,133 +49,66 @@ 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`. +- `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. -::: warning +Each item has a `label` of type `string` as well as a `type` of either: -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 - - +- `custom`: the user can edit its label or +- `preset`: the user cannot edit its label ## No pills ```ts const nullModel = ref({ - current: [] -}); + currents: [] +}) as { currents: Item[] }; ``` ```vue-html ``` - - - + ## Predefined list of pills ```ts const staticModel = ref({ - current: ["#Noise", "#FieldRecording", "#Experiment"] + currents: [ + { label: "#Noise", type: 'preset' }, + { label: "#FieldRecording", type: 'preset' }, + { label: "#Experiment", type: 'preset' } + ] }); ``` ```vue-html - + ``` - - - + ## 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"] + ...staticModel, + others: [ + { label: "#Melody", type: 'preset' }, + { label: "#Rhythm", type: 'preset' } + ] }); ``` ```vue-html - + ``` - - - + ## Let users add, remove and edit custom pills @@ -180,48 +116,16 @@ Use [reactive](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#r ```ts const customModel = ref({ - current: ["custom", "#FieldRecording", "#Experiment"], - others: ["#Melody", "#Rhythm"], - custom: ["custom"] + ...staticModel, + others: [ + { label: "#MyTag1", type: 'custom' }, + { label: "#MyTag2", type: 'custom' } + ] }); ``` ```vue-html - + ``` - - - - -## Combine Pills with other input fields - - - - - - - Ordering - - - - +