feat: Created shared TagList component with max-4 overflow, applied acr…
- "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
This commit is contained in:
parent
caa55381ab
commit
adc86446f1
11 changed files with 200 additions and 21 deletions
|
|
@ -57,7 +57,7 @@
|
|||
- Estimate: 20m
|
||||
- Files: frontend/src/pages/CreatorDetail.tsx, frontend/src/App.css
|
||||
- Verify: cd frontend && npx tsc --noEmit && npm run build
|
||||
- [ ] **T03: TagList component, tag overflow limit, and empty subtopic handling** — Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028).
|
||||
- [x] **T03: Created shared TagList component with max-4 overflow, applied across all 5 tag sites, and added empty subtopic Coming soon badge** — Create a shared TagList component for tag overflow (R027), apply it across all 5 tag-rendering sites, and add empty subtopic handling in TopicsBrowse (R028).
|
||||
|
||||
## Steps
|
||||
|
||||
|
|
|
|||
30
.gsd/milestones/M011/slices/S02/tasks/T02-VERIFY.json
Normal file
30
.gsd/milestones/M011/slices/S02/tasks/T02-VERIFY.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"schemaVersion": 1,
|
||||
"taskId": "T02",
|
||||
"unitId": "M011/S02/T02",
|
||||
"timestamp": 1774945929488,
|
||||
"passed": false,
|
||||
"discoverySource": "task-plan",
|
||||
"checks": [
|
||||
{
|
||||
"command": "cd frontend",
|
||||
"exitCode": 0,
|
||||
"durationMs": 7,
|
||||
"verdict": "pass"
|
||||
},
|
||||
{
|
||||
"command": "npx tsc --noEmit",
|
||||
"exitCode": 1,
|
||||
"durationMs": 762,
|
||||
"verdict": "fail"
|
||||
},
|
||||
{
|
||||
"command": "npm run build",
|
||||
"exitCode": 254,
|
||||
"durationMs": 89,
|
||||
"verdict": "fail"
|
||||
}
|
||||
],
|
||||
"retryAttempt": 1,
|
||||
"maxRetries": 2
|
||||
}
|
||||
87
.gsd/milestones/M011/slices/S02/tasks/T03-SUMMARY.md
Normal file
87
.gsd/milestones/M011/slices/S02/tasks/T03-SUMMARY.md
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
id: T03
|
||||
parent: S02
|
||||
milestone: M011
|
||||
provides: []
|
||||
requires: []
|
||||
affects: []
|
||||
key_files: ["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"]
|
||||
key_decisions: ["Added pillClass prop to TagList so Home.tsx can pass pill--tag while other pages use bare pill class"]
|
||||
patterns_established: []
|
||||
drill_down_paths: []
|
||||
observability_surfaces: []
|
||||
duration: ""
|
||||
verification_result: "TypeScript compilation (tsc --noEmit) passed with zero errors. Production build (npm run build) succeeded — 51 modules transformed, built in 827ms."
|
||||
completed_at: 2026-03-31T08:34:55.792Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T03: Created shared TagList component with max-4 overflow, applied across all 5 tag sites, and added empty subtopic Coming soon badge
|
||||
|
||||
> Created shared TagList component with max-4 overflow, applied across all 5 tag sites, and added empty subtopic Coming soon badge
|
||||
|
||||
## What Happened
|
||||
---
|
||||
id: T03
|
||||
parent: S02
|
||||
milestone: M011
|
||||
key_files:
|
||||
- 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
|
||||
key_decisions:
|
||||
- Added pillClass prop to TagList so Home.tsx can pass pill--tag while other pages use bare pill class
|
||||
duration: ""
|
||||
verification_result: passed
|
||||
completed_at: 2026-03-31T08:34:55.792Z
|
||||
blocker_discovered: false
|
||||
---
|
||||
|
||||
# T03: Created shared TagList component with max-4 overflow, applied across all 5 tag sites, and added empty subtopic Coming soon badge
|
||||
|
||||
**Created shared TagList component with max-4 overflow, applied across all 5 tag sites, and added empty subtopic Coming soon badge**
|
||||
|
||||
## What Happened
|
||||
|
||||
Created TagList component with configurable max and pillClass props. Replaced all 5 inline topic_tags.map() calls across Home, SearchResults, SubTopicPage, CreatorDetail with the shared component. Added empty subtopic handling in TopicsBrowse — technique_count === 0 renders a non-clickable span with Coming soon pill instead of a Link. Added pill--overflow, pill--coming-soon, and topic-subtopic--empty CSS.
|
||||
|
||||
## Verification
|
||||
|
||||
TypeScript compilation (tsc --noEmit) passed with zero errors. Production build (npm run build) succeeded — 51 modules transformed, built in 827ms.
|
||||
|
||||
## Verification Evidence
|
||||
|
||||
| # | Command | Exit Code | Verdict | Duration |
|
||||
|---|---------|-----------|---------|----------|
|
||||
| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 3600ms |
|
||||
| 2 | `cd frontend && npm run build` | 0 | ✅ pass | 2900ms |
|
||||
|
||||
|
||||
## Deviations
|
||||
|
||||
Added pillClass prop to TagList (not in plan) to preserve existing pill--tag class used in Home.tsx.
|
||||
|
||||
## Known Issues
|
||||
|
||||
None.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `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`
|
||||
|
||||
|
||||
## Deviations
|
||||
Added pillClass prop to TagList (not in plan) to preserve existing pill--tag class used in Home.tsx.
|
||||
|
||||
## Known Issues
|
||||
None.
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
33
frontend/src/components/TagList.tsx
Normal file
33
frontend/src/components/TagList.tsx
Normal file
|
|
@ -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) => (
|
||||
<span key={tag} className={cls}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
{overflow > 0 && (
|
||||
<span className="pill pill--overflow">+{overflow} more</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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() {
|
|||
</span>
|
||||
{t.topic_tags && t.topic_tags.length > 0 && (
|
||||
<span className="creator-technique-card__tags">
|
||||
{t.topic_tags.map((tag) => (
|
||||
<span key={tag} className="pill">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
<TagList tags={t.topic_tags} />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<span className="badge badge--category">{featured.topic_category}</span>
|
||||
)}
|
||||
{featured.topic_tags && featured.topic_tags.length > 0 && featured.topic_tags.map(tag => (
|
||||
<span key={tag} className="pill pill--tag">{tag}</span>
|
||||
))}
|
||||
{featured.topic_tags && featured.topic_tags.length > 0 && (
|
||||
<TagList tags={featured.topic_tags} pillClass="pill--tag" />
|
||||
)}
|
||||
{featured.key_moment_count > 0 && (
|
||||
<span className="home-featured__moments">
|
||||
{featured.key_moment_count} moment{featured.key_moment_count !== 1 ? "s" : ""}
|
||||
|
|
@ -244,9 +245,9 @@ export default function Home() {
|
|||
<span className="badge badge--category">
|
||||
{t.topic_category}
|
||||
</span>
|
||||
{t.topic_tags && t.topic_tags.length > 0 && t.topic_tags.map(tag => (
|
||||
<span key={tag} className="pill pill--tag">{tag}</span>
|
||||
))}
|
||||
{t.topic_tags && t.topic_tags.length > 0 && (
|
||||
<TagList tags={t.topic_tags} pillClass="pill--tag" />
|
||||
)}
|
||||
{t.summary && (
|
||||
<span className="recent-card__summary">
|
||||
{t.summary.length > 150
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<span className="search-result-card__tags">
|
||||
{item.topic_tags.map((tag) => (
|
||||
<span key={tag} className="pill">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
<TagList tags={item.topic_tags} />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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() {
|
|||
<span className="subtopic-technique-card__title">{t.title}</span>
|
||||
{t.topic_tags && t.topic_tags.length > 0 && (
|
||||
<span className="subtopic-technique-card__tags">
|
||||
{t.topic_tags.map((tag) => (
|
||||
<span key={tag} className="pill">{tag}</span>
|
||||
))}
|
||||
<TagList tags={t.topic_tags} />
|
||||
</span>
|
||||
)}
|
||||
{t.summary && (
|
||||
|
|
|
|||
|
|
@ -155,6 +155,19 @@ export default function TopicsBrowse() {
|
|||
<div className="topic-subtopics">
|
||||
{cat.sub_topics.map((st) => {
|
||||
const stSlug = st.name.toLowerCase().replace(/\s+/g, "-");
|
||||
if (st.technique_count === 0) {
|
||||
return (
|
||||
<span
|
||||
key={st.name}
|
||||
className="topic-subtopic topic-subtopic--empty"
|
||||
>
|
||||
<span className="topic-subtopic__name">{st.name}</span>
|
||||
<span className="topic-subtopic__counts">
|
||||
<span className="pill pill--coming-soon">Coming soon</span>
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
key={st.name}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/CategoryIcons.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/pages/About.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/utils/catSlug.ts"],"version":"5.6.3"}
|
||||
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/public-client.ts","./src/components/AdminDropdown.tsx","./src/components/AppFooter.tsx","./src/components/CategoryIcons.tsx","./src/components/CopyLinkButton.tsx","./src/components/CreatorAvatar.tsx","./src/components/ReportIssueModal.tsx","./src/components/SearchAutocomplete.tsx","./src/components/TagList.tsx","./src/pages/About.tsx","./src/pages/AdminPipeline.tsx","./src/pages/AdminReports.tsx","./src/pages/CreatorDetail.tsx","./src/pages/CreatorsBrowse.tsx","./src/pages/Home.tsx","./src/pages/SearchResults.tsx","./src/pages/SubTopicPage.tsx","./src/pages/TechniquePage.tsx","./src/pages/TopicsBrowse.tsx","./src/utils/catSlug.ts"],"version":"5.6.3"}
|
||||
Loading…
Add table
Reference in a new issue