@handled-ai/design-system 0.18.52 → 0.18.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/entity-panel.js +5 -5
- package/dist/components/entity-panel.js.map +1 -1
- package/dist/components/virtualized-data-table.js +4 -4
- package/dist/components/virtualized-data-table.js.map +1 -1
- package/package.json +1 -1
- package/src/components/__tests__/entity-metadata-grid.test.tsx +27 -1
- package/src/components/__tests__/virtualized-data-table-resize.test.tsx +18 -0
- package/src/components/entity-panel.tsx +7 -5
- package/src/components/virtualized-data-table.tsx +4 -4
|
@@ -174,12 +174,12 @@ function EntityPanelTabs({
|
|
|
174
174
|
)) });
|
|
175
175
|
}
|
|
176
176
|
function EntityMetadataGrid({ fields }) {
|
|
177
|
-
return /* @__PURE__ */ jsx("div", { className: "grid
|
|
178
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-
|
|
179
|
-
/* @__PURE__ */ jsx(field.icon, { className: "
|
|
180
|
-
/* @__PURE__ */ jsx("span", { children: field.label })
|
|
177
|
+
return /* @__PURE__ */ jsx("div", { className: "mb-7 grid min-w-0 grid-cols-1 gap-x-4 gap-y-3 overflow-hidden text-[13px] md:grid-cols-[minmax(0,140px)_minmax(0,1fr)]", children: fields.map((field, idx) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
178
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-start gap-1.5 text-[13px] font-normal text-muted-foreground", children: [
|
|
179
|
+
/* @__PURE__ */ jsx(field.icon, { className: "mt-0.5 h-3.5 w-3.5 shrink-0" }),
|
|
180
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 break-words leading-relaxed [overflow-wrap:anywhere]", children: field.label })
|
|
181
181
|
] }),
|
|
182
|
-
/* @__PURE__ */ jsx("div", { className: "min-w-0
|
|
182
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 whitespace-normal break-words leading-relaxed text-foreground [overflow-wrap:anywhere] [&_*]:max-w-full [&_*]:[overflow-wrap:anywhere] [&_a]:break-all", children: field.value })
|
|
183
183
|
] }, idx)) });
|
|
184
184
|
}
|
|
185
185
|
function EntitySection({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/entity-panel.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from \"./sheet\"\nimport { Badge } from \"./badge\"\nimport { Button } from \"./button\"\nimport { Input } from \"./input\"\nimport {\n Plus,\n ExternalLink,\n Mail,\n FileText,\n MessageCircle,\n Briefcase,\n Building2,\n Users,\n X,\n ChevronDown,\n ChevronUp,\n Link as LinkIcon,\n Maximize2,\n Minimize2,\n CalendarDays,\n} from \"lucide-react\"\nimport { TimelineActivity, type TimelineEvent } from \"./timeline-activity\"\n\n// ---------------------------------------------------------------------------\n// EntityPanel -- supports Sheet (side panel), wide, and fullscreen modes\n// ---------------------------------------------------------------------------\n\nexport type PanelMode = 'default' | 'wide' | 'fullscreen'\n\nexport function EntityPanel({\n isOpen,\n onClose,\n children,\n}: {\n isOpen: boolean\n onClose: (open: boolean) => void\n children?: React.ReactNode\n}) {\n const [panelMode, setPanelMode] = React.useState<PanelMode>('default')\n\n // Backward-compatible derived values\n const isFullscreen = panelMode === 'fullscreen'\n const setIsFullscreen = React.useCallback(\n (val: boolean) => setPanelMode(val ? 'fullscreen' : 'default'),\n [],\n )\n\n const cyclePanelMode = React.useCallback(() => {\n setPanelMode((prev) =>\n prev === 'default' ? 'wide' : prev === 'wide' ? 'fullscreen' : 'default',\n )\n }, [])\n\n React.useEffect(() => {\n if (!isOpen) setPanelMode('default')\n }, [isOpen])\n\n const handleClose = React.useCallback(() => {\n setPanelMode('default')\n onClose(false)\n }, [onClose])\n\n const panelContent = (\n <EntityPanelContext.Provider\n value={{\n isFullscreen,\n setIsFullscreen,\n panelMode,\n setPanelMode,\n cyclePanelMode,\n onClose: handleClose,\n }}\n >\n {children}\n </EntityPanelContext.Provider>\n )\n\n if (isFullscreen && isOpen) {\n return (\n <div className=\"fixed inset-0 z-50 flex flex-col overflow-hidden bg-background\">\n <div className=\"flex-1 overflow-y-auto px-5 py-5\">{panelContent}</div>\n </div>\n )\n }\n\n const widthClass =\n panelMode === 'wide'\n ? 'sm:w-[800px] sm:max-w-[900px]'\n : 'sm:w-[500px] sm:max-w-[600px]'\n\n return (\n <Sheet open={isOpen} onOpenChange={onClose}>\n <SheetContent\n side=\"right\"\n className={`w-full ${widthClass} overflow-hidden p-0 bg-background border-l border-border flex flex-col`}\n showCloseButton={false}\n >\n <SheetHeader className=\"sr-only p-0\">\n <SheetTitle>Entity panel</SheetTitle>\n </SheetHeader>\n <div className=\"flex-1 overflow-y-auto px-5 py-5\">{panelContent}</div>\n </SheetContent>\n </Sheet>\n )\n}\n\nconst EntityPanelContext = React.createContext<{\n isFullscreen: boolean\n setIsFullscreen: (v: boolean) => void\n panelMode: PanelMode\n setPanelMode: (mode: PanelMode) => void\n cyclePanelMode: () => void\n onClose: () => void\n}>({\n isFullscreen: false,\n setIsFullscreen: () => {},\n panelMode: 'default',\n setPanelMode: () => {},\n cyclePanelMode: () => {},\n onClose: () => {},\n})\n\nexport function useEntityPanel() {\n return React.useContext(EntityPanelContext)\n}\n\n// ---------------------------------------------------------------------------\n// EntityPanelHeader – MeetingDetail-inspired header bar\n// ---------------------------------------------------------------------------\n\nexport function EntityPanelHeader({\n icon,\n title,\n badgeLabel,\n subtitle,\n headerAction,\n headerSecondaryAction,\n}: {\n icon?: React.ReactNode\n title: string\n badgeLabel?: string\n subtitle?: string\n headerAction?: React.ReactNode\n headerSecondaryAction?: React.ReactNode\n}) {\n const { panelMode, cyclePanelMode, onClose } = useEntityPanel()\n\n const sizeButtonTitle =\n panelMode === 'default' ? 'Wide' : panelMode === 'wide' ? 'Fullscreen' : 'Exit fullscreen'\n\n return (\n <div className=\"mb-3 space-y-2\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 min-w-0\">\n {icon ?? <CalendarDays className=\"w-5 h-5 text-muted-foreground shrink-0\" />}\n <h2 className=\"text-[16px] font-semibold text-foreground truncate\">{title}</h2>\n {badgeLabel && (\n <Badge\n variant=\"outline\"\n className=\"text-blue-600 border-blue-300 dark:border-blue-700 dark:text-blue-400 shadow-none px-2 py-0.5 text-[11px] font-medium shrink-0\"\n >\n {badgeLabel}\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-1 shrink-0 ml-4 text-muted-foreground\">\n {headerAction}\n <button\n type=\"button\"\n className=\"p-1.5 rounded-md hover:bg-secondary transition-colors\"\n title=\"Copy Link\"\n >\n <LinkIcon className=\"w-4 h-4\" />\n </button>\n <button\n type=\"button\"\n onClick={cyclePanelMode}\n className=\"p-1.5 rounded-md hover:bg-secondary transition-colors\"\n title={sizeButtonTitle}\n >\n {panelMode === 'fullscreen' ? (\n <Minimize2 className=\"w-4 h-4\" />\n ) : (\n <Maximize2 className=\"w-4 h-4\" />\n )}\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1.5 rounded-md hover:bg-secondary transition-colors\"\n title=\"Close\"\n >\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n {(subtitle || headerSecondaryAction) && (\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-2\">\n {subtitle ? (\n <p className=\"min-w-0 flex-1 text-xs text-muted-foreground\">{subtitle}</p>\n ) : (\n <div className=\"min-w-0 flex-1\" />\n )}\n {headerSecondaryAction ? (\n <div className=\"flex shrink-0 items-center gap-2\">{headerSecondaryAction}</div>\n ) : null}\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityPanelTabs – Overview/Details tab bar\n// ---------------------------------------------------------------------------\n\nexport function EntityPanelTabs({\n tabs,\n activeTab,\n onTabChange,\n}: {\n tabs: { id: string; label: string }[]\n activeTab: string\n onTabChange: (id: string) => void\n}) {\n return (\n <div className=\"flex items-center gap-5 border-b border-border mb-4 overflow-x-auto\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n onClick={() => onTabChange(tab.id)}\n className={`whitespace-nowrap shrink-0 pb-2.5 text-[13px] font-medium border-b-2 transition-colors ${\n activeTab === tab.id\n ? \"border-primary text-foreground\"\n : \"border-transparent text-muted-foreground hover:text-foreground\"\n }`}\n >\n {tab.label}\n </button>\n ))}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityMetadataGrid – key/value metadata rows with icons\n// ---------------------------------------------------------------------------\n\nexport interface EntityMetadataField {\n icon: React.ComponentType<{ className?: string }>\n label: string\n value: React.ReactNode\n}\n\nexport function EntityMetadataGrid({ fields }: { fields: EntityMetadataField[] }) {\n return (\n <div className=\"grid grid-cols-1 md:grid-cols-[140px_1fr] gap-y-3 gap-x-4 mb-7 text-[13px] overflow-hidden\">\n {fields.map((field, idx) => (\n <React.Fragment key={idx}>\n <div className=\"flex items-center gap-1.5 text-muted-foreground text-[13px] font-normal\">\n <field.icon className=\"w-3.5 h-3.5 shrink-0\" />\n <span>{field.label}</span>\n </div>\n <div className=\"min-w-0 truncate text-foreground\">{field.value}</div>\n </React.Fragment>\n ))}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntitySection – clean section with title (MeetingDetail-style)\n// ---------------------------------------------------------------------------\n\nexport function EntitySection({\n title,\n children,\n action,\n}: {\n title: string\n children: React.ReactNode\n action?: React.ReactNode\n}) {\n return (\n <section className=\"mb-7\">\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"text-[12px] font-semibold text-muted-foreground tracking-wide\">{title}</h3>\n {action}\n </div>\n {children}\n </section>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityActivityItem – clean activity row (MeetingDetail-style)\n// ---------------------------------------------------------------------------\n\nexport function EntityActivityItem({\n icon,\n title,\n description,\n date,\n}: {\n icon?: React.ReactNode\n title: React.ReactNode\n description?: React.ReactNode\n date?: string\n}) {\n return (\n <div className=\"flex gap-3 text-[13px]\">\n <div className=\"mt-0.5 text-muted-foreground shrink-0\">\n {icon ?? <CalendarDays className=\"w-4 h-4\" />}\n </div>\n <div>\n <p className=\"text-foreground leading-relaxed\">{title}</p>\n {description && <p className=\"text-[11px] text-muted-foreground/70 mt-0.5\">{description}</p>}\n {date && <p className=\"text-[11px] text-muted-foreground/70 mt-0.5\">{date}</p>}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SystemActivity – standalone section for bottom of entity panel\n// ---------------------------------------------------------------------------\n\nexport function SystemActivity() {\n return (\n <EntitySection title=\"System Activity\">\n <div className=\"space-y-4\">\n <EntityActivityItem\n title={<><span className=\"font-medium\">System</span> enriched the lead</>}\n date=\"Today at 10:15 AM\"\n />\n <EntityActivityItem\n icon={<Mail className=\"w-4 h-4\" />}\n title={<><span className=\"font-medium\">Jackie Lee</span> submitted website form</>}\n date=\"Yesterday at 3:22 PM\"\n />\n </div>\n </EntitySection>\n )\n}\n\n// ---------------------------------------------------------------------------\n// PotentialContacts – unchanged from original\n// ---------------------------------------------------------------------------\n\nexport interface EntityPanelBrandIcons {\n linkedin?: string\n gmail?: string\n slack?: string\n gdoc?: string\n}\n\nfunction EntityPanelBrandIcon({\n src,\n alt,\n className,\n fallback,\n}: {\n src?: string\n alt: string\n className: string\n fallback: React.ReactNode\n}) {\n if (!src) {\n return <>{fallback}</>\n }\n\n return <img src={src} alt={alt} className={className} />\n}\n\nexport function PotentialContacts({\n icons,\n}: {\n icons?: Pick<EntityPanelBrandIcons, \"linkedin\" | \"gmail\">\n}) {\n return (\n <div className=\"space-y-2.5 mb-6\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-[13px] font-semibold text-foreground\">Potential Contacts</h3>\n <span className=\"text-xs text-muted-foreground\">3 identified</span>\n </div>\n <div className=\"space-y-0\">\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <Badge variant=\"outline\" className=\"bg-indigo-50 text-indigo-700 border-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-300 dark:border-indigo-800 shadow-none px-2 py-0 text-[11px] font-medium shrink-0\">Primary</Badge>\n <span className=\"font-medium text-sm text-foreground truncate\">Jackie Lee</span>\n <span className=\"text-muted-foreground text-sm shrink-0\">·</span>\n <span className=\"text-muted-foreground text-sm truncate\">VP Finance</span>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.linkedin}\n alt=\"LinkedIn\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<LinkIcon className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.gmail}\n alt=\"Gmail\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<Mail className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <Button size=\"sm\" className=\"bg-foreground text-background hover:bg-foreground/90 h-6 text-[11px] font-medium shadow-none ml-1\">\n <Plus className=\"w-3 h-3 mr-1\" /> Add to SF\n </Button>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <Badge variant=\"outline\" className=\"bg-green-50 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-300 dark:border-green-800 shadow-none px-2 py-0 text-[11px] font-medium shrink-0\">78%</Badge>\n <span className=\"font-medium text-sm text-foreground truncate\">Marcus Webb</span>\n <span className=\"text-muted-foreground text-sm shrink-0\">·</span>\n <span className=\"text-muted-foreground text-sm truncate\">CEO</span>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.linkedin}\n alt=\"LinkedIn\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<LinkIcon className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <Button size=\"sm\" className=\"bg-foreground text-background hover:bg-foreground/90 h-6 text-[11px] font-medium shadow-none ml-1\">\n <Plus className=\"w-3 h-3 mr-1\" /> Add to SF\n </Button>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 last:border-0 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <Badge variant=\"outline\" className=\"bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-800 shadow-none px-2 py-0 text-[11px] font-medium shrink-0\">65%</Badge>\n <span className=\"font-medium text-sm text-foreground truncate\">Priya Shah</span>\n <span className=\"text-muted-foreground text-sm shrink-0\">·</span>\n <span className=\"text-muted-foreground text-sm truncate\">Head of Ops</span>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.linkedin}\n alt=\"LinkedIn\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<LinkIcon className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.gmail}\n alt=\"Gmail\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<Mail className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <Button size=\"sm\" className=\"bg-foreground text-background hover:bg-foreground/90 h-6 text-[11px] font-medium shadow-none ml-1\">\n <Plus className=\"w-3 h-3 mr-1\" /> Add to SF\n </Button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// RecentActivity\n// ---------------------------------------------------------------------------\n\nexport type ActivityItem = TimelineEvent\n\nexport function RecentActivity({\n title = \"Recent Activity\",\n count = \"10 total events\",\n filters = [],\n items = [],\n}: {\n title?: string\n count?: string\n filters?: string[]\n items?: TimelineEvent[]\n}) {\n return (\n <div id=\"entity-recent-activity\" className=\"space-y-3 mb-6 scroll-m-20\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-[13px] font-semibold text-foreground\">{title}</h3>\n {count && <span className=\"text-xs text-muted-foreground\">{count}</span>}\n </div>\n\n {filters.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filters.map((filter) => (\n <Button\n key={filter}\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 text-xs rounded-md shadow-none font-medium border-border text-muted-foreground hover:text-foreground\"\n >\n {filter}\n </Button>\n ))}\n </div>\n )}\n\n <div className=\"relative\">\n <Input\n placeholder=\"Search activity...\"\n className=\"h-9 text-sm bg-background border-border shadow-none\"\n />\n </div>\n\n <div>\n <TimelineActivity events={items} />\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// ConnectedApps\n// ---------------------------------------------------------------------------\n\nexport function ConnectedApps({\n icons,\n}: {\n icons?: Pick<EntityPanelBrandIcons, \"slack\" | \"gdoc\">\n}) {\n return (\n <div className=\"space-y-2.5 mb-6\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-[13px] font-semibold text-foreground\">Connected Apps</h3>\n <span className=\"text-xs text-muted-foreground\">3 connected</span>\n </div>\n\n <div className=\"space-y-0\">\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-md border border-border/60 bg-muted/30 flex items-center justify-center shrink-0\">\n <EntityPanelBrandIcon\n src={icons?.slack}\n alt=\"Slack\"\n className=\"w-4 h-4 object-contain\"\n fallback={<MessageCircle className=\"w-4 h-4 text-muted-foreground\" />}\n />\n </div>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-sm text-foreground leading-snug truncate\">#lunchclub-acmeco</p>\n <p className=\"text-xs text-muted-foreground/60\">Slack Channel</p>\n </div>\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n <ExternalLink className=\"w-3 h-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\" />\n <span className=\"text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer hover:text-foreground\">Open</span>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-md border border-border/60 bg-muted/30 flex items-center justify-center shrink-0\">\n <EntityPanelBrandIcon\n src={icons?.gdoc}\n alt=\"Google Docs\"\n className=\"w-4 h-4 object-contain\"\n fallback={<FileText className=\"w-4 h-4 text-muted-foreground\" />}\n />\n </div>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-sm text-foreground leading-snug truncate\">Account Strategy Document</p>\n <p className=\"text-xs text-muted-foreground/60\">Google Document</p>\n </div>\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n <ExternalLink className=\"w-3 h-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\" />\n <span className=\"text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer hover:text-foreground\">Open</span>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 last:border-0 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-md border border-border/60 bg-muted/30 flex items-center justify-center shrink-0\">\n <FileText className=\"w-4 h-4 text-foreground\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-sm text-foreground leading-snug truncate\">Customer Success Playbook</p>\n <p className=\"text-xs text-muted-foreground/60\">Notion Page</p>\n </div>\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n <ExternalLink className=\"w-3 h-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\" />\n <span className=\"text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer hover:text-foreground\">Open</span>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityDetails – updated with MeetingDetail-inspired metadata grid + tabs\n// ---------------------------------------------------------------------------\n\nexport function EntityDetails({ onClose: _onClose }: { onClose?: () => void }) {\n const [activeTab, setActiveTab] = React.useState<\"overview\" | \"details\">(\"overview\")\n const [showMore, setShowMore] = React.useState(false)\n\n const leadFields: EntityMetadataField[] = [\n { icon: Users, label: \"Lead Name\", value: <span className=\"font-medium\">Jackie Lee</span> },\n { icon: Briefcase, label: \"Title\", value: <span className=\"font-medium\">VP Finance</span> },\n { icon: Building2, label: \"Company\", value: <span className=\"font-medium\">CloudKitchen</span> },\n { icon: Mail, label: \"Lead Source\", value: <span className=\"font-medium\">Inbound — Website form</span> },\n {\n icon: ({ className }) => (\n <div className={className}>\n <div className=\"w-3 h-3 rounded-full border-[2px] border-amber-500\" />\n </div>\n ),\n label: \"Lead Status\",\n value: (\n <Badge variant=\"outline\" className=\"text-amber-700 border-amber-200 bg-amber-50 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800 shadow-none font-medium px-2 py-0 text-[11px]\">\n New — Not Contacted\n </Badge>\n ),\n },\n { icon: Users, label: \"Lead Owner\", value: <span className=\"font-medium\">Sarah Johnson (SDR)</span> },\n {\n icon: Building2,\n label: \"Industry\",\n value: (\n <Badge variant=\"outline\" className=\"text-blue-700 border-blue-200 bg-blue-50 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800 shadow-none font-medium px-2 py-0 text-[11px]\">\n Food Tech / Logistics\n </Badge>\n ),\n },\n { icon: Users, label: \"Company Size\", value: <span className=\"font-medium\">200-500 employees</span> },\n ]\n\n const visibleFields = showMore ? leadFields : leadFields.slice(0, 6)\n\n return (\n <div className=\"space-y-0\">\n {/* Header */}\n <EntityPanelHeader\n icon={\n <div className=\"w-10 h-10 rounded-lg bg-muted flex items-center justify-center text-sm font-medium text-muted-foreground shrink-0\">\n CK\n </div>\n }\n title=\"CloudKitchen\"\n badgeLabel=\"Lead\"\n subtitle=\"Last enriched: Today at 10:15 AM\"\n />\n\n {/* Tabs */}\n <EntityPanelTabs\n tabs={[\n { id: \"overview\", label: \"Overview\" },\n { id: \"details\", label: \"Details\" },\n ]}\n activeTab={activeTab}\n onTabChange={(id) => setActiveTab(id as \"overview\" | \"details\")}\n />\n\n {activeTab === \"overview\" ? (\n <div className=\"space-y-0\">\n {/* Metadata Grid */}\n <EntityMetadataGrid fields={visibleFields} />\n\n {leadFields.length > 6 && (\n <button\n onClick={() => setShowMore(!showMore)}\n className=\"flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors mb-6\"\n >\n {showMore ? \"See less\" : \"See more\"}\n {showMore ? <ChevronUp className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </button>\n )}\n\n {/* Enrichment as sections */}\n <EntitySection title=\"Company Signals\">\n <ul className=\"space-y-2\">\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Recent funding: $45M Series B, 3 months ago\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">1</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Hiring: 3 finance/treasury roles in last 30 days\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">2</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Market expansion: 8 → 15 US markets planned\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">3</span>\n </span>\n </li>\n </ul>\n </EntitySection>\n\n <EntitySection title=\"Contact Signals (Jackie Lee)\">\n <ul className=\"space-y-2\">\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Started role: 12 days ago\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">4</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Previous: Deel — operations/finance\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">4</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n LinkedIn connections to existing customers: 2 detected\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">4</span>\n </span>\n </li>\n </ul>\n </EntitySection>\n\n <SourcesToggle />\n </div>\n ) : (\n <div className=\"space-y-0\">\n <EntitySection title=\"Estimated Tech Stack\">\n <div className=\"space-y-2\">\n <div className=\"flex items-start gap-2 text-sm\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span className=\"text-muted-foreground min-w-[100px] shrink-0\">Banking:</span>\n <span className=\"text-muted-foreground/50 italic\">Unknown</span>\n </div>\n <div className=\"flex items-start gap-2 text-sm\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span className=\"text-muted-foreground min-w-[100px] shrink-0\">Corporate Cards:</span>\n <span className=\"text-foreground font-medium\">\n Brex <span className=\"text-muted-foreground font-normal\">(from job posting requirements)</span>\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">2</span>\n </span>\n </div>\n <div className=\"flex items-start gap-2 text-sm\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span className=\"text-muted-foreground min-w-[100px] shrink-0\">Payroll:</span>\n <span className=\"text-foreground font-medium\">\n Gusto <span className=\"text-muted-foreground font-normal\">(from LinkedIn integrations)</span>\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">5</span>\n </span>\n </div>\n </div>\n </EntitySection>\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SourcesToggle – collapsible sources list\n// ---------------------------------------------------------------------------\n\nfunction SourcesToggle() {\n const [expanded, setExpanded] = React.useState(false)\n\n const sources = [\n { name: \"Crunchbase\", type: \"Funding data\", lastPull: \"2h ago\" },\n { name: \"LinkedIn\", type: \"People & company\", lastPull: \"12h ago\" },\n { name: \"LinkedIn Jobs\", type: \"Hiring signals\", lastPull: \"1d ago\" },\n { name: \"PR Newswire\", type: \"News & press\", lastPull: \"6h ago\" },\n { name: \"Clearbit\", type: \"Tech stack & firmographics\", lastPull: \"2h ago\" },\n ]\n\n return (\n <div className=\"mb-6\">\n <button\n onClick={() => setExpanded(!expanded)}\n className=\"flex items-center gap-1.5 text-xs font-semibold text-muted-foreground hover:text-foreground transition-colors\"\n >\n Sources\n <ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${expanded ? \"rotate-180\" : \"\"}`} />\n </button>\n\n {expanded && (\n <div className=\"pt-3 space-y-2 animate-in fade-in slide-in-from-top-1 duration-200\">\n {sources.map((src, idx) => (\n <div key={idx} className=\"flex items-center justify-between text-xs text-muted-foreground py-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"inline-flex items-center justify-center w-4 h-4 text-[9px] font-medium text-muted-foreground/50 border border-border rounded-full\">\n {idx + 1}\n </span>\n <span className=\"font-medium text-foreground\">{src.name}</span>\n <span className=\"text-muted-foreground/60\">·</span>\n <span>{src.type}</span>\n </div>\n <span className=\"text-muted-foreground/50\">{src.lastPull}</span>\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/entity-panel.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Sheet, SheetContent, SheetHeader, SheetTitle } from \"./sheet\"\nimport { Badge } from \"./badge\"\nimport { Button } from \"./button\"\nimport { Input } from \"./input\"\nimport {\n Plus,\n ExternalLink,\n Mail,\n FileText,\n MessageCircle,\n Briefcase,\n Building2,\n Users,\n X,\n ChevronDown,\n ChevronUp,\n Link as LinkIcon,\n Maximize2,\n Minimize2,\n CalendarDays,\n} from \"lucide-react\"\nimport { TimelineActivity, type TimelineEvent } from \"./timeline-activity\"\n\n// ---------------------------------------------------------------------------\n// EntityPanel -- supports Sheet (side panel), wide, and fullscreen modes\n// ---------------------------------------------------------------------------\n\nexport type PanelMode = 'default' | 'wide' | 'fullscreen'\n\nexport function EntityPanel({\n isOpen,\n onClose,\n children,\n}: {\n isOpen: boolean\n onClose: (open: boolean) => void\n children?: React.ReactNode\n}) {\n const [panelMode, setPanelMode] = React.useState<PanelMode>('default')\n\n // Backward-compatible derived values\n const isFullscreen = panelMode === 'fullscreen'\n const setIsFullscreen = React.useCallback(\n (val: boolean) => setPanelMode(val ? 'fullscreen' : 'default'),\n [],\n )\n\n const cyclePanelMode = React.useCallback(() => {\n setPanelMode((prev) =>\n prev === 'default' ? 'wide' : prev === 'wide' ? 'fullscreen' : 'default',\n )\n }, [])\n\n React.useEffect(() => {\n if (!isOpen) setPanelMode('default')\n }, [isOpen])\n\n const handleClose = React.useCallback(() => {\n setPanelMode('default')\n onClose(false)\n }, [onClose])\n\n const panelContent = (\n <EntityPanelContext.Provider\n value={{\n isFullscreen,\n setIsFullscreen,\n panelMode,\n setPanelMode,\n cyclePanelMode,\n onClose: handleClose,\n }}\n >\n {children}\n </EntityPanelContext.Provider>\n )\n\n if (isFullscreen && isOpen) {\n return (\n <div className=\"fixed inset-0 z-50 flex flex-col overflow-hidden bg-background\">\n <div className=\"flex-1 overflow-y-auto px-5 py-5\">{panelContent}</div>\n </div>\n )\n }\n\n const widthClass =\n panelMode === 'wide'\n ? 'sm:w-[800px] sm:max-w-[900px]'\n : 'sm:w-[500px] sm:max-w-[600px]'\n\n return (\n <Sheet open={isOpen} onOpenChange={onClose}>\n <SheetContent\n side=\"right\"\n className={`w-full ${widthClass} overflow-hidden p-0 bg-background border-l border-border flex flex-col`}\n showCloseButton={false}\n >\n <SheetHeader className=\"sr-only p-0\">\n <SheetTitle>Entity panel</SheetTitle>\n </SheetHeader>\n <div className=\"flex-1 overflow-y-auto px-5 py-5\">{panelContent}</div>\n </SheetContent>\n </Sheet>\n )\n}\n\nconst EntityPanelContext = React.createContext<{\n isFullscreen: boolean\n setIsFullscreen: (v: boolean) => void\n panelMode: PanelMode\n setPanelMode: (mode: PanelMode) => void\n cyclePanelMode: () => void\n onClose: () => void\n}>({\n isFullscreen: false,\n setIsFullscreen: () => {},\n panelMode: 'default',\n setPanelMode: () => {},\n cyclePanelMode: () => {},\n onClose: () => {},\n})\n\nexport function useEntityPanel() {\n return React.useContext(EntityPanelContext)\n}\n\n// ---------------------------------------------------------------------------\n// EntityPanelHeader – MeetingDetail-inspired header bar\n// ---------------------------------------------------------------------------\n\nexport function EntityPanelHeader({\n icon,\n title,\n badgeLabel,\n subtitle,\n headerAction,\n headerSecondaryAction,\n}: {\n icon?: React.ReactNode\n title: string\n badgeLabel?: string\n subtitle?: string\n headerAction?: React.ReactNode\n headerSecondaryAction?: React.ReactNode\n}) {\n const { panelMode, cyclePanelMode, onClose } = useEntityPanel()\n\n const sizeButtonTitle =\n panelMode === 'default' ? 'Wide' : panelMode === 'wide' ? 'Fullscreen' : 'Exit fullscreen'\n\n return (\n <div className=\"mb-3 space-y-2\">\n <div className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2 min-w-0\">\n {icon ?? <CalendarDays className=\"w-5 h-5 text-muted-foreground shrink-0\" />}\n <h2 className=\"text-[16px] font-semibold text-foreground truncate\">{title}</h2>\n {badgeLabel && (\n <Badge\n variant=\"outline\"\n className=\"text-blue-600 border-blue-300 dark:border-blue-700 dark:text-blue-400 shadow-none px-2 py-0.5 text-[11px] font-medium shrink-0\"\n >\n {badgeLabel}\n </Badge>\n )}\n </div>\n <div className=\"flex items-center gap-1 shrink-0 ml-4 text-muted-foreground\">\n {headerAction}\n <button\n type=\"button\"\n className=\"p-1.5 rounded-md hover:bg-secondary transition-colors\"\n title=\"Copy Link\"\n >\n <LinkIcon className=\"w-4 h-4\" />\n </button>\n <button\n type=\"button\"\n onClick={cyclePanelMode}\n className=\"p-1.5 rounded-md hover:bg-secondary transition-colors\"\n title={sizeButtonTitle}\n >\n {panelMode === 'fullscreen' ? (\n <Minimize2 className=\"w-4 h-4\" />\n ) : (\n <Maximize2 className=\"w-4 h-4\" />\n )}\n </button>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1.5 rounded-md hover:bg-secondary transition-colors\"\n title=\"Close\"\n >\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n {(subtitle || headerSecondaryAction) && (\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-2\">\n {subtitle ? (\n <p className=\"min-w-0 flex-1 text-xs text-muted-foreground\">{subtitle}</p>\n ) : (\n <div className=\"min-w-0 flex-1\" />\n )}\n {headerSecondaryAction ? (\n <div className=\"flex shrink-0 items-center gap-2\">{headerSecondaryAction}</div>\n ) : null}\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityPanelTabs – Overview/Details tab bar\n// ---------------------------------------------------------------------------\n\nexport function EntityPanelTabs({\n tabs,\n activeTab,\n onTabChange,\n}: {\n tabs: { id: string; label: string }[]\n activeTab: string\n onTabChange: (id: string) => void\n}) {\n return (\n <div className=\"flex items-center gap-5 border-b border-border mb-4 overflow-x-auto\">\n {tabs.map((tab) => (\n <button\n key={tab.id}\n type=\"button\"\n onClick={() => onTabChange(tab.id)}\n className={`whitespace-nowrap shrink-0 pb-2.5 text-[13px] font-medium border-b-2 transition-colors ${\n activeTab === tab.id\n ? \"border-primary text-foreground\"\n : \"border-transparent text-muted-foreground hover:text-foreground\"\n }`}\n >\n {tab.label}\n </button>\n ))}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityMetadataGrid – key/value metadata rows with icons\n// ---------------------------------------------------------------------------\n\nexport interface EntityMetadataField {\n icon: React.ComponentType<{ className?: string }>\n label: string\n value: React.ReactNode\n}\n\nexport function EntityMetadataGrid({ fields }: { fields: EntityMetadataField[] }) {\n return (\n <div className=\"mb-7 grid min-w-0 grid-cols-1 gap-x-4 gap-y-3 overflow-hidden text-[13px] md:grid-cols-[minmax(0,140px)_minmax(0,1fr)]\">\n {fields.map((field, idx) => (\n <React.Fragment key={idx}>\n <div className=\"flex min-w-0 items-start gap-1.5 text-[13px] font-normal text-muted-foreground\">\n <field.icon className=\"mt-0.5 h-3.5 w-3.5 shrink-0\" />\n <span className=\"min-w-0 break-words leading-relaxed [overflow-wrap:anywhere]\">{field.label}</span>\n </div>\n <div className=\"min-w-0 whitespace-normal break-words leading-relaxed text-foreground [overflow-wrap:anywhere] [&_*]:max-w-full [&_*]:[overflow-wrap:anywhere] [&_a]:break-all\">\n {field.value}\n </div>\n </React.Fragment>\n ))}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntitySection – clean section with title (MeetingDetail-style)\n// ---------------------------------------------------------------------------\n\nexport function EntitySection({\n title,\n children,\n action,\n}: {\n title: string\n children: React.ReactNode\n action?: React.ReactNode\n}) {\n return (\n <section className=\"mb-7\">\n <div className=\"flex items-center justify-between mb-3\">\n <h3 className=\"text-[12px] font-semibold text-muted-foreground tracking-wide\">{title}</h3>\n {action}\n </div>\n {children}\n </section>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityActivityItem – clean activity row (MeetingDetail-style)\n// ---------------------------------------------------------------------------\n\nexport function EntityActivityItem({\n icon,\n title,\n description,\n date,\n}: {\n icon?: React.ReactNode\n title: React.ReactNode\n description?: React.ReactNode\n date?: string\n}) {\n return (\n <div className=\"flex gap-3 text-[13px]\">\n <div className=\"mt-0.5 text-muted-foreground shrink-0\">\n {icon ?? <CalendarDays className=\"w-4 h-4\" />}\n </div>\n <div>\n <p className=\"text-foreground leading-relaxed\">{title}</p>\n {description && <p className=\"text-[11px] text-muted-foreground/70 mt-0.5\">{description}</p>}\n {date && <p className=\"text-[11px] text-muted-foreground/70 mt-0.5\">{date}</p>}\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SystemActivity – standalone section for bottom of entity panel\n// ---------------------------------------------------------------------------\n\nexport function SystemActivity() {\n return (\n <EntitySection title=\"System Activity\">\n <div className=\"space-y-4\">\n <EntityActivityItem\n title={<><span className=\"font-medium\">System</span> enriched the lead</>}\n date=\"Today at 10:15 AM\"\n />\n <EntityActivityItem\n icon={<Mail className=\"w-4 h-4\" />}\n title={<><span className=\"font-medium\">Jackie Lee</span> submitted website form</>}\n date=\"Yesterday at 3:22 PM\"\n />\n </div>\n </EntitySection>\n )\n}\n\n// ---------------------------------------------------------------------------\n// PotentialContacts – unchanged from original\n// ---------------------------------------------------------------------------\n\nexport interface EntityPanelBrandIcons {\n linkedin?: string\n gmail?: string\n slack?: string\n gdoc?: string\n}\n\nfunction EntityPanelBrandIcon({\n src,\n alt,\n className,\n fallback,\n}: {\n src?: string\n alt: string\n className: string\n fallback: React.ReactNode\n}) {\n if (!src) {\n return <>{fallback}</>\n }\n\n return <img src={src} alt={alt} className={className} />\n}\n\nexport function PotentialContacts({\n icons,\n}: {\n icons?: Pick<EntityPanelBrandIcons, \"linkedin\" | \"gmail\">\n}) {\n return (\n <div className=\"space-y-2.5 mb-6\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-[13px] font-semibold text-foreground\">Potential Contacts</h3>\n <span className=\"text-xs text-muted-foreground\">3 identified</span>\n </div>\n <div className=\"space-y-0\">\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <Badge variant=\"outline\" className=\"bg-indigo-50 text-indigo-700 border-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-300 dark:border-indigo-800 shadow-none px-2 py-0 text-[11px] font-medium shrink-0\">Primary</Badge>\n <span className=\"font-medium text-sm text-foreground truncate\">Jackie Lee</span>\n <span className=\"text-muted-foreground text-sm shrink-0\">·</span>\n <span className=\"text-muted-foreground text-sm truncate\">VP Finance</span>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.linkedin}\n alt=\"LinkedIn\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<LinkIcon className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.gmail}\n alt=\"Gmail\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<Mail className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <Button size=\"sm\" className=\"bg-foreground text-background hover:bg-foreground/90 h-6 text-[11px] font-medium shadow-none ml-1\">\n <Plus className=\"w-3 h-3 mr-1\" /> Add to SF\n </Button>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <Badge variant=\"outline\" className=\"bg-green-50 text-green-700 border-green-200 dark:bg-green-900/30 dark:text-green-300 dark:border-green-800 shadow-none px-2 py-0 text-[11px] font-medium shrink-0\">78%</Badge>\n <span className=\"font-medium text-sm text-foreground truncate\">Marcus Webb</span>\n <span className=\"text-muted-foreground text-sm shrink-0\">·</span>\n <span className=\"text-muted-foreground text-sm truncate\">CEO</span>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.linkedin}\n alt=\"LinkedIn\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<LinkIcon className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <Button size=\"sm\" className=\"bg-foreground text-background hover:bg-foreground/90 h-6 text-[11px] font-medium shadow-none ml-1\">\n <Plus className=\"w-3 h-3 mr-1\" /> Add to SF\n </Button>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 last:border-0 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <Badge variant=\"outline\" className=\"bg-amber-50 text-amber-700 border-amber-200 dark:bg-amber-900/30 dark:text-amber-300 dark:border-amber-800 shadow-none px-2 py-0 text-[11px] font-medium shrink-0\">65%</Badge>\n <span className=\"font-medium text-sm text-foreground truncate\">Priya Shah</span>\n <span className=\"text-muted-foreground text-sm shrink-0\">·</span>\n <span className=\"text-muted-foreground text-sm truncate\">Head of Ops</span>\n </div>\n <div className=\"flex items-center gap-1 shrink-0\">\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.linkedin}\n alt=\"LinkedIn\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<LinkIcon className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <button className=\"h-7 w-7 flex items-center justify-center hover:bg-muted rounded-md transition-colors\">\n <EntityPanelBrandIcon\n src={icons?.gmail}\n alt=\"Gmail\"\n className=\"w-3.5 h-3.5 object-contain\"\n fallback={<Mail className=\"w-3.5 h-3.5 text-muted-foreground\" />}\n />\n </button>\n <Button size=\"sm\" className=\"bg-foreground text-background hover:bg-foreground/90 h-6 text-[11px] font-medium shadow-none ml-1\">\n <Plus className=\"w-3 h-3 mr-1\" /> Add to SF\n </Button>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// RecentActivity\n// ---------------------------------------------------------------------------\n\nexport type ActivityItem = TimelineEvent\n\nexport function RecentActivity({\n title = \"Recent Activity\",\n count = \"10 total events\",\n filters = [],\n items = [],\n}: {\n title?: string\n count?: string\n filters?: string[]\n items?: TimelineEvent[]\n}) {\n return (\n <div id=\"entity-recent-activity\" className=\"space-y-3 mb-6 scroll-m-20\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-[13px] font-semibold text-foreground\">{title}</h3>\n {count && <span className=\"text-xs text-muted-foreground\">{count}</span>}\n </div>\n\n {filters.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {filters.map((filter) => (\n <Button\n key={filter}\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7 text-xs rounded-md shadow-none font-medium border-border text-muted-foreground hover:text-foreground\"\n >\n {filter}\n </Button>\n ))}\n </div>\n )}\n\n <div className=\"relative\">\n <Input\n placeholder=\"Search activity...\"\n className=\"h-9 text-sm bg-background border-border shadow-none\"\n />\n </div>\n\n <div>\n <TimelineActivity events={items} />\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// ConnectedApps\n// ---------------------------------------------------------------------------\n\nexport function ConnectedApps({\n icons,\n}: {\n icons?: Pick<EntityPanelBrandIcons, \"slack\" | \"gdoc\">\n}) {\n return (\n <div className=\"space-y-2.5 mb-6\">\n <div className=\"flex items-center justify-between\">\n <h3 className=\"text-[13px] font-semibold text-foreground\">Connected Apps</h3>\n <span className=\"text-xs text-muted-foreground\">3 connected</span>\n </div>\n\n <div className=\"space-y-0\">\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-md border border-border/60 bg-muted/30 flex items-center justify-center shrink-0\">\n <EntityPanelBrandIcon\n src={icons?.slack}\n alt=\"Slack\"\n className=\"w-4 h-4 object-contain\"\n fallback={<MessageCircle className=\"w-4 h-4 text-muted-foreground\" />}\n />\n </div>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-sm text-foreground leading-snug truncate\">#lunchclub-acmeco</p>\n <p className=\"text-xs text-muted-foreground/60\">Slack Channel</p>\n </div>\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n <ExternalLink className=\"w-3 h-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\" />\n <span className=\"text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer hover:text-foreground\">Open</span>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-md border border-border/60 bg-muted/30 flex items-center justify-center shrink-0\">\n <EntityPanelBrandIcon\n src={icons?.gdoc}\n alt=\"Google Docs\"\n className=\"w-4 h-4 object-contain\"\n fallback={<FileText className=\"w-4 h-4 text-muted-foreground\" />}\n />\n </div>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-sm text-foreground leading-snug truncate\">Account Strategy Document</p>\n <p className=\"text-xs text-muted-foreground/60\">Google Document</p>\n </div>\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n <ExternalLink className=\"w-3 h-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\" />\n <span className=\"text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer hover:text-foreground\">Open</span>\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 group py-2 border-b border-border/30 last:border-0 hover:bg-muted/20 -mx-2 px-2 rounded-sm transition-colors\">\n <div className=\"flex items-center gap-3 min-w-0\">\n <div className=\"w-8 h-8 rounded-md border border-border/60 bg-muted/30 flex items-center justify-center shrink-0\">\n <FileText className=\"w-4 h-4 text-foreground\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"font-medium text-sm text-foreground leading-snug truncate\">Customer Success Playbook</p>\n <p className=\"text-xs text-muted-foreground/60\">Notion Page</p>\n </div>\n </div>\n <div className=\"flex items-center gap-1.5 shrink-0\">\n <ExternalLink className=\"w-3 h-3 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity\" />\n <span className=\"text-xs text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer hover:text-foreground\">Open</span>\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// EntityDetails – updated with MeetingDetail-inspired metadata grid + tabs\n// ---------------------------------------------------------------------------\n\nexport function EntityDetails({ onClose: _onClose }: { onClose?: () => void }) {\n const [activeTab, setActiveTab] = React.useState<\"overview\" | \"details\">(\"overview\")\n const [showMore, setShowMore] = React.useState(false)\n\n const leadFields: EntityMetadataField[] = [\n { icon: Users, label: \"Lead Name\", value: <span className=\"font-medium\">Jackie Lee</span> },\n { icon: Briefcase, label: \"Title\", value: <span className=\"font-medium\">VP Finance</span> },\n { icon: Building2, label: \"Company\", value: <span className=\"font-medium\">CloudKitchen</span> },\n { icon: Mail, label: \"Lead Source\", value: <span className=\"font-medium\">Inbound — Website form</span> },\n {\n icon: ({ className }) => (\n <div className={className}>\n <div className=\"w-3 h-3 rounded-full border-[2px] border-amber-500\" />\n </div>\n ),\n label: \"Lead Status\",\n value: (\n <Badge variant=\"outline\" className=\"text-amber-700 border-amber-200 bg-amber-50 dark:bg-amber-950 dark:text-amber-300 dark:border-amber-800 shadow-none font-medium px-2 py-0 text-[11px]\">\n New — Not Contacted\n </Badge>\n ),\n },\n { icon: Users, label: \"Lead Owner\", value: <span className=\"font-medium\">Sarah Johnson (SDR)</span> },\n {\n icon: Building2,\n label: \"Industry\",\n value: (\n <Badge variant=\"outline\" className=\"text-blue-700 border-blue-200 bg-blue-50 dark:bg-blue-950 dark:text-blue-300 dark:border-blue-800 shadow-none font-medium px-2 py-0 text-[11px]\">\n Food Tech / Logistics\n </Badge>\n ),\n },\n { icon: Users, label: \"Company Size\", value: <span className=\"font-medium\">200-500 employees</span> },\n ]\n\n const visibleFields = showMore ? leadFields : leadFields.slice(0, 6)\n\n return (\n <div className=\"space-y-0\">\n {/* Header */}\n <EntityPanelHeader\n icon={\n <div className=\"w-10 h-10 rounded-lg bg-muted flex items-center justify-center text-sm font-medium text-muted-foreground shrink-0\">\n CK\n </div>\n }\n title=\"CloudKitchen\"\n badgeLabel=\"Lead\"\n subtitle=\"Last enriched: Today at 10:15 AM\"\n />\n\n {/* Tabs */}\n <EntityPanelTabs\n tabs={[\n { id: \"overview\", label: \"Overview\" },\n { id: \"details\", label: \"Details\" },\n ]}\n activeTab={activeTab}\n onTabChange={(id) => setActiveTab(id as \"overview\" | \"details\")}\n />\n\n {activeTab === \"overview\" ? (\n <div className=\"space-y-0\">\n {/* Metadata Grid */}\n <EntityMetadataGrid fields={visibleFields} />\n\n {leadFields.length > 6 && (\n <button\n onClick={() => setShowMore(!showMore)}\n className=\"flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors mb-6\"\n >\n {showMore ? \"See less\" : \"See more\"}\n {showMore ? <ChevronUp className=\"w-3 h-3\" /> : <ChevronDown className=\"w-3 h-3\" />}\n </button>\n )}\n\n {/* Enrichment as sections */}\n <EntitySection title=\"Company Signals\">\n <ul className=\"space-y-2\">\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Recent funding: $45M Series B, 3 months ago\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">1</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Hiring: 3 finance/treasury roles in last 30 days\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">2</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Market expansion: 8 → 15 US markets planned\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">3</span>\n </span>\n </li>\n </ul>\n </EntitySection>\n\n <EntitySection title=\"Contact Signals (Jackie Lee)\">\n <ul className=\"space-y-2\">\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Started role: 12 days ago\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">4</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n Previous: Deel — operations/finance\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">4</span>\n </span>\n </li>\n <li className=\"flex items-start gap-2 text-sm text-muted-foreground\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span>\n LinkedIn connections to existing customers: 2 detected\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">4</span>\n </span>\n </li>\n </ul>\n </EntitySection>\n\n <SourcesToggle />\n </div>\n ) : (\n <div className=\"space-y-0\">\n <EntitySection title=\"Estimated Tech Stack\">\n <div className=\"space-y-2\">\n <div className=\"flex items-start gap-2 text-sm\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span className=\"text-muted-foreground min-w-[100px] shrink-0\">Banking:</span>\n <span className=\"text-muted-foreground/50 italic\">Unknown</span>\n </div>\n <div className=\"flex items-start gap-2 text-sm\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span className=\"text-muted-foreground min-w-[100px] shrink-0\">Corporate Cards:</span>\n <span className=\"text-foreground font-medium\">\n Brex <span className=\"text-muted-foreground font-normal\">(from job posting requirements)</span>\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">2</span>\n </span>\n </div>\n <div className=\"flex items-start gap-2 text-sm\">\n <span className=\"text-muted-foreground/50 mt-1 shrink-0\">•</span>\n <span className=\"text-muted-foreground min-w-[100px] shrink-0\">Payroll:</span>\n <span className=\"text-foreground font-medium\">\n Gusto <span className=\"text-muted-foreground font-normal\">(from LinkedIn integrations)</span>\n <span className=\"inline-flex items-center justify-center w-4 h-4 ml-1.5 align-text-top text-[9px] font-medium text-muted-foreground bg-muted/30 border border-border/50 rounded-full\">5</span>\n </span>\n </div>\n </div>\n </EntitySection>\n </div>\n )}\n </div>\n )\n}\n\n// ---------------------------------------------------------------------------\n// SourcesToggle – collapsible sources list\n// ---------------------------------------------------------------------------\n\nfunction SourcesToggle() {\n const [expanded, setExpanded] = React.useState(false)\n\n const sources = [\n { name: \"Crunchbase\", type: \"Funding data\", lastPull: \"2h ago\" },\n { name: \"LinkedIn\", type: \"People & company\", lastPull: \"12h ago\" },\n { name: \"LinkedIn Jobs\", type: \"Hiring signals\", lastPull: \"1d ago\" },\n { name: \"PR Newswire\", type: \"News & press\", lastPull: \"6h ago\" },\n { name: \"Clearbit\", type: \"Tech stack & firmographics\", lastPull: \"2h ago\" },\n ]\n\n return (\n <div className=\"mb-6\">\n <button\n onClick={() => setExpanded(!expanded)}\n className=\"flex items-center gap-1.5 text-xs font-semibold text-muted-foreground hover:text-foreground transition-colors\"\n >\n Sources\n <ChevronDown className={`w-3.5 h-3.5 transition-transform duration-200 ${expanded ? \"rotate-180\" : \"\"}`} />\n </button>\n\n {expanded && (\n <div className=\"pt-3 space-y-2 animate-in fade-in slide-in-from-top-1 duration-200\">\n {sources.map((src, idx) => (\n <div key={idx} className=\"flex items-center justify-between text-xs text-muted-foreground py-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"inline-flex items-center justify-center w-4 h-4 text-[9px] font-medium text-muted-foreground/50 border border-border rounded-full\">\n {idx + 1}\n </span>\n <span className=\"font-medium text-foreground\">{src.name}</span>\n <span className=\"text-muted-foreground/60\">·</span>\n <span>{src.type}</span>\n </div>\n <span className=\"text-muted-foreground/50\">{src.lastPull}</span>\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
@@ -280,10 +280,10 @@ function VirtualizedDataTable({
|
|
|
280
280
|
onMouseDown: header.getResizeHandler(),
|
|
281
281
|
onTouchStart: header.getResizeHandler(),
|
|
282
282
|
className: cn(
|
|
283
|
-
"absolute right-0 top-0 h-full w-
|
|
284
|
-
"after:absolute after:right-
|
|
285
|
-
"after:bg-
|
|
286
|
-
header.column.getIsResizing() && "after:bg-primary/
|
|
283
|
+
"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none",
|
|
284
|
+
"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full",
|
|
285
|
+
"after:bg-border/70 after:transition-colors hover:after:bg-primary/60",
|
|
286
|
+
header.column.getIsResizing() && "after:bg-primary/70"
|
|
287
287
|
),
|
|
288
288
|
role: "separator",
|
|
289
289
|
"aria-orientation": "vertical"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/components/virtualized-data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\nimport {\n useReactTable,\n getCoreRowModel,\n flexRender,\n type ColumnDef,\n type SortingState,\n type ColumnFiltersState,\n type VisibilityState,\n type ColumnSizingState,\n type OnChangeFn,\n} from \"@tanstack/react-table\"\nimport type { RowData } from \"@tanstack/react-table\"\nimport { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, EyeOff, Check, SearchX, Loader2 } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nimport { cn } from \"../lib/utils\"\n\ndeclare module \"@tanstack/react-table\" {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface ColumnMeta<TData extends RowData, TValue> {\n /** Server-side sort key for this column. Enables sort in the header menu when onColumnSort is also provided. */\n sortKey?: string\n }\n}\n\nexport interface VirtualizedDataTableProps<TData> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n columns: ColumnDef<TData, any>[]\n data: TData[]\n\n // Virtualization\n height?: number | string\n estimateRowHeight?: number\n overscan?: number\n\n // Row interaction\n onRowClick?: (row: TData) => void\n getRowId?: (original: TData, index: number) => string\n\n // Infinite scroll\n onReachBottom?: () => void\n reachBottomThreshold?: number\n hasMore?: boolean\n isFetchingMore?: boolean\n\n // Column resizing\n enableColumnResizing?: boolean\n columnResizeMode?: \"onChange\" | \"onEnd\"\n columnSizing?: ColumnSizingState\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>\n\n // Server-driven state (controlled) — omit for internal state\n sorting?: SortingState\n onSortingChange?: OnChangeFn<SortingState>\n columnFilters?: ColumnFiltersState\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>\n columnVisibility?: VisibilityState\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n\n // Loading / Empty state\n isLoading?: boolean\n emptyIcon?: React.ReactNode\n emptyMessage?: string\n emptyDescription?: string\n\n // Column header menu\n /** Called when user requests sorting from column header. columnId is the column's meta.sortKey. */\n onColumnSort?: (columnId: string, direction: \"asc\" | \"desc\") => void\n /** Called when user hides a column from the header menu. */\n onColumnHide?: (columnId: string) => void\n /** The currently active sort column ID — matches a column's meta.sortKey. Used for visual indicators and aria-sort. */\n activeSortColumn?: string | null\n /** The current sort direction. Used for visual indicators and aria-sort. */\n activeSortDirection?: \"asc\" | \"desc\"\n\n // Styling\n className?: string\n}\n\nexport function VirtualizedDataTable<TData>({\n columns,\n data,\n height = 600,\n estimateRowHeight = 48,\n overscan = 8,\n onRowClick,\n getRowId,\n onReachBottom,\n reachBottomThreshold = 5,\n hasMore = true,\n isFetchingMore,\n enableColumnResizing = false,\n columnResizeMode = \"onEnd\",\n columnSizing,\n onColumnSizingChange,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n columnVisibility,\n onColumnVisibilityChange,\n onColumnSort,\n onColumnHide,\n activeSortColumn,\n activeSortDirection,\n isLoading,\n emptyIcon,\n emptyMessage = \"No rows found\",\n emptyDescription = \"Try adjusting your filters\",\n className,\n}: VirtualizedDataTableProps<TData>) {\n // Controlled/uncontrolled state for sorting\n const [internalSorting, setInternalSorting] = React.useState<SortingState>([])\n const resolvedSorting = sorting ?? internalSorting\n const resolvedOnSortingChange = onSortingChange ?? setInternalSorting\n\n // Controlled/uncontrolled state for column filters\n const [internalColumnFilters, setInternalColumnFilters] =\n React.useState<ColumnFiltersState>([])\n const resolvedColumnFilters = columnFilters ?? internalColumnFilters\n const resolvedOnColumnFiltersChange =\n onColumnFiltersChange ?? setInternalColumnFilters\n\n // Controlled/uncontrolled state for column visibility\n const [internalColumnVisibility, setInternalColumnVisibility] =\n React.useState<VisibilityState>({})\n const resolvedColumnVisibility = columnVisibility ?? internalColumnVisibility\n const resolvedOnColumnVisibilityChange =\n onColumnVisibilityChange ?? setInternalColumnVisibility\n\n // Controlled/uncontrolled state for column sizing\n const [internalColumnSizing, setInternalColumnSizing] =\n React.useState<ColumnSizingState>({})\n const resolvedColumnSizing = columnSizing ?? internalColumnSizing\n const resolvedOnColumnSizingChange =\n onColumnSizingChange ?? setInternalColumnSizing\n\n // TanStack Table setup\n const table = useReactTable({\n data,\n columns,\n ...(getRowId ? { getRowId } : {}),\n state: {\n sorting: resolvedSorting,\n columnFilters: resolvedColumnFilters,\n columnVisibility: resolvedColumnVisibility,\n columnSizing: resolvedColumnSizing,\n },\n onSortingChange: resolvedOnSortingChange,\n onColumnFiltersChange: resolvedOnColumnFiltersChange,\n onColumnVisibilityChange: resolvedOnColumnVisibilityChange,\n onColumnSizingChange: resolvedOnColumnSizingChange,\n enableColumnResizing,\n columnResizeMode,\n manualSorting: true,\n manualFiltering: true,\n manualPagination: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n // Virtualizer setup\n const scrollContainerRef = React.useRef<HTMLDivElement>(null)\n const rows = table.getRowModel().rows\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => estimateRowHeight,\n overscan,\n measureElement: (element) => element.getBoundingClientRect().height,\n })\n\n // Infinite scroll detection\n const lastTriggeredDataLengthRef = React.useRef<number>(0)\n\n // Derive a stable primitive for the last visible virtual-item index so the\n // effect below doesn't re-run on every render (getVirtualItems() returns a\n // new array reference each call).\n const virtualItems = virtualizer.getVirtualItems()\n const lastVirtualItemIndex =\n virtualItems.length > 0\n ? virtualItems[virtualItems.length - 1].index\n : -1\n\n React.useEffect(() => {\n if (!onReachBottom || isFetchingMore || hasMore === false) return\n if (lastVirtualItemIndex < 0) return\n if (lastVirtualItemIndex < rows.length - reachBottomThreshold) return\n\n // Prevent re-firing until data.length changes (i.e. new page loaded).\n if (lastTriggeredDataLengthRef.current === data.length) return\n lastTriggeredDataLengthRef.current = data.length\n\n onReachBottom()\n }, [\n lastVirtualItemIndex,\n rows.length,\n data.length,\n onReachBottom,\n isFetchingMore,\n hasMore,\n reachBottomThreshold,\n ])\n\n return (\n <div className={cn(\n \"w-full\",\n typeof height === \"string\" && height.trim().endsWith(\"%\") && \"h-full\",\n className,\n )}>\n <div\n ref={scrollContainerRef}\n className=\"relative overflow-auto\"\n style={{\n height: typeof height === \"number\" ? `${height}px` : height,\n contain: \"strict\",\n }}\n role=\"table\"\n aria-rowcount={data.length}\n aria-colcount={table.getVisibleLeafColumns().length}\n >\n {/* Sticky header */}\n <div className=\"sticky top-0 z-10 bg-background\" role=\"rowgroup\">\n {table.getHeaderGroups().map((headerGroup) => (\n <div\n key={headerGroup.id}\n className=\"flex w-max min-w-full border-b border-border/50\"\n role=\"row\"\n >\n {headerGroup.headers.map((header, colIdx) => {\n const sortKey = header.column.columnDef.meta?.sortKey\n const canServerSort = Boolean(sortKey && onColumnSort)\n\n const resolvedAriaSort = (() => {\n if (activeSortColumn !== undefined) {\n // Server-driven\n if (!sortKey) return undefined\n if (activeSortColumn === sortKey) return activeSortDirection === \"asc\" ? \"ascending\" as const : \"descending\" as const\n return \"none\" as const\n }\n // Fallback to TanStack state\n const sorted = header.column.getIsSorted()\n if (sorted === \"asc\") return \"ascending\" as const\n if (sorted === \"desc\") return \"descending\" as const\n if (header.column.getCanSort()) return \"none\" as const\n return undefined\n })()\n\n const sortIcon = (() => {\n if (!canServerSort) return null\n if (activeSortColumn === sortKey && activeSortDirection === \"asc\") return <ArrowUp className=\"w-3 h-3 shrink-0\" />\n if (activeSortColumn === sortKey && activeSortDirection === \"desc\") return <ArrowDown className=\"w-3 h-3 shrink-0\" />\n return <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n })()\n\n const handleHeaderClick = canServerSort ? () => {\n const newDir = activeSortColumn === sortKey\n ? (activeSortDirection === \"asc\" ? \"desc\" : \"asc\")\n : \"asc\"\n onColumnSort!(sortKey!, newDir)\n } : undefined\n\n return (\n <div\n key={header.id}\n className={cn(\n \"group/header h-9 min-w-0 px-3 flex items-center text-xs font-medium text-muted-foreground whitespace-nowrap relative\",\n header.column.getCanResize() && \"pr-4\",\n )}\n style={{\n width: header.getSize(),\n minWidth: header.getSize(),\n }}\n role=\"columnheader\"\n aria-colindex={colIdx + 1}\n aria-sort={resolvedAriaSort}\n >\n {header.isPlaceholder ? null : (\n <>\n {canServerSort ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={handleHeaderClick}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(header.column.columnDef.header, header.getContext())}\n </span>\n {sortIcon}\n </button>\n ) : header.column.getCanSort() ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={header.column.getToggleSortingHandler()}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n {header.column.getIsSorted() === \"asc\" ? (\n <ArrowUp className=\"w-3 h-3 shrink-0\" />\n ) : header.column.getIsSorted() === \"desc\" ? (\n <ArrowDown className=\"w-3 h-3 shrink-0\" />\n ) : (\n <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n )}\n </button>\n ) : (\n <span className=\"min-w-0 flex-1 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n )}\n {(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"ml-1 inline-flex shrink-0 items-center hover:text-foreground transition-all opacity-0 group-hover/header:opacity-100\"\n aria-label=\"Column actions\"\n >\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"asc\")}\n >\n <ArrowUp className=\"w-3.5 h-3.5 mr-2\" />\n Sort ascending\n {activeSortColumn === sortKey && activeSortDirection === \"asc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"desc\")}\n >\n <ArrowDown className=\"w-3.5 h-3.5 mr-2\" />\n Sort descending\n {activeSortColumn === sortKey && activeSortDirection === \"desc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n {header.column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={() => onColumnHide ? onColumnHide(header.column.id) : header.column.toggleVisibility(false)}\n >\n <EyeOff className=\"w-3.5 h-3.5 mr-2\" />\n Hide column\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </>\n )}\n {header.column.getCanResize() && (\n <div\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n className={cn(\n \"absolute right-0 top-0 h-full w-3 -mr-1.5 cursor-col-resize select-none touch-none\",\n \"after:absolute after:right-1.5 after:top-0 after:h-full after:w-px\",\n \"after:bg-transparent hover:after:bg-primary/30\",\n header.column.getIsResizing() && \"after:bg-primary/50\",\n )}\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n )}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n\n {/* Virtualized body or empty state */}\n {rows.length > 0 ? (\n <div\n role=\"rowgroup\"\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index]\n return (\n <div\n key={row.id}\n data-index={virtualRow.index}\n ref={virtualizer.measureElement}\n className={cn(\n \"absolute left-0 w-max min-w-full flex group transition-colors\",\n onRowClick && \"cursor-pointer\",\n )}\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n }}\n role=\"row\"\n aria-rowindex={virtualRow.index + 2}\n onClick={() => onRowClick?.(row.original)}\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault()\n onRowClick(row.original)\n }\n }\n : undefined\n }\n >\n {row.getVisibleCells().map((cell, colIdx) => (\n <div\n key={cell.id}\n className=\"px-3 py-3 flex items-center whitespace-nowrap group-hover:bg-muted/50\"\n style={{\n width: cell.column.getSize(),\n minWidth: cell.column.getSize(),\n }}\n role=\"cell\"\n aria-colindex={colIdx + 1}\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </div>\n ))}\n </div>\n )\n })}\n </div>\n ) : isLoading ? (\n <div className=\"flex flex-col items-center justify-center gap-2 text-muted-foreground py-20\">\n <Loader2 className=\"h-7 w-7 animate-spin opacity-60\" />\n <p className=\"text-sm font-medium\">Loading...</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-1 text-muted-foreground py-20\">\n {emptyIcon ?? <SearchX className=\"h-7 w-7 opacity-40\" />}\n <p className=\"text-sm font-medium\">{emptyMessage}</p>\n <p className=\"text-xs\">{emptyDescription}</p>\n </div>\n )}\n\n {/* Loading indicator */}\n {isFetchingMore && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/components/virtualized-data-table.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { useVirtualizer } from \"@tanstack/react-virtual\"\nimport {\n useReactTable,\n getCoreRowModel,\n flexRender,\n type ColumnDef,\n type SortingState,\n type ColumnFiltersState,\n type VisibilityState,\n type ColumnSizingState,\n type OnChangeFn,\n} from \"@tanstack/react-table\"\nimport type { RowData } from \"@tanstack/react-table\"\nimport { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown, EyeOff, Check, SearchX, Loader2 } from \"lucide-react\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nimport { cn } from \"../lib/utils\"\n\ndeclare module \"@tanstack/react-table\" {\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n interface ColumnMeta<TData extends RowData, TValue> {\n /** Server-side sort key for this column. Enables sort in the header menu when onColumnSort is also provided. */\n sortKey?: string\n }\n}\n\nexport interface VirtualizedDataTableProps<TData> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n columns: ColumnDef<TData, any>[]\n data: TData[]\n\n // Virtualization\n height?: number | string\n estimateRowHeight?: number\n overscan?: number\n\n // Row interaction\n onRowClick?: (row: TData) => void\n getRowId?: (original: TData, index: number) => string\n\n // Infinite scroll\n onReachBottom?: () => void\n reachBottomThreshold?: number\n hasMore?: boolean\n isFetchingMore?: boolean\n\n // Column resizing\n enableColumnResizing?: boolean\n columnResizeMode?: \"onChange\" | \"onEnd\"\n columnSizing?: ColumnSizingState\n onColumnSizingChange?: OnChangeFn<ColumnSizingState>\n\n // Server-driven state (controlled) — omit for internal state\n sorting?: SortingState\n onSortingChange?: OnChangeFn<SortingState>\n columnFilters?: ColumnFiltersState\n onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>\n columnVisibility?: VisibilityState\n onColumnVisibilityChange?: OnChangeFn<VisibilityState>\n\n // Loading / Empty state\n isLoading?: boolean\n emptyIcon?: React.ReactNode\n emptyMessage?: string\n emptyDescription?: string\n\n // Column header menu\n /** Called when user requests sorting from column header. columnId is the column's meta.sortKey. */\n onColumnSort?: (columnId: string, direction: \"asc\" | \"desc\") => void\n /** Called when user hides a column from the header menu. */\n onColumnHide?: (columnId: string) => void\n /** The currently active sort column ID — matches a column's meta.sortKey. Used for visual indicators and aria-sort. */\n activeSortColumn?: string | null\n /** The current sort direction. Used for visual indicators and aria-sort. */\n activeSortDirection?: \"asc\" | \"desc\"\n\n // Styling\n className?: string\n}\n\nexport function VirtualizedDataTable<TData>({\n columns,\n data,\n height = 600,\n estimateRowHeight = 48,\n overscan = 8,\n onRowClick,\n getRowId,\n onReachBottom,\n reachBottomThreshold = 5,\n hasMore = true,\n isFetchingMore,\n enableColumnResizing = false,\n columnResizeMode = \"onEnd\",\n columnSizing,\n onColumnSizingChange,\n sorting,\n onSortingChange,\n columnFilters,\n onColumnFiltersChange,\n columnVisibility,\n onColumnVisibilityChange,\n onColumnSort,\n onColumnHide,\n activeSortColumn,\n activeSortDirection,\n isLoading,\n emptyIcon,\n emptyMessage = \"No rows found\",\n emptyDescription = \"Try adjusting your filters\",\n className,\n}: VirtualizedDataTableProps<TData>) {\n // Controlled/uncontrolled state for sorting\n const [internalSorting, setInternalSorting] = React.useState<SortingState>([])\n const resolvedSorting = sorting ?? internalSorting\n const resolvedOnSortingChange = onSortingChange ?? setInternalSorting\n\n // Controlled/uncontrolled state for column filters\n const [internalColumnFilters, setInternalColumnFilters] =\n React.useState<ColumnFiltersState>([])\n const resolvedColumnFilters = columnFilters ?? internalColumnFilters\n const resolvedOnColumnFiltersChange =\n onColumnFiltersChange ?? setInternalColumnFilters\n\n // Controlled/uncontrolled state for column visibility\n const [internalColumnVisibility, setInternalColumnVisibility] =\n React.useState<VisibilityState>({})\n const resolvedColumnVisibility = columnVisibility ?? internalColumnVisibility\n const resolvedOnColumnVisibilityChange =\n onColumnVisibilityChange ?? setInternalColumnVisibility\n\n // Controlled/uncontrolled state for column sizing\n const [internalColumnSizing, setInternalColumnSizing] =\n React.useState<ColumnSizingState>({})\n const resolvedColumnSizing = columnSizing ?? internalColumnSizing\n const resolvedOnColumnSizingChange =\n onColumnSizingChange ?? setInternalColumnSizing\n\n // TanStack Table setup\n const table = useReactTable({\n data,\n columns,\n ...(getRowId ? { getRowId } : {}),\n state: {\n sorting: resolvedSorting,\n columnFilters: resolvedColumnFilters,\n columnVisibility: resolvedColumnVisibility,\n columnSizing: resolvedColumnSizing,\n },\n onSortingChange: resolvedOnSortingChange,\n onColumnFiltersChange: resolvedOnColumnFiltersChange,\n onColumnVisibilityChange: resolvedOnColumnVisibilityChange,\n onColumnSizingChange: resolvedOnColumnSizingChange,\n enableColumnResizing,\n columnResizeMode,\n manualSorting: true,\n manualFiltering: true,\n manualPagination: true,\n getCoreRowModel: getCoreRowModel(),\n })\n\n // Virtualizer setup\n const scrollContainerRef = React.useRef<HTMLDivElement>(null)\n const rows = table.getRowModel().rows\n\n const virtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => estimateRowHeight,\n overscan,\n measureElement: (element) => element.getBoundingClientRect().height,\n })\n\n // Infinite scroll detection\n const lastTriggeredDataLengthRef = React.useRef<number>(0)\n\n // Derive a stable primitive for the last visible virtual-item index so the\n // effect below doesn't re-run on every render (getVirtualItems() returns a\n // new array reference each call).\n const virtualItems = virtualizer.getVirtualItems()\n const lastVirtualItemIndex =\n virtualItems.length > 0\n ? virtualItems[virtualItems.length - 1].index\n : -1\n\n React.useEffect(() => {\n if (!onReachBottom || isFetchingMore || hasMore === false) return\n if (lastVirtualItemIndex < 0) return\n if (lastVirtualItemIndex < rows.length - reachBottomThreshold) return\n\n // Prevent re-firing until data.length changes (i.e. new page loaded).\n if (lastTriggeredDataLengthRef.current === data.length) return\n lastTriggeredDataLengthRef.current = data.length\n\n onReachBottom()\n }, [\n lastVirtualItemIndex,\n rows.length,\n data.length,\n onReachBottom,\n isFetchingMore,\n hasMore,\n reachBottomThreshold,\n ])\n\n return (\n <div className={cn(\n \"w-full\",\n typeof height === \"string\" && height.trim().endsWith(\"%\") && \"h-full\",\n className,\n )}>\n <div\n ref={scrollContainerRef}\n className=\"relative overflow-auto\"\n style={{\n height: typeof height === \"number\" ? `${height}px` : height,\n contain: \"strict\",\n }}\n role=\"table\"\n aria-rowcount={data.length}\n aria-colcount={table.getVisibleLeafColumns().length}\n >\n {/* Sticky header */}\n <div className=\"sticky top-0 z-10 bg-background\" role=\"rowgroup\">\n {table.getHeaderGroups().map((headerGroup) => (\n <div\n key={headerGroup.id}\n className=\"flex w-max min-w-full border-b border-border/50\"\n role=\"row\"\n >\n {headerGroup.headers.map((header, colIdx) => {\n const sortKey = header.column.columnDef.meta?.sortKey\n const canServerSort = Boolean(sortKey && onColumnSort)\n\n const resolvedAriaSort = (() => {\n if (activeSortColumn !== undefined) {\n // Server-driven\n if (!sortKey) return undefined\n if (activeSortColumn === sortKey) return activeSortDirection === \"asc\" ? \"ascending\" as const : \"descending\" as const\n return \"none\" as const\n }\n // Fallback to TanStack state\n const sorted = header.column.getIsSorted()\n if (sorted === \"asc\") return \"ascending\" as const\n if (sorted === \"desc\") return \"descending\" as const\n if (header.column.getCanSort()) return \"none\" as const\n return undefined\n })()\n\n const sortIcon = (() => {\n if (!canServerSort) return null\n if (activeSortColumn === sortKey && activeSortDirection === \"asc\") return <ArrowUp className=\"w-3 h-3 shrink-0\" />\n if (activeSortColumn === sortKey && activeSortDirection === \"desc\") return <ArrowDown className=\"w-3 h-3 shrink-0\" />\n return <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n })()\n\n const handleHeaderClick = canServerSort ? () => {\n const newDir = activeSortColumn === sortKey\n ? (activeSortDirection === \"asc\" ? \"desc\" : \"asc\")\n : \"asc\"\n onColumnSort!(sortKey!, newDir)\n } : undefined\n\n return (\n <div\n key={header.id}\n className={cn(\n \"group/header h-9 min-w-0 px-3 flex items-center text-xs font-medium text-muted-foreground whitespace-nowrap relative\",\n header.column.getCanResize() && \"pr-4\",\n )}\n style={{\n width: header.getSize(),\n minWidth: header.getSize(),\n }}\n role=\"columnheader\"\n aria-colindex={colIdx + 1}\n aria-sort={resolvedAriaSort}\n >\n {header.isPlaceholder ? null : (\n <>\n {canServerSort ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={handleHeaderClick}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(header.column.columnDef.header, header.getContext())}\n </span>\n {sortIcon}\n </button>\n ) : header.column.getCanSort() ? (\n <button\n type=\"button\"\n className=\"flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors\"\n onClick={header.column.getToggleSortingHandler()}\n >\n <span className=\"min-w-0 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n {header.column.getIsSorted() === \"asc\" ? (\n <ArrowUp className=\"w-3 h-3 shrink-0\" />\n ) : header.column.getIsSorted() === \"desc\" ? (\n <ArrowDown className=\"w-3 h-3 shrink-0\" />\n ) : (\n <ArrowUpDown className=\"w-3 h-3 shrink-0 opacity-40\" />\n )}\n </button>\n ) : (\n <span className=\"min-w-0 flex-1 truncate\">\n {flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n </span>\n )}\n {(canServerSort || header.column.getCanSort() || header.column.getCanHide()) && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n className=\"ml-1 inline-flex shrink-0 items-center hover:text-foreground transition-all opacity-0 group-hover/header:opacity-100\"\n aria-label=\"Column actions\"\n >\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"asc\")}\n >\n <ArrowUp className=\"w-3.5 h-3.5 mr-2\" />\n Sort ascending\n {activeSortColumn === sortKey && activeSortDirection === \"asc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={!canServerSort}\n onClick={() => canServerSort && onColumnSort!(sortKey!, \"desc\")}\n >\n <ArrowDown className=\"w-3.5 h-3.5 mr-2\" />\n Sort descending\n {activeSortColumn === sortKey && activeSortDirection === \"desc\" && <Check className=\"w-3.5 h-3.5 ml-auto\" />}\n </DropdownMenuItem>\n {header.column.getCanHide() && (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={() => onColumnHide ? onColumnHide(header.column.id) : header.column.toggleVisibility(false)}\n >\n <EyeOff className=\"w-3.5 h-3.5 mr-2\" />\n Hide column\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </>\n )}\n {header.column.getCanResize() && (\n <div\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n className={cn(\n \"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none\",\n \"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full\",\n \"after:bg-border/70 after:transition-colors hover:after:bg-primary/60\",\n header.column.getIsResizing() && \"after:bg-primary/70\",\n )}\n role=\"separator\"\n aria-orientation=\"vertical\"\n />\n )}\n </div>\n )\n })}\n </div>\n ))}\n </div>\n\n {/* Virtualized body or empty state */}\n {rows.length > 0 ? (\n <div\n role=\"rowgroup\"\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((virtualRow) => {\n const row = rows[virtualRow.index]\n return (\n <div\n key={row.id}\n data-index={virtualRow.index}\n ref={virtualizer.measureElement}\n className={cn(\n \"absolute left-0 w-max min-w-full flex group transition-colors\",\n onRowClick && \"cursor-pointer\",\n )}\n style={{\n transform: `translateY(${virtualRow.start}px)`,\n }}\n role=\"row\"\n aria-rowindex={virtualRow.index + 2}\n onClick={() => onRowClick?.(row.original)}\n tabIndex={onRowClick ? 0 : undefined}\n onKeyDown={\n onRowClick\n ? (e: React.KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault()\n onRowClick(row.original)\n }\n }\n : undefined\n }\n >\n {row.getVisibleCells().map((cell, colIdx) => (\n <div\n key={cell.id}\n className=\"px-3 py-3 flex items-center whitespace-nowrap group-hover:bg-muted/50\"\n style={{\n width: cell.column.getSize(),\n minWidth: cell.column.getSize(),\n }}\n role=\"cell\"\n aria-colindex={colIdx + 1}\n >\n {flexRender(\n cell.column.columnDef.cell,\n cell.getContext(),\n )}\n </div>\n ))}\n </div>\n )\n })}\n </div>\n ) : isLoading ? (\n <div className=\"flex flex-col items-center justify-center gap-2 text-muted-foreground py-20\">\n <Loader2 className=\"h-7 w-7 animate-spin opacity-60\" />\n <p className=\"text-sm font-medium\">Loading...</p>\n </div>\n ) : (\n <div className=\"flex flex-col items-center justify-center gap-1 text-muted-foreground py-20\">\n {emptyIcon ?? <SearchX className=\"h-7 w-7 opacity-40\" />}\n <p className=\"text-sm font-medium\">{emptyMessage}</p>\n <p className=\"text-xs\">{emptyDescription}</p>\n </div>\n )}\n\n {/* Loading indicator */}\n {isFetchingMore && (\n <div className=\"flex items-center justify-center py-4\">\n <Loader2 className=\"h-5 w-5 animate-spin text-muted-foreground\" />\n </div>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect } from "vitest"
|
|
2
2
|
import React from "react"
|
|
3
|
-
import { render } from "@testing-library/react"
|
|
3
|
+
import { render, screen } from "@testing-library/react"
|
|
4
4
|
import { EntityMetadataGrid } from "../entity-panel"
|
|
5
5
|
import { CalendarDays } from "lucide-react"
|
|
6
6
|
|
|
@@ -22,4 +22,30 @@ describe("EntityMetadataGrid", () => {
|
|
|
22
22
|
expect(grid).not.toBeNull()
|
|
23
23
|
expect(grid!.className).toContain("overflow-hidden")
|
|
24
24
|
})
|
|
25
|
+
|
|
26
|
+
it("allows long labels and values to wrap within the panel", () => {
|
|
27
|
+
const { container } = render(
|
|
28
|
+
<EntityMetadataGrid
|
|
29
|
+
fields={[
|
|
30
|
+
{
|
|
31
|
+
icon: CalendarDays,
|
|
32
|
+
label: "Very Long Relationship Manager Owner Label",
|
|
33
|
+
value: (
|
|
34
|
+
<span>
|
|
35
|
+
Owner: Alexandria Cassandra Montgomery, RM: Benjamin Theodore
|
|
36
|
+
Kensington, ADM: Charlotte Evangeline Worthington-Smythe
|
|
37
|
+
</span>
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
]}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const grid = container.firstElementChild
|
|
45
|
+
expect(grid?.className).toContain("min-w-0")
|
|
46
|
+
expect(grid?.className).toContain("minmax(0,1fr)")
|
|
47
|
+
expect(screen.getByText("Very Long Relationship Manager Owner Label").className).toContain("[overflow-wrap:anywhere]")
|
|
48
|
+
expect(screen.getByText(/Owner: Alexandria/).parentElement?.className).toContain("[overflow-wrap:anywhere]")
|
|
49
|
+
expect(screen.getByText(/Owner: Alexandria/).parentElement?.className).not.toContain("truncate")
|
|
50
|
+
})
|
|
25
51
|
})
|
|
@@ -84,6 +84,24 @@ describe("VirtualizedDataTable — resize handles render when enabled", () => {
|
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
it("each handle has a wider grab target and visible divider", () => {
|
|
88
|
+
const { container } = render(
|
|
89
|
+
<VirtualizedDataTable
|
|
90
|
+
columns={testColumns}
|
|
91
|
+
data={testData}
|
|
92
|
+
height={300}
|
|
93
|
+
enableColumnResizing
|
|
94
|
+
/>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
container.querySelectorAll('[role="separator"]').forEach((sep) => {
|
|
98
|
+
expect(sep.classList.contains("w-4")).toBe(true);
|
|
99
|
+
expect(sep.classList.contains("-mr-2")).toBe(true);
|
|
100
|
+
expect(sep.className).toContain("after:bg-border/70");
|
|
101
|
+
expect(sep.className).toContain("hover:after:bg-primary/60");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
87
105
|
it("header cells have 'relative' class when resizing is enabled", () => {
|
|
88
106
|
const { container } = render(
|
|
89
107
|
<VirtualizedDataTable
|
|
@@ -258,14 +258,16 @@ export interface EntityMetadataField {
|
|
|
258
258
|
|
|
259
259
|
export function EntityMetadataGrid({ fields }: { fields: EntityMetadataField[] }) {
|
|
260
260
|
return (
|
|
261
|
-
<div className="grid
|
|
261
|
+
<div className="mb-7 grid min-w-0 grid-cols-1 gap-x-4 gap-y-3 overflow-hidden text-[13px] md:grid-cols-[minmax(0,140px)_minmax(0,1fr)]">
|
|
262
262
|
{fields.map((field, idx) => (
|
|
263
263
|
<React.Fragment key={idx}>
|
|
264
|
-
<div className="flex items-
|
|
265
|
-
<field.icon className="
|
|
266
|
-
<span>{field.label}</span>
|
|
264
|
+
<div className="flex min-w-0 items-start gap-1.5 text-[13px] font-normal text-muted-foreground">
|
|
265
|
+
<field.icon className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
|
266
|
+
<span className="min-w-0 break-words leading-relaxed [overflow-wrap:anywhere]">{field.label}</span>
|
|
267
|
+
</div>
|
|
268
|
+
<div className="min-w-0 whitespace-normal break-words leading-relaxed text-foreground [overflow-wrap:anywhere] [&_*]:max-w-full [&_*]:[overflow-wrap:anywhere] [&_a]:break-all">
|
|
269
|
+
{field.value}
|
|
267
270
|
</div>
|
|
268
|
-
<div className="min-w-0 truncate text-foreground">{field.value}</div>
|
|
269
271
|
</React.Fragment>
|
|
270
272
|
))}
|
|
271
273
|
</div>
|
|
@@ -375,10 +375,10 @@ export function VirtualizedDataTable<TData>({
|
|
|
375
375
|
onMouseDown={header.getResizeHandler()}
|
|
376
376
|
onTouchStart={header.getResizeHandler()}
|
|
377
377
|
className={cn(
|
|
378
|
-
"absolute right-0 top-0 h-full w-
|
|
379
|
-
"after:absolute after:right-
|
|
380
|
-
"after:bg-
|
|
381
|
-
header.column.getIsResizing() && "after:bg-primary/
|
|
378
|
+
"absolute right-0 top-0 z-20 h-full w-4 -mr-2 cursor-col-resize select-none touch-none",
|
|
379
|
+
"after:absolute after:right-2 after:top-1 after:h-[calc(100%-0.5rem)] after:w-px after:rounded-full",
|
|
380
|
+
"after:bg-border/70 after:transition-colors hover:after:bg-primary/60",
|
|
381
|
+
header.column.getIsResizing() && "after:bg-primary/70",
|
|
382
382
|
)}
|
|
383
383
|
role="separator"
|
|
384
384
|
aria-orientation="vertical"
|