Skip to content

Commit d79c857

Browse files
Merge pull request #49 from schaefersoft/fix/flyout-inline-width
Flyout default styling
2 parents 15bd06a + dbafc19 commit d79c857

7 files changed

Lines changed: 112 additions & 35 deletions

File tree

dist/js/hui.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ function V(e) {
480480
e.setAttribute("data-hui-flyout-initialized", "");
481481
let t = null, n = !1, r = e.hasAttribute("data-hui-flyout-no-escape"), i = e.hasAttribute("data-hui-flyout-no-backdrop-close"), a = e.hasAttribute("data-hui-flyout-scroll-lock");
482482
function o() {
483-
P(e) !== null && (F(e) ? (e.setAttribute("data-hui-flyout-mode", "inline"), e.open && e.hasAttribute("data-hui-flyout-open") && e.close(), e.removeAttribute("data-hui-flyout-open")) : e.setAttribute("data-hui-flyout-mode", "flyout"));
483+
F(e) && (e.open && e.close(), e.removeAttribute("data-hui-flyout-open"));
484484
}
485485
function s() {
486486
if (F(e) || e.open || n) return;

docs/flyout.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ In inline mode:
9999
- The trigger button can be hidden with responsive utilities (e.g. `lg:hidden`)
100100
- Opening via trigger/JS API is ignored
101101

102+
> [!NOTE]
103+
> Inline visibility is CSS-driven: the component emits a small scoped `<style>` with a `min-width` media query at your
104+
> breakpoint. The sidebar is laid out at first paint and stays visible even before — or entirely without — JavaScript,
105+
> so there is no flash or layout shift on load and the content remains reachable on no-JS/SSR pages. JavaScript only
106+
> takes over below the breakpoint, where the flyout is a slide-out drawer.
107+
102108
### Swipe gestures (mobile)
103109

104110
The `swipe` prop enables touch swipe gestures. The direction is derived from `position` — you drag the panel toward

resources/css/flyout/index.css

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,3 @@
3838
.hui-flyout-bottom .hui-flyout-panel {
3939
inset: auto 0 0 0;
4040
}
41-
42-
/* Inline mode: static sidebar, no dialog behavior */
43-
.hui-flyout[data-hui-flyout-mode="inline"] {
44-
display: block;
45-
position: static;
46-
overflow: visible;
47-
}
48-
49-
.hui-flyout[data-hui-flyout-mode="inline"]::backdrop {
50-
display: none;
51-
}
52-
53-
.hui-flyout[data-hui-flyout-mode="inline"] .hui-flyout-background {
54-
display: none;
55-
}
56-
57-
.hui-flyout[data-hui-flyout-mode="inline"] .hui-flyout-panel {
58-
position: static;
59-
overflow-y: visible;
60-
}

resources/js/flyout/flyout.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ function stubPanelSize(panel: HTMLElement, width: number, height: number) {
5555
}) as DOMRect;
5656
}
5757

58+
const DEFAULT_INNER_WIDTH = window.innerWidth;
59+
60+
function setViewportWidth(width: number) {
61+
Object.defineProperty(window, 'innerWidth', { value: width, configurable: true, writable: true });
62+
}
63+
5864
async function waitFor(cond: () => boolean, timeout = 800): Promise<void> {
5965
const start = Date.now();
6066
while (!cond()) {
@@ -67,6 +73,7 @@ describe('Flyout', () => {
6773
beforeEach(() => {
6874
document.body.innerHTML = '';
6975
document.body.style.overflow = '';
76+
setViewportWidth(DEFAULT_INNER_WIDTH);
7077
});
7178

7279
// --- Open / Close ---
@@ -352,4 +359,39 @@ describe('Flyout', () => {
352359

353360
expect(flyout.open).toBe(true);
354361
});
362+
363+
// --- Inline (sidebar) mode ---
364+
365+
it('does not auto-open above the inline breakpoint (sidebar is shown via CSS)', () => {
366+
setViewportWidth(1280);
367+
const flyout = createFlyout('test-flyout', { open: true, inline: 1024 });
368+
expect(flyout.open).toBe(false);
369+
expect(flyout.hasAttribute('data-hui-flyout-open')).toBe(false);
370+
});
371+
372+
it('ignores open() while above the inline breakpoint', () => {
373+
setViewportWidth(1280);
374+
const flyout = createFlyout('test-flyout', { inline: 1024 });
375+
openFlyout('test-flyout');
376+
expect(flyout.open).toBe(false);
377+
});
378+
379+
it('behaves as a normal drawer below the inline breakpoint', () => {
380+
setViewportWidth(800);
381+
const flyout = createFlyout('test-flyout', { inline: 1024 });
382+
openFlyout('test-flyout');
383+
expect(flyout.open).toBe(true);
384+
closeFlyout('test-flyout');
385+
expect(flyout.open).toBe(false);
386+
});
387+
388+
it('never sets a presentation mode attribute (inline styling is CSS-driven)', () => {
389+
setViewportWidth(1280);
390+
const inline = createFlyout('test-flyout', { inline: 1024 });
391+
expect(inline.hasAttribute('data-hui-flyout-mode')).toBe(false);
392+
393+
setViewportWidth(800);
394+
const drawer = createFlyout('drawer-flyout', { inline: 1024 });
395+
expect(drawer.hasAttribute('data-hui-flyout-mode')).toBe(false);
396+
});
355397
});

resources/js/flyout/flyout.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,9 @@ function setupFlyout(flyout: HTMLDialogElement) {
182182
const scrollLock = flyout.hasAttribute('data-hui-flyout-scroll-lock');
183183

184184
function updateInlineState() {
185-
const bp = getBreakpoint(flyout);
186-
if (bp === null) return;
187-
188-
if (isInlineMode(flyout)) {
189-
flyout.setAttribute('data-hui-flyout-mode', 'inline');
190-
// If open as modal, close it — inline mode shows it statically
191-
if (flyout.open && flyout.hasAttribute('data-hui-flyout-open')) {
192-
flyout.close();
193-
}
194-
flyout.removeAttribute('data-hui-flyout-open');
195-
} else {
196-
flyout.setAttribute('data-hui-flyout-mode', 'flyout');
197-
}
185+
if (!isInlineMode(flyout)) return;
186+
if (flyout.open) flyout.close();
187+
flyout.removeAttribute('data-hui-flyout-open');
198188
}
199189

200190
function open() {

resources/views/flyout/flyout.blade.php

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1+
@php
2+
$flyoutId = $attributes->get('id');
3+
if ($inline && ! $flyoutId) {
4+
$flyoutId = 'hui-flyout-'.\Illuminate\Support\Str::random(8);
5+
}
6+
@endphp
7+
18
<dialog
9+
@if($flyoutId) id="{{ $flyoutId }}" @endif
210
data-hui-flyout
311
data-hui-flyout-position="{{ $position }}"
412
@class(['hui-flyout', "hui-flyout-{$position}", $class])
@@ -8,7 +16,30 @@
816
@if($scrollLock) data-hui-flyout-scroll-lock @endif
917
@if($inline) data-hui-flyout-inline="{{ $inline }}" @endif
1018
@if($swipeMode()) data-hui-flyout-swipe="{{ $swipeMode() }}" @endif
11-
{{ $attributes->except(['class', 'open', 'position', 'closeOnEscape', 'closeOnBackdropClick', 'scrollLock', 'inline', 'swipe']) }}
19+
{{ $attributes->except(['class', 'open', 'position', 'closeOnEscape', 'closeOnBackdropClick', 'scrollLock', 'inline', 'swipe', 'id']) }}
1220
>
1321
{{ $slot }}
1422
</dialog>
23+
24+
@if($inline)
25+
<style>
26+
@media (min-width: {{ $inline }}px) {
27+
#{{ $flyoutId }} {
28+
display: block;
29+
position: static;
30+
overflow: visible;
31+
width: auto;
32+
margin: 0;
33+
}
34+
35+
#{{ $flyoutId }} .hui-flyout-panel {
36+
position: static;
37+
overflow-y: visible;
38+
}
39+
40+
#{{ $flyoutId }} .hui-flyout-background {
41+
display: none;
42+
}
43+
}
44+
</style>
45+
@endif

tests/FlyoutTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,31 @@
4848

4949
$view->assertDontSee('data-hui-flyout-swipe', false);
5050
});
51+
52+
it('emits a scoped media query for inline mode at the configured breakpoint', function () {
53+
$view = $this->blade('
54+
<x-hui::flyout id="sidebar" :inline="1024">
55+
<x-hui::flyout.panel>Nav</x-hui::flyout.panel>
56+
</x-hui::flyout>
57+
');
58+
59+
$view->assertSee('data-hui-flyout-inline="1024"', false);
60+
$view->assertSee('<style>', false);
61+
$view->assertSee('@media (min-width: 1024px)', false);
62+
$view->assertSee('#sidebar', false);
63+
});
64+
65+
it('does not emit a style block when inline mode is off', function () {
66+
$view = $this->blade('<x-hui::flyout id="test-flyout">x</x-hui::flyout>');
67+
68+
$view->assertDontSee('<style', false);
69+
$view->assertDontSee('@media', false);
70+
});
71+
72+
it('generates an id to scope the inline media query when none is supplied', function () {
73+
$view = $this->blade('<x-hui::flyout :inline="768">x</x-hui::flyout>');
74+
75+
$view->assertSee('id="hui-flyout-', false);
76+
$view->assertSee('#hui-flyout-', false);
77+
$view->assertSee('@media (min-width: 768px)', false);
78+
});

0 commit comments

Comments
 (0)