5 Reusable Laravel Blade Components with Alpine.js

5 Reusable Laravel Blade Components with Alpine.js

Written by Mithicher Baro on Oct 20th, 2021 Views Report Post

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

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:

That's all for now. See you later!

Follow me on twitter at @mithicher.

Buy me a coffee

Comments (1)