Skip to content

Commit f3da833

Browse files
Copilotalexr00
andcommitted
Add sticky header functionality with compact mode
Co-authored-by: alexr00 <38270282+alexr00@users.noreply.github.com>
1 parent eadb467 commit f3da833

3 files changed

Lines changed: 83 additions & 11 deletions

File tree

webviews/components/header.tsx

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ export function Header({
3232
owner,
3333
repo,
3434
busy,
35-
stateReason
36-
}: PullRequest) {
35+
stateReason,
36+
isCompact
37+
}: PullRequest & { isCompact?: boolean }) {
3738
const [currentTitle, setCurrentTitle] = useStateProp(title);
3839
const [inEditMode, setEditMode] = useState(false);
3940
const codingAgentEvent = mostRecentCopilotEvent(events);
@@ -51,9 +52,16 @@ export function Header({
5152
canEdit={canEdit}
5253
owner={owner}
5354
repo={repo}
55+
isCompact={isCompact}
5456
/>
55-
<Subtitle state={state} stateReason={stateReason} head={head} base={base} author={author} isIssue={isIssue} isDraft={isDraft} codingAgentEvent={codingAgentEvent} />
57+
{!isCompact && <Subtitle state={state} stateReason={stateReason} head={head} base={base} author={author} isIssue={isIssue} isDraft={isDraft} codingAgentEvent={codingAgentEvent} />}
5658
<div className="header-actions">
59+
{isCompact && (
60+
<div id="status" className={`status-badge-${getStatus(state, !!isDraft, isIssue, stateReason).color}`}>
61+
<span className='icon'>{getStatus(state, !!isDraft, isIssue, stateReason).icon}</span>
62+
<span>{getStatus(state, !!isDraft, isIssue, stateReason).text}</span>
63+
</div>
64+
)}
5765
<ButtonGroup
5866
isCurrentlyCheckedOut={isCurrentlyCheckedOut}
5967
isIssue={isIssue}
@@ -80,9 +88,10 @@ interface TitleProps {
8088
canEdit: boolean;
8189
owner: string;
8290
repo: string;
91+
isCompact?: boolean;
8392
}
8493

85-
function Title({ title, titleHTML, number, url, inEditMode, setEditMode, setCurrentTitle, canEdit, owner, repo }: TitleProps): JSX.Element {
94+
function Title({ title, titleHTML, number, url, inEditMode, setEditMode, setCurrentTitle, canEdit, owner, repo, isCompact }: TitleProps): JSX.Element {
8695
const { setTitle, copyPrLink } = useContext(PullRequestContext);
8796

8897
const titleForm = (
@@ -120,22 +129,24 @@ function Title({ title, titleHTML, number, url, inEditMode, setEditMode, setCurr
120129
context['github:copyMenu'] = true;
121130

122131
const displayTitle = (
123-
<div className="overview-title">
132+
<div className={`overview-title ${isCompact ? 'compact' : ''}`}>
124133
<h2>
125134
<span dangerouslySetInnerHTML={{ __html: titleHTML }} />
126135
{' '}
127136
<a href={url} title={url} data-vscode-context={JSON.stringify(context)}>
128137
#{number}
129138
</a>
130139
</h2>
131-
{canEdit ?
140+
{!isCompact && canEdit ?
132141
<button title="Rename" onClick={() => setEditMode(true)} className="icon-button">
133142
{editIcon}
134143
</button>
135144
: null}
136-
<button title="Copy Link" onClick={copyPrLink} className="icon-button" aria-label="Copy Pull Request Link">
137-
{copyIcon}
138-
</button>
145+
{!isCompact && (
146+
<button title="Copy Link" onClick={copyPrLink} className="icon-button" aria-label="Copy Pull Request Link">
147+
{copyIcon}
148+
</button>
149+
)}
139150
</div>
140151
);
141152

webviews/editorWebview/index.css

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,45 @@ textarea:focus,
5353
margin: 20px 0 24px;
5454
padding-bottom: 24px;
5555
border-bottom: 1px solid var(--vscode-list-inactiveSelectionBackground);
56+
transition: all 0.2s ease;
57+
}
58+
59+
.title.sticky {
60+
position: sticky;
61+
top: 0;
62+
z-index: 100;
63+
background: var(--vscode-editor-background);
64+
margin: 0;
65+
padding: 8px 0;
66+
border-bottom: 1px solid var(--vscode-panel-border);
67+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
68+
}
69+
70+
.title.sticky .details {
71+
width: 100%;
72+
}
73+
74+
.title.sticky .overview-title h2 {
75+
font-size: 18px;
76+
margin: 0;
77+
}
78+
79+
.title.sticky .header-actions {
80+
padding-top: 0;
81+
}
82+
83+
.overview-title.compact {
84+
display: flex;
85+
align-items: center;
86+
gap: 8px;
87+
}
88+
89+
.overview-title.compact h2 {
90+
font-size: 18px;
91+
margin: 0;
92+
white-space: nowrap;
93+
overflow: hidden;
94+
text-overflow: ellipsis;
5695
}
5796

5897
.title .pr-number {
@@ -563,6 +602,16 @@ small-button {
563602
display: flex;
564603
gap: 8px;
565604
padding-top: 4px;
605+
align-items: center;
606+
}
607+
608+
.title.sticky .header-actions {
609+
flex-wrap: nowrap;
610+
}
611+
612+
.title.sticky .header-actions #status {
613+
margin-right: 0;
614+
flex-shrink: 0;
566615
}
567616

568617
.header-actions>div:first-of-type {

webviews/editorWebview/overview.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,23 @@ const useMediaQuery = (query: string) => {
3131

3232
export const Overview = (pr: PullRequest) => {
3333
const isSingleColumnLayout = useMediaQuery('(max-width: 768px)');
34+
const [isSticky, setIsSticky] = React.useState(false);
35+
36+
React.useEffect(() => {
37+
const handleScroll = () => {
38+
// Make header sticky when scrolled past 80px
39+
const scrolled = window.scrollY > 80;
40+
setIsSticky(scrolled);
41+
};
42+
43+
window.addEventListener('scroll', handleScroll);
44+
return () => window.removeEventListener('scroll', handleScroll);
45+
}, []);
3446

3547
return <>
36-
<div id="title" className="title">
48+
<div id="title" className={`title ${isSticky ? 'sticky' : ''}`}>
3749
<div className="details">
38-
<Header {...pr} />
50+
<Header {...pr} isCompact={isSticky} />
3951
</div>
4052
</div>
4153
{isSingleColumnLayout ?

0 commit comments

Comments
 (0)