diff --git a/.github/workflows/admin-sample.cd.yml b/.github/workflows/admin-sample.cd.yml
index 0e67144451..cd06d839b4 100644
--- a/.github/workflows/admin-sample.cd.yml
+++ b/.github/workflows/admin-sample.cd.yml
@@ -84,7 +84,7 @@ jobs:
- name: Upload server web artifact
uses: actions/upload-artifact@v5
with:
- name: AdminPanelWeb
+ name: AdminPanel
path: server-web
include-hidden-files: true # Required for wwwroot/.well-known folder
@@ -293,7 +293,7 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
- xcode-version: '26.1'
+ xcode-version: '26.2'
- uses: actions/setup-node@v6
with:
diff --git a/.github/workflows/bit.ci.BlazorUI.yml b/.github/workflows/bit.ci.BlazorUI.yml
index 06901ec1de..85d5ce4dd5 100644
--- a/.github/workflows/bit.ci.BlazorUI.yml
+++ b/.github/workflows/bit.ci.BlazorUI.yml
@@ -50,4 +50,4 @@ jobs:
run: dotnet build src/BlazorUI/Bit.BlazorUI.slnx
- name: Test
- run: cd src/BlazorUI/Bit.BlazorUI.Tests && dotnet test --no-build --verbosity normal
+ run: cd src/BlazorUI/Tests/Bit.BlazorUI.Tests && dotnet test --no-build --verbosity normal
diff --git a/.github/workflows/bit.full.ci.yml b/.github/workflows/bit.full.ci.yml
index aba7fb9a01..74d2f116df 100644
--- a/.github/workflows/bit.full.ci.yml
+++ b/.github/workflows/bit.full.ci.yml
@@ -70,7 +70,10 @@ jobs:
run: cd src && dotnet workload install wasm-tools
- name: Create project from template with PostgreSQL
- run: dotnet new bit-bp --name TestPostgreSQL --database PostgreSQL --module Sales --signalR --aspire
+ run: dotnet new bit-bp --name TestPostgreSQL --database PostgreSQL --module Sales --signalR --aspire --redis
+
+ - name: Trust dotnet cert
+ run: dotnet dev-certs https --trust # Necessary for aspire related resources
- name: Create appsettings.json for Client.Web
run: |
@@ -229,13 +232,13 @@ jobs:
- name: Build sample configuration 1
run: |
- dotnet new bit-bp --name TestProject --database SqlServer --filesStorage AzureBlobStorage --api Integrated --captcha reCaptcha --pipeline Azure --module Admin --offlineDb --appInsights --sentry --signalR --notification --cloudflare --ads --aspire
+ dotnet new bit-bp --name TestProject --database SqlServer --filesStorage AzureBlobStorage --api Integrated --captcha reCaptcha --pipeline Azure --module Admin --offlineDb --appInsights --sentry --signalR --notification --cloudflare --ads --aspire --redis
dotnet build TestProject/TestProject.sln -p:InvariantGlobalization=false -p:Environment=Staging
rm -r "TestProject"
- name: Build sample configuration 2
run: |
- dotnet new bit-bp --name TestProject2 --database Other --filesStorage S3 --api Standalone --captcha None --pipeline None --module None --offlineDb false --appInsights false --sentry false --signalR false --notification false --cloudflare false --ads false --aspire true
+ dotnet new bit-bp --name TestProject2 --database Other --filesStorage S3 --api Standalone --captcha None --pipeline None --module None --offlineDb false --appInsights false --sentry false --signalR false --notification false --cloudflare false --ads false --aspire true --redis false
dotnet build TestProject2/TestProject2.slnx -p:InvariantGlobalization=true -p:Environment=Development
rm -r "TestProject2"
diff --git a/.github/workflows/blazorui.demo.cd.yml b/.github/workflows/blazorui.demo.cd.yml
index 7a73e548a1..bdef14165a 100644
--- a/.github/workflows/blazorui.demo.cd.yml
+++ b/.github/workflows/blazorui.demo.cd.yml
@@ -164,7 +164,7 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
- xcode-version: '26.1'
+ xcode-version: '26.2'
- uses: actions/setup-node@v6
with:
diff --git a/.github/workflows/todo-sample.cd.yml b/.github/workflows/todo-sample.cd.yml
index bc510f9a98..f954080fe4 100644
--- a/.github/workflows/todo-sample.cd.yml
+++ b/.github/workflows/todo-sample.cd.yml
@@ -458,7 +458,7 @@ jobs:
- uses: maxim-lobanov/setup-xcode@v1.6.0
with:
- xcode-version: '26.1'
+ xcode-version: '26.2'
- name: Create project from Boilerplate
run: |
diff --git a/src/Besql/Bit.Besql/wwwroot/bit-besql.js b/src/Besql/Bit.Besql/wwwroot/bit-besql.js
index 85f55469cb..de69cd7353 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'] = '10.2.1';
+BitBesql.version = window['bit-besql version'] = '10.3.0';
BitBesql.persist = async function besqlPersist(fileName) {
diff --git a/src/Bit.Build.props b/src/Bit.Build.props
index bf006ebf7b..ecd9faa2df 100644
--- a/src/Bit.Build.props
+++ b/src/Bit.Build.props
@@ -27,7 +27,7 @@
https://github.com/bitfoundation/bitplatform
- 10.2.1
+ 10.3.0
$(ReleaseVersion)
https://github.com/bitfoundation/bitplatform/releases/tag/v-$(ReleaseVersion)
$([System.String]::Copy($(ReleaseVersion)).Replace('-pre-', '.'))
diff --git a/src/Bit.slnx b/src/Bit.slnx
index edbadc6304..ded9933557 100644
--- a/src/Bit.slnx
+++ b/src/Bit.slnx
@@ -44,12 +44,12 @@
+
-
-
+
diff --git a/src/BlazorES2019/Bit.BlazorES2019/buildTransitive/Bit.BlazorES2019.targets b/src/BlazorES2019/Bit.BlazorES2019/buildTransitive/Bit.BlazorES2019.targets
index c2397b923f..91d8390f56 100644
--- a/src/BlazorES2019/Bit.BlazorES2019/buildTransitive/Bit.BlazorES2019.targets
+++ b/src/BlazorES2019/Bit.BlazorES2019/buildTransitive/Bit.BlazorES2019.targets
@@ -1,13 +1,13 @@
-
-
+
+
PreserveNewest
PreserveNewest
wwwroot\_framework\%(Filename)%(Extension)
-
-
+
+
PreserveNewest
PreserveNewest
wwwroot\_framework\%(Filename)%(Extension)
diff --git a/src/BlazorUI/Bit.BlazorUI.Assets/package-lock.json b/src/BlazorUI/Bit.BlazorUI.Assets/package-lock.json
index 56f60e57e1..29f6ecf8e2 100644
--- a/src/BlazorUI/Bit.BlazorUI.Assets/package-lock.json
+++ b/src/BlazorUI/Bit.BlazorUI.Assets/package-lock.json
@@ -5,7 +5,7 @@
"packages": {
"": {
"devDependencies": {
- "sass": "1.97.0"
+ "sass": "1.97.1"
}
},
"node_modules/@parcel/watcher": {
@@ -448,9 +448,9 @@
}
},
"node_modules/sass": {
- "version": "1.97.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.0.tgz",
- "integrity": "sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ==",
+ "version": "1.97.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz",
+ "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/src/BlazorUI/Bit.BlazorUI.Assets/package.json b/src/BlazorUI/Bit.BlazorUI.Assets/package.json
index 723ab56604..46229d079a 100644
--- a/src/BlazorUI/Bit.BlazorUI.Assets/package.json
+++ b/src/BlazorUI/Bit.BlazorUI.Assets/package.json
@@ -1,5 +1,5 @@
{
"devDependencies": {
- "sass": "1.97.0"
+ "sass": "1.97.1"
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor
index ba49ac62ff..102db220cd 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor
@@ -1,9 +1,11 @@
-@namespace Bit.BlazorUI
+@namespace Bit.BlazorUI
@inherits BitComponentBase
@@ -14,7 +16,7 @@
-
+
@ChildContent
@@ -22,4 +24,4 @@
-
\ No newline at end of file
+
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor.cs
index d5c1190f6d..585b7304f7 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/AppShell/BitAppShell.razor.cs
@@ -12,8 +12,9 @@ public partial class BitAppShell : BitComponentBase
public const string Container = "BitAppShell.Container";
+
private bool _locationChanged;
- private ElementReference _containerRef = default!;
+ private ElementReference? _containerRef;
@@ -28,17 +29,12 @@ public partial class BitAppShell : BitComponentBase
[Parameter] public bool AutoGoToTop { get; set; }
///
- /// The cascading values to be provided for the children of the layout.
- ///
- [Parameter] public IEnumerable? CascadingValues { get; set; }
-
- ///
- /// The content of the layout.
+ /// The content of the app shell.
///
[Parameter] public RenderFragment? ChildContent { get; set; }
///
- /// Custom CSS classes for different parts of the layout.
+ /// Custom CSS classes for different parts of the app shell.
///
[Parameter] public BitAppShellClassStyles? Classes { get; set; }
@@ -48,10 +44,20 @@ public partial class BitAppShell : BitComponentBase
[Parameter] public bool PersistScroll { get; set; }
///
- /// Custom CSS styles for different parts of the layout.
+ /// Custom CSS styles for different parts of the app shell.
///
[Parameter] public BitAppShellClassStyles? Styles { get; set; }
+ ///
+ /// The cascading value list to be provided for the children of the app shell.
+ ///
+ [Parameter] public BitCascadingValueList? ValueList { get; set; }
+
+ ///
+ /// The cascading values to be provided for the children of the app shell.
+ ///
+ [Parameter] public IEnumerable? Values { get; set; }
+
///
@@ -59,13 +65,15 @@ public partial class BitAppShell : BitComponentBase
///
public async Task GoToTop(BitScrollBehavior? behavior = null)
{
- await _js.BitExtrasGoToTop(_containerRef, behavior);
+ if (_containerRef.HasValue is false) return;
+
+ await _js.BitExtrasGoToTop(_containerRef.Value, behavior);
}
///
- /// The element reference to the main container of the ap shell.
+ /// The element reference to the main container of the app shell.
///
- public ElementReference ContainerRef => _containerRef;
+ public ElementReference? ContainerRef => _containerRef;
@@ -95,7 +103,9 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender && PersistScroll)
{
- await _js.BitAppShellInitScroll(_containerRef, _navManager.Uri);
+ if (_containerRef.HasValue is false) return;
+
+ await _js.BitAppShellInitScroll(_containerRef.Value, _navManager.Uri);
}
if (_locationChanged && firstRender is false)
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/Components/NavPanel/BitNavPanel.razor.cs b/src/BlazorUI/Bit.BlazorUI.Extras/Components/NavPanel/BitNavPanel.razor.cs
index 0232ea7c51..04ef91b48f 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/Components/NavPanel/BitNavPanel.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/Components/NavPanel/BitNavPanel.razor.cs
@@ -391,7 +391,7 @@ private async Task HandleOnSwipeTrigger(BitSwipeTrapTriggerArgs args)
if (IsOpen is false) return $"{StyleBuilder.Value};{(isToggled ? Styles?.Toggled : string.Empty)}".Trim(';');
var translate = ((Dir != BitDir.Rtl && diffXPanel < 0) || (Dir == BitDir.Rtl && diffXPanel > 0))
- ? $"transform: translateX({diffXPanel}px)"
+ ? FormattableString.Invariant($"transform: translateX({diffXPanel}px)")
: string.Empty;
return $"{translate};{StyleBuilder.Value};{(isToggled ? Styles?.Toggled : string.Empty)}".Trim(';');
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/package-lock.json b/src/BlazorUI/Bit.BlazorUI.Extras/package-lock.json
index 2cf455c4ce..82cf55908c 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/package-lock.json
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/package-lock.json
@@ -6,7 +6,7 @@
"": {
"devDependencies": {
"esbuild": "0.27.2",
- "sass": "1.97.0",
+ "sass": "1.97.1",
"typescript": "5.9.3"
}
},
@@ -934,9 +934,9 @@
}
},
"node_modules/sass": {
- "version": "1.97.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.0.tgz",
- "integrity": "sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ==",
+ "version": "1.97.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz",
+ "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/src/BlazorUI/Bit.BlazorUI.Extras/package.json b/src/BlazorUI/Bit.BlazorUI.Extras/package.json
index aaccc035b3..fd3a150745 100644
--- a/src/BlazorUI/Bit.BlazorUI.Extras/package.json
+++ b/src/BlazorUI/Bit.BlazorUI.Extras/package.json
@@ -1,7 +1,7 @@
{
"devDependencies": {
"esbuild": "0.27.2",
- "sass": "1.97.0",
+ "sass": "1.97.1",
"typescript": "5.9.3"
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Icons/package-lock.json b/src/BlazorUI/Bit.BlazorUI.Icons/package-lock.json
index 607c92e468..142fd366ce 100644
--- a/src/BlazorUI/Bit.BlazorUI.Icons/package-lock.json
+++ b/src/BlazorUI/Bit.BlazorUI.Icons/package-lock.json
@@ -5,7 +5,7 @@
"packages": {
"": {
"devDependencies": {
- "sass": "1.97.0"
+ "sass": "1.97.1"
}
},
"node_modules/@parcel/watcher": {
@@ -448,9 +448,9 @@
}
},
"node_modules/sass": {
- "version": "1.97.0",
- "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.0.tgz",
- "integrity": "sha512-KR0igP1z4avUJetEuIeOdDlwaUDvkH8wSx7FdSjyYBS3dpyX3TzHfAMO0G1Q4/3cdjcmi3r7idh+KCmKqS+KeQ==",
+ "version": "1.97.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz",
+ "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/src/BlazorUI/Bit.BlazorUI.Icons/package.json b/src/BlazorUI/Bit.BlazorUI.Icons/package.json
index 723ab56604..46229d079a 100644
--- a/src/BlazorUI/Bit.BlazorUI.Icons/package.json
+++ b/src/BlazorUI/Bit.BlazorUI.Icons/package.json
@@ -1,5 +1,5 @@
{
"devDependencies": {
- "sass": "1.97.0"
+ "sass": "1.97.1"
}
}
diff --git a/src/BlazorUI/Bit.BlazorUI.Tests/Components/Buttons/BitActionButtonTests.cs b/src/BlazorUI/Bit.BlazorUI.Tests/Components/Buttons/BitActionButtonTests.cs
deleted file mode 100644
index 02b4a4daaf..0000000000
--- a/src/BlazorUI/Bit.BlazorUI.Tests/Components/Buttons/BitActionButtonTests.cs
+++ /dev/null
@@ -1,722 +0,0 @@
-using System;
-using System.Diagnostics;
-using Bunit;
-using Microsoft.AspNetCore.Components;
-using Microsoft.AspNetCore.Components.Forms;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Bit.BlazorUI.Tests.Components.Buttons;
-
-[TestClass]
-public class BitActionButtonTests : BunitTestContext
-{
- [TestMethod,
- DataRow(true),
- DataRow(false)
- ]
- public void BitActionButtonIsEnabledTest(bool isEnabled)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IsEnabled, isEnabled);
- });
-
- var button = component.Find(".bit-acb");
-
- if (isEnabled)
- {
- Assert.IsFalse(button.ClassList.Contains("bit-dis"));
- }
- else
- {
- Assert.IsTrue(button.ClassList.Contains("bit-dis"));
- }
- }
-
- [TestMethod,
- DataRow("Icon1"),
- DataRow("Icon2")
- ]
- public void BitActionButtonIconTest(string iconName)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IconName, iconName);
- });
-
- var icon = component.Find(".bit-icon");
-
- Assert.IsTrue(icon.ClassList.Contains($"bit-icon--{iconName}"));
- }
-
- [TestMethod,
- DataRow("title1"),
- DataRow("title2")
- ]
- public void BitActionButtonTitleTest(string title)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Title, title);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedTitle = title;
- var actualTitle = button.GetAttribute("title");
-
- Assert.AreEqual(expectedTitle, actualTitle);
- }
-
- [TestMethod,
- DataRow(true),
- DataRow(false)
- ]
- public void BitActionButtonOnClickTest(bool isEnabled)
- {
- var clicked = false;
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IsEnabled, isEnabled);
- parameters.Add(p => p.OnClick, () => clicked = true);
- });
-
- var button = component.Find(".bit-acb");
-
- if (isEnabled)
- {
- Assert.IsFalse(button.ClassList.Contains("bit-dis"));
- }
- else
- {
- Assert.IsTrue(button.ClassList.Contains("bit-dis"));
- }
-
- button.Click();
-
- var expected = isEnabled;
- var actual = clicked;
-
- Assert.AreEqual(expected, actual);
- }
-
- [TestMethod,
- DataRow(true, false, false),
- DataRow(true, true, false),
- DataRow(false, false, true),
- DataRow(false, true, false),
- ]
- public void BitActionButtonDisabledFocusTest(bool isEnabled, bool allowDisabledFocus, bool expectedResult)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IsEnabled, isEnabled);
- parameters.Add(p => p.AllowDisabledFocus, allowDisabledFocus);
- });
-
- var button = component.Find(".bit-acb");
-
- var hasTabIndexAttr = button.HasAttribute("tabindex");
-
- Assert.AreEqual(expectedResult, hasTabIndexAttr);
-
- if (hasTabIndexAttr)
- {
- Assert.IsTrue(button?.GetAttribute("tabindex")?.Equals("-1"));
- }
-
- }
-
- [TestMethod,
- DataRow("description1"),
- DataRow("description2")
- ]
- public void BitActionButtonAriaDescriptionTest(string ariaDescription)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.AriaDescription, ariaDescription);
- });
-
- var button = component.Find(".bit-acb");
-
- Assert.IsTrue(button?.GetAttribute("aria-describedby")?.Contains(ariaDescription));
- }
-
- [TestMethod,
- DataRow("label1"),
- DataRow("label2")
- ]
- public void BitActionButtonAriaLabelTest(string ariaLabel)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.AriaLabel, ariaLabel);
- });
-
- var button = component.Find(".bit-acb");
-
- Assert.IsTrue(button?.GetAttribute("aria-label")?.Contains(ariaLabel));
- }
-
- [TestMethod,
- DataRow(true),
- DataRow(false),
- ]
- public void BitActionButtonAriaHiddenTest(bool ariaHidden)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.AriaHidden, ariaHidden);
- });
-
- var button = component.Find(".bit-acb");
-
- Assert.AreEqual(ariaHidden, button.HasAttribute("aria-hidden"));
- }
-
- [TestMethod,
- DataRow(null, true),
- DataRow(null, false),
- DataRow("", true),
- DataRow("", false),
- DataRow(" ", true),
- DataRow(" ", false),
- DataRow("href", true),
- DataRow("href", false)
- ]
- public void BitActionButtonShouldRenderExpectedElementBasedOnHref(string href, bool isEnabled)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Href, href);
- parameters.Add(p => p.IsEnabled, isEnabled);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedTag = href.HasValue() ? "a" : "button";
-
- var actualTag = button.TagName;
-
- Assert.AreEqual(expectedTag, actualTag, ignoreCase: true);
- }
-
- [TestMethod,
- DataRow(BitButtonType.Button),
- DataRow(BitButtonType.Submit),
- DataRow(BitButtonType.Reset)
- ]
- public void BitActionButtonTypeOfButtonTest(BitButtonType buttonType)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.ButtonType, buttonType);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedType = buttonType switch
- {
- BitButtonType.Button => "button",
- BitButtonType.Submit => "submit",
- BitButtonType.Reset => "reset",
- _ => throw new NotSupportedException(),
- };
-
- var actualType = button.GetAttribute("type");
-
- Assert.AreEqual(expectedType, actualType);
- }
-
- [TestMethod]
- public void BitActionButtonSubmitStateInEditContextTest()
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.EditContext, new EditContext(this));
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedType = "submit";
- var actualType = button.GetAttribute("type");
-
- Assert.AreEqual(expectedType, actualType);
- }
-
- [TestMethod]
- public void BitActionButtonButtonStateNotOverriddenInEditContextTest()
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.EditContext, new EditContext(this));
- parameters.Add(p => p.ButtonType, BitButtonType.Button);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedType = "button";
- var actualType = button.GetAttribute("type");
-
- Assert.AreEqual(expectedType, actualType);
- }
-
- [TestMethod]
- public void BitActionButtonTabIndexIsRespectedWhenEnabled()
- {
- const string expectedTabIndex = "3";
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.TabIndex, expectedTabIndex);
- });
-
- var button = component.Find(".bit-acb");
-
- var actualTabIndex = button.GetAttribute("tabindex");
-
- Assert.AreEqual(expectedTabIndex, actualTabIndex);
- }
-
- [TestMethod,
- DataRow(true, "href1"),
- DataRow(false, "href2")
- ]
- public void BitActionButtonAnchorRespectsEnabledState(bool isEnabled, string href)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Href, href);
- parameters.Add(p => p.IsEnabled, isEnabled);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedHrefPresence = isEnabled;
-
- Assert.AreEqual(expectedHrefPresence, button.HasAttribute("href"));
- Assert.AreEqual(isEnabled is false, button.HasAttribute("disabled"));
-
- if (isEnabled)
- {
- Assert.AreEqual(href, button.GetAttribute("href"));
- }
- }
-
- [TestMethod,
- DataRow(BitColor.Primary),
- DataRow(BitColor.Secondary),
- DataRow(BitColor.Tertiary),
- DataRow(BitColor.Info),
- DataRow(BitColor.Success),
- DataRow(BitColor.Warning),
- DataRow(BitColor.SevereWarning),
- DataRow(BitColor.Error),
- DataRow(BitColor.PrimaryBackground),
- DataRow(BitColor.SecondaryBackground),
- DataRow(BitColor.TertiaryBackground),
- DataRow(BitColor.PrimaryForeground),
- DataRow(BitColor.SecondaryForeground),
- DataRow(BitColor.TertiaryForeground),
- DataRow(BitColor.PrimaryBorder),
- DataRow(BitColor.SecondaryBorder),
- DataRow(BitColor.TertiaryBorder),
- DataRow(null)
- ]
- public void BitActionButtonColorClassTest(BitColor? color)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Color, color);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedClass = GetColorClass(color);
-
- Assert.IsTrue(button.ClassList.Contains(expectedClass));
- }
-
- [TestMethod,
- DataRow(BitSize.Small),
- DataRow(BitSize.Medium),
- DataRow(BitSize.Large),
- DataRow(null)
- ]
- public void BitActionButtonSizeClassTest(BitSize? size)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Size, size);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedClass = GetSizeClass(size);
-
- Assert.IsTrue(button.ClassList.Contains(expectedClass));
- }
-
- [TestMethod,
- DataRow(true),
- DataRow(false)
- ]
- public void BitActionButtonFullWidthClassTest(bool fullWidth)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.FullWidth, fullWidth);
- });
-
- var button = component.Find(".bit-acb");
-
- var expectedClassPresence = fullWidth;
-
- Assert.AreEqual(expectedClassPresence, button.ClassList.Contains("bit-acb-fwi"));
- }
-
- [TestMethod,
- DataRow(BitIconPosition.Start),
- DataRow(BitIconPosition.End),
- DataRow(null)
- ]
- public void BitActionButtonIconPositionClassTest(BitIconPosition? iconPosition)
- {
- var com = RenderComponent(parameters =>
- {
- if (iconPosition.HasValue)
- {
- parameters.Add(p => p.IconPosition, iconPosition.Value);
- }
- });
-
- var bitButton = com.Find(".bit-acb");
-
- var expectedClassPresence = iconPosition == BitIconPosition.End;
-
- Assert.AreEqual(expectedClassPresence, bitButton.ClassList.Contains("bit-acb-eni"));
- }
-
- [TestMethod,
- DataRow("https://bitplatform.dev", BitLinkRels.NoOpener | BitLinkRels.NoReferrer, "noopener noreferrer"),
- DataRow("#section", BitLinkRels.NoOpener | BitLinkRels.NoReferrer, null)
- ]
- public void BitActionButtonRelAttributeShouldFollowHrefRules(string href, BitLinkRels rel, string? expectedRel)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Href, href);
- parameters.Add(p => p.Rel, rel);
- });
-
- var button = component.Find(".bit-acb");
-
- var hasRelAttribute = button.HasAttribute("rel");
-
- Assert.AreEqual(string.IsNullOrEmpty(expectedRel) is false, hasRelAttribute);
-
- if (expectedRel is not null)
- {
- Assert.AreEqual(expectedRel, button.GetAttribute("rel"));
- }
- }
-
- [TestMethod]
- public void BitActionButtonShouldNotRenderIconWhenIconNameIsNull()
- {
- var component = RenderComponent();
-
- Assert.IsEmpty(component.FindAll(".bit-acb-ico"));
- }
-
- [TestMethod]
- public void BitActionButtonShouldHideContentWhenIconOnlyIsTrue()
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IconOnly, true);
- parameters.Add(p => p.IconName, "Emoji2");
- parameters.AddChildContent("content");
- });
-
- var icon = component.Find(".bit-acb-ico");
-
- Assert.IsNotNull(icon);
- Assert.IsEmpty(component.FindAll(".bit-acb-con"));
- }
-
- [TestMethod]
- public void BitActionButtonShouldApplyCustomClasses()
- {
- var rootClass = "root-class";
- var iconClass = "icon-class";
- var contentClass = "content-class";
-
- var classes = new BitActionButtonClassStyles
- {
- Root = rootClass,
- Icon = iconClass,
- Content = contentClass
- };
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IconName, "Add");
- parameters.Add(p => p.Classes, classes);
- parameters.AddChildContent("bit");
- });
-
- var button = component.Find(".bit-acb");
- var icon = component.Find(".bit-acb-ico");
- var content = component.Find(".bit-acb-con");
-
- Assert.IsTrue(button.ClassList.Contains(rootClass));
- Assert.IsTrue(icon.ClassList.Contains(iconClass));
- Assert.IsTrue(content.ClassList.Contains(contentClass));
- }
-
- [TestMethod]
- public void BitActionButtonShouldApplyCustomStyles()
- {
- var rootStyle = "color: red;";
- var iconStyle = "margin: 4px;";
- var contentStyle = "padding: 8px;";
-
- var styles = new BitActionButtonClassStyles
- {
- Root = rootStyle,
- Icon = iconStyle,
- Content = contentStyle
- };
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IconName, "Share");
- parameters.Add(p => p.Styles, styles);
- parameters.AddChildContent("bit");
- });
-
- var button = component.Find(".bit-acb");
- var icon = component.Find(".bit-acb-ico");
- var content = component.Find(".bit-acb-con");
-
- Assert.IsTrue(button.GetAttribute("style")?.Contains(rootStyle));
- Assert.AreEqual(iconStyle, icon.GetAttribute("style"));
- Assert.AreEqual(contentStyle, content.GetAttribute("style"));
- }
-
- [TestMethod]
- public void BitActionButtonShouldRenderChildContentWhenIconOnlyIsFalse()
- {
- const string content = "Action content";
-
- var component = RenderComponent(parameters =>
- {
- parameters.AddChildContent(content);
- parameters.Add(p => p.IconOnly, false);
- });
-
- var button = component.Find(".bit-acb");
-
- Assert.Contains(content, button.TextContent);
- }
-
- [TestMethod,
- DataRow("data-value", "ID-123"),
- DataRow("aria-test", "this is test")
- ]
- public void BitActionButtonShouldRespectArbitraryHtmlAttributes(string name, string value)
- {
- var component = RenderComponent((name, value));
-
- var button = component.Find(".bit-acb");
-
- Assert.AreEqual(value, button.GetAttribute(name));
- }
-
- [TestMethod]
- public void BitActionButtonShouldRespectCascadingDir()
- {
- var component = RenderComponent>(parameters =>
- {
- parameters.Add(p => p.Value, BitDir.Rtl);
- parameters.AddChildContent(builder =>
- {
- builder.OpenComponent(0);
- builder.CloseComponent();
- });
- });
-
- var button = component.Find(".bit-acb");
-
- Assert.AreEqual("rtl", button.GetAttribute("dir"));
- }
-
- [TestMethod]
- public void BitActionButtonInsideEditFormShouldSubmitForm()
- {
- var submitted = false;
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Model, new object());
- parameters.Add(p => p.OnValidSubmit, _ => submitted = true);
- parameters.Add(p => p.ChildContent, (RenderFragment)((ec) => builder =>
- {
- builder.OpenComponent(0);
- builder.CloseComponent();
- }));
- });
-
- var form = component.Find("form");
-
- form.Submit();
-
- Assert.IsTrue(submitted);
- }
-
- [TestMethod]
- public void BitActionButtonTargetAttributeShouldRenderWhenHrefProvided()
- {
- const string href = "https://bitplatform.dev";
- const string target = "_blank";
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Href, href);
- parameters.Add(p => p.Target, target);
- });
-
- var anchor = component.Find(".bit-acb");
-
- Assert.AreEqual(target, anchor.GetAttribute("target"));
- }
-
- [TestMethod]
- public void BitActionButtonRenderPerformanceSmokeTest()
- {
- const int renderCount = 200;
-
- var stopwatch = Stopwatch.StartNew();
-
- for (var i = 0; i < renderCount; i++)
- {
- RenderComponent(parameters =>
- {
- parameters.Add(p => p.Title, $"title-{i}");
- parameters.Add(p => p.IconName, "Add");
- parameters.Add(p => p.IsEnabled, i % 3 != 0);
- parameters.Add(p => p.FullWidth, i % 2 == 0);
- parameters.Add(p => p.Href, i % 5 == 0 ? "https://bitplatform.dev" : null);
- });
- }
-
- stopwatch.Stop();
-
- Assert.IsTrue(stopwatch.Elapsed < TimeSpan.FromSeconds(5),
- $"Rendering {renderCount} BitActionButton instances took {stopwatch.Elapsed}.");
- }
-
- [TestMethod]
- public void BitActionButtonDynamicParameterUpdateShouldRefreshMarkup()
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IsEnabled, true);
- parameters.Add(p => p.IconName, "Add");
- });
-
- var button = component.Find(".bit-acb");
- var icon = component.Find(".bit-acb-ico");
-
- Assert.IsFalse(button.ClassList.Contains("bit-dis"));
- Assert.IsTrue(icon.ClassList.Contains("bit-icon--Add"));
-
- component.SetParametersAndRender(parameters =>
- {
- parameters.Add(p => p.IsEnabled, false);
- parameters.Add(p => p.IconName, "Delete");
- });
-
- Assert.IsTrue(button.ClassList.Contains("bit-dis"));
- Assert.IsTrue(icon.ClassList.Contains("bit-icon--Delete"));
- }
-
- [TestMethod]
- public void BitActionButtonRelShouldUpdateWhenHrefChanges()
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Href, "https://bitplatform.dev/docs");
- parameters.Add(p => p.Rel, BitLinkRels.NoOpener | BitLinkRels.NoReferrer);
- });
-
- var anchor = component.Find(".bit-acb");
-
- Assert.AreEqual("noopener noreferrer", anchor.GetAttribute("rel"));
-
- component.SetParametersAndRender(parameters => parameters.Add(p => p.Href, "#section-one"));
-
- anchor = component.Find(".bit-acb");
- Assert.IsFalse(anchor.HasAttribute("rel"));
-
- component.SetParametersAndRender(parameters => parameters.Add(p => p.Href, "/resources"));
-
- anchor = component.Find(".bit-acb");
- Assert.AreEqual("noopener noreferrer", anchor.GetAttribute("rel"));
- }
-
- [TestMethod,
- DataRow("5"),
- DataRow("50"),
- ]
- public void BitActionButtonTabIndexShouldRecoverAfterReEnable(string tabIndex)
- {
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.IsEnabled, false);
- parameters.Add(p => p.TabIndex, tabIndex);
- });
-
- var button = component.Find(".bit-acb");
-
- Assert.AreEqual("-1", button.GetAttribute("tabindex"));
-
- component.SetParametersAndRender(parameters => parameters.Add(p => p.IsEnabled, true));
-
- Assert.AreEqual(tabIndex, button.GetAttribute("tabindex"));
- }
-
-
- private static string GetColorClass(BitColor? color) => color switch
- {
- BitColor.Primary => "bit-acb-pri",
- BitColor.Secondary => "bit-acb-sec",
- BitColor.Tertiary => "bit-acb-ter",
- BitColor.Info => "bit-acb-inf",
- BitColor.Success => "bit-acb-suc",
- BitColor.Warning => "bit-acb-wrn",
- BitColor.SevereWarning => "bit-acb-swr",
- BitColor.Error => "bit-acb-err",
- BitColor.PrimaryBackground => "bit-acb-pbg",
- BitColor.SecondaryBackground => "bit-acb-sbg",
- BitColor.TertiaryBackground => "bit-acb-tbg",
- BitColor.PrimaryForeground => "bit-acb-pfg",
- BitColor.SecondaryForeground => "bit-acb-sfg",
- BitColor.TertiaryForeground => "bit-acb-tfg",
- BitColor.PrimaryBorder => "bit-acb-pbr",
- BitColor.SecondaryBorder => "bit-acb-sbr",
- BitColor.TertiaryBorder => "bit-acb-tbr",
- _ => "bit-acb-pri"
- };
-
- private static string GetSizeClass(BitSize? size) => size switch
- {
- BitSize.Small => "bit-acb-sm",
- BitSize.Medium => "bit-acb-md",
- BitSize.Large => "bit-acb-lg",
- _ => "bit-acb-md"
- };
-}
diff --git a/src/BlazorUI/Bit.BlazorUI.Tests/Components/Extras/AppShell/BitAppShellTests.cs b/src/BlazorUI/Bit.BlazorUI.Tests/Components/Extras/AppShell/BitAppShellTests.cs
deleted file mode 100644
index 4f8fef172b..0000000000
--- a/src/BlazorUI/Bit.BlazorUI.Tests/Components/Extras/AppShell/BitAppShellTests.cs
+++ /dev/null
@@ -1,131 +0,0 @@
-using System.Reflection;
-using Bunit;
-using Microsoft.AspNetCore.Components.Routing;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-
-namespace Bit.BlazorUI.Tests.Components.Extras.AppShell;
-
-[TestClass]
-public class BitAppShellTests : BunitTestContext
-{
- [TestMethod]
- public void BitAppShellShouldRenderStructureAndContent()
- {
- var component = RenderComponent(parameters =>
- {
- parameters.AddChildContent("Hello
");
- });
-
- var root = component.Find(".bit-ash");
- Assert.IsNotNull(root);
-
- component.Find(".bit-ash-top");
- component.Find(".bit-ash-center");
- component.Find(".bit-ash-left");
- component.Find(".bit-ash-main");
- component.Find(".bit-ash-right");
- component.Find(".bit-ash-bottom");
-
- var content = component.Find(".content");
-
- Assert.AreEqual("Hello", content.TextContent);
- }
-
- [TestMethod]
- public void BitAppShellShouldRespectClassesAndStyles()
- {
- var classes = new BitAppShellClassStyles
- {
- Root = "root-class",
- Top = "top-class",
- Center = "center-class",
- Left = "left-class",
- Main = "main-class",
- Right = "right-class",
- Bottom = "bottom-class"
- };
-
- var styles = new BitAppShellClassStyles
- {
- Root = "margin:1px;",
- Top = "padding:2px;",
- Center = "gap:3px;",
- Left = "width:4px;",
- Main = "height:5px;",
- Right = "border:6px solid transparent;",
- Bottom = "background:red;"
- };
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.Classes, classes);
- parameters.Add(p => p.Styles, styles);
- });
-
- var root = component.Find(".bit-ash");
-
- Assert.IsTrue(root.ClassList.Contains("root-class"));
- StringAssert.Contains(root.GetAttribute("style") ?? string.Empty, "margin:1px");
-
- Assert.IsTrue(component.Find(".bit-ash-top").ClassList.Contains("top-class"));
- Assert.IsTrue(component.Find(".bit-ash-center").ClassList.Contains("center-class"));
- Assert.IsTrue(component.Find(".bit-ash-left").ClassList.Contains("left-class"));
- Assert.IsTrue(component.Find(".bit-ash-main").ClassList.Contains("main-class"));
- Assert.IsTrue(component.Find(".bit-ash-right").ClassList.Contains("right-class"));
- Assert.IsTrue(component.Find(".bit-ash-bottom").ClassList.Contains("bottom-class"));
-
- StringAssert.Contains(component.Find(".bit-ash-top").GetAttribute("style") ?? string.Empty, "padding:2px");
- StringAssert.Contains(component.Find(".bit-ash-center").GetAttribute("style") ?? string.Empty, "gap:3px");
- StringAssert.Contains(component.Find(".bit-ash-left").GetAttribute("style") ?? string.Empty, "width:4px");
- StringAssert.Contains(component.Find(".bit-ash-main").GetAttribute("style") ?? string.Empty, "height:5px");
- StringAssert.Contains(component.Find(".bit-ash-right").GetAttribute("style") ?? string.Empty, "border:6px");
- StringAssert.Contains(component.Find(".bit-ash-bottom").GetAttribute("style") ?? string.Empty, "background:red");
- }
-
- [TestMethod]
- public void BitAppShellShouldPersistScroll()
- {
- Context.JSInterop.SetupVoid("BitBlazorUI.AppShell.initScroll");
- Context.JSInterop.SetupVoid("BitBlazorUI.AppShell.locationChangedScroll");
- Context.JSInterop.SetupVoid("BitBlazorUI.AppShell.afterRenderScroll");
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.PersistScroll, true);
- });
-
- Context.JSInterop.VerifyInvoke("BitBlazorUI.AppShell.initScroll");
-
- InvokeLocationChanged(component.Instance, "https://example.com/page2");
-
- Context.JSInterop.VerifyInvoke("BitBlazorUI.AppShell.locationChangedScroll");
-
- component.Render(); // trigger OnAfterRenderAsync for non-first render
-
- Context.JSInterop.VerifyInvoke("BitBlazorUI.AppShell.afterRenderScroll");
- }
-
- [TestMethod]
- public void BitAppShellShouldGoToTopWhenAutoGoToTop()
- {
- Context.JSInterop.SetupVoid("BitBlazorUI.Extras.goToTop");
-
- var component = RenderComponent(parameters =>
- {
- parameters.Add(p => p.AutoGoToTop, true);
- });
-
- InvokeLocationChanged(component.Instance, "https://example.com/other");
-
- Context.JSInterop.VerifyInvoke("BitBlazorUI.Extras.goToTop");
- }
-
- private static void InvokeLocationChanged(BitAppShell instance, string uri)
- {
- var method = instance.GetType().GetMethod("LocationChanged", BindingFlags.Instance | BindingFlags.NonPublic);
-
- Assert.IsNotNull(method);
-
- method!.Invoke(instance, new object?[] { null, new LocationChangedEventArgs(uri, false) });
- }
-}
diff --git a/src/BlazorUI/Bit.BlazorUI.Web.slnf b/src/BlazorUI/Bit.BlazorUI.Web.slnf
index 6b4261c28c..55cdd71702 100644
--- a/src/BlazorUI/Bit.BlazorUI.Web.slnf
+++ b/src/BlazorUI/Bit.BlazorUI.Web.slnf
@@ -2,12 +2,12 @@
"solution": {
"path": "Bit.BlazorUI.slnx",
"projects": [
+ "Bit.BlazorUI\\Bit.BlazorUI.csproj",
"Bit.BlazorUI.Assets\\Bit.BlazorUI.Assets.csproj",
"Bit.BlazorUI.Extras\\Bit.BlazorUI.Extras.csproj",
"Bit.BlazorUI.Icons\\Bit.BlazorUI.Icons.csproj",
"Bit.BlazorUI.SourceGenerators\\Bit.BlazorUI.SourceGenerators.csproj",
- "Bit.BlazorUI.Tests\\Bit.BlazorUI.Tests.csproj",
- "Bit.BlazorUI\\Bit.BlazorUI.csproj",
+ "Tests\\Bit.BlazorUI.Tests\\Bit.BlazorUI.Tests.csproj",
"Demo\\Bit.BlazorUI.Demo.Server\\Bit.BlazorUI.Demo.Server.csproj",
"Demo\\Bit.BlazorUI.Demo.Shared\\Bit.BlazorUI.Demo.Shared.csproj",
"Demo\\Client\\Bit.BlazorUI.Demo.Client.Core\\Bit.BlazorUI.Demo.Client.Core.csproj",
diff --git a/src/BlazorUI/Bit.BlazorUI.slnx b/src/BlazorUI/Bit.BlazorUI.slnx
index 683dcfb98b..ece4313cf9 100644
--- a/src/BlazorUI/Bit.BlazorUI.slnx
+++ b/src/BlazorUI/Bit.BlazorUI.slnx
@@ -28,10 +28,17 @@
+
+
+
+
+
+
+
+
-
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor
index edf7f9b814..f97a726780 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor
@@ -1,6 +1,11 @@
-@namespace Bit.BlazorUI
+@namespace Bit.BlazorUI
@inherits BitComponentBase
+@{
+ RenderFragment b = @
@(Body ?? ChildContent)
;
+ var body = ((Body ?? ChildContent) is not null && IconOnly is false) ? b : null;
+}
+
@if (Href.HasNoValue())
{
}
else
@@ -36,22 +56,20 @@ else
class="@ClassBuilder.Value"
dir="@Dir?.ToString().ToLower()"
disabled="@(IsEnabled is false)"
- href="@(IsEnabled ? Href : null)"
+ href="@(IsEnabled? Href : null)"
title="@Title"
target="@Target"
tabindex="@_tabIndex"
aria-label="@AriaLabel"
aria-hidden="@AriaHidden"
aria-describedby="@AriaDescription">
- @if (IconName is not null)
- {
-
- }
- @if (ChildContent is not null && IconOnly is false)
- {
-
- @ChildContent
-
- }
+
+ @if (IconName is not null)
+ {
+
+ }
+
+ @body
+
}
\ No newline at end of file
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor.cs
index b2a7093600..af250a4176 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.razor.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Components.Forms;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.AspNetCore.Components.Forms;
namespace Bit.BlazorUI;
@@ -9,6 +10,7 @@ public partial class BitActionButton : BitComponentBase
{
private string? _rel;
private string? _tabIndex;
+ private bool _isLoading;
private BitButtonType _buttonType;
@@ -19,6 +21,17 @@ public partial class BitActionButton : BitComponentBase
///
[CascadingParameter] public EditContext? EditContext { get; set; }
+ ///
+ /// Gets or sets the cascading parameters for the action button component.
+ ///
+ ///
+ /// This property receives its value from an ancestor component via Blazor's cascading parameter mechanism.
+ ///
+ /// The intended use is to allow shared configuration or settings to be applied to multiple action button components through the component.
+ ///
+ [CascadingParameter(Name = BitActionButtonParams.ParamName)]
+ public BitActionButtonParams? CascadingParameters { get; set; }
+
///
@@ -41,6 +54,11 @@ public partial class BitActionButton : BitComponentBase
///
[Parameter] public BitButtonType? ButtonType { get; set; }
+ ///
+ /// Alias for , the custom body of the action button (text and/or any render fragment).
+ ///
+ [Parameter] public RenderFragment? Body { get; set; }
+
///
/// The custom body of the action button (text and/or any render fragment).
///
@@ -100,6 +118,17 @@ public partial class BitActionButton : BitComponentBase
[Parameter, ResetClassBuilder]
public BitIconPosition? IconPosition { get; set; }
+ ///
+ /// Determines whether the action button is in loading mode or not.
+ ///
+ [Parameter, ResetClassBuilder]
+ public bool IsLoading { get; set; }
+
+ ///
+ /// The custom template used to replace the default loading indicator inside the action button in the loading state.
+ ///
+ [Parameter] public RenderFragment? LoadingTemplate { get; set; }
+
///
/// Gets or sets the callback that is invoked when the component is clicked.
///
@@ -109,16 +138,6 @@ public partial class BitActionButton : BitComponentBase
///
[Parameter] public EventCallback OnClick { get; set; }
- ///
- /// Gets or sets the custom CSS inline styles to apply to the action button component.
- ///
- ///
- /// Use this property to override the default styles of the action button.
- /// If not set, the component uses its built-in styling.
- /// This property is typically used to provide additional visual customization.
- ///
- [Parameter] public BitActionButtonClassStyles? Styles { get; set; }
-
///
/// Gets or sets the relationship type between the current element and the linked resource, as defined by the link's rel attribute.
///
@@ -140,6 +159,16 @@ public partial class BitActionButton : BitComponentBase
[Parameter, ResetClassBuilder]
public BitSize? Size { get; set; }
+ ///
+ /// Gets or sets the custom CSS inline styles to apply to the action button component.
+ ///
+ ///
+ /// Use this property to override the default styles of the action button.
+ /// If not set, the component uses its built-in styling.
+ /// This property is typically used to provide additional visual customization.
+ ///
+ [Parameter] public BitActionButtonClassStyles? Styles { get; set; }
+
///
/// Gets or sets the name of the target frame or window for the navigation action when the action button renders as an anchor (by providing the Href parameter).
///
@@ -155,6 +184,12 @@ public partial class BitActionButton : BitComponentBase
///
[Parameter] public string? Title { get; set; }
+ ///
+ /// Adds an underline to the action button text, useful for link-style buttons.
+ ///
+ [Parameter, ResetClassBuilder]
+ public bool Underlined { get; set; }
+
protected override string RootElementClass => "bit-acb";
@@ -187,6 +222,8 @@ protected override void RegisterCssClasses()
ClassBuilder.Register(() => FullWidth ? "bit-acb-fwi" : string.Empty);
+ ClassBuilder.Register(() => IsLoading ? "bit-acb-lod" : string.Empty);
+
ClassBuilder.Register(() => Size switch
{
BitSize.Small => "bit-acb-sm",
@@ -195,6 +232,8 @@ protected override void RegisterCssClasses()
_ => "bit-acb-md"
});
+ ClassBuilder.Register(() => Underlined ? "bit-acb-und" : string.Empty);
+
ClassBuilder.Register(() => IconPosition is BitIconPosition.End ? "bit-acb-eni" : string.Empty);
}
@@ -203,14 +242,19 @@ protected override void RegisterCssStyles()
StyleBuilder.Register(() => Styles?.Root);
}
+ [DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(BitActionButtonParams))]
protected override void OnParametersSet()
{
+ CascadingParameters?.UpdateParameters(this);
+
_tabIndex = IsEnabled
? TabIndex
: AllowDisabledFocus ? TabIndex : "-1";
_buttonType = ButtonType ?? (EditContext is null ? BitButtonType.Button : BitButtonType.Submit);
+ _isLoading = IsLoading;
+
base.OnParametersSet();
}
@@ -226,7 +270,7 @@ protected virtual async Task HandleOnClick(MouseEventArgs e)
- private void OnSetHrefAndRel()
+ internal void OnSetHrefAndRel()
{
if (Rel.HasValue is false || Href.HasNoValue() || Href!.StartsWith('#'))
{
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss
index 61f4b5d06b..2419f9bc96 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButton.scss
@@ -4,12 +4,9 @@
border: none;
display: flex;
cursor: pointer;
- gap: spacing(1);
color: $clr-fg-pri;
- align-items: center;
text-decoration: none;
box-sizing: border-box;
- justify-content: center;
font-family: $tg-font-family;
font-weight: $tg-font-weight;
background-color: transparent;
@@ -46,11 +43,39 @@
color: var(--bit-acb-clr-ico);
}
+.bit-acb-spn {
+ border-radius: 50%;
+ border-width: spacing(0.2125);
+ width: var(--bit-acb-spn-size);
+ height: var(--bit-acb-spn-size);
+ border-color: $clr-brd-sec;
+ border-style: $shp-border-style;
+ border-top-color: var(--bit-acb-clr-ico);
+ animation: bit-acb-spin 1.3s linear infinite;
+ animation-timing-function: cubic-bezier(0.53, 0.21, 0.29, 0.67);
+}
+
+@keyframes bit-acb-spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
.bit-acb-con {
flex-grow: 1;
display: flex;
}
+.bit-acb-inn {
+ display: inline-flex;
+ align-items: center;
+ gap: spacing(1);
+}
+
.bit-acb-eni {
flex-direction: row-reverse;
}
@@ -60,6 +85,34 @@
justify-content: flex-start;
}
+.bit-acb-lod {
+ cursor: default;
+ pointer-events: none;
+}
+
+.bit-acb-und {
+ .bit-acb-inn {
+ text-decoration: none;
+ position: relative;
+
+ &::after {
+ left: 0;
+ right: 0;
+ bottom: 0;
+ height: 1px;
+ content: "";
+ position: absolute;
+ background-color: currentColor;
+ }
+
+ @media (hover: hover) {
+ &:hover::after {
+ height: 2px;
+ }
+ }
+ }
+}
+
.bit-acb-pri {
--bit-acb-clr-ico: #{$clr-pri};
@@ -167,15 +220,18 @@
.bit-acb-sm {
--bit-acb-fontsize: #{spacing(1.25)};
+ --bit-acb-spn-size: #{spacing(1.25)};
--bit-acb-padding: #{spacing(0.50)} #{spacing(1)};
}
.bit-acb-md {
--bit-acb-fontsize: #{spacing(1.75)};
+ --bit-acb-spn-size: #{spacing(1.75)};
--bit-acb-padding: #{spacing(0.75)} #{spacing(1.5)};
}
.bit-acb-lg {
--bit-acb-fontsize: #{spacing(2.25)};
+ --bit-acb-spn-size: #{spacing(2.25)};
--bit-acb-padding: #{spacing(1.00)} #{spacing(2.00)};
-}
+}
\ No newline at end of file
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButtonClassStyles.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButtonClassStyles.cs
index d8b37e614f..c340dc4c95 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButtonClassStyles.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButtonClassStyles.cs
@@ -19,4 +19,9 @@ public class BitActionButtonClassStyles
/// Custom class or style applied to the content container.
///
public string? Content { get; set; }
+
+ ///
+ /// Custom class or style applied to the loading spinner element.
+ ///
+ public string? Spinner { get; set; }
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButtonParams.cs b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButtonParams.cs
new file mode 100644
index 0000000000..fe9665c91c
--- /dev/null
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Buttons/ActionButton/BitActionButtonParams.cs
@@ -0,0 +1,283 @@
+namespace Bit.BlazorUI;
+
+///
+/// The parameters for component.
+///
+public class BitActionButtonParams : BitComponentBaseParams, IBitComponentParams
+{
+ ///
+ /// Represents the parameter name used to identify the cascading parameters within .
+ ///
+ ///
+ /// This constant is typically used when referencing or accessing the BitActionButton value in
+ /// parameterized APIs or configuration settings. Using this constant helps ensure consistency and reduces the risk
+ /// of typographical errors.
+ ///
+ public const string ParamName = $"{nameof(BitParams)}.{nameof(BitActionButton)}";
+
+
+
+ public string Name => ParamName;
+
+
+
+ ///
+ /// Keeps the disabled action button focusable by not forcing a negative tabindex when is false.
+ ///
+ public bool? AllowDisabledFocus { get; set; }
+
+ ///
+ /// Detailed description of the button for the benefit of screen readers (rendered into aria-describedby).
+ ///
+ public string? AriaDescription { get; set; }
+
+ ///
+ /// If true, adds an aria-hidden attribute instructing screen readers to ignore the button.
+ ///
+ public bool? AriaHidden { get; set; }
+
+ ///
+ /// The type of the button element; defaults to submit inside an otherwise button.
+ ///
+ public BitButtonType? ButtonType { get; set; }
+
+ ///
+ /// Custom CSS classes for the root, icon, and content sections of the action button.
+ ///
+ public BitActionButtonClassStyles? Classes { get; set; }
+
+ ///
+ /// The general color of the button that applies to the icon and text of the action button.
+ ///
+ public BitColor? Color { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the component should expand to occupy the full available width.
+ ///
+ public bool? FullWidth { get; set; }
+
+ ///
+ /// The value of the href attribute of the link rendered by the button.
+ /// If provided, the component will be rendered as an anchor tag instead of button.
+ ///
+ public string? Href { get; set; }
+
+ ///
+ /// Gets or sets the name of the icon to display.
+ ///
+ ///
+ /// The icon name should be from the Fluent UI icon set (e.g., BitIconName.AddFriend).
+ ///
+ /// Browse available names in BitIconName of the Bit.BlazorUI.Icons nuget package or the gallery:
+ /// .
+ ///
+ /// The value is case-sensitive and must match a valid icon identifier.
+ /// If not set or set to null, no icon will be rendered.
+ ///
+ public string? IconName { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether only the icon is displayed, without accompanying text.
+ ///
+ ///
+ /// Set this property to to render the component with only its icon visible.
+ /// When , both icon and text are shown if available.
+ ///
+ public bool? IconOnly { get; set; }
+
+ ///
+ /// Gets or sets the position of the icon relative to the component's content.
+ ///
+ public BitIconPosition? IconPosition { get; set; }
+
+ ///
+ /// Determines whether the action button is in loading mode or not.
+ ///
+ public bool? IsLoading { get; set; }
+
+ ///
+ /// Gets or sets the relationship type between the current element and the linked resource, as defined by the link's rel attribute.
+ ///
+ ///
+ /// Sets the rel attribute for link-rendered buttons when is a non-anchor URL; ignored for empty or hash-only hrefs.
+ /// The rel attribute specifies the relationship between the current document and the linked document.
+ ///
+ /// Set this property to specify how the linked resource is related to the current context.
+ /// Common values include "stylesheet", "noopener", or "nofollow". The value determines how browsers and search
+ /// engines interpret the link.
+ ///
+ public BitLinkRels? Rel { get; set; }
+
+ ///
+ /// Sets the preset size (Small, Medium, Large) for typography and padding of the action button.
+ ///
+ public BitSize? Size { get; set; }
+
+ ///
+ /// Gets or sets the custom CSS inline styles to apply to the action button component.
+ ///
+ ///
+ /// Use this property to override the default styles of the action button.
+ /// If not set, the component uses its built-in styling.
+ /// This property is typically used to provide additional visual customization.
+ ///
+ public BitActionButtonClassStyles? Styles { get; set; }
+
+ ///
+ /// Gets or sets the name of the target frame or window for the navigation action when the action button renders as an anchor (by providing the Href parameter).
+ ///
+ ///
+ /// Specify a value to control where the linked content will be displayed. Common values include
+ /// "_blank" to open in a new window or tab, "_self" for the same frame, "_parent" for the parent frame, and "_top"
+ /// for the full body of the window. If not set, the default browser behavior is used.
+ ///
+ public string? Target { get; set; }
+
+ ///
+ /// The tooltip to show when the mouse is placed on the button.
+ ///
+ public string? Title { get; set; }
+
+ ///
+ /// Adds an underline to the action button text, useful for link-style buttons.
+ ///
+ public bool? Underlined { get; set; }
+
+
+
+ ///
+ /// Updates the properties of the specified instance with any values that have been set on
+ /// this object, if those properties have not already been set on the .
+ ///
+ ///
+ /// Only properties that have a value set and have not already been set on the will be updated.
+ /// This method does not overwrite existing values on .
+ ///
+ ///
+ /// The instance whose properties will be updated. Cannot be null.
+ ///
+ public void UpdateParameters(BitActionButton bitActionButton)
+ {
+ if (bitActionButton is null) return;
+
+ UpdateBaseParameters(bitActionButton);
+
+ if (AllowDisabledFocus.HasValue && bitActionButton.HasNotBeenSet(nameof(AllowDisabledFocus)))
+ {
+ bitActionButton.AllowDisabledFocus = AllowDisabledFocus.Value;
+ }
+
+ if (AriaDescription.HasValue() && bitActionButton.HasNotBeenSet(nameof(AriaDescription)))
+ {
+ bitActionButton.AriaDescription = AriaDescription;
+ }
+
+ if (AriaHidden.HasValue && bitActionButton.HasNotBeenSet(nameof(AriaHidden)))
+ {
+ bitActionButton.AriaHidden = AriaHidden.Value;
+ }
+
+ if (ButtonType.HasValue && bitActionButton.HasNotBeenSet(nameof(ButtonType)))
+ {
+ bitActionButton.ButtonType = ButtonType.Value;
+ }
+
+ if (Classes is not null && bitActionButton.HasNotBeenSet(nameof(Classes)))
+ {
+ bitActionButton.Classes = Classes;
+
+ bitActionButton.ClassBuilder.Reset();
+ }
+
+ if (Color.HasValue && bitActionButton.HasNotBeenSet(nameof(Color)))
+ {
+ bitActionButton.Color = Color.Value;
+
+ bitActionButton.ClassBuilder.Reset();
+ }
+
+ if (FullWidth.HasValue && bitActionButton.HasNotBeenSet(nameof(FullWidth)))
+ {
+ bitActionButton.FullWidth = FullWidth.Value;
+
+ bitActionButton.ClassBuilder.Reset();
+ }
+
+ bool hrefWasSet = false;
+ bool relWasSet = false;
+
+ if (Href.HasValue() && bitActionButton.HasNotBeenSet(nameof(Href)))
+ {
+ bitActionButton.Href = Href;
+
+ hrefWasSet = true;
+ }
+
+ if (IconName.HasValue() && bitActionButton.HasNotBeenSet(nameof(IconName)))
+ {
+ bitActionButton.IconName = IconName;
+ }
+
+ if (IconOnly.HasValue && bitActionButton.HasNotBeenSet(nameof(IconOnly)))
+ {
+ bitActionButton.IconOnly = IconOnly.Value;
+ }
+
+ if (IconPosition.HasValue && bitActionButton.HasNotBeenSet(nameof(IconPosition)))
+ {
+ bitActionButton.IconPosition = IconPosition.Value;
+
+ bitActionButton.ClassBuilder.Reset();
+ }
+
+ if (IsLoading.HasValue && bitActionButton.HasNotBeenSet(nameof(IsLoading)))
+ {
+ bitActionButton.IsLoading = IsLoading.Value;
+
+ bitActionButton.ClassBuilder.Reset();
+ }
+
+ if (Rel.HasValue && bitActionButton.HasNotBeenSet(nameof(Rel)))
+ {
+ bitActionButton.Rel = Rel.Value;
+
+ relWasSet = true;
+ }
+
+ // Call OnSetHrefAndRel if either Href or Rel was set, to update the _rel field
+ if (hrefWasSet || relWasSet)
+ {
+ bitActionButton.OnSetHrefAndRel();
+ }
+
+ if (Size.HasValue && bitActionButton.HasNotBeenSet(nameof(Size)))
+ {
+ bitActionButton.Size = Size.Value;
+
+ bitActionButton.ClassBuilder.Reset();
+ }
+
+ if (Styles is not null && bitActionButton.HasNotBeenSet(nameof(Styles)))
+ {
+ bitActionButton.Styles = Styles;
+ }
+
+ if (Target.HasValue() && bitActionButton.HasNotBeenSet(nameof(Target)))
+ {
+ bitActionButton.Target = Target;
+ }
+
+ if (Title.HasValue() && bitActionButton.HasNotBeenSet(nameof(Title)))
+ {
+ bitActionButton.Title = Title;
+ }
+
+ if (Underlined.HasValue && bitActionButton.HasNotBeenSet(nameof(Underlined)))
+ {
+ bitActionButton.Underlined = Underlined.Value;
+
+ bitActionButton.ClassBuilder.Reset();
+ }
+ }
+}
+
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs
index 17f22ef1d4..345f455392 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/CircularTimePicker/BitCircularTimePicker.razor.cs
@@ -254,7 +254,10 @@ public async Task _HandlePointerMove(MouseEventArgs e)
- public Task OpenCallout() => HandleOnClick();
+ public Task OpenCallout()
+ {
+ return HandleOnClick();
+ }
@@ -382,14 +385,6 @@ private void OnSetCulture()
_culture = Culture ?? CultureInfo.CurrentUICulture;
}
- private string GetTransformStyle(int index, double radius, double offsetX, double offsetY)
- {
- double angle = (6 - index) * 30 / 180d * Math.PI;
- var x = Math.Sin(angle) * radius + offsetX;
- var y = (Math.Cos(angle) + 1) * radius + offsetY;
- return $"{x:F3}px, {y:F3}px";
- }
-
private string GetHoursMinutesClass(int hourMinute)
{
StringBuilder classes = new();
@@ -436,9 +431,19 @@ private string GetHoursMinutesStyle(int hourMinute, int index, double radius, do
return styles.ToString();
}
- private int GetClockHandHeightPercent() => (_showHourView && TimeFormat == BitTimeFormat.TwentyFourHours && _hour > 0 && _hour < 13) ? 26 : 40;
+ private int GetClockHandHeightPercent()
+ {
+ return (_showHourView && TimeFormat == BitTimeFormat.TwentyFourHours && _hour > 0 && _hour < 13)
+ ? 26
+ : 40;
+ }
- private double GetPointerDegree() => _showHourView ? ((_hour.GetValueOrDefault() * 30) % 360) : ((_minute.GetValueOrDefault() * 6) % 360);
+ private double GetPointerDegree()
+ {
+ return _showHourView
+ ? (_hour.GetValueOrDefault() * 30 % 360)
+ : (_minute.GetValueOrDefault() * 6 % 360);
+ }
private async Task HandleOnPointerDown(MouseEventArgs e)
{
@@ -449,7 +454,9 @@ private async Task HandleOnPointerDown(MouseEventArgs e)
private async Task UpdateCurrentValue()
{
- CurrentValue = (_hour.HasValue is false || _minute.HasValue is false) ? null : new TimeSpan(_hour.Value, _minute.Value, 0);
+ CurrentValue = _hour.HasValue is true && _minute.HasValue is true
+ ? new TimeSpan(_hour.Value, _minute.Value, 0)
+ : null;
await OnSelectTime.InvokeAsync(CurrentValue);
}
@@ -462,15 +469,19 @@ private string GetHourString()
return Math.Min(23, Math.Max(0, hours)).ToString(CultureInfo.InvariantCulture);
}
- private string GetMinuteString() => _minute.HasValue ? $"{Math.Min(59, Math.Max(0, _minute.Value)):D2}" : "--";
-
- private int GetAmPmHours(int hours)
+ private string GetMinuteString()
{
- var result = hours % 12;
- return result == 0 ? 12 : result;
+ return _minute.HasValue
+ ? $"{Math.Min(59, Math.Max(0, _minute.Value)):D2}"
+ : "--";
}
- private int GetHours() => TimeFormat == BitTimeFormat.TwelveHours ? GetAmPmHours(_hour.GetValueOrDefault()) : _hour.GetValueOrDefault();
+ private int GetHours()
+ {
+ return TimeFormat == BitTimeFormat.TwelveHours
+ ? GetAmPmHours(_hour.GetValueOrDefault())
+ : _hour.GetValueOrDefault();
+ }
private void HandleOnHourClick()
{
@@ -515,7 +526,10 @@ private void HandleOnValueChanged(object? sender, EventArgs args)
_minute = CurrentValue?.Minutes;
}
- private bool IsAm() => _hour.GetValueOrDefault() >= 00 && _hour < 12; // am is 00:00 to 11:59
+ private bool IsAm()
+ {
+ return _hour.GetValueOrDefault() >= 00 && _hour < 12; // am is 00:00 to 11:59
+ }
private string GetValueFormat()
{
@@ -666,6 +680,22 @@ private string GetCalloutCssClasses()
+ private static string GetTransformStyle(int index, double radius, double offsetX, double offsetY)
+ {
+ double angle = (6 - index) * 30 / 180d * Math.PI;
+ var x = Math.Sin(angle) * radius + offsetX;
+ var y = (Math.Cos(angle) + 1) * radius + offsetY;
+ return FormattableString.Invariant($"{x:F3}px, {y:F3}px");
+ }
+
+ private static int GetAmPmHours(int hours)
+ {
+ var result = hours % 12;
+ return result == 0 ? 12 : result;
+ }
+
+
+
///
protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out TimeSpan? result, [NotNullWhen(false)] out string? validationErrorMessage)
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor
index 77ad9fc913..8f5fd05bae 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor
@@ -1,6 +1,13 @@
-@namespace Bit.BlazorUI
+@namespace Bit.BlazorUI
@inherits BitComponentBase
+@{
+ var thumbStyle = FormattableString.Invariant($"top:{_saturationPickerThumbPosition?.Top}px;left:{_saturationPickerThumbPosition?.Left}px;background-color:{Rgb}");
+
+ var alphaSliderBg = "url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAJUlEQVQYV2N89erVfwY0ICYmxoguxjgUFKI7GsTH5m4M3w1ChQC1/Ca8i2n1WgAAAABJRU5ErkJggg==)";
+ var alphaSliderStyle = $"background:linear-gradient(to left,{Rgb} 0%, transparent 100%), {alphaSliderBg};";
+}
+
@@ -45,8 +51,7 @@
@if (ShowAlphaSlider)
{
-
+
+ value="@(FormattableString.Invariant($"{_color.A}"))"
+ aria-valuenow="@(FormattableString.Invariant($"{_color.A}"))"
+ aria-valuetext="@(FormattableString.Invariant($"{_color.A}"))">
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor.cs b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor.cs
index 60f7a55a16..638e91e4c4 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor.cs
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/ColorPicker/BitColorPicker.razor.cs
@@ -1,7 +1,11 @@
-namespace Bit.BlazorUI;
+using System.Globalization;
+
+namespace Bit.BlazorUI;
///
-/// The color picker (ColorPicker) is used to browse through and select colors. By default, it lets people navigate through colors on a color spectrum, or specify a color in either Red-Green-Blue (RGB), or alpha color code; or Hexadecimal textboxes.
+/// The color picker (ColorPicker) is used to browse through and select colors.
+/// By default, it lets people navigate through colors on a color spectrum,
+/// or specify a color in either Red-Green-Blue (RGB), or alpha color code; or Hexadecimal textboxes.
///
public partial class BitColorPicker : BitComponentBase
{
@@ -35,6 +39,7 @@ public double Alpha
if (_color.A == value) return;
_color.A = value;
+
AlphaChanged.InvokeAsync(value);
}
}
@@ -48,7 +53,7 @@ public string Color
get => _colorType == BitInternalColorType.Hex ? _color.Hex! : _color.Rgb!;
set
{
- _colorType = value.HasValue() && value.StartsWith("#", StringComparison.InvariantCultureIgnoreCase)
+ _colorType = value.HasValue() && value.StartsWith('#')
? BitInternalColorType.Hex
: BitInternalColorType.Rgb;
@@ -85,10 +90,15 @@ public string Color
public string? Hex => _color.Hex;
+
public string? Rgb => FormattableString.Invariant($"rgb({_color.R},{_color.G},{_color.B})");
+
public string? Rgba => FormattableString.Invariant($"rgba({_color.R},{_color.G},{_color.B},{_color.A})");
+
public (double Hue, double Saturation, double Value) Hsv => _color.Hsv;
+
+
[JSInvokable(nameof(HandlePointerUp))]
public void HandlePointerUp(MouseEventArgs e)
{
@@ -132,6 +142,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
private async Task SetSaturationPickerThumbPositionAsync()
{
var (_, saturation, value) = _color.Hsv;
+
var saturationPickerRect = await _js.BitUtilsGetBoundingClientRect(_saturationPickerRef);
var width = saturationPickerRect?.Width ?? 0;
@@ -145,6 +156,7 @@ private async Task SetSaturationPickerThumbPositionAsync()
private void SetSaturationPickerStyle()
{
var rgb = BitInternalColor.ToRgb(_selectedHue, 1, 1).ToString();
+
_saturationPickerStyle = $"background-color:rgb{rgb}";
}
@@ -153,14 +165,17 @@ private async Task UpdateColor(MouseEventArgs e)
if (ColorHasBeenSet && ColorChanged.HasDelegate is false) return;
var pickerRect = await _js.BitUtilsGetBoundingClientRect(_saturationPickerRef);
- var left = e.ClientX < pickerRect.Left ? 0
+
+ var left = e.ClientX < pickerRect.Left
+ ? 0
: e.ClientX > pickerRect.Left + pickerRect.Width
- ? pickerRect.Width
- : (e.ClientX - pickerRect.Left);
- var top = e.ClientY < pickerRect.Top ? 0
+ ? pickerRect.Width
+ : (e.ClientX - pickerRect.Left);
+ var top = e.ClientY < pickerRect.Top
+ ? 0
: e.ClientY > pickerRect.Top + pickerRect.Height
- ? pickerRect.Height
- : (e.ClientY - pickerRect.Top);
+ ? pickerRect.Height
+ : (e.ClientY - pickerRect.Top);
_saturationPickerThumbPosition = new(left, top);
@@ -168,6 +183,7 @@ private async Task UpdateColor(MouseEventArgs e)
_selectedValue = Math.Clamp((pickerRect.Height - e.ClientY + pickerRect.Top) / pickerRect.Height, 0, 1);
_color.Update(_selectedHue, _selectedSaturation, _selectedValue, _color.A);
+
var colorValue = _colorType == BitInternalColorType.Hex ? _color.Hex : _color.Rgb;
SetSaturationPickerStyle();
@@ -183,7 +199,8 @@ private async Task HandleOnHueInput(ChangeEventArgs args)
{
if (ColorHasBeenSet && ColorChanged.HasDelegate is false) return;
- _selectedHue = Convert.ToDouble(args.Value);
+ _selectedHue = Convert.ToDouble(args.Value, CultureInfo.InvariantCulture);
+
_color.Update(_selectedHue, _selectedSaturation, _selectedValue, _color.A);
var colorValue = _colorType == BitInternalColorType.Hex ? _color.Hex : _color.Rgb;
@@ -199,7 +216,8 @@ private async Task HandleOnAlphaInput(ChangeEventArgs args)
{
if (AlphaHasBeenSet && AlphaChanged.HasDelegate is false) return;
- _color.A = Convert.ToDouble(args.Value);
+ _color.A = Convert.ToDouble(args.Value, CultureInfo.InvariantCulture);
+
var colorValue = _colorType == BitInternalColorType.Hex ? _color.Hex : _color.Rgb;
await ColorChanged.InvokeAsync(colorValue);
@@ -210,20 +228,20 @@ private async Task HandleOnAlphaInput(ChangeEventArgs args)
private async Task HandleOnSaturationPickerPointerDown(MouseEventArgs e)
{
_saturationPickerPointerDown = true;
+
await UpdateColor(e);
}
private string GetRootElAriaLabel()
{
- var ariaLabel = $"Color picker, Red {_color.R} Green {_color.G} Blue {_color.B} ";
+ var ariaLabel = $"Color picker, Red {_color.R} Green {_color.G} Blue {_color.B}";
+
if (ShowAlphaSlider)
{
- ariaLabel += $"Alpha {_color.A * 100}% selected.";
- }
- else
- {
- ariaLabel += "selected.";
+ ariaLabel += FormattableString.Invariant($" and Alpha {_color.A * 100}%");
}
+
+ ariaLabel += " selected.";
return ariaLabel;
}
diff --git a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor
index f0597b9ad2..4c7526594b 100644
--- a/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor
+++ b/src/BlazorUI/Bit.BlazorUI/Components/Inputs/DatePicker/BitDatePicker.razor
@@ -381,7 +381,7 @@
var monthName = _culture.DateTimeFormat.GetMonthName(month);
var disabled = IsEnabled is false || IsMonthOutOfMinAndMaxDate(month);
var selected = month == _currentMonth;
-