switch to monorepo structure
This commit is contained in:
306
packages/web/src/app/bookmarks.tsx
Normal file
306
packages/web/src/app/bookmarks.tsx
Normal file
@@ -0,0 +1,306 @@
|
||||
import type { LinkBookmark } from "@markone/core/bookmark"
|
||||
import { createFileRoute } from "@tanstack/react-router"
|
||||
import clsx from "clsx"
|
||||
import { useEffect } from "react"
|
||||
import { create } from "zustand"
|
||||
import { Button } from "~/components/button"
|
||||
|
||||
const testBookmarks: LinkBookmark[] = [
|
||||
{
|
||||
kind: "link",
|
||||
id: "1",
|
||||
title: "Running a Docker container as a non-root user",
|
||||
url: "https://test.website.com/article/123",
|
||||
},
|
||||
{
|
||||
kind: "link",
|
||||
id: "2",
|
||||
title: "Running a Docker container as a non-root user",
|
||||
url: "https://test.website.com/article/123",
|
||||
},
|
||||
]
|
||||
|
||||
const LAYOUT_MODE = {
|
||||
popup: "popup",
|
||||
sideBySide: "side-by-side",
|
||||
} as const
|
||||
type LayoutMode = (typeof LAYOUT_MODE)[keyof typeof LAYOUT_MODE]
|
||||
|
||||
interface BookmarkPageState {
|
||||
bookmarks: LinkBookmark[]
|
||||
selectedBookmarkIndex: number
|
||||
isBookmarkItemExpanded: boolean
|
||||
isBookmarkPreviewOpened: boolean
|
||||
layoutMode: LayoutMode
|
||||
|
||||
setBookmarkItemExpanded: (isExpanded: boolean) => void
|
||||
setBookmarkPreviewOpened: (isOpened: boolean) => void
|
||||
setLayoutMode: (mode: LayoutMode) => void
|
||||
selectBookmarkAt: (index: number) => void
|
||||
}
|
||||
|
||||
const useBookmarkPageStore = create<BookmarkPageState>()((set, get) => ({
|
||||
bookmarks: testBookmarks,
|
||||
selectedBookmarkIndex: 0,
|
||||
isBookmarkItemExpanded: false,
|
||||
isBookmarkPreviewOpened: false,
|
||||
layoutMode: LAYOUT_MODE.popup,
|
||||
|
||||
setBookmarkItemExpanded(isExpanded: boolean) {
|
||||
set({ isBookmarkItemExpanded: isExpanded })
|
||||
},
|
||||
|
||||
setBookmarkPreviewOpened(isOpened: boolean) {
|
||||
set({ isBookmarkPreviewOpened: isOpened })
|
||||
},
|
||||
|
||||
setLayoutMode(mode: LayoutMode) {
|
||||
set({ layoutMode: mode })
|
||||
},
|
||||
|
||||
selectBookmarkAt(index: number) {
|
||||
const bookmarks = get().bookmarks
|
||||
if (index >= 0 && index < bookmarks.length) {
|
||||
set({ selectedBookmarkIndex: index })
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
function Page() {
|
||||
const setLayoutMode = useBookmarkPageStore((state) => state.setLayoutMode)
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
const state = useBookmarkPageStore.getState()
|
||||
|
||||
switch (event.key) {
|
||||
case "ArrowDown":
|
||||
state.selectBookmarkAt(state.selectedBookmarkIndex + 1)
|
||||
break
|
||||
case "ArrowUp":
|
||||
state.selectBookmarkAt(state.selectedBookmarkIndex - 1)
|
||||
break
|
||||
case "ArrowLeft":
|
||||
state.setBookmarkItemExpanded(false)
|
||||
break
|
||||
case "ArrowRight":
|
||||
state.setBookmarkItemExpanded(true)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
function mediaQueryListener(this: MediaQueryList) {
|
||||
if (this.matches) {
|
||||
setLayoutMode(LAYOUT_MODE.sideBySide)
|
||||
} else {
|
||||
setLayoutMode(LAYOUT_MODE.popup)
|
||||
}
|
||||
}
|
||||
|
||||
const q = window.matchMedia("(width >= 64rem)")
|
||||
q.addEventListener("change", mediaQueryListener)
|
||||
|
||||
mediaQueryListener.call(q)
|
||||
|
||||
return () => {
|
||||
q.removeEventListener("change", mediaQueryListener)
|
||||
}
|
||||
}, [setLayoutMode])
|
||||
|
||||
return (
|
||||
<div className="flex justify-center h-full">
|
||||
<Main>
|
||||
<div className="flex flex-col md:flex-row justify-center py-16 lg:py-32 ">
|
||||
<header className="mb-4 md:mb-0 md:mr-16 text-start">
|
||||
<h1 className="font-bold text-start">
|
||||
<span className="invisible md:hidden">> </span>
|
||||
YOUR BOOKMARKS
|
||||
</h1>
|
||||
</header>
|
||||
<div className="flex flex-col container max-w-2xl -mt-2">
|
||||
{testBookmarks.map((bookmark, i) => (
|
||||
<BookmarkListItem key={bookmark.id} index={i} bookmark={bookmark} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<BookmarkPreview />
|
||||
</Main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Main({ children }: React.PropsWithChildren) {
|
||||
const isPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
|
||||
const layoutMode = useBookmarkPageStore((state) => state.layoutMode)
|
||||
|
||||
return (
|
||||
<main
|
||||
className={clsx(
|
||||
"px-4 lg:px-8 2xl:px-0 grid flex justify-center relative w-full",
|
||||
isPreviewOpened && layoutMode === LAYOUT_MODE.sideBySide ? "grid-cols-2" : "grid-cols-1",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
function BookmarkPreview() {
|
||||
const isVisible = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
|
||||
const layoutMode = useBookmarkPageStore((state) => state.layoutMode)
|
||||
|
||||
if (!isVisible) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
"h-screen flex justify-center items-center border-l border-stone-700 dark:border-stone-300 flex dark:bg-stone-900",
|
||||
{
|
||||
"absolute inset-0 border-l-0": layoutMode === LAYOUT_MODE.popup,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<p>Content here</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function BookmarkListItem({ bookmark, index }: { bookmark: LinkBookmark; index: number }) {
|
||||
const url = new URL(bookmark.url)
|
||||
const selectedBookmark = useBookmarkPageStore((state) => state.bookmarks[state.selectedBookmarkIndex])
|
||||
const isSelected = selectedBookmark.id === bookmark.id
|
||||
const isBookmarkItemExpanded = useBookmarkPageStore((state) => state.isBookmarkItemExpanded)
|
||||
const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded)
|
||||
const selectBookmarkAt = useBookmarkPageStore((state) => state.selectBookmarkAt)
|
||||
const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened)
|
||||
|
||||
function expandOrOpenPreview() {
|
||||
setBookmarkItemExpanded(true)
|
||||
if (useBookmarkPageStore.getState().layoutMode === LAYOUT_MODE.sideBySide) {
|
||||
console.log(useBookmarkPageStore.getState().layoutMode)
|
||||
setBookmarkPreviewOpened(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx("group flex flex-row justify-start py-2", {
|
||||
"bg-teal-600 text-stone-100": isBookmarkItemExpanded && isSelected,
|
||||
"text-teal-600": isSelected && !isBookmarkItemExpanded,
|
||||
})}
|
||||
onMouseEnter={() => {
|
||||
if (!isBookmarkItemExpanded) {
|
||||
selectBookmarkAt(index)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!isSelected}
|
||||
className={clsx("select-none flex items-start font-bold hover:bg-teal-600 hover:text-stone-100", {
|
||||
invisible: !isSelected,
|
||||
})}
|
||||
onClick={() => {
|
||||
setBookmarkItemExpanded(!isBookmarkItemExpanded)
|
||||
setBookmarkPreviewOpened(false)
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Options for this bookmark</span>
|
||||
<span> </span>
|
||||
<span className={isBookmarkItemExpanded ? "rotate-90" : ""}>></span>
|
||||
<span> </span>
|
||||
</button>
|
||||
<div className="flex flex-col w-full">
|
||||
<button type="button" className="text-start font-bold" onClick={expandOrOpenPreview}>
|
||||
{bookmark.title}
|
||||
</button>
|
||||
<p className="opacity-80 text-sm">{url.host}</p>
|
||||
{isBookmarkItemExpanded && isSelected ? (
|
||||
<div className="flex flex-col space-y-1 md:flex-row md:space-y-0 md:space-x-2 items-end justify-between pt-2">
|
||||
<p className="text-sm">#dev #devops #devops #devops #devops #devops #devops</p>
|
||||
<div className="flex space-x-2">
|
||||
<OpenBookmarkPreviewButton />
|
||||
<Button className="text-sm">
|
||||
<span className="underline">E</span>dit
|
||||
</Button>
|
||||
<Button className="text-sm">
|
||||
<span className="underline">D</span>elete
|
||||
</Button>
|
||||
<span className="-ml-2"> </span>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function OpenBookmarkPreviewButton() {
|
||||
const isBookmarkPreviewOpened = useBookmarkPageStore((state) => state.isBookmarkPreviewOpened)
|
||||
const setBookmarkPreviewOpened = useBookmarkPageStore((state) => state.setBookmarkPreviewOpened)
|
||||
const setBookmarkItemExpanded = useBookmarkPageStore((state) => state.setBookmarkItemExpanded)
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (isBookmarkPreviewOpened && event.key === "c") {
|
||||
closePreview()
|
||||
} else if (!isBookmarkPreviewOpened && event.key === "o") {
|
||||
openPreview()
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("keydown", onKeyDown)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown)
|
||||
}
|
||||
}, [isBookmarkPreviewOpened])
|
||||
|
||||
function closePreview() {
|
||||
setBookmarkPreviewOpened(false)
|
||||
setBookmarkItemExpanded(false)
|
||||
}
|
||||
|
||||
function openPreview() {
|
||||
setBookmarkPreviewOpened(true)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="text-sm"
|
||||
onClick={() => {
|
||||
if (isBookmarkPreviewOpened) {
|
||||
closePreview()
|
||||
} else {
|
||||
openPreview()
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isBookmarkPreviewOpened ? (
|
||||
<>
|
||||
<span className="underline">C</span>lose
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span className="underline">O</span>pen
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export const Route = createFileRoute("/bookmarks")({
|
||||
component: Page,
|
||||
})
|
Reference in New Issue
Block a user