refactoring dropdown

This commit is contained in:
2025-03-01 21:25:52 +01:00
parent 8c9e163007
commit 512deb6eac
22 changed files with 227 additions and 106 deletions

View File

@@ -2,13 +2,15 @@
@using GameIdeas.BlazorApp.Layouts @using GameIdeas.BlazorApp.Layouts
@using GameIdeas.BlazorApp.Pages.Games.Header @using GameIdeas.BlazorApp.Pages.Games.Header
@using GameIdeas.BlazorApp.Shared.Components @using GameIdeas.BlazorApp.Shared.Components
@using GameIdeas.BlazorApp.Shared.Headers @using GameIdeas.BlazorApp.Shared.Layouts.Header
@using GameIdeas.Resources @using GameIdeas.Resources
@layout MainLayout @layout MainLayout
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle> <PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
<HeaderBase> <HeaderLayout>
<Body>
<HeaderGame /> <HeaderGame />
</HeaderBase> </Body>
</HeaderLayout>

View File

@@ -1,4 +1,3 @@
@using GameIdeas.BlazorApp.Shared.Headers 
@inherits HeaderBaseComponent

View File

@@ -1,8 +1,6 @@
using GameIdeas.BlazorApp.Shared.Headers;
namespace GameIdeas.BlazorApp.Pages.Games.Header; namespace GameIdeas.BlazorApp.Pages.Games.Header;
public partial class HeaderGame : HeaderBaseComponent public partial class HeaderGame
{ {
} }

View File

@@ -1,7 +1,7 @@
@using GameIdeas.Resources @using GameIdeas.Resources
@using Blazored.FluentValidation; @using Blazored.FluentValidation;
<div class="account-setting-container" tabindex="1001"> <div class="account-setting-container" tabindex="1000">
<div class="account-setting-content @(ContentVisile ? string.Empty : "invisible")"> <div class="account-setting-content @(ContentVisile ? string.Empty : "invisible")">
@if (!AuthentificationService.IsLogin) @if (!AuthentificationService.IsLogin)
{ {

View File

@@ -2,7 +2,7 @@ using GameIdeas.BlazorApp.Services;
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
namespace GameIdeas.BlazorApp.Shared.Headers; namespace GameIdeas.BlazorApp.Shared.Components.Account;
public partial class AccountSettings ( public partial class AccountSettings (
AuthentificationService AuthentificationService) AuthentificationService AuthentificationService)

View File

@@ -1,13 +0,0 @@
@typeparam TItem
<div @ref=Content class="dropdown-container" tabindex="1000"
@onfocusout=HandleDropdownAddFocusOut>
<div class="dropdown-content @(ContentVisile ? string.Empty : "invisible") @(Enum.GetName(Theme)?.ToLower())">
@foreach (var item in Items)
{
<div class="drowdown-element @(Enum.GetName(Theme)?.ToLower())" @onclick=HandleItemClicked>
@(LabelSelector != null ? LabelSelector.Invoke(item) : item?.ToString())
</div>
}
</div>
</div>

View File

@@ -1,36 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Dropdown;
public partial class DropdownContent<TItem>
{
[Parameter] public TItem? Value { get; set; }
[Parameter] public EventCallback<TItem?> ValueChanged { get; set; }
[Parameter] public IEnumerable<TItem> Items { get; set; } = [];
[Parameter] public Func<TItem, string>? LabelSelector { get; set; }
[Parameter] public DropdownTheme Theme { get; set; }
private bool ContentVisile = false;
private DateTime ContentLastFocusOut = DateTime.Now;
private ElementReference Content;
public async Task OpenAsync()
{
if (DateTime.Now - ContentLastFocusOut >= TimeSpan.FromSeconds(0.2))
{
await Content.FocusAsync();
ContentVisile = true;
}
}
public void Close() => ContentVisile = false;
private void HandleDropdownAddFocusOut()
{
ContentLastFocusOut = DateTime.Now;
ContentVisile = false;
}
private async Task HandleItemClicked() => await ValueChanged.InvokeAsync(Value);
}

View File

@@ -1,7 +0,0 @@
namespace GameIdeas.BlazorApp.Shared.Components.Dropdown;
public enum DropdownTheme
{
Navigation,
Account
}

View File

@@ -0,0 +1,15 @@
@typeparam TItem
<div class="select-element @(Enum.GetName(Theme)?.ToLower())" @onclick=HandleItemClicked>
<div class="selected">
@if (Value != null && Value.IsSelected)
{
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z" />
</svg>
}
</div>
<div class="select-label">
@Value?.Label
</div>
</div>

View File

@@ -0,0 +1,18 @@
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Select.Components;
public partial class SelectListElement<TItem>
{
[Parameter] public EventCallback<SelectElement<TItem>?> ValueChanged { get; set; }
[Parameter] public SelectElement<TItem>? Value { get; set; }
[Parameter] public SelectListTheme Theme { get; set; }
private async Task HandleItemClicked()
{
if (Value != null)
{
Value.IsSelected = true;
await ValueChanged.InvokeAsync(Value);
}
}
}

View File

@@ -0,0 +1,25 @@
.select-element {
width: fit-content;
}
.select-element:hover {
cursor: pointer;
}
.selected svg {
fill: var(--white)
}
/***** Navigation Theme *****/
.navigation .selected {
display: none;
}
.navigation .select-label:hover {
color: var(--light-grey);
}
/***** Sort Theme *****/
.sort .select-label:hover {
color: var(--light-grey);
}

View File

@@ -0,0 +1,8 @@
namespace GameIdeas.BlazorApp.Shared.Components.Select;
public class SelectElement<TItem>
{
public TItem? Item { get; set; }
public string? Label { get; set; }
public bool IsSelected { get; set; } = false;
}

View File

@@ -0,0 +1,32 @@
@using GameIdeas.BlazorApp.Shared.Components.Select.Components
@typeparam TItem
<div class="select-list" @onclick=HandleButtonClicked>
<div class="select-button">
@Button
</div>
<div @ref=Container class="select-container" tabindex="1000"
@onfocusout=HandleFocusOut>
<div class="select-content @(ContentVisile ? string.Empty : "invisible") @(Enum.GetName(Theme)?.ToLower())">
@foreach (var item in Headers)
{
<SelectListElement TItem="TItem"
Value="item"
ValueChanged="HandleHeaderClicked"
Theme="Theme"/>
}
@if (Headers.Any())
{
<span class="line"></span>
}
@foreach (var item in Items)
{
<SelectListElement TItem="TItem"
Value="item"
ValueChanged="HandleItemClicked"
Theme="Theme" />
}
</div>
</div>
</div>

View File

@@ -0,0 +1,64 @@
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Select;
public partial class SelectList<TItem>
{
[Parameter] public RenderFragment? Button { get; set; }
[Parameter] public TItem? Value { get; set; }
[Parameter] public EventCallback<TItem?> ValueChanged { get; set; }
[Parameter] public TItem? Header { get; set; }
[Parameter] public EventCallback<TItem?> HeaderChanged { get; set; }
[Parameter] public IEnumerable<SelectElement<TItem>> Items { get; set; } = [];
[Parameter] public IEnumerable<SelectElement<TItem>> Headers { get; set; } = [];
[Parameter] public SelectListTheme Theme { get; set; }
[Parameter] public bool AlignRight { get; set; }
private bool ContentVisile = false;
private DateTime ContentLastFocusOut = DateTime.Now;
private ElementReference Container;
public async Task OpenAsync()
{
if (DateTime.Now - ContentLastFocusOut >= TimeSpan.FromSeconds(0.2))
{
await Container.FocusAsync();
ContentVisile = true;
}
}
public void Close() => ContentVisile = false;
private async Task HandleButtonClicked() => await OpenAsync();
private void HandleFocusOut()
{
ContentLastFocusOut = DateTime.Now;
ContentVisile = false;
}
private async Task HandleItemClicked(SelectElement<TItem> selectedValue)
{
foreach (var item in Items)
{
item.IsSelected = false;
}
selectedValue.IsSelected = true;
Value = selectedValue.Item;
await ValueChanged.InvokeAsync(Value);
Close();
}
private async Task HandleHeaderClicked(SelectElement<TItem> selectedValue)
{
foreach (var header in Headers)
{
header.IsSelected = false;
}
selectedValue.IsSelected = true;
Header = selectedValue.Item;
await HeaderChanged.InvokeAsync(Header);
}
}

View File

@@ -1,4 +1,4 @@
.dropdown-content { .select-content {
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@@ -9,25 +9,28 @@
margin-top: 4px; margin-top: 4px;
} }
.drowdown-element {
width: fit-content;
}
.drowdown-element:hover {
cursor: pointer;
}
.invisible { .invisible {
display: none; display: none;
} }
.dropdown-content.navigation { .line {
margin: 0 6px;
border-bottom: 2px solid var(--light-grey);
}
/***** Navigation Theme *****/
.select-content.navigation {
gap: 4px; gap: 4px;
background: var(--violet); background: var(--violet);
box-shadow: var(--drop-shadow); box-shadow: var(--drop-shadow);
padding: 4px 6px; padding: 4px 6px;
} }
.drowdown-element.navigation:hover { /***** Sort Theme *****/
color: var(--light-grey); .select-content.sort {
gap: 4px;
background: var(--black);
box-shadow: var(--drop-shadow);
padding: 4px 6px;
} }

View File

@@ -0,0 +1,8 @@
namespace GameIdeas.BlazorApp.Shared.Components.Select;
public enum SelectListTheme
{
Navigation,
Sort,
Filter
}

View File

@@ -1,5 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Headers;
public class HeaderBaseComponent : ComponentBase;

View File

@@ -1,14 +1,18 @@
@using GameIdeas.BlazorApp.Pages.Games @using GameIdeas.BlazorApp.Pages.Games
@using GameIdeas.BlazorApp.Pages.Games.Models @using GameIdeas.BlazorApp.Pages.Games.Models
@using GameIdeas.BlazorApp.Shared.Components.Account
@using GameIdeas.BlazorApp.Shared.Components.Dropdown @using GameIdeas.BlazorApp.Shared.Components.Dropdown
@using GameIdeas.BlazorApp.Shared.Components.Select
@using GameIdeas.Resources @using GameIdeas.Resources
@inherits LayoutComponentBase
<div class="header-tab"> <div class="header-tab">
<div class="icon-container" @onclick="HandleIconClicked"> <div class="icon-container" @onclick="HandleIconClicked">
<img src="icon.png" alt="Game Ideas"> <img src="icon.png" alt="Game Ideas">
</div> </div>
@Header @Body
<div class="account-add-container"> <div class="account-add-container">
<div class="add-container"> <div class="add-container">
@@ -18,21 +22,21 @@
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" /> <path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg> </svg>
</div> </div>
<div class="second-button button" @onclick=HandleMoreButton> <SelectList TItem="AddType"
Items="SelectElements"
ValueChanged=HandleAddTypeClickedAsync
Theme="SelectListTheme.Navigation">
<Button>
<div class="second-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M1 3H23L12 22" /> <path d="M1 3H23L12 22" />
</svg> </svg>
</div> </div>
</div> </Button>
<DropdownContent @ref="DropdownAdd" </SelectList>
TItem="AddTypeParams"
ValueChanged=HandleAddTypeClickedAsync
Items="AddTypes"
LabelSelector="(addtype => addtype.Label)"
Theme="DropdownTheme.Navigation" />
</div> </div>
<div class="account-container"> <div class="account-container">
<div class="icon-container"> <div class="icon-container" @onclick=HandleAccountClicked>
<svg class="account-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="account-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" /> <path d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
</svg> </svg>

View File

@@ -1,15 +1,21 @@
using GameIdeas.BlazorApp.Pages.Games.Models; using GameIdeas.BlazorApp.Pages.Games.Models;
using GameIdeas.BlazorApp.Shared.Components.Account;
using GameIdeas.BlazorApp.Shared.Components.Dropdown; using GameIdeas.BlazorApp.Shared.Components.Dropdown;
using GameIdeas.BlazorApp.Shared.Components.Select;
using GameIdeas.Resources; using GameIdeas.Resources;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Headers; namespace GameIdeas.BlazorApp.Shared.Layouts.Header;
public partial class HeaderBase public partial class HeaderLayout : LayoutComponentBase
{ {
[Parameter] public HeaderBaseComponent? Header { get; set; }
[Parameter] public EventCallback<AddType> AddTypeChanged { get; set; } [Parameter] public EventCallback<AddType> AddTypeChanged { get; set; }
private readonly IEnumerable<SelectElement<AddType>> SelectElements = [
new SelectElement<AddType> { Item = AddType.Manual, Label = ResourcesKey.ManualAdd },
new SelectElement<AddType> { Item = AddType.Auto, Label = ResourcesKey.AutoAdd }
];
private DropdownContent<AddTypeParams>? DropdownAdd; private DropdownContent<AddTypeParams>? DropdownAdd;
private readonly IEnumerable<AddTypeParams> AddTypes = private readonly IEnumerable<AddTypeParams> AddTypes =
[ [
@@ -34,9 +40,9 @@ public partial class HeaderBase
AccountSettings?.Close(); AccountSettings?.Close();
} }
private async Task HandleAddTypeClickedAsync(AddTypeParams value) private async Task HandleAddTypeClickedAsync(AddType value)
{ {
await AddTypeChanged.InvokeAsync(value.AddType); await AddTypeChanged.InvokeAsync(value);
} }
private void HandleAccountClicked() private void HandleAccountClicked()

View File

@@ -1,7 +1,7 @@
using FluentValidation; using FluentValidation;
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
namespace GameIdeas.BlazorApp.Shared.Headers; namespace GameIdeas.BlazorApp.Shared.Layouts.Header;
public class LoginValidator : AbstractValidator<LoginDto> public class LoginValidator : AbstractValidator<LoginDto>
{ {