¶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 :
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 :
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 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 :
-
label
dengan default value empty string
.
-
type
dengan default value text
.
-
name
dengan default value empty string
.
-
placeholder
dengan default value empty string
.
-
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 :
-
label
dengan default value empty string
.
-
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 :
-
title
dengan default value empty string
.
-
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 :
-
title
dengan default value empty string
.
-
subtitle
dengan default value empty string
.
-
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 :
-
id
dengan default value empty string
.
-
title
dengan default value empty string
.
-
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 :
-
type
tanpa default value.
-
title
dengan default value empty string
.
-
href
dengan default value empty string
.
-
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.