Add menu/popover + theme-switch partials (todo §1); data-driven .menu (items/check-groups/positioning), Light/Auto/Dark switch, shell reuses both
This commit is contained in:
37
views/partials/menu.ejs
Normal file
37
views/partials/menu.ejs
Normal file
@@ -0,0 +1,37 @@
|
||||
<%#
|
||||
Popover menu: pure <details>/<summary>, zero-JS. Mirrors html-css-foundation .menu.
|
||||
Config:
|
||||
trigger { class?(="btn", "" ⇒ none) · label?(aria-label) · icon? · text? · html?(raw inner, wins) }
|
||||
align? "left" left-align the popover (default right)
|
||||
up? boolean open upward (footer menus)
|
||||
open? boolean start open
|
||||
kebab? boolean bare kebab trigger (adds .kebab)
|
||||
width? number|string popover min-width (number ⇒ px)
|
||||
items: Item[] popover content, top→bottom
|
||||
Item ∈ { head } · { sep } · { label, icon?, href? ⇒ <a>, danger? } (default: menu-item button)
|
||||
· { group: { legend?, name, control?(="checkbox"|"radio"), options:{value,label,checked?}[] } }
|
||||
%><%
|
||||
const t = locals.trigger || {};
|
||||
const sumCls = "class" in t ? t.class : "btn";
|
||||
const items = locals.items || [];
|
||||
const popCls = "menu-pop" + (locals.align === "left" ? " left" : "") + (locals.up ? " up" : "");
|
||||
const width = locals.width;
|
||||
-%>
|
||||
<details class="menu<%= locals.kebab ? " kebab" : "" %>"<%= locals.open ? " open" : "" %>>
|
||||
<summary<% if (sumCls) { %> class="<%= sumCls %>"<% } %><% if (t.label) { %> aria-label="<%= t.label %>"<% } %>><% if (t.html != null) { %><%- t.html %><% } else { if (t.icon) { %><svg class="ico ico-sm"><use href="#<%= t.icon %>"/></svg><% } if (t.text) { %><%= t.text %><% } } %></summary>
|
||||
<div class="<%= popCls %>"<% if (width != null) { %> style="min-width:<%= typeof width === "number" ? width + "px" : width %>"<% } %>>
|
||||
<% items.forEach((it) => { -%>
|
||||
<% if (it.head != null) { -%>
|
||||
<div class="menu-head"><%= it.head %></div>
|
||||
<% } else if (it.sep) { -%>
|
||||
<div class="menu-sep"></div>
|
||||
<% } else if (it.group) { const g = it.group; -%>
|
||||
<fieldset class="menu-field"><% if (g.legend) { %><legend class="menu-head"><%= g.legend %></legend><% } %><% g.options.forEach((o) => { %><label class="menu-check"><input type="<%= g.control || "checkbox" %>" name="<%= g.name %>" value="<%= o.value %>"<%= o.checked ? " checked" : "" %>><%= o.label %></label><% }) %></fieldset>
|
||||
<% } else if (it.href) { -%>
|
||||
<a class="menu-item<%= it.danger ? " danger" : "" %>" href="<%= it.href %>"><% if (it.icon) { %><svg class="ico"><use href="#<%= it.icon %>"/></svg><% } %><%= it.label %></a>
|
||||
<% } else { -%>
|
||||
<button class="menu-item<%= it.danger ? " danger" : "" %>" type="button"><% if (it.icon) { %><svg class="ico"><use href="#<%= it.icon %>"/></svg><% } %><%= it.label %></button>
|
||||
<% } -%>
|
||||
<% }) -%>
|
||||
</div>
|
||||
</details>
|
||||
@@ -36,13 +36,10 @@
|
||||
<nav class="nav" aria-label="Main navigation"><%- nav %></nav>
|
||||
|
||||
<div class="side-footer">
|
||||
<div class="theme-switch" role="radiogroup" aria-label="Color theme">
|
||||
<label><input type="radio" name="theme" id="theme-light" /><span>Light</span></label>
|
||||
<label><input type="radio" name="theme" id="theme-auto" checked /><span>Auto</span></label>
|
||||
<label><input type="radio" name="theme" id="theme-dark" /><span>Dark</span></label>
|
||||
</div>
|
||||
<%- include("theme-switch", { value: locals.theme }) %>
|
||||
|
||||
<div class="footer-actions">
|
||||
<%# profile menu stays inline: the summary composes escaped user values %>
|
||||
<details class="menu" style="flex:1 1 auto">
|
||||
<summary class="profile">
|
||||
<span class="avatar" aria-hidden="true"><%= user.initials %></span>
|
||||
@@ -51,20 +48,18 @@
|
||||
<% if (user.email) { %><span class="profile-mail"><%= user.email %></span><% } %>
|
||||
</span>
|
||||
</summary>
|
||||
<div class="menu-pop left" style="bottom:calc(100% + 6px); top:auto; min-width:220px">
|
||||
<div class="menu-pop left up" style="min-width:220px">
|
||||
<div class="menu-head">Signed in as <%= user.name %></div>
|
||||
<button class="menu-item"><svg class="ico"><use href="#i-user" /></svg>Profile</button>
|
||||
<button class="menu-item danger"><svg class="ico"><use href="#i-logout" /></svg>Sign out</button>
|
||||
<button class="menu-item" type="button"><svg class="ico"><use href="#i-user" /></svg>Profile</button>
|
||||
<button class="menu-item danger" type="button"><svg class="ico"><use href="#i-logout" /></svg>Sign out</button>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="menu">
|
||||
<summary class="btn icon-btn" aria-label="Settings"><svg class="ico"><use href="#i-gear" /></svg></summary>
|
||||
<div class="menu-pop" style="bottom:calc(100% + 6px); top:auto">
|
||||
<div class="menu-head">Settings</div>
|
||||
<button class="menu-item"><svg class="ico"><use href="#i-gear" /></svg>Preferences</button>
|
||||
</div>
|
||||
</details>
|
||||
<%- include("menu", {
|
||||
up: true,
|
||||
trigger: { class: "btn icon-btn", label: "Settings", html: '<svg class="ico"><use href="#i-gear"/></svg>' },
|
||||
items: [{ head: "Settings" }, { label: "Preferences", icon: "i-gear" }],
|
||||
}) %>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
14
views/partials/theme-switch.ejs
Normal file
14
views/partials/theme-switch.ejs
Normal file
@@ -0,0 +1,14 @@
|
||||
<%#
|
||||
Theme switcher: Light / Auto / Dark radiogroup. Zero-JS — the radios drive the
|
||||
html:has(#theme-…:checked) token swaps in styles.css, so the ids are a fixed CSS
|
||||
contract (one switch per page). Config: value? ∈ light|auto|dark (default auto,
|
||||
rendered checked) · label?.
|
||||
%><%
|
||||
const value = locals.value || "auto";
|
||||
const label = locals.label || "Color theme";
|
||||
-%>
|
||||
<div class="theme-switch" role="radiogroup" aria-label="<%= label %>">
|
||||
<label><input type="radio" name="theme" id="theme-light"<%= value === "light" ? " checked" : "" %>><span>Light</span></label>
|
||||
<label><input type="radio" name="theme" id="theme-auto"<%= value === "auto" ? " checked" : "" %>><span>Auto</span></label>
|
||||
<label><input type="radio" name="theme" id="theme-dark"<%= value === "dark" ? " checked" : "" %>><span>Dark</span></label>
|
||||
</div>
|
||||
Reference in New Issue
Block a user