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"> <div className="flex items-center gap-3">
<button <button
onClick={onBack} 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 <svg
className="h-4 w-4 text-dark-400" className="h-4 w-4 text-dark-400"
@@ -296,14 +297,15 @@ function ChatView({
</div> </div>
</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"> <div className="flex flex-wrap gap-1.5">
{STATUS_VALUES.map((s) => ( {STATUS_VALUES.map((s) => (
<button <button
key={s} key={s}
onClick={() => onStatusChange(s)} onClick={() => onStatusChange(s)}
disabled={selectedTicket.status === s || actionLoading} 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 selectedTicket.status === s
? 'border-accent-500/50 bg-accent-500/20 text-accent-400' ? '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' : 'border-dark-700/50 text-dark-400 hover:border-dark-600 hover:text-dark-200'
@@ -364,7 +366,8 @@ function ChatView({
<button <button
onClick={onReply} onClick={onReply}
disabled={!replyText.trim() || replySending} 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 ? ( {replySending ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" /> <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 { .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; 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 { .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; @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; @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 */ /* Highlighted button for onboarding */
.btn-highlight { .btn-highlight {
@apply relative z-10 animate-spotlight; @apply relative z-10 animate-spotlight;