Compare commits
No commits in common. "f7ec04241b6499202521c7cbc1ccd01d8dda1ad0" and "d0477b1c13b5ba27b9b37a48af8de04616caf03e" have entirely different histories.
f7ec04241b
...
d0477b1c13
@ -8,7 +8,7 @@ import dayGridPlugin from '@fullcalendar/daygrid';
|
|||||||
import timeGridPlugin from '@fullcalendar/timegrid';
|
import timeGridPlugin from '@fullcalendar/timegrid';
|
||||||
import interactionPlugin from '@fullcalendar/interaction';
|
import interactionPlugin from '@fullcalendar/interaction';
|
||||||
import type { EventClickArg, DateSelectArg, EventDropArg, DatesSetArg } from '@fullcalendar/core';
|
import type { EventClickArg, DateSelectArg, EventDropArg, DatesSetArg } from '@fullcalendar/core';
|
||||||
import { ChevronLeft, ChevronRight, PanelLeft, Plus, Search } from 'lucide-react';
|
import { ChevronLeft, ChevronRight, Plus, Search } from 'lucide-react';
|
||||||
import api, { getErrorMessage } from '@/lib/api';
|
import api, { getErrorMessage } from '@/lib/api';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import type { CalendarEvent, EventTemplate, Location as LocationType, CalendarPermission } from '@/types';
|
import type { CalendarEvent, EventTemplate, Location as LocationType, CalendarPermission } from '@/types';
|
||||||
@ -17,7 +17,6 @@ import { useSettings } from '@/hooks/useSettings';
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { Select } from '@/components/ui/select';
|
import { Select } from '@/components/ui/select';
|
||||||
import { Sheet, SheetContent } from '@/components/ui/sheet';
|
|
||||||
import CalendarSidebar from './CalendarSidebar';
|
import CalendarSidebar from './CalendarSidebar';
|
||||||
import EventDetailPanel from './EventDetailPanel';
|
import EventDetailPanel from './EventDetailPanel';
|
||||||
import type { CreateDefaults } from './EventDetailPanel';
|
import type { CreateDefaults } from './EventDetailPanel';
|
||||||
@ -165,8 +164,6 @@ export default function CalendarPage() {
|
|||||||
|
|
||||||
// Track desktop breakpoint to prevent dual EventDetailPanel mount
|
// Track desktop breakpoint to prevent dual EventDetailPanel mount
|
||||||
const isDesktop = useMediaQuery('(min-width: 1024px)');
|
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
|
// Continuously resize calendar during panel open/close CSS transition
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -189,8 +186,6 @@ export default function CalendarPage() {
|
|||||||
if (!el) return;
|
if (!el) return;
|
||||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
const handleWheel = (e: WheelEvent) => {
|
const handleWheel = (e: WheelEvent) => {
|
||||||
// Skip wheel navigation on touch devices (let them scroll normally)
|
|
||||||
if ('ontouchstart' in window) return;
|
|
||||||
const api = calendarRef.current?.getApi();
|
const api = calendarRef.current?.getApi();
|
||||||
if (!api || api.view.type !== 'dayGridMonth') return;
|
if (!api || api.view.type !== 'dayGridMonth') return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@ -476,28 +471,15 @@ export default function CalendarPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-full overflow-hidden animate-fade-in">
|
<div className="flex h-full overflow-hidden animate-fade-in">
|
||||||
<div className="hidden lg:flex lg:flex-row shrink-0">
|
<CalendarSidebar ref={sidebarRef} onUseTemplate={handleUseTemplate} onSharedVisibilityChange={setVisibleSharedIds} width={sidebarWidth} />
|
||||||
<CalendarSidebar ref={sidebarRef} onUseTemplate={handleUseTemplate} onSharedVisibilityChange={setVisibleSharedIds} width={sidebarWidth} />
|
<div
|
||||||
<div
|
onMouseDown={handleSidebarMouseDown}
|
||||||
onMouseDown={handleSidebarMouseDown}
|
className="w-1 shrink-0 cursor-col-resize hover:bg-accent/30 active:bg-accent/50 transition-colors duration-150"
|
||||||
className="w-1 shrink-0 cursor-col-resize hover:bg-accent/30 active:bg-accent/50 transition-colors duration-150"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isMobile && (
|
|
||||||
<Sheet open={mobileSidebarOpen} onOpenChange={setMobileSidebarOpen}>
|
|
||||||
<SheetContent className="w-72 p-0">
|
|
||||||
<CalendarSidebar onUseTemplate={(tmpl) => { setMobileSidebarOpen(false); handleUseTemplate(tmpl); }} onSharedVisibilityChange={setVisibleSharedIds} width={288} />
|
|
||||||
</SheetContent>
|
|
||||||
</Sheet>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div ref={calendarContainerRef} className="flex-1 flex flex-col overflow-hidden">
|
<div ref={calendarContainerRef} className="flex-1 flex flex-col overflow-hidden">
|
||||||
{/* Custom toolbar */}
|
{/* Custom toolbar */}
|
||||||
<div className="border-b bg-card px-4 md:px-6 min-h-[4rem] flex items-center gap-2 md:gap-4 flex-wrap py-2 md:py-0 md:h-16 md:flex-nowrap shrink-0">
|
<div className="border-b bg-card px-4 md:px-6 min-h-[4rem] flex items-center gap-2 md:gap-4 flex-wrap py-2 md:py-0 md:h-16 md:flex-nowrap shrink-0">
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8 lg:hidden" onClick={() => setMobileSidebarOpen(true)}>
|
|
||||||
<PanelLeft className="h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={navigatePrev}>
|
<Button variant="ghost" size="icon" className="h-8 w-8" onClick={navigatePrev}>
|
||||||
<ChevronLeft className="h-4 w-4" />
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
|||||||
@ -131,7 +131,7 @@ const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(functio
|
|||||||
<span className="text-sm text-foreground truncate flex-1">{cal.name}</span>
|
<span className="text-sm text-foreground truncate flex-1">{cal.name}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleEdit(cal)}
|
onClick={() => handleEdit(cal)}
|
||||||
className="opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
className="opacity-0 group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@ -184,7 +184,7 @@ const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(functio
|
|||||||
setEditingTemplate(tmpl);
|
setEditingTemplate(tmpl);
|
||||||
setShowTemplateForm(true);
|
setShowTemplateForm(true);
|
||||||
}}
|
}}
|
||||||
className="opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
className="opacity-0 group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3 w-3" />
|
<Pencil className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
@ -194,7 +194,7 @@ const CalendarSidebar = forwardRef<HTMLDivElement, CalendarSidebarProps>(functio
|
|||||||
if (!window.confirm(`Delete template "${tmpl.name}"?`)) return;
|
if (!window.confirm(`Delete template "${tmpl.name}"?`)) return;
|
||||||
deleteTemplateMutation.mutate(tmpl.id);
|
deleteTemplateMutation.mutate(tmpl.id);
|
||||||
}}
|
}}
|
||||||
className="opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-destructive"
|
className="opacity-0 group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-destructive"
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3" />
|
<Trash2 className="h-3 w-3" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -73,7 +73,7 @@ export default function SharedCalendarSection({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onEditCalendar?.(cal)}
|
onClick={() => onEditCalendar?.(cal)}
|
||||||
className="opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
className="opacity-0 group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
@ -104,7 +104,7 @@ export default function SharedCalendarSection({
|
|||||||
<span className="text-sm text-foreground truncate flex-1">{m.calendar_name}</span>
|
<span className="text-sm text-foreground truncate flex-1">{m.calendar_name}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => setSettingsFor(m)}
|
onClick={() => setSettingsFor(m)}
|
||||||
className="opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
className="opacity-0 group-hover:opacity-100 transition-opacity duration-150 text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Pencil className="h-3.5 w-3.5" />
|
<Pencil className="h-3.5 w-3.5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -356,17 +356,6 @@ export default function LocationsPage() {
|
|||||||
sortDir={sortDir}
|
sortDir={sortDir}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
visibilityMode={visibilityMode}
|
visibilityMode={visibilityMode}
|
||||||
mobileCardRender={(location) => (
|
|
||||||
<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>}
|
|
||||||
</div>
|
|
||||||
{location.address && (
|
|
||||||
<p className="text-xs text-muted-foreground truncate">{location.address}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -316,7 +316,7 @@ export default function NotificationsPage() {
|
|||||||
<span className="text-[11px] text-muted-foreground tabular-nums">
|
<span className="text-[11px] text-muted-foreground tabular-nums">
|
||||||
{formatDistanceToNow(new Date(notification.created_at), { addSuffix: true })}
|
{formatDistanceToNow(new Date(notification.created_at), { addSuffix: true })}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex items-center gap-0.5 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity">
|
<div className="flex items-center gap-0.5 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
{!notification.is_read && (
|
{!notification.is_read && (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => { e.stopPropagation(); handleMarkRead(notification.id); }}
|
onClick={(e) => { e.stopPropagation(); handleMarkRead(notification.id); }}
|
||||||
|
|||||||
@ -744,18 +744,6 @@ export default function PeoplePage() {
|
|||||||
sortDir={sortDir}
|
sortDir={sortDir}
|
||||||
onSort={handleSort}
|
onSort={handleSort}
|
||||||
visibilityMode={visibilityMode}
|
visibilityMode={visibilityMode}
|
||||||
mobileCardRender={(person) => (
|
|
||||||
<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>}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
|
||||||
{person.email && <span className="truncate">{person.email}</span>}
|
|
||||||
{person.phone && <span>{person.phone}</span>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -53,7 +53,7 @@ function KanbanColumn({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={setNodeRef}
|
ref={setNodeRef}
|
||||||
className={`flex-1 min-w-[160px] md:min-w-[200px] rounded-lg border transition-colors duration-150 ${
|
className={`flex-1 min-w-[200px] rounded-lg border transition-colors duration-150 ${
|
||||||
isOver ? 'border-accent/40 bg-accent/5' : 'border-border bg-card/50'
|
isOver ? 'border-accent/40 bg-accent/5' : 'border-border bg-card/50'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -200,7 +200,7 @@ export default function KanbanBoard({
|
|||||||
collisionDetection={closestCorners}
|
collisionDetection={closestCorners}
|
||||||
onDragEnd={handleDragEnd}
|
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 }) => (
|
{tasksByStatus.map(({ column, tasks: colTasks }) => (
|
||||||
<KanbanColumn
|
<KanbanColumn
|
||||||
key={column.id}
|
key={column.id}
|
||||||
|
|||||||
@ -484,7 +484,7 @@ export default function TaskDetailPanel({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-5 w-5 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-destructive shrink-0"
|
className="h-5 w-5 opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-destructive shrink-0"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDeleteSubtask(subtask.id, subtask.title);
|
handleDeleteSubtask(subtask.id, subtask.title);
|
||||||
@ -527,7 +527,7 @@ export default function TaskDetailPanel({
|
|||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
className="h-5 w-5 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-destructive"
|
className="h-5 w-5 opacity-0 group-hover:opacity-100 transition-opacity text-muted-foreground hover:text-destructive"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!window.confirm('Delete this comment?')) return;
|
if (!window.confirm('Delete this comment?')) return;
|
||||||
deleteCommentMutation.mutate(comment.id);
|
deleteCommentMutation.mutate(comment.id);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ export default function CopyableField({ value, icon: Icon, label }: CopyableFiel
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
aria-label={`Copy ${label || value}`}
|
aria-label={`Copy ${label || value}`}
|
||||||
className="opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity duration-150 p-0.5 rounded text-muted-foreground hover:text-foreground shrink-0"
|
className="opacity-0 group-hover:opacity-100 transition-opacity duration-150 p-0.5 rounded text-muted-foreground hover:text-foreground shrink-0"
|
||||||
>
|
>
|
||||||
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
{copied ? <Check className="h-3.5 w-3.5" /> : <Copy className="h-3.5 w-3.5" />}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
|
import { ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react';
|
||||||
import type { VisibilityMode } from '@/hooks/useTableVisibility';
|
import type { VisibilityMode } from '@/hooks/useTableVisibility';
|
||||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
||||||
|
|
||||||
export interface ColumnDef<T> {
|
export interface ColumnDef<T> {
|
||||||
key: string;
|
key: string;
|
||||||
@ -29,7 +28,6 @@ interface EntityTableProps<T extends { id: number }> {
|
|||||||
onSort: (key: string) => void;
|
onSort: (key: string) => void;
|
||||||
visibilityMode: VisibilityMode;
|
visibilityMode: VisibilityMode;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
mobileCardRender?: (item: T) => React.ReactNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const LEVEL_ORDER: VisibilityMode[] = ['essential', 'filtered', 'all'];
|
const LEVEL_ORDER: VisibilityMode[] = ['essential', 'filtered', 'all'];
|
||||||
@ -129,51 +127,10 @@ export function EntityTable<T extends { id: number }>({
|
|||||||
onSort,
|
onSort,
|
||||||
visibilityMode,
|
visibilityMode,
|
||||||
loading = false,
|
loading = false,
|
||||||
mobileCardRender,
|
|
||||||
}: EntityTableProps<T>) {
|
}: EntityTableProps<T>) {
|
||||||
const visibleColumns = columns.filter((col) => isVisible(col.visibilityLevel, visibilityMode));
|
const visibleColumns = columns.filter((col) => isVisible(col.visibilityLevel, visibilityMode));
|
||||||
const colCount = visibleColumns.length;
|
const colCount = visibleColumns.length;
|
||||||
const showPinnedSection = showPinned && pinnedRows.length > 0;
|
const showPinnedSection = showPinned && pinnedRows.length > 0;
|
||||||
const isMobile = useMediaQuery('(max-width: 767px)');
|
|
||||||
|
|
||||||
if (isMobile && mobileCardRender) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-2">
|
|
||||||
{loading ? (
|
|
||||||
Array.from({ length: 6 }).map((_, i) => (
|
|
||||||
<div key={i} className="animate-pulse rounded-lg bg-card border border-border p-4 h-20" />
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{showPinnedSection && (
|
|
||||||
<>
|
|
||||||
<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">
|
|
||||||
{mobileCardRender(item)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{groups.map((group) => (
|
|
||||||
<React.Fragment key={group.label}>
|
|
||||||
{group.rows.length > 0 && (
|
|
||||||
<>
|
|
||||||
<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">
|
|
||||||
{mobileCardRender(item)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</React.Fragment>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import * as React from 'react';
|
|||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react';
|
import { Calendar, ChevronLeft, ChevronRight, Clock } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
|
||||||
|
|
||||||
// ── Browser detection (stable — checked once at module load) ──
|
// ── Browser detection (stable — checked once at module load) ──
|
||||||
|
|
||||||
@ -128,7 +127,6 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
|
|||||||
const blurTimeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
|
const blurTimeoutRef = React.useRef<ReturnType<typeof setTimeout>>();
|
||||||
|
|
||||||
const [pos, setPos] = React.useState<{ top: number; left: number }>({ top: 0, left: 0 });
|
const [pos, setPos] = React.useState<{ top: number; left: number }>({ top: 0, left: 0 });
|
||||||
const isMobile = useMediaQuery('(max-width: 767px)');
|
|
||||||
|
|
||||||
React.useImperativeHandle(ref, () => triggerRef.current!);
|
React.useImperativeHandle(ref, () => triggerRef.current!);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user