Compare commits

..

14 Commits

Author SHA1 Message Date
10a7454c48 Merge pull request 'features/rewrite/modal1' (#10) from features/rewrite/modal1 into features/rewrite/main
Reviewed-on: #10
Reviewed-by: Matija Koželj <matija.kozelj@tompit.com>
2023-02-27 12:44:20 +00:00
markosteger
a4d1f798f4 ModalDialog - ModalDialogService.cs, ModalEvents.cs, ModalOptions.cs file location change 2023-02-27 13:17:31 +01:00
markosteger
2a8aac91df ModalDialog - fixed ESC key press event 2023-02-27 13:12:06 +01:00
markosteger
a1e5043db8 ModalDialog - usage in Program.cs and MainLayout.razor 2023-02-27 12:18:05 +01:00
markosteger
ff3a36eb64 ModalDialog - added ModalButton class, ModalButtonType enum, relocation of classes outside the Services folder to Models/Modal 2023-02-27 12:11:37 +01:00
markosteger
422da4d079 ModalDialog - added ModalOptions 2023-02-27 08:54:01 +01:00
markosteger
4f635b573c ModalDialog 2023-02-27 08:45:13 +01:00
markosteger
69413494c5 Modal - added EventCallback for confirm click (working) but will try to rewrite it 2023-02-24 10:49:16 +01:00
markosteger
858dd59688 Added ToggleGlyphButton 2023-02-22 12:11:51 +01:00
markosteger
b9b1c16e23 New styles 2023-02-22 12:10:52 +01:00
markosteger
ea2a9e1744 Separating Button and GlyphButton 2023-02-22 09:47:19 +01:00
markosteger
60f1a90784 Code style fixes - added regions, summary, comments. 2023-02-21 15:04:45 +01:00
Matija Koželj
2d78796801 Quick review, must be completed by author 2023-02-17 13:50:59 +01:00
markosteger
3a5d96fc5e new ToggleInput, Fixed binding on Radio and CheckBox 2023-02-17 12:38:26 +01:00
42 changed files with 1447 additions and 417 deletions

View File

@ -1,5 +1,7 @@
@inherits LayoutComponentBase @inherits LayoutComponentBase
<ModalDialog/>
<main> <main>
@Body @Body
</main> </main>

View File

@ -1,6 +1,7 @@
using Connected.Components.Showcase.Runner; using Connected.Components.Showcase.Runner;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Connected.Services;
internal class Program internal class Program
{ {
@ -12,6 +13,8 @@ internal class Program
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
builder.Services.AddModalDialogService();
await builder.Build().RunAsync(); await builder.Build().RunAsync();
} }
} }

View File

@ -1,44 +1,9 @@
<button <button type="button"
type="button"
href="#"
@onclick="@OnClick" @onclick="@OnClick"
disabled=@Disabled disabled=@Disabled
style="@Style" style="@StyleList"
class="@ClassList"> class="@ClassList">
<div class="@ContentClassList"> <div class="@ContentClassList">
@if (!string.IsNullOrEmpty(Glyph))
{
@if (GlyphPosition == Position.Top || GlyphPosition == Position.Bottom)
{
<div style="align-items:center">
@if (GlyphPosition == Position.Top)
{
<Glyph SVG="@Glyph" Color="@GlyphColor"/>
}
@ChildContent @ChildContent
@if (GlyphPosition == Position.Bottom)
{
<Glyph SVG="@Glyph" Color="@GlyphColor"/>
}
</div>
}
@if (GlyphPosition == Position.Left || GlyphPosition == Position.Right)
{
<div style="display:flex; align-items:center">
@if (GlyphPosition == Position.Left)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" Class="m-1" />
}
@ChildContent
@if (GlyphPosition == Position.Right)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" Class="m-1" />
}
</div>
}
} else
{
@ChildContent
}
</div> </div>
</button> </button>

View File

@ -1,8 +1,6 @@
using Connected.Utilities; using Connected.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using static Connected.Colors;
using System;
namespace Connected.Components; namespace Connected.Components;
public partial class Button public partial class Button
@ -28,10 +26,10 @@ public partial class Button
/// <summary> /// <summary>
/// Size of the button. /// Size of the button.
/// Options: Size.[Small,Medium,Large,FullWidth] /// Options: Size.[Small,Medium,Large,FullWidth]
/// Default: Size.Large /// Default: Size.Medium
/// </summary> /// </summary>
[Parameter] [Parameter]
public Size Size { get; set; } = Size.Large; public Size Size { get; set; } = Size.Medium;
/// <summary> /// <summary>
/// Text shown inside the button /// Text shown inside the button
@ -48,29 +46,6 @@ public partial class Button
[Parameter] [Parameter]
public bool Disabled { get; set; } = false; public bool Disabled { get; set; } = false;
/// <summary>
/// Glyph (Icon) inside the button.
/// Options: SVG string --> Icons
/// Default: string.Empty
/// </summary>
[Parameter]
public string Glyph { get; set; } = string.Empty;
/// <summary>
/// Position of the glyph relative to button Text parameter. If Glyph parameter == string.Empty this parameter is ignored
/// Options: Position.[left,top,right,bottom]
/// Default: Position.left
/// </summary>
[Parameter]
public Position GlyphPosition { get; set; } = Position.Left;
/// <summary>
/// HEX string of the color for the glyph. If Glyph parameter is empty this parameter is ignored
/// Options: any HEX color string
/// Default: Black (#000000)
/// </summary>
[Parameter]
public string GlyphColor { get; set; } = "#000000";
/// <summary> /// <summary>
/// User defined custom class added on top of default generated classes /// User defined custom class added on top of default generated classes
@ -90,7 +65,7 @@ public partial class Button
/// <summary> /// <summary>
/// User defined custom style /// User defined custom style
/// Options: any user defined string with valid CSS style /// Options: any valid CSS style
/// Default: string.Empty /// Default: string.Empty
/// </summary> /// </summary>
[Parameter] [Parameter]
@ -115,10 +90,21 @@ public partial class Button
#region Styling #region Styling
public string StyleList
{
get
{
return new StyleBuilder()
.AddStyle(Style)
.Build();
}
}
/// <summary> /// <summary>
/// Generated class list for button based on user parameters /// Generated class list for button based on user parameters
/// </summary> /// </summary>
private string ClassList public string ClassList
{ {
get get
{ {
@ -134,7 +120,7 @@ public partial class Button
/// <summary> /// <summary>
/// Generated class list for button based on user parameters /// Generated class list for button based on user parameters
/// </summary> /// </summary>
private string ContentClassList public string ContentClassList
{ {
get get
{ {

View File

@ -2,12 +2,22 @@
@inherits InputBase; @inherits InputBase;
<div class="checkbox-group"> <label class="checkbox-group"
@if (Checked) for="@Id">
{ <input class="@ClassList"
<input id="@Id" name="checkbox" type="checkbox" @onchange="OnChange" @attributes=@InputAttributes checked> style="@StyleList"
} else { id="@Id"
<input id="@Id" name="checkbox" type="checkbox" @onchange="OnChange" @attributes=@InputAttributes> type="checkbox"
} @attributes=@InputAttributes
<label for="@Id" class="checkbox-label">@Label</label> checked="@Checked"
</div> readonly="@Readonly"
disabled="@Disabled">
<div class="checkbox-fill"></div>
<label for="@Id"
class="@LabelClassList"
style="@LabelStyleList">
@Label
</label>
</label>

View File

@ -1,21 +1,100 @@
using Connected.Models; using Connected.Models;
using Connected.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;
public partial class CheckBox: InputBase public partial class CheckBox : InputBase
{ {
/// <summary>
/// State of the CheckBox
/// Options: true, false
/// Default: false
/// </summary>
[Parameter] [Parameter]
public bool Checked { get; set; } = false; public bool Checked { get; set; } = false;
[Parameter, EditorRequired]
public string Id { get; set; }
/// <summary>
/// ID for the CheckBox
/// </summary>
[Parameter, EditorRequired]
public string? Id { get; set; }
/// <summary>
/// Event when the checked is changed
/// </summary>
[Parameter] [Parameter]
public EventCallback<bool> CheckedChange { get; set; } public EventCallback<bool> CheckedChanged { get; set; }
public async Task OnChange(ChangeEventArgs args)
/// <summary>
/// OPTIONAL - Style for the input
/// Options: any valid CSS style
/// Default: string.Empty
/// </summary>
[Parameter]
public string Style { get; set; } = string.Empty;
/// <summary>
/// OPTIONAL - Class for the label of input
/// Options: any valid Class name or multiple separated with space
/// Default: string.Empty
/// </summary>
[Parameter]
public string LabelClass { get; set; } = string.Empty;
/// <summary>
/// OPTIONAL - Style for the label of input
/// Options: any valid CSS style
/// Default: string.Empty
/// </summary>
[Parameter]
public string LabelStyle { get; set; } = string.Empty;
/// <summary>
/// OnChange event when checked is changed
/// </summary>
/// <returns></returns>
public async Task OnChange()
{ {
Checked = (bool)args.Value; Checked = !Checked;
CheckedChange.InvokeAsync(Checked); await CheckedChanged.InvokeAsync(Checked);
} }
private string ClassList
{
get
{
return new CssBuilder("checkbox-input")
.AddClass(base.Class)
.Build();
}
}
private string StyleList
{
get
{
return new StyleBuilder()
.AddStyle(Style)
.Build();
}
}
private string LabelClassList
{
get
{
return new CssBuilder("checkbox-label")
.AddClass(LabelClass)
.Build();
}
}
private string LabelStyleList
{
get
{
return new StyleBuilder()
.AddStyle(LabelStyle)
.Build();
}
}
} }

View File

@ -1,9 +0,0 @@
@using Connected.Models;
@inherits InputBase;
<div>
<div class="container">
@ChildContent
</div>
</div>

View File

@ -1,11 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public partial class CheckBoxGroup
{
[Parameter, EditorRequired]
public string Id { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}

View File

@ -1,6 +1,6 @@
 
<div style="width:@WidthString; height:@HeightString; overflow:hidden"> <div style="@GlyphStyleList">
<svg viewBox="0 0 24 24" style="fill:@Color;" class="@GlyphClassList" @onclick="@OnClick"> <svg viewBox="0 0 24 24" class="@GlyphClassList" @onclick="@OnClick">
@((MarkupString)SVG) @((MarkupString)SVG)
</svg> </svg>
</div> </div>

View File

@ -2,37 +2,47 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
namespace Connected.Components namespace Connected.Components;
public partial class Glyph
{ {
public partial class Glyph /// <summary>
{ /// SVG markup string for glyph
/// Options: any valid SVG markup string
/// Default: string.Empty
/// </summary>
[Parameter] [Parameter]
public string SVG { get; set; } = string.Empty; public string SVG { get; set; } = string.Empty;
/// <summary>
/// Color of the glyph
/// Options: Color.[Core,Primary,Secondary,Success,Info,Warning,Danger,White,Light,Dark]
/// Default: Color.Dark
/// </summary>
[Parameter] [Parameter]
public string Color { get; set; } = "#000000"; public Color Color { get; set; } = Color.Dark;
/// <summary>
/// Width of the glyph in px
/// Options: Any positive integer number
/// Default: 24
/// </summary>
[Parameter] [Parameter]
public int Width { get; set; } = 24; public int Width { get; set; } = 24;
/// <summary>
/// Height of the glyph in px
/// Options: Any positive integer number
/// Default: 24
/// </summary>
[Parameter] [Parameter]
public int Height { get; set; } = 24; public int Height { get; set; } = 24;
private string WidthString /// <summary>
{ /// Class name or multiple classes separated by space
get /// Options: Any valid class name or names separated by space
{ /// Default: string.Empty
return Width.ToString() + "px"; /// </summary>
}
}
private string HeightString
{
get
{
return Width.ToString() + "px";
}
}
[Parameter] [Parameter]
public string Class { get; set; } = string.Empty; public string Class { get; set; } = string.Empty;
@ -41,11 +51,31 @@ namespace Connected.Components
get get
{ {
return new CssBuilder() return new CssBuilder()
.AddClass("color-"+Helper.GetEnumDescription<Color>(Color))
.AddClass(Class) .AddClass(Class)
.Build(); .Build();
} }
} }
/// <summary>
/// User defined style for the glyph
/// Options: Any valid CSS style
/// Default: string.Empty
/// </summary>
[Parameter]
public string Style { get; set; } = string.Empty;
private string GlyphStyleList
{
get
{
return new StyleBuilder()
.AddStyle("width: "+Width.ToString()+"px; height:"+Height.ToString()+"px; overflow: hidden")
.AddStyle(Style)
.Build();
}
}
[Parameter] [Parameter]
/// <summary> /// <summary>
/// Glyph click event. /// Glyph click event.
@ -54,7 +84,5 @@ namespace Connected.Components
protected async Task OnClick(MouseEventArgs e) protected async Task OnClick(MouseEventArgs e)
{ {
await Click.InvokeAsync(e); await Click.InvokeAsync(e);
}
} }
} }

View File

@ -0,0 +1,46 @@
@inherits Button
<button type="button"
href="#"
@onclick="@OnClick"
disabled=@Disabled
style="@StyleList"
class="@ClassList">
<div class="@ContentClassList">
@if (!string.IsNullOrEmpty(Glyph))
{
@if (GlyphPosition == Position.Top || GlyphPosition == Position.Bottom)
{
<div style="align-items:center">
@if (GlyphPosition == Position.Top)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" />
}
@ChildContent
@if (GlyphPosition == Position.Bottom)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" />
}
</div>
}
@if (GlyphPosition == Position.Left || GlyphPosition == Position.Right)
{
<div style="display:flex; align-items:center">
@if (GlyphPosition == Position.Left)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" Class="m-1" />
}
@ChildContent
@if (GlyphPosition == Position.Right)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" Class="m-1" />
}
</div>
}
}
else
{
@ChildContent
}
</div>
</button>

View File

@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public partial class GlyphButton : Button
{
#region Parameters
/// <summary>
/// Glyph (Icon) inside the button.
/// Options: SVG string --> Icons
/// Default: string.Empty
/// </summary>
[Parameter, EditorRequired]
public string Glyph { get; set; } = string.Empty;
/// <summary>
/// Position of the glyph relative to button Text parameter. If Glyph parameter == string.Empty this parameter is ignored
/// Options: Position.[left,top,right,bottom]
/// Default: Position.left
/// </summary>
[Parameter]
public Position GlyphPosition { get; set; } = Position.Left;
/// <summary>
/// Color for the glyph. If Glyph parameter is empty this parameter is ignored
/// Options: Color.[Core,Primary,Secondary,Success,Info,Warning,Danger,White,Light,Dark]
/// Default: Color.Dark
/// </summary>
[Parameter]
public Color GlyphColor { get; set; } = Color.Dark;
#endregion
}

View File

@ -0,0 +1,6 @@
<a class="@LinkClassList"
style="@LinkStyleList"
href="@Url"
target="@_target">
@Text
</a>

View File

@ -0,0 +1,86 @@
using Connected.Enums;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public partial class Link
{
/// <summary>
/// URL of the link
/// Options: Any valid web page adress
/// Default: string.Empty
/// </summary>
[Parameter, EditorRequired]
public string Url { get; set; } = string.Empty;
/// <summary>
/// Text shown for the link. If this is not provided URL is used
/// Options: Any valid string
/// Default: string.Empty
/// </summary>
[Parameter]
public string Text { get; set; } = string.Empty;
/// <summary>
/// Target where the link shall open
/// Options: Target.[Self,Parent,Top,Blank]
/// Default: Target.Self
/// </summary>
[Parameter]
public Target Target { get; set; } = Target.Self;
private string _target
{
get
{
return Helper.GetEnumDescription<Target>(Target);
}
}
/// <summary>
/// Class name or multiple classes separated by space
/// Options: Any valid class name or names separated by space
/// Default: string.Empty
/// </summary>
[Parameter]
public string Class { get; set; } = string.Empty;
private string LinkClassList
{
get
{
return new CssBuilder()
.AddClass(Class)
.Build();
}
}
/// <summary>
/// Style string for the link
/// Options: Any valid CSS style
/// Default: string.Empty
/// </summary>
[Parameter]
public string Style { get; set; } = string.Empty;
private string LinkStyleList
{
get
{
return new StyleBuilder()
.AddStyle(Style)
.Build();
}
}
protected override async Task OnInitializedAsync()
{
//if Text parameter is not provided we set it to match URL
if (string.IsNullOrEmpty(Text))
Text = Url;
await base.OnInitializedAsync();
}
}

View File

@ -0,0 +1,25 @@
@using Connected.Models.Modal;
@if (IsVisible)
{
<div class="modal fade show" @onclick="@CloseIfEnabled" @onkeydown="@(e => CheckEscape(e))" tabindex="-1" @ref="@root">
<div class="modal-dialog modal-dialog-scrollable modal-dialog-centered">
<div class="modal-content" @onclick="PreventClose">
@if (!ModalOptions.NoHeader)
{
<div class="modal-header">
<h3 class="modal-title">@Title</h3>
</div>
}
<div class="modal-body">
@Content
</div>
<div class="modal-footer">
@foreach (ModalButton button in buttons)
{
<button type="button" class="btn @button.GetButtonClass" @onclick="@(()=>CloseModal(button))">@button.ButtonText</button>
}
</div>
</div>
</div>
</div>
}

View File

@ -0,0 +1,108 @@
using Connected.Models.Modal;
using Connected.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace Connected.Components;
public partial class ModalDialog : IDisposable
{
[Inject] ModalDialogService? ModalService { get; set; }
protected ElementReference root;
protected bool IsVisible { get; set; }
protected string? Title { get; set; }
protected RenderFragment? Content { get; set; }
protected bool OverlayClickToClose { get; set; } = true;
protected List<ModalButton> buttons { get; set; } = new();
protected ModalOptions? ModalOptions { get; set; }
protected override void OnInitialized()
{
ModalService.OnShow += ShowModal;
ModalService.OnClose += CloseModal;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (IsVisible)
await root.FocusAsync();
}
public void ShowModal(string title, RenderFragment content, List<ModalButton> buttons, ModalOptions options)
{
Title = title;
Content = content;
IsVisible = true;
ModalOptions = options;
this.buttons = buttons;
StateHasChanged();
}
public void CloseModal(ModalButton? button)
{
if (button is not null)
{
if (button.CloseDialogOnClick)
{
CloseModal();
}
button.OnClickEvent.Delegate.DynamicInvoke(button.OnClickEvent.args);
}
StateHasChanged();
}
public void CloseModal()
{
IsVisible = false;
Title = "";
Content = null;
StateHasChanged();
}
public void Dispose()
{
if (ModalService is not null)
{
ModalService.OnShow -= ShowModal;
ModalService.OnClose -= CloseModal;
}
}
public void CheckEscape(KeyboardEventArgs args)
{
if (!ModalOptions.DisableEscKey)
{
var key = args.Key.ToLower();
if (key.Equals("escape"))
{
CloseModal();
}
}
}
public void CloseIfEnabled(MouseEventArgs args)
{
if (!ModalOptions.DisableBackdropClick)
{
if (OverlayClickToClose)
{
CloseModal();
}
}
OverlayClickToClose = true;
}
public void PreventClose(MouseEventArgs args)
{
OverlayClickToClose = false;
}
}

View File

@ -0,0 +1,45 @@
@using Connected.Models;
@inherits InputBase;
<div class="@InputFieldClassList">
<textarea value="@Value"
placeholder="@Placeholder"
disabled="@Disabled"
readonly="@Readonly"
required="@Required"
style="overflow-x: hidden; overflow-y: hidden;"
@oninput=@ChangeValueAsync
@attributes="@InputAttributes" />
<span class="highlight"></span>
<span class="bar"></span>
@if (IsLabel)
{
<label class="label-animated">@Label</label>
}
@if (IsHelperText && !IsError)
{
<div class="input-helper-text">@HelperText</div>
}
@if (IsError)
{
<div class="input-error-text">@ErrorText</div>
}
<span class="input-glyph-wraper">
<span class="input-glyph">
@if (Clearable && !string.IsNullOrEmpty(Value))
{
<span class="input-glyph button" @onclick="Clear">
<Glyph SVG="@Icons.Material.Rounded.Dangerous" />
</span>
}
@if (IsError)
{
<span class="input-glyph error">
<Glyph SVG="@Icons.Material.Outlined.Error" />
</span>
}
</span>
</span>
</div>

View File

@ -0,0 +1,119 @@
using Connected.Models;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
namespace Connected.Components;
public partial class MultilineInput : InputBase
{
#region Parameters
private int MinRows { get; set; } = 1;
/// <summary>
/// Number of rows
/// </summary>
[Parameter]
public int Rows
{
get
{
return _numRows;
}
set
{
if (value >= MinRows) _numRows= value;
else _numRows = MinRows;
}
}
private int _numRows = 1;
/// <summary>
/// Value of the TextInput. Used for @bind-Value
/// </summary>
[Parameter]
public string Value { get; set; } = string.Empty;
#endregion
#region Events, Methods
/// <summary>
/// Event triggered when value changes
/// </summary>
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
/// <summary>
/// Method that triggers oninput -> when value inside the component changes
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task ChangeValueAsync(ChangeEventArgs args)
{
int oldRows = Rows;
await ValueChanged.InvokeAsync(args?.Value?.ToString());
int newRows = GetNumberOfLines(args.Value.ToString());
if (newRows == MinRows)
{
Rows = MinRows;
ChangeAttribute("rows", Rows);
StateHasChanged();
}
else
{
Rows = Math.Max(MinRows, newRows);
if (oldRows < Rows)
{
ChangeAttribute("rows", Rows);
StateHasChanged();
}
}
}
private int GetNumberOfLines(string s)
{
int result = Math.Max(s.Split("\r\n").Length, 1);
result = Math.Max(s.Split("\r").Length, result);
result = Math.Max(s.Split("\n").Length, result);
return result;
}
/// <summary>
/// Clear the value of the TextInput
/// </summary>
/// <returns></returns>
private async Task Clear()
{
await ValueChanged.InvokeAsync(string.Empty);
}
#endregion
#region Lifecycle
private void AddAttribute(string key, object value)
{
if (!InputAttributes.ContainsKey(key))
InputAttributes.Add(key, value);
}
private void ChangeAttribute(string key, object value)
{
if (InputAttributes.ContainsKey(key)) InputAttributes.Remove(key);
InputAttributes.Add(key, value);
}
protected override void OnInitialized()
{
base.OnInitialized();
MinRows = Rows;
AddAttribute("rows", MinRows);
}
#endregion
}

View File

@ -7,13 +7,14 @@
<div class="@InputFieldClassList"> <div class="@InputFieldClassList">
<input type="text" <input type="text"
placeholder="@Placeholder" placeholder="@Placeholder"
step="@_step" step="@Step"
disabled="@Disabled" disabled="@Disabled"
readonly="@Readonly" readonly="@Readonly"
required="@Required"
value="@_value" value="@_value"
@onkeydown=@(args => ChangeValue(args)) @onkeydown=@(args => ChangeValue(args))
@onkeydown:preventDefault="@_preventDefaultAction" @onkeydown:preventDefault="@_preventDefaultAction"
@oninput=@GetValueAsync @oninput=@SetValueAsync
@onmousewheel=@OnMouseWheel @onmousewheel=@OnMouseWheel
@onchange="@Change" @onchange="@Change"
@onwheel="OnMouseWheel" @onwheel="OnMouseWheel"
@ -39,18 +40,16 @@
<Glyph Width=16 Height=16 SVG="@Icons.Material.Outlined.KeyboardArrowUp" Click="StepUp" /> <Glyph Width=16 Height=16 SVG="@Icons.Material.Outlined.KeyboardArrowUp" Click="StepUp" />
<Glyph Width=16 Height=16 SVG="@Icons.Material.Outlined.KeyboardArrowDown" Click="StepDown"></Glyph> <Glyph Width=16 Height=16 SVG="@Icons.Material.Outlined.KeyboardArrowDown" Click="StepDown"></Glyph>
</span> </span>
@if (Clearable && Value.ToString().Length > 0) @if (Clearable && !string.IsNullOrEmpty(Value?.ToString()))
{ {
<span class="input-glyph button" @onclick="Clear"> <span class="input-glyph button" @onclick="Clear">
<Glyph SVG="@Icons.Material.Rounded.Dangerous" /> <Glyph SVG="@Icons.Material.Rounded.Dangerous" />
<!--<i class='bx bx-x-circle'></i>-->
</span> </span>
} }
@if (IsError) @if (IsError)
{ {
<span class="input-glyph error"> <span class="input-glyph error">
<Glyph SVG="@Icons.Material.Outlined.Error" Color="#D10000" /> <Glyph SVG="@Icons.Material.Outlined.Error" />
<!--<i class='bx bx-error-circle'></i>-->
</span> </span>
} }
</span> </span>

View File

@ -5,72 +5,95 @@ using Microsoft.AspNetCore.Components.Web;
using System.Numerics; using System.Numerics;
namespace Connected.Components; namespace Connected.Components;
public partial class NumberInput<NumberType>:InputBase where NumberType : INumber<NumberType> public partial class NumberInput<NumberType> : InputBase where NumberType : INumber<NumberType>
{ {
/// <summary>
private double _step =1; /// Step for up and down on numeric field
/// Options: Any double number
/// Default: 1
/// </summary>
[Parameter] [Parameter]
public double Step { public double Step { get; set; } = 1;
get
{
return _step;
}
set
{
_step=value;
}
}
/// <summary>
/// Mouse wheel disable to prevent StepUp/StepDown on number filed
/// Options: true, false
/// Default: false
/// </summary>
[Parameter] [Parameter]
public bool DisableMouseWheel public bool DisableMouseWheel { get; set; } = false;
{
get;
set;
} = false;
/// <summary>
/// Increase 'Value' for the 'Step'
/// </summary>
/// <returns>'Value' increased for the 'Step' parameter</returns>
private async Task StepUp() private async Task StepUp()
{ {
try try
{ {
double num = (double)Convert.ChangeType(Value, typeof(double)); var num = Helper.ConvertToType<double>(Value);
num += _step;
num += Step;
if (DecimalPlaces > 0) if (DecimalPlaces > 0)
num = Math.Round(num, DecimalPlaces); num = Math.Round(num, DecimalPlaces);
Value = (NumberType)Convert.ChangeType(num, typeof(NumberType)); Value = Helper.ConvertToType<NumberType>(num);
if (IsError) ErrorText = string.Empty;
if (IsError)
ErrorText = string.Empty;
} }
catch catch
{ {
ErrorText = "Error with step up!"; ErrorText = "Error with step up!";
Value = default(NumberType); Value = default;
} }
await ValueChanged.InvokeAsync(Value); await ValueChanged.InvokeAsync(Value);
} }
/// <summary>
/// Decrease 'Value' for the 'Step'
/// </summary>
/// <returns>'Value' decreased for the 'Step' parameter</returns>
private async Task StepDown() private async Task StepDown()
{ {
try try
{ {
double num = (double)Convert.ChangeType(Value, typeof(double)); var num = Helper.ConvertToType<double>(Value);
num -= Step;
num -= _step;
if (DecimalPlaces > 0) if (DecimalPlaces > 0)
num = Math.Round(num, DecimalPlaces); num = Math.Round(num, DecimalPlaces);
Value = (NumberType)Convert.ChangeType(num, typeof(NumberType));
if (IsError) ErrorText = string.Empty; Value = Helper.ConvertToType<NumberType>(num);
} catch
if (IsError)
ErrorText = string.Empty;
}
catch
{ {
ErrorText = "Error with step down!"; ErrorText = "Error with step down!";
Value = default(NumberType); Value = default;
} }
await ValueChanged.InvokeAsync(Value); await ValueChanged.InvokeAsync(Value);
} }
/// <summary>
/// Event triggered when mouse wheel is activated inside component
/// </summary>
/// <param name="args">WheelEventArgs argument</param>
/// <returns>Doesnt return values just increasing/decreasing values</returns>
protected async Task OnMouseWheel(WheelEventArgs args) protected async Task OnMouseWheel(WheelEventArgs args)
{ {
if (DisableMouseWheel == false) if (DisableMouseWheel)
{ return;
if (args.ShiftKey || Disabled || Readonly) if (args.ShiftKey || Disabled || Readonly)
return; return;
if (args.DeltaY >= 0) if (args.DeltaY >= 0)
{ {
await StepDown(); await StepDown();
@ -79,68 +102,92 @@ public partial class NumberInput<NumberType>:InputBase where NumberType : INumbe
{ {
await StepUp(); await StepUp();
} }
} else
{
return;
}
} }
private string _value; private string? _value;
/// <summary>
/// Value of any numeric type
/// Options: any numeric type variable
/// Default: null
/// </summary>
[Parameter] [Parameter]
[EditorRequired]
public NumberType? Value public NumberType? Value
{ {
get get
{ {
if (string.IsNullOrEmpty(_value)) return default(NumberType); if (string.IsNullOrEmpty(_value))
return (NumberType)Convert.ChangeType(_value, typeof(NumberType)); return default;
else
{
try
{
return Helper.ConvertToType<NumberType>(_value);
} catch
{
return default;
}
}
} }
set set
{ {
_value = value.ToString(); _value = value?.ToString();
} }
} }
/// <summary>
/// Number of decimal places for Value. If set, Value is corrected when input looses focus
/// Options: any integer number greater or equal 0
/// Default: 0
/// </summary>
[Parameter] [Parameter]
public int DecimalPlaces { get; set; } =0; public int DecimalPlaces { get; set; } = 0;
/// <summary>
/// Value change event
/// </summary>
[Parameter] [Parameter]
public EventCallback<NumberType> ValueChanged { get; set; } public EventCallback<NumberType> ValueChanged { get; set; }
public async Task GetValueAsync(ChangeEventArgs args) public async Task SetValueAsync(ChangeEventArgs args)
{ {
if (args.Value is not null) if (args.Value is not null)
{ {
string newVal = args.Value.ToString(); var newVal = args.Value.ToString()!;
if (!newVal.Equals("0")) if (!newVal.Equals("0"))
{ {
if (newVal.ToString().Contains("-")) if (newVal.ToString().Contains("-"))
newVal = "-" + newVal.ToString().Replace("-", ""); newVal = "-" + newVal.ToString().Replace("-", "");
if (newVal.ToString().ToLower().Contains("e")) if (newVal.ToString().ToLower().Contains("e"))
newVal = "e" + newVal.ToString().Replace("e", ""); newVal = "e" + newVal.ToString().Replace("e", "");
} }
if (string.IsNullOrEmpty(newVal)) if (string.IsNullOrEmpty(newVal))
{ await ValueChanged.InvokeAsync(default);
await ValueChanged.InvokeAsync((NumberType)Convert.ChangeType((NumberType)Convert.ChangeType(null, typeof(NumberType)), typeof(NumberType)));
}
else
{
if (!newVal.Equals(_value)) if (!newVal.Equals(_value))
await ValueChanged.InvokeAsync((NumberType)Convert.ChangeType((NumberType)Convert.ChangeType(newVal, typeof(NumberType)), typeof(NumberType))); await ValueChanged.InvokeAsync(Helper.ConvertToType<NumberType>(newVal));
}
} }
} }
public async Task Change(ChangeEventArgs args) public async Task Change(ChangeEventArgs args)
{ {
if (args.Value is not null) if (args.Value is not null)
{ Value = AdjustDecimalPlaces(Helper.ConvertToType<NumberType>(args.Value));
Value = AdjustDecimalPlaces((NumberType)Convert.ChangeType(args.Value, typeof(NumberType)));
} await ValueChanged.InvokeAsync(Value);
ValueChanged.InvokeAsync(Value);
} }
[Parameter] public EventCallback<KeyboardEventArgs> OnKeyDown { get; set; } /// <summary>
/// On keyboard key press event
/// </summary>
[Parameter]
public EventCallback<KeyboardEventArgs> OnKeyDown { get; set; }
private bool CheckKey(string key) private bool CheckKey(string key)
{ {
@ -172,42 +219,68 @@ public partial class NumberInput<NumberType>:InputBase where NumberType : INumbe
private bool _preventDefaultAction = true; private bool _preventDefaultAction = true;
public async Task ChangeValue(KeyboardEventArgs args) public async Task ChangeValue(KeyboardEventArgs args)
{ {
_preventDefaultAction= true; _preventDefaultAction = true;
if (args is not null) if (args is not null)
{ {
var key = args.Key.ToString().ToLower(); var key = args.Key.ToString().ToLower();
if (CheckKey(key)) if (CheckKey(key))
{ {
_preventDefaultAction = false; _preventDefaultAction = false;
await OnKeyDown.InvokeAsync(args); await OnKeyDown.InvokeAsync(args);
} }
} else }
else
{ {
args.Key = null; args.Key = null;
} }
} }
private NumberType AdjustDecimalPlaces(NumberType value) /// <summary>
/// Method for adjusting decimal places provided with parameter
/// </summary>
/// <param name="value">Value whose decimal places we want to change</param>
/// <returns>NumberType result with adjusted decimal places</returns>
private NumberType? AdjustDecimalPlaces(NumberType? value)
{ {
var result = value; var result = value;
if (DecimalPlaces > 0) if (DecimalPlaces > 0)
{ {
double converted = Math.Round((double)Convert.ChangeType(result, typeof(double)), DecimalPlaces); double converted = Math.Round(Helper.ConvertToType<double>(result), DecimalPlaces);
return (NumberType)Convert.ChangeType(converted, typeof(NumberType)); return Helper.ConvertToType<NumberType>(converted);
} }
return result; return result;
} }
/// <summary>
/// Clear event for user clear icon click. It clears the Value and set it to
/// </summary>
/// <returns></returns>
private async Task Clear() private async Task Clear()
{ {
_value = string.Empty; var val = Helper.ConvertToType<NumberType>(null);
await ValueChanged.InvokeAsync((NumberType)Convert.ChangeType(0, typeof(NumberType))); await ValueChanged.InvokeAsync(val);
} }
protected override async Task OnAfterRenderAsync(bool firstRender) #region Lifecycle
protected override async Task OnParametersSetAsync()
{
if (typeof(NumberType).Name.ToLower().Contains("int"))
{
if (Step - (int)Step > 0)
Step = (int)Step;
if (Step < 1)
Step = 1;
}
await base.OnParametersSetAsync();
}
protected override void OnAfterRender(bool firstRender)
{ {
if (firstRender) if (firstRender)
{
if (Value is not null)
{ {
if (!DecimalPlaces.Equals(Helper.GetDecimalPlaces(Value))) if (!DecimalPlaces.Equals(Helper.GetDecimalPlaces(Value)))
{ {
@ -216,25 +289,6 @@ public partial class NumberInput<NumberType>:InputBase where NumberType : INumbe
} }
} }
} }
#region Lifecycle
protected override async Task OnParametersSetAsync()
{
if (typeof(NumberType).Name.ToLower().Contains("int"))
{
if (Step - (int)Step > 0) Step = (int)Step;
if (Step < 1) Step = 1;
}
await base.OnParametersSetAsync();
}
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (Required)
{
if (!InputAttributes.ContainsKey("required")) InputAttributes.Add("required", true);
}
} }
#endregion #endregion

View File

@ -2,7 +2,20 @@
@inherits InputBase; @inherits InputBase;
<div class="radio-group"> <label class="radio-group"
<input id="@Id" name="@ParentRadioGroup.Name" type="radio" @onchange="OnChange" @attributes=@InputAttributes> for="@Id">
<label for="@Id" class="radio-label">@Label</label> <input class="@ClassNameList"
</div> id="@Id"
name="@ParentRadioGroup?.Name"
type="radio"
@onchange="OnChange"
@attributes=@InputAttributes
disabled="@Disabled"
readonly="@Readonly"
checked="@Checked">
<div class="radio-fill"></div>
<label for="@Id"
class="@LabelClassNameList">@Label</label>
</label>

View File

@ -1,9 +1,11 @@
using Connected.Models; using Connected.Models;
using Connected.Utilities;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;
public partial class Radio: InputBase public partial class Radio : InputBase
{ {
#region Parameters
[CascadingParameter] [CascadingParameter]
public RadioGroup? ParentRadioGroup { get; set; } public RadioGroup? ParentRadioGroup { get; set; }
@ -11,21 +13,51 @@ public partial class Radio: InputBase
public bool Checked { get; set; } = false; public bool Checked { get; set; } = false;
[Parameter, EditorRequired] [Parameter, EditorRequired]
public string Id { get; set; } public string? Id { get; set; }
#endregion
#region Events
[Parameter] [Parameter]
public EventCallback<bool> CheckedChange { get; set; } public EventCallback<bool> CheckedChanged { get; set; }
public async Task OnChange(ChangeEventArgs args)
public async Task OnChange()
{ {
Checked = (bool)args.Value; Checked = !Checked;
CheckedChange.InvokeAsync(Checked); await CheckedChanged.InvokeAsync(Checked);
} }
protected override async Task OnInitializedAsync() #endregion
#region Style
[Parameter]
public string ClassName { get; set; } = string.Empty;
private string ClassNameList
{ {
await base.OnInitializedAsync(); get
if (ParentRadioGroup.Disabled) Disabled = true; {
if (!InputAttributes.ContainsKey("disabled")) return new CssBuilder("radio-input")
InputAttributes.Add("disabled", Disabled); .AddClass(ClassName)
.Build();
} }
}
[Parameter]
public string LabelClassName { get; set; } = string.Empty;
private string LabelClassNameList
{
get
{
return new CssBuilder("radio-label")
.AddClass(LabelClassName)
.Build();
}
}
#endregion
} }

View File

@ -1,7 +1,5 @@
@using Connected.Models; @using Connected.Models;
@inherits InputBase;
<CascadingValue Value="this"> <CascadingValue Value="this">
<div> <div>
@if (!string.IsNullOrEmpty(Name)) @if (!string.IsNullOrEmpty(Name))

View File

@ -3,10 +3,32 @@
namespace Connected.Components; namespace Connected.Components;
public partial class RadioGroup public partial class RadioGroup
{ {
[Parameter, EditorRequired] #region Parameters
public string Name { get; set; }
/// <summary>
/// Radio group name. Mandatory! Used for proper radio button grouping
/// Options: any string will do
/// Default: 'radiogroup'
/// </summary>
[Parameter, EditorRequired]
public string? Name { get; set; } = "radiogroup";
/// <summary>
/// Used for globaly disabling radio button group and all the radios within
/// Options: true or false
/// Default: false
/// </summary>
[Parameter] [Parameter]
public RenderFragment ChildContent { get; set; } public bool Disabled { get; set; } = false;
/// <summary>
/// All the radiobuttons and other components inside radio group
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
#endregion
} }

View File

@ -4,19 +4,23 @@
@inherits InputBase; @inherits InputBase;
@if (Items is not null) @if (component_loaded)
{ {
@if (Items is not null)
{
<div class="@InputFieldClassList"> <div class="@InputFieldClassList">
<select type="textarea" style="height:0px;" @attributes=@InputAttributes> <select type="textarea"
</select> style="height:0px;"
@attributes=@InputAttributes></select>
@if (IsLabel) @if (IsLabel)
{ {
<label class="label-animated">@Label</label> <label class="label-animated">@Label</label>
} }
<span class="highlight"></span> <span class="highlight"></span>
<span class="bar">
</span> <span class="bar"></span>
<span class="input-glyph-wraper"> <span class="input-glyph-wraper">
@if (Clearable) @if (Clearable)
{ {
@ -35,7 +39,8 @@
} }
</span> </span>
<div class="drop-down"> <div class="drop-down">
<div class="dropdown-menu p-2" aria-labelledby="dropdownMenuButton"> <div class="dropdown-menu p-2"
aria-labelledby="dropdownMenuButton">
@if (EnableSearch) @if (EnableSearch)
{ {
<input type="text" <input type="text"
@ -43,14 +48,18 @@
class="dropdown-item" class="dropdown-item"
@bind-value="@SearchText" /> @bind-value="@SearchText" />
} }
@foreach (ValueType item in Items) @foreach (ValueType item in FilteredItems)
{ {
@if (item is not null) @if (item is not null)
{ {
<div class="dropdown-item" @onclick=@(()=>SetSelectedItem(@item))>@item.ToString()</div> <div class="dropdown-item"
@onclick=@(()=>SetSelectedItem(@item))>
@item.ToString()
</div>
} }
} }
</div> </div>
</div> </div>
</div> </div>
}
} }

View File

@ -1,21 +1,46 @@
using Connected.Models; using Connected.Models;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Connected.Components; namespace Connected.Components;
public partial class SimpleSelect<ValueType> : InputBase public partial class SimpleSelect<ValueType> : InputBase
{ {
private bool component_loaded = false;
#region Parameters
/// <summary>
/// Value that is currently selected in the dropdown. Used for @bind-Value
/// </summary>
[Parameter] [Parameter]
public ValueType Value { get; set; } public ValueType? Value { get; set; }
[Parameter] /// <summary>
public IEnumerable<ValueType> Items { get; set; } /// Collection of Items to work on (Filter). Filtered result is then shown in dropdown. If no filter is aplied, all the items are shown
/// </summary>
[Parameter, EditorRequired]
public ObservableCollection<ValueType>? Items { get; set; }
public IEnumerable<ValueType> OriginalItems { get; set; } /// <summary>
/// Collection of items from 'Items' filtered with 'SearchText'
/// </summary>
private List<ValueType>? FilteredItems { get; set; }
/// <summary>
/// Enable edit text search box for item filtering
/// Options: true or false
/// Default: true
/// </summary>
[Parameter] [Parameter]
public bool EnableSearch { get; set; } = true; public bool EnableSearch { get; set; } = true;
private string _searchText { get; set; } = string.Empty; private string _searchText { get; set; } = string.Empty;
/// <summary>
/// Search string provided by user
/// Options: any string
/// Default: string.Empty
/// </summary>
public string SearchText public string SearchText
{ {
get get
@ -29,48 +54,92 @@ public partial class SimpleSelect<ValueType> : InputBase
} }
} }
#endregion
#region Events, Methods
/// <summary>
/// Method for setting the item on select
/// </summary>
/// <param name="item">item that will be set as selected</param>
/// <returns>Methot returns nothing</returns>
private async Task SetSelectedItem(ValueType item) private async Task SetSelectedItem(ValueType item)
{ {
//DropDownClassToggle();
await ValueChanged.InvokeAsync(item); await ValueChanged.InvokeAsync(item);
} }
/// <summary>
/// Method for filtering items using 'SearchText' as filter
/// </summary>
private void FilterItems() private void FilterItems()
{
if (Items is not null)
{ {
if (string.IsNullOrEmpty(_searchText)) if (string.IsNullOrEmpty(_searchText))
{ {
Items = OriginalItems; SetItems();
} }
else else
{ {
Items = Items.Where(item => item.ToString().ToLower().Contains(_searchText.ToLower())); FilteredItems = Items.Where(item => item.ToString().ToLower().Contains(_searchText.ToLower())).ToList();
} }
StateHasChanged(); StateHasChanged();
} }
}
/// <summary>
/// Event triggered when value changes
/// </summary>
[Parameter] [Parameter]
public EventCallback<ValueType> ValueChanged { get; set; } public EventCallback<ValueType> ValueChanged { get; set; }
private async Task ChangeValueAsync(ChangeEventArgs args)
/// <summary>
/// Method for setting the FilteredItems collection keeping the original Item collection
/// </summary>
private void SetItems()
{ {
await ValueChanged.InvokeAsync((ValueType)Convert.ChangeType(args.Value, typeof(ValueType))); if (Items is not null)
{
FilteredItems = Items.ToList();
}
} }
protected override async Task OnParametersSetAsync() /// <summary>
/// Event triggered when the provided Items collection changes
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OriginalItems_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
SetItems();
OriginalItems = Items; FilterItems();
if (_searchText.Length>0) FilterItems();
await base.OnParametersSetAsync();
} }
#endregion
#region Lifecycle
/// <summary>
/// Initializing Collections and aplying Filters if provided
/// </summary>
/// <returns>Nothing gets returned</returns>
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
await base.OnInitializedAsync(); await base.OnInitializedAsync();
if (Required) if (Items is null)
{ {
if (InputAttributes.ContainsKey("required")) InputAttributes.Add("required", true); Items= new ObservableCollection<ValueType>();
} }
SetItems();
if (_searchText.Length > 0)
FilterItems();
Items.CollectionChanged += OriginalItems_CollectionChanged;
component_loaded = true;
StateHasChanged();
} }
#endregion
} }

View File

@ -3,27 +3,15 @@
@inherits InputBase; @inherits InputBase;
<div class="@InputFieldClassList"> <div class="@InputFieldClassList">
@if (NumOfRows==1) <input type="@InputType"
{
<input type="@inputType"
value="@Value" value="@Value"
placeholder="@Placeholder" placeholder="@Placeholder"
disabled="@Disabled" disabled="@Disabled"
readonly="@Readonly" readonly="@Readonly"
required="@Required"
@oninput=@ChangeValueAsync @oninput=@ChangeValueAsync
@attributes="@InputAttributes" /> @attributes="@InputAttributes" />
} else
{
<textarea type="textarea"
rows="@NumOfRows"
value="@Value"
placeholder="@Placeholder"
disabled="@Disabled"
readonly="@Readonly"
@oninput=@ChangeValueAsync
@attributes="@InputAttributes" />
}
<span class="highlight"></span> <span class="highlight"></span>
<span class="bar"></span> <span class="bar"></span>
@ -45,16 +33,15 @@
{ {
<span class="input-glyph button" @onclick="Clear"> <span class="input-glyph button" @onclick="Clear">
<Glyph SVG="@Icons.Material.Rounded.Dangerous" /> <Glyph SVG="@Icons.Material.Rounded.Dangerous" />
<!--<i class='bx bx-x-circle'></i>-->
</span> </span>
} }
@if (IsError) @if (IsError)
{ {
<span class="input-glyph error"> <span class="input-glyph error">
<Glyph SVG="@Icons.Material.Outlined.Error" Color="#D10000" /> <Glyph SVG="@Icons.Material.Outlined.Error" />
<!--<i class='bx bx-error-circle'></i>-->
</span> </span>
} }
</span> </span>
</span> </span>
</div> </div>

View File

@ -2,12 +2,26 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace Connected.Components; namespace Connected.Components;
public partial class TextInput: InputBase public partial class TextInput : InputBase
{ {
#region Parameters
/// <summary>
/// Setter for the TextInput if input is password field (for hiding characters)
/// Options: true or false
/// Default: false;
/// </summary>
[Parameter] [Parameter]
public bool IsPassword { get; set; } = false; public bool IsPassword { get; set; } = false;
/// <summary>
/// String for the component to use if TextInput is password field
/// </summary>
private string InputType => IsPassword ? "password" : "text";
/// <summary>
/// Number of rows
/// </summary>
[Parameter] [Parameter]
public int NumOfRows public int NumOfRows
{ {
@ -17,52 +31,45 @@ public partial class TextInput: InputBase
} }
set set
{ {
if (value < 1) _numberOfLines = Math.Max(1, value);
{
_numberOfLines = 1;
}
else
{
_numberOfLines = value;
}
} }
} }
private int _numberOfLines = 1; private int _numberOfLines = 1;
/// <summary>
/// Value of the TextInput. Used for @bind-Value
/// </summary>
[Parameter] [Parameter]
public string Value { get; set; } = string.Empty; public string Value { get; set; } = string.Empty;
private string inputType #endregion
{
get
{
if (IsPassword) return "password";
return "text";
}
}
#region Events, Methods
/// <summary>
/// Event triggered when value changes
/// </summary>
[Parameter] [Parameter]
public EventCallback<string> ValueChanged { get; set; } public EventCallback<string> ValueChanged { get; set; }
/// <summary>
/// Method that triggers oninput -> when value inside the component changes
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private async Task ChangeValueAsync(ChangeEventArgs args) private async Task ChangeValueAsync(ChangeEventArgs args)
{ {
await ValueChanged.InvokeAsync(args.Value.ToString()); await ValueChanged.InvokeAsync(args?.Value?.ToString());
} }
/// <summary>
/// Clear the value of the TextInput
/// </summary>
/// <returns></returns>
private async Task Clear() private async Task Clear()
{ {
await ValueChanged.InvokeAsync(string.Empty); await ValueChanged.InvokeAsync(string.Empty);
}
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (Required)
{
if (!InputAttributes.ContainsKey("required")) InputAttributes.Add("required", true);
}
} }
#endregion
} }

View File

@ -0,0 +1,21 @@
@inherits Button
<button type="button"
@onclick="@Clicked"
disabled=@Disabled
style="@StyleList"
class="@ClassList">
<div class="@ContentClassList">
<div style="align-items:center">
@if (GlyphPosition == Position.Top)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" />
}
@ChildContent
@if (GlyphPosition == Position.Bottom)
{
<Glyph SVG="@Glyph" Color="@GlyphColor" />
}
</div>
</div>
</button>

View File

@ -0,0 +1,120 @@
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
namespace Connected.Components;
public partial class ToggleGlyphButton: Button
{
#region Parameters
/// <summary>
/// Outline type of the button.
/// Options: true, false
/// Default: false
/// </summary>
[Parameter]
public bool Toggled { get; set; } = false;
/// <summary>
/// Glyph (Icon) inside the button.
/// Options: SVG string --> Icons
/// Default: string.Empty
/// </summary>
[Parameter, EditorRequired]
public string Glyph { get; set; } = string.Empty;
/// <summary>
/// Glyph (Icon) inside the button when tge .
/// Options: SVG string --> Icons
/// Default: string.Empty
/// </summary>
[Parameter, EditorRequired]
public string ToggledGlyph { get; set; } = string.Empty;
/// <summary>
/// Position of the glyph relative to button Text parameter. If Glyph parameter == string.Empty this parameter is ignored
/// Options: Position.[left,top,right,bottom]
/// Default: Position.left
/// </summary>
[Parameter]
public Position GlyphPosition { get; set; } = Position.Left;
/// <summary>
/// Color for the glyph. If Glyph parameter is empty this parameter is ignored
/// Options: Color.[Core,Primary,Secondary,Success,Info,Warning,Danger,White,Light,Dark]
/// Default: Color.Dark
/// </summary>
[Parameter]
public Color GlyphColor { get; set; } = Color.Dark;
/// <summary>
/// Color for the glyph. If Glyph parameter is empty this parameter is ignored
/// Options: Color.[Core,Primary,Secondary,Success,Info,Warning,Danger,White,Light,Dark]
/// Default: Color.Dark
/// </summary>
[Parameter]
public Color ToggledGlyphColor { get; set; } = Color.Dark;
#endregion
#region Events
/// <summary>
/// Button click event.
/// Options: any MouseEventCallback event
/// Default: empty
[Parameter]
public EventCallback<bool> ToggledChanged { get; set; }
protected async Task Clicked(MouseEventArgs e)
{
Toggled = !Toggled;
await ToggledChanged.InvokeAsync(Toggled);
}
#endregion
#region Styling
public string StyleList
{
get
{
return new StyleBuilder()
.AddStyle(base.Style)
.Build();
}
}
/// <summary>
/// Generated class list for button based on user parameters
/// </summary>
public string ClassList
{
get
{
return new CssBuilder("btn")
.AddClass("btn-" + Helper.GetEnumDescription<Size>(base.Size))
.AddClass("btn-" + Helper.GetEnumDescription<Color>(base.Color),!base.Outlined)
.AddClass("btn-outline-" + Helper.GetEnumDescription<Color>(base.Color), base.Outlined)
.AddClass(base.Class)
.Build();
}
}
/// <summary>
/// Generated class list for button based on user parameters
/// </summary>
public string ContentClassList
{
get
{
return new CssBuilder("")
.AddClass(base.ContentClass)
.Build();
}
}
#endregion
}

View File

@ -0,0 +1,17 @@
@using Connected.Models;
@inherits InputBase;
<label class="toggle-group" for="@Id">
<input class="toggle-input"
type="checkbox"
name="toggle"
disabled="@Disabled"
id="@Id"
checked="@Checked"
@onchange="@OnChange"
@attributes=@InputAttributes>
<div class="toggle-fill"></div>
<label for="@Id" class="toggle-label">@Label</label>
</label>

View File

@ -0,0 +1,27 @@
using Connected.Models;
using Microsoft.AspNetCore.Components;
namespace Connected.Components;
public partial class ToggleInput: InputBase
{
private bool _checked { get; set; }
[Parameter]
public bool? Checked
{
get => _checked;
set => _checked= (bool)value;
}
[Parameter, EditorRequired]
public string Id { get; set; }
[Parameter]
public EventCallback<bool> CheckedChanged { get; set; }
public async Task OnChange()
{
Checked = !Checked;
await CheckedChanged.InvokeAsync((bool)Checked);
}
}

View File

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Connected.Enums;
public enum ModalButtonType
{
OkButton,
CancelButton,
RegularButton
}

View File

@ -0,0 +1,18 @@
using System.ComponentModel;
namespace Connected.Enums;
public enum Target
{
[Description("_top")]
Top,
[Description("_parent")]
Parent,
[Description("_blank")]
Blank,
[Description("_self")]
Self,
}

View File

@ -30,16 +30,20 @@ public class InputBase : ComponentBase
[Parameter] [Parameter]
public bool Required { get; set; } = false; public bool Required { get; set; } = false;
public Dictionary<string, object> InputAttributes { get; set; } public Dictionary<string, object> InputAttributes { get; set; } = new();
/// <summary> /// <summary>
/// Show clear button. /// Disable input component
/// Options: true or false
/// Default: false
/// </summary> /// </summary>
[Parameter] [Parameter]
public bool Disabled { get; set; } = false; public bool Disabled { get; set; } = false;
/// <summary> /// <summary>
/// Show clear button. /// Make input component readonly
/// Options: true or false
/// Default: false
/// </summary> /// </summary>
[Parameter] [Parameter]
public bool Readonly { get; set; } = false; public bool Readonly { get; set; } = false;
@ -50,18 +54,11 @@ public class InputBase : ComponentBase
[Parameter] [Parameter]
public bool ShowCharacterCounter { get; set; } public bool ShowCharacterCounter { get; set; }
private string _errorText = string.Empty; /// <summary>
///
/// </summary>
[Parameter] [Parameter]
public string ErrorText { public string ErrorText { get; set; } = string.Empty;
get
{
return _errorText;
}
set
{
_errorText = value;
}
}
public bool IsError public bool IsError
{ {
@ -79,15 +76,16 @@ public class InputBase : ComponentBase
protected virtual async Task SetTextAsync(string text) protected virtual async Task SetTextAsync(string text)
{ {
if (Text != text) if (Text == text)
{ return;
Text = text; Text = text;
await TextChanged.InvokeAsync(text); await TextChanged.InvokeAsync(text);
} }
}
private string _helperText = string.Empty; private string _helperText = string.Empty;
[Parameter] [Parameter]
public string HelperText public string HelperText
{ {
@ -123,7 +121,7 @@ public class InputBase : ComponentBase
[Parameter] [Parameter]
public string Placeholder { get; set; } = string.Empty; public string Placeholder { get; set; } = string.Empty;
protected override async Task OnInitializedAsync() protected override void OnInitialized()
{ {
if (InputAttributes is null) InputAttributes = new(); if (InputAttributes is null) InputAttributes = new();
} }

View File

@ -0,0 +1,36 @@
using Connected.Enums;
using Connected.Services;
namespace Connected.Models.Modal;
public class ModalButton
{
public Event OnClickEvent { get; set; }
public ModalButtonType ModalButtonType { get; set; } = ModalButtonType.RegularButton;
public string ButtonText { get; set; }
public bool CloseDialogOnClick { get; set; } = true;
public ModalButton(Event OnClickEvent, string ButtonText, ModalButtonType ModalButtonType = ModalButtonType.RegularButton, bool CloseDialogOnClick = true)
{
this.OnClickEvent = OnClickEvent;
this.ButtonText = ButtonText;
this.ModalButtonType= ModalButtonType;
this.CloseDialogOnClick = CloseDialogOnClick;
}
public string GetButtonClass
{
get
{
switch (this.ModalButtonType)
{
case ModalButtonType.CancelButton:
return "btn-sm btn-core";
case ModalButtonType.OkButton:
return "btn-sm btn-info";
default:
return "btn-sm btn-secondary";
}
}
}
}

View File

@ -0,0 +1,12 @@
namespace Connected.Models.Modal;
public class Event
{
public Delegate Delegate;
public object[] args;
public Event(Delegate Delegate, object[] Args)
{
this.Delegate = Delegate;
args = Args;
}
}

View File

@ -0,0 +1,10 @@
namespace Connected.Models.Modal;
public class ModalOptions
{
public bool DisableEscKey { get; set; } = false;
public bool DisableBackdropClick { get; set; } = false;
public bool NoHeader { get; set; } = false;
}

View File

@ -1,4 +1,6 @@
using Microsoft.AspNetCore.Components; using Connected.Enums;
using Connected.Utilities;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop; using Microsoft.JSInterop;
namespace Connected; namespace Connected;
@ -24,8 +26,9 @@ internal class Navigation
/// Navigates to the specified url. /// Navigates to the specified url.
/// </summary> /// </summary>
/// <param name="url">The destination url (relative or absolute).</param> /// <param name="url">The destination url (relative or absolute).</param>
public async Task NavigateTo(string url, Target target=Target._self) public async Task NavigateTo(string url, Target t=Target.Self)
{ {
string target = Helper.GetEnumDescription<Target>(t);
if (!target.Equals("_self")) if (!target.Equals("_self"))
{ {
if (!url.Equals(_navigationManager.Uri)) if (!url.Equals(_navigationManager.Uri))
@ -110,10 +113,4 @@ internal class Navigation
} }
} }
enum Target
{
_self,
_blank,
_parent,
_top
}

View File

@ -0,0 +1,28 @@
using Connected.Components;
using Connected.Models.Modal;
using Microsoft.AspNetCore.Components;
namespace Connected.Services;
public class ModalDialogService
{
public event Action<string, RenderFragment, List<ModalButton>, ModalOptions> OnShow;
public event Action OnClose;
public void ShowDialog(string title, RenderFragment content, List<ModalButton> buttons, ModalOptions options)
{
OnShow?.Invoke(title, content, buttons, options);
}
public void ShowDialog(string title, MarkupString contentMarkup, List<ModalButton> buttons, ModalOptions options)
{
var content = new RenderFragment(x => x.AddContent(1, contentMarkup));
OnShow?.Invoke(title, content, buttons, options);
}
public void Close()
{
OnClose?.Invoke();
}
}

View File

@ -0,0 +1,9 @@
using Microsoft.Extensions.DependencyInjection;
namespace Connected.Services;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddModalDialogService(this IServiceCollection services)
=> services.AddScoped<ModalDialogService>();
}

View File

@ -135,12 +135,24 @@ public static class Helper
} }
} }
public static T? ConvertToType<T>(object variable)
{
try
{
if (variable is not null)
return (T)Convert.ChangeType(variable, typeof(T));
return default;
} catch
{
return default;
}
}
public static bool IsNumeric(string input) public static bool IsNumeric(string input)
{ {
try try
{ {
var number = Double.Parse(input); return Double.TryParse(input, out var number);
return true;
} }
catch catch
{ {