From ce409a55d89978448da901c676dc004ae9d3b6b9 Mon Sep 17 00:00:00 2001 From: Cole Bemis Date: Thu, 4 Apr 2024 00:54:56 -0700 Subject: [PATCH] Unship panel layout --- src/components/calendar.tsx | 77 ++++++++------- src/components/command-menu.tsx | 14 +-- src/components/markdown.tsx | 30 ++---- src/components/nav-bar.tsx | 21 +--- src/components/note-card.tsx | 10 +- src/components/note-list.tsx | 8 +- src/components/panel.tsx | 169 ++++++-------------------------- src/components/root-layout.tsx | 7 +- src/components/tag-link.tsx | 1 - src/pages/note.tsx | 8 +- src/pages/notes.tsx | 8 +- src/pages/tag.tsx | 8 +- src/pages/tags.tsx | 8 +- src/panels/daily.tsx | 3 +- src/panels/note.tsx | 2 +- src/panels/notes.tsx | 2 +- src/panels/tag.tsx | 8 +- src/panels/tags.tsx | 4 +- src/panels/weekly.tsx | 2 +- src/styles/cmdk.css | 2 +- 20 files changed, 109 insertions(+), 283 deletions(-) diff --git a/src/components/calendar.tsx b/src/components/calendar.tsx index 6085b87b..e0ec5a18 100644 --- a/src/components/calendar.tsx +++ b/src/components/calendar.tsx @@ -36,41 +36,46 @@ export function Calendar({ activeNoteId }: { activeNoteId: string }) { ) return ( -
-
- - {MONTH_NAMES[startOfWeek.getMonth()]} {startOfWeek.getFullYear()} - -
- setStartOfWeek(previousMonday(startOfWeek))} - > - - - setStartOfWeek(nextMonday(startOfWeek))} - > - - +
+
+
+ + {MONTH_NAMES[startOfWeek.getMonth()]} {startOfWeek.getFullYear()} + +
+ setStartOfWeek(previousMonday(startOfWeek))} + > + + + setStartOfWeek(nextMonday(startOfWeek))} + > + + +
-
-
- - -
- {daysOfWeek.map((date) => ( - + + +
- ))} - + {daysOfWeek.map((date) => ( + + ))} + +
) @@ -193,10 +198,10 @@ function CalendarItem({ hasNotes && !isActive && "after:bg-border", )} > -
- {shortName} +
+ {shortName} {/* Show full name when there's enough space */} - {name} + {name} { - if (openInPanel && openPanel) { - // If we're in a panels context, navigate by opening a panel - openPanel(url, panels.length - 1) - } else { - // Otherwise, navigate using the router - routerNavigate(url) - } - + routerNavigate(url) setIsOpen(false) setQuery("") }, - [openPanel, panels, routerNavigate], + [routerNavigate], ) // Toggle the menu with `command + k` diff --git a/src/components/markdown.tsx b/src/components/markdown.tsx index 39ed9a48..0d6ac967 100644 --- a/src/components/markdown.tsx +++ b/src/components/markdown.tsx @@ -100,9 +100,7 @@ export const Markdown = React.memo(

{parsedTemplate.data.name}

- - Template - + Template
{/* TODO: Display more input metadata (type, description, etc.) */} @@ -417,7 +415,7 @@ function FrontmatterValue({ entry: [key, value] }: { entry: [string, unknown] }) return ( {dateString ? ( - + {formatDate(dateString, { excludeDayOfWeek: true })} ) : ( @@ -427,7 +425,7 @@ function FrontmatterValue({ entry: [key, value] }: { entry: [string, unknown] }) )} {" · "} - + {nextAge ? `${withSuffix(nextAge)} birthday` : "Birthday"} {" "} is {formatDateDistance(toDateStringUtc(nextBirthday)).toLowerCase()}{" "} @@ -509,11 +507,7 @@ function Anchor(props: React.ComponentPropsWithoutRef<"a">) { // Open uploads in a panel if (props.href?.startsWith(UPLOADS_DIR)) { - return ( - - {props.children} - - ) + return {props.children} } // Render relative links with React Router @@ -555,11 +549,7 @@ function Image(props: React.ComponentPropsWithoutRef<"img">) { // Render local files with FilePreview if (props.src?.startsWith("/")) { return ( - + ) @@ -690,7 +680,7 @@ function NoteLink({ id, text }: NoteLinkProps) { return ( - + {isFirst && note && online ? ( )}
- - Source - + Source
) @@ -758,7 +746,7 @@ function DateLink({ date, text, className }: DateLinkProps) { return ( - + {text || formatDate(date)} @@ -786,7 +774,7 @@ function WeekLink({ week, text, className }: WeekLinkProps) { return ( - + {text || formatWeek(week)} diff --git a/src/components/nav-bar.tsx b/src/components/nav-bar.tsx index ec6f912d..af740c96 100644 --- a/src/components/nav-bar.tsx +++ b/src/components/nav-bar.tsx @@ -1,7 +1,7 @@ import { TooltipContentProps } from "@radix-ui/react-tooltip" import { useAtomValue, useSetAtom } from "jotai" import { selectAtom } from "jotai/utils" -import React from "react" +import { useHotkeys } from "react-hotkeys-hook" import { NavLinkProps, NavLink as RouterNavLink, useMatch, useResolvedPath } from "react-router-dom" import { useEvent, useNetworkState } from "react-use" import { globalStateMachineAtom } from "../global-state" @@ -22,10 +22,8 @@ import { TagFillIcon24, TagIcon24, } from "./icons" -import { usePanelActions, usePanels } from "./panels" import { SyncStatusIcon, useSyncStatusText } from "./sync-status" import { Tooltip } from "./tooltip" -import { useHotkeys } from "react-hotkeys-hook" export function NavBar({ position }: { position: "left" | "bottom" }) { const navigate = useNavigateWithCache() @@ -178,22 +176,7 @@ function NewNoteButton({ tooltipSide: TooltipContentProps["side"] className?: string }) { - const panels = usePanels() - const { openPanel } = usePanelActions() - const routerNavigate = useNavigateWithCache() - - const navigate = React.useCallback( - (url: string) => { - if (openPanel) { - // If we're in a panels context, navigate by opening a panel - openPanel(url, panels.length - 1) - } else { - // Otherwise, navigate using the router - routerNavigate(url) - } - }, - [openPanel, panels, routerNavigate], - ) + const navigate = useNavigateWithCache() useHotkeys("mod+i", (event) => { navigate(`/${Date.now()}`) diff --git a/src/components/note-card.tsx b/src/components/note-card.tsx index 8632a4a5..73759fdf 100644 --- a/src/components/note-card.tsx +++ b/src/components/note-card.tsx @@ -296,11 +296,7 @@ const _NoteCard = React.memo(function NoteCard({
{note ? ( - + {id}.md ) : ( @@ -447,7 +443,9 @@ const _NoteCard = React.memo(function NoteCard({
{mode === "read" ? ( editorValue ? ( - {editorValue} + handleSave({ id, content: value })}> + {editorValue} + ) : ( Empty note ) diff --git a/src/components/note-list.tsx b/src/components/note-list.tsx index 3837a306..b1975368 100644 --- a/src/components/note-list.tsx +++ b/src/components/note-list.tsx @@ -3,6 +3,7 @@ import { flushSync } from "react-dom" import { useInView } from "react-intersection-observer" import { z } from "zod" import { useDebouncedValue } from "../hooks/debounced-value" +import { useNavigateWithCache } from "../hooks/navigate-with-cache" import { parseQuery, useSearchNotes } from "../hooks/search" import { useSearchParam } from "../hooks/search-param" import { templateSchema } from "../schema" @@ -17,7 +18,6 @@ import { Link } from "./link" import { LinkHighlightProvider } from "./link-highlight-provider" import { NoteCard } from "./note-card" import { NoteFavicon } from "./note-favicon" -import { usePanel, usePanelActions } from "./panels" import { PillButton } from "./pill-button" import { SearchInput } from "./search-input" @@ -30,8 +30,7 @@ type NoteListProps = { } export function NoteList({ baseQuery = "" }: NoteListProps) { const searchNotes = useSearchNotes() - const { openPanel } = usePanelActions() - const panel = usePanel() + const navigate = useNavigateWithCache() const [query, setQuery] = useSearchParam("q", { validate: z.string().catch("").parse, @@ -150,7 +149,7 @@ export function NoteList({ baseQuery = "" }: NoteListProps) { onClick={() => { const resultsCount = noteResults.length const randomIndex = Math.floor(Math.random() * resultsCount) - openPanel?.(noteResults[randomIndex].id, panel?.index) + navigate(`/${noteResults[randomIndex].id}`) }} />
@@ -256,7 +255,6 @@ export function NoteList({ baseQuery = "" }: NoteListProps) { // Used for focus management data-note-id={note.id} to={`/${note.id}`} - target="_blank" className="focus-ring flex gap-3 rounded-md p-3 leading-4 hover:bg-bg-secondary coarse:p-4" > diff --git a/src/components/panel.tsx b/src/components/panel.tsx index 332388fb..e53c10dc 100644 --- a/src/components/panel.tsx +++ b/src/components/panel.tsx @@ -1,15 +1,11 @@ -import clsx from "clsx" import React from "react" -import { DraggableCore } from "react-draggable" -import { z } from "zod" -import { useSearchParam } from "../hooks/search-param" import { DropdownMenu } from "./dropdown-menu" import { IconButton } from "./icon-button" import { CloseIcon16, MoreIcon16 } from "./icons" -import { Panels, usePanel } from "./panels" import { cx } from "../utils/cx" type PanelProps = { + // TODO: Remove `id` prop id?: string className?: string title: string @@ -20,11 +16,7 @@ type PanelProps = { onClose?: () => void } -const MIN_WIDTH = 560 -const MAX_WIDTH = Number.MAX_SAFE_INTEGER - export function Panel({ - id, className, title, description, @@ -33,142 +25,41 @@ export function Panel({ children, onClose, }: PanelProps) { - const [widthParam, setWidthParam] = useSearchParam("w", { - validate: z.string().catch(String(MIN_WIDTH)).parse, - }) - - const [width, setWidth] = React.useState(parseInt(widthParam)) - const panelRef = React.useRef(null) - const panel = usePanel() - const [activeNoteId, setActiveNoteId] = React.useState("") - return ( - - {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */} +
{ - // Close with Command + X if no text is selected - if (event.metaKey && event.key === "x" && !window.getSelection()?.toString()) { - onClose?.() - event.preventDefault() - } - }} - onFocus={() => { - setActiveNoteId( - document.activeElement?.closest("[data-note-id]")?.dataset.noteId ?? "", - ) - }} + className={ + "flex h-12 shrink-0 items-center justify-between gap-2 border-b border-border-secondary bg-bg-inset p-2 pl-4 coarse:h-14" + } > -
- { - const panelRect = panelRef.current?.getBoundingClientRect() - - if (panelRect) { - setWidth(panelRect.width) - setWidthParam(String(panelRect.width)) - } - }} - /> +
+
{icon}
+
+

{title}

+ {description ? ( + {description} + ) : null} +
- {/* translateZ(0) fixes a bug in Safari where the scrollbar would appear underneath the sticky header */} -
-
-
-
{icon}
-
-

{title}

- {description ? ( - {description} - ) : null} -
-
-
- {actions ? ( - - - - - - - {actions} - - ) : null} - {onClose ? ( - onClose()}> - +
+ {actions ? ( + + + + - ) : null} -
-
-
{children}
+ + {actions} + + ) : null} + {onClose ? ( + onClose()}> + + + ) : null}
- - ) -} - -function ResizeHandle({ - value, - onChange, - onStop, -}: { - value: number - min: number - max: number - step?: number - onChange: (value: number) => void - onStop?: () => void -}) { - const draggableRef = React.useRef(null) - const [isDragging, setIsDragging] = React.useState(false) - const isResizing = isDragging - return ( - setIsDragging(true)} - onStop={() => { - setIsDragging(false) - onStop?.() - }} - onDrag={(event, { deltaX }) => { - onChange(value + deltaX) - event.preventDefault() - }} - > -
- +
{children}
+
) } diff --git a/src/components/root-layout.tsx b/src/components/root-layout.tsx index 788f0565..cbe43ffc 100644 --- a/src/components/root-layout.tsx +++ b/src/components/root-layout.tsx @@ -1,14 +1,12 @@ import { useAtomValue, useSetAtom } from "jotai" import { selectAtom } from "jotai/utils" import React from "react" -import { useLocation } from "react-router-dom" import { useEvent, useNetworkState } from "react-use" import { globalStateMachineAtom } from "../global-state" import { useThemeColorProvider } from "../hooks/theme-color" import { CommandMenu } from "./command-menu" import { ErrorIcon16 } from "./icons" import { InsertTemplateDialog } from "./insert-template" -import { Panels } from "./panels" import { SyntaxHighlighter } from "./syntax-highlighter" const errorAtom = selectAtom(globalStateMachineAtom, (state) => state.context.error) @@ -18,7 +16,6 @@ export function RootLayout({ children }: { children: React.ReactNode }) { const error = useAtomValue(errorAtom) const send = useSetAtom(globalStateMachineAtom) const { online } = useNetworkState() - const location = useLocation() // Sync when the app becomes visible again useEvent("visibilitychange", () => { @@ -33,7 +30,7 @@ export function RootLayout({ children }: { children: React.ReactNode }) { }) return ( - + <>
@@ -48,7 +45,7 @@ export function RootLayout({ children }: { children: React.ReactNode }) { ) : null}
-
+ ) } diff --git a/src/components/tag-link.tsx b/src/components/tag-link.tsx index 7436c98f..c213da0e 100644 --- a/src/components/tag-link.tsx +++ b/src/components/tag-link.tsx @@ -16,7 +16,6 @@ export function TagLink({ name, className }: TagLinkProps) { {i > 0 && /} - - - - ) + return } diff --git a/src/pages/notes.tsx b/src/pages/notes.tsx index 6e8ba4ea..53de8e29 100644 --- a/src/pages/notes.tsx +++ b/src/pages/notes.tsx @@ -1,11 +1,5 @@ -import { Panels } from "../components/panels" import { NotesPanel } from "../panels/notes" export function NotesPage() { - return ( - - - - - ) + return } diff --git a/src/pages/tag.tsx b/src/pages/tag.tsx index 4ad4f7a1..28be6dfd 100644 --- a/src/pages/tag.tsx +++ b/src/pages/tag.tsx @@ -1,14 +1,8 @@ import { useParams } from "react-router-dom" -import { Panels } from "../components/panels" import { TagPanel } from "../panels/tag" export function TagPage() { const params = useParams() - return ( - - - - - ) + return } diff --git a/src/pages/tags.tsx b/src/pages/tags.tsx index d7235d37..4797f2e8 100644 --- a/src/pages/tags.tsx +++ b/src/pages/tags.tsx @@ -1,11 +1,5 @@ -import { Panels } from "../components/panels" import { TagsPanel } from "../panels/tags" export function TagsPage() { - return ( - - - - - ) + return } diff --git a/src/panels/daily.tsx b/src/panels/daily.tsx index 6aa07df4..7b3afe91 100644 --- a/src/panels/daily.tsx +++ b/src/panels/daily.tsx @@ -27,6 +27,7 @@ export function DailyPanel({ id, params = {}, onClose }: PanelProps) { return ( } @@ -34,7 +35,7 @@ export function DailyPanel({ id, params = {}, onClose }: PanelProps) { >
-
+
{backlinks.length > 0 ? (
diff --git a/src/panels/note.tsx b/src/panels/note.tsx index 046510ae..c0c0730b 100644 --- a/src/panels/note.tsx +++ b/src/panels/note.tsx @@ -29,7 +29,7 @@ export function NotePanel({ id, params = {}, onClose }: PanelProps) { icon={!note || note.frontmatter.template ? : } onClose={onClose} > -
+
{note?.backlinks?.length ? (
diff --git a/src/panels/notes.tsx b/src/panels/notes.tsx index 40c0a60c..d7ec36c3 100644 --- a/src/panels/notes.tsx +++ b/src/panels/notes.tsx @@ -23,7 +23,7 @@ export function NotesPanel({ id, onClose }: PanelProps) { return ( } onClose={onClose}> -
+
{isEmpty ? (
diff --git a/src/panels/tag.tsx b/src/panels/tag.tsx index bb457e41..677cbff4 100644 --- a/src/panels/tag.tsx +++ b/src/panels/tag.tsx @@ -51,7 +51,7 @@ export function TagPanel({ id, params = {}, onClose }: PanelProps) { onClose={onClose} > -
+
{isRenaming ? (
@@ -61,7 +61,7 @@ export function TagPanel({ id, params = {}, onClose }: PanelProps) {
{ event.preventDefault() const formData = new FormData(event.currentTarget) @@ -78,7 +78,7 @@ export function TagPanel({ id, params = {}, onClose }: PanelProps) { -
+
-
+
diff --git a/src/panels/tags.tsx b/src/panels/tags.tsx index e3578353..ec65e4b6 100644 --- a/src/panels/tags.tsx +++ b/src/panels/tags.tsx @@ -32,7 +32,7 @@ export function TagsPanel({ id, onClose }: PanelProps) { return ( } onClose={onClose}> -
+
- + {node.name} {node.count} diff --git a/src/panels/weekly.tsx b/src/panels/weekly.tsx index 098104ce..40a94c24 100644 --- a/src/panels/weekly.tsx +++ b/src/panels/weekly.tsx @@ -43,7 +43,7 @@ export function WeeklyPanel({ id, params = {}, onClose }: PanelProps) { >
-
+
diff --git a/src/styles/cmdk.css b/src/styles/cmdk.css index fbac3e61..c110c70f 100644 --- a/src/styles/cmdk.css +++ b/src/styles/cmdk.css @@ -3,7 +3,7 @@ } [cmdk-dialog] { - @apply fixed left-1/2 top-2 z-20 w-[calc(100vw_-_1rem)] max-w-xl -translate-x-1/2 sm:top-[10vh]; + @apply fixed left-1/2 top-2 z-20 w-[calc(100vw_-_1rem)] max-w-2xl -translate-x-1/2 sm:top-[10vh]; } [cmdk-input] {