From 41b2226bebb8ba582ffdb99074429f3d3669d59c Mon Sep 17 00:00:00 2001 From: bkellam Date: Sun, 21 Sep 2025 13:05:07 -0700 Subject: [PATCH] fileTree --- .../components/pureTreePreviewPanel.tsx | 26 +++++----- .../components/fileTreeItemComponent.tsx | 21 +++++---- .../fileTree/components/pureFileTreePanel.tsx | 47 +++++++++++-------- 3 files changed, 50 insertions(+), 44 deletions(-) diff --git a/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx b/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx index 17860185..cdb8f3ca 100644 --- a/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx +++ b/packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx @@ -1,11 +1,12 @@ 'use client'; -import { useCallback, useRef } from "react"; +import { useRef } from "react"; import { FileTreeItem } from "@/features/fileTree/actions"; import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent"; -import { useBrowseNavigation } from "../../hooks/useBrowseNavigation"; +import { getBrowsePath } from "../../hooks/useBrowseNavigation"; import { ScrollArea } from "@/components/ui/scroll-area"; import { useBrowseParams } from "../../hooks/useBrowseParams"; +import { useDomain } from "@/hooks/useDomain"; interface PureTreePreviewPanelProps { items: FileTreeItem[]; @@ -13,18 +14,9 @@ interface PureTreePreviewPanelProps { export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => { const { repoName, revisionName } = useBrowseParams(); - const { navigateToPath } = useBrowseNavigation(); const scrollAreaRef = useRef(null); - - const onNodeClicked = useCallback((node: FileTreeItem) => { - navigateToPath({ - repoName: repoName, - revisionName: revisionName, - path: node.path, - pathType: node.type === 'tree' ? 'tree' : 'blob', - }); - }, [navigateToPath, repoName, revisionName]); - + const domain = useDomain(); + return ( { isActive={false} depth={0} isCollapseChevronVisible={false} - onClick={() => onNodeClicked(item)} parentRef={scrollAreaRef} + href={getBrowsePath({ + repoName, + revisionName, + path: item.path, + pathType: item.type === 'tree' ? 'tree' : 'blob', + domain, + })} /> ))} diff --git a/packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx b/packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx index 2ad2502d..17fc1ed3 100644 --- a/packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx +++ b/packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx @@ -6,6 +6,7 @@ import clsx from "clsx"; import scrollIntoView from 'scroll-into-view-if-needed'; import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons"; import { FileTreeItemIcon } from "./fileTreeItemIcon"; +import Link from "next/link"; export const FileTreeItemComponent = ({ node, @@ -13,7 +14,9 @@ export const FileTreeItemComponent = ({ depth, isCollapsed = false, isCollapseChevronVisible = true, + href, onClick, + onNavigate, parentRef, }: { node: FileTreeItem, @@ -21,10 +24,12 @@ export const FileTreeItemComponent = ({ depth: number, isCollapsed?: boolean, isCollapseChevronVisible?: boolean, - onClick: () => void, + href: string, + onClick?: (e: React.MouseEvent) => void, + onNavigate?: (e: { preventDefault: () => void }) => void, parentRef: React.RefObject, }) => { - const ref = useRef(null); + const ref = useRef(null); useEffect(() => { if (isActive && ref.current) { @@ -51,20 +56,16 @@ export const FileTreeItemComponent = ({ }, [isActive, parentRef]); return ( -
{ - if (e.key === 'Enter') { - e.preventDefault(); - onClick(); - } - }} onClick={onClick} + onNavigate={onNavigate} >
{node.name} -
+ ) } diff --git a/packages/web/src/features/fileTree/components/pureFileTreePanel.tsx b/packages/web/src/features/fileTree/components/pureFileTreePanel.tsx index bc7347fb..88c1ab2f 100644 --- a/packages/web/src/features/fileTree/components/pureFileTreePanel.tsx +++ b/packages/web/src/features/fileTree/components/pureFileTreePanel.tsx @@ -4,9 +4,9 @@ import { FileTreeNode as RawFileTreeNode } from "../actions"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import React, { useCallback, useMemo, useState, useEffect, useRef } from "react"; import { FileTreeItemComponent } from "./fileTreeItemComponent"; -import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation"; +import { getBrowsePath } from "@/app/[domain]/browse/hooks/useBrowseNavigation"; import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams"; - +import { useDomain } from "@/hooks/useDomain"; export type FileTreeNode = Omit & { isCollapsed: boolean; @@ -41,8 +41,8 @@ interface PureFileTreePanelProps { export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps) => { const [tree, setTree] = useState(buildCollapsibleTree(_tree)); const scrollAreaRef = useRef(null); - const { navigateToPath } = useBrowseNavigation(); const { repoName, revisionName } = useBrowseParams(); + const domain = useDomain(); // @note: When `_tree` changes, it indicates that a new tree has been loaded. // In that case, we need to rebuild the collapsible tree. @@ -72,21 +72,6 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps) } }, [path, setIsCollapsed]); - const onNodeClicked = useCallback((node: FileTreeNode) => { - if (node.type === 'tree') { - setIsCollapsed(node.path, !node.isCollapsed); - } - else if (node.type === 'blob') { - navigateToPath({ - repoName: repoName, - revisionName: revisionName, - path: node.path, - pathType: 'blob', - }); - - } - }, [setIsCollapsed, navigateToPath, repoName, revisionName]); - const renderTree = useCallback((nodes: FileTreeNode, depth = 0): React.ReactNode => { return ( <> @@ -94,13 +79,35 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps) return ( onNodeClicked(node)} + // Only collapse the tree when a regular click happens. + // (i.e., not ctrl/cmd click). + onClick={(e) => { + const isMetaOrCtrlKey = e.metaKey || e.ctrlKey; + if (node.type === 'tree' && !isMetaOrCtrlKey) { + setIsCollapsed(node.path, !node.isCollapsed); + } + }} + // @note: onNavigate _won't_ be called when the user ctrl/cmd clicks on a tree node. + // So when a regular click happens, we want to prevent the navigation from happening + // and instead collapse the tree. + onNavigate={(e) => { + if (node.type === 'tree') { + e.preventDefault(); + } + }} parentRef={scrollAreaRef} /> {node.children.length > 0 && !node.isCollapsed && renderTree(node, depth + 1)} @@ -109,7 +116,7 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps) })} ); - }, [path, onNodeClicked]); + }, [path]); const renderedTree = useMemo(() => renderTree(tree), [tree, renderTree]);