From bcfbdf3e49076d5a02b72d7d39f5b1012b6bbfce Mon Sep 17 00:00:00 2001 From: Yee Jia Wei Date: Sat, 16 Dec 2023 11:25:39 +0800 Subject: [PATCH] link compact list view --- components/LinkCard.tsx | 393 +++++------------- components/LinkViews/CompactGridView.tsx | 16 + components/LinkViews/DefaultGridView.tsx | 16 + components/LinkViews/LinkCardCompact.tsx | 102 +++++ .../LinkViews/LinkComponents/LinkActions.tsx | 165 ++++++++ .../LinkComponents/LinkCollection.tsx | 34 ++ .../LinkViews/LinkComponents/LinkDate.tsx | 25 ++ .../LinkViews/LinkComponents/LinkIcon.tsx | 45 ++ components/LinkViews/LinkRow.tsx | 172 ++++++++ components/LinkViews/ListView.tsx | 24 ++ components/SortDropdown.tsx | 11 +- components/ViewDropdown.tsx | 62 +++ pages/links/index.tsx | 24 +- types/global.ts | 6 + 14 files changed, 803 insertions(+), 292 deletions(-) create mode 100644 components/LinkViews/CompactGridView.tsx create mode 100644 components/LinkViews/DefaultGridView.tsx create mode 100644 components/LinkViews/LinkCardCompact.tsx create mode 100644 components/LinkViews/LinkComponents/LinkActions.tsx create mode 100644 components/LinkViews/LinkComponents/LinkCollection.tsx create mode 100644 components/LinkViews/LinkComponents/LinkDate.tsx create mode 100644 components/LinkViews/LinkComponents/LinkIcon.tsx create mode 100644 components/LinkViews/LinkRow.tsx create mode 100644 components/LinkViews/ListView.tsx create mode 100644 components/ViewDropdown.tsx diff --git a/components/LinkCard.tsx b/components/LinkCard.tsx index fc23253..d530813 100644 --- a/components/LinkCard.tsx +++ b/components/LinkCard.tsx @@ -1,307 +1,138 @@ import { - CollectionIncludingMembersAndLinkCount, - LinkIncludingShortenedCollectionAndTags, + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, } from "@/types/global"; -import { - faFolder, - faEllipsis, - faLink, -} from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useEffect, useState } from "react"; +import {faLink} from "@fortawesome/free-solid-svg-icons"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {useEffect, useState} from "react"; import Image from "next/image"; import useLinkStore from "@/store/links"; import useCollectionStore from "@/store/collections"; -import useAccountStore from "@/store/account"; import { - faCalendarDays, - faFileImage, - faFilePdf, + faFileImage, + faFilePdf, } from "@fortawesome/free-regular-svg-icons"; -import usePermissions from "@/hooks/usePermissions"; -import { toast } from "react-hot-toast"; import isValidUrl from "@/lib/shared/isValidUrl"; -import Link from "next/link"; import unescapeString from "@/lib/client/unescapeString"; -import { useRouter } from "next/router"; -import EditLinkModal from "./ModalContent/EditLinkModal"; -import DeleteLinkModal from "./ModalContent/DeleteLinkModal"; -import PreservedFormatsModal from "./ModalContent/PreservedFormatsModal"; +import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; +import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; +import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; type Props = { - link: LinkIncludingShortenedCollectionAndTags; - count: number; - className?: string; + link: LinkIncludingShortenedCollectionAndTags; + count: number; + className?: string; }; -export default function LinkCard({ link, count, className }: Props) { - const router = useRouter(); +export default function LinkCard({link, count, className}: Props) { + const {links} = useLinkStore(); - const permissions = usePermissions(link.collection.id as number); + const {collections} = useCollectionStore(); - const { collections } = useCollectionStore(); + const [collection, setCollection] = + useState( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); - const { links } = useLinkStore(); + useEffect(() => { + setCollection( + collections.find( + (e) => e.id === link.collection.id + ) as CollectionIncludingMembersAndLinkCount + ); + }, [collections, links]); - const { account } = useAccountStore(); + let shortendURL; - let shortendURL; - - try { - shortendURL = new URL(link.url || "").host.toLowerCase(); - } catch (error) { - console.log(error); - } - - const [collection, setCollection] = - useState( - collections.find( - (e) => e.id === link.collection.id - ) as CollectionIncludingMembersAndLinkCount - ); - - useEffect(() => { - setCollection( - collections.find( - (e) => e.id === link.collection.id - ) as CollectionIncludingMembersAndLinkCount - ); - }, [collections, links]); - - const { removeLink, updateLink } = useLinkStore(); - - const pinLink = async () => { - const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0]; - - const load = toast.loading("Applying..."); - - const response = await updateLink({ - ...link, - pinnedBy: isAlreadyPinned ? undefined : [{ id: account.id }], - }); - - toast.dismiss(load); - - response.ok && - toast.success(`Link ${isAlreadyPinned ? "Unpinned!" : "Pinned!"}`); - }; - - const deleteLink = async () => { - const load = toast.loading("Deleting..."); - - const response = await removeLink(link.id as number); - - toast.dismiss(load); - - response.ok && toast.success(`Link Deleted.`); - }; - - const url = - isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; - - const formattedDate = new Date(link.createdAt as string).toLocaleString( - "en-US", - { - year: "numeric", - month: "short", - day: "numeric", + try { + shortendURL = new URL(link.url || "").host.toLowerCase(); + } catch (error) { + console.log(error); } - ); - const [editLinkModal, setEditLinkModal] = useState(false); - const [deleteLinkModal, setDeleteLinkModal] = useState(false); - const [preservedFormatsModal, setPreservedFormatsModal] = useState(false); - - return ( -
- {permissions === true || - permissions?.canUpdate || - permissions?.canDelete ? ( -
-
- -
-
    - {permissions === true ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - pinLink(); - }} - > - {link?.pinnedBy && link.pinnedBy[0] - ? "Unpin" - : "Pin to Dashboard"} -
    -
  • - ) : undefined} - {permissions === true || permissions?.canUpdate ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - setEditLinkModal(true); - }} - > - Edit -
    -
  • - ) : undefined} - {permissions === true ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - setPreservedFormatsModal(true); - // updateArchive(); - }} - > - Preserved Formats -
    -
  • - ) : undefined} - {permissions === true || permissions?.canDelete ? ( -
  • -
    { - (document?.activeElement as HTMLElement)?.blur(); - e.shiftKey ? deleteLink() : setDeleteLinkModal(true); - }} - > - Delete -
    -
  • - ) : undefined} -
-
- ) : undefined} - -
link.url && window.open(link.url || "", "_blank")} - className="flex flex-col justify-between cursor-pointer h-full w-full gap-1 p-3" - > - {link.url && url ? ( - { - const target = e.target as HTMLElement; - target.style.display = "none"; - }} - /> - ) : link.type === "pdf" ? ( - - ) : link.type === "image" ? ( - - ) : undefined} - -
-

{count + 1}

-

- {unescapeString(link.name || link.description) || link.url} -

-
- - {link.url ? ( -
- -

{shortendURL}

-
- ) : ( -
{link.type}
- )} + const url = + isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; + return (
{ - e.stopPropagation(); - router.push(`/collections/${link.collection.id}`); - }} - className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100" + className={`h-fit hover:bg-base-300 hover:border-base-300 border border-solid border-neutral-content bg-base-200 shadow-md hover:shadow-none duration-200 rounded-2xl relative ${ + className || "" + }`} > - -

{collection?.name}

-
+
link.url && window.open(link.url || "", "_blank")} + className="flex flex-col justify-between cursor-pointer h-full w-full gap-1 p-3" + > + {link.url && url ? ( + { + const target = e.target as HTMLElement; + target.style.display = "none"; + }} + /> + ) : link.type === "pdf" ? ( + + ) : link.type === "image" ? ( + + ) : undefined} -
- -

{formattedDate}

-
- {/* {link.tags[0] ? ( -
-
- {link.tags.map((e, i) => ( - { - e.stopPropagation(); - }} - className="btn btn-xs btn-ghost truncate max-w-[19rem]" - > - #{e.name} - - ))} +
+

{count + 1}

+

+ {unescapeString(link.name || link.description) || link.url} +

-
-
- ) : ( -

No Tags

- )} */} -
- {editLinkModal ? ( - setEditLinkModal(false)} - activeLink={link} - /> - ) : undefined} - {deleteLinkModal ? ( - setDeleteLinkModal(false)} - activeLink={link} - /> - ) : undefined} - {preservedFormatsModal ? ( - setPreservedFormatsModal(false)} - activeLink={link} - /> - ) : undefined} -
- ); + + {link.url ? ( +
+ +

{shortendURL}

+
+ ) : ( +
{link.type}
+ )} + + + + + {/* {link.tags[0] ? ( +
+
+{link.tags.map((e, i) => ( + { + e.stopPropagation(); + }} + className="btn btn-xs btn-ghost truncate max-w-[19rem]" +> + #{e.name} + +))} +
+
+
+) : ( +

No Tags

+)} */} +
+ + +
+ ); } diff --git a/components/LinkViews/CompactGridView.tsx b/components/LinkViews/CompactGridView.tsx new file mode 100644 index 0000000..55cec7b --- /dev/null +++ b/components/LinkViews/CompactGridView.tsx @@ -0,0 +1,16 @@ +import LinkCardCompact from "@/components/LinkViews/LinkCardCompact"; +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; + +export default function CompactGridView({ + links, +}: { + links: LinkIncludingShortenedCollectionAndTags[]; +}) { + return ( +
+ {links.map((e, i) => { + return ; + })} +
+ ); +} diff --git a/components/LinkViews/DefaultGridView.tsx b/components/LinkViews/DefaultGridView.tsx new file mode 100644 index 0000000..7dbbc79 --- /dev/null +++ b/components/LinkViews/DefaultGridView.tsx @@ -0,0 +1,16 @@ +import LinkCard from "@/components/LinkCard"; +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; + +export default function DefaultGridView({ + links, +}: { + links: LinkIncludingShortenedCollectionAndTags[]; +}) { + return ( +
+ {links.map((e, i) => { + return ; + })} +
+ ); +} diff --git a/components/LinkViews/LinkCardCompact.tsx b/components/LinkViews/LinkCardCompact.tsx new file mode 100644 index 0000000..841e19a --- /dev/null +++ b/components/LinkViews/LinkCardCompact.tsx @@ -0,0 +1,102 @@ +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import { useEffect, useState } from "react"; +import useLinkStore from "@/store/links"; +import useCollectionStore from "@/store/collections"; +import Link from "next/link"; +import unescapeString from "@/lib/client/unescapeString"; +import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; +import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; +import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; +import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon"; + +type Props = { + link: LinkIncludingShortenedCollectionAndTags; + count: number; + className?: string; +}; + +export default function LinkCardCompact({ link, count, className }: Props) { + const { collections } = useCollectionStore(); + + const { links } = useLinkStore(); + + let shortendURL; + + try { + shortendURL = new URL(link.url || "").host.toLowerCase(); + } catch (error) { + console.log(error); + } + + const [collection, setCollection] = + useState( + collections.find( + (e) => e.id === link.collection.id, + ) as CollectionIncludingMembersAndLinkCount, + ); + + useEffect(() => { + setCollection( + collections.find( + (e) => e.id === link.collection.id, + ) as CollectionIncludingMembersAndLinkCount, + ); + }, [collections, links]); + + return ( +
+
+
+
+ +
+ + +
+
+

{count + 1}

+

+ {unescapeString(link.name || link.description) || link.url} +

+
+ + {link.url ? ( +
{ + e.preventDefault(); + window.open(link.url || "", "_blank"); + }} + className="flex items-center gap-1 max-w-full w-fit text-xs text-neutral hover:opacity-60 duration-100" + > +

{shortendURL}

+
+ ) : ( +
+ {link.type} +
+ )} +
+ +
+ + · + +
+ +
+
+ + +
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkActions.tsx b/components/LinkViews/LinkComponents/LinkActions.tsx new file mode 100644 index 0000000..ef4e6c7 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkActions.tsx @@ -0,0 +1,165 @@ +import { useState } from "react"; +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import usePermissions from "@/hooks/usePermissions"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faEllipsis } from "@fortawesome/free-solid-svg-icons"; +import EditLinkModal from "@/components/ModalContent/EditLinkModal"; +import DeleteLinkModal from "@/components/ModalContent/DeleteLinkModal"; +import PreservedFormatsModal from "@/components/ModalContent/PreservedFormatsModal"; +import useLinkStore from "@/store/links"; +import { toast } from "react-hot-toast"; +import useAccountStore from "@/store/account"; + +export default function LinkActions({ + link, + collection, +}: { + link: LinkIncludingShortenedCollectionAndTags; + collection: CollectionIncludingMembersAndLinkCount; +}) { + const permissions = usePermissions(link.collection.id as number); + + const [editLinkModal, setEditLinkModal] = useState(false); + const [deleteLinkModal, setDeleteLinkModal] = useState(false); + const [preservedFormatsModal, setPreservedFormatsModal] = useState(false); + const [expandedLink, setExpandedLink] = useState(false); + + const { account } = useAccountStore(); + + const { removeLink, updateLink } = useLinkStore(); + + const pinLink = async () => { + const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0]; + + const load = toast.loading("Applying..."); + + const response = await updateLink({ + ...link, + pinnedBy: isAlreadyPinned ? undefined : [{ id: account.id }], + }); + + toast.dismiss(load); + + response.ok && + toast.success(`Link ${isAlreadyPinned ? "Unpinned!" : "Pinned!"}`); + }; + + const deleteLink = async () => { + const load = toast.loading("Deleting..."); + + const response = await removeLink(link.id as number); + + toast.dismiss(load); + + response.ok && toast.success(`Link Deleted.`); + }; + + return ( +
+ {permissions === true || + permissions?.canUpdate || + permissions?.canDelete ? ( +
+
+ +
+
    + {permissions === true ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + pinLink(); + }} + > + {link?.pinnedBy && link.pinnedBy[0] + ? "Unpin" + : "Pin to Dashboard"} +
    +
  • + ) : undefined} + {permissions === true || permissions?.canUpdate ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + setEditLinkModal(true); + }} + > + Edit +
    +
  • + ) : undefined} + {permissions === true ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + setPreservedFormatsModal(true); + // updateArchive(); + }} + > + Preserved Formats +
    +
  • + ) : undefined} + {permissions === true || permissions?.canDelete ? ( +
  • +
    { + (document?.activeElement as HTMLElement)?.blur(); + e.shiftKey ? deleteLink() : setDeleteLinkModal(true); + }} + > + Delete +
    +
  • + ) : undefined} +
+
+ ) : undefined} + + {editLinkModal ? ( + setEditLinkModal(false)} + activeLink={link} + /> + ) : undefined} + {deleteLinkModal ? ( + setDeleteLinkModal(false)} + activeLink={link} + /> + ) : undefined} + {preservedFormatsModal ? ( + setPreservedFormatsModal(false)} + activeLink={link} + /> + ) : undefined} + {/* {expandedLink ? ( + setExpandedLink(false)} link={link} /> + ) : undefined} */} +
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkCollection.tsx b/components/LinkViews/LinkComponents/LinkCollection.tsx new file mode 100644 index 0000000..606352a --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkCollection.tsx @@ -0,0 +1,34 @@ +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFolder } from "@fortawesome/free-solid-svg-icons"; +import { useRouter } from "next/router"; + +export default function LinkCollection({ + link, + collection, +}: { + link: LinkIncludingShortenedCollectionAndTags; + collection: CollectionIncludingMembersAndLinkCount; +}) { + const router = useRouter(); + + return ( +
{ + e.preventDefault(); + router.push(`/collections/${link.collection.id}`); + }} + className="flex items-center gap-1 max-w-full w-fit hover:opacity-70 duration-100" + > + +

{collection?.name}

+
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkDate.tsx b/components/LinkViews/LinkComponents/LinkDate.tsx new file mode 100644 index 0000000..282ab70 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkDate.tsx @@ -0,0 +1,25 @@ +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCalendarDays } from "@fortawesome/free-regular-svg-icons"; + +export default function LinkDate({ + link, +}: { + link: LinkIncludingShortenedCollectionAndTags; +}) { + const formattedDate = new Date(link.createdAt as string).toLocaleString( + "en-US", + { + year: "numeric", + month: "short", + day: "numeric", + }, + ); + + return ( +
+ +

{formattedDate}

+
+ ); +} diff --git a/components/LinkViews/LinkComponents/LinkIcon.tsx b/components/LinkViews/LinkComponents/LinkIcon.tsx new file mode 100644 index 0000000..0e19d53 --- /dev/null +++ b/components/LinkViews/LinkComponents/LinkIcon.tsx @@ -0,0 +1,45 @@ +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faFileImage, faFilePdf } from "@fortawesome/free-regular-svg-icons"; +import Image from "next/image"; +import isValidUrl from "@/lib/shared/isValidUrl"; + +export default function LinkIcon({ + link, +}: { + link: LinkIncludingShortenedCollectionAndTags; +}) { + const url = + isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; + + const iconClasses: string = "w-20 bg-primary/20 text-primary shadow rounded-md p-2 select-none z-10"; + + return ( +
+ {link.url && url ? ( + { + const target = e.target as HTMLElement; + target.style.display = "none"; + }} + /> + ) : link.type === "pdf" ? ( + + ) : link.type === "image" ? ( + + ) : undefined} +
+ ); +} diff --git a/components/LinkViews/LinkRow.tsx b/components/LinkViews/LinkRow.tsx new file mode 100644 index 0000000..9dcbe22 --- /dev/null +++ b/components/LinkViews/LinkRow.tsx @@ -0,0 +1,172 @@ +import { + CollectionIncludingMembersAndLinkCount, + LinkIncludingShortenedCollectionAndTags, +} from "@/types/global"; +import { + faFolder, + faEllipsis, + faLink, +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useState } from "react"; +import Image from "next/image"; +import useLinkStore from "@/store/links"; +import useCollectionStore from "@/store/collections"; +import useAccountStore from "@/store/account"; +import { + faCalendarDays, + faFileImage, + faFilePdf, +} from "@fortawesome/free-regular-svg-icons"; +import usePermissions from "@/hooks/usePermissions"; +import { toast } from "react-hot-toast"; +import isValidUrl from "@/lib/shared/isValidUrl"; +import Link from "next/link"; +import unescapeString from "@/lib/client/unescapeString"; +import { useRouter } from "next/router"; +import EditLinkModal from "../ModalContent/EditLinkModal"; +import DeleteLinkModal from "../ModalContent/DeleteLinkModal"; +import ExpandedLink from "../ModalContent/ExpandedLink"; +import PreservedFormatsModal from "../ModalContent/PreservedFormatsModal"; +import LinkActions from "@/components/LinkViews/LinkComponents/LinkActions"; +import LinkDate from "@/components/LinkViews/LinkComponents/LinkDate"; +import LinkCollection from "@/components/LinkViews/LinkComponents/LinkCollection"; +import LinkIcon from "@/components/LinkViews/LinkComponents/LinkIcon"; + +type Props = { + link: LinkIncludingShortenedCollectionAndTags; + count: number; + className?: string; +}; + +export default function LinkRow({ link, count, className }: Props) { + const router = useRouter(); + + const permissions = usePermissions(link.collection.id as number); + + const { collections } = useCollectionStore(); + + const { links } = useLinkStore(); + + const { account } = useAccountStore(); + + let shortendURL; + + try { + shortendURL = new URL(link.url || "").host.toLowerCase(); + } catch (error) { + console.log(error); + } + + const [collection, setCollection] = + useState( + collections.find( + (e) => e.id === link.collection.id, + ) as CollectionIncludingMembersAndLinkCount, + ); + + useEffect(() => { + setCollection( + collections.find( + (e) => e.id === link.collection.id, + ) as CollectionIncludingMembersAndLinkCount, + ); + }, [collections, links]); + + const { removeLink, updateLink } = useLinkStore(); + + const pinLink = async () => { + const isAlreadyPinned = link?.pinnedBy && link.pinnedBy[0]; + + const load = toast.loading("Applying..."); + + const response = await updateLink({ + ...link, + pinnedBy: isAlreadyPinned ? undefined : [{ id: account.id }], + }); + + toast.dismiss(load); + + response.ok && + toast.success(`Link ${isAlreadyPinned ? "Unpinned!" : "Pinned!"}`); + }; + + const deleteLink = async () => { + const load = toast.loading("Deleting..."); + + const response = await removeLink(link.id as number); + + toast.dismiss(load); + + response.ok && toast.success(`Link Deleted.`); + }; + + const url = + isValidUrl(link.url || "") && link.url ? new URL(link.url) : undefined; + + const formattedDate = new Date(link.createdAt as string).toLocaleString( + "en-US", + { + year: "numeric", + month: "short", + day: "numeric", + }, + ); + + const iconClasses: string = + "w-20 bg-white shadow p-1 bottom-3 right-3 select-none z-10"; + + return ( +
+
+
+ + + + +
+ +

+ {unescapeString(link.name || link.description) || link.url} +

+ + +
+ + · + {link.url ? ( +
{ + e.preventDefault(); + window.open(link.url || "", "_blank"); + }} + className="flex items-center hover:opacity-60 cursor-pointer duration-100" + > +

{shortendURL}

+
+ ) : ( +
+ {link.type} +
+ )} + · + +
+
+
+
+ + +
+ ); +} diff --git a/components/LinkViews/ListView.tsx b/components/LinkViews/ListView.tsx new file mode 100644 index 0000000..06a9703 --- /dev/null +++ b/components/LinkViews/ListView.tsx @@ -0,0 +1,24 @@ +import LinkRow from "@/components/LinkViews/LinkRow"; +import { LinkIncludingShortenedCollectionAndTags } from "@/types/global"; + +export default function ListView({ + links, +}: { + links: LinkIncludingShortenedCollectionAndTags[]; +}) { + return ( +
+
+ {links.map((e, i) => { + return ; + })} + {links.map((e, i) => { + return ; + })} + {links.map((e, i) => { + return ; + })} +
+
+ ); +} diff --git a/components/SortDropdown.tsx b/components/SortDropdown.tsx index 7aebd56..e125e6b 100644 --- a/components/SortDropdown.tsx +++ b/components/SortDropdown.tsx @@ -16,11 +16,14 @@ export default function SortDropdown({ sortBy, setSort }: Props) { role="button" className="btn btn-sm btn-square btn-ghost" > - + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + > + +
  • diff --git a/components/ViewDropdown.tsx b/components/ViewDropdown.tsx new file mode 100644 index 0000000..df2ffcd --- /dev/null +++ b/components/ViewDropdown.tsx @@ -0,0 +1,62 @@ +import React, { Dispatch, SetStateAction } from "react"; +import { ViewMode } from "@/types/global"; + +type Props = { + viewMode: ViewMode; + setViewMode: Dispatch>; +}; + +export default function ViewDropdown({ viewMode, setViewMode }: Props) { + let viewModes: Array<{ mode: ViewMode; name: string }> = [ + { mode: ViewMode.Default, name: "Default" }, + { mode: ViewMode.Compact, name: "Grid" }, + { mode: ViewMode.List, name: "List" }, + ]; + + return ( +
    + + + +
    + ); +} diff --git a/pages/links/index.tsx b/pages/links/index.tsx index 6364e40..c7f31ba 100644 --- a/pages/links/index.tsx +++ b/pages/links/index.tsx @@ -4,18 +4,31 @@ import SortDropdown from "@/components/SortDropdown"; import useLinks from "@/hooks/useLinks"; import MainLayout from "@/layouts/MainLayout"; import useLinkStore from "@/store/links"; -import { Sort } from "@/types/global"; +import { Sort, ViewMode } from "@/types/global"; import { faLink } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useState } from "react"; +import ViewDropdown from "@/components/ViewDropdown"; +import DefaultGridView from "@/components/LinkViews/DefaultGridView"; +import CompactGridView from "@/components/LinkViews/CompactGridView"; +import ListView from "@/components/LinkViews/ListView"; export default function Links() { const { links } = useLinkStore(); + const [viewMode, setViewMode] = useState(ViewMode.Default); const [sortBy, setSortBy] = useState(Sort.DateNewestFirst); useLinks({ sort: sortBy }); + const components = { + [ViewMode.Default]: DefaultGridView, + [ViewMode.Compact]: CompactGridView, + [ViewMode.List]: ListView, + }; + + const Component = components[viewMode]; + return (
    @@ -32,16 +45,13 @@ export default function Links() {
    -
    +
    +
    {links[0] ? ( -
    - {links.map((e, i) => { - return ; - })} -
    + ) : ( )} diff --git a/types/global.ts b/types/global.ts index c1d1893..e1331f3 100644 --- a/types/global.ts +++ b/types/global.ts @@ -57,6 +57,12 @@ export interface PublicCollectionIncludingLinks extends Collection { links: LinksIncludingTags[]; } +export enum ViewMode { + Default, + Compact, + List, +} + export enum Sort { DateNewestFirst, DateOldestFirst,