feat: surface meal taxonomy chips
This commit is contained in:
+21
-5
@@ -94,6 +94,24 @@ function optionHtml(options, selected) {
|
||||
return options.map(([value, label]) => `<option value="${esc(value)}" ${value === selected ? 'selected' : ''}>${esc(label)}</option>`).join('');
|
||||
}
|
||||
|
||||
function optionLabel(options, value) {
|
||||
return options.find(([optionValue]) => optionValue === value)?.[1] || value || '';
|
||||
}
|
||||
|
||||
function renderTaxonomyChip(kind, value, label) {
|
||||
if (!value || !label) return '';
|
||||
return `<span class="meal-taxonomy-chip meal-taxonomy-chip--${esc(kind)}" data-meal-taxonomy="${esc(kind)}" data-taxonomy-value="${esc(value)}">${esc(label)}</span>`;
|
||||
}
|
||||
|
||||
function renderMealTaxonomyChips(meal) {
|
||||
return [
|
||||
meal.meal_category ? renderTaxonomyChip('category', meal.meal_category, optionLabel(MEAL_CATEGORY_OPTIONS, meal.meal_category)) : '',
|
||||
meal.protein ? renderTaxonomyChip('protein', meal.protein, optionLabel(PROTEIN_OPTIONS, meal.protein)) : '',
|
||||
meal.style ? renderTaxonomyChip('style', meal.style, optionLabel(STYLE_OPTIONS, meal.style)) : '',
|
||||
meal.leftover_from_meal_id ? renderTaxonomyChip('leftovers', 'linked', '↻ Rester') : '',
|
||||
].filter(Boolean).join('');
|
||||
}
|
||||
|
||||
// --------------------------------------------------------
|
||||
// API-Wrapper
|
||||
// --------------------------------------------------------
|
||||
@@ -305,8 +323,7 @@ function renderSlot(date, type, mealsForDay) {
|
||||
const ingDoneLabel = ingCount > 0 && ingDone === ingCount ? ' ✓' : '';
|
||||
const canTransfer = ingCount > 0 && ingDone < ingCount;
|
||||
const cookName = meal.cook_assignment?.cook_name;
|
||||
const mealCategoryLabel = meal.meal_category ? MEAL_CATEGORY_OPTIONS.find(([value]) => value === meal.meal_category)?.[1] : '';
|
||||
const leftoverLabel = meal.leftover_from_meal_id ? 'Rester' : '';
|
||||
const taxonomyChips = renderMealTaxonomyChips(meal);
|
||||
|
||||
return `
|
||||
<div class="meal-slot meal-slot--has-meal" data-meal-id="${meal.id}" data-date="${meal.date}" data-type="${type.key}">
|
||||
@@ -316,9 +333,8 @@ function renderSlot(date, type, mealsForDay) {
|
||||
data-meal-id="${meal.id}"
|
||||
role="button" tabindex="0">
|
||||
<div class="meal-card__title">${esc(meal.title)}</div>
|
||||
${(ingLabel || cookName || mealCategoryLabel || leftoverLabel) ? `<div class="meal-card__meta">
|
||||
${mealCategoryLabel ? `<span class="meal-card__ingredients-count">${esc(mealCategoryLabel)}</span>` : ''}
|
||||
${leftoverLabel ? `<span class="meal-card__ingredients-count">↻ ${esc(leftoverLabel)}</span>` : ''}
|
||||
${taxonomyChips ? `<div class="meal-card__taxonomy" aria-label="Meal classification">${taxonomyChips}</div>` : ''}
|
||||
${(ingLabel || cookName) ? `<div class="meal-card__meta">
|
||||
${ingLabel ? `<span class="meal-card__ingredients-count">${ingLabel}${esc(ingDoneLabel)}</span>` : ''}
|
||||
${cookName ? `<span class="meal-card__cook"><i data-lucide="chef-hat" style="width:13px;height:13px;" aria-hidden="true"></i>${esc(cookName)}</span>` : ''}
|
||||
</div>` : ''}
|
||||
|
||||
+21
-6
@@ -30,6 +30,20 @@ function optionHtml(options, selected) {
|
||||
return options.map(([value, label]) => `<option value="${value}" ${value === selected ? 'selected' : ''}>${label}</option>`).join('');
|
||||
}
|
||||
|
||||
function optionLabel(options, value) {
|
||||
return options.find(([optionValue]) => optionValue === value)?.[1] || value || '';
|
||||
}
|
||||
|
||||
function taxonomyChip(kind, value, label) {
|
||||
if (!value || !label) return null;
|
||||
const chip = document.createElement('span');
|
||||
chip.className = `recipe-taxonomy-chip recipe-taxonomy-chip--${kind}`;
|
||||
chip.dataset.recipeTaxonomy = kind;
|
||||
chip.dataset.taxonomyValue = value;
|
||||
chip.textContent = label;
|
||||
return chip;
|
||||
}
|
||||
|
||||
function mealCategories() {
|
||||
return state.categories.filter((c) => c.name !== 'Haushalt' && c.name !== 'Drogerie');
|
||||
}
|
||||
@@ -216,14 +230,15 @@ function renderRecipeList() {
|
||||
card.appendChild(h);
|
||||
|
||||
const taxonomy = [
|
||||
recipe.meal_category ? MEAL_CATEGORY_OPTIONS.find(([v]) => v === recipe.meal_category)?.[1] || recipe.meal_category : '',
|
||||
recipe.protein ? PROTEIN_OPTIONS.find(([v]) => v === recipe.protein)?.[1] || recipe.protein : '',
|
||||
recipe.style ? STYLE_OPTIONS.find(([v]) => v === recipe.style)?.[1] || recipe.style : '',
|
||||
taxonomyChip('category', recipe.meal_category, optionLabel(MEAL_CATEGORY_OPTIONS, recipe.meal_category)),
|
||||
taxonomyChip('protein', recipe.protein, optionLabel(PROTEIN_OPTIONS, recipe.protein)),
|
||||
taxonomyChip('style', recipe.style, optionLabel(STYLE_OPTIONS, recipe.style)),
|
||||
].filter(Boolean);
|
||||
if (taxonomy.length) {
|
||||
const meta = document.createElement('p');
|
||||
meta.className = 'recipe-card__notes';
|
||||
meta.textContent = `Kategori: ${taxonomy.join(' · ')}`;
|
||||
const meta = document.createElement('div');
|
||||
meta.className = 'recipe-card__taxonomy';
|
||||
meta.setAttribute('aria-label', 'Recipe classification');
|
||||
meta.append(...taxonomy);
|
||||
card.appendChild(meta);
|
||||
}
|
||||
|
||||
|
||||
@@ -208,10 +208,41 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
flex-wrap: wrap;
|
||||
align-self: stretch;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.meal-card__taxonomy {
|
||||
margin-top: var(--space-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-1);
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.meal-taxonomy-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
min-height: 22px;
|
||||
padding: 2px var(--space-2);
|
||||
border-radius: var(--radius-full);
|
||||
border: 1px solid color-mix(in srgb, var(--chip-color, var(--module-accent)) 32%, transparent);
|
||||
background: color-mix(in srgb, var(--chip-color, var(--module-accent)) 14%, var(--color-surface));
|
||||
color: color-mix(in srgb, var(--chip-color, var(--module-accent)) 76%, var(--color-text-primary));
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.meal-taxonomy-chip--category { --chip-color: var(--module-meals); }
|
||||
.meal-taxonomy-chip--protein { --chip-color: var(--module-recipes); }
|
||||
.meal-taxonomy-chip--style { --chip-color: var(--color-accent); }
|
||||
.meal-taxonomy-chip--leftovers { --chip-color: var(--color-warning, #f59e0b); }
|
||||
|
||||
.meal-card__ingredients-count {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
@@ -67,6 +67,32 @@
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.recipe-card__taxonomy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-1);
|
||||
}
|
||||
|
||||
.recipe-taxonomy-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
padding: 2px var(--space-2);
|
||||
border-radius: var(--radius-full);
|
||||
border: 1px solid color-mix(in srgb, var(--chip-color, var(--module-accent)) 32%, transparent);
|
||||
background: color-mix(in srgb, var(--chip-color, var(--module-accent)) 14%, var(--color-surface));
|
||||
color: color-mix(in srgb, var(--chip-color, var(--module-accent)) 76%, var(--color-text-primary));
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.recipe-taxonomy-chip--category { --chip-color: var(--module-meals); }
|
||||
.recipe-taxonomy-chip--protein { --chip-color: var(--module-recipes); }
|
||||
.recipe-taxonomy-chip--style { --chip-color: var(--color-accent); }
|
||||
|
||||
.recipe-card__ingredients {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
Reference in New Issue
Block a user