Merge pull request 'features/rewrite/dropdown' (#14) from features/rewrite/dropdown into features/rewrite/main

Reviewed-on: #14

Requires property comments and style fine-tuning.
pull/15/head^2
Matija Koželj 2 years ago
commit bf60457369

@ -10,6 +10,7 @@
</packageRestore> </packageRestore>
<packageSources> <packageSources>
<add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" /> <add key="NuGet official package source" value="https://api.nuget.org/v3/index.json" />
<add key="Tom PIT Connected GIT" value="https://git.tompit.com/api/packages/Tom-PIT/nuget/index.json" />
</packageSources> </packageSources>
<disabledPackageSources /> <disabledPackageSources />
</configuration> </configuration>

@ -7,8 +7,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.*" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.*-*" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.*" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.*-*" PrivateAssets="all" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -22,9 +22,4 @@
<Watch Remove="..\..\src\**\bin\**" /> <Watch Remove="..\..\src\**\bin\**" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Watch Remove="Program.cs" />
</ItemGroup>
</Project> </Project>

@ -0,0 +1,18 @@
@page "/demo/components/dropdown"
@namespace Connected.Components.Showcase.Runner
@using Connected.Components;
<Dropdown Items=@Items ItemToKey=@((e) => e.Value.ToString()) @bind-SelectedItems="@SelectedItems" AllowMultiple=true>
<OptionTemplate>
@context.Name
</OptionTemplate>
<SelectedValueTemplate>
@context.Name
</SelectedValueTemplate>
</Dropdown>
<div>
@string.Join(", ", SelectedItems.Select(e=> e.Value))
</div>

@ -0,0 +1,29 @@
using System.Collections.ObjectModel;
namespace Connected.Components.Showcase.Runner;
public partial class DropdownDemo
{
private ObservableCollection<TestObject> SelectedItems { get; set; } = new();
private ObservableCollection<TestObject> Items { get; set; } = new ObservableCollection<TestObject>
{
new TestObject
{
Name= "Simple",
Value = 1
},
new TestObject
{
Name = "Simpler",
Value = 2
}
};
public class TestObject
{
public string Name { get; set; }
public long Value { get; set; }
}
}

@ -6,7 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connected.Components.Showcase.Runner</title> <title>Connected.Components.Showcase.Runner</title>
<base href="/" /> <base href="/" />
<link href="css/app.css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/Connected.Components/Connected.Components.min.css" rel="stylesheet" /> <link href="_content/Connected.Components/Connected.Components.min.css" rel="stylesheet" />
<script src="_content/Connected.Components/Connected.Components.min.js"></script> <script src="_content/Connected.Components/Connected.Components.min.js"></script>

@ -0,0 +1,48 @@
@typeparam T;
@inherits Connected.Models.InputBase;
<div class="@InputFieldClassList">
<select type="textarea" @onfocus="(()=> Open())" @onblur="(()=> Close())">
@if (SelectedItems.Any())
{
<option selected>
@foreach (var item in SelectedItems)
{
@SelectedValueTemplate?.Invoke(item)
}
</option>
}
</select>
<span class="highlight"></span><span class="bar"></span>
<label class="label-animated">@Label</label>
<div class="input-helper-text">@HelperText</div>
<div class="input-error-text">@ErrorText</div>
<span class="input-glyph-wraper">
<span class="input-glyph button">
<Glyph SVG="@Icons.Material.Outlined.Cancel" class="icon-root svg-icon" Click="_ => { SelectedItems.Clear(); Close(); }" />
</span>
<span class="input-glyph">
<Glyph SVG="@Icons.Material.Filled.ArrowDropDown" class="icon-root svg-icon icon-size-md" Click="()=> Toggle()" />
</span>
<span class="input-glyph error">
<Glyph SVG="@Icons.Material.Outlined.ErrorOutline" class="icon-root svg-icon" />
</span>
</span>
<TransitionAnimator TransitionDuration="5000" TransitionInClass="drop-down fade-in" TransitionOutClass="drop-down fade-out" Visible=@DropdownOpen>
<div class="backdrop d-sm-none">
</div>
<div class="dropdown-menu">
@foreach (var item in Items)
{
<div class="dropdown-item @(IsItemSelected(item)?"active":"")"
@onclick="(async () => await OptionSelected(item))">
@OptionTemplate?.Invoke(item)
</div>
}
</div>
</TransitionAnimator>
</div>

@ -0,0 +1,112 @@
using Microsoft.AspNetCore.Components;
using System.Collections.ObjectModel;
using Connected.Models;
using System.Collections.Immutable;
namespace Connected.Components;
public partial class Dropdown<T> : InputBase, IAsyncDisposable
{
[Parameter, EditorRequired]
public ObservableCollection<T> Items { get; set; } = new ObservableCollection<T>();
[Parameter]
public RenderFragment<T>? OptionTemplate { get; set; }
[Parameter]
public RenderFragment<T>? SelectedValueTemplate { get; set; }
[Parameter, EditorRequired]
public Func<T, string> ItemToKey { get; set; } = default!;
[Parameter]
public ObservableCollection<T> SelectedItems { get; set; } = new();
[Parameter]
public EventCallback<ObservableCollection<T>> SelectedItemsChanged { get; set; }
[Parameter]
public bool AllowMultiple { get; set; }
private bool DropdownOpen { get; set; }
private bool IsItemSelected(T item)
{
return SelectedItems.Contains(item);
}
public void Open()
{
DropdownOpen = true;
}
public void Close()
{
DropdownOpen = false;
}
public void Toggle()
{
DropdownOpen = !DropdownOpen;
}
protected override void OnInitialized()
{
base.OnInitialized();
OptionTemplate ??= (T item) => (builder) =>
{
builder.OpenElement(0, "div");
builder.SetKey(item);
builder.AddContent(1, item?.ToString());
builder.CloseElement();
};
SelectedValueTemplate ??= (T item) => (builder) =>
{
builder.OpenElement(0, "span");
builder.SetKey(item);
builder.AddContent(1, item?.ToString());
builder.CloseElement();
};
}
protected override void OnParametersSet()
{
base.OnParametersSet();
Items.CollectionChanged += CollectionChanged;
SelectedItems.CollectionChanged += CollectionChanged;
}
private void CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (sender == SelectedItems)
SelectedItemsChanged.InvokeAsync(SelectedItems);
StateHasChanged();
}
private async Task OptionSelected(T item)
{
if (AllowMultiple)
{
if (SelectedItems.Contains(item))
{
SelectedItems.Remove(item);
return;
}
}
else
{
SelectedItems.Clear();
}
SelectedItems.Add(item);
}
public async ValueTask DisposeAsync()
{
Items.CollectionChanged -= CollectionChanged;
SelectedItems.CollectionChanged -= CollectionChanged;
}
}

@ -0,0 +1,107 @@
@using Connected.Utilities;
<div class="@ClassList" @ontransitionend="TransitionEnd" @onanimationend="TransitionEnd">
@if (ContentVisible)
{
@ChildContent
}
</div>
<style>
.transition-container-component {
transition: all @(TransitionDuration)ms;
}
</style>
@code {
private bool _visible = false;
/// <summary>
/// The class to append to the container while content is visible and transitioning in
/// </summary>
[Parameter, EditorRequired]
public string? TransitionInClass { get; set; }
/// <summary>
/// The class to append to the container while content is transitioning out and hidden
/// </summary>
[Parameter, EditorRequired]
public string? TransitionOutClass { get; set; }
/// <summary>
/// The class to append to the container to control transitions
/// </summary>
[Parameter]
public string? TransitionContainerClass { get; set; } = "transition-container-component";
/// <summary>
/// Controls the visibility of the child content
/// </summary>
[Parameter, EditorRequired]
public bool Visible { get => _visible; set => StartTransition(value); }
/// <summary>
/// The content to show/hide
/// </summary>
[Parameter, EditorRequired]
public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Indicates a transition has ended. Useful for visual cleanup.
/// </summary>
[Parameter]
public EventCallback TransitionEnded { get; set; }
/// <summary>
/// The duration of transitions in milliseconds.
/// </summary>
[Parameter]
public int TransitionDuration { get; set; } = 2000;
private bool TransitioningIn { get; set; }
private bool TransitioningOut { get; set; }
private bool ContentVisible { get; set; }
private CssBuilder ClassList
{
get
{
return new CssBuilder(TransitionContainerClass)
.AddClass(TransitionInClass, TransitioningIn)
.AddClass(TransitionOutClass, TransitioningOut);
}
}
private void StartTransition(bool visible)
{
if (visible == Visible)
return;
_visible = visible;
TransitioningIn = visible;
TransitioningOut = !visible;
if (visible)
ContentVisible = true;
StateHasChanged();
}
private void TransitionEnd()
{
ContentVisible = Visible;
StateHasChanged();
TransitionEnded.InvokeAsync();
}
protected override void OnInitialized()
{
base.OnInitialized();
ContentVisible = Visible;
}
}

@ -28,7 +28,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="7.0.3" /> <PackageReference Include="Microsoft.AspNetCore.Components" Version="7.0.*-*" />
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.*-*" /> <PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="7.0.*-*" />
</ItemGroup> </ItemGroup>

@ -0,0 +1,14 @@
using Microsoft.AspNetCore.Components;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Connected.Components;
[EventHandler("ontransitionend", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: false)]
[EventHandler("onanimationend", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: false)]
public static class EventHandlers
{
}

@ -1,22 +1,18 @@
@use "../globals/" as *; @use "../globals/" as *;
@use "../util/" as *; @use "../util/" as *;
.drop-down { .drop-down {
position: fixed; position: fixed;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
min-width: 80%; min-width: 80%;
height: auto;
max-height: max-content;
width: 80%; width: 80%;
height: 0; opacity: 0;
max-height: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
opacity: 0;
pointer-events: none;
z-index: $dropdown-zindex; z-index: $dropdown-zindex;
list-style: none; list-style: none;
background-color: #fff; background-color: #fff;
@ -25,6 +21,35 @@
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
.drop-down.fade-in {
animation: fade-in 0.3s ease-in-out forwards;
}
.drop-down.fade-out {
animation: fade-out 0.3s ease-in-out forwards;
}
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@include breakpoint(sm) { @include breakpoint(sm) {
.drop-down { .drop-down {
position: absolute; position: absolute;
@ -39,10 +64,9 @@
} }
.backdrop { .backdrop {
display: none;
opacity: 0;
z-index: $backdrop-zindex; z-index: $backdrop-zindex;
position: fixed; position: fixed;
display: block;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;

@ -1,10 +1,6 @@
@use "../util" as *; @use "../util" as *;
@use "../globals" as *; @use "../globals" as *;
$trans-time: 300ms; $trans-time: 300ms;
$width: 100%; $width: 100%;
@ -199,29 +195,8 @@ form{
display:none display:none
} }
/*SELECT*/
select {
appearance: none;
&:focus-within ~ .drop-down {
height: auto;
max-height: max-content;
opacity: 1;
pointer-events: initial;
}
&:focus-within ~ .backdrop {
opacity: 1;
display: block;
}
}
/*form group-alt*/ /*form group-alt*/
.form-group-alt { .form-group-alt {
--height: 2.5rem; --height: 2.5rem;

Loading…
Cancel
Save