import React, { type ReactNode } from "react";
/**
* Shared icon renderers for theme knob controls.
*
* Each icon function accepts a pre-resolved foreground `color` string so
* that both the TanStack Devtools panel and the Storybook addon can use
* the same rendering logic with their own color schemes.
*
* TanStack: `ic(active, isDark)` → theme-adaptive foreground
* Storybook: `ic(active)` → purple accent for active, gray for inactive
*/
export function fillIcon(value: string, color: string): ReactNode {
if (value === "filled") {
return
;
}
return
;
}
export function spaceIcon(value: string, color: string): ReactNode {
const size = ({ small: 8, medium: 12, large: 16 } as Record)[value] ?? 12;
return
;
}
export function sizeIcon(value: string, color: string): ReactNode {
const height = ({ small: 8, medium: 11, large: 14 } as Record)[value] ?? 11;
return
;
}
export function densityIcon(value: string, color: string): ReactNode {
// Comfortable = taller stack; compact = compressed stack.
const gap = value === "compact" ? 1 : 2;
const barH = value === "compact" ? 3 : 4;
return (
);
}
export function borderRadiusIcon(value: string, color: string): ReactNode {
const radius =
({ none: 0, small: 2, medium: 4, large: 7, full: 10 } as Record)[value] ?? 0;
return (
);
}
export function cornerSmoothingIcon(value: string, color: string): ReactNode {
// Same nominal corner size, different curvature: circular-arc rounded
// rect for `round`, continuous-curvature superellipse for `smooth`.
const smooth = value === "smooth";
return (
{value}
{smooth ? (
) : (
)}
);
}
export function borderWidthIcon(value: string, color: string): ReactNode {
if (value === "none") {
return (
);
}
const width = ({ small: 1, medium: 1.5, large: 2 } as Record)[value] ?? 1;
return (
);
}
export function elevationIcon(value: string, color: string, _isDark: boolean): ReactNode {
const shadowColor = "rgba(128,128,128,0.55)";
const offset = ({ none: 0, small: 1, medium: 2, large: 3 } as Record)[value] ?? 0;
const shadowSize =
({ none: 0, small: 10, medium: 11, large: 12 } as Record)[value] ?? 0;
const squareSize = 10;
const total = squareSize + offset * 2 + 1;
if (offset === 0) {
return (
);
}
return (
);
}
export function textAccentIcon(value: string, color: string): ReactNode {
const weight = value === "low" ? 300 : value === "medium" ? 500 : 700;
const opacity = value === "low" ? 0.35 : value === "medium" ? 0.7 : 1;
return (
Aa
);
}
const fontCategoryStyles: Record<
string,
{ family: string; label: string; letterSpacing?: string; style?: string }
> = {
"sans-serif": {
family: "-apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
label: "Aa",
},
serif: { family: "Georgia, 'Times New Roman', Times, serif", label: "Aa" },
mono: { family: "'SF Mono', 'Cascadia Code', Menlo, monospace", label: "Aa" },
slab: { family: "'Rockwell', 'Courier New', serif", label: "Aa" },
rounded: { family: "'Nunito', 'Varela Round', system-ui, sans-serif", label: "Aa" },
condensed: { family: "'Arial Narrow', sans-serif", label: "Aa", letterSpacing: "-0.03em" },
cursive: { family: "'Segoe Script', cursive", label: "Aa", style: "italic" },
handwriting: { family: "'Comic Sans MS', 'Caveat', cursive", label: "Aa" },
pixel: { family: "'Courier New', monospace", label: "Aa", letterSpacing: "0.05em" },
blackletter: { family: "'Old English Text MT', serif", label: "Aa" },
geometric: { family: "'Century Gothic', 'Futura', sans-serif", label: "Aa" },
};
export function headingFontIcon(value: string, color: string): ReactNode {
const s = fontCategoryStyles[value] ?? { family: "inherit", label: "Aa" };
return (
{s.label}
);
}
export function bodyFontIcon(value: string, color: string): ReactNode {
const s = fontCategoryStyles[value] ?? { family: "inherit", label: "Aa" };
return (
{s.label}
);
}
export function fontWeightIcon(value: string, color: string): ReactNode {
const weight = ({ light: 300, regular: 400, bold: 700 } as Record)[value] ?? 400;
return (
W
);
}
export function modeIcon(value: "system" | "light" | "dark", color: string): ReactNode {
const base = { width: 14, height: 14, borderRadius: "50%" } as const;
if (value === "system") {
return (
);
}
if (value === "light") {
return
;
}
return
;
}
/** Compact text glyph used by house-decision knobs in the panel. */
function glyph(label: string, color: string, size = 9): ReactNode {
return (
{label}
);
}
export function fieldLabelPlacementIcon(value: string, color: string): ReactNode {
if (value === "side") return glyph("⊟", color, 12);
if (value === "floating") return glyph("⬚", color, 12);
return glyph("⊞", color, 12); // top
}
export function requiredMarkingIcon(value: string, color: string): ReactNode {
if (value === "asterisk") return glyph("*", color, 14);
if (value === "optional") return glyph("opt", color, 8);
return glyph("min", color, 8); // minority
}
export function tableZebraIcon(value: string, color: string): ReactNode {
const on = value === "on";
return (
);
}
export function bulkBarPlacementIcon(value: string, color: string): ReactNode {
const top = value !== "bottom";
return (
);
}
export function selectAllScopeIcon(value: string, color: string): ReactNode {
const filtered = value === "filtered";
// Three rows; page scope highlights only the top rows (current page),
// filtered scope highlights all rows (all matching).
return (
);
}
export function timestampStyleIcon(value: string, color: string): ReactNode {
return glyph(value === "relative" ? "~t" : "12:00", color, value === "relative" ? 10 : 7);
}
export function disabledStyleIcon(value: string, color: string): ReactNode {
if (value === "dimWhole") {
return (
);
}
// keepLabel — solid label bar over a dimmed body
return (
);
}
export function formAutofocusIcon(value: string, color: string): ReactNode {
return (
);
}