Search code examples
csslaravelcss-transitionslaravel-livewirealpine.js

Transition a div height when the content is updated by livewire


So we got a div with a Livewire component to choose languages :

<div>
    @livewire('multiple-languages', [ ... ])
</div>

When the user selects a language in the component, it changes its content to give more options to choose from. Incidentally, the content becomes taller. I would like to animate the height change so it gives a better UX instead of just jumping to its proper size.

Since I'm using AlpineJS as well, I thought about using https://alpinejs.dev/directives/transition but without success so far. A pure css solution would be fine too.


Solution

  • To be able to utilize transitions, you can use the frontend (AlpineJS) to create transitions. This means you want to let Livewire pass the data on to Alpine so it can do its frontend magic.

    Livewire component:

    <?php
    
    namespace App\Http\Livewire;
    
    use Illuminate\Support\Str; 
    
    class TestComponent extends Component
    {
        public array $addons = [
            'test',
        ];
    
        public function render()
        {
            return view('livewire.test-component');
        }
    
        public function add()
        {
            $this->addons[] = Str::random(32);
            $this->dispatchBrowserEvent('change-event', ['addons' => $this->addons]);
        }
    }
    

    Then in the blade file:

    {{-- Use brackets with json_encode to escape double quotes; If you're using the newest version, you could also use @js($addons) --}}
    <div x-data="{ addons: {{json_encode($addons)}} }">
        <div class="w-full p-4 rounded-lg border border-solid border-blue-500 mb-4 flex flex-wrap flex-row">
            <button class="btn btn-red" role="button" wire:click="add()">
                Click Me
            </button>
        </div>
    
        <div x-on:change-event.window="addons = $event.detail.addons"
             class="flex flex-wrap flex-row w-full">
            <template x-for="(addon, index) in addons" :key="index">
                <div class="p-4 w-full bg-white shadow-lg rounded-lg border-2 border-red-500 mb-5 mr-5"
                     x-data="{show: false}" x-init="$nextTick(() => {show = true} )"
                     x-show="show"
                     x-transition:enter="transition duration-500 transform"
                     x-transition:enter-start="opacity-0 -translate-x-full"
                     x-transition:enter-end="opacity-100 translate-x-0"
                     x-transition:leave="transition duration-1000 transform"
                     x-transition:leave-start="opacity-100 translate-x-0"
                     x-transition:leave-end="opacity-0 -translate-x-full">
                    <div class="block">
                        <h3 x-html="addon" class="font-bold text-lg">
    
                        </h3>
                    </div>
                </div>
            </template>
        </div>
    </div>
    

    Notes:

    • Due to the fact you load data with AlpineJS, you will have to fiddle with how you pass potential data back to Livewire. Most likely you'll have to use $wire.emit.
    • It is important to note the $nextTick. Templates that get generated don't have transitions. However, if you change the show state after Alpine has updated the DOM, then the transitions will take effect. For my toaster notifications, I add the notification to an array which gets checked in the x-show (and thus applies the transition).
    • If you add data on page load (like I've done in the example), then the initial data will also have the transitions.

    You'll probably still need to fiddle around with the transitions to see what looks good on your part, but this hopefully will put you in the right direction!