diff --git a/apps/plugin/plugin-src/code.ts b/apps/plugin/plugin-src/code.ts index b9aa12e7..fafd2569 100644 --- a/apps/plugin/plugin-src/code.ts +++ b/apps/plugin/plugin-src/code.ts @@ -38,6 +38,7 @@ export const defaultPluginSettings: PluginSettings = { useTailwind4: false, thresholdPercent: 15, baseFontFamily: "", + fontFamilyCustomConfig: {}, }; // A helper type guard to ensure the key belongs to the PluginSettings type diff --git a/packages/backend/src/tailwind/tailwindTextBuilder.ts b/packages/backend/src/tailwind/tailwindTextBuilder.ts index 6c106d07..9b631d5c 100644 --- a/packages/backend/src/tailwind/tailwindTextBuilder.ts +++ b/packages/backend/src/tailwind/tailwindTextBuilder.ts @@ -115,17 +115,29 @@ export class TailwindTextBuilder extends TailwindDefaultBuilder { if (baseFontFamily && fontName.family.toLowerCase() === baseFontFamily.toLowerCase()) { return ""; } - - // Check if the font is in one of the Tailwind default font stacks - if (config.fontFamily.sans.includes(fontName.family)) { - return "font-sans"; - } - if (config.fontFamily.serif.includes(fontName.family)) { - return "font-serif"; - } - if (config.fontFamily.mono.includes(fontName.family)) { - return "font-mono"; + + const fontFamilyCustomConfig = localTailwindSettings.fontFamilyCustomConfig; + + if (fontFamilyCustomConfig) { + // Check if current font is part of custom tailwind config + for (const family in fontFamilyCustomConfig) { + if (fontFamilyCustomConfig[family].includes(fontName.family)) { + return `font-${family}` + } + } + } else { + // Check if the font is in one of the Tailwind default font stacks + if (config.fontFamily.sans.includes(fontName.family)) { + return "font-sans"; + } + if (config.fontFamily.serif.includes(fontName.family)) { + return "font-serif"; + } + if (config.fontFamily.mono.includes(fontName.family)) { + return "font-mono"; + } } + const underscoreFontName = fontName.family.replace(/\s/g, "_"); return "font-['" + underscoreFontName + "']"; diff --git a/packages/plugin-ui/src/components/CustomPrefixInput.tsx b/packages/plugin-ui/src/components/CustomPrefixInput.tsx index d397a4dd..4aa1efd2 100644 --- a/packages/plugin-ui/src/components/CustomPrefixInput.tsx +++ b/packages/plugin-ui/src/components/CustomPrefixInput.tsx @@ -10,7 +10,7 @@ interface FormFieldProps { helpText?: string; // Validation props - type?: "text" | "number"; + type?: "text" | "number"| "json"; min?: number; max?: number; suffix?: string; @@ -50,6 +50,7 @@ const FormField = React.memo( const [hasError, setHasError] = useState(false); const [errorMessage, setErrorMessage] = useState(""); const inputRef = useRef(null); + const textareaRef = useRef(null); // Update internal state when initialValue changes (from parent) useEffect(() => { @@ -106,6 +107,52 @@ const FormField = React.memo( return true; } + if (type === "json") { + // Check if the string is empty skip validation + if (!value.trim()) { + setHasError(false); + setErrorMessage(""); + return true; + } + + try { + // Try to parse the JSON + const config = JSON.parse(value); + + // Validate that the config is an object + if (typeof config !== 'object' || Array.isArray(config) || config === null) { + throw new Error("Configuration must be a valid JSON object"); + } + + for (const item in config) { + if (!Array.isArray(config[item])) { + throw new Error(`Key ${item} is not valid and should be an array`); + } + config[item].forEach((val) => { + if (typeof val !== 'string') { + throw new Error(`Values from Key ${item} should be string`); + } + }); + } + + // Additional validation could be added here based on expected structure + // For example, checking specific properties or types + + // If valid, update the preference + setHasError(false); + setErrorMessage(""); + return true + } catch (error) { + // Handle parsing errors + console.error("Invalid JSON configuration:", error); + setHasError(true); + setErrorMessage(`Invalid JSON configuration: ${error}`) + // You could show an error message to the user here + // Or reset to default/previous value + return false + } + } + return true; }; @@ -116,6 +163,13 @@ const FormField = React.memo( setHasChanges(newValue !== String(initialValue)); }; + const handleTextareaChange = (e: React.ChangeEvent) => { + const newValue = e.target.value; + setInputValue(newValue); + validateInput(newValue); + setHasChanges(newValue !== String(initialValue)); + }; + const applyChanges = () => { if (hasError) return; @@ -147,6 +201,15 @@ const FormField = React.memo( } }; + const handleTextareaKeyDown = (e: React.KeyboardEvent) => { + // Only apply changes on Ctrl+Enter or Command+Enter for textarea + if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { + e.preventDefault(); + applyChanges(); + textareaRef.current?.blur(); + } + }; + // Default preview transform for text prefixes const defaultPreviewTransform = (value: string, example: string) => (
@@ -190,25 +253,46 @@ const FormField = React.memo(
- setIsFocused(true)} - onBlur={handleBlur} - onKeyDown={handleKeyDown} - placeholder={placeholder} - className={`p-1.5 px-2.5 text-sm w-full transition-all focus:outline-hidden ${ - suffix ? "rounded-l-md" : "rounded-md" - } ${ - hasError - ? "border border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-900/20" - : isFocused - ? "border border-green-400 dark:border-green-600 ring-1 ring-green-300 dark:ring-green-800 bg-white dark:bg-neutral-800" - : "border border-gray-300 dark:border-gray-600 bg-white dark:bg-neutral-800 hover:border-gray-400 dark:hover:border-gray-500" - }`} - /> + {type === "json" ? ( +