diff --git a/.github/workflows/admin-sample.cd.yml b/.github/workflows/admin-sample.cd.yml
index 0e20c83b5a..20c90a0b1e 100644
--- a/.github/workflows/admin-sample.cd.yml
+++ b/.github/workflows/admin-sample.cd.yml
@@ -69,6 +69,56 @@ jobs:
path: ${{env.DOTNET_ROOT}}/server
include-hidden-files: true # Required for wwwroot/.well-known folder
+ deploy_blazor_wasm_standalone:
+ name: build blazor wasm standalone
+ runs-on: ubuntu-24.04
+
+ steps:
+
+ - name: Checkout source code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: src/global.json
+
+ - name: Create project from Boilerplate
+ run: |
+ cd src/Templates/Boilerplate && dotnet build -c Release
+ dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0
+ dotnet new install Bit.Boilerplate.0.0.0.nupkg
+ cd ../../../ && dotnet new bit-bp --name AdminPanel --database PostgreSQL --sample Admin --appInsights --sentry --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --signalR --framework net9.0
+
+ - name: Update core appsettings.json
+ uses: devops-actions/variable-substitution@v1.2
+ with:
+ files: 'AdminPanel/src/Shared/appsettings.json, AdminPanel/src/Client/AdminPanel.Client.Core/appsettings.json, AdminPanel/src/Client/AdminPanel.Client.Web/appsettings.json, AdminPanel/src/Client/AdminPanel.Client.Web/appsettings.Production.json'
+ env:
+ ServerAddress: ${{ env.SERVER_ADDRESS }}
+ Logging.Sentry.Dsn: ${{ secrets.ADMINPANEL_SENTRY_DSN }}
+ GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }}
+ AdsPushVapid.PublicKey: ${{ secrets.ADMINPANEL_PUBLIC_VAPIDKEY }}
+ ApplicationInsights.ConnectionString: ${{ secrets.APPLICATION_INSIGHTS_CONNECTION_STRING }}
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install wasm
+ run: cd src && dotnet workload install wasm-tools
+
+ - name: Generate CSS/JS files
+ run: dotnet build AdminPanel/src/Client/AdminPanel.Client.Core/AdminPanel.Client.Core.csproj -t:BeforeBuildTasks -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}" --no-restore -c Release
+
+ - name: Publish
+ run: dotnet publish AdminPanel/src/Client/AdminPanel.Client.Web/AdminPanel.Client.Web.csproj -c Release -p:PwaEnabled=true -o ${{env.DOTNET_ROOT}}/client -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}"
+
+ - name: Upload to asw
+ run: |
+ npm install -g @azure/static-web-apps-cli
+ swa deploy --deployment-token ${{ secrets.ADMINPANEL_ASW_TOKEN }} --env production --app-location ${{env.DOTNET_ROOT}}/client/wwwroot
+
deploy_api_blazor:
name: deploy api + blazor
needs: build_api_blazor
diff --git a/.github/workflows/bit.full.ci.yml b/.github/workflows/bit.full.ci.yml
index c08e74098e..003f326ba4 100644
--- a/.github/workflows/bit.full.ci.yml
+++ b/.github/workflows/bit.full.ci.yml
@@ -58,6 +58,7 @@ jobs:
- name: Simple tests (no --advancedTests)
id: simple-test
+ continue-on-error: true
run: |
dotnet new bit-bp --name SimpleTest --database Sqlite --framework net8.0
cd SimpleTest/src/Server/SimpleTest.Server.Api/
@@ -79,6 +80,7 @@ jobs:
- name: Test Sqlite database option
id: sqlite-test
+ continue-on-error: true
run: |
dotnet new bit-bp --name TestSqlite --database Sqlite --advancedTests --framework net9.0
cd TestSqlite/src/Server/TestSqlite.Server.Api/
@@ -101,6 +103,7 @@ jobs:
- name: Test SqlServer database option
id: sqlserver-test
+ continue-on-error: true
run: |
dotnet new bit-bp --name TestSqlServer --database SqlServer --advancedTests --framework net8.0
cd TestSqlServer/src/Server/TestSqlServer.Server.Api/
@@ -121,6 +124,7 @@ jobs:
- name: Test Multilingual disabled option
id: multilingual-disabled-test
+ continue-on-error: true
run: |
dotnet new bit-bp --name MultilingualDisabled --database Sqlite --advancedTests --framework net8.0
cd MultilingualDisabled/src/Server/MultilingualDisabled.Server.Api/
@@ -139,6 +143,7 @@ jobs:
retention-days: 14
- name: Test PostgreSQL, MySql, Other database options
+ continue-on-error: true
run: |
dotnet new bit-bp --name TestPostgreSQL --database PostgreSQL --framework net8.0 --signalR
cd TestPostgreSQL/src/Server/TestPostgreSQL.Server.Api/
@@ -153,6 +158,7 @@ jobs:
dotnet build
- name: Test file storage options
+ continue-on-error: true
run: |
dotnet new bit-bp --name TestLocal --filesStorage Local --framework net8.0 --appInsights
cd TestLocal/src/Server/TestLocal.Server.Api/
@@ -163,6 +169,7 @@ jobs:
dotnet build
- name: Test backend setup options
+ continue-on-error: true
run: |
dotnet new bit-bp --name TestStandalone --api Standalone --framework net8.0
cd TestStandalone/src/Server/TestStandalone.Server.Api/
@@ -176,11 +183,13 @@ jobs:
dotnet build
- name: Test sample configuration 1
+ continue-on-error: true
run: |
dotnet new bit-bp --name TestProject --database SqlServer --filesStorage AzureBlobStorage --api Integrated --captcha reCaptcha --pipeline Azure --sample Admin --offlineDb --windows --appInsights --sentry --signalR --notification --framework net9.0
dotnet build TestProject/TestProject.sln -p:MultilingualEnabled=true -p:PwaEnabled=true -p:Environment=Staging
- name: Test sample configuration 2
+ continue-on-error: true
run: |
dotnet new bit-bp --name TestProject2 --database Other --filesStorage Other --api Standalone --captcha None --pipeline None --sample None --offlineDb false --windows false --appInsights false --sentry false --signalR false --notification false --framework net8.0
dotnet build TestProject2/TestProject2.sln -p:MultilingualEnabled=false -p:PwaEnabled=false -p:Environment=Development
diff --git a/.github/workflows/todo-sample.cd.yml b/.github/workflows/todo-sample.cd.yml
index 387e890cd5..b668a00726 100644
--- a/.github/workflows/todo-sample.cd.yml
+++ b/.github/workflows/todo-sample.cd.yml
@@ -121,6 +121,53 @@ jobs:
CLOUDFLARE_ZONE: ${{ secrets.BITPLATFORM_DEV_CLOUDFLARE_ZONE }}
CLOUDFLARE_TOKEN: ${{ secrets.CLOUDFLARE_TOKEN }}
+ deploy_blazor_wasm_standalone:
+ name: build blazor wasm standalone
+ runs-on: ubuntu-24.04
+
+ steps:
+
+ - name: Checkout source code
+ uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ global-json-file: src/global.json
+
+ - name: Create project from Boilerplate
+ run: |
+ cd src/Templates/Boilerplate && dotnet build -c Release
+ dotnet pack -c Release -o . -p:ReleaseVersion=0.0.0 -p:PackageVersion=0.0.0
+ dotnet new install Bit.Boilerplate.0.0.0.nupkg
+ cd ../../../ && dotnet new bit-bp --name TodoSample --database PostgreSQL --sample Todo --serverUrl ${{ env.SERVER_ADDRESS }} --filesStorage AzureBlobStorage --notification --captcha reCaptcha --framework net9.0
+
+ - name: Update core appsettings.json
+ uses: devops-actions/variable-substitution@v1.2
+ with:
+ files: 'TodoSample/src/Shared/appsettings.json, TodoSample/src/Client/TodoSample.Client.Core/appsettings.json, TodoSample/src/Client/TodoSample.Client.Web/appsettings.json, TodoSample/src/Client/TodoSample.Client.Web/appsettings.Production.json'
+ env:
+ ServerAddress: ${{ env.SERVER_ADDRESS }}
+ GoogleRecaptchaSiteKey: ${{ secrets.GOOGLE_RECAPTCHA_SITE_KEY }}
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 22
+
+ - name: Install wasm
+ run: cd src && dotnet workload install wasm-tools
+
+ - name: Generate CSS/JS files
+ run: dotnet build TodoSample/src/Client/TodoSample.Client.Core/TodoSample.Client.Core.csproj -t:BeforeBuildTasks -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}" --no-restore -c Release
+
+ - name: Publish
+ run: dotnet publish TodoSample/src/Client/TodoSample.Client.Web/TodoSample.Client.Web.csproj -c Release -p:PwaEnabled=true -o ${{env.DOTNET_ROOT}}/client -p:Version="${{ vars.APPLICATION_DISPLAY_VERSION}}" -p:RunAOTCompilation=true -p:MultilingualEnabled=false
+
+ - name: Upload to asw
+ run: |
+ npm install -g @azure/static-web-apps-cli
+ swa deploy --deployment-token ${{ secrets.TODO_ASW_TOKEN }} --env production --app-location ${{env.DOTNET_ROOT}}/client/wwwroot
+
build_blazor_hybrid_windows:
name: build blazor hybrid (windows)
runs-on: windows-2022
diff --git a/README.md b/README.md
index d189ce9e8f..e6111238e8 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
![License](https://img.shields.io/github/license/bitfoundation/bitplatform.svg)
![CI Status](https://github.com/bitfoundation/bitplatform/actions/workflows/bit.ci.yml/badge.svg)
![NuGet version](https://img.shields.io/nuget/v/bit.blazorui.svg?logo=nuget)
-[![Nuget downloads](https://img.shields.io/badge/packages_download-5.5M-blue.svg?logo=nuget)](https://www.nuget.org/profiles/bit-foundation)
+[![Nuget downloads](https://img.shields.io/badge/packages_download-5.6M-blue.svg?logo=nuget)](https://www.nuget.org/profiles/bit-foundation)
[![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/bitfoundation/bitplatform.svg)](http://isitmaintained.com/project/bitfoundation/bitplatform "Average time to resolve an issue")
[![Percentage of issues still open](http://isitmaintained.com/badge/open/bitfoundation/bitplatform.svg)](http://isitmaintained.com/project/bitfoundation/bitplatform "Percentage of issues still open")
@@ -47,7 +47,9 @@ The following apps are our open-source projects powered by the bit platform show
1. [bitplatform.dev](https://bitplatform.dev): .NET 9 Pre-rendered SPA with Blazor WebAssembly
2. [blazorui.bitplatform.dev](https://blazorui.bitplatform.dev): .NET 9 Pre-rendered PWA with Blazor WebAssembly
3. [todo.bitplatform.dev](https://todo.bitplatform.dev): .NET 8 Pre-rendered PWA with Blazor WebAssembly
-5. [adminpanel.bitplatform.dev](https://adminpanel.bitplatform.dev): .NET 9 PWA with Blazor WebAssembly Standalone (Hosted on Cloudflare Pages)
+5. [adminpanel.bitplatform.dev](https://adminpanel.bitplatform.dev): .NET 9 PWA with Blazor WebAssembly
+6. [adminpanel.bitplatform.cc](https://adminpanel.bitplatform.cc): .NET 9 PWA with Blazor WebAssembly Standalone (Free Azure static web app)
+7. [todo.bitplatform.cc](https://todo.bitplatform.cc): AOT Compiled .NET 9 PWA with Blazor WebAssembly Standalone (Free Azure static web app)
[Todo](https://todo.bitplatform.dev) & [Adminpanel](https://adminpanel.bitplatform.dev) web apps will launch their respective Android and iOS applications if you have already installed them, mirroring the behavior of apps like YouTube and Instagram.
diff --git a/src/Besql/Bit.Besql/wwwroot/bit-besql.js b/src/Besql/Bit.Besql/wwwroot/bit-besql.js
index aa548d55e5..a7229e8651 100644
--- a/src/Besql/Bit.Besql/wwwroot/bit-besql.js
+++ b/src/Besql/Bit.Besql/wwwroot/bit-besql.js
@@ -1,5 +1,5 @@
var BitBesql = window.BitBesql || {};
-BitBesql.version = window['bit-besql version'] = '9.1.1';
+BitBesql.version = window['bit-besql version'] = '9.1.2';
BitBesql.init = async function init(fileName) {
const sqliteFilePath = `/${fileName}`;
diff --git a/src/Bit.Build.props b/src/Bit.Build.props
index c7c8af3021..29ac825be1 100644
--- a/src/Bit.Build.props
+++ b/src/Bit.Build.props
@@ -27,7 +27,7 @@
https://github.com/bitfoundation/bitplatform
- 9.1.1
+ 9.1.2
$(ReleaseVersion)
https://github.com/bitfoundation/bitplatform/releases/tag/v-$(ReleaseVersion)
$([System.String]::Copy($(ReleaseVersion)).Replace('-pre-', '.'))
diff --git a/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj b/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj
index a01542257f..cb16dd5cbb 100644
--- a/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj
+++ b/src/BlazorUI/Bit.BlazorUI.Tests/Bit.BlazorUI.Tests.csproj
@@ -13,7 +13,7 @@
-
+
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitActionButton/BitActionButton.razor b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitActionButton/BitActionButton.razor
index ff0059f783..30002ad6bf 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitActionButton/BitActionButton.razor
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitActionButton/BitActionButton.razor
@@ -31,6 +31,7 @@ else
{
- [Parameter] public string? Href { get; set; }
+ [Parameter]
+ [CallOnSet(nameof(OnSetHrefAndRel))]
+ public string? Href { get; set; }
///
/// The icon name of the icon to render inside the button.
@@ -90,6 +93,13 @@ public partial class BitActionButton : BitComponentBase
[Parameter, ResetClassBuilder]
public bool ReversedIcon { get; set; }
+ ///
+ /// If Href provided, specifies the relationship between the current document and the linked document.
+ ///
+ [Parameter]
+ [CallOnSet(nameof(OnSetHrefAndRel))]
+ public BitLinkRel? Rel { get; set; }
+
///
/// The size of the button.
///
@@ -172,4 +182,15 @@ protected virtual async Task HandleOnClick(MouseEventArgs e)
await OnClick.InvokeAsync(e);
}
}
+
+ private void OnSetHrefAndRel()
+ {
+ if (Rel.HasValue is false || Href.HasNoValue() || Href!.StartsWith('#'))
+ {
+ _rel = null;
+ return;
+ }
+
+ _rel = BitLinkRelUtils.GetRels(Rel.Value);
+ }
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor
index 036addc7c6..34a0df0ad6 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor
@@ -21,8 +21,9 @@
tabindex="@(isEnabled ? 0 : -1)"
disabled="@(isEnabled is false)"
aria-disabled="@(isEnabled is false)"
+ title="@GetItemTitle(item)"
style="@GetStyle(item)"
- class="bit-btg-itm @GetClass(item)">
+ class="@GetItemClass(item)">
@if (template is not null)
{
@template(item)
@@ -33,12 +34,17 @@
}
else
{
- var iconName = GetIconName(item);
+ var iconName = GetItemIconName(item);
@if (iconName.HasValue())
{
}
- @GetText(item)
+
+ var text = GetItemText(item);
+ if (text.HasValue())
+ {
+ @text
+ }
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs
index 9c3229eb85..73b9ff5894 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.razor.cs
@@ -4,11 +4,11 @@ namespace Bit.BlazorUI;
public partial class BitButtonGroup : BitComponentBase where TItem : class
{
+ private TItem? _toggleItem;
private List _items = [];
private IEnumerable _oldItems = default!;
-
///
/// The EditContext, which is set if the button is inside an
///
@@ -27,6 +27,11 @@ public partial class BitButtonGroup : BitComponentBase where TItem : clas
[Parameter, ResetClassBuilder]
public BitColor? Color { get; set; }
+ ///
+ /// Determines that only the icon should be rendered.
+ ///
+ [Parameter] public bool IconOnly { get; set; }
+
///
/// List of Item, each of which can be a button with different action in the ButtonGroup.
///
@@ -58,6 +63,11 @@ public partial class BitButtonGroup : BitComponentBase where TItem : clas
[Parameter, ResetClassBuilder]
public BitSize? Size { get; set; }
+ ///
+ /// Display ButtonGroup with toggle mode enabled for each button.
+ ///
+ [Parameter] public bool Toggled { get; set; }
+
///
/// The visual variant of the button group.
///
@@ -143,7 +153,7 @@ protected override void OnParametersSet()
if (_oldItems is not null && Items.SequenceEqual(_oldItems)) return;
_oldItems = Items;
- _items = Items.ToList();
+ _items = [.. Items];
}
@@ -175,6 +185,118 @@ private async Task HandleOnItemClick(TItem item)
item.GetValueFromProperty?>(NameSelectors.OnClick.Name)?.Invoke(item);
}
}
+
+ if (Toggled)
+ {
+ if (_toggleItem == item)
+ {
+ _toggleItem = null;
+ }
+ else
+ {
+ _toggleItem = item;
+ }
+ }
+ }
+
+ private string? GetItemClass(TItem? item)
+ {
+ List classes = ["bit-btg-itm"];
+
+ if (GetReversedIcon(item))
+ {
+ classes.Add("bit-btg-rvi");
+ }
+
+ if (_toggleItem == item)
+ {
+ classes.Add("bit-btg-chk");
+ }
+
+ var classItem = GetClass(item);
+ if (classItem.HasValue())
+ {
+ classes.Add(classItem!);
+ }
+
+ return string.Join(' ', classes);
+ }
+
+ private string? GetItemText(TItem? item)
+ {
+ if (IconOnly) return null;
+
+ if (Toggled)
+ {
+ if (_toggleItem == item)
+ {
+ var onText = GetOnText(item);
+ if (onText.HasValue())
+ {
+ return onText;
+ }
+ }
+ else
+ {
+ var offText = GetOffText(item);
+ if (offText.HasValue())
+ {
+ return offText;
+ }
+ }
+ }
+
+ return GetText(item);
+ }
+
+ private string? GetItemTitle(TItem? item)
+ {
+ if (Toggled)
+ {
+ if (_toggleItem == item)
+ {
+ var onTitle = GetOnTitle(item);
+ if (onTitle.HasValue())
+ {
+ return onTitle;
+ }
+ }
+ else
+ {
+ var offTitle = GetOffTitle(item);
+ if (offTitle.HasValue())
+ {
+ return offTitle;
+ }
+ }
+ }
+
+ return GetTitle(item);
+ }
+
+ private string? GetItemIconName(TItem? item)
+ {
+ if (Toggled)
+ {
+ if (_toggleItem == item)
+ {
+ var onIconName = GetOnIconName(item);
+ if (onIconName.HasValue())
+ {
+ return onIconName;
+ }
+ }
+ else
+ {
+ var offIconName = GetOffIconName(item);
+ if (offIconName.HasValue())
+ {
+ return offIconName;
+ }
+ }
+ }
+
+ return GetIconName(item);
}
private string? GetClass(TItem? item)
@@ -225,6 +347,54 @@ private async Task HandleOnItemClick(TItem item)
return item.GetValueFromProperty(NameSelectors.IconName.Name);
}
+ private string? GetOnIconName(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.OnIconName;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.OnIconName;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.OnIconName.Selector is not null)
+ {
+ return NameSelectors.OnIconName.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.OnIconName.Name);
+ }
+
+ private string? GetOffIconName(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.OffIconName;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.OffIconName;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.OffIconName.Selector is not null)
+ {
+ return NameSelectors.OffIconName.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.OffIconName.Name);
+ }
+
private bool GetIsEnabled(TItem? item)
{
if (item is null) return false;
@@ -320,4 +490,148 @@ private bool GetIsEnabled(TItem? item)
return item.GetValueFromProperty(NameSelectors.Text.Name);
}
+
+ private string? GetOnText(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.OnText;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.OnText;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.OnText.Selector is not null)
+ {
+ return NameSelectors.OnText.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.OnText.Name);
+ }
+
+ private string? GetOffText(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.OffText;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.OffText;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.OffText.Selector is not null)
+ {
+ return NameSelectors.OffText.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.OffText.Name);
+ }
+
+ private string? GetTitle(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.Title;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.Title;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.Title.Selector is not null)
+ {
+ return NameSelectors.Title.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.Title.Name);
+ }
+
+ private string? GetOnTitle(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.OnTitle;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.OnTitle;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.OnTitle.Selector is not null)
+ {
+ return NameSelectors.OnTitle.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.OnTitle.Name);
+ }
+
+ private string? GetOffTitle(TItem? item)
+ {
+ if (item is null) return null;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.OffTitle;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.OffTitle;
+ }
+
+ if (NameSelectors is null) return null;
+
+ if (NameSelectors.OffTitle.Selector is not null)
+ {
+ return NameSelectors.OffTitle.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.OffTitle.Name);
+ }
+
+ private bool GetReversedIcon(TItem? item)
+ {
+ if (item is null) return false;
+
+ if (item is BitButtonGroupItem buttonGroupItem)
+ {
+ return buttonGroupItem.ReversedIcon;
+ }
+
+ if (item is BitButtonGroupOption buttonGroupOption)
+ {
+ return buttonGroupOption.ReversedIcon;
+ }
+
+ if (NameSelectors is null) return false;
+
+ if (NameSelectors.ReversedIcon.Selector is not null)
+ {
+ return NameSelectors.ReversedIcon.Selector!(item);
+ }
+
+ return item.GetValueFromProperty(NameSelectors.ReversedIcon.Name, false);
+ }
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss
index a630e467f4..9a55b86981 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroup.scss
@@ -34,6 +34,7 @@
line-height: inherit;
text-decoration: none;
box-sizing: border-box;
+ justify-content: center;
font-family: $tg-font-family;
font-weight: $tg-font-weight;
border-style: $shp-border-style;
@@ -69,6 +70,10 @@
}
}
+.bit-btg-rvi {
+ flex-direction: row-reverse;
+}
+
.bit-btg-btx {
white-space: nowrap;
text-overflow: ellipsis;
@@ -131,12 +136,33 @@
}
}
+.bit-btg-chk {
+ color: var(--bit-btg-clr-txt);
+ border-color: var(--bit-btg-clr-dark);
+ background-color: var(--bit-btg-clr-dark);
+
+ @media (hover: hover) {
+ &:hover {
+ border-color: var(--bit-btg-clr-dark-hover);
+ background-color: var(--bit-btg-clr-dark-hover);
+ }
+ }
+
+ &:active {
+ border-color: var(--bit-btg-clr-dark-active);
+ background-color: var(--bit-btg-clr-dark-active);
+ }
+}
+
.bit-btg-pri {
--bit-btg-clr: #{$clr-pri};
--bit-btg-clr-txt: #{$clr-pri-text};
--bit-btg-clr-brd: #{$clr-pri-dark};
--bit-btg-clr-hover: #{$clr-pri-hover};
--bit-btg-clr-active: #{$clr-pri-active};
+ --bit-btg-clr-dark: #{$clr-pri-dark};
+ --bit-btg-clr-dark-hover: #{$clr-pri-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-pri-dark-active};
}
.bit-btg-sec {
@@ -145,6 +171,9 @@
--bit-btg-clr-brd: #{$clr-sec-dark};
--bit-btg-clr-hover: #{$clr-sec-hover};
--bit-btg-clr-active: #{$clr-sec-active};
+ --bit-btg-clr-dark: #{$clr-sec-dark};
+ --bit-btg-clr-dark-hover: #{$clr-sec-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-sec-dark-active};
}
.bit-btg-ter {
@@ -153,6 +182,9 @@
--bit-btg-clr-brd: #{$clr-ter-dark};
--bit-btg-clr-hover: #{$clr-ter-hover};
--bit-btg-clr-active: #{$clr-ter-active};
+ --bit-btg-clr-dark: #{$clr-ter-dark};
+ --bit-btg-clr-dark-hover: #{$clr-ter-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-ter-dark-active};
}
.bit-btg-inf {
@@ -161,6 +193,9 @@
--bit-btg-clr-brd: #{$clr-inf-dark};
--bit-btg-clr-hover: #{$clr-inf-hover};
--bit-btg-clr-active: #{$clr-inf-active};
+ --bit-btg-clr-dark: #{$clr-inf-dark};
+ --bit-btg-clr-dark-hover: #{$clr-inf-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-inf-dark-active};
}
.bit-btg-suc {
@@ -169,6 +204,9 @@
--bit-btg-clr-brd: #{$clr-suc-dark};
--bit-btg-clr-hover: #{$clr-suc-hover};
--bit-btg-clr-active: #{$clr-suc-active};
+ --bit-btg-clr-dark: #{$clr-suc-dark};
+ --bit-btg-clr-dark-hover: #{$clr-suc-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-suc-dark-active};
}
.bit-btg-wrn {
@@ -177,6 +215,9 @@
--bit-btg-clr-brd: #{$clr-wrn-dark};
--bit-btg-clr-hover: #{$clr-wrn-hover};
--bit-btg-clr-active: #{$clr-wrn-active};
+ --bit-btg-clr-dark: #{$clr-wrn-dark};
+ --bit-btg-clr-dark-hover: #{$clr-wrn-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-wrn-dark-active};
}
.bit-btg-swr {
@@ -185,6 +226,9 @@
--bit-btg-clr-brd: #{$clr-swr-dark};
--bit-btg-clr-hover: #{$clr-swr-hover};
--bit-btg-clr-active: #{$clr-swr-active};
+ --bit-btg-clr-dark: #{$clr-swr-dark};
+ --bit-btg-clr-dark-hover: #{$clr-swr-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-swr-dark-active};
}
.bit-btg-err {
@@ -193,6 +237,9 @@
--bit-btg-clr-brd: #{$clr-err-dark};
--bit-btg-clr-hover: #{$clr-err-hover};
--bit-btg-clr-active: #{$clr-err-active};
+ --bit-btg-clr-dark: #{$clr-err-dark};
+ --bit-btg-clr-dark-hover: #{$clr-err-dark-hover};
+ --bit-btg-clr-dark-active: #{$clr-err-dark-active};
}
@@ -202,6 +249,9 @@
--bit-btg-clr-brd: #{$clr-bg-pri};
--bit-btg-clr-hover: #{$clr-bg-pri-hover};
--bit-btg-clr-active: #{$clr-bg-pri-active};
+ --bit-btg-clr-dark: #{$clr-bg-pri};
+ --bit-btg-clr-dark-hover: #{$clr-bg-pri-hover};
+ --bit-btg-clr-dark-active: #{$clr-bg-pri-active};
}
.bit-btg-sbg {
@@ -210,6 +260,9 @@
--bit-btg-clr-brd: #{$clr-bg-sec};
--bit-btg-clr-hover: #{$clr-bg-sec-hover};
--bit-btg-clr-active: #{$clr-bg-sec-active};
+ --bit-btg-clr-dark: #{$clr-bg-sec};
+ --bit-btg-clr-dark-hover: #{$clr-bg-sec-hover};
+ --bit-btg-clr-dark-active: #{$clr-bg-sec-active};
}
.bit-btg-tbg {
@@ -218,6 +271,9 @@
--bit-btg-clr-brd: #{$clr-bg-ter};
--bit-btg-clr-hover: #{$clr-bg-ter-hover};
--bit-btg-clr-active: #{$clr-bg-ter-active};
+ --bit-btg-clr-dark: #{$clr-bg-ter};
+ --bit-btg-clr-dark-hover: #{$clr-bg-ter-hover};
+ --bit-btg-clr-dark-active: #{$clr-bg-ter-active};
}
.bit-btg-pfg {
@@ -226,6 +282,9 @@
--bit-btg-clr-brd: #{$clr-fg-pri};
--bit-btg-clr-hover: #{$clr-fg-pri-hover};
--bit-btg-clr-active: #{$clr-fg-pri-active};
+ --bit-btg-clr-dark: #{$clr-fg-pri};
+ --bit-btg-clr-dark-hover: #{$clr-fg-pri-hover};
+ --bit-btg-clr-dark-active: #{$clr-fg-pri-active};
}
.bit-btg-sfg {
@@ -234,6 +293,9 @@
--bit-btg-clr-brd: #{$clr-fg-sec};
--bit-btg-clr-hover: #{$clr-fg-sec-hover};
--bit-btg-clr-active: #{$clr-fg-sec-active};
+ --bit-btg-clr-dark: #{$clr-fg-sec};
+ --bit-btg-clr-dark-hover: #{$clr-fg-sec-hover};
+ --bit-btg-clr-dark-active: #{$clr-fg-sec-active};
}
.bit-btg-tfg {
@@ -242,6 +304,9 @@
--bit-btg-clr-brd: #{$clr-fg-ter};
--bit-btg-clr-hover: #{$clr-fg-ter-hover};
--bit-btg-clr-active: #{$clr-fg-ter-active};
+ --bit-btg-clr-dark: #{$clr-fg-ter};
+ --bit-btg-clr-dark-hover: #{$clr-fg-ter-hover};
+ --bit-btg-clr-dark-active: #{$clr-fg-ter-active};
}
.bit-btg-pbr {
@@ -250,6 +315,9 @@
--bit-btg-clr-brd: #{$clr-brd-pri};
--bit-btg-clr-hover: #{$clr-brd-pri-hover};
--bit-btg-clr-active: #{$clr-brd-pri-active};
+ --bit-btg-clr-dark: #{$clr-brd-pri};
+ --bit-btg-clr-dark-hover: #{$clr-brd-pri-hover};
+ --bit-btg-clr-dark-active: #{$clr-brd-pri-active};
}
.bit-btg-sbr {
@@ -258,6 +326,9 @@
--bit-btg-clr-brd: #{$clr-brd-sec};
--bit-btg-clr-hover: #{$clr-brd-sec-hover};
--bit-btg-clr-active: #{$clr-brd-sec-active};
+ --bit-btg-clr-dark: #{$clr-brd-sec};
+ --bit-btg-clr-dark-hover: #{$clr-brd-sec-hover};
+ --bit-btg-clr-dark-active: #{$clr-brd-sec-active};
}
.bit-btg-tbr {
@@ -266,6 +337,9 @@
--bit-btg-clr-brd: #{$clr-brd-ter};
--bit-btg-clr-hover: #{$clr-brd-ter-hover};
--bit-btg-clr-active: #{$clr-brd-ter-active};
+ --bit-btg-clr-dark: #{$clr-brd-ter};
+ --bit-btg-clr-dark-hover: #{$clr-brd-ter-hover};
+ --bit-btg-clr-dark-active: #{$clr-brd-ter-active};
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupItem.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupItem.cs
index afa3ee18bd..0311d50980 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupItem.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupItem.cs
@@ -23,11 +23,46 @@ public class BitButtonGroupItem
///
public string? Key { get; set; }
+ ///
+ /// The icon of the item when it is not checked in toggle mode.
+ ///
+ [Parameter] public string? OffIconName { get; set; }
+
+ ///
+ /// The text of the item when it is not checked in toggle mode.
+ ///
+ [Parameter] public string? OffText { get; set; }
+
+ ///
+ /// The title of the item when it is not checked in toggle mode.
+ ///
+ [Parameter] public string? OffTitle { get; set; }
+
+ ///
+ /// The icon of the item when it is checked in toggle mode.
+ ///
+ [Parameter] public string? OnIconName { get; set; }
+
+ ///
+ /// The text of the item when it is checked in toggle mode.
+ ///
+ [Parameter] public string? OnText { get; set; }
+
+ ///
+ /// The title of the item when it is checked in toggle mode.
+ ///
+ [Parameter] public string? OnTitle { get; set; }
+
///
/// Click event handler of the item.
///
public Action? OnClick { get; set; }
+ ///
+ /// Reverses the positions of the icon and the main content of the item.
+ ///
+ [Parameter] public bool ReversedIcon { get; set; }
+
///
/// The custom value for the style attribute of the item.
///
@@ -42,4 +77,9 @@ public class BitButtonGroupItem
/// Text to render in the item.
///
public string? Text { get; set; }
+
+ ///
+ /// Title to render in the item.
+ ///
+ public string? Title { get; set; }
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupNameSelectors.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupNameSelectors.cs
index fe1caf82d6..75cd27bc93 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupNameSelectors.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupNameSelectors.cs
@@ -22,11 +22,46 @@ public class BitButtonGroupNameSelectors
///
public BitNameSelectorPair Key { get; set; } = new(nameof(BitButtonGroupItem.Key));
+ ///
+ /// OffIconName field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair OffIconName { get; set; } = new(nameof(BitButtonGroupItem.OffIconName));
+
+ ///
+ /// OffText field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair OffText { get; set; } = new(nameof(BitButtonGroupItem.OffText));
+
+ ///
+ /// OffTitle field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair OffTitle { get; set; } = new(nameof(BitButtonGroupItem.OffTitle));
+
+ ///
+ /// OnIconName field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair OnIconName { get; set; } = new(nameof(BitButtonGroupItem.OnIconName));
+
+ ///
+ /// OnText field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair OnText { get; set; } = new(nameof(BitButtonGroupItem.OnText));
+
+ ///
+ /// OnTitle field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair OnTitle { get; set; } = new(nameof(BitButtonGroupItem.OnTitle));
+
///
/// OnClick field name and selector of the custom input class.
///
public BitNameSelectorPair?> OnClick { get; set; } = new(nameof(BitButtonGroupItem.OnClick));
+ ///
+ /// ReversedIcon field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair ReversedIcon { get; set; } = new(nameof(BitButtonGroupItem.ReversedIcon));
+
///
/// The CSS Style field name and selector of the custom input class.
///
@@ -41,4 +76,9 @@ public class BitButtonGroupNameSelectors
/// Text field name and selector of the custom input class.
///
public BitNameSelectorPair Text { get; set; } = new(nameof(BitButtonGroupItem.Text));
+
+ ///
+ /// Title field name and selector of the custom input class.
+ ///
+ public BitNameSelectorPair Title { get; set; } = new(nameof(BitButtonGroupItem.Title));
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupOption.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupOption.cs
index ce48fed62f..2380942365 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupOption.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/BitButtonGroup/BitButtonGroupOption.cs
@@ -28,11 +28,46 @@ public partial class BitButtonGroupOption : ComponentBase, IDisposable
///
[Parameter] public string? Key { get; set; }
+ ///
+ /// The icon of the option when it is not checked in toggle mode.
+ ///
+ [Parameter] public string? OffIconName { get; set; }
+
+ ///
+ /// The text of the option when it is not checked in toggle mode.
+ ///
+ [Parameter] public string? OffText { get; set; }
+
+ ///
+ /// The title of the option when it is not checked in toggle mode.
+ ///
+ [Parameter] public string? OffTitle { get; set; }
+
+ ///
+ /// The icon of the option when it is checked in toggle mode.
+ ///
+ [Parameter] public string? OnIconName { get; set; }
+
+ ///
+ /// The text of the option when it is checked in toggle mode.
+ ///
+ [Parameter] public string? OnText { get; set; }
+
+ ///
+ /// The title of the option when it is checked in toggle mode.
+ ///
+ [Parameter] public string? OnTitle { get; set; }
+
///
/// Click event handler of the option.
///
[Parameter] public EventCallback OnClick { get; set; }
+ ///
+ /// Reverses the positions of the icon and the main content of the option.
+ ///
+ [Parameter] public bool ReversedIcon { get; set; }
+
///
/// The custom value for the style attribute of the option.
///
@@ -48,6 +83,11 @@ public partial class BitButtonGroupOption : ComponentBase, IDisposable
///
[Parameter] public string? Text { get; set; }
+ ///
+ /// Title to render in the option
+ ///
+ [Parameter] public string? Title { get; set; }
+
protected override async Task OnInitializedAsync()
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor
index a8fed5cdd7..4c2cf940fd 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor
@@ -118,7 +118,7 @@
{
<_BitNavItemContainer Href="@href"
Target="@target"
- OnClick="HandleOnClick"
+ OnClick="() => HandleOnClick(renderLink)"
RenderLink="renderLink"
Disabled="@(isEnabled is false)"
AriaLabel="@Nav.GetAriaLabel(Item)"
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor.cs
index dbe148bf7d..cd2271d7ab 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Navs/Nav/_BitNavChild.razor.cs
@@ -18,7 +18,7 @@ public partial class _BitNavChild where TItem : class
- private async Task HandleOnClick()
+ private async Task HandleOnClick(bool renderLink)
{
if (Nav is null) return;
if (Nav.GetIsEnabled(Item) is false) return;
@@ -34,6 +34,10 @@ private async Task HandleOnClick()
if (Nav.SelectedItem != Item || Nav.Reselectable)
{
+ if (renderLink)
+ {
+ await Task.Yield(); // wait for the link to navigate first
+ }
await Nav.OnItemClick.InvokeAsync(Item);
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Scripts/general.ts b/src/BlazorUI/Bit.BlazorUI/Scripts/general.ts
index 7f7c98e6b8..439d021720 100644
--- a/src/BlazorUI/Bit.BlazorUI/Scripts/general.ts
+++ b/src/BlazorUI/Bit.BlazorUI/Scripts/general.ts
@@ -1,4 +1,4 @@
-(BitBlazorUI as any).version = (window as any)['bit-blazorui version'] = '9.1.1';
+(BitBlazorUI as any).version = (window as any)['bit-blazorui version'] = '9.1.2';
interface DotNetObject {
invokeMethod(methodIdentifier: string, ...args: any[]): T;
diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
index 7bbbb07f4a..a0b7a01a85 100644
--- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
+++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Bit.BlazorUI.Demo.Server.csproj
@@ -5,11 +5,11 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
index 8d494028ae..97d974449b 100644
--- a/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
+++ b/src/BlazorUI/Demo/Bit.BlazorUI.Demo.Server/Components/App.razor
@@ -60,34 +60,31 @@
}
- @if (HttpContext.Request.IsCrawlerClient() is false)
+
+ @if (AppRenderMode.PwaEnabled)
+ {
+
+
+
+ }
+ else
{
-
- @if (AppRenderMode.PwaEnabled)
- {
-
-
-
- }
- else
- {
-
- }
-
-
-
-
+ }
+ });
+
}
+
+
+
+