Move the theme settings onto the content settings page

This commit is contained in:
Coracle-Bot 2026-09-25 22:53:51 +00:00
parent 227834e947
commit 028ae1bedb
6 changed files with 88 additions and 107 deletions

View file

@ -1410,8 +1410,8 @@ Acceptance:
- Selecting "Dark" applies the dark theme to the document immediately, and
"System" follows the OS color-scheme preference.
- Choosing a different app theme applies it and persists after a reload.
- Dragging the font-size slider updates the displayed percentage and saves
without a separate save button.
- Dragging the font-size slider updates the displayed percentage, and saving the
page applies it and persists it across a reload.
### US-091 — Set up how people zap you

View file

@ -155,8 +155,7 @@ test("US-088 adjust send delay and media servers", async ({seed, as}) => {
const page = await as(users.alice, "/settings/content")
// The send delay is the only slider on this page.
await page.locator('input[type="range"]').fill("3000")
await page.getByLabel("Send delay").fill("3000")
await page.getByRole("button", {name: "Add Server"}).click()
await page.getByRole("listitem").first().locator("input").fill("https://media.test/")
@ -168,7 +167,7 @@ test("US-088 adjust send delay and media servers", async ({seed, as}) => {
await waitForToastToClear(page)
await page.reload()
await expect(page.locator('input[type="range"]')).toHaveValue("3000")
await expect(page.getByLabel("Send delay")).toHaveValue("3000")
})
test("US-089 configure privacy preferences", async ({seed, as}) => {
@ -235,7 +234,7 @@ test("US-090 change the app's appearance", async ({seed, as}) => {
space.relayList(user.alice)
})
const page = await as(users.alice, "/settings/theme", {context: {colorScheme: "light"}})
const page = await as(users.alice, "/settings/content", {context: {colorScheme: "light"}})
const body = page.locator("body")
await page.getByRole("button", {name: "Dark", exact: true}).click()
@ -259,11 +258,14 @@ test("US-090 change the app's appearance", async ({seed, as}) => {
await expect(body).toHaveAttribute("data-fl-theme", "navy")
// Font size is the only slider on this page, and moving it is the save.
await page.locator('input[type="range"]').fill("1.25")
// Font size is a setting, so it goes out with the rest of the page.
await page.getByLabel("Font size").fill("1.25")
await page.getByRole("button", {name: "Save Changes"}).click()
await expect(page.getByRole("alert")).toContainText("Your settings have been saved!")
await expect(page.locator("html")).toHaveAttribute("style", /font-size:\s*1\.25rem/)
await waitForToastToClear(page)
await page.reload()
await expect(page.locator("html")).toHaveAttribute("style", /font-size:\s*1\.25rem/)

View file

@ -67,8 +67,6 @@
</Link>
</div>
<div class="flex gap-3 items-center opacity-75 text-sm">
<Link href="/settings/theme">Theme</Link>
/
<Link href="/settings/about">About</Link>
/
<Button onclick={logout}>Log Out</Button>

View file

@ -4,7 +4,6 @@
import UserCircle from "@assets/icons/user-circle.svg?dataurl"
import Wallet from "@assets/icons/wallet.svg?dataurl"
import ServerPath from "@assets/icons/server-path.svg?dataurl"
import Moon from "@assets/icons/moon.svg?dataurl"
import Code2 from "@assets/icons/code-2.svg?dataurl"
import Exit from "@assets/icons/logout-3.svg?dataurl"
import GalleryMinimalistic from "@assets/icons/gallery-minimalistic.svg?dataurl"
@ -55,9 +54,6 @@
<SecondaryNavItem href="/settings/privacy">
<Icon icon={Shield} /> Privacy
</SecondaryNavItem>
<SecondaryNavItem href="/settings/theme">
<Icon icon={Moon} /> Theme
</SecondaryNavItem>
<SecondaryNavItem href="/settings/about">
<Icon icon={Code2} /> About
</SecondaryNavItem>

View file

@ -1,8 +1,14 @@
<script lang="ts">
import cx from "classnames"
import {BlossomServerLists, MuteLists, publish} from "@welshman/app"
import NotesMinimalistic from "@assets/icons/notes-minimalistic.svg?dataurl"
import Accessibility from "@assets/icons/accessibility.svg?dataurl"
import AddCircle from "@assets/icons/add-circle.svg?dataurl"
import Microphone from "@assets/icons/microphone.svg?dataurl"
import Monitor from "@assets/icons/monitor.svg?dataurl"
import Moon from "@assets/icons/moon.svg?dataurl"
import Palette from "@assets/icons/palette.svg?dataurl"
import Sun from "@assets/icons/sun.svg?dataurl"
import {preventDefault} from "@lib/html"
import Field from "@lib/components/Field.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
@ -18,6 +24,7 @@
import {blossomServerLists, deriveUserItem, muteLists} from "@app/core"
import {pushToast} from "@app/toast"
import {PLATFORM_NAME} from "@app/env"
import {flTheme, flThemes, theme} from "@app/theme"
import {userSettingsValues, publishSettings, createSettingsForm} from "@app/settings"
const userMuteList = deriveUserItem(MuteLists)
@ -74,6 +81,16 @@
})
</script>
{#snippet colorScheme(value: string, label: string, icon: string)}
<Button
type="button"
class={cx("join-item button button-xs", $theme === value ? "button-primary" : "button-neutral")}
onclick={() => theme.set(value)}>
<Icon {icon} size={4} />
{label}
</Button>
{/snippet}
<form {onsubmit}>
<PageContent>
<Card class="flex flex-col gap-4">
@ -116,6 +133,66 @@
{/snippet}
</Field>
</Card>
<Card class="flex flex-col gap-4">
<strong class="flex items-center gap-3 text-lg">
<Icon icon={Palette} />
Theme Settings
</strong>
<FieldInline>
{#snippet label()}
<p>Color scheme</p>
{/snippet}
{#snippet input()}
<div class="join">
{@render colorScheme("system", "System", Monitor)}
{@render colorScheme("light", "Light", Sun)}
{@render colorScheme("dark", "Dark", Moon)}
</div>
{/snippet}
{#snippet info()}
<p>System follows whatever your device is set to.</p>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>App theme</p>
{/snippet}
{#snippet input()}
<select class="select input capitalize" bind:value={$flTheme} aria-label="Style">
{#each flThemes as option (option)}
<option value={option}>{option}</option>
{/each}
</select>
{/snippet}
{#snippet info()}
<p>Changes the shape and texture of everything, not just its colors.</p>
{/snippet}
</FieldInline>
</Card>
<Card class="flex flex-col gap-4">
<strong class="flex items-center gap-3 text-lg">
<Icon icon={Accessibility} />
Accessibility
</strong>
<Field>
{#snippet label()}
<p>Font size</p>
{/snippet}
{#snippet secondary()}
<p>{Math.round($settings.font_size * 100)}%</p>
{/snippet}
{#snippet input()}
<input
class="range w-full"
type="range"
min="0.8"
max="1.3"
step="0.05"
aria-label="Font size"
bind:value={$settings.font_size} />
{/snippet}
</Field>
</Card>
<Card class="flex flex-col gap-4 shadow-md">
<strong class="text-lg">Editor Settings</strong>
<Field>
@ -129,6 +206,7 @@
min="0"
max="10000"
step="1000"
aria-label="Send delay"
bind:value={$settings.send_delay} />
{/snippet}
{#snippet info()}

View file

@ -1,93 +0,0 @@
<script lang="ts">
import cx from "classnames"
import Accessibility from "@assets/icons/accessibility.svg?dataurl"
import Monitor from "@assets/icons/monitor.svg?dataurl"
import Moon from "@assets/icons/moon.svg?dataurl"
import Palette from "@assets/icons/palette.svg?dataurl"
import Sun from "@assets/icons/sun.svg?dataurl"
import Button from "@lib/components/Button.svelte"
import Icon from "@lib/components/Icon.svelte"
import Card from "@lib/components/Card.svelte"
import Field from "@lib/components/Field.svelte"
import FieldInline from "@lib/components/FieldInline.svelte"
import PageContent from "@lib/components/PageContent.svelte"
import {publishSettings, userSettingsValues} from "@app/settings"
import {flTheme, flThemes, theme} from "@app/theme"
const saveFontSize = () => publishSettings({font_size: fontSize})
let fontSize = $state($userSettingsValues.font_size)
</script>
{#snippet colorScheme(value: string, label: string, icon: string)}
<Button
type="button"
class={cx("join-item button button-xs", $theme === value ? "button-primary" : "button-neutral")}
onclick={() => theme.set(value)}>
<Icon {icon} size={4} />
{label}
</Button>
{/snippet}
<PageContent>
<Card class="flex flex-col gap-4">
<strong class="flex items-center gap-3 text-lg">
<Icon icon={Palette} />
Theme Settings
</strong>
<FieldInline>
{#snippet label()}
<p>Color scheme</p>
{/snippet}
{#snippet input()}
<div class="join">
{@render colorScheme("system", "System", Monitor)}
{@render colorScheme("light", "Light", Sun)}
{@render colorScheme("dark", "Dark", Moon)}
</div>
{/snippet}
{#snippet info()}
<p>System follows whatever your device is set to.</p>
{/snippet}
</FieldInline>
<FieldInline>
{#snippet label()}
<p>App theme</p>
{/snippet}
{#snippet input()}
<select class="select input capitalize" bind:value={$flTheme} aria-label="Style">
{#each flThemes as option (option)}
<option value={option}>{option}</option>
{/each}
</select>
{/snippet}
{#snippet info()}
<p>Changes the shape and texture of everything, not just its colors.</p>
{/snippet}
</FieldInline>
</Card>
<Card class="flex flex-col gap-4">
<strong class="flex items-center gap-3 text-lg">
<Icon icon={Accessibility} />
Accessibility
</strong>
<Field>
{#snippet label()}
<p>Font size</p>
{/snippet}
{#snippet secondary()}
<p>{Math.round(fontSize * 100)}%</p>
{/snippet}
{#snippet input()}
<input
class="range w-full"
type="range"
min="0.8"
max="1.3"
step="0.05"
bind:value={fontSize}
onchange={saveFontSize} />
{/snippet}
</Field>
</Card>
</PageContent>