This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

web/settings: add the mobile user and repository settings screens

below md the sidebar collapses into a native select and the panel goes
full-bleed, keeping only its top and bottom hairlines, matching the Mobile
section of the settings redesign.

below sm the rows drop their trailing controls onto a second line: list
entries keep theirs left-aligned under the meta, while SettingsRow and the
SettingsBlock header align theirs right, as the user and repository frames
respectively do. SettingsRow takes an opt-in `stack` since the design keeps
short label/toggle pairs on one line either way.

Signed-off-by: eti <eti@eti.tf>

author
eti
committer
dawn
date (Jul 31, 2026, 10:57 PM +0300) commit 7691cdc9 parent 5117262c change-id vlsskoqp
+295 -62
+6 -3
web/src/lib/components/settings/SettingsBlock.svelte
··· 30 30 31 31 <div class="flex w-full flex-col gap-4"> 32 32 {#if title || lines.length > 0 || action} 33 - <!-- the action sits on the last line of the description, not its middle --> 34 - <div class="flex w-full items-end justify-between gap-16"> 33 + <!-- the action sits on the last line of the description, not its middle — until 34 + the description needs the whole phone width, where it drops below instead --> 35 + <div class="flex w-full flex-col gap-2 sm:flex-row sm:items-end sm:justify-between sm:gap-16"> 35 36 <div class="flex min-w-0 flex-col"> 36 37 {#if title} 37 38 <span ··· 47 48 {/each} 48 49 </div> 49 50 {#if action} 50 - <div class="flex shrink-0 items-center gap-2">{@render action()}</div> 51 + <div class="flex shrink-0 items-center gap-2 self-end sm:self-auto"> 52 + {@render action()} 53 + </div> 51 54 {/if} 52 55 </div> 53 56 {/if}
+6 -2
web/src/lib/components/settings/SettingsEntry.svelte
··· 18 18 const Glyph = $derived(icon); 19 19 </script> 20 20 21 - <div class="flex min-h-8 w-full items-center justify-between gap-4"> 21 + <!-- The meta lines — fingerprints, URLs — need the full width of a phone, so the 22 + trailing buttons drop under them and stay left-aligned with the title. --> 23 + <div 24 + class="flex min-h-8 w-full flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-4" 25 + > 22 26 <div class="flex min-w-0 flex-col gap-1"> 23 27 <div class="flex items-center gap-2"> 24 28 {#if Glyph} ··· 34 38 {/if} 35 39 </div> 36 40 {#if actions} 37 - <div class="flex shrink-0 items-center gap-2">{@render actions()}</div> 41 + <div class="flex shrink-0 items-center gap-2 self-start sm:self-auto">{@render actions()}</div> 38 42 {/if} 39 43 </div>
+10
web/src/lib/components/settings/SettingsNav.stories.svelte
··· 45 45 {/snippet} 46 46 </Story> 47 47 48 + <!-- the sidebar is a md-and-up shape: narrow the browser under 768px and this story 49 + renders the select it collapses into instead --> 50 + <Story name="Collapsed"> 51 + {#snippet template(args)} 52 + <div class="flex w-[378px] items-stretch"> 53 + <SettingsNav {...args} active="keys" /> 54 + </div> 55 + {/snippet} 56 + </Story> 57 + 48 58 <Story name="No icons" args={{ items: items.map(({ id, label, href }) => ({ id, label, href })) }}> 49 59 {#snippet template(args)} 50 60 <div class="flex h-[568px] w-44 items-stretch">
+41 -1
web/src/lib/components/settings/SettingsNav.svelte
··· 11 11 </script> 12 12 13 13 <script lang="ts"> 14 + import { goto } from "$app/navigation"; 14 15 import { resolve } from "$app/paths"; 16 + import ChevronDown from "$icon/chevron-down"; 15 17 16 18 interface Props { 17 19 items: SettingsNavItem[]; ··· 21 23 22 24 let { items, active, label = "Settings" }: Props = $props(); 23 25 26 + const ActiveGlyph = $derived(items.find((i) => i.id === active)?.icon); 27 + 28 + const jump = (event: Event) => { 29 + const item = items.find((i) => i.id === (event.currentTarget as HTMLSelectElement).value); 30 + if (item) void goto(resolve(item.href as "/")); 31 + }; 32 + 24 33 // Figma DS "Navigation / Button" (1009:2662) has four states across Active × 25 34 // Hovered. Inactive/resting is the only one with no fill and no border, so the 26 35 // hover borders have to be added rather than recoloured — otherwise the item ··· 37 46 ].join(" "); 38 47 </script> 39 48 49 + <!-- Below md the panel is only as wide as the phone, so the sidebar collapses into 50 + a native picker rather than seven rows that push the page content off-screen. 51 + The hairline under it stands in for the sidebar's edge. --> 52 + <div class="flex w-full flex-col gap-4 md:hidden"> 53 + <div class="relative w-full"> 54 + {#if ActiveGlyph} 55 + <ActiveGlyph 56 + class="pointer-events-none absolute top-1/2 left-2 size-4 -translate-y-1/2 text-foreground-default" 57 + aria-hidden="true" 58 + /> 59 + {/if} 60 + <select 61 + aria-label={label} 62 + value={active} 63 + onchange={jump} 64 + class="h-8 w-full appearance-none rounded-sm border border-border-default bg-background-default pr-8 typography-paragraph-regular text-foreground-default outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-border-focus {ActiveGlyph 65 + ? 'pl-7' 66 + : 'pl-2'}" 67 + > 68 + {#each items as item (item.id)} 69 + <option value={item.id}>{item.label}</option> 70 + {/each} 71 + </select> 72 + <ChevronDown 73 + class="pointer-events-none absolute top-1/2 right-2 size-3.5 -translate-y-1/2 text-foreground-default" 74 + aria-hidden="true" 75 + /> 76 + </div> 77 + <hr class="w-full border-0 border-t border-border-default" /> 78 + </div> 79 + 40 80 <nav 41 - class="flex w-full shrink-0 flex-col gap-1 border-b border-border-default bg-background-navigation-frame p-2 md:w-44 md:self-stretch md:border-r md:border-b-0" 81 + class="hidden w-full shrink-0 flex-col gap-1 bg-background-navigation-frame p-2 md:flex md:w-44 md:self-stretch md:border-r md:border-border-default" 42 82 aria-label={label} 43 83 > 44 84 {#each items as item (item.id)}
+118
web/src/lib/components/settings/SettingsPanel.stories.svelte
··· 1 + <script module lang="ts"> 2 + import { defineMeta } from "@storybook/addon-svelte-csf"; 3 + import SettingsPanel from "./SettingsPanel.svelte"; 4 + import SettingsHeader from "./SettingsHeader.svelte"; 5 + import SettingsSection from "./SettingsSection.svelte"; 6 + import SettingsBlock from "./SettingsBlock.svelte"; 7 + import SettingsList from "./SettingsList.svelte"; 8 + import SettingsRow from "./SettingsRow.svelte"; 9 + import SettingsEntry from "./SettingsEntry.svelte"; 10 + import SettingsToolbar from "./SettingsToolbar.svelte"; 11 + import CodeChip from "./CodeChip.svelte"; 12 + import type { SettingsNavItem } from "./SettingsNav.svelte"; 13 + import Button from "$lib/components/ui/Button.svelte"; 14 + import Input from "$lib/components/ui/Input.svelte"; 15 + import Toggle from "$lib/components/ui/Toggle.svelte"; 16 + import User from "$icon/user-round"; 17 + import Key from "$icon/key"; 18 + import Mail from "$icon/mail"; 19 + import SlidersHorizontal from "$icon/sliders-horizontal"; 20 + import UsersRound from "$icon/users-round"; 21 + import Plus from "$icon/plus"; 22 + import Trash from "$icon/trash-2"; 23 + import UserRoundPlus from "$icon/user-round-plus"; 24 + import UserRoundMinus from "$icon/user-round-minus"; 25 + 26 + const userItems: SettingsNavItem[] = [ 27 + { id: "profile", label: "Profile", href: "/settings", icon: User }, 28 + { id: "keys", label: "Keys", href: "/settings/keys", icon: Key }, 29 + { id: "emails", label: "Emails", href: "/settings/emails", icon: Mail } 30 + ]; 31 + 32 + const repoItems: SettingsNavItem[] = [ 33 + { id: "general", label: "General", href: "/settings", icon: SlidersHorizontal }, 34 + { id: "access", label: "Access", href: "/settings/access", icon: UsersRound } 35 + ]; 36 + 37 + const { Story } = defineMeta({ 38 + title: "Settings/SettingsPanel", 39 + component: SettingsPanel, 40 + tags: ["autodocs"], 41 + parameters: { layout: "fullscreen" } 42 + }); 43 + </script> 44 + 45 + <!-- Narrow the browser under 768px to see the nav collapse into its select and the 46 + panel go full-bleed; under 640px the rows drop their controls onto a second line. --> 47 + <Story name="User settings"> 48 + <SettingsPanel items={userItems} active="keys"> 49 + <SettingsHeader 50 + title="Keys" 51 + description="SSH public keys added here will be broadcasted to knots that you are a member of." 52 + /> 53 + <div class="flex w-full flex-col gap-4"> 54 + <SettingsToolbar> 55 + {#snippet end()} 56 + <Button icon={Plus}>Add key</Button> 57 + {/snippet} 58 + </SettingsToolbar> 59 + <SettingsList> 60 + <SettingsEntry icon={Key} title="laptop"> 61 + {#snippet meta()} 62 + <span class="font-mono typography-monospace-regular break-all text-foreground-muted"> 63 + SHA256:68LweWe1t0oBuxOD8CpUyhjxGzBiylRDTNVD8WMVKfg= 64 + </span> 65 + {/snippet} 66 + {#snippet actions()} 67 + <Button variant="danger" icon={Trash}>Delete</Button> 68 + {/snippet} 69 + </SettingsEntry> 70 + </SettingsList> 71 + </div> 72 + <SettingsSection title="Account"> 73 + <SettingsRow title="Bio" stack> 74 + <Input placeholder="Write a bio" class="w-full sm:w-80" /> 75 + </SettingsRow> 76 + <SettingsRow title="Decentralized Identifier (DID)" stack> 77 + <CodeChip class="w-full break-all sm:w-auto">did:plc:rhcxsdhvxax7ezkq6zbunls2</CodeChip> 78 + </SettingsRow> 79 + <SettingsRow title="Hide mine"> 80 + <Toggle aria-label="Hide mine" /> 81 + </SettingsRow> 82 + </SettingsSection> 83 + </SettingsPanel> 84 + </Story> 85 + 86 + <Story name="Repository settings"> 87 + <SettingsPanel items={repoItems} active="access" label="Repository settings"> 88 + <SettingsHeader title="Access" /> 89 + <SettingsBlock 90 + title="Collaborators" 91 + description="Any user added as a collaborator will be able to push commits and tags to this repository." 92 + separator 93 + > 94 + {#snippet action()} 95 + <Button icon={UserRoundPlus}>Add collaborator</Button> 96 + {/snippet} 97 + <SettingsList gap="3"> 98 + <SettingsRow stack> 99 + {#snippet label()} 100 + <span class="flex min-w-0 items-center gap-2"> 101 + <span class="typography-paragraph-regular text-foreground-default">user.tld</span> 102 + <span class="typography-paragraph-regular text-foreground-muted">Owner</span> 103 + </span> 104 + {/snippet} 105 + </SettingsRow> 106 + <SettingsRow stack> 107 + {#snippet label()} 108 + <span class="flex min-w-0 items-center gap-2"> 109 + <span class="typography-paragraph-regular text-foreground-default">user2.tld</span> 110 + <span class="typography-paragraph-regular text-foreground-muted">Collaborator</span> 111 + </span> 112 + {/snippet} 113 + <Button icon={UserRoundMinus}>Remove collaborator</Button> 114 + </SettingsRow> 115 + </SettingsList> 116 + </SettingsBlock> 117 + </SettingsPanel> 118 + </Story>
+26
web/src/lib/components/settings/SettingsPanel.svelte
··· 1 + <script lang="ts"> 2 + import type { Snippet } from "svelte"; 3 + import SettingsNav, { type SettingsNavItem } from "./SettingsNav.svelte"; 4 + 5 + interface Props { 6 + items: SettingsNavItem[]; 7 + active: string; 8 + label?: string; 9 + children: Snippet; 10 + } 11 + 12 + let { items, active, label, children }: Props = $props(); 13 + </script> 14 + 15 + <!-- Two shapes for one screen: a bordered card with the nav down its left edge on 16 + desktop, and below md a full-bleed panel that only keeps its top and bottom 17 + hairlines, since the phone frame already supplies the side margins. --> 18 + <div 19 + class="flex w-full flex-col items-stretch gap-4 border-y border-border-default bg-background-default px-3 pt-3 pb-6 md:min-h-[600px] md:flex-row md:items-start md:overflow-hidden md:rounded-sm md:border md:p-0" 20 + > 21 + <SettingsNav {items} {active} {label} /> 22 + 23 + <div class="flex min-w-0 flex-1 flex-col gap-6 md:gap-8 md:px-4 md:py-6"> 24 + {@render children()} 25 + </div> 26 + </div>
+29 -4
web/src/lib/components/settings/SettingsRow.svelte
··· 14 14 children?: Snippet; 15 15 /** top-align the control instead of centring it, for tall right-hand content */ 16 16 align?: "center" | "start"; 17 + /** 18 + * drop the trailing controls onto their own line below sm. Set it wherever the 19 + * design does: rows carrying a 320px field, a long identifier, or a labelled 20 + * button next to a wide leading block. Short label/toggle pairs stay on one line. 21 + */ 22 + stack?: boolean; 17 23 class?: string; 18 24 } 19 25 ··· 24 30 label, 25 31 children, 26 32 align = "center", 33 + stack = false, 27 34 class: className 28 35 }: Props = $props(); 36 + 37 + const alignClass = $derived( 38 + align === "start" 39 + ? stack 40 + ? "sm:items-start" 41 + : "items-start" 42 + : stack 43 + ? "sm:items-center" 44 + : "items-center" 45 + ); 29 46 30 47 const Glyph = $derived(icon); 31 48 const lines = $derived( ··· 34 51 </script> 35 52 36 53 <div 37 - class="flex min-h-8 w-full justify-between gap-4 {align === 'start' 38 - ? 'items-start' 39 - : 'items-center'} {className ?? ''}" 54 + class="flex min-h-8 w-full justify-between {stack 55 + ? 'flex-col gap-2 sm:flex-row sm:gap-4' 56 + : 'gap-4'} {alignClass} {className ?? ''}" 40 57 > 41 58 {#if label} 42 59 {@render label()} ··· 54 71 </span> 55 72 {/if} 56 73 {#if children} 57 - <div class="flex shrink-0 items-center gap-2">{@render children()}</div> 74 + <!-- stacked, the controls take the full line and align right; a field that wants 75 + the whole width says so with `w-full sm:w-80` and fills it instead --> 76 + <div 77 + class="flex items-center gap-2 {stack 78 + ? 'w-full justify-end sm:w-auto sm:shrink-0' 79 + : 'shrink-0'}" 80 + > 81 + {@render children()} 82 + </div> 58 83 {/if} 59 84 </div>
+3 -1
web/src/lib/components/settings/ThemePicker.svelte
··· 12 12 const name = $props.id(); 13 13 </script> 14 14 15 - <div class="flex gap-4"> 15 + <!-- three 135px thumbnails need 437px, so on a phone they wrap two-up and Dark 16 + drops onto a second line --> 17 + <div class="flex w-full flex-wrap gap-4 sm:w-auto"> 16 18 <label class="flex w-[135px] cursor-pointer flex-col gap-2"> 17 19 <!-- Auto is the one thumbnail that tracks the live theme, since that is 18 20 exactly what the option does -->
+14 -13
web/src/lib/components/settings/tabs/ProfileTab.svelte
··· 73 73 <Avatar did={user?.did} handle={user?.handle} size="size-[158px]" /> 74 74 <Button icon={Pencil} href="/settings/profile/avatar">Edit</Button> 75 75 </div> 76 - <SettingsRow title="Bio"> 77 - <Input bind:value={form.bio} placeholder="Write a bio" class="w-80" /> 76 + <SettingsRow title="Bio" stack> 77 + <Input bind:value={form.bio} placeholder="Write a bio" class="w-full sm:w-80" /> 78 78 </SettingsRow> 79 - <SettingsRow title="Pronouns"> 80 - <Input bind:value={form.pronouns} placeholder="they/them" class="w-80" /> 79 + <SettingsRow title="Pronouns" stack> 80 + <Input bind:value={form.pronouns} placeholder="they/them" class="w-full sm:w-80" /> 81 81 </SettingsRow> 82 - <SettingsRow title="Location"> 83 - <Input bind:value={form.location} placeholder="Anywhere" class="w-80" /> 82 + <SettingsRow title="Location" stack> 83 + <Input bind:value={form.location} placeholder="Anywhere" class="w-full sm:w-80" /> 84 84 </SettingsRow> 85 85 <SettingsRow title="Link to Bluesky account"> 86 86 <Toggle bind:checked={form.bluesky} aria-label="Link to Bluesky account" /> 87 87 </SettingsRow> 88 - <SettingsRow title="Social links" class="!items-start"> 89 - <div class="flex w-80 flex-col gap-2"> 88 + <SettingsRow title="Social links" align="start" stack> 89 + <div class="flex w-full flex-col gap-2 sm:w-80"> 90 90 {#each form.links.keys() as i (i)} 91 91 <Input bind:value={form.links[i]} placeholder="Social link" /> 92 92 {/each} 93 93 </div> 94 94 </SettingsRow> 95 - <SettingsRow title="Vanity stats" class="!items-start"> 96 - <div class="flex w-80 flex-col gap-2"> 95 + <SettingsRow title="Vanity stats" align="start" stack> 96 + <div class="flex w-full flex-col gap-2 sm:w-80"> 97 97 {#each form.stats.keys() as i (i)} 98 98 <Select bind:value={form.stats[i]} aria-label="Vanity stat {i + 1}"> 99 99 <option value="">Choose stat</option> ··· 111 111 <CodeChip>{user?.handle ?? "…"}</CodeChip> 112 112 <Button variant="ghost" href="/settings/profile/handle">Change</Button> 113 113 </SettingsRow> 114 - <SettingsRow title="Decentralized Identifier (DID)"> 115 - <CodeChip class="break-all">{user?.did ?? "…"}</CodeChip> 114 + <!-- a DID is too long to sit beside its label on a phone; a PDS hostname is not --> 115 + <SettingsRow title="Decentralized Identifier (DID)" stack> 116 + <CodeChip class="w-full break-all sm:w-auto">{user?.did ?? "…"}</CodeChip> 116 117 </SettingsRow> 117 118 <SettingsRow title="Personal Data Server (PDS)"> 118 119 <CodeChip class="break-all">{pds ?? "…"}</CodeChip> ··· 120 121 </SettingsSection> 121 122 122 123 <SettingsSection title="Interface"> 123 - <SettingsRow title="Theme"> 124 + <SettingsRow title="Theme" align="start" stack> 124 125 <ThemePicker bind:value={form.theme} /> 125 126 </SettingsRow> 126 127 </SettingsSection>
+5 -10
web/src/routes/[handle]/[repo]/settings/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import { page } from "$app/state"; 3 - import SettingsNav, { type SettingsNavItem } from "$lib/components/settings/SettingsNav.svelte"; 3 + import type { SettingsNavItem } from "$lib/components/settings/SettingsNav.svelte"; 4 + import SettingsPanel from "$lib/components/settings/SettingsPanel.svelte"; 4 5 import SlidersHorizontal from "$icon/sliders-horizontal"; 5 6 import UsersRound from "$icon/users-round"; 6 7 import Layers from "$icon/layers"; ··· 25 26 const active = $derived(items.some((i) => i.id === segment) ? segment : "general"); 26 27 </script> 27 28 28 - <div 29 - class="flex min-h-[600px] w-full flex-col items-stretch gap-4 overflow-hidden rounded-sm border border-border-default bg-background-default md:flex-row md:items-start" 30 - > 31 - <SettingsNav {items} {active} label="Repository settings" /> 32 - 33 - <div class="flex min-w-0 flex-1 flex-col gap-8 px-4 py-6"> 34 - {@render children()} 35 - </div> 36 - </div> 29 + <SettingsPanel {items} {active} label="Repository settings"> 30 + {@render children()} 31 + </SettingsPanel>
+13 -7
web/src/routes/[handle]/[repo]/settings/+page.svelte
··· 77 77 <!-- the leading pair of cards carries no heading in the design --> 78 78 <div class="flex w-full flex-col gap-4"> 79 79 <SettingsList gap="3"> 80 - <SettingsRow title="Description"> 80 + <SettingsRow title="Description" stack> 81 81 <Input 82 82 bind:value={form.description} 83 83 placeholder="What this project is about" 84 - class="w-80" 84 + class="w-full sm:w-80" 85 85 /> 86 86 </SettingsRow> 87 - <SettingsRow title="Website"> 88 - <Input bind:value={form.website} placeholder="URL" class="w-80" /> 87 + <SettingsRow title="Website" stack> 88 + <Input bind:value={form.website} placeholder="URL" class="w-full sm:w-80" /> 89 89 </SettingsRow> 90 - <SettingsRow title="Topics" description="List of topics separated by spaces."> 91 - <Input bind:value={form.topics} placeholder="infra, social, decentralized" class="w-80" /> 90 + <SettingsRow title="Topics" description="List of topics separated by spaces." stack> 91 + <Input 92 + bind:value={form.topics} 93 + placeholder="infra, social, decentralized" 94 + class="w-full sm:w-80" 95 + /> 92 96 </SettingsRow> 93 97 </SettingsList> 94 98 ··· 98 102 description={[ 99 103 'The default branch is considered the "base" branch in your repository, against which all pull requests and code commits are automatically made, unless you specify a different branch.' 100 104 ]} 105 + stack 101 106 > 102 - <Select bind:value={form.defaultBranch} aria-label="Default branch" class="w-80"> 107 + <Select bind:value={form.defaultBranch} aria-label="Default branch" class="w-full sm:w-80"> 103 108 {#each branches as branch (branch)} 104 109 <option value={branch}>{branch}</option> 105 110 {/each} ··· 166 171 <SettingsRow 167 172 title="Delete" 168 173 description="Deleting a repository is irreversible and permanent. Be certain before deleting a repository." 174 + stack 169 175 > 170 176 <Button variant="danger" icon={Trash}>Delete repository</Button> 171 177 </SettingsRow>
+1 -1
web/src/routes/[handle]/[repo]/settings/access/+page.svelte
··· 48 48 49 49 <SettingsList gap="3"> 50 50 {#each collaborators as person (person.handle)} 51 - <SettingsRow> 51 + <SettingsRow stack> 52 52 {#snippet label()} 53 53 <span class="flex min-w-0 items-center gap-2"> 54 54 <Avatar did={person.did} handle={person.handle} size="size-5" />
+5 -4
web/src/routes/[handle]/[repo]/settings/hooks/+page.svelte
··· 64 64 <SettingsList gap="3"> 65 65 {#each hooks as hook (hook.id)} 66 66 <!-- the toggle rides the top-right corner while the actions sit under it, 67 - so this row is a two-column block rather than a SettingsRow --> 68 - <div class="flex w-full items-start justify-between gap-4"> 67 + so this row is a two-column block rather than a SettingsRow. On a 68 + phone the two columns stack and the actions spread across the width. --> 69 + <div class="flex w-full flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"> 69 70 <div class="flex min-w-0 flex-col gap-1"> 70 71 <CodeChip class="w-fit break-all">{hook.url}</CodeChip> 71 72 <span ··· 82 83 {hook.by} 83 84 </span> 84 85 </div> 85 - <div class="flex shrink-0 flex-col items-end gap-4"> 86 + <div class="flex flex-col items-end gap-4 sm:shrink-0"> 86 87 <Toggle bind:checked={hook.enabled} aria-label="Enable {hook.url}" /> 87 - <div class="flex items-center gap-2"> 88 + <div class="flex w-full items-center justify-between gap-2 sm:w-auto sm:justify-end"> 88 89 <Button icon={Inbox}>Deliveries</Button> 89 90 <Button icon={Pencil}>Edit</Button> 90 91 <Button variant="danger" icon={Trash} onclick={() => remove(hook.id)}>Delete</Button>
+3 -3
web/src/routes/[handle]/[repo]/settings/pipelines/+page.svelte
··· 50 50 {/snippet} 51 51 52 52 <SettingsList gap="3"> 53 - <SettingsRow title="Spindle"> 54 - <Select bind:value={spindle} aria-label="Spindle" class="w-80"> 53 + <SettingsRow title="Spindle" stack> 54 + <Select bind:value={spindle} aria-label="Spindle" class="w-full sm:w-80"> 55 55 {#each spindles as option (option)} 56 56 <option value={option}>{option}</option> 57 57 {/each} ··· 74 74 75 75 <SettingsList gap="3"> 76 76 {#each secrets as secret (secret.name)} 77 - <SettingsRow> 77 + <SettingsRow stack> 78 78 {#snippet label()} 79 79 <span class="flex min-w-0 flex-col gap-1"> 80 80 <span class="font-mono typography-monospace-regular break-all text-foreground-default">
+7 -4
web/src/routes/[handle]/[repo]/settings/sites/+page.svelte
··· 69 69 </div> 70 70 71 71 <SettingsList gap="3"> 72 - <SettingsRow title="Branch" description="The branch to build and deploy the site from."> 73 - <Select bind:value={form.branch} aria-label="Branch" class="w-80"> 72 + <SettingsRow title="Branch" description="The branch to build and deploy the site from." stack> 73 + <Select bind:value={form.branch} aria-label="Branch" class="w-full sm:w-80"> 74 74 {#each branches as branch (branch.value)} 75 75 <option value={branch.value}>{branch.label}</option> 76 76 {/each} ··· 83 83 "Path within the repository that contains your index.html.", 84 84 "Use / for the root, or a subdirectory like /docs." 85 85 ]} 86 + stack 86 87 > 87 - <Input bind:value={form.directory} placeholder="/" class="w-80" /> 88 + <Input bind:value={form.directory} placeholder="/" class="w-full sm:w-80" /> 88 89 </SettingsRow> 89 90 90 91 <SettingsRow ··· 93 94 "An index site is served at the root of your sites domain.", 94 95 "A sub-path site is served under the repository name." 95 96 ]} 97 + align="start" 98 + stack 96 99 > 97 - <div class="flex w-80 flex-col gap-2"> 100 + <div class="flex w-full flex-col gap-2 sm:w-80"> 98 101 <div class="flex flex-col gap-1"> 99 102 <Radio value="index" bind:group={form.type} name="site-type">Index site</Radio> 100 103 <CodeChip class="w-fit">{domain}</CodeChip>
+8 -9
web/src/routes/settings/+layout.svelte
··· 1 1 <script lang="ts"> 2 2 import { page } from "$app/state"; 3 - import SettingsNav, { type SettingsNavItem } from "$lib/components/settings/SettingsNav.svelte"; 3 + import type { SettingsNavItem } from "$lib/components/settings/SettingsNav.svelte"; 4 + import SettingsPanel from "$lib/components/settings/SettingsPanel.svelte"; 4 5 import User from "$icon/user-round"; 5 6 import Key from "$icon/key"; 6 7 import Mail from "$icon/mail"; ··· 30 31 <title>Settings &middot; Tangled</title> 31 32 </svelte:head> 32 33 33 - <div class="flex w-full flex-col items-center px-4 pt-16 pb-32"> 34 - <div 35 - class="flex min-h-[600px] w-full max-w-[1200px] flex-col items-stretch gap-4 overflow-hidden rounded-sm border border-border-default bg-background-default md:flex-row md:items-start" 36 - > 37 - <SettingsNav {items} {active} /> 38 - 39 - <div class="flex min-w-0 flex-1 flex-col gap-8 px-4 py-6"> 34 + <!-- the panel runs edge to edge on a phone, so the page gutters and the generous 35 + desktop breathing room only kick in from md up --> 36 + <div class="flex w-full flex-col items-center md:px-4 md:pt-16 md:pb-32"> 37 + <div class="flex w-full max-w-[1200px] flex-col"> 38 + <SettingsPanel {items} {active}> 40 39 {@render children()} 41 - </div> 40 + </SettingsPanel> 42 41 </div> 43 42 </div>