refactor(admin): redesign system wording layout with sidebar and tabs
Replace module and surface dropdowns with a sidebar and tabbed interface. Improve navigation and usability in the admin wording section.
This commit is contained in:
@@ -2409,15 +2409,98 @@ button:disabled,
|
||||
font-weight: 850;
|
||||
}
|
||||
|
||||
.system-wording-toolbar {
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
.system-wording-header {
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.system-wording-header__locale {
|
||||
width: min(260px, 100%);
|
||||
}
|
||||
|
||||
.system-wording-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 240px) minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.system-wording-sidebar {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
align-content: start;
|
||||
gap: 6px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-card);
|
||||
background: var(--surface-soft);
|
||||
}
|
||||
|
||||
.system-wording-sidebar__title {
|
||||
padding: 2px 4px 4px;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.system-wording-sidebar__button {
|
||||
width: 100%;
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
padding: 9px 10px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: var(--radius-control);
|
||||
background: transparent;
|
||||
color: var(--ink-soft);
|
||||
font-weight: 850;
|
||||
text-align: left;
|
||||
overflow-wrap: anywhere;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.system-wording-sidebar__button:hover {
|
||||
border-color: rgba(42, 117, 187, 0.24);
|
||||
background: rgba(255, 203, 5, 0.2);
|
||||
color: var(--pokemon-blue-deep);
|
||||
}
|
||||
|
||||
.system-wording-sidebar__button.active {
|
||||
border-color: var(--line-strong);
|
||||
background: var(--pokemon-blue);
|
||||
color: #ffffff;
|
||||
box-shadow: 0 2px 0 var(--line-strong);
|
||||
}
|
||||
|
||||
.system-wording-sidebar__button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.54;
|
||||
}
|
||||
|
||||
.system-wording-content {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.system-wording-controls {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.system-wording-controls .tabs--component {
|
||||
flex: 1 1 320px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.system-wording-toolbar__check {
|
||||
min-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.system-wording-list li {
|
||||
@@ -3795,10 +3878,22 @@ button:disabled,
|
||||
.pokemon-profile-grid,
|
||||
.pokemon-profile-row,
|
||||
.pokemon-related-grid,
|
||||
.system-wording-layout,
|
||||
.admin-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.system-wording-sidebar {
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.system-wording-sidebar__button {
|
||||
width: auto;
|
||||
flex: 0 0 auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.coming-soon-panel {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
@@ -3884,6 +3979,32 @@ button:disabled,
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.system-wording-header {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.system-wording-header__locale {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.system-wording-controls .tabs--component {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
|
||||
.system-wording-toolbar__check {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.system-wording-list li {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.system-wording-list .row-actions {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.life-feed__list {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -161,6 +161,18 @@ const wordingLocaleOptions = computed(() =>
|
||||
]
|
||||
);
|
||||
const wordingModules = computed(() => [...new Set(wordingRows.value.map((item) => item.module))].sort((a, b) => a.localeCompare(b)));
|
||||
const wordingSurfaceTabs = computed<TabOption[]>(() => [
|
||||
{ value: '', label: t('pages.admin.allSurfaces') },
|
||||
{ value: 'frontend', label: t('pages.admin.surfaceFrontend') },
|
||||
{ value: 'backend', label: t('pages.admin.surfaceBackend') },
|
||||
{ value: 'email', label: t('pages.admin.surfaceEmail') }
|
||||
]);
|
||||
const activeWordingSurfaceTab = computed({
|
||||
get: () => wordingSurface.value,
|
||||
set: (value: string) => {
|
||||
wordingSurface.value = value === 'frontend' || value === 'backend' || value === 'email' ? value : '';
|
||||
}
|
||||
});
|
||||
const filteredWordingRows = computed(() =>
|
||||
wordingRows.value.filter((item) => {
|
||||
if (wordingModule.value && item.module !== wordingModule.value) return false;
|
||||
@@ -230,6 +242,10 @@ function resetWordingForm() {
|
||||
wordingForm.value = { key: '', locale: wordingLocale.value || defaultLocale, value: '', defaultValue: '', placeholders: [] };
|
||||
}
|
||||
|
||||
function selectWordingModule(module: string) {
|
||||
wordingModule.value = module;
|
||||
}
|
||||
|
||||
function openNewConfig() {
|
||||
resetConfigForm();
|
||||
configModalOpen.value = true;
|
||||
@@ -817,11 +833,9 @@ onMounted(() => {
|
||||
</section>
|
||||
|
||||
<section v-else-if="canEdit && activeTab === 'wordings'" class="detail-section">
|
||||
<div class="detail-section__header">
|
||||
<div class="detail-section__header system-wording-header">
|
||||
<h2>{{ t('pages.admin.wordings') }}</h2>
|
||||
</div>
|
||||
<div class="toolbar system-wording-toolbar">
|
||||
<div class="field">
|
||||
<div class="field system-wording-header__locale">
|
||||
<label for="wording-locale">{{ t('pages.admin.wordingLocale') }}</label>
|
||||
<select id="wording-locale" v-model="wordingLocale" :disabled="busy" @change="reloadWordings">
|
||||
<option v-for="language in wordingLocaleOptions" :key="language.code" :value="language.code">
|
||||
@@ -829,22 +843,37 @@ onMounted(() => {
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="wording-module">{{ t('pages.admin.wordingModule') }}</label>
|
||||
<select id="wording-module" v-model="wordingModule" :disabled="busy">
|
||||
<option value="">{{ t('pages.admin.allModules') }}</option>
|
||||
<option v-for="module in wordingModules" :key="module" :value="module">{{ module }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="wording-surface">{{ t('pages.admin.wordingSurface') }}</label>
|
||||
<select id="wording-surface" v-model="wordingSurface" :disabled="busy">
|
||||
<option value="">{{ t('pages.admin.allSurfaces') }}</option>
|
||||
<option value="frontend">{{ t('pages.admin.surfaceFrontend') }}</option>
|
||||
<option value="backend">{{ t('pages.admin.surfaceBackend') }}</option>
|
||||
<option value="email">{{ t('pages.admin.surfaceEmail') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="system-wording-layout">
|
||||
<nav class="system-wording-sidebar" :aria-label="t('pages.admin.wordingModule')">
|
||||
<span class="system-wording-sidebar__title">{{ t('pages.admin.wordingModule') }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="system-wording-sidebar__button"
|
||||
:class="{ active: !wordingModule }"
|
||||
:aria-current="!wordingModule ? 'true' : undefined"
|
||||
:disabled="busy"
|
||||
@click="selectWordingModule('')"
|
||||
>
|
||||
{{ t('pages.admin.allModules') }}
|
||||
</button>
|
||||
<button
|
||||
v-for="module in wordingModules"
|
||||
:key="module"
|
||||
type="button"
|
||||
class="system-wording-sidebar__button"
|
||||
:class="{ active: wordingModule === module }"
|
||||
:aria-current="wordingModule === module ? 'true' : undefined"
|
||||
:disabled="busy"
|
||||
@click="selectWordingModule(module)"
|
||||
>
|
||||
{{ module }}
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="system-wording-content">
|
||||
<div class="system-wording-controls">
|
||||
<Tabs id="admin-wording-surface" v-model="activeWordingSurfaceTab" :tabs="wordingSurfaceTabs" :label="t('pages.admin.wordingSurface')" />
|
||||
<div class="check-row system-wording-toolbar__check">
|
||||
<label>
|
||||
<input v-model="wordingMissingOnly" type="checkbox" :disabled="busy" />
|
||||
@@ -872,6 +901,8 @@ onMounted(() => {
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="meta-line">{{ t('common.noRecords') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section v-else-if="canEdit && activeTab === 'pokemon'" class="detail-section">
|
||||
|
||||
Reference in New Issue
Block a user