Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
86c0fdf
feat(listbox): add baseline implementation of ListBox and ListBoxItem…
desmondinho Dec 5, 2024
e2abc1b
feat(listbox): add `Color` and `Variant` params to Listbox and Listbo…
desmondinho Dec 5, 2024
78bb3bc
refactor(listbox): move Items list into the context
desmondinho Dec 8, 2024
f92b5a0
feat: add a new `transition-colors-shadow` transition
desmondinho Dec 8, 2024
d6e98a6
feat: defer content rendering to collect the items first
desmondinho Dec 8, 2024
778af3a
feat: allow single and multiple selection
desmondinho Dec 8, 2024
ffc5d07
feat: add start/end content parameters
desmondinho Dec 8, 2024
83ab5bd
feat: add `Description` param to the ListboxItem
desmondinho Dec 8, 2024
83d55ce
feat: add Disabled state
desmondinho Dec 9, 2024
2f4ffd9
feat: add OnClick callback on the ListboxItem component
desmondinho Dec 9, 2024
cc9acae
chore: XML summaries
desmondinho Dec 9, 2024
fac2667
feat(docs): add baseline for the listbox component page
desmondinho Dec 9, 2024
d81cc17
feat(popover): add `MatchRefWidth` parameter to the popover component
desmondinho Dec 11, 2024
c2c2354
feat(popover): add 2-way-bindable `Opened` param to the popover compo…
desmondinho Dec 11, 2024
eee0245
feat(popover): make the `Id` a public param
desmondinho Dec 11, 2024
247e5b2
feat(listbox-item): rename `Id` param to `Value`
desmondinho Dec 11, 2024
afae751
chore(input): nits
desmondinho Dec 11, 2024
183010f
feat(extensions): add baseline implementation of the Select component
desmondinho Dec 11, 2024
6498a3c
refactor: allow binding to a single item or multiple items
desmondinho Dec 21, 2024
7523539
feat: implement slots for the listbox and listbox item components
desmondinho Dec 22, 2024
f4a5ae0
feat(docs): complete the listbox component page
desmondinho Dec 22, 2024
fcb4713
feat(popover): add `MatchRefWidth` parameter to the popover component
desmondinho Dec 11, 2024
1a38eeb
feat(popover): add 2-way-bindable `Opened` param to the popover compo…
desmondinho Dec 11, 2024
8c4777f
feat(popover): make the `Id` a public param
desmondinho Dec 11, 2024
4108557
chore(input): nits
desmondinho Dec 11, 2024
241d519
feat(extensions): add baseline implementation of the Select component
desmondinho Dec 11, 2024
4c97b83
feat: add `TextValue` param to the SelectItem component
desmondinho Dec 13, 2024
2f69757
fix: styles
desmondinho Dec 13, 2024
6f41ac5
chore: rebase feat/select onto feat/listbox
desmondinho Dec 22, 2024
fc080c0
chore(listbox): nits
desmondinho Dec 23, 2024
bbab444
chore: improve rendering logic
desmondinho Dec 24, 2024
083f200
feat(plugin): add 'scrollbar-hide' CSS utility
desmondinho Dec 25, 2024
6186624
feat: implement slots
desmondinho Dec 25, 2024
989c607
feat: add `ListboxMaxHeight` parameter
desmondinho Dec 25, 2024
e42dde3
feat: add `DisabledItems` parameter
desmondinho Dec 25, 2024
7072077
feat: add `ValueContent` parameter
desmondinho Dec 25, 2024
ba9f5e8
chore: nits
desmondinho Dec 25, 2024
d2e5162
chore: add missing XML docs
desmondinho Dec 25, 2024
6900664
fix(listbox): apply CSS classes of an individual listbox item to its …
desmondinho Dec 25, 2024
50f5070
feat: add `PopoverClasses` and `ListboxClasses` parameters
desmondinho Dec 25, 2024
4fea1d4
feat(docs): add the `New` component status badge
desmondinho Dec 25, 2024
853b989
feat(docs): add the Select page
desmondinho Dec 25, 2024
95bb0ae
test(popover): fix broken tests
desmondinho Dec 25, 2024
9fb7378
chore: exclude slots from code coverage; nits
desmondinho Dec 26, 2024
1e4a42a
refactor: move value(s) selection logic into the listbox component
desmondinho Dec 26, 2024
2d37e99
test: add tests
desmondinho Dec 26, 2024
bcf081d
feat(popover): add `MatchRefWidth` parameter to the popover component
desmondinho Dec 11, 2024
308988a
feat(popover): add 2-way-bindable `Opened` param to the popover compo…
desmondinho Dec 11, 2024
b2fbec5
feat(popover): make the `Id` a public param
desmondinho Dec 11, 2024
de83d23
chore(input): nits
desmondinho Dec 11, 2024
fee6d07
feat(extensions): add baseline implementation of the Select component
desmondinho Dec 11, 2024
e98b79d
feat: add `TextValue` param to the SelectItem component
desmondinho Dec 13, 2024
089e42d
fix: styles
desmondinho Dec 13, 2024
4dfcaf3
feat(popover): add `MatchRefWidth` parameter to the popover component
desmondinho Dec 11, 2024
23a85f7
feat(popover): add 2-way-bindable `Opened` param to the popover compo…
desmondinho Dec 11, 2024
ace679f
feat(popover): make the `Id` a public param
desmondinho Dec 11, 2024
5f51855
feat(listbox-item): rename `Id` param to `Value`
desmondinho Dec 11, 2024
4acf515
feat(extensions): add baseline implementation of the Select component
desmondinho Dec 11, 2024
420439b
chore(listbox): nits
desmondinho Dec 23, 2024
2985122
chore: improve rendering logic
desmondinho Dec 24, 2024
0e1ac0a
feat(plugin): add 'scrollbar-hide' CSS utility
desmondinho Dec 25, 2024
29d6817
feat: implement slots
desmondinho Dec 25, 2024
b4b5cce
feat: add `ListboxMaxHeight` parameter
desmondinho Dec 25, 2024
39a48bd
feat: add `DisabledItems` parameter
desmondinho Dec 25, 2024
e14b6a7
feat: add `ValueContent` parameter
desmondinho Dec 25, 2024
90e2cc4
chore: nits
desmondinho Dec 25, 2024
212dc87
chore: add missing XML docs
desmondinho Dec 25, 2024
f6ee4d2
fix(listbox): apply CSS classes of an individual listbox item to its …
desmondinho Dec 25, 2024
98cbc20
feat: add `PopoverClasses` and `ListboxClasses` parameters
desmondinho Dec 25, 2024
b8701af
feat(docs): add the `New` component status badge
desmondinho Dec 25, 2024
b56f5ce
feat(docs): add the Select page
desmondinho Dec 25, 2024
da0baee
test(popover): fix broken tests
desmondinho Dec 25, 2024
8911805
chore: merge feat/listbox into feat/select
desmondinho Dec 26, 2024
46f80c6
chore: fix part of incorrectly merged code
desmondinho Dec 26, 2024
a8f619d
docs(listbox): replace the "Selection" section with "Two-way Data Bin…
desmondinho Dec 26, 2024
b4400e6
docs(select): wording
desmondinho Dec 26, 2024
0743058
chore: nits
desmondinho Dec 27, 2024
1b1f919
test: add tests
desmondinho Dec 27, 2024
318a21b
docs(listbox/select): nits
desmondinho Dec 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat: implement slots
  • Loading branch information
desmondinho committed Dec 26, 2024
commit 29d681780c2272ce16e4a13fb7a4dd5692e8e06e
29 changes: 27 additions & 2 deletions src/LumexUI/Components/Select/LumexSelect.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using LumexUI.Common;
using LumexUI.Extensions;
using LumexUI.Styles;
using LumexUI.Utilities;

using Microsoft.AspNetCore.Components;

Expand All @@ -17,7 +18,7 @@ namespace LumexUI;
///
/// </summary>
/// <typeparam name="TValue"></typeparam>
public partial class LumexSelect<TValue> : LumexInputBase<TValue>
public partial class LumexSelect<TValue> : LumexInputBase<TValue>, ISlotComponent<SelectSlots>
{
/// <summary>
///
Expand Down Expand Up @@ -93,6 +94,10 @@ public partial class LumexSelect<TValue> : LumexInputBase<TValue>
/// </summary>
[Parameter] public EventCallback<ICollection<TValue>> ValuesChanged { get; set; }

/// <summary>
/// Gets or sets the CSS class names for the select slots.
/// </summary>
[Parameter] public SelectSlots? Classes { get; set; }
private ICollection<TValue>? CurrentValues
{
get => Values;
Expand All @@ -117,6 +122,7 @@ EndContent is not null ||
!string.IsNullOrEmpty( Placeholder );

private readonly SelectContext<TValue> _context;
private readonly Memoizer<SelectSlots> _slotsMemoizer;
private readonly RenderFragment _renderMenu;
private readonly RenderFragment _renderLabel;
private readonly RenderFragment _renderValue;
Expand All @@ -134,6 +140,7 @@ EndContent is not null ||
public LumexSelect()
{
_context = new SelectContext<TValue>( this );
_slotsMemoizer = new Memoizer<SelectSlots>();
_renderMenu = RenderMenu;
_renderLabel = RenderLabel;
_renderValue = RenderValue;
Expand Down Expand Up @@ -173,7 +180,20 @@ public override async Task SetParametersAsync( ParameterView parameters )
/// <inheritdoc />
protected override void OnParametersSet()
{
_slots ??= Select.GetStyles( this, TwMerge );
// Perform a re-building only if the dependencies have changed
_slots = _slotsMemoizer.Memoize( GetSlots, [
LabelPlacement,
FullWidth,
Required,
Disabled,
Invalid,
Variant,
Radius,
Color,
Size,
Class,
Classes
] );
}

/// <inheritdoc />
Expand Down Expand Up @@ -233,4 +253,9 @@ private Task SetCurrentValuesAsync( ICollection<TValue>? values )
Values = values;
return ValuesChanged.InvokeAsync( Values );
}

private SelectSlots GetSlots()
{
return Select.GetStyles( this, TwMerge );
}
}
75 changes: 62 additions & 13 deletions src/LumexUI/Styles/Select.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ internal class Select
.Add( "group" )
.Add( "flex" )
.Add( "flex-col" )
// transition
.Add( "transition-[background]" )
.Add( "motion-reduce:transition-none" )
.ToString();

private readonly static string _label = ElementClass.Empty()
Expand Down Expand Up @@ -50,6 +47,9 @@ internal class Select
.Add( "px-3" )
.Add( "shadow-sm" )
.Add( "outline-none" )
// transition
.Add( "transition-[background]" )
.Add( "motion-reduce:transition-none" )
.ToString();

private readonly static string _innerWrapper = ElementClass.Empty()
Expand All @@ -68,7 +68,7 @@ internal class Select
.Add( "end-3" )
.Add( "data-[open=true]:rotate-180" )
// transition
.Add( "transition-transform" )
.Add( "transition-[transform,color]" )
.Add( "duration-150" )
.Add( "ease" )
.Add( "motion-reduce:transition-none" )
Expand All @@ -78,6 +78,15 @@ internal class Select
.Add( "w-full" )
.Add( "text-left" )
.Add( "text-foreground-500" )
.Add( "truncate" )
// transition
.Add( "transition-colors" )
.Add( "motion-reduce:transition-none" )
.ToString();

private readonly static string _listbox = ElementClass.Empty()
.Add( "overflow-y-auto" )
.Add( "scrollbar-hide" )
.ToString();

private readonly static string _popoverContent = ElementClass.Empty()
Expand Down Expand Up @@ -116,80 +125,99 @@ public static SelectSlots GetStyles<T>( LumexSelect<T> select, TwMerge twMerge )
ElementClass.Empty()
.Add( _base )
.Add( _fullWidth, when: select.FullWidth )
.Add( select.Class )
.Add( GetDisabledStyles( slot: nameof( _base ) ), when: select.Disabled )
.Add( GetLabelPlacementStyles( select.LabelPlacement, slot: nameof( _base ) ) )
.Add( GetCompoundStyles( select.LabelPlacement, select.Size, slot: nameof( _base ) ) )
.Add( select.Classes?.Root )
.Add( select.Class )
.ToString() ),

Label = twMerge.Merge(
ElementClass.Empty()
.Add( _label )
.Add( GetInvalidStyles( slot: nameof( _label ) ), when: select.Invalid )
.Add( GetRequiredStyles( slot: nameof( _label ) ), when: select.Required )
.Add( GetSizeStyles( select.Size, slot: nameof( _label ) ) )
.Add( GetLabelPlacementStyles( select.LabelPlacement, slot: nameof( _label ) ) )
.Add( GetCompoundStyles( select.Variant, select.Color, slot: nameof( _label ) ) )
.Add( GetCompoundStyles( select.LabelPlacement, select.Size, slot: nameof( _label ) ) )
.Add( GetCompoundStyles( select.LabelPlacement, slot: nameof( _label ) ), when: select.Color is ThemeColor.Default )
.Add( GetCompoundStyles( select.Size, select.Variant, slot: nameof( _label ) ), when: select.LabelPlacement is LabelPlacement.Inside )
.Add( select.Classes?.Label )
.ToString() ),

MainWrapper = twMerge.Merge(
ElementClass.Empty()
.Add( _mainWrapper )
.Add( select.Classes?.MainWrapper )
.ToString() ),

Trigger = twMerge.Merge(
ElementClass.Empty()
.Add( _trigger )
.Add( GetDisabledStyles( slot: nameof( _trigger ) ), when: select.Disabled )
.Add( GetSizeStyles( select.Size, slot: nameof( _trigger ) ) )
.Add( GetRadiusStyles( select.Radius, slot: nameof( _trigger ) ) )
.Add( GetVariantStyles( select.Variant, slot: nameof( _trigger ) ) )
.Add( GetLabelPlacementStyles( select.LabelPlacement, slot: nameof( _trigger ) ) )
.Add( GetCompoundStyles( select.Variant, select.Color, slot: nameof( _trigger ) ) )
.Add( GetCompoundStyles( select.LabelPlacement, select.Size, slot: nameof( _trigger ) ) )
.Add( GetCompoundStyles( select.Variant, slot: nameof( _trigger ) ), when: select.Invalid )
.Add( select.Classes?.Trigger )
.ToString() ),

InnerWrapper = twMerge.Merge(
ElementClass.Empty()
.Add( _innerWrapper )
.Add( GetCompoundStyles( select.LabelPlacement, select.Size, slot: nameof( _innerWrapper ) ) )
.Add( select.Classes?.InnerWrapper )
.ToString() ),

SelectorIcon = twMerge.Merge(
ElementClass.Empty()
.Add( _selectorIcon )
.Add( GetInvalidStyles( slot: nameof( _selectorIcon ) ), when: select.Invalid )
.Add( select.Classes?.SelectorIcon )
.ToString() ),

Value = twMerge.Merge(
ElementClass.Empty()
.Add( _value )
.Add( GetInvalidStyles( slot: nameof( _value ) ), when: select.Invalid )
.Add( GetSizeStyles( select.Size, slot: nameof( _value ) ) )
.Add( GetVariantStyles( select.Variant, slot: nameof( _value ) ) )
.Add( GetCompoundStyles( select.Variant, select.Color, slot: nameof( _value ) ) )
.Add( select.Classes?.Value )
.ToString() ),

Listbox = twMerge.Merge(
ElementClass.Empty()
.Add( _listbox )
.Add( select.Classes?.Listbox )
.ToString() ),

PopoverContent = twMerge.Merge(
ElementClass.Empty()
.Add( _popoverContent )
.Add( select.Classes?.PopoverContent )
.ToString() ),

HelperWrapper = twMerge.Merge(
ElementClass.Empty()
.Add( _helperWrapper )
.Add( select.Classes?.HelperWrapper )
.ToString() ),

Description = twMerge.Merge(
ElementClass.Empty()
.Add( _description )
.Add( select.Classes?.Description )
.ToString() ),

ErrorMessage = twMerge.Merge(
ElementClass.Empty()
.Add( _errorMessage )
.Add( select.Classes?.ErrorMessage )
.ToString() ),
};
}
Expand All @@ -212,7 +240,7 @@ private static ElementClass GetVariantStyles( InputVariant variant, string slot
.Add( "border-default-200" )
.Add( "hover:border-default-300" )
.Add( "data-[open=true]:border-default-foreground" )
.Add( "data-[focus=true]:border-default-foreground" )
.Add( "group-data-[focus=true]:border-default-foreground" )
.Add( "transition-colors" )
.Add( "motion-reduce:transition-none" ), when: slot is nameof( _trigger ) )
.Add( "group-data-[has-value=true]:text-default-foreground", when: slot is nameof( _value ) ),
Expand All @@ -237,7 +265,7 @@ private static ElementClass GetVariantStyles( InputVariant variant, string slot
.Add( "after:-bottom-[2px]" )
.Add( "after:h-[2px]" )
.Add( "data-[open=true]:after:w-full" )
.Add( "data-[focus=true]:after:w-full" )
.Add( "group-data-[focus=true]:after:w-full" )
.Add( "after:transition-[width]" )
.Add( "motion-reduce:after:transition-none" ), when: slot is nameof( _trigger ) )
.Add( "group-data-[has-value=true]:text-default-foreground", when: slot is nameof( _value ) ),
Expand Down Expand Up @@ -302,6 +330,27 @@ private static ElementClass GetLabelPlacementStyles( LabelPlacement labelPlaceme
};
}

private static ElementClass GetDisabledStyles( string slot )
{
return ElementClass.Empty()
.Add( "opacity-disabled pointer-events-none", when: slot is nameof( _base ) )
.Add( "pointer-events-none", when: slot is nameof( _trigger ) );
}

private static ElementClass GetRequiredStyles( string slot )
{
return ElementClass.Empty()
.Add( "after:content-['*'] after:text-danger after:ms-0.5", when: slot is nameof( _label ) );
}

private static ElementClass GetInvalidStyles( string slot )
{
return ElementClass.Empty()
.Add( "!text-danger", when: slot is nameof( _label ) )
.Add( "!text-danger", when: slot is nameof( _value ) )
.Add( "!text-danger", when: slot is nameof( _selectorIcon ) );
}

private static ElementClass GetCompoundStyles( InputVariant variant, ThemeColor color, string slot )
{
return (variant, color) switch
Expand Down Expand Up @@ -400,27 +449,27 @@ private static ElementClass GetCompoundStyles( InputVariant variant, ThemeColor
// outlined / color

(InputVariant.Outlined, ThemeColor.Primary ) => ElementClass.Empty()
.Add( "data-[open=true]:border-primary data-[focus=true]:border-primary", when: slot is nameof( _trigger ) )
.Add( "data-[open=true]:border-primary group-data-[focus=true]:border-primary", when: slot is nameof( _trigger ) )
.Add( "text-primary", when: slot is nameof( _label ) ),

(InputVariant.Outlined, ThemeColor.Secondary ) => ElementClass.Empty()
.Add( "data-[open=true]:border-secondary data-[focus=true]:border-secondary", when: slot is nameof( _trigger ) )
.Add( "data-[open=true]:border-secondary group-data-[focus=true]:border-secondary", when: slot is nameof( _trigger ) )
.Add( "text-secondary", when: slot is nameof( _label ) ),

(InputVariant.Outlined, ThemeColor.Success ) => ElementClass.Empty()
.Add( "data-[open=true]:border-success data-[focus=true]:border-success", when: slot is nameof( _trigger ) )
.Add( "data-[open=true]:border-success group-data-[focus=true]:border-success", when: slot is nameof( _trigger ) )
.Add( "text-success", when: slot is nameof( _label ) ),

(InputVariant.Outlined, ThemeColor.Warning ) => ElementClass.Empty()
.Add( "data-[open=true]:border-warning data-[focus=true]:border-warning", when: slot is nameof( _trigger ) )
.Add( "data-[open=true]:border-warning group-data-[focus=true]:border-warning", when: slot is nameof( _trigger ) )
.Add( "text-warning", when: slot is nameof( _label ) ),

(InputVariant.Outlined, ThemeColor.Danger ) => ElementClass.Empty()
.Add( "data-[open=true]:border-danger data-[focus=true]:border-danger", when: slot is nameof( _trigger ) )
.Add( "data-[open=true]:border-danger group-data-[focus=true]:border-danger", when: slot is nameof( _trigger ) )
.Add( "text-danger", when: slot is nameof( _label ) ),

(InputVariant.Outlined, ThemeColor.Info ) => ElementClass.Empty()
.Add( "data-[open=true]:border-info data-[focus=true]:border-info", when: slot is nameof( _trigger ) )
.Add( "data-[open=true]:border-info group-data-[focus=true]:border-info", when: slot is nameof( _trigger ) )
.Add( "text-info", when: slot is nameof( _label ) ),

_ => ElementClass.Empty()
Expand Down