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; -