mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
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:
@@ -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" />
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user