/** * Shared swatch-picker preview icons. * * Shape, layout and selector-style icons are identical between the base * General tab and extension extras, so they live here and are keyed by plain * strings to avoid coupling separate shape/layout/style unions. */ import React from "react"; export const MATERIAL_IMAGE_DEMO_ITEMS = [ { value: "oak", label: "Oak", background: "#b98245", accent: "#6f4624", motif: "grain", }, { value: "denim", label: "Denim", background: "#2f5f8f", accent: "#dbeafe", motif: "stitch", }, { value: "marble", label: "Marble", background: "#f8fafc", accent: "#94a3b8", motif: "vein", }, { value: "clay", label: "Clay", background: "#c65f3f", accent: "#fed7aa", motif: "arc", }, { value: "moss", label: "Moss", background: "#556b3f", accent: "#c7d2a6", motif: "leaf", }, { value: "carbon", label: "Carbon", background: "#111827", accent: "#64748b", motif: "grid", }, ] as const; export function buildMaterialSvgDataUri( item: (typeof MATERIAL_IMAGE_DEMO_ITEMS)[number], ): string { const motifs: Record< (typeof MATERIAL_IMAGE_DEMO_ITEMS)[number]["motif"], string > = { grain: ``, stitch: ``, vein: ``, arc: ``, leaf: ``, grid: ``, }; const svg = `${motifs[item.motif]}`; // encodeURIComponent leaves ( ) ' untouched, but the gradient reference // `fill="url(#g)"` must not close an unquoted CSS `url(...)` early, so // escape those characters too. const encoded = encodeURIComponent(svg).replace( /[()']/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`, ); return `data:image/svg+xml;charset=utf-8,${encoded}`; } /** * Layout picker icon shared by the free General tab and the extension extras. * Slider and flow icons carry animated parts (keyframes live in * src/admin/index.css so both tiers ship them). */ export function LayoutPreviewIcon({ layout, }: { layout: string; }): React.ReactElement { switch (layout) { case "grid": return ( ); case "pill": return ( ); case "slider": return ( ); case "flow": return ( ); case "wrap": default: return ( ); } } /** * Selector-style picker icon shared by the free General tab and the extension * extras. The selected-state indicator (underline bar, outline ring or * background halo) animates in on a loop; keyframes live in src/admin/index.css. */ export function SelectorStylePreviewIcon({ style, }: { style: string; }): React.ReactElement { const idle = ( ); if (style === "outline") { return ( {idle} ); } if (style === "background") { return ( {idle} ); } return ( {idle} ); } export function ShapePreviewIcon({ shape, }: { shape: string; }): React.ReactElement { switch (shape) { case "soft": return ( ); case "rounded": return ( ); case "squircle": return ( ); case "circle": return ( ); case "diamond": return ( ); case "hexagon": return ( ); case "square": default: return ( ); } } /** * Tooltip position picker icon shared by the free Tooltips tab. The bubble * fades in from the swatch side (keyframes in src/admin/index.css). */ export function TooltipPositionPreviewIcon({ position, selected, }: { position: "top" | "bottom" | "left" | "right"; selected: boolean; }): React.ReactElement { const swatchFill = selected ? "#3b82f6" : "#94a3b8"; const swatchStroke = selected ? "#2563eb" : "#cbd5e1"; const tooltipFill = selected ? "#1d4ed8" : "#64748b"; const caretFill = tooltipFill; const lineFill = selected ? "#93c5fd" : "#cbd5e1"; /* ── swatch center ── */ const cx = 42; const cy = 32; const sw = 20; // swatch width const sh = 14; // swatch height /* ── tooltip pill ── */ const tw = 30; // tooltip width const th = 8; // tooltip height const gap = 4; // space between swatch edge and tooltip pill const tooltip = { top: { x: cx - tw / 2, y: cy - sh / 2 - gap - th }, bottom: { x: cx - tw / 2, y: cy + sh / 2 + gap }, left: { x: cx - sw / 2 - gap - tw, y: cy - th / 2 }, right: { x: cx + sw / 2 + gap, y: cy - th / 2 }, }[position]; /* ── caret (small triangle pointing toward swatch) ── */ const cs = 4; // caret size const caret = { top: `M${cx - cs},${tooltip.y + th} L${cx},${tooltip.y + th + cs} L${cx + cs},${tooltip.y + th}`, bottom: `M${cx - cs},${tooltip.y} L${cx},${tooltip.y - cs} L${cx + cs},${tooltip.y}`, left: `M${tooltip.x + tw},${cy - cs} L${tooltip.x + tw + cs},${cy} L${tooltip.x + tw},${cy + cs}`, right: `M${tooltip.x},${cy - cs} L${tooltip.x - cs},${cy} L${tooltip.x},${cy + cs}`, }[position]; /* ── text line inside tooltip ── */ const lineW = tw - 10; const lineH = 2.5; const lineX = tooltip.x + (tw - lineW) / 2; const lineY = tooltip.y + (th - lineH) / 2; return ( ); }