Spaces:
Sleeping
Sleeping
import { useDrag, useDrop } from 'react-dnd'; | |
import { motion } from 'framer-motion'; | |
import { classNames } from '~/utils/classNames'; | |
import type { TabVisibilityConfig } from '~/components/@settings/core/types'; | |
import { TAB_LABELS } from '~/components/@settings/core/types'; | |
import { Switch } from '~/components/ui/Switch'; | |
interface DraggableTabListProps { | |
tabs: TabVisibilityConfig[]; | |
onReorder: (tabs: TabVisibilityConfig[]) => void; | |
onWindowChange?: (tab: TabVisibilityConfig, window: 'user' | 'developer') => void; | |
onVisibilityChange?: (tab: TabVisibilityConfig, visible: boolean) => void; | |
showControls?: boolean; | |
} | |
interface DraggableTabItemProps { | |
tab: TabVisibilityConfig; | |
index: number; | |
moveTab: (dragIndex: number, hoverIndex: number) => void; | |
showControls?: boolean; | |
onWindowChange?: (tab: TabVisibilityConfig, window: 'user' | 'developer') => void; | |
onVisibilityChange?: (tab: TabVisibilityConfig, visible: boolean) => void; | |
} | |
interface DragItem { | |
type: string; | |
index: number; | |
id: string; | |
} | |
const DraggableTabItem = ({ | |
tab, | |
index, | |
moveTab, | |
showControls, | |
onWindowChange, | |
onVisibilityChange, | |
}: DraggableTabItemProps) => { | |
const [{ isDragging }, dragRef] = useDrag({ | |
type: 'tab', | |
item: { type: 'tab', index, id: tab.id }, | |
collect: (monitor) => ({ | |
isDragging: monitor.isDragging(), | |
}), | |
}); | |
const [, dropRef] = useDrop({ | |
accept: 'tab', | |
hover: (item: DragItem, monitor) => { | |
if (!monitor.isOver({ shallow: true })) { | |
return; | |
} | |
if (item.index === index) { | |
return; | |
} | |
if (item.id === tab.id) { | |
return; | |
} | |
moveTab(item.index, index); | |
item.index = index; | |
}, | |
}); | |
const ref = (node: HTMLDivElement | null) => { | |
dragRef(node); | |
dropRef(node); | |
}; | |
return ( | |
<motion.div | |
ref={ref} | |
initial={false} | |
animate={{ | |
scale: isDragging ? 1.02 : 1, | |
boxShadow: isDragging ? '0 8px 16px rgba(0,0,0,0.1)' : 'none', | |
}} | |
className={classNames( | |
'flex items-center justify-between p-4 rounded-lg', | |
'bg-[#F5F5F5] dark:bg-[#1A1A1A]', | |
'border border-[#E5E5E5] dark:border-[#333333]', | |
isDragging ? 'z-50' : '', | |
)} | |
> | |
<div className="flex items-center gap-4"> | |
<div className="cursor-grab"> | |
<div className="i-ph:dots-six-vertical w-4 h-4 text-bolt-elements-textSecondary" /> | |
</div> | |
<div> | |
<div className="font-medium text-bolt-elements-textPrimary">{TAB_LABELS[tab.id]}</div> | |
{showControls && ( | |
<div className="text-xs text-bolt-elements-textSecondary"> | |
Order: {tab.order}, Window: {tab.window} | |
</div> | |
)} | |
</div> | |
</div> | |
{showControls && !tab.locked && ( | |
<div className="flex items-center gap-4"> | |
<div className="flex items-center gap-2"> | |
<Switch | |
checked={tab.visible} | |
onCheckedChange={(checked: boolean) => onVisibilityChange?.(tab, checked)} | |
className="data-[state=checked]:bg-purple-500" | |
aria-label={`Toggle ${TAB_LABELS[tab.id]} visibility`} | |
/> | |
<label className="text-sm text-bolt-elements-textSecondary">Visible</label> | |
</div> | |
<div className="flex items-center gap-2"> | |
<label className="text-sm text-bolt-elements-textSecondary">User</label> | |
<Switch | |
checked={tab.window === 'developer'} | |
onCheckedChange={(checked: boolean) => onWindowChange?.(tab, checked ? 'developer' : 'user')} | |
className="data-[state=checked]:bg-purple-500" | |
aria-label={`Toggle ${TAB_LABELS[tab.id]} window assignment`} | |
/> | |
<label className="text-sm text-bolt-elements-textSecondary">Dev</label> | |
</div> | |
</div> | |
)} | |
</motion.div> | |
); | |
}; | |
export const DraggableTabList = ({ | |
tabs, | |
onReorder, | |
onWindowChange, | |
onVisibilityChange, | |
showControls = false, | |
}: DraggableTabListProps) => { | |
const moveTab = (dragIndex: number, hoverIndex: number) => { | |
const items = Array.from(tabs); | |
const [reorderedItem] = items.splice(dragIndex, 1); | |
items.splice(hoverIndex, 0, reorderedItem); | |
// Update order numbers based on position | |
const reorderedTabs = items.map((tab, index) => ({ | |
...tab, | |
order: index + 1, | |
})); | |
onReorder(reorderedTabs); | |
}; | |
return ( | |
<div className="space-y-2"> | |
{tabs.map((tab, index) => ( | |
<DraggableTabItem | |
key={tab.id} | |
tab={tab} | |
index={index} | |
moveTab={moveTab} | |
showControls={showControls} | |
onWindowChange={onWindowChange} | |
onVisibilityChange={onVisibilityChange} | |
/> | |
))} | |
</div> | |
); | |
}; | |