mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-17 11:04:58 +00:00
Refine Foundry UI layout and styling (#235)
* feat: modernize chat UI and rename handoff to task - Remove agent message bubbles, keep user bubbles (right-aligned) - Rename "Handoffs" to "Tasks" with ListChecks icon in sidebar - Move model picker inside composer, add renderFooter to ChatComposer SDK - Make project sections collapsible with hover-only chevrons - Remove divider between chat and composer - Update model picker chevron to flip on open/close - Replace all user-visible "handoff" strings with "task" across frontend Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: real org mock data, model picker styling, project icons, task minutes indicator - Replace fake acme/* mock data with real rivet-dev GitHub org repos and PRs - Fix model picker popover: dark gray surface with backdrop blur instead of pure black - Add colored letter icons to project section headers (swap to chevron on hover) - Add "847 min used" indicator in transcript header - Rename browser tab title from OpenHandoff to Foundry - Reduce transcript header title font weight to 500 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: refine Foundry UI — single-line task cards, dark user bubbles, curved panel corners, send icon - Collapse task sidebar cards to single-line layout (title, number, diffs, timestamp) - Dark-themed user message bubbles matching site theme - Curved top-left corner on center chat panel with border line - Subtle focus border on composer input - Replace ArrowUpFromLine with SendHorizonal icon - Tab strip gaps, padding, and divider alignment fixes - Plus button with visible background - Right sidebar header color matching Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
20082512a3
commit
e792a720a0
9 changed files with 201 additions and 123 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { memo, useState } from "react";
|
||||
import { memo, useRef, useState } from "react";
|
||||
import { useStyletron } from "baseui";
|
||||
import { LabelSmall, LabelXSmall } from "baseui/typography";
|
||||
import { ChevronDown, ChevronUp, CloudUpload, GitPullRequestDraft, ListChecks, Plus } from "lucide-react";
|
||||
|
|
@ -30,6 +30,7 @@ export const Sidebar = memo(function Sidebar({
|
|||
onMarkUnread,
|
||||
onRenameHandoff,
|
||||
onRenameBranch,
|
||||
onReorderProjects,
|
||||
}: {
|
||||
projects: ProjectSection[];
|
||||
activeId: string;
|
||||
|
|
@ -38,10 +39,13 @@ export const Sidebar = memo(function Sidebar({
|
|||
onMarkUnread: (id: string) => void;
|
||||
onRenameHandoff: (id: string) => void;
|
||||
onRenameBranch: (id: string) => void;
|
||||
onReorderProjects: (fromIndex: number, toIndex: number) => void;
|
||||
}) {
|
||||
const [css, theme] = useStyletron();
|
||||
const contextMenu = useContextMenu();
|
||||
const [collapsedProjects, setCollapsedProjects] = useState<Record<string, boolean>>({});
|
||||
const dragIndexRef = useRef<number | null>(null);
|
||||
const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
|
||||
|
||||
return (
|
||||
<SPanel>
|
||||
|
|
@ -53,10 +57,10 @@ export const Sidebar = memo(function Sidebar({
|
|||
display: none !important;
|
||||
}
|
||||
`}</style>
|
||||
<PanelHeaderBar>
|
||||
<PanelHeaderBar $style={{ backgroundColor: "transparent", borderBottom: "none" }}>
|
||||
<LabelSmall
|
||||
color={theme.colors.contentPrimary}
|
||||
$style={{ fontWeight: 600, flex: 1, fontSize: "13px", display: "flex", alignItems: "center", gap: "6px" }}
|
||||
$style={{ fontWeight: 500, flex: 1, fontSize: "13px", display: "flex", alignItems: "center", gap: "6px" }}
|
||||
>
|
||||
<ListChecks size={14} />
|
||||
Tasks
|
||||
|
|
@ -65,17 +69,17 @@ export const Sidebar = memo(function Sidebar({
|
|||
onClick={onCreate}
|
||||
className={css({
|
||||
all: "unset",
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
borderRadius: "4px",
|
||||
backgroundColor: "#ff4f00",
|
||||
color: "#ffffff",
|
||||
width: "26px",
|
||||
height: "26px",
|
||||
borderRadius: "8px",
|
||||
backgroundColor: "rgba(255, 255, 255, 0.12)",
|
||||
color: "#e4e4e7",
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
transition: "background 200ms ease",
|
||||
":hover": { backgroundColor: "#ff6a00" },
|
||||
":hover": { backgroundColor: "rgba(255, 255, 255, 0.20)" },
|
||||
})}
|
||||
>
|
||||
<Plus size={14} />
|
||||
|
|
@ -83,11 +87,48 @@ export const Sidebar = memo(function Sidebar({
|
|||
</PanelHeaderBar>
|
||||
<ScrollBody>
|
||||
<div className={css({ padding: "8px", display: "flex", flexDirection: "column", gap: "4px" })}>
|
||||
{projects.map((project) => {
|
||||
{projects.map((project, projectIndex) => {
|
||||
const isCollapsed = collapsedProjects[project.id] === true;
|
||||
const isDragOver = dragOverIndex === projectIndex && dragIndexRef.current !== projectIndex;
|
||||
|
||||
return (
|
||||
<div key={project.id} className={css({ display: "flex", flexDirection: "column", gap: "4px" })}>
|
||||
<div
|
||||
key={project.id}
|
||||
draggable
|
||||
onDragStart={(event) => {
|
||||
dragIndexRef.current = projectIndex;
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
event.dataTransfer.setData("text/plain", String(projectIndex));
|
||||
}}
|
||||
onDragOver={(event) => {
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = "move";
|
||||
setDragOverIndex(projectIndex);
|
||||
}}
|
||||
onDragLeave={() => {
|
||||
setDragOverIndex((current) => (current === projectIndex ? null : current));
|
||||
}}
|
||||
onDrop={(event) => {
|
||||
event.preventDefault();
|
||||
const fromIndex = dragIndexRef.current;
|
||||
if (fromIndex != null && fromIndex !== projectIndex) {
|
||||
onReorderProjects(fromIndex, projectIndex);
|
||||
}
|
||||
dragIndexRef.current = null;
|
||||
setDragOverIndex(null);
|
||||
}}
|
||||
onDragEnd={() => {
|
||||
dragIndexRef.current = null;
|
||||
setDragOverIndex(null);
|
||||
}}
|
||||
className={css({
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "4px",
|
||||
borderTop: isDragOver ? "2px solid #ff4f00" : "2px solid transparent",
|
||||
transition: "border-color 150ms ease",
|
||||
})}
|
||||
>
|
||||
<div
|
||||
onClick={() =>
|
||||
setCollapsedProjects((current) => ({
|
||||
|
|
@ -102,7 +143,7 @@ export const Sidebar = memo(function Sidebar({
|
|||
justifyContent: "space-between",
|
||||
padding: "10px 8px 4px",
|
||||
gap: "8px",
|
||||
cursor: "pointer",
|
||||
cursor: "grab",
|
||||
userSelect: "none",
|
||||
":hover": { opacity: 0.8 },
|
||||
})}
|
||||
|
|
@ -150,9 +191,11 @@ export const Sidebar = memo(function Sidebar({
|
|||
{project.label}
|
||||
</LabelSmall>
|
||||
</div>
|
||||
<LabelXSmall color={theme.colors.contentTertiary}>
|
||||
{formatRelativeAge(project.updatedAtMs)}
|
||||
</LabelXSmall>
|
||||
{isCollapsed ? (
|
||||
<LabelXSmall color={theme.colors.contentTertiary}>
|
||||
{formatRelativeAge(project.updatedAtMs)}
|
||||
</LabelXSmall>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{!isCollapsed && project.handoffs.map((handoff) => {
|
||||
|
|
@ -177,7 +220,7 @@ export const Sidebar = memo(function Sidebar({
|
|||
])
|
||||
}
|
||||
className={css({
|
||||
padding: "12px",
|
||||
padding: "8px 12px",
|
||||
borderRadius: "8px",
|
||||
border: "1px solid transparent",
|
||||
backgroundColor: isActive ? "rgba(255, 255, 255, 0.06)" : "transparent",
|
||||
|
|
@ -204,35 +247,17 @@ export const Sidebar = memo(function Sidebar({
|
|||
</div>
|
||||
<LabelSmall
|
||||
$style={{
|
||||
fontWeight: 600,
|
||||
flex: 1,
|
||||
fontWeight: hasUnread ? 600 : 400,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
minWidth: 0,
|
||||
flexShrink: 1,
|
||||
}}
|
||||
color={isDim ? theme.colors.contentSecondary : theme.colors.contentPrimary}
|
||||
color={hasUnread ? "#ffffff" : theme.colors.contentSecondary}
|
||||
>
|
||||
{handoff.title}
|
||||
</LabelSmall>
|
||||
{hasDiffs ? (
|
||||
<div className={css({ display: "flex", gap: "4px", flexShrink: 0 })}>
|
||||
<span className={css({ fontSize: "11px", color: "#7ee787" })}>+{totalAdded}</span>
|
||||
<span className={css({ fontSize: "11px", color: "#ffa198" })}>-{totalRemoved}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={css({ display: "flex", alignItems: "center", marginTop: "4px", gap: "6px" })}>
|
||||
<LabelXSmall
|
||||
color={theme.colors.contentTertiary}
|
||||
$style={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
flexShrink: 1,
|
||||
}}
|
||||
>
|
||||
{handoff.repoName}
|
||||
</LabelXSmall>
|
||||
{handoff.pullRequest != null ? (
|
||||
<span className={css({ display: "inline-flex", alignItems: "center", gap: "4px", flexShrink: 0 })}>
|
||||
<LabelXSmall color={theme.colors.contentSecondary} $style={{ fontWeight: 600 }}>
|
||||
|
|
@ -243,7 +268,13 @@ export const Sidebar = memo(function Sidebar({
|
|||
) : (
|
||||
<GitPullRequestDraft size={11} color={theme.colors.contentTertiary} />
|
||||
)}
|
||||
<LabelXSmall color={theme.colors.contentTertiary} $style={{ marginLeft: "auto", flexShrink: 0 }}>
|
||||
{hasDiffs ? (
|
||||
<div className={css({ display: "flex", gap: "4px", flexShrink: 0, marginLeft: "auto" })}>
|
||||
<span className={css({ fontSize: "11px", color: "#7ee787" })}>+{totalAdded}</span>
|
||||
<span className={css({ fontSize: "11px", color: "#ffa198" })}>-{totalRemoved}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<LabelXSmall color={theme.colors.contentTertiary} $style={{ flexShrink: 0, marginLeft: hasDiffs ? undefined : "auto" }}>
|
||||
{formatRelativeAge(handoff.updatedAtMs)}
|
||||
</LabelXSmall>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue