Files
radzen-blazor/Radzen.Blazor/ThemeService.cs
2026-06-15 18:45:19 +03:00

578 lines
19 KiB
C#

using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace Radzen
{
/// <summary>
/// Options for changing the theme.
/// </summary>
public class ThemeOptions
{
/// <summary>
/// Specifies the theme.
/// </summary>
public string? Theme { get; set; }
/// <summary>
/// Specifies if the theme colors should meet WCAG contrast requirements.
/// </summary>
public bool? Wcag { get; set; }
/// <summary>
/// Specifies if the theme should be right-to-left.
/// </summary>
public bool? RightToLeft { get; set; }
/// <summary>
/// Specifies if the theme change should trigger the <see cref="ThemeService.ThemeChanged" /> event.
/// </summary>
public bool TriggerChange { get; set; }
}
/// <summary>
/// Theme definition.
/// </summary>
public class Theme
{
/// <summary>
/// Specifies the user-friendly theme name e.g. Material3.
/// </summary>
public string? Text { get; set; }
/// <summary>
/// Specifies the theme value e.g. material3.
/// </summary>
public string? Value { get; set; }
/// <summary>
/// Specifies the primary color.
/// </summary>
public string? Primary { get; set; }
/// <summary>
/// Specifies the secondary color.
/// </summary>
public string? Secondary { get; set; }
/// <summary>
/// Specifies the base color.
/// </summary>
public string? Base { get; set; }
/// <summary>
/// Specifies the content color.
/// </summary>
public string? Content { get; set; }
/// <summary>
/// Specifies the title text color.
/// </summary>
public string? TitleText { get; set; }
/// <summary>
/// Specifies the content text color.
/// </summary>
public string? ContentText { get; set; }
/// <summary>
/// Specifies the selection color.
/// </summary>
public string? Selection { get; set; }
/// <summary>
/// Specifies the selection text color.
/// </summary>
public string? SelectionText { get; set; }
/// <summary>
/// Specifies the button radius.
/// </summary>
public string? ButtonRadius { get; set; }
/// <summary>
/// Specifies the card radius.
/// </summary>
public string? CardRadius { get; set; }
/// <summary>
/// Specifies the series A color.
/// </summary>
public string? SeriesA { get; set; }
/// <summary>
/// Specifies the series B color.
/// </summary>
public string? SeriesB { get; set; }
/// <summary>
/// Specifies the series C color.
/// </summary>
public string? SeriesC { get; set; }
/// <summary>
/// Specifies if the theme is premium.
/// </summary>
public bool Premium { get; set; }
}
/// <summary>
/// Predefined themes.
/// </summary>
public static class Themes
{
/// <summary>
/// Predefined themes.
/// </summary>
public static readonly Theme[] All = [
new Theme {
Text = "Material 3",
Value = "material3",
Premium = true,
Primary = "#3481e5",
Secondary = "#5b6471",
Base = "#ffffff",
Selection = "rgba(0, 107, 255, 0.13)",
SelectionText = "#1b1d20",
Content = "#f8f7ff",
TitleText = "#1b1d20",
ContentText = "#5b6471",
ButtonRadius = "4",
CardRadius = "8",
SeriesA = "#75abf0",
SeriesB = "#9c75f0",
SeriesC = "#f075e8"
},
new Theme {
Text = "Material 3 Dark",
Value = "material3-dark",
Premium = true,
Primary = "#94c1ff",
Secondary = "#c2cbdc",
Base = "#121418",
Selection = "rgba(144, 181, 255, 0.28)",
SelectionText = "#c2cbdc",
Content = "#1b1d20",
TitleText = "#e0e0e9",
ContentText = "#e0e0e9",
ButtonRadius = "4",
CardRadius = "8",
SeriesA = "#3d71b8",
SeriesB = "#663db8",
SeriesC = "#b83dae"
},
new Theme {
Text = "Material 3 Expressive",
Value = "material3expressive",
Premium = true,
Primary = "#5f33d6",
Secondary = "#573d8f",
Base = "#ffffff",
Selection = "rgba(69, 0, 255, 0.13)",
SelectionText = "#21005d",
Content = "#f9f8fc",
TitleText = "#1c1b20",
ContentText = "#77747e",
ButtonRadius = "4",
CardRadius = "8",
SeriesA = "#9675f0",
SeriesB = "#f075ed",
SeriesC = "#f07591"
},
new Theme {
Text = "Material 3 Expressive Dark",
Value = "material3expressive-dark",
Premium = true,
Primary = "#cdbcff",
Secondary = "#c8b9e5",
Base = "#141218",
Selection = "rgba(205, 188, 255, 0.24)",
SelectionText = "#c8b9e5",
Content = "#1c1b20",
TitleText = "#e2e0e9",
ContentText = "#e2e0e9",
ButtonRadius = "4",
CardRadius = "8",
SeriesA = "#5c3db8",
SeriesB = "#b83db7",
SeriesC = "#b83d5b"
},
new Theme {
Text = "Fluent",
Value = "fluent",
Premium = true,
Primary = "#0078d4",
Secondary = "#8a8886",
Base = "#f5f5f5",
Selection = "#0078d4",
SelectionText = "#ffffff",
Content = "white",
TitleText = "black",
ContentText = "#605e5c",
ButtonRadius = "1",
CardRadius = "2",
SeriesA = "#0078d4",
SeriesB = "#b4a0ff",
SeriesC = "#d83b01"
},
new Theme {
Text = "Fluent Dark",
Value = "fluent-dark",
Premium = true,
Primary = "#479ef5",
Secondary = "#5c5c5c",
Base = "#292929",
Selection = "#479ef5",
SelectionText = "#0a0a0a",
Content = "#333333",
TitleText = "#ffffff",
ContentText = "#d6d6d6",
ButtonRadius = "1",
CardRadius = "2",
SeriesA = "#0078d4",
SeriesB = "#b4a0ff",
SeriesC = "#d83b01"
},
new Theme {
Text = "Material",
Value = "material",
Primary = "#4340d2",
Secondary = "#e31c65",
Base = "#f5f5f5",
Selection = "rgba(67, 64, 210, 0.12)",
SelectionText = "#4340d2",
Content = "#ffffff",
TitleText = "#212121",
ContentText = "#bdbdbd",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#3700b3",
SeriesB = "#ba68c8",
SeriesC = "#f06292"
},
new Theme {
Text = "Material Dark",
Value = "material-dark",
Primary = "#bb86fc",
Secondary = "#01a299",
Base = "#121212",
Selection = "rgba(187, 134, 252, 0.12)",
SelectionText = "#bb86fc",
Content = "#252525",
TitleText = "#ffffff",
ContentText = "#a0a0a0",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#3700b3",
SeriesB = "#ba68c8",
SeriesC = "#f06292"
},
new Theme {
Text = "Standard",
Value = "standard",
Primary = "#1151f3",
Secondary = "rgba(17, 81, 243, 0.16)",
Base = "#f4f5f9",
Selection = "rgba(114, 152, 248, 0.16)",
SelectionText = "#1151f3",
Content = "#ffffff",
TitleText = "#262526",
ContentText = "#afafb2",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#376df5",
SeriesB = "#64dfdf",
SeriesC = "#f68769"
},
new Theme {
Text = "Standard Dark",
Value = "standard-dark",
Primary = "#3871ff",
Secondary = "#2a3c68",
Base = "#19191a",
Selection = "rgba(56, 113, 255, 0.2)",
SelectionText = "#88aaff",
Content = "#242527",
TitleText = "#ffffff",
ContentText = "#eaebec",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#376df5",
SeriesB = "#64dfdf",
SeriesC = "#f68769"
},
new Theme {
Text = "Default",
Value = "default",
Primary = "#ff6d41",
Secondary = "#35a0d7",
Base = "#f6f7fa",
Selection = "#3193c6",
SelectionText = "#ffffff",
Content = "#ffffff",
TitleText = "#28363c",
ContentText = "#95a4a8",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#0479cc",
SeriesB = "#68d5c8",
SeriesC = "#ff6d41"
},
new Theme {
Text = "Dark",
Value="dark",
Primary = "#ff6d41",
Secondary = "#35a0d7",
Base = "#28363c",
Selection = "#3692c4",
SelectionText = "#ffffff",
Content = "#38474e",
TitleText = "#ffffff",
ContentText = "#a8b4b8",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#0479cc",
SeriesB = "#68d5c8",
SeriesC = "#ff6d41"
},
new Theme {
Text = "Humanistic",
Value = "humanistic",
Primary = "#d64d42",
Secondary = "#3ba5fc",
Base = "#f3f5f7",
Selection = "#3698e8",
SelectionText = "#ffffff",
Content = "#ffffff",
TitleText = "#2b3a50",
ContentText = "#7293b6",
ButtonRadius = "0",
CardRadius = "0",
SeriesA = "#376df5",
SeriesB = "#64dfdf",
SeriesC = "#f68769"
},
new Theme {
Text = "Humanistic Dark",
Value = "humanistic-dark",
Primary = "#d64d42",
Secondary = "#3ba5fc",
Base = "#2b3a50",
Selection = "#3698e8",
SelectionText = "#ffffff",
Content = "#30445f",
TitleText = "#ffffff",
ContentText = "#d9e1ea",
ButtonRadius = "0",
CardRadius = "0",
SeriesA = "#376df5",
SeriesB = "#64dfdf",
SeriesC = "#f68769"
},
new Theme {
Text = "Software",
Value = "software",
Primary = "#598087",
Secondary = "#80a4ab",
Base = "#f6f7fa",
Selection = "#76979d",
SelectionText = "#ffffff",
Content = "#ffffff",
TitleText = "#28363c",
ContentText = "#95a4a8",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#376df5",
SeriesB = "#64dfdf",
SeriesC = "#f68769"
},
new Theme {
Text = "Software Dark",
Value = "software-dark",
Primary = "#598087",
Secondary = "#80a4ab",
Base = "#28363c",
Selection = "#76979d",
SelectionText = "#ffffff",
Content = "#3a474d",
TitleText = "#ffffff",
ContentText = "#f5f8f9",
ButtonRadius = "2",
CardRadius = "4",
SeriesA = "#376df5",
SeriesB = "#64dfdf",
SeriesC = "#f68769"
}
];
/// <summary>
/// Free themes.
/// </summary>
public static IEnumerable<Theme> Free => All.Where(theme => !theme.Premium);
/// <summary>
/// Premium themes.
/// </summary>
public static IEnumerable<Theme> Premium => All.Where(theme => theme.Premium);
}
/// <summary>
/// Service for theme registration and management.
/// </summary>
public class ThemeService(IJSRuntime jsRuntime, IServiceProvider serviceProvider)
{
private string? theme;
/// <summary>
/// Gets the current theme.
/// </summary>
public string? Theme
{
[UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2026, Justification = TrimMessages.ThemeTypePreserved)]
get
{
if (theme == null)
{
var persistentComponentState = serviceProvider.GetService<PersistentComponentState>();
if (persistentComponentState?.TryTakeFromJson(nameof(Theme), out string? persistedTheme) == true && persistedTheme != null)
{
theme = persistedTheme;
}
}
return theme;
}
private set => theme = value;
}
/// <summary>
/// Specify if the theme colors should meet WCAG contrast requirements.
/// </summary>
public bool? Wcag { get; private set; }
/// <summary>
/// Specify if the theme should be right-to-left.
/// </summary>
public bool? RightToLeft { get; private set; }
/// <summary>
/// Custom css path. If set, it overwrites the default path.
/// </summary>
public string? CssPath { get; private set; }
/// <summary>
/// Raised when the theme changes.
/// </summary>
public event Action? ThemeChanged;
/// <summary>
/// Changes the current theme.
/// </summary>
public void SetTheme(string theme, bool triggerChange = true)
{
SetTheme(new ThemeOptions
{
Theme = theme,
Wcag = Wcag,
RightToLeft = RightToLeft,
TriggerChange = triggerChange
});
}
/// <summary>
/// Changes the current theme with additional options.
/// </summary>
public void SetTheme(ThemeOptions options)
{
ArgumentNullException.ThrowIfNull(options);
var requiresChange = false;
if (Theme != options.Theme)
{
Theme = options.Theme;
requiresChange = true;
}
if (Wcag != options.Wcag)
{
Wcag = options.Wcag;
requiresChange = true;
}
if (RightToLeft != options.RightToLeft)
{
RightToLeft = options.RightToLeft;
requiresChange = true;
}
if (requiresChange && options.TriggerChange)
{
ThemeChanged?.Invoke();
try
{
if (Href != null)
{
jsRuntime.InvokeVoid("Radzen.setTheme", Href, Wcag == true ? WcagHref : null!);
}
}
catch (Exception)
{
}
}
}
[UnconditionalSuppressMessage(TrimMessages.Trimming, TrimMessages.IL2026, Justification = TrimMessages.AssemblyMetadataPreserved)]
private static readonly string? Version = typeof(ThemeService).Assembly.GetName().Version?.ToString();
internal string Href => $"{Path}/{Theme}-base.css?v={Version}";
internal string WcagHref => $"{Path}/{Theme}-wcag.css?v={Version}";
private string Path => Embedded
? $"_content/Radzen.Blazor/css"
: !string.IsNullOrEmpty(CssPath)
? CssPath
: "css";
internal bool Embedded => Theme switch
{
"material" => true,
"material-dark" => true,
"standard" => true,
"standard-dark" => true,
"humanistic" => true,
"humanistic-dark" => true,
"software" => true,
"software-dark" => true,
"default" => true,
"dark" => true,
_ => false
};
/// <summary>
/// Enables or disables WCAG contrast requirements.
/// </summary>
public void SetWcag(bool wcag)
{
SetTheme(new ThemeOptions
{
Theme = Theme,
Wcag = wcag,
RightToLeft = RightToLeft,
TriggerChange = true
});
}
/// <summary>
/// Specifies if the theme should be right-to-left.
/// </summary>
public void SetRightToLeft(bool rightToLeft)
{
SetTheme(new ThemeOptions
{
Theme = Theme,
Wcag = Wcag,
RightToLeft = rightToLeft,
TriggerChange = true
});
}
/// <summary>
/// Sets a specific css path to the theme service.
/// </summary>
/// <param name="cssPath">New css path to look for themes.</param>
public void SetCssPath(string? cssPath)
{
CssPath = cssPath;
}
}
}