diff --git a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx index e036abcb8..64f195471 100644 --- a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx @@ -12,6 +12,14 @@ import { } from "solid-js" import type { ToolState } from "@opencode-ai/sdk/v2" import type { FileContent, FileNode } from "@opencode-ai/sdk/v2/client" +import { + DragDropProvider, + DragDropSensors, + SortableProvider, + closestCenter, + createSortable, + type DragEvent as SolidDndDragEvent, +} from "@thisbeyond/solid-dnd" import IconButton from "@suid/material/IconButton" import MenuOpenIcon from "@suid/icons-material/MenuOpen" import PushPinIcon from "@suid/icons-material/PushPin" @@ -82,6 +90,32 @@ function RightPanelTabFallback() { return
} +interface SortableRightPanelTabProps { + tab: RightPanelTabModule + active: boolean + label: string + dragTitle: string + onSelect: () => void +} + +const SortableRightPanelTab: Component = (props) => { + const sortable = createSortable(props.tab.id) + return ( +
+ +
+ ) +} + interface RightPanelProps { t: (key: string, vars?: Record) => string @@ -118,7 +152,6 @@ const RightPanel: Component = (props) => { const [rightPanelCustomization, setRightPanelCustomization] = createSignal( parseRightPanelCustomization(readClientLayoutValue(RIGHT_PANEL_CUSTOMIZATION_STORAGE_KEY)), ) - const [draggedTabId, setDraggedTabId] = createSignal(null) const [browserPath, setBrowserPath] = createSignal(".") const [browserEntries, setBrowserEntries] = createSignal(null) @@ -689,8 +722,10 @@ const RightPanel: Component = (props) => { })) } - const tabClass = (tab: RightPanelTab) => - `right-panel-tab ${rightPanelTab() === tab ? "right-panel-tab-active" : "right-panel-tab-inactive"}` + const handleTabDragEnd = ({ draggable, droppable }: SolidDndDragEvent) => { + if (!droppable) return + moveTab(String(draggable.id), String(droppable.id)) + } const rightPanelModules = createMemo(() => [ { @@ -883,38 +918,23 @@ const RightPanel: Component = (props) => {
- - {(tab) => ( - - )} - + + + tab.id)}> + + {(tab) => ( + setRightPanelTab(tab.id)} + /> + )} + + + +
diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx index 41a98d97e..84baefd15 100644 --- a/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx +++ b/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx @@ -1,5 +1,13 @@ -import { For, Show, createMemo, createSignal, type Accessor, type Component } from "solid-js" +import { For, Show, createMemo, type Accessor, type Component } from "solid-js" import type { ToolState } from "@opencode-ai/sdk/v2" +import { + DragDropProvider, + DragDropSensors, + SortableProvider, + closestCenter, + createSortable, + type DragEvent as SolidDndDragEvent, +} from "@thisbeyond/solid-dnd" import { Accordion } from "@kobalte/core" import { Tooltip } from "@kobalte/core/tooltip" import Switch from "@suid/material/Switch" @@ -41,9 +49,42 @@ interface StatusTabProps { extraSections?: readonly RightPanelSectionModule[] } +interface SortableStatusSectionProps { + section: RightPanelSectionModule + expanded: boolean + t: (key: string, vars?: Record) => string +} + +const SortableStatusSection: Component = (props) => { + const sortable = createSortable(props.section.id) + return ( +
+ + + + + + + + + + + + + + + {props.section.render()} + +
+ ) +} + const StatusTab: Component = (props) => { const isSectionExpanded = (id: string) => props.expandedItems().includes(id) - const [draggedSectionId, setDraggedSectionId] = createSignal(null) const renderYoloModeSection = () => { const session = props.activeSession() @@ -259,6 +300,11 @@ const StatusTab: Component = (props) => { props.onCustomizationChange((current) => ({ ...current, statusSectionOrder: next })) } + const handleSectionDragEnd = ({ draggable, droppable }: SolidDndDragEvent) => { + if (!droppable) return + moveSection(String(draggable.id), String(droppable.id)) + } + return (
@@ -274,53 +320,15 @@ const StatusTab: Component = (props) => { value={props.expandedItems()} onChange={props.onExpandedItemsChange} > - - {(section) => ( - { - setDraggedSectionId(section.id) - event.dataTransfer?.setData("text/plain", section.id) - if (event.dataTransfer) event.dataTransfer.effectAllowed = "move" - }} - onDragOver={(event) => { - if (!draggedSectionId() || draggedSectionId() === section.id) return - event.preventDefault() - if (event.dataTransfer) event.dataTransfer.dropEffect = "move" - }} - onDrop={(event) => { - event.preventDefault() - const sourceId = event.dataTransfer?.getData("text/plain") || draggedSectionId() - if (sourceId) moveSection(sourceId, section.id) - setDraggedSectionId(null) - }} - onDragEnd={() => setDraggedSectionId(null)} - > - - - - - - - - - - - - - - {section.render()} - - )} - + + + section.id)}> + + {(section) => } + + + +
) diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css index ef9889c9f..1d0bb460c 100644 --- a/packages/ui/src/styles/panels/right-panel.css +++ b/packages/ui/src/styles/panels/right-panel.css @@ -47,6 +47,14 @@ z-index: 1; } +.right-panel-tab-draggable { + display: inline-flex; +} + +.right-panel-tab-draggable-active { + opacity: 0.75; +} + .right-panel-tab:focus-visible { @apply ring-2 ring-offset-1; ring-color: var(--accent-primary); @@ -769,6 +777,10 @@ min-height: 0; } +.right-panel-section-draggable-active { + opacity: 0.75; +} + .right-panel-customization-popover { position: absolute; top: 2.75rem;