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 3d030ec..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,11 +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; + +
+ + + + + + +
@HelperText
+
@ErrorText
+ + + + + + + + + + + + + +
+
+ +
+
\ 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..fa821b2 --- /dev/null +++ b/src/Connected.Components/Components/Dropdown.razor.cs @@ -0,0 +1,109 @@ +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] + 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;