feat: added dropdown component
This commit is contained in:
parent
e5e2a615fc
commit
f80113c487
|
@ -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) => {
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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[]>([]);
|
||||
|
|
|
@ -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[]>([]);
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -110,7 +110,7 @@ export default async function (
|
|||
})),
|
||||
},
|
||||
title,
|
||||
isFavorites: false,
|
||||
starred: false,
|
||||
screenshotPath: "",
|
||||
pdfPath: "",
|
||||
},
|
||||
|
|
|
@ -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") {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Link" RENAME COLUMN "isFavorites" TO "starred";
|
|
@ -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())
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
|
||||
@keyframes slide-up-animation {
|
||||
0% {
|
||||
transform: translateY(10%);
|
||||
transform: translateY(15%);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
|
|
Ŝarĝante…
Reference in New Issue