Compare commits
No commits in common. "4e9194495662d4dec611c64ccf1694e17a9e1305" and "e51b09f9c537ff4203409629bbc84f65198a303d" have entirely different histories.
4e91944956
...
e51b09f9c5
@ -1,5 +1,5 @@
|
||||
import { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
||||
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
@ -20,7 +20,6 @@ import { Select } from '@/components/ui/select';
|
||||
import { Sheet, SheetContent, SheetClose } from '@/components/ui/sheet';
|
||||
import CalendarSidebar from './CalendarSidebar';
|
||||
import EventDetailPanel from './EventDetailPanel';
|
||||
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
|
||||
import type { CreateDefaults } from './EventDetailPanel';
|
||||
|
||||
type CalendarView = 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay';
|
||||
@ -165,7 +164,7 @@ export default function CalendarPage() {
|
||||
const panelOpen = panelMode !== 'closed';
|
||||
|
||||
// Track desktop breakpoint to prevent dual EventDetailPanel mount
|
||||
const isDesktop = useMediaQuery(DESKTOP);
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
|
||||
|
||||
// Continuously resize calendar during panel open/close CSS transition
|
||||
@ -640,18 +639,26 @@ export default function CalendarPage() {
|
||||
|
||||
{/* Mobile detail panel overlay */}
|
||||
{panelOpen && !isDesktop && (
|
||||
<MobileDetailOverlay open onClose={handlePanelClose} className="sm:max-w-[400px]">
|
||||
<EventDetailPanel
|
||||
event={panelMode === 'view' ? selectedEvent : null}
|
||||
isCreating={panelMode === 'create'}
|
||||
createDefaults={createDefaults}
|
||||
onClose={handlePanelClose}
|
||||
onSaved={handlePanelClose}
|
||||
locationName={selectedEvent?.location_id ? locationMap.get(selectedEvent.location_id) : undefined}
|
||||
myPermission={selectedEventPermission}
|
||||
isSharedEvent={selectedEventIsShared}
|
||||
/>
|
||||
</MobileDetailOverlay>
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
|
||||
onClick={handlePanelClose}
|
||||
>
|
||||
<div
|
||||
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
|
||||
event={panelMode === 'view' ? selectedEvent : null}
|
||||
isCreating={panelMode === 'create'}
|
||||
createDefaults={createDefaults}
|
||||
onClose={handlePanelClose}
|
||||
onSaved={handlePanelClose}
|
||||
locationName={selectedEvent?.location_id ? locationMap.get(selectedEvent.location_id) : undefined}
|
||||
myPermission={selectedEventPermission}
|
||||
isSharedEvent={selectedEventIsShared}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { Plus, MapPin, Phone, Mail, Tag, AlignLeft } from 'lucide-react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
@ -17,11 +17,10 @@ import {
|
||||
import { useTableVisibility } from '@/hooks/useTableVisibility';
|
||||
import { useCategoryOrder } from '@/hooks/useCategoryOrder';
|
||||
import LocationForm from './LocationForm';
|
||||
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
|
||||
|
||||
export default function LocationsPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const isDesktop = useMediaQuery(DESKTOP);
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
|
||||
const [selectedLocationId, setSelectedLocationId] = useState<number | null>(null);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
@ -388,9 +387,17 @@ export default function LocationsPage() {
|
||||
|
||||
{/* Mobile detail panel overlay */}
|
||||
{panelOpen && selectedLocation && !isDesktop && (
|
||||
<MobileDetailOverlay open={true} onClose={() => setSelectedLocationId(null)}>
|
||||
{renderPanel()}
|
||||
</MobileDetailOverlay>
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
|
||||
onClick={() => setSelectedLocationId(null)}
|
||||
>
|
||||
<div
|
||||
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{renderPanel()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showForm && (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useRef, useEffect } from 'react';
|
||||
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { Plus, Users, Star, Cake, Phone, Mail, MapPin, Tag, Building2, Briefcase, AlignLeft, Ghost, ChevronDown, Unlink, Link2, User2 } from 'lucide-react';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
@ -27,7 +27,6 @@ import PersonForm from './PersonForm';
|
||||
import ConnectionSearch from '@/components/connections/ConnectionSearch';
|
||||
import ConnectionRequestCard from '@/components/connections/ConnectionRequestCard';
|
||||
import { useConnections } from '@/hooks/useConnections';
|
||||
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// StatCounter — inline helper
|
||||
@ -216,7 +215,7 @@ const panelFields: PanelField[] = [
|
||||
export default function PeoplePage() {
|
||||
const queryClient = useQueryClient();
|
||||
const tableContainerRef = useRef<HTMLDivElement>(null);
|
||||
const isDesktop = useMediaQuery(DESKTOP);
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
|
||||
const [selectedPersonId, setSelectedPersonId] = useState<number | null>(null);
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
@ -777,9 +776,17 @@ export default function PeoplePage() {
|
||||
|
||||
{/* Mobile detail panel overlay */}
|
||||
{panelOpen && selectedPerson && !isDesktop && (
|
||||
<MobileDetailOverlay open={true} onClose={() => setSelectedPersonId(null)}>
|
||||
{renderPanel()}
|
||||
</MobileDetailOverlay>
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
|
||||
onClick={() => setSelectedPersonId(null)}
|
||||
>
|
||||
<div
|
||||
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{renderPanel()}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showForm && (
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import {
|
||||
DndContext,
|
||||
closestCorners,
|
||||
PointerSensor,
|
||||
PointerSensor,
|
||||
TouchSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
@ -153,8 +153,8 @@ export default function KanbanBoard({
|
||||
onBackToAllTasks,
|
||||
}: KanbanBoardProps) {
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } })
,
|
||||
useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 8 } })
|
||||
useSensor(PointerSensor, { activationConstraint: { distance: 5 } })
,
|
||||
useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 5 } })
|
||||
);
|
||||
|
||||
// Subtask view is driven by kanbanParentTask (decoupled from selected task)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useCallback } from 'react';
|
||||
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { toast } from 'sonner';
|
||||
@ -39,7 +39,6 @@ import KanbanBoard from './KanbanBoard';
|
||||
import TaskForm from './TaskForm';
|
||||
import ProjectForm from './ProjectForm';
|
||||
import { statusColors, statusLabels } from './constants';
|
||||
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
|
||||
|
||||
type SortMode = 'manual' | 'priority' | 'due_date';
|
||||
type ViewMode = 'list' | 'kanban';
|
||||
@ -259,7 +258,7 @@ export default function ProjectDetail() {
|
||||
}
|
||||
}, [topLevelTasks, sortMode, sortSubtasks]);
|
||||
|
||||
const isDesktop = useMediaQuery(DESKTOP);
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
|
||||
const selectedTask = useMemo(() => {
|
||||
if (!selectedTaskId) return null;
|
||||
@ -654,28 +653,30 @@ export default function ProjectDetail() {
|
||||
|
||||
{/* Mobile: show detail panel as overlay when task selected on small screens */}
|
||||
{selectedTaskId && selectedTask && !isDesktop && (
|
||||
<MobileDetailOverlay open={true} onClose={() => setSelectedTaskId(null)}>
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
|
||||
<span className="text-sm font-medium text-muted-foreground">Task Details</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setSelectedTaskId(null)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
<div className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm">
|
||||
<div className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg">
|
||||
<div className="flex items-center justify-between px-4 py-3 border-b border-border">
|
||||
<span className="text-sm font-medium text-muted-foreground">Task Details</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => setSelectedTaskId(null)}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</div>
|
||||
<div className="h-[calc(100%-49px)]">
|
||||
<TaskDetailPanel
|
||||
task={selectedTask}
|
||||
projectId={parseInt(id!)}
|
||||
onDelete={handleDeleteTask}
|
||||
onAddSubtask={(parentId) => openTaskForm(null, parentId)}
|
||||
onClose={() => setSelectedTaskId(null)}
|
||||
onSelectTask={setSelectedTaskId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-[calc(100%-49px)]">
|
||||
<TaskDetailPanel
|
||||
task={selectedTask}
|
||||
projectId={parseInt(id!)}
|
||||
onDelete={handleDeleteTask}
|
||||
onAddSubtask={(parentId) => openTaskForm(null, parentId)}
|
||||
onClose={() => setSelectedTaskId(null)}
|
||||
onSelectTask={setSelectedTaskId}
|
||||
/>
|
||||
</div>
|
||||
</MobileDetailOverlay>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showTaskForm && (
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Plus, Bell, BellOff, AlertCircle, Search } from 'lucide-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@ -13,7 +13,6 @@ import { Card, CardContent } from '@/components/ui/card';
|
||||
import { ListSkeleton } from '@/components/ui/skeleton';
|
||||
import ReminderList from './ReminderList';
|
||||
import ReminderDetailPanel from './ReminderDetailPanel';
|
||||
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
|
||||
|
||||
const statusFilters = [
|
||||
{ value: 'active', label: 'Active' },
|
||||
@ -26,7 +25,7 @@ type StatusFilter = (typeof statusFilters)[number]['value'];
|
||||
export default function RemindersPage() {
|
||||
const location = useLocation();
|
||||
|
||||
const isDesktop = useMediaQuery(DESKTOP);
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
|
||||
// Panel state
|
||||
const [selectedReminderId, setSelectedReminderId] = useState<number | null>(null);
|
||||
@ -236,14 +235,22 @@ export default function RemindersPage() {
|
||||
|
||||
{/* Mobile detail panel overlay */}
|
||||
{panelOpen && !isDesktop && (
|
||||
<MobileDetailOverlay open={true} onClose={handlePanelClose}>
|
||||
<ReminderDetailPanel
|
||||
reminder={panelMode === 'view' ? selectedReminder : null}
|
||||
isCreating={panelMode === 'create'}
|
||||
onClose={handlePanelClose}
|
||||
onSaved={panelMode === 'create' ? handlePanelClose : undefined}
|
||||
/>
|
||||
</MobileDetailOverlay>
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
|
||||
onClick={handlePanelClose}
|
||||
>
|
||||
<div
|
||||
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<ReminderDetailPanel
|
||||
reminder={panelMode === 'view' ? selectedReminder : null}
|
||||
isCreating={panelMode === 'create'}
|
||||
onClose={handlePanelClose}
|
||||
onSaved={panelMode === 'create' ? handlePanelClose : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown, ChevronsUpDown } from 'lucide-react';
|
||||
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
|
||||
import type { VisibilityMode } from '@/hooks/useTableVisibility';
|
||||
import { useMediaQuery, MOBILE } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
|
||||
export interface ColumnDef<T> {
|
||||
key: string;
|
||||
@ -134,30 +134,11 @@ export function EntityTable<T extends { id: number }>({
|
||||
const visibleColumns = columns.filter((col) => isVisible(col.visibilityLevel, visibilityMode));
|
||||
const colCount = visibleColumns.length;
|
||||
const showPinnedSection = showPinned && pinnedRows.length > 0;
|
||||
const isMobile = useMediaQuery(MOBILE);
|
||||
|
||||
const sortableColumns = columns.filter((col) => col.sortable);
|
||||
const isMobile = useMediaQuery('(max-width: 767px)');
|
||||
|
||||
if (isMobile && mobileCardRender) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{sortableColumns.length > 0 && !loading && (
|
||||
<div className="flex items-center gap-2 justify-end">
|
||||
<ChevronsUpDown className="h-3.5 w-3.5 text-muted-foreground" />
|
||||
<select
|
||||
value={sortKey}
|
||||
onChange={(e) => onSort(e.target.value)}
|
||||
aria-label="Sort by"
|
||||
className="h-7 rounded-md border border-border bg-card px-2 text-xs text-foreground"
|
||||
>
|
||||
{sortableColumns.map((col) => (
|
||||
<option key={col.key} value={col.key}>
|
||||
{col.label} {sortKey === col.key ? (sortDir === 'asc' ? '↑' : '↓') : ''}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
{loading ? (
|
||||
Array.from({ length: 6 }).map((_, i) => (
|
||||
<div key={i} className="animate-pulse rounded-lg bg-card border border-border p-4 h-20" />
|
||||
|
||||
@ -1,67 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface MobileDetailOverlayProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Full-screen overlay for mobile detail panels.
|
||||
* - Backdrop click closes the overlay
|
||||
* - Escape key closes the overlay
|
||||
* - Body scroll is locked while open
|
||||
*/
|
||||
export default function MobileDetailOverlay({
|
||||
open,
|
||||
onClose,
|
||||
children,
|
||||
className,
|
||||
}: MobileDetailOverlayProps) {
|
||||
// Stable ref to avoid re-registering listener on every render
|
||||
const onCloseRef = useRef(onClose);
|
||||
onCloseRef.current = onClose;
|
||||
|
||||
// Escape key handler
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const handler = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') onCloseRef.current();
|
||||
};
|
||||
document.addEventListener('keydown', handler);
|
||||
return () => document.removeEventListener('keydown', handler);
|
||||
}, [open]);
|
||||
|
||||
// Body scroll lock
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const previous = document.body.style.overflow;
|
||||
document.body.style.overflow = 'hidden';
|
||||
return () => {
|
||||
document.body.style.overflow = previous;
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm animate-fade-in"
|
||||
onClick={onClose}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 top-0 h-full w-full sm:max-w-md bg-card border-l border-border shadow-xl overflow-y-auto',
|
||||
className,
|
||||
)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState, useMemo, useEffect } from 'react';
|
||||
import { useMediaQuery, DESKTOP } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Plus, CheckSquare, CheckCircle2, AlertCircle } from 'lucide-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
@ -11,7 +11,6 @@ import { Select } from '@/components/ui/select';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { ListSkeleton } from '@/components/ui/skeleton';
|
||||
import { CategoryFilterBar } from '@/components/shared';
|
||||
import MobileDetailOverlay from '@/components/shared/MobileDetailOverlay';
|
||||
import { useCategoryOrder } from '@/hooks/useCategoryOrder';
|
||||
import TodoList from './TodoList';
|
||||
import TodoDetailPanel from './TodoDetailPanel';
|
||||
@ -27,7 +26,7 @@ const priorityFilters = [
|
||||
export default function TodosPage() {
|
||||
const location = useLocation();
|
||||
|
||||
const isDesktop = useMediaQuery(DESKTOP);
|
||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
||||
|
||||
// Panel state
|
||||
const [selectedTodoId, setSelectedTodoId] = useState<number | null>(null);
|
||||
@ -271,14 +270,22 @@ export default function TodosPage() {
|
||||
|
||||
{/* Mobile detail panel overlay */}
|
||||
{panelOpen && !isDesktop && (
|
||||
<MobileDetailOverlay open={true} onClose={handlePanelClose}>
|
||||
<TodoDetailPanel
|
||||
todo={panelMode === 'view' ? selectedTodo : null}
|
||||
isCreating={panelMode === 'create'}
|
||||
onClose={handlePanelClose}
|
||||
onSaved={panelMode === 'create' ? handlePanelClose : undefined}
|
||||
/>
|
||||
</MobileDetailOverlay>
|
||||
<div
|
||||
className="fixed inset-0 z-50 bg-background/80 backdrop-blur-sm"
|
||||
onClick={handlePanelClose}
|
||||
>
|
||||
<div
|
||||
className="fixed inset-y-0 right-0 w-full sm:w-[400px] bg-card border-l border-border shadow-lg"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<TodoDetailPanel
|
||||
todo={panelMode === 'view' ? selectedTodo : null}
|
||||
isCreating={panelMode === 'create'}
|
||||
onClose={handlePanelClose}
|
||||
onSaved={panelMode === 'create' ? handlePanelClose : undefined}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -2,7 +2,7 @@ import * as React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useMediaQuery, MOBILE } from '@/hooks/useMediaQuery';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
|
||||
// ── Browser detection (stable — checked once at module load) ──
|
||||
|
||||
@ -128,7 +128,7 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
||||
const blurTimeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
|
||||
const [pos, setPos] = React.useState<{ top: number; left: number }>({ top: 0, left: 0 });
|
||||
const isMobile = useMediaQuery(MOBILE);
|
||||
const isMobile = useMediaQuery('(max-width: 767px)');
|
||||
|
||||
React.useImperativeHandle(ref, () => triggerRef.current!);
|
||||
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export const MOBILE = '(max-width: 767px)';
|
||||
export const DESKTOP = '(min-width: 1024px)';
|
||||
|
||||
export function useMediaQuery(query: string): boolean {
|
||||
const [matches, setMatches] = useState(() =>
|
||||
typeof window !== 'undefined' ? window.matchMedia(query).matches : false
|
||||
|
||||
@ -292,12 +292,6 @@ form[data-submitted] input:invalid + button {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Mobile font scaling — overrides Tailwind text utilities below 768px.
|
||||
* Applied via .mobile-scale class on <main> in AppLayout.tsx.
|
||||
* These selectors rely on cascade order (loaded after Tailwind utilities).
|
||||
* Arbitrary sizes like text-[9px] are NOT captured — see W-03 override below.
|
||||
*/
|
||||
/* ── Global mobile content scaling ── */
|
||||
/* Scales down all page content text on mobile. Navbar and UMBRA title are excluded
|
||||
because they live outside .mobile-scale and use their own sizing. */
|
||||
@ -329,10 +323,6 @@ form[data-submitted] input:invalid + button {
|
||||
.mobile-scale .text-3xl {
|
||||
font-size: 1.375rem; /* 22px */
|
||||
}
|
||||
/* W-03: Enforce 10px floor for arbitrary small sizes */
|
||||
.mobile-scale [class*="text-\[9px\]"] {
|
||||
font-size: 10px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mobile touch optimisation ── */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user