Skip to content

Commit

Permalink
feat(components): introduce the Select and Listbox components (#121)
Browse files Browse the repository at this point in the history
* feat(listbox): add baseline implementation of ListBox and ListBoxItem components

* feat(listbox): add `Color` and `Variant` params to Listbox and ListboxItem components

* refactor(listbox): move Items list into the context

* feat: add a new `transition-colors-shadow` transition

* feat: defer content rendering to collect the items first

* feat: allow single and multiple selection

* feat: add start/end content parameters

* feat: add `Description` param to the ListboxItem

* feat: add Disabled state

* feat: add OnClick callback on the ListboxItem component

* chore: XML summaries

* feat(docs): add baseline for the listbox component page

* feat(popover): add `MatchRefWidth` parameter to the popover component

* feat(popover): add 2-way-bindable `Opened` param to the popover component

* feat(popover): make the `Id` a public param

* feat(listbox-item): rename `Id` param to `Value`

* chore(input): nits

* feat(extensions): add baseline implementation of the Select component

* refactor: allow binding to a single item or multiple items

* feat: implement slots for the listbox and listbox item components

* feat(docs): complete the listbox component page

* feat(popover): add `MatchRefWidth` parameter to the popover component

* feat(popover): add 2-way-bindable `Opened` param to the popover component

* feat(popover): make the `Id` a public param

* chore(input): nits

* feat(extensions): add baseline implementation of the Select component

* feat: add `TextValue` param to the SelectItem component

* fix: styles

* chore(listbox): nits

* chore: improve rendering logic

* feat(plugin): add 'scrollbar-hide' CSS utility

* feat: implement slots

* feat: add `ListboxMaxHeight` parameter

* feat: add `DisabledItems` parameter

* feat: add `ValueContent` parameter

* chore: nits

* chore: add missing XML docs

* fix(listbox): apply CSS classes of an individual listbox item to its slots

* feat: add `PopoverClasses` and `ListboxClasses` parameters

* feat(docs): add the `New` component status badge

* feat(docs): add the Select page

* test(popover): fix broken tests

* chore: exclude slots from code coverage; nits

* refactor: move value(s) selection logic into the listbox component

* test: add tests

* feat(popover): add `MatchRefWidth` parameter to the popover component

* feat(popover): add 2-way-bindable `Opened` param to the popover component

* feat(popover): make the `Id` a public param

* chore(input): nits

* feat(extensions): add baseline implementation of the Select component

* feat: add `TextValue` param to the SelectItem component

* fix: styles

* feat(popover): add `MatchRefWidth` parameter to the popover component

* feat(popover): add 2-way-bindable `Opened` param to the popover component

* feat(popover): make the `Id` a public param

* feat(listbox-item): rename `Id` param to `Value`

* feat(extensions): add baseline implementation of the Select component

* chore(listbox): nits

* chore: improve rendering logic

* feat(plugin): add 'scrollbar-hide' CSS utility

* feat: implement slots

* feat: add `ListboxMaxHeight` parameter

* feat: add `DisabledItems` parameter

* feat: add `ValueContent` parameter

* chore: add missing XML docs

* fix(listbox): apply CSS classes of an individual listbox item to its slots

* feat: add `PopoverClasses` and `ListboxClasses` parameters

* feat(docs): add the `New` component status badge

* feat(docs): add the Select page

* test(popover): fix broken tests

* chore: fix part of incorrectly merged code

* docs(listbox): replace the "Selection" section with "Two-way Data Binding"

* test: add tests

* docs(listbox/select): nits
  • Loading branch information
desmondinho authored Dec 27, 2024
1 parent be127f9 commit c1f620b
Show file tree
Hide file tree
Showing 96 changed files with 4,639 additions and 47 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace LumexUI.Docs.Client.Common;

public enum NavItemStatus
public enum ComponentStatus
{
New,

Soon,

Preview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ public class NavigationStore
.Add( new( nameof( LumexCheckbox ) ) )
.Add( new( nameof( LumexCheckboxGroup ) ) )
.Add( new( nameof( LumexCollapse ) ) )
.Add( new( nameof( LumexDataGrid<T> ), NavItemStatus.Preview ) )
.Add( new( nameof( LumexDataGrid<T> ), ComponentStatus.Preview ) )
.Add( new( nameof( LumexDivider ) ) )
.Add( new( nameof( LumexIcon ) ) )
.Add( new( nameof( LumexLink ) ) )
.Add( new( nameof( LumexListbox<T> ), ComponentStatus.New ) )
.Add( new( nameof( LumexNavbar ) ) )
.Add( new( nameof( LumexNumbox<T> ) ) )
.Add( new( nameof( LumexPopover ) ) )
.Add( new( nameof( LumexSelect<T> ), ComponentStatus.New ) )
.Add( new( nameof( LumexSwitch ) ) )
.Add( new( nameof( LumexTextbox ) ) );

Expand All @@ -59,6 +61,8 @@ public class NavigationStore
//.Add( nameof( LumexInputFieldBase<T> ) )
.Add( new( nameof( LumexIcon ) ) )
.Add( new( nameof( LumexLink ) ) )
.Add( new( nameof( LumexListbox<T> ) ) )
.Add( new( nameof( LumexListboxItem<T> ) ) )
.Add( new( nameof( LumexNavbar ) ) )
.Add( new( nameof( LumexNavbarBrand ) ) )
.Add( new( nameof( LumexNavbarContent ) ) )
Expand All @@ -70,6 +74,8 @@ public class NavigationStore
.Add( new( nameof( LumexPopover ) ) )
.Add( new( nameof( LumexPopoverContent ) ) )
.Add( new( nameof( LumexPopoverTrigger ) ) )
.Add( new( nameof( LumexSelect<T> ) ) )
.Add( new( nameof( LumexSelectItem<T> ) ) )
.Add( new( nameof( LumexSwitch ) ) )
.Add( new( nameof( LumexTextbox ) ) )
.Add( new( nameof( LumexThemeProvider ) ) );
Expand Down
4 changes: 2 additions & 2 deletions docs/LumexUI.Docs.Client/Common/Navigation/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ public NavigationCategory Add( NavigationItem item )
}
}

public class NavigationItem( string name, NavItemStatus? status = null )
public class NavigationItem( string name, ComponentStatus? status = null )
{
public string Name { get; } = name;
public NavItemStatus? Status { get; } = status;
public ComponentStatus? Status { get; } = status;
}
9 changes: 5 additions & 4 deletions docs/LumexUI.Docs.Client/Components/NavItemBadge.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
}

@code {
[Parameter] public NavItemStatus? Status { get; set; }
[Parameter] public ComponentStatus? Status { get; set; }

private string? RootClasses => ElementClass.Empty()
.Add( "px-1.5" )
Expand All @@ -22,9 +22,10 @@
.Add( _variants[Status!.Value], when: Status.HasValue )
.ToString();

private static readonly Dictionary<NavItemStatus, string> _variants = new()
private static readonly Dictionary<ComponentStatus, string> _variants = new()
{
[NavItemStatus.Soon] = "bg-foreground-100 ring-foreground-300 text-foreground-600",
[NavItemStatus.Preview] = "bg-indigo-50 ring-indigo-200 text-indigo-500"
[ComponentStatus.New] = "bg-orange-100 ring-orange-300 text-orange-600",
[ComponentStatus.Soon] = "bg-foreground-100 ring-foreground-300 text-foreground-600",
[ComponentStatus.Preview] = "bg-indigo-50 ring-indigo-200 text-indigo-500"
};
}
2 changes: 1 addition & 1 deletion docs/LumexUI.Docs.Client/Components/NavMenu.razor
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
var styles = ElementClass.Default( "flex items-center gap-2" )
.Add( ElementClass.Empty()
.Add( "opacity-disabled" )
.Add( "pointer-events-none" ), when: item.Status.HasValue && item.Status.Value is NavItemStatus.Soon )
.Add( "pointer-events-none" ), when: item.Status.HasValue && item.Status.Value is ComponentStatus.Soon )
.ToString();

<li class="@styles">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<div class="w-full max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox TValue="string" Color="@_color" Variant="@_variant">
<LumexListboxItem>New file</LumexListboxItem>
<LumexListboxItem>Edit file</LumexListboxItem>
<LumexListboxItem>Share file</LumexListboxItem>
<LumexListboxItem Color="@ThemeColor.Danger"
Class="text-danger">
Delete file
</LumexListboxItem>
</LumexListbox>
</div>

<fieldset>
<legend class="text-small text-default-500">Color</legend>
<div class="flex flex-wrap gap-2">
<InputRadioGroup @bind-Value="@_color">
@foreach (var color in Enum.GetValues<ThemeColor>())
{
<label>
<InputRadio Value="@color" />
@color
</label>
}
</InputRadioGroup>
</div>
</fieldset>

<fieldset>
<legend class="text-small text-default-500">Variant</legend>
<div class="flex flex-wrap gap-2">
<InputRadioGroup @bind-Value="@_variant">
@foreach (var variant in Enum.GetValues<ListboxVariant>())
{
<label>
<InputRadio Value="@variant" />
@variant
</label>
}
</InputRadioGroup>
</div>
</fieldset>

@code {
private ThemeColor _color = ThemeColor.Default;
private ListboxVariant _variant = ListboxVariant.Solid;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<link href="https://unpkg.com/[email protected]/css/boxicons.min.css" rel="stylesheet">

<LumexListbox TValue="string"
Variant="@ListboxVariant.Flat"
Classes="@_classes"
ItemClasses="@_itemClasses">
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("bug"), "bg-success/15 text-success-600"))"
EndContent="@_counter(7)">
Issues
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("git-pull-request"), "bg-primary/15 text-primary"))"
EndContent="@_counter(6)">
Pull Requests
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("conversation"), "bg-secondary/15 text-secondary"))"
EndContent="@_counter(3)">
Discussions
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("play-circle"), "bg-warning/15 text-warning"))"
EndContent="@_counter(2)">
Actions
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("layout"), "bg-default/30 text-foreground"))"
EndContent="@_counter(1)">
Projects
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("purchase-tag"), "bg-primary/15 text-primary"))"
EndContent="@_counter(12)"
Class="h-auto">
<ChildContent>
<div class="flex flex-col gap-1">
<span>Releases</span>
<div class="px-2 py-1 rounded-small bg-default-100 ring-1 ring-default-200/30 ring-inset group-hover:bg-default-200/50">
<p class="text-tiny">v1.0.0-preview.4</p>
<p class="text-tiny text-default-500">
3 months ago · <span class="text-success-600">Latest</span>
</p>
</div>
</div>
</ChildContent>
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("group"), "bg-orange-500/15 text-orange-500"))"
EndContent="@_counter(2)">
Contributors
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("show"), "bg-warning/15 text-warning"))"
EndContent="@_counter(3)">
Watchers
</LumexListboxItem>
<LumexListboxItem StartContent="@_iconWrapper(new(_icon("certification"), "bg-danger/15 text-danger"))">
<ChildContent>License</ChildContent>
<EndContent>
<span class="text-small text-default-400">MIT</span>
</EndContent>
</LumexListboxItem>
</LumexListbox>

@code {
private RenderFragment<(RenderFragment ChildContent, string Class)> _iconWrapper = props =>
@<div class="flex items-center rounded-small justify-center w-7 h-7 @props.Class">
@props.ChildContent
</div>;

private RenderFragment<string> _icon = icon =>
@<LumexIcon Icon="@($"bx bx-{icon}")" Size="@new("18")" />;

private RenderFragment<int> _counter = counter =>
@<div class="flex items-center gap-1 text-default-400">
<span class="text-small">@counter</span>
<LumexIcon Icon="@Icons.Rounded.ChevronRight" Size="@new("20")" />
</div>;

private ListboxSlots _classes = new()
{
Root = "p-0 bg-surface1 max-w-72 shadow-small rounded-medium",
List = "gap-0 divide-y divide-default-200/50"
};

private ListboxItemSlots _itemClasses = new()
{
Root = "px-3 first:rounded-t-medium last:rounded-b-medium rounded-none gap-3 h-12 hover:bg-default/15"
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<div class="w-full max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox TValue="string" Variant="@ListboxVariant.Flat">
<LumexListboxItem Description="Create a new file"
StartContent="@_newFile">
New file
</LumexListboxItem>
<LumexListboxItem Description="Edit the file"
StartContent="@_editFile">
Edit file
</LumexListboxItem>
<LumexListboxItem Description="Share the file"
StartContent="@_shareFile">
Share file
</LumexListboxItem>
<LumexListboxItem Description="Delete the file"
Color="@ThemeColor.Danger"
StartContent="@_deleteFile"
Class="text-danger">
Delete file
</LumexListboxItem>
</LumexListbox>
</div>

@code {
private RenderFragment _newFile =
@<LumexIcon Icon="@Icons.Rounded.LibraryAdd"
Size="@new("20")"
Class="text-default-500 shrink-0" />;

private RenderFragment _editFile =
@<LumexIcon Icon="@Icons.Rounded.EditSquare"
Size="@new("20")"
Class="text-default-500 shrink-0" />;

private RenderFragment _shareFile =
@<LumexIcon Icon="@Icons.Rounded.Share"
Size="@new("20")"
Class="text-default-500 shrink-0" />;

private RenderFragment _deleteFile =
@<LumexIcon Icon="@Icons.Rounded.Delete"
Size="@new("20")"
Class="shrink-0" />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="w-full max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox TValue="string">
<LumexListboxItem>New file</LumexListboxItem>
<LumexListboxItem Disabled="@true">Edit file</LumexListboxItem>
<LumexListboxItem>Share file</LumexListboxItem>
<LumexListboxItem Color="@ThemeColor.Danger"
Class="text-danger">
Delete file
</LumexListboxItem>
</LumexListbox>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="w-full max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox TValue="string" DisabledItems="@(["new", "delete"])">
<LumexListboxItem Value="@("new")">New file</LumexListboxItem>
<LumexListboxItem Value="@("edit")">Edit file</LumexListboxItem>
<LumexListboxItem Value="@("share")">Share file</LumexListboxItem>
<LumexListboxItem Value="@("delete")"
Color="@ThemeColor.Danger"
Class="text-danger">
Delete file
</LumexListboxItem>
</LumexListbox>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<div class="w-full max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox TValue="string">
<EmptyContent>
<span class="text-small">No items \(o_o)/</span>
</EmptyContent>
</LumexListbox>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="w-full max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox TValue="string" Variant="@ListboxVariant.Flat">
<LumexListboxItem StartContent="@_newFile">New file</LumexListboxItem>
<LumexListboxItem StartContent="@_editFile">Edit file</LumexListboxItem>
<LumexListboxItem StartContent="@_shareFile">Share file</LumexListboxItem>
<LumexListboxItem Color="@ThemeColor.Danger"
StartContent="@_deleteFile"
Class="text-danger">
Delete file
</LumexListboxItem>
</LumexListbox>
</div>

@code {
private RenderFragment _newFile =
@<LumexIcon Icon="@Icons.Rounded.LibraryAdd"
Size="@new("20")"
Class="text-default-500 shrink-0" />;

private RenderFragment _editFile =
@<LumexIcon Icon="@Icons.Rounded.EditSquare"
Size="@new("20")"
Class="text-default-500 shrink-0" />;

private RenderFragment _shareFile =
@<LumexIcon Icon="@Icons.Rounded.Share"
Size="@new("20")"
Class="text-default-500 shrink-0" />;

private RenderFragment _deleteFile =
@<LumexIcon Icon="@Icons.Rounded.Delete"
Size="@new("20")"
Class="shrink-0" />;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="w-full flex flex-col gap-2">
<div class="max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox @bind-Values="@_selectedValues">
<LumexListboxItem Value="@("cat")">Cat</LumexListboxItem>
<LumexListboxItem Value="@("dog")">Dog</LumexListboxItem>
<LumexListboxItem Value="@("elephant")">Elephant</LumexListboxItem>
<LumexListboxItem Value="@("lion")">Lion</LumexListboxItem>
</LumexListbox>
</div>
<p class="text-small text-default-500">
Selected values: @(string.Join(", ", _selectedValues))
</p>
</div>

@code {
private ICollection<string> _selectedValues = ["cat"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<div class="w-full flex flex-col gap-2">
<div class="max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox @bind-Value="@_selectedValue">
<LumexListboxItem Value="@("cat")">Cat</LumexListboxItem>
<LumexListboxItem Value="@("dog")">Dog</LumexListboxItem>
<LumexListboxItem Value="@("elephant")">Elephant</LumexListboxItem>
<LumexListboxItem Value="@("lion")">Lion</LumexListboxItem>
</LumexListbox>
</div>
<p class="text-small text-default-500">
Selected value: @_selectedValue
</p>
</div>

@code {
private string _selectedValue = "cat";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<div class="w-full max-w-60 px-1 py-2 border border-default-200 rounded-small">
<LumexListbox TValue="string">
<LumexListboxItem>New file</LumexListboxItem>
<LumexListboxItem>Edit file</LumexListboxItem>
<LumexListboxItem>Share file</LumexListboxItem>
<LumexListboxItem Color="@ThemeColor.Danger"
Class="text-danger">
Delete file
</LumexListboxItem>
</LumexListbox>
</div>
Loading

0 comments on commit c1f620b

Please sign in to comment.