Appearance
Reusable components β
PODCAST
Reusable components explained
TIP
Free to use components for your Laravel projects
- In this section, we will DRY up our code by using reusable Blade components
- Blade components allow us to follow the DRY or βDonβt Repeat Yourselfβ principle which means that we can reuse the component anywhere in our application
- In a previous chapter we were already introduced to (static) Blade components, but now we are going to make them dynamic
- Laravel has two types of components:
- components with a dedicated class: e.g. the layout component
- components without a class (anonymous components): e.g. the navigation and footer in the layout
- In this chapter we generate some anonymous components that we will use later in the course
REMARKS
- Anonymous components MUST be placed inside the resources/views/components folder and be rendered with the
x-
prefix, followed by the path and name of the Blade file - The path and name are separated by a dot
- When an
index.blade.php
file exists for the component, it will be rendered as the "root" node of the component
component | Render |
---|---|
resources/views/components/list.blade.php | <x-list> |
resources/views/components/list/index.blade.php | <x-list> |
resources/views/components/alert.blade.php | <x-alert> |
resources/views/components/alert/index.blade.php | <x-alert> |
resources/views/components/alert/icon.blade.php | <x-alert.icon> |
- We create our components in a subfolder resources/views/components/tmk (Thomas More Kempen π) so that we can be sure that they will not be overwritten if you later install any online component libraries
TIP
- Create a (temporary) playground page (resources/views/playground.blade.php) that we can use to test our components
- Create a route to the playground page in routes/web.php
php
Route::view('/', 'home')->name('home');
Route::view('contact', 'contact')->name('contact');
Route::view('playground', 'playground')->name('playground');
...
1
2
3
4
2
3
4
List component β
- Open http://vinyl_shop.test/admin/records in the browsers and look at the layout of the records list
- Tailwind by default removes all styles from the unordered list, so we have to add additional classes to bring back the "look" of an unordered list
- We're probably going to use a list multiple times in the application, so we'll have to add the styles over and over again (not really DRY...)
- Such a list is a good candidate to convert to a component
- Create a new list component inside the components folder: resources/views/components/tmk/list.blade.php
- Open resources/views/admin/records/index.blade.php and add four list variants inside a Tailwind grid
php
<x-vinylshop-layout>
<x-slot name="title">The Vinyl Shop: records</x-slot>
<x-slot name="subtitle">Records</x-slot>
<section class="grid sm:grid-cols-2 gap-4">
<div>
<h3>Original list</h3>
<ul>
@foreach ($records as $record)
<li>{!! $record !!}</li>
@endforeach
</ul>
</div>
<div>
<h3>Bullet list</h3>
<x-tmk.list>
@foreach ($records as $record)
<li>{!! $record !!}</li>
@endforeach
</x-tmk.list>
</div>
<div>
<h3>Numbered list</h3>
<x-tmk.list>
@foreach ($records as $record)
<li>{!! $record !!}</li>
@endforeach
</x-tmk.list>
</div>
<div>
<h3>Group list</h3>
<x-tmk.list>
@foreach ($records as $record)
<li>{!! $record !!}</li>
@endforeach
</x-tmk.list>
</div>
</section>
</x-vinylshop-layout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- Add an unordered list with a default slot to the list component
- Style the
ul
tag as a bullet list
php
<ul class="list-disc list-outside px-4">
{{ $slot }}
</ul>
1
2
3
2
3
Pass attributes to the component β
- Sometimes you may need to specify additional HTML attributes (title, class, ...) to the component
- All attributes are available inside the attributes bag, but you have to add it explicitly (with
$attributes
) to the component
- Add the 'magic' variable
$attributes
to theul
tag
php
<ul {{ $attributes }} class="list-disc list-outside px-4">
{{ $slot }}
</ul>
1
2
3
2
3
Merging the class attribute β
- Most times you specify default classes inside the component and add additional classes on the
class
attribute of the component - These classes are not automatically merged with each other!
- Add additional classes (
class="p-4 pl-8 border rounded shadow"
) to the list component
php
<div>
<h3>Bullet list</h3>
<x-tmk.list title="Bullet list" class="p-4 pl-8 border rounded shadow">
@foreach ($records as $record)
<li>{!! $record !!}</li>
@endforeach
</xx-tmk.list>
</div>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- To solve this problem, we have to merge both
class
attributes together inside the component
- Add additional classes to the list component
php
<ul {{ $attributes->merge(['class' => "list-disc list-outside px-4"]) }}>
{{ $slot }}
</ul>
1
2
3
2
3
Pass data properties to the component β
- You may specify which attributes should be considered data variables or properties (instead of 'normal' attributes) using the
@props
directive at the top of the component - Properties also can have default values
- Let's add a
type
property to specify the layout of the list - The
type
property can have the value:ul
: rendered as a numbered list (default value)ol
: rendered as a bullet listgroup
: rendered as a grouped list
- Line 3 and 11: add the
type
property to the last two list components - Line 11 and 13: add some additional classes group list and to the list-items inside the groups list
php
<div>
<h3>Numbered list</h3>
<x-tmk.list type="ol">
@foreach ($records as $record)
<li>{!! $record !!}</li>
@endforeach
</x-tmk.list>
</div>
<div>
<h3>Group list</h3>
<x-tmk.list type="group" class="border rounded shadow-lg">
@foreach ($records as $record)
<li class="p-2 hover:bg-gray-300">{!! $record !!}</li>
@endforeach
</x-tmk.list>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Section component β
- Let's create a component that renders a
section
with a white background, a tiny border and a drop shadow - This component is very simple, because it only contains a
$slot
and some basic styling - Create a new section component inside the components folder: resources/views/components/tmk/section.blade.php
- Open resources/views/playground.blade.php and add three sections to the page
php
<x-slot name="title">Playground</x-slot>
<h2>Sections</h2>
<div class="grid grid-cols-3 gap-4">
<x-tmk.section class="col-span-3 md:col-span-1">
<h3>Section 1</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi ducimus fuga nesciunt nisi quo sequi
voluptas. Accusantium consequuntur officiis veritatis.</p>
</x-tmk.section>
<x-tmk.section class="col-span-3 md:col-span-1">
<h3>Section 2</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ab distinctio eos ex excepturi possimus, reprehenderit vitae voluptatum. Accusamus eius eum ex, explicabo illo iste maxime odio soluta, vero voluptas, voluptate!</p>
</x-tmk.section>
<x-tmk.section class="col-span-3 md:col-span-1">
<h3>Section 3</h3>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Nostrum, quasi?</p>
</x-tmk.section>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Icons β
- When we created our project, we also installed the Blade UI kit icons.
- These icons are also built as components and can be used in the same way as all other components.
- Let's add, for example, the Fontawesome Home icon to page
- Open resources/views/playground.blade.php and add four list variants inside a Tailwind grid
- There are four possible ways to use these icons in your application:
- Line 7: use the component
- Line 10: use the default icon component with the name property
- Line 13: use the directive
- Line 16: use the helper
php
<x-slot name="title">Playground</x-slot>
<section class="my-4">
<h2>Blade UI kit Icons</h2>
<div class="flex gap-4 p-4 my-4 bg-white rounded border shadow">
<div class="w-6">
<x-fas-home/>
</div>
<div class="w-6 text-orange-600">
<x-icon name="fas-home"/>
</div>
<div class="w-6 text-green-600">
@svg('fas-home')
</div>
<div class="w-6 text-sky-600">
{{ svg('fas-home') }}
</div>
</div>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
REMARK
- Those icons are not visible in the resources/views/components/ folder.
- They are hidden inside the vendor folder.
Preloader component β
- A preloader is often used to indicate that the data from an external source (API) is being loaded
- This preloader component is a very simple component with only class merging, a spinning icon and a default slot
- Create a new preloader component inside the components folder: resources/views/components/tmk/preloader.blade.php
- Open resources/views/playground.blade.php and add three preloaders to the page
php
<x-slot name="title">Playground</x-slot>
<section class="my-4">
<h2>Preloader</h2>
<x-tmk.preloader class="px-0"/>
<x-tmk.preloader class="bg-green-100 text-green-700 border border-green-700"/>
<x-tmk.preloader class="bg-slate-600 text-white italic w-1/2">Loading records...</x-tmk.preloader>
</section>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Alert component β
- The alert component has the following properties:
Property | default | description |
---|---|---|
type | success | The type (color) of the alert component |
dismissible | true | Whether the component can be dismissed |
closeSelf | 0 | Close the component automatically after x ms |
- Create a new alert component inside the components folder: resources/views/components/tmk/alert.blade.php
- Open playground.blade.php and add four alert variants inside section
Default component with 'type' property β
php
<x-slot name="title">Playground</x-slot>
<section class="my-4">
<h2>Alerts</h2>
<x-tmk.alert>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Debitis dolores dolorum error eum eveniet
exercitationem expedita, impedit itaque laudantium, natus, nobis numquam omnis praesentium quis reiciendis
soluta sunt vel vero.
</x-tmk.alert>
<x-tmk.alert type="danger" class="mt-8 shadow-xl">
lorem ipsum
</x-tmk.alert>
<x-tmk.alert type="info" class="mt-8">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus eligendi facilis libero maiores non,
praesentium quam reiciendis sunt ut voluptatibus.
</x-tmk.alert>
<x-tmk.alert type="warning" dismissible="false" close-self="5000">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus eligendi facilis libero maiores non,
praesentium quam reiciendis sunt ut voluptatibus.
</x-tmk.alert>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Add the 'dismissible' functionality β
- Line 8: covert the
dismissible
property to a boolean value
The PHPFILTER_VALIDATE_BOOLEAN
filter will be used to convert the value to a booleantrue
,1
,on
,yes
,y
,t
will be converted totrue
false
,0
,off
,no
,n
,f
will be converted tofalse
- Line 27 and 31: the
X
icon will only be visible if thedismissible
property istrue
- Add some Alpine.JS code to make the
X
icon close the alert- Line 20:
x-data
will declare a new Alpine component with a default propertyopen
set totrue
- Line 21:
x-show
defines if the Alpine component is visible or hidden- The component is visible if the property
open
istrue
- The component is hidden if the property
open
isfalse
- The component is visible if the property
- Line 22:
x-transition.duration.300ms
transitions the Alpine component in and out using CSS transitions with a duration of300ms
- Line 28:
@click
(a shorthand forx-on:click
) will set theopen
property tofalse
and close the Alpine component
- Line 20:
php
@props([
'type' => 'success',
'dismissible' => true,
'closeSelf' => 0
])
@php
$dismissible = filter_var($dismissible, FILTER_VALIDATE_BOOLEAN);
$options = [
'success' => 'text-emerald-900 bg-emerald-100 border-emerald-300',
'danger' => 'text-red-900 bg-red-100 border-red-300',
'info' => 'text-sky-900 bg-sky-100 border-sky-300',
'warning' => 'text-orange-900 bg-orange-100 border-orange-300',
'light' => 'bg-white border-gray-300',
];
$style = $options[$type] ?? $options['success']
@endphp
<div
x-data="{ open: true }"
x-show="open"
x-transition.duration.300ms
{{ $attributes->merge(['class' => "$style flex gap-4 p-4 my-4 border"]) }}>
<div class="flex-1">
{{ $slot }}
</div>
@if($dismissible)
<button class="size-6 flex-none cursor-pointer" @click="open = false">
<x-heroicon-s-x-circle/>
</button>
@endif
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Add the 'closeSelf' functionality β
- Line 5 - 7: wrap the
x-init
Alpine directive in aif
statement to check if thecloseSelf
property (the time in ms after the component will close itself ) is greater than0
- Line 6:
x-init
initializes asetTimeout
function that will close the alert aftercloseSelf
milliseconds
php
<div
x-data="{ open: true }"
x-show="open"
x-transition.duration.300ms
@if($closeSelf > 0)
x-init="setTimeout(() => open = false, {{ $closeSelf }})"
@endif
{{ $attributes->merge(['class' => "$style flex gap-4 p-4 my-4 border"]) }}>
<div class="flex-1">
{{ $slot }}
</div>
@if($dismissible)
<button class="size-6 flex-none cursor-pointer" @click="open = false">
<x-heroicon-s-x-circle/>
</button>
@endif
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Make it Livewire compatible β
- One of the issues with Livewire is that the component will not always react after the DOM has changed
- When you close the alert and want to open it again, you need to re-initialize the component
- Line 2: this can be solved by adding the
wire:key
Livewire directive with a unique (= random) value to the component
php
<div
wire:key="{{ rand() }}"
x-data="{ open: true }"
x-show="open"
x-transition.duration.300ms
@if($closeSelf > 0)
x-init="setTimeout(() => open = false, {{ $closeSelf }})"
@endif
{{ $attributes->merge(['class' => "$style flex gap-4 p-4 my-4 border"]) }}>
<div class="flex-1">
{{ $slot }}
</div>
@if($dismissible)
<button class="size-6 flex-none cursor-pointer" @click="open = false">
<x-heroicon-s-x-circle/>
</button>
@endif
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Logo component β
- Jetstream use three different logos for the admin panel
- Let's create a new logo component that we will use on the navigation bar and on the admin panel
- Create a new logo component inside the components folder: resources/views/components/tmk/logo.blade.php
- Open resources/views/playground.blade.php and add three logos to the page
php
<x-slot name="title">Playground</x-slot>
<h2>Vinyl Shop logo</h2>
<section class="flex items-start space-x-4">
<x-tmk.logo class="w-12"/>
<x-tmk.logo class="w-24 fill-cyan-700"/>
<x-tmk.logo class="w-40 fill-neutral-600 hover:w-48 hover:fill-orange-700 hover:drop-shadow-lg transition"/>
</section>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Form components β
- Jetstream components are a set of components that are built on top of the Jetstream package that we us later in this course for authentication
- All files in the resources/views/components directory (except the components we created) are part by the Jetstream package, so you can use them and even customize them if you want
- Jetstream has a lot of form-related components (
input
,button
, error message, ...), but two of them are missing:- a
select
component - a
textarea
component
- a
- We create these components ourselves, but based on the
input
component, so we have the same look and feel for every element in a form - Besides these two components, we also need a refactored
button
component with four different colors, a searchinput
component and two switches, based on acheckbox
Select and textarea component β
- Create a new select component inside the components folder: resources/views/components/tmk/form/select.blade.php
- Create a new textarea component inside the components folder: resources/views/components/tmk/form/textarea.blade.php
- Open resources/views/playground.blade.php and add a form to the page
php
<x-slot name="title">Playground</x-slot>
<h2>Form</h2>
<form class="grid grid-cols-10 gap-4">
{{-- text input --}}
<div class="col-span-10 sm:col-span-5">
<x-label for="name" value="Name"/>
<x-input id="name" type="text" class="block mt-1 w-full" placeholder="Your name"/>
</div>
{{-- select --}}
<div class="col-span-10 sm:col-span-5">
<x-label for="country" value="Select a country"/>
<x-tmk.form.select id="country" type="text" class="block mt-1 w-full">
<option value="Belgium">Belgium</option>
<option value="France">France</option>
<option value="Germany">Germany</option>
</x-tmk.form.select>
</div>
{{-- textarea --}}
<div class="col-span-10">
<x-label for="message" value="Message"/>
<x-tmk.form.textarea id="message" class="block mt-1 w-full" rows="6" placeholder="Your message">
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur, corporis!
</x-tmk.form.textarea>
</div>
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Button component β
- The Jetstream button component (resources/views/components/button.blade.php) is a simple component that has only one color
- Let's create our own button component,based on the Jetstream button component, but with four different colors
- Create a new button component inside the components folder: resources/views/components/tmk/form/button.blade.php
- Let's add a
color
property to change the color of the button:color="dark"
: gray (default)color="success"
: greencolor="danger"
: redcolor="info"
: blue
- Also add a
disabled
property to disable the button- If the property exists and is set to
true
, thedisabled
property is added to the<button>
tag - If the property doesn't exist or is set to
false
, thedisabled
property is not included on<button>
tag
- If the property exists and is set to
- Open resources/views/playground.blade.php and add four buttons to the page
php
<x-slot name="title">Playground</x-slot>
<h2>Buttons</h2>
<section class="flex space-x-4">
<x-tmk.form.button type="button">default button</x-tmk.form.button>
<x-tmk.form.button type="button" color="success">success button</x-tmk.form.button>
<x-tmk.form.button type="button" color="danger">danger button</x-tmk.form.button>
<x-tmk.form.button type="button" color="info">info button</x-tmk.form.button>
</section>
<h2>Disabled buttons</h2>
<section class="flex space-x-4">
<x-tmk.form.button type="button" disabled>default button</x-tmk.form.button>
<x-tmk.form.button type="button" color="success" disabled>success button</x-tmk.form.button>
<x-tmk.form.button type="button" color="danger" disabled>danger button</x-tmk.form.button>
<x-tmk.form.button type="button" color="info" disabled>info button</x-tmk.form.button>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Search component β
- The search component is also based on the
input
component, but has a magnifying glass icon and a button to clear the input field - Create a new search component inside the components folder: resources/views/components/tmk/form/search.blade.php
- Open resources/views/playground.blade.php and add the search component to the page
php
<x-slot name="title">Playground</x-slot>
<h2>Search</h2>
<x-tmk.form.search placeholder="Search..." />
1
2
3
4
2
3
4
Toggle switch component (basic) β
- The toggle switch component changes the layout of the switch component to a more modern one
- Create a new toggle switch component inside the components folder: resources/views/components/tmk/form/toggle-switch.blade.php
- The
color
property changes the background color of the switch when the checkbox is in itschecked
state:color="dark"
: gray (default)color="success"
: greencolor="danger"
: redcolor="info"
: blue
- Open resources/views/playground.blade.php and add the toggle switch component to the page
php
<x-slot name="title">Playground</x-slot>
<section>
<h2>Toggle switch</h2>
<div class="flex items-center gap-4">
<x-tmk.form.toggle-switch />
<x-tmk.form.toggle-switch color="success" checked />
<x-tmk.form.toggle-switch color="danger" class="rotate-90"/>
<x-tmk.form.toggle-switch color="info" />
<x-tmk.form.toggle-switch color="danger" checked disabled />
</div>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Switch component (advanced) β
- The switch component is also a toggle component that can be used to replace a regular checkbox
- This switch is more advanced than the previous one and the layout is more customizable
- The switch component has the following properties:
Property | default | description |
---|---|---|
colorOff | bg-gray-200 | the background color when the checkbox is not checked |
colorOn | bg-green-200 | the background color when the checkbox is checked |
textOff | β | the text inside the switch when the checkbox is not checked |
textOn | β | the text inside the switch when the checkbox is checked |
- Create a new switch component inside the components folder: resources/views/components/tmk/form/switch.blade.php
- Open resources/views/playground.blade.php and add four switch variants to the page
php
<x-slot name="title">Playground</x-slot>
<section class="my-4">
<h2>Switch</h2>
<div class="flex items-center gap-4">
<x-tmk.form.switch />
<x-tmk.form.switch checked color-off="bg-red-200"/>
<x-tmk.form.switch disabled/>
<x-tmk.form.switch checked name="save" value="Save me"
class="text-white shadow-lg !rounded-full w-28"
color-off="bg-orange-800" color-on="bg-sky-800"
text-off="switch off" text-on="switch on"/>
<x-tmk.form.switch name="user" value="on"
class="!h-20 !text-5xl"
color-off="bg-red-200" color-on="bg-green-500"
text-on="π" text-off="π©"/>
</div>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Passing dynamic data to components β
- Hard coded, string values may be passed to the component using simple HTML attribute strings
- You can also pass a dynamic values to the component, but than you have to prefix the attribute with
:
- Open resources/views/playground.blade.php and add two alerts to the page
- Line 5 - 7: the
color
variable is a dynamic value - Line 9: the
color
type="$color" (without the leading:
) doesn't work π¦ - Line 11: the
color
:type="$color" (with the leading:
) works π
php
<x-slot name="title">Playground</x-slot>
<h2>Dynamic data</h2>
<section class="flex flex-col">
@php
$color = 'danger'; // $color is a dynamic value !!!
@endphp
<x-tmk.alert type="$color">
Is this a red, danger alert?<br>
No, <code class="px-2 text-blue-600 font-black">type="$color"</code> don't work with dynamic values.
</x-tmk.alert>
<x-tmk.alert :type="$color">
Is this a red, danger alert?<br>
Yes, use <code class="px-2 text-blue-600 font-black">:type="$color"</code> for dynamic values.
</x-tmk.alert>
</section>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18