Actions
Button Side Expand code
Copy the implementation or inspect every file that belongs to this component unit.
resources/views/components/ui/button-side-expand.blade.php
@props([
'icon' => 'plus',
'openIcon' => 'x',
'text' => null,
'iconTrailing' => 'caret-right',
'label',
'side' => 'right',
'variant' => 'primary',
'size' => 'base',
])
@php
/*
| Compact trigger that expands horizontally into a button bar. Click the
| trigger to reveal slot content; click again or press Escape to collapse.
|
| Pass `text` for a labeled button trigger (e.g. "Actions" with a chevron).
| Omit `text` for a compact icon-only trigger.
|
| side: right (default) — grows to the right; left — grows to the left
|
| Usage:
| <x-ui.button-side-expand text="Actions" label="Open actions" variant="outline">
| <x-ui.icon-button icon="pencil-simple" label="Edit" variant="ghost" size="sm" />
| </x-ui.button-side-expand>
|
| <x-ui.button-side-expand icon="plus" label="Open actions">
| <x-ui.icon-button icon="copy" label="Copy" variant="ghost" size="sm" />
| </x-ui.button-side-expand>
*/
$isTextTrigger = filled($text);
$accessibleLabel = $label ?? $text;
$iconTriggerSizes = [
'sm' => 'h-8 w-8',
'base' => 'h-9 w-9',
'lg' => 'h-11 w-11',
];
$textTriggerSizes = [
'sm' => 'gap-1.5 px-3 py-2 text-sm',
'base' => 'gap-2 px-4 py-2.5 text-sm',
'lg' => 'gap-2 px-5 py-3 text-base',
];
$iconSizes = [
'sm' => 'sm',
'base' => 'base',
'lg' => 'lg',
];
$triggerVariants = [
'primary' => 'bg-brand-600 text-white hover:bg-brand-500 focus:ring-brand-500',
'secondary' => 'bg-gray-100 text-gray-900 hover:bg-gray-200 focus:ring-gray-400 dark:bg-gray-800 dark:text-gray-100 dark:hover:bg-gray-700',
'outline' => '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' => 'text-gray-600 hover:bg-gray-100 focus:ring-gray-400 dark:text-gray-300 dark:hover:bg-gray-800',
'accent' => 'bg-accent-600 text-white hover:bg-accent-500 focus:ring-accent-500',
];
$iconSize = $iconSizes[$size] ?? $iconSizes['base'];
$triggerVariantClass = $triggerVariants[$variant] ?? $triggerVariants['primary'];
$expandDirection = $side === 'left' ? 'flex-row-reverse' : 'flex-row';
$containerRadius = $isTextTrigger ? 'rounded-lg' : 'rounded-full';
$triggerRadius = $isTextTrigger ? 'rounded-md' : 'rounded-full';
$trailingIcon = $side === 'left' && $isTextTrigger ? 'caret-left' : $iconTrailing;
@endphp
<div
x-data="{ open: false }"
@click.outside="open = false"
@keydown.escape.window="open = false"
{{ $attributes->class(['inline-flex']) }}
>
<div @class([
'inline-flex items-center overflow-hidden border border-gray-200 bg-white p-1 shadow-sm dark:border-gray-800 dark:bg-gray-900',
$containerRadius,
$expandDirection,
])>
<button
type="button"
@click="open = ! open"
:aria-expanded="open.toString()"
aria-label="{{ $accessibleLabel }}"
@class([
'inline-flex shrink-0 items-center justify-center font-semibold transition-[background-color,color,transform] duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 dark:focus:ring-offset-gray-900',
$triggerRadius,
$triggerVariantClass,
$isTextTrigger
? ($textTriggerSizes[$size] ?? $textTriggerSizes['base'])
: ($iconTriggerSizes[$size] ?? $iconTriggerSizes['base']),
])
>
@if ($isTextTrigger)
<span>{{ $text }}</span>
<x-ui.icon
:name="$trailingIcon"
:size="$iconSize"
weight="bold"
::class="open ? '{{ $side === 'left' ? '-rotate-90' : 'rotate-90' }}' : ''"
class="transition-transform duration-200"
/>
@else
<x-ui.icon :name="$icon" :size="$iconSize" weight="bold" x-show="! open" />
<x-ui.icon :name="$openIcon" :size="$iconSize" weight="bold" x-show="open" x-cloak />
@endif
</button>
<div
class="flex items-center gap-0.5 overflow-hidden whitespace-nowrap transition-[max-width,opacity,margin] duration-300 ease-out"
:class="open
? '{{ $side === 'left' ? 'max-w-96 opacity-100 me-0.5' : 'max-w-96 opacity-100 ms-0.5' }}'
: 'max-w-0 opacity-0 {{ $side === 'left' ? 'me-0' : 'ms-0' }} pointer-events-none'"
x-cloak
>
<span
class="h-5 w-px shrink-0 bg-gray-200 dark:bg-gray-700"
x-show="open"
x-cloak
aria-hidden="true"
></span>
<div class="flex items-center gap-0.5 ps-0.5">
{{ $slot }}
</div>
</div>
</div>
</div>