├── sample.png ├── src ├── BlazorTemplate.Components │ ├── Layout │ │ ├── NavMenu.razor.css │ │ ├── MainLayout.razor.js │ │ ├── MainLayout.razor.cs │ │ ├── NavMenu.razor │ │ └── MainLayout.razor │ ├── Pages │ │ ├── Demo │ │ │ ├── WhatsNew.razor │ │ │ ├── Dialog │ │ │ │ └── Examples │ │ │ │ │ ├── DialogSimpleDialog.razor │ │ │ │ │ ├── DialogDefault.razor.css │ │ │ │ │ ├── DialogCustomizableExample.razor │ │ │ │ │ ├── SimpleDialog.razor │ │ │ │ │ ├── SimpleCustomizedDialog.razor │ │ │ │ │ ├── DialogServiceExample.razor │ │ │ │ │ ├── DialogServiceCallbackExample.razor │ │ │ │ │ ├── DialogDefault.razor │ │ │ │ │ └── DialogServiceAnimationCallbackExample.razor │ │ │ ├── Toast │ │ │ │ ├── Examples │ │ │ │ │ ├── MyToastData.cs │ │ │ │ │ ├── MyToastComponent.razor │ │ │ │ │ ├── ToastDefault.razor │ │ │ │ │ ├── ToastCustomComponent.razor │ │ │ │ │ ├── ToastDetailled.razor │ │ │ │ │ ├── ToastCommunicationToasts.razor │ │ │ │ │ ├── ToastClearQueue.razor │ │ │ │ │ ├── ToastProgressToasts.razor │ │ │ │ │ ├── ToastClearList.razor │ │ │ │ │ └── ToastConfirmationToasts.razor │ │ │ │ └── ToastPage.razor │ │ │ ├── Panel │ │ │ │ ├── Examples │ │ │ │ │ ├── DialogPanel.razor │ │ │ │ │ ├── DialogPanelAsync.razor │ │ │ │ │ ├── SimplePanel.razor │ │ │ │ │ ├── DialogPanelAsync.razor.cs │ │ │ │ │ └── DialogPanel.razor.cs │ │ │ │ └── PanelPage.razor │ │ │ ├── MessageBar │ │ │ │ ├── Examples │ │ │ │ │ ├── MessageBarSimpleNotification.razor │ │ │ │ │ ├── MessageBarSimple.razor │ │ │ │ │ ├── MessageBarSamples.cs │ │ │ │ │ ├── MessageBarDetailed.razor │ │ │ │ │ ├── MessageBarDefault.razor │ │ │ │ │ └── MessageBarTimed.razor │ │ │ │ └── MessageBarPage.razor │ │ │ ├── ToastServicePage.razor │ │ │ ├── MessageBox │ │ │ │ ├── MessageBoxPage.razor │ │ │ │ └── Examples │ │ │ │ │ └── DialogMessageBoxAsync.razor │ │ │ ├── MessageServicePage.razor │ │ │ └── DialogServicePage.razor │ │ ├── Home.razor │ │ ├── Perspective │ │ │ ├── PerspectiveWeatherPage.razor │ │ │ ├── PerspectiveGrid.razor │ │ │ ├── PerspectiveGrid.razor.cs │ │ │ └── PerspectiveGrid.razor.js │ │ ├── Counter.razor │ │ ├── FetchData.razor │ │ ├── Weather.razor │ │ └── DataGridRemoteData.razor │ ├── Common │ │ ├── CodeSnippet.razor │ │ ├── ConsoleLog.razor.css │ │ ├── SiteSettings.razor │ │ ├── DemoSection.razor.css │ │ ├── NotificationCenterPanel.razor │ │ ├── SiteSettings.razor.cs │ │ ├── CodeSnippet.razor.js │ │ ├── CodeSnippet.razor.cs │ │ ├── ConsoleLog.razor │ │ ├── SiteSettingsPanel.razor.cs │ │ ├── NotificationCenter.razor │ │ ├── CodeSnippet.razor.css │ │ ├── DemoSection.razor │ │ ├── SiteSettingsPanel.razor │ │ └── DemoSection.razor.cs │ ├── wwwroot │ │ ├── images │ │ │ ├── BlazorLogo.png │ │ │ ├── newproject.png │ │ │ ├── template-home.png │ │ │ ├── template-counter.png │ │ │ ├── template-weather.png │ │ │ ├── Icons-FindReplace-RegEx.png │ │ │ └── Splash_Corporation_logo.png │ │ ├── js │ │ │ ├── CacheStorageAccessor.js │ │ │ └── highlight-extensions.js │ │ └── css │ │ │ └── site.css │ ├── Infrastructure │ │ ├── IStaticAssetService.cs │ │ ├── ServiceCollectionExtensions.cs │ │ ├── HttpBasedStaticAssetService.cs │ │ └── CacheStorageAccessor.cs │ ├── App.razor.cs │ ├── DemoLogger.cs │ ├── App.razor │ ├── _Imports.razor │ └── BlazorTemplate.Components.csproj ├── BlazorTemplate.Client │ ├── wwwroot │ │ ├── favicon.ico │ │ ├── icon-192.png │ │ ├── staticwebapp.config.json │ │ └── index.html │ ├── _Imports.razor │ ├── Properties │ │ └── launchSettings.json │ ├── Program.cs │ └── BlazorTemplate.Client.csproj ├── BlazorTemplate.Server │ ├── wwwroot │ │ ├── favicon.ico │ │ └── sample-data │ │ │ ├── weather.schema.json │ │ │ └── weather.json │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── Pages │ │ ├── _Host.cshtml │ │ ├── Error.cshtml.cs │ │ ├── Error.cshtml │ │ └── _Layout.cshtml │ ├── Properties │ │ └── launchSettings.json │ ├── Controllers │ │ └── WeatherForecastController.cs │ ├── BlazorTemplate.Server.csproj │ └── Program.cs └── BlazorTemplate.Shared │ ├── BlazorTemplate.Shared.csproj │ └── SampleData │ ├── WeatherForecast.cs │ ├── Olympics.cs │ ├── Person.cs │ ├── DataSourceExtensions.cs │ ├── FoodRecall.cs │ └── Starship.cs ├── CHANGELOG.md ├── LICENSE ├── README.md ├── Dashboard.sln └── .gitignore /sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/sample.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | ::deep .fluent-nav-icon { 2 | padding-top: 5px; 3 | } 4 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Client/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Client/wwwroot/icon-192.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/WhatsNew.razor: -------------------------------------------------------------------------------- 1 | @page "/WhatsNew" 2 | 3 | 4 | What's new? 5 | 6 |

What's new?

7 | 8 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Server/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/CodeSnippet.razor: -------------------------------------------------------------------------------- 1 |
@ChildContent
2 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Home.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | Home 4 | 5 |

Hello, world!

6 | 7 | Welcome to your new Fluent Blazor app. -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/DialogSimpleDialog.razor: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/ConsoleLog.razor.css: -------------------------------------------------------------------------------- 1 | .demo-console { 2 | text-overflow: ellipsis; 3 | overflow-y: auto; 4 | height: 300px; 5 | color: black; 6 | } 7 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/images/BlazorLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Components/wwwroot/images/BlazorLogo.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/images/newproject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Components/wwwroot/images/newproject.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/images/template-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Components/wwwroot/images/template-home.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/wwwroot/staticwebapp.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationFallback": { 3 | "rewrite": "/index.html", 4 | "exclude": [ "_content/FluentUI.Demo.Shared/sources/*" ] 5 | } 6 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/images/template-counter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Components/wwwroot/images/template-counter.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/images/template-weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Components/wwwroot/images/template-weather.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/images/Icons-FindReplace-RegEx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Components/wwwroot/images/Icons-FindReplace-RegEx.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/images/Splash_Corporation_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afedyanin/blazor-fluentui-template/HEAD/src/BlazorTemplate.Components/wwwroot/images/Splash_Corporation_logo.png -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/DialogDefault.razor.css: -------------------------------------------------------------------------------- 1 | ::deep > fluent-dialog::part(control) { 2 | --dialog-width: 300px; 3 | --dialog-height: 350px; 4 | padding: 2rem!important; 5 | } 6 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/MyToastData.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Components.Pages.Demo.Toast.Examples; 2 | 3 | public class MyToastData 4 | { 5 | public string? ToastParam { get; set; } 6 | } 7 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Infrastructure/IStaticAssetService.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Components.Infrastructure; 2 | 3 | public interface IStaticAssetService 4 | { 5 | public Task GetAsync(string assetUrl, bool useCache = true); 6 | } 7 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "DetailedErrors": true 10 | } 11 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/wwwroot/sample-data/weather.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "datetime", 3 | "temperatureC": "integer", 4 | "temperatureF": "integer", 5 | "summary": "string", 6 | "value": "float", 7 | "isCold": "boolean" 8 | } 9 | 10 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace BlazorTemplate.Server.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = "_Layout"; 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Shared/BlazorTemplate.Shared.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/SiteSettings.razor: -------------------------------------------------------------------------------- 1 | @inject IDialogService DialogService 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Perspective/PerspectiveWeatherPage.razor: -------------------------------------------------------------------------------- 1 | @page "/perspective-weather" 2 | 3 |

Perspective Wather Demo

4 | 5 | 10 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/App.razor.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Components; 2 | public partial class App 3 | { 4 | public const string MESSAGES_NOTIFICATION_CENTER = "NOTIFICATION_CENTER"; 5 | public const string MESSAGES_TOP = "TOP"; 6 | public const string MESSAGES_DIALOG = "DIALOG"; 7 | public const string MESSAGES_CARD = "CARD"; 8 | } 9 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/DemoLogger.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Components; 2 | 3 | public delegate void OnLogHandler(string text); 4 | 5 | public static class DemoLogger 6 | { 7 | public static event OnLogHandler? OnLogHandler; 8 | 9 | public static void WriteLine(string text) 10 | { 11 | OnLogHandler?.Invoke(text); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Shared/SampleData/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Shared.SampleData; 2 | 3 | public class WeatherForecast 4 | { 5 | public DateOnly Date { get; set; } 6 | 7 | public int TemperatureC { get; set; } 8 | 9 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 10 | 11 | public string? Summary { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Panel/Examples/DialogPanel.razor: -------------------------------------------------------------------------------- 1 | @inject IDialogService DialogService 2 | 3 | 4 | Open panel (>>) 5 | 6 | 7 | 8 | Open panel (<<) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Shared/SampleData/Olympics.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Shared.SampleData; 2 | 3 | public record Medals 4 | { 5 | public int Gold { get; init; } 6 | public int Silver { get; init; } 7 | public int Bronze { get; init; } 8 | 9 | public int Total => Gold + Silver + Bronze; 10 | } 11 | 12 | public record Country(string Code, string Name, Medals Medals); 13 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Panel/Examples/DialogPanelAsync.razor: -------------------------------------------------------------------------------- 1 | @inject IDialogService DialogService 2 | 3 | 4 | Open panel (>>) 5 | 6 | 7 | 8 | Open panel (<<) 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Layout/MainLayout.razor.js: -------------------------------------------------------------------------------- 1 | export function isDevice() { 2 | return /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini|mobile/i.test(navigator.userAgent); 3 | } 4 | 5 | export function isDarkMode() { 6 | let matched = window.matchMedia("(prefers-color-scheme: dark)").matches ; 7 | 8 | if (matched) 9 | return true; 10 | else 11 | return false; 12 | } 13 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBar/Examples/MessageBarSimpleNotification.razor: -------------------------------------------------------------------------------- 1 | 6 | Successfully deleted 'XYZ-blazor.pdf' 7 | 8 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "BlazorTemplate.Server": { 5 | "commandName": "Project", 6 | "launchBrowser": true, 7 | "environmentVariables": { 8 | "ASPNETCORE_ENVIRONMENT": "Development" 9 | }, 10 | "dotnetRunMessages": true, 11 | "applicationUrl": "https://localhost:5000" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 11-01-24 2 | 3 | - [x] переработаны FluentUI компоненты 4 | - [x] добавить Perspective 5 | - [ ] Authz - BFF + Keykloack 6 | - [ ] При выборе цвета моргает дропдаун 7 | - [ ] Добавить клиентскую часть для Perspective 8 | - [ ] дорабоать сообщение об ошибках и линк Reload - сейчас не работает 9 | 10 | ## 10-01-24 11 | 12 | - Доработать механику SSR и проверить работоспособность 13 | - Интегрироваться с Perspective 14 | - Authz - BFF + Keykloack 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Counter.razor: -------------------------------------------------------------------------------- 1 | @page "/counter" 2 | 3 | Counter 4 | 5 |

Counter

6 | 7 |

Current count: @currentCount

8 | 9 | Click me 10 | 11 | @code { 12 | private int currentCount = 0; 13 | 14 | private void IncrementCount() 15 | { 16 | currentCount++; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "BlazorTemplate.Client": { 4 | "commandName": "Project", 5 | "launchBrowser": true, 6 | "environmentVariables": { 7 | "ASPNETCORE_ENVIRONMENT": "Development" 8 | }, 9 | "dotnetRunMessages": true, 10 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 11 | "applicationUrl": "https://localhost:7026" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/DemoSection.razor.css: -------------------------------------------------------------------------------- 1 | ::deep .heavy { 2 | font-weight: 600; 3 | } 4 | 5 | .demo-section-example { 6 | border: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-rest); 7 | border-radius: calc(var(--control-corner-radius) * 1px); 8 | padding: 1rem 1rem; 9 | min-height: 100px; 10 | } 11 | 12 | ::deep p { 13 | margin-top: 1rem; 14 | } 15 | 16 | 17 | .demo-section-downloads { 18 | margin-top: 0.5rem; 19 | display: flex; 20 | align-items: center; 21 | } 22 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Not found 9 | 10 |

Sorry, there's nothing at this address.

11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Shared/SampleData/Person.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Shared.SampleData; 2 | 3 | public record Person(int PersonId, string CountryCode, string FirstName, string LastName, DateOnly BirthDate, string Picture) 4 | { 5 | public override string ToString() => $"{FirstName} {LastName} ({BirthDate}, {CountryCode})"; 6 | } 7 | 8 | public class SimplePerson 9 | { 10 | public string Firstname { get; set; } = string.Empty; 11 | public string Lastname { get; set; } = string.Empty; 12 | public int Age { get; set; } 13 | } 14 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/MyToastComponent.razor: -------------------------------------------------------------------------------- 1 | @implements IToastContentComponent 2 | 3 |
4 |
@Content.ToastParam
5 |
6 | Close 7 |
8 |
9 | 10 | @code { 11 | [CascadingParameter] 12 | private FluentToast Toast { get; set; } = default!; 13 | 14 | [Parameter] 15 | public MyToastData Content { get; set; } = default!; 16 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Perspective/PerspectiveGrid.razor: -------------------------------------------------------------------------------- 1 |
2 | @TableName 3 | 4 |
5 | 6 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/ToastDefault.razor: -------------------------------------------------------------------------------- 1 | @inject IToastService ToastService 2 | 3 |

Click on this button to open a Toast. The toast is created by just specifying an intent and a message.

4 | 5 | Open 6 | 7 | @code 8 | { 9 | int counter = 1; 10 | void ShowToast() 11 | { 12 | Random rnd = new(); 13 | 14 | var intent = Enum.GetValues()[rnd.Next(10)]; 15 | var message = $"Simple Toast #{counter++}"; 16 | ToastService.ShowToast(intent, message); 17 | } 18 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Panel/Examples/SimplePanel.razor: -------------------------------------------------------------------------------- 1 | @implements IDialogContentComponent 2 | 3 | 4 |

Hello @Content.Firstname

5 |

Your lastname is @Content.Lastname and you are @Content.Age years young

6 | 7 | Your firstname: 8 | Your lastname: 9 | Your age: 10 |
11 | 12 | @code { 13 | [Parameter] 14 | public SimplePerson Content { get; set; } = default!; 15 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.Extensions.Logging; 9 | @using static Microsoft.AspNetCore.Components.Web.RenderMode 10 | 11 | @using Microsoft.FluentUI.AspNetCore.Components 12 | @using Microsoft.FluentUI.AspNetCore.Components.DesignTokens 13 | @using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons 14 | 15 | @using Microsoft.JSInterop 16 | 17 | @using BlazorTemplate.Components.Common 18 | @using BlazorTemplate.Shared.SampleData 19 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Shared/SampleData/DataSourceExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Shared.SampleData; 2 | 3 | public static class DataSourceExtensions 4 | { 5 | public static IQueryable WithVeryLongName(this IQueryable values) 6 | { 7 | var longName = new Person(PersonId: 91, 8 | FirstName: "Jean", 9 | LastName: "With a very long name to validate components", 10 | CountryCode: "fr", 11 | BirthDate: new DateOnly(1984, 4, 27), 12 | Picture: DataSource.ImageFaces[0]); 13 | return values.Concat(new[] { longName }); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/NotificationCenterPanel.razor: -------------------------------------------------------------------------------- 1 | @implements IDialogContentComponent 2 | @inject IMessageService MessageService 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | Dismiss all 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 |
19 | 20 | @code { 21 | [Parameter] 22 | public GlobalState Content { get; set; } = default!; 23 | } 24 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/ToastCustomComponent.razor: -------------------------------------------------------------------------------- 1 | @inject IToastService ToastService 2 | 3 | Custom Toast with parameters 4 | 5 | @code { 6 | private void ShowExample() 7 | { 8 | ToastService.ShowToast(new ToastParameters() 9 | { 10 | Intent = ToastIntent.Custom, 11 | Title = "I'm a custom toast component with parameters", 12 | Timeout = 6000, 13 | Icon = (new Icons.Regular.Size20.Backpack(), Color.Accent), 14 | Content = new MyToastData() 15 | { 16 | ToastParam = "I'm a parameter" 17 | } 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace BlazorTemplate.Server.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/SiteSettings.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.FluentUI.AspNetCore.Components; 2 | 3 | namespace BlazorTemplate.Components.Common; 4 | 5 | public partial class SiteSettings 6 | { 7 | private IDialogReference? _dialog; 8 | 9 | private async Task OpenSiteSettingsAsync() 10 | { 11 | DemoLogger.WriteLine($"Open site settings"); 12 | _dialog = await DialogService.ShowPanelAsync(new DialogParameters() 13 | { 14 | ShowTitle = true, 15 | Title = "Site settings", 16 | Alignment = HorizontalAlignment.Right, 17 | PrimaryAction = "OK", 18 | SecondaryAction = null, 19 | ShowDismiss = true 20 | }); 21 | 22 | var result = await _dialog.Result; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Infrastructure/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace BlazorTemplate.Components.Infrastructure; 4 | 5 | public static class ServiceCollectionExtensions 6 | { 7 | /// 8 | /// Add common services required by the Fluent UI Web Components for Blazor library 9 | /// 10 | /// Service collection 11 | /// Library configuration 12 | public static IServiceCollection AddFluentUIDemoServices(this IServiceCollection services) 13 | { 14 | services.AddScoped(); 15 | services.AddHttpClient(); 16 | 17 | return services; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorTemplate.Components; 2 | using BlazorTemplate.Components.Infrastructure; 3 | using BlazorTemplate.Shared.SampleData; 4 | using Microsoft.AspNetCore.Components.Web; 5 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 6 | using Microsoft.FluentUI.AspNetCore.Components; 7 | 8 | 9 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 10 | 11 | builder.RootComponents.Add("#app"); 12 | builder.RootComponents.Add("head::after"); 13 | 14 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:5000") }); 15 | 16 | Console.WriteLine("Loading BlazorTemplate.Client"); 17 | 18 | builder.Services.AddFluentUIComponents(); 19 | builder.Services.AddFluentUIDemoServices(); 20 | builder.Services.AddScoped(); 21 | 22 | await builder.Build().RunAsync(); 23 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Shared/SampleData/FoodRecall.cs: -------------------------------------------------------------------------------- 1 | #nullable disable 2 | 3 | namespace BlazorTemplate.Shared.SampleData; 4 | 5 | 6 | // Represents the data returned by https://open.fda.gov/apis/food/enforcement/ 7 | // This is a subset of the fields available on that API 8 | public class FoodRecall 9 | { 10 | public string Event_Id { get; set; } 11 | public string Status { get; set; } 12 | public string City { get; set; } 13 | public string State { get; set; } 14 | public string Recalling_Firm { get; set; } 15 | } 16 | 17 | public class FoodRecallQueryResult 18 | { 19 | public Metadata Meta { get; set; } 20 | public FoodRecall[] Results { get; set; } 21 | 22 | public class Metadata 23 | { 24 | public ResultsInfo Results { get; set; } 25 | } 26 | 27 | public class ResultsInfo 28 | { 29 | public int Total { get; set; } 30 | } 31 | } 32 | 33 | #nullable enable 34 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/CodeSnippet.razor.js: -------------------------------------------------------------------------------- 1 | export function addCopyButton() { 2 | var snippets = document.querySelectorAll('.snippet'); 3 | var numberOfSnippets = snippets.length; 4 | for (var i = 0; i < numberOfSnippets; i++) { 5 | let copyButton = snippets[i].getElementsByClassName("hljs-copy") 6 | if (copyButton.length === 0) { 7 | let code = snippets[i].getElementsByTagName('code')[0].innerText; 8 | snippets[i].innerHTML = snippets[i].innerHTML + ''; // append copy button 9 | 10 | copyButton[0].addEventListener("click", function () { 11 | navigator.clipboard.writeText(code); 12 | 13 | this.innerText = 'Copied!'; 14 | let button = this; 15 | setTimeout(function () { 16 | button.innerText = 'Copy'; 17 | }, 1000) 18 | }); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/BlazorTemplate.Components.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | latest 8 | false 9 | false 10 | false 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Shared/SampleData/Starship.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Diagnostics.CodeAnalysis; 3 | 4 | namespace BlazorTemplate.Shared.SampleData; 5 | 6 | [RequiresUnreferencedCode("Necessary because of RangeAttribute usage")] 7 | public class Starship 8 | { 9 | [Required] 10 | [MinLength(3, ErrorMessage = "Identifier is too short!")] 11 | [StringLength(16, ErrorMessage = "Identifier too long (16 character limit).")] 12 | public string? Identifier { get; set; } 13 | 14 | public string? Description { get; set; } 15 | 16 | [Required(ErrorMessage = "A classification is required")] 17 | public string? Classification { get; set; } 18 | 19 | [Range(1, 100000, ErrorMessage = "Accommodation invalid (1-100000).")] 20 | public int MaximumAccommodation { get; set; } 21 | 22 | [Required] 23 | [Range(typeof(bool), "true", "true", 24 | ErrorMessage = "This form disallows unapproved ships.")] 25 | public bool IsValidatedDesign { get; set; } 26 | 27 | [Required] 28 | public DateTime? ProductionDate { get; set; } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Anatoly Fedyanin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBar/Examples/MessageBarSimple.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | Message providing information to the user with actionable insights. 4 | 5 | 6 | 7 | Message providing information to the user with actionable insights. 8 | 9 | 10 | 11 | Message providing information to the user with actionable insights. 12 | 13 | 14 | 15 | Message providing information to the user with actionable insights. 16 | 17 | 18 | 19 | Message providing information to the user with actionable insights. 20 | 21 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/ToastDetailled.razor: -------------------------------------------------------------------------------- 1 | @inject IToastService ToastService 2 | 3 |

Click on this button to open a Toast. The toast is created by specifying an intent, message, action and a random timeout between 3 and 15 seconds.

4 | 5 | Open 6 | 7 | @code 8 | { 9 | int counter = 1; 10 | void ShowToast() 11 | { 12 | Random rnd = new(); 13 | 14 | var intent = Enum.GetValues()[rnd.Next(10)]; 15 | var message = $"Simple Toast #{counter++}"; 16 | 17 | ToastService.ShowToast( 18 | intent, 19 | message, 20 | rnd.Next(3000, 15000), 21 | "Log click", 22 | EventCallback.Factory.Create(this, HandleTopAction) 23 | ); 24 | } 25 | 26 | private void HandleTopAction(ToastResult result) 27 | { 28 | DemoLogger.WriteLine($"Toast clicked"); 29 | } 30 | 31 | private async Task HandleTopActionAsync(ToastResult result) 32 | { 33 | await Task.Run(()=> DemoLogger.WriteLine($"Toast clicked")); 34 | } 35 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using BlazorTemplate.Shared.SampleData; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace BlazorTemplate.Server.Controllers; 5 | [ApiController] 6 | [Route("[controller]")] 7 | public class WeatherForecastController : ControllerBase 8 | { 9 | private static readonly string[] Summaries = new[] 10 | { 11 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 12 | }; 13 | 14 | private readonly ILogger _logger; 15 | 16 | public WeatherForecastController(ILogger logger) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | [HttpGet(Name = "GetWeatherForecast")] 22 | public IEnumerable Get() 23 | { 24 | return Enumerable.Range(1, 50).Select(index => new WeatherForecast 25 | { 26 | Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), 27 | TemperatureC = Random.Shared.Next(-20, 55), 28 | Summary = Summaries[Random.Shared.Next(Summaries.Length)] 29 | }) 30 | .ToArray(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blazor-fluentui-template 2 | 3 | Blazor 8 Dashboard Template based on Fluent UI Blazor .NET (v8) 4 | 5 | ![sample](sample.png) 6 | 7 | ## Fluent UI Blazor 8 | 9 | - [GitHub](https://github.com/microsoft/fluentui-blazor) 10 | - [Demo & Docs](https://www.fluentui-blazor.net/) 11 | 12 | ## Perspective Grid 13 | 14 | - [JavaScript User Guide](https://perspective.finos.org/docs/js/) 15 | - [GitHub](https://github.com/finos/perspective) 16 | - [Market Simulation Sample](https://prospective.co/blog/market-simulation) 17 | 18 | ### http-server to run static samples 19 | 20 | The easiest is to install http-server globally using node's package manager: 21 | 22 | ``` 23 | npm install -g http-server 24 | ``` 25 | 26 | Then simply run http-server in any of your project directories: 27 | 28 | ``` 29 | d:\my_project> http-server 30 | ``` 31 | 32 | 33 | ## Resources 34 | 35 | - [Keynote: Where Web Tech is Going Now - Steve Sanderson - NDC Porto 2023](https://www.youtube.com/watch?v=fIYYC_p_uU8) 36 | - [Blazor WebAssembly .NET 7 vs .NET 8 - What's Changed?](https://www.youtube.com/watch?v=2bEhiyqztwg) 37 | - [Blazor WebAssembly ASP.NET Core Hosted in .NET 8](https://www.youtube.com/watch?v=3Ur79_kHVpo) 38 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/CodeSnippet.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.JSInterop; 3 | 4 | namespace BlazorTemplate.Components.Common; 5 | 6 | /// 7 | public partial class CodeSnippet 8 | { 9 | private ElementReference codeElement; 10 | 11 | private IJSObjectReference _jsModule = default!; 12 | 13 | [Inject] 14 | protected IJSRuntime JSRuntime { get; set; } = default!; 15 | 16 | [Parameter] 17 | public RenderFragment ChildContent { get; set; } = default!; 18 | 19 | [Parameter] 20 | public string Language { get; set; } = "language-cshtml-razor"; 21 | 22 | [Parameter] 23 | public string? Style { get; set; } = null; 24 | 25 | protected override async Task OnAfterRenderAsync(bool firstRender) 26 | { 27 | if (firstRender) 28 | { 29 | await JSRuntime.InvokeVoidAsync("hljs.highlightElement", codeElement); 30 | 31 | _jsModule = await JSRuntime.InvokeAsync("import", 32 | "./_content/BlazorTemplate.Components/Common/CodeSnippet.razor.js"); 33 | 34 | await _jsModule.InvokeVoidAsync("addCopyButton"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBar/Examples/MessageBarSamples.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorTemplate.Components.Pages.Demo.MessageBar.Examples; 2 | 3 | /// 4 | public class MessageBarSamples 5 | { 6 | public static string[] Messages => new[] 7 | { 8 | "The first five days after the weekend are the toughest.", 9 | "The leading source of computer problems is computer solutions.", 10 | "The only scenario where you really need a landline today is when you're trying to find your cell phone.", 11 | "The perfect man doesn't swear, doesn't smoke, doesn't get angry, doesn't drink. He also doesn't exist.", 12 | "The road to success is always under construction.", 13 | "The shortest horror story: Monday.", 14 | "The snorers are always the ones to fall asleep first.", 15 | "There is no lousy weather, only lousy choice of clothing.", 16 | "Whenever I'm sad, I stop being sad and be awesome instead.", 17 | "It's just, eventually we're all gonna move on. It's called growing up.", 18 | "Whether a gesture's charming or alarming depends on how it's received.", 19 | }; 20 | 21 | public static string OneRandomMessage => 22 | Messages.OrderBy(i => Guid.NewGuid()).First(); 23 | } 24 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/DialogCustomizableExample.razor: -------------------------------------------------------------------------------- 1 | @inject IDialogService DialogService 2 | 3 | Open dialog 4 | 5 |

Name: @DialogData.Name - Age: @DialogData.Age

6 | 7 | @code 8 | { 9 | public record NameAndAge 10 | { 11 | public int Id { get; set; } 12 | public string? Name { get; set; } 13 | public int Age { get; set; } 14 | } 15 | 16 | NameAndAge DialogData { get; set; } = new() { Id = 1, Name = "Bill", Age = 42 }; 17 | 18 | private async Task OpenAsync() 19 | { 20 | // Create a new instance of DialogData 21 | // to allow the user to cancel the update 22 | var data = DialogData with { Id = 1 }; 23 | 24 | var dialog = await DialogService.ShowDialogAsync(data, new DialogParameters() 25 | { 26 | Height = "240px", 27 | Title = $"Updating the {DialogData.Name} sheet", 28 | PreventDismissOnOverlayClick = true, 29 | PreventScroll = true, 30 | }); 31 | 32 | var result = await dialog.Result; 33 | if (!result.Cancelled && result.Data != null) 34 | { 35 | DialogData = (NameAndAge)result.Data; 36 | } 37 | 38 | } 39 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/BlazorTemplate.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/ConsoleLog.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | @((MarkupString)ConsoleContent.Replace(Environment.NewLine, "
")) 6 | @if (!string.IsNullOrWhiteSpace(ConsoleContent)) 7 | { 8 |
9 | 10 | 11 | 12 | 13 |
14 | } 15 |
16 | 17 |
18 | 19 | @code 20 | { 21 | private bool _expanded = true; 22 | protected override void OnInitialized() 23 | { 24 | DemoLogger.OnLogHandler += OnLineReceived; 25 | 26 | base.OnInitialized(); 27 | } 28 | 29 | private async void OnLineReceived(string text) 30 | { 31 | ConsoleContent = $"{ConsoleContent}{Environment.NewLine}[{DateTime.Now:HH:mm:ss}] - {text}"; 32 | await InvokeAsync(() => StateHasChanged()); 33 | } 34 | 35 | [Parameter] 36 | public string ConsoleContent { get; set; } = ""; 37 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/SimpleDialog.razor: -------------------------------------------------------------------------------- 1 | @implements IDialogContentComponent 2 | 3 | 4 | 5 |

Your lastname is @Content.Lastname and you are @Content.Age years young

6 | 7 | Your firstname: 8 | Your lastname: 9 | Your age: 10 | 11 | @if (Dialog != null) 12 | { 13 | 14 | This section is visible only when component is hosted inside a Dialog 15 | Enable Dialog Primary Action 16 | Disable Dialog Primary Action 17 | 18 | } 19 | 20 | @code { 21 | [Parameter] 22 | public SimplePerson Content { get; set; } = default!; 23 | 24 | [CascadingParameter] 25 | public FluentDialog? Dialog { get; set; } 26 | 27 | private void ToggleDialogPrimaryActionButton(bool enable) 28 | { 29 | Dialog!.TogglePrimaryActionButton(enable); 30 | } 31 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/SimpleCustomizedDialog.razor: -------------------------------------------------------------------------------- 1 | @implements IDialogContentComponent 2 | 3 | @* Header *@ 4 | 5 | 6 | 7 | 8 | @Dialog.Instance.Parameters.Title 9 | 10 | 11 | 12 | 13 | @* Footer *@ 14 | 15 | Save 16 | Cancel 17 | 18 | 19 | @* Body *@ 20 | 21 | Name: 22 | Age: 23 | 24 | 25 | @code { 26 | [Parameter] 27 | public DialogCustomizableExample.NameAndAge Content { get; set; } = default!; 28 | 29 | [CascadingParameter] 30 | public FluentDialog Dialog { get; set; } = default!; 31 | 32 | private async Task SaveAsync() 33 | { 34 | await Dialog.CloseAsync(Content); 35 | } 36 | 37 | private async Task CancelAsync() 38 | { 39 | await Dialog.CancelAsync(); 40 | } 41 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/BlazorTemplate.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | latest 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | <_WebToolingArtifacts Remove="Properties\launchSettings.json" /> 16 | 17 | 18 | 19 | 20 | Never 21 | true 22 | Never 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/FetchData.razor: -------------------------------------------------------------------------------- 1 | @page "/fetchdata" 2 | 3 | @inject HttpClient Http 4 | 5 | Weather forecast 6 | 7 |

Weather forecast

8 | 9 |

This component demonstrates fetching data from the server.

10 | 11 | Refresh 12 | 13 | @if (forecasts == null) 14 | { 15 |

Loading...

16 | } 17 | else 18 | { 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | @foreach (var forecast in forecasts) 31 | { 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | } 40 | 41 |
Date Temp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString() @forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
42 | } 43 | 44 | @code { 45 | private WeatherForecast[]? forecasts; 46 | 47 | protected override Task OnInitializedAsync() 48 | { 49 | return RefreshData(); 50 | } 51 | 52 | private Task StartLoadingAsync() 53 | { 54 | return RefreshData(); 55 | } 56 | 57 | private async Task RefreshData() 58 | { 59 | forecasts = await Http.GetFromJsonAsync("WeatherForecast"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Weather.razor: -------------------------------------------------------------------------------- 1 | @page "/weather" 2 | @attribute [StreamRendering] 3 | 4 | Weather 5 | 6 |

Weather

7 | 8 |

This component demonstrates showing data.

9 | 10 | @if (forecasts == null) 11 | { 12 |

Loading...

13 | } 14 | else 15 | { 16 | 17 | 18 | 19 | 20 | 21 | 22 | } 23 | 24 | @code { 25 | private IQueryable? forecasts; 26 | 27 | protected override async Task OnInitializedAsync() 28 | { 29 | // Simulate asynchronous loading to demonstrate streaming rendering 30 | await Task.Delay(500); 31 | 32 | var startDate = DateOnly.FromDateTime(DateTime.Now); 33 | var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; 34 | forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast 35 | { 36 | Date = startDate.AddDays(index), 37 | TemperatureC = Random.Shared.Next(-20, 55), 38 | Summary = summaries[Random.Shared.Next(summaries.Length)] 39 | }).AsQueryable(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page "/error" 2 | @model BlazorTemplate.Server.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Error.

19 |

An error occurred while processing your request.

20 | 21 | @if (Model.ShowRequestId) 22 | { 23 |

24 | Request ID: @Model.RequestId 25 |

26 | } 27 | 28 |

Development Mode

29 |

30 | Swapping to the Development environment displays detailed information about the error that occurred. 31 |

32 |

33 | The Development environment shouldn't be enabled for deployed applications. 34 | It can result in displaying sensitive information from exceptions to end users. 35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 36 | and restarting the app. 37 |

38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Infrastructure/HttpBasedStaticAssetService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace BlazorTemplate.Components.Infrastructure; 4 | 5 | public class HttpBasedStaticAssetService : IStaticAssetService 6 | { 7 | private readonly HttpClient _httpClient; 8 | private readonly CacheStorageAccessor _cacheStorageAccessor; 9 | 10 | public HttpBasedStaticAssetService(HttpClient httpClient, NavigationManager navigationManager, CacheStorageAccessor cacheStorageAccessor) 11 | { 12 | _httpClient = httpClient; 13 | _httpClient.BaseAddress ??= new Uri(navigationManager.BaseUri); 14 | _cacheStorageAccessor = cacheStorageAccessor; 15 | 16 | } 17 | 18 | public async Task GetAsync(string assetUrl, bool useCache = true) 19 | { 20 | string? result = null; 21 | 22 | var message = CreateMessage(assetUrl); 23 | 24 | 25 | if (useCache) 26 | { 27 | // Get the result from the cache 28 | result = await _cacheStorageAccessor.GetAsync(message); 29 | } 30 | 31 | if (string.IsNullOrEmpty(result)) 32 | { 33 | //It not in the cache (or cache not used), download the asset 34 | var response = await _httpClient.SendAsync(message); 35 | 36 | // If successful, store the response in the cache and get the result 37 | if (response.IsSuccessStatusCode) 38 | { 39 | if (useCache) 40 | // Store the response in the cache and get the result 41 | result = await _cacheStorageAccessor.PutAndGetAsync(message, response); 42 | else 43 | result = await response.Content.ReadAsStringAsync(); 44 | } 45 | else 46 | result = string.Empty; 47 | } 48 | 49 | return result; 50 | } 51 | 52 | private static HttpRequestMessage CreateMessage(string url) => new(HttpMethod.Get, url); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Client/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorTemplate.Client 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 |
29 | An unhandled error has occurred. 30 | Reload 31 | 🗙 32 |
33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Perspective/PerspectiveGrid.razor.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http.Json; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.JSInterop; 4 | 5 | namespace BlazorTemplate.Components.Pages.Perspective; 6 | 7 | public partial class PerspectiveGrid : IAsyncDisposable 8 | { 9 | private IJSObjectReference? _jsModule; 10 | 11 | private ElementReference perspectiveViewer; 12 | 13 | [Parameter] 14 | public string TableName { get; set; } = "Table"; 15 | 16 | [Parameter] 17 | public string Height { get; set; } = "800px"; 18 | 19 | [Parameter] 20 | public bool UseWebSocket { get; set; } 21 | 22 | [Parameter] 23 | public string SchemaEndpoint { get; set; } = string.Empty; 24 | 25 | [Parameter] 26 | public string DataEndpoint { get; set; } = string.Empty; 27 | 28 | [Inject] 29 | private IJSRuntime JSRuntime { get; set; } = default!; 30 | 31 | [Inject] 32 | private HttpClient Http { get; set; } 33 | 34 | protected override async Task OnAfterRenderAsync(bool firstRender) 35 | { 36 | if (firstRender) 37 | { 38 | var schema = await Http.GetFromJsonAsync>(SchemaEndpoint); 39 | var data = await Http.GetFromJsonAsync[]>(DataEndpoint); 40 | // var data = await Http.GetStringAsync(DataEndpoint); 41 | 42 | _jsModule = await JSRuntime.InvokeAsync("import", "./_content/BlazorTemplate.Components/Pages/Perspective/PerspectiveGrid.razor.js"); 43 | await _jsModule.InvokeVoidAsync("loadJson", schema, data, perspectiveViewer); 44 | } 45 | } 46 | 47 | public async ValueTask DisposeAsync() 48 | { 49 | try 50 | { 51 | if (_jsModule != null) 52 | { 53 | // await _jsModule.InvokeVoidAsync("dispose"); 54 | await _jsModule.DisposeAsync(); 55 | } 56 | } 57 | catch (JSDisconnectedException) 58 | { 59 | // Client disconnected. 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorTemplate.Components.Infrastructure; 2 | using BlazorTemplate.Shared.SampleData; 3 | using Microsoft.AspNetCore.Hosting.StaticWebAssets; 4 | using Microsoft.FluentUI.AspNetCore.Components; 5 | 6 | namespace BlazorTemplate.Server; 7 | 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | var builder = WebApplication.CreateBuilder(args); 13 | 14 | StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration); 15 | 16 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:5000") }); 17 | 18 | builder.Services.AddRazorPages(); 19 | builder.Services.AddServerSideBlazor(); 20 | 21 | builder.Services.AddFluentUIComponents(); 22 | builder.Services.AddFluentUIDemoServices(); 23 | builder.Services.AddScoped(); 24 | 25 | builder.Services.AddControllers(); 26 | builder.Services.AddEndpointsApiExplorer(); 27 | builder.Services.AddSwaggerGen(); 28 | 29 | builder.Services.AddCors(opts => opts.AddDefaultPolicy(bld => 30 | { 31 | bld 32 | .AllowAnyOrigin() 33 | .AllowAnyMethod() 34 | .AllowAnyHeader() 35 | .WithExposedHeaders("*") 36 | ; 37 | })); 38 | 39 | var app = builder.Build(); 40 | 41 | if (app.Environment.IsDevelopment()) 42 | { 43 | app.UseSwagger(); 44 | app.UseSwaggerUI(); 45 | app.UseWebAssemblyDebugging(); 46 | } 47 | else 48 | { 49 | app.UseExceptionHandler("/Error"); 50 | app.UseHsts(); 51 | } 52 | 53 | app.UseHttpsRedirection(); 54 | app.UseStaticFiles(); 55 | app.UseAntiforgery(); 56 | app.UseRouting(); 57 | app.UseCors(); 58 | app.UseAuthorization(); 59 | 60 | app.MapControllers(); 61 | 62 | app.MapBlazorHub(); 63 | app.MapFallbackToPage("/_Host"); 64 | 65 | app.Run(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBar/Examples/MessageBarDetailed.razor: -------------------------------------------------------------------------------- 1 | @inject IMessageService MessageService 2 | 3 | Display 4 | Clear all messages 5 | 6 | @code 7 | { 8 | int counter = 0; 9 | void DisplayMessage() 10 | { 11 | counter++; 12 | 13 | ActionLink link = new () 14 | { 15 | Text = "Learn more", 16 | Href = "https://bing.com", 17 | OnClick = (e) => { DemoLogger.WriteLine($"Message 'learn more' clicked"); return Task.CompletedTask; } 18 | }; 19 | 20 | ActionButton action1 = new() 21 | { 22 | Text = "Action 1", 23 | OnClick = (e) => { DemoLogger.WriteLine($"Message 'action 1' clicked"); return Task.CompletedTask; } 24 | }; 25 | 26 | ActionButton action2 = new() 27 | { 28 | Text = "Action 2", 29 | OnClick = (e) => { DemoLogger.WriteLine($"Message 'action 2' clicked"); return Task.CompletedTask; } 30 | }; 31 | 32 | MessageService.ShowMessageBar(options => 33 | { 34 | options.Section = App.MESSAGES_TOP; 35 | options.Title = counter % 2 == 0 ? $"This is the important stuff (#{counter})." : null; 36 | options.Body = "The extra information should be kept at roughly 100 characters (which is about this long maximal)."; 37 | options.Intent = Enum.GetValues()[counter % 4]; 38 | options.Icon = new Icons.Regular.Size24.PersonCircle(); 39 | options.ClearAfterNavigation = true; 40 | options.OnClose = (e) => { DemoLogger.WriteLine($"Message {e.Intent} dismissed"); return Task.CompletedTask; }; 41 | options.Link = counter % 2 != 1 ? link : null; 42 | options.PrimaryAction = counter % 3 != 1 ? action1 : null; 43 | options.SecondaryAction = counter % 3 != 2 ? action2 : null; 44 | 45 | }); 46 | } 47 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Layout/MainLayout.razor.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.AspNetCore.Components.Routing; 4 | using Microsoft.JSInterop; 5 | 6 | namespace BlazorTemplate.Components.Layout; 7 | 8 | public partial class MainLayout 9 | { 10 | private const string JAVASCRIPT_FILE = "./_content/BlazorTemplate.Components/Layout/MainLayout.razor.js"; 11 | private string? _version; 12 | private bool _mobile; 13 | private string? _prevUri; 14 | private bool _menuChecked = true; 15 | 16 | [Inject] 17 | private NavigationManager NavigationManager { get; set; } = default!; 18 | 19 | [Inject] 20 | public IJSRuntime JSRuntime { get; set; } = default!; 21 | 22 | [Parameter] 23 | public RenderFragment? Body { get; set; } 24 | 25 | protected override void OnInitialized() 26 | { 27 | _version = Assembly.GetExecutingAssembly().GetCustomAttribute()?.InformationalVersion; 28 | _prevUri = NavigationManager.Uri; 29 | NavigationManager.LocationChanged += LocationChanged; 30 | } 31 | 32 | protected override async Task OnAfterRenderAsync(bool firstRender) 33 | { 34 | if (firstRender) 35 | { 36 | var jsModule = await JSRuntime.InvokeAsync("import", JAVASCRIPT_FILE); 37 | _mobile = await jsModule.InvokeAsync("isDevice"); 38 | await jsModule.DisposeAsync(); 39 | } 40 | } 41 | 42 | public EventCallback OnRefreshTableOfContents => EventCallback.Factory.Create(this, RefreshTableOfContents); 43 | 44 | private Task RefreshTableOfContents() 45 | { 46 | return Task.CompletedTask; 47 | } 48 | 49 | private void HandleChecked() 50 | { 51 | _menuChecked = !_menuChecked; 52 | } 53 | 54 | private void LocationChanged(object? sender, LocationChangedEventArgs e) 55 | { 56 | if (!e.IsNavigationIntercepted && new Uri(_prevUri!).AbsolutePath != new Uri(e.Location).AbsolutePath) 57 | { 58 | _prevUri = e.Location; 59 | if (_mobile && _menuChecked == true) 60 | { 61 | _menuChecked = false; 62 | StateHasChanged(); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Panel/Examples/DialogPanelAsync.razor.cs: -------------------------------------------------------------------------------- 1 | using BlazorTemplate.Shared.SampleData; 2 | using Microsoft.FluentUI.AspNetCore.Components; 3 | 4 | namespace BlazorTemplate.Components.Pages.Demo.Panel.Examples; 5 | public partial class DialogPanelAsync 6 | { 7 | private IDialogReference? _dialog; 8 | 9 | private readonly SimplePerson simplePerson = new() 10 | { 11 | Firstname = "Steve", 12 | Lastname = "Roth", 13 | Age = 42, 14 | }; 15 | 16 | private async Task OpenPanelRightAsync() 17 | { 18 | DemoLogger.WriteLine($"Open right panel"); 19 | 20 | _dialog = await DialogService.ShowPanelAsync(simplePerson, new DialogParameters() 21 | { 22 | Content = simplePerson, 23 | Alignment = HorizontalAlignment.Right, 24 | Title = $"Hello {simplePerson.Firstname}", 25 | PrimaryAction = "Yes", 26 | SecondaryAction = "No", 27 | }); 28 | DialogResult result = await _dialog.Result; 29 | HandlePanel(result); 30 | 31 | 32 | 33 | } 34 | 35 | private async Task OpenPanelLeftAsync() 36 | { 37 | DemoLogger.WriteLine($"Open left panel"); 38 | DialogParameters parameters = new() 39 | { 40 | Content = simplePerson, 41 | Title = $"Hello {simplePerson.Firstname}", 42 | Alignment = HorizontalAlignment.Left, 43 | Modal = false, 44 | ShowDismiss = false, 45 | PrimaryAction = "Maybe", 46 | SecondaryAction = "Cancel", 47 | Width = "500px", 48 | }; 49 | _dialog = await DialogService.ShowPanelAsync(simplePerson, parameters); 50 | DialogResult result = await _dialog.Result; 51 | HandlePanel(result); 52 | } 53 | 54 | private static void HandlePanel(DialogResult result) 55 | { 56 | if (result.Cancelled) 57 | { 58 | DemoLogger.WriteLine($"Panel cancelled"); 59 | return; 60 | } 61 | 62 | if (result.Data is not null) 63 | { 64 | SimplePerson? simplePerson = result.Data as SimplePerson; 65 | DemoLogger.WriteLine($"Panel closed by {simplePerson?.Firstname} {simplePerson?.Lastname} ({simplePerson?.Age})"); 66 | return; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/ToastServicePage.razor: -------------------------------------------------------------------------------- 1 | @page "/ToastService" 2 | 3 |

ToastService

4 | 5 |

6 | The ToastService is a service that can be used to show toasts. It can be injected into a page and used to show 7 | different type of toasts. 8 |

9 |

10 | For a component to be useable by the ToastService, it needs to implement IToastContentComponent<T> 11 | where T represent the type of the data to be shown in the toast. 12 |

13 |

14 | The ToastService is automatically registered in the DI container with the AddFluentUIComponents() call. 15 |

16 | 17 |

Toast container

18 | 19 | IMPORTANT!! 20 | 21 |

22 | Toasts are rendered through the <FluentToastProvider /> component. This component needs to be added to the main layout of your application/site. 23 | You typically do this in the MainLayout.razor file at the end of the <main> section like this: 24 |

25 | 26 | 27 | <main> 28 | <nav> 29 | : 30 | </nav> 31 | <div class="content"> 32 | <article id="article"> 33 | @@Body 34 | </article> 35 | </div> 36 | <FluentToastProvider MaxToastCount="10" /> 37 | </main> 38 | 39 | 40 | IMPORTANT!! 41 |

42 | 43 | For the <FluentToastProvider/> to work porperly, it needs interactivity! If you are using "per page" interactivity or ASP.NET Core 8 44 | or above with Server Side Rendering , make sure to add a @@rendermode to 45 | either the provider itself of the component the provider is placed in. 46 | 47 |

48 |

49 | See the documentation below for more information about the FluentToastProvider parameters. 50 |

51 | 52 |

Examples

53 | 54 |

55 | See the toast page for examples of the different types of available toasts: 56 |

    57 |
  • Confirmation toast
  • 58 |
  • Communication toast
  • 59 |
  • Progress toast
  • 60 |
61 |

62 | 63 |

Documentation

64 | 65 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Server/Pages/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @namespace BlazorTemplate.Server.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | 5 | 6 | 7 | 8 | 9 | 10 | BlazorTemplate.Server 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | @RenderBody() 23 | 24 |
25 | 26 | An error has occurred. This application may no longer respond until reloaded. 27 | 28 | 29 | An unhandled exception has occurred. See browser dev tools for details. 30 | 31 | Reload 32 | 🗙 33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/SiteSettingsPanel.razor.cs: -------------------------------------------------------------------------------- 1 | using BlazorTemplate.Components.Infrastructure; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.Extensions.Logging; 4 | using Microsoft.FluentUI.AspNetCore.Components; 5 | using Microsoft.FluentUI.AspNetCore.Components.Extensions; 6 | 7 | namespace BlazorTemplate.Components.Common; 8 | 9 | public partial class SiteSettingsPanel 10 | { 11 | private string? _status; 12 | private bool _popVisible; 13 | private bool _ltr = true; 14 | private FluentDesignTheme? _theme; 15 | 16 | [Inject] 17 | public required ILogger Logger { get; set; } 18 | 19 | [Inject] 20 | public required CacheStorageAccessor CacheStorageAccessor { get; set; } 21 | 22 | [Inject] 23 | public required GlobalState GlobalState { get; set; } 24 | 25 | public DesignThemeModes Mode { get; set; } 26 | 27 | public OfficeColor? OfficeColor { get; set; } 28 | 29 | public LocalizationDirection? Direction { get; set; } 30 | 31 | private static IEnumerable AllModes => Enum.GetValues(); 32 | 33 | protected override void OnAfterRender(bool firstRender) 34 | { 35 | if (firstRender) 36 | { 37 | Direction = GlobalState.Dir; 38 | _ltr = !Direction.HasValue || Direction.Value == LocalizationDirection.LeftToRight; 39 | } 40 | } 41 | 42 | protected void HandleDirectionChanged(bool isLeftToRight) 43 | { 44 | 45 | _ltr = isLeftToRight; 46 | Direction = isLeftToRight ? LocalizationDirection.LeftToRight : LocalizationDirection.RightToLeft; 47 | } 48 | 49 | private static string? GetCustomColor(OfficeColor? color) 50 | { 51 | return color switch 52 | { 53 | null => OfficeColorUtilities.GetRandom(true).ToAttributeValue(), 54 | Microsoft.FluentUI.AspNetCore.Components.OfficeColor.Default => "#036ac4", 55 | _ => color.ToAttributeValue(), 56 | }; 57 | } 58 | 59 | private async Task ResetSite() 60 | { 61 | var msg = "Site settings reset and cache cleared!"; 62 | 63 | await CacheStorageAccessor.RemoveAllAsync(); 64 | _theme?.ClearLocalStorageAsync(); 65 | 66 | Logger.LogInformation(msg); 67 | _status = msg; 68 | 69 | OfficeColor = Microsoft.FluentUI.AspNetCore.Components.OfficeColor.Office; 70 | Mode = DesignThemeModes.System; 71 | 72 | //StateHasChanged(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/DialogServiceExample.razor: -------------------------------------------------------------------------------- 1 | @inject IDialogService DialogService 2 | 3 |
4 |

5 | When 'Modal' is checked, the dialog can be dismissed by clicking outside of the dialog (anywhere on the overlay). When unchecked, 6 | the dialog can be dismissed only by the 'ESC' key.
The dialog can always be closed by using the 'Close dialog' 7 | button. 8 |

9 |

10 | When 'Trap focus' is checked, only the elements within the dialog will receive focus. When unchecked, focus will also move outside of the 11 | dialog. 12 |

13 | 14 | Modal 15 | 16 | 17 | Trap focus 18 | 19 |
20 |
21 | 22 | Open Dialog 23 | 24 |
25 | 26 | @code { 27 | private bool _trapFocus = true; 28 | private bool _modal = true; 29 | 30 | SimplePerson simplePerson = new() 31 | { 32 | Firstname = "Dan", 33 | Lastname = "Sanderson", 34 | Age = 42, 35 | }; 36 | 37 | private async Task OpenDialogAsync() 38 | { 39 | DemoLogger.WriteLine($"Open dialog centered"); 40 | 41 | DialogParameters parameters = new() 42 | { 43 | Title = $"Hello {simplePerson.Firstname}", 44 | PrimaryAction = "Yes", 45 | PrimaryActionEnabled = false, 46 | SecondaryAction = "No", 47 | Width = "500px", 48 | TrapFocus = _trapFocus, 49 | Modal = _modal, 50 | PreventScroll = true 51 | }; 52 | 53 | IDialogReference dialog = await DialogService.ShowDialogAsync(simplePerson, parameters); 54 | DialogResult? result = await dialog.Result; 55 | 56 | 57 | if (result.Data is not null) 58 | { 59 | SimplePerson? simplePerson = result.Data as SimplePerson; 60 | DemoLogger.WriteLine($"Dialog closed by {simplePerson?.Firstname} {simplePerson?.Lastname} ({simplePerson?.Age}) - Canceled: {result.Cancelled}"); 61 | } 62 | else 63 | { 64 | DemoLogger.WriteLine($"Dialog closed - Canceled: {result.Cancelled}"); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBox/MessageBoxPage.razor: -------------------------------------------------------------------------------- 1 | @page "/MessageBox" 2 | 3 | @using BlazorTemplate.Components.Pages.Demo.MessageBox.Examples 4 | 5 |

MessageBox

6 | 7 |

8 | A MessageBox is a dialog that is used to display information with a specific intent to the user. It uses the 9 | DialogService to display the dialog. 10 |

11 |

12 | The DialogService is a singleton service that can be injected into any component. It exposes methods to show a dialog. 13 | For working with a MessageBox, the following methods are available: 14 |

    15 |
  • ShowSuccess / ShowSuccessAsync
  • 16 |
  • ShowWarning / ShowWarningAsync
  • 17 |
  • ShowInfo / ShowInfoAsync
  • 18 |
  • ShowError / ShowErrorAsync
  • 19 |
  • ShowConfirmation / ShowConfirmationAsync
  • 20 |
  • ShowMessageBox / ShowMessageBoxAsync
  • 21 |
22 | For more information on how to use the DialogService, see the DialogService page. 23 |

24 |

25 | For defining the information to display in the MessageBox, the MessageBoxData class is used. See the API documentation 26 | below for the avialable properties. 27 |

28 |

29 | The MessageBoxData class is then used as the generic type parameter for the DialogParameters class. 30 | The DialogParameters class is used to pass parameters to the DialogService when showing a dialog. 31 |

32 | 33 |

34 | When both the PrimaryButton and SecondaryButton properties are set, the MessageBox will be 35 | displayed as a modal. This means that the user has to click one of the buttons to close the dialog. It cannot be closed by clicking 36 | outside of the dialog. Clicking PrimaryButton will return true and clicking SecondaryButton will 37 | return false as the dialog result. See the Console log for these return values. 38 |

39 | 40 |

Example

41 | 42 | 43 |

The buttons below call the async MessageBox methods on the DialogService.

44 |
45 | 46 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Infrastructure/CacheStorageAccessor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.FluentUI.AspNetCore.Components.Utilities; 2 | using Microsoft.JSInterop; 3 | 4 | namespace BlazorTemplate.Components.Infrastructure; 5 | public class CacheStorageAccessor(IJSRuntime js) : JSModule(js, "./_content/BlazorTemplate.Components/js/CacheStorageAccessor.js") 6 | { 7 | public async ValueTask PutAsync(HttpRequestMessage requestMessage, HttpResponseMessage responseMessage) 8 | { 9 | var requestMethod = requestMessage.Method.Method; 10 | var requestBody = await GetRequestBodyAsync(requestMessage); 11 | var responseBody = await responseMessage.Content.ReadAsStringAsync(); 12 | 13 | await InvokeVoidAsync("put", requestMessage.RequestUri!, requestMethod, requestBody, responseBody); 14 | } 15 | 16 | public async ValueTask PutAndGetAsync(HttpRequestMessage requestMessage, HttpResponseMessage responseMessage) 17 | { 18 | var requestMethod = requestMessage.Method.Method; 19 | var requestBody = await GetRequestBodyAsync(requestMessage); 20 | var responseBody = await responseMessage.Content.ReadAsStringAsync(); 21 | 22 | await InvokeVoidAsync("put", requestMessage.RequestUri!, requestMethod, requestBody, responseBody); 23 | 24 | return responseBody; 25 | } 26 | 27 | public async ValueTask GetAsync(HttpRequestMessage requestMessage) 28 | { 29 | var requestMethod = requestMessage.Method.Method; 30 | var requestBody = await GetRequestBodyAsync(requestMessage); 31 | var result = await InvokeAsync("get", requestMessage.RequestUri!, requestMethod, requestBody); 32 | 33 | return result; 34 | } 35 | 36 | public async ValueTask RemoveAsync(HttpRequestMessage requestMessage) 37 | { 38 | var requestMethod = requestMessage.Method.Method; 39 | var requestBody = await GetRequestBodyAsync(requestMessage); 40 | 41 | await InvokeVoidAsync("remove", requestMessage.RequestUri!, requestMethod, requestBody); 42 | } 43 | 44 | public async ValueTask RemoveAllAsync() 45 | { 46 | await InvokeVoidAsync("removeAll"); 47 | } 48 | private static async ValueTask GetRequestBodyAsync(HttpRequestMessage requestMessage) 49 | { 50 | var requestBody = string.Empty; 51 | if (requestMessage.Content is not null) 52 | { 53 | requestBody = await requestMessage.Content.ReadAsStringAsync(); 54 | } 55 | 56 | return requestBody; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageServicePage.razor: -------------------------------------------------------------------------------- 1 | @page "/MessageService" 2 |

MessageService

3 | 4 |

5 | The MessageService is a service that can be used to show MessageBars and Notifications (in a Notification Center as shown here in the demo 6 | site). It needs to be injected into a page or component before any of its methods can be called. 7 |

8 |

9 | The MessageService is automatically registered in the DI provider with the .AddFluentUIComponents() method call. 10 |

11 | 12 |

MessageBar provider

13 | IMPORTANT!! 14 |

15 | Messages are rendered by the <FluentMessageBarProvider />. This component needs to be added to the layout of your application/site. 16 | For a message bar that needs to be shown at the top of the screen, you typically do this in the MainLayout.razor file at the location in 17 | the HTML structure where you want the message bars to appear of the <main> section like this: 18 |

19 | 20 | <main> 21 | <nav> 22 | : 23 | </nav> 24 | <div class="content"> 25 | <article id="article"> 26 | <FluentMessageBarProvider Section="MESSAGES_TOP"/> 27 | @@Body 28 | </article> 29 | </div> 30 | </main> 31 | 32 | 33 | IMPORTANT!! 34 |

35 | 36 | For the <FluentMessageBarProvider/> to work porperly, it needs interactivity! If you are using "per page" interactivity or ASP.NET Core 8 37 | or above with Server Side Rendering , make sure to add a @@rendermode to 38 | either the provider itself of the component the provider is placed in. 39 | 40 |

41 | 42 |

43 | It is also possible to have message bars appear in a dialog or card. In that case you need to add the <FluentMessageBarProvider /> 44 | component to the specification the dialog or card using the Section parameter as an identification.
45 | You can then target a specific message bar by specifying the Section parameter in the MessageService.ShowMessageBar()/ 46 | MessageService.ShowMessageBarAsync() methods. 47 |

48 | 49 |

50 | See the MessageBar page for examples on how to use the messages and FluentMessageBar component. 51 |

52 | 53 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/NotificationCenter.razor: -------------------------------------------------------------------------------- 1 | @implements IDisposable 2 | @inject IDialogService DialogService 3 | @inject IMessageService MessageService 4 | 5 | 6 | @if (MessageService.Count(App.MESSAGES_NOTIFICATION_CENTER) > 0) 7 | { 8 | 15 | 16 | @NotificationIcon() 17 | 18 | 19 | } 20 | else 21 | { 22 | @NotificationIcon() 23 | } 24 | 25 | 26 | @code { 27 | private IDialogReference? _dialog; 28 | 29 | protected override void OnInitialized() 30 | { 31 | MessageService.OnMessageItemsUpdated += UpdateCount; 32 | } 33 | 34 | private void UpdateCount() 35 | { 36 | InvokeAsync(StateHasChanged); 37 | } 38 | 39 | private RenderFragment NotificationIcon() => 40 | @; 41 | 42 | private async Task OpenNotificationCenterAsync() 43 | { 44 | DemoLogger.WriteLine($"Open notification center"); 45 | 46 | _dialog = await DialogService.ShowPanelAsync(new DialogParameters() 47 | { 48 | Alignment = HorizontalAlignment.Right, 49 | Title = $"Notifications", 50 | PrimaryAction = null, 51 | SecondaryAction = null, 52 | ShowDismiss = true 53 | }); 54 | DialogResult result = await _dialog.Result; 55 | HandlePanel(result); 56 | } 57 | 58 | private static void HandlePanel(DialogResult result) 59 | { 60 | if (result.Cancelled) 61 | { 62 | DemoLogger.WriteLine($"Notification center dismissed"); 63 | return; 64 | } 65 | 66 | if (result.Data is not null) 67 | { 68 | DemoLogger.WriteLine($"Notification center closed"); 69 | return; 70 | } 71 | } 72 | 73 | public void Dispose() 74 | { 75 | MessageService.OnMessageItemsUpdated -= UpdateCount; 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/ToastCommunicationToasts.razor: -------------------------------------------------------------------------------- 1 | @inject IToastService ToastService 2 | 3 | Communication toast example 1 4 | Communication toast example 2 5 | 6 | 7 | @code { 8 | private void ShowExample1() 9 | { 10 | ToastService.ShowCommunicationToast(new ToastParameters() 11 | { 12 | Intent = ToastIntent.Success, 13 | Title = "Your dataset is ready", 14 | Timeout = 4000, 15 | PrimaryAction = "See dataset", 16 | OnPrimaryAction = EventCallback.Factory.Create(this, ClickedPrimary), 17 | SecondaryAction = "Get insights", 18 | OnSecondaryAction = EventCallback.Factory.Create(this, ClickedSecondary), 19 | Content = new CommunicationToastContent() 20 | { 21 | Subtitle = "A communication toast subtitle", 22 | Details = "Let Power BI help you explore your data.", 23 | }, 24 | }); 25 | } 26 | 27 | private void ShowExample2() 28 | { 29 | ToastService.ShowCommunicationToast(new ToastParameters() 30 | { 31 | Intent = ToastIntent.Error, 32 | Title = "File didn't upload to ABC folder", 33 | TopCTAType = ToastTopCTAType.Timestamp, 34 | Timeout = 8000, 35 | PrimaryAction = "Replace", 36 | OnPrimaryAction = EventCallback.Factory.Create(this, ClickedPrimary), 37 | SecondaryAction = "Keep both", 38 | OnSecondaryAction = EventCallback.Factory.Create(this, ClickedSecondary), 39 | Content = new CommunicationToastContent() 40 | { 41 | Details = "A file with the same _name already exists.", 42 | }, 43 | }); 44 | } 45 | 46 | 47 | private void ClickedPrimary(ToastResult result) 48 | { 49 | DemoLogger.WriteLine("Clicked primary action"); 50 | } 51 | 52 | private void ClickedSecondary() 53 | { 54 | DemoLogger.WriteLine("Clicked secondary action"); 55 | } 56 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Panel/Examples/DialogPanel.razor.cs: -------------------------------------------------------------------------------- 1 | using BlazorTemplate.Shared.SampleData; 2 | using Microsoft.FluentUI.AspNetCore.Components; 3 | 4 | namespace BlazorTemplate.Components.Pages.Demo.Panel.Examples; 5 | 6 | public partial class DialogPanel 7 | { 8 | private readonly SimplePerson simplePerson = new() 9 | { 10 | Firstname = "Steve", 11 | Lastname = "Roth", 12 | Age = 42, 13 | }; 14 | 15 | private void OpenPanelRight() 16 | { 17 | DemoLogger.WriteLine($"Open right panel"); 18 | 19 | #pragma warning disable CS0618 // Type or member is obsolete 20 | DialogService.ShowPanel(new DialogParameters() 21 | { 22 | Content = simplePerson, 23 | Alignment = HorizontalAlignment.Right, 24 | Title = $"Hello {simplePerson.Firstname}", 25 | OnDialogResult = DialogService.CreateDialogCallback(this, HandlePanel), 26 | PrimaryAction = "Yes", 27 | SecondaryAction = "No", 28 | }); 29 | #pragma warning restore CS0618 // Type or member is obsolete 30 | } 31 | 32 | private void OpenPanelLeft() 33 | { 34 | DemoLogger.WriteLine($"Open left panel"); 35 | DialogParameters parameters = new() 36 | { 37 | Content = simplePerson, 38 | Title = $"Hello {simplePerson.Firstname}", 39 | OnDialogResult = DialogService.CreateDialogCallback(this, HandlePanel), 40 | Alignment = HorizontalAlignment.Left, 41 | Modal = false, 42 | ShowDismiss = false, 43 | PrimaryAction = "Maybe", 44 | SecondaryAction = "Cancel", 45 | Width = "300px", 46 | }; 47 | #pragma warning disable CS0618 // Type or member is obsolete 48 | DialogService.ShowPanel(parameters); 49 | #pragma warning restore CS0618 // Type or member is obsolete 50 | } 51 | 52 | private async Task HandlePanel(DialogResult result) 53 | { 54 | if (result.Cancelled) 55 | { 56 | await Task.Run(() => DemoLogger.WriteLine($"Panel cancelled")); 57 | return; 58 | } 59 | 60 | if (result.Data is not null) 61 | { 62 | SimplePerson? simplePerson = result.Data as SimplePerson; 63 | await Task.Run(() => DemoLogger.WriteLine($"Panel closed by {simplePerson?.Firstname} {simplePerson?.Lastname} ({simplePerson?.Age})")); 64 | return; 65 | } 66 | 67 | await Task.Run(() => DemoLogger.WriteLine($"Panel closed")); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Common/CodeSnippet.razor.css: -------------------------------------------------------------------------------- 1 | .snippet { 2 | margin-bottom: 0.5rem; 3 | border: calc(var(--stroke-width) * 1px) solid var(--neutral-stroke-rest); 4 | border-radius: calc(var(--control-corner-radius) * 1px); 5 | overflow-y: auto; 6 | } 7 | 8 | ::deep .hljs { 9 | background-color: var(--neutral-layer-2); 10 | } 11 | 12 | ::deep .hljs-copy-button { 13 | border-color: var(--neutral-fill-strong-rest); 14 | color: var(--accent-fill-rest); 15 | top: 0.4rem; 16 | /*background-color: var(--neutral-layer-2) !important;*/ 17 | /*background-image: url('data:image/svg+xml;utf-8,');*/ 18 | } 19 | 20 | 21 | ::deep .hljs-copy-button:hover { 22 | border-color: var(--neutral-fill-strong-hover); 23 | } 24 | 25 | ::deep .hljs-copy-button:active { 26 | border-color: var(--neutral-fill-strong-active); 27 | } 28 | 29 | ::deep .hljs-copy { 30 | cursor: pointer; 31 | border-color: var(--neutral-stroke-rest); 32 | border-radius: calc(var(--control-corner-radius) * 1px); 33 | color: var(--accent-fill-rest); 34 | background-color: var(--neutral-layer-2); 35 | top: 0.4rem; 36 | } 37 | 38 | ::deep .hljs-copy { 39 | position: absolute; 40 | transform: translateX(calc(100% + 1.125em)); 41 | 42 | right: 1em; 43 | transition: background-color 200ms ease, transform 200ms ease-out; 44 | } 45 | 46 | .hljs-copy-wrapper ::deep { 47 | position: relative; 48 | overflow: hidden 49 | } 50 | 51 | .hljs-copy-wrapper:hover ::deep .hljs-copy, .hljs-copy:focus { 52 | transform: translateX(0); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/js/CacheStorageAccessor.js: -------------------------------------------------------------------------------- 1 | async function openCacheStorage() { 2 | try { 3 | return await window.caches.open("BlazorTemplate.Demo") 4 | } 5 | catch (err) { 6 | return undefined; 7 | } 8 | } 9 | 10 | function createRequest(url, method, body = "") { 11 | let requestInit = 12 | { 13 | method: method 14 | }; 15 | 16 | if (body != "") { 17 | requestInit.body = body; 18 | } 19 | 20 | let request = new Request(url, requestInit); 21 | 22 | return request; 23 | } 24 | 25 | export async function put(url, method, body = "", responseString) { 26 | const CACHING_DURATION = 7 * 24 * 3600; 27 | 28 | const expires = new Date(); 29 | expires.setSeconds(expires.getSeconds() + CACHING_DURATION); 30 | 31 | const cachedResponseFields = { 32 | headers: { 'fluent-cache-expires': expires.toUTCString() }, 33 | }; 34 | 35 | let cache = await openCacheStorage(); 36 | if (cache != null) { 37 | 38 | let request = createRequest(url, method, body); 39 | let response = new Response(responseString, cachedResponseFields); 40 | 41 | await cache.put(request, response); 42 | } 43 | } 44 | 45 | export async function get(url, method, body = "") { 46 | let cache = await openCacheStorage(); 47 | if (cache == null) { 48 | return ""; 49 | } 50 | 51 | let request = createRequest(url, method, body); 52 | let response = await cache.match(request); 53 | 54 | if (response == null) { 55 | return ""; 56 | } 57 | else { 58 | const expirationDate = Date.parse(response.headers.get('fluent-cache-expires')); 59 | const now = new Date(); 60 | // Check it is not already expired and return from the cache 61 | if (expirationDate > now) { 62 | let result = await response.text(); 63 | 64 | return result; 65 | } 66 | } 67 | 68 | return ""; 69 | } 70 | 71 | export async function remove(url, method, body = "") { 72 | let cache = await openCacheStorage(); 73 | 74 | if (cache != null) { 75 | let request = createRequest(url, method, body); 76 | await cache.delete(request); 77 | } 78 | } 79 | 80 | export async function removeAll() { 81 | let cache = await openCacheStorage(); 82 | 83 | if (cache != null) { 84 | cache.keys().then(function (names) { 85 | for (let name of names) 86 | cache.delete(name); 87 | }); 88 | //let requests = await cache.keys(); 89 | 90 | //for (let request in requests) { 91 | // await cache.delete(request); 92 | //} 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/DialogServiceCallbackExample.razor: -------------------------------------------------------------------------------- 1 | @inject IDialogService DialogService 2 | 3 |
4 |

5 | When 'Modal' is checked, the dialog can be dismissed by clicking outside of the dialog (anywhere on the overlay). When unchecked, 6 | the dialog can be dismissed only by the 'ESC' key.
The dialog can always be closed by using the 'Close dialog' 7 | button. 8 |

9 |

10 | When 'Trap focus' is checked, only the elements within the dialog will receive focus. When unchecked, focus will also move outside of the 11 | dialog. 12 |

13 | 14 | Modal 15 | 16 | 17 | Trap focus 18 | 19 |
20 |
21 | 22 | Open Dialog 23 | 24 |
25 | 26 | @code { 27 | private bool _trapFocus = true; 28 | private bool _modal = true; 29 | 30 | SimplePerson simplePerson = new() 31 | { 32 | Firstname = "Steve", 33 | Lastname = "Roth", 34 | Age = 39, 35 | }; 36 | 37 | private async Task OpenDialogAsync() 38 | { 39 | DemoLogger.WriteLine($"Open dialog centered"); 40 | 41 | await DialogService.ShowDialogAsync(simplePerson, new DialogParameters() 42 | { 43 | Title = $"Hello {simplePerson.Firstname}", 44 | OnDialogResult = DialogService.CreateDialogCallback(this, HandleDialog), 45 | PrimaryAction = "Yes", 46 | PrimaryActionEnabled = false, 47 | SecondaryAction = "No", 48 | Width = "500px", 49 | Height = "500px", 50 | TrapFocus = _trapFocus, 51 | Modal = _modal, 52 | }); 53 | } 54 | 55 | private async Task HandleDialog(DialogResult result) 56 | { 57 | if (result.Cancelled) 58 | { 59 | await Task.Run(() => DemoLogger.WriteLine($"Dialog cancelled")); 60 | return; 61 | } 62 | if (result.Data is not null) 63 | { 64 | SimplePerson? simplePerson = result.Data as SimplePerson; 65 | await Task.Run(() => DemoLogger.WriteLine($"Dialog closed by {simplePerson?.Firstname} {simplePerson?.Lastname} ({simplePerson?.Age})")); 66 | return; 67 | } 68 | 69 | await Task.Run(() => DemoLogger.WriteLine($"Dialog closed")); 70 | 71 | } 72 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/DataGridRemoteData.razor: -------------------------------------------------------------------------------- 1 | @page "/remote-data" 2 | 3 | @using Microsoft.FluentUI.AspNetCore.Components 4 | 5 | @inject HttpClient Http 6 | @inject NavigationManager NavManager 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 |

Total: @numResults results found

23 | 24 | @code { 25 | GridItemsProvider foodRecallProvider = default!; 26 | int numResults; 27 | 28 | protected override async Task OnInitializedAsync() 29 | { 30 | // Define the GridRowsDataProvider. Its job is to convert QuickGrid's GridRowsDataProviderRequest into a query against 31 | // an arbitrary data soure. In this example, we need to translate query parameters into the particular URL format 32 | // supported by the external JSON API. It's only possible to perform whatever sorting/filtering/etc is supported 33 | // by the external API. 34 | foodRecallProvider = async req => 35 | { 36 | var url = NavManager.GetUriWithQueryParameters("https://api.fda.gov/food/enforcement.json", new Dictionary 37 | { 38 | { "skip", req.StartIndex }, 39 | { "limit", req.Count }, 40 | }); 41 | 42 | var response = await Http.GetFromJsonAsync(url, req.CancellationToken); 43 | return GridItemsProviderResult.From( 44 | items: response!.Results, 45 | totalItemCount: response!.Meta.Results.Total); 46 | }; 47 | 48 | // Display the number of results just for information. This is completely separate from the grid. 49 | numResults = (await Http.GetFromJsonAsync("https://api.fda.gov/food/enforcement.json"))!.Meta.Results.Total; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Panel/PanelPage.razor: -------------------------------------------------------------------------------- 1 | @page "/Panel" 2 | 3 | @using BlazorTemplate.Components.Pages.Demo.Panel.Examples; 4 | 5 |

Panel

6 | 7 |

8 | A panel is a dialog that is anchored to the either left or right side of the screen. By implementing IDialogContentComponent<T>, 9 | a component can be used as the content of a panel. The appearance of the panel can be altered by using the DialogParameters class. 10 |

11 |

12 | Data can be exchanged between the panel and the component that opened the panel by by capturing the returned IDialogReference from 13 | one of the DialogService.Show...Async methods and then use that reference to get the dialog's result (of type DialogResult) 14 | or by using an EventCallback. 15 |

16 | 17 |

18 | A panel can be shown by calling one of following methods on the DialogService: 19 |

    20 |
  • ShowPanel<TDialogContent,TData> / ShowPanelAsync<TDialogContent,TData>
  • 21 |
  • ShowPanel<TData> / ShowPanelAsync<TData>
  • 22 |
23 |

24 |

25 | For more information on how to use the DialogService, see the DialogService page. 26 |

27 | 28 |

Examples

29 | 30 | 31 | 32 | The panel that is anchored to the right side of the screen can be dismissed by clicking the dismiss button (at the top), 33 | 'No' button (at the bottom) or by clicking outside of the panel.
34 | The panel shown on the left side of the screen can only be closed by clicking the 'Cancel' button at the bottom of the panel. It does 35 | not show a dismiss button and is shown in a modal fashion based on the settings provided through the DialogParameters. 36 |
37 |
38 | 39 | 40 | 41 | The panel that is anchored to the right side of the screen can be dismissed by clicking the dismiss button (at the top), 42 | 'No' button (at the bottom) or by clicking outside of the panel.
43 | The panel shown on the left side of the screen can only be closed by clicking the 'Cancel'' button at the bottom of the panel. It does 44 | not show a dismiss button and is shown in a modal fashion based on the settings provided through the DialogParameters 45 |
46 |
47 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/ToastPage.razor: -------------------------------------------------------------------------------- 1 | @page "/Toast" 2 | 3 | @inject IToastService ToastService 4 | 5 | @using BlazorTemplate.Components.Pages.Demo.Toast.Examples 6 | 7 | @using Microsoft.FluentUI.AspNetCore.Components 8 | 9 |

Toast

10 | 11 |

12 | Toasts, referred to as “notifications” in the UI, are pop-up notifications that keep users informed by briefly: 13 |

    14 |
  • Confirming an action they took.
  • 15 |
  • Informing them about a timely event.
  • 16 |
  • Communicating the status of a process initiated.
  • 17 |
18 |

19 |

20 | The information in a toast is useful and relevant but never critical. 21 |

22 | 23 |

Examples

24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Toasts can be removed by calling ToastManager.Clear...() methods. By default, 40 | toasts are removed from the queue as well. To keep toasts in the queue, pass false for the includeQueue parameter. 41 | 42 | 43 | 44 | 45 | 46 |

ToastProvider

47 | 48 | IMPORTANT!! 49 |

50 | Toasts are rendered through the <FluentToastProvider /> component. This component needs to be added to the main layout of your application/site. 51 | You typically do this in the MainLayout.razor file at the end of the <main> section like shown below. 52 |

53 |

54 | 55 | For the Toasts to work properly, the <FluentToastProvider/> needs interactivity! If you are using "per page" interactivity, make sure to add a @@rendermode to 56 | either the provider itself of the component the provider is placed in. 57 | 58 |

59 | 60 | <main> 61 | <nav> 62 | : 63 | </nav> 64 | <div class="content"> 65 | <article id="article"> 66 | @@Body 67 | </article> 68 | </div> 69 | <FluentToastProvider MaxToastCount="10" /> 70 | </main> 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/ToastClearQueue.razor: -------------------------------------------------------------------------------- 1 | @inject IToastService ToastService 2 | 3 | 4 | Clear all 5 | 6 | Clear specific intent 7 | 8 | 9 | 10 | Clear success 11 | 12 | 13 | 14 | 15 | Clear warning 16 | 17 | 18 | 19 | 20 | Clear error 21 | 22 | 23 | 24 | 25 | Clear info 26 | 27 | 28 | 29 | 30 | Clear progress 31 | 32 | 33 | 34 | 35 | Clear upload 36 | 37 | 38 | 39 | 40 | Clear download 41 | 42 | 43 | 44 | 45 | Clear event 46 | 47 | 48 | 49 | 50 | Clear avatar 51 | 52 | 53 | 54 | 55 | Clear custom 56 | 57 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBar/Examples/MessageBarDefault.razor: -------------------------------------------------------------------------------- 1 | @inject IMessageService MessageService 2 | @inject IDialogService DialogService 3 | 4 | @using BlazorTemplate.Components.Pages.Demo.Dialog.Examples 5 | @using BlazorTemplate.Components.Pages.Demo.MessageBar.Examples 6 | 7 | Add on top 8 | Add in Notification Center 9 | Add in a dialog 10 | 11 | Clear all alerts 12 | 13 | @code 14 | { 15 | SimplePerson simplePerson = new() 16 | { 17 | Firstname = "Dan", 18 | Lastname = "Sanderson", 19 | Age = 42, 20 | }; 21 | 22 | ActionLink link = new() 23 | { 24 | Text = "Learn more", 25 | Href = "https://bing.com", 26 | OnClick = (e) => { DemoLogger.WriteLine($"Message 'learn more' clicked"); return Task.CompletedTask; } 27 | }; 28 | 29 | int counter = 0; 30 | 31 | void AddInTopBar() 32 | { 33 | var message = $"Simple message #{counter++}"; 34 | var type = Enum.GetValues()[counter % 4]; 35 | MessageService.ShowMessageBarAsync(message, type, App.MESSAGES_TOP); 36 | } 37 | 38 | void AddInNotificationCenter() 39 | { 40 | MessageService.ShowMessageBar(options => 41 | { 42 | options.Intent = Enum.GetValues()[counter % 4]; 43 | options.Title = $"Simple message #{counter++}"; 44 | options.Body = MessageBarSamples.OneRandomMessage; 45 | options.Link = link; 46 | options.Timestamp = DateTime.Now; 47 | options.Section = App.MESSAGES_NOTIFICATION_CENTER; 48 | }); 49 | } 50 | 51 | 52 | async Task AddInDialog() 53 | { 54 | MessageService.ShowMessageBar(options => 55 | { 56 | options.Intent = Enum.GetValues()[counter % 4]; 57 | options.Title = $"Simple message #{counter++}"; 58 | options.Body = MessageBarSamples.OneRandomMessage; 59 | options.Link = link; 60 | options.Timestamp = DateTime.Now; 61 | options.Section = App.MESSAGES_DIALOG; 62 | }); 63 | 64 | await OpenDialogAsync(); 65 | } 66 | 67 | private async Task OpenDialogAsync() 68 | { 69 | DialogParameters parameters = new() 70 | { 71 | Title = $"Hi {simplePerson.Firstname}!", 72 | PrimaryAction = "Yes", 73 | PrimaryActionEnabled = false, 74 | SecondaryAction = "No", 75 | Width = "500px", 76 | Height = "500px", 77 | Content = simplePerson, 78 | TrapFocus = true, 79 | Modal = true, 80 | }; 81 | 82 | IDialogReference dialog = await DialogService.ShowDialogAsync(simplePerson, parameters); 83 | DialogResult? result = await dialog.Result; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBar/Examples/MessageBarTimed.razor: -------------------------------------------------------------------------------- 1 | @inject IMessageService MessageService 2 | @inject IDialogService DialogService 3 | 4 | @using BlazorTemplate.Components.Pages.Demo.Dialog.Examples 5 | @using BlazorTemplate.Components.Pages.Demo.MessageBar.Examples 6 | 7 | Add on top (4 sec) 8 | Add in Notification Center (3 sec) 9 | Add in a dialog (5 sec) 10 | 11 | @code 12 | { 13 | SimplePerson simplePerson = new() 14 | { 15 | Firstname = "Dan", 16 | Lastname = "Sanderson", 17 | Age = 42, 18 | }; 19 | 20 | ActionLink link = new() 21 | { 22 | Text = "Learn more", 23 | Href = "https://bing.com", 24 | OnClick = (e) => { DemoLogger.WriteLine($"Message 'learn more' clicked"); return Task.CompletedTask; } 25 | }; 26 | 27 | int counter = 0; 28 | 29 | void AddInTopBar() 30 | { 31 | var message = $"Simple message #{counter++}"; 32 | var type = Enum.GetValues()[counter % 4]; 33 | MessageService.ShowMessageBar(options => 34 | { 35 | options.Title = message; 36 | options.Intent = type; 37 | options.Section = App.MESSAGES_TOP; 38 | options.Timeout = 4000; 39 | }); 40 | } 41 | 42 | void AddInNotificationCenter() 43 | { 44 | MessageService.ShowMessageBar(options => 45 | { 46 | options.Intent = Enum.GetValues()[counter % 4]; 47 | options.Title = $"Simple message #{counter++}"; 48 | options.Body = MessageBarSamples.OneRandomMessage; 49 | options.Link = link; 50 | options.Timestamp = DateTime.Now; 51 | options.Timeout = 3000; 52 | options.Section = App.MESSAGES_NOTIFICATION_CENTER; 53 | }); 54 | } 55 | 56 | 57 | async Task AddInDialog() 58 | { 59 | MessageService.ShowMessageBar(options => 60 | { 61 | options.Intent = Enum.GetValues()[counter % 4]; 62 | options.Title = $"Simple message #{counter++}"; 63 | options.Body = MessageBarSamples.OneRandomMessage; 64 | options.Link = link; 65 | options.Timeout = 5000; 66 | options.Timestamp = DateTime.Now; 67 | options.Section = App.MESSAGES_DIALOG; 68 | }); 69 | 70 | await OpenDialogAsync(); 71 | } 72 | 73 | private async Task OpenDialogAsync() 74 | { 75 | DialogParameters parameters = new() 76 | { 77 | Title = $"Hi {simplePerson.Firstname}!", 78 | PrimaryAction = "Yes", 79 | PrimaryActionEnabled = false, 80 | SecondaryAction = "No", 81 | Width = "500px", 82 | Height = "500px", 83 | Content = simplePerson, 84 | TrapFocus = true, 85 | Modal = true, 86 | }; 87 | 88 | IDialogReference dialog = await DialogService.ShowDialogAsync(simplePerson, parameters); 89 | DialogResult? result = await dialog.Result; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Dialog/Examples/DialogDefault.razor: -------------------------------------------------------------------------------- 1 |
2 |

3 | When 'Modal' is checked, the dialog can be dismissed by clicking outside of the dialog (anywhere on the overlay). When unchecked, 4 | the dialog can be dismissed only by the 'ESC' key.
The dialog can always be closed by using the 'Close dialog' 5 | button. 6 |

7 |

8 | When 'Trap focus' is checked, only the elements within the dialog will receive focus. When unchecked, focus will also move outside of the 9 | dialog. 10 |

11 |

12 | When 'Prevent scroll' is checked, the body of the page will not be scrollable when the dialog is shown. 13 |

14 |

15 | Hidden is bound to dialog visibility. You can show/hide dialog by changing this property or calling Show() / Hide() 16 | on component reference. 17 |

18 | 19 | Modal 20 | 21 | 22 | Trap focus 23 | 24 | 25 | Prevent scroll 26 | 27 | 28 | Hidden 29 | 30 |
31 |
32 | 33 | 34 |

Just a simple dialog

35 |

The 'Close dialog' button is automatically focused.

36 |

The 'Another button' doesn't do anything other than showing it can receive focus.

37 |

The width, height and padding of the dialog are customized in (isolated) CSS

38 | Close dialog 39 | Another button 40 | 41 |
42 |
43 | 44 |

Status: @_status

45 | 46 | Open dialog 47 | 48 | @code { 49 | private FluentDialog? _myFluentDialog; 50 | private bool _trapFocus = true; 51 | private bool _modal = true; 52 | private bool _preventScroll = true; 53 | private string? _status; 54 | private bool Hidden { get; set; } = true; 55 | 56 | protected override void OnAfterRender(bool firstRender) 57 | { 58 | if (firstRender) 59 | _myFluentDialog!.Hide(); 60 | } 61 | 62 | private void OnOpen() 63 | { 64 | _status = "Dialog opened with button click"; 65 | _myFluentDialog!.Show(); 66 | DemoLogger.WriteLine(_status); 67 | } 68 | 69 | private void OnClose() 70 | { 71 | _status = $"Dialog dismissed with reason: Close button clicked"; 72 | _myFluentDialog!.Hide(); 73 | DemoLogger.WriteLine(_status); 74 | } 75 | 76 | private void OnDismiss(DialogEventArgs args) 77 | { 78 | if (args is not null && args.Reason is not null && args.Reason == "dismiss") 79 | { 80 | _status = $"Dialog dismissed with reason: Dismissed"; 81 | _myFluentDialog!.Hide(); 82 | DemoLogger.WriteLine(_status); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/Toast/Examples/ToastProgressToasts.razor: -------------------------------------------------------------------------------- 1 | @inject IToastService ToastService 2 | 3 | 4 | Progress toast example 1 5 | Update 6 | Reset 7 | Close 8 | Indeterminate progress toast 9 | 10 | 11 | @code { 12 | private FluentButton? _button; 13 | private static string _id = "progressToast"; 14 | private CancellationTokenSource _cancel = new(); 15 | 16 | private ToastParameters _progressToastData; 17 | private ToastParameters _progressToastDataIndeterminate; 18 | 19 | public ToastProgressToasts() 20 | { 21 | _progressToastData = new() 22 | { 23 | Id = _id, 24 | Intent = ToastIntent.Upload, 25 | Title = "Uploading file", 26 | Timeout = 0, 27 | TopAction = "Cancel", 28 | OnTopAction = EventCallback.Factory.Create(this, ClickedCancel), 29 | Content = new ProgressToastContent() 30 | { 31 | Details = "This may take a while.", 32 | Progress = 0, 33 | }, 34 | }; 35 | 36 | _progressToastDataIndeterminate = new() 37 | { 38 | Id = $"{_id}Indeterminate", 39 | Intent = ToastIntent.Progress, 40 | Title = "Creating resource", 41 | Content = new ProgressToastContent() 42 | { 43 | Details = "This will close after the default timeout of 7000 milliseconds.", 44 | }, 45 | }; 46 | 47 | } 48 | 49 | protected override void OnAfterRender(bool firstRender) 50 | { 51 | if (firstRender) 52 | { 53 | _button!.SetDisabled(true); 54 | } 55 | } 56 | 57 | private void ShowExample() 58 | { 59 | _button!.SetDisabled(false); 60 | ToastService.ShowProgressToast(_progressToastData); 61 | } 62 | 63 | private void ShowIndeterminateExample() 64 | { 65 | ToastService.ShowProgressToast(_progressToastDataIndeterminate); 66 | } 67 | 68 | private static void ClickedCancel() 69 | { 70 | DemoLogger.WriteLine($"Clicked cancel"); 71 | } 72 | 73 | 74 | private async Task UpdateProgress() 75 | { 76 | _button!.SetDisabled(true); 77 | 78 | for (int i = 0; i < 101; i++) 79 | { 80 | _progressToastData.Content.Progress = i; 81 | ToastService.UpdateToast(_id, _progressToastData); 82 | await Task.Delay(100, _cancel.Token); 83 | }; 84 | 85 | } 86 | 87 | private void Reset() 88 | { 89 | _cancel.Cancel(); 90 | _progressToastData.Content.Progress = 0; 91 | ToastService.UpdateToast(_id, _progressToastData); 92 | _cancel = new(); 93 | _button!.SetDisabled(false); 94 | 95 | } 96 | 97 | private void Close() 98 | { 99 | Reset(); 100 | ToastService.CloseToast(_id); 101 | _button!.SetDisabled(true); 102 | } 103 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBox/Examples/DialogMessageBoxAsync.razor: -------------------------------------------------------------------------------- 1 | @inject IDialogService DialogService 2 | 3 | 4 | 5 | Success 6 | Warning 7 | Error 8 | Information 9 | Confirmation 10 | Long message 11 | Custom message 12 | 13 | 14 |

15 | Last result: @(canceled == null ? "" : (canceled == true ? "❌ Canceled" : "✅ OK")) 16 |

17 | 18 | @code 19 | { 20 | bool? canceled; 21 | 22 | private async Task ShowSuccessAsync() 23 | { 24 | var dialog = await DialogService.ShowSuccessAsync("The action was a success"); 25 | var result = await dialog.Result; 26 | canceled = result.Cancelled; 27 | } 28 | 29 | private async Task ShowWarningAsync() 30 | { 31 | var dialog = await DialogService.ShowWarningAsync("This is your final warning"); 32 | var result = await dialog.Result; 33 | canceled = result.Cancelled; 34 | } 35 | 36 | private async Task ShowErrorAsync() 37 | { 38 | var dialog = await DialogService.ShowErrorAsync("This is an error"); 39 | var result = await dialog.Result; 40 | canceled = result.Cancelled; 41 | } 42 | 43 | private async Task ShowInformationAsync() 44 | { 45 | var dialog = await DialogService.ShowInfoAsync("This is a message"); 46 | var result = await dialog.Result; 47 | canceled = result.Cancelled; 48 | } 49 | 50 | private async Task ShowConfirmationAsync() 51 | { 52 | var dialog = await DialogService.ShowConfirmationAsync("Do you have two eyes?", "Yup", "Nope", "Eyes on you"); 53 | var result = await dialog.Result; 54 | canceled = result.Cancelled; 55 | } 56 | 57 | private async Task ShowMessageBoxLongAsync() 58 | { 59 | var dialog = await DialogService.ShowInfoAsync("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum"); 60 | var result = await dialog.Result; 61 | canceled = result.Cancelled; 62 | } 63 | 64 | private async Task ShowMessageBoxAsync() 65 | { 66 | var dialog = await DialogService.ShowMessageBoxAsync(new DialogParameters() 67 | { 68 | Content = new() 69 | { 70 | Title = "My title", 71 | MarkupMessage = new MarkupString("My customized message"), 72 | Icon = new Icons.Regular.Size24.Games(), 73 | IconColor = Color.Success, 74 | }, 75 | PrimaryAction = "Plus", 76 | SecondaryAction = "Minus", 77 | Width = "300px", 78 | }); 79 | var result = await dialog.Result; 80 | canceled = result.Cancelled; 81 | } 82 | } -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/Pages/Demo/MessageBar/MessageBarPage.razor: -------------------------------------------------------------------------------- 1 | @page "/MessageBar" 2 | 3 | @using BlazorTemplate.Components.Pages.Demo.MessageBar.Examples 4 | 5 |

MessageBar

6 | 7 | IMPORTANT!! 8 |

9 | MessageBars are rendered by the <FluentMessageBarProvider />. This component needs to be added to the layout of your application/site. 10 | For a message bar that needs to be shown at the top of the screen, you typically do this in the MainLayout.razor file at the location in 11 | the HTML structure where you want the message bars to appear of the <main> section like this: 12 |

13 |

14 | 15 | For the MessageBars to work properly, the <FluentMessageBarProvider/> needs interactivity! If you are using "per page" interactivity, make sure to add a @@rendermode to 16 | either the provider itself of the component the provider is placed in. 17 | 18 |

19 | 20 | <main> 21 | <nav> 22 | : 23 | </nav> 24 | <div class="content"> 25 | <article id="article"> 26 | <FluentMessageBarProvider Section="MESSAGES_TOP"/> 27 | @@Body 28 | </article> 29 | </div> 30 | </main> 31 | 32 | 33 | 34 |

Examples

35 | 36 | 37 |

38 | Click on these buttons to display a message (max 5) in the top, 39 | in the Notification Center (top/right bell icon) or in a dialog (max 1).
40 | If you click on "Add in a dialog" multiple times, you will see that only one message is displayed. 41 | Once you dismiss that one, the next one will be displayed. 42 |

43 |

This is the advised way to use MessageBars

44 |
45 | 46 | 47 |

48 | These message automatically dismiss after a set amount of time. 49 |

50 |
51 | 52 | 53 |

54 | Click on the button to display a message (max 5) utilizing the MessageOptions class. 55 |

56 |
57 | 58 | 59 |

60 | This example show the different intents that can be shown in a MessageBar. 61 | This example does not use the MessageBarService. 62 |

63 |
64 | 65 | 66 |

67 | This example show how a MessageBar can be used to be shown as a notification (border added for styling puroses only). 68 | This example does not use the MessageBarService. 69 |

70 |
71 | 72 | 73 |

74 | Copy of the button shown in the header which displays the number of notifications. Use the button in the first example to add notifications. 75 |

76 |
77 | 78 |

MessageBar provider

79 | 80 |

Display the list of messages

81 | 82 |

83 | You display a list of messages for a specific section using the FluentMessageBarProvider component. 84 |

85 | 86 | 87 | @(@"") 88 | 89 | 90 | -------------------------------------------------------------------------------- /Dashboard.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.7.34202.233 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7EAD0AF6-B801-435B-B6E7-EE85D4DA0A48}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{2E2C1EA8-3AA0-4915-B1E9-5B91B7184254}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{6C2A473B-C05E-4CC2-8B27-AB6A2297ADA4}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | CHANGELOG.md = CHANGELOG.md 14 | LICENSE = LICENSE 15 | README.md = README.md 16 | sample.png = sample.png 17 | EndProjectSection 18 | EndProject 19 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorTemplate.Server", "src\BlazorTemplate.Server\BlazorTemplate.Server.csproj", "{6E6CFED8-3A02-4D72-8B46-060551D0654A}" 20 | EndProject 21 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorTemplate.Shared", "src\BlazorTemplate.Shared\BlazorTemplate.Shared.csproj", "{525C0901-2CE2-4118-A4B2-9A6399E6AB70}" 22 | EndProject 23 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorTemplate.Client", "src\BlazorTemplate.Client\BlazorTemplate.Client.csproj", "{D5BD1F25-9A6B-4BD0-9AF2-FECDF8769B1C}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorTemplate.Components", "src\BlazorTemplate.Components\BlazorTemplate.Components.csproj", "{DE8F1AC2-42CD-4258-9703-BE8E84595EBC}" 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Release|Any CPU = Release|Any CPU 31 | EndGlobalSection 32 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 33 | {6E6CFED8-3A02-4D72-8B46-060551D0654A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {6E6CFED8-3A02-4D72-8B46-060551D0654A}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {6E6CFED8-3A02-4D72-8B46-060551D0654A}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {6E6CFED8-3A02-4D72-8B46-060551D0654A}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {525C0901-2CE2-4118-A4B2-9A6399E6AB70}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {525C0901-2CE2-4118-A4B2-9A6399E6AB70}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {525C0901-2CE2-4118-A4B2-9A6399E6AB70}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {525C0901-2CE2-4118-A4B2-9A6399E6AB70}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {D5BD1F25-9A6B-4BD0-9AF2-FECDF8769B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 42 | {D5BD1F25-9A6B-4BD0-9AF2-FECDF8769B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU 43 | {D5BD1F25-9A6B-4BD0-9AF2-FECDF8769B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {D5BD1F25-9A6B-4BD0-9AF2-FECDF8769B1C}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {DE8F1AC2-42CD-4258-9703-BE8E84595EBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {DE8F1AC2-42CD-4258-9703-BE8E84595EBC}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {DE8F1AC2-42CD-4258-9703-BE8E84595EBC}.Release|Any CPU.ActiveCfg = Release|Any CPU 48 | {DE8F1AC2-42CD-4258-9703-BE8E84595EBC}.Release|Any CPU.Build.0 = Release|Any CPU 49 | EndGlobalSection 50 | GlobalSection(SolutionProperties) = preSolution 51 | HideSolutionNode = FALSE 52 | EndGlobalSection 53 | GlobalSection(NestedProjects) = preSolution 54 | {6E6CFED8-3A02-4D72-8B46-060551D0654A} = {7EAD0AF6-B801-435B-B6E7-EE85D4DA0A48} 55 | {525C0901-2CE2-4118-A4B2-9A6399E6AB70} = {7EAD0AF6-B801-435B-B6E7-EE85D4DA0A48} 56 | {D5BD1F25-9A6B-4BD0-9AF2-FECDF8769B1C} = {7EAD0AF6-B801-435B-B6E7-EE85D4DA0A48} 57 | {DE8F1AC2-42CD-4258-9703-BE8E84595EBC} = {7EAD0AF6-B801-435B-B6E7-EE85D4DA0A48} 58 | EndGlobalSection 59 | GlobalSection(ExtensibilityGlobals) = postSolution 60 | SolutionGuid = {5734F812-00FE-4C56-B5DC-6EAF75AF4118} 61 | EndGlobalSection 62 | EndGlobal 63 | -------------------------------------------------------------------------------- /src/BlazorTemplate.Components/wwwroot/js/highlight-extensions.js: -------------------------------------------------------------------------------- 1 | // Add Stylesheets 2 | hljs_addStylesheet('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/styles/vs.min.css', 'highlight-light', null); 3 | hljs_addStylesheet('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/styles/vs2015.min.css', 'highlight-dark', 'disabled'); 4 | 5 | hljs_addInlineStylesheet(`pre[class~="snippet"] { 6 | --font-monospace: "courier"; 7 | --type-ramp-base-font-variations: unset; 8 | font-weight: bold; 9 | }`); 10 | 11 | // Add Scripts 12 | const highlight = hljs_addJavaScript('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/highlight.min.js'); 13 | 14 | // Add custom code 15 | highlight.onload = () => { 16 | const hljsRazor = hljs_addJavaScript('https://cdn.jsdelivr.net/npm/highlightjs-cshtml-razor@2.1.1/dist/cshtml-razor.min.js'); 17 | 18 | // Switch highlight Dark/Light theme 19 | const theme = document.querySelector('loading-theme > fluent-design-theme'); 20 | if (theme != null) { 21 | theme.addEventListener('onchange', (e) => { 22 | if (e.detail.name == 'mode') { 23 | if (e.detail.newValue === 'undefined') return; 24 | const isDark = e.detail.newValue.includes('dark'); 25 | hljs_ColorSwitcher(isDark); 26 | } 27 | }); 28 | } 29 | 30 | // Detect system theme changing 31 | window.matchMedia('(prefers-color-scheme: dark)') 32 | .addEventListener('change', (e) => { 33 | hljs_ColorSystem(); 34 | }); 35 | 36 | // First/default theme 37 | hljs_ColorSystem(); 38 | } 39 | function hljs_ColorSystem() { 40 | const theme = document.querySelector('loading-theme > fluent-design-theme'); 41 | if (theme != null) { 42 | if (theme.getAttribute('mode') == 'null' || theme.getAttribute('mode') == null || theme.getAttribute('mode').value === undefined) { 43 | const isSystemDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; 44 | hljs_ColorSwitcher(isSystemDark); 45 | } 46 | } 47 | } 48 | 49 | function hljs_ColorSwitcher(isDark) { 50 | const darkCss = document.querySelector('link[title="highlight-dark"]'); 51 | const lightCss = document.querySelector('link[title="highlight-light"]'); 52 | 53 | if (isDark) { 54 | darkCss.removeAttribute("disabled"); 55 | lightCss.setAttribute("disabled", "disabled"); 56 | } 57 | else { 58 | lightCss.removeAttribute("disabled"); 59 | darkCss.setAttribute("disabled", "disabled"); 60 | } 61 | } 62 | 63 | // Add a