customizable icon color
This commit is contained in:
parent
3e6bef875b
commit
be180d34e2
|
@ -1,10 +1,15 @@
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import {
|
||||||
import { faPenToSquare, faPlus } from "@fortawesome/free-solid-svg-icons";
|
faFolder,
|
||||||
|
faPenToSquare,
|
||||||
|
faPlus,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import useCollectionStore from "@/store/collections";
|
import useCollectionStore from "@/store/collections";
|
||||||
import { CollectionIncludingMembers } from "@/types/global";
|
import { CollectionIncludingMembers } from "@/types/global";
|
||||||
import RequiredBadge from "../../RequiredBadge";
|
import RequiredBadge from "../../RequiredBadge";
|
||||||
import SubmitButton from "@/components/SubmitButton";
|
import SubmitButton from "@/components/SubmitButton";
|
||||||
|
import { HexColorPicker } from "react-colorful";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
toggleCollectionModal: Function;
|
toggleCollectionModal: Function;
|
||||||
|
@ -46,6 +51,7 @@ export default function CollectionInfo({
|
||||||
Name
|
Name
|
||||||
<RequiredBadge />
|
<RequiredBadge />
|
||||||
</p>
|
</p>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
<input
|
<input
|
||||||
value={collection.name}
|
value={collection.name}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
@ -55,12 +61,38 @@ export default function CollectionInfo({
|
||||||
placeholder="e.g. Example Collection"
|
placeholder="e.g. Example Collection"
|
||||||
className="w-full rounded-md p-3 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
className="w-full rounded-md p-3 border-sky-100 border-solid border outline-none focus:border-sky-500 duration-100"
|
||||||
/>
|
/>
|
||||||
|
<div className="color-picker flex justify-between">
|
||||||
|
<div className="flex flex-col justify-between items-center w-32">
|
||||||
|
<p className="text-sm w-full font-bold text-sky-300 mb-2">
|
||||||
|
Icon Color
|
||||||
|
</p>
|
||||||
|
<div style={{ color: collection.color || "#7dd3fc" }}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faFolder}
|
||||||
|
className="w-12 h-12 drop-shadow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="py-1 px-2 rounded-md text-xs font-semibold cursor-pointer text-gray-500 hover:bg-slate-200 duration-100"
|
||||||
|
onClick={() =>
|
||||||
|
setCollection({ ...collection, color: "#7dd3fc" })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<HexColorPicker
|
||||||
|
color={collection.color || "#7dd3fc"}
|
||||||
|
onChange={(e) => setCollection({ ...collection, color: e })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<p className="text-sm font-bold text-sky-300 mb-2">Description</p>
|
<p className="text-sm font-bold text-sky-300 mb-2">Description</p>
|
||||||
<textarea
|
<textarea
|
||||||
className="w-full h-40 resize-none border rounded-md duration-100 bg-white p-3 outline-none border-sky-100 focus:border-sky-500"
|
className="w-full h-[11.4rem] resize-none border rounded-md duration-100 bg-white p-3 outline-none border-sky-100 focus:border-sky-500"
|
||||||
placeholder="The purpose of this Collection..."
|
placeholder="The purpose of this Collection..."
|
||||||
value={collection.description}
|
value={collection.description}
|
||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
|
|
|
@ -7,9 +7,16 @@ interface SidebarItemProps {
|
||||||
icon: ReactElement;
|
icon: ReactElement;
|
||||||
path: string;
|
path: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
iconColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ({ text, icon, path, className }: SidebarItemProps) {
|
export default function ({
|
||||||
|
text,
|
||||||
|
icon,
|
||||||
|
path,
|
||||||
|
className,
|
||||||
|
iconColor,
|
||||||
|
}: SidebarItemProps) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [active, setActive] = useState(false);
|
const [active, setActive] = useState(false);
|
||||||
|
|
||||||
|
@ -23,10 +30,13 @@ export default function ({ text, icon, path, className }: SidebarItemProps) {
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
active ? "bg-sky-500" : "hover:bg-slate-200 bg-gray-100"
|
active ? "bg-sky-500" : "hover:bg-slate-200 bg-gray-100"
|
||||||
} duration-100 py-1 px-4 cursor-pointer flex items-center gap-2 w-full ${className}`}
|
} duration-100 py-1 px-2 cursor-pointer flex items-center gap-2 w-full rounded-md ${className}`}
|
||||||
>
|
>
|
||||||
{React.cloneElement(icon, {
|
{React.cloneElement(icon, {
|
||||||
className: `w-4 h-4 ${active ? "text-white" : "text-sky-300"}`,
|
className: "w-4 h-4",
|
||||||
|
style: {
|
||||||
|
color: active ? "white" : iconColor ? iconColor : "#7dd3fc",
|
||||||
|
},
|
||||||
})}
|
})}
|
||||||
<p
|
<p
|
||||||
className={`${active ? "text-white" : "text-sky-900"} truncate w-4/6`}
|
className={`${active ? "text-white" : "text-sky-900"} truncate w-4/6`}
|
||||||
|
|
|
@ -27,19 +27,20 @@ export default function ({ className }: { className?: string }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`bg-gray-100 h-screen w-64 xl:w-80 overflow-y-auto border-solid border-r-sky-100 border z-20 ${className}`}
|
className={`bg-gray-100 h-screen w-64 xl:w-80 overflow-y-auto border-solid border-r-sky-100 px-2 border z-20 ${className}`}
|
||||||
>
|
>
|
||||||
<p className="p-4 text-sky-500 font-bold text-2xl my-2 leading-4">
|
<p className="p-3 text-sky-500 font-bold text-2xl my-2 leading-4">
|
||||||
Linkwarden
|
Linkwarden
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
<Link href="/dashboard">
|
<Link href="/dashboard">
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
active === "/dashboard"
|
active === "/dashboard"
|
||||||
? "bg-sky-500"
|
? "bg-sky-500"
|
||||||
: "hover:bg-slate-200 bg-gray-100"
|
: "hover:bg-slate-200 bg-gray-100"
|
||||||
} outline-sky-100 outline-1 duration-100 py-1 px-4 cursor-pointer flex items-center gap-2`}
|
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faChartSimple}
|
icon={faChartSimple}
|
||||||
|
@ -61,7 +62,7 @@ export default function ({ className }: { className?: string }) {
|
||||||
<div
|
<div
|
||||||
className={`${
|
className={`${
|
||||||
active === "/collections" ? "bg-sky-500" : "hover:bg-slate-200"
|
active === "/collections" ? "bg-sky-500" : "hover:bg-slate-200"
|
||||||
} outline-sky-100 outline-1 duration-100 py-1 px-4 cursor-pointer flex items-center gap-2`}
|
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faBox}
|
icon={faBox}
|
||||||
|
@ -85,7 +86,7 @@ export default function ({ className }: { className?: string }) {
|
||||||
active === "/links"
|
active === "/links"
|
||||||
? "bg-sky-500"
|
? "bg-sky-500"
|
||||||
: "hover:bg-slate-200 bg-gray-100"
|
: "hover:bg-slate-200 bg-gray-100"
|
||||||
} outline-sky-100 outline-1 duration-100 py-1 px-4 cursor-pointer flex items-center gap-2`}
|
} outline-sky-100 outline-1 duration-100 py-1 px-2 rounded-md cursor-pointer flex items-center gap-2`}
|
||||||
>
|
>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faBookmark}
|
icon={faBookmark}
|
||||||
|
@ -94,17 +95,20 @@ export default function ({ className }: { className?: string }) {
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<p
|
<p
|
||||||
className={`${active === "/links" ? "text-white" : "text-sky-900"}`}
|
className={`${
|
||||||
|
active === "/links" ? "text-white" : "text-sky-900"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
All Links
|
All Links
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="text-gray-500 mt-5">
|
<div className="text-gray-500 mt-5">
|
||||||
<p className="text-sm px-4 mb-2">Collections</p>
|
<p className="text-sm mb-2 pl-3 font-semibold">Collections</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex flex-col gap-1">
|
||||||
{collections
|
{collections
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
.map((e, i) => {
|
.map((e, i) => {
|
||||||
|
@ -113,6 +117,7 @@ export default function ({ className }: { className?: string }) {
|
||||||
key={i}
|
key={i}
|
||||||
text={e.name}
|
text={e.name}
|
||||||
icon={<FontAwesomeIcon icon={faFolder} />}
|
icon={<FontAwesomeIcon icon={faFolder} />}
|
||||||
|
iconColor={e.color}
|
||||||
path={`/collections/${e.id}`}
|
path={`/collections/${e.id}`}
|
||||||
className="capitalize"
|
className="capitalize"
|
||||||
/>
|
/>
|
||||||
|
@ -120,9 +125,9 @@ export default function ({ className }: { className?: string }) {
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-gray-500 mt-5">
|
<div className="text-gray-500 mt-5">
|
||||||
<p className="text-sm px-4 mb-2">Tags</p>
|
<p className="text-sm mb-2 pl-3 font-semibold">Tags</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="flex flex-col gap-1">
|
||||||
{tags
|
{tags
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
.map((e, i) => {
|
.map((e, i) => {
|
||||||
|
|
|
@ -39,6 +39,7 @@ export default async function (
|
||||||
},
|
},
|
||||||
name: collection.name,
|
name: collection.name,
|
||||||
description: collection.description,
|
description: collection.description,
|
||||||
|
color: collection.color,
|
||||||
members: {
|
members: {
|
||||||
create: collection.members.map((e) => ({
|
create: collection.members.map((e) => ({
|
||||||
user: { connect: { email: e.user.email } },
|
user: { connect: { email: e.user.email } },
|
||||||
|
|
|
@ -30,6 +30,7 @@ export default async function (
|
||||||
data: {
|
data: {
|
||||||
name: collection.name,
|
name: collection.name,
|
||||||
description: collection.description,
|
description: collection.description,
|
||||||
|
color: collection.color,
|
||||||
isPublic: collection.isPublic,
|
isPublic: collection.isPublic,
|
||||||
members: {
|
members: {
|
||||||
create: collection.members.map((e) => ({
|
create: collection.members.map((e) => ({
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
"puppeteer-extra-plugin-adblocker": "^2.13.6",
|
"puppeteer-extra-plugin-adblocker": "^2.13.6",
|
||||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
|
"react-colorful": "^5.6.1",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-image-file-resizer": "^0.4.8",
|
"react-image-file-resizer": "^0.4.8",
|
||||||
"react-select": "^5.7.0",
|
"react-select": "^5.7.0",
|
||||||
|
|
|
@ -104,17 +104,20 @@ export default function () {
|
||||||
<div className="p-5 flex flex-col gap-5 w-full">
|
<div className="p-5 flex flex-col gap-5 w-full">
|
||||||
<div className="bg-gradient-to-tr from-sky-100 from-10% via-gray-100 via-20% rounded-xl shadow min-h-[10rem] p-5 flex gap-5 flex-col justify-between">
|
<div className="bg-gradient-to-tr from-sky-100 from-10% via-gray-100 via-20% rounded-xl shadow min-h-[10rem] p-5 flex gap-5 flex-col justify-between">
|
||||||
<div className="flex flex-col sm:flex-row gap-3 justify-between items-center sm:items-start">
|
<div className="flex flex-col sm:flex-row gap-3 justify-between items-center sm:items-start">
|
||||||
|
{activeCollection ? (
|
||||||
<div className="flex gap-3 items-center">
|
<div className="flex gap-3 items-center">
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
icon={faFolder}
|
icon={faFolder}
|
||||||
className="sm:w-8 sm:h-8 w-6 h-6 mt-2 text-sky-300"
|
style={{ color: activeCollection?.color }}
|
||||||
|
className="sm:w-8 sm:h-8 w-6 h-6 mt-2"
|
||||||
/>
|
/>
|
||||||
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">
|
<p className="sm:text-4xl text-3xl capitalize bg-gradient-to-tr from-sky-500 to-slate-400 bg-clip-text text-transparent font-bold">
|
||||||
{activeCollection?.name}
|
{activeCollection?.name}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{activeCollection ? (
|
{activeCollection ? (
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -16,6 +16,7 @@ CREATE TABLE "Collection" (
|
||||||
"id" SERIAL NOT NULL,
|
"id" SERIAL NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"description" TEXT NOT NULL DEFAULT '',
|
"description" TEXT NOT NULL DEFAULT '',
|
||||||
|
"color" TEXT NOT NULL DEFAULT '#7dd3fc',
|
||||||
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"ownerId" INTEGER NOT NULL,
|
"ownerId" INTEGER NOT NULL,
|
||||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
@ -24,6 +24,7 @@ model Collection {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
name String
|
name String
|
||||||
description String @default("")
|
description String @default("")
|
||||||
|
color String @default("#7dd3fc")
|
||||||
isPublic Boolean @default(false)
|
isPublic Boolean @default(false)
|
||||||
owner User @relation(fields: [ownerId], references: [id])
|
owner User @relation(fields: [ownerId], references: [id])
|
||||||
ownerId Int
|
ownerId Int
|
||||||
|
|
|
@ -31,13 +31,6 @@
|
||||||
animation: fade-in-animation 100ms;
|
animation: fade-in-animation 100ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bug: "lg:block" just didn't work... */
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.lgblock {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fade-in-animation {
|
@keyframes fade-in-animation {
|
||||||
0% {
|
0% {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -89,3 +82,16 @@
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* For react-colorful */
|
||||||
|
.color-picker .react-colorful {
|
||||||
|
width: 7.5rem;
|
||||||
|
height: 7.5rem;
|
||||||
|
}
|
||||||
|
.color-picker .react-colorful__hue {
|
||||||
|
height: 1rem;
|
||||||
|
}
|
||||||
|
.color-picker .react-colorful__pointer {
|
||||||
|
width: 1.3rem;
|
||||||
|
height: 1.3rem;
|
||||||
|
}
|
||||||
|
|
|
@ -3132,6 +3132,11 @@ quick-lru@^5.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||||
|
|
||||||
|
react-colorful@^5.6.1:
|
||||||
|
version "5.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-colorful/-/react-colorful-5.6.1.tgz#7dc2aed2d7c72fac89694e834d179e32f3da563b"
|
||||||
|
integrity sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==
|
||||||
|
|
||||||
react-dom@18.2.0:
|
react-dom@18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
||||||
|
|
Ŝarĝante…
Reference in New Issue