Skip to main content

shadow_design/
theme.rs

1use dioxus::prelude::*;
2
3#[cfg(feature = "alert")]
4use crate::alert::AlertVariant;
5#[cfg(feature = "badge")]
6use crate::badge::BadgeVariant;
7#[cfg(feature = "button")]
8use crate::button::ButtonVariant;
9#[cfg(feature = "card")]
10use crate::card::CardShadow;
11
12/// Palette di colori primari in stile Mantine.
13#[derive(Clone, Copy, PartialEq, Debug, Default)]
14pub enum PrimaryColor {
15    #[default]
16    Violet,
17    Obsidian,
18    Crimson,
19    Cyan,
20    Emerald,
21}
22
23impl PrimaryColor {
24    pub fn name(&self) -> &'static str {
25        match self {
26            PrimaryColor::Violet => "Violet",
27            PrimaryColor::Obsidian => "Obsidian",
28            PrimaryColor::Crimson => "Crimson",
29            PrimaryColor::Cyan => "Cyan",
30            PrimaryColor::Emerald => "Emerald",
31        }
32    }
33
34    pub fn hex(&self) -> &'static str {
35        match self {
36            PrimaryColor::Violet => "#3b0764",
37            PrimaryColor::Obsidian => "#ffffff",
38            PrimaryColor::Crimson => "#ff1744",
39            PrimaryColor::Cyan => "#00f0ff",
40            PrimaryColor::Emerald => "#00ff9d",
41        }
42    }
43}
44
45/// Scala globale per i raggi di curvatura (Border Radius).
46#[derive(Clone, Copy, PartialEq, Debug, Default)]
47pub enum RadiusScale {
48    Sharp,
49    #[default]
50    Medium,
51    Soft,
52}
53
54impl RadiusScale {
55    pub fn name(&self) -> &'static str {
56        match self {
57            RadiusScale::Sharp => "Sharp (2px)",
58            RadiusScale::Medium => "Medium (8px)",
59            RadiusScale::Soft => "Soft (14px)",
60        }
61    }
62
63    pub fn sm(&self) -> &'static str {
64        match self {
65            RadiusScale::Sharp => "sd-radius-sharp",
66            RadiusScale::Medium => "rounded-md",
67            RadiusScale::Soft => "rounded-lg",
68        }
69    }
70
71    pub fn md(&self) -> &'static str {
72        match self {
73            RadiusScale::Sharp => "sd-radius-sharp",
74            RadiusScale::Medium => "rounded-lg",
75            RadiusScale::Soft => "rounded-xl",
76        }
77    }
78
79    pub fn lg(&self) -> &'static str {
80        match self {
81            RadiusScale::Sharp => "sd-radius-sharp",
82            RadiusScale::Medium => "rounded-xl",
83            RadiusScale::Soft => "rounded-2xl",
84        }
85    }
86}
87
88/// Intensità del micro-glow ottico.
89#[derive(Clone, Copy, PartialEq, Debug, Default)]
90pub enum GlowMode {
91    #[default]
92    Subtle,
93    Expressive,
94    Off,
95}
96
97/// Struttura centrale del tema per Shadow Design ispirata a Mantine / Craft UI.
98#[derive(Clone, PartialEq, Debug)]
99pub struct Theme {
100    pub name: String,
101    pub primary_color: PrimaryColor,
102    pub radius_scale: RadiusScale,
103    pub glow_mode: GlowMode,
104
105    // Token di Superficie a 4 Livelli (Void, Base, Elevated, Overlay)
106    pub surface_0: String,
107    pub surface_1: String,
108    pub surface_2: String,
109    pub surface_3: String,
110
111    // Layout e Struttura
112    pub root: String,
113    pub header: String,
114    pub sidebar: String,
115    pub sidebar_link: String,
116    pub sidebar_link_active: String,
117    pub content: String,
118    pub surface: String,
119    pub panel: String,
120    pub banner: String,
121
122    // Accenti e Bordi Hairline
123    pub border: String,
124    pub border_subtle: String,
125    pub border_accent: String,
126    pub text_accent: String,
127    pub text_accent_bright: String,
128    pub glow: String,
129
130    // Glow e Ombre Specifiche per Componente (Personalizzabili)
131    pub glow_button: String,
132    pub glow_card: String,
133    pub glow_input: String,
134    pub glow_badge: String,
135    pub glow_modal: String,
136    pub glow_alert: String,
137    pub glow_dropdown: String,
138    pub glow_notification: String,
139    pub glow_switch: String,
140    pub glow_checkbox: String,
141
142    // Componente Button
143    pub button_primary: String,
144    pub button_secondary: String,
145    pub button_outline: String,
146    pub button_ghost: String,
147    pub button_danger: String,
148    pub button_success: String,
149
150    // Componente Card
151    pub card_base: String,
152    pub card_header: String,
153    pub card_footer: String,
154    pub card_shadow_none: String,
155    pub card_shadow_sm: String,
156    pub card_shadow_md: String,
157    pub card_shadow_lg: String,
158    pub card_shadow_xl: String,
159
160    // Componente TextInput
161    pub input_base: String,
162    pub input_normal_border: String,
163    pub input_error_border: String,
164
165    // Componente Badge
166    pub badge_default: String,
167    pub badge_primary: String,
168    pub badge_success: String,
169    pub badge_warning: String,
170    pub badge_danger: String,
171    pub badge_outline: String,
172
173    // Componente Navbar
174    pub navbar_base: String,
175
176    // Componente Modal
177    pub modal_backdrop: String,
178    pub modal_dialog: String,
179    pub modal_header: String,
180    pub modal_close: String,
181
182    // Componente Alert
183    pub alert_info: String,
184    pub alert_success: String,
185    pub alert_warning: String,
186    pub alert_danger: String,
187    pub alert_title_info: String,
188    pub alert_title_success: String,
189    pub alert_title_warning: String,
190    pub alert_title_danger: String,
191}
192
193impl Default for Theme {
194    fn default() -> Self {
195        Self::shadow()
196    }
197}
198
199impl Theme {
200    /// Calcola i valori di glow per ogni componente in base al colore primario e alla modalità.
201    fn calculate_glows(
202        color: PrimaryColor,
203        mode: GlowMode,
204    ) -> (
205        String,
206        String,
207        String,
208        String,
209        String,
210        String,
211        String,
212        String,
213        String,
214        String,
215        String,
216    ) {
217        if mode == GlowMode::Off {
218            return (
219                String::new(),
220                String::new(),
221                String::new(),
222                String::new(),
223                String::new(),
224                String::new(),
225                String::new(),
226                String::new(),
227                String::new(),
228                String::new(),
229                String::new(),
230            );
231        }
232
233        match color {
234            PrimaryColor::Violet => (
235                "glow-violet".to_string(),
236                "sd-btn--glow".to_string(),
237                "sd-card--hover-glow".to_string(),
238                "".to_string(),
239                "sd-badge--glow".to_string(),
240                "sd-modal-dialog--glow".to_string(),
241                "sd-alert--glow-info".to_string(),
242                "sd-dropdown-menu--glow".to_string(),
243                "sd-toast--glow-info".to_string(),
244                "sd-switch__track--glow".to_string(),
245                "sd-checkbox__box--glow".to_string(),
246            ),
247            PrimaryColor::Obsidian => (
248                "".to_string(),
249                "sd-btn--glow-obsidian".to_string(),
250                "sd-card--hover-glow".to_string(),
251                "".to_string(),
252                "sd-badge--glow".to_string(),
253                "sd-modal-dialog--glow".to_string(),
254                "sd-alert--glow-info".to_string(),
255                "sd-dropdown-menu--glow".to_string(),
256                "sd-toast--glow-info".to_string(),
257                "sd-switch__track--glow-obsidian".to_string(),
258                "sd-checkbox__box--glow".to_string(),
259            ),
260            PrimaryColor::Crimson => (
261                "glow-crimson".to_string(),
262                "sd-btn--glow-crimson".to_string(),
263                "sd-card--hover-glow".to_string(),
264                "".to_string(),
265                "sd-badge--glow".to_string(),
266                "sd-modal-dialog--glow".to_string(),
267                "sd-alert--glow-danger".to_string(),
268                "sd-dropdown-menu--glow".to_string(),
269                "sd-toast--glow-danger".to_string(),
270                "sd-switch__track--glow-crimson".to_string(),
271                "sd-checkbox__box--glow".to_string(),
272            ),
273            PrimaryColor::Cyan => (
274                "glow-cyan".to_string(),
275                "sd-btn--glow-cyan".to_string(),
276                "sd-card--hover-glow".to_string(),
277                "".to_string(),
278                "sd-badge--glow".to_string(),
279                "sd-modal-dialog--glow".to_string(),
280                "sd-alert--glow-info".to_string(),
281                "sd-dropdown-menu--glow".to_string(),
282                "sd-toast--glow-info".to_string(),
283                "sd-switch__track--glow-cyan".to_string(),
284                "sd-checkbox__box--glow".to_string(),
285            ),
286            PrimaryColor::Emerald => (
287                "glow-emerald".to_string(),
288                "sd-btn--glow-emerald".to_string(),
289                "sd-card--hover-glow".to_string(),
290                "".to_string(),
291                "sd-badge--glow".to_string(),
292                "sd-modal-dialog--glow".to_string(),
293                "sd-alert--glow-success".to_string(),
294                "sd-dropdown-menu--glow".to_string(),
295                "sd-toast--glow-success".to_string(),
296                "sd-switch__track--glow-emerald".to_string(),
297                "sd-checkbox__box--glow".to_string(),
298            ),
299        }
300    }
301
302    /// Tema di default Shadow Design (Ultraviolet Obsidian: Nero profondo con accenti viola e specular highlights).
303    pub fn shadow() -> Self {
304        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
305            Self::calculate_glows(PrimaryColor::Violet, GlowMode::Subtle);
306
307        Self {
308            name: "Ultraviolet Shadow".to_string(),
309            primary_color: PrimaryColor::Violet,
310            radius_scale: RadiusScale::Medium,
311            glow_mode: GlowMode::Subtle,
312
313            surface_0: "#030305".to_string(),
314            surface_1: "#09090d".to_string(),
315            surface_2: "#111118".to_string(),
316            surface_3: "#181824".to_string(),
317
318            root: "sd-root".to_string(),
319            header: "sd-header".to_string(),
320            sidebar: "sd-sidebar".to_string(),
321            sidebar_link: "sd-sidebar-link".to_string(),
322            sidebar_link_active: "sd-sidebar-link--active".to_string(),
323            content: "sd-content".to_string(),
324            surface: "sd-surface".to_string(),
325            panel: "sd-panel".to_string(),
326            banner: "sd-banner".to_string(),
327
328            border: "border-white/[0.08]".to_string(),
329            border_subtle: "border-white/[0.05]".to_string(),
330            border_accent: "border-purple-500/40".to_string(),
331            text_accent: "text-purple-400".to_string(),
332            text_accent_bright: "text-purple-300".to_string(),
333            glow: g,
334
335            glow_button: gb,
336            glow_card: gc,
337            glow_input: gi,
338            glow_badge: gbd,
339            glow_modal: gm,
340            glow_alert: ga,
341            glow_dropdown: gd,
342            glow_notification: gn,
343            glow_switch: gs,
344            glow_checkbox: gcb,
345
346            button_primary: "sd-btn--primary".to_string(),
347            button_secondary: "sd-btn--secondary".to_string(),
348            button_outline: "sd-btn--outline".to_string(),
349            button_ghost: "sd-btn--ghost".to_string(),
350            button_danger: "sd-btn--danger".to_string(),
351            button_success: "sd-btn--success".to_string(),
352
353            card_base: "sd-card".to_string(),
354            card_header: "sd-card__header".to_string(),
355            card_footer: "sd-card__footer".to_string(),
356            card_shadow_none: "sd-card--shadow-none".to_string(),
357            card_shadow_sm: "sd-card--shadow-sm".to_string(),
358            card_shadow_md: "sd-card--shadow-md".to_string(),
359            card_shadow_lg: "sd-card--shadow-lg".to_string(),
360            card_shadow_xl: "sd-card--shadow-xl".to_string(),
361
362            input_base: "sd-input".to_string(),
363            input_normal_border: "".to_string(),
364            input_error_border: "sd-input--error".to_string(),
365
366            badge_default: "sd-badge--default".to_string(),
367            badge_primary: "sd-badge--primary".to_string(),
368            badge_success: "sd-badge--success".to_string(),
369            badge_warning: "sd-badge--warning".to_string(),
370            badge_danger: "sd-badge--danger".to_string(),
371            badge_outline: "sd-badge--outline".to_string(),
372
373            navbar_base: "sd-navbar".to_string(),
374
375            modal_backdrop: "sd-modal-backdrop".to_string(),
376            modal_dialog: "sd-modal-dialog".to_string(),
377            modal_header: "sd-modal-dialog__header".to_string(),
378            modal_close: "sd-modal-dialog__close".to_string(),
379
380            alert_info: "sd-alert--info".to_string(),
381            alert_success: "sd-alert--success".to_string(),
382            alert_warning: "sd-alert--warning".to_string(),
383            alert_danger: "sd-alert--danger".to_string(),
384            alert_title_info: "sd-alert__title--info".to_string(),
385            alert_title_success: "sd-alert__title--success".to_string(),
386            alert_title_warning: "sd-alert__title--warning".to_string(),
387            alert_title_danger: "sd-alert__title--danger".to_string(),
388        }
389    }
390
391    /// Preset Obsidian Titanium (Monocromatico rigoroso con accenti grafite e metallo).
392    pub fn obsidian() -> Self {
393        let mut t = Self::shadow();
394        t.name = "Obsidian Titanium".to_string();
395        t.primary_color = PrimaryColor::Obsidian;
396        t.sidebar_link_active = "sd-sidebar-link--active-obsidian".to_string();
397        t.border_accent = "border-zinc-500/40".to_string();
398        t.text_accent = "text-zinc-300".to_string();
399        t.text_accent_bright = "text-white".to_string();
400        t.button_primary = "sd-btn--primary-obsidian".to_string();
401        t.badge_primary = "sd-badge--default".to_string();
402        t.banner = "sd-banner sd-banner--obsidian".to_string();
403
404        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
405            Self::calculate_glows(PrimaryColor::Obsidian, t.glow_mode);
406        t.glow = g;
407        t.glow_button = gb;
408        t.glow_card = gc;
409        t.glow_input = gi;
410        t.glow_badge = gbd;
411        t.glow_modal = gm;
412        t.glow_alert = ga;
413        t.glow_dropdown = gd;
414        t.glow_notification = gn;
415        t.glow_switch = gs;
416        t.glow_checkbox = gcb;
417        t
418    }
419
420    /// Preset Crimson Phoenix (Nero profondo con fiamme cremisi fenice).
421    pub fn crimson() -> Self {
422        let mut t = Self::shadow();
423        t.name = "Crimson Phoenix".to_string();
424        t.primary_color = PrimaryColor::Crimson;
425        t.sidebar_link_active = "sd-sidebar-link--active-crimson".to_string();
426        t.border_accent = "border-rose-500/40".to_string();
427        t.text_accent = "text-rose-400".to_string();
428        t.text_accent_bright = "text-rose-300".to_string();
429        t.button_primary = "sd-btn--primary-crimson".to_string();
430        t.badge_primary = "sd-badge--danger".to_string();
431        t.banner = "sd-banner sd-banner--crimson".to_string();
432
433        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
434            Self::calculate_glows(PrimaryColor::Crimson, t.glow_mode);
435        t.glow = g;
436        t.glow_button = gb;
437        t.glow_card = gc;
438        t.glow_input = gi;
439        t.glow_badge = gbd;
440        t.glow_modal = gm;
441        t.glow_alert = ga;
442        t.glow_dropdown = gd;
443        t.glow_notification = gn;
444        t.glow_switch = gs;
445        t.glow_checkbox = gcb;
446        t
447    }
448
449    /// Preset Cyber Cyan (Nero profondo con accenti ciano elettrico ad alto contrasto).
450    pub fn cyan() -> Self {
451        let mut t = Self::shadow();
452        t.name = "Cyber Cyan".to_string();
453        t.primary_color = PrimaryColor::Cyan;
454        t.sidebar_link_active = "sd-sidebar-link--active-cyan".to_string();
455        t.border_accent = "border-cyan-500/40".to_string();
456        t.text_accent = "text-cyan-400".to_string();
457        t.text_accent_bright = "text-cyan-300".to_string();
458        t.button_primary = "sd-btn--primary-cyan".to_string();
459        t.badge_primary = "sd-badge--primary".to_string();
460        t.banner = "sd-banner sd-banner--cyan".to_string();
461
462        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
463            Self::calculate_glows(PrimaryColor::Cyan, t.glow_mode);
464        t.glow = g;
465        t.glow_button = gb;
466        t.glow_card = gc;
467        t.glow_input = gi;
468        t.glow_badge = gbd;
469        t.glow_modal = gm;
470        t.glow_alert = ga;
471        t.glow_dropdown = gd;
472        t.glow_notification = gn;
473        t.glow_switch = gs;
474        t.glow_checkbox = gcb;
475        t
476    }
477
478    /// Preset Emerald Matrix (Nero profondo con verde smeraldo ad altissima nitidezza).
479    pub fn emerald() -> Self {
480        let mut t = Self::shadow();
481        t.name = "Emerald Matrix".to_string();
482        t.primary_color = PrimaryColor::Emerald;
483        t.sidebar_link_active = "sd-sidebar-link--active-emerald".to_string();
484        t.border_accent = "border-emerald-500/40".to_string();
485        t.text_accent = "text-emerald-400".to_string();
486        t.text_accent_bright = "text-emerald-300".to_string();
487        t.button_primary = "sd-btn--primary-emerald".to_string();
488        t.badge_primary = "sd-badge--success".to_string();
489        t.banner = "sd-banner sd-banner--emerald".to_string();
490
491        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
492            Self::calculate_glows(PrimaryColor::Emerald, t.glow_mode);
493        t.glow = g;
494        t.glow_button = gb;
495        t.glow_card = gc;
496        t.glow_input = gi;
497        t.glow_badge = gbd;
498        t.glow_modal = gm;
499        t.glow_alert = ga;
500        t.glow_dropdown = gd;
501        t.glow_notification = gn;
502        t.glow_switch = gs;
503        t.glow_checkbox = gcb;
504        t
505    }
506
507    /// Applica un colore primario aggiornando tutti i token cromatici associati.
508    pub fn apply_primary_color(&mut self, color: PrimaryColor) {
509        let current_radius = self.radius_scale;
510        let current_glow = self.glow_mode;
511        let mut updated = match color {
512            PrimaryColor::Violet => Self::shadow(),
513            PrimaryColor::Obsidian => Self::obsidian(),
514            PrimaryColor::Crimson => Self::crimson(),
515            PrimaryColor::Cyan => Self::cyan(),
516            PrimaryColor::Emerald => Self::emerald(),
517        };
518        updated.radius_scale = current_radius;
519        updated.apply_glow_mode(current_glow);
520        *self = updated;
521    }
522
523    /// Applica una scala globale del raggio di curvatura.
524    pub fn apply_radius_scale(&mut self, radius: RadiusScale) {
525        self.radius_scale = radius;
526    }
527
528    /// Applica una modalità di intensità per il micro-glow, ricalcolando tutti i componenti.
529    pub fn apply_glow_mode(&mut self, glow: GlowMode) {
530        self.glow_mode = glow;
531        let (g, gb, gc, gi, gbd, gm, ga, gd, gn, gs, gcb) =
532            Self::calculate_glows(self.primary_color, glow);
533        self.glow = g;
534        self.glow_button = gb;
535        self.glow_card = gc;
536        self.glow_input = gi;
537        self.glow_badge = gbd;
538        self.glow_modal = gm;
539        self.glow_alert = ga;
540        self.glow_dropdown = gd;
541        self.glow_notification = gn;
542        self.glow_switch = gs;
543        self.glow_checkbox = gcb;
544    }
545
546    /// Restituisce la classe di glow configurata per un dato componente.
547    pub fn component_glow(&self, component: &str) -> &str {
548        match component {
549            "button" => &self.glow_button,
550            "card" => &self.glow_card,
551            "input" => &self.glow_input,
552            "badge" => &self.glow_badge,
553            "modal" => &self.glow_modal,
554            "alert" => &self.glow_alert,
555            "dropdown" => &self.glow_dropdown,
556            "notification" => &self.glow_notification,
557            "switch" => &self.glow_switch,
558            "checkbox" => &self.glow_checkbox,
559            _ => &self.glow,
560        }
561    }
562
563    /// Risolve se il glow è attivo per un componente tenendo conto dell'override opzionale.
564    pub fn is_component_glow_enabled(&self, component: &str, override_glow: Option<bool>) -> bool {
565        match override_glow {
566            Some(enabled) => enabled,
567            None => !self.component_glow(component).is_empty(),
568        }
569    }
570
571    // Builder methods
572    pub fn with_name(mut self, name: impl Into<String>) -> Self {
573        self.name = name.into();
574        self
575    }
576
577    pub fn with_primary(mut self, button_primary: impl Into<String>) -> Self {
578        self.button_primary = button_primary.into();
579        self
580    }
581
582    pub fn with_card_base(mut self, card_base: impl Into<String>) -> Self {
583        self.card_base = card_base.into();
584        self
585    }
586
587    pub fn with_glow(mut self, glow: impl Into<String>) -> Self {
588        self.glow = glow.into();
589        self
590    }
591
592    pub fn with_component_glow(mut self, component: &str, glow: impl Into<String>) -> Self {
593        let val = glow.into();
594        match component {
595            "button" => self.glow_button = val,
596            "card" => self.glow_card = val,
597            "input" => self.glow_input = val,
598            "badge" => self.glow_badge = val,
599            "modal" => self.glow_modal = val,
600            "alert" => self.glow_alert = val,
601            "dropdown" => self.glow_dropdown = val,
602            "notification" => self.glow_notification = val,
603            "switch" => self.glow_switch = val,
604            "checkbox" => self.glow_checkbox = val,
605            _ => self.glow = val,
606        }
607        self
608    }
609
610    // Helper per componenti
611    #[cfg(feature = "button")]
612    pub fn button_variant_classes(&self, variant: ButtonVariant) -> &str {
613        match variant {
614            ButtonVariant::Primary => &self.button_primary,
615            ButtonVariant::Secondary => &self.button_secondary,
616            ButtonVariant::Outline => &self.button_outline,
617            ButtonVariant::Ghost => &self.button_ghost,
618            ButtonVariant::Danger => &self.button_danger,
619            ButtonVariant::Success => &self.button_success,
620        }
621    }
622
623    #[cfg(feature = "card")]
624    pub fn card_shadow_classes(&self, shadow: CardShadow) -> &str {
625        match shadow {
626            CardShadow::None => &self.card_shadow_none,
627            CardShadow::Sm => &self.card_shadow_sm,
628            CardShadow::Md => &self.card_shadow_md,
629            CardShadow::Lg => &self.card_shadow_lg,
630            CardShadow::Xl => &self.card_shadow_xl,
631        }
632    }
633
634    #[cfg(feature = "badge")]
635    pub fn badge_variant_classes(&self, variant: BadgeVariant) -> &str {
636        match variant {
637            BadgeVariant::Default => &self.badge_default,
638            BadgeVariant::Primary => &self.badge_primary,
639            BadgeVariant::Success => &self.badge_success,
640            BadgeVariant::Warning => &self.badge_warning,
641            BadgeVariant::Danger => &self.badge_danger,
642            BadgeVariant::Outline => &self.badge_outline,
643        }
644    }
645
646    #[cfg(feature = "alert")]
647    pub fn alert_variant_classes(&self, variant: AlertVariant) -> &str {
648        match variant {
649            AlertVariant::Info => &self.alert_info,
650            AlertVariant::Success => &self.alert_success,
651            AlertVariant::Warning => &self.alert_warning,
652            AlertVariant::Danger => &self.alert_danger,
653        }
654    }
655
656    #[cfg(feature = "alert")]
657    pub fn alert_title_classes(&self, variant: AlertVariant) -> &str {
658        match variant {
659            AlertVariant::Info => &self.alert_title_info,
660            AlertVariant::Success => &self.alert_title_success,
661            AlertVariant::Warning => &self.alert_title_warning,
662            AlertVariant::Danger => &self.alert_title_danger,
663        }
664    }
665}
666
667/// Provider per iniettare il tema reattivo nel Context Dioxus.
668#[component]
669pub fn ThemeProvider(#[props(default)] theme: Option<Signal<Theme>>, children: Element) -> Element {
670    let current = theme.unwrap_or_else(|| use_signal(Theme::default));
671    use_context_provider(|| current);
672    rsx! { {children} }
673}
674
675/// Hook per accedere e modificare il tema attivo.
676pub fn use_theme() -> Signal<Theme> {
677    match try_use_context::<Signal<Theme>>() {
678        Some(s) => s,
679        None => use_context_provider(|| Signal::new(Theme::default())),
680    }
681}