Skip to content

Commit 7eff6b5

Browse files
authored
feat: modal component (#267)
2 parents 6d0fb72 + ca961f0 commit 7eff6b5

File tree

5 files changed

+94
-1
lines changed

5 files changed

+94
-1
lines changed

resources/css/app.css

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
}
2121
}
2222

23-
@layer utils {
23+
@layer utilities {
2424
.hide-scrollbar::-webkit-scrollbar {
2525
display: none;
2626
}
@@ -29,4 +29,9 @@
2929
-ms-overflow-style: none;
3030
scrollbar-width: none;
3131
}
32+
33+
/* this is used by alpine.js to properly hide elements on load */
34+
[x-cloak] {
35+
@apply hidden;
36+
}
3237
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
@props([
2+
'name',
3+
'show' => false,
4+
])
5+
6+
<div
7+
x-data="{
8+
show: @js($show),
9+
focusables() {
10+
const selector = 'a, button, input:not([type=\'hidden\']), textarea, select, details, [tabindex]:not([tabindex=\'-1\'])'
11+
return [...$el.querySelectorAll(selector)].filter(element => !element.hasAttribute('disabled'))
12+
},
13+
firstFocusable() {
14+
return this.focusables()[0]
15+
},
16+
lastFocusable() {
17+
return this.focusables().slice(-1)[0]
18+
},
19+
nextFocusable() {
20+
const focusables = this.focusables()
21+
const nextFocusableIndex = (focusables.indexOf(document.activeElement) + 1) % (focusables.length + 1)
22+
23+
return focusables[nextFocusableIndex] || this.firstFocusable()
24+
},
25+
prevFocusable() {
26+
const focusables = this.focusables()
27+
const prevFocusableIndex = Math.max(0, focusables.indexOf(document.activeElement)) -1
28+
29+
return focusables[prevFocusableIndex] || this.lastFocusable()
30+
},
31+
}"
32+
x-show="show"
33+
x-init="
34+
$watch('show', (show) => {
35+
if (show) {
36+
document.body.classList.add('overflow-hidden')
37+
} else {
38+
document.body.classList.remove('overflow-hidden')
39+
}
40+
})
41+
"
42+
x-on:open-modal.window="
43+
if ($event.detail === '{{ $name }}') {
44+
show = true
45+
}
46+
"
47+
x-on:close-modal.window="
48+
if ($event.detail === '{{ $name }}') {
49+
show = false
50+
}
51+
"
52+
x-on:keydown.escape.window="show = false"
53+
x-on:keydown.tab.prevent="$event.shiftKey || nextFocusable().focus()"
54+
x-on:keydown.shift.tab.prevent="prevFocusable().focus()"
55+
x-cloak
56+
@class([
57+
'flex' => $show,
58+
'hidden' => "$show",
59+
'fixed inset-0 flex justify-center overflow-y-scroll',
60+
])
61+
>
62+
<div
63+
@click="show = false"
64+
class="absolute inset-0 bg-slate-800/80 backdrop-blur-sm"
65+
></div>
66+
{{ $slot }}
67+
</div>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<div
2+
class="relative flex w-full flex-col gap-2 self-end sm:max-w-xl sm:self-center"
3+
>
4+
<x-modal.title>{{ $title }}</x-modal.title>
5+
<div class="w-full bg-slate-700 px-4 pt-4 pb-8 sm:rounded-2xl sm:p-4">
6+
{{ $slot }}
7+
</div>
8+
</div>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<div
2+
class="relative flex w-full flex-col gap-2 self-end sm:max-w-xl sm:self-center"
3+
>
4+
<x-modal.title>{{ $title }}</x-modal.title>
5+
<div
6+
class="flex w-full flex-col gap-2 divide-y-1 divide-slate-600 bg-slate-700 px-2 pt-4 pb-8 sm:rounded-2xl sm:p-2 [&>*]:pb-2"
7+
>
8+
{{ $slot }}
9+
</div>
10+
</div>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div class="text-center text-lg font-bold text-slate-50 sm:text-xl">
2+
{{ $slot }}
3+
</div>

0 commit comments

Comments
 (0)