generated from coulomb/repo-seed
Integrate whynot-design into Economic Observatory UI
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.
This commit is contained in:
164
projects/coulomb-pricing/ui/vendor/whynot-design/elements/atoms.js
vendored
Normal file
164
projects/coulomb-pricing/ui/vendor/whynot-design/elements/atoms.js
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
/* =============================================================
|
||||
* @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 };
|
||||
Reference in New Issue
Block a user