Textes projet éditables suivant Grav + écriture atomique du store
- Champ Présentation prérempli avec le texte Grav, suivi modifié/synchro (textOverride undefined = suit Grav, sinon figé ; bouton Réinitialiser) - Fix génération PDF : savePortfolio écrit en atomique (temp+rename) pour éviter la corruption de lecture par l'autosave concurrent du rendu print - Toast d'échec de génération explicite (message serveur) - Refonte architecture visuelle, fond unifié white/tint/full, dispositions couverture bandeau/colonne, instantané public complet, index 3 lignes
This commit is contained in:
@@ -1,50 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
// Fond de page (spec §7.2) : blanc / teinté (aplat clair) / pleine couleur.
|
||||
import type { Background } from '~~/types'
|
||||
// Fond de page unifié : Blanc / Teinté (aplat clair fixe) / Plein (couleur du
|
||||
// filet/bandeau de la page). Undefined = Blanc par défaut.
|
||||
import type { Background, BgMode } from '~~/types'
|
||||
|
||||
const model = defineModel<Background>({ required: true })
|
||||
const model = defineModel<Background | undefined>()
|
||||
|
||||
const modes = [
|
||||
{ value: 'white', label: 'Blanc' },
|
||||
{ value: 'tint', label: 'Teinté' },
|
||||
{ value: 'color', label: 'Couleur' },
|
||||
{ value: 'full', label: 'Plein' },
|
||||
] as const
|
||||
|
||||
function setMode(mode: Background['mode']) {
|
||||
if (mode === 'white') model.value = { mode: 'white' }
|
||||
else if (mode === 'tint') model.value = { mode: 'tint', color: model.value.mode === 'tint' ? model.value.color : TINT_PRESETS[0] }
|
||||
else model.value = { mode: 'color', color: model.value.mode === 'color' ? model.value.color : COLOR_PRESETS[1] }
|
||||
}
|
||||
|
||||
const swatches = computed(() =>
|
||||
model.value.mode === 'tint' ? TINT_PRESETS : model.value.mode === 'color' ? COLOR_PRESETS : [],
|
||||
)
|
||||
const current = computed<BgMode>(() => model.value?.mode ?? 'white')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-neutral-500">Fond de page</p>
|
||||
<p class="text-xs font-medium uppercase tracking-wide text-neutral-500">Fond</p>
|
||||
<div class="flex gap-1">
|
||||
<UButton
|
||||
v-for="m in modes"
|
||||
:key="m.value"
|
||||
:label="m.label"
|
||||
size="xs"
|
||||
:variant="model.mode === m.value ? 'solid' : 'outline'"
|
||||
:variant="current === m.value ? 'solid' : 'outline'"
|
||||
color="neutral"
|
||||
@click="setMode(m.value)"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="swatches.length" class="flex flex-wrap gap-1.5 pt-1">
|
||||
<button
|
||||
v-for="c in swatches"
|
||||
:key="c"
|
||||
type="button"
|
||||
class="size-7 rounded-full border transition-transform hover:scale-110"
|
||||
:class="(model as any).color === c ? 'ring-2 ring-[var(--fl-ink)] ring-offset-1 dark:ring-white' : 'border-neutral-300 dark:border-neutral-600'"
|
||||
:style="{ backgroundColor: c }"
|
||||
:aria-label="c"
|
||||
@click="model = { mode: model.mode as 'tint' | 'color', color: c }"
|
||||
@click="model = { mode: m.value }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,9 @@ const props = withDefaults(
|
||||
)
|
||||
|
||||
const model = defineModel<string | undefined>()
|
||||
|
||||
// Une couleur choisie hors palette = couleur personnalisée (input natif).
|
||||
const isCustom = computed(() => !!model.value && !BAND_COLORS.includes(model.value))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -24,7 +27,7 @@ const model = defineModel<string | undefined>()
|
||||
class="relative size-7 rounded-full border transition-transform hover:scale-110"
|
||||
:class="!model ? 'ring-2 ring-[var(--fl-ink)] ring-offset-1 dark:ring-white' : 'border-neutral-300 dark:border-neutral-600'"
|
||||
:style="{ backgroundColor: props.defaultColor }"
|
||||
title="Couleur de la catégorie (défaut)"
|
||||
title="Couleur par défaut"
|
||||
@click="model = undefined"
|
||||
>
|
||||
<UIcon name="i-lucide-rotate-ccw" class="absolute inset-0 m-auto size-3.5 text-black/40" />
|
||||
@@ -39,6 +42,21 @@ const model = defineModel<string | undefined>()
|
||||
:aria-label="c"
|
||||
@click="model = c"
|
||||
/>
|
||||
<!-- Couleur personnalisée (sélecteur natif) -->
|
||||
<label
|
||||
class="relative flex size-7 cursor-pointer items-center justify-center overflow-hidden rounded-full border transition-transform hover:scale-110"
|
||||
:class="isCustom ? 'ring-2 ring-[var(--fl-ink)] ring-offset-1 dark:ring-white' : 'border-neutral-300 dark:border-neutral-600'"
|
||||
:style="isCustom ? { backgroundColor: model } : undefined"
|
||||
title="Couleur personnalisée"
|
||||
>
|
||||
<UIcon v-if="!isCustom" name="i-lucide-pipette" class="size-3.5 text-neutral-400" />
|
||||
<input
|
||||
type="color"
|
||||
class="absolute inset-0 cursor-pointer opacity-0"
|
||||
:value="isCustom ? model : '#000000'"
|
||||
@input="model = ($event.target as HTMLInputElement).value"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user