Action QA findings: fix all critical/warning/suggestion items
Critical fixes: - C-01: DatePicker isMobile now actually used for bottom sheet positioning - C-02: Calendar title always visible (text-sm on mobile, text-lg on sm+) - C-03: Mobile card text-[10px] → text-xs (meets 12px minimum) Warning fixes: - W-01: useMediaQuery SSR-safe (typeof window guard) - W-02: KanbanBoard TouchSensor added (was lost during branch ops) - W-03: Removed duplicate isMobile query, derived from !isDesktop - W-04: Search restored on mobile for Calendar/Reminders/Projects (w-32 sm:w-52) - W-05: SheetClose added to CalendarSidebar mobile Sheet - W-06: Button icon uses min-h/min-w for touch targets instead of h-11 Suggestion fixes: - S-01: Removed deprecated WebkitOverflowScrolling from KanbanBoard - S-02: Added role/tabIndex/onKeyDown to EntityTable mobile card wrappers - S-03: Added overflow-y-auto to mobile event detail panel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f7ec04241b
commit
4d5052d731
@ -17,7 +17,7 @@ import { useSettings } from '@/hooks/useSettings';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Select } from '@/components/ui/select';
|
||||
import { Sheet, SheetContent } from '@/components/ui/sheet';
|
||||
import { Sheet, SheetContent, SheetClose } from '@/components/ui/sheet';
|
||||
import CalendarSidebar from './CalendarSidebar';
|
||||
import EventDetailPanel from './EventDetailPanel';
|
||||
import type { CreateDefaults } from './EventDetailPanel';
|
||||
@ -165,7 +165,6 @@ export default function CalendarPage() {
|
||||
|
||||
// Track desktop breakpoint to prevent dual EventDetailPanel mount
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
const isMobile = useMediaQuery('(max-width: 1023px)');
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||
|
||||
// Continuously resize calendar during panel open/close CSS transition
|
||||
@ -484,9 +483,10 @@ export default function CalendarPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{isMobile && (
|
||||
{!isDesktop && (
|
||||
<Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
|
||||
<SheetContent className="w-72 p-0">
|
||||
<SheetClose onClick={() => setMobileSidebarOpen(false)} />
|
||||
<CalendarSidebar onUseTemplate={(tmpl) => { setMobileSidebarOpen(false); handleUseTemplate(tmpl); }} onSharedVisibilityChange={setVisibleSharedIds} width={288} />
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
@ -540,12 +540,12 @@ export default function CalendarPage() {
|
||||
))}
|
||||
</div>
|
||||
|
||||
<h2 className="text-lg font-semibold font-heading hidden sm:block">{calendarTitle}</h2>
|
||||
<h2 className="text-sm sm:text-lg font-semibold font-heading truncate">{calendarTitle}</h2>
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Event search */}
|
||||
<div className="relative hidden md:block">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search events..."
|
||||
@ -553,7 +553,7 @@ export default function CalendarPage() {
|
||||
onChange={(e) => setEventSearch(e.target.value)}
|
||||
onFocus={() => setSearchFocused(true)}
|
||||
onBlur={() => setTimeout(() => setSearchFocused(false), 200)}
|
||||
className="w-52 h-8 pl-8 text-sm ring-inset"
|
||||
className="w-32 sm:w-52 h-8 pl-8 text-sm ring-inset"
|
||||
/>
|
||||
{searchFocused && searchResults.length > 0 && (
|
||||
<div className="absolute z-50 mt-1 w-72 right-0 rounded-md border bg-popover shadow-lg overflow-hidden">
|
||||
@ -644,7 +644,7 @@ export default function CalendarPage() {
|
||||
onClick={handlePanelClose}
|
||||
>
|
||||
<div
|
||||
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
|
||||
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg overflow-y-auto"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<EventDetailPanel
|
||||
|
||||
@ -360,7 +360,7 @@ export default function LocationsPage() {
|
||||
<div className={`rounded-lg border p-3 transition-colors ${selectedLocationId === location.id ? 'border-accent/40 bg-accent/5' : 'border-border bg-card hover:bg-card-elevated'}`}>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="font-medium text-sm truncate flex-1">{location.name}</span>
|
||||
{location.category && <span className="text-[10px] text-muted-foreground">{location.category}</span>}
|
||||
{location.category && <span className="text-xs text-muted-foreground">{location.category}</span>}
|
||||
</div>
|
||||
{location.address && (
|
||||
<p className="text-xs text-muted-foreground truncate">{location.address}</p>
|
||||
|
||||
@ -748,7 +748,7 @@ export default function PeoplePage() {
|
||||
<div className={`rounded-lg border p-3 transition-colors ${selectedPersonId === person.id ? 'border-accent/40 bg-accent/5' : 'border-border bg-card hover:bg-card-elevated'}`}>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<span className="font-medium text-sm truncate flex-1">{person.name}</span>
|
||||
{person.category && <span className="text-[10px] text-muted-foreground">{person.category}</span>}
|
||||
{person.category && <span className="text-xs text-muted-foreground">{person.category}</span>}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
{person.email && <span className="truncate">{person.email}</span>}
|
||||
|
||||
@ -2,6 +2,7 @@ import {
|
||||
DndContext,
|
||||
closestCorners,
|
||||
PointerSensor,
|
||||
TouchSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
type DragEndEvent,
|
||||
@ -200,7 +201,7 @@ export default function KanbanBoard({
|
||||
collisionDetection={closestCorners}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<div className="flex gap-3 overflow-x-auto pb-2" style={{ WebkitOverflowScrolling: "touch" }}>
|
||||
<div className="flex gap-3 overflow-x-auto pb-2">
|
||||
{tasksByStatus.map(({ column, tasks: colTasks }) => (
|
||||
<KanbanColumn
|
||||
key={column.id}
|
||||
|
||||
@ -105,13 +105,13 @@ export default function ProjectsPage() {
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<div className="relative hidden md:block">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-52 h-8 pl-8 text-sm"
|
||||
className="w-32 sm:w-52 h-8 pl-8 text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -135,13 +135,13 @@ export default function RemindersPage() {
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<div className="relative hidden md:block">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search..."
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-52 h-8 pl-8 text-sm ring-inset"
|
||||
className="w-32 sm:w-52 h-8 pl-8 text-sm ring-inset"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@ -149,7 +149,7 @@ export function EntityTable<T extends { id: number }>({
|
||||
<>
|
||||
<p className="text-[11px] uppercase tracking-wider text-muted-foreground font-medium pt-2">{pinnedLabel}</p>
|
||||
{pinnedRows.map((item) => (
|
||||
<div key={item.id} onClick={() => onRowClick(item.id)} className="cursor-pointer">
|
||||
<div key={item.id} onClick={() => onRowClick(item.id)} className="cursor-pointer" role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onRowClick(item.id); } }}>
|
||||
{mobileCardRender(item)}
|
||||
</div>
|
||||
))}
|
||||
@ -161,7 +161,7 @@ export function EntityTable<T extends { id: number }>({
|
||||
<>
|
||||
<p className="text-[11px] uppercase tracking-wider text-muted-foreground font-medium pt-2">{group.label}</p>
|
||||
{group.rows.map((item) => (
|
||||
<div key={item.id} onClick={() => onRowClick(item.id)} className="cursor-pointer">
|
||||
<div key={item.id} onClick={() => onRowClick(item.id)} className="cursor-pointer" role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onRowClick(item.id); } }}>
|
||||
{mobileCardRender(item)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
@ -18,7 +18,7 @@ const buttonVariants = cva(
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-11 w-11 md:h-10 md:w-10',
|
||||
icon: 'h-10 w-10 min-h-[44px] min-w-[44px] md:min-h-0 md:min-w-0',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@ -326,8 +326,8 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
||||
<div
|
||||
ref={popupRef}
|
||||
onMouseDown={(e) => e.stopPropagation()}
|
||||
style={{ position: 'fixed', top: pos.top, left: pos.left, zIndex: 60 }}
|
||||
className="w-[280px] rounded-lg border border-input bg-card shadow-lg animate-fade-in"
|
||||
style={isMobile ? { position: 'fixed', bottom: 0, left: 0, right: 0, zIndex: 60 } : { position: 'fixed', top: pos.top, left: pos.left, zIndex: 60 }}
|
||||
className={isMobile ? 'w-full rounded-t-lg border border-input bg-card shadow-lg animate-fade-in pb-[env(safe-area-inset-bottom)]' : 'w-[280px] rounded-lg border border-input bg-card shadow-lg animate-fade-in'}
|
||||
>
|
||||
{/* Month/Year nav */}
|
||||
<div className="flex items-center justify-between px-3 pt-3 pb-2">
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
const [matches, setMatches] = useState(() => window.matchMedia(query).matches);
|
||||
const [matches, setMatches] = useState(() =>
|
||||
typeof window !== 'undefined' ? window.matchMedia(query).matches : false
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const mql = window.matchMedia(query);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user