diff --git a/nuget.config b/nuget.config
index 05dc31f..2611e88 100644
--- a/nuget.config
+++ b/nuget.config
@@ -10,6 +10,7 @@
+
\ No newline at end of file
diff --git a/src/Connected.Components.Showcase.Runner/Connected.Components.Showcase.Runner.csproj b/src/Connected.Components.Showcase.Runner/Connected.Components.Showcase.Runner.csproj
index f6e0064..7de2092 100644
--- a/src/Connected.Components.Showcase.Runner/Connected.Components.Showcase.Runner.csproj
+++ b/src/Connected.Components.Showcase.Runner/Connected.Components.Showcase.Runner.csproj
@@ -7,8 +7,8 @@
-
-
+
+
@@ -22,9 +22,4 @@
-
-
-
-
-
diff --git a/src/Connected.Components.Showcase.Runner/Pages/DropdownDemo.razor b/src/Connected.Components.Showcase.Runner/Pages/DropdownDemo.razor
new file mode 100644
index 0000000..c06ae02
--- /dev/null
+++ b/src/Connected.Components.Showcase.Runner/Pages/DropdownDemo.razor
@@ -0,0 +1,18 @@
+@page "/demo/components/dropdown"
+
+@namespace Connected.Components.Showcase.Runner
+
+@using Connected.Components;
+
+ e.Value.ToString()) @bind-SelectedItems="@SelectedItems" AllowMultiple=true>
+
+ @context.Name
+
+
+ @context.Name
+
+
+
+
+ @string.Join(", ", SelectedItems.Select(e=> e.Value))
+
diff --git a/src/Connected.Components.Showcase.Runner/Pages/DropdownDemo.razor.cs b/src/Connected.Components.Showcase.Runner/Pages/DropdownDemo.razor.cs
new file mode 100644
index 0000000..75f36ad
--- /dev/null
+++ b/src/Connected.Components.Showcase.Runner/Pages/DropdownDemo.razor.cs
@@ -0,0 +1,29 @@
+using System.Collections.ObjectModel;
+
+namespace Connected.Components.Showcase.Runner;
+
+public partial class DropdownDemo
+{
+ private ObservableCollection SelectedItems { get; set; } = new();
+
+ private ObservableCollection Items { get; set; } = new ObservableCollection
+ {
+ 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; }
+ }
+}
\ No newline at end of file
diff --git a/src/Connected.Components.Showcase.Runner/wwwroot/index.html b/src/Connected.Components.Showcase.Runner/wwwroot/index.html
index 3a6c8ca..c905d6e 100644
--- a/src/Connected.Components.Showcase.Runner/wwwroot/index.html
+++ b/src/Connected.Components.Showcase.Runner/wwwroot/index.html
@@ -6,7 +6,6 @@
Connected.Components.Showcase.Runner
-
diff --git a/src/Connected.Components/Components/Dropdown.razor b/src/Connected.Components/Components/Dropdown.razor
new file mode 100644
index 0000000..99a445e
--- /dev/null
+++ b/src/Connected.Components/Components/Dropdown.razor
@@ -0,0 +1,48 @@
+@typeparam T;
+@inherits Connected.Models.InputBase;
+
+
\ No newline at end of file
diff --git a/src/Connected.Components/Components/Dropdown.razor.cs b/src/Connected.Components/Components/Dropdown.razor.cs
new file mode 100644
index 0000000..2c2780c
--- /dev/null
+++ b/src/Connected.Components/Components/Dropdown.razor.cs
@@ -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 : InputBase, IAsyncDisposable
+{
+ [Parameter, EditorRequired]
+ public ObservableCollection Items { get; set; } = new ObservableCollection();
+
+ [Parameter]
+ public RenderFragment? OptionTemplate { get; set; }
+
+ [Parameter]
+ public RenderFragment? SelectedValueTemplate { get; set; }
+
+ [Parameter, EditorRequired]
+ public Func ItemToKey { get; set; } = default!;
+
+ [Parameter]
+ public ObservableCollection SelectedItems { get; set; } = new();
+
+ [Parameter]
+ public EventCallback> 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;
+ }
+}
\ No newline at end of file
diff --git a/src/Connected.Components/Components/TransitionAnimator.razor b/src/Connected.Components/Components/TransitionAnimator.razor
new file mode 100644
index 0000000..399cfd1
--- /dev/null
+++ b/src/Connected.Components/Components/TransitionAnimator.razor
@@ -0,0 +1,107 @@
+@using Connected.Utilities;
+
+
+ @if (ContentVisible)
+ {
+ @ChildContent
+ }
+
+
+
+
+@code {
+ private bool _visible = false;
+
+ ///
+ /// The class to append to the container while content is visible and transitioning in
+ ///
+ [Parameter, EditorRequired]
+ public string? TransitionInClass { get; set; }
+
+ ///
+ /// The class to append to the container while content is transitioning out and hidden
+ ///
+ [Parameter, EditorRequired]
+ public string? TransitionOutClass { get; set; }
+
+ ///
+ /// The class to append to the container to control transitions
+ ///
+ [Parameter]
+ public string? TransitionContainerClass { get; set; } = "transition-container-component";
+
+ ///
+ /// Controls the visibility of the child content
+ ///
+ [Parameter, EditorRequired]
+ public bool Visible { get => _visible; set => StartTransition(value); }
+
+ ///
+ /// The content to show/hide
+ ///
+ [Parameter, EditorRequired]
+ public RenderFragment? ChildContent { get; set; }
+
+ ///
+ /// Indicates a transition has ended. Useful for visual cleanup.
+ ///
+ [Parameter]
+ public EventCallback TransitionEnded { get; set; }
+
+ ///
+ /// The duration of transitions in milliseconds.
+ ///
+ [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;
+ }
+}
diff --git a/src/Connected.Components/Connected.Components.csproj b/src/Connected.Components/Connected.Components.csproj
index 68de09a..163f7f4 100644
--- a/src/Connected.Components/Connected.Components.csproj
+++ b/src/Connected.Components/Connected.Components.csproj
@@ -28,7 +28,7 @@
-
+
diff --git a/src/Connected.Components/Events/EventHandlers.cs b/src/Connected.Components/Events/EventHandlers.cs
new file mode 100644
index 0000000..146a4ee
--- /dev/null
+++ b/src/Connected.Components/Events/EventHandlers.cs
@@ -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
+{
+}
diff --git a/src/Connected.Components/Styles/components/_dropdown.scss b/src/Connected.Components/Styles/components/_dropdown.scss
index 71014c8..5ac4e40 100644
--- a/src/Connected.Components/Styles/components/_dropdown.scss
+++ b/src/Connected.Components/Styles/components/_dropdown.scss
@@ -1,30 +1,55 @@
@use "../globals/" as *;
@use "../util/" as *;
+.drop-down {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ min-width: 80%;
+ height: auto;
+ max-height: max-content;
+ width: 80%;
+ opacity: 0;
+ display: flex;
+ flex-direction: column;
+ z-index: $dropdown-zindex;
+ list-style: none;
+ background-color: #fff;
+ box-shadow: $base-box-shadow;
+ border-radius: $border-radius-lg;
+ 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;
+}
-.drop-down {
- position: fixed;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- min-width: 80%;
- width: 80%;
- height: 0;
- max-height: 0;
- display: flex;
- flex-direction: column;
- opacity: 0;
- pointer-events: none;
- z-index: $dropdown-zindex;
- list-style: none;
- background-color: #fff;
- box-shadow: $base-box-shadow;
- border-radius: $border-radius-lg;
- transition: all 0.3s ease-in-out;
+@keyframes fade-in {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes fade-out {
+ 0% {
+ opacity: 1;
+ }
+
+ 100% {
+ opacity: 0;
+ }
}
+
@include breakpoint(sm) {
.drop-down {
position: absolute;
@@ -39,17 +64,16 @@
}
.backdrop {
- display: none;
- opacity: 0;
- z-index: $backdrop-zindex;
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- overflow: hidden;
- background-color: rgba(0, 0, 0, 0.5);
- transition: all 0.3s ease-in-out;
+ z-index: $backdrop-zindex;
+ position: fixed;
+ display: block;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ overflow: hidden;
+ background-color: rgba(0, 0, 0, 0.5);
+ transition: all 0.3s ease-in-out;
}
.dropdown-header {
diff --git a/src/Connected.Components/Styles/components/_inputs.scss b/src/Connected.Components/Styles/components/_inputs.scss
index 0057e5a..935b75a 100644
--- a/src/Connected.Components/Styles/components/_inputs.scss
+++ b/src/Connected.Components/Styles/components/_inputs.scss
@@ -1,10 +1,6 @@
@use "../util" as *;
@use "../globals" as *;
-
-
-
-
$trans-time: 300ms;
$width: 100%;
@@ -199,29 +195,8 @@ form{
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 {
--height: 2.5rem;