-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(components): add collapse component (#16)
* feat(utils): allow adding existing value to the ElementStyle * feat(components): add extension for `ElementReference` to get scroll height * feat(components): add the collapse component * test(components): add tests for the collapse component (not all) * test(components): nits
- Loading branch information
1 parent
4e630bb
commit 13655b4
Showing
13 changed files
with
394 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
// Copyright (c) LumexUI 2024 | ||
// LumexUI licenses this file to you under the MIT license | ||
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
||
using LumexUI.Extensions; | ||
using LumexUI.Styles; | ||
using LumexUI.Utilities; | ||
|
||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.AspNetCore.Components.Rendering; | ||
using Microsoft.JSInterop; | ||
|
||
namespace LumexUI; | ||
|
||
public class LumexCollapse : LumexComponentBase | ||
{ | ||
/// <summary> | ||
/// Gets or sets content to be rendered inside the collapse. | ||
/// </summary> | ||
[Parameter] public RenderFragment? ChildContent { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether the collapse is expanded. | ||
/// </summary> | ||
[Parameter] public bool Expanded { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the callback to be invoked when a collapse/expand transition ends. | ||
/// </summary> | ||
[Parameter] public EventCallback OnTransitionEnd { get; set; } | ||
|
||
internal CollapseState State { get; set; } | ||
|
||
private protected override string RootClass => | ||
TwMerge.Merge( Collapse.GetStyles( this ) ); | ||
|
||
private protected override string RootStyle => | ||
ElementStyle.Empty() | ||
.Add( "height", $"{_height}px", when: State is CollapseState.Collapsing or CollapseState.Expanding ) | ||
.Add( base.RootStyle ) | ||
.ToString(); | ||
|
||
private int _height; | ||
private bool _expanded; | ||
private bool _isRendered; | ||
private bool _heightUpdated; | ||
private ElementReference _collapse; | ||
|
||
/// <inheritdoc /> | ||
protected override void BuildRenderTree( RenderTreeBuilder builder ) | ||
{ | ||
builder.OpenElement( 0, As ); | ||
builder.AddAttribute( 1, "class", RootClass ); | ||
builder.AddAttribute( 2, "style", RootStyle ); | ||
builder.AddAttribute( 3, "ontransitionend", EventCallback.Factory.Create( this, OnTransitionEndAsync ) ); | ||
builder.AddMultipleAttributes( 4, AdditionalAttributes ); | ||
builder.AddElementReferenceCapture( 5, elementReference => _collapse = elementReference ); | ||
builder.AddContent( 6, ChildContent ); | ||
builder.CloseElement(); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void OnParametersSet() | ||
{ | ||
// We don't want to call `UpdateHeightAsync` every time the component is rendered. | ||
if( _expanded == Expanded ) | ||
{ | ||
return; | ||
} | ||
|
||
_expanded = Expanded; | ||
|
||
if( _isRendered ) | ||
{ | ||
_heightUpdated = false; | ||
SetTransitionState(); | ||
} | ||
else if( _expanded ) | ||
{ | ||
State = CollapseState.Expanded; | ||
} | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override async Task OnAfterRenderAsync( bool firstRender ) | ||
{ | ||
if( _heightUpdated ) | ||
{ | ||
return; | ||
} | ||
|
||
if( firstRender ) | ||
{ | ||
_isRendered = true; | ||
await UpdateHeightAsync(); | ||
return; | ||
} | ||
|
||
if( State is CollapseState.Expanding or CollapseState.Collapsing ) | ||
{ | ||
await UpdateHeightAsync(); | ||
StateHasChanged(); | ||
} | ||
} | ||
|
||
private async ValueTask UpdateHeightAsync() | ||
{ | ||
try | ||
{ | ||
_height = await _collapse.GetScrollHeightAsync(); | ||
|
||
if( State is CollapseState.Collapsing ) | ||
{ | ||
_height = 0; | ||
} | ||
|
||
_heightUpdated = true; | ||
} | ||
catch( Exception ex ) when( ex is JSDisconnectedException or TaskCanceledException ) | ||
{ | ||
_height = 0; | ||
} | ||
} | ||
|
||
private Task OnTransitionEndAsync() | ||
{ | ||
if( State is CollapseState.Expanding ) | ||
{ | ||
State = CollapseState.Expanded; | ||
} | ||
else if( State is CollapseState.Collapsing ) | ||
{ | ||
State = CollapseState.Collapsed; | ||
} | ||
|
||
return OnTransitionEnd.InvokeAsync(); | ||
} | ||
|
||
private void SetTransitionState() | ||
{ | ||
if( State is CollapseState.Expanded ) | ||
{ | ||
State = CollapseState.Collapsing; | ||
} | ||
else if( State is CollapseState.Collapsed ) | ||
{ | ||
State = CollapseState.Expanding; | ||
} | ||
} | ||
|
||
internal enum CollapseState | ||
{ | ||
Collapsed, Expanding, Expanded, Collapsing | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Copyright (c) LumexUI 2024 | ||
// LumexUI licenses this file to you under the MIT license | ||
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
using System.Reflection; | ||
|
||
using Microsoft.AspNetCore.Components; | ||
using Microsoft.JSInterop; | ||
|
||
namespace LumexUI.Extensions; | ||
|
||
[ExcludeFromCodeCoverage] | ||
public static class ElementReferenceExtensions | ||
{ | ||
private static readonly PropertyInfo? _jsRuntimeProp = typeof( WebElementReferenceContext ) | ||
.GetProperty( "JSRuntime", BindingFlags.Instance | BindingFlags.NonPublic ); | ||
|
||
public static ValueTask<int> GetScrollHeightAsync( this ElementReference elementReference ) | ||
{ | ||
var jsRuntime = elementReference.GetJSRuntime(); | ||
return jsRuntime.InvokeAsync<int>( "Lumex.elementReference.getScrollHeight", elementReference ); | ||
} | ||
|
||
private static IJSRuntime GetJSRuntime( this ElementReference elementReference ) | ||
{ | ||
if( elementReference.Context is not WebElementReferenceContext context ) | ||
{ | ||
throw new InvalidOperationException( "ElementReference has not been configured correctly." ); | ||
} | ||
|
||
var jsRuntime = (IJSRuntime?)_jsRuntimeProp?.GetValue( context ); | ||
return jsRuntime ?? throw new InvalidOperationException( "No JavaScript runtime found." ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,4 +22,5 @@ | |
<ItemGroup> | ||
<InternalsVisibleTo Include="LumexUI.Tests" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright (c) LumexUI 2024 | ||
// LumexUI licenses this file to you under the MIT license | ||
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
|
||
using LumexUI.Utilities; | ||
|
||
using static LumexUI.LumexCollapse; | ||
|
||
namespace LumexUI.Styles; | ||
|
||
[ExcludeFromCodeCoverage] | ||
internal readonly record struct Collapse | ||
{ | ||
private static ElementClass GetStateStyles( CollapseState state ) | ||
{ | ||
var transitioning = state is CollapseState.Expanding or CollapseState.Collapsing; | ||
|
||
return ElementClass.Empty() | ||
.Add( "hidden", when: state is CollapseState.Collapsed ) | ||
.Add( "h-0 overflow-hidden transition-[height]", when: transitioning ); | ||
} | ||
|
||
public static string GetStyles( LumexCollapse collapse ) | ||
{ | ||
return ElementClass.Empty() | ||
.Add( GetStateStyles( collapse.State ) ) | ||
.Add( collapse.Class ) | ||
.ToString(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (c) LumexUI 2024 | ||
// LumexUI licenses this file to you under the MIT license | ||
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
||
import { elementReference } from './elementReference.js' | ||
|
||
export const Lumex = { | ||
elementReference | ||
}; | ||
|
||
window['Lumex'] = Lumex |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright (c) LumexUI 2024 | ||
// LumexUI licenses this file to you under the MIT license | ||
// See the license here https://github.com/LumexUI/lumexui/blob/main/LICENSE | ||
|
||
function getScrollHeight(element) { | ||
if (!element) { | ||
throw "No element found!"; | ||
} | ||
|
||
return element.scrollHeight; | ||
} | ||
|
||
export const elementReference = { | ||
getScrollHeight | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.