fix(ui): prevent grid blowouts and fix detail view reactivity
Add min-width constraints to grid containers to prevent overflow Replace .detail-grid with .detail-tab-panel for consistent layouts Add key attributes to detail views to ensure proper state reset
This commit is contained in:
@@ -1205,7 +1205,9 @@ svg {
|
|||||||
.page-stack {
|
.page-stack {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header {
|
.page-header {
|
||||||
@@ -1649,9 +1651,15 @@ button:disabled,
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-edit-form--tabbed {
|
.modal-edit-form--tabbed {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-edit-form--tabbed > .pokemon-edit-panel {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.ai-moderation-form {
|
.ai-moderation-form {
|
||||||
max-width: 680px;
|
max-width: 680px;
|
||||||
}
|
}
|
||||||
@@ -2337,7 +2345,12 @@ button:disabled,
|
|||||||
|
|
||||||
.tabs--component {
|
.tabs--component {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
flex: 1 0 100%;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab-list {
|
.tab-list {
|
||||||
@@ -4581,21 +4594,18 @@ button:disabled,
|
|||||||
height: 15px;
|
height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-grid--stack {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-tabs,
|
.detail-tabs,
|
||||||
.detail-tab-panel {
|
.detail-tab-panel {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-tabs > .detail-tab-panel {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.habitat-detail-stack {
|
.habitat-detail-stack {
|
||||||
@@ -7144,8 +7154,14 @@ button:disabled,
|
|||||||
.profile-tab-panel,
|
.profile-tab-panel,
|
||||||
.profile-activity-list {
|
.profile-activity-list {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-public-layout > .profile-tab-panel {
|
||||||
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.profile-secondary-tabs .tab-list {
|
.profile-secondary-tabs .tab-list {
|
||||||
@@ -8110,7 +8126,6 @@ button:disabled,
|
|||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-grid,
|
|
||||||
.entity-profile-grid,
|
.entity-profile-grid,
|
||||||
.home-hero,
|
.home-hero,
|
||||||
.pokemon-image-detail,
|
.pokemon-image-detail,
|
||||||
@@ -8796,7 +8811,6 @@ button:disabled,
|
|||||||
height: 56px;
|
height: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-grid,
|
|
||||||
.pokemon-related-grid,
|
.pokemon-related-grid,
|
||||||
.entity-profile-grid,
|
.entity-profile-grid,
|
||||||
.pokemon-profile-grid,
|
.pokemon-profile-grid,
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ watch(initialHabitat, applyInitialHabitat, { immediate: true });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section v-if="!habitat" class="page-stack" aria-busy="true" :aria-label="t('pages.habitats.loadingDetail')">
|
<section v-if="!habitat" key="habitat-detail-loading" class="page-stack" aria-busy="true" :aria-label="t('pages.habitats.loadingDetail')">
|
||||||
<div class="page-header page-header--skeleton" aria-hidden="true">
|
<div class="page-header page-header--skeleton" aria-hidden="true">
|
||||||
<div class="page-header__copy">
|
<div class="page-header__copy">
|
||||||
<Skeleton width="132px" />
|
<Skeleton width="132px" />
|
||||||
@@ -275,7 +275,7 @@ watch(initialHabitat, applyInitialHabitat, { immediate: true });
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section v-else class="page-stack">
|
<section v-else :key="`habitat-detail-${habitat.id}`" class="page-stack">
|
||||||
<PageHeader :title="habitat.name" :subtitle="t('pages.habitats.detailSubtitle')">
|
<PageHeader :title="habitat.name" :subtitle="t('pages.habitats.detailSubtitle')">
|
||||||
<template #kicker>{{ detailKicker }}</template>
|
<template #kicker>{{ detailKicker }}</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -293,7 +293,7 @@ watch(initialHabitat, applyInitialHabitat, { immediate: true });
|
|||||||
<div class="detail-tabs">
|
<div class="detail-tabs">
|
||||||
<Tabs id="habitat-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
<Tabs id="habitat-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
||||||
|
|
||||||
<div v-if="detailTab === 'details'" class="detail-grid detail-grid--stack">
|
<div v-if="detailTab === 'details'" class="detail-tab-panel">
|
||||||
<div class="entity-profile-grid">
|
<div class="entity-profile-grid">
|
||||||
<section class="detail-section entity-profile-media-section" :aria-label="t('media.image')">
|
<section class="detail-section entity-profile-media-section" :aria-label="t('media.image')">
|
||||||
<div class="entity-detail-image">
|
<div class="entity-detail-image">
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ watch(initialItem, applyInitialItem, { immediate: true });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section v-if="!item" class="page-stack" aria-busy="true" :aria-label="t('pages.items.loadingDetail')">
|
<section v-if="!item" key="item-detail-loading" class="page-stack" aria-busy="true" :aria-label="t('pages.items.loadingDetail')">
|
||||||
<div class="page-header page-header--skeleton" aria-hidden="true">
|
<div class="page-header page-header--skeleton" aria-hidden="true">
|
||||||
<div class="page-header__copy">
|
<div class="page-header__copy">
|
||||||
<Skeleton width="96px" />
|
<Skeleton width="96px" />
|
||||||
@@ -238,7 +238,7 @@ watch(initialItem, applyInitialItem, { immediate: true });
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-grid" aria-hidden="true">
|
<div class="detail-tab-panel" aria-hidden="true">
|
||||||
<section v-for="index in 3" :key="`chips-${index}`" class="detail-section skeleton-detail-section">
|
<section v-for="index in 3" :key="`chips-${index}`" class="detail-section skeleton-detail-section">
|
||||||
<div class="detail-section__header">
|
<div class="detail-section__header">
|
||||||
<Skeleton :width="index === 2 ? '68px' : '92px'" height="24px" />
|
<Skeleton :width="index === 2 ? '68px' : '92px'" height="24px" />
|
||||||
@@ -277,7 +277,7 @@ watch(initialItem, applyInitialItem, { immediate: true });
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section v-else class="page-stack">
|
<section v-else :key="`item-detail-${item.id}`" class="page-stack">
|
||||||
<PageHeader :title="item.name" :subtitle="itemSubtitle">
|
<PageHeader :title="item.name" :subtitle="itemSubtitle">
|
||||||
<template #kicker>{{ detailKicker }}</template>
|
<template #kicker>{{ detailKicker }}</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -295,7 +295,7 @@ watch(initialItem, applyInitialItem, { immediate: true });
|
|||||||
<div class="detail-tabs">
|
<div class="detail-tabs">
|
||||||
<Tabs id="item-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
<Tabs id="item-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
||||||
|
|
||||||
<div v-if="detailTab === 'details'" class="detail-grid detail-grid--stack">
|
<div v-if="detailTab === 'details'" class="detail-tab-panel">
|
||||||
<div class="entity-profile-grid">
|
<div class="entity-profile-grid">
|
||||||
<section class="detail-section entity-profile-media-section" :aria-label="t('media.image')">
|
<section class="detail-section entity-profile-media-section" :aria-label="t('media.image')">
|
||||||
<div class="entity-detail-image">
|
<div class="entity-detail-image">
|
||||||
@@ -394,7 +394,7 @@ watch(initialItem, applyInitialItem, { immediate: true });
|
|||||||
</div>
|
</div>
|
||||||
</DetailSection>
|
</DetailSection>
|
||||||
|
|
||||||
<div class="detail-grid">
|
<div class="detail-tab-panel">
|
||||||
<DetailSection :title="t('pages.items.recipeInfo')">
|
<DetailSection :title="t('pages.items.recipeInfo')">
|
||||||
<template v-if="item.recipe">
|
<template v-if="item.recipe">
|
||||||
<RouterLink class="related-entity-link related-entity-link--compact" :to="`/recipes/${item.recipe.id}`">
|
<RouterLink class="related-entity-link related-entity-link--compact" :to="`/recipes/${item.recipe.id}`">
|
||||||
|
|||||||
@@ -706,7 +706,7 @@ watch(initialPokemon, applyInitialPokemon, { immediate: true });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section v-if="!pokemon" class="page-stack" aria-busy="true" :aria-label="t('pages.pokemon.loadingDetail')">
|
<section v-if="!pokemon" key="pokemon-detail-loading" class="page-stack" aria-busy="true" :aria-label="t('pages.pokemon.loadingDetail')">
|
||||||
<div class="page-header page-header--skeleton" aria-hidden="true">
|
<div class="page-header page-header--skeleton" aria-hidden="true">
|
||||||
<div class="page-header__copy">
|
<div class="page-header__copy">
|
||||||
<Skeleton width="142px" />
|
<Skeleton width="142px" />
|
||||||
@@ -719,7 +719,7 @@ watch(initialPokemon, applyInitialPokemon, { immediate: true });
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-grid detail-grid--stack" aria-hidden="true">
|
<div class="detail-tab-panel" aria-hidden="true">
|
||||||
<section class="detail-section skeleton-detail-section">
|
<section class="detail-section skeleton-detail-section">
|
||||||
<div class="detail-section__header">
|
<div class="detail-section__header">
|
||||||
<Skeleton width="56px" height="24px" />
|
<Skeleton width="56px" height="24px" />
|
||||||
@@ -762,7 +762,7 @@ watch(initialPokemon, applyInitialPokemon, { immediate: true });
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section v-else class="page-stack">
|
<section v-else :key="`pokemon-detail-${pokemon.id}`" class="page-stack">
|
||||||
<PageHeader :title="`#${pokemon.displayId} ${pokemon.name}`" :subtitle="pokemon.genus || t('pages.pokemon.detailSubtitle')">
|
<PageHeader :title="`#${pokemon.displayId} ${pokemon.name}`" :subtitle="pokemon.genus || t('pages.pokemon.detailSubtitle')">
|
||||||
<template #kicker>{{ detailKicker }}</template>
|
<template #kicker>{{ detailKicker }}</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -780,7 +780,7 @@ watch(initialPokemon, applyInitialPokemon, { immediate: true });
|
|||||||
<div class="detail-tabs">
|
<div class="detail-tabs">
|
||||||
<Tabs id="pokemon-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
<Tabs id="pokemon-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
||||||
|
|
||||||
<div v-if="detailTab === 'details'" class="detail-grid detail-grid--stack">
|
<div v-if="detailTab === 'details'" class="detail-tab-panel">
|
||||||
<div class="pokemon-description-grid">
|
<div class="pokemon-description-grid">
|
||||||
<button v-if="pokemon.image" type="button" class="pokemon-description-image" :aria-label="pokemonImageLabel()" @click="openImageModal">
|
<button v-if="pokemon.image" type="button" class="pokemon-description-image" :aria-label="pokemonImageLabel()" @click="openImageModal">
|
||||||
<img :src="pokemon.image.url" :alt="pokemonImageAlt()" />
|
<img :src="pokemon.image.url" :alt="pokemonImageAlt()" />
|
||||||
@@ -983,7 +983,7 @@ watch(initialPokemon, applyInitialPokemon, { immediate: true });
|
|||||||
</DetailSection>
|
</DetailSection>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="detailTab === 'reference'" class="detail-grid detail-grid--stack">
|
<div v-else-if="detailTab === 'reference'" class="detail-tab-panel">
|
||||||
<DetailSection :title="t('pages.pokemon.referenceData')">
|
<DetailSection :title="t('pages.pokemon.referenceData')">
|
||||||
<p class="meta-line">{{ t('pages.pokemon.pokedexReferenceNote') }}</p>
|
<p class="meta-line">{{ t('pages.pokemon.pokedexReferenceNote') }}</p>
|
||||||
</DetailSection>
|
</DetailSection>
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ watch(initialRecipe, applyInitialRecipe, { immediate: true });
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section v-if="!recipe" class="page-stack" aria-busy="true" :aria-label="t('pages.recipes.loadingDetail')">
|
<section v-if="!recipe" key="recipe-detail-loading" class="page-stack" aria-busy="true" :aria-label="t('pages.recipes.loadingDetail')">
|
||||||
<div class="page-header page-header--skeleton" aria-hidden="true">
|
<div class="page-header page-header--skeleton" aria-hidden="true">
|
||||||
<div class="page-header__copy">
|
<div class="page-header__copy">
|
||||||
<Skeleton width="112px" />
|
<Skeleton width="112px" />
|
||||||
@@ -141,7 +141,7 @@ watch(initialRecipe, applyInitialRecipe, { immediate: true });
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="detail-grid" aria-hidden="true">
|
<div class="detail-tab-panel" aria-hidden="true">
|
||||||
<section v-for="index in 2" :key="index" class="detail-section skeleton-detail-section">
|
<section v-for="index in 2" :key="index" class="detail-section skeleton-detail-section">
|
||||||
<div class="detail-section__header">
|
<div class="detail-section__header">
|
||||||
<Skeleton :width="index === 1 ? '92px' : '88px'" height="24px" />
|
<Skeleton :width="index === 1 ? '92px' : '88px'" height="24px" />
|
||||||
@@ -154,7 +154,7 @@ watch(initialRecipe, applyInitialRecipe, { immediate: true });
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section v-else class="page-stack">
|
<section v-else :key="`recipe-detail-${recipe.id}`" class="page-stack">
|
||||||
<PageHeader :title="recipe.name" :subtitle="recipeSubtitle">
|
<PageHeader :title="recipe.name" :subtitle="recipeSubtitle">
|
||||||
<template #kicker>{{ t('pages.recipes.detailKicker') }}</template>
|
<template #kicker>{{ t('pages.recipes.detailKicker') }}</template>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
@@ -172,7 +172,7 @@ watch(initialRecipe, applyInitialRecipe, { immediate: true });
|
|||||||
<div class="detail-tabs">
|
<div class="detail-tabs">
|
||||||
<Tabs id="recipe-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
<Tabs id="recipe-detail-tabs" v-model="detailTab" :tabs="detailTabs" :label="t('common.details')" />
|
||||||
|
|
||||||
<div v-if="detailTab === 'details'" class="detail-grid detail-grid--stack">
|
<div v-if="detailTab === 'details'" class="detail-tab-panel">
|
||||||
<div class="entity-profile-grid">
|
<div class="entity-profile-grid">
|
||||||
<section class="detail-section entity-profile-media-section" :aria-label="t('pages.recipes.item')">
|
<section class="detail-section entity-profile-media-section" :aria-label="t('pages.recipes.item')">
|
||||||
<div class="entity-detail-image">
|
<div class="entity-detail-image">
|
||||||
|
|||||||
Reference in New Issue
Block a user