Left rail: horizontal rows, 44px avatars, left-aligned (WorkClaw)
Match WorkClaw's left sidebar (verified vs left-sidebar captures): claw rows are horizontal — a 44px squircle avatar (was 58px, centered) on the left with the name beside it, hugging the edge at px-4, gap-3. The "New claw" tile is a bare 44px dashed + at the same inset (no label, like the reference). RosterList drops items-center; collapsed (full-panel) mode still centers the avatars. Avatar `rail` size 58px→44px (size-11) / radius 15px; test updated. typecheck/lint(0)/86 unit/31 E2E/6 visual green; workspace-home baseline regenerated. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
0923fb3f6a
commit
3a81be17d9
@@ -21,14 +21,14 @@ export function AgentRosterItem({
|
|||||||
onTogglePin?: (id: string) => void;
|
onTogglePin?: (id: string) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="group relative flex flex-col items-center">
|
<div className="group relative flex items-center">
|
||||||
<Link
|
<Link
|
||||||
href={`/claws/${agent.id}`}
|
href={`/claws/${agent.id}`}
|
||||||
aria-current={active ? "page" : undefined}
|
aria-current={active ? "page" : undefined}
|
||||||
className="flex w-full flex-col items-center gap-1"
|
className="flex w-full items-center gap-3 px-4 py-1 md:group-data-[collapsed=true]/rail:justify-center md:group-data-[collapsed=true]/rail:px-0"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={`rounded-[22px] p-0.5 transition-colors duration-(--duration-normal) ease-app ${
|
className={`shrink-0 rounded-[17px] p-0.5 transition-colors duration-(--duration-normal) ease-app ${
|
||||||
active ? "ring-2 ring-coral" : ""
|
active ? "ring-2 ring-coral" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@@ -49,7 +49,7 @@ export function AgentRosterItem({
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
className={`max-w-full truncate text-xs leading-[1.3] transition-colors duration-(--duration-normal) ease-app md:group-data-[collapsed=true]/rail:hidden ${
|
className={`min-w-0 flex-1 truncate text-sm leading-tight transition-colors duration-(--duration-normal) ease-app md:group-data-[collapsed=true]/rail:hidden ${
|
||||||
active
|
active
|
||||||
? "font-semibold text-foreground"
|
? "font-semibold text-foreground"
|
||||||
: "font-medium text-muted-foreground group-hover:text-foreground"
|
: "font-medium text-muted-foreground group-hover:text-foreground"
|
||||||
@@ -58,7 +58,7 @@ export function AgentRosterItem({
|
|||||||
{agent.name}
|
{agent.name}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="md:group-data-[collapsed=true]/rail:hidden">
|
<div className="absolute top-1/2 right-1.5 -translate-y-1/2 md:group-data-[collapsed=true]/rail:hidden">
|
||||||
<AgentRowMenu
|
<AgentRowMenu
|
||||||
agentId={agent.id}
|
agentId={agent.id}
|
||||||
agentName={agent.name}
|
agentName={agent.name}
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ import { RosterList } from "./RosterList";
|
|||||||
import { ShellNav } from "./ShellNav";
|
import { ShellNav } from "./ShellNav";
|
||||||
import { UserMenu } from "./UserMenu";
|
import { UserMenu } from "./UserMenu";
|
||||||
|
|
||||||
/* Labels hide (md+) when the rail collapses in full-panel mode. Applied to
|
|
||||||
every text node via the group/rail data flag on the aside. */
|
|
||||||
const HIDE_WHEN_COLLAPSED = "md:group-data-[collapsed=true]/rail:hidden";
|
|
||||||
|
|
||||||
interface LeftRailProps {
|
interface LeftRailProps {
|
||||||
user: User;
|
user: User;
|
||||||
roster: Agent[];
|
roster: Agent[];
|
||||||
@@ -90,18 +86,14 @@ export function LeftRail({ user, roster, creditsBalance }: LeftRailProps) {
|
|||||||
)}
|
)}
|
||||||
<Link
|
<Link
|
||||||
href="/claws/new"
|
href="/claws/new"
|
||||||
className="group mt-1 flex w-full flex-col items-center gap-1 py-1.5"
|
aria-label="New claw"
|
||||||
|
className="group mt-0.5 flex items-center px-4 py-1 md:group-data-[collapsed=true]/rail:justify-center md:group-data-[collapsed=true]/rail:px-0"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className="inline-flex size-12 items-center justify-center rounded-[17px] border border-dashed border-border text-coral transition-colors duration-(--duration-normal) ease-app group-hover:border-coral"
|
className="inline-flex size-11 shrink-0 items-center justify-center rounded-[15px] border border-dashed border-border text-coral transition-colors duration-(--duration-normal) ease-app group-hover:border-coral"
|
||||||
>
|
>
|
||||||
<Plus size={20} />
|
<Plus size={18} />
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`text-xs text-muted-foreground transition-colors duration-(--duration-normal) ease-app group-hover:text-foreground ${HIDE_WHEN_COLLAPSED}`}
|
|
||||||
>
|
|
||||||
New claw
|
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function RosterList({ roster }: { roster: Agent[] }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul aria-label="Your claws" className="flex flex-col items-center gap-1">
|
<ul aria-label="Your claws" className="flex flex-col gap-0.5">
|
||||||
{ordered.map((agent) => (
|
{ordered.map((agent) => (
|
||||||
<li key={agent.id} className="w-full">
|
<li key={agent.id} className="w-full">
|
||||||
<AgentRosterItem
|
<AgentRosterItem
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ describe("Avatar", () => {
|
|||||||
<Avatar name="Scout" shape="squircle" size="rail" />,
|
<Avatar name="Scout" shape="squircle" size="rail" />,
|
||||||
);
|
);
|
||||||
const span = container.querySelector("span[aria-hidden]")!;
|
const span = container.querySelector("span[aria-hidden]")!;
|
||||||
expect(span.className).toContain("rounded-[20px]");
|
expect(span.className).toContain("rounded-[15px]");
|
||||||
expect(span.className).not.toContain("rounded-full");
|
expect(span.className).not.toContain("rounded-full");
|
||||||
expect(span.className).toContain("size-[58px]"); /* 58px rail tile */
|
expect(span.className).toContain("size-11"); /* 44px rail tile */
|
||||||
});
|
});
|
||||||
|
|
||||||
it("welcome size renders the 126px hero avatar", () => {
|
it("welcome size renders the 126px hero avatar", () => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const SIZES = {
|
|||||||
sm: "size-6 text-xxs",
|
sm: "size-6 text-xxs",
|
||||||
md: "size-8 text-xs",
|
md: "size-8 text-xs",
|
||||||
lg: "size-16 text-xl",
|
lg: "size-16 text-xl",
|
||||||
rail: "size-[58px] text-xl" /* 58px claw rail tile */,
|
rail: "size-11 text-base" /* 44px claw rail tile (WorkClaw) */,
|
||||||
chat: "size-[50px] text-base" /* assistant message avatar */,
|
chat: "size-[50px] text-base" /* assistant message avatar */,
|
||||||
header: "size-[58px] text-2xl" /* chat-header identity */,
|
header: "size-[58px] text-2xl" /* chat-header identity */,
|
||||||
settings: "size-24 text-3xl" /* 96px Settings hero (measured) */,
|
settings: "size-24 text-3xl" /* 96px Settings hero (measured) */,
|
||||||
@@ -23,7 +23,7 @@ const SIZES = {
|
|||||||
/* Squircle radius scales with the tile so big avatars read as rounded as
|
/* Squircle radius scales with the tile so big avatars read as rounded as
|
||||||
the 48px rail tile (a flat 17px looks square at 126px). */
|
the 48px rail tile (a flat 17px looks square at 126px). */
|
||||||
const SQUIRCLE_RADIUS: Partial<Record<keyof typeof SIZES, string>> = {
|
const SQUIRCLE_RADIUS: Partial<Record<keyof typeof SIZES, string>> = {
|
||||||
rail: "rounded-[20px]",
|
rail: "rounded-[15px]",
|
||||||
header: "rounded-[20px]",
|
header: "rounded-[20px]",
|
||||||
settings: "rounded-[34px]",
|
settings: "rounded-[34px]",
|
||||||
welcome: "rounded-[40px]",
|
welcome: "rounded-[40px]",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
Reference in New Issue
Block a user