import { Download } from "lucide-react";
import type React from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { ProgressCard } from "@/components/common/progress-card";
import { useToolProcessor } from "@/hooks/use-tool-processor";
import { useFileStore } from "@/stores/file-store";
// ── Presets ──────────────────────────────────────────────────────────
interface BorderPreset {
name: string;
borderWidth: number;
borderColor: string;
padding: number;
paddingColor: string;
cornerRadius: number;
shadow: boolean;
shadowBlur: number;
shadowOffsetX: number;
shadowOffsetY: number;
shadowColor: string;
shadowOpacity: number;
}
const PRESETS: BorderPreset[] = [
{
name: "Clean White",
borderWidth: 40,
borderColor: "#FFFFFF",
padding: 0,
paddingColor: "#FFFFFF",
cornerRadius: 0,
shadow: false,
shadowBlur: 15,
shadowOffsetX: 0,
shadowOffsetY: 5,
shadowColor: "#000000",
shadowOpacity: 40,
},
{
name: "Gallery Black",
borderWidth: 30,
borderColor: "#1A1A1A",
padding: 12,
paddingColor: "#FFFFFF",
cornerRadius: 0,
shadow: false,
shadowBlur: 15,
shadowOffsetX: 0,
shadowOffsetY: 5,
shadowColor: "#000000",
shadowOpacity: 40,
},
{
name: "Shadow",
borderWidth: 0,
borderColor: "#FFFFFF",
padding: 20,
paddingColor: "#FFFFFF",
cornerRadius: 0,
shadow: true,
shadowBlur: 20,
shadowOffsetX: 0,
shadowOffsetY: 8,
shadowColor: "#000000",
shadowOpacity: 40,
},
{
name: "Rounded",
borderWidth: 20,
borderColor: "#FFFFFF",
padding: 0,
paddingColor: "#FFFFFF",
cornerRadius: 30,
shadow: true,
shadowBlur: 15,
shadowOffsetX: 0,
shadowOffsetY: 4,
shadowColor: "#000000",
shadowOpacity: 30,
},
{
name: "Polaroid",
borderWidth: 0,
borderColor: "#FFFFFF",
padding: 40,
paddingColor: "#FFFFFF",
cornerRadius: 4,
shadow: true,
shadowBlur: 12,
shadowOffsetX: 2,
shadowOffsetY: 6,
shadowColor: "#000000",
shadowOpacity: 35,
},
{
name: "Vintage",
borderWidth: 12,
borderColor: "#8B7355",
padding: 8,
paddingColor: "#F5F0E8",
cornerRadius: 0,
shadow: false,
shadowBlur: 15,
shadowOffsetX: 0,
shadowOffsetY: 5,
shadowColor: "#000000",
shadowOpacity: 40,
},
{
name: "Minimal",
borderWidth: 2,
borderColor: "#D4D4D4",
padding: 0,
paddingColor: "#FFFFFF",
cornerRadius: 0,
shadow: false,
shadowBlur: 15,
shadowOffsetX: 0,
shadowOffsetY: 5,
shadowColor: "#000000",
shadowOpacity: 40,
},
{
name: "Cinematic",
borderWidth: 2,
borderColor: "#333333",
padding: 30,
paddingColor: "#0A0A0A",
cornerRadius: 0,
shadow: false,
shadowBlur: 15,
shadowOffsetX: 0,
shadowOffsetY: 5,
shadowColor: "#000000",
shadowOpacity: 40,
},
];
// ── Color Swatches ───────────────────────────────────────────────────
const COLOR_SWATCHES = ["#FFFFFF", "#000000", "#333333", "#F5F5F5", "#8B7355"];
function ColorSwatches({
value,
onChange,
id,
}: {
value: string;
onChange: (color: string) => void;
id: string;
}) {
return (
{COLOR_SWATCHES.map((swatch) => (
);
}
// ── Helpers ──────────────────────────────────────────────────────────
function hexToRgb(hex: string) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return { r, g, b };
}
function buildPreviewStyle(s: {
borderWidth: number;
borderColor: string;
padding: number;
paddingColor: string;
cornerRadius: number;
shadow: boolean;
shadowBlur: number;
shadowOffsetX: number;
shadowOffsetY: number;
shadowColor: string;
shadowOpacity: number;
}): React.CSSProperties {
const sc = hexToRgb(s.shadowColor);
return {
border: s.borderWidth > 0 ? `${s.borderWidth}px solid ${s.borderColor}` : undefined,
padding: s.padding > 0 ? `${s.padding}px` : undefined,
backgroundColor: s.padding > 0 ? s.paddingColor : undefined,
borderRadius: s.cornerRadius > 0 ? `${s.cornerRadius}px` : undefined,
boxShadow: s.shadow
? `${s.shadowOffsetX}px ${s.shadowOffsetY}px ${s.shadowBlur}px rgba(${sc.r},${sc.g},${sc.b},${s.shadowOpacity / 100})`
: undefined,
};
}
// ── Controls ─────────────────────────────────────────────────────────
export interface BorderControlsProps {
settings?: Record;
onChange?: (settings: Record) => void;
onImageStyle?: (style: React.CSSProperties | null) => void;
}
export function BorderControls({
settings: initialSettings,
onChange,
onImageStyle,
}: BorderControlsProps) {
const [selectedPreset, setSelectedPreset] = useState(null);
const [borderWidth, setBorderWidth] = useState(10);
const [borderColor, setBorderColor] = useState("#000000");
const [padding, setPadding] = useState(0);
const [paddingColor, setPaddingColor] = useState("#FFFFFF");
const [cornerRadius, setCornerRadius] = useState(0);
const [shadow, setShadow] = useState(false);
const [shadowBlur, setShadowBlur] = useState(15);
const [shadowOffsetX, setShadowOffsetX] = useState(0);
const [shadowOffsetY, setShadowOffsetY] = useState(5);
const [shadowColor, setShadowColor] = useState("#000000");
const [shadowOpacity, setShadowOpacity] = useState(40);
const initializedRef = useRef(false);
useEffect(() => {
if (!initialSettings || initializedRef.current) return;
initializedRef.current = true;
if (initialSettings.borderWidth != null) setBorderWidth(Number(initialSettings.borderWidth));
if (initialSettings.borderColor != null) setBorderColor(String(initialSettings.borderColor));
if (initialSettings.padding != null) setPadding(Number(initialSettings.padding));
if (initialSettings.paddingColor != null) setPaddingColor(String(initialSettings.paddingColor));
if (initialSettings.cornerRadius != null) setCornerRadius(Number(initialSettings.cornerRadius));
if (initialSettings.shadow != null) setShadow(Boolean(initialSettings.shadow));
if (initialSettings.shadowBlur != null) setShadowBlur(Number(initialSettings.shadowBlur));
if (initialSettings.shadowOffsetX != null)
setShadowOffsetX(Number(initialSettings.shadowOffsetX));
if (initialSettings.shadowOffsetY != null)
setShadowOffsetY(Number(initialSettings.shadowOffsetY));
if (initialSettings.shadowColor != null) setShadowColor(String(initialSettings.shadowColor));
if (initialSettings.shadowOpacity != null)
setShadowOpacity(Number(initialSettings.shadowOpacity));
}, [initialSettings]);
const onChangeRef = useRef(onChange);
useEffect(() => {
onChangeRef.current = onChange;
});
const onImageStyleRef = useRef(onImageStyle);
useEffect(() => {
onImageStyleRef.current = onImageStyle;
});
useEffect(() => {
const vals = {
borderWidth,
borderColor,
padding,
paddingColor,
cornerRadius,
shadow,
shadowBlur,
shadowOffsetX,
shadowOffsetY,
shadowColor,
shadowOpacity,
};
onChangeRef.current?.(vals);
onImageStyleRef.current?.(buildPreviewStyle(vals));
}, [
borderWidth,
borderColor,
padding,
paddingColor,
cornerRadius,
shadow,
shadowBlur,
shadowOffsetX,
shadowOffsetY,
shadowColor,
shadowOpacity,
]);
const applyPreset = (preset: BorderPreset) => {
setSelectedPreset(preset.name);
setBorderWidth(preset.borderWidth);
setBorderColor(preset.borderColor);
setPadding(preset.padding);
setPaddingColor(preset.paddingColor);
setCornerRadius(preset.cornerRadius);
setShadow(preset.shadow);
setShadowBlur(preset.shadowBlur);
setShadowOffsetX(preset.shadowOffsetX);
setShadowOffsetY(preset.shadowOffsetY);
setShadowColor(preset.shadowColor);
setShadowOpacity(preset.shadowOpacity);
};
const clearPreset = () => setSelectedPreset(null);
return (
{/* Presets */}
Presets
{PRESETS.map((preset) => (
))}
{/* Border */}
{
setBorderColor(c);
clearPreset();
}}
/>
{/* Padding */}
{
setPaddingColor(c);
clearPreset();
}}
/>
{/* Corner Radius */}
{/* Shadow */}
Shadow
{shadow && (
{
setShadowColor(c);
clearPreset();
}}
/>
)}
);
}
// ── Settings Panel ───────────────────────────────────────────────────
export function BorderSettings({
onImageStyle,
}: {
onImageStyle?: (style: React.CSSProperties | null) => void;
}) {
const { files } = useFileStore();
const { processFiles, processAllFiles, processing, error, downloadUrl, progress } =
useToolProcessor("border");
const [settings, setSettings] = useState>({});
const handleSettingsChange = useCallback((s: Record) => {
setSettings(s);
}, []);
const hasFile = files.length > 0;
const hasEffect =
Number(settings.borderWidth) > 0 ||
Number(settings.padding) > 0 ||
Number(settings.cornerRadius) > 0 ||
Boolean(settings.shadow);
const canProcess = hasFile && !processing && hasEffect;
const handleProcess = () => {
if (files.length > 1) {
processAllFiles(files, settings);
} else {
processFiles(files, settings);
}
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (canProcess) handleProcess();
};
return (
);
}