Animations work, tinder layout

This commit is contained in:
Tanishq Dubey 2024-09-09 13:31:32 -04:00
parent 70b3d7327a
commit 5bbfa98ed3
8 changed files with 158 additions and 55 deletions

View File

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

View File

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

26
package-lock.json generated
View File

@ -8,6 +8,7 @@
"name": "terminaltinder",
"version": "0.1.0",
"dependencies": {
"framer-motion": "^11.5.4",
"next": "14.2.8",
"prism-react-renderer": "^2.4.0",
"react": "^18",
@ -2383,6 +2384,31 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/framer-motion": {
"version": "11.5.4",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.5.4.tgz",
"integrity": "sha512-E+tb3/G6SO69POkdJT+3EpdMuhmtCh9EWuK4I1DnIC23L7tFPrl8vxP+LSovwaw6uUr73rUbpb4FgK011wbRJQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",

View File

@ -9,6 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"framer-motion": "^11.5.4",
"next": "14.2.8",
"prism-react-renderer": "^2.4.0",
"react": "^18",

17
public/cross-icon.svg Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 25 25" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
<title>cross</title>
<desc>Created with Sketch Beta.</desc>
<defs>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
<g id="Icon-Set" sketch:type="MSLayerGroup" transform="translate(-467.000000, -1039.000000)" fill="#000000">
<path d="M489.396,1061.4 C488.614,1062.18 487.347,1062.18 486.564,1061.4 L479.484,1054.32 L472.404,1061.4 C471.622,1062.18 470.354,1062.18 469.572,1061.4 C468.79,1060.61 468.79,1059.35 469.572,1058.56 L476.652,1051.48 L469.572,1044.4 C468.79,1043.62 468.79,1042.35 469.572,1041.57 C470.354,1040.79 471.622,1040.79 472.404,1041.57 L479.484,1048.65 L486.564,1041.57 C487.347,1040.79 488.614,1040.79 489.396,1041.57 C490.179,1042.35 490.179,1043.62 489.396,1044.4 L482.316,1051.48 L489.396,1058.56 C490.179,1059.35 490.179,1060.61 489.396,1061.4 L489.396,1061.4 Z M485.148,1051.48 L490.813,1045.82 C492.376,1044.26 492.376,1041.72 490.813,1040.16 C489.248,1038.59 486.712,1038.59 485.148,1040.16 L479.484,1045.82 L473.82,1040.16 C472.257,1038.59 469.721,1038.59 468.156,1040.16 C466.593,1041.72 466.593,1044.26 468.156,1045.82 L473.82,1051.48 L468.156,1057.15 C466.593,1058.71 466.593,1061.25 468.156,1062.81 C469.721,1064.38 472.257,1064.38 473.82,1062.81 L479.484,1057.15 L485.148,1062.81 C486.712,1064.38 489.248,1064.38 490.813,1062.81 C492.376,1061.25 492.376,1058.71 490.813,1057.15 L485.148,1051.48 L485.148,1051.48 Z" id="cross" sketch:type="MSShapeGroup">
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

5
public/download-icon.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5535 16.5061C12.4114 16.6615 12.2106 16.75 12 16.75C11.7894 16.75 11.5886 16.6615 11.4465 16.5061L7.44648 12.1311C7.16698 11.8254 7.18822 11.351 7.49392 11.0715C7.79963 10.792 8.27402 10.8132 8.55352 11.1189L11.25 14.0682V3C11.25 2.58579 11.5858 2.25 12 2.25C12.4142 2.25 12.75 2.58579 12.75 3V14.0682L15.4465 11.1189C15.726 10.8132 16.2004 10.792 16.5061 11.0715C16.8118 11.351 16.833 11.8254 16.5535 12.1311L12.5535 16.5061Z" fill="#1C274C"/>
<path d="M3.75 15C3.75 14.5858 3.41422 14.25 3 14.25C2.58579 14.25 2.25 14.5858 2.25 15V15.0549C2.24998 16.4225 2.24996 17.5248 2.36652 18.3918C2.48754 19.2919 2.74643 20.0497 3.34835 20.6516C3.95027 21.2536 4.70814 21.5125 5.60825 21.6335C6.47522 21.75 7.57754 21.75 8.94513 21.75H15.0549C16.4225 21.75 17.5248 21.75 18.3918 21.6335C19.2919 21.5125 20.0497 21.2536 20.6517 20.6516C21.2536 20.0497 21.5125 19.2919 21.6335 18.3918C21.75 17.5248 21.75 16.4225 21.75 15.0549V15C21.75 14.5858 21.4142 14.25 21 14.25C20.5858 14.25 20.25 14.5858 20.25 15C20.25 16.4354 20.2484 17.4365 20.1469 18.1919C20.0482 18.9257 19.8678 19.3142 19.591 19.591C19.3142 19.8678 18.9257 20.0482 18.1919 20.1469C17.4365 20.2484 16.4354 20.25 15 20.25H9C7.56459 20.25 6.56347 20.2484 5.80812 20.1469C5.07435 20.0482 4.68577 19.8678 4.40901 19.591C4.13225 19.3142 3.9518 18.9257 3.85315 18.1919C3.75159 17.4365 3.75 16.4354 3.75 15Z" fill="#1C274C"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

4
public/heart-icon.svg Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 6.00019C10.2006 3.90317 7.19377 3.2551 4.93923 5.17534C2.68468 7.09558 2.36727 10.3061 4.13778 12.5772C5.60984 14.4654 10.0648 18.4479 11.5249 19.7369C11.6882 19.8811 11.7699 19.9532 11.8652 19.9815C11.9483 20.0062 12.0393 20.0062 12.1225 19.9815C12.2178 19.9532 12.2994 19.8811 12.4628 19.7369C13.9229 18.4479 18.3778 14.4654 19.8499 12.5772C21.6204 10.3061 21.3417 7.07538 19.0484 5.17534C16.7551 3.2753 13.7994 3.90317 12 6.00019Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 801 B

5
public/settings-icon.svg Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12.9046 3.06005C12.6988 3 12.4659 3 12 3C11.5341 3 11.3012 3 11.0954 3.06005C10.7942 3.14794 10.5281 3.32808 10.3346 3.57511C10.2024 3.74388 10.1159 3.96016 9.94291 4.39272C9.69419 5.01452 9.00393 5.33471 8.36857 5.123L7.79779 4.93281C7.3929 4.79785 7.19045 4.73036 6.99196 4.7188C6.70039 4.70181 6.4102 4.77032 6.15701 4.9159C5.98465 5.01501 5.83376 5.16591 5.53197 5.4677C5.21122 5.78845 5.05084 5.94882 4.94896 6.13189C4.79927 6.40084 4.73595 6.70934 4.76759 7.01551C4.78912 7.2239 4.87335 7.43449 5.04182 7.85566C5.30565 8.51523 5.05184 9.26878 4.44272 9.63433L4.16521 9.80087C3.74031 10.0558 3.52786 10.1833 3.37354 10.3588C3.23698 10.5141 3.13401 10.696 3.07109 10.893C3 11.1156 3 11.3658 3 11.8663C3 12.4589 3 12.7551 3.09462 13.0088C3.17823 13.2329 3.31422 13.4337 3.49124 13.5946C3.69158 13.7766 3.96395 13.8856 4.50866 14.1035C5.06534 14.3261 5.35196 14.9441 5.16236 15.5129L4.94721 16.1584C4.79819 16.6054 4.72367 16.829 4.7169 17.0486C4.70875 17.3127 4.77049 17.5742 4.89587 17.8067C5.00015 18.0002 5.16678 18.1668 5.5 18.5C5.83323 18.8332 5.99985 18.9998 6.19325 19.1041C6.4258 19.2295 6.68733 19.2913 6.9514 19.2831C7.17102 19.2763 7.39456 19.2018 7.84164 19.0528L8.36862 18.8771C9.00393 18.6654 9.6942 18.9855 9.94291 19.6073C10.1159 20.0398 10.2024 20.2561 10.3346 20.4249C10.5281 20.6719 10.7942 20.8521 11.0954 20.94C11.3012 21 11.5341 21 12 21C12.4659 21 12.6988 21 12.9046 20.94C13.2058 20.8521 13.4719 20.6719 13.6654 20.4249C13.7976 20.2561 13.8841 20.0398 14.0571 19.6073C14.3058 18.9855 14.9961 18.6654 15.6313 18.8773L16.1579 19.0529C16.605 19.2019 16.8286 19.2764 17.0482 19.2832C17.3123 19.2913 17.5738 19.2296 17.8063 19.1042C17.9997 18.9999 18.1664 18.8333 18.4996 18.5001C18.8328 18.1669 18.9994 18.0002 19.1037 17.8068C19.2291 17.5743 19.2908 17.3127 19.2827 17.0487C19.2759 16.8291 19.2014 16.6055 19.0524 16.1584L18.8374 15.5134C18.6477 14.9444 18.9344 14.3262 19.4913 14.1035C20.036 13.8856 20.3084 13.7766 20.5088 13.5946C20.6858 13.4337 20.8218 13.2329 20.9054 13.0088C21 12.7551 21 12.4589 21 11.8663C21 11.3658 21 11.1156 20.9289 10.893C20.866 10.696 20.763 10.5141 20.6265 10.3588C20.4721 10.1833 20.2597 10.0558 19.8348 9.80087L19.5569 9.63416C18.9478 9.26867 18.6939 8.51514 18.9578 7.85558C19.1262 7.43443 19.2105 7.22383 19.232 7.01543C19.2636 6.70926 19.2003 6.40077 19.0506 6.13181C18.9487 5.94875 18.7884 5.78837 18.4676 5.46762C18.1658 5.16584 18.0149 5.01494 17.8426 4.91583C17.5894 4.77024 17.2992 4.70174 17.0076 4.71872C16.8091 4.73029 16.6067 4.79777 16.2018 4.93273L15.6314 5.12287C14.9961 5.33464 14.3058 5.0145 14.0571 4.39272C13.8841 3.96016 13.7976 3.74388 13.6654 3.57511C13.4719 3.32808 13.2058 3.14794 12.9046 3.06005Z" stroke="#000000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB