From cffe65ec5f67843dbcb6956709346a4fb7d84317 Mon Sep 17 00:00:00 2001 From: Aslam Date: Tue, 24 Sep 2024 18:55:40 +0700 Subject: [PATCH] fix(page): improve keybind (#180) --- web/components/routes/link/list.tsx | 1 - .../routes/link/partials/link-item.tsx | 5 +- web/components/routes/page/PageRoute.tsx | 24 +--- web/components/routes/page/list.tsx | 122 +++++++----------- .../routes/page/partials/page-item.tsx | 31 ++++- 5 files changed, 78 insertions(+), 105 deletions(-) diff --git a/web/components/routes/link/list.tsx b/web/components/routes/link/list.tsx index 9710c83b..d76dfaf2 100644 --- a/web/components/routes/link/list.tsx +++ b/web/components/routes/link/list.tsx @@ -258,7 +258,6 @@ const LinkList: React.FC = () => { setEditId(null) setActiveItemIndex(lastActiveIndexRef.current) setKeyboardActiveIndex(lastActiveIndexRef.current) - console.log(keyboardActiveIndex) }} index={index} onItemSelected={link => setEditId(link.id)} diff --git a/web/components/routes/link/partials/link-item.tsx b/web/components/routes/link/partials/link-item.tsx index 8340aac4..9eab3b6f 100644 --- a/web/components/routes/link/partials/link-item.tsx +++ b/web/components/routes/link/partials/link-item.tsx @@ -87,7 +87,10 @@ export const LinkItem = React.forwardRef( 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} >
(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 (
- +
) } diff --git a/web/components/routes/page/list.tsx b/web/components/routes/page/list.tsx index cd085729..de35cded 100644 --- a/web/components/routes/page/list.tsx +++ b/web/components/routes/page/list.tsx @@ -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> - disableEnterKey: boolean -} +interface PageListProps {} -export const PageList: React.FC = ({ activeItemIndex, setActiveItemIndex, disableEnterKey }) => { +export const PageList: React.FC = () => { 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(null) + const [keyboardActiveIndex, setKeyboardActiveIndex] = React.useState(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({ activeIndex: keyboardActiveIndex }) return (
{!isTablet && } - + + {personalPages?.map( + (page, index) => + page?.id && ( + setElementRef(el, index)} + page={page} + isActive={index === activeItemIndex} + onPointerMove={() => { + setKeyboardActiveIndex(null) + setActiveItemIndex(index) + }} + data-keyboard-active={keyboardActiveIndex === index} + /> + ) + )} +
) } @@ -82,32 +87,3 @@ export const ColumnHeader: React.FC = () => {
) } - -interface PageListItemsProps { - personalPages?: PersonalPageLists | null - activeItemIndex: number | null -} - -const PageListItems: React.FC = ({ personalPages, activeItemIndex }) => { - const { setElementRef } = useActiveItemScroll({ activeIndex: activeItemIndex }) - - return ( - - {personalPages?.map( - (page, index) => - page?.id && ( - setElementRef(el, index)} - page={page} - isActive={index === activeItemIndex} - /> - ) - )} - - ) -} diff --git a/web/components/routes/page/partials/page-item.tsx b/web/components/routes/page/partials/page-item.tsx index 5a0d1af1..ebf5df18 100644 --- a/web/components/routes/page/partials/page-item.tsx +++ b/web/components/routes/page/partials/page-item.tsx @@ -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 { page: PersonalPage isActive: boolean } -export const PageItem = React.forwardRef(({ page, isActive }, ref) => { +export const PageItem = React.forwardRef(({ page, isActive, ...props }, ref) => { const isTablet = useMedia("(max-width: 640px)") const columnStyles = useColumnStyles() + const router = useRouter() + + const handleKeyDown = React.useCallback( + (ev: React.KeyboardEvent) => { + if (ev.key === "Enter") { + ev.preventDefault() + ev.stopPropagation() + router.push(`/pages/${page.id}`) + } + }, + [router, page.id] + ) return (