Browse Source

feat(desktop): sticky diff headers

Adam 3 months ago
parent
commit
0ccb26df94

+ 28 - 13
packages/desktop/src/pages/session.tsx

@@ -44,7 +44,7 @@ import {
   useDragDropContext,
 } from "@thisbeyond/solid-dnd"
 import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
-import type { JSX } from "solid-js"
+import type { JSX, ParentProps } from "solid-js"
 import { useSync } from "@/context/sync"
 import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
 import { Markdown } from "@opencode-ai/ui"
@@ -477,7 +477,7 @@ export default function Page() {
                                   class="flex flex-col items-start self-stretch gap-8 pb-20"
                                 >
                                   {/* Title */}
-                                  <div class="flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-10 pb-1">
+                                  <div class="flex flex-col items-start gap-2 self-stretch sticky top-0 bg-background-stronger z-20 pb-1">
                                     <div class="w-full text-14-medium text-text-strong">
                                       <Show
                                         when={titled()}
@@ -524,7 +524,7 @@ export default function Page() {
                                         <For each={message.summary?.diffs ?? []}>
                                           {(diff) => (
                                             <Accordion.Item value={diff.file}>
-                                              <Accordion.Header>
+                                              <StickyAccordionHeader class="top-10 data-expanded:before:-top-10 ">
                                                 <Accordion.Trigger>
                                                   <div class="flex items-center justify-between w-full gap-5">
                                                     <div class="grow flex items-center gap-5 min-w-0">
@@ -549,8 +549,8 @@ export default function Page() {
                                                     </div>
                                                   </div>
                                                 </Accordion.Trigger>
-                                              </Accordion.Header>
-                                              <Accordion.Content class="max-h-[360px] overflow-y-auto no-scrollbar">
+                                              </StickyAccordionHeader>
+                                              <Accordion.Content class="max-h-60 overflow-y-auto no-scrollbar">
                                                 <Diff
                                                   before={{
                                                     name: diff.file!,
@@ -682,7 +682,7 @@ export default function Page() {
                       <For each={session.diffs()}>
                         {(diff) => (
                           <Accordion.Item value={diff.file} defaultOpen>
-                            <Accordion.Header>
+                            <StickyAccordionHeader>
                               <Accordion.Trigger>
                                 <div class="flex items-center justify-between w-full gap-5">
                                   <div class="grow flex items-center gap-5 min-w-0">
@@ -702,7 +702,7 @@ export default function Page() {
                                   </div>
                                 </div>
                               </Accordion.Trigger>
-                            </Accordion.Header>
+                            </StickyAccordionHeader>
                             <Accordion.Content>
                               <Diff
                                 before={{
@@ -725,19 +725,19 @@ export default function Page() {
             </div>
           </Tabs.Content>
           <Show when={local.layout.review.state() === "tab" && session.diffs().length}>
-            <Tabs.Content value="review" class="select-text mt-8">
+            <Tabs.Content value="review" class="select-text flex flex-col h-full overflow-hidden mt-8">
               <div
                 classList={{
-                  "relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0": true,
+                  "relative px-6 py-2 w-full flex flex-col gap-6 flex-1 min-h-0 overflow-hidden": true,
                 }}
               >
-                <div class="text-14-medium text-text-strong">All changes</div>
-                <div class="h-full pb-40 overflow-y-auto no-scrollbar">
+                <div class="text-14-medium text-text-strong shrink-0">All changes</div>
+                <div class="flex-1 min-h-0 pb-40 overflow-y-auto no-scrollbar">
                   <Accordion class="w-full" multiple>
                     <For each={session.diffs()}>
                       {(diff) => (
                         <Accordion.Item value={diff.file} defaultOpen>
-                          <Accordion.Header>
+                          <StickyAccordionHeader>
                             <Accordion.Trigger>
                               <div class="flex items-center justify-between w-full gap-5">
                                 <div class="grow flex items-center gap-5 min-w-0">
@@ -755,7 +755,7 @@ export default function Page() {
                                 </div>
                               </div>
                             </Accordion.Trigger>
-                          </Accordion.Header>
+                          </StickyAccordionHeader>
                           <Accordion.Content>
                             <Diff
                               diffStyle="split"
@@ -895,3 +895,18 @@ export default function Page() {
     </div>
   )
 }
+
+function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
+  return (
+    <Accordion.Header
+      classList={{
+        "sticky top-0 data-expanded:z-10": true,
+        "data-expanded:before:content-[''] data-expanded:before:z-[-10]": true,
+        "data-expanded:before:absolute data-expanded:before:inset-0 data-expanded:before:bg-background-stronger": true,
+        [props.class ?? ""]: !!props.class,
+      }}
+    >
+      {props.children}
+    </Accordion.Header>
+  )
+}

+ 23 - 15
packages/ui/src/components/accordion.css

@@ -12,9 +12,6 @@
     align-items: flex-start;
     gap: 0px;
     align-self: stretch;
-    border: 1px solid var(--border-weak-base);
-    border-bottom: none;
-    border-top: none;
     overflow: clip;
 
     [data-slot="accordion-header"] {
@@ -36,7 +33,7 @@
         user-select: none;
 
         background-color: var(--surface-base);
-        border-bottom: 1px solid var(--border-weak-base);
+        border: 1px solid var(--border-weak-base);
         overflow: clip;
         color: var(--text-strong);
         transition: background-color 0.15s ease;
@@ -62,11 +59,19 @@
     }
 
     &[data-expanded] {
-      border: 1px solid var(--border-weak-base);
-      border-bottom: 1px solid var(--border-weak-base);
       margin-top: 8px;
       margin-bottom: 8px;
-      border-radius: 8px;
+
+      [data-slot="accordion-trigger"] {
+        border-radius: 8px 8px 0 0;
+      }
+
+      [data-slot="accordion-content"] {
+        border: 1px solid var(--border-weak-base);
+        border-top: none;
+        border-bottom-left-radius: 8px;
+        border-bottom-right-radius: 8px;
+      }
 
       [data-slot="accordion-item"]:has(+ &) {
         &[data-closed] {
@@ -81,18 +86,23 @@
       }
 
       & + [data-slot="accordion-item"] {
-        border-top: 1px solid var(--border-weak-base);
-        border-top-left-radius: 8px;
-        border-top-right-radius: 8px;
         margin-top: 8px;
+
+        [data-slot="accordion-trigger"] {
+          border-top-left-radius: 8px;
+          border-top-right-radius: 8px;
+        }
+      }
+    }
+
+    &[data-closed] + &[data-closed] {
+      [data-slot="accordion-trigger"] {
+        border-top: none;
       }
     }
 
     &:first-child {
       margin-top: 0px;
-      border-top: 1px solid var(--border-weak-base);
-      border-top-left-radius: 8px;
-      border-top-right-radius: 8px;
 
       &[data-closed] {
         [data-slot="accordion-trigger"] {
@@ -104,8 +114,6 @@
 
     &:last-child {
       margin-bottom: 0px;
-      border-bottom-left-radius: 8px;
-      border-bottom-right-radius: 8px;
 
       &[data-closed] {
         [data-slot="accordion-trigger"] {

+ 2 - 2
packages/ui/src/styles/theme.css

@@ -77,7 +77,7 @@
   --background-weak: var(--smoke-light-3);
   --background-strong: var(--smoke-light-1);
   --background-stronger: #fcfcfc;
-  --surface-base: var(--smoke-light-alpha-2);
+  --surface-base: var(--smoke-light-3);
   --base: var(--smoke-light-alpha-2);
   --surface-base-hover: #0500000f;
   --surface-base-active: var(--smoke-light-alpha-3);
@@ -317,7 +317,7 @@
     --background-weak: #1b1818;
     --background-strong: #151313;
     --background-stronger: #191515;
-    --surface-base: var(--smoke-dark-alpha-2);
+    --surface-base: var(--smoke-dark-3);
     --base: var(--smoke-dark-alpha-2);
     --surface-base-hover: #e0b7b716;
     --surface-base-active: var(--smoke-dark-alpha-3);