Skip to content

Commit 0e3667a

Browse files
tsahimatsliahclaude
andcommitted
refactor(explore): flat trending/popular/recent lists above the directory
Move the trending, popular, and recently-added lists up to sit between the hero separator and the category directory, and render them flat as link columns (same treatment as the categories) instead of bordered leaderboard cards. A single border divider separates the featured lists from the full directory. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent aa8315e commit 0e3667a

2 files changed

Lines changed: 78 additions & 43 deletions

File tree

packages/shared/src/components/explore/ExploreTopicsPage.spec.tsx

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { render, screen } from '@testing-library/react';
2+
import { render, screen, within } from '@testing-library/react';
33
import { QueryClient } from '@tanstack/react-query';
44
import { TestBootProvider } from '../../../__tests__/helpers/boot';
55
import { ExploreTopicsPage } from './ExploreTopicsPage';
@@ -45,22 +45,45 @@ describe('ExploreTopicsPage', () => {
4545
).toBeInTheDocument();
4646
});
4747

48+
const getCategorySection = (): HTMLElement => {
49+
const section = screen
50+
.getByRole('heading', { name: /Web Development/ })
51+
.closest('section');
52+
if (!section) {
53+
throw new Error('category section not found');
54+
}
55+
return section;
56+
};
57+
4858
it('should render category sections with their tags', () => {
4959
renderComponent();
5060

51-
expect(
52-
screen.getByRole('heading', { name: /Web Development/ }),
53-
).toBeInTheDocument();
54-
expect(screen.getByText('React')).toBeInTheDocument();
55-
expect(screen.getByText('Vue')).toBeInTheDocument();
61+
const section = getCategorySection();
62+
expect(within(section).getByText('React')).toBeInTheDocument();
63+
expect(within(section).getByText('Vue')).toBeInTheDocument();
5664
});
5765

5866
it('should link topic entries to the explore topic page', () => {
5967
renderComponent();
6068

61-
expect(screen.getByText('React').closest('a')).toHaveAttribute(
69+
const section = getCategorySection();
70+
expect(within(section).getByText('React').closest('a')).toHaveAttribute(
6271
'href',
6372
expect.stringContaining('explore/react'),
6473
);
6574
});
75+
76+
it('should render the trending / popular / recently added lists', () => {
77+
renderComponent();
78+
79+
expect(
80+
screen.getByRole('heading', { name: /Trending tags/ }),
81+
).toBeInTheDocument();
82+
expect(
83+
screen.getByRole('heading', { name: /Popular tags/ }),
84+
).toBeInTheDocument();
85+
expect(
86+
screen.getByRole('heading', { name: /Recently added tags/ }),
87+
).toBeInTheDocument();
88+
});
6689
});

packages/shared/src/components/explore/ExploreTopicsPage.tsx

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import React, { useMemo } from 'react';
33
import type { Keyword } from '../../graphql/keywords';
44
import type { TagCategory } from '../../graphql/feedSettings';
55
import useFeedSettings from '../../hooks/useFeedSettings';
6-
import { TagTopList } from '../cards/Leaderboard/TagTopList';
76
import { ExploreCategorySection } from './ExploreCategorySection';
87
import { ExploreTopicSearch } from './ExploreTopicSearch';
98
import { useChipBarNavigation } from './useChipBarNavigation';
@@ -19,7 +18,6 @@ interface ExploreTopicsPageProps {
1918
trendingTags: Keyword[];
2019
popularTags: Keyword[];
2120
tagsCategories: TagCategory[];
22-
isLoading?: boolean;
2321
}
2422

2523
const scrollToCategory = (id: string): void => {
@@ -28,12 +26,14 @@ const scrollToCategory = (id: string): void => {
2826
?.scrollIntoView({ behavior: 'smooth', block: 'start' });
2927
};
3028

29+
const toTagValues = (items?: Keyword[]): string[] =>
30+
items?.map((item) => item.value).filter(Boolean) ?? [];
31+
3132
export function ExploreTopicsPage({
3233
tags,
3334
trendingTags,
3435
popularTags,
3536
tagsCategories,
36-
isLoading = false,
3737
}: ExploreTopicsPageProps): ReactElement {
3838
const { feedSettings } = useFeedSettings();
3939
const { ref: categoryNavRef, onKeyDown: onCategoryNavKeyDown } =
@@ -59,6 +59,37 @@ export function ExploreTopicsPage({
5959
[tags],
6060
);
6161

62+
// The trending / popular / recently-added lists, shaped like categories so
63+
// they render with the same flat column treatment as the directory below.
64+
const featuredLists = useMemo<TagCategory[]>(() => {
65+
const lists: TagCategory[] = [];
66+
if (trendingTags?.length) {
67+
lists.push({
68+
id: 'trending-tags',
69+
title: 'Trending tags',
70+
emoji: '🔥',
71+
tags: toTagValues(trendingTags),
72+
});
73+
}
74+
if (popularTags?.length) {
75+
lists.push({
76+
id: 'popular-tags',
77+
title: 'Popular tags',
78+
emoji: '⭐',
79+
tags: toTagValues(popularTags),
80+
});
81+
}
82+
if (recentlyAddedTags?.length) {
83+
lists.push({
84+
id: 'recently-added-tags',
85+
title: 'Recently added tags',
86+
emoji: '🆕',
87+
tags: toTagValues(recentlyAddedTags),
88+
});
89+
}
90+
return lists;
91+
}, [trendingTags, popularTags, recentlyAddedTags]);
92+
6293
const recommendedTags = useMemo(
6394
() => popularTags?.slice(0, 5).map((tag) => tag.value) ?? [],
6495
[popularTags],
@@ -119,6 +150,20 @@ export function ExploreTopicsPage({
119150

120151
<div className="my-10 h-px w-full bg-border-subtlest-tertiary" />
121152

153+
{/* Featured — trending / popular / recently added, flat link columns. */}
154+
{featuredLists.length > 0 && (
155+
<div className="grid w-full grid-cols-1 gap-x-10 tablet:grid-cols-2 laptop:grid-cols-3">
156+
{featuredLists.map((list) => (
157+
<ExploreCategorySection key={list.id} category={list} />
158+
))}
159+
</div>
160+
)}
161+
162+
{/* Border separating the featured lists from the full directory. */}
163+
{featuredLists.length > 0 && categories.length > 0 && (
164+
<div className="mb-10 mt-2 h-px w-full bg-border-subtlest-tertiary" />
165+
)}
166+
122167
{/* Directory — categories as columns of topic links. */}
123168
{categories.length > 0 && (
124169
<div className="w-full columns-1 gap-x-10 tablet:columns-2 laptop:columns-3">
@@ -127,39 +172,6 @@ export function ExploreTopicsPage({
127172
))}
128173
</div>
129174
)}
130-
131-
{/* Featured leaderboards */}
132-
<section className="mt-6 w-full">
133-
<Typography
134-
tag={TypographyTag.H2}
135-
type={TypographyType.Title2}
136-
color={TypographyColor.Primary}
137-
bold
138-
className="mb-6 text-center"
139-
>
140-
Trending on daily.dev
141-
</Typography>
142-
<div className="grid auto-rows-fr grid-cols-1 gap-0 tablet:grid-cols-2 tablet:gap-6 laptopL:grid-cols-3">
143-
<TagTopList
144-
containerProps={{ title: 'Trending tags' }}
145-
items={trendingTags}
146-
isLoading={isLoading}
147-
/>
148-
<TagTopList
149-
containerProps={{ title: 'Popular tags' }}
150-
items={popularTags}
151-
isLoading={isLoading}
152-
/>
153-
<TagTopList
154-
containerProps={{
155-
title: 'Recently added tags',
156-
className: 'col-span-1 tablet:col-span-2 laptopL:col-span-1',
157-
}}
158-
items={recentlyAddedTags}
159-
isLoading={isLoading}
160-
/>
161-
</div>
162-
</section>
163175
</div>
164176
);
165177
}

0 commit comments

Comments
 (0)