History, more icons, color palette component

This commit is contained in:
Tanishq Dubey 2024-09-09 16:02:26 -04:00
parent 7851d2c0f6
commit e21173c8e4
8 changed files with 212 additions and 16 deletions

View 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;

View File

@ -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"

View 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;

View File

@ -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>
);
}

View File

@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M6 6L18 18M18 6L6 18" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 569 B

View File

@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M6 6L18 18M18 6L6 18" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 569 B

View File

@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C9.61061 4 7.46589 5.04751 6 6.70835C5.91595 6.80358 5.83413 6.90082 5.75463 7M12 8V12L14.5 14.5M5.75391 4.00391V7.00391H8.75391" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 763 B

View File

@ -0,0 +1,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="SVGRepo_bgCarrier" stroke-width="0"/>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/>
<g id="SVGRepo_iconCarrier"> <path d="M4 12C4 16.4183 7.58172 20 12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C9.61061 4 7.46589 5.04751 6 6.70835C5.91595 6.80358 5.83413 6.90082 5.75463 7M12 8V12L14.5 14.5M5.75391 4.00391V7.00391H8.75391" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> </g>
</svg>

After

Width:  |  Height:  |  Size: 763 B