Skip to content

Commit 91b93af

Browse files
committed
fix(frontend): tighten the nav rail and make the app shell lines continuous
The collapsed sidebar was 80px wide around 14-16px icons, and the rail painted two different greys because the antd menu drew its own background over the middle. The frame lines were nearly invisible, so the rail edge and the top bars never read as one frame. - Rail is 48px collapsed, one flat colour, darker than the content in dark mode and white in light mode. - New shell tokens (rail background, frame line, scroll thumb) so the rail edge, the breadcrumb bar and the playground header share one line. - Workflow sidebar header rows are 45px, so the rail's lines land on the same y as the content's and carry straight across. - The agent picker loses its own border inside the framed row. - Configuration panel gets a quiet scrollbar: gutter always reserved, so no content shift, thumb only on hover or focus.
1 parent 5ceca94 commit 91b93af

12 files changed

Lines changed: 104 additions & 22 deletions

File tree

web/oss/src/components/Layout/assets/styles.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const useStyles = createUseStyles((theme: JSSTheme) => ({
2727
justifyContent: "space-between",
2828
width: "100%",
2929
padding: "8px 1.5rem",
30-
borderBottom: `1px solid ${theme.colorBorderSecondary}`,
30+
borderBottom: "1px solid var(--ag-shell-line)",
3131
},
3232
topRightBar: {
3333
display: "flex",

web/oss/src/components/Playground/Components/MainLayout/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ const PlaygroundMainView = ({
384384
ref={setConfigPanelRef}
385385
className={clsx([
386386
{
387-
"grow w-full min-h-0 overflow-y-auto": !isComparisonView,
387+
"ag-scroll-quiet grow w-full min-h-0 overflow-y-auto":
388+
!isComparisonView,
388389
"grow w-full min-h-0 overflow-x-auto flex [&::-webkit-scrollbar]:w-0":
389390
isComparisonView,
390391
},

web/oss/src/components/Playground/Components/PlaygroundHeader/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ const PlaygroundHeader: React.FC<PlaygroundHeaderProps> = ({className, ...divPro
633633
<>
634634
<div
635635
className={clsx(
636-
"flex items-center justify-between gap-4 px-2.5 py-2 bg-[var(--ag-surface-raised)] border-0 border-b border-solid border-[var(--ag-surface-divider)]",
636+
"flex items-center justify-between gap-4 px-2.5 py-2 bg-[var(--ag-surface-raised)] border-0 border-b border-solid border-[var(--ag-shell-line)]",
637637
className,
638638
)}
639639
{...divProps}

web/oss/src/components/ProtectedRoute/ProtectedRoute.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ const BootShell = memo(function BootShell({shell}: {shell: "app" | "blank"}) {
3232
<div className="flex h-dvh w-full">
3333
<div
3434
className={clsx(
35-
"h-full shrink-0 border-0 border-r border-solid border-[var(--ag-surface-divider)] bg-[var(--ag-sidebar-bg)]",
36-
collapsed ? "w-[80px]" : "w-[236px]",
35+
"h-full shrink-0 border-0 border-r border-solid border-[var(--ag-shell-line)] bg-[var(--ag-sidebar-bg)]",
36+
collapsed ? "w-[48px]" : "w-[236px]",
3737
)}
3838
/>
3939
<div className="grow" />

web/oss/src/components/Sidebar/components/SidebarSkeletonLoader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const SidebarSkeletonLoader = () => {
1414
className={clsx(
1515
"flex flex-col justify-between h-screen border border-r border-solid border-gray-100",
1616
{
17-
"w-[80px] items-center": collapsed,
17+
"w-[48px] items-center": collapsed,
1818
"w-[236px]": !collapsed,
1919
},
2020
)}

web/oss/src/components/Sidebar/components/WorkflowPicker.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ const WorkflowPicker = memo(({collapsed}: WorkflowPickerProps) => {
4545
aria-label="Switch workflow"
4646
className={clsx(
4747
"flex items-center justify-between overflow-hidden transition-[width,height,padding,gap,border-color] duration-300 ease-in-out",
48-
collapsed
49-
? "!w-8 !h-8 !p-1 gap-0"
50-
: "w-full pl-2 pr-3 py-3 h-12 gap-2 border border-solid border-gray-200",
48+
// No border when expanded: the header row it sits in is already
49+
// framed by the rail's own line, so a box inside a box reads wrong.
50+
collapsed ? "!w-8 !h-8 !p-1 gap-0" : "w-full h-full pl-1.5 pr-2 gap-2",
5151
)}
5252
>
5353
<WorkflowIdentity

web/oss/src/components/Sidebar/engine/SidebarShell.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ const {Sider} = Layout
1212
const MENU_CLASS_NAME =
1313
"border-r-0 overflow-y-auto relative [&_.ant-menu-item-selected]:font-medium"
1414

15+
// The menu paints its own container background, which would break the rail into two
16+
// shades. Transparent lets the rail colour run edge to edge.
17+
const MENU_SURFACE_CLASS_NAME = "!bg-transparent"
18+
19+
// antd hardcodes a 80px width on collapsed inline menus; the rail is narrower than that,
20+
// so let the menu fill the rail and keep antd's own centering math working off that width.
21+
const COLLAPSED_MENU_CLASS_NAME = "[&.ant-menu-inline-collapsed]:!w-full"
22+
1523
class SidebarErrorBoundary extends React.Component<React.PropsWithChildren, {hasError: boolean}> {
1624
state = {hasError: false}
1725

@@ -247,7 +255,13 @@ const SidebarShell: React.FC<SidebarShellProps> = ({
247255
{renderSlot(section.before, collapsed)}
248256
<SidebarMenu
249257
menuProps={{
250-
className: isBottomSection ? "" : MENU_CLASS_NAME,
258+
className: [
259+
isBottomSection ? "" : MENU_CLASS_NAME,
260+
MENU_SURFACE_CLASS_NAME,
261+
COLLAPSED_MENU_CLASS_NAME,
262+
]
263+
.filter(Boolean)
264+
.join(" "),
251265
selectedKeys,
252266
...(isInlineSection
253267
? {
@@ -280,18 +294,18 @@ const SidebarShell: React.FC<SidebarShellProps> = ({
280294
const bottomSections = visibleSections.filter((section) => section.placement === "bottom")
281295

282296
return (
283-
<div className="border-0 border-r border-solid border-[var(--ag-surface-divider)]">
297+
<div className="border-0 border-r border-solid border-[var(--ag-shell-line)]">
284298
<Sider
285299
theme={theme}
286300
className="sticky top-0 bottom-0 h-screen bg-[var(--ag-sidebar-bg)]"
287301
collapsible
288-
width={collapsed ? 80 : 236}
302+
width={collapsed ? 48 : 236}
289303
trigger={null}
290304
>
291305
<div
292306
className={[
293307
"flex flex-col h-full transition-all duration-300",
294-
collapsed ? "w-[80px]" : "w-[236px]",
308+
collapsed ? "w-[48px]" : "w-[236px]",
295309
].join(" ")}
296310
>
297311
{renderSlot(scope.header, collapsed, scope.lastPath)}

web/oss/src/components/Sidebar/scopes/workflowScope.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import {useMemo} from "react"
22

3-
import {Divider} from "antd"
4-
53
import SidebarBackButton from "../components/SidebarBackButton"
64
import WorkflowPicker from "../components/WorkflowPicker"
75
import type {SidebarScope, SidebarSection, SidebarSlotContext} from "../engine/types"
@@ -14,21 +12,27 @@ interface WorkflowScopeOptions {
1412
lastPath?: string
1513
}
1614

15+
// The two header rows are 45px tall so the rail's lines land on the same y as the
16+
// breadcrumb bar's and the playground header's, and read as one line across the app.
1717
const WorkflowSidebarHeader = ({collapsed, lastPath}: SidebarSlotContext) => (
1818
<>
1919
<div
2020
className={[
21-
"w-full h-[48px] flex items-center",
22-
collapsed ? "justify-center" : "mx-1.5",
21+
"w-full h-[45px] shrink-0 flex items-center border-0 border-b border-solid border-[var(--ag-shell-line)]",
22+
collapsed ? "justify-center" : "px-1.5",
2323
].join(" ")}
2424
>
2525
<SidebarBackButton collapsed={collapsed} lastPath={lastPath} />
2626
</div>
2727

28-
<div className={collapsed ? "flex w-full justify-center p-2" : "px-2 pt-1 pb-2"}>
28+
<div
29+
className={[
30+
"flex h-[45px] shrink-0 items-center border-0 border-b border-solid border-[var(--ag-shell-line)]",
31+
collapsed ? "w-full justify-center" : "px-2",
32+
].join(" ")}
33+
>
2934
<WorkflowPicker collapsed={collapsed} />
3035
</div>
31-
<Divider className="mb-1 mt-0" />
3236
</>
3337
)
3438

web/oss/src/styles/globals.css

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -896,3 +896,43 @@ body {
896896
.ag-icon-segmented .ant-segmented-item-label svg {
897897
display: block;
898898
}
899+
900+
/* Quiet scrollbars. The gutter is reserved at the thumb's width whether or not the
901+
content overflows, so revealing the thumb never shifts the content, and the thumb
902+
only appears while the pointer or focus is inside the scroller. */
903+
.ag-scroll-quiet {
904+
scrollbar-gutter: stable;
905+
scrollbar-width: thin;
906+
scrollbar-color: transparent transparent;
907+
}
908+
909+
.ag-scroll-quiet:hover,
910+
.ag-scroll-quiet:focus-within {
911+
scrollbar-color: var(--ag-scroll-thumb) transparent;
912+
}
913+
914+
.ag-scroll-quiet::-webkit-scrollbar {
915+
width: 8px;
916+
height: 8px;
917+
}
918+
919+
.ag-scroll-quiet::-webkit-scrollbar-track {
920+
background: transparent;
921+
}
922+
923+
.ag-scroll-quiet::-webkit-scrollbar-thumb {
924+
background-color: transparent;
925+
background-clip: content-box;
926+
border: 2px solid transparent;
927+
border-radius: 999px;
928+
transition: background-color 150ms ease;
929+
}
930+
931+
.ag-scroll-quiet:hover::-webkit-scrollbar-thumb,
932+
.ag-scroll-quiet:focus-within::-webkit-scrollbar-thumb {
933+
background-color: var(--ag-scroll-thumb);
934+
}
935+
936+
.ag-scroll-quiet::-webkit-scrollbar-thumb:hover {
937+
background-color: var(--ag-scroll-thumb-hover);
938+
}

web/oss/src/styles/theme-variables.css

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,11 @@
197197
--ag-strip-card-border: #eaeff5;
198198
--ag-strip-card-border-hover: #bdc7d1;
199199
--ag-strip-card-hover-shadow: 0 2px 8px -2px rgba(28, 44, 61, 0.12);
200-
--ag-sidebar-bg: var(--ag-surface-raised);
200+
--ag-shell-rail-bg: #ffffff;
201+
--ag-shell-line: #e3e5e9;
202+
--ag-scroll-thumb: rgba(15, 23, 42, 0.22);
203+
--ag-scroll-thumb-hover: rgba(15, 23, 42, 0.38);
204+
--ag-sidebar-bg: var(--ag-shell-rail-bg);
201205

202206
/* --- legacy --ag-c-* shim (light = original hex) --- */
203207
--ag-c-051729: #051729;
@@ -507,7 +511,11 @@
507511
--ag-strip-card-border: #232327;
508512
--ag-strip-card-border-hover: #3a3a40;
509513
--ag-strip-card-hover-shadow: 0 2px 8px -2px rgba(0, 0, 0, 0.45);
510-
--ag-sidebar-bg: var(--ag-surface-raised);
514+
--ag-shell-rail-bg: #121316;
515+
--ag-shell-line: #2b2d32;
516+
--ag-scroll-thumb: rgba(255, 255, 255, 0.20);
517+
--ag-scroll-thumb-hover: rgba(255, 255, 255, 0.34);
518+
--ag-sidebar-bg: var(--ag-shell-rail-bg);
511519

512520
/* --- legacy --ag-c-* shim (dark aliased to roles where possible) --- */
513521
--ag-c-051729: rgba(255, 255, 255, 0.88);

0 commit comments

Comments
 (0)