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();