diff options
| author | Shoubhit Dash <[email protected]> | 2026-04-03 19:02:53 +0530 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-04-03 08:32:53 -0500 |
| commit | 9d57f21f9fedddc753908d2727f0422408eaa878 (patch) | |
| tree | 84b76f250c98c8b180f10148274df9b9aa0d43cf /packages | |
| parent | 3deee3a02b26bc9fd73f9ed86f0f3d49cd168203 (diff) | |
| download | opencode-9d57f21f9fedddc753908d2727f0422408eaa878.tar.gz opencode-9d57f21f9fedddc753908d2727f0422408eaa878.zip | |
feat(ui): redesign modified files section in session turn (#20348)
Co-authored-by: David Hill <[email protected]>
Diffstat (limited to 'packages')
| -rw-r--r-- | packages/ui/src/components/session-turn.css | 64 | ||||
| -rw-r--r-- | packages/ui/src/components/session-turn.tsx | 209 | ||||
| -rw-r--r-- | packages/ui/src/i18n/en.ts | 4 |
3 files changed, 134 insertions, 143 deletions
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index c4a8726ac..53e2b58f4 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -92,33 +92,15 @@ min-width: 0; } - [data-slot="session-turn-diffs"] - > [data-component="collapsible"] - > [data-slot="collapsible-trigger"][aria-expanded="true"] { - position: sticky; - top: var(--sticky-accordion-top, 0px); - z-index: 20; - height: 40px; - padding-bottom: 8px; - background-color: var(--background-stronger); - } - - [data-component="session-turn-diffs-trigger"] { - width: 100%; + [data-slot="session-turn-diffs-header"] { display: flex; - align-items: center; - justify-content: flex-start; - gap: 8px; - padding: 0; - } - - [data-slot="session-turn-diffs-title"] { - display: inline-flex; align-items: baseline; gap: 8px; + padding-bottom: 12px; } [data-slot="session-turn-diffs-label"] { + font-variant-numeric: tabular-nums; color: var(--text-strong); font-family: var(--font-family-sans); font-size: var(--font-size-base); @@ -126,28 +108,38 @@ line-height: var(--line-height-large); } - [data-slot="session-turn-diffs-count"] { - color: var(--text-base); + [data-slot="session-turn-diffs-toggle"] { + color: var(--text-interactive-base); font-family: var(--font-family-sans); - font-variant-numeric: tabular-nums; font-size: var(--font-size-base); font-weight: var(--font-weight-regular); - line-height: var(--line-height-x-large); + line-height: var(--line-height-large); + cursor: pointer; + opacity: 0; + transition: opacity 0.15s ease; + margin-left: 4px; } - [data-slot="session-turn-diffs-meta"] { - display: inline-flex; - align-items: center; - gap: 8px; - flex-shrink: 0; + [data-component="session-turn-diffs-group"]:hover [data-slot="session-turn-diffs-toggle"] { + opacity: 1; + } - [data-slot="collapsible-arrow"] { - margin-left: -6px; - transform: translateY(2px); - } + [data-component="session-turn-diffs-group"][data-show-all] [data-slot="session-turn-diffs-toggle"] { + opacity: 1; + } + + [data-slot="session-turn-diffs-more"] { + color: var(--text-weak); + font-family: var(--font-family-sans); + font-size: var(--font-size-small); + line-height: var(--line-height-large); + margin-top: 12px; + padding: 0 0 6px; + cursor: pointer; + transition: color 0.15s ease; - [data-component="diff-changes"][data-variant="bars"] { - transform: translateY(1px); + &:hover { + color: var(--text-link-base); } } diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index fe029485a..c20e5fb1c 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -12,7 +12,6 @@ import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions import { Card } from "./card" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" -import { Collapsible } from "./collapsible" import { DiffChanges } from "./diff-changes" import { Icon } from "./icon" import { TextShimmer } from "./text-shimmer" @@ -241,23 +240,20 @@ export function SessionTurn( }, []) .reverse() }) + const MAX_FILES = 10 const edited = createMemo(() => diffs().length) const [state, setState] = createStore({ - open: false, + showAll: false, expanded: [] as string[], }) - const open = () => state.open + const showAll = () => state.showAll const expanded = () => state.expanded - - createEffect( - on( - open, - (value, prev) => { - if (!value && prev) setState("expanded", []) - }, - { defer: true }, - ), - ) + const overflow = createMemo(() => Math.max(0, edited() - MAX_FILES)) + const visible = createMemo(() => (showAll() ? diffs() : diffs().slice(0, MAX_FILES))) + const toggleAll = () => { + autoScroll.pause() + setState("showAll", !showAll()) + } const assistantMessages = createMemo( () => { @@ -425,101 +421,100 @@ export function SessionTurn( </Show> <SessionRetry status={status()} show={active()} /> <Show when={edited() > 0 && !working()}> - <div data-slot="session-turn-diffs"> - <Collapsible open={open()} onOpenChange={(value) => setState("open", value)} variant="ghost"> - <Collapsible.Trigger> - <div data-component="session-turn-diffs-trigger"> - <div data-slot="session-turn-diffs-title"> - <span data-slot="session-turn-diffs-label">{i18n.t("ui.sessionReview.change.modified")}</span> - <span data-slot="session-turn-diffs-count"> - {edited()} {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")} - </span> - <div data-slot="session-turn-diffs-meta"> - <DiffChanges changes={diffs()} variant="bars" /> - <Collapsible.Arrow /> - </div> - </div> - </div> - </Collapsible.Trigger> - <Collapsible.Content> - <Show when={open()}> - <div data-component="session-turn-diffs-content"> - <Accordion - multiple - style={{ "--sticky-accordion-offset": "40px" }} - value={expanded()} - onChange={(value) => - setState("expanded", Array.isArray(value) ? value : value ? [value] : []) - } - > - <For each={diffs()}> - {(diff) => { - const active = createMemo(() => expanded().includes(diff.file)) - const [visible, setVisible] = createSignal(false) - - createEffect( - on( - active, - (value) => { - if (!value) { - setVisible(false) - return - } - - requestAnimationFrame(() => { - if (!active()) return - setVisible(true) - }) - }, - { defer: true }, - ), - ) - - return ( - <Accordion.Item value={diff.file}> - <StickyAccordionHeader> - <Accordion.Trigger> - <div data-slot="session-turn-diff-trigger"> - <span data-slot="session-turn-diff-path"> - <Show when={diff.file.includes("/")}> - <span data-slot="session-turn-diff-directory"> - {`\u202A${getDirectory(diff.file)}\u202C`} - </span> - </Show> - <span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span> - </span> - <div data-slot="session-turn-diff-meta"> - <span data-slot="session-turn-diff-changes"> - <DiffChanges changes={diff} /> - </span> - <span data-slot="session-turn-diff-chevron"> - <Icon name="chevron-down" size="small" /> - </span> - </div> - </div> - </Accordion.Trigger> - </StickyAccordionHeader> - <Accordion.Content> - <Show when={visible()}> - <div data-slot="session-turn-diff-view" data-scrollable> - <Dynamic - component={fileComponent} - mode="diff" - before={{ name: diff.file, contents: diff.before }} - after={{ name: diff.file, contents: diff.after }} - /> - </div> + <div + data-slot="session-turn-diffs" + data-component="session-turn-diffs-group" + data-show-all={showAll() || undefined} + > + <div data-slot="session-turn-diffs-header"> + <span data-slot="session-turn-diffs-label"> + {edited()} {i18n.t("ui.sessionTurn.diffs.changed")}{" "} + {i18n.t(edited() === 1 ? "ui.common.file.one" : "ui.common.file.other")} + </span> + <DiffChanges changes={diffs()} /> + <Show when={overflow() > 0}> + <span data-slot="session-turn-diffs-toggle" onClick={toggleAll}> + {showAll() ? i18n.t("ui.sessionTurn.diffs.showLess") : i18n.t("ui.sessionTurn.diffs.showAll")} + </span> + </Show> + </div> + <div data-component="session-turn-diffs-content"> + <Accordion + multiple + style={{ "--sticky-accordion-offset": "40px" }} + value={expanded()} + onChange={(value) => setState("expanded", Array.isArray(value) ? value : value ? [value] : [])} + > + <For each={visible()}> + {(diff) => { + const active = createMemo(() => expanded().includes(diff.file)) + const [shown, setShown] = createSignal(false) + + createEffect( + on( + active, + (value) => { + if (!value) { + setShown(false) + return + } + + requestAnimationFrame(() => { + if (!active()) return + setShown(true) + }) + }, + { defer: true }, + ), + ) + + return ( + <Accordion.Item value={diff.file}> + <StickyAccordionHeader> + <Accordion.Trigger> + <div data-slot="session-turn-diff-trigger"> + <span data-slot="session-turn-diff-path"> + <Show when={diff.file.includes("/")}> + <span data-slot="session-turn-diff-directory"> + {`\u202A${getDirectory(diff.file)}\u202C`} + </span> </Show> - </Accordion.Content> - </Accordion.Item> - ) - }} - </For> - </Accordion> - </div> - </Show> - </Collapsible.Content> - </Collapsible> + <span data-slot="session-turn-diff-filename">{getFilename(diff.file)}</span> + </span> + <div data-slot="session-turn-diff-meta"> + <span data-slot="session-turn-diff-changes"> + <DiffChanges changes={diff} /> + </span> + <span data-slot="session-turn-diff-chevron"> + <Icon name="chevron-down" size="small" /> + </span> + </div> + </div> + </Accordion.Trigger> + </StickyAccordionHeader> + <Accordion.Content> + <Show when={shown()}> + <div data-slot="session-turn-diff-view" data-scrollable> + <Dynamic + component={fileComponent} + mode="diff" + before={{ name: diff.file, contents: diff.before }} + after={{ name: diff.file, contents: diff.after }} + /> + </div> + </Show> + </Accordion.Content> + </Accordion.Item> + ) + }} + </For> + </Accordion> + <Show when={!showAll() && overflow() > 0}> + <div data-slot="session-turn-diffs-more" onClick={toggleAll}> + {i18n.t("ui.sessionTurn.diffs.more", { count: String(overflow()) })} + </div> + </Show> + </div> </div> </Show> <Show when={error()}> diff --git a/packages/ui/src/i18n/en.ts b/packages/ui/src/i18n/en.ts index 18823aeaa..e66b55092 100644 --- a/packages/ui/src/i18n/en.ts +++ b/packages/ui/src/i18n/en.ts @@ -38,6 +38,10 @@ export const dict: Record<string, string> = { "ui.sessionTurn.steps.hide": "Hide steps", "ui.sessionTurn.summary.response": "Response", "ui.sessionTurn.diff.showMore": "Show more changes ({{count}})", + "ui.sessionTurn.diffs.changed": "Changed", + "ui.sessionTurn.diffs.showAll": "Show all", + "ui.sessionTurn.diffs.showLess": "Show less", + "ui.sessionTurn.diffs.more": "+{{count}} more files", "ui.sessionTurn.retry.retrying": "retrying", "ui.sessionTurn.retry.inSeconds": "in {{seconds}}s", |
