- Swapping to Development environment will display more detailed information about the error that occurred.
-
-
- The Development environment shouldn't be enabled for deployed applications.
- It can result in displaying sensitive information from exceptions to end users.
- For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development
- and restarting the app.
-
+
+@code {
+
+}
diff --git a/CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor b/CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor
new file mode 100644
index 0000000..f68bec6
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor
@@ -0,0 +1,104 @@
+@using blazor_multi_tab_ui.Services
+@inject IJSRuntime JS
+@inject MdiStateService stateService;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+@code {
+ private DxContextMenu? menu;
+ private IJSObjectReference? module;
+ private int? targetTabVisibleIndex;
+
+ [Parameter]
+ public string? TabSelector { get; set; }
+
+ public async Task AddContextMenuHandler()
+ {
+ if (module is null) return;
+ await module.InvokeVoidAsync(
+ "addContextMenuHandler",
+ TabSelector,
+ DotNetObjectReference.Create(this));
+ }
+
+ protected async override Task OnAfterRenderAsync(bool firstRender)
+ {
+ if (firstRender) module = await JS.InvokeAsync(
+ "import", "./Components/MDI/TabsContextMenu.razor.js");
+
+ await AddContextMenuHandler();
+
+ await base.OnAfterRenderAsync(firstRender);
+ }
+
+ [JSInvokable]
+ public async Task ShowContextMenu(MouseEventArgs e, int tabVisibleIndex)
+ {
+ targetTabVisibleIndex = tabVisibleIndex;
+ if (menu != null)
+ {
+ await menu.ShowAsync(e);
+ }
+ }
+
+ private void HideTab()
+ {
+ if (targetTabVisibleIndex is not null)
+ stateService.SetTabVisible((int)targetTabVisibleIndex, false);
+ }
+
+ private async Task HideAllTabs()
+ {
+ await stateService.SetAllTabsVisible(false);
+ }
+
+ private async Task HideOtherTabs()
+ {
+ if (targetTabVisibleIndex is null) return;
+ await stateService.SetAllTabsVisible(false);
+ stateService.SetTabVisible((int)targetTabVisibleIndex, true);
+ }
+
+ private async Task RestoreHiddenTabs()
+ {
+ await stateService.SetAllTabsVisible(true);
+ }
+
+ private async Task CloseTab()
+ {
+ if (targetTabVisibleIndex is not null)
+ await stateService.RemoveTab((int)targetTabVisibleIndex);
+ }
+
+ private async Task CloseAllTabs()
+ {
+ await stateService.RemoveAllTabs();
+ }
+ private async Task CloseOtherTabs()
+ {
+ if (targetTabVisibleIndex is not null)
+ await stateService.RemoveAllButTab((int)targetTabVisibleIndex);
+ }
+
+}
diff --git a/CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor.js b/CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor.js
new file mode 100644
index 0000000..de94e6a
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor.js
@@ -0,0 +1,31 @@
+export function addContextMenuHandler(tabSelector, dotNetObject) {
+ var tabElements = document.querySelectorAll(tabSelector);
+ if (!tabElements) return;
+
+ tabElements.forEach(tabElement => {
+
+ tabElement.addEventListener('contextmenu', (event) => {
+ event.preventDefault();
+ let eventArgs = {
+ clientX: event.clientX,
+ clientY: event.clientY,
+ screenX: event.screenX,
+ screenY: event.screenY,
+ offsetX: event.offsetX,
+ offsetY: event.offsetY,
+ pageX: event.pageX,
+ pageY: event.pageY,
+ button: event.button,
+ buttons: event.buttons,
+ ctrlKey: event.ctrlKey,
+ shiftKey: event.shiftKey,
+ altKey: event.altKey,
+ metaKey: event.metaKey,
+ detail: event.detail,
+ type: event.type
+ };
+ var index = parseInt(tabElement.getAttribute("index"));
+ dotNetObject.invokeMethodAsync("ShowContextMenu", eventArgs, index);
+ });
+ });
+};
\ No newline at end of file
diff --git a/CS/blazor_multi_tab_ui/Components/Pages/Index.razor b/CS/blazor_multi_tab_ui/Components/Pages/Index.razor
new file mode 100644
index 0000000..d7d9326
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Components/Pages/Index.razor
@@ -0,0 +1,10 @@
+@page "/"
+@using blazor_multi_tab_ui.Components.MDI
+
+Dynamic Tabbed Interface
+
+
+
+@code {
+
+}
\ No newline at end of file
diff --git a/CS/DxBlazorApplication1/Components/Pages/Index.razor.css b/CS/blazor_multi_tab_ui/Components/Pages/Index.razor.css
similarity index 100%
rename from CS/DxBlazorApplication1/Components/Pages/Index.razor.css
rename to CS/blazor_multi_tab_ui/Components/Pages/Index.razor.css
diff --git a/CS/DxBlazorApplication1/Components/Routes.razor b/CS/blazor_multi_tab_ui/Components/Routes.razor
similarity index 100%
rename from CS/DxBlazorApplication1/Components/Routes.razor
rename to CS/blazor_multi_tab_ui/Components/Routes.razor
diff --git a/CS/DxBlazorApplication1/Components/_Imports.razor b/CS/blazor_multi_tab_ui/Components/_Imports.razor
similarity index 76%
rename from CS/DxBlazorApplication1/Components/_Imports.razor
rename to CS/blazor_multi_tab_ui/Components/_Imports.razor
index 799ecfb..8f8decc 100644
--- a/CS/DxBlazorApplication1/Components/_Imports.razor
+++ b/CS/blazor_multi_tab_ui/Components/_Imports.razor
@@ -6,8 +6,8 @@
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using static Microsoft.AspNetCore.Components.Web.RenderMode
-@using DxBlazorApplication1
-@using DxBlazorApplication1.Components
-@using DxBlazorApplication1.Components.Layout
+@using blazor_multi_tab_ui
+@using blazor_multi_tab_ui.Components
+@using blazor_multi_tab_ui.Components.Layout
@using DevExpress.Blazor
\ No newline at end of file
diff --git a/CS/blazor_multi_tab_ui/Models/CustomerModel.cs b/CS/blazor_multi_tab_ui/Models/CustomerModel.cs
new file mode 100644
index 0000000..9414a74
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Models/CustomerModel.cs
@@ -0,0 +1,13 @@
+namespace blazor_multi_tab_ui.Models
+{
+ public class CustomerModel
+ {
+ public int Id { get; set; }
+ public string? Name { get; set; }
+ public List? Orders { get; set; }
+ public int? TotalOrders
+ {
+ get { return Orders?.Count(); }
+ }
+ }
+}
diff --git a/CS/blazor_multi_tab_ui/Models/CustomersChartPoint.cs b/CS/blazor_multi_tab_ui/Models/CustomersChartPoint.cs
new file mode 100644
index 0000000..21027ff
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Models/CustomersChartPoint.cs
@@ -0,0 +1,9 @@
+namespace blazor_multi_tab_ui.Models
+{
+ public class CustomersChartPoint
+ {
+ public string? Name { get; set; }
+ public int Orders { get; set; }
+ public double Sales { get; set; }
+ }
+}
diff --git a/CS/blazor_multi_tab_ui/Models/MdiStateModel.cs b/CS/blazor_multi_tab_ui/Models/MdiStateModel.cs
new file mode 100644
index 0000000..66f0c8e
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Models/MdiStateModel.cs
@@ -0,0 +1,8 @@
+namespace blazor_multi_tab_ui.Models
+{
+ public class MdiStateModel
+ {
+ public List? Tabs { get; set; }
+ public int ActiveTabIndex { get; set; }
+ }
+}
diff --git a/CS/blazor_multi_tab_ui/Models/MdiTabModel.cs b/CS/blazor_multi_tab_ui/Models/MdiTabModel.cs
new file mode 100644
index 0000000..80a3404
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Models/MdiTabModel.cs
@@ -0,0 +1,50 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace blazor_multi_tab_ui.Models
+{
+ public class MdiTabModel
+ {
+ public MdiTabModel() => Id = Guid.NewGuid().ToString();
+ public string Id { get; }
+ public int VisibleIndex { get; set; }
+ public bool Visible { get; set; }
+ public string? Text { get; set; }
+ public string? TabTypeName { get; set; }
+ public Dictionary? UntypedParameters { get; set; }
+ private Dictionary? _cachedParameters;
+
+ [JsonIgnore]
+ public Dictionary? Parameters
+ {
+ get
+ {
+ if (_cachedParameters == null && UntypedParameters != null)
+ {
+ _cachedParameters = UntypedParameters.ToDictionary(
+ p => p.Key,
+ p => (object)(p.Value.ValueKind switch
+ {
+ JsonValueKind.Number when p.Value.TryGetInt32(out var i) => i,
+ JsonValueKind.Number when p.Value.TryGetInt64(out var l) => l,
+ JsonValueKind.Number when p.Value.TryGetDouble(out var d) => d,
+ JsonValueKind.True => true,
+ JsonValueKind.False => false,
+ JsonValueKind.String => p.Value.GetString()!,
+ _ => p.Value.ToString()!
+ })
+ );
+ }
+ return _cachedParameters;
+ }
+ set
+ {
+ UntypedParameters = value?.ToDictionary(
+ p => p.Key,
+ p => JsonSerializer.SerializeToElement(p.Value)
+ );
+ _cachedParameters = value;
+ }
+ }
+ }
+}
diff --git a/CS/blazor_multi_tab_ui/Models/OrderModel.cs b/CS/blazor_multi_tab_ui/Models/OrderModel.cs
new file mode 100644
index 0000000..22fc1d3
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Models/OrderModel.cs
@@ -0,0 +1,10 @@
+namespace blazor_multi_tab_ui.Models
+{
+ public class OrderModel
+ {
+ public int Id { get; set; }
+ public string? Description { get; set; }
+ public DateTime CreatedDate { get; set; }
+ public double Cost { get; set; }
+ }
+}
diff --git a/CS/DxBlazorApplication1/NOTICE.txt b/CS/blazor_multi_tab_ui/NOTICE.txt
similarity index 100%
rename from CS/DxBlazorApplication1/NOTICE.txt
rename to CS/blazor_multi_tab_ui/NOTICE.txt
diff --git a/CS/DxBlazorApplication1/Program.cs b/CS/blazor_multi_tab_ui/Program.cs
similarity index 56%
rename from CS/DxBlazorApplication1/Program.cs
rename to CS/blazor_multi_tab_ui/Program.cs
index 8e9defa..b9bb907 100644
--- a/CS/DxBlazorApplication1/Program.cs
+++ b/CS/blazor_multi_tab_ui/Program.cs
@@ -1,34 +1,33 @@
-using DxBlazorApplication1.Services;
-using DxBlazorApplication1.Components;
-using DxBlazorApplication1.Components.MDI;
+using blazor_multi_tab_ui.Components;
+using blazor_multi_tab_ui.Services;
var builder = WebApplication.CreateBuilder(args);
-// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
-builder.Services.AddDevExpressBlazor(options => {
- options.BootstrapVersion = DevExpress.Blazor.BootstrapVersion.v5;
+builder.Services.AddDevExpressBlazor(options =>
+{
options.SizeMode = DevExpress.Blazor.SizeMode.Medium;
});
-builder.Services.AddSingleton();
-builder.Services.AddScoped();
builder.Services.AddMvc();
-var app = builder.Build();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
-// Configure the HTTP request pipeline.
-if (!app.Environment.IsDevelopment()) {
+var app = builder.Build();
+if (!app.Environment.IsDevelopment())
+{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
+
app.UseHttpsRedirection();
-app.UseStaticFiles();
app.UseAntiforgery();
+app.MapStaticAssets();
app.MapRazorComponents()
.AddInteractiveServerRenderMode()
.AllowAnonymous();
diff --git a/CS/blazor_multi_tab_ui/Services/DataService.cs b/CS/blazor_multi_tab_ui/Services/DataService.cs
new file mode 100644
index 0000000..38863e5
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Services/DataService.cs
@@ -0,0 +1,56 @@
+using blazor_multi_tab_ui.Models;
+
+namespace blazor_multi_tab_ui.Services
+{
+ public class DataService
+ {
+ private List customers;
+ public DataService() { customers = GenerateTestData(); }
+
+ public List GetCustomers() { return customers; }
+ public List? GetOrdersByCustomerId(int customerId)
+ {
+ var customer = customers.FirstOrDefault(c => c.Id == customerId);
+ if (customer == null) return null;
+ return customer.Orders;
+ }
+ public List GetCustomersChartData()
+ {
+ return customers.Select(customer => new CustomersChartPoint
+ {
+ Name = customer.Name,
+ Orders = customer?.Orders?.Count ?? 0,
+ Sales = customer?.Orders?.Sum(order => order.Cost) ?? 0
+ }).ToList();
+ }
+
+
+ private List GenerateTestData()
+ {
+ var now = DateTime.Now;
+ return new List{
+ new CustomerModel()
+ {
+ Id = 1,
+ Name = "John Doe",
+ Orders = new List
+ {
+ new OrderModel{ Id = 1, Description = $"Apples", CreatedDate = new DateTime(now.Year, now.Month, now.Day ), Cost = 100 },
+ new OrderModel{ Id = 2, Description = $"Bananas", CreatedDate = new DateTime(now.Year, now.Month, now.Day - 1 ), Cost = 200 },
+ }
+ },
+ new CustomerModel()
+ {
+ Id = 2,
+ Name = "Jane Smith",
+ Orders = new List
+ {
+ new OrderModel{ Id = 3, Description = $"Kiwi", CreatedDate = new DateTime(now.Year, now.Month, now.Day ), Cost = 150 },
+ new OrderModel{ Id = 4, Description = $"Durian", CreatedDate = new DateTime(now.Year, now.Month, now.Day - 1 ), Cost = 300 },
+ new OrderModel{ Id = 5, Description = $"Potatoes", CreatedDate = new DateTime(now.Year, now.Month, now.Day - 2 ), Cost = 42 },
+ }
+ },
+ };
+ }
+ }
+}
diff --git a/CS/blazor_multi_tab_ui/Services/MdiStateService.cs b/CS/blazor_multi_tab_ui/Services/MdiStateService.cs
new file mode 100644
index 0000000..a9032f0
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/Services/MdiStateService.cs
@@ -0,0 +1,136 @@
+using blazor_multi_tab_ui.Components.MDI.Tabs;
+using blazor_multi_tab_ui.Models;
+using Microsoft.JSInterop;
+using System.Runtime.Intrinsics.Arm;
+using System.Text.Json;
+
+namespace blazor_multi_tab_ui.Services
+{
+ public class MdiStateService(IJSRuntime _js)
+ {
+ private MdiStateModel state = new();
+ private const string LOCAL_STORAGE_KEY = "MDI-Layout";
+ private readonly Dictionary _stringToTypeMap = new(StringComparer.OrdinalIgnoreCase)
+ {
+ ["customers"] = typeof(Customers),
+ ["orders"] = typeof(Orders),
+ ["chart"] = typeof(Chart),
+ };
+
+ public bool TryGetType(string? key, out Type? type) => _stringToTypeMap.TryGetValue(key ?? "unknown", out type);
+ public event Action? OnTabsChanged;
+
+ public async Task LoadState()
+ {
+ var cachedState = await LoadStateFromLocalStorageAsync();
+ if (cachedState != null) state = cachedState;
+ else state = new MdiStateModel { Tabs = GetDefaultTabs(), ActiveTabIndex = 0 };
+ return state;
+ }
+ public async Task SaveState(bool InvokeChanged = false)
+ {
+ var orderedTabs = state?.Tabs?.OrderBy(i => i.VisibleIndex).ToList();
+ if (orderedTabs is null) return;
+ for (int i = 0; i < orderedTabs.Count; i++)
+ {
+ orderedTabs[i].VisibleIndex = i;
+ }
+ if (state is null) return;
+ await SaveStateToLocalStorageAsync(state);
+ if (InvokeChanged) OnTabsChanged?.Invoke();
+ }
+ public async Task SetActiveTabIndex(int newIndex)
+ {
+ state.ActiveTabIndex = newIndex;
+ await SaveState();
+ }
+
+ public async Task AddTab(string Text, string TypeName, Dictionary? Parameters = null)
+ {
+ state?.Tabs?.Add(new MdiTabModel { Text = Text, TabTypeName = TypeName, Parameters = Parameters, Visible = true, VisibleIndex = state.Tabs.Count });
+ await SaveState(true);
+ }
+ public async Task SetAllTabsVisible(bool Visible)
+ {
+ state?.Tabs?.ForEach(t => t.Visible = Visible);
+ await SaveState(true);
+ }
+ public void SetTabVisible(int Index, bool Visible)
+ {
+ var tab = state?.Tabs?.FirstOrDefault(tab => tab.VisibleIndex == Index);
+ if (tab == null) { return; }
+ tab.Visible = Visible;
+ }
+
+ public async Task RemoveTab(int Index)
+ {
+ var tab = state?.Tabs?.FirstOrDefault(tab => tab.VisibleIndex == Index);
+ if (tab == null) { return; }
+ state?.Tabs?.Remove(tab);
+ await SaveState(true);
+ }
+ public async Task RemoveAllTabs()
+ {
+ state?.Tabs?.Clear();
+ await SaveState(true);
+ }
+ public async Task RemoveAllButTab(int Index)
+ {
+ var tab = state?.Tabs?.FirstOrDefault(tab => tab.VisibleIndex == Index);
+ if (tab == null) { return; }
+ state?.Tabs?.Clear();
+ state?.Tabs?.Add(tab);
+ await SaveState(true);
+ }
+ public void ReorderTabs(int fromIndex, int toIndex)
+ {
+ if (fromIndex == toIndex || fromIndex < 0 || toIndex < 0 ||
+ fromIndex >= state?.Tabs?.Count || toIndex >= state?.Tabs?.Count)
+ return;
+
+ var orderedTabs = state?.Tabs?.OrderBy(i => i.VisibleIndex).ToList();
+ if (orderedTabs is null) return;
+
+ var tabToMove = orderedTabs[fromIndex];
+ orderedTabs.RemoveAt(fromIndex);
+ orderedTabs.Insert(toIndex, tabToMove);
+ for (int i = 0; i < orderedTabs.Count; i++)
+ {
+ orderedTabs[i].VisibleIndex = i;
+ }
+ for (int i = 0; i < orderedTabs.Count; i++)
+ {
+ var originalTab = state?.Tabs?.First(x => x == orderedTabs[i]);
+ if (originalTab != null) originalTab.VisibleIndex = orderedTabs[i].VisibleIndex;
+ }
+ }
+
+
+
+ private List GetDefaultTabs()
+ {
+ List result = new();
+ result.Add(new MdiTabModel { Text = "Customers", Visible = true, VisibleIndex = 0, TabTypeName = "Customers" });
+ return result;
+ }
+ private async Task SaveStateToLocalStorageAsync(MdiStateModel state)
+ {
+ try
+ {
+ var json = JsonSerializer.Serialize(state);
+ await _js.InvokeVoidAsync("localStorage.setItem", LOCAL_STORAGE_KEY, json);
+ }
+ catch { return; }
+ }
+
+ private async Task LoadStateFromLocalStorageAsync()
+ {
+ try
+ {
+ var json = await _js.InvokeAsync("localStorage.getItem", LOCAL_STORAGE_KEY);
+ return JsonSerializer.Deserialize(json);
+ }
+ catch { return null; }
+ }
+ }
+}
diff --git a/CS/DxBlazorApplication1/Services/UrlGenerator.cs b/CS/blazor_multi_tab_ui/Services/UrlGenerator.cs
similarity index 84%
rename from CS/DxBlazorApplication1/Services/UrlGenerator.cs
rename to CS/blazor_multi_tab_ui/Services/UrlGenerator.cs
index c995faa..e78d361 100644
--- a/CS/DxBlazorApplication1/Services/UrlGenerator.cs
+++ b/CS/blazor_multi_tab_ui/Services/UrlGenerator.cs
@@ -1,12 +1,17 @@
using System.Web;
-namespace DxBlazorApplication1 {
- public static class UrlGenerator {
+namespace blazor_multi_tab_ui
+{
+ public static class UrlGenerator
+ {
public const string ToggleSidebarName = "toggledSidebar";
- public static string GetUrl(string baseUrl, bool toggledSidebar) {
+
+ public static string GetUrl(string baseUrl, bool toggledSidebar)
+ {
return $"{baseUrl}?{ToggleSidebarName}={toggledSidebar}";
}
- public static string GetUrl(bool toggledSidebar, string returnUrl) {
+ public static string GetUrl(bool toggledSidebar, string returnUrl)
+ {
var baseUriBuilder = new UriBuilder(returnUrl);
var query = HttpUtility.ParseQueryString(baseUriBuilder.Query);
var baseUrl = baseUriBuilder.Fragment + baseUriBuilder.Host + baseUriBuilder.Path;
diff --git a/CS/DxBlazorApplication1/appsettings.Development.json b/CS/blazor_multi_tab_ui/appsettings.Development.json
similarity index 100%
rename from CS/DxBlazorApplication1/appsettings.Development.json
rename to CS/blazor_multi_tab_ui/appsettings.Development.json
diff --git a/CS/DxBlazorApplication1/appsettings.json b/CS/blazor_multi_tab_ui/appsettings.json
similarity index 100%
rename from CS/DxBlazorApplication1/appsettings.json
rename to CS/blazor_multi_tab_ui/appsettings.json
diff --git a/CS/blazor_multi_tab_ui/blazor_multi_tab_ui.csproj b/CS/blazor_multi_tab_ui/blazor_multi_tab_ui.csproj
new file mode 100644
index 0000000..a7e40ee
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/blazor_multi_tab_ui.csproj
@@ -0,0 +1,12 @@
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/CS/DxBlazorApplication1/wwwroot/css/bootstrap/bootstrap.css b/CS/blazor_multi_tab_ui/wwwroot/css/bootstrap/bootstrap.css
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/bootstrap/bootstrap.css
rename to CS/blazor_multi_tab_ui/wwwroot/css/bootstrap/bootstrap.css
diff --git a/CS/DxBlazorApplication1/wwwroot/css/bootstrap/bootstrap.min.css b/CS/blazor_multi_tab_ui/wwwroot/css/bootstrap/bootstrap.min.css
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/bootstrap/bootstrap.min.css
rename to CS/blazor_multi_tab_ui/wwwroot/css/bootstrap/bootstrap.min.css
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/FONT-LICENSE.txt b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/FONT-LICENSE.txt
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/FONT-LICENSE.txt
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/FONT-LICENSE.txt
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/ICON-LICENSE.txt b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/ICON-LICENSE.txt
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/ICON-LICENSE.txt
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/ICON-LICENSE.txt
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/README.md b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/README.md
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/README.md
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/README.md
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/css/open-iconic-bootstrap.min.css b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/css/open-iconic-bootstrap.min.css
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.eot b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.eot
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.eot
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.eot
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.otf b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.otf
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.otf
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.otf
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.svg b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.svg
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.svg
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.ttf b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.ttf
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf
diff --git a/CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.woff b/CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.woff
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/css/open.iconic/font/fonts/open-iconic.woff
rename to CS/blazor_multi_tab_ui/wwwroot/css/open-iconic/font/fonts/open-iconic.woff
diff --git a/CS/DxBlazorApplication1/wwwroot/css/site.css b/CS/blazor_multi_tab_ui/wwwroot/css/site.css
similarity index 66%
rename from CS/DxBlazorApplication1/wwwroot/css/site.css
rename to CS/blazor_multi_tab_ui/wwwroot/css/site.css
index 42faecc..c3fbbbf 100644
--- a/CS/DxBlazorApplication1/wwwroot/css/site.css
+++ b/CS/blazor_multi_tab_ui/wwwroot/css/site.css
@@ -1,4 +1,4 @@
-@import url('open.iconic/font/css/open-iconic-bootstrap.min.css');
+@import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
@@ -39,12 +39,12 @@ html, body {
z-index: 1000;
}
- #blazor-error-ui .dismiss {
- cursor: pointer;
- position: absolute;
- right: 0.75rem;
- top: 0.5rem;
- }
+#blazor-error-ui .dismiss {
+ cursor: pointer;
+ position: absolute;
+ right: 0.75rem;
+ top: 0.5rem;
+}
.title {
display: flex;
@@ -53,11 +53,11 @@ html, body {
padding-bottom: 0.625rem;
}
- .title.title-secondary {
- padding-top: 0.313rem;
- padding-bottom: 0;
- color: var(--bs-secondary-color);
- }
+.title.title-secondary {
+ padding-top: 0.313rem;
+ padding-bottom: 0;
+ color: var(--bs-secondary-color, var(--DS-color-content-neutral-default-rest));
+}
.title-header-text {
font-size: 2.5rem;
@@ -89,4 +89,16 @@ html, body {
flex-direction: column;
gap: 0.625rem;
max-width: 100%;
-}
\ No newline at end of file
+}
+
+.icon {
+ width: var(--icon-width);
+ height: var(--icon-height);
+ background-color: currentcolor;
+ mask-position: center center;
+ -webkit-mask-repeat: no-repeat;
+ mask-repeat: no-repeat;
+ -webkit-mask-image: var(--icon-mask-image);
+ mask-image: var(--icon-mask-image);
+}
+
diff --git a/CS/blazor_multi_tab_ui/wwwroot/css/theme-bs.css b/CS/blazor_multi_tab_ui/wwwroot/css/theme-bs.css
new file mode 100644
index 0000000..ef1b457
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/css/theme-bs.css
@@ -0,0 +1,18 @@
+.nav-buttons-container .dxbl-btn.dxbl-btn-icon-only {
+ --dxbl-btn-padding-x: 0.75rem;
+ --dxbl-btn-padding-y: 0.25rem;
+}
+
+.icon {
+ --icon-width: 1rem;
+ --icon-height: 1rem;
+ --icon-back-mask-image: url("/images/back.svg");
+ --icon-close-mask-image: url("/images/close.svg");
+ --icon-menu-mask-image: url("/images/menu.svg");
+ --icon-docs-mask-image: url("/images/doc.svg");
+ --icon-demos-mask-image: url("/images/demos.svg");
+ --icon-home-mask-image: url("/images/home.svg");
+ --icon-weather-mask-image: url("/images/weather.svg");
+ --icon-counter-mask-image: url("/images/counter.svg");
+}
+
diff --git a/CS/blazor_multi_tab_ui/wwwroot/css/theme-fluent.css b/CS/blazor_multi_tab_ui/wwwroot/css/theme-fluent.css
new file mode 100644
index 0000000..06d6ee0
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/css/theme-fluent.css
@@ -0,0 +1,43 @@
+body {
+ margin: 0;
+}
+
+
+.w-100 {
+ width: 100%;
+}
+
+.text-danger {
+ color: var(--DS-color-surface-danger-default-rest);
+}
+
+.p-4 {
+ padding: 1.5rem;
+}
+
+.navigation-drawer {
+ --dxbl-drawer-separator-border-width: 0;
+}
+
+.menu-item {
+ --dxbl-menu-item-color: #fff;
+ --dxbl-menu-item-image-color: #fff;
+}
+
+[data-fluent-darkmode] .dxbl-theme-fluent .welcome-card {
+ color: var(--DS-primary-70);
+}
+
+.icon {
+ --icon-width: 1.25rem;
+ --icon-height: 1.25rem;
+ --icon-back-mask-image: url("/images/back-fluent.svg");
+ --icon-close-mask-image: url("/images/close-fluent.svg");
+ --icon-menu-mask-image: url("/images/menu-fluent.svg");
+ --icon-docs-mask-image: url("/images/doc-fluent.svg");
+ --icon-demos-mask-image: url("/images/demos-fluent.svg");
+ --icon-home-mask-image: url("/images/home-fluent.svg");
+ --icon-weather-mask-image: url("/images/weather-fluent.svg");
+ --icon-counter-mask-image: url("/images/counter-fluent.svg");
+}
+
diff --git a/CS/DxBlazorApplication1/wwwroot/favicon.ico b/CS/blazor_multi_tab_ui/wwwroot/favicon.ico
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/favicon.ico
rename to CS/blazor_multi_tab_ui/wwwroot/favicon.ico
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/log-in-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/log-in-fluent.svg
new file mode 100644
index 0000000..bc795c7
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/log-in-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/log-in.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/log-in.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/log-in.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/log-in.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/log-out-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/log-out-fluent.svg
new file mode 100644
index 0000000..528d338
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/log-out-fluent.svg
@@ -0,0 +1,4 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/log-out.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/log-out.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/log-out.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/log-out.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-email-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-email-fluent.svg
new file mode 100644
index 0000000..8e25a2c
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-email-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/manage-email.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-email.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/manage-email.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/manage-email.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-password-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-password-fluent.svg
new file mode 100644
index 0000000..57cce63
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-password-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/manage-password.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-password.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/manage-password.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/manage-password.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-personal-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-personal-fluent.svg
new file mode 100644
index 0000000..a8aaddc
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-personal-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/manage-personal.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-personal.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/manage-personal.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/manage-personal.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-profile-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-profile-fluent.svg
new file mode 100644
index 0000000..3da67b0
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-profile-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/manage-profile.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-profile.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/manage-profile.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/manage-profile.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-two-factor-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-two-factor-fluent.svg
new file mode 100644
index 0000000..574f211
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-two-factor-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/manage-two-factor.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/manage-two-factor.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/manage-two-factor.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/manage-two-factor.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/facebook-logo-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/facebook-logo-fluent.svg
new file mode 100644
index 0000000..a7dac6d
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/facebook-logo-fluent.svg
@@ -0,0 +1,4 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/providers/facebook-logo.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/facebook-logo.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/providers/facebook-logo.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/providers/facebook-logo.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/google-logo-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/google-logo-fluent.svg
new file mode 100644
index 0000000..bea8c68
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/google-logo-fluent.svg
@@ -0,0 +1,6 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/providers/google-logo.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/google-logo.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/providers/google-logo.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/providers/google-logo.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/microsoft-logo-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/microsoft-logo-fluent.svg
new file mode 100644
index 0000000..6dde382
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/microsoft-logo-fluent.svg
@@ -0,0 +1,6 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/providers/microsoft-logo.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/microsoft-logo.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/providers/microsoft-logo.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/providers/microsoft-logo.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/x-logo-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/x-logo-fluent.svg
new file mode 100644
index 0000000..6d25991
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/x-logo-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/providers/x-logo.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/providers/x-logo.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/providers/x-logo.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/providers/x-logo.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/settings-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/settings-fluent.svg
new file mode 100644
index 0000000..8456fd6
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/settings-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/settings.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/settings.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/settings.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/settings.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/account/user-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/user-fluent.svg
new file mode 100644
index 0000000..0887fad
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/account/user-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/account/user.svg b/CS/blazor_multi_tab_ui/wwwroot/images/account/user.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/account/user.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/account/user.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/back-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/back-fluent.svg
new file mode 100644
index 0000000..9b596d0
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/back-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/back.svg b/CS/blazor_multi_tab_ui/wwwroot/images/back.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/back.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/back.svg
diff --git a/CS/DxBlazorApplication1/wwwroot/images/cards.svg b/CS/blazor_multi_tab_ui/wwwroot/images/cards.svg
similarity index 98%
rename from CS/DxBlazorApplication1/wwwroot/images/cards.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/cards.svg
index 5ca6db7..cb273c8 100644
--- a/CS/DxBlazorApplication1/wwwroot/images/cards.svg
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/cards.svg
@@ -7,10 +7,10 @@
-
+
-
+
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/close-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/close-fluent.svg
new file mode 100644
index 0000000..38064af
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/close-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/close.svg b/CS/blazor_multi_tab_ui/wwwroot/images/close.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/close.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/close.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/counter-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/counter-fluent.svg
new file mode 100644
index 0000000..8ed0cae
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/counter-fluent.svg
@@ -0,0 +1,4 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/counter.svg b/CS/blazor_multi_tab_ui/wwwroot/images/counter.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/counter.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/counter.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/demos-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/demos-fluent.svg
new file mode 100644
index 0000000..f9f4953
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/demos-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/demos.svg b/CS/blazor_multi_tab_ui/wwwroot/images/demos.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/demos.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/demos.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/doc-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/doc-fluent.svg
new file mode 100644
index 0000000..e746057
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/doc-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/doc.svg b/CS/blazor_multi_tab_ui/wwwroot/images/doc.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/doc.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/doc.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/home-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/home-fluent.svg
new file mode 100644
index 0000000..0919bd5
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/home-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/home.svg b/CS/blazor_multi_tab_ui/wwwroot/images/home.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/home.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/home.svg
diff --git a/CS/DxBlazorApplication1/wwwroot/images/logo.svg b/CS/blazor_multi_tab_ui/wwwroot/images/logo.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/logo.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/logo.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/menu-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/menu-fluent.svg
new file mode 100644
index 0000000..d9cd93e
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/menu-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/menu.svg b/CS/blazor_multi_tab_ui/wwwroot/images/menu.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/menu.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/menu.svg
diff --git a/CS/blazor_multi_tab_ui/wwwroot/images/weather-fluent.svg b/CS/blazor_multi_tab_ui/wwwroot/images/weather-fluent.svg
new file mode 100644
index 0000000..f24c5c3
--- /dev/null
+++ b/CS/blazor_multi_tab_ui/wwwroot/images/weather-fluent.svg
@@ -0,0 +1,3 @@
+
diff --git a/CS/DxBlazorApplication1/wwwroot/images/weather.svg b/CS/blazor_multi_tab_ui/wwwroot/images/weather.svg
similarity index 100%
rename from CS/DxBlazorApplication1/wwwroot/images/weather.svg
rename to CS/blazor_multi_tab_ui/wwwroot/images/weather.svg
diff --git a/README.md b/README.md
index b62c3f0..44e3109 100644
--- a/README.md
+++ b/README.md
@@ -6,81 +6,95 @@
# Blazor Tabs - Create a Dynamic Tabbed Interface
-The example creates an interactive, multi-tab web interface using DevExpress Blazor [Tabs](https://docs.devexpress.com/Blazor/405074/components/layout/tabs) and [Context Menu](https://docs.devexpress.com/Blazor/405060/components/navigation-controls/context-menu) components.
+This example creates an interactive, multi-tab web interface using DevExpress Blazor [Tabs](https://docs.devexpress.com/Blazor/405074/components/layout/tabs) and [Context Menu](https://docs.devexpress.com/Blazor/405060/components/navigation-controls/context-menu) components. It illustrates how end users can create personalized workspaces and multitask effectively.

-It illustrates how end users can create personalized workspaces and multitask effectively.
-
## Implementation Details
-### Organize Content Into Tabs
+### Organize Content into Tabs
-Place [DxTabs](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxTabs) container on the page ([Index.razor](CS/DxBlazorApplication1/Components/Pages/Index.razor)) and add a [DxTabPage](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxTabPage) for each tab.
+The [MdiTabs](CS/blazor_multi_tab_ui/Components/MDI/MdiTabs.razor) custom component is based on the [DxTabs](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxTabs) control.
-Insert your custom Blazor components or content directly into each `DxTabPage`.
+Tabs are rendered by iterating over a persisted state collection (see details below). The content within each tab is loaded dynamically using a [DynamicComponent](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.dynamiccomponent). This approach allows for the flexible rendering of different components within the tabs without the need to hardcode them.
```razor
-
-
-
-
-
-
+ RenderMode="TabsRenderMode.OnDemand">
+ @for (int i = 0; i < tabsCollection.Count; i++)
+ {
+ var tabModel = tabsCollection[i];
+
+
+ @if (stateService.TryGetType(tabModel.TabTypeName, out var type))
+ {
+
+ }
+ else
+ {
+
+ }
+
+
+
+ }
+
```
-The `CssClass` property of a tab page serves as a unique identifier, allowing client-side scripts to interact with specific tabs.
-
### Persist Tab State
-Implement a custom `MDITab` class ([MDITab.cs](CS/DxBlazorApplication1/Components/MDI/MDITab.cs)) to encapsulate properties associated with each individual tab. `MDITabCollection` class ([MDITabCollection.cs](CS/DxBlazorApplication1/Components/MDI/MDITabCollection.cs)) will control visibility, display order, and titles used for all tabs. The title links an underlying object to the visual tab representation in the UI.
+The state of the multi-tab interface is managed by the following classes:
-Bind these properties to the visual tab elements in the UI. To ensure the `MDITabCollection` accurately reflects the live interface, implement event handlers for `TabReorder` and `TabClosing`. These handlers will listen for user actions and dynamically update the collection to match current tab state.
+- `MdiTabModel` ([MdiTabModel.cs](CS/blazor_multi_tab_ui/Models/MdiTabModel.cs)) encapsulates properties associated with each individual tab: unique identifier, visibility, title, and so on.
+- `MdiStateModel` ([MdiStateModel.cs](CS/blazor_multi_tab_ui/Models/MdiStateModel.cs)) contains the list of all tabs (`MdiTabModel`) and the index of the active tab.
+- `MdiStateService` ([MdiStateService.cs](CS/blazor_multi_tab_ui/Services/MdiStateService.cs)) exposes methods for tab state management and maintains tab layout across sessions even after the user closes and reopens the browser. It serializes `MdiStateModel` to JSON and saves it to the browser's local storage every time the UI layout changes. The tab state is restored in the `MdiTabs` component's `OnInitializedAsync` method.
-To maintain tab layout across sessions, serialize the collection to JSON and save it to the browser's local storage with `MDIStateHelper` class ([MDIStateHelper.cs](CS/DxBlazorApplication1/Components/MDI/MDIStateHelper.cs)) every time the UI layout changes. This will maintain tab visibility and order even after the user closes and reopens the browser. Tab state is restored in the `OnAfterRenderAsync` event handler.
+To ensure that the state model (`MdiStateModel`) accurately reflects the live interface, implement event handlers for `TabReorder`, `TabClosing`, and `ActiveTabIndexChanged`. These handlers will listen for user actions and dynamically update the state to match the current tab layout.
### Add Context Menu to Tabs
-Create a context menu that allows users to manage tabs as needed:
+Implement a context menu that allows users to manage tabs as needed:
-- Close the current tab.
-- Close all tabs.
-- Close all tabs except for the current tab.
-- Restore closed tabs.
+- **Close** the current tab.
+- **Close** all tabs.
+- **Close** all tabs except for the current one.
+- **Hide** the current tab.
+- **Hide** all tabs.
+- **Hide** all tabs except for the current one.
+- **Restore** hidden tabs.
-Place [DxContextMenu](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxContextMenu) on the page ([Index.razor](CS/DxBlazorApplication1/Components/Pages/Index.razor)) and add a [DxContextMenuItem](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxContextMenuItem) for each menu action.
+Create a custom [`TabsContextMenu`](CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor) component that contains [DxContextMenu](https://docs.devexpress.com/Blazor/DevExpress.Blazor.DxContextMenu) and all related actions. Place it inside the `MdiTabs` component.
```razor
-
-
-
-
-
-
-
-
+
```
-Implement a client-side script ([mdi.js](CS/DxBlazorApplication1/wwwroot/js/mdi.js)) to handle right-clicks on specific tabs (identified by their `CssClass` property). This script should prevent the default browser context menu. Capture the mouse position, and invoke a .NET `[JSInvokable]` method.
+Use the `TabSelector` parameter to associate the specific tab with the context menu.
+
+Implement a client-side script [`TabsContextMenu.razor.js`](CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor.js) that handles right-clicks on tabs and performs the following actions:
+
+- Finds the matching elements by their `CssClass` property.
+- Suppresses the default browser context menu.
+- Captures the mouse position and invokes a .NET `[JSInvokable]` method that opens the context menu at the pointer's coordinates.
+
+When a menu item is clicked, the handler calls the corresponding method of [`MdiStateService`](CS/blazor_multi_tab_ui/Services/MdiStateService.cs) to update the tab state (close, hide, or restore).
## Files to Review
-- [Index.razor](CS/DxBlazorApplication1/Components/Pages/Index.razor)
-- [NavMenu.razor](CS/DxBlazorApplication1/Components/Layout/NavMenu.razor)
-- [MainLayout.razor](CS/DxBlazorApplication1/Components/Layout/MainLayout.razor.css)
-- [MDITab.cs](CS/DxBlazorApplication1/Components/MDI/MDITab.cs)
-- [MDITabCollection.cs](CS/DxBlazorApplication1/Components/MDI/MDITabCollection.cs)
-- [MDIStateHelper.cs](CS/DxBlazorApplication1/Components/MDI/MDIStateHelper.cs)
-- [mdi.js](CS/DxBlazorApplication1/wwwroot/js/mdi.js)
+- [`MdiTabs.razor`](CS/blazor_multi_tab_ui/Components/MDI/MdiTabs.razor)
+- [`MdiTabModel.cs`](CS/blazor_multi_tab_ui/Models/MdiTabModel.cs)
+- [`MdiStateModel.cs`](CS/blazor_multi_tab_ui/Models/MdiStateModel.cs)
+- [`MdiStateService.cs`](CS/blazor_multi_tab_ui/Services/MdiStateService.cs)
+- [`TabsContextMenu.razor`](CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor)
+- [`TabsContextMenu.razor.js`](CS/blazor_multi_tab_ui/Components/MDI/TabsContextMenu.razor.js)
## Documentation
diff --git a/images/blazor-tabbed-ui.png b/images/blazor-tabbed-ui.png
index 3e1a23f..28d0128 100644
Binary files a/images/blazor-tabbed-ui.png and b/images/blazor-tabbed-ui.png differ