diff --git a/examples/Demo/Shared/Pages/Design/DesignTokensPage.razor b/examples/Demo/Shared/Pages/Design/DesignTokensPage.razor index fe17128f88..5597728a02 100644 --- a/examples/Demo/Shared/Pages/Design/DesignTokensPage.razor +++ b/examples/Demo/Shared/Pages/Design/DesignTokensPage.razor @@ -1,4 +1,5 @@ @page "/DesignTokens" +@using FluentUI.Demo.Shared.Pages.Design.Examples @App.PageTitle("Design Tokens") @@ -6,6 +7,28 @@ + + +

Use the first button go swith bewteen dark and light mode. Click the second button to toggle the accent and neutral colors. The third button has a custom font, and the last one has a custom border width and corner radius.

+

+ As can be seen in the code tab (with the `ref4.Element`), it is possible to apply multiple tokens to the same component. +

+ +

+ For Design Tokens that work with a color value, you must call the ToSwatch() extension method* on a string value or use one of the Swatch constructors. This + makes sure the color is using a format that Design Tokens can handle. A Swatch has a lot of commonality with the System.Drawing.Color struct. Instead of + the values of the components being between 0 and 255, in a Swatch the components are expressed as a value between 0 and 1 +

+

* except for the AccentBaseColor and NeutralBaseColor. These just take a hex value as a string for the color.

+
+
+ +

Colors for integration with specific Microsoft products

+

+ If you are configuring the components for integration into a specific Microsoft product, the following table provides `AccentBaseColor` values you can use. + The specific accent colors for many Office applications are offered in the `OfficeColor` enumeration. +

+ @if (refreshCount > 0) { diff --git a/examples/Demo/Shared/Pages/Design/Examples/DesignTokensDefault.razor b/examples/Demo/Shared/Pages/Design/Examples/DesignTokensDefault.razor new file mode 100644 index 0000000000..3bccf11a68 --- /dev/null +++ b/examples/Demo/Shared/Pages/Design/Examples/DesignTokensDefault.razor @@ -0,0 +1,74 @@ +@using Microsoft.FluentUI.AspNetCore.Components.DesignTokens + +Dark/Light +Accent button +And one more +Last button + + +@code { + [Inject] + private BaseLayerLuminance BaseLayerLuminance { get; set; } = default!; + + [Inject] + private AccentBaseColor AccentBaseColor { get; set; } = default!; + + [Inject] + private NeutralBaseColor NeutralBaseColor { get; set; } = default!; + + [Inject] + private BodyFont BodyFont { get; set; } = default!; + + [Inject] + private StrokeWidth StrokeWidth { get; set; } = default!; + + [Inject] + private ControlCornerRadius ControlCornerRadius { get; set; } = default!; + + private FluentButton? ref1; + private FluentButton? ref2; + private FluentButton? ref3; + private FluentButton? ref4; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + //Set to dark mode + //await BaseLayerLuminance.SetValueFor(ref1!.Element, (float)0.15); + + //Set the font + await BodyFont.SetValueFor(ref3!.Element, "Comic Sans MS"); + + //Set 'border' width for ref4 + await StrokeWidth.SetValueFor(ref4!.Element, 7); + //And change conrner radius as well + await ControlCornerRadius.SetValueFor(ref4!.Element, 15); + + StateHasChanged(); + } + } + + public async Task OnClickFirst() + { + float? value = await BaseLayerLuminance.GetValueFor(ref1!.Element); + //Set to light mode + await BaseLayerLuminance.WithDefault(value == 0.15f ? (float)1.0 : (float)0.15); + } + + public async Task OnClickSecond() + { + Swatch accent = await AccentBaseColor.GetValueFor(ref2!.Element); + Swatch neutral = await NeutralBaseColor.GetValueFor(ref2!.Element); + + await AccentBaseColor.WithDefault(accent.B == (float)0.274509817 ? "#0078D4" : "#217346"); + await NeutralBaseColor.WithDefault(neutral.B == (float)0.3372549 ? "#808080" : "#c75656"); + } + + public async Task OnClickLast() + { + //Remove the wide border + await StrokeWidth.DeleteValueFor(ref4!.Element); + } + +} diff --git a/examples/Demo/Shared/Pages/Lab/IssueTester.razor b/examples/Demo/Shared/Pages/Lab/IssueTester.razor index 5f282702bb..8bcfb12f9d 100644 --- a/examples/Demo/Shared/Pages/Lab/IssueTester.razor +++ b/examples/Demo/Shared/Pages/Lab/IssueTester.razor @@ -1 +1,68 @@ - \ No newline at end of file +@using Microsoft.FluentUI.AspNetCore.Components.DesignTokens + +Dark/Light +Accent button +And one more +Last button + + +@code { + [Inject] + private BaseLayerLuminance BaseLayerLuminance { get; set; } = default!; + + [Inject] + private AccentBaseColor AccentBaseColor { get; set; } = default!; + + [Inject] + private NeutralBaseColor NeutralBaseColor { get; set; } = default!; + + [Inject] + private BodyFont BodyFont { get; set; } = default!; + + [Inject] + private StrokeWidth StrokeWidth { get; set; } = default!; + + [Inject] + private ControlCornerRadius ControlCornerRadius { get; set; } = default!; + + private FluentButton? ref1; + private FluentButton? ref2; + private FluentButton? ref3; + private FluentButton? ref4; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + //Set to dark mode + //await BaseLayerLuminance.SetValueFor(ref1!.Element, (float)0.15); + + //Set the font + await BodyFont.SetValueFor(ref3!.Element, "Comic Sans MS"); + + //Set 'border' width for ref4 + await StrokeWidth.SetValueFor(ref4!.Element, 7); + //And change conrner radius as well + await ControlCornerRadius.SetValueFor(ref4!.Element, 15); + + await AccentBaseColor.WithDefault("#217346"); + await NeutralBaseColor.WithDefault("#c75656"); + + StateHasChanged(); + } + } + + public async Task OnClickFirst() + { + float? value = await BaseLayerLuminance.GetValueFor(ref1!.Element); + //Set to light mode + await BaseLayerLuminance.WithDefault(value == 0.15f ? (float)1.0 : (float)0.15); + } + + public async Task OnClickLast() + { + //Remove the wide border + await StrokeWidth.DeleteValueFor(ref4!.Element); + } + +} diff --git a/examples/Demo/Shared/wwwroot/docs/DesignTokens.md b/examples/Demo/Shared/wwwroot/docs/DesignTokens.md index 4e6af245cc..34af0d1e6e 100644 --- a/examples/Demo/Shared/wwwroot/docs/DesignTokens.md +++ b/examples/Demo/Shared/wwwroot/docs/DesignTokens.md @@ -237,78 +237,7 @@ There are a couple of methods available **per design token** to get or set its v - `{DesignTokenName}.WithDefault(T value)` - Sets the default value for the whole design system use. - `{DesignTokenName}.GetValueFor(ElementReference element)` - Gets the value for the given element.- ` -#### Example -Given the following `.razor` page fragment: - -```cshtml -A button -Another button -And one more -Last button -``` - -You can use Design Tokens to manipulate the styles from C# code as follows: - -```csharp -@using Microsoft.FluentUI.AspNetCore.Components.DesignTokens - -[Inject] -private BaseLayerLuminance BaseLayerLuminance { get; set; } = default!; - -[Inject] -private AccentBaseColor AccentBaseColor { get; set; } = default!; - -[Inject] -private BodyFont BodyFont { get; set; } = default!; - -[Inject] -private StrokeWidth StrokeWidth { get; set; } = default!; - -[Inject] -private ControlCornerRadius ControlCornerRadius { get; set; } = default!; - -private FluentButton? ref1; -private FluentButton? ref2; -private FluentButton? ref3; -private FluentButton? ref4; -protected override async Task OnAfterRenderAsync(bool firstRender) -{ - if (firstRender) - { - //Set to dark mode - await BaseLayerLuminance.SetValueFor(ref1!.Element, (float)0.15); - - //Set to Excel color - await AccentBaseColor.SetValueFor(ref2!.Element, "#217346".ToSwatch()); - - //Set the font - await BodyFont.SetValueFor(ref3!.Element, "Comic Sans MS"); - - //Set 'border' width for ref4 - await StrokeWidth.SetValueFor(ref4!.Element, 7); - //And change conrner radius as well - await ControlCornerRadius.SetValueFor(ref4!.Element, 15); - - // If you would like to change the BaseLayerLuminance value for the whole site, you can use the WithDefault method - await BaseLayerLuminance.WithDefault((float)0.15); - - StateHasChanged(); - } -} - -public async Task OnClick() -{ - //Remove the wide border - await StrokeWidth.DeleteValueFor(ref4!.Element); -} -``` - -As can be seen in the code above (with the `ref4.Element`), it is possible to apply multiple tokens to the same component. - -For Design Tokens that work with a color value, you must call the `ToSwatch()` extension method on a string value or use one of the Swatch constructors. This -makes sure the color is using a format that Design Tokens can handle. A Swatch has a lot of commonality with the `System.Drawing.Color` struct. Instead of -the values of the components being between 0 and 255, in a Swatch the components are expressed as a value between 0 and 1. ### Using Design Tokens as components The Design Tokens can also be used as components in a `.razor` page directely. It looks like this: @@ -334,6 +263,3 @@ To make this work, a link needs to be created between the Design Token component > Only one Design Token component at a time can be used this way. If you need to set more tokens, use the code approach as described in Option 1 above. -## Colors for integration with specific Microsoft products -If you are configuring the components for integration into a specific Microsoft product, the following table provides `AccentBaseColor` values you can use. -*The specific accent colors for many Office applications are offered in the `OfficeColor` enumeration.* diff --git a/src/Core.Assets/src/Design/ColorsUtils.ts b/src/Core.Assets/src/Design/ColorsUtils.ts index 7a9edd80a0..d14eb716ce 100644 --- a/src/Core.Assets/src/Design/ColorsUtils.ts +++ b/src/Core.Assets/src/Design/ColorsUtils.ts @@ -4,53 +4,6 @@ class ColorsUtils { return (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches); } - /** - * See https://github.com/microsoft/fast -> packages/utilities/fast-colors/src/parse-color.ts - */ - public static parseColorHexRGB(raw: string | null): ColorRGB | null { - // Matches #RGB and #RRGGBB, where R, G, and B are [0-9] or [A-F] - const hexRGBRegex: RegExp = /^#((?:[0-9a-f]{6}|[0-9a-f]{3}))$/i; - const result: string[] | null = hexRGBRegex.exec(raw ?? ColorsUtils.DEFAULT_COLOR); - - if (result === null) { - return null; - } - - let digits: string = result[1]; - - if (digits.length === 3) { - const r: string = digits.charAt(0); - const g: string = digits.charAt(1); - const b: string = digits.charAt(2); - - digits = r.concat(r, g, g, b, b); - } - - const rawInt: number = parseInt(digits, 16); - - if (isNaN(rawInt)) { - return null; - } - - return new ColorRGB( - this.normalized((rawInt & 0xff0000) >>> 16, 0, 255), - this.normalized((rawInt & 0x00ff00) >>> 8, 0, 255), - this.normalized(rawInt & 0x0000ff, 0, 255), - ); - } - - /** - * Scales an input to a number between 0 and 1 - */ - public static normalized(i: number, min: number, max: number): number { - if (isNaN(i) || i <= min) { - return 0.0; - } else if (i >= max) { - return 1.0; - } - return i / (max - min); - } - /** * Convert to named color to an equivalent Hex color * @param name Office color name @@ -94,16 +47,4 @@ class ColorsUtils { ]; } -class ColorRGB { - constructor(red: number, green: number, blue: number) { - this.r = red; - this.g = green; - this.b = blue; - } - - public readonly r: number; - public readonly g: number; - public readonly b: number; -} - export { ColorsUtils }; diff --git a/src/Core.Assets/src/DesignTheme.ts b/src/Core.Assets/src/DesignTheme.ts index d21334a8cb..12c12129c5 100644 --- a/src/Core.Assets/src/DesignTheme.ts +++ b/src/Core.Assets/src/DesignTheme.ts @@ -3,6 +3,7 @@ // ******************** import { ColorsUtils } from "./Design/ColorsUtils"; +import { parseColorHexRGB } from '@microsoft/fast-colors' import { baseLayerLuminance, StandardLuminance, @@ -81,8 +82,8 @@ class DesignTheme extends HTMLElement { /** * Gets the current color or office name attribute value. - * Access, Booking, Exchange, Excel, GroupMe, Office, OneDrive, OneNote, Outlook, - * Planner, PowerApps, PowerBI, PowerPoint, Project, Publisher, SharePoint, Skype, + * Access, Booking, Exchange, Excel, GroupMe, Office, OneDrive, OneNote, Outlook, + * Planner, PowerApps, PowerBI, PowerPoint, Project, Publisher, SharePoint, Skype, * Stream, Sway, Teams, Visio, Windows, Word, Yammer */ get primaryColor(): string | null { @@ -101,7 +102,7 @@ class DesignTheme extends HTMLElement { : value; // Apply the color - const rgb = ColorsUtils.parseColorHexRGB(color); + const rgb = parseColorHexRGB(color); if (rgb != null) { const swatch = SwatchRGB.from(rgb); accentBaseColor.withDefault(swatch); @@ -250,7 +251,7 @@ class DesignTheme extends HTMLElement { // If not, the dev already "forced" the mode to "dark" or "light" if (currentMode == null) { - // console.log(` ** colorSchemeListener = "${currentMode}"`) + // console.log(` ** colorSchemeListener = "${currentMode}"`) // Dark if (e.matches) { diff --git a/src/Core.Assets/src/index.ts b/src/Core.Assets/src/index.ts index 837f31d5ea..a7f22097b6 100644 --- a/src/Core.Assets/src/index.ts +++ b/src/Core.Assets/src/index.ts @@ -1,5 +1,9 @@ export * from '@fluentui/web-components/dist/web-components' export { parseColorHexRGB } from '@microsoft/fast-colors' + +import { accentBaseColor, neutralBaseColor, SwatchRGB, } from '@fluentui/web-components/dist/web-components' +import { parseColorHexRGB } from '@microsoft/fast-colors' +import { ColorsUtils } from './Design/ColorsUtils' import { SplitPanels } from './SplitPanels' import { DesignTheme } from './DesignTheme' import { FluentPageScript, onEnhancedLoad } from './FluentPageScript' @@ -28,7 +32,6 @@ interface FluentUIEventType { type: string; } - var styleSheet = new CSSStyleSheet(); const styles = ` @@ -301,7 +304,7 @@ export function afterStarted(blazor: Blazor, mode: string) { browserEventName: 'change', createEventArgs: event => { return { - value: event.target._selectedOptions[0] ? event.target._selectedOptions[0].value : event.target.value + value: event.target._selectedOptions[0] ? event.target._selectedOptions[0].value : event.target.value } } }); @@ -331,3 +334,21 @@ export function beforeStart(options: any) { beforeStartCalled = true; } + +export function updateAccentBaseColor(value: string | null) { + const color = value == null || !value.startsWith("#") ? ColorsUtils.getHexColor(value) : value; + const rgb = parseColorHexRGB(color); + if (rgb != null) { + const swatch = SwatchRGB.from(rgb); + accentBaseColor.withDefault(swatch); + } +} + +export function updateNeutralBaseColor(value: string | null) { + const color = value == null || !value.startsWith("#") ? ColorsUtils.getHexColor(value) : value; + const rgb = parseColorHexRGB(color); + if (rgb != null) { + const swatch = SwatchRGB.from(rgb); + neutralBaseColor.withDefault(swatch); + } +} diff --git a/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs b/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs index cdadb36c4a..376bad2a5c 100644 --- a/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs +++ b/src/Core/Components/DesignSystemProvider/FluentDesignTheme.razor.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.Globalization; using System.Text.Json; using Microsoft.AspNetCore.Components; @@ -175,7 +179,7 @@ public async Task OnChangeRaisedAsync(string name, string value) } } - protected async override Task OnAfterRenderAsync(bool firstRender) + protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { diff --git a/src/Core/DesignTokens/DesignToken.razor.cs b/src/Core/DesignTokens/DesignToken.razor.cs index 0791948ef3..15cdd41d57 100644 --- a/src/Core/DesignTokens/DesignToken.razor.cs +++ b/src/Core/DesignTokens/DesignToken.razor.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.Diagnostics.CodeAnalysis; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -84,7 +88,19 @@ private async Task InitJSReferenceAsync() public async ValueTask> WithDefault(string value) { await InitJSReferenceAsync(); - await _jsModule.InvokeVoidAsync(Name + ".withDefault", value); + + if (Name == "accentBaseColor") + { + await _jsModule.InvokeVoidAsync("updateAccentBaseColor", value); + } + else if (Name == "neutralBaseColor") + { + await _jsModule.InvokeVoidAsync("updateNeutralBaseColor", value); + } + else + { + await _jsModule.InvokeVoidAsync(Name + ".withDefault", value); + } return this; } @@ -147,7 +163,6 @@ public async ValueTask GetValueFor(ElementReference element) /// Convert a hex color string to a value the DesignToken can work with /// /// the value - [SuppressMessage("Style", "VSTHRD200:Use `Async` suffix for async methods", Justification = "#vNext: To update in the next version")] public async ValueTask ParseColorHex(string color) { await InitJSReferenceAsync(); diff --git a/src/Core/DesignTokens/DesignTokenHelpers.cs b/src/Core/DesignTokens/DesignTokenHelpers.cs index 3ac5e600b6..d3295373dc 100644 --- a/src/Core/DesignTokens/DesignTokenHelpers.cs +++ b/src/Core/DesignTokens/DesignTokenHelpers.cs @@ -1,3 +1,7 @@ +// ------------------------------------------------------------------------ +// MIT License - Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------------------ + using System.Drawing; namespace Microsoft.FluentUI.AspNetCore.Components.DesignTokens;