diff --git a/web/src/lib/components/repo/RepoTabs.svelte b/web/src/lib/components/repo/RepoTabs.svelte index 0e927d0b..4483be61 100644 --- a/web/src/lib/components/repo/RepoTabs.svelte +++ b/web/src/lib/components/repo/RepoTabs.svelte @@ -3,6 +3,7 @@ import CircleDot from "$icon/circle-dot"; import GitPullRequest from "$icon/git-pull-request"; import Layers2 from "$icon/layers-2"; + import Settings from "$icon/settings"; import Tabs, { type TabDef } from "$lib/components/ui/Tabs.svelte"; import type { RepoCounts, RepoInfo } from "./types"; @@ -32,7 +33,10 @@ count: counts.pulls, href: `${base}/pulls` }, - { id: "pipelines", label: "Pipelines", icon: Layers2, href: `${base}/pipelines` } + { id: "pipelines", label: "Pipelines", icon: Layers2, href: `${base}/pipelines` }, + // todo: only show this to collaborators — needs the viewer's push access on + // RepoInfo, which the layout load doesn't resolve yet + { id: "settings", label: "Settings", icon: Settings, href: `${base}/settings` } ]); diff --git a/web/src/lib/components/settings/DrillDown.svelte b/web/src/lib/components/settings/DrillDown.svelte index f1ba0981..35694911 100644 --- a/web/src/lib/components/settings/DrillDown.svelte +++ b/web/src/lib/components/settings/DrillDown.svelte @@ -8,11 +8,16 @@ backLabel: string; backHref: string; title: string; - description?: string; + /** pass an array to get one line each */ + description?: string | string[]; children: Snippet; } let { backLabel, backHref, title, description, children }: Props = $props(); + + const lines = $derived( + description === undefined ? [] : Array.isArray(description) ? description : [description] + ); +
+
+ {#if title} + + {title} + + {/if} + {#each lines as line (line)} +

{line}

+ {/each} +
+ {#if action} +
{@render action()}
+ {/if} +
+ {/if} + {#if separator} +
+ {/if} + {#if children} + {@render children()} + {/if} + diff --git a/web/src/lib/components/settings/SettingsEmpty.svelte b/web/src/lib/components/settings/SettingsEmpty.svelte new file mode 100644 index 00000000..910433b7 --- /dev/null +++ b/web/src/lib/components/settings/SettingsEmpty.svelte @@ -0,0 +1,15 @@ + + + +
+ {message} +
diff --git a/web/src/lib/components/settings/SettingsList.svelte b/web/src/lib/components/settings/SettingsList.svelte index 270c02d9..6f72428e 100644 --- a/web/src/lib/components/settings/SettingsList.svelte +++ b/web/src/lib/components/settings/SettingsList.svelte @@ -6,10 +6,12 @@ variants: { // Figma spaces rows with a gap and drops the hairline in the middle of it, // so the divider is split evenly across the two rows it separates rather - // than sitting flush against one of them. + // than sitting flush against one of them. Named in Tailwind spacing units + // because the designs use all three of 8 / 12 / 16px. gap: { - md: "[&>*+*]:mt-2 [&>*+*]:border-t [&>*+*]:border-border-default [&>*+*]:pt-2", - sm: "[&>*+*]:mt-1 [&>*+*]:border-t [&>*+*]:border-border-default [&>*+*]:pt-1" + "2": "[&>*+*]:mt-1 [&>*+*]:border-t [&>*+*]:border-border-default [&>*+*]:pt-1", + "3": "[&>*+*]:mt-1.5 [&>*+*]:border-t [&>*+*]:border-border-default [&>*+*]:pt-1.5", + "4": "[&>*+*]:mt-2 [&>*+*]:border-t [&>*+*]:border-border-default [&>*+*]:pt-2" }, padding: { default: "px-4 py-3", @@ -17,7 +19,7 @@ } }, defaultVariants: { - gap: "md", + gap: "4", padding: "default" } }); @@ -35,7 +37,7 @@ children: Snippet; } - let { gap = "md", padding = "default", class: className, children }: Props = $props(); + let { gap = "4", padding = "default", class: className, children }: Props = $props(); const classes = $derived(settingsList({ gap, padding, class: className })); diff --git a/web/src/lib/components/settings/SettingsRow.svelte b/web/src/lib/components/settings/SettingsRow.svelte index ee2e2b50..74bb6d9a 100644 --- a/web/src/lib/components/settings/SettingsRow.svelte +++ b/web/src/lib/components/settings/SettingsRow.svelte @@ -5,28 +5,52 @@ interface Props { /** plain-text label — omit and render whatever you need through `label` */ title?: string; + /** muted explanation under the title; pass an array to get one line each */ + description?: string | string[]; icon?: Component; /** replaces `title`/`icon` when the leading side is more than a label */ label?: Snippet; /** trailing controls: buttons, toggles, code chips */ children?: Snippet; + /** top-align the control instead of centring it, for tall right-hand content */ + align?: "center" | "start"; class?: string; } - let { title, icon, label, children, class: className }: Props = $props(); + let { + title, + description, + icon, + label, + children, + align = "center", + class: className + }: Props = $props(); const Glyph = $derived(icon); + const lines = $derived( + description === undefined ? [] : Array.isArray(description) ? description : [description] + ); -
+
{#if label} {@render label()} {:else} - - {#if Glyph} -