Add vapor shell preset selection flow for host apps
This commit is contained in:
@@ -23,3 +23,5 @@ All notable changes to the public design kit surface are documented here.
|
|||||||
- Table-management docs clarified as theme-agnostic geometry/semantics contract.
|
- Table-management docs clarified as theme-agnostic geometry/semantics contract.
|
||||||
- Vapor shell CSS now supports reusable preset selection via `data-vapor-shell` and shared
|
- Vapor shell CSS now supports reusable preset selection via `data-vapor-shell` and shared
|
||||||
`--shell-*` tokens across default, auto-dark, and explicit `data-theme` modes.
|
`--shell-*` tokens across default, auto-dark, and explicit `data-theme` modes.
|
||||||
|
- Demo/scaffold runtime now includes a deterministic vapor preset selection strategy:
|
||||||
|
`?vapor_shell` → `localStorage["vapor_shell"]` → markup default.
|
||||||
|
|||||||
@@ -138,7 +138,8 @@ header height and non-Vapor accent choices.
|
|||||||
**Decision:** Introduce reusable shell palette presets in `theme-vapor` with stable preset IDs and
|
**Decision:** Introduce reusable shell palette presets in `theme-vapor` with stable preset IDs and
|
||||||
document them in `kit/patterns/theme-vapor/palette-catalog.md`. Presets are selected by
|
document them in `kit/patterns/theme-vapor/palette-catalog.md`. Presets are selected by
|
||||||
`data-vapor-shell` on the root element and resolve shared shell tokens for light/dark backgrounds,
|
`data-vapor-shell` on the root element and resolve shared shell tokens for light/dark backgrounds,
|
||||||
accent gradients, accent borders, and shell height.
|
accent gradients, accent borders, and shell height. Runtime selection precedence is standardized as
|
||||||
|
query `vapor_shell` → local storage key `vapor_shell` → markup default preset.
|
||||||
|
|
||||||
**Consequences:**
|
**Consequences:**
|
||||||
|
|
||||||
|
|||||||
@@ -1,2 +1,66 @@
|
|||||||
document.documentElement.classList.add("js");
|
(() => {
|
||||||
document.documentElement.setAttribute("data-theme-mode", "auto");
|
const root = document.documentElement;
|
||||||
|
const storageKey = "vapor_shell";
|
||||||
|
const queryKey = "vapor_shell";
|
||||||
|
const fallbackPreset = "miami-sunset";
|
||||||
|
const allowed = new Set([
|
||||||
|
"miami-sunset",
|
||||||
|
"neon-grid",
|
||||||
|
"laser-flamingo",
|
||||||
|
"synth-lagoon",
|
||||||
|
"mall-soft",
|
||||||
|
"hologram-sky",
|
||||||
|
"peach-drive",
|
||||||
|
"ultraviolet-plaza",
|
||||||
|
"cyber-mint",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const normalizePreset = (value) => {
|
||||||
|
const trimmed = (value || "").trim().toLowerCase();
|
||||||
|
if (!trimmed || !allowed.has(trimmed)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPreset = (value) => {
|
||||||
|
const preset = normalizePreset(value);
|
||||||
|
if (!preset) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
root.setAttribute("data-vapor-shell", preset);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
root.classList.add("js");
|
||||||
|
root.setAttribute("data-theme-mode", "auto");
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const fromQuery = normalizePreset(url.searchParams.get(queryKey));
|
||||||
|
if (fromQuery) {
|
||||||
|
setPreset(fromQuery);
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(storageKey, fromQuery);
|
||||||
|
} catch (_err) {
|
||||||
|
// Keep behavior deterministic even when storage is blocked.
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromMarkup = normalizePreset(root.getAttribute("data-vapor-shell"));
|
||||||
|
if (fromMarkup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fromStorage = normalizePreset(window.localStorage.getItem(storageKey));
|
||||||
|
if (fromStorage) {
|
||||||
|
setPreset(fromStorage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_err) {
|
||||||
|
// Fallback handled below.
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreset(fallbackPreset);
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{ define "demo_doc_start" }}
|
{{ define "demo_doc_start" }}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" data-vapor-shell="miami-sunset">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ This module is the active reusable theme source for design-code consumers.
|
|||||||
- Pattern contracts should reference shared behavior contracts and rely on this module for visual primitives.
|
- Pattern contracts should reference shared behavior contracts and rely on this module for visual primitives.
|
||||||
- When baseline visuals change, update this module first to keep downstream integration deterministic.
|
- When baseline visuals change, update this module first to keep downstream integration deterministic.
|
||||||
- For shell color selection, use `palette-catalog.md` and set `data-vapor-shell` on `<html>`.
|
- For shell color selection, use `palette-catalog.md` and set `data-vapor-shell` on `<html>`.
|
||||||
|
- For runtime switching, the recommended query key is `vapor_shell` (same IDs as the catalog).
|
||||||
- Keep host-specific brand overrides outside this module unless they are reusable presets.
|
- Keep host-specific brand overrides outside this module unless they are reusable presets.
|
||||||
|
|
||||||
## Boundary
|
## Boundary
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ Optional: host project can override shell height without changing preset IDs:
|
|||||||
:root { --shell-height: 72px; }
|
:root { --shell-height: 72px; }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Selection Approach
|
||||||
|
|
||||||
|
Recommended precedence for host integration:
|
||||||
|
|
||||||
|
1. `?vapor_shell=<preset-id>` query parameter (preview/share links)
|
||||||
|
2. `localStorage["vapor_shell"]` (remembered user choice)
|
||||||
|
3. `<html data-vapor-shell="...">` static default from server/templates
|
||||||
|
4. fallback preset: `miami-sunset`
|
||||||
|
|
||||||
|
The scaffold and demo JavaScript implement this precedence.
|
||||||
|
|
||||||
## Presets (Header + Accent)
|
## Presets (Header + Accent)
|
||||||
|
|
||||||
| Preset ID | Header core | Accent core | Default shell height |
|
| Preset ID | Header core | Accent core | Default shell height |
|
||||||
|
|||||||
@@ -8,3 +8,8 @@ Minimal starter skeleton for a Go web app using:
|
|||||||
|
|
||||||
This scaffold is intended as a starting point that host repositories can adapt.
|
This scaffold is intended as a starting point that host repositories can adapt.
|
||||||
|
|
||||||
|
Vapor shell preset integration is included:
|
||||||
|
|
||||||
|
- default preset via `<html data-vapor-shell="miami-sunset">`
|
||||||
|
- runtime override via `?vapor_shell=<preset-id>`
|
||||||
|
- persisted selection via `localStorage["vapor_shell"]`
|
||||||
|
|||||||
@@ -1,2 +1,65 @@
|
|||||||
document.documentElement.classList.add("js");
|
(() => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
const storageKey = "vapor_shell";
|
||||||
|
const queryKey = "vapor_shell";
|
||||||
|
const fallbackPreset = "miami-sunset";
|
||||||
|
const allowed = new Set([
|
||||||
|
"miami-sunset",
|
||||||
|
"neon-grid",
|
||||||
|
"laser-flamingo",
|
||||||
|
"synth-lagoon",
|
||||||
|
"mall-soft",
|
||||||
|
"hologram-sky",
|
||||||
|
"peach-drive",
|
||||||
|
"ultraviolet-plaza",
|
||||||
|
"cyber-mint",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const normalizePreset = (value) => {
|
||||||
|
const trimmed = (value || "").trim().toLowerCase();
|
||||||
|
if (!trimmed || !allowed.has(trimmed)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return trimmed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPreset = (value) => {
|
||||||
|
const preset = normalizePreset(value);
|
||||||
|
if (!preset) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
root.setAttribute("data-vapor-shell", preset);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
root.classList.add("js");
|
||||||
|
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const fromQuery = normalizePreset(url.searchParams.get(queryKey));
|
||||||
|
if (fromQuery) {
|
||||||
|
setPreset(fromQuery);
|
||||||
|
try {
|
||||||
|
window.localStorage.setItem(storageKey, fromQuery);
|
||||||
|
} catch (_err) {
|
||||||
|
// Keep behavior deterministic even when storage is blocked.
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromMarkup = normalizePreset(root.getAttribute("data-vapor-shell"));
|
||||||
|
if (fromMarkup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fromStorage = normalizePreset(window.localStorage.getItem(storageKey));
|
||||||
|
if (fromStorage) {
|
||||||
|
setPreset(fromStorage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (_err) {
|
||||||
|
// Fallback handled below.
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreset(fallbackPreset);
|
||||||
|
})();
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{ define "base.html" }}
|
{{ define "base.html" }}
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en" data-vapor-shell="miami-sunset">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -19,4 +19,3 @@
|
|||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user