Composables

useTheme

Gestione tema chiaro/scuro/sistema con persistenza localStorage e sincronizzazione MediaQuery del SO.

Signature

function useTheme(): {
  isDark: Ref<boolean>
  mode: Ref<'light' | 'dark' | 'system'>
  setTheme: (newMode: 'light' | 'dark' | 'system') => void
  toggleTheme: () => void
}

Gestisce lo stato del tema (chiaro/scuro/sistema) come singleton globale con persistenza localStorage e sync automatica con prefers-color-scheme del browser.

Stato globale condiviso: isDark e mode sono ref dichiarate al module-scope e condivise tra tutti i consumer di useTheme(). Modificare il tema in un componente lo aggiorna ovunque.

Comportamento

Init

Al primo useTheme() montato:

  1. Legge localStorage['pzeta-fogli-theme'] ('light' | 'dark' | 'system')
  2. Risolve mode === 'system' consultando matchMedia('(prefers-color-scheme: dark)')
  3. Applica document.documentElement.classList.toggle('dark', isDark)
  4. Registra listener su MediaQueryList.change per reagire ai cambi del SO

setTheme(newMode)

setTheme('dark')
// → mode.value = 'dark'
// → localStorage['pzeta-fogli-theme'] = 'dark'
// → isDark.value = true
// → <html class="dark">

toggleTheme()

toggleTheme()
// → setTheme(isDark.value ? 'light' : 'dark')

Se la modalità corrente è 'system', parte dallo isDark calcolato (che dipende dal SO).

Sync MediaQuery

Se mode.value === 'system', un cambio della preferenza OS (es. dark mode automatico al tramonto) aggiorna automaticamente isDark senza richiedere refresh.

Esempio: theme switcher

<script setup lang="ts">
import { useTheme } from '@pzeta/vue-foglidicalcolo'

const { isDark, mode, setTheme, toggleTheme } = useTheme()
</script>

<template>
  <button @click="toggleTheme">
    <i :class="isDark ? 'pi pi-sun' : 'pi pi-moon'" />
  </button>

  <select :value="mode" @change="setTheme($event.target.value)">
    <option value="light">Chiaro</option>
    <option value="dark">Scuro</option>
    <option value="system">Sistema</option>
  </select>
</template>

Storage key

pzeta-fogli-theme

Valori ammessi: 'light' | 'dark' | 'system'. Valori non validi vengono ignorati (cade su 'system' default).

Vincoli

  • Browser only: usa localStorage, matchMedia, document. In SSR i side-effect sono evitati ma mode resta a 'system' finché non avviene il mount client-side.
  • CSS dark class: il toggle aggiunge/rimuove la classe dark su <html>. La libreria si aspetta un sistema CSS dark-mode basato su selettori .dark .foo {...} (Tailwind, PrimeVue dark theme).
  • Tema indipendente dal MFEContext.theme (di @pzeta/mfe-contracts): se la shell usa un proprio sistema tema, è responsabilità del consumer sincronizzarli.

Pattern: sync con MFEContext.theme

Se l'app è un MFE caricato in una shell che fornisce MFEContext.theme:

<script setup lang="ts">
import { watch } from 'vue'
import { useMfeContext } from '@pzeta/mfe-contracts/composables'
import { useTheme } from '@pzeta/vue-foglidicalcolo'

const ctx = useMfeContext()
const { mode, setTheme } = useTheme()

// Quando la shell cambia tema, aggiorna anche il tema del foglio
watch(() => ctx.theme?.mode, (newMode) => {
  if (newMode && newMode !== mode.value) {
    setTheme(newMode)
  }
})
</script>