Add field + auth-card partials (todo §1); data-driven .field (label/icon/hint/server error) and auth-card shell (head/SSO/body/alt)
This commit is contained in:
31
views/partials/auth-card.ejs
Normal file
31
views/partials/auth-card.ejs
Normal file
@@ -0,0 +1,31 @@
|
||||
<%#
|
||||
Auth card: the <form class="auth-card"> shell — head (back · title · sub),
|
||||
optional SSO providers + divider, a body slot (field partials + submit), alt footer.
|
||||
Mirrors html-css-foundation auth markup. Config:
|
||||
title, sub?
|
||||
method? (default "post"), action?
|
||||
back? { href, label } back link above the title
|
||||
sso? { label?, divider?, providers: Provider[] } omit / empty ⇒ no SSO section
|
||||
Provider: { label, logo? (text) | icon? (sprite id), href? ⇒ <a>, else <button> }
|
||||
body pre-rendered HTML placed inside .auth-form (fields + submit)
|
||||
alt? { text, href, label } centered footer line
|
||||
%><%
|
||||
const method = locals.method || "post";
|
||||
const back = locals.back;
|
||||
const sso = locals.sso;
|
||||
const providers = (sso && sso.providers) || [];
|
||||
const alt = locals.alt;
|
||||
-%>
|
||||
<form class="auth-card" method="<%= method %>"<% if (locals.action) { %> action="<%= locals.action %>"<% } %>>
|
||||
<div class="auth-head"><% if (back) { %><a class="auth-back" href="<%= back.href %>"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-arrow-left"/></svg><%= back.label %></a><% } %><h1><%= locals.title %></h1><% if (locals.sub) { %><p class="auth-sub"><%= locals.sub %></p><% } %></div>
|
||||
<% if (providers.length) { -%>
|
||||
<div class="sso" aria-label="<%= sso.label || "Single sign-on options" %>">
|
||||
<ul class="sso-list"><% providers.forEach((p) => { %><li><% if (p.href) { %><a class="sso-btn" href="<%= p.href %>"><% } else { %><button type="button" class="sso-btn"><% } %><span class="sso-logo" aria-hidden="true"><% if (p.icon) { %><svg class="ico ico-sm"><use href="#<%= p.icon %>"/></svg><% } else { %><%= p.logo %><% } %></span><span class="sso-label"><%= p.label %></span><% if (p.href) { %></a><% } else { %></button><% } %></li><% }) %></ul>
|
||||
<div class="auth-divider"><%= sso.divider || "or" %></div>
|
||||
</div>
|
||||
<% } -%>
|
||||
<div class="auth-form"><%- locals.body || "" %></div>
|
||||
<% if (alt) { -%>
|
||||
<p class="auth-alt"><%= alt.text %> <a href="<%= alt.href %>"><%= alt.label %></a></p>
|
||||
<% } -%>
|
||||
</form>
|
||||
34
views/partials/field.ejs
Normal file
34
views/partials/field.ejs
Normal file
@@ -0,0 +1,34 @@
|
||||
<%#
|
||||
Form field: label (+ inline link / "Optional") · icon input · hint · error.
|
||||
Mirrors html-css-foundation auth markup. Config:
|
||||
id, name, label
|
||||
type? (default "text"), value?, placeholder?, autocomplete?, required?, minlength?
|
||||
icon? input-ico id (e.g. "i-mail") → left-padded input
|
||||
optional? show an "Optional" tag in the label row
|
||||
link? { href, label } inline beside the label (e.g. Forgot password?)
|
||||
hint? muted helper text under the input
|
||||
error? string | { text } | { html } — server-driven; sets aria-invalid + describedby
|
||||
%><%
|
||||
const id = locals.id;
|
||||
const type = locals.type || "text";
|
||||
const icon = locals.icon;
|
||||
const optional = !!locals.optional;
|
||||
const link = locals.link;
|
||||
const hint = locals.hint;
|
||||
const error = locals.error;
|
||||
const errId = id + "-err";
|
||||
-%>
|
||||
<div class="field<% if (error) { %> has-error<% } %>">
|
||||
<% if (link || optional) { -%>
|
||||
<div class="field-top"><label for="<%= id %>"><%= locals.label %></label><% if (link) { %><a class="field-link" href="<%= link.href %>"><%= link.label %></a><% } else { %><span class="optional">Optional</span><% } %></div>
|
||||
<% } else { -%>
|
||||
<label for="<%= id %>"><%= locals.label %></label>
|
||||
<% } -%>
|
||||
<div class="input-wrap"><% if (icon) { %><svg class="ico ico-sm input-ico" aria-hidden="true"><use href="#<%= icon %>"/></svg><% } %><input class="input<% if (icon) { %> has-ico<% } %>" id="<%= id %>" name="<%= locals.name %>" type="<%= type %>"<% if (locals.autocomplete) { %> autocomplete="<%= locals.autocomplete %>"<% } %><% if (locals.placeholder) { %> placeholder="<%= locals.placeholder %>"<% } %><% if (locals.value != null) { %> value="<%= locals.value %>"<% } %><% if (locals.minlength != null) { %> minlength="<%= locals.minlength %>"<% } %><% if (error) { %> aria-invalid="true" aria-describedby="<%= errId %>"<% } %><% if (locals.required) { %> required<% } %>></div>
|
||||
<% if (hint) { -%>
|
||||
<span class="field-hint"><%= hint %></span>
|
||||
<% } -%>
|
||||
<% if (error) { -%>
|
||||
<p class="field-error" id="<%= errId %>" role="alert"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-alert"/></svg><span><% if (error.html != null) { %><%- error.html %><% } else { %><%= typeof error === "string" ? error : error.text %><% } %></span></p>
|
||||
<% } -%>
|
||||
</div>
|
||||
Reference in New Issue
Block a user