jrdavidson's avatar

AlpineJS for Menus

I'm having a hard time passing around the x-data of if the menu-item arrows should be in the open state or not open state. Currently both + and - icons display. Can anyone shed some light on what I'm doing wrong?

<x-sidebar.menu-item
    x-data="{
        open: false,
        toggle() {
            if (this.open) {
                return this.close()
            }

            this.$refs.button.focus()

            this.open = true
        },
        close(focusAfter) {
            if (! this.open) return

            this.open = false

            focusAfter && focusAfter.focus()
        }}">
    <x-sidebar.menu-label hasSub isOpen=open>
        <x-sidebar.menu-icon icon="ki-people" />
        <x-sidebar.menu-title>Groups</x-sidebar.menu-title>
    </x-sidebar.menu-label>
    <x-slot:subMenu isOpen>
        <x-sidebar.menu-link :href="route('users.index')">Users</x-sidebar.menu-link>
    </x-slot:subMenu>
</x-sidebar.menu-item>

sidebar/menu-label

@aware([
    'isOpen' => false,
    'hasSub' => false,
])

<x-menu.menu-label class="border border-transparent gap-[10px] ps-[10px] pe-[10px] py-[6px]">
    {{ $slot }}
    @if ($hasSub)
        <x-menu.menu-arrow :$isOpen/>
    @endif
</x-menu.menu-label>

menu/menu-arrow

@props([
    'isOpen' => false
])

<span class="flex items-center text-gray-400 w-[20px] shrink-0 justify-end ml-1 mr-[-10px]">
    <i x-show="{{ $isOpen }}" class="ki-filled ki-plus text-2xs"></i>
    <i x-show="{{ !$isOpen }}" class="ki-filled ki-minus text-2xs"></i>
</span>
0 likes
3 replies
LaryAI's avatar
Level 58

The issue you're facing with both the + and - icons displaying is likely due to the way the x-show directive is being used in your menu/menu-arrow component. The x-show directive expects a JavaScript expression, but you're passing a Blade variable directly, which might not be evaluated correctly in the context of AlpineJS.

Here's a solution to ensure that the x-show directive works as expected:

  1. Ensure x-data is properly initialized: Make sure that the x-data object is correctly set up and that the open state is being toggled as expected.

  2. Use AlpineJS expressions in x-show: Instead of using Blade syntax directly in x-show, use AlpineJS expressions. You can bind the isOpen state to the x-data object and use it directly in your x-show directives.

Here's how you can adjust your code:

x-sidebar.menu-item Component

<x-sidebar.menu-item
    x-data="{
        open: false,
        toggle() {
            this.open = !this.open;
            this.$refs.button.focus();
        },
        close(focusAfter) {
            if (!this.open) return;
            this.open = false;
            focusAfter && focusAfter.focus();
        }
    }"
>
    <x-sidebar.menu-label :hasSub="true" :isOpen="open">
        <x-sidebar.menu-icon icon="ki-people" />
        <x-sidebar.menu-title>Groups</x-sidebar.menu-title>
    </x-sidebar.menu-label>
    <x-slot:subMenu :isOpen="open">
        <x-sidebar.menu-link :href="route('users.index')">Users</x-sidebar.menu-link>
    </x-slot:subMenu>
</x-sidebar.menu-item>

menu/menu-arrow Component

@props([
    'isOpen' => false
])

<span class="flex items-center text-gray-400 w-[20px] shrink-0 justify-end ml-1 mr-[-10px]">
    <i x-show="isOpen" class="ki-filled ki-minus text-2xs"></i>
    <i x-show="!isOpen" class="ki-filled ki-plus text-2xs"></i>
</span>

Explanation

  • x-data Initialization: The x-data object is initialized with an open property and a toggle method to switch the state. This ensures that the open state is managed within the component.

  • Passing isOpen: The isOpen property is passed as a prop to the menu-label and subMenu slots, and it is bound to the open state of the x-data object.

  • AlpineJS Expressions: The x-show directives in the menu/menu-arrow component now use AlpineJS expressions (isOpen and !isOpen) to conditionally display the icons based on the open state.

This setup should correctly toggle between the + and - icons based on the menu's open state.

2 likes
jrdavidson's avatar

Unfortunately using the suggetion results in an error due to open being an undefined constant.

Please or to participate in this conversation.