|
| 1 | +--- |
| 2 | +/** |
| 3 | + * `TagPills.astro` | Clickable Brand Logo Tag Pills for Cards & Detail Headers. |
| 4 | + * |
| 5 | + * Resolves a document's Sanity `tags[]` + title/excerpt matches into canonical |
| 6 | + * `TopicDefinition` entries with Light Mode (`symbolLight`) and Dark Mode (`symbolDark`) |
| 7 | + * brand SVG icons. |
| 8 | + */ |
| 9 | +import { resolveItemTopics, type SanityTagItem } from "@/lib/topics"; |
| 10 | +
|
| 11 | +interface Props { |
| 12 | + title?: string | null; |
| 13 | + excerpt?: string | null; |
| 14 | + tags?: SanityTagItem[] | null; |
| 15 | + /** Maximum number of logo tag pills to render (omit on detail pages to show all). */ |
| 16 | + maxCount?: number; |
| 17 | + size?: "sm" | "md"; |
| 18 | + /** Optional href for the "+N more" overflow pill on cards. */ |
| 19 | + overflowHref?: string; |
| 20 | + /** Optional data attribute so in-page filter scripts can intercept clicks if desired. */ |
| 21 | + inPageFilter?: boolean; |
| 22 | + /** Render as "span" when placed inside an outer <a> element to avoid nested links. */ |
| 23 | + as?: "a" | "span"; |
| 24 | +} |
| 25 | +
|
| 26 | +const { |
| 27 | + title, |
| 28 | + excerpt, |
| 29 | + tags, |
| 30 | + maxCount, |
| 31 | + size = "sm", |
| 32 | + overflowHref, |
| 33 | + inPageFilter = false, |
| 34 | + as: TagEl = "a", |
| 35 | +} = Astro.props; |
| 36 | +
|
| 37 | +const { visible, remainingCount } = resolveItemTopics( |
| 38 | + { title, excerpt, tags }, |
| 39 | + maxCount, |
| 40 | +); |
| 41 | +
|
| 42 | +const isSmall = size === "sm"; |
| 43 | +--- |
| 44 | + |
| 45 | +{ |
| 46 | + visible.length > 0 && ( |
| 47 | + <> |
| 48 | + {visible.map((topic) => ( |
| 49 | + <TagEl |
| 50 | + href={TagEl === "a" ? `/topic/${topic.slug}` : undefined} |
| 51 | + data-tag-pill={inPageFilter ? topic.slug : undefined} |
| 52 | + data-tag-title={inPageFilter ? topic.title : undefined} |
| 53 | + class:list={[ |
| 54 | + "inline-flex items-center rounded-full border border-[#2A1155] bg-white font-mono font-bold text-[#2A1155] transition-all hover:bg-[#EDE9FE] hover:text-[#7C3AED] dark:border-white/25 dark:bg-[#0B1326] dark:text-slate-100 dark:hover:bg-purple-950/60 dark:hover:text-[#C084FC]", |
| 55 | + TagEl === "a" && "hover:-translate-y-0.5", |
| 56 | + isSmall |
| 57 | + ? "gap-1.5 px-2.5 py-0.5 text-[11px]" |
| 58 | + : "gap-2 border-2 px-3.5 py-1 font-display text-xs font-black shadow-sketch-sm", |
| 59 | + ]} |
| 60 | + > |
| 61 | + {topic.symbolLight && ( |
| 62 | + <img |
| 63 | + src={topic.symbolLight} |
| 64 | + alt="" |
| 65 | + aria-hidden="true" |
| 66 | + class:list={[ |
| 67 | + "w-auto shrink-0 object-contain dark:hidden", |
| 68 | + isSmall ? "h-3" : "h-3.5", |
| 69 | + ]} |
| 70 | + loading="lazy" |
| 71 | + decoding="async" |
| 72 | + /> |
| 73 | + )} |
| 74 | + {topic.symbolDark && ( |
| 75 | + <img |
| 76 | + src={topic.symbolDark} |
| 77 | + alt="" |
| 78 | + aria-hidden="true" |
| 79 | + class:list={[ |
| 80 | + "hidden w-auto shrink-0 object-contain dark:block", |
| 81 | + isSmall ? "h-3" : "h-3.5", |
| 82 | + ]} |
| 83 | + loading="lazy" |
| 84 | + decoding="async" |
| 85 | + /> |
| 86 | + )} |
| 87 | + <span>{topic.title}</span> |
| 88 | + </TagEl> |
| 89 | + ))} |
| 90 | + {remainingCount > 0 && ( |
| 91 | + <TagEl |
| 92 | + href={TagEl === "a" ? overflowHref || "/topics" : undefined} |
| 93 | + class:list={[ |
| 94 | + "inline-flex items-center rounded-full border border-dashed border-[#2A1155]/40 bg-[#EDE9FE] font-mono font-extrabold text-[#7C3AED] transition-colors hover:bg-[#DDD6FE] dark:border-purple-400/40 dark:bg-purple-950/70 dark:text-purple-200", |
| 95 | + isSmall ? "px-2 py-0.5 text-[10px]" : "px-3 py-1 text-xs", |
| 96 | + ]} |
| 97 | + title={`${remainingCount} more ${remainingCount === 1 ? "tag" : "tags"}`} |
| 98 | + > |
| 99 | + +{remainingCount} |
| 100 | + </TagEl> |
| 101 | + )} |
| 102 | + </> |
| 103 | + ) |
| 104 | +} |
0 commit comments