@grove-dev/astro 0.5.0-next.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/dist/index.js +2 -2
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/index.d.ts +0 -2
  4. package/dist/lib/index.d.ts.map +1 -1
  5. package/dist/lib/index.js +0 -2
  6. package/dist/lib/index.js.map +1 -1
  7. package/dist/ui/button.d.ts +45 -0
  8. package/dist/ui/button.d.ts.map +1 -0
  9. package/dist/ui/button.js +82 -0
  10. package/dist/ui/button.js.map +1 -0
  11. package/package.json +8 -5
  12. package/src/components/CardGrid.astro +41 -0
  13. package/src/components/CardIcon.astro +67 -0
  14. package/src/components/CategoryGrid.astro +20 -5
  15. package/src/components/CollectionCard.astro +56 -0
  16. package/src/components/CollectionIndex.astro +21 -26
  17. package/src/components/CollectionPage.astro +36 -13
  18. package/src/components/CollectionRow.astro +62 -192
  19. package/src/components/CollectionTeaser.astro +4 -0
  20. package/src/components/ContributorsGrid.astro +4 -1
  21. package/src/components/DirectoryIndexClient.astro +127 -30
  22. package/src/components/EditorialSummary.astro +6 -5
  23. package/src/components/FilterGroupMenu.astro +24 -10
  24. package/src/components/FilterOptions.astro +6 -1
  25. package/src/components/FinalCta.astro +5 -3
  26. package/src/components/Hero.astro +85 -139
  27. package/src/components/IndexRow.astro +36 -69
  28. package/src/components/LanguageBreakdown.astro +5 -1
  29. package/src/components/OriginalCollection.astro +3 -2
  30. package/src/components/Pagination.astro +7 -20
  31. package/src/components/ProjectCard.astro +345 -0
  32. package/src/components/RecordHeader.astro +111 -82
  33. package/src/components/RecordSection.astro +14 -10
  34. package/src/components/RecordSidebar.astro +68 -20
  35. package/src/components/RefinePanel.astro +41 -62
  36. package/src/components/SmartLensTabs.astro +3 -7
  37. package/src/components/StackGrid.astro +26 -6
  38. package/src/components/SubmissionClient.astro +140 -63
  39. package/src/components/TableOfContents.astro +31 -5
  40. package/src/components/WhyThisExists.astro +1 -1
  41. package/src/index.ts +2 -2
  42. package/src/layouts/BaseLayout.astro +79 -9
  43. package/src/layouts/Header.astro +5 -4
  44. package/src/layouts/SectionHeader.astro +3 -2
  45. package/src/layouts/Seo.astro +28 -17
  46. package/src/layouts/ThemeToggle.astro +23 -6
  47. package/src/lib/index.ts +0 -2
  48. package/src/server/collections.ts +11 -0
  49. package/src/server/contrast.test.ts +82 -0
  50. package/src/server/contrast.ts +117 -0
  51. package/src/server/directory.test.ts +177 -0
  52. package/src/server/directory.ts +328 -206
  53. package/src/server/github-repo.test.ts +88 -0
  54. package/src/server/github-repo.ts +104 -0
  55. package/src/server/index.ts +4 -3
  56. package/src/server/models-home.test.ts +101 -0
  57. package/src/server/models.test.ts +135 -0
  58. package/src/server/models.ts +170 -42
  59. package/src/styles.css +128 -16
  60. package/dist/lib/scores.d.ts +0 -2
  61. package/dist/lib/scores.d.ts.map +0 -1
  62. package/dist/lib/scores.js +0 -2
  63. package/dist/lib/scores.js.map +0 -1
  64. package/src/components/CurationGrid.astro +0 -41
  65. package/src/components/DecisionRow.astro +0 -89
  66. package/src/components/ExploreByCategory.astro +0 -67
  67. package/src/components/ExploreByStack.astro +0 -78
  68. package/src/components/GroveDocumentHead.astro +0 -28
  69. package/src/components/ItemCard.astro +0 -324
  70. package/src/components/MinimalAbout.astro +0 -82
  71. package/src/components/ScoreBars.astro +0 -102
  72. package/src/lib/scores.ts +0 -1
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * - `default` export: an Astro integration that wires the
6
6
  * `@grove-dev/astro/components/*` and `@grove-dev/astro/layouts/*`
7
7
  * subpath imports into Vite so consumer projects can write:
8
- * import ItemCard from "@grove-dev/astro/components/ItemCard.astro";
8
+ * import ProjectCard from "@grove-dev/astro/components/ProjectCard.astro";
9
9
  * and have Vite resolve the source `.astro` file (the package's
10
10
  * `dist/` only contains TypeScript helpers, not the components
11
11
  * themselves).
@@ -54,7 +54,7 @@ export default function groveAstro() {
54
54
  "astro:config:setup": async ({ config, updateConfig, injectScript }) => {
55
55
  // Alias the components/layouts source directories so
56
56
  // consumer builds can import from
57
- // @grove-dev/astro/components/ItemCard.astro
57
+ // @grove-dev/astro/components/ProjectCard.astro
58
58
  // without us shipping a glob-shaped `exports` map that
59
59
  // Vite/Rollup does not expand reliably.
60
60
  const consumerRoot = fileURLToPath(config.root);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -8,7 +8,6 @@
8
8
  * - format counts, dates, stars (`lib/format`)
9
9
  * - apply curated lenses / facets / search filters (`lib/lenses`,
10
10
  * `lib/search`)
11
- * - render score bars (`lib/scores`)
12
11
  * - compute taxonomy counts at build time (`lib/taxonomy-counts`)
13
12
  * - pretty-print slugs and label/lens/sort/status ids (`lib/display`)
14
13
  *
@@ -20,7 +19,6 @@ export * from "./repo.js";
20
19
  export * from "./format.js";
21
20
  export * from "./lenses.js";
22
21
  export * from "./search.js";
23
- export * from "./scores.js";
24
22
  export * from "./taxonomy-counts.js";
25
23
  export * from "./display.js";
26
24
  export * from "./load-collections.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC"}
package/dist/lib/index.js CHANGED
@@ -8,7 +8,6 @@
8
8
  * - format counts, dates, stars (`lib/format`)
9
9
  * - apply curated lenses / facets / search filters (`lib/lenses`,
10
10
  * `lib/search`)
11
- * - render score bars (`lib/scores`)
12
11
  * - compute taxonomy counts at build time (`lib/taxonomy-counts`)
13
12
  * - pretty-print slugs and label/lens/sort/status ids (`lib/display`)
14
13
  *
@@ -20,7 +19,6 @@ export * from "./repo.js";
20
19
  export * from "./format.js";
21
20
  export * from "./lenses.js";
22
21
  export * from "./search.js";
23
- export * from "./scores.js";
24
22
  export * from "./taxonomy-counts.js";
25
23
  export * from "./display.js";
26
24
  export * from "./load-collections.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,cAAc,CAAC;AAC7B,cAAc,uBAAuB,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Button / chip class builders — the single source for interactive
3
+ * control styling.
4
+ *
5
+ * Plain TS (not an .astro component) on purpose: `DirectoryIndexClient`
6
+ * rebuilds pagination links and active-filter chips in CLIENT-side JS,
7
+ * and those class strings must stay byte-identical with the server
8
+ * render. Both sides import these builders, so they cannot drift.
9
+ *
10
+ * Variant semantics (see styles.css):
11
+ * - `primary` — the brand action (config `theme.primaryColor`).
12
+ * - `secondary` — default chrome buttons.
13
+ * - `outline` / `ghost` — quieter chrome.
14
+ * - `selected` — "you are here" state (active filter, current lens,
15
+ * current page). Neutral inversion via the `selected` tokens —
16
+ * deliberately NOT the brand primary, so green never doubles as a
17
+ * selection indicator.
18
+ */
19
+ export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "selected";
20
+ /** `bare` emits no size classes — pass custom geometry via `extra`. */
21
+ export type ButtonSize = "sm" | "md" | "lg" | "bare";
22
+ export declare function buttonClass(variant?: ButtonVariant, size?: ButtonSize, extra?: string): string;
23
+ /**
24
+ * Layout extras for pagination controls — shared by `Pagination.astro`
25
+ * and the client-side pagination rebuild in `DirectoryIndexClient`.
26
+ */
27
+ export declare const PAGINATION_EXTRA = "min-w-9 px-2 py-1.5 text-2xs";
28
+ /**
29
+ * Facet filter trigger (the "Stack: Any" dropdown buttons). Includes
30
+ * the `grove-filter-trigger` hook class because the client script
31
+ * reassigns the whole className when the active state changes — the
32
+ * hook must survive that reassignment.
33
+ */
34
+ export declare function filterTriggerClass(active: boolean): string;
35
+ /**
36
+ * Lens tab (the All / Hot / Mature row). Same geometry as the filter
37
+ * trigger; `data-lens-id` targeting keeps its own hook attribute.
38
+ */
39
+ export declare function lensTabClass(active: boolean): string;
40
+ /**
41
+ * Active-filter chip (the removable "Stack: Python ×" pills). One
42
+ * definition for the server render and the client rebuild.
43
+ */
44
+ export declare function chipClass(extra?: string): string;
45
+ //# sourceMappingURL=button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.d.ts","sourceRoot":"","sources":["../../src/ui/button.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,UAAU,CAAC;AACvF,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAwBrD,wBAAgB,WAAW,CACzB,OAAO,GAAE,aAA2B,EACpC,IAAI,GAAE,UAAiB,EACvB,KAAK,SAAK,GACT,MAAM,CAER;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,iCAAiC,CAAC;AAE/D;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAO1D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAMpD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,SAAK,GAAG,MAAM,CAO5C"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Button / chip class builders — the single source for interactive
3
+ * control styling.
4
+ *
5
+ * Plain TS (not an .astro component) on purpose: `DirectoryIndexClient`
6
+ * rebuilds pagination links and active-filter chips in CLIENT-side JS,
7
+ * and those class strings must stay byte-identical with the server
8
+ * render. Both sides import these builders, so they cannot drift.
9
+ *
10
+ * Variant semantics (see styles.css):
11
+ * - `primary` — the brand action (config `theme.primaryColor`).
12
+ * - `secondary` — default chrome buttons.
13
+ * - `outline` / `ghost` — quieter chrome.
14
+ * - `selected` — "you are here" state (active filter, current lens,
15
+ * current page). Neutral inversion via the `selected` tokens —
16
+ * deliberately NOT the brand primary, so green never doubles as a
17
+ * selection indicator.
18
+ */
19
+ const BASE = "inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-[var(--radius)] border border-transparent font-medium leading-none no-underline outline-none transition-colors focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/35";
20
+ /**
21
+ * `md`/`lg` guarantee a ≥44px hit target on small viewports
22
+ * (`max-sm:min-h-11`) while keeping the compact desktop height.
23
+ */
24
+ const SIZES = {
25
+ sm: "h-8 px-3 text-sm",
26
+ md: "h-9 px-3 text-sm max-sm:min-h-11",
27
+ lg: "h-11 px-4 text-sm",
28
+ bare: "",
29
+ };
30
+ const VARIANTS = {
31
+ primary: "bg-primary text-primary-foreground hover:bg-primary/90",
32
+ secondary: "border-border bg-secondary text-foreground hover:bg-accent",
33
+ outline: "border-border bg-transparent text-foreground hover:bg-accent",
34
+ ghost: "text-foreground hover:bg-accent",
35
+ selected: "bg-selected text-selected-foreground hover:bg-selected/90",
36
+ };
37
+ export function buttonClass(variant = "secondary", size = "sm", extra = "") {
38
+ return [BASE, SIZES[size], VARIANTS[variant], extra].filter(Boolean).join(" ");
39
+ }
40
+ /**
41
+ * Layout extras for pagination controls — shared by `Pagination.astro`
42
+ * and the client-side pagination rebuild in `DirectoryIndexClient`.
43
+ */
44
+ export const PAGINATION_EXTRA = "min-w-9 px-2 py-1.5 text-2xs";
45
+ /**
46
+ * Facet filter trigger (the "Stack: Any" dropdown buttons). Includes
47
+ * the `grove-filter-trigger` hook class because the client script
48
+ * reassigns the whole className when the active state changes — the
49
+ * hook must survive that reassignment.
50
+ */
51
+ export function filterTriggerClass(active) {
52
+ return [
53
+ "grove-filter-trigger",
54
+ BASE,
55
+ "h-9 px-3 text-[0.8125rem] max-sm:min-h-11",
56
+ active ? `${VARIANTS.selected} hover:text-selected-foreground` : VARIANTS.secondary,
57
+ ].join(" ");
58
+ }
59
+ /**
60
+ * Lens tab (the All / Hot / Mature row). Same geometry as the filter
61
+ * trigger; `data-lens-id` targeting keeps its own hook attribute.
62
+ */
63
+ export function lensTabClass(active) {
64
+ return [
65
+ BASE,
66
+ "h-9 px-3 text-[0.8125rem] max-sm:min-h-11",
67
+ active ? `${VARIANTS.selected} hover:text-selected-foreground` : VARIANTS.secondary,
68
+ ].join(" ");
69
+ }
70
+ /**
71
+ * Active-filter chip (the removable "Stack: Python ×" pills). One
72
+ * definition for the server render and the client rebuild.
73
+ */
74
+ export function chipClass(extra = "") {
75
+ return [
76
+ "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",
77
+ extra,
78
+ ]
79
+ .filter(Boolean)
80
+ .join(" ");
81
+ }
82
+ //# sourceMappingURL=button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"button.js","sourceRoot":"","sources":["../../src/ui/button.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAMH,MAAM,IAAI,GACR,iQAAiQ,CAAC;AAEpQ;;;GAGG;AACH,MAAM,KAAK,GAA+B;IACxC,EAAE,EAAE,kBAAkB;IACtB,EAAE,EAAE,kCAAkC;IACtC,EAAE,EAAE,mBAAmB;IACvB,IAAI,EAAE,EAAE;CACT,CAAC;AAEF,MAAM,QAAQ,GAAkC;IAC9C,OAAO,EAAE,wDAAwD;IACjE,SAAS,EAAE,4DAA4D;IACvE,OAAO,EAAE,8DAA8D;IACvE,KAAK,EAAE,iCAAiC;IACxC,QAAQ,EAAE,2DAA2D;CACtE,CAAC;AAEF,MAAM,UAAU,WAAW,CACzB,UAAyB,WAAW,EACpC,OAAmB,IAAI,EACvB,KAAK,GAAG,EAAE;IAEV,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACjF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,8BAA8B,CAAC;AAE/D;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAe;IAChD,OAAO;QACL,sBAAsB;QACtB,IAAI;QACJ,2CAA2C;QAC3C,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,iCAAiC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS;KACpF,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,MAAe;IAC1C,OAAO;QACL,IAAI;QACJ,2CAA2C;QAC3C,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,iCAAiC,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS;KACpF,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAK,GAAG,EAAE;IAClC,OAAO;QACL,sOAAsO;QACtO,KAAK;KACN;SACE,MAAM,CAAC,OAAO,CAAC;SACf,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grove-dev/astro",
3
- "version": "0.5.0-next.2",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "description": "Composable Astro UI, data adapters, and integration for Grove directories.",
6
6
  "license": "MIT",
@@ -46,9 +46,12 @@
46
46
  "./styles.css": "./src/styles.css",
47
47
  "./components/*.astro": "./src/components/*.astro",
48
48
  "./components/*": "./src/components/*.astro",
49
+ "./server": "./src/server/index.ts",
50
+ "./server/*": "./dist/server/*.js",
51
+ "./ui/*.astro": "./src/ui/*.astro",
52
+ "./ui/*": "./src/ui/*.ts",
49
53
  "./layouts/*.astro": "./src/layouts/*.astro",
50
- "./layouts/*": "./src/layouts/*.astro",
51
- "./server": "./src/server/index.ts"
54
+ "./layouts/*": "./src/layouts/*.astro"
52
55
  },
53
56
  "dependencies": {
54
57
  "astro": "^7.1.3",
@@ -56,7 +59,7 @@
56
59
  "sanitize-html": "^2.17.5",
57
60
  "shiki": "^1.29.2",
58
61
  "yaml": "^2.9.0",
59
- "@grove-dev/core": "0.5.0-next.2"
62
+ "@grove-dev/core": "0.5.0"
60
63
  },
61
64
  "peerDependencies": {
62
65
  "astro": "^6.0.0 || ^7.0.0"
@@ -67,6 +70,6 @@
67
70
  "scripts": {
68
71
  "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
69
72
  "check": "tsc -p tsconfig.json --noEmit",
70
- "test": "echo \"No astro package tests yet\""
73
+ "test": "vitest run"
71
74
  }
72
75
  }
@@ -0,0 +1,41 @@
1
+ ---
2
+ /**
3
+ * CardGrid — the one responsive grid host for card-shaped children.
4
+ *
5
+ * Owns the three-column layout every listing surface shares (home
6
+ * record sections, browse results, collection entries, taxonomy
7
+ * terms) so pages never re-declare grid classes. Cards are passed
8
+ * through the default slot, which keeps the grid card-agnostic: any
9
+ * component that follows the sizing contract (`h-full flex flex-col`
10
+ * root, package radius/padding tokens) drops in — including new card
11
+ * types added by consumers.
12
+ *
13
+ * Children may be bare cards or `<li>` wrappers; bare children are
14
+ * valid because the grid renders `role="list"` semantics regardless
15
+ * (Safari drops list semantics on `list-none` either way).
16
+ *
17
+ * `empty` swaps the grid for the `empty` slot so pages keep their
18
+ * empty-state markup next to the list they describe.
19
+ */
20
+ interface Props {
21
+ /** id forwarded to the `<ul>` (client scripts key off it). */
22
+ id?: string;
23
+ /** Render the `empty` slot instead of the grid. */
24
+ empty?: boolean;
25
+ class?: string;
26
+ }
27
+
28
+ const { id, empty = false, class: className = "" } = Astro.props;
29
+ ---
30
+
31
+ {empty ? (
32
+ <slot name="empty" />
33
+ ) : (
34
+ <ul
35
+ id={id}
36
+ role="list"
37
+ class={`m-0 grid list-none grid-cols-1 items-stretch gap-5 p-0 sm:grid-cols-2 lg:grid-cols-3 ${className}`}
38
+ >
39
+ <slot />
40
+ </ul>
41
+ )}
@@ -0,0 +1,67 @@
1
+ ---
2
+ /**
3
+ * CardIcon — the small metadata glyphs shared by every card component
4
+ * (star, clock, curated check, navigation arrow, external link).
5
+ *
6
+ * Extracted so a visual tweak (size, stroke, path) is one edit instead
7
+ * of a copy-paste hunt across ProjectCard/IndexRow/CollectionRow.
8
+ * Color comes from `currentColor`; wrap in a colored span (e.g.
9
+ * `text-amber-500` for the star) to tint.
10
+ */
11
+ interface Props {
12
+ name: "star" | "clock" | "curated" | "arrow" | "external";
13
+ /** Square size in px. Defaults per-glyph to match card typography. */
14
+ size?: number;
15
+ class?: string;
16
+ }
17
+
18
+ const { name, size, class: className = "" } = Astro.props;
19
+
20
+ const defaults: Record<Props["name"], number> = {
21
+ star: 13,
22
+ clock: 11,
23
+ curated: 10,
24
+ arrow: 16,
25
+ external: 10,
26
+ };
27
+ const px = size ?? defaults[name];
28
+ ---
29
+
30
+ {name === "star" && (
31
+ <svg viewBox="0 0 16 16" width={px} height={px} aria-hidden="true" fill="currentColor" class={className}>
32
+ <path d="M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Z" />
33
+ </svg>
34
+ )}
35
+ {name === "clock" && (
36
+ <svg viewBox="0 0 16 16" width={px} height={px} aria-hidden="true" fill="currentColor" class={className}>
37
+ <path d="M8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2Zm0 1.5a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm-.5 1.5a.5.5 0 0 0-1 0v3.25c0 .15.07.29.18.39l1.95 1.65a.5.5 0 0 0 .66-.76L7.5 7.55V5Z" />
38
+ </svg>
39
+ )}
40
+ {name === "curated" && (
41
+ <svg viewBox="0 0 16 16" width={px} height={px} aria-hidden="true" fill="currentColor" class={className}>
42
+ <path d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0Zm3.41 5.09a.75.75 0 0 0-1.06 0L6.75 8.69 5.65 7.59a.75.75 0 1 0-1.06 1.06l1.65 1.65a.75.75 0 0 0 1.06 0l4.11-4.11a.75.75 0 0 0 0-1.06Z" />
43
+ </svg>
44
+ )}
45
+ {name === "arrow" && (
46
+ <svg
47
+ viewBox="0 0 24 24"
48
+ width={px}
49
+ height={px}
50
+ fill="none"
51
+ stroke="currentColor"
52
+ stroke-width="2"
53
+ stroke-linecap="round"
54
+ stroke-linejoin="round"
55
+ aria-hidden="true"
56
+ class={className}
57
+ >
58
+ <path d="M5 12h14"></path>
59
+ <path d="m12 5 7 7-7 7"></path>
60
+ </svg>
61
+ )}
62
+ {name === "external" && (
63
+ <svg viewBox="0 0 16 16" width={px} height={px} aria-hidden="true" fill="currentColor" class={className}>
64
+ <path d="M3.75 2A1.75 1.75 0 0 0 2 3.75v8.5C2 13.216 2.784 14 3.75 14h8.5A1.75 1.75 0 0 0 14 12.25v-3.5a.75.75 0 0 0-1.5 0v3.5a.25.25 0 0 1-.25.25h-8.5a.25.25 0 0 1-.25-.25v-8.5a.25.25 0 0 1 .25-.25h3.5a.75.75 0 0 0 0-1.5h-3.5Z" />
65
+ <path d="M10 0a.75.75 0 0 0-.75.75v.75h-3a.75.75 0 0 0 0 1.5h3v.75A.75.75 0 0 0 10.75 4.5h2.5A.75.75 0 0 0 14 3.75v-2.5A.75.75 0 0 0 13.25.5H10Z" />
66
+ </svg>
67
+ )}
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { formatCount } from "@grove-dev/core";
2
3
  /**
3
4
  * CategoryGrid — browse-by-category grid for the home page.
4
5
  *
@@ -23,6 +24,12 @@ interface Props {
23
24
  /** Hide the section header. */
24
25
  bare?: boolean;
25
26
  itemLabelPlural?: string;
27
+ /** Noun pair for count chips — singular fixes "1 projects". */
28
+ itemNoun?: { singular: string; plural: string };
29
+ /** Pass 1 when this grid IS the page (`/categories` index). */
30
+ headingLevel?: 1 | 2;
31
+ /** Card link builder (defaults to browse filtered by category). */
32
+ hrefFor?: (slug: string) => string;
26
33
  }
27
34
 
28
35
  const {
@@ -31,9 +38,15 @@ const {
31
38
  class: className = "",
32
39
  bare = false,
33
40
  itemLabelPlural = "items",
41
+ itemNoun = { singular: "item", plural: "items" },
42
+ headingLevel = 2,
43
+ hrefFor,
34
44
  } = Astro.props;
35
45
 
46
+ const Heading = `h${headingLevel}` as "h1" | "h2";
47
+
36
48
  function buildHref(slug: string): string {
49
+ if (hrefFor) return hrefFor(slug);
37
50
  const base = pathPrefix.replace(/\/$/, "");
38
51
  return `${base}?category=${encodeURIComponent(slug)}`;
39
52
  }
@@ -50,12 +63,12 @@ function buildHref(slug: string): string {
50
63
  <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
51
64
  Categories
52
65
  </span>
53
- <h2
66
+ <Heading
54
67
  id="grove-categorygrid-heading"
55
68
  class="m-0 mt-1 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
56
69
  >
57
70
  Browse by category
58
- </h2>
71
+ </Heading>
59
72
  <p class="m-0 max-w-[60ch] text-[0.9375rem] text-ink-500 dark:text-ink-400">
60
73
  Find {itemLabelPlural} by what they do, not by which stack they use.
61
74
  </p>
@@ -72,12 +85,14 @@ function buildHref(slug: string): string {
72
85
  >
73
86
  <div class="flex min-w-0 flex-1 flex-col gap-0.5">
74
87
  <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">
88
+ {/* Not a heading see StackGrid: link tiles under a
89
+ page h1 must not introduce h3 skips. */}
90
+ <span class="m-0 text-[0.9375rem] font-semibold tracking-tight text-ink-900 group-hover:underline dark:text-ink-100">
76
91
  {c.name}
77
- </h3>
92
+ </span>
78
93
  {typeof c.count === "number" && (
79
94
  <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}
95
+ {formatCount(c.count, itemNoun)}
81
96
  </span>
82
97
  )}
83
98
  </div>
@@ -0,0 +1,56 @@
1
+ ---
2
+ /**
3
+ * CollectionCard — card for one collection (not a record): kind
4
+ * eyebrow, title, description, entry count.
5
+ *
6
+ * Extracted from `CollectionIndex` so the collection tile shares the
7
+ * `ProjectCard` visual language (border, radius, hover lift, tokens)
8
+ * and stays in sync with the record cards as the design evolves.
9
+ * Follows the `CardGrid` sizing contract (`h-full flex flex-col`).
10
+ */
11
+ import { formatCount } from "@grove-dev/core";
12
+
13
+ interface Props {
14
+ collection: {
15
+ slug: string;
16
+ url: string;
17
+ kind: string;
18
+ title: string;
19
+ description?: string;
20
+ count: number;
21
+ };
22
+ /** Analytics source emitted with `collection_card_click`. */
23
+ eventSource?: string;
24
+ /** Noun pair for the entry count (blueprint labels). */
25
+ countNoun?: { singular: string; plural: string };
26
+ class?: string;
27
+ }
28
+
29
+ const {
30
+ collection: c,
31
+ eventSource = "collection_index",
32
+ countNoun = { singular: "item", plural: "items" },
33
+ class: className = "",
34
+ } = Astro.props;
35
+ ---
36
+
37
+ <a
38
+ href={c.url}
39
+ data-event="collection_card_click"
40
+ data-event-source={eventSource}
41
+ data-event-item={c.slug}
42
+ class={`group relative flex h-full min-w-0 flex-col gap-2 rounded-xl border border-ink-200 bg-card p-5 no-underline outline-none transition-[border-color,background-color,transform] duration-150 hover:-translate-y-0.5 hover:border-ink-300 hover:bg-ink-50 focus-visible:border-ink-500 focus-visible:ring-2 focus-visible:ring-ink-900/15 dark:border-ink-800 dark:hover:border-ink-700 dark:hover:bg-ink-900 dark:focus-visible:border-ink-500 dark:focus-visible:ring-ink-100/15 ${className}`}
43
+ >
44
+ <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
45
+ {c.kind === "curated" ? "Curated" : "Generated"}
46
+ </span>
47
+ <span class="text-[1.0625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
48
+ {c.title}
49
+ </span>
50
+ <span class="line-clamp-2 text-[0.875rem] text-ink-500 dark:text-ink-400">
51
+ {c.description}
52
+ </span>
53
+ <span class="mt-auto pt-2 text-2xs font-medium text-ink-500 transition-colors group-hover:text-ink-900 dark:text-ink-400 dark:group-hover:text-ink-100">
54
+ {formatCount(c.count, countNoun)} →
55
+ </span>
56
+ </a>
@@ -16,8 +16,11 @@
16
16
  * - `<slot name="header-actions">` — custom buttons in header
17
17
  * - `class` — extra class on the outer <section>
18
18
  */
19
+ import CardGrid from "./CardGrid.astro";
20
+ import CollectionCard from "./CollectionCard.astro";
19
21
  import Container from "../layouts/Container.astro";
20
22
  import SectionHeader from "../layouts/SectionHeader.astro";
23
+ import EmptyState from "../ui/EmptyState.astro";
21
24
  import type { CollectionIndexModel } from "../server/collections.js";
22
25
 
23
26
  interface Props {
@@ -32,6 +35,14 @@ interface Props {
32
35
  emptyLabel?: string;
33
36
  /** Cap the number of cards rendered. Omit for "all". */
34
37
  limit?: number;
38
+ /**
39
+ * Heading level for the section title. Pass 1 when this component
40
+ * renders the page's title (the `/collections` index); the default
41
+ * 2 fits embedded use (homepage teaser).
42
+ */
43
+ headingLevel?: 1 | 2;
44
+ /** Noun pair for the per-card entry counts (blueprint labels). */
45
+ countNoun?: { singular: string; plural: string };
35
46
  class?: string;
36
47
  }
37
48
 
@@ -45,6 +56,8 @@ const {
45
56
  viewAllLabel = "View all",
46
57
  emptyLabel = "No collections yet.",
47
58
  limit,
59
+ headingLevel = 2,
60
+ countNoun,
48
61
  class: className = "",
49
62
  } = Astro.props;
50
63
 
@@ -55,7 +68,7 @@ const headerActions = Astro.slots.has("header-actions");
55
68
  <section id={id} class={`py-12 sm:py-16 ${className}`}>
56
69
  <Container>
57
70
  <div class="mb-6 flex flex-wrap items-end justify-between gap-3">
58
- <SectionHeader eyebrow={eyebrow} title={title} description={description} />
71
+ <SectionHeader eyebrow={eyebrow} title={title} description={description} level={headingLevel} />
59
72
  {(viewAllHref || headerActions) && (
60
73
  <div class="flex shrink-0 items-center gap-2 pb-2">
61
74
  {viewAllHref && (
@@ -72,36 +85,18 @@ const headerActions = Astro.slots.has("header-actions");
72
85
  </div>
73
86
 
74
87
  {cards.length === 0 ? (
75
- <p class="rounded-md border border-dashed border-ink-200 bg-white p-6 text-sm text-ink-500 dark:border-ink-800 dark:bg-ink-950 dark:text-ink-400">
76
- {emptyLabel}
77
- </p>
88
+ <EmptyState
89
+ title={emptyLabel}
90
+ description="Collections are defined in data/collections/*.yml — add a file there and it appears here on the next build."
91
+ />
78
92
  ) : (
79
- <ul class="m-0 grid list-none grid-cols-1 gap-3 p-0 sm:grid-cols-2 lg:grid-cols-3">
93
+ <CardGrid>
80
94
  {cards.map((c) => (
81
95
  <li class="m-0 p-0">
82
- <a
83
- href={c.url}
84
- data-event="collection_card_click"
85
- data-event-source="collection_index"
86
- data-event-item={c.slug}
87
- class="flex h-full flex-col gap-2 rounded-md border border-ink-200 bg-white p-5 transition-colors hover:border-ink-400 dark:border-ink-800 dark:bg-ink-950 dark:hover:border-ink-600"
88
- >
89
- <span class="text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
90
- {c.kind === "curated" ? "Curated" : "Generated"}
91
- </span>
92
- <span class="text-[1.0625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100">
93
- {c.title}
94
- </span>
95
- <span class="text-[0.875rem] text-ink-500 dark:text-ink-400">
96
- {c.description}
97
- </span>
98
- <span class="mt-auto pt-2 text-2xs font-medium text-ink-500 dark:text-ink-400">
99
- {c.count} {c.count === 1 ? "item" : "items"} →
100
- </span>
101
- </a>
96
+ <CollectionCard collection={c} countNoun={countNoun} />
102
97
  </li>
103
98
  ))}
104
- </ul>
99
+ </CardGrid>
105
100
  )}
106
101
  </Container>
107
102
  </section>