• Reading time ~ 4 min
  • 05.07.2023

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/[email protected]/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>

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

ABOUT

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

About author CrazyBoy49z
WORK EXPERIENCE
Contact
Ukraine, Lutsk
+380979856297