From 801eb5d2cb868d0a14c056439e3898b110f4cc21 Mon Sep 17 00:00:00 2001 From: adamelmore <2363879+adamdottv@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:39:25 -0600 Subject: [PATCH] wip(app): file tree mode --- packages/app/src/components/file-tree.tsx | 85 ++++++++++++++++------- packages/app/src/pages/session.tsx | 65 +++++++++++++---- packages/ui/src/components/tabs.css | 53 ++++++++++++++ packages/ui/src/components/tabs.tsx | 2 +- 4 files changed, 165 insertions(+), 40 deletions(-) diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx index ac435095d..a48f0039f 100644 --- a/packages/app/src/components/file-tree.tsx +++ b/packages/app/src/components/file-tree.tsx @@ -9,6 +9,7 @@ import { Match, splitProps, Switch, + untrack, type ComponentProps, type ParentProps, } from "solid-js" @@ -21,10 +22,14 @@ export default function FileTree(props: { nodeClass?: string level?: number allowed?: readonly string[] + draggable?: boolean + tooltip?: boolean onFileClick?: (file: FileNode) => void }) { const file = useFile() const level = props.level ?? 0 + const draggable = () => props.draggable ?? true + const tooltip = () => props.tooltip ?? true const filter = createMemo(() => { const allowed = props.allowed @@ -45,6 +50,18 @@ export default function FileTree(props: { return { files, dirs } }) + createEffect(() => { + const current = filter() + if (!current) return + if (level !== 0) return + + for (const dir of current.dirs) { + const expanded = untrack(() => file.tree.state(dir)?.expanded) ?? false + if (expanded) continue + file.tree.expand(dir) + } + }) + createEffect(() => { void file.tree.list(props.path) }) @@ -78,8 +95,9 @@ export default function FileTree(props: { [props.nodeClass ?? ""]: !!props.nodeClass, }} style={`padding-left: ${8 + level * 12}px`} - draggable={true} + draggable={draggable()} onDragStart={(e: DragEvent) => { + if (!draggable()) return e.dataTransfer?.setData("text/plain", `file:${local.node.path}`) e.dataTransfer?.setData("text/uri-list", `file://${local.node.path}`) if (e.dataTransfer) e.dataTransfer.effectAllowed = "copy" @@ -123,41 +141,54 @@ export default function FileTree(props: { {(node) => { const expanded = () => file.tree.state(node.path)?.expanded ?? false + const Wrapper = (p: ParentProps) => { + if (!tooltip()) return p.children + return ( + + {p.children} + + ) + } + return ( - - - - (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))} - > - + + + (open ? file.tree.expand(node.path) : file.tree.collapse(node.path))} + > + + - - - - - - - + + + + + + + + + props.onFileClick?.(node)}>
- - - + + + ) }} diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 5160c5288..5ce03f403 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -1037,7 +1037,7 @@ export default function Page() { return `session-review-diff-${sum}` } - const scrollToReviewDiff = (path: string, behavior: ScrollBehavior) => { + const reviewDiffTop = (path: string) => { const root = reviewScroll() if (!root) return @@ -1050,15 +1050,25 @@ export default function Page() { const a = el.getBoundingClientRect() const b = root.getBoundingClientRect() - const top = a.top - b.top + root.scrollTop - root.scrollTo({ top, behavior }) + return a.top - b.top + root.scrollTop + } + + const scrollToReviewDiff = (path: string) => { + const root = reviewScroll() + if (!root) return false + + const top = reviewDiffTop(path) + if (top === undefined) return false + + view().setScroll("review", { x: root.scrollLeft, y: top }) + root.scrollTo({ top, behavior: "auto" }) + return true } const focusReviewDiff = (path: string) => { const current = view().review.open() ?? [] if (!current.includes(path)) view().review.setOpen([...current, path]) setPendingDiff(path) - requestAnimationFrame(() => scrollToReviewDiff(path, "smooth")) } createEffect(() => { @@ -1067,10 +1077,39 @@ export default function Page() { if (!reviewScroll()) return if (!diffsReady()) return - requestAnimationFrame(() => { - scrollToReviewDiff(pending, "smooth") - setPendingDiff(undefined) - }) + const attempt = (count: number) => { + if (pendingDiff() !== pending) return + if (count > 60) { + setPendingDiff(undefined) + return + } + + const root = reviewScroll() + if (!root) { + requestAnimationFrame(() => attempt(count + 1)) + return + } + + if (!scrollToReviewDiff(pending)) { + requestAnimationFrame(() => attempt(count + 1)) + return + } + + const top = reviewDiffTop(pending) + if (top === undefined) { + requestAnimationFrame(() => attempt(count + 1)) + return + } + + if (Math.abs(root.scrollTop - top) <= 1) { + setPendingDiff(undefined) + return + } + + requestAnimationFrame(() => attempt(count + 1)) + } + + requestAnimationFrame(() => attempt(0)) }) const activeTab = createMemo(() => { @@ -2605,12 +2644,12 @@ export default function Page() {
- - - + + + Changes - + All files @@ -2624,6 +2663,8 @@ export default function Page() { d.file)} + draggable={false} + tooltip={false} onFileClick={(node) => focusReviewDiff(node.path)} /> diff --git a/packages/ui/src/components/tabs.css b/packages/ui/src/components/tabs.css index cebbd3b4f..2f3c914e1 100644 --- a/packages/ui/src/components/tabs.css +++ b/packages/ui/src/components/tabs.css @@ -212,6 +212,59 @@ /* } */ } + &[data-variant="pill"][data-orientation="horizontal"] { + background-color: transparent; + + [data-slot="tabs-list"] { + height: auto; + padding: 6px; + gap: 4px; + border-bottom: 1px solid var(--border-weak-base); + background-color: var(--background-base); + + &::after { + display: none; + } + } + + [data-slot="tabs-trigger-wrapper"] { + height: 32px; + border: none; + border-radius: 999px; + background-color: transparent; + gap: 0; + + /* text-13-medium */ + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + font-style: normal; + font-weight: var(--font-weight-medium); + line-height: var(--line-height-large); + letter-spacing: var(--letter-spacing-normal); + + [data-slot="tabs-trigger"] { + height: 100%; + width: 100%; + padding: 0 12px; + background-color: transparent; + } + + &:hover:not(:disabled) { + background-color: var(--surface-raised-base-hover); + color: var(--text-strong); + } + + &:has([data-selected]) { + background-color: var(--surface-raised-base-active); + color: var(--text-strong); + + &:hover:not(:disabled) { + background-color: var(--surface-raised-base-active); + } + } + } + } + &[data-orientation="vertical"] { flex-direction: row; diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx index 825bfa859..ddd22ec51 100644 --- a/packages/ui/src/components/tabs.tsx +++ b/packages/ui/src/components/tabs.tsx @@ -3,7 +3,7 @@ import { Show, splitProps, type JSX } from "solid-js" import type { ComponentProps, ParentProps, Component } from "solid-js" export interface TabsProps extends ComponentProps { - variant?: "normal" | "alt" | "settings" + variant?: "normal" | "alt" | "pill" | "settings" orientation?: "horizontal" | "vertical" } export interface TabsListProps extends ComponentProps {}