Skip to content

Commit 41b2226

Browse files
fileTree
1 parent 8ae43fd commit 41b2226

File tree

3 files changed

+50
-44
lines changed

3 files changed

+50
-44
lines changed

packages/web/src/app/[domain]/browse/[...path]/components/pureTreePreviewPanel.tsx

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,22 @@
11
'use client';
22

3-
import { useCallback, useRef } from "react";
3+
import { useRef } from "react";
44
import { FileTreeItem } from "@/features/fileTree/actions";
55
import { FileTreeItemComponent } from "@/features/fileTree/components/fileTreeItemComponent";
6-
import { useBrowseNavigation } from "../../hooks/useBrowseNavigation";
6+
import { getBrowsePath } from "../../hooks/useBrowseNavigation";
77
import { ScrollArea } from "@/components/ui/scroll-area";
88
import { useBrowseParams } from "../../hooks/useBrowseParams";
9+
import { useDomain } from "@/hooks/useDomain";
910

1011
interface PureTreePreviewPanelProps {
1112
items: FileTreeItem[];
1213
}
1314

1415
export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => {
1516
const { repoName, revisionName } = useBrowseParams();
16-
const { navigateToPath } = useBrowseNavigation();
1717
const scrollAreaRef = useRef<HTMLDivElement>(null);
18-
19-
const onNodeClicked = useCallback((node: FileTreeItem) => {
20-
navigateToPath({
21-
repoName: repoName,
22-
revisionName: revisionName,
23-
path: node.path,
24-
pathType: node.type === 'tree' ? 'tree' : 'blob',
25-
});
26-
}, [navigateToPath, repoName, revisionName]);
27-
18+
const domain = useDomain();
19+
2820
return (
2921
<ScrollArea
3022
className="flex flex-col p-0.5"
@@ -37,8 +29,14 @@ export const PureTreePreviewPanel = ({ items }: PureTreePreviewPanelProps) => {
3729
isActive={false}
3830
depth={0}
3931
isCollapseChevronVisible={false}
40-
onClick={() => onNodeClicked(item)}
4132
parentRef={scrollAreaRef}
33+
href={getBrowsePath({
34+
repoName,
35+
revisionName,
36+
path: item.path,
37+
pathType: item.type === 'tree' ? 'tree' : 'blob',
38+
domain,
39+
})}
4240
/>
4341
))}
4442
</ScrollArea>

packages/web/src/features/fileTree/components/fileTreeItemComponent.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,30 @@ import clsx from "clsx";
66
import scrollIntoView from 'scroll-into-view-if-needed';
77
import { ChevronDownIcon, ChevronRightIcon } from "@radix-ui/react-icons";
88
import { FileTreeItemIcon } from "./fileTreeItemIcon";
9+
import Link from "next/link";
910

1011
export const FileTreeItemComponent = ({
1112
node,
1213
isActive,
1314
depth,
1415
isCollapsed = false,
1516
isCollapseChevronVisible = true,
17+
href,
1618
onClick,
19+
onNavigate,
1720
parentRef,
1821
}: {
1922
node: FileTreeItem,
2023
isActive: boolean,
2124
depth: number,
2225
isCollapsed?: boolean,
2326
isCollapseChevronVisible?: boolean,
24-
onClick: () => void,
27+
href: string,
28+
onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void,
29+
onNavigate?: (e: { preventDefault: () => void }) => void,
2530
parentRef: React.RefObject<HTMLDivElement | null>,
2631
}) => {
27-
const ref = useRef<HTMLDivElement>(null);
32+
const ref = useRef<HTMLAnchorElement>(null);
2833

2934
useEffect(() => {
3035
if (isActive && ref.current) {
@@ -51,20 +56,16 @@ export const FileTreeItemComponent = ({
5156
}, [isActive, parentRef]);
5257

5358
return (
54-
<div
59+
<Link
5560
ref={ref}
61+
href={href}
5662
className={clsx("flex flex-row gap-1 items-center hover:bg-accent hover:text-accent-foreground rounded-sm cursor-pointer p-0.5", {
5763
'bg-accent': isActive,
5864
})}
5965
style={{ paddingLeft: `${depth * 16}px` }}
6066
tabIndex={0}
61-
onKeyDown={(e) => {
62-
if (e.key === 'Enter') {
63-
e.preventDefault();
64-
onClick();
65-
}
66-
}}
6767
onClick={onClick}
68+
onNavigate={onNavigate}
6869
>
6970
<div
7071
className="flex flex-row gap-1 cursor-pointer w-4 h-4 flex-shrink-0"
@@ -79,6 +80,6 @@ export const FileTreeItemComponent = ({
7980
</div>
8081
<FileTreeItemIcon item={node} />
8182
<span className="text-sm">{node.name}</span>
82-
</div>
83+
</Link>
8384
)
8485
}

packages/web/src/features/fileTree/components/pureFileTreePanel.tsx

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import { FileTreeNode as RawFileTreeNode } from "../actions";
44
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
55
import React, { useCallback, useMemo, useState, useEffect, useRef } from "react";
66
import { FileTreeItemComponent } from "./fileTreeItemComponent";
7-
import { useBrowseNavigation } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
7+
import { getBrowsePath } from "@/app/[domain]/browse/hooks/useBrowseNavigation";
88
import { useBrowseParams } from "@/app/[domain]/browse/hooks/useBrowseParams";
9-
9+
import { useDomain } from "@/hooks/useDomain";
1010

1111
export type FileTreeNode = Omit<RawFileTreeNode, 'children'> & {
1212
isCollapsed: boolean;
@@ -41,8 +41,8 @@ interface PureFileTreePanelProps {
4141
export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps) => {
4242
const [tree, setTree] = useState<FileTreeNode>(buildCollapsibleTree(_tree));
4343
const scrollAreaRef = useRef<HTMLDivElement>(null);
44-
const { navigateToPath } = useBrowseNavigation();
4544
const { repoName, revisionName } = useBrowseParams();
45+
const domain = useDomain();
4646

4747
// @note: When `_tree` changes, it indicates that a new tree has been loaded.
4848
// In that case, we need to rebuild the collapsible tree.
@@ -72,35 +72,42 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps)
7272
}
7373
}, [path, setIsCollapsed]);
7474

75-
const onNodeClicked = useCallback((node: FileTreeNode) => {
76-
if (node.type === 'tree') {
77-
setIsCollapsed(node.path, !node.isCollapsed);
78-
}
79-
else if (node.type === 'blob') {
80-
navigateToPath({
81-
repoName: repoName,
82-
revisionName: revisionName,
83-
path: node.path,
84-
pathType: 'blob',
85-
});
86-
87-
}
88-
}, [setIsCollapsed, navigateToPath, repoName, revisionName]);
89-
9075
const renderTree = useCallback((nodes: FileTreeNode, depth = 0): React.ReactNode => {
9176
return (
9277
<>
9378
{nodes.children.map((node) => {
9479
return (
9580
<React.Fragment key={node.path}>
9681
<FileTreeItemComponent
82+
href={getBrowsePath({
83+
repoName,
84+
revisionName,
85+
path: node.path,
86+
pathType: node.type === 'tree' ? 'tree' : 'blob',
87+
domain,
88+
})}
9789
key={node.path}
9890
node={node}
9991
isActive={node.path === path}
10092
depth={depth}
10193
isCollapsed={node.isCollapsed}
10294
isCollapseChevronVisible={node.type === 'tree'}
103-
onClick={() => onNodeClicked(node)}
95+
// Only collapse the tree when a regular click happens.
96+
// (i.e., not ctrl/cmd click).
97+
onClick={(e) => {
98+
const isMetaOrCtrlKey = e.metaKey || e.ctrlKey;
99+
if (node.type === 'tree' && !isMetaOrCtrlKey) {
100+
setIsCollapsed(node.path, !node.isCollapsed);
101+
}
102+
}}
103+
// @note: onNavigate _won't_ be called when the user ctrl/cmd clicks on a tree node.
104+
// So when a regular click happens, we want to prevent the navigation from happening
105+
// and instead collapse the tree.
106+
onNavigate={(e) => {
107+
if (node.type === 'tree') {
108+
e.preventDefault();
109+
}
110+
}}
104111
parentRef={scrollAreaRef}
105112
/>
106113
{node.children.length > 0 && !node.isCollapsed && renderTree(node, depth + 1)}
@@ -109,7 +116,7 @@ export const PureFileTreePanel = ({ tree: _tree, path }: PureFileTreePanelProps)
109116
})}
110117
</>
111118
);
112-
}, [path, onNodeClicked]);
119+
}, [path]);
113120

114121
const renderedTree = useMemo(() => renderTree(tree), [tree, renderTree]);
115122

0 commit comments

Comments
 (0)