Skip to content

Instantly share code, notes, and snippets.

@janklan
Created October 18, 2021 02:52
Show Gist options
  • Save janklan/ea6d0128dbd6d6ef5391ac481dcc483d to your computer and use it in GitHub Desktop.
Save janklan/ea6d0128dbd6d6ef5391ac481dcc483d to your computer and use it in GitHub Desktop.
Tailwind CSS Alpine slideover
import Alpine from "alpinejs";
/*
* Slideover component
*
* How to use:
*
* 1. Use any valid Slideover component
* 2. Add `x-cloak x-data="slideover" x-bind="container"` outside element (containing the overlay, and the slideover)
* 3. Add `x-bind="overlay"` to the overlay that makes the background go darker
* 4. Add `x-bind="modal"` to the element that is meant to slide in from the side
* 5. Add `x-show="isLoading"` to whatever message you wish to see while the URL is loading
* 6. Add `x-show="!isLoading" x-html="html"` to the element that should contain the server response
*
* How to invoke:
*
* The slideover is triggered by emitting a CustomEvent from anywhere on the page, for example:
*
* `<button x-data @click.prevent="$dispatch('open-slideover', { url:'/tasks/create'})">Open Slideover</button>`
*
*
* Important server-side consideration:
*
* The server must return a HTML snippet that will work in the context of the rest of the slideover markup - not the entire
* HTML page (incl. <head> section etc.). This slideover component adds a custom HTTP header `'X-UI': 'slideover'` to indicate
* that the response will live inside the slideover component. You need to make sure that the server returns full HTML page
* when this header is missing, and just a snippet when it is present.
*
* Also, note that this component does not handle form submissions, so if your slideover contains a form, make sure it
* is submitted in a way that allows handling any submission errors should they occur.
*
* Code example:
*
* ```html
* <div x-cloak x-data="slideover" x-bind="container" class="fixed z-50 inset-0 overflow-hidden" aria-labelledby="slide-over-title" role="dialog" aria-modal="true">
* <div class="absolute inset-0 overflow-hidden">
* <div x-bind="overlay" class="absolute inset-0 bg-black bg-opacity-75 transition-opacity" aria-hidden="true"></div>
* <div class="absolute inset-0" aria-hidden="true">
* <div class="fixed inset-y-0 pl-16 max-w-full right-0 flex">
* <div x-bind="modal" class="w-screen max-w-md">
* <div x-show="isLoading" class="text-center h-full items-center flex flex-col justify-center w-full items-center text-gray-600 bg-white">
* <i class="fal fa-circle-notch fa-3x fa-spin mb-2" aria-hidden="true"></i>
* <span>Loading&hellip;</span>
* </div>
* <div class="flex h-full w-full" x-show="!isLoading" x-html="html"></div>
* </div>
* </div>
* </div>
* </div>
* </div>
* ```
*
* @see https://localhost:8000/test/ui To see this in action
* @see https://alpinejs.dev/directives/cloak If you don't know what `x-cloak` is there for
* @see https://tailwindui.com/components/application-ui/overlays/slide-overs For off-the-shelf dropdowns
*/
document.addEventListener('alpine:init', () => {
Alpine.data('slideover', () => ({
isOpen: false,
isLoading: false,
html: '',
container: {
'@open-slideover.window'($event) {
if ($event.detail.url) {
this.open($event.detail.url);
}
},
'x-show'() { return this.isOpen },
':aria-hidden'() { return !this.isOpen },
'x-on:keydown.escape.window'() { this.close() },
},
overlay: {
'x-show'() { return this.isOpen },
':aria-hidden'() { return !this.isOpen },
'x-transition:enter'() { return 'ease-in-out duration-500' },
'x-transition:enter-start'() { return 'opacity-0' },
'x-transition:enter-end'() { return 'opacity-100' },
'x-transition:leave'() { return 'ease-in-out duration-500' },
'x-transition:leave-start'() { return 'opacity-100' },
'x-transition:leave-end'() { return 'opacity-0' },
},
trigger: {
'x-on:click'() { this.isOpen = ! this.isOpen },
':aria-expanded'() { return this.isOpen },
},
modal: {
'x-show'() { return this.isOpen },
':aria-hidden'() { return !this.isOpen },
'x-on:click.outside'() { this.close() },
'x-transition:enter'() { return 'transform transition ease-in-out duration-500 sm:duration-700' },
'x-transition:enter-start'() { return 'translate-x-full' },
'x-transition:enter-end'() { return 'translate-x-0' },
'x-transition:leave'() { return 'transform transition ease-in-out duration-500 sm:duration-700' },
'x-transition:leave-start'() { return 'translate-x-0' },
'x-transition:leave-end'() { return 'translate-x-full' },
},
open(url) {
this.html = 'Loading ...';
this.isLoading = true;
this.isOpen = true;
fetch(url, {
method: 'GET',
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-UI': 'slideover'
},
})
.then(response => response.text())
.then(text => {
console.log(this.isLoading)
this.html = text
this.isLoading = false;
});
},
close() {
if (this.isOpen) {
this.isOpen = false;
}
},
}))
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment