// ============================================================ // vibn-ai-templates/components.jsx // ------------------------------------------------------------ // The core component set. Every visual property is wired to a // CSS variable from tokens.css — flipping `class="theme-glass"` // (or any other theme class) reskins the whole library. // // Components export to `window` for use in script-tag HTML // projects. In a real codebase, swap the bottom-of-file // assignment for `export { … }`. // // Components included: // Button, IconButton, Field, Input, Textarea, Select, // Checkbox, Radio, Switch, Card, Badge, Tag, Avatar, // AvatarStack, Tabs, Table, Modal, Banner, Divider, // FieldGroup, KBD, Spinner. // ============================================================ // ─── Helpers ───────────────────────────────────────────────── const cx = (...names) => names.filter(Boolean).join(" "); const noop = () => {}; // ─── Button ────────────────────────────────────────────────── // variant: primary (default), secondary, ghost, destructive // size: sm | md (default) | lg // leadingIcon / trailingIcon: // loading: disables and shows a spinner const Button = ({ children, variant = "primary", size = "md", full = false, leadingIcon, trailingIcon, loading, disabled, onClick = noop, style, type = "button", ...rest }) => { const sizing = { sm: { padY: 6, padX: 12, font: "var(--text-sm)", iconSize: 13 }, md: { padY: 9, padX: 16, font: "var(--text-md)", iconSize: 15 }, lg: { padY: 12, padX: 22, font: "var(--text-lg)", iconSize: 16 }, }[size]; const variants = { primary: { background: "var(--button-bg)", color: "var(--button-fg)", border: "1px solid var(--button-border)", }, secondary: { background: "var(--button-secondary-bg)", color: "var(--button-secondary-fg)", border: "1px solid var(--button-secondary-border)", }, ghost: { background: "transparent", color: "var(--button-ghost-fg)", border: "1px solid transparent", }, destructive: { background: "var(--danger)", color: "#ffffff", border: "1px solid var(--danger)", }, }[variant]; return ( ); }; // ─── IconButton ────────────────────────────────────────────── const IconButton = ({ icon, name, size = "md", variant = "ghost", onClick = noop, label, style }) => { const dims = { sm: 28, md: 32, lg: 38 }[size]; const iconSize = { sm: 14, md: 16, lg: 18 }[size]; const variants = { ghost: { background: "transparent", color: "var(--text-2)", border: "1px solid transparent" }, secondary: { background: "var(--button-secondary-bg)", color: "var(--button-secondary-fg)", border: "1px solid var(--button-secondary-border)" }, }[variant]; return ( ); }; // ─── Spinner ───────────────────────────────────────────────── const Spinner = ({ size = 14, stroke = 2 }) => ( ); // ─── Field (wraps a labelled input with hint / error) ──────── const Field = ({ label, hint, error, optional, htmlFor, children, style }) => (
{label && ( )} {children} {(hint || error) && (
{error || hint}
)}
); // ─── Input ─────────────────────────────────────────────────── // Bare input (use inside ). leadingIcon / trailingIcon // add an inner ornament. invalid red-rings the border. const Input = ({ value, placeholder, type = "text", leadingIcon, trailingIcon, invalid, disabled, autofocus, onChange = noop, id, style, ...rest }) => (
{leadingIcon && {leadingIcon}} onChange(e.target.value, e)} style={{ flex: 1, minWidth: 0, border: "none", outline: "none", background: "transparent", fontFamily: "inherit", fontSize: "inherit", color: "inherit", padding: 0, }} {...rest} /> {trailingIcon && {trailingIcon}}
); // ─── Textarea ──────────────────────────────────────────────── const Textarea = ({ value, placeholder, rows = 4, onChange = noop, invalid, id, style, ...rest }) => (