generated from coulomb/repo-seed
Vendor whynot-design Layer 1 (tokens, CSS) and Layer 2 (<wn-*> components) via scripts/sync-whynot-design.sh with a pinned ref. Migrate the observatory shell to canonical web components, keep observatory-specific layout in styles.css, and add vendor integrity tests plus correct JS MIME types on the dev server.
165 lines
5.8 KiB
JavaScript
165 lines
5.8 KiB
JavaScript
/* =============================================================
|
|
* @whynot/design — atoms.js
|
|
* ------------------------------------------------------------
|
|
* <wn-button>, <wn-tag>, <wn-eyebrow>, <wn-stamp>,
|
|
* <wn-stage-dot>, <wn-phase-dot>, <wn-icon>
|
|
*
|
|
* Shadow-DOM components. Each adopts the shared component
|
|
* stylesheet so utility classes inside the shadow root work.
|
|
* Token CSS variables cascade through shadow boundaries
|
|
* because they're inherited properties.
|
|
* ============================================================= */
|
|
|
|
import { LitElement, html, nothing } from "lit";
|
|
import { getSharedSheet } from "./_styles.js";
|
|
import { ICON_PATHS } from "./icons.js";
|
|
|
|
class WnBase extends LitElement {
|
|
static styles = [];
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
// Adopt the shared sheet on first connect, after super() has built the shadow root.
|
|
const root = this.shadowRoot;
|
|
if (root && !root.adoptedStyleSheets.includes(getSharedSheet())) {
|
|
root.adoptedStyleSheets = [...root.adoptedStyleSheets, getSharedSheet()];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------- <wn-button> ---------- */
|
|
export class WnButton extends WnBase {
|
|
static properties = {
|
|
variant: { type: String, reflect: true },
|
|
size: { type: String, reflect: true },
|
|
icon: { type: String },
|
|
iconEnd: { type: String, attribute: "icon-end" },
|
|
type: { type: String },
|
|
disabled: { type: Boolean, reflect: true },
|
|
href: { type: String },
|
|
};
|
|
constructor() {
|
|
super();
|
|
this.variant = "secondary";
|
|
this.size = "md";
|
|
this.type = "button";
|
|
this.disabled = false;
|
|
}
|
|
render() {
|
|
const cls = [
|
|
"wn-btn",
|
|
this.variant && this.variant !== "secondary" ? `wn-btn--${this.variant}` : "",
|
|
this.size === "sm" ? "wn-btn--sm" : this.size === "lg" ? "wn-btn--lg" : "",
|
|
].filter(Boolean).join(" ");
|
|
const iconStart = this.icon
|
|
? html`<wn-icon name=${this.icon} size="sm" class="wn-btn__icon"></wn-icon>`
|
|
: nothing;
|
|
const iconEnd = this.iconEnd
|
|
? html`<wn-icon name=${this.iconEnd} size="sm" class="wn-btn__icon"></wn-icon>`
|
|
: nothing;
|
|
if (this.href) {
|
|
return html`<a class=${cls} href=${this.href} part="button"
|
|
aria-disabled=${this.disabled ? "true" : "false"}>${iconStart}<slot></slot>${iconEnd}</a>`;
|
|
}
|
|
return html`<button class=${cls} part="button"
|
|
type=${this.type} ?disabled=${this.disabled}>${iconStart}<slot></slot>${iconEnd}</button>`;
|
|
}
|
|
}
|
|
|
|
/* ---------- <wn-tag> ---------- */
|
|
export class WnTag extends WnBase {
|
|
static properties = {
|
|
active: { type: Boolean, reflect: true },
|
|
draft: { type: Boolean, reflect: true },
|
|
};
|
|
render() {
|
|
const cls = ["wn-tag",
|
|
this.active ? "wn-tag--active" : "",
|
|
this.draft ? "wn-tag--draft" : "",
|
|
].filter(Boolean).join(" ");
|
|
return html`<span class=${cls} part="tag"><slot></slot></span>`;
|
|
}
|
|
}
|
|
|
|
/* ---------- <wn-eyebrow> ---------- */
|
|
export class WnEyebrow extends WnBase {
|
|
static properties = { strong: { type: Boolean, reflect: true } };
|
|
render() {
|
|
const cls = "wn-eyebrow" + (this.strong ? " wn-eyebrow--strong" : "");
|
|
return html`<span class=${cls} part="eyebrow"><slot></slot></span>`;
|
|
}
|
|
}
|
|
|
|
/* ---------- <wn-stamp> ---------- */
|
|
export class WnStamp extends WnBase {
|
|
render() { return html`<span class="wn-stamp" part="stamp"><slot></slot></span>`; }
|
|
}
|
|
|
|
/* ---------- <wn-stage-dot> ---------- */
|
|
export class WnStageDot extends WnBase {
|
|
static properties = {
|
|
level: { type: String, reflect: true },
|
|
label: { type: String },
|
|
};
|
|
constructor() { super(); this.level = "S2"; }
|
|
render() {
|
|
const lvl = String(this.level || "S2").toLowerCase();
|
|
const cls = `wn-dot wn-stage-dot wn-stage-dot--${lvl}`;
|
|
return html`
|
|
<span class=${cls} part="root">
|
|
<span class="wn-dot__bullet"></span>
|
|
<slot>${this.label || this.level}</slot>
|
|
</span>`;
|
|
}
|
|
}
|
|
|
|
/* ---------- <wn-phase-dot> ---------- */
|
|
export class WnPhaseDot extends WnBase {
|
|
static properties = {
|
|
state: { type: String, reflect: true },
|
|
num: { type: String, reflect: true },
|
|
};
|
|
constructor() { super(); this.state = "todo"; this.num = ""; }
|
|
render() {
|
|
const cls = `wn-phase-dot wn-phase-dot--${this.state}`;
|
|
const glyph = this.state === "done" ? "✓" : this.num;
|
|
return html`
|
|
<span class=${cls} part="root">
|
|
<span class="wn-phase-dot__bullet">${glyph}</span>
|
|
<slot></slot>
|
|
</span>`;
|
|
}
|
|
}
|
|
|
|
/* ---------- <wn-icon> ---------- */
|
|
export class WnIcon extends WnBase {
|
|
static properties = {
|
|
name: { type: String, reflect: true },
|
|
size: { type: String, reflect: true },
|
|
};
|
|
constructor() { super(); this.size = "md"; }
|
|
render() {
|
|
const path = ICON_PATHS[this.name];
|
|
const cls = `wn-icon wn-icon--${this.size || "md"}`;
|
|
if (!path) {
|
|
return html`<svg class=${cls} viewBox="0 0 24 24" aria-hidden="true" part="svg">
|
|
<rect x="3" y="3" width="18" height="18" rx="2" fill="none" stroke="currentColor"/>
|
|
</svg>`;
|
|
}
|
|
return html`<svg class=${cls} viewBox="0 0 24 24" aria-hidden="true" part="svg">${
|
|
path.map(d => html`<path d=${d}></path>`)
|
|
}</svg>`;
|
|
}
|
|
}
|
|
|
|
export function defineAtoms() {
|
|
if (!customElements.get("wn-button")) customElements.define("wn-button", WnButton);
|
|
if (!customElements.get("wn-tag")) customElements.define("wn-tag", WnTag);
|
|
if (!customElements.get("wn-eyebrow")) customElements.define("wn-eyebrow", WnEyebrow);
|
|
if (!customElements.get("wn-stamp")) customElements.define("wn-stamp", WnStamp);
|
|
if (!customElements.get("wn-stage-dot")) customElements.define("wn-stage-dot", WnStageDot);
|
|
if (!customElements.get("wn-phase-dot")) customElements.define("wn-phase-dot", WnPhaseDot);
|
|
if (!customElements.get("wn-icon")) customElements.define("wn-icon", WnIcon);
|
|
}
|
|
|
|
export { WnBase };
|