Skip to main content

General

Avatar code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/avatar.blade.php

@props([
    'src' => null,
    'name' => null,
    'size' => 'base',
    'shape' => 'circle',
    'status' => null,
    'icon' => 'user',
    'initials' => null,
    'background' => null,
    'color' => null,
    'text' => true,
])

@php
    /*
    | Avatar image with initials fallback and optional status dot.
    |
    | size:   xs, sm, base, lg, xl, 2xl
    | shape:  circle, square, rounded
    | status: online, away, busy, offline
    |
    | Customisation:
    |   initials:   override the derived initials (e.g. a user's custom characters)
    |   background: CSS background value — solid hex, linear-gradient(...), or a
    |               generated pattern from PaintSwirlGenerator / GlassOrbGenerator /
    |               IdenticonGenerator / FacetedGemGenerator /
    |               MeshGradientGenerator / TopographicGenerator /
    |               User::avatarBackground()
    |   color:      hex color for the initials/icon characters (e.g. #ffffff)
    |   text:       set false to hide the initials and icon entirely so a
    |               generated background shows on its own. The name is kept as an
    |               aria-label so the avatar stays announced.
    |
    | Identicon canvases are light, so pair them with a dark character color, or
    | pass :text="false" to show the mark on its own.
    |
    | Usage:
    |   <x-ui.avatar name="Ada Lovelace" status="online" />
    |   <x-ui.avatar src="/img/u.jpg" size="lg" />
    |   <x-ui.avatar name="Ada Lovelace" initials="AL" background="#4f46e5" color="#ffffff" />
    |   <x-ui.avatar name="Ada Lovelace" background="linear-gradient(to bottom right, #4f46e5, #ec4899)" />
    |   <x-ui.avatar name="Ada Lovelace" shape="square" :background="\App\Support\GlassOrbGenerator::cssBackground(['#0ea5e9', '#1d4ed8'], 88)" />
    |   <x-ui.avatar name="Ada Lovelace" shape="rounded" :background="\App\Support\GlassOrbGenerator::cssBackground(['#0ea5e9', '#1d4ed8'], 88)" />
    |   <x-ui.avatar name="Ada Lovelace" :background="\App\Support\GlassOrbGenerator::cssBackground(['#0ea5e9', '#1d4ed8'], 88)" />
    |   <x-ui.avatar name="Ada Lovelace" :background="\App\Support\IdenticonGenerator::cssBackground(['#0ea5e9', '#f59e0b'], 4242)" color="#0f172a" />
    |   <x-ui.avatar name="Ada Lovelace" :text="false" :background="\App\Support\IdenticonGenerator::cssBackground(['#0ea5e9', '#f59e0b'], 4242)" />
    |   <x-ui.avatar name="Ada Lovelace" :background="\App\Support\FacetedGemGenerator::cssBackground(['#38bdf8', '#2563eb', '#0f172a'], 42)" />
    |   <x-ui.avatar name="Ada Lovelace" :background="\App\Support\MeshGradientGenerator::cssBackground(['#f472b6', '#a855f7', '#38bdf8'], 128)" />
    |   <x-ui.avatar name="Ada Lovelace" :background="\App\Support\TopographicGenerator::cssBackground(['#052e16', '#15803d', '#0891b2'], 314)" />
    |   <x-ui.avatar name="Ada Lovelace" :background="\App\Support\GlassOrbGenerator::cssBackground(
    |       ['#0ea5e9', '#1d4ed8'],
    |       88,
    |       ['highlight' => ['colors' => ['#ffffff', '#67e8f9']], 'shadow' => ['colors' => ['#020617', '#312e81']]],
    |   )" />
    |   <x-ui.avatar name="Ada Lovelace" :background="$user->avatarBackground()" :color="$user->avatar_character_color" />
    */
    $sizes = [
        'xs' => 'w-6 h-6 text-[10px]',
        'sm' => 'w-8 h-8 text-xs',
        'base' => 'w-10 h-10 text-sm',
        'lg' => 'w-12 h-12 text-base',
        'xl' => 'w-16 h-16 text-lg',
        '2xl' => 'w-20 h-20 text-2xl',
    ];

    $shapes = [
        'circle' => 'rounded-full',
        'square' => 'rounded-none',
        'rounded' => 'rounded-2xl',
    ];

    $statusColors = [
        'online' => 'bg-green-500',
        'away' => 'bg-amber-500',
        'busy' => 'bg-red-500',
        'offline' => 'bg-gray-400',
    ];

    $statusSize = in_array($size, ['xs', 'sm']) ? 'w-2 h-2' : 'w-3 h-3';

    $showText = (bool) $text;

    $resolvedInitials = $showText
        ? ($initials
            ?: ($name
                ? \Illuminate\Support\Str::of($name)->explode(' ')->take(2)->map(fn ($p) => mb_substr($p, 0, 1))->implode('')
                : null))
        : null;

    // With the characters hidden there is no text for assistive tech to read, so
    // the name moves onto the wrapper as a label. With no name at all the avatar
    // is purely decorative and is hidden instead.
    $silent = ! $showText && ! $src;
    $accessibilityAttributes = $silent
        ? (filled($name) ? ['role' => 'img', 'aria-label' => $name] : ['aria-hidden' => 'true'])
        : [];

    $sizeClass = $sizes[$size] ?? $sizes['base'];
    $shapeClass = $shapes[$shape] ?? $shapes['circle'];
    $isCircular = ($shapes[$shape] ?? $shapes['circle']) === $shapes['circle'];

    // Custom colors are arbitrary hex values, so apply them inline. Fall back to
    // the brand gradient/white text when no custom colors are provided.
    $hasCustomBackground = filled($background);

    // Circular generators (glass orbs) leave transparent corners in their SVG.
    // For square/card shapes, zoom the background so the marble fills the tile.
    $resolvedBackground = $background;
    if ($hasCustomBackground && ! $isCircular && is_string($background) && str_contains($background, 'center/cover')) {
        $resolvedBackground = str_replace('center/cover no-repeat', 'center / 145% 145% no-repeat', $background);
    }

    $bubbleClasses = $hasCustomBackground
        ? 'inline-flex items-center justify-center overflow-hidden font-semibold ring-2 ring-white dark:ring-gray-900 '.$shapeClass.' '.$sizeClass
        : 'inline-flex items-center justify-center overflow-hidden bg-gradient-to-br from-brand-500 to-brand-700 font-semibold text-white ring-2 ring-white dark:ring-gray-900 '.$shapeClass.' '.$sizeClass;

    $bubbleStyles = collect([
        $hasCustomBackground ? "background:{$resolvedBackground}" : null,
        filled($color) ? "color:{$color}" : null,
    ])->filter()->implode(';');
@endphp

<span {{ $attributes->merge(['class' => 'relative inline-flex shrink-0'] + $accessibilityAttributes) }}>
    <span class="{{ $bubbleClasses }}" @if ($bubbleStyles) style="{{ $bubbleStyles }}" @endif>
        @if ($src)
            <img src="{{ $src }}" alt="{{ $name }}" class="h-full w-full object-cover">
        @elseif ($resolvedInitials)
            {{ $resolvedInitials }}
        @elseif ($showText)
            <x-ui.icon :name="$icon" weight="fill" size="base" />
        @endif
    </span>

    @if ($status)
        <span class="absolute bottom-0 end-0 rounded-full ring-2 ring-white dark:ring-gray-900 {{ $statusSize }} {{ $statusColors[$status] ?? $statusColors['offline'] }}"></span>
    @endif
</span>