Skip to content

Commit ee1c390

Browse files
authored
feat(theme): introduce a mechanism to toggle light/dark modes (#230)
* feat(theme): introduce Theme service to manage and persist theme settings (JS) * feat(theme): introduce Theme service to manage and persist theme settings * docs(*): rename ComponentStatus enum to PageStatus * docs(theming): add Dark Mode page * chore(docs): component rename
1 parent edc436b commit ee1c390

File tree

25 files changed

+696
-54
lines changed

25 files changed

+696
-54
lines changed

docs/LumexUI.Docs.Client/Common/Enums/ComponentStatus.cs renamed to docs/LumexUI.Docs.Client/Common/Enums/PageStatus.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
namespace LumexUI.Docs.Client.Common;
22

3-
public enum ComponentStatus
3+
public enum PageStatus
44
{
55
New,
66

77
Soon,
88

9-
Preview
9+
Preview,
10+
11+
Updated
1012
}

docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,21 @@ public class NavigationStore
1515

1616
private static NavigationCategory ThemingCategory =>
1717
new NavigationCategory( "Theming", Icons.Rounded.DesignServices )
18-
.Add( new( "Design Tokens" ) )
19-
.Add( new( "Customization" ) );
18+
.Add( new( "Design Tokens", PageStatus.New ) )
19+
.Add( new( "Customization", PageStatus.Updated ) )
20+
.Add( new( "Dark Mode", PageStatus.New ) );
2021

2122
private static NavigationCategory ComponentsCategory =>
2223
new NavigationCategory( "Components", Icons.Rounded.Joystick )
2324
.Add( new( nameof( LumexAccordion ) ) )
24-
.Add( new( nameof( LumexAlert ), ComponentStatus.New ) )
25-
.Add( new( nameof( LumexAvatar ), ComponentStatus.New ) )
26-
.Add( new( nameof( LumexBadge ), ComponentStatus.New ) )
25+
.Add( new( nameof( LumexAlert ), PageStatus.New ) )
26+
.Add( new( nameof( LumexAvatar ), PageStatus.New ) )
27+
.Add( new( nameof( LumexBadge ), PageStatus.New ) )
2728
.Add( new( nameof( LumexButton ) ) )
2829
.Add( new( nameof( LumexCard ) ) )
2930
.Add( new( nameof( LumexCheckbox ) ) )
3031
.Add( new( nameof( LumexCheckboxGroup ) ) )
31-
.Add( new( nameof( LumexChip ), ComponentStatus.New ) )
32+
.Add( new( nameof( LumexChip ), PageStatus.New ) )
3233
.Add( new( nameof( LumexCollapse ) ) )
3334
.Add( new( nameof( LumexDataGrid<T> ) ) )
3435
.Add( new( nameof( LumexDivider ) ) )
@@ -41,12 +42,12 @@ public class NavigationStore
4142
.Add( new( nameof( LumexPopover ) ) )
4243
.Add( new( nameof( LumexRadioGroup<T> ) ) )
4344
.Add( new( nameof( LumexSelect<T> ) ) )
44-
.Add( new( nameof( LumexSkeleton ), ComponentStatus.New ) )
45-
.Add( new( nameof( LumexSpinner ), ComponentStatus.New ) )
45+
.Add( new( nameof( LumexSkeleton ), PageStatus.New ) )
46+
.Add( new( nameof( LumexSpinner ), PageStatus.New ) )
4647
.Add( new( nameof( LumexSwitch ) ) )
4748
.Add( new( nameof( LumexTabs ) ) )
4849
.Add( new( nameof( LumexTextbox ) ) )
49-
.Add( new( nameof( LumexTooltip ), ComponentStatus.New ) );
50+
.Add( new( nameof( LumexTooltip ), PageStatus.New ) );
5051

5152
private static NavigationCategory ComponentsApiCategory =>
5253
new NavigationCategory( "Components API", Icons.Rounded.Manufacturing )

docs/LumexUI.Docs.Client/Common/Navigation/Types.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ public NavigationCategory Add( NavigationItem item )
2828
}
2929
}
3030

31-
public class NavigationItem( string name, ComponentStatus? status = null )
31+
public class NavigationItem( string name, PageStatus? status = null )
3232
{
3333
public string Name { get; } = name;
34-
public ComponentStatus? Status { get; } = status;
34+
public PageStatus? Status { get; } = status;
3535
}

docs/LumexUI.Docs.Client/Components/Layouts/MainLayout.razor

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
@namespace LumexUI.Docs.Client.Components
22
@inherits LayoutComponentBase
33

4-
<LumexThemeProvider />
5-
64
@Body
75

86
<div id="blazor-error-ui">

docs/LumexUI.Docs.Client/Components/NavItemBadge.razor

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,25 @@
88
}
99

1010
@code {
11-
[Parameter] public ComponentStatus? Status { get; set; }
11+
[Parameter] public PageStatus? Status { get; set; }
1212

1313
private string? RootClasses => ElementClass.Empty()
1414
.Add( "px-1.5" )
1515
.Add( "py-[3px]" )
1616
.Add( "rounded-full" )
1717
.Add( "ring" )
1818
.Add( "font-semibold" )
19-
.Add( "text-[0.5rem]" )
19+
.Add( "text-[9px]" )
2020
.Add( "uppercase" )
2121
.Add( "leading-none" )
2222
.Add( _variants[Status!.Value], when: Status.HasValue )
2323
.ToString();
2424

25-
private static readonly Dictionary<ComponentStatus, string> _variants = new()
25+
private static readonly Dictionary<PageStatus, string> _variants = new()
2626
{
27-
[ComponentStatus.New] = "bg-orange-100 ring-orange-300 text-orange-600",
28-
[ComponentStatus.Soon] = "bg-foreground-100 ring-foreground-300 text-foreground-600",
29-
[ComponentStatus.Preview] = "bg-indigo-50 ring-indigo-200 text-indigo-500"
27+
[PageStatus.New] = "bg-orange-100 ring-orange-300 text-orange-600",
28+
[PageStatus.Soon] = "bg-foreground-100 ring-foreground-300 text-foreground-600",
29+
[PageStatus.Preview] = "bg-indigo-50 ring-indigo-200 text-indigo-600",
30+
[PageStatus.Updated] = "bg-teal-50 ring-teal-200 text-teal-600",
3031
};
3132
}

docs/LumexUI.Docs.Client/Components/NavMenu.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
var styles = ElementClass.Default( "flex items-center gap-2" )
5151
.Add( ElementClass.Empty()
5252
.Add( "opacity-disabled" )
53-
.Add( "pointer-events-none" ), when: item.Status.HasValue && item.Status.Value is ComponentStatus.Soon )
53+
.Add( "pointer-events-none" ), when: item.Status.HasValue && item.Status.Value is PageStatus.Soon )
5454
.ToString();
5555

5656
<li class="@styles">

docs/LumexUI.Docs.Client/Pages/Theming/Customization/ThemeTogglePreview.razor renamed to docs/LumexUI.Docs.Client/Pages/Theming/Customization/CustomThemesPreview.razor

File renamed without changes.

docs/LumexUI.Docs.Client/Pages/Theming/Customization/Customization.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<p>To apply a theme at runtime, toggle the class on a wrapper element such as <code>&lt;html&gt;</code> or <code>&lt;body&gt;</code>.</p>
3939

4040
<CodeSnippet Code="@(new CodeBlock( name: "App.razor", snippet: "customizationToggle" ))" />
41-
<ThemeTogglePreview />
41+
<CustomThemesPreview />
4242
</DocsSection>
4343

4444
@code {
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
@page "/docs/theming/dark-mode"
2+
@layout DocsContentLayout
3+
4+
<DocsSection Title="Global dark mode">
5+
<p>
6+
LumexUI supports both <code>light</code> and <code>dark</code> modes.
7+
To enable dark mode globally, simply add the <code>dark</code> class
8+
to a wrapper element such as <code>html</code> or <code>body</code>.
9+
</p>
10+
11+
<CodeSnippet Code="@(new CodeBlock( name: "App.razor", snippet: "darkModeRoot" ))" />
12+
</DocsSection>
13+
14+
<DocsSection Title="Mode toggling">
15+
<p>LumexUI provides a simple mechanism for switching themes, which consists of two key parts:</p>
16+
17+
<ul>
18+
<li><strong>A theme provider</strong> component that sets the initial theme on first load.</li>
19+
<li><strong>A theme service</strong> that allows you to change the theme dynamically.</li>
20+
</ul>
21+
22+
<DocsSection Title="Add the theme provider">
23+
<p>Insert the <code>LumexThemeProvider</code> into your <code>MainLayout.razor</code> file:</p>
24+
25+
<CodeSnippet Code="@(new CodeBlock( name: "MainLayout.razor", snippet: "darkModeProvider" ))" />
26+
27+
<LumexAlert Radius="@Radius.Large">
28+
<DescriptionContent>
29+
<p class="mt-0 mb-3">
30+
The <code>LumexThemeProvider</code> component requires interactivity.
31+
If you're using the Blazor Web App template, make sure to apply the appropriate <code>@@rendermode</code>.
32+
</p>
33+
<LumexLink Href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-9.0"
34+
External="@true">
35+
Learn more about render modes
36+
</LumexLink>
37+
</DescriptionContent>
38+
</LumexAlert>
39+
</DocsSection>
40+
41+
<DocsSection Title="Add a mode toggle">
42+
<p>Place a mode toggle on your site to toggle between light and dark mode.</p>
43+
<ThemeTogglePreview />
44+
</DocsSection>
45+
</DocsSection>
46+
47+
@code {
48+
[CascadingParameter] private DocsContentLayout Layout { get; set; } = default!;
49+
50+
private readonly Heading[] _headings = new Heading[]
51+
{
52+
new("Global dark mode"),
53+
new("Mode toggling", [
54+
new("Setup"),
55+
new("Add a mode toggle"),
56+
]),
57+
};
58+
59+
protected override void OnInitialized()
60+
{
61+
Layout.Initialize(
62+
title: "Dark Mode",
63+
category: "Theming",
64+
description: "Adding dark mode to your app.",
65+
_headings
66+
);
67+
}
68+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
@inject ThemeService ThemeService
2+
@inject IPopoverService PopoverService
3+
4+
<LumexButton Variant="@Variant.Outlined"
5+
Class="min-w-10 w-10 h-10 p-0"
6+
OnClick="@TriggerAsync"
7+
data-popoverref="@_dropdownId">
8+
<LumexIcon Icon="@Icons.Rounded.DarkMode" Size="@new( "20" )" Class="hidden dark:block" />
9+
<LumexIcon Icon="@Icons.Rounded.LightMode" Size="@new( "20" )" Class="dark:hidden" />
10+
</LumexButton>
11+
12+
<LumexDropdown Id="@_dropdownId"
13+
Placement="@PopoverPlacement.BottomEnd"
14+
Class="min-w-32">
15+
<LumexDropdownMenu Variant="@MenuVariant.Flat">
16+
<LumexDropdownItem OnClick="@(async () => await ThemeService.SetThemeAsync( Theme.Light ))">
17+
Light
18+
</LumexDropdownItem>
19+
<LumexDropdownItem OnClick="@(async () => await ThemeService.SetThemeAsync( Theme.Dark ))">
20+
Dark
21+
</LumexDropdownItem>
22+
<LumexDropdownItem OnClick="@(async () => await ThemeService.SetThemeAsync( Theme.System ))">
23+
System
24+
</LumexDropdownItem>
25+
</LumexDropdownMenu>
26+
</LumexDropdown>
27+
28+
@code {
29+
private string _dropdownId = "theme-toggle";
30+
31+
private Task TriggerAsync() => PopoverService.TriggerAsync( _dropdownId );
32+
}

0 commit comments

Comments
 (0)