Tutorial Laravel Livewire - #7 - Membuat Reusable Component Dengan Laravel

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita membuat sebuah reusable component menggunakan laravel.

Rafi Taufiqurrahman
Dipublish 09/07/2024

Pendahuluan

Pada studi kasus kali ini kita akan mencoba menggunakan laravel Components, laravel component itu sendiri disediakan oleh laravel untuk pembuatan reusable UI (User Interface) yang dapat digunakan secara fleksibel. Component juga dapat digunakan untuk memisahkan logika tampilan dan logika dari apilkasi utama, sehingga mempermudah dalam pengembangan, pemeliharaan, dan pengujian kode.

Component Card

Silahkan teman-teman buka folder yang bernama components, folder tersebut terletak di resources/views/components. Selanjutnya silahkan buat file baru dengan nama card.blade.php didalam folder tersebut. kemudian masukan kode berikut ini :

card.blade.php
@props(['title' => ''])

<div class="py-3 px-4 bg-white rounded-t-lg border">
    <div class='flex items-center gap-2 uppercase font-semibold text-sm'>
        {{ $title }}
    </div>
</div>
<div class="px-4 py-3 bg-white border border-t-0 rounded-b-lg">
    {{ $slot }}
</div>

Pada kode diatas kita menambahkan sebuah blade directive @props, props tersebut merupakan sebuah data yang wajib yang harus dikirimkan ketika kita menggunakan component card. Pada component ini kita mendefinisikan sebuah props dengan nama title yang kita beri default value dengan empty string, jika kita tidak mendefinisikan default value dari sebuah props, maka ketika kita ingin menggunakan komponen tersebut kita wajib mengirimkan sebuah props sesuai yang didefinisikan menggunakan blade directive @props, contohnya kurang lebih seperti berikut ini :

Informasi : Contoh ini teman - teman tidak perlu ikuti, disini saya hanya menjelaskan tentang props, saya harap teman - teman dapat mengerti, akan tetapi untuk pembuatan component card teman - teman wajib membuatnya karena kita akan menggunakan komponen tersebut kedepannya.

Disini kita akan mecoba membuat sebuah halaman dengan nama index.blade.php dan dihalaman tersebut kita akan mencoba menggunakan component card.

index.blade.php
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <x-card/>
        </div>
    </div>
</x-app-layout

Outputnya kurang lebih seperti berikut ini :

components

Pada gambar diatas kita tidak memiliki error apapun dikarenakan kita sudah mendefinisikan sebuah default value untuk props title, sekarang kita akan coba untuk menghapus default value dari props title kurang lebih seperti berikut ini :

card.blade.php
@props(['title'])

<div class="py-3 px-4 bg-white rounded-t-lg border">
    <div class='flex items-center gap-2 uppercase font-semibold text-sm'>
        {{ $title }}
    </div>
</div>
<div class="px-4 py-3 bg-white border border-t-0 rounded-b-lg">
    {{ $slot }}
</div>

Setelah default value dari props tersebut dihapus kita akan menemukan error, kurang lebih seperti berikut ini :

components-error

Lalu bagaimana caranya agar kita bisa meperbaiki error tersebut, teman - teman cukup menambahkan sebuah attribute sesuai dengan props yang telah kita definisikan, karena disini kita mendefinisikannya dengan nama title maka teman - teman cukup menambahkan attribute title didalam component card tempat kita menggunakannya.

dashboard.blade.php
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Dashboard') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <x-card title="Dashboard"/>
        </div>
    </div>
</x-app-layout>

Setelah ada penambahan attribute pada component card, maka error tersebut telah teratasi.

component-fix

Component Input

Pada tahap ini, kita akan lanjutkan untuk pembuatan component input. Silahkan teman-teman buat file baru dengan nama input.blade.php didalam folder resources/views/components, kemudian silahkan masukan kode berikut ini :

input.blade.php
@props(['label' => '', 'type' => 'text', 'name' => '', 'placeholder' => '', 'value' => ''])

<div class='flex flex-col gap-2'>
    <label class='text-gray-600 text-sm'>{{ $label }}</label>
    <input
        type="{{ $type }}"
        class="w-full px-3 py-1.5 border bg-white text-sm rounded-md focus:outline-none focus:ring-0 text-gray-700 focus:border-sky-500 border-gray-200 @error($name) border-rose-500 @enderror"
        wire:model="{{ $name }}"
        placeholder="{{ $placeholder }}"
        value="{{ $value }}"
        autocomplete="off"
        {{ $attributes }}
    />
    @error($name)
        <small class='text-xs text-red-500'>{{ $message }}</small>
    @enderror
</div>

Pada kode diatas, kita mendefinisikan beberapa props diantarnya sebagai berikut :

  1. label dengan default value empty string.
  2. type dengan default value text.
  3. name dengan default value empty string.
  4. placeholder dengan default value empty string.
  5. value dengan default value empty string.

Component Search

Silahkan teman - teman buat file baru dengan nama search.blade.php didalam folder resources/views/components, kemudian masukan kode berikut ini :

search.blade.php
@props(['placeholder' => ''])

<div class="relative flex w-full flex-col gap-1 text-gray-400">
    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true" class="absolute left-2.5 top-1/2 size-5 -translate-y-1/2 text-gray-700/50">
        <path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
    </svg>
    <input
        type="test"
        class="w-full rounded-lg border border-gray-200 text-gray-700 py-2 px-4 pl-10 pr-2 text-sm focus:outline-none disabled:cursor-not-allowed disabled:opacity-750"
        wire:model.live="search"
        placeholder="{{ $placeholder }}"
        aria-label="search"
    />
</div>

Pada kode diatas, kita hanya menambahkan sebuah props dengan nama placeholder dengan default value empty string.

Component Select

Silahkan teman - teman buat file baru dengan nama select.blade.php didalam folder resources/views/components, kemudian masukan kode berikut ini :

select.blade.php
@props(['label' => '', 'name' => ''])

<div class='flex flex-col gap-2'>
    <label class='text-gray-600 text-sm'>{{ $label }}</label>
    <select class="w-full px-3 py-1.5 border bg-white text-sm rounded-md focus:outline-none focus:ring-0 text-gray-700 focus:border-sky-500 border-gray-200 @error($name) border-rose-500 @enderror" wire:model="{{ $name }}" {{ $attributes }}>
        {{ $slot }}
    </select>
    @error($name)
        <small class='text-xs text-red-500'>{{ $message }}</small>
    @enderror
</div>

Pada kode diatas, kita mendefinisikan beberapa props diantarnya sebagai berikut :

  1. label dengan default value empty string.
  2. name dengan default value empty string.

Dan jika teman-teman perhatikan kembali, kita juga mendefinisikan sebuah variabel $slot, variabel tersebut digunakan untuk meletakan konten dinamis kedalam komponen tersebut.

Component Table

Silahkan teman - teman buat file baru dengan nama table.blade.php didalam folder resources/views/components, kemudian masukan kode berikut ini :

table.blade.php
@props(['title' => '', 'heads' => []])

<div class="p-4 bg-white rounded-t-lg border">
    <div class='flex items-center gap-2 uppercase font-semibold text-sm'>
        {{ $title }}
    </div>
</div>
<div class="bg-white rounded-b-lg border border-t-0">
    <div class="w-full overflow-hidden overflow-x-auto border-collapse rounded-xl">
        <table class="w-full text-sm border-collapse">
            <thead class="border-b">
                <tr>
                    @foreach ($heads as $head)
                        <th scope="col" class="h-12 px-6 text-left align-middle font-medium whitespace-nowrap">
                            {{ $head }}
                        </th>
                    @endforeach
                </tr>
            </thead>
            <tbody class="divide-y">
                {{ $slot }}
            </tbody>
        </table>
    </div>
</div>

Pada kode diatas, kita mendefinisikan beberapa props diantarnya sebagai berikut :

  1. title dengan default value empty string.
  2. heads dengan default value empty array.

Component Widget

Silahkan teman - teman buat file baru dengan nama widget.blade.php didalam folder resources/views/components, kemudian masukan kode berikut ini :

widget.blade.php
@props(['title' => '', 'subtitle' => '', 'data' => ''])

<div {{ $attributes->merge(['class' => 'bg-white p-4 rounded-lg shadow']) }}>
    <div class='flex justify-between items-center gap-4'>
        <div class='flex items-center gap-3'>
            {{ $slot }}
            <div class='flex flex-col'>
                <div class='font-semibold text-sm line-clamp-1'>{{ $title }}</div>
                <div class='text-xs text-gray-500'>{{ $subtitle }}</div>
            </div>
        </div>
        <div class='font-semibold text-sm font-mono p-2 min-w-fit'>
            {{ $data }}
        </div>
    </div>
</div>

Pada kode diatas, kita mendefinisikan beberapa props diantarnya sebagai berikut :

  1. title dengan default value empty string.
  2. subtitle dengan default value empty string.
  3. data dengan default value empty string.

Component Modal

Silahkan teman - teman buat file baru dengan nama modal.blade.php didalam folder resources/views/components, kemudian masukan kode berikut ini :

modal.blade.php
@props(['id' => '', 'title' => '', 'type'])

@switch($type)
    @case('delete')
        <div x-cloak x-show="openModalConfirm" x-transition.opacity.duration.200ms x-trap.inert.noscroll="openModalConfirm"
            @keydown.esc.window="openModalConfirm = false" @click.self="openModalConfirm = false"
            class="fixed inset-0 z-50 flex justify-center bg-black/20 p-4 pb-8 backdrop-blur-md items-center lg:p-8 "
            role="dialog" aria-modal="true" aria-labelledby="modalConfirm">
            <!-- Modal Dialog -->
            <div x-show="openModalConfirm"
                x-transition:enter="transition ease-out duration-200 delay-100 motion-reduce:transition-opacity"
                x-transition:enter-start="opacity-0 scale-50" x-transition:enter-end="opacity-100 scale-100"
                class="flex flex-col gap-4 rounded-xl border border-slate-700 bg-slate-800 text-slate-300 w-full max-w-lg">
                <!-- Dialog Header -->
                <div class="flex items-center justify-between border-b px-4 py-2 border-slate-700 bg-slate-900/20">
                    <div class="flex items-center justify-center rounded-full bg-rose-600/20 text-rose-600 p-1 animate-pulse">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-6"
                            aria-hidden="true">
                            <path fill-rule="evenodd"
                                d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z"
                                clip-rule="evenodd" />
                        </svg>
                    </div>
                    <button @click="openModalConfirm = false" aria-label="close modal">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor"
                            fill="none" stroke-width="1.4" class="w-5 h-5">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                    </button>
                </div>
                <!-- Dialog Body -->
                <div class="px-4 text-center">
                    <h3 id="dangerModalTitle" class="mb-2 font-bold tracking-wide text-white text-lg">Confirmation</h3>
                    <p class="text-sm text-gray-500">Are you sure you want to delete this data?</p>
                </div>
                <!-- Dialog Footer -->
                <div class="flex items-center justify-center border-slate-300 p-4 dark:border-slate-700">
                    <button @click="openModalConfirm = false" type="button"
                        class="w-full cursor-pointer whitespace-nowrap rounded-xl bg-rose-600 px-4 py-2 text-center text-sm font-semibold tracking-wide text-white transition hover:opacity-75 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-rose-600 active:opacity-100 active:outline-offset-0"
                        wire:click="{{ $id }}">
                        Of Course, Delete it!
                    </button>
                </div>
            </div>
        </div>
    @break

    @case('transaction')
        <div x-cloak x-show="showModal" x-transition.opacity.duration.200ms x-trap.inert.noscroll="showModal"
            @keydown.esc.window="showModal = false" @click.self="showModal = false"
            class="fixed inset-0 z-50 flex justify-center bg-black/20 p-4 pb-8 backdrop-blur-md items-center lg:p-8 "
            role="dialog" aria-modal="true" aria-labelledby="modalConfirm">
            <!-- Modal Dialog -->
            <div x-show="showModal"
                x-transition:enter="transition ease-out duration-200 delay-100 motion-reduce:transition-opacity"
                x-transition:enter-start="opacity-0 scale-50" x-transition:enter-end="opacity-100 scale-100"
                class="flex flex-col gap-4 rounded-xl border border-slate-700 bg-slate-800 text-slate-300 w-full max-w-lg">
                <!-- Dialog Header -->
                <div class="flex items-center justify-between border-b px-4 py-2 border-slate-700 bg-slate-900/20">
                    <div>
                        Detail Transaction
                    </div>
                    <button @click="showModal = false" aria-label="close modal">
                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor"
                            fill="none" stroke-width="1.4" class="w-5 h-5">
                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                    </button>
                </div>
                <!-- Dialog Body -->
                {{ $slot }}
            </div>
        </div>
    @break
@endswitch

Pada kode diatas, kita mendefinisikan beberapa props diantarnya sebagai berikut :

  1. id dengan default value empty string.
  2. title dengan default value empty string.
  3. type tanpa default value.

Component Button

Silahkan teman - teman buat file baru dengan nama button.blade.php didalam folder resources/views/components, kemudian masukan kode berikut ini :

modal.blade.php
@props(['type', 'title' => '', 'href' => '', 'id' => ''])

@switch($type)
    @case('create')
        <a href="{{ $href }}" class="px-4 py-2 flex items-center gap-1 rounded-lg text-sm font-semibold bg-white border"
            wire:navigate>
            {{ $title }}
        </a>
    @break

    @case('edit')
        <a href="{{ $href }}"
            class="px-3 py-2 flex items-center gap-1 rounded-lg text-sm font-semibold text-indigo-500 bg-indigo-100 border-indigo-200 border hover:border-indigo-300 hover:bg-indigo-200">
            {{ $title }}
        </a>
    @break

    @case('delete')
        <div x-data="{openModalConfirm: false}">
            <button @click="openModalConfirm = true" type="button"
                class="px-3 py-2 flex items-center gap-1 rounded-lg text-sm font-semibold text-rose-500 bg-rose-100 border-rose-200 border hover:border-rose-300 hover:bg-rose-200">
                {{ $title }}
            </button>
            <x-modal :id="$id" type="delete"/>
        </div>
    @break

    @case('submit')
        <button type="submit"
            class="px-4 py-2 flex items-center gap-1 rounded-lg text-xs font-semibold bg-teal-500/20 border-teal-500/40 text-teal-500 border hover:bg-teal-600/40"
            {{ $attributes }}>
            <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none"
                stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"
                class="icon icon-tabler icons-tabler-outline icon-tabler-pencil-plus">
                <path stroke="none" d="M0 0h24v24H0z" fill="none" />
                <path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" />
                <path d="M13.5 6.5l4 4" />
                <path d="M16 19h6" />
                <path d="M19 16v6" />
            </svg>
            {{ $title }}
        </button>
    @break

    @case('cancel')
        <a href="{{ $href }}"
            class="px-4 py-2 flex items-center gap-1 rounded-lg text-xs font-semibold bg-rose-500/20 border-rose-500/40 text-rose-500 border hover:bg-rose-600/40"
            wire:navigate>
            <svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-narrow-left" width="18"
                height="18" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
                stroke-linecap="round" stroke-linejoin="round">
                <path stroke="none" d="M0 0h24v24H0z" fill="none" />
                <path d="M5 12l14 0" />
                <path d="M5 12l4 4" />
                <path d="M5 12l4 -4" />
            </svg>
            {{ $title }}
        </a>
    @break

    @default
@endswitch

Pada kode diatas, kita mendefinisikan beberapa props diantarnya sebagai berikut :

  1. type tanpa default value.
  2. title dengan default value empty string.
  3. href dengan default value empty string.
  4. id dengan default value empty string.

Jika teman - teman perhatikan, pada komponen ini kita menanfaatkan blade directive @switch, disini kita gunakan sebagai paramater untuk menampilkan component button yang kita miliki, jika props type yang dikirimkan memiliki value create maka, button ini yang akan dirender.

button.blade.php
@case('create')
    <a href="{{ $href }}" class="px-4 py-2 flex items-center gap-1 rounded-lg text-sm font-semibold bg-white border"
        wire:navigate>
        {{ $title }}
    </a>
@break

jika props type yang dikirimkan tidak memiliki value create, maka akan disesuaikan dengan case yang telah kita definisikan.

Penutup

Pada artikel kali ini kita telah berhasil menyelesaikan pembuatan reusable component yang akan kita gunakan nantinya di study case kali ini, selanjutnya kita akan lanjutkan untuk pembuatan module category.

Series Artikel

Berikut ini daftar series artikel dari Tutorial Point of Sales Laravel, Livewire, Alpine.js & Tailwind Css

1
Tutorial Laravel Livewire - #1 - Project Overview

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan bahasa teknologi apa saja yang kita gunakan dan scope dari project yang akan kita bangun.

2
Tutorial Laravel Livewire - #2 - Installasi Project Laravel

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan mulai melakukan installasi project laravel kita dari awal menggunakan composer.

3
Tutorial Laravel Livewire - #3 - Installasi Laravel Breeze & Livewire

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan melakukan installasi laravel breeze dan livewire sebagai starter-kit kita.

4
Tutorial Laravel Livewire - #4 - Membuat Schema Database Dengan Laravel

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan membuat sebuah schema database yang nantinya akan kita gunakan pada study case kali ini dan kita akan memanfaatkan model dan migration dari laravel.

5
Tutorial Laravel Livewire - #5 - Membuat Relasi Antar Table Dengan Laravel

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan membuat relasi antar tabel menggunakan eloquent relationship yang telah disedikan oleh laravel.

6
Tutorial Laravel Livewire - #6 - Memanfaatkan Salah Satu Magic Laravel Yaitu Eloquent Accessor

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan mengenal salah satu magic laravel yang sangat powerfull yaitu laravel accessor.

7
Tutorial Laravel Livewire - #7 - Membuat Reusable Component Dengan Laravel

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita membuat sebuah reusable component menggunakan laravel.

8
Tutorial Laravel Livewire - #8 - Membuat Module Category Dengan Livewire

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan membuat sebuah module category dengan livewire.

9
Tutorial Laravel Livewire - #9 - Membuat Module Product Dengan Livewire

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan membuat sebuah module product dengan livewire.

10
Tutorial Laravel Livewire - #10 - Membuat Module POS Dengan Livewire

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan membuat sebuah module pos dengan livewire.

11
Tutorial Laravel Livewire - #11 - Membuat Module Dashboard Dengan Livewire

Artikel ini merupakan series dari Tutorial Laravel Livewire Study Case Point Of Sales, disini kita akan membuat sebuah module dashboard dengan livewire.

JurnalKoding

Mulai asah skill dengan berbagai macam teknologi - teknologi terbaru seperti Laravel, React, Vue, Inertia, Tailwind CSS, dan masih banyak lagi.

© 2024 JurnalKoding, Inc. All rights reserved.