diff --git a/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.scss b/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.scss index 25eb1674dd..b9fdbb0a6f 100644 --- a/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.scss +++ b/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.scss @@ -43,6 +43,16 @@ &-mentionEmpty { padding: var(--bp-space-030) var(--bp-space-040); } + + // Layout-transparent hook that paints focus chrome onto the vendor card (CSS-modules-hashed, so match a substring). + &-threadRow { + display: contents; + + &.is-focused [class*='threadedAnnotations'] { + border-color: var(--bp-box-blue-80); + background-color: var(--bp-box-blue-opacity-04); + } + } } // Containing block for the absolutely positioned .bcs-NewActivityFeed child, diff --git a/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsx b/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsx index 9c0afc2ee1..66a4a91212 100644 --- a/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsx +++ b/src/elements/content-sidebar/activity-feed-v2/ActivityFeedV2.tsx @@ -417,6 +417,7 @@ const ActivityFeedV2 = ({ {filteredItems.map(item => ( { + const threadRowClassName = classNames('bcs-NewActivityFeed-threadRow', { + 'is-focused': item.id === activeFeedEntryId, + }); + switch (item.type) { case 'comment': { const { permissions } = item; @@ -145,27 +152,28 @@ const FeedItemRow = ({ ? { ...item.annotationTarget, timestamp: formatByTimeFormat(timestampMs, timeFormat, fps) } : item.annotationTarget; return ( - onCommentCopyLink({ id }) : undefined} - onDelete={handleDelete} - onEdit={handleEdit} - onEditError={logEditError} - onPost={buildReplyPost(item.id, FEED_ITEM_TYPE_COMMENT, isDisabled, onReplyCreate)} - onResolve={handleStatusChange('resolved')} - onThreadDelete={() => handleDelete(item.id)} - onUnresolve={handleStatusChange('open')} - resolvedAt={item.resolvedAt} - resolvedBy={item.resolvedBy} - userSelectorProps={userSelectorProps} - /> +
+ onCommentCopyLink({ id }) : undefined} + onDelete={handleDelete} + onEdit={handleEdit} + onEditError={logEditError} + onPost={buildReplyPost(item.id, FEED_ITEM_TYPE_COMMENT, isDisabled, onReplyCreate)} + onResolve={handleStatusChange('resolved')} + onThreadDelete={() => handleDelete(item.id)} + onUnresolve={handleStatusChange('open')} + resolvedAt={item.resolvedAt} + resolvedBy={item.resolvedBy} + userSelectorProps={userSelectorProps} + /> +
); } @@ -209,31 +217,32 @@ const FeedItemRow = ({ } : badgeTarget; return ( - onAnnotationSelect?.(item.annotation)} - onAvatarClick={noop} - onCopyLink={ - onAnnotationCopyLink && fileVersionId - ? () => onAnnotationCopyLink({ annotationId: item.id, fileVersionId }) - : undefined - } - onDelete={handleDelete} - onEdit={handleEdit} - onEditError={logEditError} - onPost={buildReplyPost(item.id, FEED_ITEM_TYPE_ANNOTATION, isDisabled, onReplyCreate)} - onResolve={handleStatusChange('resolved')} - onThreadDelete={() => handleDelete(item.id)} - onUnresolve={handleStatusChange('open')} - resolvedAt={item.resolvedAt} - resolvedBy={item.resolvedBy} - userSelectorProps={userSelectorProps} - /> +
+ onAnnotationSelect?.(item.annotation)} + onAvatarClick={noop} + onCopyLink={ + onAnnotationCopyLink && fileVersionId + ? () => onAnnotationCopyLink({ annotationId: item.id, fileVersionId }) + : undefined + } + onDelete={handleDelete} + onEdit={handleEdit} + onEditError={logEditError} + onPost={buildReplyPost(item.id, FEED_ITEM_TYPE_ANNOTATION, isDisabled, onReplyCreate)} + onResolve={handleStatusChange('resolved')} + onThreadDelete={() => handleDelete(item.id)} + onUnresolve={handleStatusChange('open')} + resolvedAt={item.resolvedAt} + resolvedBy={item.resolvedBy} + userSelectorProps={userSelectorProps} + /> +
); } diff --git a/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx b/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx index 18392fb715..f5bfa4f392 100644 --- a/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx +++ b/src/elements/content-sidebar/activity-feed-v2/__tests__/FeedItemRow.test.tsx @@ -1033,4 +1033,40 @@ describe('elements/content-sidebar/activity-feed-v2/FeedItemRow', () => { }); }); }); + + describe('focus state', () => { + const getThreadRow = () => screen.getByRole('article', { name: 'threaded annotation' }).parentElement; + + test('should wrap a comment thread in a threadRow div without breaking rendering', () => { + render(); + + expect(screen.getByRole('article', { name: 'threaded annotation' })).toBeVisible(); + expect(getThreadRow()).toHaveClass('bcs-NewActivityFeed-threadRow'); + }); + + test('should mark the comment row focused when activeFeedEntryId matches the item id', () => { + render(); + expect(getThreadRow()).toHaveClass('is-focused'); + }); + + test('should not mark the comment row focused when activeFeedEntryId does not match', () => { + render(); + expect(getThreadRow()).not.toHaveClass('is-focused'); + }); + + test('should not mark the comment row focused when activeFeedEntryId is undefined', () => { + render(); + expect(getThreadRow()).not.toHaveClass('is-focused'); + }); + + test('should mark the annotation row focused when activeFeedEntryId matches the item id', () => { + render(); + expect(getThreadRow()).toHaveClass('bcs-NewActivityFeed-threadRow', 'is-focused'); + }); + + test('should not mark the annotation row focused when activeFeedEntryId does not match', () => { + render(); + expect(getThreadRow()).not.toHaveClass('is-focused'); + }); + }); });