From 6f51aec4c6699ff0938aba9291af637f2d955fb3 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sun, 5 Jul 2026 15:40:09 +0200 Subject: [PATCH] feat: preset stili contenuto con whitelist --- src/lib/style-presets.ts | 31 +++++++++++++++++++++++++++++++ src/styles/global.css | 19 +++++++++++++++++++ tests/style-presets.test.ts | 24 ++++++++++++++++++++++++ 3 files changed, 74 insertions(+) create mode 100644 src/lib/style-presets.ts create mode 100644 tests/style-presets.test.ts diff --git a/src/lib/style-presets.ts b/src/lib/style-presets.ts new file mode 100644 index 0000000..821ad38 --- /dev/null +++ b/src/lib/style-presets.ts @@ -0,0 +1,31 @@ +export const STYLE_GROUPS = { + size: ['s', 'm', 'l'], + color: ['accent', 'accent-dark', 'dark', 'heading', 'text', 'text-light', 'white'], + weight: ['normal', 'bold'], + style: ['normal', 'italic'], + align: ['left', 'center', 'right'], +} as const; + +export type StyleGroup = keyof typeof STYLE_GROUPS; + +export function validateStyles(input: unknown): + | { ok: true; styles: Record } + | { ok: false; error: string } { + if (typeof input !== 'object' || input === null || Array.isArray(input)) { + return { ok: false, error: 'Stili non validi.' }; + } + const out: Record = {}; + for (const [k, v] of Object.entries(input)) { + const allowed = (STYLE_GROUPS as Record)[k]; + if (!allowed) return { ok: false, error: `Gruppo stile sconosciuto: ${k}` }; + if (typeof v !== 'string' || !allowed.includes(v)) { + return { ok: false, error: `Valore non ammesso per ${k}: ${String(v)}` }; + } + out[k] = v; + } + return { ok: true, styles: out }; +} + +export function stylesToClasses(styles: Record): string { + return Object.entries(styles).map(([k, v]) => `cs-${k}-${v}`).join(' '); +} diff --git a/src/styles/global.css b/src/styles/global.css index 09dd32e..2b49c61 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -68,3 +68,22 @@ html.js [data-reveal].is-inview, html.js .section-heading.is-inview { opacity: 1 html.js [data-reveal], html.js .section-heading { opacity: 1; transform: none; transition: none; } *, *::before, *::after { animation-duration: .01ms !important; animation-iteration-count: 1 !important; transition-duration: .01ms !important; } } + +/* === Stili contenuto (preset pannello /admin/content) === */ +.cs-size-s { font-size: .85em; } +.cs-size-m { font-size: 1em; } +.cs-size-l { font-size: 1.25em; } +.cs-color-accent { color: var(--c-accent); } +.cs-color-accent-dark { color: var(--c-accent-dark); } +.cs-color-dark { color: var(--c-dark); } +.cs-color-heading { color: var(--c-heading); } +.cs-color-text { color: var(--c-text); } +.cs-color-text-light { color: var(--c-text-light); } +.cs-color-white { color: #fff; } +.cs-weight-normal { font-weight: 400; } +.cs-weight-bold { font-weight: 700; } +.cs-style-normal { font-style: normal; } +.cs-style-italic { font-style: italic; } +.cs-align-left { text-align: left; } +.cs-align-center { text-align: center; } +.cs-align-right { text-align: right; } diff --git a/tests/style-presets.test.ts b/tests/style-presets.test.ts new file mode 100644 index 0000000..32b324f --- /dev/null +++ b/tests/style-presets.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest'; +import { validateStyles, stylesToClasses, STYLE_GROUPS } from '../src/lib/style-presets'; + +describe('style presets', () => { + it('accetta chiavi e valori in whitelist', () => { + const r = validateStyles({ size: 'l', color: 'accent', align: 'center' }); + expect(r).toEqual({ ok: true, styles: { size: 'l', color: 'accent', align: 'center' } }); + }); + + it('rifiuta gruppo sconosciuto e valore fuori lista', () => { + expect(validateStyles({ font: 'comic' }).ok).toBe(false); + expect(validateStyles({ size: 'xxl' }).ok).toBe(false); + expect(validateStyles('non-oggetto').ok).toBe(false); + }); + + it('mappa stili in classi cs-*', () => { + expect(stylesToClasses({ size: 's', weight: 'bold' })).toBe('cs-size-s cs-weight-bold'); + expect(stylesToClasses({})).toBe(''); + }); + + it('espone i gruppi attesi', () => { + expect(Object.keys(STYLE_GROUPS)).toEqual(['size', 'color', 'weight', 'style', 'align']); + }); +});