Regroup settings into collapsible sections

Restructure the flat settingsCategories list into five top-level groups
(General, Appearance, Screens, Productivity, Add-ons), each a collapsible
nav section with persisted open/closed state. Consolidate the six
single-toggle modern_* categories (posts/pages/media/users/comments/
plugins) into one "Content tables" screen. Search now matches on group +
category + subtitle and hides empty groups. Field definitions, save flow,
and setting keys are unchanged.

settingsConfig.js exports settingsGroups plus a flattened settingsCategories
for default-seeding and deep-link lookups.

(package-lock.json: version field synced 2.16.0 -> 3.1.0 to match package.json.)

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
NixReaper
2026-07-12 09:27:09 -05:00
co-authored by Claude Opus 4.8
parent ce8fcc8fdd
commit 1c8c351654
3 changed files with 451 additions and 369 deletions
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "aurora-admin-app",
"version": "2.16.0",
"version": "3.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "aurora-admin-app",
"version": "2.16.0",
"version": "3.1.0",
"dependencies": {
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.11",
+95 -25
View File
@@ -1,6 +1,6 @@
<script setup>
import { ref, reactive, computed, watch, nextTick } from 'vue';
import { settingsCategories } from './settingsConfig.js';
import { settingsGroups, settingsCategories } from './settingsConfig.js';
import SettingsField from './components/SettingsField.vue';
import { applyThemePalette, applyFontFamily } from './applyLiveTheme.js';
@@ -45,6 +45,29 @@ const activeCategory = computed(
() => visibleCategories.find((c) => c.id === activeId.value) || visibleCategories[0]
);
const groupOfCategory = (catId) =>
settingsGroups.find((g) => g.categories.some((c) => c.id === catId));
// Collapsible nav groups. Default open; collapse state persisted per group.
const OPEN_KEY = 'aurora-settings-groups-open';
const loadOpen = () => {
try {
return JSON.parse(localStorage.getItem(OPEN_KEY)) || {};
} catch {
return {};
}
};
const openState = reactive(loadOpen());
// Keep the active category's group open so its item is always reachable.
const activeGroup = groupOfCategory(activeId.value);
if (activeGroup) openState[activeGroup.id] = true;
const isGroupOpen = (id) => openState[id] !== false;
const toggleGroup = (id) => {
openState[id] = !isGroupOpen(id);
localStorage.setItem(OPEN_KEY, JSON.stringify(openState));
};
// The content panel scrolls independently of the nav (see .settings__content
// below) so picking a category from a scrolled-down spot in the nav doesn't
// require scrolling the whole page back up to see it. But the panel is one
@@ -56,6 +79,8 @@ const activeCategory = computed(
const contentEl = ref(null);
const selectCategory = (id) => {
activeId.value = id;
const g = groupOfCategory(id);
if (g) openState[g.id] = true;
const url = new URL(window.location.href);
url.searchParams.set('category', id);
window.history.replaceState({}, '', url);
@@ -64,15 +89,25 @@ const selectCategory = (id) => {
});
};
// Search filter over the nav.
// Search filter over the nav — matches on group title, category title, or
// subtitle; groups with no surviving categories drop out entirely.
const query = ref('');
const filteredCategories = computed(() => {
const searching = computed(() => query.value.trim().length > 0);
const filteredGroups = computed(() => {
const q = query.value.trim().toLowerCase();
if (!q) return visibleCategories;
return visibleCategories.filter(
if (!q) return settingsGroups;
return settingsGroups
.map((g) => {
const groupMatch = g.title.toLowerCase().includes(q);
const categories = g.categories.filter(
(c) =>
c.title.toLowerCase().includes(q) || c.subtitle.toLowerCase().includes(q)
groupMatch ||
c.title.toLowerCase().includes(q) ||
c.subtitle.toLowerCase().includes(q)
);
return { ...g, categories };
})
.filter((g) => g.categories.length > 0);
});
// Options for multiselect fields, resolved from server-provided lists.
@@ -172,22 +207,39 @@ const importSettings = (e) => {
<input v-model="query" type="text" placeholder="Search settings…" />
</div>
<ul class="settings__list">
<li v-for="cat in filteredCategories" :key="cat.id">
<ul class="settings__groups">
<li v-for="group in filteredGroups" :key="group.id" class="settings__group">
<button
type="button"
class="settings__group-head"
:aria-expanded="isGroupOpen(group.id) || searching"
@click="toggleGroup(group.id)"
>
<span class="settings__group-icon dashicons" :class="group.icon" />
<span class="settings__group-title">{{ group.title }}</span>
<span
class="settings__group-caret dashicons dashicons-arrow-down-alt2"
:class="{ 'is-collapsed': !isGroupOpen(group.id) && !searching }"
/>
</button>
<ul v-show="isGroupOpen(group.id) || searching" class="settings__list">
<li v-for="cat in group.categories" :key="cat.id">
<button
type="button"
class="settings__cat"
:class="{ 'settings__cat--active': cat.id === activeId }"
@click="selectCategory(cat.id)"
>
<span class="settings__cat-icon"><span class="dashicons" :class="cat.icon" /></span>
<span class="settings__cat-text">
<span class="settings__cat-icon dashicons" :class="cat.icon" />
<span class="settings__cat-title">{{ cat.title }}</span>
<span class="settings__cat-sub">{{ cat.subtitle }}</span>
</span>
</button>
</li>
</ul>
</li>
<li v-if="!filteredGroups.length" class="settings__empty">
No settings match “{{ query }}”.
</li>
</ul>
</aside>
<!-- Right content panel -->
@@ -280,27 +332,45 @@ const importSettings = (e) => {
font-size: 0.8125rem; width: 100%;
}
.settings__list { list-style: none; margin: 0; padding: 0; }
.settings__groups { list-style: none; margin: 0; padding: 0; }
.settings__group { margin-bottom: 4px; }
.settings__group-head {
display: flex; align-items: center; gap: 8px; width: 100%;
padding: 6px 8px; border: none; background: none; cursor: pointer;
border-radius: 8px; color: var(--aurora-text-muted);
}
.settings__group-head:hover { color: var(--aurora-text); }
.settings__group-icon { font-size: 15px; width: 16px; height: 16px; }
.settings__group-title {
font-size: 0.6875rem; font-weight: 600; letter-spacing: 0.04em;
text-transform: uppercase;
}
.settings__group-caret {
margin-left: auto; font-size: 16px; width: 16px; height: 16px;
transition: transform 0.15s ease;
}
.settings__group-caret.is-collapsed { transform: rotate(-90deg); }
.settings__list { list-style: none; margin: 2px 0 8px; padding: 0; }
.settings__cat {
display: flex; align-items: center; gap: 12px; width: 100%;
padding: 10px; border: none; background: none; cursor: pointer;
border-radius: 12px; text-align: left; margin-bottom: 2px;
display: flex; align-items: center; gap: 10px; width: 100%;
padding: 8px 10px; border: none; background: none; cursor: pointer;
border-radius: 10px; text-align: left; margin-bottom: 1px;
color: var(--aurora-text);
}
.settings__cat:hover { background: var(--aurora-bg-subtle); }
.settings__cat--active { background: var(--aurora-accent-soft); }
.settings__cat-icon {
width: 34px; height: 34px; flex-shrink: 0;
border-radius: 9px; background: var(--aurora-bg-subtle);
display: flex; align-items: center; justify-content: center;
font-size: 16px; width: 18px; height: 18px; flex-shrink: 0;
color: var(--aurora-text-muted);
}
.settings__cat--active .settings__cat-icon { color: var(--aurora-accent); }
.settings__cat-text { display: flex; flex-direction: column; min-width: 0; }
.settings__cat-title { font-size: 0.875rem; font-weight: 600; color: var(--aurora-text); }
.settings__cat--active .settings__cat-title { color: var(--aurora-accent); }
.settings__cat-sub {
font-size: 0.75rem; color: var(--aurora-text-muted);
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
.settings__cat-title { font-size: 0.8125rem; font-weight: 500; }
.settings__cat--active .settings__cat-title { color: var(--aurora-accent); font-weight: 600; }
.settings__empty {
padding: 12px 10px; font-size: 0.8125rem; color: var(--aurora-text-muted);
}
/* Right content — no background/border of its own, same reasoning.
+127 -115
View File
@@ -1,10 +1,18 @@
/**
* Declarative settings schema for the Aurora Admin settings page.
* Each category renders as an item in the left nav; its fields render in
* the right panel. Field `key`s map directly into the aurora_admin_settings
* option, saved via the wp/v2/settings REST route.
*
* Categories are organized into top-level groups. Each group renders as a
* collapsible section in the left nav; each category within it renders as a
* nav item, and its fields render in the right panel. Field `key`s map
* directly into the aurora_admin_settings option, saved via the
* wp/v2/settings REST route.
*/
export const settingsCategories = [
export const settingsGroups = [
{
id: 'general',
title: 'General',
icon: 'dashicons-admin-settings',
categories: [
{
id: 'general',
title: 'General',
@@ -47,25 +55,33 @@ export const settingsCategories = [
],
},
{
id: 'login',
title: 'Login',
subtitle: 'Customize login page appearance and security',
icon: 'dashicons-lock',
id: 'analytics',
title: 'Analytics',
subtitle: 'Built-in analytics and tracking settings',
icon: 'dashicons-chart-bar',
fields: [
{
key: 'style_login',
key: 'enable_analytics',
type: 'toggle',
label: 'Style login page',
description: 'Apply the Aurora theme to the WordPress login screen.',
label: 'Enable analytics',
description: 'Track first-party visitor analytics for the dashboard.',
default: true,
},
{
key: 'login_logo',
type: 'image',
label: 'Login logo',
description: 'Replace the WordPress logo on the login screen.',
key: 'track_admins',
type: 'toggle',
label: 'Track logged-in admins',
description: 'Include administrator visits in analytics (off by default).',
},
],
},
],
},
{
id: 'appearance',
title: 'Appearance',
icon: 'dashicons-admin-appearance',
categories: [
{
id: 'theme',
title: 'Theme',
@@ -104,7 +120,7 @@ export const settingsCategories = [
type: 'text',
label: 'Rename Aurora Admin',
description:
"This will rename Aurora Admin in the plugins list as well as renaming its settings link.",
'This will rename Aurora Admin in the plugins list as well as renaming its settings link.',
placeholder: 'Aurora Admin',
},
{
@@ -117,6 +133,26 @@ export const settingsCategories = [
},
],
},
{
id: 'login',
title: 'Login',
subtitle: 'Customize login page appearance and security',
icon: 'dashicons-lock',
fields: [
{
key: 'style_login',
type: 'toggle',
label: 'Style login page',
description: 'Apply the Aurora theme to the WordPress login screen.',
},
{
key: 'login_logo',
type: 'image',
label: 'Login logo',
description: 'Replace the WordPress logo on the login screen.',
},
],
},
{
id: 'menu',
title: 'Menu',
@@ -131,51 +167,13 @@ export const settingsCategories = [
},
],
},
{
id: 'posts',
title: 'Posts',
subtitle: 'Post management and display preferences',
icon: 'dashicons-admin-post',
fields: [
{
key: 'modern_posts',
type: 'toggle',
label: 'Modern posts table',
description: 'Use the Aurora posts management interface.',
default: true,
},
],
},
{
id: 'pages',
title: 'Pages',
subtitle: 'Page management and display preferences',
icon: 'dashicons-admin-page',
fields: [
{
key: 'modern_pages',
type: 'toggle',
label: 'Modern pages table',
description: 'Use the Aurora pages management interface.',
default: true,
},
],
},
{
id: 'plugins',
title: 'Plugins',
subtitle: 'Plugin management interface options',
icon: 'dashicons-admin-plugins',
fields: [
{
key: 'modern_plugins',
type: 'toggle',
label: 'Modern plugins page',
description: 'Use the Aurora plugins management interface.',
default: true,
},
],
},
id: 'screens',
title: 'Screens',
icon: 'dashicons-desktop',
categories: [
{
id: 'dashboard',
title: 'Dashboard',
@@ -192,77 +190,78 @@ export const settingsCategories = [
],
},
{
id: 'analytics',
title: 'Analytics',
subtitle: 'Built-in analytics and tracking settings',
icon: 'dashicons-chart-bar',
id: 'content-tables',
title: 'Content tables',
subtitle: 'Choose which admin list screens use the Aurora interface',
icon: 'dashicons-editor-table',
fields: [
{
key: 'enable_analytics',
key: 'modern_posts',
type: 'toggle',
label: 'Enable analytics',
description: 'Track first-party visitor analytics for the dashboard.',
label: 'Posts',
description: 'Use the Aurora posts management interface.',
default: true,
},
{
key: 'track_admins',
key: 'modern_pages',
type: 'toggle',
label: 'Track logged-in admins',
description: 'Include administrator visits in analytics (off by default).',
label: 'Pages',
description: 'Use the Aurora pages management interface.',
default: true,
},
],
},
{
id: 'media',
title: 'Media',
subtitle: 'Media library and upload management settings',
icon: 'dashicons-admin-media',
fields: [
{
key: 'modern_media',
type: 'toggle',
label: 'Modern media library',
label: 'Media library',
description: 'Use the Aurora media library interface.',
default: true,
},
{
key: 'enable_svg',
type: 'toggle',
label: 'Allow SVG uploads',
description: 'Permit SVG files to be uploaded to the media library.',
},
],
},
{
id: 'users',
title: 'Users',
subtitle: 'User management and administration settings',
icon: 'dashicons-admin-users',
fields: [
{
key: 'modern_users',
type: 'toggle',
label: 'Modern users page',
label: 'Users',
description: 'Use the Aurora users management interface.',
default: true,
},
],
},
{
id: 'comments',
title: 'Comments',
subtitle: 'Comment management and administration',
icon: 'dashicons-admin-comments',
fields: [
{
key: 'modern_comments',
type: 'toggle',
label: 'Modern comments page',
label: 'Comments',
description: 'Use the Aurora comments management interface.',
default: true,
},
{
key: 'modern_plugins',
type: 'toggle',
label: 'Plugins',
description: 'Use the Aurora plugins management interface.',
default: true,
},
],
},
{
id: 'admin-takeover',
title: 'Admin Takeover',
subtitle: 'Replace the remaining native WordPress admin screens with Aurora',
icon: 'dashicons-shield',
fields: [
{
key: 'full_admin_takeover',
type: 'toggle',
label: 'Full admin takeover',
description:
'Replace Media Upload, Add User, Profile, Appearance, Tools, and native Settings screens with Aurora equivalents. Off by default — these cover more ground than Posts/Pages/Media/Users/Comments, so it’s opt-in rather than on by default.',
default: false,
},
],
},
],
},
{
id: 'productivity',
title: 'Productivity',
icon: 'dashicons-superhero-alt',
categories: [
{
id: 'image-formats',
title: 'Modern Image Formats',
@@ -302,6 +301,27 @@ export const settingsCategories = [
},
],
},
{
id: 'media',
title: 'Media',
subtitle: 'Media library and upload options',
icon: 'dashicons-admin-media',
fields: [
{
key: 'enable_svg',
type: 'toggle',
label: 'Allow SVG uploads',
description: 'Permit SVG files to be uploaded to the media library.',
},
],
},
],
},
{
id: 'add-ons',
title: 'Add-ons',
icon: 'dashicons-admin-plugins',
categories: [
{
id: 'add-ons',
title: 'Add-ons',
@@ -340,20 +360,12 @@ export const settingsCategories = [
},
],
},
{
id: 'admin-takeover',
title: 'Admin Takeover',
subtitle: 'Replace the remaining native WordPress admin screens with Aurora',
icon: 'dashicons-shield',
fields: [
{
key: 'full_admin_takeover',
type: 'toggle',
label: 'Full admin takeover',
description:
'Replace Media Upload, Add User, Profile, Appearance, Tools, and native Settings screens with Aurora equivalents. Off by default — these cover more ground than Posts/Pages/Media/Users/Comments, so it’s opt-in rather than on by default.',
default: false,
},
],
},
];
/**
* Flattened category list — every category across all groups, in order.
* Used for default-seeding, active-category lookup, and URL deep-links.
*/
export const settingsCategories = settingsGroups.flatMap((g) => g.categories);