@grove-dev/astro 0.2.10 → 0.2.14
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 +26 -18
- package/dist/lib/display.d.ts +11 -17
- package/dist/lib/display.d.ts.map +1 -1
- package/dist/lib/display.js +43 -43
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/format.d.ts +6 -7
- package/dist/lib/format.d.ts.map +1 -1
- package/dist/lib/format.js +11 -12
- package/dist/lib/format.js.map +1 -1
- package/dist/lib/lenses.d.ts +35 -73
- package/dist/lib/lenses.d.ts.map +1 -1
- package/dist/lib/lenses.js +144 -126
- package/dist/lib/lenses.js.map +1 -1
- package/dist/lib/repo.d.ts +12 -22
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +13 -25
- package/dist/lib/repo.js.map +1 -1
- package/dist/lib/scores.d.ts +15 -16
- package/dist/lib/scores.d.ts.map +1 -1
- package/dist/lib/scores.js +4 -11
- package/dist/lib/scores.js.map +1 -1
- package/dist/lib/search.d.ts +44 -129
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +192 -180
- package/dist/lib/search.js.map +1 -1
- package/dist/lib/taxonomy-counts.d.ts +7 -21
- package/dist/lib/taxonomy-counts.d.ts.map +1 -1
- package/dist/lib/taxonomy-counts.js +12 -31
- package/dist/lib/taxonomy-counts.js.map +1 -1
- package/package.json +4 -4
- package/src/components/CategoryGrid.astro +43 -31
- package/src/components/ContributorsGrid.astro +3 -3
- package/src/components/CurationGrid.astro +41 -0
- package/src/components/ExploreByStack.astro +6 -0
- package/src/components/FilterGroupMenu.astro +49 -0
- package/src/components/FilterOptions.astro +44 -0
- package/src/components/Hero.astro +198 -147
- package/src/components/Icon.astro +10 -3
- package/src/components/IndexRow.astro +75 -0
- package/src/components/ItemCard.astro +266 -248
- package/src/components/Pagination.astro +63 -0
- package/src/components/RecordSection.astro +70 -34
- package/src/components/RefinePanel.astro +115 -141
- package/src/components/StackGrid.astro +32 -24
- package/src/components/WhyThisExists.astro +32 -32
- package/src/layouts/BaseLayout.astro +104 -49
- package/src/layouts/Container.astro +5 -44
- package/src/layouts/Footer.astro +31 -20
- package/src/layouts/Header.astro +40 -19
- package/src/layouts/SectionHeader.astro +27 -43
- package/src/layouts/Seo.astro +38 -56
- package/src/layouts/ThemeToggle.astro +35 -48
- package/src/lib/display.ts +46 -53
- package/src/lib/format.ts +11 -12
- package/src/lib/lenses.ts +151 -172
- package/src/lib/repo.ts +17 -28
- package/src/lib/scores.ts +15 -19
- package/src/lib/search.ts +218 -291
- package/src/lib/taxonomy-counts.ts +12 -46
- package/src/styles.css +209 -1
- package/templates/default/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/templates/default/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- package/templates/default/.github/ISSUE_TEMPLATE/record_submission.md +48 -0
- package/templates/default/.github/pull_request_template.md +27 -0
- package/templates/default/.github/workflows/build.yml +55 -0
- package/templates/default/.github/workflows/cleanup-stale-records.yml +48 -0
- package/templates/default/.github/workflows/daily-refresh.yml +53 -0
- package/templates/default/.github/workflows/sync-contributors.yml +53 -0
- package/templates/default/.github/workflows/sync-github-metadata.yml +58 -0
- package/templates/default/.github/workflows/validate-data.yml +35 -0
- package/templates/default/data/records/coolify.yml +46 -0
- package/templates/default/data/records/gitea.yml +46 -0
- package/templates/default/data/records/hoppscotch.yml +47 -0
- package/templates/default/data/records/inventree.yml +47 -0
- package/templates/default/data/records/label-studio.yml +46 -0
- package/templates/default/data/records/logseq.yml +48 -0
- package/templates/default/data/records/private-gpt.yml +47 -0
- package/templates/default/data/records/wakapi.yml +44 -0
- package/templates/default/data/taxonomy/categories.yml +47 -0
- package/templates/default/data/taxonomy/distribution-channels.yml +43 -0
- package/templates/default/data/taxonomy/platforms.yml +27 -0
- package/templates/default/data/taxonomy/stacks.yml +41 -0
- package/templates/default/grove.config.ts +1 -1
- package/templates/default/package.json +5 -4
- package/templates/default/public/icons/stacks/bun.svg +1 -0
- package/templates/default/public/icons/stacks/clojure.svg +1 -0
- package/templates/default/public/icons/stacks/clojurescript.svg +1 -0
- package/templates/default/public/icons/stacks/deno.svg +1 -0
- package/templates/default/public/icons/stacks/django.svg +1 -0
- package/templates/default/public/icons/stacks/docker.svg +1 -0
- package/templates/default/public/icons/stacks/go.svg +1 -0
- package/templates/default/public/icons/stacks/llm.svg +1 -0
- package/templates/default/public/icons/stacks/node.js.svg +1 -0
- package/templates/default/public/icons/stacks/sveltekit.svg +1 -0
- package/templates/default/public/icons/stacks/vue.svg +1 -0
- package/templates/default/public/llms-full.txt +197 -5
- package/templates/default/public/llms.txt +107 -6
- package/templates/default/public/sitemap.xml +63 -0
- package/templates/default/scripts/build-llms.mjs +123 -0
- package/templates/default/scripts/build-records-json.mjs +27 -0
- package/templates/default/scripts/build-sitemap.mjs +20 -0
- package/templates/default/scripts/cleanup-stale-records.mjs +20 -0
- package/templates/default/scripts/enrich-github-metadata.mjs +99 -0
- package/templates/default/scripts/fetch-icons.mjs +137 -0
- package/templates/default/scripts/migrate-legacy-to-schema-v1.mjs +86 -0
- package/templates/default/scripts/parse-legacy-readme.mjs +59 -0
- package/templates/default/scripts/refresh-records-activity.mjs +24 -0
- package/templates/default/scripts/repair-license-format.mjs +72 -0
- package/templates/default/scripts/report-cleanup-candidates.mjs +21 -0
- package/templates/default/scripts/seed-from-github.mjs +62 -0
- package/templates/default/scripts/sync-contributors.mjs +145 -0
- package/templates/default/scripts/sync-github-metadata.mjs +28 -0
- package/templates/default/scripts/validate-records.mjs +27 -0
- package/templates/default/src/data/records.ts +138 -51
- package/templates/default/src/pages/404.astro +64 -0
- package/templates/default/src/pages/{projects/[slug].astro → [slug]/[recordSlug].astro} +236 -66
- package/templates/default/src/pages/[slug]/index.astro +477 -0
- package/templates/default/src/pages/about.astro +165 -82
- package/templates/default/src/pages/apps/[recordSlug].astro +33 -0
- package/templates/default/src/pages/contributors.astro +184 -0
- package/templates/default/src/pages/index.astro +119 -162
- package/templates/default/src/pages/submit.astro +276 -114
- package/templates/default/wrangler.jsonc +28 -0
- package/LICENSE +0 -21
- package/src/components/AppsIndexRow.astro +0 -204
- package/src/components/AppsPagination.astro +0 -126
- package/templates/default/.astro/content.d.ts +0 -155
- package/templates/default/.astro/settings.json +0 -5
- package/templates/default/.astro/types.d.ts +0 -1
- package/templates/default/data/generated/apps.json +0 -1
- package/templates/default/data/generated/records.full.json +0 -8
- package/templates/default/data/generated/records.index.json +0 -7
- package/templates/default/data/generated/records.json +0 -8
- package/templates/default/data/generated/repo-stats.json +0 -1
- package/templates/default/data/generated/site-config.json +0 -31
- package/templates/default/dist/_astro/BaseLayout.Dz_HR-68.css +0 -1
- package/templates/default/dist/about/index.html +0 -165
- package/templates/default/dist/icons/platforms/android.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-dark.svg +0 -1
- package/templates/default/dist/icons/platforms/apple-light.svg +0 -1
- package/templates/default/dist/icons/platforms/chrome.svg +0 -1
- package/templates/default/dist/icons/platforms/chromeos.svg +0 -5
- package/templates/default/dist/icons/platforms/desktop.svg +0 -4
- package/templates/default/dist/icons/platforms/embedded.svg +0 -5
- package/templates/default/dist/icons/platforms/ios.svg +0 -1
- package/templates/default/dist/icons/platforms/linux.svg +0 -1
- package/templates/default/dist/icons/platforms/macos.svg +0 -1
- package/templates/default/dist/icons/platforms/ubuntu.svg +0 -1
- package/templates/default/dist/icons/platforms/web.svg +0 -5
- package/templates/default/dist/icons/platforms/windows.svg +0 -6
- package/templates/default/dist/icons/stacks/android.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-dark.svg +0 -1
- package/templates/default/dist/icons/stacks/apple-light.svg +0 -1
- package/templates/default/dist/icons/stacks/capacitor.svg +0 -14
- package/templates/default/dist/icons/stacks/dart.svg +0 -1
- package/templates/default/dist/icons/stacks/firebase.svg +0 -1
- package/templates/default/dist/icons/stacks/flutter.svg +0 -1
- package/templates/default/dist/icons/stacks/graphql.svg +0 -1
- package/templates/default/dist/icons/stacks/ionic.svg +0 -4
- package/templates/default/dist/icons/stacks/java.svg +0 -1
- package/templates/default/dist/icons/stacks/javascript.svg +0 -1
- package/templates/default/dist/icons/stacks/kotlin.svg +0 -1
- package/templates/default/dist/icons/stacks/mongodb.svg +0 -1
- package/templates/default/dist/icons/stacks/nodejs.svg +0 -1
- package/templates/default/dist/icons/stacks/python.svg +0 -1
- package/templates/default/dist/icons/stacks/react-native.svg +0 -1
- package/templates/default/dist/icons/stacks/react.svg +0 -1
- package/templates/default/dist/icons/stacks/rust.svg +0 -1
- package/templates/default/dist/icons/stacks/solidity.svg +0 -1
- package/templates/default/dist/icons/stacks/swift.svg +0 -1
- package/templates/default/dist/icons/stacks/tensorflow.svg +0 -1
- package/templates/default/dist/icons/stacks/typescript.svg +0 -1
- package/templates/default/dist/index.html +0 -55
- package/templates/default/dist/llms-full.txt +0 -14
- package/templates/default/dist/llms.txt +0 -11
- package/templates/default/dist/og-image.svg +0 -48
- package/templates/default/dist/projects/index.html +0 -61
- package/templates/default/dist/robots.txt +0 -53
- package/templates/default/dist/sitemap.xml +0 -21
- package/templates/default/dist/submit/index.html +0 -69
- package/templates/default/node_modules/.bin/astro +0 -21
- package/templates/default/node_modules/.bin/astro-check +0 -21
- package/templates/default/node_modules/.bin/grove +0 -21
- package/templates/default/node_modules/.bin/jiti +0 -21
- package/templates/default/node_modules/.bin/prettier +0 -21
- package/templates/default/node_modules/.bin/rollup +0 -21
- package/templates/default/node_modules/.bin/tsc +0 -21
- package/templates/default/node_modules/.bin/tsserver +0 -21
- package/templates/default/node_modules/.bin/tsx +0 -21
- package/templates/default/node_modules/.bin/vite +0 -21
- package/templates/default/node_modules/.bin/yaml +0 -21
- package/templates/default/node_modules/.vite/deps/_metadata.json +0 -79
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js +0 -433
- package/templates/default/node_modules/.vite/deps/astro-KRVZHBC5.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___aria-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js +0 -5
- package/templates/default/node_modules/.vite/deps/astro___axobject-query.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js +0 -9
- package/templates/default/node_modules/.vite/deps/astro___html-escaper.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -241
- package/templates/default/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js +0 -1592
- package/templates/default/node_modules/.vite/deps/audit-GYHQ3ITH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js +0 -30
- package/templates/default/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js +0 -186
- package/templates/default/node_modules/.vite/deps/chunk-7P4KIK53.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js +0 -48
- package/templates/default/node_modules/.vite/deps/chunk-BQGHFQJH.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js +0 -6779
- package/templates/default/node_modules/.vite/deps/chunk-CJCDKWP2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js +0 -33
- package/templates/default/node_modules/.vite/deps/chunk-FHJ3XBDE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js +0 -88
- package/templates/default/node_modules/.vite/deps/chunk-FQEMNIUQ.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js +0 -72
- package/templates/default/node_modules/.vite/deps/chunk-HBSVDAQD.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js +0 -3757
- package/templates/default/node_modules/.vite/deps/chunk-HDOQ2U3U.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js +0 -54
- package/templates/default/node_modules/.vite/deps/chunk-XEBKRCNE.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/package.json +0 -3
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js +0 -213
- package/templates/default/node_modules/.vite/deps/settings-C5H4VHXU.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js +0 -525
- package/templates/default/node_modules/.vite/deps/toolbar-H5DB6HVP.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js +0 -1020
- package/templates/default/node_modules/.vite/deps/ui-library-JGHU6JE2.js.map +0 -7
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js +0 -147
- package/templates/default/node_modules/.vite/deps/xray-EFCQOW4E.js.map +0 -7
- package/templates/default/src/components/layout/BaseLayout.astro +0 -47
- package/templates/default/src/components/layout/Container.astro +0 -18
- package/templates/default/src/components/layout/Footer.astro +0 -27
- package/templates/default/src/components/layout/Header.astro +0 -43
- package/templates/default/src/lib/markdown.ts +0 -171
- package/templates/default/src/pages/projects/index.astro +0 -193
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Hero — the home page banner.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Mirrors the openapps layout: trust-stats row across the top,
|
|
6
|
+
* big two-line headline with a gradient accent on the second
|
|
7
|
+
* line, descriptive paragraph, search form with a Filters
|
|
8
|
+
* shortcut, quick-filter chips, and up to 3 CTAs.
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Generic: every value comes through props — site name, tagline,
|
|
14
|
-
* stats, search target. No siteConfig import.
|
|
10
|
+
* Generic: every value comes through props (or `siteConfig` for
|
|
11
|
+
* the trust stats). The label next to the count is blueprint-
|
|
12
|
+
* driven via `itemsLabel` — so a project-directory renders
|
|
13
|
+
* "apps indexed", a resource-hub renders "resources indexed",
|
|
14
|
+
* an ecosystem-map renders "entities indexed".
|
|
15
15
|
*/
|
|
16
|
+
import Container from "../layouts/Container.astro";
|
|
17
|
+
import { compact } from "../lib/format";
|
|
16
18
|
|
|
17
19
|
interface CTA {
|
|
18
20
|
label: string;
|
|
@@ -21,33 +23,43 @@ interface CTA {
|
|
|
21
23
|
external?: boolean;
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
interface HeroStats {
|
|
25
|
-
apps?: number;
|
|
26
|
-
categories?: number;
|
|
27
|
-
stacks?: number;
|
|
28
|
-
contributors?: number;
|
|
29
|
-
stars?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
26
|
interface Props {
|
|
33
|
-
/** Site/brand name
|
|
27
|
+
/** Site/brand name. */
|
|
34
28
|
name: string;
|
|
35
|
-
/** Short tagline
|
|
29
|
+
/** Short tagline — usually pulled from siteConfig.tagline. */
|
|
36
30
|
tagline: string;
|
|
37
|
-
/** Longer
|
|
31
|
+
/** Longer descriptive paragraph. */
|
|
38
32
|
description?: string;
|
|
39
|
-
/** Trust stats
|
|
40
|
-
stats?:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
/** Trust stats: items count, categories, stacks, contributors, stars. */
|
|
34
|
+
stats?: {
|
|
35
|
+
totalApps?: number;
|
|
36
|
+
totalCategories?: number;
|
|
37
|
+
totalStacks?: number;
|
|
38
|
+
totalOwners?: number;
|
|
39
|
+
totalStars?: number;
|
|
40
|
+
originalRepo?: string;
|
|
41
|
+
};
|
|
42
|
+
/** Search form target — the list page. */
|
|
44
43
|
searchHref?: string;
|
|
45
|
-
/**
|
|
44
|
+
/** Search input placeholder. */
|
|
45
|
+
searchPlaceholder?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Plural noun for the primary count (e.g. "apps", "resources",
|
|
48
|
+
* "entities"). Used in the trust-stats label and CTA copy.
|
|
49
|
+
*/
|
|
50
|
+
itemsLabel?: string;
|
|
51
|
+
/** Singular noun for the Submit / Browse copy. */
|
|
52
|
+
itemLabel?: string;
|
|
53
|
+
/** Quick filter chips under the search bar. */
|
|
54
|
+
quickFilters?: Array<{ label: string; href: string }>;
|
|
55
|
+
/** CTAs rendered below the form. */
|
|
46
56
|
ctas?: CTA[];
|
|
47
|
-
/** Extra class
|
|
57
|
+
/** Extra class. */
|
|
48
58
|
class?: string;
|
|
49
59
|
/** Eyebrow text rendered above the title. */
|
|
50
60
|
eyebrow?: string;
|
|
61
|
+
/** Optional badge content rendered next to the eyebrow. */
|
|
62
|
+
eyebrowBadge?: string;
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
const {
|
|
@@ -55,98 +67,191 @@ const {
|
|
|
55
67
|
tagline,
|
|
56
68
|
description,
|
|
57
69
|
stats = {},
|
|
58
|
-
searchPlaceholder = "Search the directory...",
|
|
59
70
|
searchHref = "/",
|
|
71
|
+
searchPlaceholder = "Search the directory...",
|
|
72
|
+
itemsLabel = "items",
|
|
73
|
+
itemLabel,
|
|
74
|
+
quickFilters = [],
|
|
60
75
|
ctas = [],
|
|
61
76
|
class: className = "",
|
|
62
|
-
eyebrow,
|
|
77
|
+
eyebrow = "A directory of curated knowledge",
|
|
78
|
+
eyebrowBadge,
|
|
63
79
|
} = Astro.props;
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const k = n / 1000;
|
|
69
|
-
return (k >= 10 ? Math.round(k).toString() : k.toFixed(1)) + "k";
|
|
70
|
-
}
|
|
71
|
-
return n.toString();
|
|
72
|
-
}
|
|
81
|
+
// Singular for the Submit-style CTA copy. Defaults to the lowercased
|
|
82
|
+
// items label so "apps" → "app" without an extra prop.
|
|
83
|
+
const singular = (itemLabel ?? itemsLabel.replace(/s$/, "")).toLowerCase();
|
|
73
84
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
stats.
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
const githubUrl = stats.originalRepo;
|
|
86
|
+
|
|
87
|
+
const trustStats: { value: string; label: string }[] = [
|
|
88
|
+
...(stats.totalApps !== undefined
|
|
89
|
+
? [{ value: compact(stats.totalApps) ?? "0", label: `${itemsLabel} indexed` }]
|
|
90
|
+
: []),
|
|
91
|
+
...(stats.totalCategories !== undefined
|
|
92
|
+
? [{ value: String(stats.totalCategories), label: "categories" }]
|
|
93
|
+
: []),
|
|
94
|
+
...(stats.totalStacks !== undefined
|
|
95
|
+
? [{ value: String(stats.totalStacks), label: "stacks" }]
|
|
96
|
+
: []),
|
|
97
|
+
...(stats.totalOwners !== undefined
|
|
98
|
+
? [{ value: compact(stats.totalOwners) ?? "0", label: "contributors" }]
|
|
99
|
+
: []),
|
|
100
|
+
...(stats.totalStars !== undefined
|
|
101
|
+
? [{ value: compact(stats.totalStars) ?? "0", label: "GitHub stars" }]
|
|
102
|
+
: []),
|
|
103
|
+
];
|
|
80
104
|
---
|
|
81
105
|
|
|
82
106
|
<section
|
|
83
107
|
class:list={[
|
|
84
|
-
"
|
|
108
|
+
"relative overflow-hidden border-b border-ink-200 dark:border-ink-800",
|
|
85
109
|
className,
|
|
86
110
|
]}
|
|
87
|
-
aria-labelledby="grove-hero-heading"
|
|
88
111
|
>
|
|
89
112
|
<div
|
|
90
113
|
aria-hidden="true"
|
|
91
|
-
class="
|
|
114
|
+
class="pointer-events-none absolute inset-0 opacity-60 dark:opacity-40"
|
|
115
|
+
style="
|
|
116
|
+
background-image:
|
|
117
|
+
radial-gradient(60% 50% at 15% 20%, rgba(31, 111, 235, 0.12), transparent 60%),
|
|
118
|
+
radial-gradient(50% 40% at 85% 10%, rgba(110, 231, 183, 0.10), transparent 60%),
|
|
119
|
+
radial-gradient(40% 40% at 50% 90%, rgba(251, 146, 60, 0.08), transparent 60%);
|
|
120
|
+
"
|
|
92
121
|
></div>
|
|
93
122
|
|
|
94
|
-
<
|
|
95
|
-
<div class="
|
|
96
|
-
|
|
97
|
-
<span
|
|
123
|
+
<Container>
|
|
124
|
+
<div class="relative py-14 sm:py-20">
|
|
125
|
+
<div class="max-w-3xl">
|
|
126
|
+
<span
|
|
127
|
+
class="inline-flex animate-fade-in items-center gap-2 text-2xs font-medium text-ink-500 dark:text-ink-400"
|
|
128
|
+
>
|
|
129
|
+
<span
|
|
130
|
+
aria-hidden="true"
|
|
131
|
+
class="inline-block h-1.5 w-1.5 animate-pulse rounded-full bg-emerald-500"
|
|
132
|
+
></span>
|
|
98
133
|
{eyebrow}
|
|
134
|
+
{eyebrowBadge && (
|
|
135
|
+
<span
|
|
136
|
+
class="inline-flex items-center rounded-full border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-amber-700 dark:border-amber-700/50 dark:bg-amber-900/20 dark:text-amber-300"
|
|
137
|
+
aria-label={eyebrowBadge}
|
|
138
|
+
>
|
|
139
|
+
{eyebrowBadge}
|
|
140
|
+
</span>
|
|
141
|
+
)}
|
|
99
142
|
</span>
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
143
|
+
|
|
144
|
+
<h1
|
|
145
|
+
class="mt-3 animate-fade-in-up text-[2.25rem] sm:text-[2.75rem] md:text-[3rem] font-semibold tracking-[-0.02em] leading-[1.05] text-ink-900 dark:text-ink-100 m-0"
|
|
146
|
+
style="animation-delay: 0.05s;"
|
|
147
|
+
>
|
|
148
|
+
{name}<br class="hidden sm:block" />
|
|
149
|
+
<span
|
|
150
|
+
class="bg-gradient-to-r from-ink-900 via-ink-700 to-ink-900 bg-clip-text text-transparent dark:from-ink-100 dark:via-ink-300 dark:to-ink-100"
|
|
151
|
+
style="background-size: 200% 200%; animation: fade-in-up 0.7s ease-out 0.1s both, gradient-shift 8s ease-in-out 1s infinite;"
|
|
152
|
+
>
|
|
153
|
+
{tagline}
|
|
154
|
+
</span>
|
|
155
|
+
</h1>
|
|
156
|
+
|
|
157
|
+
{description && (
|
|
158
|
+
<p
|
|
159
|
+
class="mt-4 animate-fade-in-up text-[0.9375rem] sm:text-[1rem] leading-relaxed text-ink-500 dark:text-ink-400 m-0 max-w-[64ch]"
|
|
160
|
+
style="animation-delay: 0.15s;"
|
|
161
|
+
>
|
|
162
|
+
{description}
|
|
163
|
+
</p>
|
|
164
|
+
)}
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
{/* Trust stats row */}
|
|
168
|
+
{trustStats.length > 0 && (
|
|
169
|
+
<dl
|
|
170
|
+
class="mt-7 grid grid-cols-2 gap-x-4 gap-y-3 sm:grid-cols-4"
|
|
171
|
+
aria-label="Directory at a glance"
|
|
172
|
+
>
|
|
173
|
+
{trustStats.map((s) => (
|
|
174
|
+
<div class="flex flex-col gap-0.5">
|
|
175
|
+
<dt class="sr-only">{s.label}</dt>
|
|
176
|
+
<dd class="m-0 font-mono text-[1.25rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
177
|
+
{s.value}
|
|
178
|
+
</dd>
|
|
179
|
+
<dd class="m-0 text-2xs uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
180
|
+
{s.label}
|
|
181
|
+
</dd>
|
|
182
|
+
</div>
|
|
183
|
+
))}
|
|
184
|
+
</dl>
|
|
114
185
|
)}
|
|
115
186
|
|
|
187
|
+
{/* Search + filters */}
|
|
116
188
|
<form
|
|
117
|
-
class="mt-
|
|
189
|
+
class="mt-7 flex flex-col gap-2 sm:flex-row sm:items-stretch"
|
|
118
190
|
role="search"
|
|
119
191
|
action={searchHref}
|
|
120
192
|
method="get"
|
|
121
|
-
data-grove-hero-search
|
|
122
193
|
>
|
|
123
194
|
<label class="relative flex-1">
|
|
124
|
-
<span class="sr-only">Search</span>
|
|
195
|
+
<span class="sr-only">Search {itemsLabel}, stacks, categories, licenses</span>
|
|
125
196
|
<svg
|
|
126
|
-
aria-hidden="true"
|
|
127
197
|
viewBox="0 0 16 16"
|
|
128
|
-
width="
|
|
129
|
-
height="
|
|
130
|
-
|
|
198
|
+
width="16"
|
|
199
|
+
height="16"
|
|
200
|
+
aria-hidden="true"
|
|
201
|
+
class="pointer-events-none absolute left-3.5 top-1/2 -translate-y-1/2 text-ink-400"
|
|
131
202
|
fill="currentColor"
|
|
132
203
|
>
|
|
133
|
-
<path
|
|
204
|
+
<path
|
|
205
|
+
d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215ZM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7Z"
|
|
206
|
+
></path>
|
|
134
207
|
</svg>
|
|
135
208
|
<input
|
|
136
209
|
type="search"
|
|
137
210
|
name="q"
|
|
138
211
|
placeholder={searchPlaceholder}
|
|
139
|
-
|
|
140
|
-
class="w-full rounded-md border border-ink-200 bg-white py-2.5 pl-9 pr-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"
|
|
212
|
+
class="h-10 w-full rounded-md border border-ink-200 bg-white pl-10 pr-3 text-[0.9375rem] text-ink-900 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 dark:placeholder:text-ink-500 dark:focus:border-ink-100 dark:focus:ring-ink-100/15"
|
|
141
213
|
/>
|
|
142
214
|
</label>
|
|
143
|
-
<
|
|
215
|
+
<a
|
|
216
|
+
href={searchHref}
|
|
217
|
+
class="inline-flex h-10 items-center justify-center gap-1.5 rounded-md border border-ink-200 bg-white px-3 text-[0.875rem] font-medium text-ink-700 transition-colors hover:border-ink-300 hover:bg-ink-50 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300 dark:hover:border-ink-700 dark:hover:bg-ink-900/40"
|
|
218
|
+
aria-label={`Browse all ${itemsLabel} with filters`}
|
|
219
|
+
>
|
|
220
|
+
<svg viewBox="0 0 16 16" width="13" height="13" aria-hidden="true" fill="currentColor">
|
|
221
|
+
<path d="M.75 3h14.5a.75.75 0 0 1 .55 1.26l-5.55 6.1v4.39a.75.75 0 0 1-1.2.6l-2.5-1.87a.75.75 0 0 1-.3-.6v-2.52L.2 4.26A.75.75 0 0 1 .75 3Z"/>
|
|
222
|
+
</svg>
|
|
223
|
+
<span>Filters</span>
|
|
224
|
+
</a>
|
|
225
|
+
<button
|
|
226
|
+
type="submit"
|
|
227
|
+
class="grove-btn grove-btn-primary h-10 px-4 sm:px-5"
|
|
228
|
+
>
|
|
144
229
|
Search
|
|
145
230
|
</button>
|
|
146
231
|
</form>
|
|
147
232
|
|
|
233
|
+
{quickFilters.length > 0 && (
|
|
234
|
+
<p
|
|
235
|
+
class="mt-2 text-2xs text-ink-500 dark:text-ink-400 m-0"
|
|
236
|
+
aria-hidden="true"
|
|
237
|
+
>
|
|
238
|
+
Try{" "}
|
|
239
|
+
{quickFilters.map((f, i) => (
|
|
240
|
+
<>
|
|
241
|
+
<a
|
|
242
|
+
href={f.href}
|
|
243
|
+
class="font-mono text-ink-700 underline-offset-2 hover:underline dark:text-ink-300"
|
|
244
|
+
>
|
|
245
|
+
{f.label}
|
|
246
|
+
</a>
|
|
247
|
+
{i < quickFilters.length - 1 && <span class="mx-1">·</span>}
|
|
248
|
+
</>
|
|
249
|
+
))}
|
|
250
|
+
</p>
|
|
251
|
+
)}
|
|
252
|
+
|
|
148
253
|
{ctas.length > 0 && (
|
|
149
|
-
<div class="mt-
|
|
254
|
+
<div class="mt-5 flex flex-wrap items-center gap-2">
|
|
150
255
|
{ctas.map((cta) => (
|
|
151
256
|
<a
|
|
152
257
|
href={cta.href}
|
|
@@ -170,77 +275,23 @@ const hasStats =
|
|
|
170
275
|
</div>
|
|
171
276
|
)}
|
|
172
277
|
|
|
173
|
-
{
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
189
|
-
{fmt(stats.categories)}
|
|
190
|
-
</dd>
|
|
191
|
-
<dt>categories</dt>
|
|
192
|
-
</div>
|
|
193
|
-
)}
|
|
194
|
-
{stats.stacks !== undefined && (
|
|
195
|
-
<div class="flex items-baseline gap-1.5">
|
|
196
|
-
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
197
|
-
{fmt(stats.stacks)}
|
|
198
|
-
</dd>
|
|
199
|
-
<dt>stacks</dt>
|
|
200
|
-
</div>
|
|
201
|
-
)}
|
|
202
|
-
{stats.contributors !== undefined && (
|
|
203
|
-
<div class="flex items-baseline gap-1.5">
|
|
204
|
-
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
205
|
-
{fmt(stats.contributors)}
|
|
206
|
-
</dd>
|
|
207
|
-
<dt>contributors</dt>
|
|
208
|
-
</div>
|
|
209
|
-
)}
|
|
210
|
-
{stats.stars !== undefined && (
|
|
211
|
-
<div class="flex items-baseline gap-1.5">
|
|
212
|
-
<dd class="m-0 font-mono text-base font-semibold text-ink-900 dark:text-ink-100">
|
|
213
|
-
{fmt(stats.stars)}
|
|
214
|
-
</dd>
|
|
215
|
-
<dt>total stars</dt>
|
|
216
|
-
</div>
|
|
217
|
-
)}
|
|
218
|
-
</dl>
|
|
278
|
+
{/* Help debug the type system when callers forget to pass ctas. */}
|
|
279
|
+
{ctas.length === 0 && githubUrl && (
|
|
280
|
+
<div class="mt-5 flex flex-wrap items-center gap-2">
|
|
281
|
+
<a href={searchHref} class="grove-btn grove-btn-primary">
|
|
282
|
+
Browse {itemsLabel}
|
|
283
|
+
</a>
|
|
284
|
+
<a
|
|
285
|
+
href={`${githubUrl}/blob/main/CONTRIBUTING.md`}
|
|
286
|
+
target="_blank"
|
|
287
|
+
rel="noopener noreferrer"
|
|
288
|
+
class="grove-btn grove-btn-outline"
|
|
289
|
+
>
|
|
290
|
+
Submit via GitHub
|
|
291
|
+
</a>
|
|
292
|
+
</div>
|
|
219
293
|
)}
|
|
294
|
+
{singular /* suppress unused warning on the singular variable */ ? null : null}
|
|
220
295
|
</div>
|
|
221
|
-
</
|
|
296
|
+
</Container>
|
|
222
297
|
</section>
|
|
223
|
-
|
|
224
|
-
<style>
|
|
225
|
-
.grove-hero__gradient {
|
|
226
|
-
background: linear-gradient(
|
|
227
|
-
120deg,
|
|
228
|
-
rgba(31, 111, 235, 0.08) 0%,
|
|
229
|
-
rgba(110, 231, 183, 0.06) 35%,
|
|
230
|
-
rgba(253, 186, 116, 0.06) 65%,
|
|
231
|
-
rgba(147, 197, 253, 0.08) 100%
|
|
232
|
-
);
|
|
233
|
-
background-size: 200% 200%;
|
|
234
|
-
animation: var(--animate-gradient-shift, gradient-shift 8s ease-in-out infinite);
|
|
235
|
-
}
|
|
236
|
-
:global(.dark) .grove-hero__gradient {
|
|
237
|
-
background: linear-gradient(
|
|
238
|
-
120deg,
|
|
239
|
-
rgba(31, 111, 235, 0.18) 0%,
|
|
240
|
-
rgba(6, 95, 70, 0.12) 35%,
|
|
241
|
-
rgba(67, 20, 7, 0.12) 65%,
|
|
242
|
-
rgba(30, 64, 175, 0.18) 100%
|
|
243
|
-
);
|
|
244
|
-
background-size: 200% 200%;
|
|
245
|
-
}
|
|
246
|
-
</style>
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* this component.
|
|
11
11
|
*
|
|
12
12
|
* The component is **generic** — it does not import any consumer-side
|
|
13
|
-
* `siteConfig`, `
|
|
13
|
+
* `siteConfig`, `item`, or blueprint-specific module. Every consumer
|
|
14
14
|
* supplies its own icon list (and optionally an `aliases` map for
|
|
15
15
|
* friendly ids) via props, and any `name` not in the registry
|
|
16
16
|
* falls back to the initials placeholder.
|
|
@@ -104,8 +104,14 @@ function resolveName(raw: string): string {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
const resolved = resolveName(name);
|
|
107
|
+
const platformStackIcons = new Set(["docker"]);
|
|
108
|
+
const textOnlyPlatforms = new Set(["self-host", "self-hosted"]);
|
|
107
109
|
const folder =
|
|
108
|
-
category === "stack"
|
|
110
|
+
category === "stack" || (category === "platform" && platformStackIcons.has(resolved))
|
|
111
|
+
? "stacks"
|
|
112
|
+
: category === "platform"
|
|
113
|
+
? "platforms"
|
|
114
|
+
: "brands";
|
|
109
115
|
const baseUrl = `${base.replace(/\/$/, "")}/${folder}/${resolved}`;
|
|
110
116
|
|
|
111
117
|
// Static: just point at the canonical svg.
|
|
@@ -116,7 +122,8 @@ const init = (fallbackInitials ?? name ?? "")
|
|
|
116
122
|
.replace(/[^a-z0-9]/gi, "")
|
|
117
123
|
.slice(0, 2)
|
|
118
124
|
.toUpperCase();
|
|
119
|
-
const showFallback =
|
|
125
|
+
const showFallback =
|
|
126
|
+
!resolved || (category === "platform" && textOnlyPlatforms.has(resolved));
|
|
120
127
|
|
|
121
128
|
// Build aria-label.
|
|
122
129
|
const ariaLabel = title ?? name;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { IndexRecord } from "@grove-dev/core";
|
|
3
|
+
import Icon from "./Icon.astro";
|
|
4
|
+
import { formatRelative, formatStars, getOwnerAndRepoFromRepoUrl, getOwnerAvatarUrl, statusDisplay } from "@grove-dev/astro";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
record: IndexRecord;
|
|
8
|
+
detailHref: string;
|
|
9
|
+
/** Whether to show stack/platform chips. Default true. */
|
|
10
|
+
showChips?: boolean;
|
|
11
|
+
class?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const { record, detailHref, showChips = true, class: className = "" } = Astro.props;
|
|
15
|
+
const item = record;
|
|
16
|
+
const project = item.kind === "project" ? item : null;
|
|
17
|
+
const name = item.kind === "resource" ? item.title : item.name;
|
|
18
|
+
const repoHref = project?.repoUrl ?? item.links.github;
|
|
19
|
+
const { owner, repo } = getOwnerAndRepoFromRepoUrl(repoHref ?? "");
|
|
20
|
+
const avatar = project ? project.logoUrl ?? getOwnerAvatarUrl(owner, 80) : undefined;
|
|
21
|
+
const initials = name.split(/\s+/).map((word) => word[0]).filter(Boolean).slice(0, 2).join("").toUpperCase();
|
|
22
|
+
const stacks = project ? [project.stack, ...project.stacks].filter(Boolean) : [];
|
|
23
|
+
const platforms = project?.platforms ?? [];
|
|
24
|
+
const stars = project?.github?.stars;
|
|
25
|
+
const updatedAt = project?.github?.pushedAt ?? (item.kind === "resource" ? item.publishedAt : null);
|
|
26
|
+
const license = project?.github?.license;
|
|
27
|
+
const status = project?.health?.status;
|
|
28
|
+
const curated = Boolean(item.curation?.reviewed || (project && (project.bestFor.length || project.whyListed.length || project.caveats.length)));
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<article class={`group relative flex flex-col gap-3 rounded-lg border border-ink-200 bg-white p-4 transition-colors hover:border-ink-300 hover:shadow-[0_1px_2px_rgba(0,0,0,0.04),0_4px_12px_-2px_rgba(0,0,0,0.06)] dark:border-ink-800 dark:bg-ink-950 dark:hover:border-ink-700 ${className}`}>
|
|
32
|
+
<header class="flex items-start gap-3">
|
|
33
|
+
<a href={detailHref} class="relative z-10 flex shrink-0" aria-label={`Open ${name} details`}>
|
|
34
|
+
{avatar ? (
|
|
35
|
+
<img src={avatar} alt={`${name} avatar`} width="44" height="44" loading="lazy" class="h-11 w-11 rounded-md border border-ink-200 bg-white object-cover dark:border-ink-800 dark:bg-ink-900" />
|
|
36
|
+
) : (
|
|
37
|
+
<span class="flex h-11 w-11 items-center justify-center rounded-md border border-ink-200 bg-ink-50 text-sm font-semibold text-ink-700 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-300">{initials}</span>
|
|
38
|
+
)}
|
|
39
|
+
</a>
|
|
40
|
+
<div class="min-w-0 flex-1">
|
|
41
|
+
<div class="flex flex-wrap items-baseline gap-x-2 gap-y-0.5">
|
|
42
|
+
<h3 class="m-0 text-[1.0625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
43
|
+
<a href={detailHref} class="relative z-10 hover:underline">{name}</a>
|
|
44
|
+
</h3>
|
|
45
|
+
{curated && <span class="text-[10px] font-medium uppercase tracking-wider text-emerald-700 dark:text-emerald-400">✓ curated</span>}
|
|
46
|
+
</div>
|
|
47
|
+
<div class="flex flex-wrap items-center gap-x-1.5 text-2xs text-ink-500 dark:text-ink-400">
|
|
48
|
+
{repoHref && owner && <a href={repoHref} target="_blank" rel="noopener noreferrer" class="relative z-10 font-mono hover:text-ink-900 dark:hover:text-ink-100">@{owner}{repo ? `/${repo}` : ""}</a>}
|
|
49
|
+
{owner && <span class="text-ink-300 dark:text-ink-700">·</span>}
|
|
50
|
+
<span>{item.category}</span>
|
|
51
|
+
{status && <><span class="text-ink-300 dark:text-ink-700">·</span><span>{statusDisplay(status)}</span></>}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
</header>
|
|
55
|
+
|
|
56
|
+
<p class="m-0 line-clamp-2 text-[0.8125rem] leading-relaxed text-ink-600 dark:text-ink-300">{item.description}</p>
|
|
57
|
+
|
|
58
|
+
<dl class="grid grid-cols-3 gap-x-3 text-2xs">
|
|
59
|
+
<div class="flex items-baseline gap-1.5"><dt class="font-medium uppercase tracking-wider text-ink-500">Stars</dt><dd class="font-mono text-ink-700 dark:text-ink-300">{formatStars(stars) ?? "—"}</dd></div>
|
|
60
|
+
<div class="flex items-baseline gap-1.5"><dt class="font-medium uppercase tracking-wider text-ink-500">Updated</dt><dd class="font-mono text-ink-700 dark:text-ink-300">{formatRelative(updatedAt)}</dd></div>
|
|
61
|
+
<div class="flex items-baseline gap-1.5"><dt class="font-medium uppercase tracking-wider text-ink-500">License</dt><dd class="font-mono text-ink-700 dark:text-ink-300">{license ?? "—"}</dd></div>
|
|
62
|
+
</dl>
|
|
63
|
+
|
|
64
|
+
{showChips && (platforms.length > 0 || stacks.length > 0) && (
|
|
65
|
+
<div class="flex flex-col gap-1.5">
|
|
66
|
+
<div class="flex flex-wrap gap-1.5">{platforms.slice(0, 4).map((platform) => <span class="inline-flex items-center gap-1 rounded-md border border-ink-200 bg-white px-1.5 py-0.5 text-2xs font-medium text-ink-700 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300"><Icon name={platform} category="platform" size={11} />{platform}</span>)}</div>
|
|
67
|
+
<div class="flex flex-wrap gap-1.5">{stacks.slice(0, 4).map((stack) => <span class="inline-flex items-center gap-1 rounded-md border border-ink-200 bg-ink-50 px-1.5 py-0.5 text-2xs font-medium text-ink-700 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-300"><Icon name={stack} category="stack" size={11} />{stack}</span>)}</div>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
70
|
+
|
|
71
|
+
<footer class="relative z-10 mt-1 flex items-center gap-2 border-t border-ink-100 pt-2 text-2xs dark:border-ink-900">
|
|
72
|
+
<a href={detailHref} class="inline-flex items-center gap-1 rounded-md border border-ink-200 bg-white px-2 py-1 font-medium text-ink-700 hover:bg-ink-50 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300">View details →</a>
|
|
73
|
+
{repoHref && <a href={repoHref} target="_blank" rel="noopener noreferrer" class="inline-flex items-center gap-1 rounded-md border border-ink-200 bg-white px-2 py-1 font-medium text-ink-700 hover:bg-ink-50 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-300">GitHub</a>}
|
|
74
|
+
</footer>
|
|
75
|
+
</article>
|