fix: keep modal save button visible on mobile (#803, #804)

Two fixes in Modal.tsx:
- Replace 100vh with 100dvh so iOS Safari PWA respects the actual
  visible viewport. Explicitly subtract --bottom-nav-h on mobile so
  the modal never extends behind the tab bar.
- overflow-hidden on the container so the footer's bottom corners
  inherit rounded-2xl.
- flex-shrink-0 on header and footer + min-h-0 on the body so the
  body shrinks and scrolls while the footer stays put.

One fix in PlaceFormModal.tsx:
- Save/cancel were rendered inside the scrollable body. Moved them
  into the Modal's footer slot.
This commit is contained in:
Maurice
2026-04-21 21:36:43 +02:00
parent fccf13a7e2
commit 9e5100c71c
2 changed files with 28 additions and 25 deletions
@@ -360,6 +360,25 @@ export default function PlaceFormModal({
onClose={onClose}
title={place ? t('places.editPlace') : t('places.addPlace')}
size="lg"
footer={
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-sm text-gray-600 hover:text-gray-900 border border-gray-200 rounded-lg hover:bg-gray-50"
>
{t('common.cancel')}
</button>
<button
type="button"
onClick={handleSubmit}
disabled={isSaving || hasTimeError}
className="px-6 py-2 bg-slate-900 text-white text-sm rounded-lg hover:bg-slate-700 disabled:opacity-60 font-medium"
>
{isSaving ? t('common.saving') : place ? t('common.update') : t('common.add')}
</button>
</div>
}
>
<form onSubmit={handleSubmit} className="space-y-4" onPaste={handlePaste}>
{/* Place Search */}
@@ -613,23 +632,6 @@ export default function PlaceFormModal({
</div>
)}
{/* Actions */}
<div className="flex justify-end gap-3 pt-2 border-t border-gray-100">
<button
type="button"
onClick={onClose}
className="px-4 py-2 text-sm text-gray-600 hover:text-gray-900 border border-gray-200 rounded-lg hover:bg-gray-50"
>
{t('common.cancel')}
</button>
<button
type="submit"
disabled={isSaving || hasTimeError}
className="px-6 py-2 bg-slate-900 text-white text-sm rounded-lg hover:bg-slate-700 disabled:opacity-60 font-medium"
>
{isSaving ? t('common.saving') : place ? t('common.update') : t('common.add')}
</button>
</div>
</form>
</Modal>
)
+9 -8
View File
@@ -61,14 +61,15 @@ export default function Modal({
<div
className={`
trek-modal-enter
rounded-2xl shadow-2xl w-full ${sizeClasses[size] || sizeClasses.md}
flex flex-col max-h-[calc(100vh-180px)] md:max-h-[calc(100vh-90px)]
rounded-2xl overflow-hidden shadow-2xl w-full ${sizeClasses[size] || sizeClasses.md}
flex flex-col
max-h-[calc(100dvh-var(--bottom-nav-h)-90px)] sm:max-h-[calc(100dvh-90px)]
`}
style={{ background: 'var(--bg-card)' }}
onClick={e => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-6" style={{ borderBottom: '1px solid var(--border-secondary)' }}>
{/* Header — stays put even while the body scrolls */}
<div className="flex items-center justify-between p-6 flex-shrink-0" style={{ borderBottom: '1px solid var(--border-secondary)' }}>
<h2 className="text-lg font-semibold" style={{ color: 'var(--text-primary)' }}>{title}</h2>
{!hideCloseButton && (
<button
@@ -80,14 +81,14 @@ export default function Modal({
)}
</div>
{/* Body */}
<div className="flex-1 overflow-y-auto p-6">
{/* Body — scrolls when content overflows. min-h-0 lets the flex child shrink below its intrinsic height. */}
<div className="flex-1 overflow-y-auto p-6 min-h-0">
{children}
</div>
{/* Footer */}
{/* Footer — sticky at the bottom of the modal, never compressed */}
{footer && (
<div className="p-6" style={{ borderTop: '1px solid var(--border-secondary)' }}>
<div className="p-6 flex-shrink-0" style={{ borderTop: '1px solid var(--border-secondary)' }}>
{footer}
</div>
)}