mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-12 23:31:02 +00:00
feat(mobile): admin, settings picker and place sheets in the common style, all sheets on glass
Admin sheets: their frame draws the icon tile header (an icon per sheet, the plugin sheets' icons moved out of the title into the tile), the soft scroll edge and 42 px footer buttons; admin fields use the sheets' eyebrow labels and round inputs. The settings picker, the bulk category sheet and the save to list sheet get the common header (TileHeader may now leave out the icon for a plain picker). Every phone sheet now uses the glass material, as the trip settings do; it stays 94 % opaque, so forms over the map remain legible.
This commit is contained in:
@@ -201,7 +201,7 @@
|
||||
"mobile/screens/settings/MMobileNavCustomizer.tsx": 8,
|
||||
"mobile/screens/settings/MPhotoProvidersSection.tsx": 2,
|
||||
"mobile/screens/settings/MScopeGroupPicker.tsx": 5,
|
||||
"mobile/screens/settings/MSetPickerSheet.tsx": 2,
|
||||
"mobile/screens/settings/MSetPickerSheet.tsx": 1,
|
||||
"mobile/screens/settings/MSettings.tsx": 2,
|
||||
"mobile/screens/settings/MSettingsAbout.tsx": 5,
|
||||
"mobile/screens/settings/MSettingsAccount.tsx": 20,
|
||||
@@ -216,9 +216,9 @@
|
||||
"mobile/screens/trip/MTripLoadingSplash.tsx": 2,
|
||||
"mobile/screens/trip/MTripShell.tsx": 9,
|
||||
"mobile/screens/trip/places/MPlacesBrowser.tsx": 10,
|
||||
"mobile/screens/trip/places/MPlacesBulkCategorySheet.tsx": 4,
|
||||
"mobile/screens/trip/places/MPlacesBulkCategorySheet.tsx": 2,
|
||||
"mobile/screens/trip/places/MPlacesFilterControls.tsx": 1,
|
||||
"mobile/screens/trip/places/MPlacesSaveToCollectionSheet.tsx": 7,
|
||||
"mobile/screens/trip/places/MPlacesSaveToCollectionSheet.tsx": 6,
|
||||
"mobile/screens/trip/places/MPlacesToursModeSwitch.tsx": 1,
|
||||
"mobile/screens/trip/plan/MPlanTimeline.tsx": 12,
|
||||
"mobile/screens/trip/plan/MPlanTimelineRows.tsx": 24,
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { useState } from 'react'
|
||||
import { useFormatDate } from '../../../hooks/useDateFormat'
|
||||
import MPluginVersionDate from './MPluginVersionDate'
|
||||
import {
|
||||
Blocks, AlertTriangle, PackageOpen, RefreshCw, Trash2, Download, Bug, X, ShieldCheck, UploadCloud,
|
||||
ArrowUpCircle, Github, ExternalLink, ChevronDown, Check, Lock, Search, Link2, KeyRound, ShieldAlert,
|
||||
SlidersHorizontal, ArrowUpDown, CircleDot, MoreHorizontal, RotateCw, Database,
|
||||
Globe, Info, History, PauseCircle, Puzzle, Bot,
|
||||
} from 'lucide-react'
|
||||
import { Blocks, AlertTriangle, PackageOpen, RefreshCw, Trash2, Download, Bug, X, ShieldCheck, UploadCloud, ArrowUpCircle, Github, ExternalLink, ChevronDown, Check, Lock, Search, Link2, KeyRound, ShieldAlert, SlidersHorizontal, ArrowUpDown, CircleDot, MoreHorizontal, RotateCw, Database, Globe, Info, History, PauseCircle, Puzzle, Bot, ListChecks } from 'lucide-react'
|
||||
import PluginIcon from '../../../components/shared/PluginIcon'
|
||||
import { type RangeWarning } from '../../../components/Admin/useRangeBypass'
|
||||
import { deriveCaps } from '../../../components/Admin/pluginCaps'
|
||||
@@ -353,9 +348,10 @@ export default function MAdminPluginsPanel() {
|
||||
|
||||
{/* Error-log sheet */}
|
||||
{errorsFor && (
|
||||
<MSheet open onClose={() => setErrorsFor(null)} variant="bottom" material="opaque" ariaLabel={t('admin.plugins.errorLog')}>
|
||||
<MSheet open onClose={() => setErrorsFor(null)} variant="bottom" material="glass" ariaLabel={t('admin.plugins.errorLog')}>
|
||||
<MAdminSheetFrame
|
||||
title={<span className="flex items-center gap-2"><Bug size={15} /> {errorsFor.id} — {t('admin.plugins.errorLog')}</span>}
|
||||
icon={<Bug size={19} strokeWidth={1.8} />}
|
||||
title={<>{errorsFor.id} — {t('admin.plugins.errorLog')}</>}
|
||||
onClose={() => setErrorsFor(null)}
|
||||
>
|
||||
<div className="font-mono text-[0.6875rem]">
|
||||
@@ -374,9 +370,10 @@ export default function MAdminPluginsPanel() {
|
||||
|
||||
{/* Operator-supplied egress hosts */}
|
||||
{egressFor && (
|
||||
<MSheet open onClose={() => setEgressFor(null)} variant="bottom" material="opaque" ariaLabel={t('admin.plugins.allowedHosts')}>
|
||||
<MSheet open onClose={() => setEgressFor(null)} variant="bottom" material="glass" ariaLabel={t('admin.plugins.allowedHosts')}>
|
||||
<MAdminSheetFrame
|
||||
title={<span className="flex items-center gap-2"><Globe size={15} /> {egressFor.id} — {t('admin.plugins.allowedHosts')}</span>}
|
||||
icon={<Globe size={19} strokeWidth={1.8} />}
|
||||
title={<>{egressFor.id} — {t('admin.plugins.allowedHosts')}</>}
|
||||
onClose={() => setEgressFor(null)}
|
||||
>
|
||||
{!egressFor.supported ? (
|
||||
@@ -421,9 +418,10 @@ export default function MAdminPluginsPanel() {
|
||||
|
||||
{/* Instance-wide settings (the admin-owned scope:'instance' fields) */}
|
||||
{settings.form && (
|
||||
<MSheet open onClose={settings.close} variant="bottom" material="opaque" ariaLabel={t('admin.plugins.instanceSettings')}>
|
||||
<MSheet open onClose={settings.close} variant="bottom" material="glass" ariaLabel={t('admin.plugins.instanceSettings')}>
|
||||
<MAdminSheetFrame
|
||||
title={<span className="flex items-center gap-2"><SlidersHorizontal size={15} /> {settings.form.id} — {t('admin.plugins.instanceSettings')}</span>}
|
||||
icon={<SlidersHorizontal size={19} strokeWidth={1.8} />}
|
||||
title={<>{settings.form.id} — {t('admin.plugins.instanceSettings')}</>}
|
||||
onClose={settings.close}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
@@ -602,8 +600,8 @@ function PickerSheet({ open, onClose, title, options, value, onPick }: {
|
||||
options: Array<[string, string]>; value: string; onPick: (v: string) => void
|
||||
}) {
|
||||
return (
|
||||
<MSheet open={open} onClose={onClose} variant="bottom" material="opaque" ariaLabel={title}>
|
||||
<MAdminSheetFrame title={title} onClose={onClose}>
|
||||
<MSheet open={open} onClose={onClose} variant="bottom" material="glass" ariaLabel={title}>
|
||||
<MAdminSheetFrame icon={<ListChecks size={19} strokeWidth={1.8} />} title={title} onClose={onClose}>
|
||||
<div className="space-y-1">
|
||||
{options.map(([v, lbl]) => (
|
||||
<button key={v} type="button" onClick={() => { onPick(v); onClose() }}
|
||||
@@ -760,8 +758,8 @@ function RowActionsSheet({ p, t, onClose, onRestart, onErrors, onEgress, onSetti
|
||||
const linkable = p.source_repo && p.source_repo !== 'local:upload' && p.source_repo !== 'local:link'
|
||||
const rowClass = 'flex w-full items-center gap-2.5 rounded-xl px-3 py-3 text-start text-[0.8125rem] font-semibold text-m-ink'
|
||||
return (
|
||||
<MSheet open onClose={onClose} variant="bottom" material="opaque" ariaLabel={p.name}>
|
||||
<MAdminSheetFrame title={p.name} onClose={onClose}>
|
||||
<MSheet open onClose={onClose} variant="bottom" material="glass" ariaLabel={p.name}>
|
||||
<MAdminSheetFrame icon={<Puzzle size={19} strokeWidth={1.8} />} title={p.name} onClose={onClose}>
|
||||
<div className="space-y-1">
|
||||
{p.enabled === 1 && (
|
||||
<button type="button" className={rowClass} onClick={onRestart}><RotateCw size={16} /> {t('admin.plugins.restart')}</button>
|
||||
@@ -813,8 +811,8 @@ function VersionPickerSheet({ plugin, versions, failed, busy, t, onPick, onClose
|
||||
t: T; onPick: (version: string) => void; onClose: () => void
|
||||
}) {
|
||||
return (
|
||||
<MSheet open onClose={onClose} variant="bottom" material="opaque" ariaLabel={t('admin.plugins.versionPickerTitle', { name: plugin.name })}>
|
||||
<MAdminSheetFrame title={t('admin.plugins.versionPickerTitle', { name: plugin.name })} onClose={onClose}>
|
||||
<MSheet open onClose={onClose} variant="bottom" material="glass" ariaLabel={t('admin.plugins.versionPickerTitle', { name: plugin.name })}>
|
||||
<MAdminSheetFrame icon={<History size={19} strokeWidth={1.8} />} title={t('admin.plugins.versionPickerTitle', { name: plugin.name })} onClose={onClose}>
|
||||
<div className="space-y-0.5">
|
||||
{failed && <p className="px-2.5 py-2 text-xs text-[color:var(--m-st-danger)]">{t('admin.plugins.detailError')}</p>}
|
||||
{!failed && !versions && <p className="px-2.5 py-2 text-xs text-m-faint">{t('common.loading')}</p>}
|
||||
@@ -973,7 +971,7 @@ function PluginDetailSheet({ item, installed, busy, onInstall, onClose, t, local
|
||||
const sectionH = 'text-[11px] font-semibold uppercase tracking-wider text-m-muted'
|
||||
|
||||
return (
|
||||
<MSheet open onClose={onClose} variant="card" material="opaque" ariaLabel={item.name}>
|
||||
<MSheet open onClose={onClose} variant="card" material="glass" ariaLabel={item.name}>
|
||||
<div className="relative flex-none">
|
||||
<Screenshot url={item.screenshotUrl} className="aspect-[16/9] w-full" iconSize={36} />
|
||||
<div className="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent" />
|
||||
@@ -1195,7 +1193,7 @@ function SignatureBlockSheet({ data, entry, busy, t, onRetrust, onClose }: {
|
||||
const offerRetrust = retrust !== null
|
||||
|
||||
return (
|
||||
<MSheet open onClose={onClose} variant="card" material="opaque" ariaLabel={t('admin.plugins.sig.title', { name: data.subject.name })}>
|
||||
<MSheet open onClose={onClose} variant="card" material="glass" ariaLabel={t('admin.plugins.sig.title', { name: data.subject.name })}>
|
||||
<div className="flex-none px-[18px] pt-[18px]">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`grid h-9 w-9 shrink-0 place-items-center rounded-lg ${CHIP_PENDING}`}><ShieldAlert size={18} /></div>
|
||||
@@ -1248,7 +1246,7 @@ function UpdateConsentSheet({ data, unsigned, t, onApprove, onLater }: {
|
||||
t: T; onApprove: () => void; onLater: () => void
|
||||
}) {
|
||||
return (
|
||||
<MSheet open onClose={onLater} variant="card" material="opaque" ariaLabel={t('admin.plugins.updateConsentTitle')}>
|
||||
<MSheet open onClose={onLater} variant="card" material="glass" ariaLabel={t('admin.plugins.updateConsentTitle')}>
|
||||
<div className="flex-none px-[18px] pt-[18px]">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`grid h-9 w-9 shrink-0 place-items-center rounded-lg ${CHIP_PENDING}`}><ShieldCheck size={18} /></div>
|
||||
@@ -1305,7 +1303,7 @@ function DependencyResolveSheet({ data, t, busy, installedIds, onDownload, onClo
|
||||
}) {
|
||||
const rows = dependencyRows(data)
|
||||
return (
|
||||
<MSheet open onClose={onClose} variant="card" material="opaque" ariaLabel={t('admin.plugins.dep.resolveTitle')}>
|
||||
<MSheet open onClose={onClose} variant="card" material="glass" ariaLabel={t('admin.plugins.dep.resolveTitle')}>
|
||||
<div className="flex-none px-[18px] pt-[18px]">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className={`grid h-9 w-9 shrink-0 place-items-center rounded-lg ${CHIP_PENDING}`}><Puzzle size={18} /></div>
|
||||
|
||||
@@ -32,7 +32,8 @@ export default function MAdminResetMfaSheet({ open, onClose, user, onReset, t }:
|
||||
return (
|
||||
<MSheet open={open} onClose={onClose} ariaLabel={t('admin.mfa.reset')}>
|
||||
<MAdminSheetFrame
|
||||
title={t('admin.mfa.reset')}
|
||||
icon={<ShieldOff size={19} strokeWidth={1.8} />}
|
||||
title={t('admin.mfa.reset')}
|
||||
onClose={onClose}
|
||||
footer={
|
||||
<>
|
||||
|
||||
@@ -41,9 +41,10 @@ function AddCountrySheet({
|
||||
onClose();
|
||||
};
|
||||
return (
|
||||
<MSheet open={open} onClose={close} variant="card" material="opaque" ariaLabel={t('schoolCatalog.addCountry')}>
|
||||
<MSheet open={open} onClose={close} variant="card" material="glass" ariaLabel={t('schoolCatalog.addCountry')}>
|
||||
<MAdminSheetFrame
|
||||
title={t('schoolCatalog.addCountry')}
|
||||
icon={<GraduationCap size={19} strokeWidth={1.8} />}
|
||||
title={t('schoolCatalog.addCountry')}
|
||||
onClose={close}
|
||||
footer={
|
||||
<>
|
||||
@@ -113,12 +114,13 @@ function RegionSheet({ region, busy, offline, error, onClose, onSave }: EditorPr
|
||||
if (!busy) onClose();
|
||||
}}
|
||||
variant="card"
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={name.trim() || t('schoolCatalog.region')}
|
||||
discardGuard={body}
|
||||
>
|
||||
<MAdminSheetFrame
|
||||
title={name.trim() || t('schoolCatalog.region')}
|
||||
icon={<GraduationCap size={19} strokeWidth={1.8} />}
|
||||
title={name.trim() || t('schoolCatalog.region')}
|
||||
onClose={() => {
|
||||
if (!busy) onClose();
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import { AlertTriangle, CheckCircle, ExternalLink, Fingerprint, ShieldOff, Trash2 } from 'lucide-react'
|
||||
import { AlertTriangle, CheckCircle, ExternalLink, Fingerprint, ShieldOff, Trash2, UserPlus, UserCog, Download, KeyRound, RotateCcw } from 'lucide-react'
|
||||
import { useAdminUserActions } from '../../../components/Admin/useAdminUserActions'
|
||||
import type { TranslationFn } from '../../../types'
|
||||
import type { useAdmin } from '../../../pages/admin/useAdmin'
|
||||
@@ -63,6 +63,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) {
|
||||
{/* Create user */}
|
||||
<MSheet open={showCreateUser} onClose={() => setShowCreateUser(false)} ariaLabel={t('admin.createUser')}>
|
||||
<MAdminSheetFrame
|
||||
icon={<UserPlus size={19} strokeWidth={1.8} />}
|
||||
title={t('admin.createUser')}
|
||||
onClose={() => setShowCreateUser(false)}
|
||||
footer={
|
||||
@@ -115,6 +116,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) {
|
||||
{/* Edit user */}
|
||||
<MSheet open={!!editingUser} onClose={() => setEditingUser(null)} ariaLabel={t('admin.editUser')}>
|
||||
<MAdminSheetFrame
|
||||
icon={<UserCog size={19} strokeWidth={1.8} />}
|
||||
title={t('admin.editUser')}
|
||||
onClose={() => setEditingUser(null)}
|
||||
footer={
|
||||
@@ -205,6 +207,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) {
|
||||
{/* How to update */}
|
||||
<MSheet open={showUpdateModal} onClose={() => setShowUpdateModal(false)} ariaLabel={t('admin.update.howTo')}>
|
||||
<MAdminSheetFrame
|
||||
icon={<Download size={19} strokeWidth={1.8} />}
|
||||
title={t('admin.update.howTo')}
|
||||
onClose={() => setShowUpdateModal(false)}
|
||||
footer={<MAdminButton onClick={() => setShowUpdateModal(false)}>{t('common.close')}</MAdminButton>}
|
||||
@@ -256,6 +259,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) {
|
||||
{/* Reset passkeys confirm */}
|
||||
<MSheet open={showResetPasskeys} onClose={() => setShowResetPasskeys(false)} ariaLabel={t('admin.passkey.reset')}>
|
||||
<MAdminSheetFrame
|
||||
icon={<KeyRound size={19} strokeWidth={1.8} />}
|
||||
title={t('admin.passkey.reset')}
|
||||
onClose={() => setShowResetPasskeys(false)}
|
||||
footer={
|
||||
@@ -288,6 +292,7 @@ export default function MAdminSheets({ admin, t }: MAdminSheetsProps) {
|
||||
{/* Rotate JWT confirm */}
|
||||
<MSheet open={showRotateJwtModal} onClose={() => setShowRotateJwtModal(false)} ariaLabel={t('admin.rotateJwt.title')}>
|
||||
<MAdminSheetFrame
|
||||
icon={<RotateCcw size={19} strokeWidth={1.8} />}
|
||||
title={t('admin.rotateJwt.title')}
|
||||
onClose={() => setShowRotateJwtModal(false)}
|
||||
footer={
|
||||
|
||||
@@ -83,7 +83,7 @@ export function MAdminClampedText({ children, className = '' }: { children: stri
|
||||
export function MAdminField({ label, hint, children }: { label: ReactNode; hint?: ReactNode; children: ReactNode }) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-[6px] text-[0.75rem] font-semibold text-m-ink">{label}</div>
|
||||
<div className="mb-[5px] font-geist text-[0.625rem] font-bold uppercase tracking-[.09em] text-m-faint">{label}</div>
|
||||
{children}
|
||||
{hint && <div className="mt-[5px] font-geist text-[0.625rem] leading-relaxed text-m-muted">{hint}</div>}
|
||||
</div>
|
||||
@@ -94,7 +94,7 @@ export function MAdminInput({ className = '', ...props }: InputHTMLAttributes<HT
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
className={`h-[42px] w-full rounded-xl border border-[color:var(--m-rowbr)] bg-[color:var(--m-ic)] px-3 text-[0.84375rem] text-m-ink outline-none placeholder:text-m-faint focus:border-[color:var(--m-faint)] disabled:cursor-not-allowed disabled:opacity-60 ${className}`}
|
||||
className={`h-[42px] w-full rounded-full border border-[color:var(--m-rowbr)] bg-[color:var(--m-ic)] px-[14px] text-[0.84375rem] text-m-ink outline-none placeholder:text-m-faint focus:border-[color:var(--m-faint)] disabled:cursor-not-allowed disabled:opacity-60 ${className}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -161,11 +161,14 @@ export function MAdminButton({
|
||||
/** Sheet body frame: title row + close, scrollable content, optional footer. */
|
||||
export function MAdminSheetFrame({
|
||||
title,
|
||||
icon,
|
||||
onClose,
|
||||
footer,
|
||||
children,
|
||||
}: {
|
||||
title: ReactNode
|
||||
/** The icon tile in front of the title, as every phone sheet has. */
|
||||
icon?: ReactNode
|
||||
onClose: () => void
|
||||
footer?: ReactNode
|
||||
children: ReactNode
|
||||
@@ -173,15 +176,22 @@ export function MAdminSheetFrame({
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-none items-center gap-2 px-4 pb-2 pt-4">
|
||||
<div className="min-w-0 flex-1 truncate text-[0.9375rem] font-extrabold text-m-ink">{title}</div>
|
||||
{/* TileHeader's shape: the icon tile, the title and the round close. */}
|
||||
<div className="flex flex-none items-center gap-3 px-[18px] pb-1 pt-4">
|
||||
{icon && (
|
||||
<span className="flex h-10 w-10 flex-none items-center justify-center rounded-[13px] bg-[color:var(--m-ic)] text-m-ink">
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
<div className="min-w-0 flex-1 truncate text-[1.0625rem] font-bold leading-tight text-m-ink">{title}</div>
|
||||
<MIconBtn variant="neutral" size={34} ariaLabel={t('common.close')} onClick={onClose}>
|
||||
<X size={16} strokeWidth={2.2} />
|
||||
<X size={15} strokeWidth={2.2} />
|
||||
</MIconBtn>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-4 pb-4">{children}</div>
|
||||
<div className="m-scroll-fade mt-2 min-h-0 flex-1 overflow-y-auto px-[18px] pb-[18px] pt-[14px]">{children}</div>
|
||||
{footer && (
|
||||
<div className="flex flex-none items-center justify-end gap-2 border-t border-[color:var(--m-rowbr)] px-4 py-3">
|
||||
// The footer's buttons take the sheets' 42 px size; elsewhere MAdminButton stays a small pill.
|
||||
<div className="flex flex-none items-center justify-end gap-2 border-t border-[color:var(--m-rowbr)] px-[18px] pb-4 pt-3 [&>button]:h-[42px] [&>button]:px-4 [&>button]:text-[length:calc(13px*var(--fs-scale-body,1))] [&>button]:font-semibold">
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Copy, Link2, Trash2, UserPlus } from 'lucide-react'
|
||||
import { Copy, Link2, Trash2, UserPlus, Ticket } from 'lucide-react'
|
||||
import type { TranslationFn } from '../../../types'
|
||||
import type { useAdmin } from '../../../pages/admin/useAdmin'
|
||||
import { inviteMeta, inviteStatus } from '../../../pages/admin/adminModel'
|
||||
@@ -167,6 +167,7 @@ export default function MAdminUsersSection({ admin, t, locale }: MAdminUsersSect
|
||||
{/* Create invite sheet */}
|
||||
<MSheet open={showCreateInvite} onClose={() => setShowCreateInvite(false)} ariaLabel={t('admin.invite.create')}>
|
||||
<MAdminSheetFrame
|
||||
icon={<Ticket size={19} strokeWidth={1.8} />}
|
||||
title={t('admin.invite.create')}
|
||||
onClose={() => setShowCreateInvite(false)}
|
||||
footer={
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function MCollPlaceSheet({
|
||||
'flex flex-1 items-center justify-center gap-[6px] rounded-[13px] border border-[color:var(--m-rowbr)] bg-[color:var(--m-sheet)] px-2 py-[11px] text-[0.78125rem] font-semibold text-m-ink'
|
||||
|
||||
return (
|
||||
<MSheet open={place != null} onClose={onClose} material="opaque" className="!rounded-[24px]" ariaLabel={held?.name}>
|
||||
<MSheet open={place != null} onClose={onClose} material="glass" className="!rounded-[24px]" ariaLabel={held?.name}>
|
||||
{held && (
|
||||
<>
|
||||
{/* Hero: auto cover (photo when available, the design gradient otherwise) */}
|
||||
|
||||
@@ -528,7 +528,7 @@ export default function MJourneyDetail({ journeyId, embedded = false }: JourneyD
|
||||
{/* Opaque, not the glass default: these two sit over a full-screen map whose
|
||||
card carousel is bright and busy, and the frosted material let the titles
|
||||
behind read straight through the menu. */}
|
||||
<MSheet open={showUploadMenu} onClose={() => setShowUploadMenu(false)} variant="bottom" material="opaque" ariaLabel={t('common.upload')}>
|
||||
<MSheet open={showUploadMenu} onClose={() => setShowUploadMenu(false)} variant="bottom" material="glass" ariaLabel={t('common.upload')}>
|
||||
<div className="flex flex-col gap-2 p-[10px]">
|
||||
<MListRow
|
||||
icon={Camera}
|
||||
@@ -547,7 +547,7 @@ export default function MJourneyDetail({ journeyId, embedded = false }: JourneyD
|
||||
</MSheet>
|
||||
|
||||
{/* Journey actions: book export, suggestions switch, settings */}
|
||||
<MSheet open={showActionMenu} onClose={() => setShowActionMenu(false)} variant="bottom" material="opaque" ariaLabel={t('files.menu')}>
|
||||
<MSheet open={showActionMenu} onClose={() => setShowActionMenu(false)} variant="bottom" material="glass" ariaLabel={t('files.menu')}>
|
||||
<div className="flex flex-col gap-1 p-[10px]">
|
||||
<div className="flex items-center gap-[11px] px-[10px] py-[11px]">
|
||||
<EyeOff size={16} strokeWidth={2} className="flex-none text-m-muted" />
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { Check } from 'lucide-react'
|
||||
import MSheet from '../../components/MSheet'
|
||||
import { TileHeader } from '../trip/sheets/MTripSheetUi'
|
||||
import { useTranslation } from '../../../i18n'
|
||||
|
||||
export interface MSetPickerOption {
|
||||
value: string
|
||||
@@ -21,10 +23,14 @@ interface MSetPickerSheetProps {
|
||||
* presets). Selecting closes the sheet.
|
||||
*/
|
||||
export default function MSetPickerSheet({ open, onClose, title, options, value, onSelect }: MSetPickerSheetProps) {
|
||||
const { t } = useTranslation()
|
||||
const closeLabel = t('common.close')
|
||||
return (
|
||||
<MSheet open={open} onClose={onClose} variant="bottom" material="opaque" ariaLabel={title}>
|
||||
<div className="px-[14px] pb-2 pt-4 text-[0.875rem] font-extrabold text-m-ink">{title}</div>
|
||||
<div className="min-h-0 overflow-y-auto px-2 pb-3">
|
||||
<MSheet open={open} onClose={onClose} variant="bottom" material="glass" ariaLabel={title}>
|
||||
<div className="flex-none px-[18px] pt-4">
|
||||
<TileHeader title={title} onClose={onClose} closeLabel={closeLabel} />
|
||||
</div>
|
||||
<div className="m-scroll-fade mt-1 min-h-0 overflow-y-auto px-2 pb-3 pt-2">
|
||||
{options.map((opt) => {
|
||||
const active = opt.value === value
|
||||
return (
|
||||
|
||||
@@ -300,7 +300,7 @@ export default function MSettingsMcp() {
|
||||
if (!createdClient) setCreateOpen(false)
|
||||
}}
|
||||
variant="card"
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={t('settings.oauth.modal.createTitle')}
|
||||
>
|
||||
<div className="min-h-0 overflow-y-auto p-[18px]">
|
||||
@@ -409,7 +409,7 @@ export default function MSettingsMcp() {
|
||||
if (!createdToken) setTokenModalOpen(false)
|
||||
}}
|
||||
variant="card"
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={t('settings.mcp.modal.createTitle')}
|
||||
>
|
||||
<div className="p-[18px]">
|
||||
@@ -461,7 +461,7 @@ export default function MSettingsMcp() {
|
||||
</MSheet>
|
||||
|
||||
{/* Rotated secret display */}
|
||||
<MSheet open={rotatedSecret !== null} onClose={() => setRotatedSecret(null)} variant="card" material="opaque" ariaLabel={t('settings.oauth.rotateSecretDoneTitle')}>
|
||||
<MSheet open={rotatedSecret !== null} onClose={() => setRotatedSecret(null)} variant="card" material="glass" ariaLabel={t('settings.oauth.rotateSecretDoneTitle')}>
|
||||
<div className="p-[18px]">
|
||||
<div className="text-[0.9375rem] font-extrabold text-m-ink">{t('settings.oauth.rotateSecretDoneTitle')}</div>
|
||||
<p className="mt-2 rounded-xl border border-[color:var(--m-rowbr)] bg-[color:var(--m-ic)] p-3 font-geist text-[0.625rem] leading-relaxed text-[color:var(--m-st-pending)]">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { MapPin, X } from 'lucide-react'
|
||||
import { MapPin, Tag } from 'lucide-react'
|
||||
import MSheet from '../../../components/MSheet'
|
||||
import MIconBtn from '../../../components/MIconBtn'
|
||||
import { TileHeader } from '../sheets/MTripSheetUi'
|
||||
import { getCategoryIcon } from '../../../../components/shared/categoryIcons'
|
||||
import { useTranslation } from '../../../../i18n'
|
||||
import type { Category } from '../../../../types'
|
||||
@@ -21,17 +21,17 @@ interface MPlacesBulkCategorySheetProps {
|
||||
export default function MPlacesBulkCategorySheet({ open, count, categories, onPick, onClose }: MPlacesBulkCategorySheetProps) {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<MSheet open={open} onClose={onClose} variant="card" ariaLabel={t('places.changeCategory')}>
|
||||
<div className="flex flex-none items-center border-b border-[color:var(--m-rowbr)] px-[18px] pb-[11px] pt-4">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-[1.03125rem] font-bold text-m-ink">{t('places.changeCategory')}</div>
|
||||
<div className="mt-[2px] font-geist text-[0.6875rem] text-m-muted">{t('places.selectionCount', { count })}</div>
|
||||
</div>
|
||||
<MIconBtn variant="neutral" size={34} onClick={onClose} ariaLabel={t('common.close')}>
|
||||
<X size={15} strokeWidth={2.2} />
|
||||
</MIconBtn>
|
||||
<MSheet open={open} onClose={onClose} variant="card" material="glass" ariaLabel={t('places.changeCategory')}>
|
||||
<div className="flex-none px-[18px] pt-4">
|
||||
<TileHeader
|
||||
icon={<Tag size={19} strokeWidth={1.8} />}
|
||||
title={t('places.changeCategory')}
|
||||
sub={t('places.selectionCount', { count })}
|
||||
onClose={onClose}
|
||||
closeLabel={t('common.close')}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-[10px] pb-3 pt-[6px]">
|
||||
<div className="m-scroll-fade mt-2 min-h-0 flex-1 overflow-y-auto px-[10px] pb-3 pt-[10px]">
|
||||
{categories.map(c => {
|
||||
const CatIcon = getCategoryIcon(c.icon)
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ArrowRight, Bookmark, Loader2, Plus, X } from 'lucide-react'
|
||||
import { ArrowRight, Bookmark, Loader2, Plus } from 'lucide-react'
|
||||
import MSheet from '../../../components/MSheet'
|
||||
import MIconBtn from '../../../components/MIconBtn'
|
||||
import { TileHeader } from '../sheets/MTripSheetUi'
|
||||
import { useTranslation } from '../../../../i18n'
|
||||
import { useSaveTripPlacesToList } from '../../../../components/Collections/useSaveTripPlacesToList'
|
||||
|
||||
@@ -25,14 +25,14 @@ export default function MPlacesSaveToCollectionSheet({ open, tripId, placeIds, o
|
||||
})
|
||||
|
||||
return (
|
||||
<MSheet open={open} onClose={onClose} variant="card" ariaLabel={t('collections.saveNToList', { count: placeIds.length })}>
|
||||
<div className="flex flex-none items-center border-b border-[color:var(--m-rowbr)] px-[18px] pb-[11px] pt-4">
|
||||
<div className="min-w-0 flex-1 text-[1.03125rem] font-bold text-m-ink">
|
||||
{t('collections.saveNToList', { count: placeIds.length })}
|
||||
</div>
|
||||
<MIconBtn variant="neutral" size={34} onClick={onClose} ariaLabel={t('common.close')}>
|
||||
<X size={15} strokeWidth={2.2} />
|
||||
</MIconBtn>
|
||||
<MSheet open={open} onClose={onClose} variant="card" material="glass" ariaLabel={t('collections.saveNToList', { count: placeIds.length })}>
|
||||
<div className="flex-none px-[18px] pt-4">
|
||||
<TileHeader
|
||||
icon={<Bookmark size={19} strokeWidth={1.8} />}
|
||||
title={t('collections.saveNToList', { count: placeIds.length })}
|
||||
onClose={onClose}
|
||||
closeLabel={t('common.close')}
|
||||
/>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-[14px] pb-[14px] pt-1">
|
||||
{lists.length > 5 && (
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function MRtCorridorSheet({ planner, shell }: MTripSheetsProps) {
|
||||
open={open}
|
||||
onClose={shell.closeSheet}
|
||||
variant="bottom"
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={t('roadtrip.poi.title')}
|
||||
>
|
||||
<div className="flex-none px-[18px] pt-4">
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function MRtDraftSheet({ planner }: MRtDraftSheetProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<MSheet open={stopDraft != null} onClose={discard} variant="bottom" material="opaque" ariaLabel={draft.poi.name}>
|
||||
<MSheet open={stopDraft != null} onClose={discard} variant="bottom" material="glass" ariaLabel={draft.poi.name}>
|
||||
<FormSheetHeader
|
||||
icon={kind?.Icon ?? ParkingSquare}
|
||||
title={draft.poi.name}
|
||||
|
||||
@@ -172,7 +172,7 @@ export default function MRtInfoSheet({ planner, shell }: MTripSheetsProps) {
|
||||
]
|
||||
|
||||
return (
|
||||
<MSheet open={open} onClose={shell.closeSheet} variant="bottom" material="opaque" ariaLabel={t('mobileTrip.rtInfoTitle')}>
|
||||
<MSheet open={open} onClose={shell.closeSheet} variant="bottom" material="glass" ariaLabel={t('mobileTrip.rtInfoTitle')}>
|
||||
<div className="flex-none px-[18px] pt-4">
|
||||
<TileHeader
|
||||
icon={<VehicleIcon size={19} strokeWidth={1.8} />}
|
||||
|
||||
@@ -81,7 +81,7 @@ export default function MRtKindSheet({ planner, shell }: MTripSheetsProps) {
|
||||
open={open && !!stop}
|
||||
onClose={shell.closeSheet}
|
||||
variant="bottom"
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={t('roadtrip.stop.kind')}
|
||||
>
|
||||
{stop && (
|
||||
|
||||
@@ -150,7 +150,7 @@ export default function MRtStaySheet({ planner, shell }: MTripSheetsProps) {
|
||||
open={open && !!stop}
|
||||
onClose={shell.closeSheet}
|
||||
variant="bottom"
|
||||
material="opaque"
|
||||
material="glass"
|
||||
// Named for what it holds: a stop left at a set time has no stay to add.
|
||||
ariaLabel={leave.until ? t('roadtrip.stop.stay') : t('roadtrip.stay.add')}
|
||||
>
|
||||
|
||||
@@ -290,7 +290,7 @@ export default function MRtStopSheet({ planner, shell }: MTripSheetsProps) {
|
||||
const tiles = (navTargets.length > 0 ? 1 : 0) + 1
|
||||
|
||||
return (
|
||||
<MSheet open={open && !!live} onClose={shell.closeSheet} variant="bottom" material="opaque" ariaLabel={stop.name}>
|
||||
<MSheet open={open && !!live} onClose={shell.closeSheet} variant="bottom" material="glass" ariaLabel={stop.name}>
|
||||
{/* ── Header: the disc the chain draws, the name, the way to the editor and the way out ── */}
|
||||
<div className="flex-none px-[18px] pt-4">
|
||||
{/* Centred, not top-aligned: one line of the name is shorter than the 34px disc, so
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function MCostSheet({ tripId, base, people, me, editing, prefill,
|
||||
<MSheet
|
||||
open={open}
|
||||
onClose={requestClose}
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={editing ? t('costs.editExpense') : t('costs.addExpense')}
|
||||
>
|
||||
<FormSheetHeader
|
||||
|
||||
@@ -233,7 +233,7 @@ export default function MPlaceEditSheet({ planner, onOpenExpense }: MPlaceEditSh
|
||||
<MSheet
|
||||
open={showPlaceForm}
|
||||
onClose={handleClose}
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={sheetPlace ? t('places.editPlace') : t('places.addPlace')}
|
||||
discardGuard={showPlaceForm ? { form, files: pendingFiles.length } : undefined}
|
||||
>
|
||||
|
||||
@@ -175,7 +175,7 @@ export default function MReservationSheet({ planner, onOpenExpense }: MReservati
|
||||
<MSheet
|
||||
open={showReservationModal}
|
||||
onClose={handleClose}
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={res ? t('reservations.editTitle') : t('reservations.newTitle')}
|
||||
discardGuard={showReservationModal ? { form, files: pendingFiles.length, travelers: [...travelerIds] } : undefined}
|
||||
>
|
||||
|
||||
@@ -163,7 +163,7 @@ export default function MTransportFormSheet({ planner, onOpenExpense }: MTranspo
|
||||
<MSheet
|
||||
open={showTransportModal}
|
||||
onClose={handleClose}
|
||||
material="opaque"
|
||||
material="glass"
|
||||
ariaLabel={headerTitle}
|
||||
discardGuard={showTransportModal ? { form, waypoints, trainWaypoints, carStops, files: pendingFiles.length, travelers: [...travelerIds] } : undefined}
|
||||
>
|
||||
|
||||
@@ -76,7 +76,8 @@ export function SheetSection({
|
||||
}
|
||||
|
||||
interface TileHeaderProps {
|
||||
icon: ReactNode
|
||||
/** Left out for a plain picker; every other sheet shows its icon tile. */
|
||||
icon?: ReactNode
|
||||
title: ReactNode
|
||||
sub?: ReactNode
|
||||
/** Let a sentence-length subtitle wrap instead of truncating it to one line. */
|
||||
@@ -97,9 +98,11 @@ interface TileHeaderProps {
|
||||
export function TileHeader({ icon, title, sub, subWrap = false, onClose, closeLabel }: TileHeaderProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-10 w-10 flex-none items-center justify-center rounded-[13px] bg-[color:var(--m-ic)]">
|
||||
{icon}
|
||||
</div>
|
||||
{icon && (
|
||||
<div className="flex h-10 w-10 flex-none items-center justify-center rounded-[13px] bg-[color:var(--m-ic)]">
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-[6px] text-[1.0625rem] font-bold leading-tight">{title}</div>
|
||||
{sub && (
|
||||
|
||||
Reference in New Issue
Block a user