Refactor: Update style and dropdown (#10)

Co-authored-by: Maxime Adler <madler@sqli.com>
Reviewed-on: #10
This commit was merged in pull request #10.
This commit is contained in:
2025-04-08 16:10:01 +02:00
parent 003f2547a3
commit 3d5056ce91
38 changed files with 426 additions and 303 deletions

View File

@@ -1,12 +1,13 @@
.page { .page {
display: flex;
flex-direction: column;
height: 100%; height: 100%;
overflow: hidden;
} }
.orb { .orb {
position: absolute; position: absolute;
border-radius: 100%; border-radius: 100%;
z-index: -999; z-index: var(--index-orb);
} }
.green { .green {
@@ -42,5 +43,5 @@
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
z-index: -1000; z-index: var(--index-background);
} }

View File

@@ -27,7 +27,7 @@ public partial class AdvancedGameFilter
new() { Item = "Electronic Arts", Label = "Electronic Arts" }, new() { Item = "Electronic Arts", Label = "Electronic Arts" },
new() { Item = "Ubisoft", Label = "Ubisoft" }, new() { Item = "Ubisoft", Label = "Ubisoft" },
new() { Item = "Activision Blizzard", Label = "Activision Blizzard" }, new() { Item = "Activision Blizzard", Label = "Activision Blizzard" },
new() { Item = "Bethesda", Label = "Bethesda" }, new() { Item = "Bethesda", Label = "Bethesda" }
]; ];
private readonly IEnumerable<SelectElement<string>> Developers = [ private readonly IEnumerable<SelectElement<string>> Developers = [

View File

@@ -5,7 +5,10 @@
padding-right: 20px; padding-right: 20px;
padding-left: 10px; padding-left: 10px;
height: 100%; height: 100%;
border-left: 2px solid var(--light-grey); box-sizing: border-box;
width: 240px;
border-left: 2px solid var(--line);
z-index: var(--index-component);
} }
.duplicate { .duplicate {

View File

@@ -2,11 +2,9 @@
@using GameIdeas.BlazorApp.Shared.Components.Select @using GameIdeas.BlazorApp.Shared.Components.Select
@using GameIdeas.BlazorApp.Shared.Components.Select.Models @using GameIdeas.BlazorApp.Shared.Components.Select.Models
@using GameIdeas.BlazorApp.Shared.Components.SliderRange @using GameIdeas.BlazorApp.Shared.Components.SliderRange
@using GameIdeas.BlazorApp.Shared.Models
@using GameIdeas.Shared.Dto @using GameIdeas.Shared.Dto
@using GameIdeas.BlazorApp.Pages.Games.Models
<EditForm EditContext="EditContext">
<div class="form-filter"> <div class="form-filter">
<SelectList TItem="Func<GameDto, object>" <SelectList TItem="Func<GameDto, object>"
Headers="SortTypes" Headers="SortTypes"
@@ -14,13 +12,11 @@
@bind-Value=GameFilterParams!.SortProperty @bind-Value=GameFilterParams!.SortProperty
HeaderChanged=HandleSortTypeChanged HeaderChanged=HandleSortTypeChanged
Theme="SelectListTheme.Sort"> Theme="SelectListTheme.Sort">
<Button>
<div class="square-button"> <div class="square-button">
<svg class="sort-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="sort-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M10,13H22V11H10M10,19H22V17H10M10,7H22V5H10M6,7H8.5L5,3.5L1.5,7H4V17H1.5L5,20.5L8.5,17H6V7Z" /> <path d="M10,13H22V11H10M10,19H22V17H10M10,7H22V5H10M6,7H8.5L5,3.5L1.5,7H4V17H1.5L5,20.5L8.5,17H6V7Z" />
</svg> </svg>
</div> </div>
</Button>
</SelectList> </SelectList>
<div class="square-button" @onclick="@(() => HandleDisplayClicked(DisplayType.List))"> <div class="square-button" @onclick="@(() => HandleDisplayClicked(DisplayType.List))">
@@ -62,7 +58,5 @@
@bind-Min=GameFilterParams.MinRating /> @bind-Min=GameFilterParams.MinRating />
</div> </div>
</div> </div>
</EditForm>

View File

@@ -1,6 +1,6 @@
using GameIdeas.BlazorApp.Pages.Games.Models;
using GameIdeas.BlazorApp.Shared.Components.Select.Models; using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.BlazorApp.Shared.Components.SliderRange; using GameIdeas.BlazorApp.Shared.Components.SliderRange;
using GameIdeas.BlazorApp.Shared.Models;
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
using GameIdeas.Shared.Enum; using GameIdeas.Shared.Enum;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
@@ -10,7 +10,7 @@ namespace GameIdeas.BlazorApp.Pages.Games.Filter;
public partial class GameFilter public partial class GameFilter
{ {
[Parameter] public GameFilterParams? GameFilterParams { get; set; } [Parameter] public GameFilterParams GameFilterParams { get; set; } = new();
[Parameter] public EventCallback<GameFilterParams> GameFilterParamsChanged { get; set; } [Parameter] public EventCallback<GameFilterParams> GameFilterParamsChanged { get; set; }
[Parameter] public DisplayType DisplayType { get; set; } [Parameter] public DisplayType DisplayType { get; set; }
[Parameter] public EventCallback<DisplayType> DisplayTypeChanged { get; set; } [Parameter] public EventCallback<DisplayType> DisplayTypeChanged { get; set; }
@@ -45,7 +45,7 @@ public partial class GameFilter
protected override void OnInitialized() protected override void OnInitialized()
{ {
EditContext = new EditContext(GameFilterParams!); EditContext = new EditContext(GameFilterParams);
EditContext.OnFieldChanged += async (s, e) => EditContext.OnFieldChanged += async (s, e) =>
{ {
await GameFilterParamsChanged.InvokeAsync(GameFilterParams); await GameFilterParamsChanged.InvokeAsync(GameFilterParams);
@@ -54,7 +54,7 @@ public partial class GameFilter
private void HandleSortTypeChanged(Func<GameDto?, object?> getHeader) private void HandleSortTypeChanged(Func<GameDto?, object?> getHeader)
{ {
GameFilterParams!.SortType = (SortType?)getHeader(null) ?? SortType.Ascending; GameFilterParams.SortType = (SortType?)getHeader(null) ?? SortType.Ascending;
} }
private async Task HandleDisplayClicked(DisplayType displayType) private async Task HandleDisplayClicked(DisplayType displayType)

View File

@@ -3,6 +3,7 @@
flex-direction: row; flex-direction: row;
gap: 8px; gap: 8px;
align-items: center; align-items: center;
z-index: var(--index-component);
} }
.search-container { .search-container {
@@ -26,7 +27,7 @@
width: 28px; width: 28px;
min-width: 28px; min-width: 28px;
border-radius: var(--small-radius); border-radius: var(--small-radius);
background: var(--black); background: var(--input-primary);
overflow: hidden; overflow: hidden;
} }
@@ -40,7 +41,7 @@
.square-button svg:hover { .square-button svg:hover {
cursor: pointer; cursor: pointer;
background: var(--low-white); background: var(--input-selected);
} }
.selected-icon { .selected-icon {

View File

@@ -1,20 +1,18 @@
@page "/Games" @page "/Games"
@using GameIdeas.BlazorApp.Layouts @using GameIdeas.BlazorApp.Layouts
@using GameIdeas.BlazorApp.Pages.Games.Filter @using GameIdeas.BlazorApp.Pages.Games.Filter
@using GameIdeas.BlazorApp.Pages.Games.Header
@using GameIdeas.BlazorApp.Shared.Components @using GameIdeas.BlazorApp.Shared.Components
@using GameIdeas.BlazorApp.Shared.Layouts.Header
@using GameIdeas.Resources @using GameIdeas.Resources
@layout MainLayout @layout MainLayout
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle> <PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
<HeaderLayout> <GamesHeader>
<Body>
<GameFilter @bind-DisplayType=DisplayType <GameFilter @bind-DisplayType=DisplayType
@bind-GameFilterParams=GameFilterParams /> @bind-GameFilterParams=GameFilterParams />
</Body> </GamesHeader>
</HeaderLayout>
<div class="container"> <div class="container">
<div class="content"> <div class="content">

View File

@@ -1,5 +1,5 @@
using GameIdeas.BlazorApp.Pages.Games.Filter; using GameIdeas.BlazorApp.Pages.Games.Filter;
using GameIdeas.BlazorApp.Pages.Games.Models; using GameIdeas.BlazorApp.Shared.Models;
namespace GameIdeas.BlazorApp.Pages.Games; namespace GameIdeas.BlazorApp.Pages.Games;

View File

@@ -1,5 +1,6 @@
.container { .container {
margin-top: 20px; margin-top: 20px;
margin-bottom: 10px;
justify-content: space-between; justify-content: space-between;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -7,5 +8,5 @@
} }
.content { .content {
z-index: var(--index-content);
} }

View File

@@ -1,18 +1,18 @@
@using GameIdeas.BlazorApp.Pages.Games @using GameIdeas.BlazorApp.Pages.Games
@using GameIdeas.BlazorApp.Pages.Games.Models
@using GameIdeas.BlazorApp.Shared.Components.Account @using GameIdeas.BlazorApp.Shared.Components.Account
@using GameIdeas.BlazorApp.Shared.Components.Select @using GameIdeas.BlazorApp.Shared.Components.Select
@using GameIdeas.BlazorApp.Shared.Components.Select.Models @using GameIdeas.BlazorApp.Shared.Components.Select.Models
@using GameIdeas.BlazorApp.Shared.Models
@using GameIdeas.Resources @using GameIdeas.Resources
@inherits LayoutComponentBase @inherits ComponentBase
<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>
@Body @ChildContent
<div class="account-add-container"> <div class="account-add-container">
<div class="add-container"> <div class="add-container">
@@ -27,13 +27,11 @@
ValueChanged=HandleAddTypeClickedAsync ValueChanged=HandleAddTypeClickedAsync
Theme="SelectListTheme.Navigation" Theme="SelectListTheme.Navigation"
AlignRight=true> AlignRight=true>
<Button>
<div class="second-button 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>
</Button>
</SelectList> </SelectList>
</div> </div>
</div> </div>

View File

@@ -1,14 +1,16 @@
using GameIdeas.BlazorApp.Pages.Games.Models;
using GameIdeas.BlazorApp.Shared.Components.Account; using GameIdeas.BlazorApp.Shared.Components.Account;
using GameIdeas.BlazorApp.Shared.Components.Select.Models; using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.BlazorApp.Shared.Models;
using GameIdeas.Resources; using GameIdeas.Resources;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Layouts.Header; namespace GameIdeas.BlazorApp.Pages.Games.Header;
public partial class HeaderLayout : LayoutComponentBase public partial class GamesHeader : ComponentBase
{ {
[Parameter] public EventCallback<AddType> AddTypeChanged { get; set; } [Parameter] public EventCallback<AddType> AddTypeChanged { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
private readonly IEnumerable<SelectElement<AddType>> SelectElements = [ private readonly IEnumerable<SelectElement<AddType>> SelectElements = [
new SelectElement<AddType> { Item = AddType.Manual, Label = ResourcesKey.ManualAdd }, new SelectElement<AddType> { Item = AddType.Manual, Label = ResourcesKey.ManualAdd },

View File

@@ -33,7 +33,6 @@
} }
.add-container { .add-container {
overflow: visible;
margin-right: 40px; margin-right: 40px;
} }
@@ -42,7 +41,6 @@
flex-direction: row; flex-direction: row;
background: var(--violet); background: var(--violet);
border-radius: var(--small-radius); border-radius: var(--small-radius);
overflow: visible;
} }
.button { .button {
@@ -54,11 +52,15 @@
} }
.first-button { .first-button {
border-right: 2px solid var(--line-black); border-right: 2px solid var(--violet-selected);
border-radius: var(--small-radius) 0 0 var(--small-radius);
overflow: hidden;
} }
.second-button .button-icon { .second-button .button-icon {
padding: 6px; padding: 6px;
border-radius: 0 var(--small-radius) var(--small-radius) 0;
overflow: hidden;
} }
.button-icon { .button-icon {
@@ -66,10 +68,10 @@
} }
.button-icon:hover { .button-icon:hover {
background: var(--line-black); background: var(--violet-selected);
cursor: pointer; cursor: pointer;
} }
.account-icon { .account-icon {
fill: var(--light-grey); fill: var(--line);
} }

View File

@@ -1,7 +0,0 @@
namespace GameIdeas.BlazorApp.Pages.Games.Models;
public enum AddType
{
Manual,
Auto
}

View File

@@ -1,7 +0,0 @@
namespace GameIdeas.BlazorApp.Pages.Games.Models;
public enum DisplayType
{
Card,
List
}

View File

@@ -4,10 +4,11 @@
position: fixed; position: fixed;
animation-name: fade-in; animation-name: fade-in;
animation-duration: 0.4s; animation-duration: 0.4s;
border: 2px solid var(--low-white); border: 2px solid var(--input-selected);
background: var(--semi-black); background: var(--dropdown-content);
right: 10px; right: 10px;
margin-top: 4px; margin-top: 4px;
z-index: var(--index-floating);
} }
.invisible { .invisible {
@@ -30,8 +31,8 @@
} }
::deep .input-text { ::deep .input-text {
background: var(--low-white); background: var(--input-selected);
border: 2px solid var(--low-white); border: 2px solid var(--input-selected);
border-radius: var(--small-radius); border-radius: var(--small-radius);
padding: 6px; padding: 6px;
color: var(--white); color: var(--white);
@@ -64,7 +65,7 @@
width: 18px; width: 18px;
height: 18px; height: 18px;
border-radius: 50%; border-radius: 50%;
border: 3px solid var(--line-black); border: 3px solid rgba(0, 0, 0, 0.2);
border-top-color: var(--white); border-top-color: var(--white);
animation: loading 1s linear infinite; animation: loading 1s linear infinite;
justify-self: center; justify-self: center;
@@ -77,7 +78,7 @@
.line { .line {
margin: 0 6px; margin: 0 6px;
border-bottom: 2px solid var(--light-grey); border-bottom: 2px solid var(--line);
} }
.settings-element { .settings-element {
@@ -88,7 +89,7 @@
} }
.settings-element:hover { .settings-element:hover {
background: var(--light-grey) background: var(--line)
} }

View File

@@ -0,0 +1,55 @@
@if (IsVisible)
{
<div class="backdrop-filter @Color.ToString().ToLower()" @onclick="HandleBackdropClicked"></div>
}
@code {
[Inject] private IJSRuntime Js { get; set; } = default!;
[Parameter] public EventCallback OnClick { get; set; }
[Parameter] public bool AllowBodyScroll { get; set; }
[Parameter] public BackdropFilterColor Color { get; set; } = BackdropFilterColor.Overlay;
[Parameter] public bool CloseOnClick { get; set; } = true;
[Parameter] public bool IsVisible { get; set; }
public async Task Show()
{
IsVisible = true;
await HandleBodyOverflow();
}
public async Task Hide()
{
IsVisible = false;
await HandleBodyOverflow();
}
private async Task HandleBodyOverflow()
{
try
{
if (AllowBodyScroll) return;
if (IsVisible)
{
await Js.InvokeVoidAsync("setBodyOverflow", "hidden");
}
else
{
await Js.InvokeVoidAsync("setBodyOverflow", "auto");
}
}
catch (Exception)
{
// ignored because js not loaded
}
}
private async Task HandleBackdropClicked()
{
if (!CloseOnClick) return;
await Hide();
await OnClick.InvokeAsync();
}
}

View File

@@ -0,0 +1,13 @@
.backdrop-filter {
position: fixed;
inset: 0;
z-index: var(--index-backdrop);
}
.backdrop-filter.overlay {
background-color: var(--grey-filter);
}
.backdrop-filter.transparent {
background-color: transparent;
}

View File

@@ -0,0 +1,3 @@
window.setBodyOverflow = (overflow) => {
document.getElementsByTagName('html')[0].style.overflow = overflow;
}

View File

@@ -0,0 +1,7 @@
namespace GameIdeas.BlazorApp.Shared.Components.BackdropFilter;
public enum BackdropFilterColor
{
Overlay,
Transparent
}

View File

@@ -1,31 +1,25 @@
<div class="search-container"> @using GameIdeas.Shared.Constants
<input type="text" <div class="search-container">
<input @ref=InputText
type="text"
class="search-field" class="search-field"
placeholder="@Placeholder" placeholder="@Placeholder"
@bind=@Text @bind=@Text
@bind:event="oninput" @bind:event="oninput"
@bind:after=HandleTextChanged /> @bind:after=HandleTextChanged
@onfocusin=HandleFocusIn/>
<div class="buttons">
@if (!string.IsNullOrEmpty(Text)) @if (!string.IsNullOrEmpty(Text))
{ {
<div class="clear-icon" @onclick=HandleClearClicked> <div class="clear-icon" @onclick=HandleClearClicked>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> @ClearIcon
<path d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</svg>
</div> </div>
} }
<div class="search-icon @(Enum.GetName(Icon)?.ToLower())" @onclick=HandleSearchClicked> <div class="search-icon @(Enum.GetName(Icon)?.ToLower())" @onclick=HandleSearchClicked>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> @GetSearchIcon()
@if (Icon == SearchInputIcon.Search) </div>
{
<path d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z" />
}
else if (Icon == SearchInputIcon.Dropdown)
{
<path d="M1 3H23L12 22" />
}
</svg>
</div> </div>
</div> </div>

View File

@@ -1,3 +1,4 @@
using GameIdeas.Shared.Constants;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Search; namespace GameIdeas.BlazorApp.Shared.Components.Search;
@@ -9,8 +10,12 @@ public partial class SearchInput
[Parameter] public EventCallback<string> TextChanged { get; set; } [Parameter] public EventCallback<string> TextChanged { get; set; }
[Parameter] public EventCallback ClearClicked { get; set; } [Parameter] public EventCallback ClearClicked { get; set; }
[Parameter] public EventCallback SearchClicked { get; set; } [Parameter] public EventCallback SearchClicked { get; set; }
[Parameter] public EventCallback FocusIn { get; set; }
[Parameter] public SearchInputIcon Icon { get; set; } [Parameter] public SearchInputIcon Icon { get; set; }
private ElementReference InputText;
private readonly MarkupString ClearIcon = new(Icons.Search.Clear);
protected override void OnInitialized() protected override void OnInitialized()
{ {
Text = string.Empty; Text = string.Empty;
@@ -36,5 +41,20 @@ public partial class SearchInput
{ {
await TextChanged.InvokeAsync(Text); await TextChanged.InvokeAsync(Text);
await SearchClicked.InvokeAsync(); await SearchClicked.InvokeAsync();
await InputText.FocusAsync();
}
private async Task HandleFocusIn()
{
await FocusIn.InvokeAsync();
}
private MarkupString GetSearchIcon()
{
return Icon switch
{
SearchInputIcon.Dropdown => new MarkupString(Icons.Search.Triangle),
SearchInputIcon.Search => new MarkupString(Icons.Search.Glass),
_ => new MarkupString()
};
} }
} }

View File

@@ -6,10 +6,16 @@
padding-left: 8px; padding-left: 8px;
padding-right: 2px; padding-right: 2px;
border-radius: var(--small-radius); border-radius: var(--small-radius);
background: var(--black); background: var(--input-primary);
overflow: hidden; overflow: hidden;
align-items: center; align-items: center;
width: 140px; z-index: var(--index-component);
justify-content: space-between;
}
.buttons {
display: flex;
align-items: center;
} }
::deep .search-field { ::deep .search-field {
@@ -33,7 +39,7 @@
cursor: pointer; cursor: pointer;
} }
.clear-icon svg { ::deep .clear-icon svg {
fill: var(--white); fill: var(--white);
} }
@@ -48,11 +54,11 @@
cursor: pointer; cursor: pointer;
} }
.search-icon svg { ::deep .search-icon svg {
fill: var(--white); fill: var(--white);
} }
.search-icon.dropdown svg { ::deep .search-icon.dropdown svg {
fill: var(--violet); fill: var(--violet);
transform: scale(.8, .5); transform: scale(.8, .5);
} }

View File

@@ -10,13 +10,6 @@ public partial class SelectListElement<TItem>
[Parameter] public SelectListTheme Theme { get; set; } [Parameter] public SelectListTheme Theme { get; set; }
private async Task HandleItemClicked() private async Task HandleItemClicked()
{ {
if (Value == null)
{
return;
}
Value.IsSelected = true;
StateHasChanged();
await ValueChanged.InvokeAsync(Value); await ValueChanged.InvokeAsync(Value);
} }
} }

View File

@@ -1,12 +1,10 @@
.select-element { .select-element {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: fit-content; width: auto;
width: 100%;
gap: 6px; gap: 6px;
height: 20px; height: 20px;
align-items: center; align-items: center;
z-index: 501;
} }
.select-element:hover { .select-element:hover {
@@ -18,6 +16,7 @@
min-width: 12px; min-width: 12px;
height: 12px; height: 12px;
min-height: 12px; min-height: 12px;
border: 1px solid var(--line);
} }
.selected svg { .selected svg {
@@ -43,7 +42,7 @@
} }
.sort:hover { .sort:hover {
background: var(--low-white); background: var(--input-selected);
} }
.sort .select-label { .sort .select-label {
@@ -57,7 +56,7 @@
} }
.filter:hover { .filter:hover {
background: var(--low-white); background: var(--input-selected);
} }
.filter .select-label { .filter .select-label {
@@ -65,27 +64,13 @@
margin-right: 6px; margin-right: 6px;
} }
/***** Filter Theme *****/ /***** Advanced Filter Theme *****/
.filter {
padding: 2px 6px;
}
.filter:hover {
background: var(--low-white);
}
.filter .select-label {
text-wrap: nowrap;
margin-right: 6px;
}
/***** Filter Theme *****/
.advancedfilter { .advancedfilter {
padding: 2px 6px; padding: 2px 6px;
} }
.advancedfilter:hover { .advancedfilter:hover {
background: var(--low-white); background: var(--input-selected);
} }
.advancedfilter .select-label { .advancedfilter .select-label {

View File

@@ -1,21 +1,19 @@
@using GameIdeas.BlazorApp.Shared.Components.Search @using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
@using GameIdeas.BlazorApp.Shared.Components.Search
@using GameIdeas.BlazorApp.Shared.Components.Select.Components @using GameIdeas.BlazorApp.Shared.Components.Select.Components
@typeparam TItem @typeparam TItem
<div class="select-list" tabindex="1001" @ref="BaseElement"> <div class="select-list">
<div class="select-button @(Enum.GetName(Theme)?.ToLower())" @onfocusin=HandleFocusIn @onfocusout=HandleFocusOut> <div class="select-button @(Enum.GetName(Theme)?.ToLower())">
<SearchInput @ref=SearchInput <SearchInput @ref=SearchInput
Icon="SearchInputIcon.Dropdown" Icon="SearchInputIcon.Dropdown"
Placeholder="@Placeholder" Placeholder="@Placeholder"
TextChanged="HandleTextChanged" TextChanged="HandleTextChanged"
ClearClicked="HandleTextChanged" ClearClicked="HandleTextChanged"
SearchClicked="HandleSearchClicked" /> SearchClicked="HandleSearchClicked"
FocusIn="HandleTextFocusIn"/>
</div> </div>
<div class="select-container @(AlignRight ? "align-right" : "")" <div class="select-container">
tabindex="1000"
@ref=ContentElement
@onfocusin=HandleContentFocusIn
@onfocusout=HandleContentFocusOut>
@if (IsContentOpen) @if (IsContentOpen)
{ {
@@ -32,3 +30,6 @@
</div> </div>
</div> </div>
<BackdropFilter AllowBodyScroll=true CloseOnClick=true Color="BackdropFilterColor.Transparent"
IsVisible=IsContentOpen OnClick="HandleContentClosed" />

View File

@@ -1,11 +1,13 @@
using GameIdeas.BlazorApp.Shared.Components.Search; using GameIdeas.BlazorApp.Shared.Components.Search;
using GameIdeas.BlazorApp.Shared.Components.Select.Models; using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace GameIdeas.BlazorApp.Shared.Components.Select; namespace GameIdeas.BlazorApp.Shared.Components.Select;
public partial class MultipleSelectList<TItem> public partial class MultipleSelectList<TItem>
{ {
[Inject] IJSRuntime JS { get; set; } = default!;
[Parameter] public IEnumerable<TItem>? Values { get; set; } [Parameter] public IEnumerable<TItem>? Values { get; set; }
[Parameter] public EventCallback<IEnumerable<TItem>?> ValuesChanged { get; set; } [Parameter] public EventCallback<IEnumerable<TItem>?> ValuesChanged { get; set; }
[Parameter] public IEnumerable<SelectElement<TItem>> Items { get; set; } = []; [Parameter] public IEnumerable<SelectElement<TItem>> Items { get; set; } = [];
@@ -13,60 +15,51 @@ public partial class MultipleSelectList<TItem>
[Parameter] public bool AlignRight { get; set; } [Parameter] public bool AlignRight { get; set; }
[Parameter] public string? Placeholder { get; set; } [Parameter] public string? Placeholder { get; set; }
private bool IsContentOpen private bool IsContentOpen = false;
{
get => InputFocus || ContentFocus;
}
private bool InputFocus = false;
private bool ContentFocus = false;
private SearchInput? SearchInput; private SearchInput? SearchInput;
private ElementReference ContentElement;
private ElementReference BaseElement;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await JS.InvokeVoidAsync("addResizeListener");
await base.OnAfterRenderAsync(firstRender);
}
private async Task HandleItemClicked(SelectElement<TItem> selectedValue) private async Task HandleItemClicked(SelectElement<TItem> selectedValue)
{ {
selectedValue.IsSelected = !selectedValue.IsSelected;
Values = Items.Where(x => x.IsSelected && x.Item != null).Select(x => x.Item!); Values = Items.Where(x => x.IsSelected && x.Item != null).Select(x => x.Item!);
SearchInput?.SetText(string.Join(", ", Values)); SearchInput?.SetText(string.Join(", ", Values));
await ValuesChanged.InvokeAsync(Values); await ValuesChanged.InvokeAsync(Values);
StateHasChanged();
} }
private async Task HandleTextChanged() private async Task HandleTextChanged()
{ {
await BaseElement.FocusAsync(); IsContentOpen = false;
foreach (var item in Items)
{
item.IsSelected = false;
} }
private void HandleFocusIn() Values = Items.Where(x => x.IsSelected && x.Item != null).Select(x => x.Item!);
{ await ValuesChanged.InvokeAsync(Values);
InputFocus = true;
} }
private void HandleContentFocusIn() private void HandleSearchClicked()
{ {
ContentFocus = true; IsContentOpen = !IsContentOpen;
} }
private void HandleContentClosed()
private async Task HandleContentFocusOut()
{ {
await Task.Delay(TimeSpan.FromSeconds(0.3)); IsContentOpen = false;
ContentFocus = false;
} }
private void HandleTextFocusIn()
private async Task HandleFocusOut()
{ {
await Task.Delay(TimeSpan.FromSeconds(0.3)); IsContentOpen = true;
InputFocus = false;
}
private async Task HandleSearchClicked()
{
if (!IsContentOpen)
{
await ContentElement.FocusAsync();
}
else
{
await BaseElement.FocusAsync();
}
} }
} }

View File

@@ -3,63 +3,62 @@
} }
.select-container { .select-container {
overflow: auto;
right: 0;
min-width: 100%;
margin-top: 4px; margin-top: 4px;
position: absolute; position: absolute;
z-index: 500; z-index: var(--index-dropdown);
} border-radius: var(--small-radius);
.align-right {
right: 0;
} }
.select-content { .select-content {
overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-radius: var(--small-radius);
animation-name: fade-in; animation-name: fade-in;
animation-duration: 0.4s; animation-duration: 0.4s;
z-index: 500; background: var(--dropdown-content);
box-shadow: var(--drop-shadow);
padding: 4px 0;
}
.select-button {
z-index: var(--index-component)
} }
.line { .line {
margin: 2px 6px; margin: 2px 6px;
border-bottom: 2px solid var(--low-white); border-bottom: 2px solid var(--input-selected);
}
/***** 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;
}
/***** AdvanceFilter Theme *****/
.select-content.advancedfilter {
background: var(--light-grey);
box-shadow: var(--drop-shadow);
padding: 4px 0;
min-width: 150px;
} }
::deep .select-button.advancedfilter .search-container { ::deep .select-button.advancedfilter .search-container {
height: 24px; height: 24px;
width: 210px; background: var(--input-secondary);
border: 2px solid var(--low-white); }
::deep .select-button.advancedfilter .search-container input::placeholder {
color: #bbb;
}
/* width */
.select-container::-webkit-scrollbar {
width: 10px;
}
/* Track */
.select-container::-webkit-scrollbar-track {
background: var(--input-secondary);
border-radius: 0 var(--small-radius) var(--small-radius) 0;
}
/* Handle */
.select-container::-webkit-scrollbar-thumb {
background: #555;
border-radius: var(--small-radius);
}
/* Handle on hover */
.select-container::-webkit-scrollbar-thumb:hover {
background: #777;
} }

View File

@@ -0,0 +1,19 @@
function offset(el) {
var rect = el.getBoundingClientRect(),
scrollLeft = window.scrollY || document.documentElement.scrollLeft,
scrollTop = window.scrollX || document.documentElement.scrollTop;
return { top: rect.top + scrollTop, left: rect.left + scrollLeft }
}
function resizeSelectContent() {
const height = window.innerHeight;
const selects = document.getElementsByClassName('select-container');
for (var i = 0; i < selects.length; i++) {
selects[i].style.maxHeight = height - offset(selects[i]).top - 10 + "px";
}
}
window.addResizeListener = () => {
resizeSelectContent();
window.addEventListener('resize', resizeSelectContent);
};

View File

@@ -1,15 +1,14 @@
@using GameIdeas.BlazorApp.Shared.Components.Select.Components @using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
@using GameIdeas.BlazorApp.Shared.Components.Select.Components
@typeparam TItem @typeparam TItem
<div class="select-list" @onclick=HandleButtonClicked> <div class="select-list">
<div class="select-button"> <div class="select-button" @onclick=HandleButtonClicked>
@Button @ChildContent
</div> </div>
<div @ref=Container @onfocusout=HandleFocusOut
class="select-container @(AlignRight ? "align-right" : "")"
tabindex="1000">
@if (ContentVisile) <div class="select-container @(AlignRight ? "align-right" : "")">
@if (IsContentOpen)
{ {
<div class="select-content @(Enum.GetName(Theme)?.ToLower())"> <div class="select-content @(Enum.GetName(Theme)?.ToLower())">
@foreach (var item in Headers) @foreach (var item in Headers)
@@ -35,3 +34,6 @@
</div> </div>
</div> </div>
<BackdropFilter AllowBodyScroll=true CloseOnClick=true Color="BackdropFilterColor.Transparent"
IsVisible=IsContentOpen OnClick="HandleContentClosed" />

View File

@@ -5,7 +5,7 @@ namespace GameIdeas.BlazorApp.Shared.Components.Select;
public partial class SelectList<TItem> public partial class SelectList<TItem>
{ {
[Parameter] public RenderFragment? Button { get; set; } [Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public TItem? Value { get; set; } [Parameter] public TItem? Value { get; set; }
[Parameter] public EventCallback<TItem?> ValueChanged { get; set; } [Parameter] public EventCallback<TItem?> ValueChanged { get; set; }
[Parameter] public TItem? Header { get; set; } [Parameter] public TItem? Header { get; set; }
@@ -15,27 +15,16 @@ public partial class SelectList<TItem>
[Parameter] public SelectListTheme Theme { get; set; } [Parameter] public SelectListTheme Theme { get; set; }
[Parameter] public bool AlignRight { get; set; } [Parameter] public bool AlignRight { get; set; }
private bool ContentVisile = false; private bool IsContentOpen = false;
private DateTime ContentLastFocusOut = DateTime.Now;
private ElementReference Container;
public async Task OpenAsync() private void HandleButtonClicked()
{ {
if (DateTime.Now - ContentLastFocusOut >= TimeSpan.FromSeconds(0.2)) IsContentOpen = !IsContentOpen;
{
await Container.FocusAsync();
ContentVisile = true;
}
} }
public void Close() => ContentVisile = false; private void HandleContentClosed()
private async Task HandleButtonClicked() => await OpenAsync();
private void HandleFocusOut()
{ {
ContentLastFocusOut = DateTime.Now; IsContentOpen = false;
ContentVisile = false;
} }
private async Task HandleItemClicked(SelectElement<TItem> selectedValue) private async Task HandleItemClicked(SelectElement<TItem> selectedValue)
@@ -46,6 +35,7 @@ public partial class SelectList<TItem>
} }
selectedValue.IsSelected = true; selectedValue.IsSelected = true;
Value = selectedValue.Item; Value = selectedValue.Item;
await ValueChanged.InvokeAsync(Value); await ValueChanged.InvokeAsync(Value);
} }
@@ -58,6 +48,7 @@ public partial class SelectList<TItem>
} }
selectedValue.IsSelected = true; selectedValue.IsSelected = true;
Header = selectedValue.Item; Header = selectedValue.Item;
await HeaderChanged.InvokeAsync(Header); await HeaderChanged.InvokeAsync(Header);
} }

View File

@@ -5,6 +5,7 @@
.select-container { .select-container {
margin-top: 4px; margin-top: 4px;
position: absolute; position: absolute;
z-index: var(--index-dropdown)
} }
.align-right { .align-right {
@@ -20,9 +21,13 @@
animation-duration: 0.4s; animation-duration: 0.4s;
} }
.select-button {
z-index: var(--index-component)
}
.line { .line {
margin: 2px 6px; margin: 2px 6px;
border-bottom: 2px solid var(--low-white); border-bottom: 2px solid var(--input-selected);
} }
@@ -34,7 +39,7 @@
/***** Sort Theme *****/ /***** Sort Theme *****/
.select-content.sort { .select-content.sort {
background: var(--semi-black); background: var(--dropdown-content);
box-shadow: var(--drop-shadow); box-shadow: var(--drop-shadow);
padding: 4px 0; padding: 4px 0;
} }

View File

@@ -37,7 +37,7 @@ public partial class SliderRange
{ {
var percent1 = (double)(Params.ValueMin - Params.Min) / (Params.Max - Params.Min) * 100; var percent1 = (double)(Params.ValueMin - Params.Min) / (Params.Max - Params.Min) * 100;
var percent2 = (double)(Params.ValueMax - Params.Min) / (Params.Max - Params.Min) * 100; var percent2 = (double)(Params.ValueMax - Params.Min) / (Params.Max - Params.Min) * 100;
return $"background: linear-gradient(to right, var(--light-grey) {percent1}% , var(--violet) {percent1}% , var(--violet) {percent2}%, var(--light-grey) {percent2}%)"; return $"background: linear-gradient(to right, var(--line) {percent1}% , var(--violet) {percent1}% , var(--violet) {percent2}%, var(--line) {percent2}%)";
} }
private string StatusColor(int value) private string StatusColor(int value)

View File

@@ -1,6 +1,7 @@
.container { .container {
position: relative; position: relative;
width: 100%; width: 100%;
z-index: var(--index-component)
} }
input[type="range"] { input[type="range"] {

View File

@@ -0,0 +1,7 @@
namespace GameIdeas.BlazorApp.Shared.Models;
public enum AddType
{
Manual,
Auto
}

View File

@@ -0,0 +1,7 @@
namespace GameIdeas.BlazorApp.Shared.Models;
public enum DisplayType
{
Card,
List
}

View File

@@ -7,29 +7,39 @@
--red: #FF5E51; --red: #FF5E51;
--yellow: #FFC107; --yellow: #FFC107;
--green: #43F8C0; --green: #43F8C0;
--light-grey: #5C5C5E;
--black: rgba(0, 0, 0, 0.8);
--white: #fff; --white: #fff;
--low-white: rgb(255, 255, 255, 0.1); --line: #5C5C5E;
--semi-black: rgba(0, 0, 0, 0.5); --input-primary: rgba(0, 0, 0, 0.8);
--line-black: rgba(0, 0, 0, 0.2); --input-secondary: rgb(45, 45, 48);
--input-selected: rgb(255, 255, 255, 0.1);
--dropdown-content: rgb(45, 45, 48);
--small-radius: 4px; --small-radius: 4px;
--big-radius: 10px; --big-radius: 10px;
--radius: 6px;
--drop-shadow: 0px 0px 20px rgba(0, 0, 0, 0.25); --drop-shadow: 0px 0px 20px rgba(0, 0, 0, 0.25);
--index-background: -100;
--index-orb: -1;
--index-content: 0;
--index-component: 300;
--index-floating: 500;
--index-backdrop: 700;
--index-dropdown: 900;
--index-popup: 1000;
} }
html { html {
font-family: 'Noto Sans', sans-serif; font-family: 'Noto Sans', sans-serif;
font-size: 12px; font-size: 12px;
color: var(--white) color: var(--white);
overflow: hidden;
} }
html, body, #app { html, body, #app {
margin: 0; margin: 0;
padding: 0; padding: 0;
height: 100%; height: 100%;
overflow: hidden;
} }
.valid.modified:not([type=checkbox]) { .valid.modified:not([type=checkbox]) {

View File

@@ -26,6 +26,8 @@
<span class="dismiss">🗙</span> <span class="dismiss">🗙</span>
</div> </div>
<script src="_framework/blazor.webassembly.js"></script> <script src="_framework/blazor.webassembly.js"></script>
<script src="Shared/Components/BackdropFilter/BackdropFilter.razor.js"></script>
<script src="Shared/Components/Select/MultipleSelectList.razor.js"></script>
</body> </body>
</html> </html>

View File

@@ -0,0 +1,23 @@
namespace GameIdeas.Shared.Constants;
public static class Icons
{
private const string OpenBraket = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\">";
private const string CloseBraket = "</svg>";
public static class Search
{
public const string Clear = OpenBraket +
"<path d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\" />" +
CloseBraket;
public const string Glass = OpenBraket +
"<path d=\"M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z\" />" +
CloseBraket;
public const string Triangle = OpenBraket +
"<path d=\"M1 3H23L12 22\" />" +
CloseBraket;
}
}