-
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 popover component (#60)
* feat(utils): add an ID generator * update .editorconfig * feat(popover): initial implementation * refactor(extensions): nits * feat(popover): allow only one popover to be shown at a time * feat(popover): add outside click event and handler * feat(popover): allow configure the placement * feat(popover): add offset configuration * feat(popover): add arrow visibility configuration * refactor(scripts): improve scripts code and distribution * feat(popover): add color configuration * feat(popover): add text size configuration * feat(popover): add border radius configuration * feat(popover): add shadow configuration * chore(components): nits * feat(popover): add slots styling support * chore(button): add focus ring * test(popover): add tests * test(components): fix broken tests * chore(popover): XML docs nits
- Loading branch information
1 parent
3c8c47a
commit b6232df
Showing
40 changed files
with
3,185 additions
and
48 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// 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 | ||
|
||
namespace LumexUI.Utilities; | ||
|
||
public static class Identifier | ||
{ | ||
private static readonly Random _rnd = new(); | ||
|
||
/// <summary> | ||
/// Generates a new small Id. For example, 'f127d9edf14385adb'. | ||
/// </summary> | ||
/// <remarks>HTML id must start with a letter.</remarks> | ||
/// <returns>A <see cref="string"/> that represents the generated ID.</returns> | ||
public static string New( int length = 8 ) | ||
{ | ||
if( length > 16 ) | ||
{ | ||
throw new ArgumentOutOfRangeException( nameof( length ), "length must be less than 16" ); | ||
} | ||
|
||
if( length <= 8 ) | ||
{ | ||
return $"f{_rnd.Next():x}"; | ||
} | ||
|
||
return $"f{_rnd.Next():x}{_rnd.Next():x}"[..length]; | ||
} | ||
} |
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,85 @@ | ||
// 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.ComponentModel; | ||
|
||
namespace LumexUI.Common; | ||
|
||
/// <summary> | ||
/// Specifies the placement options for a popover component. | ||
/// </summary> | ||
public enum PopoverPlacement | ||
{ | ||
/// <summary> | ||
/// Places the popover above the reference. | ||
/// </summary> | ||
[Description( "top" )] | ||
Top, | ||
|
||
/// <summary> | ||
/// Places the popover above the reference, aligned to the start (left) of the reference. | ||
/// </summary> | ||
[Description( "top-start" )] | ||
TopStart, | ||
|
||
/// <summary> | ||
/// Places the popover above the reference, aligned to the end (right) of the reference. | ||
/// </summary> | ||
[Description( "top-end" )] | ||
TopEnd, | ||
|
||
/// <summary> | ||
/// Places the popover to the right of the reference. | ||
/// </summary> | ||
[Description( "right" )] | ||
Right, | ||
|
||
/// <summary> | ||
/// Places the popover to the right of the reference, aligned to the start (top) of the reference. | ||
/// </summary> | ||
[Description( "right-start" )] | ||
RightStart, | ||
|
||
/// <summary> | ||
/// Places the popover to the right of the reference, aligned to the end (bottom) of the reference. | ||
/// </summary> | ||
[Description( "right-end" )] | ||
RightEnd, | ||
|
||
/// <summary> | ||
/// Places the popover below the reference. | ||
/// </summary> | ||
[Description( "bottom" )] | ||
Bottom, | ||
|
||
/// <summary> | ||
/// Places the popover below the reference, aligned to the start (left) of the reference. | ||
/// </summary> | ||
[Description( "bottom-start" )] | ||
BottomStart, | ||
|
||
/// <summary> | ||
/// Places the popover below the reference, aligned to the end (right) of the reference. | ||
/// </summary> | ||
[Description( "bottom-end" )] | ||
BottomEnd, | ||
|
||
/// <summary> | ||
/// Places the popover to the left of the reference. | ||
/// </summary> | ||
[Description( "left" )] | ||
Left, | ||
|
||
/// <summary> | ||
/// Places the popover to the left of the reference, aligned to the start (top) of the reference. | ||
/// </summary> | ||
[Description( "left-start" )] | ||
LeftStart, | ||
|
||
/// <summary> | ||
/// Places the popover to the left of the reference, aligned to the end (bottom) of reference. | ||
/// </summary> | ||
[Description( "left-end" )] | ||
LeftEnd | ||
} |
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
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,6 @@ | ||
@namespace LumexUI | ||
@inherits LumexComponentBase | ||
|
||
<CascadingValue TValue="PopoverContext" Value="@_context" IsFixed="@true"> | ||
@ChildContent | ||
</CascadingValue> |
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,161 @@ | ||
// 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.Common; | ||
using LumexUI.Services; | ||
using LumexUI.Utilities; | ||
|
||
using Microsoft.AspNetCore.Components; | ||
|
||
namespace LumexUI; | ||
|
||
/// <summary> | ||
/// A component representing a popover, providing a floating container | ||
/// that displays additional content or information. | ||
/// </summary> | ||
public partial class LumexPopover : LumexComponentBase, ISlotComponent<PopoverSlots>, IDisposable | ||
{ | ||
/// <summary> | ||
/// Gets or sets content to be rendered inside the popover. | ||
/// </summary> | ||
[Parameter] public RenderFragment? ChildContent { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets a color of the popover. | ||
/// </summary> | ||
/// <remarks> | ||
/// The default value is <see cref="ThemeColor.Default"/> | ||
/// </remarks> | ||
[Parameter] public ThemeColor Color { get; set; } = ThemeColor.Default; | ||
|
||
/// <summary> | ||
/// Gets or sets a size of the popover content text. | ||
/// </summary> | ||
/// <remarks> | ||
/// The default value is <see cref="Size.Medium"/> | ||
/// </remarks> | ||
[Parameter] public Size Size { get; set; } = Size.Medium; | ||
|
||
/// <summary> | ||
/// Gets or sets a border radius of the popover. | ||
/// </summary> | ||
/// <remarks> | ||
/// The default value is <see cref="Radius.Large"/> | ||
/// </remarks> | ||
[Parameter] public Radius Radius { get; set; } = Radius.Large; | ||
|
||
/// <summary> | ||
/// Gets or sets a shadow of the popover. | ||
/// </summary> | ||
/// <remarks> | ||
/// Default value is <see cref="Shadow.Small"/> | ||
/// </remarks> | ||
[Parameter] public Shadow Shadow { get; set; } = Shadow.Small; | ||
|
||
/// <summary> | ||
/// Gets or sets a placement of the popover relative to a reference. | ||
/// </summary> | ||
/// <remarks> | ||
/// The default value is <see cref="PopoverPlacement.Top"/> | ||
/// </remarks> | ||
[Parameter] public PopoverPlacement Placement { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the offset distance between the popover and the reference, in pixels. | ||
/// </summary> | ||
/// <remarks> | ||
/// The default value is 8 | ||
/// </remarks> | ||
[Parameter] public int Offset { get; set; } = 8; | ||
|
||
/// <summary> | ||
/// Gets or sets a value indicating whether the popover should display an arrow pointing to the reference. | ||
/// </summary> | ||
[Parameter] public bool ShowArrow { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the CSS class names for the popover slots. | ||
/// </summary> | ||
[Parameter] public PopoverSlots? Classes { get; set; } | ||
|
||
[Inject] private IPopoverService PopoverService { get; set; } = default!; | ||
|
||
internal string Id { get; private set; } = Identifier.New(); | ||
internal bool IsShown { get; private set; } | ||
internal PopoverOptions Options { get; private set; } | ||
|
||
private readonly PopoverContext _context; | ||
private bool _disposed; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="LumexPopover"/>. | ||
/// </summary> | ||
public LumexPopover() | ||
{ | ||
_context = new PopoverContext( this ); | ||
} | ||
|
||
internal bool Show() | ||
{ | ||
if( PopoverService.LastShown == this ) | ||
{ | ||
PopoverService.SetLastShown( null ); | ||
return false; | ||
} | ||
|
||
IsShown = true; | ||
PopoverService.SetLastShown( this ); | ||
return true; | ||
} | ||
|
||
internal void Hide() | ||
{ | ||
IsShown = false; | ||
StateHasChanged(); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void OnInitialized() | ||
{ | ||
PopoverService.Register( this ); | ||
} | ||
|
||
/// <inheritdoc /> | ||
protected override void OnParametersSet() | ||
{ | ||
Options = new PopoverOptions( this ); | ||
} | ||
|
||
/// <inheritdoc /> | ||
public void Dispose() | ||
{ | ||
Dispose( disposing: true ); | ||
GC.SuppressFinalize( this ); | ||
} | ||
|
||
/// <inheritdoc cref="IDisposable.Dispose" /> | ||
protected virtual void Dispose( bool disposing ) | ||
{ | ||
if( !_disposed ) | ||
{ | ||
if( disposing ) | ||
{ | ||
PopoverService.Unregister( this ); | ||
} | ||
|
||
_disposed = true; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Represents the configuration options for a <see cref="LumexPopover"/> component. | ||
/// </summary> | ||
/// <param name="popover">The <see cref="LumexPopover"/> instance associated with these options.</param> | ||
internal readonly struct PopoverOptions( LumexPopover popover ) | ||
{ | ||
public int Offset { get; } = popover.Offset; | ||
public bool ShowArrow { get; } = popover.ShowArrow; | ||
public string Placement { get; } = popover.Placement.ToDescription(); | ||
} | ||
} |
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,26 @@ | ||
@namespace LumexUI | ||
@inherits LumexComponentBase | ||
|
||
@if( Context.Owner.IsShown ) | ||
{ | ||
<div style="position: absolute; z-index: 10000;" | ||
data-popover="@Context.Owner.Id" | ||
@onclickoutside="@(() => ClickOutsideAsync())"> | ||
<div class="@InnerWrapperClass"> | ||
<div role="dialog" tabindex="-1" data-slot="base"> | ||
<LumexComponent As="@As" | ||
Class="@RootClass" | ||
Style="@RootStyle" | ||
data-slot="content" | ||
@attributes="@AdditionalAttributes"> | ||
@ChildContent | ||
</LumexComponent> | ||
</div> | ||
|
||
@if( Context.Owner.ShowArrow ) | ||
{ | ||
<span class="@ArrowClass" data-slot="arrow" /> | ||
} | ||
</div> | ||
</div> | ||
} |
Oops, something went wrong.