Skip to content

Commit ada71d4

Browse files
authored
Improve PR panel accessibility and keyboard shortcuts (#107)
- Add ARIA labels and selected-state semantics to collapsible PR rails - Wire `[` and `]` shortcuts for toggling the left rail and inspector - Mark the center workspace as `<main>` for better page structure
1 parent 8993120 commit ada71d4

3 files changed

Lines changed: 375 additions & 293 deletions

File tree

Lines changed: 115 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { NativeApi, PrConflictAnalysis, PrReviewConfig } from "@okcode/contracts";
2-
import { useEffect, useRef, type ReactNode } from "react";
1+
import { useId, type ReactNode } from "react";
32
import {
43
ChevronsLeftIcon,
54
ChevronsRightIcon,
@@ -27,96 +26,121 @@ export function PrInspectorPanel({
2726
hasBlockedWorkflow: boolean;
2827
children: ReactNode;
2928
}) {
30-
if (collapsed) {
31-
return (
32-
<div className="flex min-h-0 w-12 flex-col items-center border-l border-border/70 bg-background/96 py-2 gap-1">
33-
<Button
34-
size="icon-xs"
35-
variant="ghost"
36-
onClick={onToggleCollapsed}
37-
title="Expand inspector"
38-
className="mb-2"
39-
>
40-
<ChevronsLeftIcon className="size-4" />
41-
</Button>
42-
<Tooltip>
43-
<TooltipTrigger
44-
onClick={() => onExpandToTab?.("threads")}
45-
render={
46-
<button
47-
type="button"
48-
className="relative flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted/50"
49-
/>
50-
}
51-
>
52-
<MessageSquareIcon className="size-4" />
53-
{unresolvedThreadCount > 0 ? (
54-
<span className="absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-amber-500/20 text-[9px] font-bold text-amber-700 dark:text-amber-300">
55-
{unresolvedThreadCount > 9 ? "9+" : unresolvedThreadCount}
56-
</span>
57-
) : null}
58-
</TooltipTrigger>
59-
<TooltipPopup side="left" sideOffset={8}>
60-
Threads ({unresolvedThreadCount} open)
61-
</TooltipPopup>
62-
</Tooltip>
63-
<Tooltip>
64-
<TooltipTrigger
65-
onClick={() => onExpandToTab?.("workflow")}
66-
render={
67-
<button
68-
type="button"
69-
className={cn(
70-
"relative flex size-8 items-center justify-center rounded-lg transition-colors hover:bg-muted/50",
71-
hasBlockedWorkflow
72-
? "text-amber-600 dark:text-amber-400"
73-
: "text-muted-foreground",
74-
)}
75-
/>
76-
}
77-
>
78-
<SparklesIcon className="size-4" />
79-
{hasBlockedWorkflow ? (
80-
<span className="absolute right-1 top-1 size-1.5 rounded-full bg-amber-500" />
81-
) : null}
82-
</TooltipTrigger>
83-
<TooltipPopup side="left" sideOffset={8}>
84-
Workflow {hasBlockedWorkflow ? "(blocked)" : ""}
85-
</TooltipPopup>
86-
</Tooltip>
87-
<Tooltip>
88-
<TooltipTrigger
89-
onClick={() => onExpandToTab?.("people")}
90-
render={
91-
<button
92-
type="button"
93-
className="flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted/50"
94-
/>
95-
}
96-
>
97-
<UsersIcon className="size-4" />
98-
</TooltipTrigger>
99-
<TooltipPopup side="left" sideOffset={8}>
100-
People
101-
</TooltipPopup>
102-
</Tooltip>
103-
</div>
104-
);
105-
}
29+
const panelId = useId();
10630

10731
return (
108-
<div className="flex min-h-0 w-[360px] flex-col border-l border-border/70">
109-
<div className="flex h-10 items-center justify-end px-2 border-b border-border/70">
110-
<Button
111-
size="icon-xs"
112-
variant="ghost"
113-
onClick={onToggleCollapsed}
114-
title="Collapse inspector"
115-
>
116-
<ChevronsRightIcon className="size-4" />
117-
</Button>
118-
</div>
119-
<div className="min-h-0 flex-1">{children}</div>
120-
</div>
32+
<aside
33+
id={panelId}
34+
aria-label="Pull request inspector"
35+
className={cn(
36+
"flex min-h-0 flex-col border-l border-border/70",
37+
"transition-[width] duration-200 ease-in-out overflow-hidden",
38+
collapsed ? "w-12 bg-background/96" : "w-[360px]",
39+
)}
40+
>
41+
{collapsed ? (
42+
/* ---------- Collapsed icon rail ---------- */
43+
<div className="flex min-h-0 flex-1 w-12 shrink-0 flex-col items-center py-2 gap-1">
44+
<Button
45+
size="icon-xs"
46+
variant="ghost"
47+
onClick={onToggleCollapsed}
48+
aria-expanded={false}
49+
aria-controls={panelId}
50+
aria-label="Expand inspector panel"
51+
className="mb-2"
52+
>
53+
<ChevronsLeftIcon className="size-4" />
54+
</Button>
55+
<Tooltip>
56+
<TooltipTrigger
57+
onClick={() => onExpandToTab?.("threads")}
58+
render={
59+
<button
60+
type="button"
61+
aria-label={`Threads, ${unresolvedThreadCount} unresolved`}
62+
className="relative flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted/50"
63+
/>
64+
}
65+
>
66+
<MessageSquareIcon className="size-4" />
67+
{unresolvedThreadCount > 0 ? (
68+
<span
69+
className="absolute -right-0.5 -top-0.5 flex size-4 items-center justify-center rounded-full bg-amber-500/20 text-[9px] font-bold text-amber-700 dark:text-amber-300"
70+
aria-hidden="true"
71+
>
72+
{unresolvedThreadCount > 9 ? "9+" : unresolvedThreadCount}
73+
</span>
74+
) : null}
75+
</TooltipTrigger>
76+
<TooltipPopup side="left" sideOffset={8}>
77+
Threads ({unresolvedThreadCount} open)
78+
</TooltipPopup>
79+
</Tooltip>
80+
<Tooltip>
81+
<TooltipTrigger
82+
onClick={() => onExpandToTab?.("workflow")}
83+
render={
84+
<button
85+
type="button"
86+
aria-label={`Workflow${hasBlockedWorkflow ? ", blocked" : ""}`}
87+
className={cn(
88+
"relative flex size-8 items-center justify-center rounded-lg transition-colors hover:bg-muted/50",
89+
hasBlockedWorkflow
90+
? "text-amber-600 dark:text-amber-400"
91+
: "text-muted-foreground",
92+
)}
93+
/>
94+
}
95+
>
96+
<SparklesIcon className="size-4" />
97+
{hasBlockedWorkflow ? (
98+
<span
99+
className="absolute right-1 top-1 size-1.5 rounded-full bg-amber-500"
100+
aria-hidden="true"
101+
/>
102+
) : null}
103+
</TooltipTrigger>
104+
<TooltipPopup side="left" sideOffset={8}>
105+
Workflow {hasBlockedWorkflow ? "(blocked)" : ""}
106+
</TooltipPopup>
107+
</Tooltip>
108+
<Tooltip>
109+
<TooltipTrigger
110+
onClick={() => onExpandToTab?.("people")}
111+
render={
112+
<button
113+
type="button"
114+
aria-label="People"
115+
className="flex size-8 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted/50"
116+
/>
117+
}
118+
>
119+
<UsersIcon className="size-4" />
120+
</TooltipTrigger>
121+
<TooltipPopup side="left" sideOffset={8}>
122+
People
123+
</TooltipPopup>
124+
</Tooltip>
125+
</div>
126+
) : (
127+
/* ---------- Expanded panel ---------- */
128+
<>
129+
<div className="flex h-10 items-center justify-end px-2 border-b border-border/70">
130+
<Button
131+
size="icon-xs"
132+
variant="ghost"
133+
onClick={onToggleCollapsed}
134+
aria-expanded={true}
135+
aria-controls={panelId}
136+
aria-label="Collapse inspector panel"
137+
>
138+
<ChevronsRightIcon className="size-4" />
139+
</Button>
140+
</div>
141+
<div className="min-h-0 flex-1">{children}</div>
142+
</>
143+
)}
144+
</aside>
121145
);
122146
}

0 commit comments

Comments
 (0)