From fa1fc82d5a288d116cb1c719c71b4598df1a32bf Mon Sep 17 00:00:00 2001 From: jlightner Date: Tue, 31 Mar 2026 08:35:07 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Created=20shared=20TagList=20component?= =?UTF-8?q?=20with=20max-4=20overflow,=20applied=20acr=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "frontend/src/components/TagList.tsx" - "frontend/src/pages/Home.tsx" - "frontend/src/pages/SearchResults.tsx" - "frontend/src/pages/SubTopicPage.tsx" - "frontend/src/pages/CreatorDetail.tsx" - "frontend/src/pages/TopicsBrowse.tsx" - "frontend/src/App.css" GSD-Task: S02/T03 --- frontend/src/App.css | 22 +++++++++++++++++++ frontend/src/components/TagList.tsx | 33 ++++++++++++++++++++++++++++ frontend/src/pages/CreatorDetail.tsx | 7 ++---- frontend/src/pages/Home.tsx | 13 ++++++----- frontend/src/pages/SearchResults.tsx | 7 ++---- frontend/src/pages/SubTopicPage.tsx | 5 ++--- frontend/src/pages/TopicsBrowse.tsx | 13 +++++++++++ frontend/tsconfig.app.tsbuildinfo | 2 +- 8 files changed, 82 insertions(+), 20 deletions(-) create mode 100644 frontend/src/components/TagList.tsx diff --git a/frontend/src/App.css b/frontend/src/App.css index c8bceb0..3ca4eb4 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -1456,6 +1456,19 @@ a.app-footer__repo:hover { color: var(--color-pill-plugin-text); } +.pill--overflow { + background: var(--color-surface-2); + color: var(--color-text-secondary); + font-style: italic; +} + +.pill--coming-soon { + font-size: 0.65rem; + background: var(--color-surface-2); + color: var(--color-text-secondary); + font-style: italic; +} + .pill-list { display: flex; flex-wrap: wrap; @@ -2353,6 +2366,15 @@ a.app-footer__repo:hover { background: var(--color-bg-surface-hover); } +.topic-subtopic--empty { + opacity: 0.5; + cursor: default; +} + +.topic-subtopic--empty:hover { + background: transparent; +} + .topic-subtopic + .topic-subtopic { border-top: 1px solid var(--color-bg-surface-hover); } diff --git a/frontend/src/components/TagList.tsx b/frontend/src/components/TagList.tsx new file mode 100644 index 0000000..b83121b --- /dev/null +++ b/frontend/src/components/TagList.tsx @@ -0,0 +1,33 @@ +/** + * Shared tag list with overflow indicator. + * + * Renders up to `max` tag pills (default 4), plus a "+N more" pill + * when the list exceeds the limit. Used across cards and detail pages + * to keep tag displays compact and consistent (R027). + */ + +interface TagListProps { + tags: string[]; + max?: number; + /** Extra CSS class added to each pill (e.g. "pill--tag") */ + pillClass?: string; +} + +export default function TagList({ tags, max = 4, pillClass }: TagListProps) { + const visible = tags.slice(0, max); + const overflow = tags.length - max; + const cls = pillClass ? `pill ${pillClass}` : "pill"; + + return ( + <> + {visible.map((tag) => ( + + {tag} + + ))} + {overflow > 0 && ( + +{overflow} more + )} + + ); +} diff --git a/frontend/src/pages/CreatorDetail.tsx b/frontend/src/pages/CreatorDetail.tsx index 98acd53..ced4bca 100644 --- a/frontend/src/pages/CreatorDetail.tsx +++ b/frontend/src/pages/CreatorDetail.tsx @@ -15,6 +15,7 @@ import { } from "../api/public-client"; import CreatorAvatar from "../components/CreatorAvatar"; import { catSlug } from "../utils/catSlug"; +import TagList from "../components/TagList"; export default function CreatorDetail() { const { slug } = useParams<{ slug: string }>(); @@ -156,11 +157,7 @@ export default function CreatorDetail() { {t.topic_tags && t.topic_tags.length > 0 && ( - {t.topic_tags.map((tag) => ( - - {tag} - - ))} + )} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 0f6e642..319f3a7 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -7,6 +7,7 @@ import { IconTopics, IconCreators } from "../components/CategoryIcons"; import SearchAutocomplete from "../components/SearchAutocomplete"; +import TagList from "../components/TagList"; import { useEffect, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { @@ -198,9 +199,9 @@ export default function Home() { {featured.topic_category && ( {featured.topic_category} )} - {featured.topic_tags && featured.topic_tags.length > 0 && featured.topic_tags.map(tag => ( - {tag} - ))} + {featured.topic_tags && featured.topic_tags.length > 0 && ( + + )} {featured.key_moment_count > 0 && ( {featured.key_moment_count} moment{featured.key_moment_count !== 1 ? "s" : ""} @@ -244,9 +245,9 @@ export default function Home() { {t.topic_category} - {t.topic_tags && t.topic_tags.length > 0 && t.topic_tags.map(tag => ( - {tag} - ))} + {t.topic_tags && t.topic_tags.length > 0 && ( + + )} {t.summary && ( {t.summary.length > 150 diff --git a/frontend/src/pages/SearchResults.tsx b/frontend/src/pages/SearchResults.tsx index 85ff179..27777d1 100644 --- a/frontend/src/pages/SearchResults.tsx +++ b/frontend/src/pages/SearchResults.tsx @@ -11,6 +11,7 @@ import { Link, useSearchParams, useNavigate } from "react-router-dom"; import { searchApi, type SearchResultItem } from "../api/public-client"; import { catSlug } from "../utils/catSlug"; import SearchAutocomplete from "../components/SearchAutocomplete"; +import TagList from "../components/TagList"; export default function SearchResults() { const [searchParams] = useSearchParams(); @@ -142,11 +143,7 @@ function SearchResultCard({ item, staggerIndex }: { item: SearchResultItem; stag )} {item.topic_tags.length > 0 && ( - {item.topic_tags.map((tag) => ( - - {tag} - - ))} + )} diff --git a/frontend/src/pages/SubTopicPage.tsx b/frontend/src/pages/SubTopicPage.tsx index c4667b9..81f4cae 100644 --- a/frontend/src/pages/SubTopicPage.tsx +++ b/frontend/src/pages/SubTopicPage.tsx @@ -12,6 +12,7 @@ import { type TechniqueListItem, } from "../api/public-client"; import { catSlug } from "../utils/catSlug"; +import TagList from "../components/TagList"; /** Convert a URL slug to a display name: replace hyphens with spaces, title-case. */ function slugToDisplayName(slug: string): string { @@ -146,9 +147,7 @@ export default function SubTopicPage() { {t.title} {t.topic_tags && t.topic_tags.length > 0 && ( - {t.topic_tags.map((tag) => ( - {tag} - ))} + )} {t.summary && ( diff --git a/frontend/src/pages/TopicsBrowse.tsx b/frontend/src/pages/TopicsBrowse.tsx index 97a5ca1..8543df8 100644 --- a/frontend/src/pages/TopicsBrowse.tsx +++ b/frontend/src/pages/TopicsBrowse.tsx @@ -155,6 +155,19 @@ export default function TopicsBrowse() {
{cat.sub_topics.map((st) => { const stSlug = st.name.toLowerCase().replace(/\s+/g, "-"); + if (st.technique_count === 0) { + return ( + + {st.name} + + Coming soon + + + ); + } return (