diff --git a/NEWS-BANNER.md b/NEWS-BANNER.md new file mode 100644 index 0000000000..ac77936a63 --- /dev/null +++ b/NEWS-BANNER.md @@ -0,0 +1,6 @@ +--- +Title: New release available +Intent: Success +--- +Version RC4 is now available with many new components. +Check out the GitHub changelog for all the details and breaking changes. \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo.Client/Layout/DemoMainLayout.razor b/examples/Demo/FluentUI.Demo.Client/Layout/DemoMainLayout.razor index 017e513c53..c3a962b353 100644 --- a/examples/Demo/FluentUI.Demo.Client/Layout/DemoMainLayout.razor +++ b/examples/Demo/FluentUI.Demo.Client/Layout/DemoMainLayout.razor @@ -2,7 +2,9 @@ @App.PageTitle("Demo") - + + + @* ------------------- *@ @* Header *@ diff --git a/examples/Demo/FluentUI.Demo.Client/Layout/DemoNewsBar.razor b/examples/Demo/FluentUI.Demo.Client/Layout/DemoNewsBar.razor new file mode 100644 index 0000000000..4466c7e60f --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Layout/DemoNewsBar.razor @@ -0,0 +1,44 @@ +@if (Visible) +{ + + +
+ @if (!string.IsNullOrEmpty(NewsContent)) + { + @NewsTitle:  + } + @NewsContent +
+ + +
+ +
+} + + \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo.Client/Layout/DemoNewsBar.razor.cs b/examples/Demo/FluentUI.Demo.Client/Layout/DemoNewsBar.razor.cs new file mode 100644 index 0000000000..50f95ab8ab --- /dev/null +++ b/examples/Demo/FluentUI.Demo.Client/Layout/DemoNewsBar.razor.cs @@ -0,0 +1,176 @@ +// ------------------------------------------------------------------------ +// This file is licensed to you under the MIT License. +// ------------------------------------------------------------------------ + +using System.Security.Cryptography; +using System.Text; +using Microsoft.AspNetCore.Components; +using Microsoft.FluentUI.AspNetCore.Components; +using Microsoft.JSInterop; + +namespace FluentUI.Demo.Client.Layout; + +/// +/// A component that displays a news message bar in the demo application. +/// +public partial class DemoNewsBar +{ + /* + -------------------------------------------------------------------- + NEWS-BANNER.md + -------------------------------------------------------------------- + + --- + Title: New release available + Intent: Success + --- + Version RC4 is now available with many new components. + Check out the changelog for all the details and breaking changes. + */ + + /// + /// The URI of the news content to display in the message bar. + /// + private static readonly Uri NewsUri = new("https://raw.githubusercontent.com/microsoft/fluentui-blazor/refs/heads/dev-v5/NEWS-BANNER.md"); + + private const string LocalStorageKey = "fluentui-demo-newsbar-sha"; + + private string NewsTitle { get; set; } = "News"; + + private MessageBarIntent NewsIntent { get; set; } = MessageBarIntent.Info; + + private string? NewsContent { get; set; } + + private string? NewsSha { get; set; } + + private bool Visible { get; set; } + + [Inject] + public required HttpClient HttpClient { get; set; } + + [Inject] + public required IJSRuntime JSRuntime { get; set; } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender) + { + return; + } + + // Read the news content. + var raw = ""; + + try + { + raw = await HttpClient.GetStringAsync(NewsUri); + } + catch (HttpRequestException) + { + Console.WriteLine($"DemoNewsBar: Failed to read the news content from {NewsUri}"); + } + + if (string.IsNullOrWhiteSpace(raw)) + { + Visible = false; + return; + } + + // Extract the Title, Intent and content from the front matter. + ParseNewsBanner(raw); + + if (string.IsNullOrWhiteSpace(NewsContent)) + { + Visible = false; + return; + } + + // Compute a small SHA of the whole file content. + NewsSha = ComputeSha(raw); + + // If the stored SHA matches the current content, the user already + // read this message, so the message bar stays hidden. + var storedSha = await JSRuntime.InvokeAsync("localStorage.getItem", LocalStorageKey); + Visible = !string.Equals(storedSha, NewsSha, StringComparison.Ordinal); + + StateHasChanged(); + + // When the content is set and the message bar is visible, apply the + // notification style override defined in the razor script. + if (Visible) + { + await JSRuntime.InvokeVoidAsync("applyDemoNotificationStyle"); + } + } + + private async Task DismissClickAsync() + { + // Save the SHA of the read message into the user local storage. + if (NewsSha is not null) + { + await JSRuntime.InvokeVoidAsync("localStorage.setItem", LocalStorageKey, NewsSha); + } + + Visible = false; + } + + private static string ComputeSha(string content) + { + var hash = SHA256.HashData(Encoding.UTF8.GetBytes(content)); + + // Keep a small SHA: the first 16 bytes are enough to detect content changes. + return Convert.ToHexString(hash, 0, 16).ToLowerInvariant(); + } + + /// + /// Parses the news banner content using the front matter format: + /// a metadata block delimited by "---" lines containing the Title and + /// Intent, followed by the content body. + /// + /// The raw file content to parse. + private void ParseNewsBanner(string raw) + { + var lines = raw.Replace("\r\n", "\n").Split('\n'); + var index = 0; + + // The front matter must start with a "---" delimiter line. + if (index < lines.Length && lines[index].Trim() == "---") + { + index++; + + // Read the metadata until the closing "---" delimiter line. + while (index < lines.Length && lines[index].Trim() != "---") + { + var line = lines[index]; + var separator = line.IndexOf(':'); + + if (separator > 0) + { + var key = line[..separator].Trim(); + var value = line[(separator + 1)..].Trim(); + + if (string.Equals(key, "Title", StringComparison.OrdinalIgnoreCase)) + { + NewsTitle = value; + } + else if (string.Equals(key, "Intent", StringComparison.OrdinalIgnoreCase) + && Enum.TryParse(value, ignoreCase: true, out var intent)) + { + NewsIntent = intent; + } + } + + index++; + } + + // Skip the closing "---" delimiter line. + if (index < lines.Length) + { + index++; + } + } + + // The remaining lines are the content body. + NewsContent = string.Join(Environment.NewLine, lines[index..]).Trim(); + } +} \ No newline at end of file diff --git a/examples/Demo/FluentUI.Demo.Client/wwwroot/app.css b/examples/Demo/FluentUI.Demo.Client/wwwroot/app.css index 9279bef649..a30d3de7e9 100644 --- a/examples/Demo/FluentUI.Demo.Client/wwwroot/app.css +++ b/examples/Demo/FluentUI.Demo.Client/wwwroot/app.css @@ -18,6 +18,21 @@ body[data-media="xs"] svg g[data-name="MS-symbol"] { display: none; } +/* Web Site Notification bar height variable */ +body { + --demo-notification-height: 0px; +} + +body:has(fluent-message-bar.demo-notification) { + --demo-notification-height: 60px; +} + +.demo-notification { + height: var(--demo-notification-height); + font-size: var(--fontSizeBase300); + overflow-y: auto; +} + /* Blazor Styles */ #blazor-error-ui { @@ -34,12 +49,12 @@ body[data-media="xs"] svg g[data-name="MS-symbol"] { z-index: 1000; } - #blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; - } +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} .blazor-error-boundary { background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; @@ -47,9 +62,9 @@ body[data-media="xs"] svg g[data-name="MS-symbol"] { color: white; } - .blazor-error-boundary::after { - content: "An error has occurred." - } +.blazor-error-boundary::after { + content: "An error has occurred." +} /* loading-progress */ @@ -61,19 +76,19 @@ body[data-media="xs"] svg g[data-name="MS-symbol"] { margin: 20vh auto 1rem auto; } - .loading-progress circle { - fill: none; - stroke: #e0e0e0; - stroke-width: 0.6rem; - transform-origin: 50% 50%; - transform: rotate(-90deg); - } +.loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); +} - .loading-progress circle:last-child { - stroke: #1b6ec2; - stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; - transition: stroke-dasharray 0.05s ease-in-out; - } +.loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; +} .loading-progress-text { position: absolute; @@ -82,6 +97,6 @@ body[data-media="xs"] svg g[data-name="MS-symbol"] { inset: calc(20vh + 3.25rem) 0 auto 0.2rem; } - .loading-progress-text:after { - content: var(--blazor-load-percentage-text, "Loading"); - } +.loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); +} \ No newline at end of file