diff --git a/frontend/src/components/TagsSelect.vue b/frontend/src/components/TagsSelect.vue
index 402b084..996c2c3 100644
--- a/frontend/src/components/TagsSelect.vue
+++ b/frontend/src/components/TagsSelect.vue
@@ -8,12 +8,14 @@ export type TagsSelectOption = {
id: number | string;
name: string;
label?: string;
+ thumbnailUrl?: string | null;
};
type OptionRow = {
value: string;
label: string;
id: string;
+ thumbnailUrl: string | null;
};
type CandidateRow = { type: 'option'; id: string; value: string; label: string } | { type: 'create'; id: string };
@@ -65,7 +67,8 @@ const optionRows = computed(() =>
props.options.map((option, index) => ({
value: String(option.id),
label: option.label ?? option.name,
- id: `${props.id}-option-${index}`
+ id: `${props.id}-option-${index}`,
+ thumbnailUrl: option.thumbnailUrl ?? null
}))
);
@@ -79,9 +82,10 @@ const maxReached = computed(() => props.multiple && props.max > 0 && modelValues
const selectedRows = computed(() =>
modelValues.value
.map((value) => optionRows.value.find((option) => option.value === value))
- .filter((option) => option !== undefined)
+ .filter((option): option is OptionRow => option !== undefined)
);
const selectedLabel = computed(() => selectedRows.value[0]?.label ?? '');
+const selectedThumbnailUrl = computed(() => selectedRows.value[0]?.thumbnailUrl ?? '');
const filteredRows = computed(() => {
const keyword = search.value.trim().toLowerCase();
@@ -360,6 +364,7 @@ watch(
+
{{ option.label }}
- {{ selectedLabel }}
+
+
+ {{ selectedLabel }}
+
{{ placeholderText }}
@@ -417,7 +425,10 @@ watch(
:disabled="!selectedValues.has(option.value) && maxReached"
@click="selectOption(option.value)"
>
- {{ option.label }}
+
+
+ {{ option.label }}
+
{{ t('common.selected') }}
diff --git a/frontend/src/styles/main.css b/frontend/src/styles/main.css
index 059f992..b917626 100644
--- a/frontend/src/styles/main.css
+++ b/frontend/src/styles/main.css
@@ -2041,10 +2041,18 @@ button:disabled,
}
.tags-select__single-value {
- display: block;
+ display: flex;
+ align-items: center;
+ gap: 8px;
min-width: 0;
+ max-width: 100%;
overflow: hidden;
color: var(--ink);
+}
+
+.tags-select__single-value span {
+ min-width: 0;
+ overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@@ -2054,6 +2062,7 @@ button:disabled,
align-items: center;
justify-content: center;
gap: 6px;
+ min-width: 0;
min-height: 28px;
padding: 4px 8px;
border: 1px solid rgba(42, 117, 187, 0.28);
@@ -2064,6 +2073,27 @@ button:disabled,
font-weight: 850;
}
+.tags-select__tag > span:first-of-type {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.tags-select__thumb {
+ flex: 0 0 auto;
+ width: 28px;
+ height: 28px;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-small);
+ background: var(--surface-soft);
+ object-fit: contain;
+}
+
+.tags-select__thumb--tag {
+ width: 20px;
+ height: 20px;
+}
+
.tags-select__remove {
min-width: 18px;
min-height: 18px;
@@ -2145,6 +2175,20 @@ button:disabled,
cursor: pointer;
}
+.tags-select__option-label {
+ display: flex;
+ align-items: center;
+ flex: 1 1 auto;
+ gap: 8px;
+ min-width: 0;
+}
+
+.tags-select__option-label span {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
.tags-select__option:hover,
.tags-select__option.active,
.tags-select__option.selected {
diff --git a/frontend/src/views/AdminView.vue b/frontend/src/views/AdminView.vue
index af58826..23e3e8d 100644
--- a/frontend/src/views/AdminView.vue
+++ b/frontend/src/views/AdminView.vue
@@ -371,7 +371,9 @@ const dishModalTitle = computed(() => (dishForm.value.id ? t('pages.dish.editDis
const dishRows = computed(() => dishCategoryRows.value.flatMap((category) => category.dishes));
const selectedDishFormCategory = computed(() => dishCategoryRows.value.find((category) => String(category.id) === dishForm.value.categoryId) ?? null);
const dishAllowsSecondSecondaryMaterial = computed(() => (selectedDishFormCategory.value?.totalMaterialQuantity ?? 0) > 2);
-const dishItemSelectOptions = computed(() => dishItemRows.value.map((item) => ({ id: item.id, name: item.name })));
+const dishItemSelectOptions = computed(() =>
+ dishItemRows.value.map((item) => ({ id: item.id, name: item.name, thumbnailUrl: item.image?.url }))
+);
const optionalDishItemSelectOptions = computed(() => [{ id: '', name: t('common.none') }, ...dishItemSelectOptions.value]);
const dishCategorySelectOptions = computed(() =>
dishCategoryRows.value.map((category) => ({ id: category.id, name: category.name }))
diff --git a/frontend/src/views/DishView.vue b/frontend/src/views/DishView.vue
index 2597e7e..41cfb7b 100644
--- a/frontend/src/views/DishView.vue
+++ b/frontend/src/views/DishView.vue
@@ -73,7 +73,7 @@ const dishCategoryModalTitle = computed(() =>
);
const dishModalTitle = computed(() => (dishForm.value.id ? t('pages.dish.editDish') : t('pages.dish.newDish')));
const itemSelectOptions = computed(() =>
- items.value.map((item) => ({ id: item.id, name: item.name }))
+ items.value.map((item) => ({ id: item.id, name: item.name, thumbnailUrl: item.image?.url }))
);
const optionalItemSelectOptions = computed(() => [{ id: '', name: t('common.none') }, ...itemSelectOptions.value]);
const categorySelectOptions = computed(() => categories.value.map((category) => ({ id: category.id, name: category.name })));
diff --git a/frontend/src/views/HabitatEdit.vue b/frontend/src/views/HabitatEdit.vue
index 88ab458..928e6c6 100644
--- a/frontend/src/views/HabitatEdit.vue
+++ b/frontend/src/views/HabitatEdit.vue
@@ -73,9 +73,9 @@ const weatherOptions = computed(() => [
const routeId = computed(() => (typeof route.params.id === 'string' ? route.params.id : ''));
const isEditing = computed(() => routeId.value !== '');
const isEventCreate = computed(() => route.name === 'event-habitat-new');
-const itemSelectOptions = computed(() => itemRows.value.map((item) => ({ id: item.id, name: item.name })));
+const itemSelectOptions = computed(() => itemRows.value.map((item) => ({ id: item.id, name: item.name, thumbnailUrl: item.image?.url })));
const pokemonSelectOptions = computed(() =>
- pokemonRows.value.map((pokemon) => ({ id: pokemon.id, name: pokemon.name, label: `#${pokemon.displayId} ${pokemon.name}` }))
+ pokemonRows.value.map((pokemon) => ({ id: pokemon.id, name: pokemon.name, label: `#${pokemon.displayId} ${pokemon.name}`, thumbnailUrl: pokemon.image?.url }))
);
const pageTitle = computed(() =>
isEditing.value
diff --git a/frontend/src/views/PokemonEdit.vue b/frontend/src/views/PokemonEdit.vue
index 7117e55..f55c7bb 100644
--- a/frontend/src/views/PokemonEdit.vue
+++ b/frontend/src/views/PokemonEdit.vue
@@ -9,7 +9,7 @@ import PokemonStatsFields from '../components/PokemonStatsFields.vue';
import Skeleton from '../components/Skeleton.vue';
import StatusMessage from '../components/StatusMessage.vue';
import Tabs from '../components/Tabs.vue';
-import TagsSelect from '../components/TagsSelect.vue';
+import TagsSelect, { type TagsSelectOption } from '../components/TagsSelect.vue';
import TranslationFields from '../components/TranslationFields.vue';
import { iconCancel, iconSave, iconSearch } from '../icons';
import {
@@ -19,7 +19,6 @@ import {
type EntityImage,
type EntityImageUpload,
type Language,
- type NamedEntity,
type Options,
type PokemonFetchOption,
type PokemonFetchResult,
@@ -39,7 +38,7 @@ const route = useRoute();
const router = useRouter();
const { locale, t } = useI18n();
const options = ref(null);
-const itemOptions = ref([]);
+const itemOptions = ref([]);
const languages = ref([]);
const currentUser = ref(null);
const loading = ref(true);
@@ -189,7 +188,7 @@ function errorText(error: unknown, fallback: string) {
async function loadOptions() {
const [loadedOptions, loadedItems, loadedLanguages] = await Promise.all([api.options(), api.items({}), api.languages()]);
options.value = loadedOptions;
- itemOptions.value = loadedItems.map((item) => ({ id: item.id, name: item.name }));
+ itemOptions.value = loadedItems.map((item) => ({ id: item.id, name: item.name, thumbnailUrl: item.image?.url }));
languages.value = loadedLanguages;
}
diff --git a/frontend/src/views/RecipeEdit.vue b/frontend/src/views/RecipeEdit.vue
index 651b028..0eec717 100644
--- a/frontend/src/views/RecipeEdit.vue
+++ b/frontend/src/views/RecipeEdit.vue
@@ -28,11 +28,11 @@ const recipeForm = ref({
const routeId = computed(() => (typeof route.params.id === 'string' ? route.params.id : ''));
const isEditing = computed(() => routeId.value !== '');
-const materialItemOptions = computed(() => itemRows.value.map((item) => ({ id: item.id, name: item.name })));
+const materialItemOptions = computed(() => itemRows.value.map((item) => ({ id: item.id, name: item.name, thumbnailUrl: item.image?.url })));
const resultItemOptions = computed(() =>
itemRows.value
.filter((item) => !item.noRecipe || String(item.id) === recipeForm.value.itemId)
- .map((item) => ({ id: item.id, name: item.name }))
+ .map((item) => ({ id: item.id, name: item.name, thumbnailUrl: item.image?.url }))
);
const selectedItemName = computed(() => resultItemOptions.value.find((item) => String(item.id) === recipeForm.value.itemId)?.name ?? '');
const pageTitle = computed(() =>