// ===== ai-shared/kit.js — shared primitives for the BetterDocs AI surfaces ===== // Ported from the AI Studio mockup (ai-kit.jsx). Plain-React components styled // with inline styles + the CSS custom properties defined in tokens.scss. Every // portal root carries `.bd-ai-scope` so those `var(--x)` tokens resolve. // // Consumed by BOTH the Write-with-AI modal and the restyled Edit-with-AI modal. import { useState, useEffect, useRef, createPortal } from "@wordpress/element"; import Select from "react-select"; // ── Icons (stroke 1.7, currentColor — same convention as the mockup) ───────── export const AIcon = ({ d, size = 16, fill = "none", stroke = "currentColor", sw = 1.7, children, ...p }) => ( {d && } {children} ); export const IcClose = (p) => ; export const IcChevDown = (p) => ; export const IcPlus = (p) => ; export const IcTrash = (p) => ; export const IcGrip = (p) => ( ); export const IcCheck = (p) => ; export const IcArrowR = (p) => ; export const IcRefresh = (p) => ; export const IcAlert = (p) => ; export const IcList = (p) => ; export const IcSummary = (p) => ; // BetterDocs AI mark — the accent-colored pencil from the modal header export const AIMark = ({ size = 18 }) => ( ); export const AIMarkWhite = ({ size = 16 }) => ( ); export const AIBadgeIcon = ({ size = 14 }) => ( ); // ── AI icon disc (header) ──────────────────────────────────────────────────── export const AIDisc = ({ size = 44 }) => (
); // ── Buttons ────────────────────────────────────────────────────────────────── export const PrimaryBtn = ({ children, icon, onClick, disabled, style }) => ( ); // Outlined accent button — uses the AI accent color export const AccentBtn = ({ children, icon, onClick, disabled, style }) => ( ); export const GhostBtn = ({ children, icon, onClick, style, disabled }) => ( ); // Quiet tertiary text button — used for "Discard" export const TextBtn = ({ children, onClick, tone = "muted", style }) => { const color = tone === "danger" ? "#dc2626" : "var(--muted)"; return ( ); }; // ── "AI generated" pill ────────────────────────────────────────────────────── // Neutral ash pill so the surface isn't flooded with green — the AI identity is // carried by the small green sparkle icon alone. export const AIPill = ({ children = "AI generated", style }) => ( {children} ); // ── WP-style notice banner (light red, left accent bar) ────────────────────── export const Banner = ({ tone = "error", children, style }) => { const t = tone === "error" ? { bg: "var(--wp-red-50)", bar: "var(--wp-red)", fg: "#8a1f20", icon: "var(--wp-red)" } : { bg: "#fef6e0", bar: "#d9a406", fg: "#8a6400", icon: "#d9a406" }; return (
{children}
); }; // ── Spinner + inline "working" status ──────────────────────────────────────── export const Spinner = ({ size = 18, color = "var(--ai)", sw = 2.4 }) => ( ); export const WorkingDots = ({ color = "var(--ai)" }) => ( {[0, 1, 2].map(i => ( ))} ); // ── Inputs ─────────────────────────────────────────────────────────────────── export const FieldLabel = ({ children, required, style }) => (
{children}{required && *}
); export const TextInput = ({ value, onChange, placeholder, autoFocus, disabled }) => { const [focus, setFocus] = useState(false); return ( onChange(e.target.value)} onFocus={() => setFocus(true)} onBlur={() => setFocus(false)} style={{ width: "100%", border: `1px solid ${focus ? "var(--green)" : "var(--border)"}`, background: focus ? "white" : "#fcfcfd", boxShadow: focus ? "0 0 0 3px rgba(0,184,132,.14)" : "none", borderRadius: 10, padding: "12px 14px", fontSize: 14, color: "var(--ink)", outline: "none", transition: "border-color .15s, box-shadow .15s, background .15s", }} /> ); }; export const TextArea = ({ value, onChange, placeholder, rows = 4, disabled }) => { const [focus, setFocus] = useState(false); return (