Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
namespace LumexUI.Docs.Client.Common;

public enum ComponentStatus
public enum PageStatus
{
New,

Soon,

Preview
Preview,

Updated
}
19 changes: 10 additions & 9 deletions docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,21 @@ public class NavigationStore

private static NavigationCategory ThemingCategory =>
new NavigationCategory( "Theming", Icons.Rounded.DesignServices )
.Add( new( "Design Tokens" ) )
.Add( new( "Customization" ) );
.Add( new( "Design Tokens", PageStatus.New ) )
.Add( new( "Customization", PageStatus.Updated ) )
.Add( new( "Dark Mode", PageStatus.New ) );

private static NavigationCategory ComponentsCategory =>
new NavigationCategory( "Components", Icons.Rounded.Joystick )
.Add( new( nameof( LumexAccordion ) ) )
.Add( new( nameof( LumexAlert ), ComponentStatus.New ) )
.Add( new( nameof( LumexAvatar ), ComponentStatus.New ) )
.Add( new( nameof( LumexBadge ), ComponentStatus.New ) )
.Add( new( nameof( LumexAlert ), PageStatus.New ) )
.Add( new( nameof( LumexAvatar ), PageStatus.New ) )
.Add( new( nameof( LumexBadge ), PageStatus.New ) )
.Add( new( nameof( LumexButton ) ) )
.Add( new( nameof( LumexCard ) ) )
.Add( new( nameof( LumexCheckbox ) ) )
.Add( new( nameof( LumexCheckboxGroup ) ) )
.Add( new( nameof( LumexChip ), ComponentStatus.New ) )
.Add( new( nameof( LumexChip ), PageStatus.New ) )
.Add( new( nameof( LumexCollapse ) ) )
.Add( new( nameof( LumexDataGrid<T> ) ) )
.Add( new( nameof( LumexDivider ) ) )
Expand All @@ -41,12 +42,12 @@ public class NavigationStore
.Add( new( nameof( LumexPopover ) ) )
.Add( new( nameof( LumexRadioGroup<T> ) ) )
.Add( new( nameof( LumexSelect<T> ) ) )
.Add( new( nameof( LumexSkeleton ), ComponentStatus.New ) )
.Add( new( nameof( LumexSpinner ), ComponentStatus.New ) )
.Add( new( nameof( LumexSkeleton ), PageStatus.New ) )
.Add( new( nameof( LumexSpinner ), PageStatus.New ) )
.Add( new( nameof( LumexSwitch ) ) )
.Add( new( nameof( LumexTabs ) ) )
.Add( new( nameof( LumexTextbox ) ) )
.Add( new( nameof( LumexTooltip ), ComponentStatus.New ) );
.Add( new( nameof( LumexTooltip ), PageStatus.New ) );

private static NavigationCategory ComponentsApiCategory =>
new NavigationCategory( "Components API", Icons.Rounded.Manufacturing )
Expand Down
4 changes: 2 additions & 2 deletions docs/LumexUI.Docs.Client/Common/Navigation/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public NavigationCategory Add( NavigationItem item )
}
}

public class NavigationItem( string name, ComponentStatus? status = null )
public class NavigationItem( string name, PageStatus? status = null )
{
public string Name { get; } = name;
public ComponentStatus? Status { get; } = status;
public PageStatus? Status { get; } = status;
}
2 changes: 0 additions & 2 deletions docs/LumexUI.Docs.Client/Components/Layouts/MainLayout.razor
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
@namespace LumexUI.Docs.Client.Components
@inherits LayoutComponentBase

<LumexThemeProvider />

@Body

<div id="blazor-error-ui">
Expand Down
13 changes: 7 additions & 6 deletions docs/LumexUI.Docs.Client/Components/NavItemBadge.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,25 @@
}

@code {
[Parameter] public ComponentStatus? Status { get; set; }
[Parameter] public PageStatus? Status { get; set; }

private string? RootClasses => ElementClass.Empty()
.Add( "px-1.5" )
.Add( "py-[3px]" )
.Add( "rounded-full" )
.Add( "ring" )
.Add( "font-semibold" )
.Add( "text-[0.5rem]" )
.Add( "text-[9px]" )
.Add( "uppercase" )
.Add( "leading-none" )
.Add( _variants[Status!.Value], when: Status.HasValue )
.ToString();

private static readonly Dictionary<ComponentStatus, string> _variants = new()
private static readonly Dictionary<PageStatus, string> _variants = new()
{
[ComponentStatus.New] = "bg-orange-100 ring-orange-300 text-orange-600",
[ComponentStatus.Soon] = "bg-foreground-100 ring-foreground-300 text-foreground-600",
[ComponentStatus.Preview] = "bg-indigo-50 ring-indigo-200 text-indigo-500"
[PageStatus.New] = "bg-orange-100 ring-orange-300 text-orange-600",
[PageStatus.Soon] = "bg-foreground-100 ring-foreground-300 text-foreground-600",
[PageStatus.Preview] = "bg-indigo-50 ring-indigo-200 text-indigo-600",
[PageStatus.Updated] = "bg-teal-50 ring-teal-200 text-teal-600",
};
}
2 changes: 1 addition & 1 deletion docs/LumexUI.Docs.Client/Components/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
var styles = ElementClass.Default( "flex items-center gap-2" )
.Add( ElementClass.Empty()
.Add( "opacity-disabled" )
.Add( "pointer-events-none" ), when: item.Status.HasValue && item.Status.Value is ComponentStatus.Soon )
.Add( "pointer-events-none" ), when: item.Status.HasValue && item.Status.Value is PageStatus.Soon )
.ToString();

<li class="@styles">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<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>

<CodeSnippet Code="@(new CodeBlock( name: "App.razor", snippet: "customizationToggle" ))" />
<ThemeTogglePreview />
<CustomThemesPreview />
</DocsSection>

@code {
Expand Down
68 changes: 68 additions & 0 deletions docs/LumexUI.Docs.Client/Pages/Theming/Dark Mode/DarkMode.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
@page "/docs/theming/dark-mode"
@layout DocsContentLayout

<DocsSection Title="Global dark mode">
<p>
LumexUI supports both <code>light</code> and <code>dark</code> modes.
To enable dark mode globally, simply add the <code>dark</code> class
to a wrapper element such as <code>html</code> or <code>body</code>.
</p>

<CodeSnippet Code="@(new CodeBlock( name: "App.razor", snippet: "darkModeRoot" ))" />
</DocsSection>

<DocsSection Title="Mode toggling">
<p>LumexUI provides a simple mechanism for switching themes, which consists of two key parts:</p>

<ul>
<li><strong>A theme provider</strong> component that sets the initial theme on first load.</li>
<li><strong>A theme service</strong> that allows you to change the theme dynamically.</li>
</ul>

<DocsSection Title="Add the theme provider">
<p>Insert the <code>LumexThemeProvider</code> into your <code>MainLayout.razor</code> file:</p>

<CodeSnippet Code="@(new CodeBlock( name: "MainLayout.razor", snippet: "darkModeProvider" ))" />

<LumexAlert Radius="@Radius.Large">
<DescriptionContent>
<p class="mt-0 mb-3">
The <code>LumexThemeProvider</code> component requires interactivity.
If you're using the Blazor Web App template, make sure to apply the appropriate <code>@@rendermode</code>.
</p>
<LumexLink Href="https://learn.microsoft.com/en-us/aspnet/core/blazor/components/render-modes?view=aspnetcore-9.0"
External="@true">
Learn more about render modes
</LumexLink>
</DescriptionContent>
</LumexAlert>
</DocsSection>

<DocsSection Title="Add a mode toggle">
<p>Place a mode toggle on your site to toggle between light and dark mode.</p>
<ThemeTogglePreview />
</DocsSection>
</DocsSection>

@code {
[CascadingParameter] private DocsContentLayout Layout { get; set; } = default!;

private readonly Heading[] _headings = new Heading[]
{
new("Global dark mode"),
new("Mode toggling", [
new("Setup"),
new("Add a mode toggle"),
]),
};

protected override void OnInitialized()
{
Layout.Initialize(
title: "Dark Mode",
category: "Theming",
description: "Adding dark mode to your app.",
_headings
);
}
}
32 changes: 32 additions & 0 deletions docs/LumexUI.Docs.Client/Pages/Theming/Dark Mode/ThemeToggle.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@inject ThemeService ThemeService
@inject IPopoverService PopoverService

<LumexButton Variant="@Variant.Outlined"
Class="min-w-10 w-10 h-10 p-0"
OnClick="@TriggerAsync"
data-popoverref="@_dropdownId">
<LumexIcon Icon="@Icons.Rounded.DarkMode" Size="@new( "20" )" Class="hidden dark:block" />
<LumexIcon Icon="@Icons.Rounded.LightMode" Size="@new( "20" )" Class="dark:hidden" />
</LumexButton>

<LumexDropdown Id="@_dropdownId"
Placement="@PopoverPlacement.BottomEnd"
Class="min-w-32">
<LumexDropdownMenu Variant="@MenuVariant.Flat">
<LumexDropdownItem OnClick="@(async () => await ThemeService.SetThemeAsync( Theme.Light ))">
Light
</LumexDropdownItem>
<LumexDropdownItem OnClick="@(async () => await ThemeService.SetThemeAsync( Theme.Dark ))">
Dark
</LumexDropdownItem>
<LumexDropdownItem OnClick="@(async () => await ThemeService.SetThemeAsync( Theme.System ))">
System
</LumexDropdownItem>
</LumexDropdownMenu>
</LumexDropdown>

@code {
private string _dropdownId = "theme-toggle";

private Task TriggerAsync() => PopoverService.TriggerAsync( _dropdownId );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@rendermode InteractiveWebAssembly

<PreviewCode Code="@new( name: null, snippet: "darkModeToggle" )"
PreviewClasses="@(new() { Preview = "justify-center" })">
<LumexUI.Docs.Client.Pages.Theming.Dark_Mode.ThemeToggle />
</PreviewCode>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<pre><code class="language-razor">&lt;LumexThemeProvider @rendermode="@InteractiveWebAssembly" /&gt;
</code></pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<pre data-line="2"><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html class=&quot;dark&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;utf-8&quot; /&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
&lt;link href=&quot;app.css&quot; rel=&quot;stylesheet&quot; /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;script src=&quot;_content/LumexUI/js/LumexUI.js&quot; type=&quot;module&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</code></pre>
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<pre><code class="language-razor">@inject ThemeService ThemeService
@inject IPopoverService PopoverService

&lt;LumexButton Variant=&quot;@Variant.Outlined&quot;
Class=&quot;min-w-10 w-10 h-10 p-0&quot;
OnClick=&quot;@TriggerAsync&quot;
data-popoverref=&quot;@_dropdownId&quot;&gt;
&lt;LumexIcon Icon=&quot;@Icons.Rounded.DarkMode&quot; Size=&quot;@new( &quot;20&quot; )&quot; Class=&quot;hidden dark:block&quot; /&gt;
&lt;LumexIcon Icon=&quot;@Icons.Rounded.LightMode&quot; Size=&quot;@new( &quot;20&quot; )&quot; Class=&quot;dark:hidden&quot; /&gt;
&lt;/LumexButton&gt;

&lt;LumexDropdown Id=&quot;@_dropdownId&quot;
Placement=&quot;@PopoverPlacement.BottomEnd&quot;
Class=&quot;min-w-32&quot;&gt;
&lt;LumexDropdownMenu Variant=&quot;@MenuVariant.Flat&quot;&gt;
&lt;LumexDropdownItem OnClick=&quot;@(async () =&gt; await ThemeService.SetThemeAsync( Theme.Light ))&quot;&gt;
Light
&lt;/LumexDropdownItem&gt;
&lt;LumexDropdownItem OnClick=&quot;@(async () =&gt; await ThemeService.SetThemeAsync( Theme.Dark ))&quot;&gt;
Dark
&lt;/LumexDropdownItem&gt;
&lt;LumexDropdownItem OnClick=&quot;@(async () =&gt; await ThemeService.SetThemeAsync( Theme.System ))&quot;&gt;
System
&lt;/LumexDropdownItem&gt;
&lt;/LumexDropdownMenu&gt;
&lt;/LumexDropdown&gt;

@code {
private string _dropdownId = &quot;theme-toggle&quot;;

private Task TriggerAsync() =&gt; PopoverService.TriggerAsync( _dropdownId );
}
</code></pre>
1 change: 1 addition & 0 deletions docs/LumexUI.Docs.Client/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

@using LumexUI.Common
@using LumexUI.Extensions
@using LumexUI.Services
@using LumexUI.Utilities

@using TailwindMerge
8 changes: 8 additions & 0 deletions docs/LumexUI.Docs/Components/App.razor
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
<link rel="icon" type="image/png" href="favicon.png" />
<PageTitle>LumexUI - A versatile Blazor UI library built using Tailwind CSS</PageTitle>
<HeadOutlet />
<script>
const stored = localStorage.getItem("lumexui.theme") || "system";
const theme = stored === "system"
? window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
: stored;
document.documentElement.classList.add(theme);
document.documentElement.style.colorScheme = theme;
</script>
</head>

<body>
Expand Down
3 changes: 2 additions & 1 deletion docs/LumexUI.Docs/Components/Routes.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }">
<LumexThemeProvider @rendermode="@InteractiveWebAssembly" />
<Router AppAssembly="typeof(Program).Assembly" AdditionalAssemblies="new[] { typeof(Client._Imports).Assembly }">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
Expand Down
31 changes: 31 additions & 0 deletions src/LumexUI/Common/Enums/Theme.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// 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;

/// <summary>
/// Specifies the available theme options.
/// </summary>
public enum Theme
{
/// <summary>
/// A light theme.
/// </summary>
[Description( "light" )]
Light,

/// <summary>
/// A dark theme.
/// </summary>
[Description( "dark" )]
Dark,

/// <summary>
/// A theme that matches the system setting.
/// </summary>
[Description( "system" )]
System
}
29 changes: 29 additions & 0 deletions src/LumexUI/Components/Providers/LumexThemeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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.Diagnostics.CodeAnalysis;

using LumexUI.Services;

using Microsoft.AspNetCore.Components;

namespace LumexUI;

/// <summary>
/// A component that provides theming support.
/// </summary>
[ExcludeFromCodeCoverage]
public class LumexThemeProvider : ComponentBase
{
[Inject] private ThemeService ThemeService { get; set; } = default!;

/// <inheritdoc />
protected override async Task OnAfterRenderAsync( bool firstRender )
{
if( firstRender )
{
await ThemeService.InitializeAsync();
}
}
}
Loading
Loading