adapt(admin): mobile touch-target floor on shared .btn + ticket chat

Audit P2: admin pages prioritise density but cabinet also runs in the
Telegram Mini App on phones. Small button targets (~24-32px) work for
mouse precision but fail mobile thumbs. Apply targeted bumps that keep
desktop density unchanged.

src/styles/globals.css
  • .btn — add min-h-[44px] on mobile, sm:min-h-0 on desktop.
    Cascades to every .btn-primary / .btn-secondary / .btn-ghost /
    .btn-danger consumer (76 + 48 + N usages app-wide). One change,
    everywhere.
  • .btn-icon — same logic (min-h/min-w 44px mobile). Drops the
    duplicate definition that appeared further down the file.

components/admin/userDetail/TicketsTab.tsx — mobile-critical chat
  • Back button h-8 w-8 → h-10 w-10 sm:h-8 sm:w-8 (40px / 32px).
    Adds aria-label since it had none.
  • Status pills px-2.5 py-1 → +min-h-[36px] py-1.5 sm:min-h-0
    sm:py-1 (36px / 26px). Mis-tap penalty on mobile is high (it
    flips ticket status).
  • Reply send button — add min-h/min-w 44px mobile + aria-label.

Why not blanket bump py-1.5 → py-2.5 across all 40+ admin pages:
visual density patterns were iteratively designed and a global sweep
would regress them. Per-page review can target specific buttons that
deserve mobile touch floor without breaking density elsewhere.

icon-only buttons not using .btn-icon (raw <button><svg/></button>)
left as-is — they're typically in compact toolbars where visual
density is the point. Per-page review under /impeccable adapt
target=<page> if mobile flow analysis flags one.
This commit is contained in:
c0mrade
2026-05-26 20:53:40 +03:00
parent 8baaa0d760
commit 251c862922
2 changed files with 18 additions and 11 deletions

View File

@@ -262,7 +262,8 @@ function ChatView({
<div className="flex items-center gap-3">
<button
onClick={onBack}
className="flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-dark-800 transition-colors hover:bg-dark-700"
aria-label={t('common.back', 'Back')}
className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-dark-800 transition-colors hover:bg-dark-700 sm:h-8 sm:w-8"
>
<svg
className="h-4 w-4 text-dark-400"
@@ -296,14 +297,15 @@ function ChatView({
</div>
</div>
{/* Status buttons */}
{/* Status buttons — 36px mobile / 26px desktop. Active state matters
and gets mis-tapped on mobile when too small. */}
<div className="flex flex-wrap gap-1.5">
{STATUS_VALUES.map((s) => (
<button
key={s}
onClick={() => onStatusChange(s)}
disabled={selectedTicket.status === s || actionLoading}
className={`rounded-lg border px-2.5 py-1 text-xs transition-all ${
className={`min-h-[36px] rounded-lg border px-2.5 py-1.5 text-xs transition-all sm:min-h-0 sm:py-1 ${
selectedTicket.status === s
? 'border-accent-500/50 bg-accent-500/20 text-accent-400'
: 'border-dark-700/50 text-dark-400 hover:border-dark-600 hover:text-dark-200'
@@ -364,7 +366,8 @@ function ChatView({
<button
onClick={onReply}
disabled={!replyText.trim() || replySending}
className="shrink-0 self-end rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:opacity-50"
aria-label={t('admin.tickets.sendReply', 'Send reply')}
className="min-h-[44px] min-w-[44px] shrink-0 self-end rounded-lg bg-accent-500 px-4 py-2 text-sm text-white transition-colors hover:bg-accent-600 disabled:opacity-50 sm:min-h-0 sm:min-w-0"
>
{replySending ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />

View File

@@ -550,12 +550,21 @@ img.twemoji {
}
}
/* Buttons - Dark */
/* Buttons - Dark
min-h-[44px] on mobile (WCAG 2.5.5 AAA touch target floor), keeping
visual at py-2 / ~36px on desktop where mouse precision rules.
Cascades to every .btn-primary / .btn-secondary / .btn-ghost / .btn-danger. */
.btn {
@apply inline-flex items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium duration-200 ease-smooth focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-dark-900 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50;
@apply inline-flex min-h-[44px] items-center justify-center gap-2 rounded-lg px-4 py-2 text-sm font-medium duration-200 ease-smooth focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-offset-dark-900 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-h-0;
transition-property: color, background-color, border-color, box-shadow, transform, opacity;
}
.btn-icon {
/* Same logic for icon-only buttons. */
@apply min-h-[44px] min-w-[44px] rounded-lg p-2 text-dark-400 duration-200 hover:bg-dark-800 hover:text-dark-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 active:scale-95 sm:min-h-0 sm:min-w-0;
transition-property: color, background-color, transform;
}
.btn-primary {
@apply btn bg-accent-500 text-white shadow-lg shadow-accent-500/25 hover:bg-accent-400 hover:shadow-xl hover:shadow-accent-500/30 focus-visible:ring-accent-500 active:bg-accent-600;
}
@@ -572,11 +581,6 @@ img.twemoji {
@apply btn border border-error-500/30 bg-error-500/10 text-error-400 hover:border-error-500/50 hover:bg-error-500/20 focus-visible:ring-error-500 active:bg-error-500/30;
}
.btn-icon {
@apply rounded-lg p-2 text-dark-400 duration-200 hover:bg-dark-800 hover:text-dark-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 active:scale-95;
transition-property: color, background-color, transform;
}
/* Highlighted button for onboarding */
.btn-highlight {
@apply relative z-10 animate-spotlight;