feat(notes): swap heading and paragraph fonts with a button
All checks were successful
CI / verify (push) Successful in 1m7s
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:
@@ -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;
|
||||||
|
|||||||
@@ -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", () => {
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user