1const e=`import { lazy, Suspense, useState, useMemo } from "react"; 2import { useSearchParams } from "react-router-dom"; 3import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 4import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; 5import { PenLine, Layers, StickyNote, HelpCircle } from "lucide-react"; 6import { useZettelCards } from "@/hooks/useZettelCards"; 7import type { ZettelCard as ZettelCardType } from "@/types/zettel"; 8 9const ScratchPad = lazy(() => import("@/components/ScratchPad").then(m => ({ default: m.ScratchPad }))); 10const CardsNotesWorkspace = lazy(() => import("@/components/workspaces/CardsNotesWorkspace").then(m => ({ default: m.CardsNotesWorkspace }))); 11const StickyNotesSimple = lazy(() => import("@/components/StickyNotesSimple").then(m => ({ default: m.StickyNotesSimple }))); 12 13type HubTab = "scratchpad" | "workspace" | "stickynotes"; 14 15const TAXONOMY: Array<{ 16 id: HubTab; 17 label: string; 18 icon: React.ComponentType<{ className?: string }>; 19 tagline: string; 20 when: string; 21 limit: string; 22 accent: string; 23}> = [ 24 { 25 id: "scratchpad", 26 label: "Scratchpad", 27 icon: PenLine, 28 tagline: "Napkin scribbles", 29 when: "Quick ideas, web snippets, brain dumps you don't want to lose.", 30 limit: "⤠500 characters", 31 accent: "text-amber-600", 32 }, 33 { 34 id: "workspace", 35 label: "Cards & Notes", 36 icon: Layers, 37 tagline: "Atomic ideas â long-form", 38 when: "Unified split-pane workspace. Atomic cards (â¤1,500 chars) live alongside long-form notes. Type [[ to link, drop cards into notes to cite, and promote cards to notes when they outgrow the card limit.", 39 limit: "Cards ⤠1,500 · Notes unlimited", 40 accent: "text-sky-600", 41 }, 42 { 43 id: "stickynotes", 44 label: "Sticky Notes", 45 icon: StickyNote, 46 tagline: "Reminders & tasks", 47 when: "Quick reminders, follow-ups, and short to-dos.", 48 limit: "Short", 49 accent: "text-rose-600", 50 }, 51]; 52 53export function CaptureHub() { 54 const [params, setParams] = useSearchParams(); 55 const raw = params.get("hub"); 56 const initial: HubTab = raw === "cards" || raw === "notes" || raw === "workspace" 57 ? "workspace" 58 : raw === "stickynotes" ? "stickynotes" : "scratchpad"; 59 const [tab, setTab] = useState<HubTab>(initial); 60 const { createCard } = useZettelCards(); 61 62 const handleChange = (val: string) => { 63 setTab(val as HubTab); 64 const next = new URLSearchParams(params); 65 next.set("hub", val); 66 setParams(next, { replace: true }); 67 }; 68 69 const active = useMemo(() => TAXONOMY.find(t => t.id === tab)!, [tab]); 70 71 const handleCreateCard = (card: Omit<ZettelCardType, "id" | "created" | "modified">) => { 72 createCard(card); 73 }; 74 75 return ( 76 <div className="px-2 sm:px-4 py-2 space-y-2"> 77 <div className="flex items-center gap-1.5"> 78 <div 79 role="tablist" 80 aria-label="Capture type" 81 className="flex flex-1 items-center gap-1 rounded-full border border-border/60 bg-card/60 p-1 overflow-x-auto scrollbar-hide" 82 > 83 {TAXONOMY.map((t) => { 84 const Icon = t.icon; 85 const isActive = t.id === tab; 86 return ( 87 <button 88 key={t.id} 89 role="tab" 90 aria-selected={isActive} 91 onClick={() => handleChange(t.id)} 92 title={\`\${t.label} · \${t.tagline} · \${t.limit}\`} 93 className={\`flex items-center gap-1.5 rounded-full px-3 py-1.5 text-xs font-medium whitespace-nowrap transition-all \${ 94 isActive 95 ? "bg-primary text-primary-foreground shadow-sm" 96 : "text-muted-foreground hover:text-foreground hover:bg-accent/60" 97 }\`} 98 > 99 <Icon className={\`h-3.5 w-3.5 \${isActive ? "" : t.accent}\`} /> 100 {t.label} 101 </button> 102 ); 103 })} 104 </div> 105 106 <Popover> 107 <PopoverTrigger asChild> 108 <button 109 aria-label="How capture types work" 110 className="shrink-0 rounded-full border border-border/60 bg-card/60 p-1.5 text-muted-foreground hover:text-foreground hover:bg-accent/60 transition-colors" 111 > 112 <HelpCircle className="h-3.5 w-3.5" /> 113 </button> 114 </PopoverTrigger> 115 <PopoverContent align="end" className="w-80 p-3 space-y-2.5"> 116 <div className="text-xs"> 117 <p className="font-semibold text-foreground mb-1">One place to capture everything.</p> 118 <p className="text-muted-foreground leading-relaxed"> 119 Pick the shape that fits. Cards & Notes share one split-pane workspace â promote a card to a note the moment it outgrows 1,500 characters. 120 </p> 121 </div> 122 <div className="border-t border-border/60 pt-2 space-y-2"> 123 {TAXONOMY.map((t) => { 124 const Icon = t.icon; 125 return ( 126 <div key={t.id} className="flex items-start gap-2"> 127 <Icon className={\`h-3.5 w-3.5 mt-0.5 shrink-0 \${t.accent}\`} /> 128 <div className="text-[11px] leading-snug">
129 <span className="font-semibold text-foreground">{t.label}</span> 130 <span className="text-muted-foreground/80"> · {t.limit}</span> 131 <p className="text-muted-foreground mt-0.5">{t.when}</p> 132 </div> 133 </div> 134 ); 135 })} 136 </div> 137 </PopoverContent> 138 </Popover> 139 </div> 140 141 <Tabs value={tab} onValueChange={handleChange} className="w-full"> 142 <TabsList className="sr-only"> 143 {TAXONOMY.map((t) => ( 144 <TabsTrigger key={t.id} value={t.id}>{t.label}</TabsTrigger> 145 ))} 146 </TabsList> 147 148 <Suspense fallback={<div className="text-xs text-muted-foreground p-4">Loadingâ¦</div>}> 149 <TabsContent value="scratchpad" className="mt-0"> 150 <ScratchPad onCreateCard={handleCreateCard} /> 151 </TabsContent> 152 <TabsContent value="workspace" className="mt-0"> 153 <CardsNotesWorkspace /> 154 </TabsContent> 155 <TabsContent value="stickynotes" className="mt-0"> 156 <StickyNotesSimple /> 157 </TabsContent> 158 </Suspense> 159 </Tabs> 160 </div> 161 ); 162} 163 164`;export{e as default};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.