Skip to content

Commit 992afb6

Browse files
committed
refactor stacking structure
1 parent 7ac6b1f commit 992afb6

File tree

7 files changed

+154
-178
lines changed

7 files changed

+154
-178
lines changed

resources/views/components/modal.blade.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
@props([
22
'position' => 'center',
3-
'fullscreen' => false,
43
])
54

65
<div {!! $attributes->class([
76
'max-w-full min-w-0 transition',
8-
'max-h-[calc(100%-3rem)]' => !$fullscreen,
97
match ($position) {
108
'center' => 'm-auto origin-top',
119
'left' => 'mr-auto my-auto',
@@ -18,6 +16,6 @@
1816
'bottom-right' => 'mt-auto ml-auto',
1917
default => '',
2018
},
21-
]) !!}>
19+
]) !!} x-bind="modalAttributes">
2220
{{ $slot }}
2321
</div>

resources/views/components/slideover.blade.php

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@
22
'position' => 'right',
33
])
44

5-
<x-livewire-modal::modal :attributes="$attributes->class([
6-
'h-full',
7-
match ($position) {
8-
'center' => 'sm:mx-6',
9-
'right' => 'sm:mr-6',
10-
'left' => 'sm:ml-6',
11-
default => '',
12-
},
13-
])" :position="$position">
5+
<x-livewire-modal::modal :attributes="$attributes->class(['h-full'])" :position="$position">
146
{{ $slot }}
157
</x-livewire-modal::modal>
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
@props([
2+
'fullscreen' => false,
3+
])
4+
5+
<div {!! $attributes->class([
6+
'pt-20 sm:p-5' => !$fullscreen,
7+
'size-full min-w-0 flex pointer-events-none [&>*]:pointer-events-auto',
8+
]) !!} x-data="{
9+
modalPosition: [null, null],
10+
get modalIndexReversed() { return findModalHistoryIndex(modalId, true); },
11+
get modalStackDirection() {
12+
const [px, py] = this.modalPosition;
13+
14+
const directions = {
15+
left: [1, 0],
16+
right: [-1, 0],
17+
top: [0, 1],
18+
bottom: [0, -1],
19+
};
20+
21+
return directions[px || py] || [0, -1];
22+
},
23+
computeModalPosition() {
24+
const parent = this.$el.getBoundingClientRect();
25+
const rect = this.$el.firstElementChild.getBoundingClientRect();
26+
27+
const dx = parent.width - rect.right;
28+
const dy = parent.height - rect.bottom;
29+
30+
const px = dx === rect.left ? 'center' : dx < rect.left ? 'right' : 'left';
31+
const py = dy === rect.top ? 'center' : dy < rect.top ? 'bottom' : 'top';
32+
33+
return [px, py];
34+
},
35+
init() {
36+
this.$nextTick(() => {
37+
this.modalPosition = this.computeModalPosition();
38+
});
39+
},
40+
modalAttributes: {
41+
['x-show']() { return isModalStacked ? modalHistory.includes(modalId) : isModalActive; },
42+
['x-bind:inert']() { return !isModalActive },
43+
['x-bind:style']() {
44+
if (isModalStacked) {
45+
const [dx, dy] = this.modalStackDirection;
46+
47+
return {
48+
'--dx': dx,
49+
'--dy': dy,
50+
'--i': this.modalIndexReversed,
51+
transform: `scale(calc(1 - 0.05 * var(--i))) translate(calc(2rem * var(--dx) * var(--i)), calc(2rem * var(--dy) * var(--i)))`,
52+
opacity: this.modalIndexReversed <= 2 ? 1 : 0,
53+
};
54+
}
55+
56+
return {};
57+
},
58+
},
59+
}">
60+
{{ $slot }}
61+
</div>

resources/views/livewire/modal.blade.php

Lines changed: 9 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -152,94 +152,16 @@ class="fixed left-0 top-0 z-40 grid h-dvh w-full select-none overflow-hidden bg-
152152
grid-template-areas: 'stack';
153153
">
154154

155-
@foreach ($components as $component)
156-
@php
157-
['id' => $id, 'component' => $componentName, 'props' => $props, 'params' => $params] = $component;
158-
@endphp
155+
@foreach ($components as ['id' => $id, 'component' => $component, 'props' => $props])
159156
<div x-data="{
160-
modalPosition: [null, null],
161-
get modalId() {
162-
return '{{ $component['id'] }}';
163-
},
164-
get isModalActive() {
165-
return modalActiveId === this.modalId;
166-
},
167-
get modal() {
168-
return findModalById(this.modalId);
169-
},
170-
get modalStack() {
171-
return this.modal?.stack ?? null;
172-
},
173-
get isModalStacked() {
174-
return modalActiveStack && modalActiveStack === this.modalStack;
175-
},
176-
get modalIndexReversed() {
177-
return findModalHistoryIndex(this.modalId, true);
178-
},
179-
computeModalPosition() {
180-
const parent = this.$el.getBoundingClientRect();
181-
const rect = this.$el.firstElementChild.getBoundingClientRect();
182-
183-
const dx = parent.width - rect.right;
184-
const dy = parent.height - rect.bottom;
185-
186-
const px = dx === rect.left ? 'center' : dx < rect.left ? 'right' : 'left';
187-
const py = dy === rect.top ? 'center' : dy < rect.top ? 'bottom' : 'top';
188-
189-
return [px, py];
190-
},
191-
get modalStackDirection() {
192-
const [px, py] = this.modalPosition;
193-
194-
const directions = {
195-
left: [1, 0],
196-
right: [-1, 0],
197-
top: [0, 1],
198-
bottom: [0, -1],
199-
};
200-
201-
return directions[px || py] || [0, -1];
202-
},
203-
init() {
204-
this.$nextTick(() => {
205-
this.modalPosition = this.computeModalPosition();
206-
});
207-
},
208-
modalWrapper: {
209-
['x-bind:inert']() {
210-
return !this.isModalActive;
211-
},
212-
['x-show']() {
213-
if (this.isModalStacked) {
214-
return modalHistory.includes(this.modalId);
215-
}
216-
return this.isModalActive;
217-
},
218-
['x-bind:style']() {
219-
let base = {
220-
'grid-area': 'stack'
221-
};
222-
223-
if (this.isModalStacked) {
224-
const [dx, dy] = this.modalStackDirection;
225-
226-
return {
227-
...base,
228-
'--dx': dx,
229-
'--dy': dy,
230-
'--i': this.modalIndexReversed,
231-
transform: `scale(calc(1 - 0.05 * var(--i))) translate(calc(1rem * var(--dx) * var(--i)), calc(1rem * var(--dy) * var(--i)))`,
232-
opacity: this.modalIndexReversed <= 2 ? 1 : 0,
233-
};
234-
}
235-
236-
return base;
237-
}
238-
},
239-
}" x-bind="modalWrapper"
240-
class="pointer-events-none isolate flex size-full min-w-0 select-text transition [&>*]:pointer-events-auto"
241-
wire:key="{{ $this->getId() }}.modalComponents.{{ $id }}">
242-
@livewire($componentName, $props, key("{$this->getId()}.modalComponents.{$id}.component"))
157+
get modalId() { return '{{ $id }}'; },
158+
get isModalActive() { return modalActiveId === this.modalId; },
159+
get modal() { return findModalById(this.modalId); },
160+
get modalStack() { return this.modal?.stack ?? null; },
161+
get isModalStacked() { return modalActiveStack && modalActiveStack === this.modalStack; },
162+
}" class="min-w-0 select-text" style="grid-area: stack;" x-bind:id="modalId"
163+
x-on:mousedown.self="onCloseModal" wire:key="{{ $this->getId() }}.modalComponents.{{ $id }}">
164+
@livewire($component, $props, key("{$this->getId()}.modalComponents.{$id}.component"))
243165
</div>
244166
@endforeach
245167

src/Facades/.gitkeep

Whitespace-only changes.
Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,54 @@
1-
<x-livewire-modal::modal class="flex size-96 flex-col overflow-auto rounded-md border bg-white" :position="$position">
2-
<div class="p-5">
3-
<p>
4-
Modal ID: <span x-text="modalId"></span>
5-
</p>
6-
<p>
7-
Rendered At: <span>{{ now() }}</span>
8-
</p>
9-
</div>
1+
<x-livewire-modal::stack>
2+
<x-livewire-modal::modal :position="$position" class="size-96 overflow-auto rounded-md border bg-white">
3+
<div class="p-5">
4+
<p>
5+
Modal ID: <span x-text="modalId"></span>
6+
</p>
7+
<p>
8+
Rendered At: <span>{{ now() }}</span>
9+
</p>
10+
</div>
1011

11-
<div class="flex flex-col gap-2 p-5">
12-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
13-
x-modal:open="{ component: 'card' }">
14-
Open Modal
15-
</button>
12+
<div class="flex flex-col gap-2 p-5">
13+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
14+
x-modal:open="{ component: 'card' }">
15+
Open Modal
16+
</button>
1617

17-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
18-
x-modal:open="{ component: 'slideover' }">
19-
Open Slideover
20-
</button>
18+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
19+
x-modal:open="{ component: 'slideover' }">
20+
Open Slideover
21+
</button>
2122

22-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
23-
x-modal:open="{ component: 'slideover', stack: 'slideover' }">
24-
Open Stacked Slideover
25-
</button>
26-
</div>
23+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
24+
x-modal:open="{ component: 'slideover', stack: 'slideover' }">
25+
Open Stacked Slideover
26+
</button>
27+
</div>
2728

28-
<div class="flex flex-col gap-2 p-5">
29-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
30-
x-modal:open="{ component: 'card', stack: 'card', }">
31-
Open Stacked
32-
</button>
33-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
34-
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'left' } }">
35-
Open Stacked Left
36-
</button>
37-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
38-
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'right' } }">
39-
Open Stacked Right
40-
</button>
29+
<div class="flex flex-col gap-2 p-5">
30+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
31+
x-modal:open="{ component: 'card', stack: 'card', }">
32+
Open Stacked
33+
</button>
34+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
35+
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'left' } }">
36+
Open Stacked Left
37+
</button>
38+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
39+
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'right' } }">
40+
Open Stacked Right
41+
</button>
4142

42-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
43-
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'bottom' } }">
44-
Open Stacked Bottom
45-
</button>
43+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
44+
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'bottom' } }">
45+
Open Stacked Bottom
46+
</button>
4647

47-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
48-
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'top' } }">
49-
Open Stacked Top
50-
</button>
51-
</div>
52-
</x-livewire-modal::modal>
48+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
49+
x-modal:open="{ component: 'card', stack: 'card', props: { position: 'top' } }">
50+
Open Stacked Top
51+
</button>
52+
</div>
53+
</x-livewire-modal::modal>
54+
</x-livewire-modal::stack>
Lines changed: 34 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,39 @@
1-
<x-livewire-modal::slideover class="flex w-96 flex-col rounded-md border bg-white" :position="$position">
1+
<x-livewire-modal::stack>
2+
<x-livewire-modal::slideover :position="$position" class="w-full overflow-auto rounded-md border bg-white sm:w-96">
3+
<div class="p-5">
4+
<p>
5+
Modal ID: <span x-text="modalId"></span>
6+
</p>
7+
<p>
8+
Rendered At: <span>{{ now() }}</span>
9+
</p>
10+
</div>
211

3-
<div class="p-5">
4-
<p>
5-
Modal ID: <span x-text="modalId"></span>
6-
</p>
7-
<p>
8-
Rendered At: <span>{{ now() }}</span>
9-
</p>
10-
</div>
12+
<div class="flex flex-col gap-2 p-5">
13+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
14+
x-modal:open="{ component: 'card' }">
15+
Open Modal
16+
</button>
1117

12-
<div class="flex flex-col gap-2 p-5">
13-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
14-
x-modal:open="{ component: 'card' }">
15-
Open Modal
16-
</button>
18+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
19+
x-modal:open="{ component: 'slideover' }">
20+
Open Slideover
21+
</button>
1722

18-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
19-
x-modal:open="{ component: 'slideover' }">
20-
Open Slideover
21-
</button>
23+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
24+
x-modal:open="{ component: 'card', stack: 'card' }">
25+
Open Stacked Modal
26+
</button>
2227

23-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
24-
x-modal:open="{ component: 'card', stack: 'card' }">
25-
Open Stacked Modal
26-
</button>
28+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
29+
x-modal:open="{ component: 'slideover', stack: 'slideover' }">
30+
Open Stacked Slideover
31+
</button>
2732

28-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
29-
x-modal:open="{ component: 'slideover', stack: 'slideover' }">
30-
Open Stacked Slideover
31-
</button>
32-
33-
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
34-
x-modal:open="{ component: 'slideover', props:{ position: 'left' }, stack: 'slideover-left' }">
35-
Open Stacked Slideover Left
36-
</button>
37-
</div>
38-
</x-livewire-modal::slideover>
33+
<button type="button" class="rounded-md border px-3.5 py-1.5 shadow-sm focus:ring active:shadow-none"
34+
x-modal:open="{ component: 'slideover', props:{ position: 'left' }, stack: 'slideover-left' }">
35+
Open Stacked Slideover Left
36+
</button>
37+
</div>
38+
</x-livewire-modal::slideover>
39+
</x-livewire-modal::stack>

0 commit comments

Comments
 (0)