@grove-dev/astro 0.7.0 → 0.8.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.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +22 -33
- package/dist/index.js.map +1 -1
- package/dist/lib/display.d.ts +1 -1
- package/dist/lib/display.js +1 -1
- package/dist/lib/format.d.ts +1 -1
- package/dist/lib/format.js +1 -1
- package/dist/lib/icon-kinds.d.ts +1 -1
- package/dist/lib/icon-kinds.js +43 -43
- package/dist/lib/icon-registry.d.ts +2 -2
- package/dist/lib/icon-registry.js +23 -23
- package/dist/lib/index.d.ts +9 -9
- package/dist/lib/index.d.ts.map +1 -1
- package/dist/lib/index.js +9 -9
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/lenses.d.ts +1 -1
- package/dist/lib/lenses.js +1 -1
- package/dist/lib/packaged-icons.d.ts +1 -1
- package/dist/lib/packaged-icons.js +4 -4
- package/dist/lib/repo.d.ts +1 -1
- package/dist/lib/repo.js +1 -1
- package/dist/lib/search.d.ts +1 -1
- package/dist/lib/search.js +1 -1
- package/dist/lib/taxonomy-counts.d.ts +1 -1
- package/dist/lib/taxonomy-counts.js +1 -1
- package/package.json +7 -17
- package/src/index.ts +23 -38
- package/src/lib/display.ts +1 -1
- package/src/lib/format.ts +1 -1
- package/src/lib/icon-kinds.ts +44 -44
- package/src/lib/icon-registry.ts +25 -25
- package/src/lib/index.ts +9 -9
- package/src/lib/lenses.ts +1 -1
- package/src/lib/packaged-icons.ts +8 -8
- package/src/lib/repo.ts +1 -1
- package/src/lib/search.ts +1 -1
- package/src/lib/taxonomy-counts.ts +1 -1
- package/src/server/collections.ts +53 -42
- package/src/server/contrast.ts +14 -9
- package/src/server/directory.ts +22 -17
- package/src/server/index.ts +3 -2
- package/src/server/models.ts +404 -213
- package/src/server/seo.ts +34 -31
- package/src/styles.css +235 -91
- package/dist/layouts/index.d.ts +0 -25
- package/dist/layouts/index.d.ts.map +0 -1
- package/dist/layouts/index.js +0 -25
- package/dist/layouts/index.js.map +0 -1
- package/dist/ui/button.d.ts +0 -53
- package/dist/ui/button.d.ts.map +0 -1
- package/dist/ui/button.js +0 -90
- package/dist/ui/button.js.map +0 -1
- package/src/components/CardGrid.astro +0 -41
- package/src/components/CardIcon.astro +0 -67
- package/src/components/CategoryGrid.astro +0 -114
- package/src/components/CollectionCard.astro +0 -56
- package/src/components/CollectionIndex.astro +0 -102
- package/src/components/CollectionPage.astro +0 -128
- package/src/components/CollectionRow.astro +0 -81
- package/src/components/CollectionTeaser.astro +0 -47
- package/src/components/ContributorsGrid.astro +0 -143
- package/src/components/DirectoryIndexClient.astro +0 -510
- package/src/components/EditorialSummary.astro +0 -58
- package/src/components/FilterGroupMenu.astro +0 -69
- package/src/components/FilterOptions.astro +0 -49
- package/src/components/FinalCta.astro +0 -43
- package/src/components/Hero.astro +0 -243
- package/src/components/Icon.astro +0 -191
- package/src/components/IndexRow.astro +0 -37
- package/src/components/LanguageBreakdown.astro +0 -67
- package/src/components/MarkdownBody.astro +0 -37
- package/src/components/OriginalCollection.astro +0 -117
- package/src/components/Pagination.astro +0 -50
- package/src/components/PoweredBy.astro +0 -60
- package/src/components/ProjectCard.astro +0 -321
- package/src/components/RecordHeader.astro +0 -221
- package/src/components/RecordSection.astro +0 -128
- package/src/components/RecordSidebar.astro +0 -387
- package/src/components/RefinePanel.astro +0 -168
- package/src/components/SmartLensTabs.astro +0 -81
- package/src/components/StackGrid.astro +0 -151
- package/src/components/StackPlatformChips.astro +0 -73
- package/src/components/SubmissionClient.astro +0 -254
- package/src/components/TableOfContents.astro +0 -140
- package/src/components/WhyThisExists.astro +0 -116
- package/src/layouts/BaseLayout.astro +0 -415
- package/src/layouts/Container.astro +0 -18
- package/src/layouts/Footer.astro +0 -231
- package/src/layouts/Header.astro +0 -214
- package/src/layouts/SectionHeader.astro +0 -43
- package/src/layouts/Seo.astro +0 -171
- package/src/layouts/ThemeToggle.astro +0 -118
- package/src/layouts/astro-shim.d.ts +0 -29
- package/src/layouts/index.ts +0 -24
- package/src/server/collections.test.ts +0 -212
- package/src/server/contrast.test.ts +0 -82
- package/src/server/directory.test.ts +0 -177
- package/src/server/github-repo.test.ts +0 -88
- package/src/server/models-home.test.ts +0 -101
- package/src/server/models.test.ts +0 -135
- package/src/server/seo.test.ts +0 -123
- package/src/ui/Badge.astro +0 -38
- package/src/ui/Button.astro +0 -42
- package/src/ui/EmptyState.astro +0 -38
- package/src/ui/FilterDrawer.astro +0 -124
- package/src/ui/PageHeader.astro +0 -39
- package/src/ui/SearchField.astro +0 -106
- package/src/ui/button.test.ts +0 -54
- package/src/ui/button.ts +0 -107
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* CollectionPage — renders a single curated/generated collection.
|
|
4
|
-
*
|
|
5
|
-
* Consumes a `CollectionPageModel` produced by `getCollectionPageModel`.
|
|
6
|
-
* Renders the header via `SectionHeader` (framework consistency),
|
|
7
|
-
* the matched entries via `CollectionRow`, and a related-collections
|
|
8
|
-
* section.
|
|
9
|
-
*
|
|
10
|
-
* Generic: no blueprint-specific copy. Consumers can override
|
|
11
|
-
* `eyebrow`, `title`, `description`; the `<slot name="intro">` is
|
|
12
|
-
* the natural place for editorial intros (markdown rendered with
|
|
13
|
-
* the consumer's content pipeline). `emptyLabel` matches
|
|
14
|
-
* `RecordSection`'s API for empty-state copy.
|
|
15
|
-
*/
|
|
16
|
-
import { formatCount } from "@grove-dev/core";
|
|
17
|
-
import CardGrid from "./CardGrid.astro";
|
|
18
|
-
import CollectionRow from "./CollectionRow.astro";
|
|
19
|
-
import Container from "../layouts/Container.astro";
|
|
20
|
-
import SectionHeader from "../layouts/SectionHeader.astro";
|
|
21
|
-
import EmptyState from "../ui/EmptyState.astro";
|
|
22
|
-
import type { CollectionPageModel } from "../server/collections.js";
|
|
23
|
-
|
|
24
|
-
interface Props {
|
|
25
|
-
model: CollectionPageModel;
|
|
26
|
-
/** Override the eyebrow text (default: derived from collection kind). */
|
|
27
|
-
eyebrow?: string;
|
|
28
|
-
/** Override the title (default: collection.title). */
|
|
29
|
-
title?: string;
|
|
30
|
-
/** Override the description (default: collection.description). */
|
|
31
|
-
description?: string;
|
|
32
|
-
/** Default copy when the collection has no matching entries. */
|
|
33
|
-
emptyLabel?: string;
|
|
34
|
-
/** Section id (deep link + stable key). */
|
|
35
|
-
id?: string;
|
|
36
|
-
/**
|
|
37
|
-
* Heading level for the collection title. This component IS the
|
|
38
|
-
* page on collection detail routes, so it defaults to 1.
|
|
39
|
-
*/
|
|
40
|
-
headingLevel?: 1 | 2;
|
|
41
|
-
/** Noun pair for the entry count (blueprint labels). */
|
|
42
|
-
countNoun?: { singular: string; plural: string };
|
|
43
|
-
class?: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const {
|
|
47
|
-
model,
|
|
48
|
-
eyebrow,
|
|
49
|
-
title,
|
|
50
|
-
description,
|
|
51
|
-
emptyLabel = "This collection is currently empty.",
|
|
52
|
-
id,
|
|
53
|
-
headingLevel = 1,
|
|
54
|
-
countNoun = { singular: "item", plural: "items" },
|
|
55
|
-
class: className = "",
|
|
56
|
-
} = Astro.props;
|
|
57
|
-
|
|
58
|
-
const finalTitle = title ?? model.collection.title;
|
|
59
|
-
const finalDescription = description ?? model.collection.description;
|
|
60
|
-
const finalEyebrow = eyebrow ?? (model.collection.kind === "curated" ? "Curated collection" : "Generated collection");
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
<section id={id} class={`py-12 sm:py-16 ${className}`}>
|
|
64
|
-
<Container>
|
|
65
|
-
<SectionHeader eyebrow={finalEyebrow} title={finalTitle} description={finalDescription} level={headingLevel} />
|
|
66
|
-
|
|
67
|
-
{/* Editorial context reads as a quiet callout, not metadata
|
|
68
|
-
shouting in uppercase. */}
|
|
69
|
-
{model.collection.selectionNote && (
|
|
70
|
-
<aside class="mb-6 max-w-[64ch] rounded-[var(--radius-lg)] border-l-2 border-border bg-surface-sunken px-4 py-3">
|
|
71
|
-
<p class="m-0 text-2xs font-medium uppercase tracking-wider text-muted-foreground">
|
|
72
|
-
Selection criteria
|
|
73
|
-
</p>
|
|
74
|
-
<p class="m-0 mt-1 text-[0.875rem] leading-relaxed text-foreground">
|
|
75
|
-
{model.collection.selectionNote}
|
|
76
|
-
</p>
|
|
77
|
-
</aside>
|
|
78
|
-
)}
|
|
79
|
-
|
|
80
|
-
<slot name="intro" />
|
|
81
|
-
|
|
82
|
-
{/* Bridges the heading outline: the page title is an h1 and the
|
|
83
|
-
entry cards render h3 titles — without this h2 Lighthouse
|
|
84
|
-
flags a heading-order skip. */}
|
|
85
|
-
<h2 class="sr-only">Entries</h2>
|
|
86
|
-
<p class="m-0 mb-4 text-2xs font-medium uppercase tracking-wider text-ink-500 dark:text-ink-400">
|
|
87
|
-
{formatCount(model.total, countNoun)}
|
|
88
|
-
</p>
|
|
89
|
-
|
|
90
|
-
{model.isEmpty ? (
|
|
91
|
-
<EmptyState
|
|
92
|
-
title={emptyLabel}
|
|
93
|
-
description="No records currently match this collection's query. The collection fills automatically as matching records are added."
|
|
94
|
-
/>
|
|
95
|
-
) : (
|
|
96
|
-
<CardGrid>
|
|
97
|
-
{model.entries.map((entry, i) => (
|
|
98
|
-
<li class="m-0 p-0">
|
|
99
|
-
<CollectionRow entry={entry} eager={i < 3} />
|
|
100
|
-
</li>
|
|
101
|
-
))}
|
|
102
|
-
</CardGrid>
|
|
103
|
-
)}
|
|
104
|
-
|
|
105
|
-
{model.related.length > 0 && (
|
|
106
|
-
<section class="mt-12 border-t border-ink-200 pt-8 dark:border-ink-800">
|
|
107
|
-
<SectionHeader
|
|
108
|
-
eyebrow="Related"
|
|
109
|
-
title="Related collections"
|
|
110
|
-
description="Other curated groupings of related items."
|
|
111
|
-
level={2}
|
|
112
|
-
/>
|
|
113
|
-
<ul class="m-0 grid list-none grid-cols-1 gap-2 p-0 sm:grid-cols-2 lg:grid-cols-4">
|
|
114
|
-
{model.related.map((r) => (
|
|
115
|
-
<li class="m-0 p-0">
|
|
116
|
-
<a
|
|
117
|
-
href={r.url}
|
|
118
|
-
class="group flex h-full flex-col gap-1 rounded-md border border-ink-200 bg-white px-3 py-2.5 no-underline transition-colors hover:border-ink-400 dark:border-ink-800 dark:bg-ink-950 dark:hover:border-ink-600"
|
|
119
|
-
>
|
|
120
|
-
<span class="text-sm font-medium text-ink-900 group-hover:underline dark:text-ink-100">{r.title}</span>
|
|
121
|
-
</a>
|
|
122
|
-
</li>
|
|
123
|
-
))}
|
|
124
|
-
</ul>
|
|
125
|
-
</section>
|
|
126
|
-
)}
|
|
127
|
-
</Container>
|
|
128
|
-
</section>
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* CollectionRow — collection-page adapter around the canonical
|
|
4
|
-
* `ProjectCard`.
|
|
5
|
-
*
|
|
6
|
-
* Consumes the lightweight `CollectionEntry` shape directly and maps
|
|
7
|
-
* it onto `ProjectCard`'s explicit props, so a collection page sitting
|
|
8
|
-
* next to the directory index reads as the same site. The historical
|
|
9
|
-
* public props are preserved:
|
|
10
|
-
*
|
|
11
|
-
* - `entry` — the collection entry (slug, title, description, …)
|
|
12
|
-
* - `href` — full detail URL; wins over `routeSlug`
|
|
13
|
-
* - `routeSlug` — blueprint slug (e.g. "projects"); details derived
|
|
14
|
-
* as `/${routeSlug}/${slug}` when `href` is not provided
|
|
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
|
|
20
|
-
* - `class` — extra class on the card root
|
|
21
|
-
* - `eager` — bypass lazy loading for above-the-fold avatars
|
|
22
|
-
*
|
|
23
|
-
* The repository is reached through the card's `owner/repo` line;
|
|
24
|
-
* there is no separate footer link button.
|
|
25
|
-
*
|
|
26
|
-
* Analytics: emits `data-event="collection_item_click"` /
|
|
27
|
-
* `data-event-source="collection_row"` on the card root, so sites wire
|
|
28
|
-
* one handler in BaseLayout.
|
|
29
|
-
*/
|
|
30
|
-
import type { CollectionEntry } from "@grove-dev/core";
|
|
31
|
-
import ProjectCard from "./ProjectCard.astro";
|
|
32
|
-
|
|
33
|
-
interface Props {
|
|
34
|
-
entry: CollectionEntry;
|
|
35
|
-
/** Full detail-page URL. Wins over `routeSlug`. */
|
|
36
|
-
href?: string;
|
|
37
|
-
/** Blueprint route slug; detail URL = `/${routeSlug}/${slug}`. */
|
|
38
|
-
routeSlug?: string;
|
|
39
|
-
/** @deprecated Ignored — the card renders one fixed layout. */
|
|
40
|
-
showChips?: boolean;
|
|
41
|
-
/** Pass stars / last-updated through to the card. Default true. */
|
|
42
|
-
showMeta?: boolean;
|
|
43
|
-
/** @deprecated Ignored — see `showChips`. */
|
|
44
|
-
showLabel?: boolean;
|
|
45
|
-
eager?: boolean;
|
|
46
|
-
class?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const {
|
|
50
|
-
entry,
|
|
51
|
-
href: hrefProp,
|
|
52
|
-
routeSlug,
|
|
53
|
-
showMeta = true,
|
|
54
|
-
eager = false,
|
|
55
|
-
class: className = "",
|
|
56
|
-
} = Astro.props;
|
|
57
|
-
|
|
58
|
-
const slug = entry.slug;
|
|
59
|
-
const base = (routeSlug ?? "").replace(/^\/+|\/+$/g, "");
|
|
60
|
-
const detailHref =
|
|
61
|
-
typeof hrefProp === "string" && hrefProp.length > 0
|
|
62
|
-
? hrefProp
|
|
63
|
-
: entry.url || (base ? `/${base}/${slug}` : `/${slug}`);
|
|
64
|
-
|
|
65
|
-
const repoHref = entry.repoHref;
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
<ProjectCard
|
|
69
|
-
href={detailHref}
|
|
70
|
-
slug={slug}
|
|
71
|
-
name={entry.title}
|
|
72
|
-
description={entry.description ?? ""}
|
|
73
|
-
repoUrl={repoHref}
|
|
74
|
-
stars={showMeta ? entry.stars : undefined}
|
|
75
|
-
pushedAt={showMeta ? entry.pushedAt : undefined}
|
|
76
|
-
stack={entry.stack}
|
|
77
|
-
eager={eager}
|
|
78
|
-
event="collection_item_click"
|
|
79
|
-
eventSource="collection_row"
|
|
80
|
-
class={className}
|
|
81
|
-
/>
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* CollectionTeaser — homepage-friendly subset of `CollectionIndex`.
|
|
4
|
-
*
|
|
5
|
-
* Thin wrapper that defaults `limit` to 3 and sets homepage-friendly
|
|
6
|
-
* "View all collections" link + copy. For custom card counts or
|
|
7
|
-
* different copy, use `CollectionIndex` directly with explicit
|
|
8
|
-
* `limit` and `viewAllHref` props.
|
|
9
|
-
*/
|
|
10
|
-
import CollectionIndex from "./CollectionIndex.astro";
|
|
11
|
-
import type { CollectionIndexModel } from "../server/collections.js";
|
|
12
|
-
|
|
13
|
-
interface Props {
|
|
14
|
-
model: CollectionIndexModel;
|
|
15
|
-
title?: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
viewAllHref?: string;
|
|
18
|
-
viewAllLabel?: string;
|
|
19
|
-
/** Cap the number of cards rendered. Default: 3. */
|
|
20
|
-
limit?: number;
|
|
21
|
-
/** Noun pair for the per-card entry counts (blueprint labels). */
|
|
22
|
-
countNoun?: { singular: string; plural: string };
|
|
23
|
-
class?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const {
|
|
27
|
-
model,
|
|
28
|
-
title = "Featured collections",
|
|
29
|
-
description = "Hand-picked and auto-generated groupings of related items.",
|
|
30
|
-
viewAllHref = "/collections/",
|
|
31
|
-
viewAllLabel = "View all collections",
|
|
32
|
-
limit = 3,
|
|
33
|
-
countNoun,
|
|
34
|
-
class: className,
|
|
35
|
-
} = Astro.props;
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
<CollectionIndex
|
|
39
|
-
model={model}
|
|
40
|
-
title={title}
|
|
41
|
-
description={description}
|
|
42
|
-
viewAllHref={viewAllHref}
|
|
43
|
-
viewAllLabel={viewAllLabel}
|
|
44
|
-
limit={limit}
|
|
45
|
-
countNoun={countNoun}
|
|
46
|
-
class={className}
|
|
47
|
-
/>
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
/**
|
|
3
|
-
* ContributorsGrid — avatar grid for the home page.
|
|
4
|
-
*
|
|
5
|
-
* Renders a dense grid of contributor avatars with optional name and
|
|
6
|
-
* contribution count. Each tile links to the contributor's profile.
|
|
7
|
-
* No blueprint-specific terms — works for any "people" feed.
|
|
8
|
-
*
|
|
9
|
-
* Empty tiles are skipped. When the list is empty the entire section
|
|
10
|
-
* hides its body but keeps the header (so a "no contributors yet"
|
|
11
|
-
* state can be wired by a consumer via a slot).
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
interface Contributor {
|
|
15
|
-
username: string;
|
|
16
|
-
name?: string;
|
|
17
|
-
avatarUrl?: string;
|
|
18
|
-
profileUrl?: string;
|
|
19
|
-
contributions?: number;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface Props {
|
|
23
|
-
contributors: Contributor[];
|
|
24
|
-
/** Total count to display in the header (e.g. "1,234 contributors"). */
|
|
25
|
-
total?: number;
|
|
26
|
-
/** Optional view-all href rendered top-right. */
|
|
27
|
-
viewAllHref?: string;
|
|
28
|
-
viewAllLabel?: string;
|
|
29
|
-
title?: string;
|
|
30
|
-
description?: string;
|
|
31
|
-
/** Render per-user contribution counts (config: contributors.showContributionCount). */
|
|
32
|
-
showContributionCount?: boolean;
|
|
33
|
-
class?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const {
|
|
37
|
-
contributors,
|
|
38
|
-
total,
|
|
39
|
-
viewAllHref,
|
|
40
|
-
viewAllLabel = "View all",
|
|
41
|
-
title = "Built by the community",
|
|
42
|
-
description = "The people improving this directory through code, curation, and review.",
|
|
43
|
-
showContributionCount = true,
|
|
44
|
-
class: className = "",
|
|
45
|
-
} = Astro.props;
|
|
46
|
-
|
|
47
|
-
function fmt(n: number | undefined): string | null {
|
|
48
|
-
if (typeof n !== "number" || !Number.isFinite(n)) return null;
|
|
49
|
-
if (n >= 1000) {
|
|
50
|
-
const k = n / 1000;
|
|
51
|
-
return (k >= 10 ? Math.round(k).toString() : k.toFixed(1)) + "k";
|
|
52
|
-
}
|
|
53
|
-
return n.toString();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const visible = contributors
|
|
57
|
-
.filter((c) => c.username)
|
|
58
|
-
.slice(0, 100);
|
|
59
|
-
---
|
|
60
|
-
|
|
61
|
-
<section
|
|
62
|
-
class:list={["py-12 sm:py-16", className]}
|
|
63
|
-
aria-labelledby="grove-contributors-heading"
|
|
64
|
-
>
|
|
65
|
-
<div class="mx-auto w-full max-w-container px-5 sm:px-7">
|
|
66
|
-
<header class="mb-6 flex flex-wrap items-end justify-between gap-3">
|
|
67
|
-
<div>
|
|
68
|
-
<h2
|
|
69
|
-
id="grove-contributors-heading"
|
|
70
|
-
class="m-0 text-[1.5rem] sm:text-[1.625rem] font-semibold tracking-tight text-ink-900 dark:text-ink-100"
|
|
71
|
-
>
|
|
72
|
-
{title}
|
|
73
|
-
{typeof total === "number" && (
|
|
74
|
-
<span class="ml-2 font-mono text-[0.9375rem] font-normal text-ink-500 dark:text-ink-400">
|
|
75
|
-
({fmt(total) ?? total})
|
|
76
|
-
</span>
|
|
77
|
-
)}
|
|
78
|
-
</h2>
|
|
79
|
-
{description && (
|
|
80
|
-
<p class="m-0 mt-1 max-w-[60ch] text-[0.9375rem] text-ink-500 dark:text-ink-400">
|
|
81
|
-
{description}
|
|
82
|
-
</p>
|
|
83
|
-
)}
|
|
84
|
-
</div>
|
|
85
|
-
{viewAllHref && (
|
|
86
|
-
<a
|
|
87
|
-
href={viewAllHref}
|
|
88
|
-
class="text-sm font-medium text-ink-500 hover:text-ink-900 dark:text-ink-400 dark:hover:text-ink-100"
|
|
89
|
-
>
|
|
90
|
-
{viewAllLabel} →
|
|
91
|
-
</a>
|
|
92
|
-
)}
|
|
93
|
-
</header>
|
|
94
|
-
|
|
95
|
-
{visible.length === 0 ? (
|
|
96
|
-
<p class="m-0 text-sm text-ink-500 dark:text-ink-400">
|
|
97
|
-
Community profiles appear after the first repository metadata sync.
|
|
98
|
-
</p>
|
|
99
|
-
) : (
|
|
100
|
-
<ul
|
|
101
|
-
class="m-0 grid list-none grid-cols-[repeat(auto-fill,minmax(72px,1fr))] gap-x-3 gap-y-5 p-0"
|
|
102
|
-
aria-label="Contributors"
|
|
103
|
-
>
|
|
104
|
-
{visible.map((c) => {
|
|
105
|
-
const href = c.profileUrl ?? `https://github.com/${c.username}`;
|
|
106
|
-
const avatar =
|
|
107
|
-
c.avatarUrl ??
|
|
108
|
-
`https://avatars.githubusercontent.com/${c.username}?v=4&s=80`;
|
|
109
|
-
return (
|
|
110
|
-
<li class="m-0 p-0">
|
|
111
|
-
<a
|
|
112
|
-
href={href}
|
|
113
|
-
target="_blank"
|
|
114
|
-
rel="noopener noreferrer"
|
|
115
|
-
class="group flex flex-col items-center gap-1.5 p-2 text-center no-underline"
|
|
116
|
-
data-event="contributor_click"
|
|
117
|
-
data-event-source="contributors_grid"
|
|
118
|
-
>
|
|
119
|
-
<img
|
|
120
|
-
src={avatar}
|
|
121
|
-
alt={c.name ?? c.username}
|
|
122
|
-
width="48"
|
|
123
|
-
height="48"
|
|
124
|
-
loading="lazy"
|
|
125
|
-
decoding="async"
|
|
126
|
-
class="h-12 w-12 rounded-full border border-ink-200 object-cover dark:border-ink-800"
|
|
127
|
-
/>
|
|
128
|
-
<span class="line-clamp-1 text-2xs font-medium text-ink-700 group-hover:text-ink-900 dark:text-ink-300 dark:group-hover:text-ink-100">
|
|
129
|
-
{c.name ?? c.username}
|
|
130
|
-
</span>
|
|
131
|
-
{showContributionCount && typeof c.contributions === "number" && (
|
|
132
|
-
<span class="font-mono text-2xs text-ink-400">
|
|
133
|
-
{fmt(c.contributions) ?? c.contributions}
|
|
134
|
-
</span>
|
|
135
|
-
)}
|
|
136
|
-
</a>
|
|
137
|
-
</li>
|
|
138
|
-
);
|
|
139
|
-
})}
|
|
140
|
-
</ul>
|
|
141
|
-
)}
|
|
142
|
-
</div>
|
|
143
|
-
</section>
|