/** * wishlist-picker.tsx — Full-screen Ink component for browsing and promoting * wishlist items. * * Replaces the neo-blessed WishlistPicker class from tui-wishlist.ts. * * Layout: * ┌───────────────────────────────────────────────────────┐ * │ wombo-combo Wishlist │ 5 items │ * ├──────────────────────────┬────────────────────────────┤ * │ • Fix login timeout... │ Description: Fix login... │ * │ • Add dark mode support │ │ * │ • Refactor DB layer │ Tags: auth, ux │ * │ │ Created: 2025-01-15 │ * ├──────────────────────────┴────────────────────────────┤ * │ E:errand P:quest G:genesis D:delete Esc:back Q:quit│ * └───────────────────────────────────────────────────────┘ * * Keybinds: * E — promote selected item to errand * P — promote selected item to quest * G — promote selected item to genesis * D / Del — delete selected item * S-Up/Down — move selected item up/down in order * Up/Down — navigate items * Esc — go back * Q — quit */ import React from "react"; import { Box, Text, useInput } from "ink"; import type { WishlistItem } from "../lib/wishlist-store"; import { useWishlistStore } from "./use-wishlist-store"; import { formatDate, truncateText } from "./wishlist-helpers"; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface WishlistPickerProps { /** Project root directory. */ projectRoot: string; /** Called when the user promotes an item to errand (E key). */ onPromoteErrand: (item: WishlistItem) => void; /** Called when the user promotes an item to genesis (G key). */ onPromoteGenesis: (item: WishlistItem) => void; /** Called when the user promotes an item to quest (P key). */ onPromoteQuest: (item: WishlistItem) => void; /** Called when the user presses Esc to go back. */ onBack: () => void; /** Called when the user presses Q to quit. */ onQuit: () => void; } // --------------------------------------------------------------------------- // Sub-components // --------------------------------------------------------------------------- /** * Header — displays app name, "Wishlist" label, and item count. */ function Header({ itemCount }: { itemCount: number }): React.ReactElement { return ( wombo-combo Wishlist | {itemCount} item{itemCount !== 1 ? "s" : ""} Promote items to errands, quests, or genesis ); } /** * ItemList — left pane showing all wishlist items. */ function ItemList({ items, selectedIndex, }: { items: WishlistItem[]; selectedIndex: number; }): React.ReactElement { if (items.length === 0) { return ( No wishlist items ); } return ( {items.map((item, i) => { const isSelected = i === selectedIndex; const pos = String(i + 1).padStart(2, " "); const text = truncateText(item.text, 38); const date = formatDate(item.created_at); const tagsBadge = item.tags.length > 0 ? ` [${item.tags.join(", ")}]` : ""; return ( {isSelected ? ( {` ${pos}. \u2022 ${text}${tagsBadge} ${date} `} ) : ( {` ${pos}. `} {"\u2022"} {` ${text}`} {tagsBadge} {` ${date}`} )} ); })} ); } /** * DetailPane — right pane showing details of the selected item. */ function DetailPane({ item, index, totalCount, }: { item: WishlistItem | null; index: number; totalCount: number; }): React.ReactElement { if (!item) { return ( No wishlist items Your wishlist is empty. Add ideas from the CLI: {'woco wishlist add "your idea"'} Or from the Task Browser: Press W to open the wishlist overlay, then press A to add an item. Press Esc to go back. ); } return ( {`Item ${index + 1}/${totalCount}`} {/* Description */} Description: {item.text} {/* Tags */} {item.tags.length > 0 && ( <> Tags: {" "} {item.tags.map((tag, i) => ( {i > 0 && {" "}} {tag} ))} )} {/* Created */} Created: {" "} {item.created_at.slice(0, 10)} {item.created_at.slice(11, 19)} {/* ID */} ID: {" "}{item.id} {/* Promote hints */} Promote: {" "} E {" \u2192 Create errand from this item"} {" "} P {" \u2192 Create quest (goal pre-filled)"} {" "} G {" \u2192 Use as genesis vision"} ); } /** * StatusBar — bottom bar with keybind hints and selected item preview. */ function StatusBar({ selectedItem, }: { selectedItem: WishlistItem | null; }): React.ReactElement { return ( Keys: E errand P quest G genesis D delete S-{"\u2191"}/{"\u2193"} reorder Esc back Q quit {selectedItem ? ( {truncateText(selectedItem.text, 60)} ) : ( {'Add items with: woco wishlist add "your idea"'} )} ); } // --------------------------------------------------------------------------- // Main Component // --------------------------------------------------------------------------- /** * WishlistPicker — full-screen Ink component for browsing and promoting * wishlist items. */ export function WishlistPicker({ projectRoot, onPromoteErrand, onPromoteGenesis, onPromoteQuest, onBack, onQuit, }: WishlistPickerProps): React.ReactElement { const store = useWishlistStore({ projectRoot }); useInput((input, key) => { // Navigation if (key.downArrow && !key.shift) { store.selectNext(); return; } if (key.upArrow && !key.shift) { store.selectPrev(); return; } // Shift+Up/Down — reorder if (key.upArrow && key.shift) { store.moveSelectedUp(); return; } if (key.downArrow && key.shift) { store.moveSelectedDown(); return; } // E — promote to errand if (input === "e") { if (store.selectedItem) { onPromoteErrand(store.selectedItem); } return; } // G — promote to genesis if (input === "g") { if (store.selectedItem) { onPromoteGenesis(store.selectedItem); } return; } // P — promote to quest if (input === "p") { if (store.selectedItem) { onPromoteQuest(store.selectedItem); } return; } // D / Delete — delete selected item if (input === "d" || key.delete) { store.deleteSelected(); return; } // Q — quit if (input === "q") { onQuit(); return; } // Escape — go back if (key.escape) { onBack(); return; } }); return ( {/* Header */}
{/* Main content: list + detail */} {/* Status bar */} ); }