Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion web/components/routes/link/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ const LinkList: React.FC<LinkListProps> = () => {
setEditId(null)
setActiveItemIndex(lastActiveIndexRef.current)
setKeyboardActiveIndex(lastActiveIndexRef.current)
console.log(keyboardActiveIndex)
}}
index={index}
onItemSelected={link => setEditId(link.id)}
Expand Down
5 changes: 4 additions & 1 deletion web/components/routes/link/partials/link-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ export const LinkItem = React.forwardRef<HTMLDivElement, LinkItemProps>(
aria-selected={isActive}
data-disabled={disabled}
data-active={isActive}
className="w-full overflow-visible border-b-[0.5px] border-transparent outline-none data-[active='true']:bg-[var(--link-background-muted)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]"
className={cn(
"w-full overflow-visible border-b-[0.5px] border-transparent outline-none",
"data-[active='true']:bg-[var(--link-background-muted)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]"
)}
onKeyDown={handleKeyDown}
>
<div
Expand Down
24 changes: 1 addition & 23 deletions web/components/routes/page/PageRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,13 @@
"use client"

import { useCallback, useEffect, useState } from "react"
import { PageHeader } from "./header"
import { PageList } from "./list"
import { useAtom } from "jotai"
import { commandPaletteOpenAtom } from "@/components/custom/command-palette/command-palette"

export function PageRoute() {
const [activeItemIndex, setActiveItemIndex] = useState<number | null>(null)
const [isCommandPaletteOpen] = useAtom(commandPaletteOpenAtom)
const [disableEnterKey, setDisableEnterKey] = useState(false)

const handleCommandPaletteClose = useCallback(() => {
setDisableEnterKey(true)
setTimeout(() => setDisableEnterKey(false), 100)
}, [])

useEffect(() => {
if (!isCommandPaletteOpen) {
handleCommandPaletteClose()
}
}, [isCommandPaletteOpen, handleCommandPaletteClose])

return (
<div className="flex h-full flex-auto flex-col overflow-hidden">
<PageHeader />
<PageList
activeItemIndex={activeItemIndex}
setActiveItemIndex={setActiveItemIndex}
disableEnterKey={disableEnterKey}
/>
<PageList />
</div>
)
}
122 changes: 49 additions & 73 deletions web/components/routes/page/list.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,71 @@
import React, { useMemo, useCallback, useEffect } from "react"
import * as React from "react"
import { Primitive } from "@radix-ui/react-primitive"
import { useAccount } from "@/lib/providers/jazz-provider"
import { useAtom } from "jotai"
import { commandPaletteOpenAtom } from "@/components/custom/command-palette/command-palette"
import { PageItem } from "./partials/page-item"
import { useMedia } from "@/hooks/use-media"
import { useColumnStyles } from "./hooks/use-column-styles"
import { PersonalPage, PersonalPageLists } from "@/lib/schema"
import { useRouter } from "next/navigation"
import { useActiveItemScroll } from "@/hooks/use-active-item-scroll"
import { Column } from "@/components/custom/column"
import { useKeyDown } from "@/hooks/use-key-down"

interface PageListProps {
activeItemIndex: number | null
setActiveItemIndex: React.Dispatch<React.SetStateAction<number | null>>
disableEnterKey: boolean
}
interface PageListProps {}

export const PageList: React.FC<PageListProps> = ({ activeItemIndex, setActiveItemIndex, disableEnterKey }) => {
export const PageList: React.FC<PageListProps> = () => {
const isTablet = useMedia("(max-width: 640px)")
const [isCommandPaletteOpen] = useAtom(commandPaletteOpenAtom)
const { me } = useAccount({ root: { personalPages: [] } })
const personalPages = useMemo(() => me?.root?.personalPages, [me?.root?.personalPages])
const router = useRouter()
const itemCount = personalPages?.length || 0
const [activeItemIndex, setActiveItemIndex] = React.useState<number | null>(null)
const [keyboardActiveIndex, setKeyboardActiveIndex] = React.useState<number | null>(null)
const personalPages = React.useMemo(() => me?.root?.personalPages, [me?.root?.personalPages])

const handleEnter = useCallback(
(selectedPage: PersonalPage) => {
router.push(`/pages/${selectedPage.id}`)
},
[router]
)
const next = () => Math.min((activeItemIndex ?? 0) + 1, (personalPages?.length ?? 0) - 1)

const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
if (isCommandPaletteOpen) return
const prev = () => Math.max((activeItemIndex ?? 0) - 1, 0)

if (e.key === "ArrowUp" || e.key === "ArrowDown") {
e.preventDefault()
setActiveItemIndex(prevIndex => {
if (prevIndex === null) return 0
const newIndex = e.key === "ArrowUp" ? (prevIndex - 1 + itemCount) % itemCount : (prevIndex + 1) % itemCount
return newIndex
})
} else if (e.key === "Enter" && !disableEnterKey && activeItemIndex !== null && personalPages) {
e.preventDefault()
const selectedPage = personalPages[activeItemIndex]
if (selectedPage) handleEnter?.(selectedPage)
}
},
[itemCount, isCommandPaletteOpen, activeItemIndex, setActiveItemIndex, disableEnterKey, personalPages, handleEnter]
)
const handleKeyDown = (ev: KeyboardEvent) => {
switch (ev.key) {
case "ArrowDown":
ev.preventDefault()
ev.stopPropagation()
setActiveItemIndex(next())
setKeyboardActiveIndex(next())
break
case "ArrowUp":
ev.preventDefault()
ev.stopPropagation()
setActiveItemIndex(prev())
setKeyboardActiveIndex(prev())
}
}

useEffect(() => {
window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown)
}, [handleKeyDown])
useKeyDown(() => true, handleKeyDown)

const { setElementRef } = useActiveItemScroll<HTMLAnchorElement>({ activeIndex: keyboardActiveIndex })

return (
<div className="flex h-full w-full flex-col overflow-hidden border-t">
{!isTablet && <ColumnHeader />}
<PageListItems personalPages={personalPages} activeItemIndex={activeItemIndex} />
<Primitive.div
className="divide-primary/5 flex flex-1 flex-col divide-y overflow-y-auto outline-none [scrollbar-gutter:stable]"
tabIndex={-1}
role="list"
>
{personalPages?.map(
(page, index) =>
page?.id && (
<PageItem
key={page.id}
ref={el => setElementRef(el, index)}
page={page}
isActive={index === activeItemIndex}
onPointerMove={() => {
setKeyboardActiveIndex(null)
setActiveItemIndex(index)
}}
data-keyboard-active={keyboardActiveIndex === index}
/>
)
)}
</Primitive.div>
</div>
)
}
Expand All @@ -82,32 +87,3 @@ export const ColumnHeader: React.FC = () => {
</div>
)
}

interface PageListItemsProps {
personalPages?: PersonalPageLists | null
activeItemIndex: number | null
}

const PageListItems: React.FC<PageListItemsProps> = ({ personalPages, activeItemIndex }) => {
const { setElementRef } = useActiveItemScroll<HTMLAnchorElement>({ activeIndex: activeItemIndex })

return (
<Primitive.div
className="divide-primary/5 flex flex-1 flex-col divide-y overflow-y-auto outline-none [scrollbar-gutter:stable]"
tabIndex={-1}
role="list"
>
{personalPages?.map(
(page, index) =>
page?.id && (
<PageItem
key={page.id}
ref={el => setElementRef(el, index)}
page={page}
isActive={index === activeItemIndex}
/>
)
)}
</Primitive.div>
)
}
31 changes: 24 additions & 7 deletions web/components/routes/page/partials/page-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,43 @@ import { useMedia } from "@/hooks/use-media"
import { useColumnStyles } from "../hooks/use-column-styles"
import { format } from "date-fns"
import { Column } from "@/components/custom/column"
import { useRouter } from "next/navigation"

interface PageItemProps {
interface PageItemProps extends React.HTMLAttributes<HTMLAnchorElement> {
page: PersonalPage
isActive: boolean
}

export const PageItem = React.forwardRef<HTMLAnchorElement, PageItemProps>(({ page, isActive }, ref) => {
export const PageItem = React.forwardRef<HTMLAnchorElement, PageItemProps>(({ page, isActive, ...props }, ref) => {
const isTablet = useMedia("(max-width: 640px)")
const columnStyles = useColumnStyles()
const router = useRouter()

const handleKeyDown = React.useCallback(
(ev: React.KeyboardEvent<HTMLAnchorElement>) => {
if (ev.key === "Enter") {
ev.preventDefault()
ev.stopPropagation()
router.push(`/pages/${page.id}`)
}
},
[router, page.id]
)

return (
<Link
ref={ref}
tabIndex={isActive ? 0 : -1}
className={cn("relative block cursor-default outline-none", "min-h-12 py-2 max-lg:px-4 sm:px-6", {
"bg-muted-foreground/5": isActive,
"hover:bg-muted/50": !isActive
})}
className={cn(
"relative block cursor-default outline-none",
"min-h-12 py-2 max-lg:px-4 sm:px-6",
"data-[active='true']:bg-[var(--link-background-muted)] data-[keyboard-active='true']:focus-visible:shadow-[var(--link-shadow)_0px_0px_0px_1px_inset]"
)}
href={`/pages/${page.id}`}
role="listitem"
aria-selected={isActive}
data-active={isActive}
onKeyDown={handleKeyDown}
{...props}
>
<div className="flex h-full items-center gap-4">
<Column.Wrapper style={columnStyles.title}>
Expand Down