1.2.6: dedicated FontPicker component with in-line type previews

Replaces the bare <select> with a custom combobox that fits the
properties panel's visual language:

- Trigger looks like the other inputs (var(--bg)/var(--border)/
  var(--radius-sm)) and shows the selected font's name rendered in
  that font itself, with a rotating caret.
- Options live in a teleported popover anchored beneath the trigger.
  Each option renders its own label in its own typeface so the user
  sees what they're picking before applying it. Active row uses
  var(--accent-soft) to match the rest of the editor.
- Click-outside closes via window pointerdown; Escape closes and
  returns focus to the trigger.
- Storage shape simplified: textFontModel uses '' for system-sans
  instead of the literal 'system-sans' id, so the model field round-
  trips through JSON without a placeholder string ever leaking.

Updated PropertiesPanel test to walk the new picker (click trigger,
read options from .font-popover, click an option) instead of
dispatching a select 'change' event. Picker label text doubles as
the test selector — System Sans / Inter / Permanent Marker confirm
the option list is wired up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-26 10:27:19 +02:00
1 parent 4c2b7c7c35
commit 8c4f744afc
5 files changed
+258 -23

No files matched your search

+7 -6
View File
@@ -18,8 +18,8 @@ import { sampleLayer } from '@/lib/interpolate'
import type { Action, AnimatableProps, Layer } from '@/types/scene'
import RangeSlider from './RangeSlider.vue'
import ActionEditor from './ActionEditor.vue'
import FontPicker from './FontPicker.vue'
import { t } from '@/i18n'
import { AVAILABLE_FONTS } from '@/lib/fonts'
const SECTION_STORAGE_KEY = 'animatr:propSections'
@@ -138,11 +138,14 @@ const textAsset = computed(() => {
return a && a.kind === 'text' ? a : null
})
// Empty string represents the system-sans default. Stored as
// `undefined` on the asset so the model field round-trips cleanly
// through JSON without leaking a bogus 'system-sans' id.
const textFontModel = computed({
get: () => textAsset.value?.fontFamily ?? 'system-sans',
get: () => textAsset.value?.fontFamily ?? '',
set: (id: string) => {
if (!textAsset.value) return
textAsset.value.fontFamily = id === 'system-sans' ? undefined : id
textAsset.value.fontFamily = id || undefined
},
})
@@ -497,9 +500,7 @@ function deleteSelection() {
<label>{{ t('properties.text.fontSize') }}<input type="number" min="1" v-model.number="textAsset.fontSize" /></label>
<label>
{{ t('properties.text.font') }}
<select v-model="textFontModel">
<option v-for="f in AVAILABLE_FONTS" :key="f.id" :value="f.id">{{ f.label }}</option>
</select>
<FontPicker v-model="textFontModel" />
</label>
</div>
</section>