Files
Htmx/docs/Components/Avatar.md
T
2026-05-04 19:57:48 +05:00

170 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Avatar
A circular user avatar. Shows an image when a `src` URL is provided; falls back to a text/initials span otherwise.
---
## HTML structure
```
span.relative.flex.{size classes}.shrink-0.overflow-hidden.rounded-full
img[src, alt, class] ← when src is provided
span.flex.items-center... ← fallback when no src
{fallback text}
```
---
## CSS mechanics
| Class | Effect |
|---|---|
| `rounded-full overflow-hidden` | Clips content to a circle |
| `aspect-square h-full w-full object-cover` | Image fills the circle without distortion |
| `bg-muted text-muted-foreground` | Neutral background for the initials fallback |
| Size `h-8 w-8` / `h-10 w-10` / `h-14 w-14` / `h-20 w-20` | sm / default / lg / xl |
---
## Constructor signature
```csharp
public Avatar(
string fallback,
string? src = null,
string size = "default")
```
| Parameter | Description |
|---|---|
| `fallback` | Text shown when no `src` is given; also used as `alt` text on the image |
| `src` | Optional image URL |
| `size` | `"sm"` / `"default"` / `"lg"` / `"xl"` |
---
## Usage examples
### Initials avatar
```csharp
new Avatar(fallback: "JD")
new Avatar(fallback: "JD", size: "lg")
```
### Image avatar with fallback
```csharp
new Avatar(fallback: "Jane Doe", src: "/avatars/jane.jpg", size: "default")
```
### Sizes
```csharp
new Avatar(fallback: "SM", size: "sm") // 32×32
new Avatar(fallback: "DF", size: "default") // 40×40
new Avatar(fallback: "LG", size: "lg") // 56×56
new Avatar(fallback: "XL", size: "xl") // 80×80
```
### Inside a user card
```csharp
var avatar = new Avatar(fallback: user.Initials, src: user.AvatarUrl, size: "lg");
// In a page's RenderUserCard override:
protected override void RenderUserAvatar(HtmxRenderContext ctx)
=> avatar.Render(ctx.Next());
```
---
## Tips and tricks
- Compute initials before constructing the Avatar — the component does not extract them from a full name. See `MainLayout`'s `GetInitials` helper for a reference implementation.
- Always provide `fallback` even when you also provide `src` — it serves as the `alt` attribute for accessibility.
- The Avatar does not handle image load errors. If you need a graceful image fallback on 404, add an `onerror="this.style.display='none'"` attribute by embedding it in the `src` or use `hxAttrs` in a subclassed version.
- For a group of overlapping avatars (avatar stack), wrap several Avatars in a flex container with negative margin: `<div class="flex -space-x-2">`.
---
## Complete page example
**`Templates/ProfilePage.htmx`**
```html
<div class="max-w-lg mx-auto py-10">
<div class="flex items-center gap-4 mb-6">
$$UserAvatar$$
<div>
<h1 class="text-xl font-bold">$$DisplayName$$</h1>
<p class="text-sm text-muted-foreground">$$Email$$</p>
</div>
</div>
<p class="text-sm">Member since $$JoinDate$$</p>
</div>
```
**`Templates/ProfilePage.htmx.cs`**
```csharp
namespace Htmx.ApiDemo.Templates;
public sealed class ProfilePage : ProfilePageBase
{
private readonly IHtmxComponent _avatar;
private readonly byte[] _displayName;
private readonly byte[] _email;
private readonly byte[] _joinDate;
public ProfilePage(AppUser user)
{
_avatar = new Components.Avatar(
fallback: GetInitials(user.DisplayName),
size: "lg");
_displayName = (user.DisplayName ?? "Unknown").ToUtf8Bytes();
_email = user.Email.ToUtf8Bytes();
_joinDate = user.CreatedAt.ToString("MMMM yyyy").ToUtf8Bytes();
}
private static string GetInitials(string? name)
{
if (string.IsNullOrWhiteSpace(name)) return "?";
var parts = name.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return parts.Length >= 2
? $"{parts[0][0]}{parts[^1][0]}"
: name[..1].ToUpperInvariant();
}
protected override void RenderUserAvatar(HtmxRenderContext ctx)
=> _avatar.Render(ctx.Next());
protected override void RenderDisplayName(HtmxRenderContext ctx)
=> ctx.Writer.WriteUtf8(_displayName);
protected override void RenderEmail(HtmxRenderContext ctx)
=> ctx.Writer.WriteUtf8(_email);
protected override void RenderJoinDate(HtmxRenderContext ctx)
=> ctx.Writer.WriteUtf8(_joinDate);
}
```
**GET handler**
```csharp
[Handler]
[MapGet("/profile")]
public static partial class GetProfileHandler
{
public record Query();
private static async Task<IResult> HandleAsync(
Query _,
HttpContext ctx,
MongoDbService db,
CancellationToken ct)
{
var email = ctx.User.FindFirst(ClaimTypes.Email)?.Value ?? "";
var user = await db.FindByNormalizedEmailAsync(email.ToUpperInvariant(), ct);
if (user is null) return Results.Redirect("/login");
return await ctx.WriteHtmxPage(new ProfilePage(user), title: "Profile");
}
}
```