@grove-dev/astro 0.2.18 → 0.2.20
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -1
- package/dist/index.js.map +1 -1
- package/dist/theme.test.d.ts +2 -0
- package/dist/theme.test.d.ts.map +1 -0
- package/dist/theme.test.js +55 -0
- package/dist/theme.test.js.map +1 -0
- package/package.json +1 -1
- package/src/components/CategoryGrid.astro +2 -11
- package/src/components/ContributorsGrid.astro +2 -11
- package/src/components/CurationGrid.astro +1 -1
- package/src/components/DecisionRow.astro +1 -1
- package/src/components/ExploreByCategory.astro +3 -3
- package/src/components/ExploreByStack.astro +3 -3
- package/src/components/FilterGroupMenu.astro +5 -5
- package/src/components/Hero.astro +11 -11
- package/src/components/Icon.astro +6 -33
- package/src/components/IndexRow.astro +5 -5
- package/src/components/ItemCard.astro +5 -16
- package/src/components/MinimalAbout.astro +2 -2
- package/src/components/OriginalCollection.astro +4 -4
- package/src/components/Pagination.astro +2 -2
- package/src/components/RecordSection.astro +1 -1
- package/src/components/RefinePanel.astro +1 -1
- package/src/components/SmartLensTabs.astro +3 -3
- package/src/components/StackGrid.astro +6 -15
- package/src/components/WhyThisExists.astro +3 -3
- package/src/index.ts +52 -1
- package/src/layouts/BaseLayout.astro +1 -1
- package/src/layouts/Container.astro +4 -4
- package/src/layouts/Footer.astro +3 -3
- package/src/layouts/Header.astro +5 -5
- package/src/layouts/ThemeToggle.astro +1 -1
- package/src/styles.css +217 -408
- package/src/theme.test.ts +67 -0
- package/templates/default/package.json +1 -1
- package/templates/default/public/icons/stacks/rag.svg +7 -0
- package/templates/default/public/llms-full.txt +1 -1
- package/templates/default/public/sitemap.xml +10 -10
- package/templates/default/src/pages/404.astro +5 -5
- package/templates/default/src/pages/[slug]/[recordSlug].astro +27 -31
- package/templates/default/src/pages/[slug]/index.astro +8 -37
- package/templates/default/src/pages/about.astro +9 -9
- package/templates/default/src/pages/contributors.astro +8 -17
- package/templates/default/src/pages/submit.astro +23 -31
- package/templates/default/src/styles/global.css +14 -39
- package/templates/default/tailwind.config.mjs +0 -130
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
const astroTheme = readFileSync(resolve(import.meta.dirname, "styles.css"), "utf8");
|
|
6
|
+
const scaffoldTheme = readFileSync(
|
|
7
|
+
resolve(import.meta.dirname, "../templates/default/src/styles/global.css"),
|
|
8
|
+
"utf8",
|
|
9
|
+
);
|
|
10
|
+
const tailwindMarkup = [
|
|
11
|
+
"components/ItemCard.astro",
|
|
12
|
+
"layouts/Header.astro",
|
|
13
|
+
"../templates/default/src/pages/submit.astro",
|
|
14
|
+
].map((file) => readFileSync(resolve(import.meta.dirname, file), "utf8")).join("\n");
|
|
15
|
+
|
|
16
|
+
describe("Astro theme contract", () => {
|
|
17
|
+
it("keeps Starlight's light and dark foundation values", () => {
|
|
18
|
+
expect(astroTheme).toContain("--grove-background: oklch(100% 0 0)");
|
|
19
|
+
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-950)");
|
|
20
|
+
expect(astroTheme).toContain("--grove-background: var(--color-ink-950)");
|
|
21
|
+
expect(astroTheme).toContain("--grove-foreground: var(--color-ink-50)");
|
|
22
|
+
expect(astroTheme).toContain("--color-ink-50: oklch(98.5% 0 0)");
|
|
23
|
+
expect(astroTheme).toContain("--color-ink-950: oklch(14.5% 0 0)");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("exposes runtime semantic colors to Tailwind", () => {
|
|
27
|
+
for (const token of [
|
|
28
|
+
"background",
|
|
29
|
+
"foreground",
|
|
30
|
+
"primary",
|
|
31
|
+
"secondary",
|
|
32
|
+
"muted",
|
|
33
|
+
"accent",
|
|
34
|
+
"border",
|
|
35
|
+
"card",
|
|
36
|
+
"popover",
|
|
37
|
+
]) {
|
|
38
|
+
expect(astroTheme).toContain(`--color-${token}: var(--grove-${token})`);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("uses the same system and mono font stacks as the docs", () => {
|
|
43
|
+
expect(astroTheme).toContain(
|
|
44
|
+
'--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,',
|
|
45
|
+
);
|
|
46
|
+
expect(astroTheme).toContain(
|
|
47
|
+
"--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,",
|
|
48
|
+
);
|
|
49
|
+
expect(astroTheme).toContain("font-family: var(--font-sans)");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("does not duplicate package-owned foundation tokens in the scaffold", () => {
|
|
53
|
+
expect(scaffoldTheme).not.toContain("--color-ink-");
|
|
54
|
+
expect(scaffoldTheme).not.toContain("font-family:");
|
|
55
|
+
expect(scaffoldTheme).not.toContain("color-scheme:");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("styles components in HTML with Starlight-aligned Tailwind utilities", () => {
|
|
59
|
+
expect(tailwindMarkup).toContain("rounded-[calc(var(--radius)+0.25rem)]");
|
|
60
|
+
expect(tailwindMarkup).toContain("rounded-full");
|
|
61
|
+
expect(tailwindMarkup).toContain("min-h-5");
|
|
62
|
+
expect(tailwindMarkup).toContain("h-8");
|
|
63
|
+
expect(tailwindMarkup).toContain("focus-visible:ring-3");
|
|
64
|
+
expect(astroTheme).not.toContain(".grove-card");
|
|
65
|
+
expect(astroTheme).not.toContain(".grove-btn");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
|
2
|
+
<rect width="100" height="100" rx="18" fill="#0F172A"/>
|
|
3
|
+
<path d="M29 28h42v13H29zM29 59h42v13H29z" stroke="#38BDF8" stroke-width="4" stroke-linejoin="round"/>
|
|
4
|
+
<path d="M50 41v18M42 50h16" stroke="#A78BFA" stroke-width="4" stroke-linecap="round"/>
|
|
5
|
+
<circle cx="36" cy="34.5" r="2.5" fill="#38BDF8"/>
|
|
6
|
+
<circle cx="36" cy="65.5" r="2.5" fill="#38BDF8"/>
|
|
7
|
+
</svg>
|
|
@@ -2,61 +2,61 @@
|
|
|
2
2
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3
3
|
<url>
|
|
4
4
|
<loc>https://example.com/</loc>
|
|
5
|
-
<lastmod>2026-06-
|
|
5
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
6
6
|
<changefreq>daily</changefreq>
|
|
7
7
|
<priority>1.0</priority>
|
|
8
8
|
</url>
|
|
9
9
|
<url>
|
|
10
10
|
<loc>https://example.com/projects</loc>
|
|
11
|
-
<lastmod>2026-06-
|
|
11
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
12
12
|
<changefreq>daily</changefreq>
|
|
13
13
|
<priority>0.9</priority>
|
|
14
14
|
</url>
|
|
15
15
|
<url>
|
|
16
16
|
<loc>https://example.com/projects/coolify</loc>
|
|
17
|
-
<lastmod>2026-06-
|
|
17
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
18
18
|
<changefreq>weekly</changefreq>
|
|
19
19
|
<priority>0.7</priority>
|
|
20
20
|
</url>
|
|
21
21
|
<url>
|
|
22
22
|
<loc>https://example.com/projects/gitea</loc>
|
|
23
|
-
<lastmod>2026-06-
|
|
23
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
24
24
|
<changefreq>weekly</changefreq>
|
|
25
25
|
<priority>0.7</priority>
|
|
26
26
|
</url>
|
|
27
27
|
<url>
|
|
28
28
|
<loc>https://example.com/projects/hoppscotch</loc>
|
|
29
|
-
<lastmod>2026-06-
|
|
29
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
30
30
|
<changefreq>weekly</changefreq>
|
|
31
31
|
<priority>0.7</priority>
|
|
32
32
|
</url>
|
|
33
33
|
<url>
|
|
34
34
|
<loc>https://example.com/projects/inventree</loc>
|
|
35
|
-
<lastmod>2026-06-
|
|
35
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
36
36
|
<changefreq>weekly</changefreq>
|
|
37
37
|
<priority>0.7</priority>
|
|
38
38
|
</url>
|
|
39
39
|
<url>
|
|
40
40
|
<loc>https://example.com/projects/label-studio</loc>
|
|
41
|
-
<lastmod>2026-06-
|
|
41
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
42
42
|
<changefreq>weekly</changefreq>
|
|
43
43
|
<priority>0.7</priority>
|
|
44
44
|
</url>
|
|
45
45
|
<url>
|
|
46
46
|
<loc>https://example.com/projects/logseq</loc>
|
|
47
|
-
<lastmod>2026-06-
|
|
47
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
48
48
|
<changefreq>weekly</changefreq>
|
|
49
49
|
<priority>0.7</priority>
|
|
50
50
|
</url>
|
|
51
51
|
<url>
|
|
52
52
|
<loc>https://example.com/projects/private-gpt</loc>
|
|
53
|
-
<lastmod>2026-06-
|
|
53
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
54
54
|
<changefreq>weekly</changefreq>
|
|
55
55
|
<priority>0.7</priority>
|
|
56
56
|
</url>
|
|
57
57
|
<url>
|
|
58
58
|
<loc>https://example.com/projects/wakapi</loc>
|
|
59
|
-
<lastmod>2026-06-
|
|
59
|
+
<lastmod>2026-06-22T16:19:39.853Z</lastmod>
|
|
60
60
|
<changefreq>weekly</changefreq>
|
|
61
61
|
<priority>0.7</priority>
|
|
62
62
|
</url>
|
|
@@ -21,8 +21,8 @@ const description = `That page doesn't exist on ${siteConfig.name}.`;
|
|
|
21
21
|
site={siteConfig}
|
|
22
22
|
noindex
|
|
23
23
|
>
|
|
24
|
-
<section class="
|
|
25
|
-
<div class="
|
|
24
|
+
<section class="py-12 sm:py-16">
|
|
25
|
+
<div class="mx-auto w-full max-w-container px-5 py-20 sm:px-7 sm:py-28">
|
|
26
26
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
27
27
|
404
|
|
28
28
|
</span>
|
|
@@ -50,14 +50,14 @@ const description = `That page doesn't exist on ${siteConfig.name}.`;
|
|
|
50
50
|
class="w-full rounded-md border border-ink-200 bg-white py-2.5 px-3 text-sm placeholder:text-ink-400 focus:border-ink-900 focus:outline-none focus:ring-2 focus:ring-ink-900/10 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-100"
|
|
51
51
|
/>
|
|
52
52
|
</label>
|
|
53
|
-
<button type="submit" class="
|
|
53
|
+
<button type="submit" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">
|
|
54
54
|
Search
|
|
55
55
|
</button>
|
|
56
56
|
</form>
|
|
57
57
|
|
|
58
58
|
<div class="mt-6 flex flex-wrap gap-3">
|
|
59
|
-
<a href="/" class="
|
|
60
|
-
<a href={`/${slug}`} class="
|
|
59
|
+
<a href="/" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">Back to home</a>
|
|
60
|
+
<a href={`/${slug}`} class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent">Browse {slug}</a>
|
|
61
61
|
</div>
|
|
62
62
|
</div>
|
|
63
63
|
</section>
|
|
@@ -101,18 +101,14 @@ const tags = record.tags ?? [];
|
|
|
101
101
|
const healthStatus = proj?.health?.status;
|
|
102
102
|
const healthLabel = healthStatus ? statusDisplay(healthStatus) : null;
|
|
103
103
|
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
// name — every badge became `grove-badge-{l}` regardless of value.
|
|
108
|
-
// Map each known id to its stable CSS class in the package
|
|
109
|
-
// stylesheet. Unknown ids (e.g. legacy "featured") fall back to the
|
|
110
|
-
// neutral `.grove-badge` look instead of producing broken classes.
|
|
104
|
+
// Map each known label to a static Tailwind class string so every
|
|
105
|
+
// utility is visible to Tailwind's source scanner. Unknown/legacy
|
|
106
|
+
// labels use the neutral secondary badge treatment.
|
|
111
107
|
const labelClassMap: Record<LabelId, string> = {
|
|
112
|
-
new: "
|
|
113
|
-
hot: "
|
|
114
|
-
mature: "
|
|
115
|
-
featured: "
|
|
108
|
+
new: "!bg-emerald-50 !text-emerald-700 dark:!bg-emerald-950/80 dark:!text-emerald-300",
|
|
109
|
+
hot: "!bg-orange-50 !text-orange-800 dark:!bg-orange-950/80 dark:!text-orange-300",
|
|
110
|
+
mature: "!bg-blue-50 !text-blue-700 dark:!bg-blue-950/80 dark:!text-blue-300",
|
|
111
|
+
featured: "inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground",
|
|
116
112
|
};
|
|
117
113
|
|
|
118
114
|
// ── Optional content markdown (record.content is a path under
|
|
@@ -248,8 +244,8 @@ if (isProject && proj) {
|
|
|
248
244
|
submitHref="/submit"
|
|
249
245
|
submitLabel={`Submit ${name}`}
|
|
250
246
|
>
|
|
251
|
-
<article class="
|
|
252
|
-
<div class="
|
|
247
|
+
<article class="py-12 sm:py-16">
|
|
248
|
+
<div class="mx-auto w-full max-w-wide px-5 sm:px-7">
|
|
253
249
|
<nav class="mb-6 text-2xs text-ink-500 dark:text-ink-400" aria-label="Breadcrumb">
|
|
254
250
|
<a href={`/${slug}`} class="hover:text-ink-900 dark:hover:text-ink-100 capitalize">
|
|
255
251
|
{slug}
|
|
@@ -259,7 +255,7 @@ if (isProject && proj) {
|
|
|
259
255
|
</nav>
|
|
260
256
|
|
|
261
257
|
{/* ── AppCard-style header ──────────────────────────────── */}
|
|
262
|
-
<header class="
|
|
258
|
+
<header class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-col gap-5 p-6 sm:flex-row sm:items-start">
|
|
263
259
|
<div class="flex-shrink-0">
|
|
264
260
|
{avatarSrc ? (
|
|
265
261
|
<img
|
|
@@ -292,7 +288,7 @@ if (isProject && proj) {
|
|
|
292
288
|
</span>
|
|
293
289
|
)}
|
|
294
290
|
{curationLabels.map((l) => (
|
|
295
|
-
<span class={`
|
|
291
|
+
<span class={`inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground ${labelClassMap[l as LabelId] ?? ""}`} title={`Curated: ${labelDisplay(l) ?? l}`}>
|
|
296
292
|
{labelDisplay(l) ?? prettySlug(l)}
|
|
297
293
|
</span>
|
|
298
294
|
))}
|
|
@@ -308,7 +304,7 @@ if (isProject && proj) {
|
|
|
308
304
|
</span>
|
|
309
305
|
)}
|
|
310
306
|
{healthLabel && (
|
|
311
|
-
<span class="
|
|
307
|
+
<span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground !bg-amber-50 !text-amber-800 dark:!bg-amber-950/80 dark:!text-amber-300">{healthLabel}</span>
|
|
312
308
|
)}
|
|
313
309
|
</div>
|
|
314
310
|
|
|
@@ -350,7 +346,7 @@ if (isProject && proj) {
|
|
|
350
346
|
href={repoUrl}
|
|
351
347
|
target="_blank"
|
|
352
348
|
rel="noopener noreferrer"
|
|
353
|
-
class="
|
|
349
|
+
class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90"
|
|
354
350
|
>
|
|
355
351
|
View on GitHub →
|
|
356
352
|
</a>
|
|
@@ -360,7 +356,7 @@ if (isProject && proj) {
|
|
|
360
356
|
href={homepageUrl}
|
|
361
357
|
target="_blank"
|
|
362
358
|
rel="noopener noreferrer"
|
|
363
|
-
class="
|
|
359
|
+
class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent"
|
|
364
360
|
>
|
|
365
361
|
Homepage
|
|
366
362
|
</a>
|
|
@@ -371,7 +367,7 @@ if (isProject && proj) {
|
|
|
371
367
|
|
|
372
368
|
{/* ── Score bars ───────────────────────────────────────── */}
|
|
373
369
|
{proj && (
|
|
374
|
-
<section class="mt-8
|
|
370
|
+
<section class="mt-8 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-6" aria-labelledby="scores-heading">
|
|
375
371
|
<h2 id="scores-heading" class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
376
372
|
Scores
|
|
377
373
|
</h2>
|
|
@@ -396,7 +392,7 @@ if (isProject && proj) {
|
|
|
396
392
|
|
|
397
393
|
{/* ── GitHub signals dl ────────────────────────────────── */}
|
|
398
394
|
{isProject && github && (
|
|
399
|
-
<section class="mt-8
|
|
395
|
+
<section class="mt-8 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-6" aria-labelledby="signals-heading">
|
|
400
396
|
<h2 id="signals-heading" class="m-0 text-lg font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
401
397
|
GitHub signals
|
|
402
398
|
</h2>
|
|
@@ -461,8 +457,8 @@ if (isProject && proj) {
|
|
|
461
457
|
{/* ── Decision signals (3-col) ──────────────────────────── */}
|
|
462
458
|
{isProject && github && (
|
|
463
459
|
<section class="mt-8 grid grid-cols-1 gap-4 lg:grid-cols-3" aria-label="Decision signals">
|
|
464
|
-
{/* Repository health
|
|
465
|
-
<div class="
|
|
460
|
+
{/* Repository health card */}
|
|
461
|
+
<div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
466
462
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
467
463
|
Repository health
|
|
468
464
|
</h3>
|
|
@@ -501,7 +497,7 @@ if (isProject && proj) {
|
|
|
501
497
|
</div>
|
|
502
498
|
|
|
503
499
|
{/* Activity bar chart */}
|
|
504
|
-
<div class="
|
|
500
|
+
<div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
505
501
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
506
502
|
Activity
|
|
507
503
|
</h3>
|
|
@@ -537,7 +533,7 @@ if (isProject && proj) {
|
|
|
537
533
|
</div>
|
|
538
534
|
|
|
539
535
|
{/* Contribution readiness chips */}
|
|
540
|
-
<div class="
|
|
536
|
+
<div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
541
537
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
542
538
|
Contribution readiness
|
|
543
539
|
</h3>
|
|
@@ -563,7 +559,7 @@ if (isProject && proj) {
|
|
|
563
559
|
|
|
564
560
|
{/* ── Language mix ─────────────────────────────────────── */}
|
|
565
561
|
{isProject && languages.length > 0 && (
|
|
566
|
-
<section class="mt-4
|
|
562
|
+
<section class="mt-4 rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors p-5">
|
|
567
563
|
<h3 class="m-0 text-[1rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
568
564
|
Language mix
|
|
569
565
|
</h3>
|
|
@@ -597,7 +593,7 @@ if (isProject && proj) {
|
|
|
597
593
|
Stack
|
|
598
594
|
</span>
|
|
599
595
|
{stacks.map((s) => (
|
|
600
|
-
<span class="
|
|
596
|
+
<span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5">
|
|
601
597
|
<Icon name={s} category="stack" size={14} />
|
|
602
598
|
{s}
|
|
603
599
|
</span>
|
|
@@ -610,7 +606,7 @@ if (isProject && proj) {
|
|
|
610
606
|
Platform
|
|
611
607
|
</span>
|
|
612
608
|
{platforms.map((p) => (
|
|
613
|
-
<span class="
|
|
609
|
+
<span class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5">
|
|
614
610
|
<Icon name={p} category="platform" size={14} />
|
|
615
611
|
{p}
|
|
616
612
|
</span>
|
|
@@ -632,7 +628,7 @@ if (isProject && proj) {
|
|
|
632
628
|
<li>
|
|
633
629
|
<a
|
|
634
630
|
href={`/${slug}?q=${encodeURIComponent(t)}`}
|
|
635
|
-
class="
|
|
631
|
+
class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground items-center gap-1.5"
|
|
636
632
|
>
|
|
637
633
|
{t}
|
|
638
634
|
</a>
|
|
@@ -650,12 +646,12 @@ if (isProject && proj) {
|
|
|
650
646
|
</h2>
|
|
651
647
|
<ul class="mt-4 m-0 flex list-none flex-col gap-2 p-0">
|
|
652
648
|
{distribution.map((c) => (
|
|
653
|
-
<li class="
|
|
649
|
+
<li class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-wrap items-baseline justify-between gap-2 p-3">
|
|
654
650
|
<div class="flex flex-col">
|
|
655
651
|
<span class="text-sm font-semibold text-ink-900 dark:text-ink-100">
|
|
656
652
|
{c.label ?? c.type}
|
|
657
653
|
{c.verified && (
|
|
658
|
-
<span class="ml-2
|
|
654
|
+
<span class="ml-2 inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground !bg-emerald-50 !text-emerald-700 dark:!bg-emerald-950/80 dark:!text-emerald-300">Verified</span>
|
|
659
655
|
)}
|
|
660
656
|
</span>
|
|
661
657
|
{c.notes && (
|
|
@@ -666,7 +662,7 @@ if (isProject && proj) {
|
|
|
666
662
|
href={c.url}
|
|
667
663
|
target="_blank"
|
|
668
664
|
rel="noopener noreferrer"
|
|
669
|
-
class="
|
|
665
|
+
class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent"
|
|
670
666
|
>
|
|
671
667
|
{c.platform ?? c.type} →
|
|
672
668
|
</a>
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* - RefinePanel (Stack / Platform / Category / License / Sort)
|
|
8
8
|
* - RecordSection rows (full record cards; the `ItemSection` V0
|
|
9
9
|
* name was renamed to `RecordSection` in V1)
|
|
10
|
-
* - ExploreByCategory / ExploreByStack (compact pill lists)
|
|
11
10
|
*
|
|
12
11
|
* Generic: the URL slug (e.g. `/projects/`, `/resources/`,
|
|
13
12
|
* `/entities/`) is derived from the blueprint via `getStaticPaths`,
|
|
@@ -28,14 +27,10 @@ import SmartLensTabs from "@grove-dev/astro/components/SmartLensTabs.astro";
|
|
|
28
27
|
import RefinePanel from "@grove-dev/astro/components/RefinePanel.astro";
|
|
29
28
|
import IndexRow from "@grove-dev/astro/components/IndexRow.astro";
|
|
30
29
|
import Pagination from "@grove-dev/astro/components/Pagination.astro";
|
|
31
|
-
import ExploreByCategory from "@grove-dev/astro/components/ExploreByCategory.astro";
|
|
32
|
-
import ExploreByStack from "@grove-dev/astro/components/ExploreByStack.astro";
|
|
33
30
|
import {
|
|
34
31
|
activeFilterChips,
|
|
35
32
|
applySort,
|
|
36
33
|
buildFacets,
|
|
37
|
-
countByCategory,
|
|
38
|
-
countByStack,
|
|
39
34
|
effectivePage,
|
|
40
35
|
effectiveSort,
|
|
41
36
|
filterRecords,
|
|
@@ -70,16 +65,6 @@ const chips = activeFilterChips(filters);
|
|
|
70
65
|
const title = `Browse ${slug} — ${siteConfig.name}`;
|
|
71
66
|
const description = `A searchable directory of ${itemLabelPlural()} for ${siteConfig.name}.`;
|
|
72
67
|
|
|
73
|
-
// Compute the top categories and stacks for the explore pills
|
|
74
|
-
const categoryEntries = [...countByCategory(items as Parameters<typeof countByCategory>[0]).entries()]
|
|
75
|
-
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
76
|
-
.slice(0, 12)
|
|
77
|
-
.map(([name, count]) => ({ name, slug: name, count }));
|
|
78
|
-
const stackEntries = [...countByStack(items as Parameters<typeof countByStack>[0]).entries()]
|
|
79
|
-
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
80
|
-
.slice(0, 18)
|
|
81
|
-
.map(([name, count]) => ({ name, slug: name, count }));
|
|
82
|
-
|
|
83
68
|
function hrefForPage(target: number): string {
|
|
84
69
|
const next: IndexFilters = { ...filters, page: target };
|
|
85
70
|
const query = searchParamsFromFilters(next).toString();
|
|
@@ -125,7 +110,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
125
110
|
submitLabel={`Submit ${itemLabel()}`}
|
|
126
111
|
>
|
|
127
112
|
<section class="border-b border-ink-200 bg-white dark:border-ink-800 dark:bg-ink-950">
|
|
128
|
-
<div class="
|
|
113
|
+
<div class="mx-auto w-full max-w-wide px-5 pt-10 sm:px-7 sm:pt-12">
|
|
129
114
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
130
115
|
{slug}
|
|
131
116
|
</span>
|
|
@@ -186,7 +171,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
186
171
|
<path d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06Z" />
|
|
187
172
|
</svg>
|
|
188
173
|
</label>
|
|
189
|
-
<button type="submit" class="
|
|
174
|
+
<button type="submit" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">Search</button>
|
|
190
175
|
</form>
|
|
191
176
|
|
|
192
177
|
<div class="mt-4">
|
|
@@ -207,22 +192,8 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
207
192
|
</div>
|
|
208
193
|
</section>
|
|
209
194
|
|
|
210
|
-
|
|
211
|
-
<
|
|
212
|
-
categories={categoryEntries}
|
|
213
|
-
pathPrefix={`/${slug}`}
|
|
214
|
-
/>
|
|
215
|
-
)}
|
|
216
|
-
|
|
217
|
-
{stackEntries.length > 0 && (
|
|
218
|
-
<ExploreByStack
|
|
219
|
-
stacks={stackEntries}
|
|
220
|
-
pathPrefix={`/${slug}`}
|
|
221
|
-
/>
|
|
222
|
-
)}
|
|
223
|
-
|
|
224
|
-
<section class="grove-section-tight">
|
|
225
|
-
<div class="grove-container-wide mx-auto px-5 sm:px-7">
|
|
195
|
+
<section class="py-10 sm:py-12">
|
|
196
|
+
<div class="mx-auto w-full max-w-wide px-5 sm:px-7">
|
|
226
197
|
<header class="mb-6">
|
|
227
198
|
<h2 class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
228
199
|
{`All ${itemLabelPlural()}`}
|
|
@@ -249,7 +220,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
249
220
|
{chips.map((chip) => (
|
|
250
221
|
<a
|
|
251
222
|
href={hrefForRemove(chip.key, chip.value)}
|
|
252
|
-
class="
|
|
223
|
+
class="inline-flex min-h-5 items-center justify-center gap-1 rounded-full border border-transparent bg-secondary px-2 py-0.5 align-middle text-xs font-medium leading-none text-secondary-foreground transition-colors hover:border-ink-400"
|
|
253
224
|
>
|
|
254
225
|
{chip.label} <span aria-hidden="true">x</span>
|
|
255
226
|
</a>
|
|
@@ -277,7 +248,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
277
248
|
<p class="m-0 text-sm font-medium text-ink-700 dark:text-ink-200">
|
|
278
249
|
{`No ${itemLabel()}s match these filters.`}
|
|
279
250
|
</p>
|
|
280
|
-
<a href={`/${slug}`} class="
|
|
251
|
+
<a href={`/${slug}`} class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent mt-4">Clear filters</a>
|
|
281
252
|
</div>
|
|
282
253
|
<nav class="mt-8 flex flex-wrap items-center justify-center gap-1.5" aria-label="Pagination">
|
|
283
254
|
<Pagination current={page} total={pages} hrefFor={hrefForPage} />
|
|
@@ -286,7 +257,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
286
257
|
</section>
|
|
287
258
|
|
|
288
259
|
<section class="border-t border-ink-200 dark:border-ink-800">
|
|
289
|
-
<div class="
|
|
260
|
+
<div class="mx-auto w-full max-w-wide px-5 sm:px-7">
|
|
290
261
|
<div class="flex flex-col items-center gap-3 py-12 text-center">
|
|
291
262
|
<h2 class="m-0 text-xl font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
292
263
|
{`Know a real ${itemLabel()} that belongs here?`}
|
|
@@ -296,7 +267,7 @@ function hrefForRemove(key: keyof IndexFilters, value: string): string {
|
|
|
296
267
|
</p>
|
|
297
268
|
<a
|
|
298
269
|
href={siteConfig.submitUrl ?? "/submit"}
|
|
299
|
-
class="
|
|
270
|
+
class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90 mt-2"
|
|
300
271
|
>
|
|
301
272
|
{`Submit ${itemLabel()}`}
|
|
302
273
|
</a>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* - New: "A growing community knowledge site. A curated, health-
|
|
14
14
|
* aware developer directory…"
|
|
15
15
|
*
|
|
16
|
-
* The card
|
|
16
|
+
* The Tailwind card and button utilities match the
|
|
17
17
|
* rest of the framework, and the data fields it references
|
|
18
18
|
* (`siteConfig.tagline`, `siteConfig.repoUrl`) all flow from the
|
|
19
19
|
* site config — no hardcoded brand or copy.
|
|
@@ -57,7 +57,7 @@ const item = itemLabel();
|
|
|
57
57
|
</p>
|
|
58
58
|
</header>
|
|
59
59
|
|
|
60
|
-
<section class="
|
|
60
|
+
<section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-8 p-5 sm:p-6">
|
|
61
61
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
62
62
|
Why
|
|
63
63
|
</span>
|
|
@@ -78,7 +78,7 @@ const item = itemLabel();
|
|
|
78
78
|
</p>
|
|
79
79
|
</section>
|
|
80
80
|
|
|
81
|
-
<section class="
|
|
81
|
+
<section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
|
|
82
82
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
83
83
|
Why real {item}s
|
|
84
84
|
</span>
|
|
@@ -95,7 +95,7 @@ const item = itemLabel();
|
|
|
95
95
|
</p>
|
|
96
96
|
</section>
|
|
97
97
|
|
|
98
|
-
<section class="
|
|
98
|
+
<section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
|
|
99
99
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
100
100
|
What you get
|
|
101
101
|
</span>
|
|
@@ -130,7 +130,7 @@ const item = itemLabel();
|
|
|
130
130
|
</ul>
|
|
131
131
|
</section>
|
|
132
132
|
|
|
133
|
-
<section class="
|
|
133
|
+
<section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
|
|
134
134
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
135
135
|
Generic by design
|
|
136
136
|
</span>
|
|
@@ -145,7 +145,7 @@ const item = itemLabel();
|
|
|
145
145
|
</p>
|
|
146
146
|
</section>
|
|
147
147
|
|
|
148
|
-
<section class="
|
|
148
|
+
<section class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors mt-4 p-5 sm:p-6">
|
|
149
149
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
150
150
|
Free to fork, free to extend
|
|
151
151
|
</span>
|
|
@@ -164,18 +164,18 @@ const item = itemLabel();
|
|
|
164
164
|
</section>
|
|
165
165
|
|
|
166
166
|
<section class="mt-8 flex flex-wrap items-center gap-3">
|
|
167
|
-
<a href={`/${slug}`} class="
|
|
167
|
+
<a href={`/${slug}`} class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-primary text-primary-foreground hover:bg-primary/90">Browse the directory</a>
|
|
168
168
|
{githubUrl && (
|
|
169
169
|
<a
|
|
170
170
|
href={githubUrl}
|
|
171
171
|
target="_blank"
|
|
172
172
|
rel="noopener noreferrer"
|
|
173
|
-
class="
|
|
173
|
+
class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 border-border bg-secondary text-foreground hover:bg-accent"
|
|
174
174
|
>
|
|
175
175
|
View on GitHub
|
|
176
176
|
</a>
|
|
177
177
|
)}
|
|
178
|
-
<a href="/submit" class="
|
|
178
|
+
<a href="/submit" class="inline-flex h-8 cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent px-3 text-sm font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35 bg-transparent text-foreground hover:bg-accent hover:text-accent-foreground">Submit a {item} →</a>
|
|
179
179
|
</section>
|
|
180
180
|
</article>
|
|
181
181
|
</Container>
|
|
@@ -82,8 +82,8 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
|
|
|
82
82
|
---
|
|
83
83
|
|
|
84
84
|
<BaseLayout title={title} description={description} site={siteConfig}>
|
|
85
|
-
<section class="
|
|
86
|
-
<div class="
|
|
85
|
+
<section class="py-12 sm:py-16">
|
|
86
|
+
<div class="mx-auto w-full max-w-wide px-5 py-12 sm:px-7 sm:py-16">
|
|
87
87
|
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
88
88
|
Contributors
|
|
89
89
|
</span>
|
|
@@ -112,9 +112,9 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
|
|
|
112
112
|
</section>
|
|
113
113
|
|
|
114
114
|
{sorted.length === 0 ? (
|
|
115
|
-
<section class="
|
|
116
|
-
<div class="
|
|
117
|
-
<div class="
|
|
115
|
+
<section class="py-12 sm:py-16">
|
|
116
|
+
<div class="mx-auto w-full max-w-container px-5 pb-16 sm:px-7">
|
|
117
|
+
<div class="rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-col items-center gap-3 p-8 text-center">
|
|
118
118
|
<p class="m-0 text-sm text-ink-500 dark:text-ink-400">
|
|
119
119
|
No contributors synced yet. Once the first sync runs, every
|
|
120
120
|
contributor avatar lands here.
|
|
@@ -123,8 +123,8 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
|
|
|
123
123
|
</div>
|
|
124
124
|
</section>
|
|
125
125
|
) : (
|
|
126
|
-
<section class="
|
|
127
|
-
<div class="
|
|
126
|
+
<section class="py-12 sm:py-16" aria-label="Contributors list">
|
|
127
|
+
<div class="mx-auto w-full max-w-wide px-5 pb-16 sm:px-7">
|
|
128
128
|
<ul
|
|
129
129
|
class="m-0 grid list-none grid-cols-2 gap-3 p-0 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6"
|
|
130
130
|
aria-label="Contributors"
|
|
@@ -140,7 +140,7 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
|
|
|
140
140
|
href={href}
|
|
141
141
|
target="_blank"
|
|
142
142
|
rel="noopener noreferrer"
|
|
143
|
-
class="group
|
|
143
|
+
class="group rounded-[calc(var(--radius)+0.25rem)] border-0 bg-muted shadow-none transition-colors flex flex-col items-center gap-2 p-3 text-center no-underline hover:border-ink-300 dark:hover:border-ink-700"
|
|
144
144
|
data-event="contributor_click"
|
|
145
145
|
data-event-source="contributors_page"
|
|
146
146
|
>
|
|
@@ -173,12 +173,3 @@ const contributorsGraphHref = repoUrl ? `${repoUrl.replace(/\/$/, "")}/graphs/co
|
|
|
173
173
|
</section>
|
|
174
174
|
)}
|
|
175
175
|
</BaseLayout>
|
|
176
|
-
|
|
177
|
-
<style>
|
|
178
|
-
.line-clamp-1 {
|
|
179
|
-
display: -webkit-box;
|
|
180
|
-
-webkit-line-clamp: 1;
|
|
181
|
-
-webkit-box-orient: vertical;
|
|
182
|
-
overflow: hidden;
|
|
183
|
-
}
|
|
184
|
-
</style>
|