feat(notes): swap heading and paragraph fonts with a button
All checks were successful
CI / verify (push) Successful in 1m7s

Replace the heading/paragraph toggle with the intended UX: keep both font
dropdowns visible and add a button that exchanges the two fonts via a new
swapFonts store action.
This commit is contained in:
Julien Calixte
2026-06-30 00:47:10 +02:00
parent 30eadaf21b
commit 84a252e926
3 changed files with 63 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref } from "vue" import { computed } from "vue"
import ThemeSwap from "@/components/ThemeSwap.vue" import ThemeSwap from "@/components/ThemeSwap.vue"
@@ -32,15 +32,13 @@ const sortedFontFamilies = computed(() => {
}) })
const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`) const fontSizes = Array.from({ length: 7 }, (_, i) => `${9 + i * 2}pt`)
// false → the dropdown edits the heading font, true → the paragraph font. const headingFont = computed({
const editingBody = ref(false) get: () => store.userSettings?.chosenHeadingFont,
const activeFont = computed({ set: (value) => store.setHeadingFont(value!)
get: () => })
editingBody.value const bodyFont = computed({
? store.userSettings?.chosenBodyFont get: () => store.userSettings?.chosenBodyFont,
: store.userSettings?.chosenHeadingFont, set: (value) => store.setBodyFont(value!)
set: (value) =>
editingBody.value ? store.setBodyFont(value!) : store.setHeadingFont(value!)
}) })
const fontSize = computed({ const fontSize = computed({
get: () => store.userSettings?.chosenFontSize, get: () => store.userSettings?.chosenFontSize,
@@ -51,22 +49,37 @@ const fontSize = computed({
<template> <template>
<div class="font-change"> <div class="font-change">
<div> <div>
<label class="font-toggle"> <label for="heading-font" class="font-label">h</label>
<span class="font-label" :class="{ active: !editingBody }">h</span> <select id="heading-font" class="select" v-model="headingFont">
<input <option v-for="font in sortedFontFamilies" :key="font" :value="font">
type="checkbox" {{ font }}
class="toggle toggle-sm" </option>
v-model="editingBody" </select>
aria-label="Switch between heading and paragraph font"
/> <button
<span class="font-label" :class="{ active: editingBody }">p</span> type="button"
</label> class="btn btn-ghost btn-sm btn-circle"
<select aria-label="Swap heading and paragraph fonts"
id="font-target" @click="store.swapFonts()"
class="select"
v-model="activeFont"
:aria-label="editingBody ? 'Paragraph font' : 'Heading font'"
> >
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M7 10h14l-4 -4" />
<path d="M17 14h-14l4 4" />
</svg>
</button>
<label for="body-font" class="font-label">p</label>
<select id="body-font" class="select" v-model="bodyFont">
<option v-for="font in sortedFontFamilies" :key="font" :value="font"> <option v-for="font in sortedFontFamilies" :key="font" :value="font">
{{ font }} {{ font }}
</option> </option>
@@ -101,17 +114,6 @@ const fontSize = computed({
} }
} }
.font-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
.font-label.active {
opacity: 1;
}
}
.font-label { .font-label {
font-weight: bold; font-weight: bold;
font-size: 0.75rem; font-size: 0.75rem;

View File

@@ -147,6 +147,24 @@ describe("userRepo store — synchronous mutations", () => {
expect(persisted.chosenHeadingFont).toBe("Serif") expect(persisted.chosenHeadingFont).toBe("Serif")
expect(persisted.chosenBodyFont).toBe("Sans") expect(persisted.chosenBodyFont).toBe("Sans")
}) })
it("swapFonts exchanges the heading and body fonts and persists", () => {
const store = useUserRepoStore()
store.user = "alice"
store.repo = "notes"
store.setHeadingFont("Lora")
store.setBodyFont("Inter")
store.swapFonts()
expect(store.userSettings?.chosenHeadingFont).toBe("Inter")
expect(store.userSettings?.chosenBodyFont).toBe("Lora")
const persisted = JSON.parse(
localStorage.getItem("remanso:layout:alice:notes") as string
)
expect(persisted.chosenHeadingFont).toBe("Inter")
expect(persisted.chosenBodyFont).toBe("Lora")
})
}) })
describe("userRepo store — setUserRepo", () => { describe("userRepo store — setUserRepo", () => {

View File

@@ -317,6 +317,13 @@ export const useUserRepoStore = defineStore("USER_REPO_STATE", {
} }
this.userSettings.chosenBodyFont = font this.userSettings.chosenBodyFont = font
this._persistLayout() this._persistLayout()
},
swapFonts() {
if (!this.userSettings) return
const { chosenHeadingFont, chosenBodyFont } = this.userSettings
this.userSettings.chosenHeadingFont = chosenBodyFont
this.userSettings.chosenBodyFont = chosenHeadingFont
this._persistLayout()
} }
} }
}) })