Skip to content

Commit

Permalink
feat(components): add theme provider component (#8)
Browse files Browse the repository at this point in the history
* feat(components): add theme provider

* feat(theme): add colors config

* feat(theme): add the color scale type that represents a color with various shades

* feat(theme): add the base colors model

* feat(theme): add the theme colors model

* feat(theme): add semantic colors

* chore(utils): add color utils

* chore(theme): add XML documentation

* chore(theme): nits

* feat(theme): add theme and layout configs

* chore(theme): add divider opacity to layout config

* feat(components): generate basic light and dark themes via theme provider

* chore(theme): populate color variants

* chore(components): uncomment `bg-divider` class for the divider component

* chore(theme): nits

* refactor(theme): make default color lighter in light theme

* refactor(utils): remove null or whitespace string check in the `GetReadableColor`

* chore(theme): add `Default` static method for the layout config

* refactor(theme): make info color in light theme one shade darker

* refactor(theme): add additional constructor and method to allow set the color key as default in the color scale

* feat(theme): add light and dark theme colors models

* feat(theme): add light and dark theme configs

* chore(theme): rename `LumexThemeConfig` to `LumexTheme`

* refactor(components): add culture invariance when generating a numeric value CSS vars in the theme provider

* test(components): add tests for the theme provider

* test(theme): add tests for the theme related types

* chore(theme): nits

* refactor(theme): change access modifier of the `ReverseColorValues`

* refactor(theme): correct the default color of the Default color scale

* feat(theme): add the new constructor for the theme with a specified default theme

* fix(theme): transform semantic Light and Dark theme colors into the properties

* feat(theme): add typography configuration

* test(theme): add the default theme value test

* test(theme): add missing tests to satisfy codecov

* chore(utils): exclude color utils from code coverage
  • Loading branch information
desmondinho authored May 19, 2024
1 parent dc36184 commit 36c3827
Show file tree
Hide file tree
Showing 20 changed files with 1,541 additions and 51 deletions.
16 changes: 16 additions & 0 deletions src/LumexUI/Common/Enums/ThemeType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) LumexUI 2024
// LumexUI licenses this file to you under the MIT license
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE

using System.ComponentModel;

namespace LumexUI.Common;

public enum ThemeType
{
[Description( "light" )]
Light,

[Description( "dark" )]
Dark
}
10 changes: 10 additions & 0 deletions src/LumexUI/Components/Providers/LumexThemeProvider.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@namespace LumexUI
@inherits ComponentBase

<style>
@((MarkupString)GenerateTheme(Theme.Light))

Check warning on line 5 in src/LumexUI/Components/Providers/LumexThemeProvider.razor

View workflow job for this annotation

GitHub Actions / Build & Test LumexUI

Possible null reference argument for parameter 'value' in 'MarkupString.explicit operator MarkupString(string value)'.

Check warning on line 5 in src/LumexUI/Components/Providers/LumexThemeProvider.razor

View workflow job for this annotation

GitHub Actions / Build & Test LumexUI

Possible null reference argument for parameter 'value' in 'MarkupString.explicit operator MarkupString(string value)'.
</style>

<style>
@((MarkupString)GenerateTheme(Theme.Dark))

Check warning on line 9 in src/LumexUI/Components/Providers/LumexThemeProvider.razor

View workflow job for this annotation

GitHub Actions / Build & Test LumexUI

Possible null reference argument for parameter 'value' in 'MarkupString.explicit operator MarkupString(string value)'.

Check warning on line 9 in src/LumexUI/Components/Providers/LumexThemeProvider.razor

View workflow job for this annotation

GitHub Actions / Build & Test LumexUI

Possible null reference argument for parameter 'value' in 'MarkupString.explicit operator MarkupString(string value)'.
</style>
93 changes: 93 additions & 0 deletions src/LumexUI/Components/Providers/LumexThemeProvider.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) LumexUI 2024
// LumexUI licenses this file to you under the MIT license
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE

using System.Globalization;
using System.Text;

using LumexUI.Theme;
using LumexUI.Utilities;

using Microsoft.AspNetCore.Components;

namespace LumexUI;

public partial class LumexThemeProvider : ComponentBase
{
private const string Prefix = "lumex";

/// <summary>
/// Gets or sets the configuration of the theme.
/// </summary>
[Parameter] public LumexTheme Theme { get; set; }

public LumexThemeProvider()
{
Theme = new();
}

private string? GenerateTheme( ThemeConfig theme )
{
var cssSelector = $"[data-theme={theme.Type.ToDescription()}]";

if( theme.Type == Theme.DefaultTheme )
{
cssSelector = $":root, {cssSelector}";
}

var sb = new StringBuilder();
sb.AppendLine( $"{cssSelector} {{" );

// Colors
var themeColors = GetThemeColorsDict( theme.Colors );

foreach( var color in themeColors )
{
foreach( var scale in color.Value )
{
var scaleKey = scale.Key == "default" ? "" : $"-{scale.Key}";
var scaleValue = ColorUtils.HexToHsl( scale.Value );

sb.AppendLine( $"--{Prefix}-{color.Key}{scaleKey}: {scaleValue};" );
}
}

// Layout
sb.AppendLine( $"--{Prefix}-font-sans: {theme.Layout.FontFamily?.Sans};" );
sb.AppendLine( $"--{Prefix}-font-mono: {theme.Layout.FontFamily?.Mono};" );
sb.AppendLine( $"--{Prefix}-font-size-xs: {theme.Layout.FontSize.Xs};" );
sb.AppendLine( $"--{Prefix}-font-size-sm: {theme.Layout.FontSize.Sm};" );
sb.AppendLine( $"--{Prefix}-font-size-md: {theme.Layout.FontSize.Md};" );
sb.AppendLine( $"--{Prefix}-font-size-lg: {theme.Layout.FontSize.Lg};" );
sb.AppendLine( $"--{Prefix}-line-height-xs: {theme.Layout.LineHeight.Xs};" );
sb.AppendLine( $"--{Prefix}-line-height-sm: {theme.Layout.LineHeight.Sm};" );
sb.AppendLine( $"--{Prefix}-line-height-md: {theme.Layout.LineHeight.Md};" );
sb.AppendLine( $"--{Prefix}-line-height-lg: {theme.Layout.LineHeight.Lg};" );
sb.AppendLine( CultureInfo.InvariantCulture, $"--{Prefix}-divider-opacity: {theme.Layout.DividerOpacity};" );
sb.AppendLine( CultureInfo.InvariantCulture, $"--{Prefix}-disabled-opacity: {theme.Layout.DisabledOpacity};" );
sb.AppendLine( CultureInfo.InvariantCulture, $"--{Prefix}-focus-opacity: {theme.Layout.FocusOpacity};" );
sb.AppendLine( CultureInfo.InvariantCulture, $"--{Prefix}-hover-opacity: {theme.Layout.HoverOpacity};" );

sb.AppendLine( "}" );
return sb.ToString();
}

private static Dictionary<string, ColorScale> GetThemeColorsDict( ThemeColors colors )
{
return new()
{
["background"] = colors.Background,
["foreground"] = colors.Foreground,
["overlay"] = colors.Overlay,
["focus"] = colors.Focus,
["divider"] = colors.Divider,
["default"] = colors.Default,
["primary"] = colors.Primary,
["secondary"] = colors.Secondary,
["success"] = colors.Success,
["warning"] = colors.Warning,
["danger"] = colors.Danger,
["info"] = colors.Info,
};
}
}
97 changes: 48 additions & 49 deletions src/LumexUI/Styles/ColorVariants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,71 @@

namespace LumexUI.Styles;

// TODO: Populate
internal record ColorVariants
{
public static Dictionary<ThemeColor, string> Solid = new()
public readonly static Dictionary<ThemeColor, string> Solid = new()
{
[ThemeColor.Default] = "",
[ThemeColor.Primary] = "",
[ThemeColor.Secondary] = "",
[ThemeColor.Success] = "",
[ThemeColor.Warning] = "",
[ThemeColor.Danger] = "",
[ThemeColor.Info] = ""
[ThemeColor.Default] = "bg-default text-default-foreground",
[ThemeColor.Primary] = "bg-primary text-primary-foreground",
[ThemeColor.Secondary] = "bg-secondary text-secondary-foreground",
[ThemeColor.Success] = "bg-success text-success-foreground",
[ThemeColor.Warning] = "bg-warning text-warning-foreground",
[ThemeColor.Danger] = "bg-danger text-danger-foreground",
[ThemeColor.Info] = "bg-info text-info-foreground"
};

public static Dictionary<ThemeColor, string> Outlined = new()
public readonly static Dictionary<ThemeColor, string> Outlined = new()
{
[ThemeColor.Default] = "",
[ThemeColor.Primary] = "",
[ThemeColor.Secondary] = "",
[ThemeColor.Success] = "",
[ThemeColor.Warning] = "",
[ThemeColor.Danger] = "",
[ThemeColor.Info] = ""
[ThemeColor.Default] = "border-default text-foreground",
[ThemeColor.Primary] = "border-primary text-primary",
[ThemeColor.Secondary] = "border-secondary text-secondary",
[ThemeColor.Success] = "border-success text-success",
[ThemeColor.Warning] = "border-warning text-warning",
[ThemeColor.Danger] = "border-danger text-danger",
[ThemeColor.Info] = "border-info text-info"
};

public static Dictionary<ThemeColor, string> Flat = new()
public readonly static Dictionary<ThemeColor, string> Flat = new()
{
[ThemeColor.Default] = "",
[ThemeColor.Primary] = "",
[ThemeColor.Secondary] = "",
[ThemeColor.Success] = "",
[ThemeColor.Warning] = "",
[ThemeColor.Danger] = "",
[ThemeColor.Info] = ""
[ThemeColor.Default] = "bg-default-100 text-default-foreground",
[ThemeColor.Primary] = "bg-primary-50 text-primary",
[ThemeColor.Secondary] = "bg-secondary-50 text-secondary",
[ThemeColor.Success] = "bg-success-50 text-success-600",
[ThemeColor.Warning] = "bg-warning-50 text-warning-600",
[ThemeColor.Danger] = "bg-danger-50 text-danger-600",
[ThemeColor.Info] = "bg-info-50 text-info"
};

public static Dictionary<ThemeColor, string> Shadow = new()
public readonly static Dictionary<ThemeColor, string> Shadow = new()
{
[ThemeColor.Default] = "",
[ThemeColor.Primary] = "",
[ThemeColor.Secondary] = "",
[ThemeColor.Success] = "",
[ThemeColor.Warning] = "",
[ThemeColor.Danger] = "",
[ThemeColor.Info] = ""
[ThemeColor.Default] = "shadow-lg shadow-default/40 bg-default text-default-foreground",
[ThemeColor.Primary] = "shadow-lg shadow-primary/40 bg-primary text-primary-foreground",
[ThemeColor.Secondary] = "shadow-lg shadow-secondary/40 bg-secondary text-secondary-foreground",
[ThemeColor.Success] = "shadow-lg shadow-success/40 bg-success text-success-foreground",
[ThemeColor.Warning] = "shadow-lg shadow-warning/40 bg-warning text-warning-foreground",
[ThemeColor.Danger] = "shadow-lg shadow-danger/40 bg-danger text-danger-foreground",
[ThemeColor.Info] = "shadow-lg shadow-info/40 bg-info text-info-foreground"
};

public static Dictionary<ThemeColor, string> Ghost = new()
public readonly static Dictionary<ThemeColor, string> Ghost = new()
{
[ThemeColor.Default] = "",
[ThemeColor.Primary] = "",
[ThemeColor.Secondary] = "",
[ThemeColor.Success] = "",
[ThemeColor.Warning] = "",
[ThemeColor.Danger] = "",
[ThemeColor.Info] = ""
[ThemeColor.Default] = "borde-default text-default-foreground hover:bg-default",
[ThemeColor.Primary] = "border-primary text-primary hover:text-primary-foreground hover:bg-primary",
[ThemeColor.Secondary] = "border-secondary text-secondary hover:text-secondary-foreground hover:bg-secondary",
[ThemeColor.Success] = "border-success text-success hover:text-success-foreground hover:bg-success",
[ThemeColor.Warning] = "border-warning text-warning hover:text-warning-foreground hover:bg-warning",
[ThemeColor.Danger] = "border-danger text-danger hover:text-danger-foreground hover:bg-danger",
[ThemeColor.Info] = "border-info text-info hover:text-info-foreground hover:bg-info"
};

public static Dictionary<ThemeColor, string> Light = new()
public readonly static Dictionary<ThemeColor, string> Light = new()
{
[ThemeColor.Default] = "",
[ThemeColor.Primary] = "",
[ThemeColor.Secondary] = "",
[ThemeColor.Success] = "",
[ThemeColor.Warning] = "",
[ThemeColor.Danger] = "",
[ThemeColor.Info] = ""
[ThemeColor.Default] = "text-default-foreground",
[ThemeColor.Primary] = "text-primary",
[ThemeColor.Secondary] = "text-secondary",
[ThemeColor.Success] = "text-success",
[ThemeColor.Warning] = "text-warning",
[ThemeColor.Danger] = "text-danger",
[ThemeColor.Info] = "text-info"
};
}
3 changes: 1 addition & 2 deletions src/LumexUI/Styles/Divider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ namespace LumexUI.Styles;
internal readonly record struct Divider
{
private readonly static string _base = CssBuilder.Empty()
// TODO: Uncomment when the theme provider is ready
//.AddClass( "bg-border" )
.AddClass( "bg-divider" )
.AddClass( "border-none" )
.Build();

Expand Down
Loading

0 comments on commit 36c3827

Please sign in to comment.