History, more icons, color palette component
This commit is contained in:
		
							
								
								
									
										79
									
								
								app/components/ColorPalette.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								app/components/ColorPalette.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| import React, { useState, useRef } from 'react'; | ||||
|  | ||||
| interface ColorPaletteProps { | ||||
|   colors: string[]; | ||||
|   size?: 'small' | 'large'; | ||||
| } | ||||
|  | ||||
| const ColorPalette: React.FC<ColorPaletteProps> = ({ colors, size = 'small' }) => { | ||||
|   const [copiedColor, setCopiedColor] = useState<string | null>(null); | ||||
|   const [hoveredColor, setHoveredColor] = useState<string | null>(null); | ||||
|   const textAreaRef = useRef<HTMLTextAreaElement>(null); | ||||
|  | ||||
|   const handleColorClick = (color: string) => { | ||||
|     if (navigator.clipboard && navigator.clipboard.writeText) { | ||||
|       navigator.clipboard.writeText(color).then(() => { | ||||
|         setCopiedColor(color); | ||||
|         setTimeout(() => setCopiedColor(null), 1500); | ||||
|       }); | ||||
|     } else { | ||||
|       // Fallback method for iOS and other unsupported browsers | ||||
|       const textArea = textAreaRef.current; | ||||
|       if (textArea) { | ||||
|         textArea.value = color; | ||||
|         textArea.select(); | ||||
|         try { | ||||
|           document.execCommand('copy'); | ||||
|           setCopiedColor(color); | ||||
|           setTimeout(() => setCopiedColor(null), 1500); | ||||
|         } catch (err) { | ||||
|           console.error('Failed to copy color: ', err); | ||||
|         } | ||||
|         textArea.blur(); | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const sizeClasses = size === 'small' ? 'w-full pt-full' : 'w-8 h-8 pt-full'; | ||||
|  | ||||
|   return ( | ||||
|     <> | ||||
|       <div className={`grid grid-cols-8 gap-2 ${size === 'large' ? 'mb-4' : 'mb-2'} z-10`}> | ||||
|         {colors.map((color, index) => ( | ||||
|           <div  | ||||
|             key={index}  | ||||
|             className={`${sizeClasses} rounded-sm cursor-pointer relative group`} | ||||
|             style={{backgroundColor: color}} | ||||
|             onClick={() => handleColorClick(color)} | ||||
|             onMouseEnter={() => setHoveredColor(color)} | ||||
|             onMouseLeave={() => setHoveredColor(null)} | ||||
|           > | ||||
|             {size === 'small' && hoveredColor === color && ( | ||||
|               <div className="absolute bottom-full left-1/2 transform -translate-x-1/2 mb-2 px-2 py-1 bg-white dark:bg-gray-800 rounded shadow-lg z-10"> | ||||
|                 <div className="w-4 h-4 rounded-sm mb-1" style={{backgroundColor: color}}></div> | ||||
|                 <span className="text-xs">{color}</span> | ||||
|               </div> | ||||
|             )} | ||||
|             {size === 'large' && ( | ||||
|               <div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-black bg-opacity-50 text-white text-[8px]"> | ||||
|                 {color} | ||||
|               </div> | ||||
|             )} | ||||
|             {copiedColor === color && ( | ||||
|               <div className="absolute inset-0 flex items-center justify-center bg-black bg-opacity-70 text-white text-xs"> | ||||
|                 Copied! | ||||
|               </div> | ||||
|             )} | ||||
|           </div> | ||||
|         ))} | ||||
|       </div> | ||||
|       <textarea | ||||
|         ref={textAreaRef} | ||||
|         style={{ position: 'absolute', left: '-9999px' }} | ||||
|         aria-hidden="true" | ||||
|       /> | ||||
|     </> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default ColorPalette; | ||||
| @@ -4,6 +4,7 @@ import { ColorScheme } from '../utils/colorSchemes'; | ||||
| import { generateYAML } from '../utils/yamlExport'; | ||||
| import { Highlight, themes } from 'prism-react-renderer'; | ||||
| import { motion, useAnimation } from 'framer-motion'; | ||||
| import ColorPalette from './ColorPalette'; | ||||
|  | ||||
| interface ColorSchemeCardProps { | ||||
|   scheme: ColorScheme; | ||||
| @@ -127,19 +128,10 @@ fetchData().then(data => console.log(data)); `; | ||||
|             )} | ||||
|           </Highlight> | ||||
|         </div> | ||||
|         <div className="grid grid-cols-8 gap-2 mb-4 z-10"> | ||||
|           {Object.values(scheme.colors.normal).concat(Object.values(scheme.colors.bright)).map((color, index) => ( | ||||
|             <div  | ||||
|               key={index}  | ||||
|               className="w-full pt-full rounded-sm transition-colors duration-300 relative group"  | ||||
|               style={{backgroundColor: color}} | ||||
|             > | ||||
|               <div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-black bg-opacity-50 text-white text-[8px]"> | ||||
|                 {color} | ||||
|               </div> | ||||
|             </div> | ||||
|           ))} | ||||
|         </div> | ||||
|         <ColorPalette  | ||||
|           colors={Object.values(scheme.colors.normal).concat(Object.values(scheme.colors.bright))} | ||||
|           size="large" | ||||
|         /> | ||||
|         <div className="flex justify-center space-x-8 mt-4 z-10"> | ||||
|           <button  | ||||
|             className="bg-red-500 text-white p-3 rounded-full shadow-lg hover:bg-red-600 transition-colors duration-300" | ||||
|   | ||||
							
								
								
									
										69
									
								
								app/components/HistoryPopup.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/components/HistoryPopup.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| import React, { useState } from 'react'; | ||||
| import { ColorScheme } from '../utils/colorSchemes'; | ||||
| import { generateYAML } from '../utils/yamlExport'; | ||||
| import Image from 'next/image'; | ||||
| import ColorPalette from './ColorPalette'; | ||||
|  | ||||
| interface HistoryPopupProps { | ||||
|   likedSchemes: ColorScheme[]; | ||||
|   dislikedSchemes: ColorScheme[]; | ||||
|   onClose: () => void; | ||||
|   isDarkMode: boolean; | ||||
| } | ||||
|  | ||||
| const HistoryPopup: React.FC<HistoryPopupProps> = ({ likedSchemes, dislikedSchemes, onClose, isDarkMode }) => { | ||||
|   const [copiedColor, setCopiedColor] = useState<string | null>(null); | ||||
|  | ||||
|   const handleDownload = (scheme: ColorScheme) => { | ||||
|     const yaml = generateYAML(scheme); | ||||
|     const blob = new Blob([yaml], { type: 'text/yaml' }); | ||||
|     const url = URL.createObjectURL(blob); | ||||
|     const a = document.createElement('a'); | ||||
|     a.href = url; | ||||
|     a.download = `${scheme.name.replace(/\s+/g, '_').toLowerCase()}.yaml`; | ||||
|     document.body.appendChild(a); | ||||
|     a.click(); | ||||
|     document.body.removeChild(a); | ||||
|     URL.revokeObjectURL(url); | ||||
|   }; | ||||
|  | ||||
|   const renderSchemeGrid = (schemes: ColorScheme[], title: string) => ( | ||||
|     <div> | ||||
|       <h3 className="text-xl font-semibold mb-3">{title}</h3> | ||||
|       <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 mb-6"> | ||||
|         {schemes.map((scheme, index) => ( | ||||
|           <div key={`${scheme.name}-${index}`} className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg"> | ||||
|             <h3 className="text-sm font-semibold mb-2 truncate">{scheme.name}</h3> | ||||
|             <ColorPalette  | ||||
|               colors={Object.values(scheme.colors.normal).concat(Object.values(scheme.colors.bright))} | ||||
|               size="small" | ||||
|             /> | ||||
|             <button  | ||||
|               onClick={() => handleDownload(scheme)} | ||||
|               className="w-full bg-blue-500 text-white text-xs py-1 px-2 rounded hover:bg-blue-600 transition-colors duration-300" | ||||
|             > | ||||
|               Download | ||||
|             </button> | ||||
|           </div> | ||||
|         ))} | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
|  | ||||
|   return ( | ||||
|     <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> | ||||
|       <div className="bg-white dark:bg-gray-800 p-6 rounded-lg shadow-xl w-[90vw] h-[90vh] overflow-y-auto"> | ||||
|         <div className="flex justify-between items-center mb-4"> | ||||
|           <h2 className="text-2xl font-bold">Color Scheme History</h2> | ||||
|           <button onClick={onClose} className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"> | ||||
|             <Image src={isDarkMode ? "/close-icon-dark.svg" : "/close-icon-light.svg"} alt="Close" width={24} height={24} /> | ||||
|           </button> | ||||
|         </div> | ||||
|         {renderSchemeGrid(likedSchemes, "Liked Schemes")} | ||||
|         {renderSchemeGrid(dislikedSchemes, "Disliked Schemes")} | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
|  | ||||
| export default HistoryPopup; | ||||
							
								
								
									
										34
									
								
								app/page.tsx
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								app/page.tsx
									
									
									
									
									
								
							| @@ -3,6 +3,7 @@ | ||||
| import React, { useState, useEffect } from 'react'; | ||||
| import Image from 'next/image'; | ||||
| import ColorSchemeCard from "./components/ColorSchemeCard"; | ||||
| import HistoryPopup from "./components/HistoryPopup"; | ||||
| import { ColorScheme, knownSchemes, generateRandomScheme, generateSchemeFromGeneticAlgorithm } from './utils/colorSchemes'; | ||||
| import { AnimatePresence } from 'framer-motion'; | ||||
|  | ||||
| @@ -11,6 +12,7 @@ export default function Home() { | ||||
|   const [isDarkMode, setIsDarkMode] = useState(false); | ||||
|   const [likedSchemes, setLikedSchemes] = useState<ColorScheme[]>([]); | ||||
|   const [dislikedSchemes, setDislikedSchemes] = useState<ColorScheme[]>([]); | ||||
|   const [isHistoryOpen, setIsHistoryOpen] = useState(false); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     generateNewSchemes(8); | ||||
| @@ -86,12 +88,30 @@ export default function Home() { | ||||
|     setIsDarkMode(prev => !prev); | ||||
|   }; | ||||
|  | ||||
|   const toggleHistory = () => { | ||||
|     setIsHistoryOpen(!isHistoryOpen); | ||||
|   }; | ||||
|  | ||||
|   const getAllSchemes = () => { | ||||
|     const allSchemes = [...likedSchemes, ...dislikedSchemes]; | ||||
|     const uniqueSchemes = allSchemes.filter((scheme, index, self) => | ||||
|       index === self.findIndex((t) => t.name === scheme.name) | ||||
|     ); | ||||
|     return uniqueSchemes.reverse(); // Most recent first | ||||
|   }; | ||||
|  | ||||
|   return ( | ||||
|     <div className="h-screen w-screen overflow-hidden p-8 font-[family-name:var(--font-geist-sans)] dark:bg-gray-900 dark:text-white transition-colors duration-300"> | ||||
|       <header className="flex items-center mb-8"> | ||||
|     <div className="h-screen w-screen overflow-hidden font-[family-name:var(--font-geist-sans)] dark:bg-gray-900 dark:text-white transition-colors duration-300"> | ||||
|       <header className="absolute top-4 left-4 z-10"> | ||||
|         <Image src="/app-icon.svg" alt="App Icon" width={40} height={40} /> | ||||
|       </header> | ||||
|       <main className="flex flex-col items-center justify-center h-[calc(100vh-100px)]"> | ||||
|       <button  | ||||
|         className="absolute top-4 right-4 z-10" | ||||
|         onClick={toggleHistory} | ||||
|       > | ||||
|         <Image src={isDarkMode ? "/history-icon-dark.svg" : "/history-icon-light.svg"} alt="History" width={32} height={32} /> | ||||
|       </button> | ||||
|       <main className="flex flex-col items-center justify-center h-full"> | ||||
|         <AnimatePresence> | ||||
|           {schemes.slice(0, 3).map((scheme, index) => ( | ||||
|             <ColorSchemeCard | ||||
| @@ -105,6 +125,14 @@ export default function Home() { | ||||
|           ))} | ||||
|         </AnimatePresence> | ||||
|       </main> | ||||
|       {isHistoryOpen && ( | ||||
|         <HistoryPopup | ||||
|           likedSchemes={likedSchemes} | ||||
|           dislikedSchemes={dislikedSchemes} | ||||
|           onClose={toggleHistory} | ||||
|           isDarkMode={isDarkMode} | ||||
|         /> | ||||
|       )} | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user