RS6 修复移动响应式布局体系

这个提交包含在:
2026-05-11 12:52:30 +08:00
父节点 5dd4c96bec
当前提交 e399376e25
共修改 16 个文件,包含 841 行新增和 97 行删除
@@ -34,7 +34,7 @@ export function MobileBottomNav() {
return (
<nav
className="pointer-events-auto fixed inset-x-0 bottom-0 z-[70] min-h-[calc(var(--mobile-bottom-nav-height,64px)+env(safe-area-inset-bottom,0px))] transform-gpu border-t border-zinc-200 bg-white/95 px-2 pb-[calc(0.35rem+env(safe-area-inset-bottom,0px))] pt-1.5 shadow-[0_-6px_18px_rgba(15,23,42,0.08)] backdrop-blur md:hidden"
className="mobile-bottom-nav-surface pointer-events-auto fixed inset-x-0 bottom-0 z-[70] transform-gpu border-t border-zinc-200 bg-white/95 shadow-[0_-6px_18px_rgba(15,23,42,0.08)] backdrop-blur md:hidden"
data-testid="mobile-bottom-nav"
>
<div className="relative z-[71] grid grid-cols-4 gap-1">
+1 -1
查看文件
@@ -65,7 +65,7 @@ export function AppShellLayout() {
>
{isMobileRoute ? null : <Topbar />}
<div className={`flex-1 px-4 py-5 ${isMobileRoute ? `min-h-0 overscroll-contain overflow-y-auto px-3 py-3 sm:px-5 sm:py-5 ${showMobileBottomNav ? "pb-[calc(var(--mobile-bottom-nav-height,64px)+env(safe-area-inset-bottom,0px)+1rem)]" : "pb-5 sm:pb-6"}` : "sm:px-7 sm:py-7 lg:px-8"}`}>
<div className={`flex-1 px-4 py-5 ${isMobileRoute ? `min-h-0 overscroll-contain overflow-y-auto px-[var(--mobile-page-x)] py-[var(--mobile-page-y)] ${showMobileBottomNav ? "pb-[calc(var(--mobile-bottom-nav-height,68px)+env(safe-area-inset-bottom,0px)+var(--mobile-page-y)+0.5rem)]" : "pb-5 sm:pb-6"}` : "sm:px-7 sm:py-7 lg:px-8"}`}>
<div className="layout-container max-w-none">
<ErrorBoundary>
<Outlet />
+1 -1
查看文件
@@ -235,7 +235,7 @@ export function MobileCapturePage() {
) : null}
</div>
<div className="grid gap-2 sm:grid-cols-2">
<div className="mobile-two-action-grid">
<Button
className="min-h-12 w-full"
data-testid="mobile-capture-upload"
+11 -11
查看文件
@@ -64,7 +64,7 @@ function LeaveRecordCard({
const canSubmit = item.status === "draft" || item.status === "rejected";
return (
<article className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-leave-record-card">
<article className="mobile-card" data-testid="mobile-leave-record-card">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-zinc-900">{item.leaveType?.name ?? "请假"}</div>
@@ -73,7 +73,7 @@ function LeaveRecordCard({
<span className="badge-neutral shrink-0">{STATUS_LABEL[item.status]}</span>
</div>
<div className="mt-4 grid grid-cols-2 gap-3 text-sm">
<div className="mobile-info-grid mt-4 text-sm">
<div>
<div className="text-xs text-zinc-500">天数</div>
<div className="mt-1 text-lg font-semibold text-zinc-900">{item.requestedDays}</div>
@@ -87,7 +87,7 @@ function LeaveRecordCard({
{item.reason ? <p className="mt-3 line-clamp-2 text-sm text-zinc-500">{item.reason}</p> : null}
{canEdit || canSubmit ? (
<div className="mt-4 grid grid-cols-2 gap-2">
<div className="mobile-two-action-grid mt-4">
{canEdit ? (
<Button variant="secondary" type="button" disabled={isSaving} onClick={() => onEdit(item)}>
编辑
@@ -251,8 +251,8 @@ export function MobileLeavePage() {
<PageScaffold className="space-y-3 pb-4">
<PageHeader title="请假" />
<section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-summary">
<div className="grid grid-cols-3 gap-3 text-sm">
<section className="mobile-card" data-testid="mobile-leave-summary">
<div className="mobile-metric-grid text-sm">
<div>
<div className="text-xs text-zinc-500">可用</div>
<div className="mt-1 text-2xl font-semibold text-zinc-900">{balanceSummary.available.toFixed(1)}</div>
@@ -268,7 +268,7 @@ export function MobileLeavePage() {
</div>
</section>
<section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-form">
<section className="mobile-card" data-testid="mobile-leave-form">
<div className="mb-4 flex items-center justify-between gap-3">
<h3 className="text-base font-semibold text-zinc-900">{editingItem ? "编辑请假" : "新建请假"}</h3>
{editingItem ? (
@@ -291,7 +291,7 @@ export function MobileLeavePage() {
</select>
</label>
<div className="grid grid-cols-2 gap-3">
<div className="mobile-info-grid">
<label className="block space-y-2 text-sm text-zinc-700">
<span>开始日期</span>
<input className="input-std" type="date" value={startDate} onChange={(event) => setStartDate(event.target.value)} />
@@ -302,7 +302,7 @@ export function MobileLeavePage() {
</label>
</div>
<div className="grid grid-cols-2 gap-3">
<div className="mobile-info-grid">
<label className="block space-y-2 text-sm text-zinc-700">
<span>开始时段</span>
<select className="select-std" value={startSession} onChange={(event) => setStartSession(event.target.value as LeaveSession)}>
@@ -370,7 +370,7 @@ export function MobileLeavePage() {
</div>
</section>
<section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-balances">
<section className="mobile-card" data-testid="mobile-leave-balances">
<h3 className="text-base font-semibold text-zinc-900">额度</h3>
<div className="mt-3 space-y-2">
{balancesQuery.isLoading ? <div className="text-sm text-zinc-500">加载中...</div> : null}
@@ -381,7 +381,7 @@ export function MobileLeavePage() {
<div className="font-medium text-zinc-900">{item.leaveType.name}</div>
<span className="badge-success">启用</span>
</div>
<div className="mt-3 grid grid-cols-3 gap-3 text-sm">
<div className="mobile-metric-grid mt-3 text-sm">
<div>
<div className="text-xs text-zinc-500">可用</div>
<div className="font-semibold text-zinc-900">{item.available}</div>
@@ -400,7 +400,7 @@ export function MobileLeavePage() {
</div>
</section>
<section className="rounded-lg border border-zinc-200 bg-white p-3 shadow-sm" data-testid="mobile-leave-records">
<section className="mobile-card" data-testid="mobile-leave-records">
<h3 className="text-base font-semibold text-zinc-900">请假记录</h3>
<Tabs value={statusFilter} onValueChange={(value) => setStatusFilter(value as LeaveStatusFilter)}>
<TabsList className="mt-3 grid h-auto w-full grid-cols-4">
+2 -2
查看文件
@@ -109,7 +109,7 @@ export function MobileMePage() {
</SectionPanel>
<SectionPanel title="更多入口">
<div className="grid grid-cols-2 gap-2" data-testid="mobile-more-links">
<div className="mobile-adaptive-grid" data-testid="mobile-more-links">
<MobileActionLink to="/mobile/notifications" label="通知中心" icon="notifications" />
<MobileActionLink to="/mobile/leave" label="请假" icon="event_available" />
<MobileActionLink to="/mobile/reimbursements" label="报销" icon="request_quote" />
@@ -121,7 +121,7 @@ export function MobileMePage() {
{canAccessAdmin ? (
<SectionPanel title="管理入口">
<div className="grid grid-cols-2 gap-2">
<div className="mobile-adaptive-grid">
<MobileActionLink to="/mobile/me" label="人员管理" icon="group" />
<MobileActionLink to="/mobile/me" label="组织架构" icon="account_tree" />
<MobileActionLink to="/mobile/me" label="系统设置" icon="settings" />
+1 -1
查看文件
@@ -188,7 +188,7 @@ export function MobileNotificationsPage() {
</div>
<div className="mt-3 text-xs text-zinc-500">{formatTime(item.createdAt)}</div>
<div className="mt-3 grid grid-cols-2 gap-2">
<div className="mobile-two-action-grid mt-3">
{targetLink ? (
<Button asChild size="sm" variant="secondary" data-testid="mobile-notification-target-link">
<Link to={targetLink}>查看</Link>
+31 -32
查看文件
@@ -130,25 +130,26 @@ function ActionTile({ action }: { action: MobileAction }) {
to={action.to}
className={
isPrimary
? "relative flex min-h-24 flex-col justify-between rounded-lg border border-primary-100 bg-primary-50 p-3 pr-14 text-primary shadow-sm"
? "mobile-icon-action mobile-icon-action--primary"
: isWarning
? "relative flex min-h-24 flex-col justify-between rounded-lg border border-amber-200 bg-amber-50 p-3 pr-14 text-amber-900"
: "relative flex min-h-24 flex-col justify-between rounded-lg border border-zinc-200 bg-white p-3 pr-14 text-zinc-900 shadow-sm"
? "mobile-icon-action mobile-icon-action--warning"
: "mobile-icon-action"
}
data-testid={`mobile-action-${action.key}`}
title={`${action.label}:${action.meta}`}
aria-label={`${action.label},${action.meta}`}
>
<div className="min-w-0">
<span className="block min-w-0 whitespace-nowrap text-base font-semibold leading-6" data-testid={`mobile-action-${action.key}-label`}>
{action.label}
</span>
</div>
<span
className="material-symbols-outlined absolute right-3 top-3 flex size-8 items-center justify-center rounded-md bg-white/70 text-[21px] leading-none shadow-sm"
className="material-symbols-outlined mobile-icon-action__icon"
data-testid={`mobile-action-${action.key}-icon`}
aria-hidden="true"
>
{action.icon}
</span>
<div className={isPrimary ? "truncate text-xs text-primary/70" : "truncate text-xs text-zinc-500"}>{action.meta}</div>
<span className="mobile-icon-action__label" data-testid={`mobile-action-${action.key}-label`}>
{action.label}
</span>
<span className="sr-only">{action.meta}</span>
</Link>
);
}
@@ -217,16 +218,14 @@ export function MobilePortalPage() {
const unreadNotificationCount = notifications.filter((item) => !item.readAt).length;
const actions: MobileAction[] = [
canAccessApprovals
? {
key: "approvals",
label: "审批",
meta: pendingApprovalCount > 0 ? `${pendingApprovalCount} 条待处理` : "暂无待办",
icon: "fact_check",
to: "/mobile/approvals",
tone: pendingApprovalCount > 0 ? "warning" : "primary",
}
: null,
{
key: "approvals",
label: "审批",
meta: pendingApprovalCount > 0 ? `${pendingApprovalCount} 条待处理` : "暂无待办",
icon: "fact_check",
to: "/mobile/approvals",
tone: pendingApprovalCount > 0 ? "warning" : "primary",
},
canAccessCapture
? {
key: "capture",
@@ -246,6 +245,13 @@ export function MobilePortalPage() {
to: "/mobile/reimbursements",
}
: null,
{
key: "me",
label: "我的",
meta: unreadNotificationCount > 0 ? `${unreadNotificationCount} 条新通知` : "资料和通知",
icon: "person",
to: "/mobile/me",
},
canAccessLeave
? {
key: "leave",
@@ -255,25 +261,18 @@ export function MobilePortalPage() {
to: "/mobile/leave",
}
: null,
{
key: "me",
label: "我的",
meta: unreadNotificationCount > 0 ? `${unreadNotificationCount} 条新通知` : "资料和通知",
icon: "person",
to: "/mobile/me",
},
].filter((item): item is MobileAction => Boolean(item)).slice(0, 4);
return (
<PageScaffold className="space-y-4">
<PageHeader title="今天" />
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-today-focus">
<section className="mobile-card" data-testid="mobile-today-focus">
<div className="mb-3 flex items-center justify-between gap-3">
<div className="text-base font-semibold text-zinc-900">待处理</div>
{pendingApprovalCount > 0 ? <span className="badge-warning">{pendingApprovalCount} 待批</span> : null}
</div>
<div className="grid grid-cols-2 gap-2">
<div className="mobile-icon-grid" data-testid="mobile-action-grid">
{actions.map((action) => (
<ActionTile key={action.key} action={action} />
))}
@@ -281,7 +280,7 @@ export function MobilePortalPage() {
</section>
{!attendanceFeatureDisabled ? (
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-attendance-card">
<section className="mobile-card" data-testid="mobile-attendance-card">
<div className="mb-3 flex items-center justify-between gap-3">
<div className="text-base font-semibold text-zinc-900">打卡</div>
<span className="badge-neutral">{attendanceToday?.workDate ?? "今日"}</span>
@@ -326,7 +325,7 @@ export function MobilePortalPage() {
) : null}
{canAccessApprovals ? (
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-pending-tasks">
<section className="mobile-card" data-testid="mobile-pending-tasks">
<div className="mb-3 flex items-center justify-between gap-3">
<div className="text-base font-semibold text-zinc-900">待审批</div>
<Button asChild size="sm" type="button" variant="secondary">
@@ -359,7 +358,7 @@ export function MobilePortalPage() {
</section>
) : null}
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-notifications">
<section className="mobile-card" data-testid="mobile-notifications">
<div className="mb-3 flex items-center justify-between gap-3">
<div className="text-base font-semibold text-zinc-900">通知</div>
<Button asChild size="sm" type="button" variant="secondary">
@@ -761,7 +761,7 @@ export function MobileReimbursementDetailPage() {
"space-y-4",
showStickyActions ? "mobile-fixed-action-page" : "",
showStickyActions
? "pb-[calc(var(--mobile-sticky-action-height,76px)+env(safe-area-inset-bottom,0px)+1.25rem)]"
? "pb-[calc(var(--mobile-sticky-action-height,78px)+env(safe-area-inset-bottom,0px)+1.25rem)]"
: "pb-6",
)}
data-testid="mobile-reimbursement-detail"
@@ -779,10 +779,10 @@ export function MobileReimbursementDetailPage() {
</span>
</header>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm">
<section className="mobile-card">
<div className="text-xs text-zinc-500">合计金额</div>
<div className="mt-1 text-3xl font-semibold text-zinc-900">{formatCurrency(detail.totalAmount)}</div>
<div className="mt-4 grid grid-cols-3 gap-2 text-sm">
<div className="mobile-metric-grid mt-4 text-sm">
<div className="rounded-lg bg-zinc-50 px-3 py-2">
<div className="text-xs text-zinc-500">明细</div>
<div className="mt-1 font-semibold text-zinc-900">{detail.items.length}</div>
@@ -799,7 +799,7 @@ export function MobileReimbursementDetailPage() {
{detail.description ? <p className="mt-3 text-sm leading-6 text-zinc-600">{detail.description}</p> : null}
</section>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-reimbursement-items">
<section className="mobile-card" data-testid="mobile-reimbursement-items">
<div className="flex items-center justify-between gap-3">
<h2 className="text-base font-semibold text-zinc-900">报销明细</h2>
{canEdit ? (
@@ -815,7 +815,7 @@ export function MobileReimbursementDetailPage() {
{itemForm ? (
<div className="mt-3 space-y-3 rounded-lg border border-primary-100 bg-primary-50/40 p-3">
<div className="grid grid-cols-2 gap-3">
<div className="mobile-info-grid">
<label className="col-span-2 block space-y-1 text-sm text-zinc-700">
<span>费用类型</span>
<select
@@ -861,7 +861,7 @@ export function MobileReimbursementDetailPage() {
/>
</label>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="mobile-two-action-grid">
<Button
type="button"
disabled={!itemForm.expenseTypeId || !itemForm.expenseDate || !itemForm.amount.trim() || itemSaveMutation.isPending}
@@ -889,7 +889,7 @@ export function MobileReimbursementDetailPage() {
</div>
{item.description ? <p className="mt-2 text-sm leading-5 text-zinc-600">{item.description}</p> : null}
{canEdit ? (
<div className="mt-3 grid grid-cols-2 gap-2">
<div className="mobile-two-action-grid mt-3">
<Button size="sm" variant="secondary" type="button" onClick={() => startEditItem(item)}>
编辑
</Button>
@@ -914,7 +914,7 @@ export function MobileReimbursementDetailPage() {
</div>
</section>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid={TEST_IDS.reimbursement.invoices}>
<section className="mobile-card" data-testid={TEST_IDS.reimbursement.invoices}>
<div className="flex items-center justify-between gap-3">
<h2 className="text-base font-semibold text-zinc-900">发票</h2>
{canOpenInvoiceDialog ? (
@@ -971,7 +971,7 @@ export function MobileReimbursementDetailPage() {
)}
</section>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-reimbursement-attachments">
<section className="mobile-card" data-testid="mobile-reimbursement-attachments">
<div className="flex items-center justify-between gap-3">
<div>
<h2 className="text-base font-semibold text-zinc-900">附件材料</h2>
@@ -1031,7 +1031,7 @@ export function MobileReimbursementDetailPage() {
{Math.ceil(attachment.fileSize / 1024)} KB · {formatDateTime(attachment.createdAt)}
</div>
</div>
<div className="mt-3 grid grid-cols-2 gap-2">
<div className="mobile-two-action-grid mt-3">
<Button
size="sm"
variant="secondary"
@@ -1064,7 +1064,7 @@ export function MobileReimbursementDetailPage() {
</section>
{showSecondaryActions ? (
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-reimbursement-actions">
<section className="mobile-card" data-testid="mobile-reimbursement-actions">
<h2 className="text-base font-semibold text-zinc-900">操作</h2>
{actionError ? (
<div className="mt-3 whitespace-pre-wrap rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
@@ -1090,10 +1090,10 @@ export function MobileReimbursementDetailPage() {
{showStickyActions ? (
<div
className="fixed inset-x-0 bottom-0 z-50 border-t border-zinc-200 bg-white/95 px-4 pb-[max(0.75rem,env(safe-area-inset-bottom))] pt-3 shadow-[0_-10px_24px_rgba(15,23,42,0.10)] backdrop-blur"
className="mobile-sticky-action-surface fixed inset-x-0 bottom-0 z-50 border-t border-zinc-200 bg-white/95 shadow-[0_-10px_24px_rgba(15,23,42,0.10)] backdrop-blur"
data-testid="mobile-reimbursement-sticky-actions"
>
<div className={cn("grid gap-2", canOpenInvoiceDialog && showSubmitAction ? "grid-cols-2" : "grid-cols-1")}>
<div className={cn(canOpenInvoiceDialog && showSubmitAction ? "mobile-two-action-grid" : "grid grid-cols-1 gap-2")}>
{canOpenInvoiceDialog ? (
<Button
className="min-h-11 min-w-0 px-2 text-sm whitespace-nowrap"
@@ -1132,7 +1132,7 @@ export function MobileReimbursementDetailPage() {
<DialogDescription>可一次上传多张,识别完成后会自动添加。</DialogDescription>
</DialogHeader>
<div className="mt-4 grid grid-cols-2 gap-2 rounded-lg bg-zinc-100 p-1">
<div className="mobile-two-action-grid mt-4 rounded-lg bg-zinc-100 p-1">
<button
type="button"
className={cn("rounded-md px-3 py-2 text-sm font-medium", invoicePanel === "upload" ? "bg-white text-zinc-900 shadow-sm" : "text-zinc-500")}
@@ -1348,7 +1348,7 @@ export function MobileReimbursementDetailPage() {
<div className="whitespace-pre-wrap rounded-lg border border-zinc-200 bg-zinc-50 p-3 text-sm text-zinc-700">
{buildPrecheckDialogMessage(submitConfirmPrecheck)}
</div>
<div className="grid grid-cols-2 gap-2">
<div className="mobile-two-action-grid">
<Button type="button" variant="secondary" onClick={() => setSubmitConfirmPrecheck(null)}>
取消
</Button>
@@ -65,7 +65,7 @@ function ReimbursementCard({
const displayCode = resolveReimbursementDisplayCode(item);
return (
<article className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-reimbursement-card">
<article className="mobile-card" data-testid="mobile-reimbursement-card">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<div className="truncate text-sm font-semibold text-zinc-900">{displayCode}</div>
@@ -74,7 +74,7 @@ function ReimbursementCard({
<span className="badge-neutral shrink-0">{STATUS_LABEL[item.status]}</span>
</div>
<div className="mt-4 grid grid-cols-2 gap-3 text-sm">
<div className="mobile-info-grid mt-4 text-sm">
<div>
<div className="text-xs text-zinc-500">金额</div>
<div className="mt-1 text-lg font-semibold text-zinc-900">{formatCurrency(item.totalAmount)}</div>
@@ -87,7 +87,7 @@ function ReimbursementCard({
{item.description ? <p className="mt-3 line-clamp-2 text-sm text-zinc-500">{item.description}</p> : null}
<div className={canDeleteDraft ? "mt-4 grid grid-cols-2 gap-2" : "mt-4"}>
<div className={canDeleteDraft ? "mobile-two-action-grid mt-4" : "mt-4"}>
<Button asChild className="w-full" type="button">
<Link to={`/mobile/reimbursements/${item.id}`}>{actionLabel}</Link>
</Button>
@@ -174,8 +174,8 @@ export function MobileReimbursementsPage() {
}
/>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm">
<div className="grid grid-cols-2 gap-3">
<section className="mobile-card">
<div className="mobile-info-grid">
<div>
<div className="text-xs text-zinc-500">当前列表</div>
<div className="mt-1 text-2xl font-semibold text-zinc-900">{total}</div>
@@ -187,7 +187,7 @@ export function MobileReimbursementsPage() {
</div>
</section>
<section className="rounded-lg border border-zinc-200 bg-white p-4 shadow-sm" data-testid="mobile-reimbursements-list">
<section className="mobile-card" data-testid="mobile-reimbursements-list">
<Tabs value={status} onValueChange={(value) => setStatus(value as ReimbursementStatus)}>
<TabsList className="grid h-auto w-full grid-cols-4">
{STATUS_TABS.map((item) => (
+137 -2
查看文件
@@ -8,8 +8,13 @@
:root {
--mobile-viewport-height: 100vh;
--mobile-bottom-nav-height: 64px;
--mobile-sticky-action-height: 76px;
--mobile-page-x: clamp(10px, 3vw, 20px);
--mobile-page-y: clamp(10px, 2.8vw, 20px);
--mobile-card-gap: clamp(8px, 2.5vw, 14px);
--mobile-card-padding: clamp(12px, 3.4vw, 16px);
--mobile-bottom-nav-height: 68px;
--mobile-sticky-action-height: 78px;
--mobile-icon-action-min-height: 72px;
}
.page-scaffold.mobile-fixed-action-page {
@@ -22,3 +27,133 @@
--mobile-viewport-height: 100dvh;
}
}
.mobile-card {
border: 1px solid rgb(228 228 231);
border-radius: 12px;
background: rgb(255 255 255);
box-shadow: 0 1px 2px rgb(15 23 42 / 0.06);
padding: var(--mobile-card-padding);
}
.mobile-icon-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: var(--mobile-card-gap);
min-width: 0;
}
.mobile-icon-action {
min-width: 0;
min-height: var(--mobile-icon-action-min-height);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
border: 1px solid rgb(228 228 231);
border-radius: 14px;
background: rgb(255 255 255);
color: rgb(24 24 27);
padding: 8px 4px;
text-align: center;
box-shadow: 0 1px 2px rgb(15 23 42 / 0.05);
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.mobile-icon-action--primary {
border-color: rgb(191 219 254);
background: rgb(239 246 255);
color: rgb(0 57 138);
}
.mobile-icon-action--warning {
border-color: rgb(253 230 138);
background: rgb(255 251 235);
color: rgb(146 64 14);
}
.mobile-icon-action__icon {
width: 36px;
height: 36px;
flex: none;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: rgb(255 255 255 / 0.86);
font-size: 22px;
line-height: 1;
box-shadow: 0 1px 2px rgb(15 23 42 / 0.07);
}
.mobile-icon-action__label {
display: block;
max-width: 100%;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
font-weight: 650;
line-height: 18px;
}
.mobile-adaptive-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 9rem), 1fr));
gap: var(--mobile-card-gap);
min-width: 0;
}
.mobile-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr));
gap: var(--mobile-card-gap);
min-width: 0;
}
.mobile-metric-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 5.75rem), 1fr));
gap: var(--mobile-card-gap);
min-width: 0;
}
.mobile-two-action-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 0.5rem;
min-width: 0;
}
.mobile-bottom-nav-surface {
min-height: calc(var(--mobile-bottom-nav-height) + env(safe-area-inset-bottom, 0px));
padding: 0.375rem var(--mobile-page-x) calc(0.35rem + env(safe-area-inset-bottom, 0px));
}
.mobile-sticky-action-surface {
padding: 0.75rem var(--mobile-page-x) max(0.75rem, env(safe-area-inset-bottom, 0px));
}
@media (max-width: 359px) {
:root {
--mobile-page-x: 8px;
--mobile-card-gap: 6px;
--mobile-card-padding: 10px;
--mobile-icon-action-min-height: 68px;
}
.mobile-icon-action__icon {
width: 32px;
height: 32px;
border-radius: 10px;
font-size: 20px;
}
.mobile-icon-action__label {
font-size: 12px;
line-height: 17px;
}
}