Skip to content

Commit bbeb565

Browse files
authored
Alternate inactive project name tones in sidebar (#356)
- Keep the selected project in its project color - Alternate inactive project names between stronger and softer muted greys
1 parent 7bd566c commit bbeb565

3 files changed

Lines changed: 49 additions & 6 deletions

File tree

apps/web/src/components/Sidebar.logic.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getProjectSortTimestamp,
77
hasUnseenCompletion,
88
resolveProjectStatusIndicator,
9+
resolveProjectNameTone,
910
resolveSidebarNewThreadEnvMode,
1011
resolveThreadRowClassName,
1112
resolveThreadStatusPill,
@@ -315,6 +316,22 @@ describe("resolveThreadRowClassName", () => {
315316
});
316317
});
317318

319+
describe("resolveProjectNameTone", () => {
320+
it("keeps the selected project name on its assigned project color", () => {
321+
expect(resolveProjectNameTone({ isSelectedProject: true, visualIndex: 3 })).toBe("project");
322+
});
323+
324+
it("starts inactive project names on the stronger muted grey", () => {
325+
expect(resolveProjectNameTone({ isSelectedProject: false, visualIndex: 0 })).toBe(
326+
"mutedStrong",
327+
);
328+
});
329+
330+
it("alternates inactive project names to a softer muted grey on the next row", () => {
331+
expect(resolveProjectNameTone({ isSelectedProject: false, visualIndex: 1 })).toBe("mutedSoft");
332+
});
333+
});
334+
318335
describe("resolveProjectStatusIndicator", () => {
319336
it("returns null when no threads have a notable status", () => {
320337
expect(resolveProjectStatusIndicator([null, null])).toBeNull();

apps/web/src/components/Sidebar.logic.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,17 @@ export function resolveThreadRowClassName(input: {
113113
return cn(baseClassName, "text-muted-foreground hover:bg-accent hover:text-foreground");
114114
}
115115

116+
export function resolveProjectNameTone(input: {
117+
isSelectedProject: boolean;
118+
visualIndex: number;
119+
}): "project" | "mutedStrong" | "mutedSoft" {
120+
if (input.isSelectedProject) {
121+
return "project";
122+
}
123+
124+
return input.visualIndex % 2 === 0 ? "mutedStrong" : "mutedSoft";
125+
}
126+
116127
export function resolveThreadStatusPill(input: {
117128
thread: ThreadStatusInput;
118129
hasPendingApprovals: boolean;

apps/web/src/components/Sidebar.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ import { OkCodeMark } from "./OkCodeMark";
9595
import {
9696
getVisibleThreadsForProject,
9797
isActionableThreadStatus,
98+
resolveProjectNameTone,
9899
resolveSidebarNewThreadEnvMode,
99100
resolveThreadStatusPill,
100101
shouldClearThreadSelectionOnMouseDown,
@@ -556,6 +557,7 @@ export default function Sidebar() {
556557
() => new Map(threads.map((thread) => [thread.id, thread] as const)),
557558
[threads],
558559
);
560+
const activeProjectId = routeThreadId ? (threadById.get(routeThreadId)?.projectId ?? null) : null;
559561
const sortedThreadsByProjectId = useMemo(
560562
() => sortThreadsByProjectIdForSidebar(threads, appSettings.sidebarThreadSortOrder),
561563
[appSettings.sidebarThreadSortOrder, threads],
@@ -1258,6 +1260,7 @@ export default function Sidebar() {
12581260
function renderProjectItem(
12591261
project: (typeof sortedProjects)[number],
12601262
dragHandleProps: SortableProjectHandleProps | null,
1263+
visualIndex: number,
12611264
) {
12621265
const projectThreads = sortedThreadsByProjectId.get(project.id) ?? EMPTY_THREADS;
12631266
const activeThreadId = routeThreadId ?? undefined;
@@ -1277,6 +1280,10 @@ export default function Sidebar() {
12771280
const renderedThreads = pinnedCollapsedThread ? [pinnedCollapsedThread] : visibleThreads;
12781281
const pColor = getProjectColor(project.id);
12791282
const isDark = resolvedTheme === "dark";
1283+
const projectNameTone = resolveProjectNameTone({
1284+
isSelectedProject: activeProjectId === project.id,
1285+
visualIndex,
1286+
});
12801287

12811288
return (
12821289
<Collapsible className="group/collapsible" open={shouldShowThreadPanel}>
@@ -1326,8 +1333,16 @@ export default function Sidebar() {
13261333
) : (
13271334
<span className="min-w-0 flex-1">
13281335
<span
1329-
className="block truncate text-xs font-semibold"
1330-
style={{ color: isDark ? pColor.textDark : pColor.text }}
1336+
className={cn(
1337+
"block truncate text-xs font-semibold",
1338+
projectNameTone === "mutedStrong" && "text-muted-foreground/72",
1339+
projectNameTone === "mutedSoft" && "text-muted-foreground/48",
1340+
)}
1341+
style={
1342+
projectNameTone === "project"
1343+
? { color: isDark ? pColor.textDark : pColor.text }
1344+
: undefined
1345+
}
13311346
onDoubleClick={(e) => {
13321347
e.stopPropagation();
13331348
startProjectEditing({
@@ -2020,19 +2035,19 @@ export default function Sidebar() {
20202035
items={sortedProjects.map((project) => project.id)}
20212036
strategy={verticalListSortingStrategy}
20222037
>
2023-
{sortedProjects.map((project) => (
2038+
{sortedProjects.map((project, index) => (
20242039
<SortableProjectItem key={project.id} projectId={project.id}>
2025-
{(dragHandleProps) => renderProjectItem(project, dragHandleProps)}
2040+
{(dragHandleProps) => renderProjectItem(project, dragHandleProps, index)}
20262041
</SortableProjectItem>
20272042
))}
20282043
</SortableContext>
20292044
</SidebarMenu>
20302045
</DndContext>
20312046
) : (
20322047
<SidebarMenu className="gap-0.5">
2033-
{sortedProjects.map((project) => (
2048+
{sortedProjects.map((project, index) => (
20342049
<SidebarMenuItem key={project.id} className="rounded-md">
2035-
{renderProjectItem(project, null)}
2050+
{renderProjectItem(project, null, index)}
20362051
</SidebarMenuItem>
20372052
))}
20382053
</SidebarMenu>

0 commit comments

Comments
 (0)