In this blog post, we’ll create five reusable components using Laravel Blade Components, Alpine.js and TailwindCSS. We will build the following components:
- Copy to Clipboard
- Global Progress Bar
- Image
- QrCode
- Avatar
I'm assuming you've used AlpineJS, Laravel Blade Components, and TailwindCSS before. If you haven't tried AlpineJS and TailwindCSS yet, you should. They're fantastic.
Let's get started!!
Copy to Clipboard
This copy to clipboard component is highly inspired from this stack overflow discussion https://stackoverflow.com/questions/400212/how-do-i-copy-to-the-clipboard-in-javascript.
How to use in our blade views
<x-copytoclipboard
content="Laravel Blade Component is Awesome"
/>
Here is an example with HTML content
<x-copytoclipboard
content='<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path d="M12 14l9-5-9-5-9 5 9 5z" /><path d="M12 14l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 14l9-5-9-5-9 5 9 5zm0 0l6.16-3.422a12.083 12.083 0 01.665 6.479A11.952 11.952 0 0012 20.055a11.952 11.952 0 00-6.824-2.998 12.078 12.078 0 01.665-6.479L12 14zm-4 6v-7.5l4-2.222" /></svg>'
/>
Full Component
// views/components/copytoclipboard.blade.php
@props(['content' => 'Content' ])
<a
{{
$attributes->merge([
"class" => "no-underline border border-gray-200 px-1 rounded text-xs uppercase tracking-wide font-semibold text-gray-500"
])
}}
href="#"
x-data="{
isCopied: false,
copyToClipBoard() {
if (! navigator.clipboard) {
textArea = document.createElement('textarea');
textArea.value = this.$refs.content.innerHTML;
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.position = 'fixed';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
if (document.execCommand('copy')) {
this.isCopied = true;
setTimeout(() => {
this.isCopied = false;
}, 2500);
}
} catch (err) {
console.error('Fallback: Oops, unable to copy', err);
}
document.body.removeChild(textArea);
return;
} else {
navigator.clipboard.writeText(this.$refs.content.innerHTML)
.then(() => {
this.isCopied = true;
setTimeout(() => {
this.isCopied = false;
}, 2500);
});
}
}
}"
x-on:click.prevent="copyToClipBoard()"
x-cloak
>
<span class="hidden" x-ref="content">{!! $content !!}</span>
<span x-text="isCopied ? 'Copied' : 'Copy'"></span></a>
Global Progress Bar
This component shows a progress bar at the top of the page when we navigate from one page to another. This component uses the Pace.js library.
Pace.js is a beautiful progress indicator for your page load and ajax navigation - as defined in their official website
🤩 Our Amazing Sponsors 👇DigitalOcean offers a simple and reliable cloud hosting solution that enables developers to get their website or application up and running quickly.View Website
Laravel News keeps you up to date with everything Laravel. Everything from framework news to new community packages, Laravel tutorials, and more.View Website
A Laravel Starter Kit that includes Authentication, User Dashboard, Edit Profile, and a set of UI Components.View Website
How to use in our blade views
<x-global-progress color="coral" />
Put the above code inside the head section of your master layout eg. app.blade.php
.
Full Component
// views/components/global-progress.blade.php
@props([
'color' => '#d72630'
])
<script src="https://cdn.jsdelivr.net/npm/pace-js@latest/pace.min.js" defer></script>
@once
@push('styles')
<style>
/*!
* pace.js v1.2.4 | Default theme
* https://github.com/CodeByZach/pace/
* Licensed MIT © HubSpot, Inc.
*/
.pace {
-webkit-pointer-events: none;
pointer-events: none;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none
}
.pace-inactive {
display: none
}
.pace .pace-progress {
background: {{ $color }};
position: fixed;
z-index: 2000;
top: 0;
right: 100%;
width: 100%;
height: 3px
}
</style>
@endpush
@endonce
Image Component
A simple image component that supports aspect ratios.
How to use in our blade views
{{-- Give a width to adjust the height of image --}}
<div class="w-96">
<x-image
image="https://pixabay.com/get/g3b015eccf776a5dbd497250775cdfaeb9846ad5b6f2828a69ae591385ad13fa5344f02279549c74909e2d1429158af0f3565a3384da23ed1ff7fcd9f5b8a26d132cdc60aad5e4795970a71d990e82d6b_640.jpg"
image-aspect-ratio="16:9" // Aspect Ratio 1:1, 4:3, 2:1
/>
</div>
Full Component
// views/components/image.blade.php
@props([
'image' => null,
'imageAspectRatio' => '16:9',
'rounded' => true,
'srcsets' => null,
'alt' => '',
'sizes' => "(min-width: 768px) 40vw, 90vw"
])
{{--
The sizes attribute describes the width that the image will display within the layout of your specific site
--}}
@php
$roundedClass = $rounded ? 'rounded-md' : '';
$aspectRatio = [
'16:9' => 'padding-bottom: 56.25%',
'4:3' => 'padding-bottom: 75%',
'2:1' => 'padding-bottom: 50%',
'1:1' => 'padding-bottom: 100%',
'custom' => ''
][$imageAspectRatio];
$defaultImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAHgCAIAAADytinCAAAACXBIWXMAAC4jAAAuIwF4pT92AAARrklEQVR42u3dW2wU5f/AYbBIKYdSPCEazocoAhorkpggF8IfNGI4iQh4BIKpigoiGiUaLyTxcIFRsFEEMUFCiIcLY6IRkVBEQVCRABcoCBQCpbY/aWkB5f+GN25qi7WyW7orz3PRbLfT2emEfPplOjPb7CQAaamZXQAg0AAINIBAAyDQAAINgEADINAAAg2AQAMINAACDYBAAwg0AAININAACDSAQAMg0AAINIBAAyDQAAINgEADINAAAg2AQAMINAACDSDQAAg0AAININAACDSAQAMg0AAINIBAAyDQAAINgEADCDQAAg2AQAMINAACDSDQAAg0AAININAACDSAQAMg0AAINIBAAyDQAAINgEADCDQAAg2AQAMINAACDSDQAAg0AAININAACDSAQAMg0AACDYBAAyDQAAINgEADCDQAAg2AQAMINAACDSDQAAg0gEADINAACDSAQAMg0AACDYBAAyDQAAINgEADCDQAAg2AQAMINAACDSDQAAg0gEADINAACDSAQAMg0AACDYBAAyDQAAINgEADCDQAAg0g0AAINAACDSDQAAg0gEADINAACDSAQAMg0AACDYBAAwg0AAINgEADCDQAAg0g0AAINAACDSDQAAg0gEADINAACDSAQAMg0AACDYBAAwg0AAINgEADCDQAAg0g0AAINAACDSDQAAg0gEADINAAAg2AQAMg0AACDYBAAwg0AAINgEADCDQAAg0g0AAINIBAAyDQAAg0gEADINAAAg2AQAMg0AACDYBAAwg0AAINgEADCDQAAg0g0AAINIBAAyDQAAg0gEADINAAAg2AQAMg0AACDYBAAwg055I//vjj+PHjJ0ipsEv900KgSbbOgf3QSPvWTkCgSaogJSUlTz755LBhw/6PFBkxYsT8+fOrq6tlGoEmKTfddFOzZs0uvPDCjh07XkLSwm7My8sLu7SgoCDs3t9//92/MQSafyeGY+vWrSEljzzyiJSk9v8lQ4cOzcnJ+e233wzRCDT/2okTJ8LHDRs2hEAvWbIkPPZ3rZSIu/GBBx4IO7a0tFSgEWiSCvSiRYvC42PHjv1B0sJuDDtz+vTpYcf++uuvAo1Ak1Sg3377bRN0Y0zQAo1AI9AC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';
@endphp
<div
@if($aspectRatio)
style="{{ $aspectRatio }}"
@endif
{{
$attributes->merge([
'class' => 'relative border border-gray-100 overflow-hidden '. $roundedClass
])
}}
>
@if ($image)
<img
class="absolute object-cover h-full w-full"
src="{{ $defaultImage }}"
alt="{{ $alt ?? $image }}"
loading="lazy"
data-src="{{ $image }}"
onload="if(this.src !== this.getAttribute('data-src')) this.src=this.getAttribute('data-src');"
@if (! is_null($srcsets))
sizes="{{ $sizes }}"
srcset="{{ $srcsets }}"
@endif
/>
@else
<img
class="absolute object-cover h-full w-full"
src="{{ $defaultImage }}"
alt="Placehoder-image"
loading="lazy"
/>
@endif
</div>
QrCode Component
Simple QrCode Component based on the library https://github.com/milon/barcode
Install this package via composer in your laravel project
composer require milon/barcode
How to use in our blade views
<x-qrcode data="Laravel is awesome" />
Full Component
// views/components/qrcode.blade.php
@props([
'size' => 6,
'data' => 'Hello QR Code',
'type' => 'QRCODE',
'dimension' => 2
])
<img
@if ($dimension === 2)
src="data:image/png;base64,{{ DNS2D::getBarcodePNG($data, $type, $size, $size) }}"
@else
src="data:image/png;base64,{{ DNS1D::getBarcodePNG($data, $type) }}"
@endif
alt="qrcode"
/>
Avatar Component
An avatar component based on the amazing api provided by https://avatars.dicebear.com/ and https://boringavatars.com/.
How to use in our blade views
<x-avatar name="Loki" size="128" />
<x-avatar name="Loki" size="128" source="boringAvatars" />
Full Component
// views/components/avatar.blade.php
@props([
'shape' => 'circle', // circle or square
'size' => 40,
// works only with source type boringAvatars
'variant' => 'beam', // marble, beam, pixel, sunset, bauhaus, ring
'colors' => ['5e9fa3', 'dcd1b4', 'fab87f', 'f87e7b', 'b05574'],
'name' => 'Tony Stark',
'source' => "dicebearAvatars" // boringAvatars or dicebearAvatars
])
@php
$avatarShapeClass = [
'rounded' => 'rounded-lg',
'square' => 'rounded-lg',
'circle' => 'rounded-full',
][$shape];
@endphp
<div class="inline-flex flex-shrink-0 overflow-hidden bg-gray-100 {{ $avatarShapeClass }}" style="width: {{ $size }}px; height: {{ $size }}px;">
@if ($source === 'dicebearAvatars')
<img
src="https://avatars.dicebear.com/api/initials/{{ urlencode($name) }}.svg?&width={{ $size }}&height={{ $size }}"
alt="{{ $name }}"
title="{{ $name }}"
class="object-fit"
loading="lazy"
/>
@else
<img
src="https://source.boringavatars.com/{{ $variant }}/{{ $size }}/{{ urlencode($name) }}?colors={{ implode(',', $colors) }}&{{ $shape }}"
alt="{{ $name }}"
title="{{ $name }}"
class="object-fit"
loading="lazy"
/>
@endif
</div>
Check out my previous blog posts:
- Create a MeiliSearch Laravel Blade Component
- Building a Laravel Blade Table Component with Alpine.js
That's all for now. See you later!
Follow me on twitter at @mithicher.
Comments (1)