@grove-dev/astro 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -5
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -33
- package/dist/index.js.map +1 -1
- package/dist/lib/display.d.ts +1 -1
- package/dist/lib/display.js +1 -1
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +1 -1
- package/dist/lib/icon-kinds.d.ts +1 -1
- package/dist/lib/icon-kinds.js +43 -43
- package/dist/lib/icon-registry.d.ts +2 -2
- package/dist/lib/icon-registry.js +23 -23
- package/dist/lib/index.d.ts +9 -9
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +9 -9
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/lenses.d.ts +1 -1
- package/dist/lib/lenses.js +1 -1
- package/dist/lib/packaged-icons.d.ts +1 -1
- package/dist/lib/packaged-icons.js +4 -4
- package/dist/lib/repo.d.ts +1 -1
- package/dist/lib/repo.js +1 -1
- package/dist/lib/search.d.ts +1 -1
- package/dist/lib/search.js +1 -1
- package/dist/lib/taxonomy-counts.d.ts +1 -1
- package/dist/lib/taxonomy-counts.js +1 -1
- package/package.json +7 -17
- package/src/index.ts +23 -38
- package/src/lib/display.ts +1 -1
- package/src/lib/format.ts +1 -1
- package/src/lib/icon-kinds.ts +44 -44
- package/src/lib/icon-registry.ts +25 -25
- package/src/lib/index.ts +9 -9
- package/src/lib/lenses.ts +1 -1
- package/src/lib/packaged-icons.ts +8 -8
- package/src/lib/repo.ts +1 -1
- package/src/lib/search.ts +1 -1
- package/src/lib/taxonomy-counts.ts +1 -1
- package/src/server/collections.ts +53 -42
- package/src/server/contrast.ts +14 -9
- package/src/server/directory.ts +22 -17
- package/src/server/index.ts +3 -2
- package/src/server/models.ts +404 -213
- package/src/server/seo.ts +34 -31
- package/src/styles.css +235 -91
- package/dist/layouts/index.d.ts +0 -25
- package/dist/layouts/index.d.ts.map +0 -1
- package/dist/layouts/index.js +0 -25
- package/dist/layouts/index.js.map +0 -1
- package/dist/ui/button.d.ts +0 -53
- package/dist/ui/button.d.ts.map +0 -1
- package/dist/ui/button.js +0 -90
- package/dist/ui/button.js.map +0 -1
- package/src/components/CardGrid.astro +0 -41
- package/src/components/CardIcon.astro +0 -67
- package/src/components/CategoryGrid.astro +0 -114
- package/src/components/CollectionCard.astro +0 -56
- package/src/components/CollectionIndex.astro +0 -102
- package/src/components/CollectionPage.astro +0 -128
- package/src/components/CollectionRow.astro +0 -81
- package/src/components/CollectionTeaser.astro +0 -47
- package/src/components/ContributorsGrid.astro +0 -143
- package/src/components/DirectoryIndexClient.astro +0 -510
- package/src/components/EditorialSummary.astro +0 -58
- package/src/components/FilterGroupMenu.astro +0 -69
- package/src/components/FilterOptions.astro +0 -49
- package/src/components/FinalCta.astro +0 -43
- package/src/components/Hero.astro +0 -243
- package/src/components/Icon.astro +0 -191
- package/src/components/IndexRow.astro +0 -37
- package/src/components/LanguageBreakdown.astro +0 -67
- package/src/components/MarkdownBody.astro +0 -37
- package/src/components/OriginalCollection.astro +0 -117
- package/src/components/Pagination.astro +0 -50
- package/src/components/PoweredBy.astro +0 -60
- package/src/components/ProjectCard.astro +0 -321
- package/src/components/RecordHeader.astro +0 -221
- package/src/components/RecordSection.astro +0 -128
- package/src/components/RecordSidebar.astro +0 -387
- package/src/components/RefinePanel.astro +0 -168
- package/src/components/SmartLensTabs.astro +0 -81
- package/src/components/StackGrid.astro +0 -151
- package/src/components/StackPlatformChips.astro +0 -73
- package/src/components/SubmissionClient.astro +0 -254
- package/src/components/TableOfContents.astro +0 -140
- package/src/components/WhyThisExists.astro +0 -116
- package/src/layouts/BaseLayout.astro +0 -415
- package/src/layouts/Container.astro +0 -18
- package/src/layouts/Footer.astro +0 -231
- package/src/layouts/Header.astro +0 -214
- package/src/layouts/SectionHeader.astro +0 -43
- package/src/layouts/Seo.astro +0 -171
- package/src/layouts/ThemeToggle.astro +0 -118
- package/src/layouts/astro-shim.d.ts +0 -29
- package/src/layouts/index.ts +0 -24
- package/src/server/collections.test.ts +0 -212
- package/src/server/contrast.test.ts +0 -82
- package/src/server/directory.test.ts +0 -177
- package/src/server/github-repo.test.ts +0 -88
- package/src/server/models-home.test.ts +0 -101
- package/src/server/models.test.ts +0 -135
- package/src/server/seo.test.ts +0 -123
- package/src/ui/Badge.astro +0 -38
- package/src/ui/Button.astro +0 -42
- package/src/ui/EmptyState.astro +0 -38
- package/src/ui/FilterDrawer.astro +0 -124
- package/src/ui/PageHeader.astro +0 -39
- package/src/ui/SearchField.astro +0 -106
- package/src/ui/button.test.ts +0 -54
- package/src/ui/button.ts +0 -107
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* RecordHeader.astro
|
|
4
|
-
*
|
|
5
|
-
* Compact project-identity header used at the top of every record
|
|
6
|
-
* detail page. The header carries:
|
|
7
|
-
* - avatar / initials
|
|
8
|
-
* - pills row (category + curation labels + "Curated" check +
|
|
9
|
-
* optional activity badge)
|
|
10
|
-
* - record name + owner/repo line
|
|
11
|
-
* - short description
|
|
12
|
-
* - GitHub + Homepage CTAs
|
|
13
|
-
*
|
|
14
|
-
* Tags + activity metadata are surfaced separately (sidebar), so
|
|
15
|
-
* the header stays narrow on mobile and doesn't compete with the
|
|
16
|
-
* two-column layout below.
|
|
17
|
-
*
|
|
18
|
-
* Tone classes for the activity badge are static `[data-activity-tone]`
|
|
19
|
-
* selectors in `styles.css` — Tailwind purges any classes built from
|
|
20
|
-
* dynamic strings, so the package ships the rules statically and
|
|
21
|
-
* the badge's `data-activity-tone` attribute picks one at runtime.
|
|
22
|
-
*/
|
|
23
|
-
import type { RecordDetailModel } from "../server/models.js";
|
|
24
|
-
import { buttonClass } from "../ui/button.js";
|
|
25
|
-
|
|
26
|
-
interface Props {
|
|
27
|
-
detail: RecordDetailModel;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const { detail } = Astro.props;
|
|
31
|
-
const {
|
|
32
|
-
record,
|
|
33
|
-
repoUrl,
|
|
34
|
-
homepageUrl,
|
|
35
|
-
ownerRepo,
|
|
36
|
-
avatarSrc,
|
|
37
|
-
initials,
|
|
38
|
-
curationLabels,
|
|
39
|
-
activityBadge,
|
|
40
|
-
summary,
|
|
41
|
-
sourceDescription,
|
|
42
|
-
screenshots,
|
|
43
|
-
category,
|
|
44
|
-
name,
|
|
45
|
-
} = detail;
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
<header class="mb-8">
|
|
49
|
-
{/* Identity row: logo sized to the name+repo block beside it — no
|
|
50
|
-
tall avatar column leaving dead space under the logo. Pills sit
|
|
51
|
-
at the row's right edge on wide viewports and wrap below on
|
|
52
|
-
mobile. */}
|
|
53
|
-
<div class="flex items-center gap-4">
|
|
54
|
-
{/* Initials stay in the DOM behind the logo and are revealed by the
|
|
55
|
-
image's own `onerror` — a dead logo URL or a renamed GitHub org
|
|
56
|
-
would otherwise render the broken-image glyph. */}
|
|
57
|
-
{avatarSrc && (
|
|
58
|
-
<img
|
|
59
|
-
src={avatarSrc}
|
|
60
|
-
alt=""
|
|
61
|
-
width="56"
|
|
62
|
-
height="56"
|
|
63
|
-
loading="lazy"
|
|
64
|
-
decoding="async"
|
|
65
|
-
onerror="this.hidden=true;this.nextElementSibling.hidden=false"
|
|
66
|
-
class="h-12 w-12 shrink-0 rounded-lg border border-ink-200 object-contain bg-white p-1 dark:border-ink-800 sm:h-14 sm:w-14"
|
|
67
|
-
/>
|
|
68
|
-
)}
|
|
69
|
-
<span
|
|
70
|
-
aria-hidden="true"
|
|
71
|
-
hidden={Boolean(avatarSrc)}
|
|
72
|
-
data-grove-card-fallback-initials
|
|
73
|
-
class="inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-lg border border-ink-200 bg-ink-50 text-xl font-semibold text-ink-700 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-200 sm:h-14 sm:w-14"
|
|
74
|
-
>
|
|
75
|
-
{initials || "·"}
|
|
76
|
-
</span>
|
|
77
|
-
|
|
78
|
-
<div class="min-w-0 flex-1">
|
|
79
|
-
<h1 class="m-0 text-[1.75rem] font-semibold leading-tight tracking-tight text-ink-900 dark:text-ink-100 sm:text-[2rem]">
|
|
80
|
-
{name}
|
|
81
|
-
</h1>
|
|
82
|
-
{ownerRepo && (
|
|
83
|
-
<p class="m-0 mt-0.5 text-sm text-ink-500 dark:text-ink-400">
|
|
84
|
-
<a
|
|
85
|
-
href={`https://github.com/${ownerRepo.owner}`}
|
|
86
|
-
target="_blank"
|
|
87
|
-
rel="noopener noreferrer"
|
|
88
|
-
class="text-inherit no-underline hover:text-ink-900 dark:hover:text-ink-100"
|
|
89
|
-
>
|
|
90
|
-
@{ownerRepo.owner}
|
|
91
|
-
</a>
|
|
92
|
-
<span class="mx-1 text-ink-300 dark:text-ink-700">/</span>
|
|
93
|
-
<a
|
|
94
|
-
href={repoUrl}
|
|
95
|
-
target="_blank"
|
|
96
|
-
rel="noopener noreferrer"
|
|
97
|
-
class="text-inherit no-underline hover:text-ink-900 dark:hover:text-ink-100"
|
|
98
|
-
>
|
|
99
|
-
{ownerRepo.repo}
|
|
100
|
-
</a>
|
|
101
|
-
</p>
|
|
102
|
-
)}
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
{/* Pills — right-aligned beside the name on wide viewports. */}
|
|
106
|
-
<div class="hidden shrink-0 flex-wrap items-center justify-end gap-1.5 md:flex">
|
|
107
|
-
{category && (
|
|
108
|
-
<span class="rounded-full border border-ink-200 px-2 py-0.5 text-2xs font-medium text-ink-700 dark:border-ink-800 dark:text-ink-300">
|
|
109
|
-
{category}
|
|
110
|
-
</span>
|
|
111
|
-
)}
|
|
112
|
-
{curationLabels.map((l) => (
|
|
113
|
-
<span class="rounded-full bg-secondary px-2 py-0.5 text-2xs font-medium text-secondary-foreground">
|
|
114
|
-
{l}
|
|
115
|
-
</span>
|
|
116
|
-
))}
|
|
117
|
-
{/* Curated = reviewed provenance, not a success status. */}
|
|
118
|
-
{record.curation?.reviewed && (
|
|
119
|
-
<span class="inline-flex items-center gap-1 rounded-full border border-border bg-secondary px-2 py-0.5 text-2xs font-medium text-secondary-foreground">
|
|
120
|
-
<svg viewBox="0 0 16 16" width="10" height="10" aria-hidden="true" fill="currentColor">
|
|
121
|
-
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0Zm3.41 5.09a.75.75 0 0 0-1.06 0L6.75 8.69 5.65 7.59a.75.75 0 1 0-1.06 1.06l1.65 1.65a.75.75 0 0 0 1.06 0l4.11-4.11a.75.75 0 0 0 0-1.06Z" />
|
|
122
|
-
</svg>
|
|
123
|
-
Curated
|
|
124
|
-
</span>
|
|
125
|
-
)}
|
|
126
|
-
{activityBadge && (
|
|
127
|
-
<span
|
|
128
|
-
data-activity-tone={activityBadge.tone}
|
|
129
|
-
class="grove-pill grove-pill--activity inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-2xs font-medium"
|
|
130
|
-
>
|
|
131
|
-
<span class="relative flex h-1.5 w-1.5">
|
|
132
|
-
<span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-current opacity-60"></span>
|
|
133
|
-
<span class="relative inline-flex h-1.5 w-1.5 rounded-full bg-current"></span>
|
|
134
|
-
</span>
|
|
135
|
-
{activityBadge.label}
|
|
136
|
-
</span>
|
|
137
|
-
)}
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
{/* Same pills below the identity row on narrow viewports. */}
|
|
142
|
-
<div class="mt-3 flex flex-wrap items-center gap-1.5 md:hidden">
|
|
143
|
-
{category && (
|
|
144
|
-
<span class="rounded-full border border-ink-200 px-2 py-0.5 text-2xs font-medium text-ink-700 dark:border-ink-800 dark:text-ink-300">
|
|
145
|
-
{category}
|
|
146
|
-
</span>
|
|
147
|
-
)}
|
|
148
|
-
{curationLabels.map((l) => (
|
|
149
|
-
<span class="rounded-full bg-secondary px-2 py-0.5 text-2xs font-medium text-secondary-foreground">
|
|
150
|
-
{l}
|
|
151
|
-
</span>
|
|
152
|
-
))}
|
|
153
|
-
{record.curation?.reviewed && (
|
|
154
|
-
<span class="inline-flex items-center gap-1 rounded-full border border-border bg-secondary px-2 py-0.5 text-2xs font-medium text-secondary-foreground">
|
|
155
|
-
<svg viewBox="0 0 16 16" width="10" height="10" aria-hidden="true" fill="currentColor">
|
|
156
|
-
<path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0Zm3.41 5.09a.75.75 0 0 0-1.06 0L6.75 8.69 5.65 7.59a.75.75 0 1 0-1.06 1.06l1.65 1.65a.75.75 0 0 0 1.06 0l4.11-4.11a.75.75 0 0 0 0-1.06Z" />
|
|
157
|
-
</svg>
|
|
158
|
-
Curated
|
|
159
|
-
</span>
|
|
160
|
-
)}
|
|
161
|
-
</div>
|
|
162
|
-
|
|
163
|
-
{/* Description — full width below the identity row. */}
|
|
164
|
-
{summary && (
|
|
165
|
-
<p class="m-0 mt-4 max-w-[72ch] text-base leading-relaxed text-ink-700 dark:text-ink-200">
|
|
166
|
-
{summary}
|
|
167
|
-
</p>
|
|
168
|
-
)}
|
|
169
|
-
{sourceDescription && sourceDescription !== summary && (
|
|
170
|
-
<p class="m-0 mt-2 max-w-[72ch] text-sm leading-relaxed text-ink-500 dark:text-ink-400">
|
|
171
|
-
<span class="text-2xs font-semibold uppercase tracking-wider">From the project's README: </span>
|
|
172
|
-
{sourceDescription}
|
|
173
|
-
</p>
|
|
174
|
-
)}
|
|
175
|
-
|
|
176
|
-
{/* CTA row — primary actions only. */}
|
|
177
|
-
<div class="mt-4 flex flex-wrap items-center gap-2">
|
|
178
|
-
{repoUrl && (
|
|
179
|
-
<a
|
|
180
|
-
href={repoUrl}
|
|
181
|
-
target="_blank"
|
|
182
|
-
rel="noopener noreferrer"
|
|
183
|
-
class={buttonClass("primary", "sm")}
|
|
184
|
-
>
|
|
185
|
-
View on GitHub →
|
|
186
|
-
</a>
|
|
187
|
-
)}
|
|
188
|
-
{homepageUrl && (
|
|
189
|
-
<a
|
|
190
|
-
href={homepageUrl}
|
|
191
|
-
target="_blank"
|
|
192
|
-
rel="noopener noreferrer"
|
|
193
|
-
class={buttonClass("secondary", "sm")}
|
|
194
|
-
>
|
|
195
|
-
Homepage
|
|
196
|
-
</a>
|
|
197
|
-
)}
|
|
198
|
-
</div>
|
|
199
|
-
{screenshots && screenshots.length > 0 && (
|
|
200
|
-
<div class="mt-6 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3" aria-label="Screenshots">
|
|
201
|
-
{screenshots.map((shot) => (
|
|
202
|
-
<figure class="m-0 overflow-hidden rounded-[var(--radius-lg)] border border-ink-200 bg-muted dark:border-ink-800">
|
|
203
|
-
<img
|
|
204
|
-
src={shot.src}
|
|
205
|
-
alt={shot.alt}
|
|
206
|
-
loading="lazy"
|
|
207
|
-
decoding="async"
|
|
208
|
-
width={shot.width}
|
|
209
|
-
height={shot.height}
|
|
210
|
-
class="block h-auto w-full object-cover"
|
|
211
|
-
/>
|
|
212
|
-
{shot.source && (
|
|
213
|
-
<figcaption class="px-3 py-1.5 text-2xs text-ink-500 dark:text-ink-400">
|
|
214
|
-
Source: <a href={shot.source} target="_blank" rel="noopener noreferrer" class="text-inherit no-underline hover:underline">{shot.source.replace(/^https?:\/\//, "")}</a>
|
|
215
|
-
</figcaption>
|
|
216
|
-
)}
|
|
217
|
-
</figure>
|
|
218
|
-
))}
|
|
219
|
-
</div>
|
|
220
|
-
)}
|
|
221
|
-
</header>
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* RecordSection — generic lens-style section wrapper for the
|
|
4
|
-
* home page.
|
|
5
|
-
*
|
|
6
|
-
* Renders a SectionHeader + a 3-column grid of `ProjectCard`s
|
|
7
|
-
* (the v0.5.0 refined card), plus an optional "View all" link
|
|
8
|
-
* in the header. Generic: takes any
|
|
9
|
-
* `IndexRecord[]` (the slim list-page shape, discriminated union
|
|
10
|
-
* over `kind`).
|
|
11
|
-
*
|
|
12
|
-
* Two equivalent APIs are supported so the same template works
|
|
13
|
-
* against both the workspace source and the published
|
|
14
|
-
* `@grove-dev/astro@0.2.10` package:
|
|
15
|
-
*
|
|
16
|
-
* - **`hrefFor(record)`** callback — V1 published API. Caller
|
|
17
|
-
* passes a function that returns the detail-page URL for
|
|
18
|
-
* each item.
|
|
19
|
-
* - **`pathPrefix` string** — convenience API for the common
|
|
20
|
-
* case `${pathPrefix}/${item.slug}`. Mutually exclusive with
|
|
21
|
-
* `hrefFor`; if both are provided, `hrefFor` wins.
|
|
22
|
-
*
|
|
23
|
-
* Empty-state copy is rendered when the list has no items, so the
|
|
24
|
-
* homepage never collapses to a blank section.
|
|
25
|
-
*/
|
|
26
|
-
import type { IndexRecord } from "@grove-dev/core";
|
|
27
|
-
import CardGrid from "./CardGrid.astro";
|
|
28
|
-
import ProjectCard from "./ProjectCard.astro";
|
|
29
|
-
import Container from "../layouts/Container.astro";
|
|
30
|
-
import SectionHeader from "../layouts/SectionHeader.astro";
|
|
31
|
-
|
|
32
|
-
interface Props {
|
|
33
|
-
/** Used as the section id (so deep links work) and a stable key. */
|
|
34
|
-
id: string;
|
|
35
|
-
title: string;
|
|
36
|
-
description?: string;
|
|
37
|
-
items: IndexRecord[];
|
|
38
|
-
/** Detail-page href builder (V1 published API). */
|
|
39
|
-
hrefFor?: (record: IndexRecord) => string;
|
|
40
|
-
/** Detail-page URL prefix (convenience API). */
|
|
41
|
-
pathPrefix?: string;
|
|
42
|
-
/** Optional "View all" link rendered in the header. */
|
|
43
|
-
viewAllHref?: string;
|
|
44
|
-
viewAllLabel?: string;
|
|
45
|
-
/** Optional eyebrow. */
|
|
46
|
-
eyebrow?: string;
|
|
47
|
-
/** Empty-state copy. */
|
|
48
|
-
emptyLabel?: string;
|
|
49
|
-
/** Extra class on the outer <section>. */
|
|
50
|
-
class?: string;
|
|
51
|
-
/** How many records to render. Default 3 — the refined grid shows one row. */
|
|
52
|
-
limit?: number;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const {
|
|
56
|
-
id,
|
|
57
|
-
title,
|
|
58
|
-
description,
|
|
59
|
-
items,
|
|
60
|
-
hrefFor,
|
|
61
|
-
pathPrefix = "/items",
|
|
62
|
-
viewAllHref,
|
|
63
|
-
viewAllLabel = "View all",
|
|
64
|
-
eyebrow,
|
|
65
|
-
emptyLabel = "No records yet.",
|
|
66
|
-
class: className = "",
|
|
67
|
-
limit = 3,
|
|
68
|
-
} = Astro.props;
|
|
69
|
-
|
|
70
|
-
const visible = items.slice(0, limit);
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
<section
|
|
74
|
-
id={id}
|
|
75
|
-
class={`py-12 sm:py-16 scroll-mt-20 ${className}`}
|
|
76
|
-
>
|
|
77
|
-
<Container>
|
|
78
|
-
<div class="mb-6 flex flex-wrap items-end justify-between gap-3">
|
|
79
|
-
<SectionHeader
|
|
80
|
-
eyebrow={eyebrow}
|
|
81
|
-
title={title}
|
|
82
|
-
description={description}
|
|
83
|
-
/>
|
|
84
|
-
{viewAllHref && (
|
|
85
|
-
<a
|
|
86
|
-
href={viewAllHref}
|
|
87
|
-
class="hidden shrink-0 pb-2 text-2xs font-medium text-ink-500 transition-colors hover:text-ink-900 sm:inline dark:text-ink-400 dark:hover:text-ink-100"
|
|
88
|
-
>
|
|
89
|
-
{viewAllLabel} →
|
|
90
|
-
</a>
|
|
91
|
-
)}
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{visible.length === 0 ? (
|
|
95
|
-
<p class="m-0 text-sm text-ink-500 dark:text-ink-400">
|
|
96
|
-
{emptyLabel}
|
|
97
|
-
</p>
|
|
98
|
-
) : (
|
|
99
|
-
<CardGrid>
|
|
100
|
-
{visible.map((item) => {
|
|
101
|
-
// Resolve href: caller-supplied `hrefFor` callback wins;
|
|
102
|
-
// otherwise fall back to `${pathPrefix}/${slug}`. The href
|
|
103
|
-
// is what each card actually navigates to — getting it
|
|
104
|
-
// right is the difference between a working home page
|
|
105
|
-
// and a 404-storm of `/items/<slug>` links.
|
|
106
|
-
const base = pathPrefix.replace(/\/$/, "");
|
|
107
|
-
const fallbackHref = `${base}/${item.slug}`;
|
|
108
|
-
const finalHref =
|
|
109
|
-
typeof hrefFor === "function" ? hrefFor(item) : fallbackHref;
|
|
110
|
-
return (
|
|
111
|
-
<li class="m-0 p-0">
|
|
112
|
-
<ProjectCard record={item} href={finalHref} />
|
|
113
|
-
</li>
|
|
114
|
-
);
|
|
115
|
-
})}
|
|
116
|
-
</CardGrid>
|
|
117
|
-
)}
|
|
118
|
-
|
|
119
|
-
{viewAllHref && (
|
|
120
|
-
<div class="mt-4 sm:hidden">
|
|
121
|
-
<a
|
|
122
|
-
href={viewAllHref}
|
|
123
|
-
class="text-2xs font-medium text-ink-500 transition-colors hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"
|
|
124
|
-
>{viewAllLabel} →</a>
|
|
125
|
-
</div>
|
|
126
|
-
)}
|
|
127
|
-
</Container>
|
|
128
|
-
</section>
|