Skip to main content

shadow_design/
theme.rs

1use dioxus::prelude::*;
2use std::collections::HashMap;
3
4#[cfg(feature = "alert")]
5use crate::alert::AlertVariant;
6#[cfg(feature = "badge")]
7use crate::badge::BadgeVariant;
8#[cfg(feature = "button")]
9use crate::button::ButtonVariant;
10#[cfg(feature = "card")]
11use crate::card::CardShadow;
12
13/// Palette di colori primari in stile Mantine / Material-UI, con supporto a colori Custom estendibili.
14#[derive(Clone, PartialEq, Debug, Default)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub enum PrimaryColor {
17    #[default]
18    Violet,
19    Obsidian,
20    Crimson,
21    Cyan,
22    Emerald,
23    Custom {
24        name: String,
25        hex: String,
26        css_prefix: Option<String>,
27    },
28}
29
30impl PrimaryColor {
31    pub fn name(&self) -> &str {
32        match self {
33            PrimaryColor::Violet => "Violet",
34            PrimaryColor::Obsidian => "Obsidian",
35            PrimaryColor::Crimson => "Crimson",
36            PrimaryColor::Cyan => "Cyan",
37            PrimaryColor::Emerald => "Emerald",
38            PrimaryColor::Custom { name, .. } => name.as_str(),
39        }
40    }
41
42    pub fn hex(&self) -> &str {
43        match self {
44            PrimaryColor::Violet => "#3b0764",
45            PrimaryColor::Obsidian => "#ffffff",
46            PrimaryColor::Crimson => "#ff1744",
47            PrimaryColor::Cyan => "#00f0ff",
48            PrimaryColor::Emerald => "#00ff9d",
49            PrimaryColor::Custom { hex, .. } => hex.as_str(),
50        }
51    }
52
53    /// Ricava automaticamente il prefisso o slug per le classi CSS (es. "cartoon", "manga-yellow").
54    pub fn css_prefix(&self) -> String {
55        match self {
56            PrimaryColor::Violet => "violet".to_string(),
57            PrimaryColor::Obsidian => "obsidian".to_string(),
58            PrimaryColor::Crimson => "crimson".to_string(),
59            PrimaryColor::Cyan => "cyan".to_string(),
60            PrimaryColor::Emerald => "emerald".to_string(),
61            PrimaryColor::Custom { name, css_prefix, .. } => {
62                if let Some(prefix) = css_prefix {
63                    prefix.clone()
64                } else {
65                    name.to_lowercase()
66                        .chars()
67                        .map(|c| if c.is_alphanumeric() { c } else { '-' })
68                        .collect::<String>()
69                        .trim_matches('-')
70                        .to_string()
71                }
72            }
73        }
74    }
75
76    pub fn custom(name: impl Into<String>, hex: impl Into<String>) -> Self {
77        Self::Custom {
78            name: name.into(),
79            hex: hex.into(),
80            css_prefix: None,
81        }
82    }
83
84    pub fn custom_with_prefix(
85        name: impl Into<String>,
86        hex: impl Into<String>,
87        css_prefix: impl Into<String>,
88    ) -> Self {
89        Self::Custom {
90            name: name.into(),
91            hex: hex.into(),
92            css_prefix: Some(css_prefix.into()),
93        }
94    }
95}
96
97/// Scala globale per i raggi di curvatura (Border Radius).
98#[derive(Clone, Copy, PartialEq, Debug, Default)]
99#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
100pub enum RadiusScale {
101    Sharp,
102    #[default]
103    Medium,
104    Soft,
105}
106
107impl RadiusScale {
108    pub fn name(&self) -> &'static str {
109        match self {
110            RadiusScale::Sharp => "Sharp (2px)",
111            RadiusScale::Medium => "Medium (8px)",
112            RadiusScale::Soft => "Soft (14px)",
113        }
114    }
115
116    pub fn sm(&self) -> &'static str {
117        match self {
118            RadiusScale::Sharp => "sd-radius-sharp",
119            RadiusScale::Medium => "rounded-md",
120            RadiusScale::Soft => "rounded-lg",
121        }
122    }
123
124    pub fn md(&self) -> &'static str {
125        match self {
126            RadiusScale::Sharp => "sd-radius-sharp",
127            RadiusScale::Medium => "rounded-lg",
128            RadiusScale::Soft => "rounded-xl",
129        }
130    }
131
132    pub fn lg(&self) -> &'static str {
133        match self {
134            RadiusScale::Sharp => "sd-radius-sharp",
135            RadiusScale::Medium => "rounded-xl",
136            RadiusScale::Soft => "rounded-2xl",
137        }
138    }
139}
140
141/// Intensità del micro-glow ottico radiale.
142#[derive(Clone, Copy, PartialEq, Debug, Default)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
144pub enum GlowMode {
145    #[default]
146    Subtle,
147    Expressive,
148    Off,
149}
150
151/// Stile di elevazione delle ombre (Livello 1): Neon Glow, Hard Block cartoon, Soft o Flat.
152#[derive(Clone, Copy, PartialEq, Debug, Default)]
153#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
154pub enum ElevationStyle {
155    #[default]
156    NeonGlow,     // Specular neon glow sfumato (Cyberpunk / Ultraviolet)
157    HardBlock,    // Ombre piene offset senza blur (Cartoon / Manga Pop / Neo-brutalist)
158    SoftElevated, // Ombre material soft
159    Flat,         // Senza ombre
160}
161
162/// Personalizzazione specifica per un componente (Livello 2 - Component Level).
163#[derive(Clone, PartialEq, Debug, Default)]
164#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
165pub struct ComponentOverride {
166    /// Classe base aggiuntiva o sostitutiva
167    pub base_class: Option<String>,
168    /// Mappa delle varianti (es. "primary" -> "my-primary-btn", "danger" -> "my-danger-btn")
169    pub variant_classes: HashMap<String, String>,
170    /// Mappa delle taglie (es. "sm" -> "my-sm-btn")
171    pub size_classes: HashMap<String, String>,
172    /// Override dell'ombra o elevazione per questo componente
173    pub shadow_class: Option<String>,
174    /// Modificatori condizionali per classi specifiche
175    pub class_modifiers: HashMap<String, String>,
176}
177
178impl ComponentOverride {
179    pub fn new() -> Self {
180        Self::default()
181    }
182
183    pub fn with_base_class(mut self, base: impl Into<String>) -> Self {
184        self.base_class = Some(base.into());
185        self
186    }
187
188    pub fn with_variant(mut self, variant: impl Into<String>, class: impl Into<String>) -> Self {
189        self.variant_classes.insert(variant.into(), class.into());
190        self
191    }
192
193    pub fn with_size(mut self, size: impl Into<String>, class: impl Into<String>) -> Self {
194        self.size_classes.insert(size.into(), class.into());
195        self
196    }
197
198    pub fn with_shadow(mut self, shadow: impl Into<String>) -> Self {
199        self.shadow_class = Some(shadow.into());
200        self
201    }
202
203    pub fn with_class_modifier(
204        mut self,
205        target_class: impl Into<String>,
206        modifier: impl Into<String>,
207    ) -> Self {
208        self.class_modifiers
209            .insert(target_class.into(), modifier.into());
210        self
211    }
212}
213
214/// Struttura centrale del tema per Shadow Design ispirata a Mantine e Material-UI.
215/// Supporta personalizzazione gerarchica su 3 livelli.
216#[derive(Clone, PartialEq, Debug)]
217#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
218pub struct Theme {
219    pub name: String,
220    pub primary_color: PrimaryColor,
221    pub radius_scale: RadiusScale,
222    pub elevation_style: ElevationStyle,
223    pub glow_mode: GlowMode,
224
225    // Token di Superficie a 4 Livelli (Void, Base, Elevated, Overlay)
226    pub surface_0: String,
227    pub surface_1: String,
228    pub surface_2: String,
229    pub surface_3: String,
230
231    // Layout e Struttura
232    pub root: String,
233    pub header: String,
234    pub sidebar: String,
235    pub sidebar_link: String,
236    pub sidebar_link_active: String,
237    pub content: String,
238    pub surface: String,
239    pub panel: String,
240    pub banner: String,
241
242    // Accenti e Bordi
243    pub border: String,
244    pub border_subtle: String,
245    pub border_accent: String,
246    pub text_accent: String,
247    pub text_accent_bright: String,
248    pub glow: String,
249
250    // Glow specifici per componente (Livello 1 fallback)
251    pub glow_button: String,
252    pub glow_card: String,
253    pub glow_input: String,
254    pub glow_badge: String,
255    pub glow_modal: String,
256    pub glow_alert: String,
257    pub glow_dropdown: String,
258    pub glow_notification: String,
259    pub glow_switch: String,
260    pub glow_checkbox: String,
261
262    // Classi dei componenti (Livello 1 fallback)
263    pub button_primary: String,
264    pub button_secondary: String,
265    pub button_outline: String,
266    pub button_ghost: String,
267    pub button_danger: String,
268    pub button_success: String,
269
270    pub card_base: String,
271    pub card_header: String,
272    pub card_footer: String,
273    pub card_shadow_none: String,
274    pub card_shadow_sm: String,
275    pub card_shadow_md: String,
276    pub card_shadow_lg: String,
277    pub card_shadow_xl: String,
278
279    pub input_base: String,
280    pub input_normal_border: String,
281    pub input_error_border: String,
282
283    pub badge_default: String,
284    pub badge_primary: String,
285    pub badge_success: String,
286    pub badge_warning: String,
287    pub badge_danger: String,
288    pub badge_outline: String,
289
290    pub navbar_base: String,
291
292    pub modal_backdrop: String,
293    pub modal_dialog: String,
294    pub modal_header: String,
295    pub modal_close: String,
296
297    pub alert_info: String,
298    pub alert_success: String,
299    pub alert_warning: String,
300    pub alert_danger: String,
301    pub alert_title_info: String,
302    pub alert_title_success: String,
303    pub alert_title_warning: String,
304    pub alert_title_danger: String,
305
306    // Livello 2: Registro di personalizzazione per componente e per classe
307    pub components: HashMap<String, ComponentOverride>,
308    pub class_overrides: HashMap<String, String>,
309}
310
311impl Default for Theme {
312    fn default() -> Self {
313        Self::shadow()
314    }
315}
316
317impl Theme {
318    /// Inizia la costruzione di un tema personalizzato con builder fluido.
319    pub fn builder() -> ThemeBuilder {
320        ThemeBuilder::default()
321    }
322
323    /// Risolve la classe variante tenendo conto della gerarchia:
324    /// Livello 2 (Component Override) -> Livello 1 (Global Theme Fallback).
325    pub fn resolve_variant_class(
326        &self,
327        component: &str,
328        variant: &str,
329        fallback_class: &str,
330    ) -> String {
331        if let Some(comp_override) = self.components.get(component) {
332            if let Some(custom) = comp_override.variant_classes.get(variant) {
333                return custom.clone();
334            }
335        }
336        fallback_class.to_string()
337    }
338
339    /// Risolve la classe taglia per un componente tenendo conto di Livello 2 -> Livello 1.
340    pub fn resolve_size_class(&self, component: &str, size: &str, fallback_class: &str) -> String {
341        if let Some(comp_override) = self.components.get(component) {
342            if let Some(custom) = comp_override.size_classes.get(size) {
343                return custom.clone();
344            }
345        }
346        fallback_class.to_string()
347    }
348
349    /// Restituisce la classe base opzionale definita per il componente (Livello 2).
350    pub fn resolve_component_base(&self, component: &str) -> Option<&str> {
351        self.components
352            .get(component)
353            .and_then(|c| c.base_class.as_deref())
354    }
355
356    /// Risolve modificatori condizionali per classi specifiche (Livello 2 per classi).
357    pub fn resolve_class_modifiers(&self, component: &str, instance_classes: &str) -> String {
358        let mut modifiers = Vec::new();
359
360        // Controllo sui modificatori del componente specifico
361        if let Some(comp) = self.components.get(component) {
362            for word in instance_classes.split_whitespace() {
363                if let Some(m) = comp.class_modifiers.get(word) {
364                    modifiers.push(m.as_str());
365                }
366            }
367        }
368
369        // Controllo sugli override globali per classe
370        for word in instance_classes.split_whitespace() {
371            if let Some(m) = self.class_overrides.get(word) {
372                modifiers.push(m.as_str());
373            }
374        }
375
376        modifiers.join(" ")
377    }
378
379    /// Risolve la classe di ombra / glow in base all'ElevationStyle e alla configurazione del componente.
380    pub fn resolve_elevation_class(&self, component: &str, override_glow: Option<bool>) -> String {
381        if override_glow == Some(false) {
382            return String::new();
383        }
384
385        // Se il componente ha un override esplicito di ombra/glow a Livello 2
386        if let Some(comp) = self.components.get(component) {
387            if let Some(custom_shadow) = &comp.shadow_class {
388                return custom_shadow.clone();
389            }
390        }
391
392        match self.elevation_style {
393            ElevationStyle::Flat => String::new(),
394            ElevationStyle::HardBlock => match component {
395                "button" => "sd-comic-shadow-sm".to_string(),
396                "card" => "sd-comic-shadow-lg".to_string(),
397                "modal" => "sd-comic-shadow-xl".to_string(),
398                "dropdown" => "sd-comic-shadow-md".to_string(),
399                "badge" => "sd-comic-shadow-xs".to_string(),
400                "alert" => "sd-comic-shadow-md".to_string(),
401                _ => "sd-comic-shadow-sm".to_string(),
402            },
403            ElevationStyle::SoftElevated => match component {
404                "button" => "shadow-sm".to_string(),
405                "card" => "shadow-md".to_string(),
406                "modal" => "shadow-xl".to_string(),
407                _ => "shadow".to_string(),
408            },
409            ElevationStyle::NeonGlow => {
410                if self.is_component_glow_enabled(component, override_glow) {
411                    self.component_glow(component).to_string()
412                } else {
413                    String::new()
414                }
415            }
416        }
417    }
418
419    /// Genera la stringa di CSS Custom Properties da iniettare dinamicamente a runtime.
420    pub fn to_css_variables(&self) -> String {
421        format!(
422            ":root {{ \
423                --sd-primary-color: {primary}; \
424                --sd-surface-0: {s0}; \
425                --sd-surface-1: {s1}; \
426                --sd-surface-2: {s2}; \
427                --sd-surface-3: {s3}; \
428            }}",
429            primary = self.primary_color.hex(),
430            s0 = self.surface_0,
431            s1 = self.surface_1,
432            s2 = self.surface_2,
433            s3 = self.surface_3,
434        )
435    }
436
437    /// Calcola i valori di glow per ogni componente in base al colore primario e alla modalità.
438    fn calculate_glows(
439        color: &PrimaryColor,
440        mode: GlowMode,
441    ) -> (
442        String,
443        String,
444        String,
445        String,
446        String,
447        String,
448        String,
449        String,
450        String,
451        String,
452        String,
453    ) {
454        if mode == GlowMode::Off {
455            return (
456                String::new(),
457                String::new(),
458                String::new(),
459                String::new(),
460                String::new(),
461                String::new(),
462                String::new(),
463                String::new(),
464                String::new(),
465                String::new(),
466                String::new(),
467            );
468        }
469
470        match color {
471            PrimaryColor::Violet => (
472                "glow-violet".to_string(),
473                "sd-btn--glow".to_string(),
474                "sd-card--hover-glow".to_string(),
475                "".to_string(),
476                "sd-badge--glow".to_string(),
477                "sd-modal-dialog--glow".to_string(),
478                "sd-alert--glow-info".to_string(),
479                "sd-dropdown-menu--glow".to_string(),
480                "sd-toast--glow-info".to_string(),
481                "sd-switch__track--glow".to_string(),
482                "sd-checkbox__box--glow".to_string(),
483            ),
484            PrimaryColor::Obsidian => (
485                "".to_string(),
486                "sd-btn--glow-obsidian".to_string(),
487                "sd-card--hover-glow".to_string(),
488                "".to_string(),
489                "sd-badge--glow".to_string(),
490                "sd-modal-dialog--glow".to_string(),
491                "sd-alert--glow-info".to_string(),
492                "sd-dropdown-menu--glow".to_string(),
493                "sd-toast--glow-info".to_string(),
494                "sd-switch__track--glow-obsidian".to_string(),
495                "sd-checkbox__box--glow".to_string(),
496            ),
497            PrimaryColor::Crimson => (
498                "glow-crimson".to_string(),
499                "sd-btn--glow-crimson".to_string(),
500                "sd-card--hover-glow".to_string(),
501                "".to_string(),
502                "sd-badge--glow".to_string(),
503                "sd-modal-dialog--glow".to_string(),
504                "sd-alert--glow-danger".to_string(),
505                "sd-dropdown-menu--glow".to_string(),
506                "sd-toast--glow-danger".to_string(),
507                "sd-switch__track--glow-crimson".to_string(),
508                "sd-checkbox__box--glow".to_string(),
509            ),
510            PrimaryColor::Cyan => (
511                "glow-cyan".to_string(),
512                "sd-btn--glow-cyan".to_string(),
513                "sd-card--hover-glow".to_string(),
514                "".to_string(),
515                "sd-badge--glow".to_string(),
516                "sd-modal-dialog--glow".to_string(),
517                "sd-alert--glow-info".to_string(),
518                "sd-dropdown-menu--glow".to_string(),
519                "sd-toast--glow-info".to_string(),
520                "sd-switch__track--glow-cyan".to_string(),
521                "sd-checkbox__box--glow".to_string(),
522            ),
523            PrimaryColor::Emerald => (
524                "glow-emerald".to_string(),
525                "sd-btn--glow-emerald".to_string(),
526                "sd-card--hover-glow".to_string(),
527                "".to_string(),
528                "sd-badge--glow".to_string(),
529                "sd-modal-dialog--glow".to_string(),
530                "sd-alert--glow-success".to_string(),
531                "sd-dropdown-menu--glow".to_string(),
532                "sd-toast--glow-success".to_string(),
533                "sd-switch__track--glow-emerald".to_string(),
534                "sd-checkbox__box--glow".to_string(),
535            ),
536            PrimaryColor::Custom { .. } => {
537                let prefix = color.css_prefix();
538                (
539                    format!("glow-{}", prefix),
540                    format!("sd-btn--glow-{}", prefix),
541                    format!("sd-card--glow-{}", prefix),
542                    "".to_string(),
543                    format!("sd-badge--glow-{}", prefix),
544                    format!("sd-modal-dialog--glow-{}", prefix),
545                    "sd-alert--glow-info".to_string(),
546                    "sd-dropdown-menu--glow".to_string(),
547                    "sd-toast--glow-info".to_string(),
548                    format!("sd-switch__track--glow-{}", prefix),
549                    "sd-checkbox__box--glow".to_string(),
550                )
551            }
552        }
553    }
554
555    /// Tema di default Shadow Design (Ultraviolet Obsidian: Nero profondo con accenti viola).
556    pub fn shadow() -> Self {
557        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
558            Self::calculate_glows(&PrimaryColor::Violet, GlowMode::Subtle);
559
560        Self {
561            name: "Ultraviolet Shadow".to_string(),
562            primary_color: PrimaryColor::Violet,
563            radius_scale: RadiusScale::Medium,
564            elevation_style: ElevationStyle::NeonGlow,
565            glow_mode: GlowMode::Subtle,
566
567            surface_0: "#030305".to_string(),
568            surface_1: "#09090d".to_string(),
569            surface_2: "#111118".to_string(),
570            surface_3: "#181824".to_string(),
571
572            root: "sd-root".to_string(),
573            header: "sd-header".to_string(),
574            sidebar: "sd-sidebar".to_string(),
575            sidebar_link: "sd-sidebar-link".to_string(),
576            sidebar_link_active: "sd-sidebar-link--active".to_string(),
577            content: "sd-content".to_string(),
578            surface: "sd-surface".to_string(),
579            panel: "sd-panel".to_string(),
580            banner: "sd-banner".to_string(),
581
582            border: "border-purple-500/25".to_string(),
583            border_subtle: "border-purple-500/10".to_string(),
584            border_accent: "border-purple-500/40".to_string(),
585            text_accent: "text-purple-400".to_string(),
586            text_accent_bright: "text-purple-300".to_string(),
587            glow: g,
588
589            glow_button: gb,
590            glow_card: gc,
591            glow_input: gi,
592            glow_badge: gbd,
593            glow_modal: gm,
594            glow_alert: ga,
595            glow_dropdown: gd,
596            glow_notification: gn,
597            glow_switch: gs,
598            glow_checkbox: gcb,
599
600            button_primary: "sd-btn--primary".to_string(),
601            button_secondary: "sd-btn--secondary".to_string(),
602            button_outline: "sd-btn--outline".to_string(),
603            button_ghost: "sd-btn--ghost".to_string(),
604            button_danger: "sd-btn--danger".to_string(),
605            button_success: "sd-btn--success".to_string(),
606
607            card_base: "sd-card".to_string(),
608            card_header: "sd-card__header".to_string(),
609            card_footer: "sd-card__footer".to_string(),
610            card_shadow_none: "sd-card--shadow-none".to_string(),
611            card_shadow_sm: "sd-card--shadow-sm".to_string(),
612            card_shadow_md: "sd-card--shadow-md".to_string(),
613            card_shadow_lg: "sd-card--shadow-lg".to_string(),
614            card_shadow_xl: "sd-card--shadow-xl".to_string(),
615
616            input_base: "sd-input".to_string(),
617            input_normal_border: "".to_string(),
618            input_error_border: "sd-input--error".to_string(),
619
620            badge_default: "sd-badge--default".to_string(),
621            badge_primary: "sd-badge--primary".to_string(),
622            badge_success: "sd-badge--success".to_string(),
623            badge_warning: "sd-badge--warning".to_string(),
624            badge_danger: "sd-badge--danger".to_string(),
625            badge_outline: "sd-badge--outline".to_string(),
626
627            navbar_base: "sd-navbar".to_string(),
628
629            modal_backdrop: "sd-modal-backdrop".to_string(),
630            modal_dialog: "sd-modal-dialog".to_string(),
631            modal_header: "sd-modal-dialog__header".to_string(),
632            modal_close: "sd-modal-dialog__close".to_string(),
633
634            alert_info: "sd-alert--info".to_string(),
635            alert_success: "sd-alert--success".to_string(),
636            alert_warning: "sd-alert--warning".to_string(),
637            alert_danger: "sd-alert--danger".to_string(),
638            alert_title_info: "sd-alert__title--info".to_string(),
639            alert_title_success: "sd-alert__title--success".to_string(),
640            alert_title_warning: "sd-alert__title--warning".to_string(),
641            alert_title_danger: "sd-alert__title--danger".to_string(),
642
643            components: HashMap::new(),
644            class_overrides: HashMap::new(),
645        }
646    }
647
648    /// Preset Obsidian Titanium (Monocromatico con accenti grafite).
649    pub fn obsidian() -> Self {
650        let mut t = Self::shadow();
651        t.name = "Obsidian Titanium".to_string();
652        t.primary_color = PrimaryColor::Obsidian;
653        t.sidebar_link_active = "sd-sidebar-link--active-obsidian".to_string();
654        t.border_accent = "border-zinc-500/40".to_string();
655        t.text_accent = "text-zinc-300".to_string();
656        t.text_accent_bright = "text-white".to_string();
657        t.button_primary = "sd-btn--primary-obsidian".to_string();
658        t.badge_primary = "sd-badge--default".to_string();
659        t.banner = "sd-banner sd-banner--obsidian".to_string();
660
661        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
662            Self::calculate_glows(&PrimaryColor::Obsidian, t.glow_mode);
663        t.glow = g;
664        t.glow_button = gb;
665        t.glow_card = gc;
666        t.glow_input = gi;
667        t.glow_badge = gbd;
668        t.glow_modal = gm;
669        t.glow_alert = ga;
670        t.glow_dropdown = gd;
671        t.glow_notification = gn;
672        t.glow_switch = gs;
673        t.glow_checkbox = gcb;
674        t
675    }
676
677    /// Preset Crimson Phoenix.
678    pub fn crimson() -> Self {
679        let mut t = Self::shadow();
680        t.name = "Crimson Phoenix".to_string();
681        t.primary_color = PrimaryColor::Crimson;
682        t.sidebar_link_active = "sd-sidebar-link--active-crimson".to_string();
683        t.border_accent = "border-rose-500/40".to_string();
684        t.text_accent = "text-rose-400".to_string();
685        t.text_accent_bright = "text-rose-300".to_string();
686        t.button_primary = "sd-btn--primary-crimson".to_string();
687        t.badge_primary = "sd-badge--danger".to_string();
688        t.banner = "sd-banner sd-banner--crimson".to_string();
689
690        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
691            Self::calculate_glows(&PrimaryColor::Crimson, t.glow_mode);
692        t.glow = g;
693        t.glow_button = gb;
694        t.glow_card = gc;
695        t.glow_input = gi;
696        t.glow_badge = gbd;
697        t.glow_modal = gm;
698        t.glow_alert = ga;
699        t.glow_dropdown = gd;
700        t.glow_notification = gn;
701        t.glow_switch = gs;
702        t.glow_checkbox = gcb;
703        t
704    }
705
706    /// Preset Cyber Cyan.
707    pub fn cyan() -> Self {
708        let mut t = Self::shadow();
709        t.name = "Cyber Cyan".to_string();
710        t.primary_color = PrimaryColor::Cyan;
711        t.sidebar_link_active = "sd-sidebar-link--active-cyan".to_string();
712        t.border_accent = "border-cyan-500/40".to_string();
713        t.text_accent = "text-cyan-400".to_string();
714        t.text_accent_bright = "text-cyan-300".to_string();
715        t.button_primary = "sd-btn--primary-cyan".to_string();
716        t.badge_primary = "sd-badge--primary".to_string();
717        t.banner = "sd-banner sd-banner--cyan".to_string();
718
719        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
720            Self::calculate_glows(&PrimaryColor::Cyan, t.glow_mode);
721        t.glow = g;
722        t.glow_button = gb;
723        t.glow_card = gc;
724        t.glow_input = gi;
725        t.glow_badge = gbd;
726        t.glow_modal = gm;
727        t.glow_alert = ga;
728        t.glow_dropdown = gd;
729        t.glow_notification = gn;
730        t.glow_switch = gs;
731        t.glow_checkbox = gcb;
732        t
733    }
734
735    /// Preset Emerald Matrix.
736    pub fn emerald() -> Self {
737        let mut t = Self::shadow();
738        t.name = "Emerald Matrix".to_string();
739        t.primary_color = PrimaryColor::Emerald;
740        t.sidebar_link_active = "sd-sidebar-link--active-emerald".to_string();
741        t.border_accent = "border-emerald-500/40".to_string();
742        t.text_accent = "text-emerald-400".to_string();
743        t.text_accent_bright = "text-emerald-300".to_string();
744        t.button_primary = "sd-btn--primary-emerald".to_string();
745        t.badge_primary = "sd-badge--success".to_string();
746        t.banner = "sd-banner sd-banner--emerald".to_string();
747
748        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
749            Self::calculate_glows(&PrimaryColor::Emerald, t.glow_mode);
750        t.glow = g;
751        t.glow_button = gb;
752        t.glow_card = gc;
753        t.glow_input = gi;
754        t.glow_badge = gbd;
755        t.glow_modal = gm;
756        t.glow_alert = ga;
757        t.glow_dropdown = gd;
758        t.glow_notification = gn;
759        t.glow_switch = gs;
760        t.glow_checkbox = gcb;
761        t
762    }
763
764    /// Nuovo Preset Nativo: Cartoon Pop / Manga Neo-Brutalist.
765    pub fn cartoon() -> Self {
766        let mut t = Self::shadow();
767        t.name = "Cartoon Pop".to_string();
768        t.primary_color = PrimaryColor::custom_with_prefix("Cartoon Yellow", "#FFCC00", "cartoon");
769        t.radius_scale = RadiusScale::Sharp;
770        t.elevation_style = ElevationStyle::HardBlock;
771        t.glow_mode = GlowMode::Off;
772
773        t.surface_0 = "#FBFAF3".to_string();
774        t.surface_1 = "#FFFFFF".to_string();
775        t.surface_2 = "#F4F4F4".to_string();
776        t.surface_3 = "#EAE6DF".to_string();
777
778        t.root = "sd-root--cartoon".to_string();
779        t.header = "sd-header--cartoon".to_string();
780        t.border = "border-3 border-black".to_string();
781        t.border_accent = "border-4 border-black".to_string();
782        t.text_accent = "text-[#FF3333]".to_string();
783        t.text_accent_bright = "text-[#FFCC00]".to_string();
784
785        t.button_primary = "sd-btn--primary-cartoon".to_string();
786        t.button_secondary = "sd-btn--secondary-cartoon".to_string();
787        t.card_base = "sd-card--comic".to_string();
788        t.badge_primary = "sd-badge--primary-cartoon".to_string();
789
790        // Livello 2: Configurazione dei componenti
791        let mut btn_override = ComponentOverride::new();
792        btn_override.base_class = Some("sd-btn--comic".to_string());
793        btn_override
794            .variant_classes
795            .insert("primary".to_string(), "sd-btn--primary-cartoon".to_string());
796        btn_override
797            .variant_classes
798            .insert("secondary".to_string(), "sd-btn--secondary-cartoon".to_string());
799        t.components.insert("button".to_string(), btn_override);
800
801        let mut card_override = ComponentOverride::new();
802        card_override.base_class = Some("sd-card--comic".to_string());
803        t.components.insert("card".to_string(), card_override);
804
805        let mut badge_override = ComponentOverride::new();
806        badge_override.base_class = Some("sd-badge--comic".to_string());
807        t.components.insert("badge".to_string(), badge_override);
808
809        t
810    }
811
812    /// Applica un colore primario aggiornando tutti i token cromatici associati.
813    pub fn apply_primary_color(&mut self, color: PrimaryColor) {
814        let current_radius = self.radius_scale;
815        let current_elevation = self.elevation_style;
816        let current_glow = self.glow_mode;
817        let mut updated = match &color {
818            PrimaryColor::Violet => Self::shadow(),
819            PrimaryColor::Obsidian => Self::obsidian(),
820            PrimaryColor::Crimson => Self::crimson(),
821            PrimaryColor::Cyan => Self::cyan(),
822            PrimaryColor::Emerald => Self::emerald(),
823            PrimaryColor::Custom { .. } => {
824                let mut custom_t = Self::shadow();
825                custom_t.name = color.name().to_string();
826                custom_t.primary_color = color.clone();
827                let prefix = color.css_prefix();
828                custom_t.button_primary = format!("sd-btn--primary-{}", prefix);
829                custom_t
830            }
831        };
832        updated.radius_scale = current_radius;
833        updated.elevation_style = current_elevation;
834        updated.apply_glow_mode(current_glow);
835        *self = updated;
836    }
837
838    /// Applica una scala globale del raggio di curvatura.
839    pub fn apply_radius_scale(&mut self, radius: RadiusScale) {
840        self.radius_scale = radius;
841    }
842
843    /// Applica uno stile di elevazione globale.
844    pub fn apply_elevation_style(&mut self, elevation: ElevationStyle) {
845        self.elevation_style = elevation;
846    }
847
848    /// Applica una modalità di intensità per il micro-glow, ricalcolando tutti i componenti.
849    pub fn apply_glow_mode(&mut self, glow: GlowMode) {
850        self.glow_mode = glow;
851        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
852            Self::calculate_glows(&self.primary_color, glow);
853        self.glow = g;
854        self.glow_button = gb;
855        self.glow_card = gc;
856        self.glow_input = gi;
857        self.glow_badge = gbd;
858        self.glow_modal = gm;
859        self.glow_alert = ga;
860        self.glow_dropdown = gd;
861        self.glow_notification = gn;
862        self.glow_switch = gs;
863        self.glow_checkbox = gcb;
864    }
865
866    /// Restituisce la classe di glow configurata per un dato componente.
867    pub fn component_glow(&self, component: &str) -> &str {
868        match component {
869            "button" => &self.glow_button,
870            "card" => &self.glow_card,
871            "input" => &self.glow_input,
872            "badge" => &self.glow_badge,
873            "modal" => &self.glow_modal,
874            "alert" => &self.glow_alert,
875            "dropdown" => &self.glow_dropdown,
876            "notification" => &self.glow_notification,
877            "switch" => &self.glow_switch,
878            "checkbox" => &self.glow_checkbox,
879            _ => &self.glow,
880        }
881    }
882
883    /// Risolve se il glow è attivo per un componente tenendo conto dell'override opzionale.
884    pub fn is_component_glow_enabled(&self, component: &str, override_glow: Option<bool>) -> bool {
885        match override_glow {
886            Some(enabled) => enabled,
887            None => !self.component_glow(component).is_empty(),
888        }
889    }
890
891    // Builder methods fluenti retrocompatibili
892    pub fn with_name(mut self, name: impl Into<String>) -> Self {
893        self.name = name.into();
894        self
895    }
896
897    pub fn with_primary(mut self, button_primary: impl Into<String>) -> Self {
898        self.button_primary = button_primary.into();
899        self
900    }
901
902    pub fn with_card_base(mut self, card_base: impl Into<String>) -> Self {
903        self.card_base = card_base.into();
904        self
905    }
906
907    pub fn with_glow(mut self, glow: impl Into<String>) -> Self {
908        self.glow = glow.into();
909        self
910    }
911
912    pub fn with_elevation(mut self, elevation: ElevationStyle) -> Self {
913        self.elevation_style = elevation;
914        self
915    }
916
917    pub fn with_component_override(
918        mut self,
919        component: impl Into<String>,
920        component_override: ComponentOverride,
921    ) -> Self {
922        self.components.insert(component.into(), component_override);
923        self
924    }
925
926    pub fn with_class_override(
927        mut self,
928        target_class: impl Into<String>,
929        replacement_or_extra: impl Into<String>,
930    ) -> Self {
931        self.class_overrides
932            .insert(target_class.into(), replacement_or_extra.into());
933        self
934    }
935
936    // Helper per componenti
937    #[cfg(feature = "button")]
938    pub fn button_variant_classes(&self, variant: ButtonVariant) -> &str {
939        match variant {
940            ButtonVariant::Primary => &self.button_primary,
941            ButtonVariant::Secondary => &self.button_secondary,
942            ButtonVariant::Outline => &self.button_outline,
943            ButtonVariant::Ghost => &self.button_ghost,
944            ButtonVariant::Danger => &self.button_danger,
945            ButtonVariant::Success => &self.button_success,
946        }
947    }
948
949    #[cfg(feature = "card")]
950    pub fn card_shadow_classes(&self, shadow: CardShadow) -> &str {
951        match shadow {
952            CardShadow::None => &self.card_shadow_none,
953            CardShadow::Sm => &self.card_shadow_sm,
954            CardShadow::Md => &self.card_shadow_md,
955            CardShadow::Lg => &self.card_shadow_lg,
956            CardShadow::Xl => &self.card_shadow_xl,
957        }
958    }
959
960    #[cfg(feature = "badge")]
961    pub fn badge_variant_classes(&self, variant: BadgeVariant) -> &str {
962        match variant {
963            BadgeVariant::Default => &self.badge_default,
964            BadgeVariant::Primary => &self.badge_primary,
965            BadgeVariant::Success => &self.badge_success,
966            BadgeVariant::Warning => &self.badge_warning,
967            BadgeVariant::Danger => &self.badge_danger,
968            BadgeVariant::Outline => &self.badge_outline,
969        }
970    }
971
972    #[cfg(feature = "alert")]
973    pub fn alert_variant_classes(&self, variant: AlertVariant) -> &str {
974        match variant {
975            AlertVariant::Info => &self.alert_info,
976            AlertVariant::Success => &self.alert_success,
977            AlertVariant::Warning => &self.alert_warning,
978            AlertVariant::Danger => &self.alert_danger,
979        }
980    }
981
982    #[cfg(feature = "alert")]
983    pub fn alert_title_classes(&self, variant: AlertVariant) -> &str {
984        match variant {
985            AlertVariant::Info => &self.alert_title_info,
986            AlertVariant::Success => &self.alert_title_success,
987            AlertVariant::Warning => &self.alert_title_warning,
988            AlertVariant::Danger => &self.alert_title_danger,
989        }
990    }
991}
992
993/// Builder avanzato per costruire temi in modo fluido ed ergonomico.
994#[derive(Clone, Default)]
995pub struct ThemeBuilder {
996    theme: Theme,
997}
998
999impl ThemeBuilder {
1000    pub fn new() -> Self {
1001        Self::default()
1002    }
1003
1004    pub fn name(mut self, name: impl Into<String>) -> Self {
1005        self.theme.name = name.into();
1006        self
1007    }
1008
1009    pub fn primary_color(mut self, color: PrimaryColor) -> Self {
1010        self.theme.apply_primary_color(color);
1011        self
1012    }
1013
1014    pub fn radius_scale(mut self, radius: RadiusScale) -> Self {
1015        self.theme.apply_radius_scale(radius);
1016        self
1017    }
1018
1019    pub fn elevation_style(mut self, elevation: ElevationStyle) -> Self {
1020        self.theme.apply_elevation_style(elevation);
1021        self
1022    }
1023
1024    pub fn glow_mode(mut self, glow: GlowMode) -> Self {
1025        self.theme.apply_glow_mode(glow);
1026        self
1027    }
1028
1029    pub fn surface_0(mut self, val: impl Into<String>) -> Self {
1030        self.theme.surface_0 = val.into();
1031        self
1032    }
1033
1034    pub fn surface_1(mut self, val: impl Into<String>) -> Self {
1035        self.theme.surface_1 = val.into();
1036        self
1037    }
1038
1039    pub fn surface_2(mut self, val: impl Into<String>) -> Self {
1040        self.theme.surface_2 = val.into();
1041        self
1042    }
1043
1044    pub fn surface_3(mut self, val: impl Into<String>) -> Self {
1045        self.theme.surface_3 = val.into();
1046        self
1047    }
1048
1049    /// Personalizzazione Livello 2 per uno specifico componente.
1050    pub fn override_component(
1051        mut self,
1052        component: impl Into<String>,
1053        config_fn: impl FnOnce(ComponentOverride) -> ComponentOverride,
1054    ) -> Self {
1055        let key = component.into();
1056        let existing = self.theme.components.remove(&key).unwrap_or_default();
1057        let updated = config_fn(existing);
1058        self.theme.components.insert(key, updated);
1059        self
1060    }
1061
1062    /// Personalizzazione Livello 2 per una specifica classe target.
1063    pub fn override_class(
1064        mut self,
1065        target_class: impl Into<String>,
1066        modifier: impl Into<String>,
1067    ) -> Self {
1068        self.theme
1069            .class_overrides
1070            .insert(target_class.into(), modifier.into());
1071        self
1072    }
1073
1074    pub fn build(self) -> Theme {
1075        self.theme
1076    }
1077}
1078
1079/// Provider per iniettare il tema reattivo nel Context Dioxus.
1080#[component]
1081pub fn ThemeProvider(#[props(default)] theme: Option<Signal<Theme>>, children: Element) -> Element {
1082    let current = theme.unwrap_or_else(|| use_signal(Theme::default));
1083    use_context_provider(|| current);
1084    rsx! { {children} }
1085}
1086
1087/// Hook per accedere e modificare il tema attivo.
1088pub fn use_theme() -> Signal<Theme> {
1089    match try_use_context::<Signal<Theme>>() {
1090        Some(s) => s,
1091        None => use_context_provider(|| Signal::new(Theme::default())),
1092    }
1093}