Skip to main content

Navigation

Hover Card code

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

resources/views/components/ui/hover-card.blade.php

@props([
    'align' => 'bottom',
    'width' => '72',
    'delay' => 200,
])

@php
    /*
    | Hover-activated rich preview card (like a popover, but opens on hover).
    | Put the hover target in the `trigger` slot and any markup in the default
    | slot. Opens after `delay` ms and stays open while the cursor is over the
    | trigger or the card itself.
    |
    | align: top, bottom, left, right
    |
    | Usage:
    |   <x-ui.hover-card>
    |       <x-slot:trigger><a href="#" class="font-medium">@ada</a></x-slot:trigger>
    |       <p>Profile preview content.</p>
    |   </x-ui.hover-card>
    */
    $alignClass = [
        'bottom' => 'top-full start-0 mt-2',
        'top' => 'bottom-full start-0 mb-2',
        'left' => 'end-full top-0 me-2',
        'right' => 'start-full top-0 ms-2',
    ][$align] ?? 'top-full start-0 mt-2';

    $widthClass = [
        '56' => 'w-56', '64' => 'w-64', '72' => 'w-72', '80' => 'w-80', '96' => 'w-96',
    ][$width] ?? $width;

    $cardId = 'hover-card-'.substr(md5($align.$width.$slot), 0, 10);
@endphp

<div
    x-data="{
        open: false,
        timer: null,
        show() {
            clearTimeout(this.timer);
            this.timer = setTimeout(() => { this.open = true; }, @js((int) $delay));
        },
        hide() {
            clearTimeout(this.timer);
            this.open = false;
        },
    }"
    @mouseenter="show()"
    @mouseleave="hide()"
    @focusin="show()"
    @focusout="hide()"
    @keydown.escape.window="hide()"
    {{ $attributes->merge(['class' => 'relative inline-block']) }}
>
    <div :aria-expanded="open.toString()" aria-controls="{{ $cardId }}">
        {{ $trigger }}
    </div>

    <div
        id="{{ $cardId }}"
        x-show="open" x-cloak
        x-transition:enter="transition ease-out duration-150"
        x-transition:enter-start="opacity-0 scale-95"
        x-transition:enter-end="opacity-100 scale-100"
        x-transition:leave="transition ease-in duration-100"
        x-transition:leave-start="opacity-100 scale-100"
        x-transition:leave-end="opacity-0 scale-95"
        role="dialog"
        class="absolute z-50 {{ $widthClass }} {{ $alignClass }} rounded-xl border border-gray-200 dark:border-gray-800 bg-white dark:bg-gray-900 p-4 text-sm text-gray-600 dark:text-gray-300 shadow-lg ring-1 ring-black/5"
    >
        {{ $slot }}
    </div>
</div>