@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.
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/index.d.ts +0 -2
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +0 -2
- package/dist/lib/index.js.map +1 -1
- package/dist/ui/button.d.ts +45 -0
- package/dist/ui/button.d.ts.map +1 -0
- package/dist/ui/button.js +82 -0
- package/dist/ui/button.js.map +1 -0
- package/package.json +8 -5
- package/src/components/CardGrid.astro +41 -0
- package/src/components/CardIcon.astro +67 -0
- package/src/components/CategoryGrid.astro +20 -5
- package/src/components/CollectionCard.astro +56 -0
- package/src/components/CollectionIndex.astro +21 -26
- package/src/components/CollectionPage.astro +36 -13
- package/src/components/CollectionRow.astro +62 -192
- package/src/components/CollectionTeaser.astro +4 -0
- package/src/components/ContributorsGrid.astro +4 -1
- package/src/components/DirectoryIndexClient.astro +127 -30
- package/src/components/EditorialSummary.astro +6 -5
- package/src/components/FilterGroupMenu.astro +24 -10
- package/src/components/FilterOptions.astro +6 -1
- package/src/components/FinalCta.astro +5 -3
- package/src/components/Hero.astro +85 -139
- package/src/components/IndexRow.astro +36 -69
- package/src/components/LanguageBreakdown.astro +5 -1
- package/src/components/OriginalCollection.astro +3 -2
- package/src/components/Pagination.astro +7 -20
- package/src/components/ProjectCard.astro +345 -0
- package/src/components/RecordHeader.astro +111 -82
- package/src/components/RecordSection.astro +14 -10
- package/src/components/RecordSidebar.astro +68 -20
- package/src/components/RefinePanel.astro +41 -62
- package/src/components/SmartLensTabs.astro +3 -7
- package/src/components/StackGrid.astro +26 -6
- package/src/components/SubmissionClient.astro +140 -63
- package/src/components/TableOfContents.astro +31 -5
- package/src/components/WhyThisExists.astro +1 -1
- package/src/index.ts +2 -2
- package/src/layouts/BaseLayout.astro +79 -9
- package/src/layouts/Header.astro +5 -4
- package/src/layouts/SectionHeader.astro +3 -2
- package/src/layouts/Seo.astro +28 -17
- package/src/layouts/ThemeToggle.astro +23 -6
- package/src/lib/index.ts +0 -2
- package/src/server/collections.ts +11 -0
- package/src/server/contrast.test.ts +82 -0
- package/src/server/contrast.ts +117 -0
- package/src/server/directory.test.ts +177 -0
- package/src/server/directory.ts +328 -206
- package/src/server/github-repo.test.ts +88 -0
- package/src/server/github-repo.ts +104 -0
- package/src/server/index.ts +4 -3
- package/src/server/models-home.test.ts +101 -0
- package/src/server/models.test.ts +135 -0
- package/src/server/models.ts +170 -42
- package/src/styles.css +128 -16
- package/dist/lib/scores.d.ts +0 -2
- package/dist/lib/scores.d.ts.map +0 -1
- package/dist/lib/scores.js +0 -2
- package/dist/lib/scores.js.map +0 -1
- package/src/components/CurationGrid.astro +0 -41
- package/src/components/DecisionRow.astro +0 -89
- package/src/components/ExploreByCategory.astro +0 -67
- package/src/components/ExploreByStack.astro +0 -78
- package/src/components/GroveDocumentHead.astro +0 -28
- package/src/components/ItemCard.astro +0 -324
- package/src/components/MinimalAbout.astro +0 -82
- package/src/components/ScoreBars.astro +0 -102
- 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
|
|
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/
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAE/D,cAAc,iBAAiB,CAAC;AAEhC,+DAA+D;AAC/D,mEAAmE;AACnE,iEAAiE;AACjE,mCAAmC;AACnC,cAAc,gBAAgB,CAAC;AAE/B,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AACrD,+DAA+D;AAC/D,0DAA0D;AAC1D,8DAA8D;AAC9D,sCAAsC;AACtC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3C,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;AACrD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AAE/C,2EAA2E;AAC3E,qEAAqE;AACrE,+DAA+D;AAC/D,MAAM,8BAA8B,GAAG,mCAAmC,CAAC;AAC3E,MAAM,4BAA4B,GAChC,2CAA2C,CAAC;AAE9C,MAAM,CAAC,OAAO,UAAU,UAAU;IAChC,OAAO;QACL,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE;YACL,oBAAoB,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,EAAE,EAAE;gBACrE,qDAAqD;gBACrD,kCAAkC;gBAClC
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/index.d.ts
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";
|
package/dist/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA
|
|
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";
|
package/dist/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA
|
|
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
|
|
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
|
|
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": "
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
-
|
|
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
|
-
</
|
|
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
|
|
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
|
-
<
|
|
76
|
-
{emptyLabel}
|
|
77
|
-
|
|
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
|
-
<
|
|
93
|
+
<CardGrid>
|
|
80
94
|
{cards.map((c) => (
|
|
81
95
|
<li class="m-0 p-0">
|
|
82
|
-
<
|
|
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
|
-
</
|
|
99
|
+
</CardGrid>
|
|
105
100
|
)}
|
|
106
101
|
</Container>
|
|
107
102
|
</section>
|