<script lang="ts">
import InlineMarkdown from "#lib/components/ui/InlineMarkdown.svelte";
import { resolve } from "$app/paths";
import { getAuth } from "#lib/auth.svelte.ts";
import BookMarked from "$icon/book-marked";
import Star from "$icon/star";
import GitFork from "$icon/git-fork";
import CircleDot from "$icon/circle-dot";
import GitPullRequest from "$icon/git-pull-request";
import ColorBall from "#lib/components/ui/ColorBall.svelte";
import StarButton from "#lib/components/repo/StarButton.svelte";
import {
LANGUAGE_COLORS,
LANGUAGE_COLOR_FALLBACK
} from "#lib/components/repo/languageColors.ts";
import type { RepoCardData, StarChange } from "#lib/components/profile/types.ts";
import { formatCount } from "#lib/format.ts";
import { createOptimisticCount } from "#lib/optimistic.svelte.ts";
let {
repo,
starButton = true,
showOwner = true,
uniformHeight = false,
layout = "card",
links = true,
onStar
}: {
repo: RepoCardData;
starButton?: boolean;
showOwner?: boolean;
uniformHeight?: boolean;
layout?: "card" | "row";
links?: boolean;
onStar?: (change: StarChange) => void;
} = $props();
const auth = getAuth();
const showStar = $derived(starButton && Boolean(auth.currentDid));
const starCount = createOptimisticCount({
key: () => repo.repoDid,
loaded: () => repo.stars
});
const stats = $derived(
[
{ value: repo.stars === undefined ? undefined : starCount.value, Icon: Star },
{ value: repo.forks, Icon: GitFork },
{ value: repo.issues, Icon: CircleDot },
{ value: repo.pulls, Icon: GitPullRequest }
].filter((s) => s.value !== undefined && s.value > 0)
);
</script>
<div
class={layout === "row"
? "flex flex-col gap-2xs sm:flex-row sm:items-center sm:gap-md"
: "flex h-full flex-1 flex-col gap-2xs"}
>
<div
class={[
"flex items-start justify-between font-medium text-foreground-default",
layout === "row" && "sm:contents"
]}
>
<div
class={[
"mr-xs flex min-w-0 flex-1 items-center",
layout === "card" && "mt-2xs",
layout === "row" && "self-center"
]}
data-vt="move"
>
<BookMarked
class="mr-1.5 size-3.5 shrink-0 text-foreground-subtle"
aria-hidden="true"
/>
{#if links}
<a
href={resolve(`/${repo.ownerHandle}/${repo.name}` as "/")}
class="min-w-0 truncate text-foreground-default no-underline hover:underline"
>
{showOwner ? `${repo.ownerHandle}/${repo.name}` : repo.name}
</a>
{:else}
<span class="min-w-0 truncate">
{showOwner ? `${repo.ownerHandle}/${repo.name}` : repo.name}
</span>
{/if}
</div>
{#if showStar}
<div class={["ml-md shrink-0", layout === "row" && "sm:order-last"]} data-vt="move">
<StarButton
subject={{
kind: "repo",
did: repo.repoDid,
ownerHandle: repo.ownerHandle,
name: repo.name
}}
initialCount={repo.stars}
initialRkey={repo.viewerStarRkey}
count="never"
onAdjust={(delta) => starCount.adjust(delta)}
onCommit={onStar}
/>
</div>
{/if}
</div>
<!-- in a grid the description and footer keep their space even when empty, so a card
with nothing to show doesn't sit shorter than the ones around it. the right gutter
narrows from 6rem at a 24rem card to none at 18rem, so narrow ones like the topbar
search keep their width -->
{#if layout === "card" && (repo.description || uniformHeight)}
<p
data-vt="fade"
class={[
"line-clamp-2 pr-[clamp(0rem,100%-18rem,6rem)] typography-paragraph-small text-foreground-muted",
uniformHeight && "min-h-[2lh]"
]}
>
{#if repo.description}<InlineMarkdown text={repo.description} {links} />{/if}
</p>
{/if}
{#if repo.language || stats.length > 0 || (layout === "card" && uniformHeight)}
<div
class={[
"flex flex-wrap items-center gap-md typography-monospace-small text-foreground-muted",
layout === "card" && "mt-auto pt-xs",
layout === "card" && uniformHeight && "box-content min-h-lh"
]}
data-vt="move"
>
{#if repo.language}
<span class="flex items-center gap-xs">
<ColorBall
color={LANGUAGE_COLORS[repo.language] ?? LANGUAGE_COLOR_FALLBACK}
class="size-2.5"
/>
<span>{repo.language}</span>
</span>
{/if}
{#each stats as stat (stat.Icon)}
<span class="flex items-center gap-2xs">
<stat.Icon class="size-3.5 shrink-0" aria-hidden="true" />
<span>{formatCount(stat.value)}</span>
</span>
{/each}
</div>
{/if}
</div>