Skip to content

Commit 39b253f

Browse files
committed
fix(desktop): restore sticky date divider handoff
Group timeline rows by day so sticky date pills push each other during scroll, while keeping the divider line on non-sticky day sections.
1 parent b2bf471 commit 39b253f

2 files changed

Lines changed: 88 additions & 34 deletions

File tree

desktop/src/features/messages/ui/DayDivider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export function DayDivider({ label }: { label: string }) {
22
return (
33
<section
44
aria-label={label}
5-
className="sticky top-[92px] z-[5] flex justify-center py-1 before:absolute before:inset-x-0 before:top-1/2 before:h-px before:-translate-y-1/2 before:bg-border/35 before:content-['']"
5+
className="sticky top-(--buzz-channel-content-top-padding,5.75rem) z-[5] flex justify-center"
66
data-testid="message-timeline-day-divider"
77
data-day-label={label}
88
>

desktop/src/features/messages/ui/TimelineMessageList.tsx

Lines changed: 87 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ type TimelineRenderRow =
9292
| TimelineUnreadRow
9393
| TimelineMessageRowModel;
9494

95+
type TimelineNonDayRow = TimelineUnreadRow | TimelineMessageRowModel;
96+
97+
type TimelineDayGroup = {
98+
key: string;
99+
label: string;
100+
rows: TimelineNonDayRow[];
101+
};
102+
95103
function buildTimelineRenderRows({
96104
firstUnreadMessageId,
97105
messages,
@@ -139,6 +147,36 @@ function buildTimelineRenderRows({
139147
return rows;
140148
}
141149

150+
function buildTimelineDayGroups(rows: TimelineRenderRow[]): TimelineDayGroup[] {
151+
const groups: TimelineDayGroup[] = [];
152+
let currentGroup: TimelineDayGroup | null = null;
153+
154+
for (const row of rows) {
155+
if (row.type === "day") {
156+
currentGroup = {
157+
key: row.key,
158+
label: row.label,
159+
rows: [],
160+
};
161+
groups.push(currentGroup);
162+
continue;
163+
}
164+
165+
if (!currentGroup) {
166+
currentGroup = {
167+
key: "day-undated",
168+
label: "",
169+
rows: [],
170+
};
171+
groups.push(currentGroup);
172+
}
173+
174+
currentGroup.rows.push(row);
175+
}
176+
177+
return groups;
178+
}
179+
142180
export const TimelineMessageList = React.memo(function TimelineMessageList({
143181
agentPubkeys,
144182
channelId,
@@ -169,39 +207,55 @@ export const TimelineMessageList = React.memo(function TimelineMessageList({
169207
() => buildTimelineRenderRows({ firstUnreadMessageId, messages }),
170208
[firstUnreadMessageId, messages],
171209
);
172-
return rows.map((row) => (
173-
<TimelineRenderRowView
174-
agentPubkeys={agentPubkeys}
175-
allMessages={
176-
row.type === "message" && hasVideoAttachment(row.message)
177-
? messages
178-
: undefined
179-
}
180-
channelId={channelId}
181-
channelName={channelName}
182-
channelType={channelType}
183-
currentPubkey={currentPubkey}
184-
followThreadById={followThreadById}
185-
highlightedMessageId={highlightedMessageId}
186-
isFollowingThreadById={isFollowingThreadById}
187-
isSendingVideoReviewComment={isSendingVideoReviewComment}
188-
key={row.key}
189-
messageFooters={messageFooters}
190-
onDelete={onDelete}
191-
onEdit={onEdit}
192-
onMarkUnread={onMarkUnread}
193-
onReply={onReply}
194-
onSendVideoReviewComment={onSendVideoReviewComment}
195-
onToggleReaction={onToggleReaction}
196-
profiles={profiles}
197-
row={row}
198-
searchActiveMessageId={searchActiveMessageId}
199-
searchMatchingMessageIds={searchMatchingMessageIds}
200-
searchQuery={searchQuery}
201-
threadUnreadCounts={threadUnreadCounts}
202-
unfollowThreadById={unfollowThreadById}
203-
/>
204-
));
210+
const dayGroups = React.useMemo(() => buildTimelineDayGroups(rows), [rows]);
211+
212+
return (
213+
<div className="flex flex-col gap-0">
214+
{dayGroups.map((group) => (
215+
<section
216+
className="relative flex flex-col gap-2 before:absolute before:inset-x-0 before:top-4 before:h-px before:bg-border/35 before:content-['']"
217+
data-day-label={group.label}
218+
data-testid="message-timeline-day-group"
219+
key={group.key}
220+
>
221+
{group.label ? <DayDivider label={group.label} /> : null}
222+
{group.rows.map((row) => (
223+
<TimelineRenderRowView
224+
agentPubkeys={agentPubkeys}
225+
allMessages={
226+
row.type === "message" && hasVideoAttachment(row.message)
227+
? messages
228+
: undefined
229+
}
230+
channelId={channelId}
231+
channelName={channelName}
232+
channelType={channelType}
233+
currentPubkey={currentPubkey}
234+
followThreadById={followThreadById}
235+
highlightedMessageId={highlightedMessageId}
236+
isFollowingThreadById={isFollowingThreadById}
237+
isSendingVideoReviewComment={isSendingVideoReviewComment}
238+
key={row.key}
239+
messageFooters={messageFooters}
240+
onDelete={onDelete}
241+
onEdit={onEdit}
242+
onMarkUnread={onMarkUnread}
243+
onReply={onReply}
244+
onSendVideoReviewComment={onSendVideoReviewComment}
245+
onToggleReaction={onToggleReaction}
246+
profiles={profiles}
247+
row={row}
248+
searchActiveMessageId={searchActiveMessageId}
249+
searchMatchingMessageIds={searchMatchingMessageIds}
250+
searchQuery={searchQuery}
251+
threadUnreadCounts={threadUnreadCounts}
252+
unfollowThreadById={unfollowThreadById}
253+
/>
254+
))}
255+
</section>
256+
))}
257+
</div>
258+
);
205259
});
206260

207261
type TimelineRenderRowViewProps = Omit<

0 commit comments

Comments
 (0)