diff --git a/examples/Demo/Shared/Components/SiteSettingsPanel.razor b/examples/Demo/Shared/Components/SiteSettingsPanel.razor index 1c1422357f..33ce0537ed 100644 --- a/examples/Demo/Shared/Components/SiteSettingsPanel.razor +++ b/examples/Demo/Shared/Components/SiteSettingsPanel.razor @@ -6,6 +6,7 @@ @@ -31,6 +32,20 @@ + Neutral base color + + + + +
Neutral base color
+ +

+ The NeutralBaseColor design token is used to determine the color for layers and other neutral components. It uses a recipe to make sure a high enough value for the contrast is maintained. +

+ +
+
+ AllModes => Enum.GetValues(); @@ -49,7 +53,12 @@ protected override void OnAfterRender(bool firstRender) { Direction = GlobalState.Dir; _ltr = !Direction.HasValue || Direction.Value == LocalizationDirection.LeftToRight; + + NeutralColor = GlobalState.NeutralColor; + // Same default values is used for light and dark theme + NeutralColor ??= DEFAULT_NEUTRAL_COLOR; } + } protected void HandleDirectionChanged(bool isLeftToRight) @@ -71,6 +80,7 @@ private async Task ResetSiteAsync() OfficeColor = OfficeColorUtilities.GetRandom(); Mode = DesignThemeModes.System; + NeutralColor = DEFAULT_NEUTRAL_COLOR; } private async Task ManageCookieSettingsAsync() diff --git a/src/Core.Assets/src/Design/ThemeStorage.ts b/src/Core.Assets/src/Design/ThemeStorage.ts index 2f27fd8a0d..f0e67a30fc 100644 --- a/src/Core.Assets/src/Design/ThemeStorage.ts +++ b/src/Core.Assets/src/Design/ThemeStorage.ts @@ -20,7 +20,7 @@ class ThemeStorage { } - public updateLocalStorage(mode: string | null, primaryColor: string | null): void { + public updateLocalStorage(mode: string | null, primaryColor: string | null, neutralColor: string | null): void { // If LocalStorage is not available, do nothing. if (localStorage == null) { @@ -41,10 +41,11 @@ class ThemeStorage { localStorage.setItem(this.storageName, JSON.stringify({ mode: ThemeStorage.getValueOrNull(mode), primaryColor: ThemeStorage.getValueOrNull(primaryColor), + neutralColor: ThemeStorage.getValueOrNull(neutralColor), })); } - public readLocalStorage(): { mode: string | null, primaryColor: string | null } | null { + public readLocalStorage(): { mode: string | null, primaryColor: string | null, neutralColor: string | null } | null { // If LocalStorage is not available, do nothing. if (localStorage == null) { @@ -69,6 +70,7 @@ class ThemeStorage { return { mode: ThemeStorage.getValueOrNull(storageItems?.mode), primaryColor: ThemeStorage.getValueOrNull(storageItems?.primaryColor), + neutralColor: ThemeStorage.getValueOrNull(storageItems?.neutralColor), } } diff --git a/src/Core.Assets/src/DesignTheme.ts b/src/Core.Assets/src/DesignTheme.ts index 12c12129c5..1993098f48 100644 --- a/src/Core.Assets/src/DesignTheme.ts +++ b/src/Core.Assets/src/DesignTheme.ts @@ -112,6 +112,32 @@ class DesignTheme extends HTMLElement { this._synchronization.synchronizeOtherComponents("primary-color", value); } + /** + * Gets the current neutral base color attribute value. + */ + get neutralColor(): string | null { + return this.getAttribute("neutral-color"); + } + + /** + * Sets the current neutral base color attribute value. + */ + set neutralColor(value: string | null) { + this.updateAttribute("neutral-color", value); + + // Apply the color + if (value != null) { + const rgb = parseColorHexRGB(value); + if (rgb != null) { + const swatch = SwatchRGB.from(rgb); + neutralBaseColor.withDefault(swatch); + } + + // Synchronization + this._synchronization.synchronizeOtherComponents("neutral-color", value); + } + } + /** * Gets the current storage-name key, to persist the theme/color between sessions. */ @@ -147,6 +173,7 @@ class DesignTheme extends HTMLElement { if (existingComponent != null) { const mode = ThemeStorage.getValueOrNull(existingComponent.getAttribute("mode")); const color = ThemeStorage.getValueOrNull(existingComponent.getAttribute("primary-color")) + const neutralColor = ThemeStorage.getValueOrNull(existingComponent.getAttribute("neutral-color")); // Mode can be null in other components this.attributeChangedCallback("mode", this.mode, mode); @@ -155,6 +182,11 @@ class DesignTheme extends HTMLElement { if (color != null) { this.attributeChangedCallback("primary-color", this.primaryColor, color); } + + // Neutral color cannot be null in other components + if (neutralColor != null) { + this.attributeChangedCallback("neutral-color", this.neutralColor, neutralColor); + } } // Load from LocalStorage @@ -164,6 +196,7 @@ class DesignTheme extends HTMLElement { if (theme != null) { this.attributeChangedCallback("mode", this.mode, theme.mode); this.attributeChangedCallback("primary-color", this.primaryColor, theme.primaryColor); + this.attributeChangedCallback("neutral-color", this.neutralColor, theme.neutralColor); } } @@ -202,7 +235,7 @@ class DesignTheme extends HTMLElement { // Attributes to observe static get observedAttributes() { - return ["mode", "primary-color", "storage-name"]; + return ["mode", "primary-color", "neutral-color", "storage-name"]; } // Attributes has changed. @@ -234,6 +267,11 @@ class DesignTheme extends HTMLElement { this.primaryColor = newValue; break; + case "neutral-color": + this.dispatchAttributeChanged(name, oldValue, newValue); + this.neutralColor = newValue; + break; + case "storage-name": this.storageName = newValue; break; @@ -298,7 +336,7 @@ class DesignTheme extends HTMLElement { } } if (this.storageName != null) { - this._themeStorage.updateLocalStorage(this.mode, this.primaryColor); + this._themeStorage.updateLocalStorage(this.mode, this.primaryColor, this.neutralColor); } this._isInternalChange = false; diff --git a/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor b/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor index 19aac245bb..7e4b806213 100644 --- a/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor +++ b/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor @@ -1,4 +1,4 @@ -@namespace Microsoft.FluentUI.AspNetCore.Components +@namespace Microsoft.FluentUI.AspNetCore.Components - -@ChildContent \ No newline at end of file + +@ChildContent diff --git a/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs b/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs index 376bad2a5c..fd2ef55243 100644 --- a/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs +++ b/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs @@ -77,6 +77,12 @@ public partial class FluentDesignTheme : ComponentBase [Parameter] public EventCallback OfficeColorChanged { get; set; } + [Parameter] + public string? NeutralBaseColor { get; set; } + + [Parameter] + public EventCallback NeutralBaseColorChanged { get; set; } + /// /// Gets or sets the local storage name to save and retrieve the and the / . /// @@ -175,6 +181,12 @@ public async Task OnChangeRaisedAsync(string name, string value) } } + break; + case "neutral-color": + if (value.StartsWith('#')) + { + GlobalDesign.SetNeutralColor(value); + } break; } } @@ -256,6 +268,16 @@ private async Task ApplyLocalStorageValuesAsync(DataLocalStorage? theme) await OnChangeRaisedAsync("primary-color", theme.PrimaryColor); } } + + // Neutral base color + if (!string.IsNullOrEmpty(theme?.NeutralBaseColor)) + { + if (theme.NeutralBaseColor.StartsWith('#')) + { + GlobalDesign.SetNeutralColor(theme.NeutralBaseColor); + } + await OnChangeRaisedAsync("neutral-base-color", theme.NeutralBaseColor); + } } /// @@ -263,6 +285,7 @@ private class DataLocalStorage { public string? Mode { get; set; } public string? PrimaryColor { get; set; } + public string? NeutralBaseColor { get; set; } } /// diff --git a/src/Core/GlobalState.cs b/src/Core/GlobalState.cs index d0b3ac76ad..0c83adf269 100644 --- a/src/Core/GlobalState.cs +++ b/src/Core/GlobalState.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using Microsoft.AspNetCore.Components; namespace Microsoft.FluentUI.AspNetCore.Components; @@ -16,6 +20,8 @@ public class GlobalState public string? Color { get; set; } + public string? NeutralColor { get; set; } + public event Action? OnChange; public void SetDirection(LocalizationDirection dir) @@ -42,6 +48,12 @@ public void SetColor(string? color) NotifyStateHasChanged(); } + public void SetNeutralColor(string? neutralColor) + { + NeutralColor = neutralColor; + NotifyStateHasChanged(); + } + private void NotifyStateHasChanged() { OnChange?.Invoke();