Skip to content

Commit

Permalink
feat: add basic functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
gimnathperera committed Sep 29, 2023
1 parent b90e039 commit 2a1bf31
Show file tree
Hide file tree
Showing 15 changed files with 658 additions and 77 deletions.
21 changes: 21 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
"use client";
import { Toaster } from "react-hot-toast";
import Hero from "@/components/hero";
import { useState } from "react";
import Uploader from "@/components/uploader";
import { ColorInfo } from "@/types/uploader";
import ColorList from "@/components/color-list";

const Home = () => {
const [colorPalette, setColorPalette] = useState<ColorInfo[] | null>(null);

const onImageSelect = (colors: ColorInfo[]) => {
setColorPalette(colors);
};

return (
<section>
<Hero />

<div className="hero-content text-center">
<div className="max-w-2xl">
<Uploader onImageSelect={onImageSelect} />
<ColorList colorPalette={colorPalette} />
</div>
</div>

<Toaster position="bottom-left" reverseOrder={false} />
</section>
);
};
Expand Down
60 changes: 60 additions & 0 deletions components/color-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import toast from "react-hot-toast";
import {
ActiveColorBox as ColorBox,
InactiveColorBox,
} from "@/components/colorbox";
import { ColorInfo } from "@/types/uploader";
import { MaxPaletteSize } from "@/constants";

type Props = {
colorPalette: ColorInfo[] | null;
};

const ColorList = ({ colorPalette }: Props) => {
const handleOnCopyToClipboard = (selectedColor: string): void => {
navigator.clipboard.writeText(selectedColor ?? "");
toast.success("Copied to clipboard");
};

const InactivePanel = () => {
const boxes = Array.from({ length: MaxPaletteSize }, (_, index) => (
<InactiveColorBox key={index} />
));

return (
<div className="flex flex-row justify-around flex-wrap gap-2">
{boxes}
</div>
);
};

const ActivePanel = () => {
const boxes = (colorPalette || [])
.slice(0, MaxPaletteSize)
.map((colorInfo, index) => (
<ColorBox
key={index}
color={colorInfo.hex}
handleOnCopyToClipboard={handleOnCopyToClipboard}
/>
));

return (
<div className="flex flex-row justify-around flex-wrap gap-2">
{boxes}
</div>
);
};

return (
<div className="pt-14">
{colorPalette && colorPalette?.length > 0 ? (
<ActivePanel />
) : (
<InactivePanel />
)}
</div>
);
};

export default ColorList;
30 changes: 30 additions & 0 deletions components/colorbox/active.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ProhibitIcon from "../icons/prohibit";

type Props = {
color: string;
handleOnCopyToClipboard: (color: string) => void;
};

const ColorBox = ({ color, handleOnCopyToClipboard }: Props) => {
return (
<div
className="bg-transparent p-1 w-12 h-12 md:w-28 md:h-28 lg:w-28 lg:h-28 rounded-lg shadow-xl border-2 border-gray-300 border-dashed"
onClick={() => {
handleOnCopyToClipboard(color);
}}
>
<div
className="transform active:scale-y-75 transition-transform cursor-pointer flex flex-col justify-end h-full rounded-lg p-0"
style={{
backgroundColor: color,
}}
>
<p className="p-2 rounded-b-lg rounded-none w-full text-white text-xs md:text-sm hidden md:block">
{color}
</p>
</div>
</div>
);
};

export default ColorBox;
20 changes: 20 additions & 0 deletions components/colorbox/disabled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import ProhibitIcon from "../icons/prohibit";

type Props = {
color?: string;
};

const Disabled = ({ color = "#E5E5E5" }: Props) => {
const backgroundColor = `bg-[${color}]`;

return (
<div className="bg-transparent p-1 w-12 h-12 md:w-28 md:h-28 lg:w-28 lg:h-28 rounded-lg shadow-xl border-2 border-gray-300 border-dashed">
<div
className={`cursor-not-allowed flex flex-col justify-center items-center h-full rounded-lg p-0 ${backgroundColor}`}
>
<ProhibitIcon />
</div>
</div>
);
};
export default Disabled;
2 changes: 2 additions & 0 deletions components/colorbox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as ActiveColorBox } from "./active";
export { default as InactiveColorBox } from "./disabled";
2 changes: 1 addition & 1 deletion components/footer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ type Props = {};

const Footer = (props: Props) => {
return (
<footer className="footer footer-center p-4 text-base-content">
<footer className="footer footer-center p-4 text-base-content pt-12">
<aside>
<p>Made with 💖 by Gimnath</p>
</aside>
Expand Down
45 changes: 13 additions & 32 deletions components/hero/index.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
"use client";
import { HeroColors } from "@/constants";
import { useState, useEffect } from "react";
// import AnimatedCursor from "react-animated-cursor";
import Uploader from "../uploader";

const Colors = [
"#FF5117",
"#FF8E00",
"#F9D81E",
"#97CC37",
"#8177FA",
"#00C0E9",
"#C902E6",
];

const Hero = () => {
const [currentColorIndex, setCurrentColorIndex] = useState(0);

useEffect(() => {
const cycleColors = () => {
setCurrentColorIndex((prevIndex) => (prevIndex + 1) % Colors.length);
setCurrentColorIndex((prevIndex) => (prevIndex + 1) % HeroColors.length);
};

const colorInterval = setInterval(cycleColors, 3000);
Expand All @@ -28,37 +18,28 @@ const Hero = () => {
};
}, []);

const currentColor = Colors[currentColorIndex];
const currentColor = HeroColors[currentColorIndex];

return (
<div className="hero lg:pt-24 pt-12">
<div className="hero lg:pt-18 pt-12">
<div className="hero-content text-center">
<div className="max-w-2xl">
<h1 className="text-4xl lg:text-5xl font-bold ">
Your Images, Your{" "}
<span className="animate-fadeOut" style={{ color: currentColor }}>
Colors
<h1 className="text-4xl lg:text-5xl font-bold">
Your Images, Your{" "}
<span className="before:block before:absolute before:-inset-1 before:-skew-y-3 before:bg-pink-500 relative inline-block">
<span
className="relative text-white animate-fadeOut"
style={{ color: currentColor }}
>
Colors
</span>
</span>
</h1>
<p className="py-6 text-xl">
Transform your photos into beautiful color palettes with a single
click. Create unique color palettes for your art and design
endeavors.
</p>

<Uploader />

<div className="pt-16">
<div className="flex flex-row justify-around">
<div className="bg-transparent p-1 w-12 h-12 lg:w-28 lg:h-28 rounded-lg shadow-xl cursor-pointer border-2 border-gray-300 border-dashed">
<div className="btn btn-ghost flex flex-col justify-end h-full bg-red-300 rounded-lg p-0">
<div className="p-2 rounded-b-lg rounded-none w-full text-white text-xs">
#F42185
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{/* <AnimatedCursor /> */}
Expand Down
18 changes: 18 additions & 0 deletions components/icons/prohibit.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const ProhibitIcon = () => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="57"
height="57"
fill="none"
viewBox="0 0 57 57"
>
<path
fill="#FF5A79"
d="M28.457.533C12.894.533.332 13.095.332 28.657c0 15.563 12.563 28.125 28.125 28.125 15.563 0 28.125-12.562 28.125-28.124C56.582 13.094 44.019.532 28.457.532zm20.625 28.124a20.43 20.43 0 01-3.656 11.72L16.738 11.688a20.429 20.429 0 0111.719-3.656c11.438 0 20.625 9.28 20.625 20.625zm-41.25 0a20.43 20.43 0 013.656-11.718l28.688 28.687a20.43 20.43 0 01-11.719 3.657C17.113 49.282 7.832 40 7.832 28.656z"
></path>
</svg>
);
};

export default ProhibitIcon;
77 changes: 62 additions & 15 deletions components/uploader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,73 @@
import UploadIcon from "../icons/upload";
/* eslint-disable @next/next/no-img-element */
"use client";
import React, { useState } from "react";
import { extractColors } from "extract-colors";
import UploadIcon from "@/components/icons/upload";
import { ColorInfo } from "@/types/uploader";

type Props = {};
type Props = {
onImageSelect: (colors: ColorInfo[]) => void;
};

const Uploader = ({ onImageSelect }: Props) => {
const [selectedImage, setSelectedImage] = useState<string | null>(null);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];

if (file) {
const reader = new FileReader();

reader.onload = async (event) => {
const imageDataUrl = reader.result as string;
setSelectedImage(imageDataUrl);
const img = new Image();

img.onload = async () => {
const result = await extractColors(img);
onImageSelect(result);
};

img.src = event.target?.result as string;
};

reader.readAsDataURL(file);
}
};

const Uploader = ({}: Props) => {
return (
<div className="flex items-center justify-center w-full">
<div className="flex flex-col items-center justify-center w-full">
<label
htmlFor="dropzone-file"
className="flex flex-col items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-50 dark:hover:bg-bray-800 hover:bg-gray-100 "
className="flex flex-col p-1 items-center justify-center w-full h-64 border-2 border-gray-300 border-dashed rounded-lg cursor-pointer bg-gray-5 hover:bg-gray-100"
>
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<div className="animate-bounce">
<UploadIcon />
{selectedImage ? (
<div className="w-full h-full overflow-hidden">
<img
src={selectedImage}
alt="Selected"
className="object-cover w-full h-full"
/>
</div>
) : (
<div className="flex flex-col items-center justify-center pt-5 pb-6">
<div className="animate-bounce">
<UploadIcon />
</div>
<p className="mb-2 text-sm">
<span className="font-semibold">Click to upload</span> or drag and
drop
</p>
<p className="text-xs">SVG, PNG, or JPG</p>
</div>
)}

<p className="mb-2 text-sm">
<span className="font-semibold">Click to upload</span> or drag and
drop
</p>
<p className="text-xs">SVG, PNG or JPG</p>
</div>
<input id="dropzone-file" type="file" className="hidden" />
<input
id="dropzone-file"
type="file"
className="hidden"
onChange={handleFileChange}
/>
</label>
</div>
);
Expand Down
11 changes: 11 additions & 0 deletions constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const HeroColors = [
"#FF5117",
"#FF8E00",
"#F9D81E",
"#97CC37",
"#8177FA",
"#00C0E9",
"#C902E6",
];

export const MaxPaletteSize = 5;
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
"lint": "next lint"
},
"dependencies": {
"colorthief": "^2.4.0",
"extract-colors": "^4.0.2",
"next": "latest",
"react": "latest",
"react-animated-cursor": "^2.10.1",
"react-dom": "latest"
"react-dom": "latest",
"react-hot-toast": "^2.4.1"
},
"devDependencies": {
"@types/node": "latest",
Expand Down
Loading

0 comments on commit 2a1bf31

Please sign in to comment.