Skip to content

feat(components): add User component#252

Merged
desmondinho merged 17 commits intoLumexUI:mainfrom
Denny09310:feature/user
Sep 17, 2025
Merged

feat(components): add User component#252
desmondinho merged 17 commits intoLumexUI:mainfrom
Denny09310:feature/user

Conversation

@Denny09310
Copy link
Contributor

@Denny09310 Denny09310 commented Sep 15, 2025

Description

This PR introduces a new LumexUser component, including its initial implementation, styling, usage properties, and accompanying documentation.

What's been done?

  • Added the LumexUser component.
  • Defined initial styles for LumexUser.
  • Added configurable component properties (props) to LumexUser to allow customization.
  • Created initial documentation page / docs entry for LumexUser so users know how to use it.
  • Created initial tests.

Checklist

  • My code follows the project's coding style and guidelines.
  • I have included inline docs for my changes, where applicable.
  • I have added, updated or removed tests according to my changes.
  • All tests are passing.
  • There's an open issue for the PR that I am making.

Additional Notes

I'd like to take this PR more as a draft, to ask you questions about possible implementations of properties and refinements.

Summary by CodeRabbit

  • New Features

    • Introduced a User component to display user info (avatar, name, description) with customizable slots and optional focusable styling.
  • Documentation

    • Added a dedicated User component page with usage, custom description example, slots overview, and API references.
    • Integrated interactive code previews and added the User page to the components navigation.
  • Tests

    • Added tests verifying basic rendering, description handling, and custom content via slots, improving component reliability.

@coderabbitai
Copy link

coderabbitai bot commented Sep 15, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a new LumexUser component with slots and styles, integrates it into docs (usage and custom description examples, preview code wrappers, and a full docs page), updates docs navigation to include LumexUser, and introduces component tests.

Changes

Cohort / File(s) Summary
Docs Navigation
docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs
Appends LumexUser item with PageStatus.New to ComponentsCategory and ComponentsApiCategory.
Docs: User Page
docs/LumexUI.Docs.Client/Pages/Components/User/User.razor
Adds the User docs page: route, sections (Usage, Custom Description, Slots, API), headings, slots metadata, and API components list.
Docs: User Examples
docs/LumexUI.Docs.Client/Pages/Components/User/Examples/Usage.razor, .../Examples/CustomDescription.razor
Adds example components showing basic usage and a customized description/avatar via slots.
Docs: Preview Code Wrappers
docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/Usage.razor, .../PreviewCodes/CustomDescription.razor
Adds InteractiveWebAssembly preview wrappers binding snippets and rendering the examples.
Component: LumexUser
src/LumexUI/Components/User/LumexUser.razor, src/LumexUI/Components/User/LumexUser.razor.cs
Introduces LumexUser component with slot-based rendering (Avatar, Wrapper, Name, Description), parameters (Name, Description, DescriptionContent, AvatarContent, IsFocusable, Classes), and style resolution.
Component Slots Model
src/LumexUI/Components/User/UserSlots.cs
Adds UserSlots class (Wrapper, Name, Description) for CSS class overrides per slot.
Styles
src/LumexUI/Styles/User.cs
Adds style builder for User component defining slots and a focusable variant.
Tests
tests/LumexUI.Tests/Components/User/UserTests.razor
Adds tests covering render, description rendering, and DescriptionContent precedence via presence checks.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Dev as App/Docs Page
  participant LU as LumexUser
  participant ST as Styles.User
  participant TM as TwMerge/TwVariants

  Dev->>LU: Render LumexUser(Name, Description, AvatarContent, DescriptionContent, IsFocusable, Classes)
  LU->>ST: Style(TwMerge)
  ST->>TM: Build ComponentVariant (slots, variants)
  TM-->>ST: ComponentVariant
  ST-->>LU: Variant (slot builders)
  LU->>LU: Resolve classes per slot (Base, Wrapper, Name, Description)
  alt DescriptionContent provided
    LU->>Dev: Render DescriptionContent
  else
    LU->>Dev: Render Description text
  end
  LU-->>Dev: Markup with data-slot attributes
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

📦 Scope: Components, 🚀 Type: Feature, 📚 Scope: Docs

Poem

I thump my paws—behold, a User appears!
Name, pic, and notes arranged like little gears.
Slots to nibble, styles to chew—so neat!
Focus rings twinkle beneath my feet.
Docs now hop; tests go boop—
Another carrot in the component soup! 🥕🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat(components): add User component" is concise, follows conventional commit style, and accurately highlights the primary change (adding the User component), so it clearly communicates the main intent to reviewers and in project history.
Description Check ✅ Passed The PR description largely follows the repository template: it provides a clear Description, a "What's been done?" bullet list detailing the component, styles, props, docs, and tests, includes a Checklist, and provides Additional Notes; however it omits the required "Closes #{issue_number}" line from the template and notes the PR is currently a draft requesting feedback. Because the content is otherwise complete and well-structured, this should be treated as mostly compliant with the template.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7379d6e and e14bdd0.

📒 Files selected for processing (7)
  • docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs (2 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/Examples/CustomDescription.razor (1 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/Examples/Usage.razor (1 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/CustomDescription.razor (1 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/User.razor (1 hunks)
  • src/LumexUI/Components/User/LumexUser.razor (1 hunks)
  • tests/LumexUI.Tests/Components/User/UserTests.razor (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Style="@RootStyle"
data-slot="base"
data-focus=""
data-focus-visible=""
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see on the HeroUI docs that this two attributes are set, I don't know if they can be useful to this library

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are setting interaction states programmatically. We don't do this.

These should be covered by Styles.Utils.FocusVisible in the styles. Search for the FocusVisible usages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perfect, I was already using that, I was only wandering if this was the right path. So the IsFocusable property, can be used to toggle this behavior or should I remove it directly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I haven't noticed this prop before. According to HeroUI isFocusable means: "Whether the user is focusable. This is useful when using Dropdown or similar components."

I think it's better to keep it to toggle this behaviour.

@codecov
Copy link

codecov bot commented Sep 15, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 93.28%. Comparing base (19b89dc) to head (e14bdd0).
⚠️ Report is 134 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #252      +/-   ##
==========================================
- Coverage   96.95%   93.28%   -3.67%     
==========================================
  Files          70      151      +81     
  Lines        1542     2635    +1093     
  Branches      150      393     +243     
==========================================
+ Hits         1495     2458     +963     
- Misses         28       92      +64     
- Partials       19       85      +66     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

/// <summary>
/// Gets or sets the content to render when using a custom description element.
/// </summary>
[Parameter] public RenderFragment? DescriptionContent { get; set; }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can merge Description and DescriptionContent all into a render fragment? Or it's better to leave the separated, giving the developer the possibility to choose wheter pass a text or a component?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. I tend to separate these for the sake of flexibility, yes. This is something we might want to merge in the future though. For now, we should stay consistent with other components that have a similar API.

}
</span>
}
} No newline at end of file
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw you have done this for the LumexAvatar. I've overcomplicated the code or should I leave it as it is?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually extract code into a separate render method when the logic is complex (like the fallback in the avatar) or when the rendering logic involves loops.

So, here is no need for this. See alert component for the reference. Note that RenderFragment goes first in the condition.

/// <summary>
/// Gets or sets the CSS class names for the user slots.
/// </summary>
[Parameter] public UserSlots? Classes { get; set; }
Copy link
Contributor Author

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?

Copy link
Contributor

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...

Copy link
Contributor

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:

  • Create the AvatarProps class and pass it as parameter. Flaw: Avatar props duplicates + need to always keep them synced;
  • Create the AvatarProps parameter of type Dictionary<string, object> and pass it via attribute splatting (@attributes=...) to LumexAvatar. Flaw: we lose strict typing and intelliSense;
  • Create the AvatarContent parameter of type RenderFragment to 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:

<Alert>
  <Alert.Icon />
  <Alert.Content>
    <Alert.Title>Success</Alert.Title>
    <Alert.Description>Your changes have been saved.</Alert.Description>
  </Alert.Content>
  <Alert.Close />
</Alert>

, 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.

Copy link
Contributor

@desmondinho desmondinho Sep 15, 2025

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 (AvatarContent approach) :D Can't remember own library solutions haha

Copy link
Contributor

@desmondinho desmondinho Sep 15, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But don't wrap AvatarContent with the condition as I did -- it does not make sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Oh fantastic! I love this approach 🚀

Ok, so for now I add this 'AvatarContent' as RenderFragment

Copy link
Contributor Author

@Denny09310 Denny09310 Sep 15, 2025

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 😁

Copy link
Contributor

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 😁

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.

/// <summary>
/// Gets or sets the content to render when using a custom name element.
/// </summary>
[Parameter] public RenderFragment? NameContent { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we can remove this for the time being to follow HeroUI's API.

They define name as just string. Basically, if some prop in HeroUI is string -- we follow it. If some prop is ReactNode or string | ReactNode -- we do both string and RenderFragment separately.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good, I didn't saw that it was only string. Probably I've confused it with the description. I'll fix it right away

/// <inheritdoc/>
protected override void OnParametersSet()
{
if( ( Description is not null ) && ( DescriptionContent is not null ) )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this check. I recently realized that it's not good to throw in such cases because passing both won't break anything. In the future, this should be refactored everywhere.

We just need to notify consumers that *Content takes precedence. See https://lumexui.org/docs/components/alert#usage

@Denny09310
Copy link
Contributor Author

I'm not sure about the last commit, but it's the first thing that has come to my mind about the focus. If it's wrong I'll revert the changes

@desmondinho
Copy link
Contributor

I'm not sure about the last commit, but it's the first thing that has come to my mind about the focus. If it's wrong I'll revert the changes

Good job -- I would do the same!

@Denny09310 Denny09310 marked this pull request as ready for review September 16, 2025 06:25
@Denny09310
Copy link
Contributor Author

For this last commit, I've found out that I was missing the link to the component API even in the 'LumexCode' and 'LumexKbd' components. I've updated the respective branches. Should I open another PR or it's possible for you to implement/retrieve these changes directly?

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (12)
src/LumexUI/Components/User/UserSlots.cs (1)

15-31: Consider sealing the slots class.

Slots containers are not intended for inheritance; sealing avoids accidental subclassing and enables minor JIT optimizations.

Apply:

-[ExcludeFromCodeCoverage]
-public class UserSlots : SlotBase
+[ExcludeFromCodeCoverage]
+public sealed class UserSlots : SlotBase
docs/LumexUI.Docs.Client/Pages/Components/User/Examples/Usage.razor (1)

1-5: Add alt text for the avatar (a11y) and consider a local image to avoid flaky docs.

Alt improves screen‑reader UX; local assets reduce external HTTP reliance.

-        <LumexAvatar Src="https://i.pravatar.cc/300" />
+        <LumexAvatar Src="https://i.pravatar.cc/300" Alt="Jane Doe avatar" />
docs/LumexUI.Docs.Client/Pages/Components/User/Examples/LinkDescription.razor (1)

1-10: Add alt text for the avatar (a11y).

Minor, but helpful for accessibility.

-        <LumexAvatar Src="https://avatars.githubusercontent.com/u/68395709?v=4" />
+        <LumexAvatar Src="https://avatars.githubusercontent.com/u/68395709?v=4" Alt="Dan avatar" />
tests/LumexUI.Tests/Components/User/UserTests.razor (2)

23-27: Fix typo in sample description.

Spelling: “Engineer”.

-            @<LumexUser Name="John Doe" Description="Software Engeneer" />
+            @<LumexUser Name="John Doe" Description="Software Engineer" />

30-46: Optional: add a focused-style test to cover the IsFocusable variant.

Covers the variant wiring and style application.

     [Fact]
     public void ShouldRenderDescriptionContent()
     {
@@
         cut.FindByTestId("user-link").Should().NotBeNull();
     }
+
+    [Fact]
+    public void ShouldApplyFocusableStylesWhenEnabled()
+    {
+        var cut = Render(
+            @<LumexUser Name="John Doe" IsFocusable="true" data-testid="user-root" />
+        );
+
+        var root = cut.FindByTestId("user-root");
+        root.Should().NotBeNull();
+        root.GetAttribute("class").Should().Contain("focus-visible:outline-2");
+    }

If Base is not the root node, adjust the testid to target the Base element.

docs/LumexUI.Docs.Client/Pages/Components/User/User.razor (2)

25-27: Fix “Class/Classes” description to match implementation

Root Class is merged into the Base slot, while per-slot overrides live in Classes.*. Update the copy to avoid confusion.

Apply this diff:

-            <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>
+            <li><code>Class</code>: CSS classes merged into the root element (base slot).</li>
+            <li><code>Classes</code>: Per‑slot CSS class overrides (<code>Base</code>, <code>Wrapper</code>, <code>Name</code>, <code>Description</code>).</li>

13-19: Add an accessibility note about IsFocusable

IsFocusable only toggles focus-visible styles. Consumers still need a focusable root (e.g., As="button") or tabindex="0" on a non-interactive element.

Apply this diff:

     </LumexAlert>
 
+    <LumexAlert Radius="@Radius.Large" Class="my-4">
+        <DescriptionContent>
+            Accessibility: <code>IsFocusable</code> only affects styles. For keyboard focus when using a non‑interactive root (e.g., <code>div</code>), set <code>As="button"</code> or provide <code>tabindex="0"</code> via <code>AdditionalAttributes</code>.
+        </DescriptionContent>
+    </LumexAlert>
src/LumexUI/Components/User/LumexUser.razor.cs (5)

15-16: Tighten summary wording

The component exposes Name/Description; “email” isn’t part of the API here.

-/// A component that represents user information, such as an avatar, name, and email.
+/// A component that represents user information, such as an avatar, name, and description.

39-42: Fix doc comment grammar

Minor typo.

-/// Gets or sets the whether the user is focusable.
+/// Gets or sets whether the user is focusable.

64-67: Use a more appropriate exception with context

NotImplementedException is misleading here. Prefer InvalidOperationException (or KeyNotFoundException) with a clear message.

-        if( !_slots.TryGetValue( slot, out var styles ) )
-        {
-            throw new NotImplementedException();
-        }
+        if( !_slots.TryGetValue( slot, out var styles ) )
+        {
+            throw new InvalidOperationException($"Unknown slot '{slot}' for {nameof(LumexUser)}.");
+        }
@@
-            _ => throw new NotImplementedException()
+            _ => throw new InvalidOperationException($"Unknown slot '{slot}' for {nameof(LumexUser)}.")

Also applies to: 75-76


49-59: Minor: cache Style() result locally/static

Style() already caches internally, but you can keep a private field to avoid re-fetching on every parameters set (micro). Optional.

-    protected override void OnParametersSet()
-    {
-        var user = Styles.User.Style( TwMerge );
-        _slots = user( new()
-        {
-            [nameof( IsFocusable )] = IsFocusable.ToString(),
-        } );
-    }
+    private ComponentVariant? _userVariant;
+
+    protected override void OnParametersSet()
+    {
+        _userVariant ??= Styles.User.Style( TwMerge );
+        _slots = _userVariant( new()
+        {
+            [nameof( IsFocusable )] = IsFocusable.ToString(),
+        } );
+    }

22-33: Optional: consider an Avatar slot in the future

If consumers need to style/target the avatar container via the slot system, introducing an Avatar slot (and Classes.Avatar) would align with the other slots. Non-blocking and can wait for the multi-component slot refactor you plan.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7fa3575 and d852201.

📒 Files selected for processing (11)
  • docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs (2 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/Examples/LinkDescription.razor (1 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/Examples/Usage.razor (1 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/LinkDescription.razor (1 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/Usage.razor (1 hunks)
  • docs/LumexUI.Docs.Client/Pages/Components/User/User.razor (1 hunks)
  • src/LumexUI/Components/User/LumexUser.razor (1 hunks)
  • src/LumexUI/Components/User/LumexUser.razor.cs (1 hunks)
  • src/LumexUI/Components/User/UserSlots.cs (1 hunks)
  • src/LumexUI/Styles/User.cs (1 hunks)
  • tests/LumexUI.Tests/Components/User/UserTests.razor (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
src/LumexUI/Components/User/UserSlots.cs (2)
src/LumexUI/Styles/User.cs (1)
  • ExcludeFromCodeCoverage (13-54)
src/LumexUI/Components/User/LumexUser.razor.cs (1)
  • ExcludeFromCodeCoverage (61-77)
docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs (1)
src/LumexUI/Components/User/LumexUser.razor.cs (1)
  • LumexUser (17-78)
src/LumexUI/Styles/User.cs (6)
src/LumexUI/Components/User/LumexUser.razor.cs (2)
  • ExcludeFromCodeCoverage (61-77)
  • LumexUser (17-78)
src/LumexUI/Components/User/UserSlots.cs (1)
  • ExcludeFromCodeCoverage (14-31)
src/LumexUI/Utilities/Variants/SlotCollection.cs (1)
  • SlotCollection (9-34)
src/LumexUI/Utilities/Variants/VariantCollection.cs (1)
  • VariantCollection (9-34)
src/LumexUI/Utilities/Variants/VariantValueCollection.cs (1)
  • VariantValueCollection (9-34)
src/LumexUI/Styles/Utils.cs (1)
  • Utils (5-28)
src/LumexUI/Components/User/LumexUser.razor.cs (4)
src/LumexUI/Components/Bases/LumexComponentBase.cs (1)
  • LumexComponentBase (18-58)
src/LumexUI/Utilities/Variants/TwVariants.cs (1)
  • ComponentSlot (112-115)
src/LumexUI/Styles/User.cs (1)
  • ExcludeFromCodeCoverage (13-54)
src/LumexUI/Components/User/UserSlots.cs (1)
  • ExcludeFromCodeCoverage (14-31)
🔇 Additional comments (11)
src/LumexUI/Components/User/UserSlots.cs (1)

11-31: LGTM — slots DTO looks consistent with the slot infra.

Docs are clear, nullability is correct, and it aligns with GetStyles usage across slots.

Please confirm SlotBase exposes Base so Classes?.Base works end-to-end.

docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/Usage.razor (1)

1-5: LGTM — matches the existing PreviewCode pattern.

Confirm @rendermode is consistent with other User previews for a uniform DX.

docs/LumexUI.Docs.Client/Pages/Components/User/PreviewCodes/LinkDescription.razor (1)

1-5: LGTM — consistent with the Usage preview.

src/LumexUI/Styles/User.cs (3)

7-11: Resolve potential missing usings for type references.

If there isn’t a global using for UserSlots/LumexUser, this will not compile.

 using LumexUI.Utilities;
+using LumexUI; // for UserSlots
+using LumexUI.Components.User; // for LumexUser
 using TailwindMerge;

16-23: Cache ties the first TwMerge instance to the variant.

If TwMerge isn’t a singleton, later calls won’t pick up a different instance/config.

Consider either (a) guaranteeing TwMerge is singleton in DI, or (b) keying the cache by the TwMerge instance and recreating when it changes.


44-51: Focus styles require a focusable element.

Ensure the Base slot renders on a focusable element or sets tabindex when IsFocusable=true; otherwise the variant has no effect.

docs/LumexUI.Docs.Client/Common/Navigation/NavigationStore.cs (1)

49-51: New nav entries: verify target pages/routes exist (LumexUser verified; LumexTooltip outstanding)

  • LumexUser — verified: docs/LumexUI.Docs.Client/Pages/Components/User/User.razor contains @page "/docs/components/user".
  • LumexTooltip — not verified here; ensure a corresponding page/route and correct filename/casing exist to avoid broken links.
docs/LumexUI.Docs.Client/Pages/Components/User/User.razor (2)

6-12: Solid docs section scaffolding

Usage + example previews read well and match the component surface.


57-66: Use full component name for ComponentLinksProps id — replace "Usr" with "User" or confirm shorthand is intentional.
Location: docs/LumexUI.Docs.Client/Pages/Components/User/User.razor (linksProps: new ComponentLinksProps("Usr", isServer: true), line 64).

src/LumexUI/Components/User/LumexUser.razor (2)

6-11: Root wrapper looks good

Props/attrs are correctly forwarded to LumexComponent and the base slot class is applied.


14-27: Avoid rendering empty spans when no content

Skip Name/Description wrappers when the corresponding content is empty to reduce DOM noise.

File: src/LumexUI/Components/User/LumexUser.razor (lines 14-27)

-        <span class="@GetStyles( nameof( S.Name ) )" data-slot="name">
-            @Name
-        </span>
+        @if (!string.IsNullOrWhiteSpace(Name))
+        {
+            <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>
+        @if (DescriptionContent is not null || !string.IsNullOrWhiteSpace(Description))
+        {
+            <span class="@GetStyles( nameof( S.Description ) )" data-slot="description">
+                @if ( DescriptionContent is not null )
+                {
+                    @DescriptionContent
+                }
+                else
+                {
+                    @Description
+                }
+            </span>
+        }

Run locally to confirm there are no tests depending on these wrappers:
rg -n -C2 'data-slot="(description|name)"' tests || git grep -n 'data-slot="(description|name)"'

@desmondinho
Copy link
Contributor

For this last commit, I've found out that I was missing the link to the component API even in the 'LumexCode' and 'LumexKbd' components. I've updated the respective branches. Should I open another PR or it's possible for you to implement/retrieve these changes directly?

I also constantly forget to add the API links 😄 I think there might be more missing, so it’s better to create a single PR to add them all. You can do this if you want. I'd immediately approve and merge.

@desmondinho desmondinho merged commit c3cfefa into LumexUI:main Sep 17, 2025
3 of 4 checks passed
@desmondinho
Copy link
Contributor

Thank you very much for your contribution! <3

P.S. Sorry, I just realized I forgot to thank you..

@coderabbitai coderabbitai bot mentioned this pull request Feb 22, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants