{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "position-table",
  "title": "Position Table",
  "description": "A sortable table of open trading positions with built-in TP/SL editor and close position dialogs.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "dialog",
    "table",
    "https://solanaui.dev/r/action-box.json",
    "https://solanaui.dev/r/order-form.json",
    "https://solanaui.dev/r/token-icon.json",
    "https://solanaui.dev/r/sol-lib-sort-utils.json"
  ],
  "files": [
    {
      "path": "src/registry/sol/position-table.tsx",
      "content": "\"use client\";\n\nimport {\n  ChevronDownIcon,\n  ChevronUpIcon,\n  PencilIcon,\n  XIcon,\n} from \"lucide-react\";\nimport React from \"react\";\nimport { ActionBox } from \"@/registry/sol/action-box\";\nimport { OrderForm } from \"@/registry/sol/order-form\";\nimport { TokenIcon } from \"@/registry/sol/token-icon\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Dialog,\n  DialogContent,\n  DialogDescription,\n  DialogTitle,\n  DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport type { SortDirection } from \"@/registry/lib/sort-utils\";\nimport { compareValues } from \"@/registry/lib/sort-utils\";\nimport { cn } from \"@/lib/utils\";\n\ninterface PositionTablePosition {\n  symbol: string;\n  icon: string;\n  side: \"long\" | \"short\";\n  size: string;\n  value: string;\n  leverage: string;\n  entryPrice: string;\n  markPrice: string;\n  liquidationPrice?: string;\n  pnl: string;\n  pnlPercent?: string;\n  pnlTrend?: \"up\" | \"down\";\n}\n\ninterface PositionTableProps {\n  positions: PositionTablePosition[];\n  onEditTpSl?: (\n    position: PositionTablePosition,\n    values: {\n      tpPrice: string;\n      tpPercent: string;\n      slPrice: string;\n      slPercent: string;\n    },\n  ) => void;\n  onClosePosition?: (position: PositionTablePosition) => void;\n  className?: string;\n}\n\nconst SORT_KEYS = [\n  \"side\",\n  \"symbol\",\n  \"size\",\n  \"value\",\n  \"entryPrice\",\n  \"markPrice\",\n  \"leverage\",\n  \"pnl\",\n] as const;\n\ntype SortKey = (typeof SORT_KEYS)[number];\n\nconst SortableHeader = ({\n  label,\n  sortKey,\n  activeSortKey,\n  sortDirection,\n  onSort,\n  className,\n}: {\n  label: string;\n  sortKey: SortKey;\n  activeSortKey: SortKey | null;\n  sortDirection: SortDirection;\n  onSort: (key: SortKey) => void;\n  className?: string;\n}) => {\n  const isActive = activeSortKey === sortKey;\n  return (\n    <TableHead className={className}>\n      <button\n        type=\"button\"\n        onClick={() => onSort(sortKey)}\n        className={cn(\n          \"inline-flex items-center gap-1 cursor-pointer hover:text-foreground transition-colors\",\n          isActive ? \"text-foreground\" : \"text-muted-foreground\",\n        )}\n      >\n        {label}\n        {isActive && sortDirection === \"asc\" && (\n          <ChevronUpIcon className=\"size-3.5\" />\n        )}\n        {isActive && sortDirection === \"desc\" && (\n          <ChevronDownIcon className=\"size-3.5\" />\n        )}\n      </button>\n    </TableHead>\n  );\n};\n\nconst PositionTable = ({\n  positions,\n  onEditTpSl,\n  onClosePosition,\n  className,\n}: PositionTableProps) => {\n  const [sortKey, setSortKey] = React.useState<SortKey | null>(null);\n  const [sortDirection, setSortDirection] = React.useState<SortDirection>(null);\n\n  const handleSort = (key: SortKey) => {\n    if (sortKey !== key) {\n      setSortKey(key);\n      setSortDirection(\"asc\");\n    } else if (sortDirection === \"asc\") {\n      setSortDirection(\"desc\");\n    } else {\n      setSortKey(null);\n      setSortDirection(null);\n    }\n  };\n\n  const sortedIndices = React.useMemo(() => {\n    const indices = positions.map((_, i) => i);\n    if (!sortKey || !sortDirection) return indices;\n\n    return indices.sort((a, b) => {\n      const posA = positions[a];\n      const posB = positions[b];\n      let result: number;\n      switch (sortKey) {\n        case \"side\":\n          result = posA.side.localeCompare(posB.side);\n          break;\n        case \"symbol\":\n          result = posA.symbol.localeCompare(posB.symbol);\n          break;\n        case \"size\":\n          result = compareValues(posA.size, posB.size);\n          break;\n        case \"value\":\n          result = compareValues(posA.value, posB.value);\n          break;\n        case \"entryPrice\":\n          result = compareValues(posA.entryPrice, posB.entryPrice);\n          break;\n        case \"markPrice\":\n          result = compareValues(posA.markPrice, posB.markPrice);\n          break;\n        case \"leverage\":\n          result = compareValues(posA.leverage, posB.leverage);\n          break;\n        case \"pnl\":\n          result = compareValues(posA.pnl, posB.pnl);\n          break;\n        default:\n          result = 0;\n      }\n      return sortDirection === \"desc\" ? -result : result;\n    });\n  }, [positions, sortKey, sortDirection]);\n\n  return (\n    <Table className={className}>\n      <TableHeader>\n        <TableRow>\n          <SortableHeader\n            label=\"Type\"\n            sortKey=\"side\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n            className=\"w-[80px]\"\n          />\n          <SortableHeader\n            label=\"Asset\"\n            sortKey=\"symbol\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n          />\n          <SortableHeader\n            label=\"Size\"\n            sortKey=\"size\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n          />\n          <SortableHeader\n            label=\"Value\"\n            sortKey=\"value\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n          />\n          <SortableHeader\n            label=\"Entry\"\n            sortKey=\"entryPrice\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n          />\n          <SortableHeader\n            label=\"Mark\"\n            sortKey=\"markPrice\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n          />\n          <SortableHeader\n            label=\"Leverage\"\n            sortKey=\"leverage\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n          />\n          <SortableHeader\n            label=\"P&L\"\n            sortKey=\"pnl\"\n            activeSortKey={sortKey}\n            sortDirection={sortDirection}\n            onSort={handleSort}\n          />\n          <TableHead className=\"w-[60px] text-muted-foreground\">\n            TP/SL\n          </TableHead>\n          <TableHead className=\"w-[60px] text-muted-foreground\">\n            Close\n          </TableHead>\n        </TableRow>\n      </TableHeader>\n      <TableBody>\n        {sortedIndices.map((originalIndex) => {\n          const position = positions[originalIndex];\n          const pnlTrend =\n            position.pnlTrend ??\n            (position.pnl.trim().startsWith(\"-\") ? \"down\" : \"up\");\n\n          return (\n            <TableRow\n              key={`${position.symbol}-${position.side}-${originalIndex}`}\n            >\n              <TableCell className=\"w-[80px]\">\n                <span\n                  className={cn(\n                    \"text-xs font-medium uppercase\",\n                    position.side === \"long\"\n                      ? \"text-emerald-500\"\n                      : \"text-red-400\",\n                  )}\n                >\n                  {position.side}\n                </span>\n              </TableCell>\n              <TableCell>\n                <div className=\"flex items-center gap-2\">\n                  <TokenIcon\n                    src={position.icon}\n                    alt={position.symbol}\n                    width={20}\n                    height={20}\n                  />\n                  <span className=\"font-medium\">{position.symbol}</span>\n                </div>\n              </TableCell>\n              <TableCell>{position.size}</TableCell>\n              <TableCell>{position.value}</TableCell>\n              <TableCell>{position.entryPrice}</TableCell>\n              <TableCell>{position.markPrice}</TableCell>\n              <TableCell>{position.leverage}</TableCell>\n              <TableCell>\n                <span\n                  className={cn(\n                    \"text-sm font-medium\",\n                    pnlTrend === \"up\" ? \"text-emerald-500\" : \"text-red-400\",\n                  )}\n                >\n                  {position.pnl}\n                  {position.pnlPercent && ` (${position.pnlPercent})`}\n                </span>\n              </TableCell>\n              <TableCell>\n                <Dialog>\n                  <DialogTrigger asChild>\n                    <Button variant=\"ghost\" size=\"icon-sm\">\n                      <PencilIcon className=\"size-3.5\" />\n                    </Button>\n                  </DialogTrigger>\n                  <DialogContent\n                    showCloseButton={false}\n                    className=\"p-0 border-none bg-transparent shadow-none sm:max-w-sm\"\n                  >\n                    <DialogTitle className=\"sr-only\">\n                      Edit TP/SL for {position.symbol}\n                    </DialogTitle>\n                    <DialogDescription className=\"sr-only\">\n                      Set take profit and stop loss for this position\n                    </DialogDescription>\n                    <OrderForm\n                      entryPrice={Number.parseFloat(\n                        position.entryPrice.replace(/[$,]/g, \"\"),\n                      )}\n                      details={[\n                        { label: \"Size\", value: position.size },\n                        { label: \"Entry Price\", value: position.entryPrice },\n                        { label: \"Mark Price\", value: position.markPrice },\n                        ...(position.liquidationPrice\n                          ? [\n                              {\n                                label: \"Liquidation Price\",\n                                value: position.liquidationPrice,\n                              },\n                            ]\n                          : []),\n                        { label: \"P&L\", value: position.pnl },\n                      ]}\n                      onSubmit={\n                        onEditTpSl\n                          ? (values) => onEditTpSl(position, values)\n                          : undefined\n                      }\n                    />\n                  </DialogContent>\n                </Dialog>\n              </TableCell>\n              <TableCell>\n                <Dialog>\n                  <DialogTrigger asChild>\n                    <Button variant=\"ghost\" size=\"icon-sm\">\n                      <XIcon className=\"size-3.5\" />\n                    </Button>\n                  </DialogTrigger>\n                  <DialogContent className=\"flex items-center justify-center p-8 sm:max-w-md\">\n                    <DialogTitle className=\"sr-only\">\n                      Close {position.symbol} position\n                    </DialogTitle>\n                    <DialogDescription className=\"sr-only\">\n                      Close this position\n                    </DialogDescription>\n                    <ActionBox\n                      tokens={[\n                        { icon: position.icon, symbol: position.symbol },\n                      ]}\n                      defaultToken={position.symbol}\n                      label={`Close ${position.side.toUpperCase()} ${position.symbol}`}\n                      details={[\n                        { label: \"Size\", value: position.size },\n                        { label: \"Entry Price\", value: position.entryPrice },\n                        { label: \"Mark Price\", value: position.markPrice },\n                        { label: \"P&L\", value: position.pnl },\n                      ]}\n                      submitLabel=\"Close Position\"\n                      onSubmit={\n                        onClosePosition\n                          ? () => onClosePosition(position)\n                          : undefined\n                      }\n                      className=\"border-none p-0\"\n                    />\n                  </DialogContent>\n                </Dialog>\n              </TableCell>\n            </TableRow>\n          );\n        })}\n      </TableBody>\n    </Table>\n  );\n};\n\nexport type { PositionTableProps, PositionTablePosition };\nexport { PositionTable };\n",
      "type": "registry:component",
      "target": "components/sol/position-table.tsx"
    }
  ],
  "type": "registry:component"
}