Actions
Split Button code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/split-button.blade.php
@props([
'variant' => 'primary',
'size' => 'base',
'icon' => null,
'iconWeight' => 'bold',
'href' => null,
'type' => 'submit',
'align' => 'right',
'width' => '48',
'loading' => false,
])
@php
/*
| Button with a primary action plus a dropdown of related actions. The
| label goes in the default slot (renders an <a> when `href` is set) and
| <x-ui.dropdown.item> rows go in the `menu` slot.
|
| Variants: primary, secondary, outline, ghost, danger, success, accent
| Sizes: xs, sm, base, lg, xl
|
| Usage:
| <x-ui.split-button icon="plus">
| Create project
| <x-slot:menu>
| <x-ui.dropdown.item icon="copy">Duplicate existing</x-ui.dropdown.item>
| <x-ui.dropdown.item icon="download-simple">Import from CSV</x-ui.dropdown.item>
| </x-slot:menu>
| </x-ui.split-button>
*/
$base = 'inline-flex items-center justify-center gap-2 font-semibold transition-[box-shadow,border-color,color,transform] ease-in-out duration-150 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:pointer-events-none cursor-pointer whitespace-nowrap';
$variants = [
'primary' => 'bg-brand-600 text-white shadow-sm hover:bg-brand-500 active:bg-brand-700 focus:ring-brand-500',
'secondary' => 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-700 focus:ring-gray-400',
'outline' => 'bg-transparent border border-brand-300 text-brand-700 hover:bg-brand-50 focus:ring-brand-500 dark:border-brand-700 dark:text-brand-300 dark:hover:bg-brand-950/40',
'ghost' => 'bg-transparent text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:ring-gray-400',
'danger' => 'bg-red-600 text-white shadow-sm hover:bg-red-500 active:bg-red-700 focus:ring-red-500',
'success' => 'bg-green-600 text-white shadow-sm hover:bg-green-500 active:bg-green-700 focus:ring-green-500',
'accent' => 'bg-accent-600 text-white shadow-sm hover:bg-accent-500 active:bg-accent-700 focus:ring-accent-500',
];
$sizes = [
'xs' => 'text-xs px-2.5 py-1.5',
'sm' => 'text-sm px-3 py-2',
'base' => 'text-sm px-4 py-2.5',
'lg' => 'text-base px-5 py-3',
'xl' => 'text-base px-6 py-3.5',
];
$toggleSizes = [
'xs' => 'px-1.5', 'sm' => 'px-2', 'base' => 'px-2.5', 'lg' => 'px-3', 'xl' => 'px-3.5',
];
$iconSize = [
'xs' => 'sm', 'sm' => 'sm', 'base' => 'base', 'lg' => 'lg', 'xl' => 'lg',
][$size] ?? 'base';
$divider = [
'primary' => 'border-brand-500 dark:border-brand-700',
'secondary' => 'border-gray-200 dark:border-gray-700',
'outline' => 'border-brand-300 dark:border-brand-700',
'ghost' => 'border-gray-200 dark:border-gray-800',
'danger' => 'border-red-500 dark:border-red-700',
'success' => 'border-green-500 dark:border-green-700',
'accent' => 'border-accent-500 dark:border-accent-700',
];
$variantClass = $variants[$variant] ?? $variants['primary'];
$sizeClass = $sizes[$size] ?? $sizes['base'];
$toggleSizeClass = $toggleSizes[$size] ?? $toggleSizes['base'];
$dividerClass = $divider[$variant] ?? $divider['primary'];
$alignClass = $align === 'left' ? 'origin-top-left start-0' : 'origin-top-right end-0';
$widthClass = ['48' => 'w-48', '56' => 'w-56', '64' => 'w-64'][$width] ?? $width;
$menuId = 'split-button-'.substr(md5($variant.$size.$slot), 0, 10);
@endphp
<div
x-data="{ open: false }"
@click.outside="open = false"
@keydown.escape.window="open = false"
{{ $attributes->merge(['class' => 'relative inline-flex']) }}
>
@if ($href)
<a href="{{ $href }}" class="{{ $base }} {{ $variantClass }} {{ $sizeClass }} rounded-s-lg rounded-e-none">
@if ($loading)
<x-ui.icon name="spinner-gap" :size="$iconSize" class="animate-spin" />
@elseif ($icon)
<x-ui.icon :name="$icon" :weight="$iconWeight" :size="$iconSize" />
@endif
{{ $slot }}
</a>
@else
<button type="{{ $type }}" @disabled($loading) class="{{ $base }} {{ $variantClass }} {{ $sizeClass }} rounded-s-lg rounded-e-none">
@if ($loading)
<x-ui.icon name="spinner-gap" :size="$iconSize" class="animate-spin" />
@elseif ($icon)
<x-ui.icon :name="$icon" :weight="$iconWeight" :size="$iconSize" />
@endif
{{ $slot }}
</button>
@endif
<button
type="button"
@click="open = ! open"
@keydown.arrow-down.prevent="open = true"
:aria-expanded="open.toString()"
aria-controls="{{ $menuId }}"
aria-haspopup="menu"
aria-label="More actions"
class="{{ $base }} {{ $variantClass }} {{ $toggleSizeClass }} rounded-e-lg rounded-s-none border-s {{ $dividerClass }}"
>
<x-ui.icon name="caret-down" weight="bold" :size="$iconSize" x-bind:class="open && 'rotate-180'" class="transition-transform" />
</button>
<div
id="{{ $menuId }}"
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"
class="absolute top-full z-50 mt-2 {{ $widthClass }} {{ $alignClass }} rounded-xl border border-gray-200 bg-white p-1.5 shadow-lg ring-1 ring-black/5 dark:border-gray-800 dark:bg-gray-900"
role="menu"
@click="open = false"
>
{{ $menu }}
</div>
</div>