feat: added dropdown component

This commit is contained in:
Daniel 2023-03-23 02:41:54 +03:30
parent e5e2a615fc
commit f80113c487
20 changed files with 152 additions and 54 deletions

View File

@ -5,7 +5,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { useRouter } from "next/router";
import { NewLink } from "@/types/global";
import useLinkSlice from "@/store/links";
import useLinkStore from "@/store/links";
export default function ({ toggleLinkModal }: { toggleLinkModal: Function }) {
const router = useRouter();
@ -17,7 +17,7 @@ export default function ({ toggleLinkModal }: { toggleLinkModal: Function }) {
collection: { id: Number(router.query.id) },
});
const { addLink } = useLinkSlice();
const { addLink } = useLinkStore();
const setTags = (e: any) => {
const tagNames = e.map((e: any) => {

View File

@ -1,10 +1,10 @@
import React, { useRef, useEffect, ReactNode, RefObject } from "react";
interface Props {
type Props = {
children: ReactNode;
onClickOutside: Function;
className?: string;
}
};
function useOutsideAlerter(
ref: RefObject<HTMLElement>,

47
components/Dropdown.tsx Normal file
View File

@ -0,0 +1,47 @@
import Link from "next/link";
import React, { MouseEventHandler, ReactElement } from "react";
import ClickAwayHandler from "./ClickAwayHandler";
type MenuItem = {
name: string;
icon: ReactElement;
onClick?: MouseEventHandler;
href?: string;
};
type Props = {
onClickOutside: Function;
className?: string;
items: MenuItem[];
};
export default function ({ onClickOutside, className, items }: Props) {
return (
<ClickAwayHandler
onClickOutside={onClickOutside}
className={`${className} border border-sky-100 shadow mb-5 bg-gray-50 p-4 rounded flex flex-col gap-4`}
>
{items.map((e, i) => {
return e.href ? (
<Link key={i} href={e.href}>
<div className="flex items-center gap-2 px-2 cursor-pointer">
{React.cloneElement(e.icon, {
className: "text-sky-500 w-5 h-5",
})}
<p className="text-sky-900">{e.name}</p>
</div>
</Link>
) : (
<div key={i} onClick={e.onClick}>
<div className="flex items-center gap-2 px-2 cursor-pointer">
{React.cloneElement(e.icon, {
className: "text-sky-500 w-5 h-5",
})}
<p className="text-sky-900">{e.name}</p>
</div>
</div>
);
})}
</ClickAwayHandler>
);
}

View File

@ -1,4 +1,4 @@
import useCollectionSlice from "@/store/collections";
import useCollectionStore from "@/store/collections";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import CreatableSelect from "react-select/creatable";
@ -6,7 +6,7 @@ import { styles } from "./styles";
import { Options } from "./types";
export default function ({ onChange }: any) {
const { collections } = useCollectionSlice();
const { collections } = useCollectionStore();
const router = useRouter();
const [options, setOptions] = useState<Options[]>([]);

View File

@ -1,11 +1,11 @@
import useTagSlice from "@/store/tags";
import useTagStore from "@/store/tags";
import { useEffect, useState } from "react";
import CreatableSelect from "react-select/creatable";
import { styles } from "./styles";
import { Options } from "./types";
export default function ({ onChange }: any) {
const { tags } = useTagSlice();
const { tags } = useTagStore();
const [options, setOptions] = useState<Options[]>([]);

View File

@ -3,13 +3,16 @@ import {
faFolder,
faArrowUpRightFromSquare,
faEllipsis,
faHeart,
faStar,
faPenToSquare,
faTrashCan,
} from "@fortawesome/free-solid-svg-icons";
import { faFileImage, faFilePdf } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useState } from "react";
import Image from "next/image";
import Dropdown from "./Dropdown";
export default function ({
link,
@ -18,6 +21,7 @@ export default function ({
link: ExtendedLink;
count: number;
}) {
const [editDropdown, setEditDropdown] = useState(false);
const [archiveLabel, setArchiveLabel] = useState("Archived Formats");
const shortendURL = new URL(link.url).host.toLowerCase();
@ -42,8 +46,8 @@ export default function ({
<div className="flex items-baseline gap-1">
<p className="text-sm text-sky-300 font-bold">{count + 1}.</p>
<p className="text-lg text-sky-600">{link.name}</p>
{link.isFavorites ? (
<FontAwesomeIcon icon={faHeart} className="w-3 text-red-600" />
{link.starred ? (
<FontAwesomeIcon icon={faStar} className="w-3 text-amber-400" />
) : null}
</div>
<p className="text-sky-400 text-sm font-medium">{link.title}</p>
@ -76,10 +80,12 @@ export default function ({
</a>
</div>
</div>
<div className="flex flex-col justify-between items-end">
<div className="flex flex-col justify-between items-end relative">
<FontAwesomeIcon
icon={faEllipsis}
className="w-6 h-6 text-gray-500 cursor-pointer"
className="w-6 h-6 text-gray-500 hover:text-gray-400 duration-100 cursor-pointer"
onClick={() => setEditDropdown(!editDropdown)}
/>
<div>
<p className="text-center text-sky-500 text-sm font-bold">
@ -121,6 +127,27 @@ export default function ({
/>
</div>
</div>
{editDropdown ? (
<Dropdown
items={[
{
name: "Star",
icon: <FontAwesomeIcon icon={faStar} />,
},
{
name: "Edit",
icon: <FontAwesomeIcon icon={faPenToSquare} />,
},
{
name: "Delete",
icon: <FontAwesomeIcon icon={faTrashCan} />,
},
]}
onClickOutside={() => setEditDropdown(!editDropdown)}
className="absolute top-8 right-0"
/>
) : null}
</div>
</div>
</div>

View File

@ -1,6 +1,5 @@
import { signOut } from "next-auth/react";
import { useRouter } from "next/router";
import useCollectionSlice from "@/store/collections";
import useCollectionStore from "@/store/collections";
import { Collection, Tag } from "@prisma/client";
import ClickAwayHandler from "./ClickAwayHandler";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@ -15,15 +14,15 @@ import {
} from "@fortawesome/free-solid-svg-icons";
import { useEffect, useState } from "react";
import AddLinkModal from "./AddLinkModal";
import useTagSlice from "@/store/tags";
import useTagStore from "@/store/tags";
export default function () {
const router = useRouter();
const [pageName, setPageName] = useState<string | null>("");
const [pageIcon, setPageIcon] = useState<IconDefinition | null>(null);
const { collections } = useCollectionSlice();
const { tags } = useTagSlice();
const { collections } = useCollectionStore();
const { tags } = useTagStore();
useEffect(() => {
if (router.route === "/collections/[id]") {
@ -83,12 +82,6 @@ export default function () {
icon={faMagnifyingGlass}
className="select-none cursor-pointer w-5 h-5 text-white bg-sky-500 p-2 rounded hover:bg-sky-400 duration-100"
/>
<div
onClick={() => signOut()}
className="cursor-pointer w-max text-sky-900"
>
Sign Out
</div>
{linkModal ? (
<div className="fixed top-0 bottom-0 right-0 left-0 bg-gray-500 bg-opacity-10 flex items-center fade-in z-10">

View File

@ -1,9 +1,9 @@
import { useSession } from "next-auth/react";
import ClickAwayHandler from "@/components/ClickAwayHandler";
import { useState } from "react";
import useCollectionSlice from "@/store/collections";
import useCollectionStore from "@/store/collections";
import { signOut } from "next-auth/react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faUserCircle } from "@fortawesome/free-regular-svg-icons";
import {
faPlus,
faChevronDown,
@ -11,19 +11,24 @@ import {
faBox,
faHashtag,
faBookmark,
faCircleUser,
faSliders,
faArrowRightFromBracket,
} from "@fortawesome/free-solid-svg-icons";
import SidebarItem from "./SidebarItem";
import useTagSlice from "@/store/tags";
import useTagStore from "@/store/tags";
import Link from "next/link";
import Dropdown from "@/components/Dropdown";
export default function () {
const { data: session } = useSession();
const [collectionInput, setCollectionInput] = useState(false);
const [profileDropdown, setProfileDropdown] = useState(false);
const { collections, addCollection } = useCollectionSlice();
const { collections, addCollection } = useCollectionStore();
const { tags } = useTagSlice();
const { tags } = useTagStore();
const user = session?.user;
@ -44,12 +49,35 @@ export default function () {
return (
<div className="fixed bg-gray-100 top-0 bottom-0 left-0 w-80 p-5 overflow-y-auto hide-scrollbar border-solid border-r-sky-100 border z-10">
<div className="flex gap-3 items-center mb-5 p-3 cursor-pointer w-fit text-gray-600">
<FontAwesomeIcon icon={faUserCircle} className="h-8" />
<div className="flex items-center gap-1">
<div className="flex gap-3 items-center mb-5 p-3 w-fit text-gray-600 relative">
<FontAwesomeIcon icon={faCircleUser} className="h-8" />
<div
className="flex items-center gap-1 cursor-pointer"
onClick={() => setProfileDropdown(!profileDropdown)}
>
<p>{user?.name}</p>
<FontAwesomeIcon icon={faChevronDown} className="h-3" />
</div>
{profileDropdown ? (
<Dropdown
items={[
{
name: "Settings",
icon: <FontAwesomeIcon icon={faSliders} />,
},
{
name: "Logout",
icon: <FontAwesomeIcon icon={faArrowRightFromBracket} />,
onClick: () => {
signOut();
setProfileDropdown(!profileDropdown);
},
},
]}
onClickOutside={() => setProfileDropdown(!profileDropdown)}
className="absolute top-14 left-0"
/>
) : null}
</div>
<Link href="links">

View File

@ -110,7 +110,7 @@ export default async function (
})),
},
title,
isFavorites: false,
starred: false,
screenshotPath: "",
pdfPath: "",
},

View File

@ -1,14 +1,14 @@
import useCollectionSlice from "@/store/collections";
import useCollectionStore from "@/store/collections";
import { useEffect } from "react";
import { useSession } from "next-auth/react";
import useTagSlice from "@/store/tags";
import useLinkSlice from "@/store/links";
import useTagStore from "@/store/tags";
import useLinkStore from "@/store/links";
export default function () {
const { status } = useSession();
const { setCollections } = useCollectionSlice();
const { setTags } = useTagSlice();
const { setLinks } = useLinkSlice();
const { setCollections } = useCollectionStore();
const { setTags } = useTagStore();
const { setLinks } = useLinkStore();
useEffect(() => {
if (status === "authenticated") {

View File

@ -4,6 +4,7 @@
"main": "index.js",
"repository": "https://github.com/Daniel31x13/link-warden.git",
"author": "Daniel31X13 <daniel31x13@gmail.com>",
"license": "MIT",
"private": true,
"scripts": {
"dev": "next dev",

View File

@ -1,10 +1,10 @@
import LinkList from "@/components/LinkList";
import useLinkSlice from "@/store/links";
import useLinkStore from "@/store/links";
import { useRouter } from "next/router";
export default function () {
const router = useRouter();
const { links } = useLinkSlice();
const { links } = useLinkStore();
const linksByCollection = links.filter(
(e) => e.collectionId === Number(router.query.id)

View File

@ -1,10 +1,10 @@
import { useSession } from "next-auth/react";
import useCollectionSlice from "@/store/collections";
import useCollectionStore from "@/store/collections";
import CollectionCard from "@/components/CollectionCard";
export default function () {
const { collections } = useCollectionSlice();
const { collections } = useCollectionStore();
const { data: session, status } = useSession();
const user = session?.user;

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Link" RENAME COLUMN "isFavorites" TO "starred";

View File

@ -50,7 +50,7 @@ model Link {
collection Collection @relation(fields: [collectionId], references: [id])
collectionId Int
tags Tag[]
isFavorites Boolean
starred Boolean
screenshotPath String
pdfPath String
createdAt DateTime @default(now())

View File

@ -1,7 +1,7 @@
import { create } from "zustand";
import { Collection } from "@prisma/client";
type CollectionSlice = {
type CollectionStore = {
collections: Collection[];
setCollections: () => void;
addCollection: (collectionName: string) => void;
@ -9,7 +9,7 @@ type CollectionSlice = {
removeCollection: (collectionId: number) => void;
};
const useCollectionSlice = create<CollectionSlice>()((set) => ({
const useCollectionStore = create<CollectionStore>()((set) => ({
collections: [],
setCollections: async () => {
const response = await fetch("/api/routes/collections");
@ -47,4 +47,4 @@ const useCollectionSlice = create<CollectionSlice>()((set) => ({
},
}));
export default useCollectionSlice;
export default useCollectionStore;

View File

@ -1,7 +1,7 @@
import { create } from "zustand";
import { ExtendedLink, NewLink } from "@/types/global";
type LinkSlice = {
type LinkStore = {
links: ExtendedLink[];
setLinks: () => void;
addLink: (linkName: NewLink) => Promise<boolean>;
@ -9,7 +9,7 @@ type LinkSlice = {
removeLink: (linkId: number) => void;
};
const useLinkSlice = create<LinkSlice>()((set) => ({
const useLinkStore = create<LinkStore>()((set) => ({
links: [],
setLinks: async () => {
const response = await fetch("/api/routes/links");
@ -47,4 +47,4 @@ const useLinkSlice = create<LinkSlice>()((set) => ({
},
}));
export default useLinkSlice;
export default useLinkStore;

View File

@ -1,12 +1,12 @@
import { create } from "zustand";
import { Tag } from "@prisma/client";
type TagSlice = {
type TagStore = {
tags: Tag[];
setTags: () => void;
};
const useTagSlice = create<TagSlice>()((set) => ({
const useTagStore = create<TagStore>()((set) => ({
tags: [],
setTags: async () => {
const response = await fetch("/api/routes/tags");
@ -17,4 +17,4 @@ const useTagSlice = create<TagSlice>()((set) => ({
},
}));
export default useTagSlice;
export default useTagStore;

View File

@ -39,7 +39,7 @@
@keyframes slide-up-animation {
0% {
transform: translateY(10%);
transform: translateY(15%);
opacity: 0;
}
100% {