Add multiple select component

This commit is contained in:
Maxime Adler
2025-03-11 15:00:52 +01:00
parent 3ba79fdf03
commit 7d8f1c9544
17 changed files with 163 additions and 75 deletions

View File

@@ -35,6 +35,12 @@
<SearchInput @bind-Text=GameFilterParams.SearchName />
<MultipleSelectList TItem="string"
Items="Plateforms"
@bind-Values=GameFilterParams.Plateforms
Theme="SelectListTheme.Filter"/>
</div>
</EditForm>

View File

@@ -1,5 +1,5 @@
using GameIdeas.BlazorApp.Pages.Games.Models;
using GameIdeas.BlazorApp.Shared.Components.Select;
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.Shared.Dto;
using GameIdeas.Shared.Enum;
using Microsoft.AspNetCore.Components;
@@ -24,6 +24,13 @@ public partial class GameFilter
new() { Item = game => game?.ReleaseDate, Label = "Date de parution" }
];
private readonly IEnumerable<SelectElement<string>> Plateforms = [
new() { Item = "Steam", Label = "Steam" },
new() { Item = "GOG", Label = "GOG" },
new() { Item = "Epic games", Label = "Epic games" },
new() { Item = "Ubisoft", Label = "Ubisoft" },
];
private EditContext? EditContext;
protected override void OnInitialized()

View File

@@ -8,4 +8,7 @@ public class GameFilterParams
public SortType? SortType { get; set; }
public Func<GameDto?, object?>? SortProperty { get; set; }
public string? SearchName { get; set; }
public IEnumerable<string>? Plateforms { get; set; }
public IEnumerable<string>? Genres { get; set; }
}

View File

@@ -7,6 +7,7 @@
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5172",
"launchUrl": "http://localhost:5172/Games",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -17,6 +18,7 @@
"launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7060;http://localhost:5172",
"launchUrl": "http://localhost:7060/Games",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@@ -1,7 +1,9 @@
<EditForm EditContext="EditContext">
<div class="search-container">
<InputText class="search-field"
@bind-Value=Text />
<div class="search-container">
<input type="text"
class="search-field"
@bind=@Text
@bind:event="oninput"
@bind:after=HandleTextChanged />
@if (!string.IsNullOrEmpty(Text))
{
@@ -12,7 +14,7 @@
</div>
}
<div type="submit" class="search-icon">
<div class="search-icon @(Enum.GetName(Icon)?.ToLower())" @onclick=HandleSearchClicked>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
@if (Icon == SearchInputIcon.Search)
{
@@ -20,10 +22,9 @@
}
else if (Icon == SearchInputIcon.Dropdown)
{
<path style="fill: var(--violet)" d="M1 3H23L12 22" />
<path d="M1 3H23L12 22" />
}
</svg>
</div>
</div>
</EditForm>
</div>

View File

@@ -1,6 +1,4 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using System.Threading.Tasks;
namespace GameIdeas.BlazorApp.Shared.Components.Search;
@@ -8,23 +6,32 @@ public partial class SearchInput
{
[Parameter] public string? Text { get; set; }
[Parameter] public EventCallback<string> TextChanged { get; set; }
[Parameter] public EventCallback ClearClicked { get; set; }
[Parameter] public EventCallback SearchClicked { get; set; }
[Parameter] public SearchInputIcon Icon { get; set; }
private EditContext? EditContext;
protected override void OnInitialized()
{
Text = string.Empty;
EditContext = new EditContext(Text);
}
EditContext.OnFieldChanged += async (s, e) =>
public void SetText(string str)
{
Text = str;
}
private async Task HandleTextChanged()
{
await TextChanged.InvokeAsync(Text);
};
}
private void HandleClearClicked()
private async Task HandleClearClicked()
{
Text = string.Empty;
await ClearClicked.InvokeAsync();
}
private async Task HandleSearchClicked()
{
await SearchClicked.InvokeAsync();
}
}

View File

@@ -51,3 +51,8 @@
.search-icon svg {
fill: var(--white);
}
.search-icon.dropdown svg {
fill: var(--violet);
transform: scale(.8, .5);
}

View File

@@ -1,3 +1,4 @@
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Select.Components;
@@ -11,6 +12,7 @@ public partial class SelectListElement<TItem>
{
if (Value != null)
{
Value.IsSelected = true;
await ValueChanged.InvokeAsync(Value);
}
}

View File

@@ -28,15 +28,14 @@
.navigation {
padding: 4px 8px;
}
.navigation .selected {
display: none;
}
.navigation:hover {
background: var(--violet-selected);
}
.navigation .selected {
display: none;
}
/***** Sort Theme *****/
.sort {
padding: 2px 6px;
@@ -46,9 +45,23 @@
background: var(--low-white);
}
.sort .select-label {
.sort .select-label {
text-wrap: nowrap;
margin-right: 6px;
}
/***** Filter Theme *****/
.filter {
padding: 2px 6px;
}
.filter:hover {
background: var(--low-white);
}
.filter .select-label {
text-wrap: nowrap;
margin-right: 6px;
}

View File

@@ -1,4 +1,4 @@
namespace GameIdeas.BlazorApp.Shared.Components.Select;
namespace GameIdeas.BlazorApp.Shared.Components.Select.Models;
public class SelectElement<TItem>
{

View File

@@ -1,4 +1,4 @@
namespace GameIdeas.BlazorApp.Shared.Components.Select;
namespace GameIdeas.BlazorApp.Shared.Components.Select.Models;
public enum SelectListTheme
{

View File

@@ -2,13 +2,15 @@
@using GameIdeas.BlazorApp.Shared.Components.Select.Components
@typeparam TItem
<div class="select-list" @onclick=HandleButtonClicked>
<div class="select-button">
<SearchInput Icon="SearchInputIcon.Dropdown"
Text="Text"
TextChanged="HandleTextChanged"/>
<div class="select-list">
<div class="select-button" @onfocusin=HandleFocusIn>
<SearchInput @ref=SearchInput
Icon="SearchInputIcon.Dropdown"
TextChanged="HandleTextChanged"
ClearClicked="HandleTextChanged"
SearchClicked="Open" />
</div>
<div @ref=Container @onfocusout=HandleFocusOut
<div @onfocusout=HandleFocusOut
class="select-container @(AlignRight ? "align-right" : "")"
tabindex="1000">

View File

@@ -1,3 +1,5 @@
using GameIdeas.BlazorApp.Shared.Components.Search;
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Select;
@@ -11,41 +13,31 @@ public partial class MultipleSelectList<TItem>
[Parameter] public bool AlignRight { get; set; }
private bool ContentVisile = false;
private DateTime ContentLastFocusOut = DateTime.Now;
private ElementReference Container;
private string? Text;
private SearchInput? SearchInput;
public async Task OpenAsync()
{
if (DateTime.Now - ContentLastFocusOut >= TimeSpan.FromSeconds(0.2))
{
await Container.FocusAsync();
ContentVisile = true;
}
}
public void Open() => ContentVisile = true;
public void Close() => ContentVisile = false;
private async Task HandleButtonClicked() => await OpenAsync();
private void HandleFocusOut() => Close();
private void HandleFocusOut()
{
ContentLastFocusOut = DateTime.Now;
ContentVisile = true;
}
private void HandleFocusIn() => Open();
private async Task HandleItemClicked(SelectElement<TItem> selectedValue)
{
selectedValue.IsSelected = !selectedValue.IsSelected;
Values = Items.Where(x => x.IsSelected && x.Item != null).Select(x => x.Item!);
Text = string.Join(", ", Values);
SearchInput?.SetText(string.Join(", ", Values));
await ValuesChanged.InvokeAsync(Values);
}
private void HandleTextChanged(string args)
private void HandleTextChanged()
{
foreach (var item in Items)
{
item.IsSelected = false;
}
Close();
}
}

View File

@@ -1 +1,49 @@
@import "SelectList.razor.css";
.select-list {
position: relative;
}
.select-container {
margin-top: 4px;
position: absolute;
}
.align-right {
right: 0;
}
.select-content {
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: var(--small-radius);
animation-name: fade-in;
animation-duration: 0.4s;
}
.line {
margin: 2px 6px;
border-bottom: 2px solid var(--low-white);
}
/***** Navigation Theme *****/
.select-content.navigation {
background: var(--violet);
box-shadow: var(--drop-shadow);
}
/***** Sort Theme *****/
.select-content.sort {
background: var(--semi-black);
box-shadow: var(--drop-shadow);
padding: 4px 0;
}
/***** Filter Theme *****/
.select-content.filter {
background: var(--semi-black);
box-shadow: var(--drop-shadow);
padding: 4px 0;
min-width: 150px;
}

View File

@@ -1,3 +1,4 @@
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Select;
@@ -34,7 +35,7 @@ public partial class SelectList<TItem>
private void HandleFocusOut()
{
ContentLastFocusOut = DateTime.Now;
ContentVisile = true;
ContentVisile = false;
}
private async Task HandleItemClicked(SelectElement<TItem> selectedValue)

View File

@@ -38,4 +38,3 @@
box-shadow: var(--drop-shadow);
padding: 4px 0;
}

View File

@@ -1,6 +1,6 @@
using GameIdeas.BlazorApp.Pages.Games.Models;
using GameIdeas.BlazorApp.Shared.Components.Account;
using GameIdeas.BlazorApp.Shared.Components.Select;
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.Resources;
using Microsoft.AspNetCore.Components;