feat(mobile): mobile-responsive UI — bottom tab bar, bottom-sheet dialogs, PWA

All changes are gated behind max-md:/md:hidden so the >=768px desktop layout is untouched (verified with lint + build + live checks at 375px).

- globals.css: one mobile block — 16px inputs (no iOS auto-zoom), units sidebar stacks above content, wizard item rows become 2-col (text fields full-width, delete button full-width), dd-panels / flow diagram / conv-form stack, larger touch targets
- ui/dialog.tsx: dialogs become full-width bottom sheets on mobile (all modals inherit it); desktop keeps the centered dialog
- Header: sticky on mobile only + PageLayout bottom clearance
- New MobileTabBar: role-aware bottom tabs (office admin/manager, inventory PM, inventory admin); hidden on /wizard
- Wizard step 4: TouchSensor (150ms delay) so touch drag works without stealing scroll
- JalaliDatePicker: dropdown capped to viewport; bigger day cells and time inputs on mobile
- PWA: manifest.webmanifest + 192/512 icons (scripts/gen-pwa-icons.ps1) + theme-color/manifest links in index.html
- New docs/mobile-responsive.md documenting the full change set
This commit is contained in:
2026-09-03 19:26:40 +03:30
parent 7f58593f04
commit 9e2089e853
17 changed files with 427 additions and 18 deletions
+58
View File
@@ -0,0 +1,58 @@
# Generate PWA icons (192px / 512px) — indigo rounded square + white clipboard glyph
Add-Type -AssemblyName System.Drawing
Add-Type -AssemblyName System.Drawing.Drawing2D
function Make-RoundedRect([float]$x, [float]$y, [float]$w, [float]$h, [float]$r) {
$p = New-Object System.Drawing.Drawing2D.GraphicsPath
$d = 2 * $r
$p.AddArc($x, $y, $d, $d, 180, 90)
$p.AddArc($x + $w - $d, $y, $d, $d, 270, 90)
$p.AddArc($x + $w - $d, $y + $h - $d, $d, $d, 0, 90)
$p.AddArc($x, $y + $h - $d, $d, $d, 90, 90)
$p.CloseFigure()
return $p
}
$outDir = "public/icons"
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
foreach ($s in @(192, 512)) {
$bmp = New-Object System.Drawing.Bitmap($s, $s)
$g = [System.Drawing.Graphics]::FromImage($bmp)
$g.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::AntiAlias
$g.Clear([System.Drawing.Color]::Transparent)
$m = [float]($s * 0.055)
$bg = Make-RoundedRect $m $m ([float]($s - 2 * $m)) ([float]($s - 2 * $m)) ([float]($s * 0.20))
$indigo = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(79, 70, 229))
$g.FillPath($indigo, $bg)
# clipboard board (white)
$bw = [float]($s * 0.56); $bh = [float]($s * 0.62)
$bx = [float](($s - $bw) / 2); $by = [float]($s * 0.31)
$board = Make-RoundedRect $bx $by $bw $bh ([float]($s * 0.05))
$white = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::White)
$g.FillPath($white, $board)
# clip on top
$cw = [float]($s * 0.15); $ch = [float]($s * 0.11)
$cx = [float](($s - $cw) / 2); $cy = [float]($by - $s * 0.085)
$clip = Make-RoundedRect $cx $cy $cw $ch ([float]($s * 0.03))
$g.FillPath($white, $clip)
# ring holes (indigo)
$g.FillEllipse($indigo, [float]($cx + $cw * 0.16), [float]($cy + $ch * 0.30), [float]($s * 0.035), [float]($s * 0.035))
$g.FillEllipse($indigo, [float]($cx + $cw * 0.62), [float]($cy + $ch * 0.30), [float]($s * 0.035), [float]($s * 0.035))
# lines on the board
$slate = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(148, 163, 184))
$lh = [float]($s * 0.032)
$g.FillRectangle($slate, [float]($bx + $s * 0.09), [float]($by + $s * 0.15), [float]($bw - $s * 0.18), $lh)
$g.FillRectangle($slate, [float]($bx + $s * 0.09), [float]($by + $s * 0.29), [float]($bw - $s * 0.30), $lh)
$g.FillRectangle($slate, [float]($bx + $s * 0.09), [float]($by + $s * 0.43), [float]($bw - $s * 0.24), $lh)
$path = Join-Path $outDir "icon-$s.png"
$bmp.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
$g.Dispose(); $bmp.Dispose()
Write-Output "saved $path"
}