"use client" import { SelectItem } from "@/components/ui/select" import type React from "react" import { useState, useRef, useEffect } from "react" import { useColorPalette } from "@/contexts/color-context" import { useMaterial } from "@/contexts/material-context" import { useDesign, availableFonts } from "@/contexts/design-context" import { useTheme } from "next-themes" import { Card, CardContent } from "@/components/ui/card" import { Select, SelectContent, SelectTrigger, SelectValue } from "@/components/ui/select" import { createPortal } from "react-dom" // Using different icon libraries for variety - Fixed Feather import import { FiDroplet } from "react-icons/fi" import { Label } from "@/components/ui/label" import { Check } from "lucide-react" import { useSharedDesign } from "@/hooks/use-shared-design" // Remove these imports: // import { IoChevronUp, IoClose, IoShare, IoRefresh } from "react-icons/io5" // Add these imports: import { Share, RotateCcw, ChevronUp, X } from "lucide-react" import { generateShareUrl, copyToClipboard, type DesignTokens } from "@/utils/share-design" import { ExportButtons } from "@/components/export-buttons" // Import ExportButtons component // Helper function to determine if a color is warm or cool const isWarmColor = (hex: string): boolean => { // Remove the # if present hex = hex.replace("#", "") // Convert hex to RGB let r = 0, g = 0, b = 0 if (hex.length === 3) { r = Number.parseInt(hex[0] + hex[0], 16) g = Number.parseInt(hex[1] + hex[1], 16) b = Number.parseInt(hex[2] + hex[2], 16) } else if (hex.length === 6) { r = Number.parseInt(hex.substring(0, 2), 16) g = Number.parseInt(hex.substring(2, 4), 16) b = Number.parseInt(hex.substring(4, 6), 16) } // Warm colors have more red and yellow (red + green) than blue return r + g > b * 1.5 } // Helper function to add a subtle tint to a color const addTint = (baseColor: string, tintColor: string, amount: number): string => { // Remove the # if present baseColor = baseColor.replace("#", "") tintColor = tintColor.replace("#", "") // Convert hex to RGB let r1 = 0, g1 = 0, b1 = 0 if (baseColor.length === 3) { r1 = Number.parseInt(baseColor[0] + baseColor[0], 16) g1 = Number.parseInt(baseColor[1] + baseColor[1], 16) b1 = Number.parseInt(baseColor[2] + baseColor[2], 16) } else if (baseColor.length === 6) { r1 = Number.parseInt(baseColor.substring(0, 2), 16) g1 = Number.parseInt(baseColor.substring(2, 4), 16) b1 = Number.parseInt(baseColor.substring(4, 6), 16) } let r2 = 0, g2 = 0, b2 = 0 if (tintColor.length === 3) { r2 = Number.parseInt(tintColor[0] + tintColor[0], 16) g2 = Number.parseInt(tintColor[1] + tintColor[1], 16) b2 = Number.parseInt(tintColor[2] + tintColor[2], 16) } else if (tintColor.length === 6) { r2 = Number.parseInt(tintColor.substring(0, 2), 16) g2 = Number.parseInt(tintColor.substring(2, 4), 16) b2 = Number.parseInt(tintColor.substring(4, 6), 16) } // Mix the colors const r = Math.round(r1 * (1 - amount) + r2 * amount) const g = Math.round(g1 * (1 - amount) + g2 * amount) const b = Math.round(b1 * (1 - amount) + b2 * amount) // Convert back to hex return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}` } // Updated color palettes with distinct background colors and 9% lightness difference // In dark mode, primary is LIGHTER than secondary (opposite of light mode) const colorPalettes = [ { name: "Monochrome", primary: "#2563eb", // Refined blue backgroundPrimary: { light: "#ffffff", // Pure white (100% lightness) dark: "#1a1a1a", // Dark gray (~13% lightness) }, backgroundSecondary: { light: "#f2f2f2", // 9% darker than primary (91% lightness) dark: "#0d0d0d", // 9% darker than primary (4% lightness) }, }, { name: "Sage", primary: "#87a96b", // Sage green backgroundPrimary: { light: "#fbfcfb", // Subtle green tint dark: "#1a1b1a", // Subtle green tint in dark }, backgroundSecondary: { light: "#ebecea", // 9% darker with green tint dark: "#0d0e0d", // 9% darker with green tint }, }, { name: "Terracotta", primary: "#c17b5a", // Warm terracotta backgroundPrimary: { light: "#fdfbfa", // Warm terracotta tint dark: "#1c1a19", // Warm tint in dark }, backgroundSecondary: { light: "#eceae9", // 9% darker with terracotta tint dark: "#0f0d0c", // 9% darker with warm tint }, }, { name: "Indigo", primary: "#4f46e5", // Deep indigo backgroundPrimary: { light: "#fafafc", // Deep midnight blue tint dark: "#19191c", // Deep midnight tint in dark }, backgroundSecondary: { light: "#eaeaec", // 9% darker with indigo tint dark: "#0c0c0f", // 9% darker with midnight tint }, }, { name: "Ocean", primary: "#0ea5e9", // Sky blue backgroundPrimary: { light: "#f8fcfe", // Subtle ocean blue tint dark: "#18191c", // Deep ocean tint in dark }, backgroundSecondary: { light: "#e8ebee", // 9% darker with blue tint dark: "#0b0c0f", // 9% darker with deep blue tint }, }, { name: "Sunset", primary: "#f97316", // Vibrant orange backgroundPrimary: { light: "#fefaf8", // Warm sunset tint dark: "#1c1a18", // Warm sunset tint in dark }, backgroundSecondary: { light: "#eee9e7", // 9% darker with sunset tint dark: "#0f0d0b", // 9% darker with sunset tint }, }, { name: "Forest", primary: "#059669", // Emerald green backgroundPrimary: { light: "#f8fcfa", // Forest green tint dark: "#181c1a", // Forest green tint in dark }, backgroundSecondary: { light: "#e8ebe9", // 9% darker with forest tint dark: "#0b0f0d", // 9% darker with forest tint }, }, { name: "Lavender", primary: "#8b5cf6", // Soft purple backgroundPrimary: { light: "#fbfafd", // Lavender tint dark: "#1a191d", // Lavender tint in dark }, backgroundSecondary: { light: "#ebe9ed", // 9% darker with lavender tint dark: "#0d0c10", // 9% darker with lavender tint }, }, { name: "Rose Gold", primary: "#e11d48", // Rose red backgroundPrimary: { light: "#fdf9fa", // Rose gold tint dark: "#1d191a", // Rose gold tint in dark }, backgroundSecondary: { light: "#ede8e9", // 9% darker with rose tint dark: "#100c0d", // 9% darker with rose tint }, }, { name: "Mint", primary: "#10b981", // Emerald green backgroundPrimary: { light: "#f7fcfa", // Subtle mint tint dark: "#0a1a14", // Deep mint tint in dark }, backgroundSecondary: { light: "#e7ebe9", // 9% darker with mint tint dark: "#050d08", // 9% darker with mint tint }, }, { name: "Coral", primary: "#f97316", // Vibrant coral orange backgroundPrimary: { light: "#fefbf8", // Warm coral tint dark: "#1c1610", // Warm coral tint in dark }, backgroundSecondary: { light: "#eeeae7", // 9% darker with coral tint dark: "#0f0b08", // 9% darker with coral tint }, }, { name: "Amethyst", primary: "#a855f7", // Rich purple backgroundPrimary: { light: "#fdfaff", // Subtle purple tint dark: "#1a0f1d", // Purple tint in dark }, backgroundSecondary: { light: "#ede9ef", // 9% darker with purple tint dark: "#0d0810", // 9% darker with purple tint }, }, { name: "Amber", primary: "#f59e0b", // Golden amber backgroundPrimary: { light: "#fffcf5", // Warm amber tint dark: "#1f1a0f", // Amber tint in dark }, backgroundSecondary: { light: "#efebe4", // 9% darker with amber tint dark: "#100d08", // 9% darker with amber tint }, }, { name: "Rose", primary: "#f43f5e", // Modern rose backgroundPrimary: { light: "#fef7f8", // Soft rose tint dark: "#1d0f12", // Rose tint in dark }, backgroundSecondary: { light: "#eee6e7", // 9% darker with rose tint dark: "#100809", // 9% darker with rose tint }, }, { name: "Teal", primary: "#14b8a6", // Modern teal backgroundPrimary: { light: "#f0fdfc", // Fresh teal tint dark: "#0f1a19", // Teal tint in dark }, backgroundSecondary: { light: "#e0edec", // 9% darker with teal tint dark: "#080d0c", // 9% darker with teal tint }, }, { name: "Violet", primary: "#8b5cf6", // Soft violet backgroundPrimary: { light: "#faf8ff", // Gentle violet tint dark: "#16121d", // Violet tint in dark }, backgroundSecondary: { light: "#eae7ef", // 9% darker with violet tint dark: "#0b0910", // 9% darker with violet tint }, }, { name: "Emerald", primary: "#059669", // Deep emerald backgroundPrimary: { light: "#ecfdf5", // Fresh emerald tint dark: "#0f1b14", // Emerald tint in dark }, backgroundSecondary: { light: "#dcede4", // 9% darker with emerald tint dark: "#080e0a", // 9% darker with emerald tint }, }, { name: "Peach", primary: "#fb7185", // Soft peach backgroundPrimary: { light: "#fef8f9", // Warm peach tint dark: "#1d1214", // Peach tint in dark }, backgroundSecondary: { light: "#eee8e9", // 9% darker with peach tint dark: "#100a0b", // 9% darker with peach tint }, }, { name: "Sky", primary: "#0ea5e9", // Bright sky blue backgroundPrimary: { light: "#f0f9ff", // Clear sky tint dark: "#0f1419", // Deep sky tint in dark }, backgroundSecondary: { light: "#e0e9ef", // 9% darker with sky tint dark: "#080b0c", // 9% darker with sky tint }, }, ] // Fixed border radius for UI components const UI_BORDER_RADIUS = "6px" // Default UI font that won't change with font selection const UI_FONT = "Inter, sans-serif" // Common fonts to prioritize const COMMON_FONTS = [ { name: "Inter", value: "Inter, sans-serif" }, { name: "Arial", value: "Arial, sans-serif" }, { name: "Helvetica", value: "Helvetica, sans-serif" }, { name: "Roboto", value: "Roboto, sans-serif" }, { name: "Open Sans", value: "Open Sans, sans-serif" }, { name: "Poppins", value: "Poppins, sans-serif" }, ] export function RightSidebar() { const { primaryColor, setPrimaryColor, systemColors, setSystemColor, updatePaletteImmediately } = useColorPalette() const { materials, selectedMaterial, setSelectedMaterial } = useMaterial() const { borderRadius, setBorderRadius, font, setFont } = useDesign() const [showColorPicker, setShowColorPicker] = useState(false) const [activeColorKey, setActiveColorKey] = useState(null) const [inputColor, setInputColor] = useState(primaryColor) const [selectedPalette, setSelectedPalette] = useState("Monochrome") const [isUserCustomizing, setIsUserCustomizing] = useState(false) const colorInputRef = useRef(null) const colorPickerRef = useRef(null) const colorButtonRef = useRef(null) const [colorButtonRect, setColorButtonRect] = useState(null) const { setTheme, theme } = useTheme() const [mounted, setMounted] = useState(false) const [renderedFonts, setRenderedFonts] = useState>(new Set()) const [selectedFontName, setSelectedFontName] = useState(() => { // Find the font name based on the current font value const fontObj = availableFonts.find((f) => typeof f === "object" && f.value === font) return fontObj ? fontObj.name : "Inter" }) const [isResetting, setIsResetting] = useState(false) const [isMinimized, setIsMinimized] = useState(false) const [isAnimatedIn, setIsAnimatedIn] = useState(false) const [isSharing, setIsSharing] = useState(false) const [shareSuccess, setShareSuccess] = useState(false) // Initialize shared design hook useSharedDesign() // Handle mounting for theme useEffect(() => { setMounted(true) // Add the reset animation to the stylesheet const style = document.createElement("style") style.innerHTML = ` @keyframes reset-spin { 0% { transform: rotate(0deg) scale(1); } 50% { transform: rotate(-180deg) scale(0.95); } 100% { transform: rotate(-180deg) scale(1); } } .animate-reset { animation: reset-spin 400ms cubic-bezier(0.4, 0, 0.2, 1); } @keyframes share-success { 0% { transform: scale(1); } 50% { transform: scale(1.1); } 100% { transform: scale(1); } } .animate-share-success { animation: share-success 300ms cubic-bezier(0.4, 0, 0.2, 1); } /* Add this to ensure font previews work correctly */ .font-preview-text { font-family: inherit !important; } ` document.head.appendChild(style) // Clean up the style element when the component unmounts return () => { document.head.removeChild(style) } }, []) // Trigger sidebar animation after banner animations complete useEffect(() => { const timer = setTimeout(() => { setIsAnimatedIn(true) }, 2100) // Start after banner animations complete (1900ms + 200ms buffer) return () => clearTimeout(timer) }, []) // Update input color when primary color changes or active color changes useEffect(() => { if (!activeColorKey) return if (activeColorKey === "primary") { setInputColor(primaryColor) } else if (systemColors[activeColorKey]) { setInputColor(systemColors[activeColorKey]) } }, [primaryColor, activeColorKey, systemColors]) // Handle click outside to close color picker useEffect(() => { function handleClickOutside(event: MouseEvent) { if ( colorPickerRef.current && !colorPickerRef.current.contains(event.target as Node) && colorButtonRef.current && !colorButtonRef.current.contains(event.target as Node) ) { setShowColorPicker(false) setActiveColorKey(null) } } // Add event listener when color picker is open if (showColorPicker) { document.addEventListener("mousedown", handleClickOutside) } // Cleanup return () => { document.removeEventListener("mousedown", handleClickOutside) } }, [showColorPicker]) // Initialize with common fonts const initializedRef = useRef(false) // Initialize with common fonts - using a ref to ensure it only runs once const hasInitializedRef = useRef(false) useEffect(() => { if (mounted && !hasInitializedRef.current) { hasInitializedRef.current = true // Set default border radius only once setBorderRadius(12) // Set to 12px (0.8rem) // Initialize fonts const initialFonts = new Set() COMMON_FONTS.forEach((font) => { initialFonts.add(typeof font === "object" ? font.value : font) }) // Add the currently selected font initialFonts.add(font) setRenderedFonts(initialFonts) } }, [mounted]) // Only depend on mounted state // Initialize selected palette from localStorage useEffect(() => { if (mounted) { const storedPalette = localStorage.getItem("selectedPalette") if (storedPalette) { const palette = colorPalettes.find((p) => p.name === storedPalette) if (palette) { setSelectedPalette(storedPalette) // Apply the palette colors without triggering user customization setIsUserCustomizing(false) setPrimaryColor(palette.primary) setSystemColor( "backgroundPrimary", theme === "dark" ? palette.backgroundPrimary.dark : palette.backgroundPrimary.light, ) setSystemColor( "backgroundSecondary", theme === "dark" ? palette.backgroundSecondary.dark : palette.backgroundSecondary.light, ) } } else { // If no stored palette, use Monochrome as default const oceanTheme = colorPalettes.find((p) => p.name === "Monochrome") if (oceanTheme) { setSelectedPalette("Monochrome") setIsUserCustomizing(false) setPrimaryColor(oceanTheme.primary) setSystemColor( "backgroundPrimary", theme === "dark" ? oceanTheme.backgroundPrimary.dark : oceanTheme.backgroundPrimary.light, ) setSystemColor( "backgroundSecondary", theme === "dark" ? oceanTheme.backgroundSecondary.dark : oceanTheme.backgroundSecondary.light, ) } } } }, [mounted]) // Add this useEffect to handle theme changes // Update colors when theme changes useEffect(() => { if (mounted && selectedPalette !== "Custom") { const palette = colorPalettes.find((p) => p.name === selectedPalette) if (palette) { // Apply the theme-specific background colors without changing the primary color setSystemColor( "backgroundPrimary", theme === "dark" ? palette.backgroundPrimary.dark : palette.backgroundPrimary.light, ) setSystemColor( "backgroundSecondary", theme === "dark" ? palette.backgroundSecondary.dark : palette.backgroundSecondary.light, ) // Log for debugging console.log(`Theme changed to ${theme}, updating colors for palette: ${selectedPalette}`, { backgroundPrimary: theme === "dark" ? palette.backgroundPrimary.dark : palette.backgroundPrimary.light, backgroundSecondary: theme === "dark" ? palette.backgroundSecondary.dark : palette.backgroundSecondary.light, }) } } }, [theme, mounted, selectedPalette, setSystemColor]) // Custom setPrimaryColor function that also updates palette name const handleSetPrimaryColor = (color: string) => { setPrimaryColor(color) if (isUserCustomizing) { setSelectedPalette("Custom") } } // Custom setSystemColor function that also updates palette name const handleSetSystemColor = (key: string, color: string) => { setSystemColor(key, color) if (isUserCustomizing) { setSelectedPalette("Custom") } } // Function to handle color change from the color picker const handleColorPickerChange = (e: React.ChangeEvent) => { const newColor = e.target.value setInputColor(newColor) setIsUserCustomizing(true) if (activeColorKey === "primary") { handleSetPrimaryColor(newColor) } else if (activeColorKey) { handleSetSystemColor(activeColorKey, newColor) } } // Function to handle color input change const handleColorInputChange = (e: React.ChangeEvent) => { const newColor = e.target.value setInputColor(newColor) setIsUserCustomizing(true) // Validate if it's a valid hex color if (/^#([0-9A-F]{3}){1,2}$/i.test(newColor)) { if (activeColorKey === "primary") { handleSetPrimaryColor(newColor) } else if (activeColorKey) { handleSetSystemColor(activeColorKey, newColor) } } } // Function to handle color input blur const handleColorInputBlur = () => { // Ensure the color is valid, if not revert to the current color if (!/^#([0-9A-F]{3}){1,2}$/i.test(inputColor)) { if (activeColorKey === "primary") { setInputColor(primaryColor) } else if (activeColorKey && systemColors[activeColorKey]) { setInputColor(systemColors[activeColorKey]) } } else { if (activeColorKey === "primary") { handleSetPrimaryColor(inputColor) } else if (activeColorKey) { handleSetSystemColor(activeColorKey, inputColor) } } } // Function to handle font selection const handleFontSelect = (value: string) => { // Find the font object by value const fontObj = availableFonts.find((f) => typeof f === "object" && f.value === value) if (fontObj && typeof fontObj === "object") { setFont(fontObj.value) setSelectedFontName(fontObj.name) } else { setFont(value) setSelectedFontName(value) } } // Function to handle palette selection const handlePaletteSelect = (paletteName: string) => { const palette = colorPalettes.find((p) => p.name === paletteName) if (palette) { // Temporarily disable user customization flag during palette selection setIsUserCustomizing(false) // Store the selected palette name in localStorage localStorage.setItem("selectedPalette", paletteName) // Use the direct CSS update method for immediate visual feedback const { primaryColor: newPrimary, bgPrimary, bgSecondary } = updatePaletteImmediately(palette) // Update the React state after the visual update (will be batched by React) setPrimaryColor(newPrimary) setSystemColor("backgroundPrimary", bgPrimary) setSystemColor("backgroundSecondary", bgSecondary) // Update the selected palette name setSelectedPalette(paletteName) } } // Function to handle sharing design const handleShareDesign = async () => { if (isSharing) return setIsSharing(true) try { const designTokens: DesignTokens = { primaryColor, backgroundPrimary: systemColors.backgroundPrimary || (theme === "dark" ? "#0a0a0a" : "#ffffff"), backgroundSecondary: systemColors.backgroundSecondary || (theme === "dark" ? "#050505" : "#f7f7f7"), borderRadius, font, selectedPalette, } const shareUrl = generateShareUrl(designTokens) const success = await copyToClipboard(shareUrl) if (success) { setShareSuccess(true) setTimeout(() => setShareSuccess(false), 2000) } else { console.error("Failed to copy to clipboard") } } catch (error) { console.error("Failed to share design:", error) } finally { setIsSharing(false) } } // Get theme-aware selection border color const getSelectionBorderColor = (opacity = 0.9) => { return theme === "dark" ? `rgba(255, 255, 255, ${opacity})` : `rgba(0, 0, 0, ${opacity})` } // Get theme-aware hover border color with 36% opacity const getHoverBorderColor = () => { return theme === "dark" ? "rgba(255, 255, 255, 0.36)" : "rgba(0, 0, 0, 0.36)" } // Get theme-aware selection background color const getSelectionBackgroundColor = () => { return theme === "dark" ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.05)" } // Material tile border radius - consistent value for all material tiles const MATERIAL_TILE_RADIUS = "8px" if (!mounted) { return null } // Toggle color picker for a specific color const toggleColorPicker = (colorKey: string, buttonRef: React.RefObject) => { if (showColorPicker && activeColorKey === colorKey) { setShowColorPicker(false) setActiveColorKey(null) } else { if (buttonRef.current) { setColorButtonRect(buttonRef.current.getBoundingClientRect()) } setActiveColorKey(colorKey) setShowColorPicker(true) // Set the input color to the current value if (colorKey === "primary") { setInputColor(primaryColor) } else if (systemColors[colorKey]) { setInputColor(systemColors[colorKey]) } // Enable user customization flag when opening color picker setIsUserCustomizing(true) } } // Calculate position for the color picker const getColorPickerStyle = () => { if (!colorButtonRect) return {} return { position: "fixed" as const, top: `${colorButtonRect.top}px`, left: `${colorButtonRect.left - 288 - 8}px`, // 288px is width of picker (272) + padding (16), 8px is margin zIndex: 9999, } } // Get color name for display const getColorName = (colorKey: string) => { if (colorKey === "primary") return "Primary" if (colorKey === "backgroundPrimary") return "Background Primary" if (colorKey === "backgroundSecondary") return "Background Secondary" return colorKey } // Function to determine if a color is light or dark const isLightColor = (color: string) => { // Convert hex to RGB let r = 0, g = 0, b = 0 if (color.length === 4) { r = Number.parseInt(color[1] + color[1], 16) g = Number.parseInt(color[2] + color[2], 16) b = Number.parseInt(color[3] + color[3], 16) } else if (color.length === 7) { r = Number.parseInt(color.substring(1, 3), 16) g = Number.parseInt(color.substring(3, 5), 16) b = Number.parseInt(color.substring(5, 7), 16) } // Calculate perceived brightness (YIQ formula) const yiq = (r * 299 + g * 587 + b * 114) / 1000 return yiq >= 128 } return (
{/* Close button for mobile */} {/* Properties Card - Keep original structure */} {/* Header Section */}

Design controls

{/* Main content sections - Only show if not minimized */} {!isMinimized && (
{/* Color Section */}
{/* Color Palette */}
{/* Primary Color */} {/* Background Primary */} {/* Background Secondary */}
{/* Palette Dropdown */}
{/* Custom Color Picker - Rendered in a portal to avoid z-index issues */} {showColorPicker && mounted && document.body && createPortal(
, document.body, )}
{/* Separator */}
{/* Roundedness Section */}
{[0, 6, 12, 24].map((value) => (
setBorderRadius(value)} className={`flex-1 h-8 cursor-pointer transition-all relative group input-overlay-bg border input-hover-effect shadow-[0_1px_3px_rgba(0,0,0,0.1)] dark:shadow-[0_1px_3px_rgba(0,0,0,0.3)] ${ borderRadius === value ? "" : "border-input-border" }`} style={{ borderRadius: `${value}px`, borderColor: borderRadius === value ? "hsl(var(--primary))" : undefined, transition: borderRadius === value ? "none" : "all 0.2s ease-in-out", }} > {/* Number value label */}
{value === 0 ? "0" : (value / 16).toFixed(1)}
))}
{/* Separator */}
{/* Font Section */}
{/* Separator */}
)} {/* Export Button Section - Always visible */}
) } function cn(...classes: (string | boolean | undefined)[]) { return classes.filter(Boolean).join(" ") }