Animations work, tinder layout
This commit is contained in:
@ -1,16 +1,21 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import Image from 'next/image';
|
||||
import { ColorScheme } from '../utils/colorSchemes';
|
||||
import { generateYAML } from '../utils/yamlExport';
|
||||
import { Highlight, themes } from 'prism-react-renderer';
|
||||
import { motion, useAnimation } from 'framer-motion';
|
||||
|
||||
interface ColorSchemeCardProps {
|
||||
scheme: ColorScheme;
|
||||
isSelected: boolean;
|
||||
onSelect: () => void;
|
||||
onLike: () => void;
|
||||
onDislike: () => void;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const ColorSchemeCard: React.FC<ColorSchemeCardProps> = ({ scheme, isSelected, onSelect }) => {
|
||||
const ColorSchemeCard: React.FC<ColorSchemeCardProps> = ({ scheme, onLike, onDislike, index }) => {
|
||||
const [overlayColor, setOverlayColor] = useState('rgba(0, 0, 0, 0)');
|
||||
const controls = useAnimation();
|
||||
|
||||
const codeExample = `
|
||||
// User object and function
|
||||
const user = {
|
||||
@ -54,13 +59,39 @@ fetchData().then(data => console.log(data)); `;
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const handleLike = () => {
|
||||
setOverlayColor('rgba(0, 255, 0, 0.1)');
|
||||
controls.start({ x: 300, opacity: 0, transition: { duration: 0.3 } }).then(onLike);
|
||||
};
|
||||
|
||||
const handleDislike = () => {
|
||||
setOverlayColor('rgba(255, 0, 0, 0.1)');
|
||||
controls.start({ x: -300, opacity: 0, transition: { duration: 0.3 } }).then(onDislike);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-96 bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden hover:shadow-xl transition-all duration-300 ease-in-out cursor-pointer ${isSelected ? 'ring-4 ring-blue-500' : ''}`}
|
||||
onClick={onSelect}
|
||||
<motion.div
|
||||
className="absolute w-[350px] h-[600px] bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden"
|
||||
initial={{ scale: 1 - index * 0.05, y: index * 15, opacity: 1 }}
|
||||
animate={controls}
|
||||
style={{
|
||||
zIndex: 3 - index
|
||||
}}
|
||||
drag="x"
|
||||
dragConstraints={{ left: -100, right: 100 }}
|
||||
onDragEnd={(e, { offset, velocity }) => {
|
||||
if (offset.x > 100) handleLike();
|
||||
else if (offset.x < -100) handleDislike();
|
||||
}}
|
||||
>
|
||||
<div className="p-6">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="p-6 h-full flex flex-col relative">
|
||||
<motion.div
|
||||
className="absolute inset-0 rounded-lg"
|
||||
animate={{ backgroundColor: overlayColor }}
|
||||
initial={{ backgroundColor: 'rgba(0, 0, 0, 0)' }}
|
||||
transition={{ duration: 0.2 }}
|
||||
/>
|
||||
<div className="flex justify-between items-center mb-4 z-10">
|
||||
<h2 className="text-xl font-semibold">{scheme.name}</h2>
|
||||
<button
|
||||
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 transition-colors duration-300"
|
||||
@ -69,11 +100,11 @@ fetchData().then(data => console.log(data)); `;
|
||||
<Image src="/download-icon.svg" alt="Download" width={24} height={24} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-md mb-4 transition-colors duration-300">
|
||||
<div className="bg-gray-100 dark:bg-gray-700 rounded-md mb-4 flex-grow overflow-hidden z-10 shadow-md">
|
||||
<Highlight theme={themes.dracula} code={codeExample} language="javascript">
|
||||
{({ className, style, tokens, getLineProps, getTokenProps }) => (
|
||||
<pre className={`${className} text-sm overflow-x-auto`} style={{ ...style, backgroundColor: scheme.colors.primary.background }}>
|
||||
{tokens.map((line, i) => (
|
||||
<pre className={`${className} text-xs p-4 overflow-x-auto`} style={{ ...style, backgroundColor: scheme.colors.primary.background }}>
|
||||
{tokens.slice(0, 20).map((line, i) => (
|
||||
<div key={i} {...getLineProps({ line, key: i })}>
|
||||
{line.map((token, key) => {
|
||||
let color = scheme.colors.primary.foreground;
|
||||
@ -95,21 +126,35 @@ fetchData().then(data => console.log(data)); `;
|
||||
)}
|
||||
</Highlight>
|
||||
</div>
|
||||
<div className="grid grid-cols-8 gap-2">
|
||||
<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-xs">
|
||||
<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>
|
||||
<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"
|
||||
onClick={handleDislike}
|
||||
>
|
||||
<Image src="/cross-icon.svg" alt="Dislike" width={28} height={28} />
|
||||
</button>
|
||||
<button
|
||||
className="bg-green-500 text-white p-3 rounded-full shadow-lg hover:bg-green-600 transition-colors duration-300"
|
||||
onClick={handleLike}
|
||||
>
|
||||
<Image src="/heart-icon.svg" alt="Like" width={28} height={28} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
|
80
app/page.tsx
80
app/page.tsx
@ -2,21 +2,20 @@
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import ColorSchemeCard from "./components/ColorSchemeCard";
|
||||
import ActionButton from "./components/ActionButton";
|
||||
import SettingsIcon from "./components/SettingsIcon";
|
||||
import Settings from "./components/Settings";
|
||||
import { ColorScheme, knownSchemes, generateRandomScheme, crossSchemes, generateSchemeFromGeneticAlgorithm } from './utils/colorSchemes';
|
||||
import { ColorScheme, knownSchemes, generateRandomScheme, generateSchemeFromGeneticAlgorithm } from './utils/colorSchemes';
|
||||
import { AnimatePresence } from 'framer-motion';
|
||||
|
||||
export default function Home() {
|
||||
const [schemes, setSchemes] = useState<ColorScheme[]>([]);
|
||||
const [selectedSchemes, setSelectedSchemes] = useState<number[]>([]);
|
||||
const [isDarkMode, setIsDarkMode] = useState(false);
|
||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||
const [likedSchemes, setLikedSchemes] = useState<ColorScheme[]>([]);
|
||||
const [dislikedSchemes, setDislikedSchemes] = useState<ColorScheme[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
generateNewSchemes();
|
||||
generateNewSchemes(8);
|
||||
setIsDarkMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
const storedLikedSchemes = localStorage.getItem('likedSchemes');
|
||||
const storedDislikedSchemes = localStorage.getItem('dislikedSchemes');
|
||||
@ -40,39 +39,43 @@ export default function Home() {
|
||||
localStorage.setItem('dislikedSchemes', JSON.stringify(dislikedSchemes));
|
||||
}, [dislikedSchemes]);
|
||||
|
||||
const generateNewSchemes = () => {
|
||||
const generateNewSchemes = (count: number) => {
|
||||
const knownCount = Math.floor(count / 2);
|
||||
const generatedCount = count - knownCount;
|
||||
|
||||
const newSchemes = [
|
||||
knownSchemes[Math.floor(Math.random() * knownSchemes.length)],
|
||||
generateRandomScheme(),
|
||||
likedSchemes.length > 0 ? generateSchemeFromGeneticAlgorithm(likedSchemes, dislikedSchemes) : generateRandomScheme()
|
||||
...knownSchemes.sort(() => 0.5 - Math.random()).slice(0, knownCount),
|
||||
...Array(generatedCount).fill(null).map(() =>
|
||||
likedSchemes.length > 0 ? generateSchemeFromGeneticAlgorithm(likedSchemes, dislikedSchemes) : generateRandomScheme()
|
||||
)
|
||||
];
|
||||
setSchemes(newSchemes);
|
||||
setSelectedSchemes([]);
|
||||
|
||||
// Shuffle the new schemes
|
||||
const shuffledSchemes = newSchemes.sort(() => 0.5 - Math.random());
|
||||
|
||||
setSchemes(prevSchemes => [...prevSchemes, ...shuffledSchemes]);
|
||||
};
|
||||
|
||||
const handleSchemeSelect = (index: number) => {
|
||||
setSelectedSchemes(prev => {
|
||||
if (prev.includes(index)) {
|
||||
return prev.filter(i => i !== index);
|
||||
} else if (prev.length < 2) {
|
||||
return [...prev, index];
|
||||
const handleLike = (scheme: ColorScheme) => {
|
||||
setLikedSchemes(prev => [...prev, scheme]);
|
||||
removeTopScheme();
|
||||
};
|
||||
|
||||
const handleDislike = (scheme: ColorScheme) => {
|
||||
setDislikedSchemes(prev => [...prev, scheme]);
|
||||
removeTopScheme();
|
||||
};
|
||||
|
||||
const removeTopScheme = () => {
|
||||
setSchemes(prevSchemes => {
|
||||
const newSchemes = prevSchemes.slice(1);
|
||||
if (newSchemes.length < 3) {
|
||||
generateNewSchemes(3);
|
||||
}
|
||||
return prev;
|
||||
return newSchemes;
|
||||
});
|
||||
};
|
||||
|
||||
const handleAction = () => {
|
||||
if (selectedSchemes.length === 2) {
|
||||
const crossedScheme = crossSchemes(schemes[selectedSchemes[0]], schemes[selectedSchemes[1]]);
|
||||
setLikedSchemes(prev => [...prev, schemes[selectedSchemes[0]], schemes[selectedSchemes[1]]]);
|
||||
setSchemes([crossedScheme, knownSchemes[Math.floor(Math.random() * knownSchemes.length)], generateSchemeFromGeneticAlgorithm(likedSchemes, dislikedSchemes)]);
|
||||
setSelectedSchemes([]);
|
||||
} else {
|
||||
setDislikedSchemes(prev => [...prev, ...schemes]);
|
||||
generateNewSchemes();
|
||||
}
|
||||
};
|
||||
|
||||
const toggleDarkMode = () => {
|
||||
setIsDarkMode(prev => !prev);
|
||||
};
|
||||
@ -83,21 +86,18 @@ export default function Home() {
|
||||
<h1 className="text-2xl font-bold">Terminal Color Scheme Generator</h1>
|
||||
<SettingsIcon onClick={() => setIsSettingsOpen(true)} />
|
||||
</header>
|
||||
<main className="flex flex-col gap-8 items-center">
|
||||
<div className="flex flex-wrap justify-center gap-8">
|
||||
{schemes.map((scheme, index) => (
|
||||
<main className="flex flex-col items-center justify-center h-[calc(100vh-200px)] relative">
|
||||
<AnimatePresence>
|
||||
{schemes.slice(0, 3).map((scheme, index) => (
|
||||
<ColorSchemeCard
|
||||
key={index}
|
||||
key={`${scheme.name}-${index}`}
|
||||
scheme={scheme}
|
||||
isSelected={selectedSchemes.includes(index)}
|
||||
onSelect={() => handleSchemeSelect(index)}
|
||||
onLike={() => handleLike(scheme)}
|
||||
onDislike={() => handleDislike(scheme)}
|
||||
index={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ActionButton
|
||||
onClick={handleAction}
|
||||
label={selectedSchemes.length === 2 ? 'Mix' : 'Shuffle'}
|
||||
/>
|
||||
</AnimatePresence>
|
||||
</main>
|
||||
<Settings
|
||||
isDarkMode={isDarkMode}
|
||||
|
Reference in New Issue
Block a user