feat(settings): tidy Cleanup tab into sectioned compact tiles (pass 1)
The Cleanup + Maintenance sections had ~17 full-width stacked cards with long descriptions — a hunt to scan. Operator wants compact, sectioned, scannable tiles (2026-06-18: keep both tabs, group inside, compact tiles in a grid). New common/MaintenanceTile.vue: a compact expandable tile (icon + short title + one-line blurb collapsed; click the header to expand the full controls/preview/ result inline; keyboard-accessible button + focus ring; tints the icon, keeps a running task expanded). Cleanup tab (this pass) restructured into 3 sections — Import-filter audits (Min dimensions, Transparency, Single-color) / Duplicates & posts (Bare posts, Duplicate posts, Deduplicate videos, Gated-post previews) / Tags (Unused, Legacy, Reset content tagging, Standardize casing) — each a responsive grid of tiles. PostMaintenanceCard split into 2 tiles, TagMaintenanceCard into 4. Moved VideoDedupCard + GatedPurgeCard from the Maintenance tab here (both are destructive content cleanup). All card logic unchanged — only the chrome. Maintenance tab tiling is pass 2 (TODO noted in MaintenancePanel). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
<!--
|
||||
Compact, expandable maintenance/cleanup action tile. Collapsed it shows just an
|
||||
icon + short title + one-line purpose, so a section of these tiles in a grid is
|
||||
scannable at a glance; clicking the header expands the full controls / preview /
|
||||
result UI inline (the operator opted for "compact tiles in a grid, detail on
|
||||
expand", 2026-06-18, replacing the old long stack of full-width cards).
|
||||
|
||||
Usage: wrap a card's action body in the default slot; pass icon/title/blurb.
|
||||
`destructive` tints the icon error-red for delete actions. `open` can be forced
|
||||
(e.g. keep a running task's tile expanded). Keyboard accessible: the header is a
|
||||
real <button> with aria-expanded + focus ring.
|
||||
-->
|
||||
<template>
|
||||
<v-card class="fc-tile" :class="{ 'fc-tile--open': isOpen }">
|
||||
<button
|
||||
type="button"
|
||||
class="fc-tile__head"
|
||||
:aria-expanded="isOpen"
|
||||
@click="toggle"
|
||||
>
|
||||
<v-icon
|
||||
:icon="icon"
|
||||
:color="destructive ? 'error' : 'accent'"
|
||||
class="fc-tile__icon"
|
||||
/>
|
||||
<span class="fc-tile__text">
|
||||
<span class="fc-tile__title">{{ title }}</span>
|
||||
<span class="fc-tile__blurb">{{ blurb }}</span>
|
||||
</span>
|
||||
<v-icon
|
||||
:icon="isOpen ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
||||
class="fc-tile__chev"
|
||||
/>
|
||||
</button>
|
||||
<v-expand-transition>
|
||||
<div v-show="isOpen" class="fc-tile__body">
|
||||
<slot />
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
icon: { type: String, required: true },
|
||||
title: { type: String, required: true },
|
||||
blurb: { type: String, default: '' },
|
||||
destructive: { type: Boolean, default: false },
|
||||
// Force-open (e.g. a tile whose task is mid-run). When set, the operator can
|
||||
// still collapse it locally, but a change to `open` re-applies.
|
||||
open: { type: Boolean, default: false },
|
||||
})
|
||||
|
||||
const local = ref(props.open)
|
||||
watch(() => props.open, (v) => { local.value = v })
|
||||
const isOpen = computed(() => local.value)
|
||||
|
||||
function toggle() {
|
||||
local.value = !local.value
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.fc-tile {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fc-tile__head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
padding: 12px 14px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
}
|
||||
.fc-tile__head:hover {
|
||||
background: rgba(var(--v-theme-on-surface), 0.04);
|
||||
}
|
||||
.fc-tile__head:focus-visible {
|
||||
outline: 2px solid rgb(var(--v-theme-accent));
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.fc-tile__icon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.fc-tile__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.fc-tile__title {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.fc-tile__blurb {
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.25;
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fc-tile--open .fc-tile__blurb {
|
||||
white-space: normal;
|
||||
}
|
||||
.fc-tile__chev {
|
||||
flex: 0 0 auto;
|
||||
color: rgb(var(--v-theme-on-surface-variant));
|
||||
}
|
||||
.fc-tile__body {
|
||||
padding: 0 14px 14px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user