refactor(shared): consolidate empty-state into shared chrome (BEM)
Three tools (archive, browse, classifier) independently implemented
an empty-state pattern with three different CSS class naming
conventions and slightly different rules:
archive: .empty-state + .empty-state-content (BEM-less)
browse: .empty-state + .empty-state__inner (BEM)
classifier: .empty-state + .empty-state-content (BEM-less)
Same visual intent ("nothing's loaded yet — here's a welcome card
with instructions"), implemented three times with subtly different
spacing, no shared body styling for h2/p/ul/li, and incompatible
class names that prevented a future tool from copy-pasting the
pattern.
Promote a single consolidated rule set to shared/base.css using
BEM naming throughout:
.empty-state — base (flex centered, padding)
.empty-state--overlay — modifier: position absolute,
top 50px to clear app-header,
z-index 10. Used by archive
and classifier (their empty
states sit OVER the main
layout).
.empty-state__inner — content card (left-aligned,
text-muted, max-width 640)
.empty-state__inner--centered — modifier: tighter max-width
500, centered text, 2rem
padding. Used by tools whose
welcome screen reads as a
centered card.
.empty-state__inner h2/p/ul/ol/li — typography defaults
.empty-state__inner .note — italic small-print
.welcome-list — bullet list with left-aligned
text + auto margins; safe to
nest inside a centered card.
Per-tool changes:
- archive/template.html, archive/js/app.js: rename
.empty-state-content → .empty-state__inner empty-state__inner--centered;
add .empty-state--overlay to the outer .empty-state container.
Also the runtime-injected unsupported-browser markup in
showUnsupportedBrowserMessage() and the showHttpErrorState
selector.
- classifier/template.html: same renames.
- archive/css/layout.css + components.css: delete .empty-state*
and .welcome-list rules (now in shared).
- classifier/css/layout.css: same. Keep .empty-state.drag-over
locally — classifier is the only tool whose empty state acts
as a drop target.
- browse/css/base.css: delete .empty-state* (shared covers it).
browse's template was already using .empty-state__inner so no
template change needed.
LOC: shared/base.css gains ~70 lines; per-tool overrides lose ~85
combined. Net -15. More importantly, future tools can reuse the
pattern by adding two divs and (optionally) the --centered or
--overlay modifiers; no copy-paste required.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
baf5958174
commit
677ac01b32
8 changed files with 90 additions and 112 deletions
|
|
@ -643,12 +643,7 @@ input[type="checkbox"] {
|
|||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ── Welcome screen list ─────────────────────────────────────────────────── */
|
||||
.welcome-list {
|
||||
text-align: left;
|
||||
margin: 0.5rem auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
/* .welcome-list lives in shared/base.css. */
|
||||
|
||||
/* ── Windows path tip (inside welcome screen) ────────────────────────────── */
|
||||
.windows-tip {
|
||||
|
|
|
|||
|
|
@ -191,24 +191,7 @@
|
|||
}
|
||||
|
||||
/* Empty State — positioned below the app header */
|
||||
.empty-state {
|
||||
position: absolute;
|
||||
top: 50px; /* clear the header */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.empty-state-content {
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
padding: 2rem;
|
||||
}
|
||||
/* .empty-state / .empty-state__inner / .welcome-list live in shared/base.css. */
|
||||
|
||||
/* Project warning banner */
|
||||
.project-warning-banner {
|
||||
|
|
@ -233,16 +216,6 @@
|
|||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.empty-state-content h2 {
|
||||
color: var(--text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-state-content p {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Project access warning banner */
|
||||
.project-warning-banner {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@
|
|||
function showHttpErrorState(message) {
|
||||
var el = document.getElementById('noDirectoryMessage');
|
||||
if (!el) return;
|
||||
var content = el.querySelector('.empty-state-content');
|
||||
var content = el.querySelector('.empty-state__inner');
|
||||
if (content) {
|
||||
content.innerHTML =
|
||||
'<h2>Could not connect to server</h2>' +
|
||||
|
|
@ -302,8 +302,8 @@
|
|||
function showUnsupportedBrowserMessage() {
|
||||
const app = document.getElementById('appContainer');
|
||||
app.innerHTML = `
|
||||
<div class="empty-state">
|
||||
<div class="empty-state-content">
|
||||
<div class="empty-state empty-state--overlay">
|
||||
<div class="empty-state__inner empty-state__inner--centered">
|
||||
<h2>Browser Not Supported</h2>
|
||||
<p>This application requires a Chromium-based browser (Chrome, Edge, Brave) with File System Access API support.</p>
|
||||
<p>Please use one of these browsers to access the Archive Browser.</p>
|
||||
|
|
|
|||
|
|
@ -237,8 +237,8 @@
|
|||
</div>
|
||||
|
||||
<!-- No Directory Selected Message -->
|
||||
<div id="noDirectoryMessage" class="empty-state">
|
||||
<div class="empty-state-content">
|
||||
<div id="noDirectoryMessage" class="empty-state empty-state--overlay">
|
||||
<div class="empty-state__inner empty-state__inner--centered">
|
||||
<h2>Welcome to ZDDC Archive</h2>
|
||||
<p>Click <strong>Add Local Directory</strong> to select an archive folder to browse.</p>
|
||||
<p>This browser provides a convenient interface for searching and retrieving files from ZDDC-compliant archives.</p>
|
||||
|
|
|
|||
|
|
@ -22,35 +22,7 @@ body {
|
|||
min-height: 0;
|
||||
}
|
||||
|
||||
/* Empty / first-paint state */
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.empty-state__inner {
|
||||
max-width: 640px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.empty-state__inner h2 {
|
||||
color: var(--text);
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.empty-state__inner ul {
|
||||
margin: 1rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.empty-state__inner li {
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
/* .empty-state / .empty-state__inner live in shared/base.css. */
|
||||
|
||||
/* .hidden lives in shared/base.css; no per-tool override needed. */
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,8 @@
|
|||
/* Classifier layout — tokens from shared/base.css */
|
||||
|
||||
/* Empty State — positioned below the app header */
|
||||
.empty-state {
|
||||
position: absolute;
|
||||
top: 50px; /* clear the header */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.empty-state-content {
|
||||
text-align: center;
|
||||
max-width: 500px;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.empty-state-content h2 {
|
||||
color: var(--text);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-state-content p {
|
||||
margin-bottom: 1rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.empty-state-content .note {
|
||||
font-size: 0.85rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.welcome-list {
|
||||
text-align: left;
|
||||
margin: 0.5rem auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* .empty-state / .empty-state__inner / .welcome-list live in
|
||||
shared/base.css. Classifier keeps the .drag-over modifier locally
|
||||
because it's the only tool whose empty state is a drop target. */
|
||||
.empty-state.drag-over {
|
||||
background: var(--primary-light);
|
||||
outline: 2px dashed var(--primary);
|
||||
|
|
|
|||
|
|
@ -129,8 +129,8 @@
|
|||
</div>
|
||||
|
||||
<!-- Empty State — shown until a directory is selected -->
|
||||
<div id="welcomeScreen" class="empty-state">
|
||||
<div class="empty-state-content">
|
||||
<div id="welcomeScreen" class="empty-state empty-state--overlay">
|
||||
<div class="empty-state__inner empty-state__inner--centered">
|
||||
<h2>ZDDC Classifier</h2>
|
||||
<p>Rename a folder of files to ZDDC format using a spreadsheet interface.</p>
|
||||
<p>Open a directory, fill in tracking number, revision, status, and title for each file, then save — the files are renamed on disk.</p>
|
||||
|
|
|
|||
|
|
@ -349,7 +349,83 @@ a:hover {
|
|||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Toast CSS lives in classifier/css/base.css — only that tool uses toasts. */
|
||||
/* Toast CSS lives in shared/toast.css; loaded by every tool's build. */
|
||||
|
||||
/* ── Empty state ──────────────────────────────────────────────────────────── */
|
||||
/* The "nothing's loaded yet" screen. By default, centers its inner
|
||||
content in whatever space the parent gives it (works inside a flex
|
||||
column). Tools that need to overlay an existing layout (archive,
|
||||
classifier) add .empty-state--overlay; the screen pins below the
|
||||
app header and on top of whatever underlying layout already exists.
|
||||
Inner content uses BEM-ish .empty-state__inner with two variants:
|
||||
plain (left-aligned, doc-style) and --centered (centered card). */
|
||||
|
||||
.empty-state {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.empty-state--overlay {
|
||||
position: absolute;
|
||||
top: 50px; /* clear the app-header */
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.empty-state__inner {
|
||||
max-width: 640px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.empty-state__inner h2 {
|
||||
color: var(--text);
|
||||
margin: 0 0 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.empty-state__inner p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-state__inner ul,
|
||||
.empty-state__inner ol {
|
||||
margin: 1rem 0;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
.empty-state__inner li {
|
||||
margin: 0.4rem 0;
|
||||
}
|
||||
|
||||
.empty-state__inner .note {
|
||||
font-size: 0.85rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Centered variant: tighter max-width + centered text. Used by tools
|
||||
whose empty-state reads as a "welcome card" (archive, classifier)
|
||||
rather than a doc-style page (browse). */
|
||||
.empty-state__inner--centered {
|
||||
max-width: 500px;
|
||||
text-align: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
/* Bullet list inside an empty-state — keep the bullets left-aligned
|
||||
even when the surrounding card is centered. */
|
||||
.welcome-list {
|
||||
text-align: left;
|
||||
margin: 0.5rem auto;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
/* ── Theme and help icon buttons ─────────────────────────────────────────── */
|
||||
#theme-btn,
|
||||
|
|
|
|||
Loading…
Reference in a new issue