The Add-tags and Add-to-collection editors opened as centered Modals on every form factor, so a mobile user went action-Sheet → centered Modal — an inconsistent hop, and AddTagsSheet's docstring promised a sheet it never got. Introduce AdaptiveDialog, which renders a bottom Sheet under 640px and a centered Modal above it behind one shared contract (open / title / onClose / children / testid). Route both editors through it: - AddToCollectionModal now wraps AdaptiveDialog (both its call sites — manga detail and the reader — adapt automatically). - The reader hosts AddTagsSheet in an AdaptiveDialog instead of a Modal, so its "slots into a Sheet (mobile) or Modal (desktop)" contract is now true. Modal/Sheet already share an API, so the switch is transparent: the dialog keeps the same role, title, testid, and close affordance. Desktop is unchanged. AdaptiveDialog has its own unit test (Sheet on mobile, Modal on desktop); an e2e proves the mobile long-press → Add-tag flow now opens a Sheet. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
54 lines
1.8 KiB
Svelte
54 lines
1.8 KiB
Svelte
<script lang="ts">
|
|
import type { Snippet } from 'svelte';
|
|
import Modal from './Modal.svelte';
|
|
import Sheet from './Sheet.svelte';
|
|
|
|
// One overlay that adapts to the form factor: a bottom Sheet on phones,
|
|
// a centered Modal on desktop. Both share the same chrome contract
|
|
// (open / title / onClose / children / testid), so callers stay agnostic
|
|
// and the page-action editors present consistently with the rest of the
|
|
// mobile UI (long-press → action sheet → editor sheet, no Modal hop).
|
|
let {
|
|
open,
|
|
title,
|
|
onClose,
|
|
children,
|
|
size = 'md',
|
|
testid
|
|
}: {
|
|
open: boolean;
|
|
title: string;
|
|
onClose: () => void;
|
|
children: Snippet;
|
|
/** Desktop modal width; ignored by the mobile sheet. */
|
|
size?: 'sm' | 'md' | 'lg';
|
|
testid?: string;
|
|
} = $props();
|
|
|
|
// Bottom sheet under the 640px breakpoint. Guarded on matchMedia rather
|
|
// than $app/environment so it's SSR-safe (no window) and unit-testable
|
|
// (stub matchMedia). First paint defaults to the modal; the effect
|
|
// corrects to a sheet on phones before the dialog is opened.
|
|
let isMobile = $state(false);
|
|
$effect(() => {
|
|
if (typeof window === 'undefined' || !window.matchMedia) return;
|
|
const mql = window.matchMedia('(max-width: 640px)');
|
|
isMobile = mql.matches;
|
|
const onChange = (e: MediaQueryListEvent) => {
|
|
isMobile = e.matches;
|
|
};
|
|
mql.addEventListener('change', onChange);
|
|
return () => mql.removeEventListener('change', onChange);
|
|
});
|
|
</script>
|
|
|
|
{#if isMobile}
|
|
<Sheet {open} {title} {onClose} {testid}>
|
|
{@render children()}
|
|
</Sheet>
|
|
{:else}
|
|
<Modal {open} {title} {onClose} {size} {testid}>
|
|
{@render children()}
|
|
</Modal>
|
|
{/if}
|