diff --git a/change/@ni-nimble-blazor-f343ecb0-c88b-4d86-ae5d-5a25ef104809.json b/change/@ni-nimble-blazor-f343ecb0-c88b-4d86-ae5d-5a25ef104809.json new file mode 100644 index 0000000000..55def647ff --- /dev/null +++ b/change/@ni-nimble-blazor-f343ecb0-c88b-4d86-ae5d-5a25ef104809.json @@ -0,0 +1,7 @@ +{ + "type": "minor", + "comment": "Blazor integration for switch, text area, toggle button, icons. Fix 2-way binding for checkbox.", + "packageName": "@ni/nimble-blazor", + "email": "20709258+msmithNI@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/package-lock.json b/package-lock.json index c587b63399..667377a673 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41452,8 +41452,10 @@ "@ni/eslint-config-javascript": "^3.1.0", "@ni/nimble-components": "*", "@ni/nimble-tokens": "*", + "@rollup/plugin-node-resolve": "^13.1.3", "cross-env": "^7.0.3", - "glob": "^7.2.0" + "glob": "^7.2.0", + "rollup": "^2.61.1" } }, "packages/nimble-blazor/node_modules/glob": { @@ -45164,8 +45166,10 @@ "@ni/eslint-config-javascript": "^3.1.0", "@ni/nimble-components": "*", "@ni/nimble-tokens": "*", + "@rollup/plugin-node-resolve": "^13.1.3", "cross-env": "^7.0.3", - "glob": "^7.2.0" + "glob": "^7.2.0", + "rollup": "^2.61.1" }, "dependencies": { "glob": { diff --git a/packages/nimble-blazor/.eslintrc.js b/packages/nimble-blazor/.eslintrc.js index 5282d15e86..f14de2782c 100644 --- a/packages/nimble-blazor/.eslintrc.js +++ b/packages/nimble-blazor/.eslintrc.js @@ -6,14 +6,17 @@ module.exports = { overrides: [ { files: [ - 'build/copyNimbleResources.js' + 'build/**/*.js' ], rules: { // Okay to use dev dependencies in build scripts 'import/no-extraneous-dependencies': 'off', // Okay to use console.log in build scripts - 'no-console': 'off' + 'no-console': 'off', + + // Rollup config files use default exports + 'import/no-default-export': 'off' } } ] diff --git a/packages/nimble-blazor/.gitignore b/packages/nimble-blazor/.gitignore index a8dc543ada..28488c7536 100644 --- a/packages/nimble-blazor/.gitignore +++ b/packages/nimble-blazor/.gitignore @@ -1,5 +1,6 @@ # Folders NimbleBlazor.Components/wwwroot/nimble-*/ +NimbleBlazor.Components/Components/Icons/ artifacts/ bin/ obj/ diff --git a/packages/nimble-blazor/CONTRIBUTING.md b/packages/nimble-blazor/CONTRIBUTING.md index 41a22be10c..1530160cf2 100644 --- a/packages/nimble-blazor/CONTRIBUTING.md +++ b/packages/nimble-blazor/CONTRIBUTING.md @@ -1,5 +1,16 @@ # Contributing to Nimble Blazor +## Getting Started (Windows) + +For Nimble Blazor development on Windows, the suggested tools to install are: +- Visual Studio 2022 (Enterprise, if available): Choose the "ASP.NET and Web Development" Workload in the installer +- (Optional) Enable IIS (see "Enabling IIS", below) +- ASP.NET Core Runtime 6.0.x (6.0.3 or higher): Choose "Hosting Bundle" under ASP.NET Core Runtime, on the [.NET 6.0 Download Page](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) + +Initialize and build the Nimble monorepo (`npm install` + `npm run build` from the root `nimble` directory) before working with the solution in Visual Studio. + +In Visual Studio, run either the `NimbleBlazor.Demo.Server` or `NimbleBlazor.Demo.Projects` to see the Blazor demo apps. + ## Creating a Blazor wrapper for a Nimble element In Nimble Blazor, the Nimble web components are wrapped as [Razor Components](https://docs.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-6.0#components) that consist of a `.razor` template file and a corresponding `.razor.cs` C# implementation file. @@ -37,4 +48,20 @@ public partial class NimbleButton : ComponentBase Testing the Nimble Blazor components is possible through the use of xUnit and bUnit. Each Nimble Blazor component should have a corresponding test file. -Each Nimble Blazor component should also be showcased in the `NimbleBlazor.Demo` example projects. Simple component examples can be added directly in the `ComponentsDemo.razor` file (in the `NimbleBlazor.Demo.Shared` project). \ No newline at end of file +Each Nimble Blazor component should also be showcased in the `NimbleBlazor.Demo` example projects. Simple component examples can be added directly in the `ComponentsDemo.razor` file (in the `NimbleBlazor.Demo.Shared` project). + +## Additional Tips + +### Enabling IIS + +Click Start, open "Turn Windows features on or off", and configure "Web Management Tools" and "World Wide Web Services" in the following way: +![IIS Feature Configuration](/packages/nimble-blazor/docs/WindowsFeatures-IIS.jpg) +### Running published output + +The commandline build will create a published distribution of the Blazor client example app, which can also be tested via IIS: +- Open Internet Information Services (IIS) Manager +- In the left pane, right click "Sites" and click "Add Website..." +- Pick a site name +- Under "Physical Path", click [...] and browse to your `nimble-blazor\dist\blazor-client-app` directory +- Under "Binding", pick a port other than 80 (such as 8080), then click "OK" +- Open http://localhost:8080 (or whatever port you chose) \ No newline at end of file diff --git a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor index 5ccbc7fe4d..97cc45ee8b 100644 --- a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor +++ b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor @@ -16,19 +16,37 @@ Block Button Ghost Button +
+
Buttons - Toggle
+ Outline Toggle Button + Block Toggle Button + Ghost Toggle Button + + + Icon Toggle Button + +
Checkbox
Checkbox label Checkbox label Checkbox label
+
+
Icons (subset - see + Icons Storybook page for complete set)
+ + + + +
Menu
Header 1
Item 1 - [+] + Item 2
@@ -44,6 +62,19 @@ Option 3
+
+
Switch
+ Switch + + Switch with checked/unchecked messages + Off + On + +
+
+
Text Area
+ Text Area Label +
Text Field
Text Field Label diff --git a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.cs b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.cs index 34c378922a..1ec8b7a44a 100644 --- a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.cs +++ b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.cs @@ -1,9 +1,9 @@ namespace NimbleBlazor.Demo.Shared.Pages { /// - /// The CustomApp Demo. + /// The components demo page /// - public partial class CustomApp + public partial class ComponentsDemo { } } diff --git a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.css b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.css index d1ee6746ad..2892b75b94 100644 --- a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.css +++ b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Pages/ComponentsDemo.razor.css @@ -1,4 +1,8 @@ -.container-label { +.root { + background-color: var(--ni-nimble-application-background-color); +} + +.container-label { font: var(--ni-nimble-group-header-font); color: var(--ni-nimble-group-header-font-color); padding-bottom: var(--ni-nimble-standard-padding); diff --git a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Shared/MainLayout.razor b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Shared/MainLayout.razor index e75f731c6a..01ddfa8532 100644 --- a/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Shared/MainLayout.razor +++ b/packages/nimble-blazor/Examples/NimbleBlazor.Demo.Shared/Shared/MainLayout.razor @@ -17,4 +17,4 @@
-> + diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor index af6d96dd1b..e059dd46c6 100644 --- a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor @@ -3,6 +3,7 @@ diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor.cs b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor.cs index 6d626a9851..4b35d85ad9 100644 --- a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor.cs +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleCheckbox.razor.cs @@ -11,6 +11,9 @@ public partial class NimbleCheckbox : NimbleInputBase [Parameter] public bool? Required { get; set; } + [Parameter] + public bool? Indeterminate { get; set; } + [Parameter] public bool? ReadOnly { get; set; } diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleSwitch.razor b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleSwitch.razor new file mode 100644 index 0000000000..f5d08462a5 --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleSwitch.razor @@ -0,0 +1,10 @@ +@namespace NimbleBlazor.Components +@inherits NimbleInputBase + + @ChildContent + \ No newline at end of file diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleSwitch.razor.cs b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleSwitch.razor.cs new file mode 100644 index 0000000000..8bc6a0a139 --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleSwitch.razor.cs @@ -0,0 +1,21 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; + +namespace NimbleBlazor.Components; + +public partial class NimbleSwitch : NimbleInputBase +{ + [Parameter] + public bool? Disabled { get; set; } + + [Parameter] + public bool? Required { get; set; } + + [Parameter] + public bool? ReadOnly { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out bool result, [NotNullWhen(false)] out string? validationErrorMessage) => throw new NotSupportedException($"This component does not parse string inputs. Bind to the '{nameof(CurrentValue)}' property, not '{nameof(CurrentValueAsString)}'."); +} diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleTextArea.razor b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleTextArea.razor new file mode 100644 index 0000000000..a8e8147f14 --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleTextArea.razor @@ -0,0 +1,20 @@ +@namespace NimbleBlazor.Components +@inherits NimbleInputBase + + @ChildContent + \ No newline at end of file diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleTextArea.razor.cs b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleTextArea.razor.cs new file mode 100644 index 0000000000..3564142067 --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleTextArea.razor.cs @@ -0,0 +1,56 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; + +namespace NimbleBlazor.Components; + +public partial class NimbleTextArea : NimbleInputBase +{ + [Parameter] + public bool? Disabled { get; set; } + + [Parameter] + public bool? ReadOnly { get; set; } + + [Parameter] + public bool? Required { get; set; } + + [Parameter] + public bool? AutoFocus { get; set; } + + [Parameter] + public int? Size { get; set; } + + [Parameter] + public Appearance? Appearance { get; set; } + + [Parameter] + public TextAreaResize? TextAreaResize { get; set; } + + [Parameter] + public string? Placeholder { get; set; } + + [Parameter] + public int? MinLength { get; set; } + + [Parameter] + public int? MaxLength { get; set; } + + [Parameter] + public int? Rows { get; set; } + + [Parameter] + public int? Cols { get; set; } + + [Parameter] + public bool? Spellcheck { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + protected override bool TryParseValueFromString(string? value, out string? result, [NotNullWhen(false)] out string? validationErrorMessage) + { + result = value; + validationErrorMessage = null; + return true; + } +} diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleToggleButton.razor b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleToggleButton.razor new file mode 100644 index 0000000000..4ebd0ab9db --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleToggleButton.razor @@ -0,0 +1,12 @@ +@namespace NimbleBlazor.Components +@inherits NimbleInputBase + + @ChildContent + \ No newline at end of file diff --git a/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleToggleButton.razor.cs b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleToggleButton.razor.cs new file mode 100644 index 0000000000..3a7a534a89 --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/Components/NimbleToggleButton.razor.cs @@ -0,0 +1,24 @@ +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Components; + +namespace NimbleBlazor.Components; + +public partial class NimbleToggleButton : NimbleInputBase +{ + [Parameter] + public Appearance? Appearance { get; set; } + + [Parameter] + public bool? Disabled { get; set; } + + [Parameter] + public bool? ContentHidden { get; set; } + + [Parameter] + public bool? AutoFocus { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + protected override bool TryParseValueFromString(string? value, [MaybeNullWhen(false)] out bool result, [NotNullWhen(false)] out string? validationErrorMessage) => throw new NotSupportedException($"This component does not parse string inputs. Bind to the '{nameof(CurrentValue)}' property, not '{nameof(CurrentValueAsString)}'."); +} diff --git a/packages/nimble-blazor/NimbleBlazor.Components/NimbleIconBase.cs b/packages/nimble-blazor/NimbleBlazor.Components/NimbleIconBase.cs new file mode 100644 index 0000000000..a9e00def48 --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/NimbleIconBase.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Components; + +namespace NimbleBlazor.Components; + +/// +/// Base class for Nimble icons. +/// +public abstract class NimbleIconBase : ComponentBase +{ + /// + /// Gets or sets a collection of additional attributes that will be applied to the created element. + /// + [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary? AdditionalAttributes { get; set; } +} diff --git a/packages/nimble-blazor/NimbleBlazor.Components/TextAreaResize.cs b/packages/nimble-blazor/NimbleBlazor.Components/TextAreaResize.cs new file mode 100644 index 0000000000..38c2d22c7a --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/TextAreaResize.cs @@ -0,0 +1,16 @@ +namespace NimbleBlazor.Components; + +public enum TextAreaResize +{ + None, + Both, + Horizontal, + Vertical +} + +internal static class TextAreaResizeExtensions +{ + private static readonly Dictionary _textAreaResizeValues = AttributeHelpers.GetEnumNamesAsKebabCaseValues(); + + public static string? ToAttributeValue(this TextAreaResize? value) => value == null ? null : _textAreaResizeValues[value.Value]; +} diff --git a/packages/nimble-blazor/NimbleBlazor.Components/wwwroot/NimbleBlazor.Components.lib.module.js b/packages/nimble-blazor/NimbleBlazor.Components/wwwroot/NimbleBlazor.Components.lib.module.js new file mode 100644 index 0000000000..5bcd66fac6 --- /dev/null +++ b/packages/nimble-blazor/NimbleBlazor.Components/wwwroot/NimbleBlazor.Components.lib.module.js @@ -0,0 +1,19 @@ +/** + * Register the custom event type used for the change events for NimbleCheckbox/NimbleSwitch/NimbleToggleButton. + * Necessary because the control's value property is always just the value 'on', so we need to look + * at the checked property to correctly get the value. + * @see NimbleCheckbox.razor, NimbleSwitch.razor, NimbleToggleButton.razor + * + * JavaScript initializer for NimbleBlazor.Components project, see + * https://docs.microsoft.com/en-us/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-6.0#javascript-initializers + */ +export function afterStarted(Blazor) { + Blazor.registerCustomEventType('nimblecheckedchange', { + browserEventName: 'change', + createEventArgs: event => { + return { + checked: event.target.currentChecked + }; + } + }); +} \ No newline at end of file diff --git a/packages/nimble-blazor/NimbleBlazor.Components/wwwroot/Readme.md b/packages/nimble-blazor/NimbleBlazor.Components/wwwroot/Readme.md deleted file mode 100644 index 36ba014af1..0000000000 --- a/packages/nimble-blazor/NimbleBlazor.Components/wwwroot/Readme.md +++ /dev/null @@ -1,3 +0,0 @@ -# Nimble Blazor - -Find out more at https://github.com/ni/nimble diff --git a/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleSwitchTests.cs b/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleSwitchTests.cs new file mode 100644 index 0000000000..5a6b56506b --- /dev/null +++ b/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleSwitchTests.cs @@ -0,0 +1,23 @@ +using Bunit; +using NimbleBlazor.Components; +using Xunit; + +namespace NimbleBlazor.Tests.Unit.Components; + +/// +/// Tests for +/// +public class NimbleSwitchTests +{ + [Fact] + public void NimbleSwitch_Rendered_HasSwitchMarkup() + { + var context = new TestContext(); + context.JSInterop.Mode = JSRuntimeMode.Loose; + var expectedMarkup = "nimble-switch"; + + var switchComponent = context.RenderComponent(); + + Assert.Contains(expectedMarkup, switchComponent.Markup); + } +} diff --git a/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleTextAreaTests.cs b/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleTextAreaTests.cs new file mode 100644 index 0000000000..3b42a12fab --- /dev/null +++ b/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleTextAreaTests.cs @@ -0,0 +1,59 @@ +using Bunit; +using NimbleBlazor.Components; +using Xunit; + +namespace NimbleBlazor.Tests.Unit.Components; + +/// +/// Tests for +/// +public class NimbleTextAreaTests +{ + [Fact] + public void NimbleTextArea_Rendered_HasTextAreaMarkup() + { + var context = new TestContext(); + context.JSInterop.Mode = JSRuntimeMode.Loose; + var expectedMarkup = "nimble-text-area"; + + var textField = context.RenderComponent(); + + Assert.Contains(expectedMarkup, textField.Markup); + } + + [Theory] + [InlineData(TextAreaResize.None, "none")] + [InlineData(TextAreaResize.Both, "both")] + [InlineData(TextAreaResize.Horizontal, "horizontal")] + [InlineData(TextAreaResize.Vertical, "vertical")] + public void TextAreaTextAreaResize_AttributeIsSet(TextAreaResize value, string expectedAttribute) + { + var textArea = RenderNimbleTextArea(value); + + Assert.Contains(expectedAttribute, textArea.Markup); + } + + [Theory] + [InlineData(Appearance.Block, "block")] + [InlineData(Appearance.Outline, "outline")] + public void TextAreaAppearance_AttributeIsSet(Appearance value, string expectedAttribute) + { + var textArea = RenderNimbleTextArea(value); + + Assert.Contains(expectedAttribute, textArea.Markup); + } + + private IRenderedComponent RenderNimbleTextArea(TextAreaResize textAreaResize) + { + var context = new TestContext(); + context.JSInterop.Mode = JSRuntimeMode.Loose; + return context.RenderComponent(p => p.Add(x => x.TextAreaResize, textAreaResize)); + } + + private IRenderedComponent RenderNimbleTextArea(Appearance appearance) + { + var context = new TestContext(); + context.JSInterop.Mode = JSRuntimeMode.Loose; + return context.RenderComponent(p => p.Add(x => x.Appearance, appearance)); + } +} diff --git a/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleToggleButtonTests.cs b/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleToggleButtonTests.cs new file mode 100644 index 0000000000..8ef7e0cc31 --- /dev/null +++ b/packages/nimble-blazor/Tests.NimbleBlazor/Unit/Components/NimbleToggleButtonTests.cs @@ -0,0 +1,41 @@ +using Bunit; +using NimbleBlazor.Components; +using Xunit; + +namespace NimbleBlazor.Tests.Unit.Components; + +/// +/// Tests for +/// +public class NimbleToggleButtonTests +{ + [Fact] + public void NimbleToggleButton_Rendered_HasButtonMarkup() + { + var context = new TestContext(); + context.JSInterop.Mode = JSRuntimeMode.Loose; + var expectedMarkup = "nimble-toggle-button"; + + var button = context.RenderComponent(); + + Assert.Contains(expectedMarkup, button.Markup); + } + + [Theory] + [InlineData(Appearance.Block, "block")] + [InlineData(Appearance.Underline, "underline")] + [InlineData(Appearance.Ghost, "ghost")] + public void ButtonAppearance_AttributeIsSet(Appearance value, string expectedAttribute) + { + var button = RenderNimbleToggleButton(value); + + Assert.Contains(expectedAttribute, button.Markup); + } + + private IRenderedComponent RenderNimbleToggleButton(Appearance appearance) + { + var context = new TestContext(); + context.JSInterop.Mode = JSRuntimeMode.Loose; + return context.RenderComponent(p => p.Add(x => x.Appearance, appearance)); + } +} diff --git a/packages/nimble-blazor/build/generate-icons/README.md b/packages/nimble-blazor/build/generate-icons/README.md new file mode 100644 index 0000000000..1b59b885de --- /dev/null +++ b/packages/nimble-blazor/build/generate-icons/README.md @@ -0,0 +1,15 @@ +# Generate Icons + +## Behavior + +- Depends on the build output of `nimble-tokens` to generate icon Angular integrations. +- Generates a Razor component file for each icon. + +## How to run + +This script runs as part of the Nimble Blazor build. + +To run manually: + +1. Run a Nimble Blazor build. +2. Edit `index.js` for this script and run `npm run generate-icons` (can re-run when modifying `index.js` behavior). diff --git a/packages/nimble-blazor/build/generate-icons/rollup.config.js b/packages/nimble-blazor/build/generate-icons/rollup.config.js new file mode 100644 index 0000000000..89a182bd41 --- /dev/null +++ b/packages/nimble-blazor/build/generate-icons/rollup.config.js @@ -0,0 +1,12 @@ +import { nodeResolve } from '@rollup/plugin-node-resolve'; + +const path = require('path'); + +export default { + input: path.resolve(__dirname, 'source/index.js'), + output: { + file: path.resolve(__dirname, 'dist/index.js'), + format: 'cjs' + }, + plugins: [nodeResolve()] +}; diff --git a/packages/nimble-blazor/build/generate-icons/source/index.js b/packages/nimble-blazor/build/generate-icons/source/index.js new file mode 100644 index 0000000000..f091a7cc07 --- /dev/null +++ b/packages/nimble-blazor/build/generate-icons/source/index.js @@ -0,0 +1,60 @@ +/** + * Build script for generating nimble-blazor integration for Nimble icons. + * + * Iterates through icons provided by nimble-tokens, and generates a Razor component + * file for each. + */ + +import * as icons from '@ni/nimble-tokens/dist-icons-esm/nimble-icons-inline'; + +const fs = require('fs'); +const path = require('path'); + +const trimSizeFromName = text => { + // Remove dimensions from icon name, e.g. "add16X16" -> "add" + return text.replace(/\d+X\d+$/, ''); +}; + +const camelToPascalCase = text => { + return text.substring(0, 1).toUpperCase() + text.substring(1); +}; + +const camelToKebabCase = text => { + // Adapted from https://stackoverflow.com/a/67243723 + return text.replace(/[A-Z]+(?![a-z])|[A-Z]/g, (substring, offset) => (offset !== 0 ? '-' : '') + substring.toLowerCase()); +}; + +const generatedFilePrefix = `@* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY + // See generation source in nimble-blazor/build/generate-icons *@\n`; + +const packageDirectory = path.resolve(__dirname, '../../../'); +const iconsDirectory = path.resolve(packageDirectory, 'NimbleBlazor.Components/Components/Icons'); +console.log(iconsDirectory); + +if (fs.existsSync(iconsDirectory)) { + console.log(`Deleting existing icons directory "${iconsDirectory}"`); + fs.rmSync(iconsDirectory, { recursive: true }); + console.log('Finished deleting existing icons directory'); +} +console.log(`Creating icons directory "${iconsDirectory}"`); +fs.mkdirSync(iconsDirectory); +console.log('Finished creating icons directory'); + +console.log('Writing icon Razor component files'); +for (const key of Object.keys(icons)) { + const iconName = trimSizeFromName(key); // "arrowExpanderLeft" + const elementName = `nimble-${camelToKebabCase(iconName)}-icon`; // e.g. "nimble-arrow-expander-left-icon" + const className = `${camelToPascalCase(iconName)}Icon`; // e.g. "ArrowExpanderLeftIcon" + const componentName = `Nimble${className}`; // e.g. "NimbleArrowExpanderLeftIcon" + + const directiveFileContents = `${generatedFilePrefix} +@namespace NimbleBlazor.Components +@inherits NimbleIconBase +<${elementName} @attributes="AdditionalAttributes"> + + `; + const componentFileName = `${componentName}.razor`; + const componentFilePath = path.resolve(iconsDirectory, componentFileName); + fs.writeFileSync(componentFilePath, directiveFileContents, { encoding: 'utf-8' }); +} +console.log('Finshed writing icon Razor component files'); diff --git a/packages/nimble-blazor/docs/WindowsFeatures-IIS.jpg b/packages/nimble-blazor/docs/WindowsFeatures-IIS.jpg new file mode 100644 index 0000000000..2515f8bdaa Binary files /dev/null and b/packages/nimble-blazor/docs/WindowsFeatures-IIS.jpg differ diff --git a/packages/nimble-blazor/package.json b/packages/nimble-blazor/package.json index 06b6f5b4e1..1982743dcd 100644 --- a/packages/nimble-blazor/package.json +++ b/packages/nimble-blazor/package.json @@ -3,9 +3,12 @@ "version": "1.0.6", "description": "Blazor components for the NI Nimble Design System", "scripts": { - "build": "npm run build:all && npm run build:client", - "build:all": "dotnet build -c Release /p:TreatWarningsAsErrors=true /warnaserror", + "build": "npm run generate-icons && npm run build:release && npm run build:client", + "build:release": "dotnet build -c Release /p:TreatWarningsAsErrors=true /warnaserror", "build:client": "dotnet publish -p:BlazorEnableCompression=false -c Release Examples/NimbleBlazor.Demo.Client --output dist/blazor-client-app", + "generate-icons": "npm run generate-icons:bundle && npm run generate-icons:run", + "generate-icons:bundle": "rollup --config build/generate-icons/rollup.config.js", + "generate-icons:run": "node build/generate-icons/dist/index.js", "lint": "npm run lint:cs && npm run lint:js", "lint:cs": "dotnet format --verify-no-changes", "lint:js": "eslint .", @@ -36,6 +39,8 @@ "@ni/eslint-config-javascript": "^3.1.0", "@ni/nimble-components": "*", "@ni/nimble-tokens": "*", + "@rollup/plugin-node-resolve": "^13.1.3", + "rollup": "^2.61.1", "cross-env": "^7.0.3", "glob": "^7.2.0" }