// Hotel Walks — shared UI primitives.
// Use sparingly; layout is mostly bespoke per screen.
const UI = (() => {
const C = {
ink: "var(--ink)",
bone: "var(--bone)",
bone2: "var(--bone-2)",
rule: "var(--rule)",
ruleStrong: "var(--rule-strong)",
muted: "var(--muted)",
warn: "var(--warn)",
good: "var(--good)",
};
function Caps({ children, color, style, className }) {
return (
{children}
);
}
function SerifI({ children, size = 24, style }) {
return (
{children}
);
}
// Primary button — Ink filled on Bone, or Bone filled on Ink.
function Btn({ children, onClick, kind = "primary", size = "md", disabled, style, type, full, as = "button", href }) {
const sizes = {
sm: { padding: "9px 14px", fontSize: 12, letterSpacing: "0.08em" },
md: { padding: "13px 22px", fontSize: 12, letterSpacing: "0.12em" },
lg: { padding: "18px 28px", fontSize: 13, letterSpacing: "0.14em" },
};
const base = {
...sizes[size],
fontFamily: "'JetBrains Mono', monospace",
textTransform: "uppercase",
border: "1px solid " + C.ink,
borderRadius: 0,
cursor: disabled ? "not-allowed" : "pointer",
opacity: disabled ? 0.4 : 1,
transition: "background 0.12s, color 0.12s, border-color 0.12s",
width: full ? "100%" : undefined,
display: "inline-block",
textAlign: "center",
textDecoration: "none",
whiteSpace: "nowrap",
};
const kinds = {
primary: { background: C.ink, color: C.bone },
ghost: { background: "transparent", color: C.ink },
danger: { background: "transparent", color: C.warn, borderColor: C.warn },
onInk: { background: C.bone, color: C.ink, borderColor: C.bone },
onInkGhost: { background: "transparent", color: C.bone, borderColor: C.bone },
};
const Tag = as;
return (
{children}
);
}
// Field — label sits as a caps row above the underlined input.
function Field({ label, suffix, children, hint, style }) {
return (
);
}
function Input({ value, onChange, placeholder, type = "text", mono, style }) {
return (
);
}
// Numeric stepper — for room counts, rates, days
function Stepper({ value, onChange, min = 0, max = 99, step = 1, prefix, mono = true, width = 140 }) {
const v = value;
const set = (n) => onChange(Math.max(min, Math.min(max, n)));
return (
{prefix}{v}
);
}
// Small stroke icons paired with status pills — non-color-only signals.
function PillIcon({ tone }) {
const sw = 1.6;
const common = { width: 11, height: 11, viewBox: "0 0 12 12", fill: "none", stroke: "currentColor", strokeWidth: sw, strokeLinecap: "square", strokeLinejoin: "round" };
if (tone === "warn") return (
);
if (tone === "good") return (
);
return null;
}
// Pill — for status badges. Filled (solid) for warn/good/filled; outline for neutral/muted.
// Auto-prepends a small icon for warn/good tones.
function Pill({ children, tone = "neutral", style, icon = "auto" }) {
const tones = {
neutral: { color: C.ink, border: "1px solid " + C.ruleStrong, background: "transparent" },
filled: { color: C.bone, border: "1px solid " + C.ink, background: C.ink },
warn: { color: C.bone, border: "1px solid " + C.warn, background: C.warn },
good: { color: C.bone, border: "1px solid " + C.good, background: C.good },
muted: { color: C.muted, border: "1px solid " + C.rule, background: "transparent" },
};
const showIcon = icon === "auto" ? (tone === "warn" || tone === "good") : !!icon;
return (
{showIcon && }
{children}
);
}
// Toggle — for no-show, urgent etc.
function Toggle({ on, onChange, label }) {
return (
);
}
// Plain horizontal rule
function Hr({ tone = "default", style }) {
return
;
}
// Section heading — caps eyebrow + italic title
function SectionHead({ eyebrow, title, sub, right, style }) {
return (
{eyebrow &&
{eyebrow}
}
{title}
{sub &&
{sub}
}
{right}
);
}
// Money helpers — integer dollars look natural; fractions get 2 decimals.
const money = (n) => {
const v = Number(n);
const hasFrac = Math.round(v * 100) !== Math.round(v) * 100;
return "$" + v.toLocaleString(undefined, {
minimumFractionDigits: hasFrac ? 2 : 0,
maximumFractionDigits: 2,
});
};
return { C, Caps, SerifI, Btn, Field, Input, Stepper, Pill, Toggle, Hr, SectionHead, money };
})();
window.UI = UI;