|
import { useDrag, useDrop } from 'react-dnd'; |
|
import { motion } from 'framer-motion'; |
|
import { classNames } from '~/utils/classNames'; |
|
import type { TabVisibilityConfig } from '~/components/settings/settings.types'; |
|
import { TAB_LABELS } from '~/components/settings/settings.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); |
|
|
|
|
|
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> |
|
); |
|
}; |
|
|