// ===== 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 }) => (
);
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 (