@grove-dev/astro 0.5.2 → 0.5.3
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/assets/icons/.grove-icons.json +47 -0
- package/assets/icons/platforms/android.svg +1 -0
- package/assets/icons/platforms/app.svg +4 -0
- package/assets/icons/platforms/chrome.svg +1 -0
- package/assets/icons/platforms/desktop.svg +4 -0
- package/assets/icons/platforms/embedded.svg +5 -0
- package/assets/icons/platforms/ios.svg +1 -0
- package/assets/icons/platforms/linux.svg +1 -0
- package/assets/icons/platforms/macos.svg +1 -0
- package/assets/icons/platforms/ubuntu.svg +1 -0
- package/assets/icons/platforms/web.svg +5 -0
- package/assets/icons/platforms/windows.svg +1 -0
- package/assets/icons/stacks/android.svg +1 -0
- package/assets/icons/stacks/apple.svg +1 -0
- package/assets/icons/stacks/bun.svg +1 -0
- package/assets/icons/stacks/capacitor.svg +1 -0
- package/assets/icons/stacks/clojure.svg +1 -0
- package/assets/icons/stacks/dart.svg +1 -0
- package/assets/icons/stacks/deno.svg +1 -0
- package/assets/icons/stacks/django.svg +1 -0
- package/assets/icons/stacks/docker.svg +1 -0
- package/assets/icons/stacks/firebase.svg +1 -0
- package/assets/icons/stacks/flutter.svg +1 -0
- package/assets/icons/stacks/go.svg +1 -0
- package/assets/icons/stacks/graphql.svg +1 -0
- package/assets/icons/stacks/ionic.svg +1 -0
- package/assets/icons/stacks/java.svg +1 -0
- package/assets/icons/stacks/javascript.svg +1 -0
- package/assets/icons/stacks/kotlin.svg +1 -0
- package/assets/icons/stacks/llm.svg +4 -0
- package/assets/icons/stacks/mongodb.svg +1 -0
- package/assets/icons/stacks/nodejs.svg +1 -0
- package/assets/icons/stacks/python.svg +1 -0
- package/assets/icons/stacks/rag.svg +6 -0
- package/assets/icons/stacks/react-native.svg +1 -0
- package/assets/icons/stacks/react.svg +1 -0
- package/assets/icons/stacks/rust.svg +1 -0
- package/assets/icons/stacks/solidity.svg +1 -0
- package/assets/icons/stacks/sveltekit.svg +1 -0
- package/assets/icons/stacks/swift.svg +1 -0
- package/assets/icons/stacks/tauri.svg +1 -0
- package/assets/icons/stacks/tensorflow.svg +1 -0
- package/assets/icons/stacks/typescript.svg +1 -0
- package/assets/icons/stacks/vue.svg +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/icon-kinds.d.ts +13 -0
- package/dist/lib/icon-kinds.d.ts.map +1 -0
- package/dist/lib/icon-kinds.js +46 -0
- package/dist/lib/icon-kinds.js.map +1 -0
- package/dist/lib/icon-registry.d.ts +40 -0
- package/dist/lib/icon-registry.d.ts.map +1 -0
- package/dist/lib/icon-registry.js +85 -0
- package/dist/lib/icon-registry.js.map +1 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +4 -0
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/packaged-icons.d.ts +9 -0
- package/dist/lib/packaged-icons.d.ts.map +1 -0
- package/dist/lib/packaged-icons.js +21 -0
- package/dist/lib/packaged-icons.js.map +1 -0
- package/package.json +3 -2
- package/src/components/CollectionRow.astro +12 -47
- package/src/components/Icon.astro +74 -152
- package/src/components/IndexRow.astro +7 -25
- package/src/components/ProjectCard.astro +76 -117
- package/src/index.ts +28 -4
- package/src/layouts/BaseLayout.astro +11 -1
- package/src/layouts/Header.astro +19 -4
- package/src/lib/icon-kinds.ts +59 -0
- package/src/lib/icon-registry.ts +111 -0
- package/src/lib/index.ts +4 -0
- package/src/lib/packaged-icons.ts +32 -0
- package/src/styles.css +61 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Icon registry resolution — the name → asset lookup shared by
|
|
3
|
+
* `components/Icon.astro` and the icon test suite.
|
|
4
|
+
*
|
|
5
|
+
* Kept out of the component's frontmatter so tests can assert on the
|
|
6
|
+
* exact resolution the component performs instead of reimplementing
|
|
7
|
+
* (and drifting from) the alias and folder rules.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Normalize: lowercase + replace spaces/underscores with dashes.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately NOT `@grove-dev/core`'s `slugify`, which also strips
|
|
13
|
+
* dots and other punctuation. Icon names are file names, so they must
|
|
14
|
+
* survive verbatim apart from case and separator normalization.
|
|
15
|
+
*/
|
|
16
|
+
export function slugifyIconName(s) {
|
|
17
|
+
return s
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.trim()
|
|
20
|
+
.replace(/[\s_]+/g, "-");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Built-in id remaps. Stack-only by design: `category="platform"`
|
|
24
|
+
* resolves straight to the file name so a consumer shipping their own
|
|
25
|
+
* `public/icons/platforms/ios.svg` is never silently bypassed.
|
|
26
|
+
*/
|
|
27
|
+
const defaultAliases = {
|
|
28
|
+
stack: {
|
|
29
|
+
ios: "apple",
|
|
30
|
+
"native-ios": "apple",
|
|
31
|
+
ipados: "apple",
|
|
32
|
+
macos: "apple",
|
|
33
|
+
"objective-c": "apple",
|
|
34
|
+
spritekit: "apple",
|
|
35
|
+
swiftui: "apple",
|
|
36
|
+
kmp: "kotlin",
|
|
37
|
+
"kotlin-multiplatform": "kotlin",
|
|
38
|
+
// Names the shipped set covers under a different file.
|
|
39
|
+
"node.js": "nodejs",
|
|
40
|
+
node: "nodejs",
|
|
41
|
+
clojurescript: "clojure",
|
|
42
|
+
svelte: "sveltekit",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
/** Stack marks that `category="platform"` should reuse rather than duplicate. */
|
|
46
|
+
const platformStackIcons = new Set(["docker"]);
|
|
47
|
+
/** Platforms with no mark at all — always render the initials chip. */
|
|
48
|
+
export const textOnlyPlatforms = new Set(["self-host", "self-hosted"]);
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a registry id to the asset it points at.
|
|
51
|
+
*
|
|
52
|
+
* Lookup order: `aliases` → the built-in stack aliases → the raw
|
|
53
|
+
* slugified name. Any name not covered by the shipped set simply
|
|
54
|
+
* resolves to `{category-folder}/{slug}`, which is what lets consumers
|
|
55
|
+
* drop their own SVGs in without touching this file.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveIconAsset(name, category, aliases) {
|
|
58
|
+
const resolved = resolveName(name, category, aliases);
|
|
59
|
+
const folder = category === "stack" || (category === "platform" && platformStackIcons.has(resolved))
|
|
60
|
+
? "stacks"
|
|
61
|
+
: category === "platform"
|
|
62
|
+
? "platforms"
|
|
63
|
+
: "brands";
|
|
64
|
+
return {
|
|
65
|
+
folder,
|
|
66
|
+
name: resolved,
|
|
67
|
+
key: `${folder}/${resolved}`,
|
|
68
|
+
textOnly: category === "platform" && textOnlyPlatforms.has(resolved),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function resolveName(raw, category, aliases) {
|
|
72
|
+
if (!raw)
|
|
73
|
+
return "";
|
|
74
|
+
for (const registry of [aliases, defaultAliases[category]]) {
|
|
75
|
+
if (!registry)
|
|
76
|
+
continue;
|
|
77
|
+
const lower = raw.toLowerCase();
|
|
78
|
+
for (const [k, v] of Object.entries(registry)) {
|
|
79
|
+
if (k.toLowerCase() === lower)
|
|
80
|
+
return slugifyIconName(v);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return slugifyIconName(raw);
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=icon-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icon-registry.js","sourceRoot":"","sources":["../../src/lib/icon-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,CAAS;IACvC,OAAO,CAAC;SACL,WAAW,EAAE;SACb,IAAI,EAAE;SACN,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;AAC7B,CAAC;AAED;;;;GAIG;AACH,MAAM,cAAc,GAA0D;IAC5E,KAAK,EAAE;QACL,GAAG,EAAE,OAAO;QACZ,YAAY,EAAE,OAAO;QACrB,MAAM,EAAE,OAAO;QACf,KAAK,EAAE,OAAO;QACd,aAAa,EAAE,OAAO;QACtB,SAAS,EAAE,OAAO;QAClB,OAAO,EAAE,OAAO;QAChB,GAAG,EAAE,QAAQ;QACb,sBAAsB,EAAE,QAAQ;QAChC,uDAAuD;QACvD,SAAS,EAAE,QAAQ;QACnB,IAAI,EAAE,QAAQ;QACd,aAAa,EAAE,SAAS;QACxB,MAAM,EAAE,WAAW;KACpB;CACF,CAAC;AAEF,iFAAiF;AACjF,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AAE/C,uEAAuE;AACvE,MAAM,CAAC,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC,CAAC;AAavE;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,QAAsB,EACtB,OAAgC;IAEhC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACtD,MAAM,MAAM,GACV,QAAQ,KAAK,OAAO,IAAI,CAAC,QAAQ,KAAK,UAAU,IAAI,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnF,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,QAAQ,KAAK,UAAU;YACvB,CAAC,CAAC,WAAW;YACb,CAAC,CAAC,QAAQ,CAAC;IAEjB,OAAO;QACL,MAAM;QACN,IAAI,EAAE,QAAQ;QACd,GAAG,EAAE,GAAG,MAAM,IAAI,QAAQ,EAAE;QAC5B,QAAQ,EAAE,QAAQ,KAAK,UAAU,IAAI,iBAAiB,CAAC,GAAG,CAAC,QAAQ,CAAC;KACrE,CAAC;AACJ,CAAC;AAED,SAAS,WAAW,CAClB,GAAW,EACX,QAAsB,EACtB,OAAgC;IAEhC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,KAAK,MAAM,QAAQ,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,QAAQ;YAAE,SAAS;QACxB,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;QAChC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9C,IAAI,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK;gBAAE,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IACD,OAAO,eAAe,CAAC,GAAG,CAAC,CAAC;AAC9B,CAAC"}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `lib/search`)
|
|
11
11
|
* - compute taxonomy counts at build time (`lib/taxonomy-counts`)
|
|
12
12
|
* - pretty-print slugs and label/lens/sort/status ids (`lib/display`)
|
|
13
|
+
* - resolve icon registry ids to asset paths (`lib/icon-registry`)
|
|
13
14
|
*
|
|
14
15
|
* All helpers are pure, dependency-free, and typed against
|
|
15
16
|
* `@grove-dev/core` (specifically `ProjectRecord` for the index
|
|
@@ -22,4 +23,7 @@ export * from "./search.js";
|
|
|
22
23
|
export * from "./taxonomy-counts.js";
|
|
23
24
|
export * from "./display.js";
|
|
24
25
|
export * from "./load-collections.js";
|
|
26
|
+
export * from "./icon-registry.js";
|
|
27
|
+
export * from "./icon-kinds.js";
|
|
28
|
+
export * from "./packaged-icons.js";
|
|
25
29
|
//# sourceMappingURL=index.d.ts.map
|
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;;;;;;;;;;;;;;;;;GAiBG;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;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
package/dist/lib/index.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `lib/search`)
|
|
11
11
|
* - compute taxonomy counts at build time (`lib/taxonomy-counts`)
|
|
12
12
|
* - pretty-print slugs and label/lens/sort/status ids (`lib/display`)
|
|
13
|
+
* - resolve icon registry ids to asset paths (`lib/icon-registry`)
|
|
13
14
|
*
|
|
14
15
|
* All helpers are pure, dependency-free, and typed against
|
|
15
16
|
* `@grove-dev/core` (specifically `ProjectRecord` for the index
|
|
@@ -22,4 +23,7 @@ export * from "./search.js";
|
|
|
22
23
|
export * from "./taxonomy-counts.js";
|
|
23
24
|
export * from "./display.js";
|
|
24
25
|
export * from "./load-collections.js";
|
|
26
|
+
export * from "./icon-registry.js";
|
|
27
|
+
export * from "./icon-kinds.js";
|
|
28
|
+
export * from "./packaged-icons.js";
|
|
25
29
|
//# sourceMappingURL=index.js.map
|
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;;;;;;;;;;;;;;;;;GAiBG;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;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IconSyncOptions, IconSyncResult } from "@grove-dev/core";
|
|
2
|
+
/**
|
|
3
|
+
* `packages/astro/assets/icons` — two levels up, which holds for both
|
|
4
|
+
* `src/lib/` (tests, Vite) and `dist/lib/` (published).
|
|
5
|
+
*/
|
|
6
|
+
export declare const packagedIconsDir: string;
|
|
7
|
+
/** Copy the packaged icon set into a site's `public/`. */
|
|
8
|
+
export declare function syncPackagedIcons(publicDir: string, options?: IconSyncOptions): Promise<IconSyncResult>;
|
|
9
|
+
//# sourceMappingURL=packaged-icons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packaged-icons.d.ts","sourceRoot":"","sources":["../../src/lib/packaged-icons.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGvE;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAM5B,CAAC;AAEF,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAEzB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Locate the icon set that ships inside `@grove-dev/astro`.
|
|
3
|
+
*
|
|
4
|
+
* The component and the SVGs it points at live in the same package on
|
|
5
|
+
* purpose: an upgrade that changes how `Icon.astro` resolves a name
|
|
6
|
+
* carries the matching artwork with it, so a consumer can never end up
|
|
7
|
+
* with a component asking for files their site does not have.
|
|
8
|
+
*/
|
|
9
|
+
import { dirname, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import { syncIconAssets } from "@grove-dev/core";
|
|
12
|
+
/**
|
|
13
|
+
* `packages/astro/assets/icons` — two levels up, which holds for both
|
|
14
|
+
* `src/lib/` (tests, Vite) and `dist/lib/` (published).
|
|
15
|
+
*/
|
|
16
|
+
export const packagedIconsDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "assets", "icons");
|
|
17
|
+
/** Copy the packaged icon set into a site's `public/`. */
|
|
18
|
+
export function syncPackagedIcons(publicDir, options) {
|
|
19
|
+
return syncIconAssets(packagedIconsDir, publicDir, options);
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=packaged-icons.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"packaged-icons.js","sourceRoot":"","sources":["../../src/lib/packaged-icons.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,OAAO,CACrC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EACvC,IAAI,EACJ,IAAI,EACJ,QAAQ,EACR,OAAO,CACR,CAAC;AAEF,0DAA0D;AAC1D,MAAM,UAAU,iBAAiB,CAC/B,SAAiB,EACjB,OAAyB;IAEzB,OAAO,cAAc,CAAC,gBAAgB,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grove-dev/astro",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Composable Astro UI, data adapters, and integration for Grove directories.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"registry": "https://registry.npmjs.org/"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
|
+
"assets",
|
|
32
33
|
"dist",
|
|
33
34
|
"src/components",
|
|
34
35
|
"src/layouts",
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
"sanitize-html": "^2.17.5",
|
|
61
62
|
"shiki": "^1.29.2",
|
|
62
63
|
"yaml": "^2.9.0",
|
|
63
|
-
"@grove-dev/core": "0.5.
|
|
64
|
+
"@grove-dev/core": "0.5.3"
|
|
64
65
|
},
|
|
65
66
|
"peerDependencies": {
|
|
66
67
|
"astro": "^6.0.0 || ^7.0.0"
|
|
@@ -12,19 +12,20 @@
|
|
|
12
12
|
* - `href` — full detail URL; wins over `routeSlug`
|
|
13
13
|
* - `routeSlug` — blueprint slug (e.g. "projects"); details derived
|
|
14
14
|
* as `/${routeSlug}/${slug}` when `href` is not provided
|
|
15
|
-
* - `showChips` —
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* - `showChips`, `showLabel` — deprecated no-ops. The card renders
|
|
16
|
+
* one fixed layout everywhere, so a record cannot look like a
|
|
17
|
+
* different record depending on the page it appears on.
|
|
18
|
+
* - `showMeta` — still honoured: it controls whether the *data*
|
|
19
|
+
* (stars, last-updated) is passed in at all, not how it is drawn
|
|
18
20
|
* - `class` — extra class on the card root
|
|
19
21
|
* - `eager` — bypass lazy loading for above-the-fold avatars
|
|
20
22
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* - "Visit site" — when `entry.homepageHref` is set
|
|
23
|
+
* The repository is reached through the card's `owner/repo` line;
|
|
24
|
+
* there is no separate footer link button.
|
|
24
25
|
*
|
|
25
26
|
* Analytics: emits `data-event="collection_item_click"` /
|
|
26
|
-
* `data-event-source="collection_row"` on the card root
|
|
27
|
-
*
|
|
27
|
+
* `data-event-source="collection_row"` on the card root, so sites wire
|
|
28
|
+
* one handler in BaseLayout.
|
|
28
29
|
*/
|
|
29
30
|
import type { CollectionEntry } from "@grove-dev/core";
|
|
30
31
|
import ProjectCard from "./ProjectCard.astro";
|
|
@@ -35,8 +36,11 @@ interface Props {
|
|
|
35
36
|
href?: string;
|
|
36
37
|
/** Blueprint route slug; detail URL = `/${routeSlug}/${slug}`. */
|
|
37
38
|
routeSlug?: string;
|
|
39
|
+
/** @deprecated Ignored — the card renders one fixed layout. */
|
|
38
40
|
showChips?: boolean;
|
|
41
|
+
/** Pass stars / last-updated through to the card. Default true. */
|
|
39
42
|
showMeta?: boolean;
|
|
43
|
+
/** @deprecated Ignored — see `showChips`. */
|
|
40
44
|
showLabel?: boolean;
|
|
41
45
|
eager?: boolean;
|
|
42
46
|
class?: string;
|
|
@@ -46,9 +50,7 @@ const {
|
|
|
46
50
|
entry,
|
|
47
51
|
href: hrefProp,
|
|
48
52
|
routeSlug,
|
|
49
|
-
showChips = true,
|
|
50
53
|
showMeta = true,
|
|
51
|
-
showLabel = true,
|
|
52
54
|
eager = false,
|
|
53
55
|
class: className = "",
|
|
54
56
|
} = Astro.props;
|
|
@@ -61,33 +63,6 @@ const detailHref =
|
|
|
61
63
|
: entry.url || (base ? `/${base}/${slug}` : `/${slug}`);
|
|
62
64
|
|
|
63
65
|
const repoHref = entry.repoHref;
|
|
64
|
-
const homepageHref = entry.homepageHref;
|
|
65
|
-
const curated = Boolean(entry.curationScore || (entry.activityScore && entry.activityScore > 0.5));
|
|
66
|
-
|
|
67
|
-
const secondaryLinks = [
|
|
68
|
-
...(repoHref
|
|
69
|
-
? [
|
|
70
|
-
{
|
|
71
|
-
label: "View repo",
|
|
72
|
-
href: repoHref,
|
|
73
|
-
external: true,
|
|
74
|
-
event: "external_repo_click",
|
|
75
|
-
eventSource: "collection_row_view_repo",
|
|
76
|
-
},
|
|
77
|
-
]
|
|
78
|
-
: []),
|
|
79
|
-
...(homepageHref
|
|
80
|
-
? [
|
|
81
|
-
{
|
|
82
|
-
label: "Visit site",
|
|
83
|
-
href: homepageHref,
|
|
84
|
-
external: true,
|
|
85
|
-
event: "external_link_click",
|
|
86
|
-
eventSource: "collection_row_visit_site",
|
|
87
|
-
},
|
|
88
|
-
]
|
|
89
|
-
: []),
|
|
90
|
-
];
|
|
91
66
|
---
|
|
92
67
|
|
|
93
68
|
<ProjectCard
|
|
@@ -98,17 +73,7 @@ const secondaryLinks = [
|
|
|
98
73
|
repoUrl={repoHref}
|
|
99
74
|
stars={showMeta ? entry.stars : undefined}
|
|
100
75
|
pushedAt={showMeta ? entry.pushedAt : undefined}
|
|
101
|
-
license={showMeta ? (entry.license ?? null) : null}
|
|
102
|
-
category={entry.categories?.[0]}
|
|
103
|
-
status={entry.status}
|
|
104
|
-
curated={curated}
|
|
105
76
|
stack={entry.stack}
|
|
106
|
-
platforms={entry.platform ?? []}
|
|
107
|
-
showCurated={showLabel}
|
|
108
|
-
showLicense={showMeta}
|
|
109
|
-
showCategoryStatus={showLabel}
|
|
110
|
-
labelledChips={showChips}
|
|
111
|
-
secondaryLinks={secondaryLinks}
|
|
112
77
|
eager={eager}
|
|
113
78
|
event="collection_item_click"
|
|
114
79
|
eventSource="collection_row"
|
|
@@ -2,18 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Icon — brand icon registry.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* `public/icons
|
|
8
|
-
*
|
|
9
|
-
* under the same path (or override the `base` prop) without forking
|
|
5
|
+
* Resolves a registry name to `/icons/{stacks|platforms|brands}/{name}.svg`
|
|
6
|
+
* and renders it. The Astro integration syncs the packaged set into the
|
|
7
|
+
* consumer's `public/icons/` on every build; consumers can ship their own
|
|
8
|
+
* SVGs under the same paths (or override the `base` prop) without forking
|
|
10
9
|
* this component.
|
|
11
10
|
*
|
|
12
11
|
* The component is **generic** — it does not import any consumer-side
|
|
13
|
-
* `siteConfig`, `item`, or blueprint-specific module.
|
|
14
|
-
* supplies its own icon list (and optionally an `aliases` map for
|
|
15
|
-
* friendly ids) via props, and any `name` not in the registry
|
|
16
|
-
* falls back to the initials placeholder.
|
|
12
|
+
* `siteConfig`, `item`, or blueprint-specific module.
|
|
17
13
|
*
|
|
18
14
|
* `category` selects the subfolder:
|
|
19
15
|
*
|
|
@@ -21,21 +17,43 @@
|
|
|
21
17
|
* - "platform" → `/icons/platforms/{name}.svg`
|
|
22
18
|
* - "brand" → `/icons/brands/{name}.svg` (consumer-supplied)
|
|
23
19
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
20
|
+
* ## Theming
|
|
21
|
+
*
|
|
22
|
+
* `ICON_KINDS` (generated by `scripts/sync-icons.mjs`) classifies each
|
|
23
|
+
* packaged icon:
|
|
24
|
+
*
|
|
25
|
+
* - **color** — the default. Any brand with a color mark keeps it,
|
|
26
|
+
* rendered as `<img>` with an `onerror` fallback to an initials
|
|
27
|
+
* chip.
|
|
28
|
+
* - **mono** — marks with no color to lose (Apple, Rust, Tauri,
|
|
29
|
+
* Solidity, Deno) plus the concept glyphs. Rendered as a CSS-masked
|
|
30
|
+
* `<span>` painted from `--grove-foreground`, which reproduces how
|
|
31
|
+
* those brands are actually used: solid black on light, solid white
|
|
32
|
+
* on dark, with no script and no second file. See
|
|
33
|
+
* `.grove-icon-mask` in `styles.css`.
|
|
34
|
+
*
|
|
35
|
+
* The map CLASSIFIES; it does not gate availability. A name it has never
|
|
36
|
+
* heard of — a consumer's own SVG — takes the `<img>` path, exactly as
|
|
37
|
+
* before. Pass `kind="mono"` to opt a custom mark into `currentColor`
|
|
38
|
+
* tinting.
|
|
30
39
|
*
|
|
31
40
|
* Aliases: a tiny `aliases` map (e.g. `rn → react-native`) lets the
|
|
32
|
-
* registry accept multiple ids for the same asset
|
|
33
|
-
*
|
|
41
|
+
* registry accept multiple ids for the same asset. Lookup falls through
|
|
42
|
+
* `aliases` → the built-in stack aliases → the raw slug, then fails over
|
|
34
43
|
* to the initials placeholder.
|
|
35
44
|
*/
|
|
36
|
-
import {
|
|
45
|
+
import { ICON_KINDS } from "../lib/icon-kinds";
|
|
46
|
+
import type { IconKind } from "../lib/icon-kinds";
|
|
47
|
+
import { resolveIconAsset, slugifyIconName } from "../lib/icon-registry";
|
|
48
|
+
import type { IconCategory } from "../lib/icon-registry";
|
|
37
49
|
|
|
38
|
-
export type IconCategory
|
|
50
|
+
export type { IconCategory, IconKind };
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @deprecated Theme variants are no longer file-based — `mono` icons
|
|
54
|
+
* inherit `currentColor`. Accepted and ignored so consumers passing
|
|
55
|
+
* `mode="auto"` do not hit a type error.
|
|
56
|
+
*/
|
|
39
57
|
export type IconMode = "static" | "auto";
|
|
40
58
|
|
|
41
59
|
interface Props {
|
|
@@ -52,11 +70,14 @@ interface Props {
|
|
|
52
70
|
class?: string;
|
|
53
71
|
/** Tooltip / aria-label. */
|
|
54
72
|
title?: string;
|
|
73
|
+
/** @deprecated No-op. Kept for one minor release. */
|
|
74
|
+
mode?: IconMode;
|
|
55
75
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
76
|
+
* Override the packaged kind classification. Pass `"mono"` for a
|
|
77
|
+
* consumer-supplied single-color mark so it is painted black on
|
|
78
|
+
* light and white on dark instead of shipping two files.
|
|
58
79
|
*/
|
|
59
|
-
|
|
80
|
+
kind?: IconKind;
|
|
60
81
|
/**
|
|
61
82
|
* Optional id → id remap. Lookup goes `name → aliases[name] →
|
|
62
83
|
* {name}.svg` (case-insensitive).
|
|
@@ -74,7 +95,7 @@ interface Props {
|
|
|
74
95
|
fallbackInitials?: string;
|
|
75
96
|
/**
|
|
76
97
|
* Base path for icon files. Defaults to `/icons` (matches the
|
|
77
|
-
*
|
|
98
|
+
* synced `public/icons/`).
|
|
78
99
|
*/
|
|
79
100
|
base?: string;
|
|
80
101
|
}
|
|
@@ -85,125 +106,66 @@ const {
|
|
|
85
106
|
size = 16,
|
|
86
107
|
class: className = "",
|
|
87
108
|
title,
|
|
88
|
-
|
|
109
|
+
kind,
|
|
89
110
|
aliases,
|
|
90
111
|
available,
|
|
91
112
|
fallbackInitials,
|
|
92
113
|
base = "/icons",
|
|
93
114
|
} = Astro.props;
|
|
94
115
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Resolve `name` through the aliases map.
|
|
101
|
-
function resolveName(raw: string): string {
|
|
102
|
-
if (!raw) return "";
|
|
103
|
-
const defaultAliases: Partial<Record<IconCategory, Record<string, string>>> = {
|
|
104
|
-
stack: {
|
|
105
|
-
ios: "apple",
|
|
106
|
-
"native-ios": "apple",
|
|
107
|
-
ipados: "apple",
|
|
108
|
-
macos: "apple",
|
|
109
|
-
"objective-c": "apple",
|
|
110
|
-
spritekit: "apple",
|
|
111
|
-
swiftui: "apple",
|
|
112
|
-
kmp: "kotlin",
|
|
113
|
-
"kotlin-multiplatform": "kotlin",
|
|
114
|
-
},
|
|
115
|
-
};
|
|
116
|
-
for (const registry of [aliases, defaultAliases[category]]) {
|
|
117
|
-
if (!registry) continue;
|
|
118
|
-
const lower = raw.toLowerCase();
|
|
119
|
-
for (const [k, v] of Object.entries(registry)) {
|
|
120
|
-
if (k.toLowerCase() === lower) return slugify(v);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return slugify(raw);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const resolved = resolveName(name);
|
|
127
|
-
const availableIcons = available?.map(slugify);
|
|
116
|
+
const asset = resolveIconAsset(name, category, aliases);
|
|
117
|
+
const resolved = asset.name;
|
|
118
|
+
const availableIcons = available?.map(slugifyIconName);
|
|
128
119
|
const hasAsset =
|
|
129
120
|
category === "brand" ||
|
|
130
121
|
availableIcons === undefined ||
|
|
131
122
|
availableIcons.includes(resolved);
|
|
132
|
-
const
|
|
133
|
-
const platformStackIcons = new Set(["docker"]);
|
|
134
|
-
const textOnlyPlatforms = new Set(["self-host", "self-hosted"]);
|
|
135
|
-
const folder =
|
|
136
|
-
category === "stack" || (category === "platform" && platformStackIcons.has(resolved))
|
|
137
|
-
? "stacks"
|
|
138
|
-
: category === "platform"
|
|
139
|
-
? "platforms"
|
|
140
|
-
: "brands";
|
|
141
|
-
const baseUrl = `${base.replace(/\/$/, "")}/${folder}/${resolved}`;
|
|
142
|
-
|
|
143
|
-
// Static: just point at the canonical svg.
|
|
144
|
-
const staticSrc = `${baseUrl}.svg`;
|
|
123
|
+
const src = `${base.replace(/\/$/, "")}/${asset.folder}/${resolved}.svg`;
|
|
145
124
|
|
|
146
125
|
// Initials fallback: first 1–2 alphanumerics, uppercased.
|
|
147
126
|
const init = (fallbackInitials ?? name ?? "")
|
|
148
127
|
.replace(/[^a-z0-9]/gi, "")
|
|
149
128
|
.slice(0, 2)
|
|
150
129
|
.toUpperCase();
|
|
151
|
-
const showFallback =
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
130
|
+
const showFallback = !resolved || !hasAsset || asset.textOnly;
|
|
131
|
+
|
|
132
|
+
// Unknown names fall through to `<img>`, never to `mono` — a mask has
|
|
133
|
+
// no `onerror`, so a missing file would render an invisible blank.
|
|
134
|
+
const effectiveKind: IconKind = kind ?? ICON_KINDS[asset.key] ?? "color";
|
|
135
|
+
|
|
136
|
+
// `name` reaches the mask branch as part of a CSS `url()` token, and
|
|
137
|
+
// slugifying does not remove the characters that would close it. Astro
|
|
138
|
+
// escapes the attribute; this escapes the CSS inside it.
|
|
139
|
+
const cssUrl = src.replace(/['"()\\\s]/g, encodeURIComponent);
|
|
155
140
|
|
|
156
141
|
// Build aria-label.
|
|
157
142
|
const ariaLabel = title ?? name;
|
|
158
143
|
const a11y: Record<string, string> = title
|
|
159
144
|
? { "aria-label": ariaLabel, role: "img" }
|
|
160
145
|
: { "aria-hidden": "true", "focusable": "false" };
|
|
146
|
+
|
|
147
|
+
const fallbackClass = [
|
|
148
|
+
"inline-flex shrink-0 select-none items-center justify-center rounded-sm bg-ink-100 align-middle font-semibold uppercase leading-none text-ink-700 dark:bg-ink-800 dark:text-ink-200",
|
|
149
|
+
className,
|
|
150
|
+
];
|
|
151
|
+
const fallbackStyle = `width:${size}px;height:${size}px;font-size:${Math.max(10, size - 6)}px;`;
|
|
161
152
|
---
|
|
162
153
|
|
|
163
154
|
{
|
|
164
155
|
showFallback ? (
|
|
165
|
-
<span
|
|
166
|
-
class:list={[
|
|
167
|
-
"inline-flex shrink-0 select-none items-center justify-center rounded-sm bg-ink-100 align-middle font-semibold uppercase leading-none text-ink-700 dark:bg-ink-800 dark:text-ink-200",
|
|
168
|
-
className,
|
|
169
|
-
]}
|
|
170
|
-
style={`width:${size}px;height:${size}px;font-size:${Math.max(10, size - 6)}px;`}
|
|
171
|
-
{...a11y}
|
|
172
|
-
>
|
|
156
|
+
<span class:list={fallbackClass} style={fallbackStyle} {...a11y}>
|
|
173
157
|
{init || "·"}
|
|
174
158
|
</span>
|
|
175
|
-
) :
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
data-grove-icon-dark={`${baseUrl}-dark.svg`}
|
|
182
|
-
data-grove-icon-fallback={`${baseUrl}-light.svg`}
|
|
183
|
-
width={size}
|
|
184
|
-
height={size}
|
|
185
|
-
alt=""
|
|
186
|
-
class:list={["inline-block shrink-0 align-middle", className]}
|
|
187
|
-
onerror="this.hidden=true;this.nextElementSibling.hidden=false"
|
|
188
|
-
{...a11y}
|
|
189
|
-
/>
|
|
190
|
-
<span
|
|
191
|
-
hidden
|
|
192
|
-
data-grove-icon-fallback-initials
|
|
193
|
-
class:list={[
|
|
194
|
-
"inline-flex shrink-0 select-none items-center justify-center rounded-sm bg-ink-100 align-middle font-semibold uppercase leading-none text-ink-700 dark:bg-ink-800 dark:text-ink-200",
|
|
195
|
-
className,
|
|
196
|
-
]}
|
|
197
|
-
style={`width:${size}px;height:${size}px;font-size:${Math.max(10, size - 6)}px;`}
|
|
198
|
-
{...a11y}
|
|
199
|
-
>
|
|
200
|
-
{init || "·"}
|
|
201
|
-
</span>
|
|
202
|
-
</>
|
|
159
|
+
) : effectiveKind === "mono" ? (
|
|
160
|
+
<span
|
|
161
|
+
class:list={["grove-icon-mask", className]}
|
|
162
|
+
style={`width:${size}px;height:${size}px;--grove-icon-url:url('${cssUrl}');`}
|
|
163
|
+
{...a11y}
|
|
164
|
+
/>
|
|
203
165
|
) : (
|
|
204
166
|
<>
|
|
205
167
|
<img
|
|
206
|
-
src={
|
|
168
|
+
src={src}
|
|
207
169
|
width={size}
|
|
208
170
|
height={size}
|
|
209
171
|
alt=""
|
|
@@ -213,49 +175,9 @@ const a11y: Record<string, string> = title
|
|
|
213
175
|
onerror="this.hidden=true;this.nextElementSibling.hidden=false"
|
|
214
176
|
{...a11y}
|
|
215
177
|
/>
|
|
216
|
-
<span
|
|
217
|
-
hidden
|
|
218
|
-
data-grove-icon-fallback-initials
|
|
219
|
-
class:list={[
|
|
220
|
-
"inline-flex shrink-0 select-none items-center justify-center rounded-sm bg-ink-100 align-middle font-semibold uppercase leading-none text-ink-700 dark:bg-ink-800 dark:text-ink-200",
|
|
221
|
-
className,
|
|
222
|
-
]}
|
|
223
|
-
style={`width:${size}px;height:${size}px;font-size:${Math.max(10, size - 6)}px;`}
|
|
224
|
-
{...a11y}
|
|
225
|
-
>
|
|
178
|
+
<span hidden data-grove-icon-fallback-initials class:list={fallbackClass} style={fallbackStyle} {...a11y}>
|
|
226
179
|
{init || "·"}
|
|
227
180
|
</span>
|
|
228
181
|
</>
|
|
229
182
|
)
|
|
230
183
|
}
|
|
231
|
-
|
|
232
|
-
{effectiveMode === "auto" && (
|
|
233
|
-
<script is:inline>
|
|
234
|
-
// Theme-aware icon swap. Runs after THEME_INIT (BaseLayout) so
|
|
235
|
-
// `data-resolved-theme` is already populated. Idempotent — the
|
|
236
|
-
// MutationObserver picks up theme changes from ThemeToggle.
|
|
237
|
-
(function () {
|
|
238
|
-
function swap() {
|
|
239
|
-
var theme =
|
|
240
|
-
document.documentElement.dataset.resolvedTheme || "light";
|
|
241
|
-
var nodes = document.querySelectorAll(
|
|
242
|
-
"img[data-grove-icon]:not([data-grove-icon-bound])",
|
|
243
|
-
);
|
|
244
|
-
for (var i = 0; i < nodes.length; i++) {
|
|
245
|
-
var img = nodes[i];
|
|
246
|
-
img.setAttribute("data-grove-icon-bound", "1");
|
|
247
|
-
var next =
|
|
248
|
-
theme === "dark"
|
|
249
|
-
? img.getAttribute("data-grove-icon-dark")
|
|
250
|
-
: img.getAttribute("data-grove-icon-light");
|
|
251
|
-
if (next) img.src = next;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
swap();
|
|
255
|
-
new MutationObserver(swap).observe(document.documentElement, {
|
|
256
|
-
attributes: true,
|
|
257
|
-
attributeFilter: ["data-resolved-theme"],
|
|
258
|
-
});
|
|
259
|
-
})();
|
|
260
|
-
</script>
|
|
261
|
-
)}
|