Created
October 18, 2021 02:52
-
-
Save janklan/ea6d0128dbd6d6ef5391ac481dcc483d to your computer and use it in GitHub Desktop.
Tailwind CSS Alpine slideover
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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…</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