11"use client" ;
2-
3- import React from "react" ;
4- import { Pencil , Trash2 } from "lucide-react" ;
2+ import React , { useRef } from "react" ;
3+ import { Pencil , Trash2 , GripVertical } from "lucide-react" ;
54import { AnnouncementCategory } from "./types" ;
65
76interface AnnouncementTabProps {
@@ -12,6 +11,12 @@ interface AnnouncementTabProps {
1211 onClick : ( id : AnnouncementCategory ) => void ;
1312 onEdit ?: ( id : AnnouncementCategory , label : string ) => void ;
1413 onDelete ?: ( id : AnnouncementCategory ) => void ;
14+ onDragStart ?: ( e : React . DragEvent , id : AnnouncementCategory ) => void ;
15+ onDragOver ?: ( e : React . DragEvent , id : AnnouncementCategory ) => void ;
16+ onDragEnd ?: ( ) => void ;
17+ onDrop ?: ( e : React . DragEvent , id : AnnouncementCategory ) => void ;
18+ isDragging ?: boolean ;
19+ isDragOver ?: boolean ;
1520}
1621
1722export const AnnouncementTab : React . FC < AnnouncementTabProps > = ( {
@@ -22,21 +27,68 @@ export const AnnouncementTab: React.FC<AnnouncementTabProps> = ({
2227 onClick,
2328 onEdit,
2429 onDelete,
30+ onDragStart,
31+ onDragOver,
32+ onDragEnd,
33+ onDrop,
34+ isDragging = false ,
35+ isDragOver = false ,
2536} ) => {
37+ // Ref-based gate: synchronous, no re-render delay
38+ const gripPressed = useRef ( false ) ;
39+
2640 return (
2741 < div
42+ draggable = { isAdmin }
43+ onDragStart = { ( e ) => {
44+ if ( ! gripPressed . current ) {
45+ e . preventDefault ( ) ;
46+ return ;
47+ }
48+ e . dataTransfer . setData ( "text/plain" , id ) ;
49+ e . dataTransfer . effectAllowed = "move" ;
50+ onDragStart ?.( e , id ) ;
51+ } }
52+ onDragOver = { ( e ) => {
53+ e . preventDefault ( ) ;
54+ e . dataTransfer . dropEffect = "move" ;
55+ onDragOver ?.( e , id ) ;
56+ } }
57+ onDragEnd = { ( ) => {
58+ gripPressed . current = false ;
59+ onDragEnd ?.( ) ;
60+ } }
61+ onDrop = { ( e ) => {
62+ e . preventDefault ( ) ;
63+ onDrop ?.( e , id ) ;
64+ } }
2865 className = { `
2966 group relative flex items-center justify-between rounded-lg w-full
30- ${
31- isActive
32- ? "bg-blue-500/10 text-blue-500 dark:bg-blue-500/20"
33- : "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800/50"
67+ transition-all duration-150 select-none
68+ ${ isActive
69+ ? "bg-blue-500/10 text-blue-500 dark:bg-blue-500/20"
70+ : "text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800/50"
3471 }
72+ ${ isDragging ? "opacity-40 scale-[0.98]" : "opacity-100" }
73+ ${ isDragOver ? "border-t-2 border-blue-500" : "border-t-2 border-transparent" }
3574 ` }
3675 >
76+ { /* Grip handle — only shown to admins on hover */ }
77+ { isAdmin && (
78+ < span
79+ onMouseDown = { ( ) => { gripPressed . current = true ; } }
80+ onMouseUp = { ( ) => { gripPressed . current = false ; } }
81+ className = "pl-2 shrink-0 opacity-0 group-hover:opacity-100 transition-opacity duration-150 cursor-grab active:cursor-grabbing text-gray-400 dark:text-gray-500"
82+ title = "Drag to reorder"
83+ >
84+ < GripVertical className = "h-4 w-4 pointer-events-none" />
85+ </ span >
86+ ) }
87+
3788 < button
89+ draggable = { false }
3890 onClick = { ( ) => onClick ( id ) }
39- className = "flex-1 px-4 sm:px-6 py-3 sm:py-4 text-sm sm:text-base font-medium text-left truncate transition-all duration-200 "
91+ className = "flex-1 px-3 sm:px-4 py-3 sm:py-4 text-sm sm:text-base font-medium text-left truncate"
4092 role = "tab"
4193 aria-selected = { isActive }
4294 aria-controls = { `panel-${ id } ` }
@@ -45,8 +97,9 @@ export const AnnouncementTab: React.FC<AnnouncementTabProps> = ({
4597 </ button >
4698
4799 { isAdmin && (
48- < div className = "flex items-center gap-0.5 pr-2 opacity-0 group-hover:opacity-100 transition-opacity" >
100+ < div className = "flex items-center gap-0.5 pr-2 opacity-0 group-hover:opacity-100 transition-opacity duration-150 " >
49101 < button
102+ draggable = { false }
50103 onClick = { ( e ) => {
51104 e . stopPropagation ( ) ;
52105 onEdit ?.( id , label ) ;
@@ -57,6 +110,7 @@ export const AnnouncementTab: React.FC<AnnouncementTabProps> = ({
57110 < Pencil className = "h-3.5 w-3.5" />
58111 </ button >
59112 < button
113+ draggable = { false }
60114 onClick = { ( e ) => {
61115 e . stopPropagation ( ) ;
62116 onDelete ?.( id ) ;
0 commit comments