diff --git a/src/BootstrapBlazor.WebConsole/Pages/Buttons.razor b/src/BootstrapBlazor.WebConsole/Pages/Buttons.razor index 8b6829ec298b33741d4da661610d10cdbd45a239..a19cb4719724311d4af1c10dcf9f06ae3632bd52 100644 --- a/src/BootstrapBlazor.WebConsole/Pages/Buttons.razor +++ b/src/BootstrapBlazor.WebConsole/Pages/Buttons.razor @@ -10,30 +10,30 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
- \ No newline at end of file + diff --git a/src/BootstrapBlazor.WebConsole/Properties/launchSettings.json b/src/BootstrapBlazor.WebConsole/Properties/launchSettings.json index 3f8d9b78007e8139fb63b784338306992ba12fa8..e01a67e3198ee8c459e01e62c2770e0c8d1f44c3 100644 --- a/src/BootstrapBlazor.WebConsole/Properties/launchSettings.json +++ b/src/BootstrapBlazor.WebConsole/Properties/launchSettings.json @@ -1,16 +1,32 @@ { "iisSettings": { "windowsAuthentication": false, - "anonymousAuthentication": true + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:50853/", + "sslPort": 0 + } }, "profiles": { - "BootstrapBlazor.WebConsole": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Docker": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}" + }, + "Bootstrap.Admin": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "applicationUrl": "http://localhost:5000" + "applicationUrl": "http://localhost:50853/" } } } \ No newline at end of file diff --git a/src/BootstrapBlazor.WebConsole/_Imports.razor b/src/BootstrapBlazor.WebConsole/_Imports.razor index 0ea699e1e55f73fed27f2ac53dcb0e1fa2d266ab..a6006fd9c0f17df7a6d8134fab616414308832b3 100644 --- a/src/BootstrapBlazor.WebConsole/_Imports.razor +++ b/src/BootstrapBlazor.WebConsole/_Imports.razor @@ -7,3 +7,4 @@ @using Microsoft.JSInterop @using BootstrapBlazor.WebConsole @using BootstrapBlazor.WebConsole.Shared +@using BootstrapBlazor.Components \ No newline at end of file diff --git a/src/BootstrapBlazor/Components/Button/Button.razor b/src/BootstrapBlazor/Components/Button/Button.razor new file mode 100644 index 0000000000000000000000000000000000000000..2cb67b857839961b0b0d9d26b2fe450b7143686d --- /dev/null +++ b/src/BootstrapBlazor/Components/Button/Button.razor @@ -0,0 +1,6 @@ +@namespace BootstrapBlazor.Components +@inherits ButtonBase + + + @ChildContent + diff --git a/src/BootstrapBlazor/Components/Button/ButtonBase.cs b/src/BootstrapBlazor/Components/Button/ButtonBase.cs new file mode 100644 index 0000000000000000000000000000000000000000..e78ae061bc1de685a4f5390a9c0ab2b6c2ab8c34 --- /dev/null +++ b/src/BootstrapBlazor/Components/Button/ButtonBase.cs @@ -0,0 +1,126 @@ +using BootstrapBlazor.Utils; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace BootstrapBlazor.Components +{ + /// + /// Button 按钮组件 + /// + public abstract class ButtonBase : ComponentBase + { + /// + /// 获得/设置 用户自定义属性 + /// + /// + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary? AdditionalAttributes { get; set; } + + /// + /// 获得 按钮样式集合 + /// + /// + protected string ClassName => new CssBuilder("btn") + .AddClass($"btn-outline-{Color.ToDescriptionString()}", IsOutline) + .AddClass($"btn-{Color.ToDescriptionString()}", Color != Color.None && !IsOutline) + .AddClass($"btn-{Size.ToDescriptionString()}", Size != Size.None) + .AddClass("btn-block", IsBlock) + .AddClass("disabled", ButtonType == ButtonType.Link && IsDisabled) + .AddClass(Class) + .Build(); + + /// + /// 获得/设置 按钮 disabled 属性 + /// + protected string? Tag { get; set; } = "button"; + + /// + /// 获得 按钮 disabled 属性 + /// + protected string? Disabled => IsDisabled ? "true" : null; + + /// + /// 获得 按钮 tabindex 属性 + /// + protected string? Tab => IsDisabled ? "-1" : null; + + /// + /// 获得 按钮类型 + /// + protected string type => ButtonType switch + { + ButtonType.Submit => "submit", + ButtonType.Reset => "reset", + _ => "button" + }; + + /// + /// OnClick 事件 + /// + [Parameter] public EventCallback OnClick { get; set; } + + /// + /// 获得/设置 按钮颜色 + /// + [Parameter] public Color Color { get; set; } = Color.Primary; + + private ButtonType _buttonType = ButtonType.Button; + + /// + /// + /// + [Parameter] + public ButtonType ButtonType + { + get => _buttonType; + set + { + _buttonType = value; + Tag = _buttonType switch + { + ButtonType.Link => "a", + ButtonType.Input => "input", + ButtonType.Reset => "input", + _ => "button" + }; + } + } + + /// + /// + /// + [Parameter] public bool IsOutline { get; set; } + + /// + /// + /// + [Parameter] public Size Size { get; set; } = Size.None; + + /// + /// + /// + [Parameter] public bool IsBlock { get; set; } + + /// + /// + /// + [Parameter] public string? Value { get; set; } + + /// + /// + /// + [Parameter] public bool IsDisabled { get; set; } + + /// + /// + /// + [Parameter] public string Class { get; set; } = ""; + + /// + /// + /// + [Parameter] public RenderFragment? ChildContent { get; set; } + } +} diff --git a/src/BootstrapBlazor/Components/Button/ButtonType.cs b/src/BootstrapBlazor/Components/Button/ButtonType.cs new file mode 100644 index 0000000000000000000000000000000000000000..e190fe0163c87948afe90d3ceda50345a3b57b24 --- /dev/null +++ b/src/BootstrapBlazor/Components/Button/ButtonType.cs @@ -0,0 +1,29 @@ +namespace BootstrapBlazor.Components +{ + /// + /// 按钮类型枚举 + /// + public enum ButtonType + { + /// + /// 正常按钮 + /// + Button, + /// + /// 提交按钮 + /// + Submit, + /// + /// Link 按钮 + /// + Link, + /// + /// 重置按钮 + /// + Reset, + /// + /// 普通按钮 + /// + Input, + } +} diff --git a/src/BootstrapBlazor/Components/DynamicElement.cs b/src/BootstrapBlazor/Components/DynamicElement.cs new file mode 100644 index 0000000000000000000000000000000000000000..3fa2516160dde0b22a609d53dc348ac3b068ec41 --- /dev/null +++ b/src/BootstrapBlazor/Components/DynamicElement.cs @@ -0,0 +1,66 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using System; +using System.Collections.Generic; + +namespace BootstrapBlazor.Components +{ + /// + /// Renders an element with the specified name and attributes. This is useful + /// when you want to combine a set of attributes declared at compile time with + /// another set determined at runtime. + /// + public class DynamicElement : ComponentBase + { + /// + /// Gets or sets the name of the element to render. + /// + [Parameter] public string TagName { get; set; } = ""; + + /// + /// + /// + [Parameter] public ElementReference? ElementRef { get; set; } + + /// + /// + /// + [Parameter] public Action? ElementRefChanged { get; set; } + + /// + /// + /// + [Parameter] public RenderFragment? ChildContent { get; set; } + + /// + /// + /// + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary? AdditionalAttributes { get; set; } + + /// + /// + /// + /// + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var index = 0; + builder.OpenElement(index++, TagName); + if (AdditionalAttributes != null) + { + if (AdditionalAttributes.Remove("@onclick", out var v)) + { + builder.AddAttribute(index++, "onclick", v); + } + builder.AddMultipleAttributes(index++, AdditionalAttributes); + } + if (ChildContent != null) builder.AddContent(index++, ChildContent); + if (ElementRefChanged != null) builder.AddElementReferenceCapture(index++, capturedRef => + { + ElementRef = capturedRef; + ElementRefChanged.Invoke(capturedRef); + }); + builder.CloseElement(); + } + } +} diff --git a/src/BootstrapBlazor/Enums/Color.cs b/src/BootstrapBlazor/Enums/Color.cs new file mode 100644 index 0000000000000000000000000000000000000000..5b022259944a7054e281fee5c7d66744c5b72209 --- /dev/null +++ b/src/BootstrapBlazor/Enums/Color.cs @@ -0,0 +1,75 @@ +using System.ComponentModel; + +namespace BootstrapBlazor.Components +{ + /// + /// 颜色枚举类型 + /// + public enum Color + { + /// + /// 无颜色 + /// + None, + + /// + /// active + /// + [Description("active")] + Active, + + /// + /// primary + /// + [Description("primary")] + Primary, + + /// + /// secondary + /// + [Description("secondary")] + Secondary, + + /// + /// success + /// + [Description("success")] + Success, + + /// + /// danger + /// + [Description("danger")] + Danger, + + /// + /// warning + /// + [Description("warning")] + Warning, + + /// + /// info + /// + [Description("info")] + Info, + + /// + /// light + /// + [Description("light")] + Light, + + /// + /// dark + /// + [Description("dark")] + Dark, + + /// + /// link + /// + [Description("link")] + Link + } +} diff --git a/src/BootstrapBlazor/Enums/Size.cs b/src/BootstrapBlazor/Enums/Size.cs new file mode 100644 index 0000000000000000000000000000000000000000..a29614466eeca27ec148a6020b80f0f86b6de546 --- /dev/null +++ b/src/BootstrapBlazor/Enums/Size.cs @@ -0,0 +1,45 @@ +using System.ComponentModel; + +namespace BootstrapBlazor.Components +{ + /// + /// Size 枚举类型 + /// + public enum Size + { + /// + /// 无设置 + /// + None, + + /// + /// xs 超小设置 + /// + [Description("xs")] + ExtraSmall, + + /// + /// xs 小设置 + /// + [Description("sm")] + Small, + + /// + /// xs 中等设置 + /// + [Description("md")] + Medium, + + /// + /// xs 大设置 + /// + [Description("lg")] + Large, + + /// + /// xs 超大设置 + /// + [Description("xl")] + ExtraLarge + } +} diff --git a/src/BootstrapBlazor/Utils/CssBuilder.cs b/src/BootstrapBlazor/Utils/CssBuilder.cs new file mode 100644 index 0000000000000000000000000000000000000000..db3a26d7a08c6d06f74214e45593af1e11205da5 --- /dev/null +++ b/src/BootstrapBlazor/Utils/CssBuilder.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; + +namespace BootstrapBlazor.Utils +{ + /// + /// Css 生成操作类 + /// + public struct CssBuilder + { + private string stringBuffer; + + /// + /// Creates a CssBuilder used to define conditional CSS classes used in a component. + /// Call Build() to return the completed CSS Classes as a string. + /// + /// + public static CssBuilder Default(string value) => new CssBuilder(value); + + /// + /// Creates an Empty CssBuilder used to define conditional CSS classes used in a component. + /// Call Build() to return the completed CSS Classes as a string. + /// + public static CssBuilder Empty() => new CssBuilder(); + + /// + /// Creates a CssBuilder used to define conditional CSS classes used in a component. + /// Call Build() to return the completed CSS Classes as a string. + /// + /// + public CssBuilder(string value) => stringBuffer = value; + + /// + /// Adds a raw string to the builder that will be concatenated with the next class or value added to the builder. + /// + /// + /// CssBuilder + public CssBuilder AddValue(string value) + { + stringBuffer += value; + return this; + } + + /// + /// Adds a CSS Class to the builder with space separator. + /// + /// CSS Class to add + /// CssBuilder + public CssBuilder AddClass(string value) => AddValue(" " + value); + + /// + /// Adds a conditional CSS Class to the builder with space separator. + /// + /// CSS Class to conditionally add. + /// Condition in which the CSS Class is added. + /// CssBuilder + public CssBuilder AddClass(string value, bool when = true) => when ? AddClass(value) : this; + + /// + /// Adds a conditional CSS Class to the builder with space separator. + /// + /// CSS Class to conditionally add. + /// Condition in which the CSS Class is added. + /// CssBuilder + public CssBuilder AddClass(string value, Func when) => AddClass(value, when()); + + /// + /// Adds a conditional CSS Class to the builder with space separator. + /// + /// Function that returns a CSS Class to conditionally add. + /// Condition in which the CSS Class is added. + /// CssBuilder + public CssBuilder AddClass(Func value, bool when = true) => when ? AddClass(value()) : this; + + /// + /// Adds a conditional CSS Class to the builder with space separator. + /// + /// Function that returns a CSS Class to conditionally add. + /// Condition in which the CSS Class is added. + /// CssBuilder + public CssBuilder AddClass(Func value, Func when) => AddClass(value, when()); + + /// + /// Adds a conditional nested CssBuilder to the builder with space separator. + /// + /// CSS Class to conditionally add. + /// Condition in which the CSS Class is added. + /// CssBuilder + public CssBuilder AddClass(CssBuilder builder, bool when = true) => when ? AddClass(builder.Build()) : this; + + /// + /// Adds a conditional CSS Class to the builder with space separator. + /// + /// CSS Class to conditionally add. + /// Condition in which the CSS Class is added. + /// CssBuilder + public CssBuilder AddClass(CssBuilder builder, Func when) => AddClass(builder, when()); + + /// + /// Adds a conditional CSS Class when it exists in a dictionary to the builder with space separator. + /// Null safe operation. + /// + /// Additional Attribute splat parameters + /// CssBuilder + public CssBuilder AddClassFromAttributes(IReadOnlyDictionary additionalAttributes) => + additionalAttributes == null ? this : + additionalAttributes.TryGetValue("class", out var c) ? AddClass(((string)c ?? "").ToString()) : this; + + /// + /// Finalize the completed CSS Classes as a string. + /// + /// string + public string Build() + { + // String buffer finalization code + return stringBuffer != null ? stringBuffer.Trim() : string.Empty; + } + + /// + /// ToString should only and always call Build to finalize the rendered string. + /// + /// + public override string ToString() => Build(); + } +} diff --git a/src/BootstrapBlazor/Utils/EnumExtensions.cs b/src/BootstrapBlazor/Utils/EnumExtensions.cs new file mode 100644 index 0000000000000000000000000000000000000000..3fce88da2373851877dae94bb5befd070d47be7a --- /dev/null +++ b/src/BootstrapBlazor/Utils/EnumExtensions.cs @@ -0,0 +1,58 @@ +using BootstrapBlazor.Components; +using System.ComponentModel; + +namespace BootstrapBlazor.Utils +{ + /// + /// + /// + public static class EnumExtensions + { + /// + /// + /// + /// + /// + public static string ToDescriptionString(this Color val) + { + var attributes = val.GetType().GetField(val.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[] ?? new DescriptionAttribute[0]; + return attributes.Length > 0 ? attributes[0].Description : string.Empty; + } + + /// + /// + /// + /// + /// + public static string ToDescriptionString(this Size val) + { + var attributes = val.GetType().GetField(val.ToString())?.GetCustomAttributes(typeof(DescriptionAttribute), false) as DescriptionAttribute[] ?? new DescriptionAttribute[0]; + return attributes.Length > 0 ? attributes[0].Description : string.Empty; + } + + /* + public static string ToDescriptionString(this InputType val) + { + var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : string.Empty; + } + + public static string ToDescriptionString(this DropdownDirection val) + { + var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : string.Empty; + } + + public static string ToDescriptionString(this Placement val) + { + var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : string.Empty; + } + public static string ToDescriptionString(this HeadingSize val) + { + var attributes = (DescriptionAttribute[])val.GetType().GetField(val.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false); + return attributes.Length > 0 ? attributes[0].Description : string.Empty; + } + */ + } +}