@grove-dev/astro 0.5.1 → 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.
Files changed (78) hide show
  1. package/assets/icons/.grove-icons.json +47 -0
  2. package/assets/icons/platforms/android.svg +1 -0
  3. package/assets/icons/platforms/app.svg +4 -0
  4. package/assets/icons/platforms/chrome.svg +1 -0
  5. package/assets/icons/platforms/desktop.svg +4 -0
  6. package/assets/icons/platforms/embedded.svg +5 -0
  7. package/assets/icons/platforms/ios.svg +1 -0
  8. package/assets/icons/platforms/linux.svg +1 -0
  9. package/assets/icons/platforms/macos.svg +1 -0
  10. package/assets/icons/platforms/ubuntu.svg +1 -0
  11. package/assets/icons/platforms/web.svg +5 -0
  12. package/assets/icons/platforms/windows.svg +1 -0
  13. package/assets/icons/stacks/android.svg +1 -0
  14. package/assets/icons/stacks/apple.svg +1 -0
  15. package/assets/icons/stacks/bun.svg +1 -0
  16. package/assets/icons/stacks/capacitor.svg +1 -0
  17. package/assets/icons/stacks/clojure.svg +1 -0
  18. package/assets/icons/stacks/dart.svg +1 -0
  19. package/assets/icons/stacks/deno.svg +1 -0
  20. package/assets/icons/stacks/django.svg +1 -0
  21. package/assets/icons/stacks/docker.svg +1 -0
  22. package/assets/icons/stacks/firebase.svg +1 -0
  23. package/assets/icons/stacks/flutter.svg +1 -0
  24. package/assets/icons/stacks/go.svg +1 -0
  25. package/assets/icons/stacks/graphql.svg +1 -0
  26. package/assets/icons/stacks/ionic.svg +1 -0
  27. package/assets/icons/stacks/java.svg +1 -0
  28. package/assets/icons/stacks/javascript.svg +1 -0
  29. package/assets/icons/stacks/kotlin.svg +1 -0
  30. package/assets/icons/stacks/llm.svg +4 -0
  31. package/assets/icons/stacks/mongodb.svg +1 -0
  32. package/assets/icons/stacks/nodejs.svg +1 -0
  33. package/assets/icons/stacks/python.svg +1 -0
  34. package/assets/icons/stacks/rag.svg +6 -0
  35. package/assets/icons/stacks/react-native.svg +1 -0
  36. package/assets/icons/stacks/react.svg +1 -0
  37. package/assets/icons/stacks/rust.svg +1 -0
  38. package/assets/icons/stacks/solidity.svg +1 -0
  39. package/assets/icons/stacks/sveltekit.svg +1 -0
  40. package/assets/icons/stacks/swift.svg +1 -0
  41. package/assets/icons/stacks/tauri.svg +1 -0
  42. package/assets/icons/stacks/tensorflow.svg +1 -0
  43. package/assets/icons/stacks/typescript.svg +1 -0
  44. package/assets/icons/stacks/vue.svg +1 -0
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +23 -4
  47. package/dist/index.js.map +1 -1
  48. package/dist/lib/icon-kinds.d.ts +13 -0
  49. package/dist/lib/icon-kinds.d.ts.map +1 -0
  50. package/dist/lib/icon-kinds.js +46 -0
  51. package/dist/lib/icon-kinds.js.map +1 -0
  52. package/dist/lib/icon-registry.d.ts +40 -0
  53. package/dist/lib/icon-registry.d.ts.map +1 -0
  54. package/dist/lib/icon-registry.js +85 -0
  55. package/dist/lib/icon-registry.js.map +1 -0
  56. package/dist/lib/index.d.ts +4 -0
  57. package/dist/lib/index.d.ts.map +1 -1
  58. package/dist/lib/index.js +4 -0
  59. package/dist/lib/index.js.map +1 -1
  60. package/dist/lib/packaged-icons.d.ts +9 -0
  61. package/dist/lib/packaged-icons.d.ts.map +1 -0
  62. package/dist/lib/packaged-icons.js +21 -0
  63. package/dist/lib/packaged-icons.js.map +1 -0
  64. package/package.json +3 -2
  65. package/src/components/CollectionRow.astro +12 -47
  66. package/src/components/Icon.astro +74 -152
  67. package/src/components/IndexRow.astro +7 -25
  68. package/src/components/ProjectCard.astro +76 -117
  69. package/src/components/RefinePanel.astro +10 -1
  70. package/src/components/SmartLensTabs.astro +11 -1
  71. package/src/index.ts +28 -4
  72. package/src/layouts/BaseLayout.astro +11 -1
  73. package/src/layouts/Header.astro +19 -4
  74. package/src/lib/icon-kinds.ts +59 -0
  75. package/src/lib/icon-registry.ts +111 -0
  76. package/src/lib/index.ts +4 -0
  77. package/src/lib/packaged-icons.ts +32 -0
  78. package/src/styles.css +61 -0
@@ -0,0 +1,111 @@
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
+ export type IconCategory = "stack" | "platform" | "brand";
11
+ export type IconFolder = "stacks" | "platforms" | "brands";
12
+
13
+ /**
14
+ * Normalize: lowercase + replace spaces/underscores with dashes.
15
+ *
16
+ * Deliberately NOT `@grove-dev/core`'s `slugify`, which also strips
17
+ * dots and other punctuation. Icon names are file names, so they must
18
+ * survive verbatim apart from case and separator normalization.
19
+ */
20
+ export function slugifyIconName(s: string): string {
21
+ return s
22
+ .toLowerCase()
23
+ .trim()
24
+ .replace(/[\s_]+/g, "-");
25
+ }
26
+
27
+ /**
28
+ * Built-in id remaps. Stack-only by design: `category="platform"`
29
+ * resolves straight to the file name so a consumer shipping their own
30
+ * `public/icons/platforms/ios.svg` is never silently bypassed.
31
+ */
32
+ const defaultAliases: Partial<Record<IconCategory, Record<string, string>>> = {
33
+ stack: {
34
+ ios: "apple",
35
+ "native-ios": "apple",
36
+ ipados: "apple",
37
+ macos: "apple",
38
+ "objective-c": "apple",
39
+ spritekit: "apple",
40
+ swiftui: "apple",
41
+ kmp: "kotlin",
42
+ "kotlin-multiplatform": "kotlin",
43
+ // Names the shipped set covers under a different file.
44
+ "node.js": "nodejs",
45
+ node: "nodejs",
46
+ clojurescript: "clojure",
47
+ svelte: "sveltekit",
48
+ },
49
+ };
50
+
51
+ /** Stack marks that `category="platform"` should reuse rather than duplicate. */
52
+ const platformStackIcons = new Set(["docker"]);
53
+
54
+ /** Platforms with no mark at all — always render the initials chip. */
55
+ export const textOnlyPlatforms = new Set(["self-host", "self-hosted"]);
56
+
57
+ export interface ResolvedIconAsset {
58
+ /** Subfolder under the icon base path. */
59
+ folder: IconFolder;
60
+ /** Alias-resolved, slugified file name (no extension). */
61
+ name: string;
62
+ /** `${folder}/${name}` — the key used by the generated kind map. */
63
+ key: string;
64
+ /** True when this name is text-only and must fall back to initials. */
65
+ textOnly: boolean;
66
+ }
67
+
68
+ /**
69
+ * Resolve a registry id to the asset it points at.
70
+ *
71
+ * Lookup order: `aliases` → the built-in stack aliases → the raw
72
+ * slugified name. Any name not covered by the shipped set simply
73
+ * resolves to `{category-folder}/{slug}`, which is what lets consumers
74
+ * drop their own SVGs in without touching this file.
75
+ */
76
+ export function resolveIconAsset(
77
+ name: string,
78
+ category: IconCategory,
79
+ aliases?: Record<string, string>,
80
+ ): ResolvedIconAsset {
81
+ const resolved = resolveName(name, category, aliases);
82
+ const folder: IconFolder =
83
+ category === "stack" || (category === "platform" && platformStackIcons.has(resolved))
84
+ ? "stacks"
85
+ : category === "platform"
86
+ ? "platforms"
87
+ : "brands";
88
+
89
+ return {
90
+ folder,
91
+ name: resolved,
92
+ key: `${folder}/${resolved}`,
93
+ textOnly: category === "platform" && textOnlyPlatforms.has(resolved),
94
+ };
95
+ }
96
+
97
+ function resolveName(
98
+ raw: string,
99
+ category: IconCategory,
100
+ aliases?: Record<string, string>,
101
+ ): string {
102
+ if (!raw) return "";
103
+ for (const registry of [aliases, defaultAliases[category]]) {
104
+ if (!registry) continue;
105
+ const lower = raw.toLowerCase();
106
+ for (const [k, v] of Object.entries(registry)) {
107
+ if (k.toLowerCase() === lower) return slugifyIconName(v);
108
+ }
109
+ }
110
+ return slugifyIconName(raw);
111
+ }
package/src/lib/index.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
@@ -23,3 +24,6 @@ export * from "./search.js";
23
24
  export * from "./taxonomy-counts.js";
24
25
  export * from "./display.js";
25
26
  export * from "./load-collections.js";
27
+ export * from "./icon-registry.js";
28
+ export * from "./icon-kinds.js";
29
+ export * from "./packaged-icons.js";
@@ -0,0 +1,32 @@
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 type { IconSyncOptions, IconSyncResult } from "@grove-dev/core";
12
+ import { syncIconAssets } from "@grove-dev/core";
13
+
14
+ /**
15
+ * `packages/astro/assets/icons` — two levels up, which holds for both
16
+ * `src/lib/` (tests, Vite) and `dist/lib/` (published).
17
+ */
18
+ export const packagedIconsDir = resolve(
19
+ dirname(fileURLToPath(import.meta.url)),
20
+ "..",
21
+ "..",
22
+ "assets",
23
+ "icons",
24
+ );
25
+
26
+ /** Copy the packaged icon set into a site's `public/`. */
27
+ export function syncPackagedIcons(
28
+ publicDir: string,
29
+ options?: IconSyncOptions,
30
+ ): Promise<IconSyncResult> {
31
+ return syncIconAssets(packagedIconsDir, publicDir, options);
32
+ }
package/src/styles.css CHANGED
@@ -424,6 +424,67 @@
424
424
  * Colors come from the package's `ink-*` scale via CSS variables so
425
425
  * every consumer gets the same baseline regardless of brand overrides.
426
426
  * ────────────────────────────────────────────────────────────────── */
427
+
428
+ /* ──────────────────────────────────────────────────────────────────
429
+ * Monochrome brand marks (`Icon.astro`, kind: "mono")
430
+ *
431
+ * An SVG loaded through `<img src>` is an independent document that
432
+ * page CSS cannot reach, so `currentColor` inside it never resolves
433
+ * against the theme — it silently renders black in both modes. That
434
+ * is why single-color marks (Apple, Rust, Tauri, Deno, …) used to
435
+ * need a hand-maintained `-light`/`-dark` file pair plus a JS `src`
436
+ * swap, and why the pair was easy to get backwards.
437
+ *
438
+ * Masking inverts the relationship: the SVG supplies the *shape*
439
+ * (via its alpha channel) and the page supplies the *color* — no
440
+ * script and no second file.
441
+ *
442
+ * The paint is `--grove-foreground`, NOT `currentColor`. These are
443
+ * brand marks, and a brand mark's real presentation is solid black on
444
+ * light and solid white on dark — not whatever tint the surrounding
445
+ * chip label happens to use. Inheriting `currentColor` washed the
446
+ * Apple mark down to the chip's muted `ink-500`, which reads as a
447
+ * greyed-out logo rather than a logo. Callers that genuinely want a
448
+ * tinted mark can override `--grove-icon-mono`.
449
+ *
450
+ * The URL is per-icon and computed at render time, so it can never
451
+ * appear literally in a source file for Tailwind's extractor to find
452
+ * — arbitrary `mask-[url(…)]` utilities are structurally impossible
453
+ * here. Everything static lives in this class; only the URL travels
454
+ * inline, as `--grove-icon-url`.
455
+ * ────────────────────────────────────────────────────────────────── */
456
+ .grove-icon-mask {
457
+ display: inline-block;
458
+ flex-shrink: 0;
459
+ vertical-align: middle;
460
+ background-color: var(--grove-icon-mono, var(--grove-foreground));
461
+ -webkit-mask-image: var(--grove-icon-url);
462
+ mask-image: var(--grove-icon-url);
463
+ -webkit-mask-size: contain;
464
+ mask-size: contain;
465
+ -webkit-mask-repeat: no-repeat;
466
+ mask-repeat: no-repeat;
467
+ -webkit-mask-position: center;
468
+ mask-position: center;
469
+ }
470
+
471
+ /* Print stylesheets drop background colors, which would erase the
472
+ * glyph entirely — an `<img>` would still print. */
473
+ @media print {
474
+ .grove-icon-mask {
475
+ -webkit-print-color-adjust: exact;
476
+ print-color-adjust: exact;
477
+ }
478
+ }
479
+
480
+ /* Forced-colors mode overrides `background-color`; pin it to the
481
+ * system text color so the mark stays visible. */
482
+ @media (forced-colors: active) {
483
+ .grove-icon-mask {
484
+ background-color: CanvasText;
485
+ }
486
+ }
487
+
427
488
  .grove-prose {
428
489
  font-size: 1.0625rem; /* 17px */
429
490
  line-height: 1.75;