@grove-dev/astro 0.2.10 → 0.2.13
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/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 +11 -22
- package/dist/lib/repo.d.ts.map +1 -1
- package/dist/lib/repo.js +12 -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 +47 -129
- package/dist/lib/search.d.ts.map +1 -1
- package/dist/lib/search.js +195 -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/AppsIndexRow.astro +55 -184
- package/src/components/AppsPagination.astro +45 -108
- 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/ItemCard.astro +266 -248
- package/src/components/RecordSection.astro +68 -33
- 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 +16 -28
- package/src/lib/scores.ts +15 -19
- package/src/lib/search.ts +222 -290
- package/src/lib/taxonomy-counts.ts +12 -46
- package/src/styles.css +207 -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 +134 -51
- package/templates/default/src/pages/404.astro +64 -0
- package/templates/default/src/pages/{projects/[slug].astro → [slug]/[itemSlug].astro} +236 -66
- package/templates/default/src/pages/[slug]/index.astro +476 -0
- package/templates/default/src/pages/about.astro +165 -82
- package/templates/default/src/pages/apps/[itemSlug].astro +301 -0
- package/templates/default/src/pages/apps/index.astro +146 -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/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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"taxonomy-counts.js","sourceRoot":"","sources":["../../src/lib/taxonomy-counts.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"taxonomy-counts.js","sourceRoot":"","sources":["../../src/lib/taxonomy-counts.ts"],"names":[],"mappings":"AAMA,2DAA2D;AAC3D,MAAM,UAAU,eAAe,CAAC,QAA0B,EAAE;IAC1D,MAAM,CAAC,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC;QAChC,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,YAAY,CAAC,QAA0B,EAAE;IACvD,MAAM,CAAC,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC;QACtB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAa,CAAC,CAAC;QACnF,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3C,KAAK,MAAM,KAAK,IAAI,MAAM;YAAE,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACpE,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grove-dev/astro",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Astro framework adapter for Grove. Components, layouts, design tokens, and a default template.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"./layouts/*": "./src/layouts/*.astro"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"
|
|
47
|
-
"@grove-dev/
|
|
48
|
-
"
|
|
46
|
+
"@grove-dev/core": "0.2.13",
|
|
47
|
+
"@grove-dev/ui": "1.0.3",
|
|
48
|
+
"astro": "^6.4.6"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"astro": "^6.0.0"
|
|
@@ -1,204 +1,75 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Renders: avatar + name + description + 3 metadata tiles
|
|
6
|
-
* (license, stars, updated) + chip row (stack/platform) + two
|
|
7
|
-
* CTAs (Details, GitHub). Designed for a dense table that the
|
|
8
|
-
* client-side filter script (and screen readers) can scan.
|
|
9
|
-
*
|
|
10
|
-
* `data-*` attributes are emitted so the consumer's filter script
|
|
11
|
-
* can read them and toggle visibility without re-rendering.
|
|
12
|
-
*
|
|
13
|
-
* Generic: takes a `Resource`. For non-project kinds the project
|
|
14
|
-
* fields are simply absent and the row degrades to a minimal
|
|
15
|
-
* name + description layout.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import type { Resource, ProjectRecord } from "@grove-dev/core";
|
|
19
|
-
import { getOwnerAndRepoFromRepoUrl, getOwnerAvatarUrl } from "../lib/repo";
|
|
20
|
-
import { formatRelative, formatStars } from "../lib/format";
|
|
21
|
-
import { statusDisplay, prettySlug } from "../lib/display";
|
|
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";
|
|
22
5
|
|
|
23
6
|
interface Props {
|
|
24
|
-
record:
|
|
7
|
+
record: IndexRecord;
|
|
25
8
|
detailHref: string;
|
|
26
9
|
/** Whether to show stack/platform chips. Default true. */
|
|
27
10
|
showChips?: boolean;
|
|
28
11
|
class?: string;
|
|
29
12
|
}
|
|
30
13
|
|
|
31
|
-
const {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
} =
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const name =
|
|
42
|
-
record.kind === "resource"
|
|
43
|
-
? (record as { title: string }).title
|
|
44
|
-
: (record as { name: string }).name;
|
|
45
|
-
const description = record.description ?? "";
|
|
46
|
-
const repoUrl = project?.repoUrl;
|
|
47
|
-
const logoUrl = project?.logoUrl;
|
|
48
|
-
const stacks = project?.stacks ?? (project?.stack ? [project.stack] : []);
|
|
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) : [];
|
|
49
23
|
const platforms = project?.platforms ?? [];
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const githubUrl =
|
|
56
|
-
repoUrl ?? (record.links as { github?: string } | undefined)?.github;
|
|
57
|
-
|
|
58
|
-
const ownerRepo = repoUrl ? getOwnerAndRepoFromRepoUrl(repoUrl) : null;
|
|
59
|
-
const avatarSrc =
|
|
60
|
-
logoUrl ?? (ownerRepo ? getOwnerAvatarUrl(ownerRepo.owner, 80) : null);
|
|
61
|
-
const initials = (name || "")
|
|
62
|
-
.replace(/[^a-z0-9]/gi, "")
|
|
63
|
-
.slice(0, 2)
|
|
64
|
-
.toUpperCase();
|
|
65
|
-
|
|
66
|
-
const allTags = [
|
|
67
|
-
...stacks.slice(0, 2).map((s) => `Stack: ${s}`),
|
|
68
|
-
...platforms.slice(0, 1).map((p) => `Platform: ${p}`),
|
|
69
|
-
];
|
|
70
|
-
const starsLabel = formatStars(stars);
|
|
71
|
-
const updatedLabel = formatRelative(pushedAt);
|
|
72
|
-
const healthLabel = healthStatus ? statusDisplay(healthStatus) : null;
|
|
73
|
-
|
|
74
|
-
const tags = record.tags ?? [];
|
|
75
|
-
const labels = record.curation?.labels ?? [];
|
|
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)));
|
|
76
29
|
---
|
|
77
30
|
|
|
78
|
-
<article
|
|
79
|
-
class
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
data-record-slug={record.slug}
|
|
84
|
-
data-record-name={name}
|
|
85
|
-
data-record-kind={record.kind}
|
|
86
|
-
data-record-category={record.category}
|
|
87
|
-
data-record-stacks={stacks.join(",")}
|
|
88
|
-
data-record-platforms={platforms.join(",")}
|
|
89
|
-
data-record-tags={tags.join(",")}
|
|
90
|
-
data-record-labels={labels.join(",")}
|
|
91
|
-
data-record-search={[name, description, ownerRepo?.owner ?? "", ...stacks, ...platforms, ...tags, licenseSpdx ?? ""].join(" ").toLowerCase()}
|
|
92
|
-
>
|
|
93
|
-
<div class="flex items-center gap-3">
|
|
94
|
-
<div class="flex-shrink-0">
|
|
95
|
-
{avatarSrc ? (
|
|
96
|
-
<img
|
|
97
|
-
src={avatarSrc}
|
|
98
|
-
alt=""
|
|
99
|
-
width="32"
|
|
100
|
-
height="32"
|
|
101
|
-
loading="lazy"
|
|
102
|
-
decoding="async"
|
|
103
|
-
class="h-8 w-8 rounded-md border border-ink-200 object-contain bg-white dark:border-ink-800 dark:bg-ink-950"
|
|
104
|
-
/>
|
|
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" />
|
|
105
36
|
) : (
|
|
106
|
-
<span
|
|
107
|
-
aria-hidden="true"
|
|
108
|
-
class="inline-flex h-8 w-8 items-center justify-center rounded-md border border-ink-200 bg-ink-50 text-xs font-semibold text-ink-700 dark:border-ink-800 dark:bg-ink-900 dark:text-ink-200"
|
|
109
|
-
>
|
|
110
|
-
{initials || "·"}
|
|
111
|
-
</span>
|
|
112
|
-
)}
|
|
113
|
-
</div>
|
|
114
|
-
<div class="min-w-0">
|
|
115
|
-
<h3 class="m-0 text-[0.9375rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
|
|
116
|
-
<a href={detailHref} class="text-inherit no-underline hover:underline">
|
|
117
|
-
{name}
|
|
118
|
-
</a>
|
|
119
|
-
</h3>
|
|
120
|
-
{ownerRepo && (
|
|
121
|
-
<p class="m-0 mt-0.5 text-2xs text-ink-500 dark:text-ink-400">
|
|
122
|
-
@{ownerRepo.owner}/{ownerRepo.repo}
|
|
123
|
-
</p>
|
|
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>
|
|
124
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>
|
|
125
53
|
</div>
|
|
126
|
-
</
|
|
54
|
+
</header>
|
|
127
55
|
|
|
128
|
-
<
|
|
129
|
-
{description && (
|
|
130
|
-
<p class="m-0 line-clamp-2 text-[0.8125rem] leading-snug text-ink-500 dark:text-ink-400">
|
|
131
|
-
{description}
|
|
132
|
-
</p>
|
|
133
|
-
)}
|
|
134
|
-
{showChips && allTags.length > 0 && (
|
|
135
|
-
<ul class="mt-1.5 flex list-none flex-wrap gap-1.5 p-0">
|
|
136
|
-
{allTags.map((t) => <li class="grove-badge">{t}</li>)}
|
|
137
|
-
</ul>
|
|
138
|
-
)}
|
|
139
|
-
</div>
|
|
56
|
+
<p class="m-0 line-clamp-2 text-[0.8125rem] leading-relaxed text-ink-600 dark:text-ink-300">{item.description}</p>
|
|
140
57
|
|
|
141
|
-
<dl
|
|
142
|
-
class="
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
{starsLabel && (
|
|
146
|
-
<div class="flex items-center gap-1">
|
|
147
|
-
<dt class="sr-only">Stars</dt>
|
|
148
|
-
<dd class="m-0 font-mono text-ink-700 dark:text-ink-200">{starsLabel} ★</dd>
|
|
149
|
-
</div>
|
|
150
|
-
)}
|
|
151
|
-
{licenseSpdx && (
|
|
152
|
-
<div class="flex items-center gap-1">
|
|
153
|
-
<dt class="sr-only">License</dt>
|
|
154
|
-
<dd class="m-0">{licenseSpdx}</dd>
|
|
155
|
-
</div>
|
|
156
|
-
)}
|
|
157
|
-
{updatedLabel && updatedLabel !== "—" && (
|
|
158
|
-
<div class="flex items-center gap-1">
|
|
159
|
-
<dt class="sr-only">Last updated</dt>
|
|
160
|
-
<dd class="m-0">Updated {updatedLabel}</dd>
|
|
161
|
-
</div>
|
|
162
|
-
)}
|
|
163
|
-
{healthLabel && (
|
|
164
|
-
<div class="flex items-center gap-1">
|
|
165
|
-
<dt class="sr-only">Health</dt>
|
|
166
|
-
<dd class="m-0">{healthLabel}</dd>
|
|
167
|
-
</div>
|
|
168
|
-
)}
|
|
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>
|
|
169
62
|
</dl>
|
|
170
63
|
|
|
171
|
-
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
class="
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
data-event-app={record.slug}
|
|
178
|
-
>
|
|
179
|
-
View details
|
|
180
|
-
</a>
|
|
181
|
-
{githubUrl && (
|
|
182
|
-
<a
|
|
183
|
-
href={githubUrl}
|
|
184
|
-
target="_blank"
|
|
185
|
-
rel="noopener noreferrer"
|
|
186
|
-
class="grove-btn grove-btn-ghost"
|
|
187
|
-
data-event="row_repo_click"
|
|
188
|
-
data-event-source="apps_index_row"
|
|
189
|
-
data-event-app={record.slug}
|
|
190
|
-
>
|
|
191
|
-
GitHub
|
|
192
|
-
</a>
|
|
193
|
-
)}
|
|
194
|
-
</div>
|
|
195
|
-
</article>
|
|
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
|
+
)}
|
|
196
70
|
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
overflow: hidden;
|
|
203
|
-
}
|
|
204
|
-
</style>
|
|
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>
|
|
@@ -1,126 +1,63 @@
|
|
|
1
1
|
---
|
|
2
|
-
/**
|
|
3
|
-
* AppsPagination — windowed page list.
|
|
4
|
-
*
|
|
5
|
-
* Renders a row of page links with first / prev / numeric / next /
|
|
6
|
-
* last. The numeric window is a 5-page sliding window centered on
|
|
7
|
-
* the current page (clamped at the ends). Pages with no links
|
|
8
|
-
* (gaps) are rendered as "…" to preserve visual rhythm.
|
|
9
|
-
*
|
|
10
|
-
* Generic: takes a `current` and `total` page count and an
|
|
11
|
-
* `hrefFor` builder. The href builder is the only consumer-side
|
|
12
|
-
* knowledge; no siteConfig import.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
2
|
interface Props {
|
|
16
3
|
current: number;
|
|
17
4
|
total: number;
|
|
18
5
|
hrefFor: (page: number) => string;
|
|
19
6
|
class?: string;
|
|
20
|
-
/** Window size for the numeric page list. Default 5. */
|
|
21
|
-
windowSize?: number;
|
|
22
7
|
}
|
|
23
8
|
|
|
24
|
-
const {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Compute the numeric window. Clamp so it never extends past 1 or
|
|
37
|
-
// total, and never narrower than `windowSize` unless total is
|
|
38
|
-
// smaller.
|
|
39
|
-
function buildWindow(): (number | "…")[] {
|
|
40
|
-
if (total <= windowSize + 2) {
|
|
41
|
-
return Array.from({ length: total }, (_, i) => i + 1);
|
|
9
|
+
const { current, total, hrefFor, class: className = "" } = Astro.props;
|
|
10
|
+
|
|
11
|
+
function pageList(active: number, count: number): Array<number | "ellipsis"> {
|
|
12
|
+
if (count <= 7) return Array.from({ length: count }, (_, index) => index + 1);
|
|
13
|
+
const pages = new Set([1, count, active - 1, active, active + 1]);
|
|
14
|
+
const sorted = [...pages].filter((page) => page >= 1 && page <= count).sort((a, b) => a - b);
|
|
15
|
+
const result: Array<number | "ellipsis"> = [];
|
|
16
|
+
for (const page of sorted) {
|
|
17
|
+
const previous = result.at(-1);
|
|
18
|
+
if (typeof previous === "number" && page - previous > 1) result.push("ellipsis");
|
|
19
|
+
result.push(page);
|
|
42
20
|
}
|
|
43
|
-
|
|
44
|
-
let end = Math.min(total - 1, safeCurrent + half);
|
|
45
|
-
// Expand the window toward the nearest edge if it shrinks.
|
|
46
|
-
if (end - start + 1 < windowSize) {
|
|
47
|
-
if (safeCurrent <= half + 1) {
|
|
48
|
-
end = Math.min(total - 1, end + (windowSize - (end - start + 1)));
|
|
49
|
-
} else {
|
|
50
|
-
start = Math.max(2, start - (windowSize - (end - start + 1)));
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const items: (number | "…")[] = [1];
|
|
54
|
-
if (start > 2) items.push("…");
|
|
55
|
-
for (let i = start; i <= end; i++) items.push(i);
|
|
56
|
-
if (end < total - 1) items.push("…");
|
|
57
|
-
items.push(total);
|
|
58
|
-
return items;
|
|
21
|
+
return result;
|
|
59
22
|
}
|
|
60
23
|
|
|
61
|
-
const pages =
|
|
62
|
-
const
|
|
63
|
-
|
|
24
|
+
const pages = pageList(current, total);
|
|
25
|
+
const linkClass =
|
|
26
|
+
"grove-btn grove-btn-outline min-w-9 px-2 py-1.5 text-2xs";
|
|
64
27
|
---
|
|
65
28
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
>
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
rel="prev"
|
|
77
|
-
class="grove-btn grove-btn-ghost"
|
|
78
|
-
data-event="pagination_click"
|
|
79
|
-
data-event-source="apps_pagination"
|
|
80
|
-
>
|
|
81
|
-
← Prev
|
|
82
|
-
</a>
|
|
83
|
-
)}
|
|
29
|
+
{total > 1 && (
|
|
30
|
+
<nav
|
|
31
|
+
class:list={["flex flex-wrap items-center justify-center gap-1.5", className]}
|
|
32
|
+
aria-label="Pagination"
|
|
33
|
+
>
|
|
34
|
+
{current > 1 ? (
|
|
35
|
+
<a href={hrefFor(current - 1)} class={linkClass} rel="prev">Previous</a>
|
|
36
|
+
) : (
|
|
37
|
+
<span class={`${linkClass} pointer-events-none opacity-40`}>Previous</span>
|
|
38
|
+
)}
|
|
84
39
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<li aria-hidden="true" class="px-2 text-2xs text-ink-400">
|
|
89
|
-
…
|
|
90
|
-
</li>
|
|
40
|
+
{pages.map((page) =>
|
|
41
|
+
page === "ellipsis" ? (
|
|
42
|
+
<span class="px-1 text-2xs text-ink-400" aria-hidden="true">...</span>
|
|
91
43
|
) : (
|
|
92
|
-
<
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
href={hrefFor(p)}
|
|
103
|
-
class="grove-btn grove-btn-ghost"
|
|
104
|
-
data-event="pagination_click"
|
|
105
|
-
data-event-source="apps_pagination"
|
|
106
|
-
>
|
|
107
|
-
{p}
|
|
108
|
-
</a>
|
|
109
|
-
)}
|
|
110
|
-
</li>
|
|
44
|
+
<a
|
|
45
|
+
href={hrefFor(page)}
|
|
46
|
+
class:list={[
|
|
47
|
+
linkClass,
|
|
48
|
+
page === current && "grove-btn-primary",
|
|
49
|
+
]}
|
|
50
|
+
aria-current={page === current ? "page" : undefined}
|
|
51
|
+
>
|
|
52
|
+
{page}
|
|
53
|
+
</a>
|
|
111
54
|
),
|
|
112
55
|
)}
|
|
113
|
-
</ol>
|
|
114
56
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
>
|
|
123
|
-
Next →
|
|
124
|
-
</a>
|
|
125
|
-
)}
|
|
126
|
-
</nav>
|
|
57
|
+
{current < total ? (
|
|
58
|
+
<a href={hrefFor(current + 1)} class={linkClass} rel="next">Next</a>
|
|
59
|
+
) : (
|
|
60
|
+
<span class={`${linkClass} pointer-events-none opacity-40`}>Next</span>
|
|
61
|
+
)}
|
|
62
|
+
</nav>
|
|
63
|
+
)}
|
|
@@ -22,6 +22,7 @@ interface Props {
|
|
|
22
22
|
class?: string;
|
|
23
23
|
/** Hide the section header. */
|
|
24
24
|
bare?: boolean;
|
|
25
|
+
itemLabelPlural?: string;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
const {
|
|
@@ -29,6 +30,7 @@ const {
|
|
|
29
30
|
pathPrefix = "/",
|
|
30
31
|
class: className = "",
|
|
31
32
|
bare = false,
|
|
33
|
+
itemLabelPlural = "items",
|
|
32
34
|
} = Astro.props;
|
|
33
35
|
|
|
34
36
|
function buildHref(slug: string): string {
|
|
@@ -38,50 +40,60 @@ function buildHref(slug: string): string {
|
|
|
38
40
|
---
|
|
39
41
|
|
|
40
42
|
<section
|
|
41
|
-
|
|
43
|
+
id="categories"
|
|
44
|
+
class:list={["grove-section scroll-mt-20 border-b border-ink-200 dark:border-ink-800", className]}
|
|
42
45
|
aria-labelledby={bare ? undefined : "grove-categorygrid-heading"}
|
|
43
46
|
>
|
|
44
|
-
<div class="grove-container
|
|
47
|
+
<div class="grove-container mx-auto px-5 sm:px-7">
|
|
45
48
|
{!bare && (
|
|
46
|
-
<header class="mb-6
|
|
49
|
+
<header class="mb-6">
|
|
50
|
+
<span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
51
|
+
Categories
|
|
52
|
+
</span>
|
|
47
53
|
<h2
|
|
48
54
|
id="grove-categorygrid-heading"
|
|
49
|
-
class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
|
|
55
|
+
class="m-0 mt-1 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
|
|
50
56
|
>
|
|
51
57
|
Browse by category
|
|
52
58
|
</h2>
|
|
53
59
|
<p class="m-0 max-w-[60ch] text-[0.9375rem] text-ink-500 dark:text-ink-400">
|
|
54
|
-
|
|
55
|
-
reference apps, libraries, demos, and more.
|
|
60
|
+
Find {itemLabelPlural} by what they do, not by which stack they use.
|
|
56
61
|
</p>
|
|
57
62
|
</header>
|
|
58
63
|
)}
|
|
59
64
|
|
|
60
|
-
<div class="
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
65
|
+
<div class="overflow-hidden rounded-lg border border-ink-200 dark:border-ink-800">
|
|
66
|
+
<ul class="m-0 grid list-none grid-cols-1 divide-y divide-ink-200 p-0 sm:grid-cols-2 sm:divide-x sm:divide-y-0 lg:grid-cols-3 dark:divide-ink-800">
|
|
67
|
+
{categories.map((c) => (
|
|
68
|
+
<li class="divide-x divide-ink-200 dark:divide-ink-800">
|
|
69
|
+
<a
|
|
70
|
+
href={buildHref(c.slug)}
|
|
71
|
+
class="group flex h-full items-start gap-3 p-4 no-underline transition-colors hover:bg-ink-50/60 dark:hover:bg-ink-900/40"
|
|
72
|
+
>
|
|
73
|
+
<div class="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
74
|
+
<div class="flex items-center gap-2">
|
|
75
|
+
<h3 class="m-0 text-[0.9375rem] font-semibold tracking-tight text-ink-900 group-hover:underline dark:text-ink-100">
|
|
76
|
+
{c.name}
|
|
77
|
+
</h3>
|
|
78
|
+
{typeof c.count === "number" && (
|
|
79
|
+
<span class="inline-flex items-center rounded-md border border-ink-200 bg-ink-50 px-1.5 py-0.5 font-mono text-[10px] font-medium text-ink-600 dark:border-ink-800 dark:bg-ink-900/60 dark:text-ink-400">
|
|
80
|
+
{c.count} {itemLabelPlural}
|
|
81
|
+
</span>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
{c.blurb && (
|
|
85
|
+
<p class="m-0 text-2xs leading-relaxed text-ink-500 dark:text-ink-400">
|
|
86
|
+
{c.blurb}
|
|
87
|
+
</p>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true" class="mt-1 shrink-0 text-ink-300 transition-colors group-hover:text-ink-500 dark:text-ink-700 dark:group-hover:text-ink-400" fill="currentColor">
|
|
91
|
+
<path d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.75.75 0 0 1-1.06-1.06L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z" />
|
|
92
|
+
</svg>
|
|
93
|
+
</a>
|
|
94
|
+
</li>
|
|
95
|
+
))}
|
|
96
|
+
</ul>
|
|
85
97
|
</div>
|
|
86
98
|
</div>
|
|
87
99
|
</section>
|
|
@@ -52,14 +52,14 @@ function fmt(n: number | undefined): string | null {
|
|
|
52
52
|
|
|
53
53
|
const visible = contributors
|
|
54
54
|
.filter((c) => c.username)
|
|
55
|
-
.slice(0,
|
|
55
|
+
.slice(0, 100);
|
|
56
56
|
---
|
|
57
57
|
|
|
58
58
|
<section
|
|
59
59
|
class:list={["grove-section", className]}
|
|
60
60
|
aria-labelledby="grove-contributors-heading"
|
|
61
61
|
>
|
|
62
|
-
<div class="grove-container
|
|
62
|
+
<div class="grove-container mx-auto px-5 sm:px-7">
|
|
63
63
|
<header class="mb-6 flex flex-wrap items-end justify-between gap-3">
|
|
64
64
|
<div>
|
|
65
65
|
<h2
|
|
@@ -95,7 +95,7 @@ const visible = contributors
|
|
|
95
95
|
</p>
|
|
96
96
|
) : (
|
|
97
97
|
<ul
|
|
98
|
-
class="m-0 grid list-none grid-cols-
|
|
98
|
+
class="m-0 grid list-none grid-cols-[repeat(auto-fill,minmax(72px,1fr))] gap-x-3 gap-y-5 p-0"
|
|
99
99
|
aria-label="Contributors"
|
|
100
100
|
>
|
|
101
101
|
{visible.map((c) => {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
bestFor?: string[];
|
|
4
|
+
whyListed?: string[];
|
|
5
|
+
caveats?: string[];
|
|
6
|
+
class?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
bestFor = [],
|
|
11
|
+
whyListed = [],
|
|
12
|
+
caveats = [],
|
|
13
|
+
class: className = "",
|
|
14
|
+
} = Astro.props;
|
|
15
|
+
|
|
16
|
+
const columns = [
|
|
17
|
+
{ title: "Best for", items: bestFor },
|
|
18
|
+
{ title: "Why listed", items: whyListed },
|
|
19
|
+
{ title: "Caveats", items: caveats },
|
|
20
|
+
].filter((column) => column.items.length > 0);
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
{columns.length > 0 && (
|
|
24
|
+
<section
|
|
25
|
+
class:list={["grid grid-cols-1 gap-4 md:grid-cols-3", className]}
|
|
26
|
+
aria-label="Curation notes"
|
|
27
|
+
>
|
|
28
|
+
{columns.map((column) => (
|
|
29
|
+
<div class="grove-card p-5">
|
|
30
|
+
<h3 class="m-0 text-sm font-semibold uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
31
|
+
{column.title}
|
|
32
|
+
</h3>
|
|
33
|
+
<ul class="mt-3 m-0 flex list-none flex-col gap-1.5 p-0">
|
|
34
|
+
{column.items.map((item) => (
|
|
35
|
+
<li class="text-sm text-ink-700 dark:text-ink-200">- {item}</li>
|
|
36
|
+
))}
|
|
37
|
+
</ul>
|
|
38
|
+
</div>
|
|
39
|
+
))}
|
|
40
|
+
</section>
|
|
41
|
+
)}
|