PronounsPage/components/LegendOpinionListInput.vue

115 lines
3.8 KiB
Vue

<script setup lang="ts">
import opinions from '../src/opinions.ts';
import type { Opinion } from '../src/opinions.ts';
import { colours, styles } from '../src/styling.ts';
const props = defineProps<{
readonly?: boolean;
maxitems?: number | undefined;
}>();
const modelValue = defineModel<Opinion[]>({ required: true });
const showIconSelector = ref(false);
const prototype = () => {
return { icon: '', description: '', colour: '', style: '' };
};
const skipIcons = [...Object.values(opinions).map((op) => op.icon), 'ad', 'helicopter', 'meh'];
const validation = (v: Opinion) => {
if (JSON.stringify(v) === JSON.stringify(prototype())) {
return null;
}
if (!v.icon) {
return 'profile.opinions.validation.missingIcon';
}
if (!v.description) {
return 'profile.opinions.validation.missingDescription';
}
if (modelValue.value.filter((el) => el.icon === v.icon).length > 1) {
return 'profile.opinions.validation.duplicateIcon';
}
if (modelValue.value.filter((el) => el.description === v.description).length > 1) {
return 'profile.opinions.validation.duplicateDescription';
}
if (v.description.match(/\bkys\b/i) || v.description.match(/\bkill\b/i)) {
return 'profile.opinions.validation.kys';
}
return null;
};
</script>
<template>
<ListInput v-model="modelValue" :prototype="prototype()" :readonly="readonly" :maxitems="maxitems">
<template #default="s">
<button
type="button"
:class="['btn', props.readonly ? 'btn-light border' : 'btn-outline-secondary', showIconSelector === s.i ? 'btn-secondary text-white border' : '']"
:disabled="readonly"
@click="showIconSelector = showIconSelector === s.i ? false : s.i"
>
<Icon :v="s.val.icon" />
</button>
<input
v-model="s.val.description"
class="form-control"
:readonly="readonly"
required
maxlength="36"
:placeholder="$t('profile.opinions.description')"
@keyup="s.update(s.val)"
@paste="$nextTick(() => s.update(s.val))"
@change="s.update(s.val)"
>
<select v-model="s.val.colour" :class="['form-control', s.val.colour ? `colour-${s.val.colour}` : 'text-muted']" :disabled="readonly" @change="s.update(s.val)">
<option v-for="colour in colours" :value="colour">
{{ $t(`profile.opinions.colours.${colour || '_'}`) }}
</option>
</select>
<select v-model="s.val.style" :class="['form-control', s.val.style || 'text-muted']" :disabled="readonly" @change="s.update(s.val)">
<option v-for="st in styles" :value="st">
{{ $t(`profile.opinions.styles.${st || '_'}`) }}
</option>
</select>
<IconSelector
v-if="showIconSelector === s.i"
class="hanging shadow shadow-lg border"
:skip-icons="skipIcons"
@change="(icon) => {
s.update({ ...s.val, icon });
showIconSelector = false;
}"
/>
</template>
<template #validation="s">
<p v-if="validation(s.val)" class="small text-danger">
<Icon v="exclamation-triangle" />
<span class="ml-1">{{ $t(validation(s.val)!) }}</span>
</p>
</template>
</ListInput>
</template>
<style lang="scss" scoped>
@import "assets/variables";
// TODO
select > option.small {
font-size: $small-font-size !important;
}
.hanging {
position: absolute;
top: 100%;
left: 0;
width: 100%;
max-width: 500px;
z-index: 5000;
}
</style>