Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202<script module lang="ts"> // shared with the table header so the columns line up export const repoTableGrid = "grid grid-cols-[auto_minmax(0,1fr)_2rem] items-center gap-x-3 px-3 md:grid-cols-[auto_minmax(8rem,1fr)_4rem_7rem_minmax(0,1.5fr)_minmax(0,10rem)_2rem]";</script>
<script lang="ts"> import Check from "$icon/check"; import Lock from "$icon/lock"; import Pencil from "$icon/pencil"; import Star from "$icon/star"; import { LANGUAGE_COLORS, LANGUAGE_COLOR_FALLBACK } from "#lib/components/repo/languageColors.ts"; import Button from "#lib/components/ui/Button.svelte"; import Checkbox from "#lib/components/ui/Checkbox.svelte"; import Input from "#lib/components/ui/Input.svelte"; import Select from "#lib/components/ui/Select.svelte"; import Tag from "#lib/components/ui/Tag.svelte"; import type { GitHubRepo } from "#lib/github.ts"; import { formatCount } from "#lib/format.ts";
interface Edit { name: string; description: string; knot: string; }
interface Props { repo: GitHubRepo; name: string; description: string; knot: string; knots: readonly string[]; selected: boolean; editing: boolean; disabled?: boolean; onToggle: () => void; onEdit: () => void; onSave: (edit: Edit) => void; language?: string; stars?: number; }
let { repo, name, description, knot, knots, selected, editing, disabled = false, onToggle, onEdit, onSave, language, stars }: Props = $props();
// edits stay local until the tick confirms them let edit = $state<Edit>({ name: "", description: "", knot: "" }); $effect(() => { if (editing) edit = { name, description, knot }; });
const confirm = () => onSave({ ...edit, name: edit.name.trim() || name });
let editButton = $state<HTMLAnchorElement | HTMLButtonElement>();
const onkeydown = (event: KeyboardEvent) => { if (event.key === "Enter") { event.preventDefault(); confirm(); editButton?.focus(); } else if (event.key === "Escape") { onEdit(); editButton?.focus(); } };
// the checkbox and pencil handle their own clicks const onRowClick = (event: MouseEvent) => { if (editing || disabled) return; if ((event.target as Element).closest("button, label, input, a")) return; onToggle(); };
const knotOptions = $derived(knots.map((value) => ({ value }))); const languageColor = $derived( language ? (LANGUAGE_COLORS[language] ?? LANGUAGE_COLOR_FALLBACK) : null );</script>
<!-- the checkbox carries the keyboard path, the row click is a pointer shortcut --><!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions --><div class="{repoTableGrid} min-h-11 gap-y-xs border-b border-border-default py-1.5 typography-paragraph-regular last:border-b-0 {editing ? '' : disabled ? 'cursor-not-allowed text-foreground-disabled' : 'cursor-pointer hover:bg-background-subtle'}" onclick={onRowClick}> <Checkbox data-testid="repoRowCheckbox" data-repo={repo.fullName} class="hit-area-3" checked={selected} onchange={onToggle} {disabled} aria-label={`Select ${repo.fullName}`} /> {#if editing} <Input value={edit.name} maxlength={100} aria-label="Name" oninput={(event) => (edit.name = event.currentTarget.value)} {onkeydown} /> {:else} <span class="flex min-w-0 items-center gap-xs" title={repo.fullName}> <span class="truncate">{name}</span> {#if repo.private} <Tag size="small" icon={Lock}>Private</Tag> {/if} </span> {/if} <span class="hidden items-center gap-1 text-foreground-muted md:flex"> {#if stars !== undefined} <Star class="size-3.5 shrink-0" aria-hidden="true" /> {formatCount(stars)} {/if} </span> <span class="hidden min-w-0 items-center gap-1.5 text-foreground-muted md:flex"> {#if language} <span aria-hidden="true" class="size-2 shrink-0 rounded-full" style="background: {languageColor}" ></span> <span class="truncate" title={language}>{language}</span> {/if} </span> {#if editing} <Input class="hidden md:flex" value={edit.description} maxlength={140} placeholder="Description" aria-label="Description" oninput={(event) => (edit.description = event.currentTarget.value)} {onkeydown} /> {:else} <span class="hidden truncate text-foreground-muted md:block" title={description}> {description} </span> {/if} {#if editing} <Select class="hidden md:block" options={knotOptions} value={edit.knot} label="Destination knot" onSelect={(value) => (edit.knot = value)} /> {:else} <span class="hidden truncate text-foreground-muted md:block" title={knot}>{knot}</span> {/if} <Button variant="ghost" square icon={editing ? Check : Pencil} disabled={disabled && !editing} aria-label={editing ? `Save ${repo.fullName}` : `Edit ${repo.fullName}`} bind:ref={editButton} onclick={editing ? confirm : onEdit} /> {#if editing} <!-- below md the description and knot columns are hidden, so edit them underneath --> <div class="col-start-2 col-end-3 flex flex-col gap-xs md:hidden"> <Input value={edit.description} maxlength={140} placeholder="Description" aria-label="Description" oninput={(event) => (edit.description = event.currentTarget.value)} {onkeydown} /> <Select options={knotOptions} value={edit.knot} label="Destination knot" onSelect={(value) => (edit.knot = value)} /> </div> {/if}</div>