-
-
Notifications
You must be signed in to change notification settings - Fork 26
feat(components): add User component #252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
292a0cd
feat: added initial LumexUser component
Denny09310 f11693d
feat: added LumexUser initial styles
Denny09310 53a9ab2
feat: added LumexUser component properties
Denny09310 b4e0ccd
docs: added initial documentation page
Denny09310 c318301
feat: added initial tests
Denny09310 161867e
fix: removed 'data-focus-*' attributes, removed rendering methods
Denny09310 bce89d8
fix: removed unnecessary fragment property
Denny09310 1e8aec9
fix: remove unnecessary throw, added warning on docs
Denny09310 7e8dc81
fix: moved focus handling down to variants
Denny09310 1bdbc4c
fix: added missing attribute to UserSlots class
Denny09310 7bb72fa
feat: added 'AvatarContent' as property
Denny09310 d4ef66e
chore: added some more tests
Denny09310 bc1f488
fix: used wrong null coalescing operator
Denny09310 d852201
fix: missing docs link to component API
Denny09310 7379d6e
Merge branch 'main' into feature/user
desmondinho 4b009ea
chore(*): minor adjustments
desmondinho e14bdd0
docs(user): fix heading
desmondinho File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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
10 changes: 10 additions & 0 deletions
10
docs/LumexUI.Docs.Client/Pages/Components/User/Examples/LinkDescription.razor
This file contains hidden or 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,10 @@ | ||
| <LumexUser Name="Dan"> | ||
| <AvatarContent> | ||
| <LumexAvatar Src="https://avatars.githubusercontent.com/u/68395709?v=4" /> | ||
| </AvatarContent> | ||
| <DescriptionContent> | ||
| <LumexLink External Href="https://github.com/desmondinho"> | ||
| @@desmondinho | ||
| </LumexLink> | ||
| </DescriptionContent> | ||
| </LumexUser> |
5 changes: 5 additions & 0 deletions
5
docs/LumexUI.Docs.Client/Pages/Components/User/Examples/Usage.razor
This file contains hidden or 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,5 @@ | ||
| <LumexUser Name="Jane Doe" Description="Product Designer"> | ||
| <AvatarContent> | ||
| <LumexAvatar Src="https://i.pravatar.cc/300" /> | ||
| </AvatarContent> | ||
| </LumexUser> |
5 changes: 5 additions & 0 deletions
5
docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/LinkDescription.razor
This file contains hidden or 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,5 @@ | ||
| @rendermode InteractiveWebAssembly | ||
|
|
||
| <PreviewCode Code="@new(name: null, snippet: "User.Code.LinkDescription")"> | ||
| <LumexUI.Docs.Client.Pages.Components.User.Examples.LinkDescription /> | ||
| </PreviewCode> |
5 changes: 5 additions & 0 deletions
5
docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/Usage.razor
This file contains hidden or 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,5 @@ | ||
| @rendermode InteractiveWebAssembly | ||
|
|
||
| <PreviewCode Code="@new(name: null, snippet: "User.Code.Usage")"> | ||
| <LumexUI.Docs.Client.Pages.Components.User.Examples.Usage /> | ||
| </PreviewCode> |
This file contains hidden or 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,67 @@ | ||
| @page "/docs/components/user" | ||
| @layout DocsContentLayout | ||
|
|
||
| @using LumexUI.Docs.Client.Pages.Components.User.PreviewCodes | ||
|
|
||
| <DocsSection Title="Usage"> | ||
| <Usage /> | ||
|
|
||
| <DocsSection Title="Link Description"> | ||
| <LinkDescription /> | ||
| </DocsSection> | ||
|
|
||
| <LumexAlert Radius="@Radius.Large" Class="my-6"> | ||
| <DescriptionContent> | ||
| When both <code>Description</code> and <code>DescriptionContent</code> are provided, <code>DescriptionContent</code> takes precedence. | ||
| </DescriptionContent> | ||
| </LumexAlert> | ||
|
|
||
| </DocsSection> | ||
|
|
||
| <DocsSlotsSection Slots="@_slots"> | ||
| <div> | ||
| <h4 class="font-semibold">User</h4> | ||
| <ul> | ||
| <li><code>Class</code>: The basic CSS class name styles the wrapper of the user contents.</li> | ||
| <li><code>Classes</code>: The CSS class names for the user slots style the entire user at once.</li> | ||
| </ul> | ||
| </div> | ||
| </DocsSlotsSection> | ||
|
|
||
| <DocsApiSection Components="@_apiComponents" /> | ||
|
|
||
| @code { | ||
| [CascadingParameter] private DocsContentLayout Layout { get; set; } = default!; | ||
|
|
||
| private readonly Heading[] _headings = new Heading[] | ||
| { | ||
| new("Usage", [ | ||
| new("Link Description") | ||
| ]), | ||
| new("API") | ||
| }; | ||
|
|
||
| private readonly Slot[] _slots = new Slot[] | ||
| { | ||
| new(nameof( UserSlots.Base ), "The base slot of the user, it is the main container."), | ||
| new(nameof( UserSlots.Wrapper ), "The name and description wrapper."), | ||
| new(nameof( UserSlots.Name ), "The name of the user."), | ||
| new(nameof( UserSlots.Description ), "The description of the user."), | ||
| }; | ||
|
|
||
| private readonly string[] _apiComponents = new string[] | ||
| { | ||
| nameof(LumexUser) | ||
| }; | ||
|
|
||
| protected override void OnInitialized() | ||
| { | ||
| Layout.Initialize( | ||
| title: "User", | ||
| category: "Components", | ||
| description: "Display user information with avatar and name.", | ||
| headings: _headings, | ||
| linksProps: new ComponentLinksProps("Usr", isServer: true) | ||
| ); | ||
| } | ||
| } |
This file contains hidden or 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 @@ | ||
| @namespace LumexUI | ||
| @inherits LumexComponentBase | ||
|
|
||
| @using S = UserSlots; | ||
|
|
||
| <LumexComponent As="@As" | ||
| Class="@GetStyles( nameof(S.Base) )" | ||
| Style="@RootStyle" | ||
| data-slot="base" | ||
| @attributes="AdditionalAttributes"> | ||
| @AvatarContent | ||
| <div class="@GetStyles( nameof( S.Wrapper ) )" data-slot="wrapper"> | ||
|
|
||
| <span class="@GetStyles( nameof( S.Name ) )" data-slot="name"> | ||
| @Name | ||
| </span> | ||
|
|
||
| <span class="@GetStyles( nameof( S.Description ) )" data-slot="description"> | ||
| @if ( DescriptionContent is not null ) | ||
| { | ||
| @DescriptionContent | ||
| } | ||
| else | ||
| { | ||
| @Description | ||
| } | ||
| </span> | ||
|
|
||
| </div> | ||
| </LumexComponent> |
This file contains hidden or 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,78 @@ | ||
| // 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.Common; | ||
| using LumexUI.Utilities; | ||
|
|
||
| using Microsoft.AspNetCore.Components; | ||
|
|
||
| namespace LumexUI; | ||
|
|
||
| /// <summary> | ||
| /// A component that represents user information, such as an avatar, name, and email. | ||
| /// </summary> | ||
| public partial class LumexUser : LumexComponentBase, ISlotComponent<UserSlots> | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the content to render when using a custom description element. | ||
| /// </summary> | ||
| [Parameter] public RenderFragment? DescriptionContent { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the content to render for the avatar element. | ||
| /// </summary> | ||
| [Parameter] public RenderFragment? AvatarContent { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the name of the user. | ||
| /// </summary> | ||
| [Parameter] public string? Name { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the description of the user. | ||
| /// </summary> | ||
| [Parameter] public string? Description { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the whether the user is focusable. | ||
| /// </summary> | ||
| [Parameter] public bool IsFocusable { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the CSS class names for the user slots. | ||
| /// </summary> | ||
| [Parameter] public UserSlots? Classes { get; set; } | ||
|
|
||
| private Dictionary<string, ComponentSlot> _slots = []; | ||
|
|
||
| /// <inheritdoc/> | ||
| protected override void OnParametersSet() | ||
| { | ||
| var user = Styles.User.Style( TwMerge ); | ||
| _slots = user( new() | ||
| { | ||
| [nameof( IsFocusable )] = IsFocusable.ToString(), | ||
| } ); | ||
| } | ||
|
|
||
| [ExcludeFromCodeCoverage] | ||
| private string? GetStyles( string slot ) | ||
| { | ||
| if( !_slots.TryGetValue( slot, out var styles ) ) | ||
| { | ||
| throw new NotImplementedException(); | ||
| } | ||
|
|
||
| return slot switch | ||
| { | ||
| nameof( UserSlots.Base ) => styles( Classes?.Base, Class ), | ||
| nameof( UserSlots.Wrapper ) => styles( Classes?.Wrapper ), | ||
| nameof( UserSlots.Name ) => styles( Classes?.Name ), | ||
| nameof( UserSlots.Description ) => styles( Classes?.Description ), | ||
| _ => throw new NotImplementedException() | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or 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,31 @@ | ||
| // 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.Common; | ||
|
|
||
| namespace LumexUI; | ||
|
|
||
| /// <summary> | ||
| /// Represents the set of customizable slots for the <see cref="LumexUser"/> component. | ||
| /// </summary> | ||
| [ExcludeFromCodeCoverage] | ||
| public class UserSlots : SlotBase | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the CSS class for the wrapper slot. | ||
| /// </summary> | ||
| public string? Wrapper { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the CSS class for the name slot. | ||
| /// </summary> | ||
| public string? Name { get; set; } | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the CSS class for the description slot. | ||
| /// </summary> | ||
| public string? Description { get; set; } | ||
| } |
This file contains hidden or 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,54 @@ | ||
| // 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 TailwindMerge; | ||
|
|
||
| namespace LumexUI.Styles; | ||
|
|
||
| [ExcludeFromCodeCoverage] | ||
| internal static class User | ||
| { | ||
| private static ComponentVariant? _variant; | ||
|
|
||
| public static ComponentVariant Style( TwMerge twMerge ) | ||
| { | ||
| var twVariants = new TwVariants( twMerge ); | ||
|
|
||
| return _variant ??= twVariants.Create( new VariantConfig() | ||
| { | ||
| Slots = new SlotCollection | ||
| { | ||
| [nameof(UserSlots.Base)] = new ElementClass() | ||
| .Add( "inline-flex" ) | ||
| .Add( "items-center" ) | ||
| .Add( "justify-center" ) | ||
| .Add( "gap-2" ) | ||
| .Add( "rounded-small" ) | ||
| .Add( "outline-solid" ) | ||
| .Add( "outline-transparent" ), | ||
|
|
||
| [nameof(UserSlots.Wrapper)] = "inline-flex flex-col items-start", | ||
|
|
||
| [nameof(UserSlots.Name)] = "text-small text-inherit", | ||
|
|
||
| [nameof(UserSlots.Description)] = "text-tiny text-foreground-400", | ||
| }, | ||
|
|
||
| Variants = new VariantCollection | ||
| { | ||
| [nameof(LumexUser.IsFocusable)] = new VariantValueCollection | ||
| { | ||
| [bool.TrueString] = new SlotCollection | ||
| { | ||
| [nameof(UserSlots.Base)] = Utils.FocusVisible, | ||
| } | ||
| } | ||
| } | ||
| } ); | ||
| } | ||
| } |
This file contains hidden or 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,46 @@ | ||
| @namespace LumexUI.Tests.Components | ||
| @inherits TestContext | ||
|
|
||
| @code { | ||
| public UserTests() | ||
| { | ||
| Services.AddSingleton<TwMerge>(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ShouldRenderCorrectly() | ||
| { | ||
| var action = () => Render( | ||
| @<LumexUser Name="John Doe" /> | ||
| ); | ||
|
|
||
| action.Should().NotThrow(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ShouldRenderCorrectlyWithDescription() | ||
| { | ||
| var action = () => Render( | ||
| @<LumexUser Name="John Doe" Description="Software Engeneer" /> | ||
| ); | ||
|
|
||
| action.Should().NotThrow(); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ShouldRenderDescriptionContent() | ||
| { | ||
| var cut = Render( | ||
| @<LumexUser Name="John Doe" data-testid="user-root"> | ||
| <DescriptionContent> | ||
| <LumexLink External Href="https://github.com/desmondinho" data-testid="user-link"> | ||
| @@desmondinho | ||
| </LumexLink> | ||
| </DescriptionContent> | ||
| </LumexUser> | ||
| ); | ||
|
|
||
| cut.FindByTestId("user-root").Should().NotBeNull(); | ||
| cut.FindByTestId("user-link").Should().NotBeNull(); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a missing property for the 'Avatar Props', but I'm unsure how to handle the class. Should I use a dedicated class? Or there are already some examples in the codebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need to think about that...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually sad :D I see a couple of options:
AvatarPropsclass and pass it as parameter. Flaw: Avatar props duplicates + need to always keep them synced;AvatarPropsparameter of typeDictionary<string, object>and pass it via attribute splatting (@attributes=...) toLumexAvatar. Flaw: we lose strict typing and intelliSense;AvatarContentparameter of typeRenderFragmentto allow consumers define avatars themselves.I think that the last option is the best here.
I have plans to move to the following structure in the future:
, where each slot is a separate component that can be styled and arranged independently.
This is something shadcn has been doing for a while alredy, and HeroUI v3 moved to the same concept. With this approach we will gain maximum flexibility as well.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I just realized that LumexChip already does that (
AvatarContentapproach) :D Can't remember own library solutions hahaUh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But don't wrap
AvatarContentwith the condition as I did -- it does not make sense.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh fantastic! I love this approach 🚀
Ok, so for now I add this 'AvatarContent' as RenderFragment
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it cool for you, if after this component is reviewed I make a branch to try and convert all the components with old styling method to the new? Just to have the project prepared for the architectural shift. I'd like to help, but I don't want to be too overwhelming 😁
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I’m totally fine with it! I really appreciate your input. I will do my best to make the process as smooth as possible for you.
I have to let you know that the utility for the new styling method is far from ideal, so we’ll most likely need to update it a bit.