@handled-ai/design-system 0.20.34 → 0.21.0

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.
Files changed (34) hide show
  1. package/dist/components/badge.d.ts +1 -1
  2. package/dist/components/button.d.ts +1 -1
  3. package/dist/components/contextual-quick-action-launcher.d.ts +0 -6
  4. package/dist/components/contextual-quick-action-launcher.js +0 -1
  5. package/dist/components/contextual-quick-action-launcher.js.map +1 -1
  6. package/dist/components/data-table.js +1 -15
  7. package/dist/components/data-table.js.map +1 -1
  8. package/dist/components/linked-entity-cell.d.ts +16 -1
  9. package/dist/components/linked-entity-cell.js +33 -3
  10. package/dist/components/linked-entity-cell.js.map +1 -1
  11. package/dist/components/owner-chips.js +1 -1
  12. package/dist/components/owner-chips.js.map +1 -1
  13. package/dist/components/pill.d.ts +1 -1
  14. package/dist/components/tabs.d.ts +1 -1
  15. package/dist/components/virtualized-data-table.js +7 -11
  16. package/dist/components/virtualized-data-table.js.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +2 -0
  19. package/dist/index.js.map +1 -1
  20. package/dist/lib/entity-color.d.ts +3 -0
  21. package/dist/lib/entity-color.js +19 -0
  22. package/dist/lib/entity-color.js.map +1 -0
  23. package/package.json +1 -1
  24. package/src/components/__tests__/contextual-quick-action-launcher.test.tsx +0 -14
  25. package/src/components/__tests__/linked-entity-cell.test.tsx +143 -0
  26. package/src/components/__tests__/owner-chips.test.tsx +0 -10
  27. package/src/components/__tests__/virtualized-data-table.test.tsx +124 -0
  28. package/src/components/contextual-quick-action-launcher.tsx +0 -7
  29. package/src/components/data-table.tsx +1 -17
  30. package/src/components/linked-entity-cell.tsx +44 -2
  31. package/src/components/owner-chips.tsx +1 -1
  32. package/src/components/virtualized-data-table.tsx +15 -14
  33. package/src/index.ts +1 -0
  34. package/src/lib/entity-color.ts +22 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/owner-chips.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * owner-chips.tsx — disambiguates the two ownership concepts operators kept\n * confusing on the case panel:\n *\n * 1. SIGNAL OWNER — Handled's OWN assignment. Who owns working this\n * signal/action inside Handled. Editable here (assign / reassign /\n * unassign). Replaces the ambiguous bare \"Unassigned\" chip.\n *\n * 2. ACCOUNT OWNER(S) — read-through from Salesforce. An account can carry\n * more than one (AE + RM). Informational + links out to Salesforce; never\n * assigned from inside Handled. Leads with the Salesforce mark.\n *\n * Account owner chips are read-only dropdowns. A single owner still opens a\n * small Salesforce-sourced details menu; multiple owners show stacked avatars\n * and a ×N badge before listing each owner with optional Salesforce links.\n *\n * Presentational only: data + handlers come from the consumer (the app).\n */\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n Check,\n UserPlus,\n UserMinus,\n Info,\n ArrowUpRight,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { BRAND_ICONS } from \"../lib/icons\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nexport interface OwnerPerson {\n /** Stable id (profile id for signal owner; SF user id for account owner). */\n id?: string\n name: string\n email?: string\n /** e.g. \"Relationship Manager\", \"Account Executive\". */\n role?: string\n /** Avatar image; falls back to initials when absent. */\n avatarUrl?: string | null\n /** External link (Salesforce) for an account owner. */\n href?: string\n}\n\n/* ── shared bits ─────────────────────────────────────────────────────────── */\n\nfunction SalesforceMark({ size = 13 }: { size?: number }) {\n return (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={BRAND_ICONS.salesforce}\n alt=\"Salesforce\"\n width={size}\n height={size}\n style={{ width: size, height: size, objectFit: \"contain\", display: \"block\" }}\n />\n )\n}\n\nfunction OwnerAvatar({ person, size = \"sm\" }: { person: OwnerPerson; size?: \"sm\" | \"default\" }) {\n return (\n <Avatar size={size} className=\"ring-background ring-1\">\n {person.avatarUrl ? <AvatarImage src={person.avatarUrl} alt={person.name} /> : null}\n <AvatarFallback className=\"bg-muted text-muted-foreground text-[10px] font-medium uppercase\">\n {getInitials({ name: person.name, email: person.email })}\n </AvatarFallback>\n </Avatar>\n )\n}\n\n// Component-owned compact sizing. Set directly here (not via consumer\n// className) because cn()/tailwind-merge can strip conflicting overrides.\nconst chipBase =\n \"inline-flex h-7 items-center gap-1 rounded-md border border-border bg-background px-2 text-[12px] \" +\n \"shadow-[0_1px_1px_rgba(0,0,0,0.03)]\"\n\n/* ── Signal owner (Handled assignment, editable) ─────────────────────────── */\n\nexport interface SignalOwnerChipProps {\n /** Current Handled assignee, or null when unassigned. */\n owner: OwnerPerson | null\n /** Operators the case can be assigned to (preloaded — no fetch on open). */\n assignableOwners?: OwnerPerson[]\n onAssign?: (owner: OwnerPerson) => void\n onUnassign?: () => void\n /** Read-only: render a static chip without the assignment menu. */\n disabled?: boolean\n className?: string\n}\n\nfunction SignalOwnerChip({\n owner,\n assignableOwners = [],\n onAssign,\n onUnassign,\n disabled,\n className,\n}: SignalOwnerChipProps) {\n const [open, setOpen] = React.useState(false)\n\n const value = (\n <>\n {owner ? (\n <OwnerAvatar person={owner} />\n ) : (\n <span className=\"text-muted-foreground inline-flex size-4 items-center justify-center\">\n <UserPlus size={12} />\n </span>\n )}\n <span className=\"text-muted-foreground text-[10px] font-medium tracking-wide uppercase\">\n Signal owner\n </span>\n <span className=\"bg-border/70 mx-0.5 h-3 w-px\" aria-hidden />\n <span className={cn(\"font-medium\", owner ? \"text-foreground\" : \"text-muted-foreground\")}>\n {owner ? owner.name : \"Unassigned\"}\n </span>\n </>\n )\n\n // Read-only or nothing to assign to: static chip.\n if (disabled || (!onAssign && !onUnassign)) {\n return (\n <span\n data-slot=\"signal-owner-chip\"\n data-empty={owner ? undefined : \"true\"}\n className={cn(chipBase, className)}\n title=\"Who owns this signal inside Handled\"\n >\n {value}\n </span>\n )\n }\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"signal-owner-chip\"\n data-empty={owner ? undefined : \"true\"}\n className={cn(chipBase, \"hover:bg-muted cursor-pointer transition-colors\", className)}\n title=\"Who owns this signal inside Handled\"\n >\n {value}\n <span className=\"text-muted-foreground ml-0.5\">\n {open ? <ChevronUp size={12} /> : <ChevronDown size={12} />}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-64\">\n <DropdownMenuLabel className=\"flex flex-col gap-0.5\">\n <span className=\"text-[13px] font-semibold\">Assign signal owner</span>\n <span className=\"text-muted-foreground text-[11px] font-normal\">\n Who works this inside Handled, separate from the Salesforce account owner.\n </span>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n {assignableOwners.map((o) => {\n const active = !!owner && (owner.id ? owner.id === o.id : owner.name === o.name)\n return (\n <DropdownMenuItem\n key={o.id ?? o.name}\n onSelect={() => onAssign?.(o)}\n className=\"gap-2\"\n >\n <OwnerAvatar person={o} size=\"default\" />\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate text-[13px] font-medium\">{o.name}</span>\n {o.role ? (\n <span className=\"text-muted-foreground truncate text-[11px]\">{o.role}</span>\n ) : null}\n </span>\n {active ? <Check size={14} className=\"text-foreground ml-auto\" /> : null}\n </DropdownMenuItem>\n )\n })}\n {owner && onUnassign ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => onUnassign()} className=\"text-muted-foreground gap-2\">\n <UserMinus size={13} /> Unassign\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/* ── Account owner(s) (Salesforce, read-through) ─────────────────────────── */\n\nexport interface AccountOwnerChipProps {\n /** Salesforce account owners (RM, AE, …). Empty -> renders nothing. */\n owners: OwnerPerson[]\n className?: string\n}\n\nfunction AccountOwnerSummary({ person }: { person: OwnerPerson }) {\n return (\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate text-[13px] font-medium\">{person.name}</span>\n {person.role ? (\n <span className=\"text-muted-foreground truncate text-[11px]\">{person.role}</span>\n ) : null}\n {person.email ? (\n <span className=\"text-muted-foreground truncate text-[11px]\">{person.email}</span>\n ) : null}\n </span>\n )\n}\n\nfunction AccountOwnerSalesforceLink({ person }: { person: OwnerPerson }) {\n if (!person.href) return null\n\n return (\n <DropdownMenuItem asChild className=\"p-0\">\n <a\n href={person.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-account-owner-salesforce-link=\"true\"\n className=\"text-foreground focus:bg-accent focus:text-accent-foreground flex items-center gap-1.5 rounded-sm px-2 py-1.5 text-[12px] font-medium outline-hidden transition-colors\"\n title={`Open ${person.name} in Salesforce`}\n >\n <ArrowUpRight size={12} className=\"text-muted-foreground\" />\n Open in Salesforce\n </a>\n </DropdownMenuItem>\n )\n}\n\nfunction AccountOwnerRow({ person }: { person: OwnerPerson }) {\n return (\n <>\n <DropdownMenuItem\n disabled\n data-owner-row=\"true\"\n className=\"items-start gap-2 py-1.5 data-[disabled]:opacity-100\"\n >\n <OwnerAvatar person={person} size=\"default\" />\n <AccountOwnerSummary person={person} />\n </DropdownMenuItem>\n <AccountOwnerSalesforceLink person={person} />\n </>\n )\n}\n\nfunction SalesforceReadOnlyHelper() {\n return (\n <div role=\"presentation\" className=\"text-muted-foreground flex items-start gap-1.5 px-2 py-1.5 text-[11px]\">\n <Info size={12} className=\"mt-0.5 shrink-0\" />\n <span>Read-only from Salesforce. Manage owners in Salesforce.</span>\n </div>\n )\n}\n\nfunction AccountOwnerChip({ owners, className }: AccountOwnerChipProps) {\n const [open, setOpen] = React.useState(false)\n if (!owners.length) return null\n const multi = owners.length > 1\n\n if (!multi) {\n const only = owners[0]\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"account-owner-chip\"\n className={cn(chipBase, \"hover:bg-muted cursor-pointer transition-colors\", className)}\n title={`Account owner in Salesforce — ${only.name}`}\n >\n <span className=\"inline-flex shrink-0 items-center\">\n <SalesforceMark />\n </span>\n <OwnerAvatar person={only} />\n <span className=\"text-foreground font-medium\">{only.name}</span>\n <span className=\"text-muted-foreground ml-0.5\">\n {open ? <ChevronUp size={12} /> : <ChevronDown size={12} />}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-72\">\n <DropdownMenuLabel className=\"flex items-center gap-1.5 text-[13px]\">\n <SalesforceMark />\n Account owner\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <AccountOwnerRow person={only} />\n <DropdownMenuSeparator />\n <SalesforceReadOnlyHelper />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n }\n\n // Multiple owners: stacked avatars + ×N + a menu listing each.\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"account-owner-chip\"\n data-multi=\"true\"\n className={cn(chipBase, \"hover:bg-muted cursor-pointer transition-colors\", className)}\n title=\"Account owners in Salesforce\"\n >\n <span className=\"inline-flex shrink-0 items-center\">\n <SalesforceMark />\n </span>\n <span className=\"flex -space-x-2\">\n {owners.map((o) => (\n <OwnerAvatar key={o.id ?? o.name} person={o} />\n ))}\n </span>\n <span className=\"text-foreground font-medium\">Account owners</span>\n <span className=\"bg-muted text-muted-foreground rounded px-1 text-[10px] font-semibold tabular-nums\">\n ×{owners.length}\n </span>\n <span className=\"text-muted-foreground ml-0.5\">\n {open ? <ChevronUp size={12} /> : <ChevronDown size={12} />}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-72\">\n <DropdownMenuLabel className=\"flex items-center gap-1.5 text-[13px]\">\n <SalesforceMark />\n {owners.length} account owners\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n {owners.map((o) => (\n <AccountOwnerRow key={o.id ?? o.name} person={o} />\n ))}\n <DropdownMenuSeparator />\n <SalesforceReadOnlyHelper />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/* ── Convenience composite ──────────────────────────────────────────────── */\n\nexport interface OwnerChipsProps extends SignalOwnerChipProps {\n /** Salesforce account owners (read-through). */\n accountOwners?: OwnerPerson[]\n}\n\nfunction OwnerChips({ accountOwners = [], className, ...signal }: OwnerChipsProps) {\n return (\n <>\n <SignalOwnerChip {...signal} className={className} />\n <AccountOwnerChip owners={accountOwners} className={className} />\n </>\n )\n}\n\nexport { SignalOwnerChip, AccountOwnerChip, OwnerChips }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+DI,SAoDA,UApDA,KAYA,YAZA;AA1CJ,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAC5B,SAAS,QAAQ,gBAAgB,mBAAmB;AACpD;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAiBP,SAAS,eAAe,EAAE,OAAO,GAAG,GAAsB;AACxD;AAAA;AAAA,IAEE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK,YAAY;AAAA,QACjB,KAAI;AAAA,QACJ,OAAO;AAAA,QACP,QAAQ;AAAA,QACR,OAAO,EAAE,OAAO,MAAM,QAAQ,MAAM,WAAW,WAAW,SAAS,QAAQ;AAAA;AAAA,IAC7E;AAAA;AAEJ;AAEA,SAAS,YAAY,EAAE,QAAQ,OAAO,KAAK,GAAqD;AAC9F,SACE,qBAAC,UAAO,MAAY,WAAU,0BAC3B;AAAA,WAAO,YAAY,oBAAC,eAAY,KAAK,OAAO,WAAW,KAAK,OAAO,MAAM,IAAK;AAAA,IAC/E,oBAAC,kBAAe,WAAU,oEACvB,sBAAY,EAAE,MAAM,OAAO,MAAM,OAAO,OAAO,MAAM,CAAC,GACzD;AAAA,KACF;AAEJ;AAIA,MAAM,WACJ;AAiBF,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA,mBAAmB,CAAC;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAE5C,QAAM,QACJ,iCACG;AAAA,YACC,oBAAC,eAAY,QAAQ,OAAO,IAE5B,oBAAC,UAAK,WAAU,wEACd,8BAAC,YAAS,MAAM,IAAI,GACtB;AAAA,IAEF,oBAAC,UAAK,WAAU,yEAAwE,0BAExF;AAAA,IACA,oBAAC,UAAK,WAAU,gCAA+B,eAAW,MAAC;AAAA,IAC3D,oBAAC,UAAK,WAAW,GAAG,eAAe,QAAQ,oBAAoB,uBAAuB,GACnF,kBAAQ,MAAM,OAAO,cACxB;AAAA,KACF;AAIF,MAAI,YAAa,CAAC,YAAY,CAAC,YAAa;AAC1C,WACE;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,cAAY,QAAQ,SAAY;AAAA,QAChC,WAAW,GAAG,UAAU,SAAS;AAAA,QACjC,OAAM;AAAA,QAEL;AAAA;AAAA,IACH;AAAA,EAEJ;AAEA,SACE,qBAAC,gBAAa,MAAY,cAAc,SACtC;AAAA,wBAAC,uBAAoB,SAAO,MAC1B;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAU;AAAA,QACV,cAAY,QAAQ,SAAY;AAAA,QAChC,WAAW,GAAG,UAAU,mDAAmD,SAAS;AAAA,QACpF,OAAM;AAAA,QAEL;AAAA;AAAA,UACD,oBAAC,UAAK,WAAU,gCACb,iBAAO,oBAAC,aAAU,MAAM,IAAI,IAAK,oBAAC,eAAY,MAAM,IAAI,GAC3D;AAAA;AAAA;AAAA,IACF,GACF;AAAA,IACA,qBAAC,uBAAoB,OAAM,SAAQ,WAAU,QAC3C;AAAA,2BAAC,qBAAkB,WAAU,yBAC3B;AAAA,4BAAC,UAAK,WAAU,6BAA4B,iCAAmB;AAAA,QAC/D,oBAAC,UAAK,WAAU,iDAAgD,wFAEhE;AAAA,SACF;AAAA,MACA,oBAAC,yBAAsB;AAAA,MACtB,iBAAiB,IAAI,CAAC,MAAM;AA3KrC;AA4KU,cAAM,SAAS,CAAC,CAAC,UAAU,MAAM,KAAK,MAAM,OAAO,EAAE,KAAK,MAAM,SAAS,EAAE;AAC3E,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,UAAU,MAAM,qCAAW;AAAA,YAC3B,WAAU;AAAA,YAEV;AAAA,kCAAC,eAAY,QAAQ,GAAG,MAAK,WAAU;AAAA,cACvC,qBAAC,UAAK,WAAU,yBACd;AAAA,oCAAC,UAAK,WAAU,oCAAoC,YAAE,MAAK;AAAA,gBAC1D,EAAE,OACD,oBAAC,UAAK,WAAU,8CAA8C,YAAE,MAAK,IACnE;AAAA,iBACN;AAAA,cACC,SAAS,oBAAC,SAAM,MAAM,IAAI,WAAU,2BAA0B,IAAK;AAAA;AAAA;AAAA,WAX/D,OAAE,OAAF,YAAQ,EAAE;AAAA,QAYjB;AAAA,MAEJ,CAAC;AAAA,MACA,SAAS,aACR,iCACE;AAAA,4BAAC,yBAAsB;AAAA,QACvB,qBAAC,oBAAiB,UAAU,MAAM,WAAW,GAAG,WAAU,+BACxD;AAAA,8BAAC,aAAU,MAAM,IAAI;AAAA,UAAE;AAAA,WACzB;AAAA,SACF,IACE;AAAA,OACN;AAAA,KACF;AAEJ;AAUA,SAAS,oBAAoB,EAAE,OAAO,GAA4B;AAChE,SACE,qBAAC,UAAK,WAAU,yBACd;AAAA,wBAAC,UAAK,WAAU,oCAAoC,iBAAO,MAAK;AAAA,IAC/D,OAAO,OACN,oBAAC,UAAK,WAAU,8CAA8C,iBAAO,MAAK,IACxE;AAAA,IACH,OAAO,QACN,oBAAC,UAAK,WAAU,8CAA8C,iBAAO,OAAM,IACzE;AAAA,KACN;AAEJ;AAEA,SAAS,2BAA2B,EAAE,OAAO,GAA4B;AACvE,MAAI,CAAC,OAAO,KAAM,QAAO;AAEzB,SACE,oBAAC,oBAAiB,SAAO,MAAC,WAAU,OAClC;AAAA,IAAC;AAAA;AAAA,MACC,MAAM,OAAO;AAAA,MACb,QAAO;AAAA,MACP,KAAI;AAAA,MACJ,sCAAmC;AAAA,MACnC,WAAU;AAAA,MACV,OAAO,QAAQ,OAAO,IAAI;AAAA,MAE1B;AAAA,4BAAC,gBAAa,MAAM,IAAI,WAAU,yBAAwB;AAAA,QAAE;AAAA;AAAA;AAAA,EAE9D,GACF;AAEJ;AAEA,SAAS,gBAAgB,EAAE,OAAO,GAA4B;AAC5D,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,UAAQ;AAAA,QACR,kBAAe;AAAA,QACf,WAAU;AAAA,QAEV;AAAA,8BAAC,eAAY,QAAgB,MAAK,WAAU;AAAA,UAC5C,oBAAC,uBAAoB,QAAgB;AAAA;AAAA;AAAA,IACvC;AAAA,IACA,oBAAC,8BAA2B,QAAgB;AAAA,KAC9C;AAEJ;AAEA,SAAS,2BAA2B;AAClC,SACE,qBAAC,SAAI,MAAK,gBAAe,WAAU,0EACjC;AAAA,wBAAC,QAAK,MAAM,IAAI,WAAU,mBAAkB;AAAA,IAC5C,oBAAC,UAAK,qEAAuD;AAAA,KAC/D;AAEJ;AAEA,SAAS,iBAAiB,EAAE,QAAQ,UAAU,GAA0B;AACtE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAC5C,MAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAM,QAAQ,OAAO,SAAS;AAE9B,MAAI,CAAC,OAAO;AACV,UAAM,OAAO,OAAO,CAAC;AAErB,WACE,qBAAC,gBAAa,MAAY,cAAc,SACtC;AAAA,0BAAC,uBAAoB,SAAO,MAC1B;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,aAAU;AAAA,UACV,WAAW,GAAG,UAAU,mDAAmD,SAAS;AAAA,UACpF,OAAO,sCAAiC,KAAK,IAAI;AAAA,UAEjD;AAAA,gCAAC,UAAK,WAAU,qCACd,8BAAC,kBAAe,GAClB;AAAA,YACA,oBAAC,eAAY,QAAQ,MAAM;AAAA,YAC3B,oBAAC,UAAK,WAAU,+BAA+B,eAAK,MAAK;AAAA,YACzD,oBAAC,UAAK,WAAU,gCACb,iBAAO,oBAAC,aAAU,MAAM,IAAI,IAAK,oBAAC,eAAY,MAAM,IAAI,GAC3D;AAAA;AAAA;AAAA,MACF,GACF;AAAA,MACA,qBAAC,uBAAoB,OAAM,SAAQ,WAAU,QAC3C;AAAA,6BAAC,qBAAkB,WAAU,yCAC3B;AAAA,8BAAC,kBAAe;AAAA,UAAE;AAAA,WAEpB;AAAA,QACA,oBAAC,yBAAsB;AAAA,QACvB,oBAAC,mBAAgB,QAAQ,MAAM;AAAA,QAC/B,oBAAC,yBAAsB;AAAA,QACvB,oBAAC,4BAAyB;AAAA,SAC5B;AAAA,OACF;AAAA,EAEJ;AAGA,SACE,qBAAC,gBAAa,MAAY,cAAc,SACtC;AAAA,wBAAC,uBAAoB,SAAO,MAC1B;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,aAAU;AAAA,QACV,cAAW;AAAA,QACX,WAAW,GAAG,UAAU,mDAAmD,SAAS;AAAA,QACpF,OAAM;AAAA,QAEN;AAAA,8BAAC,UAAK,WAAU,qCACd,8BAAC,kBAAe,GAClB;AAAA,UACA,oBAAC,UAAK,WAAU,mBACb,iBAAO,IAAI,CAAC,MAAG;AAtU5B;AAuUc,uCAAC,eAAiC,QAAQ,MAAxB,OAAE,OAAF,YAAQ,EAAE,IAAiB;AAAA,WAC9C,GACH;AAAA,UACA,oBAAC,UAAK,WAAU,+BAA8B,4BAAc;AAAA,UAC5D,qBAAC,UAAK,WAAU,sFAAqF;AAAA;AAAA,YACjG,OAAO;AAAA,aACX;AAAA,UACA,oBAAC,UAAK,WAAU,gCACb,iBAAO,oBAAC,aAAU,MAAM,IAAI,IAAK,oBAAC,eAAY,MAAM,IAAI,GAC3D;AAAA;AAAA;AAAA,IACF,GACF;AAAA,IACA,qBAAC,uBAAoB,OAAM,SAAQ,WAAU,QAC3C;AAAA,2BAAC,qBAAkB,WAAU,yCAC3B;AAAA,4BAAC,kBAAe;AAAA,QACf,OAAO;AAAA,QAAO;AAAA,SACjB;AAAA,MACA,oBAAC,yBAAsB;AAAA,MACtB,OAAO,IAAI,CAAC,MAAG;AAzVxB;AA0VU,mCAAC,mBAAqC,QAAQ,MAAxB,OAAE,OAAF,YAAQ,EAAE,IAAiB;AAAA,OAClD;AAAA,MACD,oBAAC,yBAAsB;AAAA,MACvB,oBAAC,4BAAyB;AAAA,OAC5B;AAAA,KACF;AAEJ;AASA,SAAS,WAAW,IAA+D;AAA/D,eAAE,kBAAgB,CAAC,GAAG,UA1W1C,IA0WoB,IAAoC,mBAApC,IAAoC,CAAlC,iBAAoB;AACxC,SACE,iCACE;AAAA,wBAAC,kDAAoB,SAApB,EAA4B,YAAsB;AAAA,IACnD,oBAAC,oBAAiB,QAAQ,eAAe,WAAsB;AAAA,KACjE;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/owner-chips.tsx"],"sourcesContent":["\"use client\"\n\n/**\n * owner-chips.tsx — disambiguates the two ownership concepts operators kept\n * confusing on the case panel:\n *\n * 1. SIGNAL OWNER — Handled's OWN assignment. Who owns working this\n * signal/action inside Handled. Editable here (assign / reassign /\n * unassign). Replaces the ambiguous bare \"Unassigned\" chip.\n *\n * 2. ACCOUNT OWNER(S) — read-through from Salesforce. An account can carry\n * more than one (AE + RM). Informational + links out to Salesforce; never\n * assigned from inside Handled. Leads with the Salesforce mark.\n *\n * Account owner chips are read-only dropdowns. A single owner still opens a\n * small Salesforce-sourced details menu; multiple owners show stacked avatars\n * and a ×N badge before listing each owner with optional Salesforce links.\n *\n * Presentational only: data + handlers come from the consumer (the app).\n */\n\nimport * as React from \"react\"\nimport {\n ChevronDown,\n ChevronUp,\n Check,\n UserPlus,\n UserMinus,\n Info,\n ArrowUpRight,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { getInitials } from \"../lib/user-display\"\nimport { BRAND_ICONS } from \"../lib/icons\"\nimport { Avatar, AvatarFallback, AvatarImage } from \"./avatar\"\nimport {\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n} from \"./dropdown-menu\"\n\nexport interface OwnerPerson {\n /** Stable id (profile id for signal owner; SF user id for account owner). */\n id?: string\n name: string\n email?: string\n /** e.g. \"Relationship Manager\", \"Account Executive\". */\n role?: string\n /** Avatar image; falls back to initials when absent. */\n avatarUrl?: string | null\n /** External link (Salesforce) for an account owner. */\n href?: string\n}\n\n/* ── shared bits ─────────────────────────────────────────────────────────── */\n\nfunction SalesforceMark({ size = 13 }: { size?: number }) {\n return (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={BRAND_ICONS.salesforce}\n alt=\"Salesforce\"\n width={size}\n height={size}\n style={{ width: size, height: size, objectFit: \"contain\", display: \"block\" }}\n />\n )\n}\n\nfunction OwnerAvatar({ person, size = \"sm\" }: { person: OwnerPerson; size?: \"sm\" | \"default\" }) {\n return (\n <Avatar size={size} className=\"ring-background ring-2\">\n {person.avatarUrl ? <AvatarImage src={person.avatarUrl} alt={person.name} /> : null}\n <AvatarFallback className=\"bg-muted text-muted-foreground text-[10px] font-medium uppercase\">\n {getInitials({ name: person.name, email: person.email })}\n </AvatarFallback>\n </Avatar>\n )\n}\n\n// Component-owned compact sizing. Set directly here (not via consumer\n// className) because cn()/tailwind-merge can strip conflicting overrides.\nconst chipBase =\n \"inline-flex h-7 items-center gap-1 rounded-md border border-border bg-background px-2 text-[12px] \" +\n \"shadow-[0_1px_1px_rgba(0,0,0,0.03)]\"\n\n/* ── Signal owner (Handled assignment, editable) ─────────────────────────── */\n\nexport interface SignalOwnerChipProps {\n /** Current Handled assignee, or null when unassigned. */\n owner: OwnerPerson | null\n /** Operators the case can be assigned to (preloaded — no fetch on open). */\n assignableOwners?: OwnerPerson[]\n onAssign?: (owner: OwnerPerson) => void\n onUnassign?: () => void\n /** Read-only: render a static chip without the assignment menu. */\n disabled?: boolean\n className?: string\n}\n\nfunction SignalOwnerChip({\n owner,\n assignableOwners = [],\n onAssign,\n onUnassign,\n disabled,\n className,\n}: SignalOwnerChipProps) {\n const [open, setOpen] = React.useState(false)\n\n const value = (\n <>\n {owner ? (\n <OwnerAvatar person={owner} />\n ) : (\n <span className=\"text-muted-foreground inline-flex size-4 items-center justify-center\">\n <UserPlus size={12} />\n </span>\n )}\n <span className=\"text-muted-foreground text-[10px] font-medium tracking-wide uppercase\">\n Signal owner\n </span>\n <span className=\"bg-border/70 mx-0.5 h-3 w-px\" aria-hidden />\n <span className={cn(\"font-medium\", owner ? \"text-foreground\" : \"text-muted-foreground\")}>\n {owner ? owner.name : \"Unassigned\"}\n </span>\n </>\n )\n\n // Read-only or nothing to assign to: static chip.\n if (disabled || (!onAssign && !onUnassign)) {\n return (\n <span\n data-slot=\"signal-owner-chip\"\n data-empty={owner ? undefined : \"true\"}\n className={cn(chipBase, className)}\n title=\"Who owns this signal inside Handled\"\n >\n {value}\n </span>\n )\n }\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"signal-owner-chip\"\n data-empty={owner ? undefined : \"true\"}\n className={cn(chipBase, \"hover:bg-muted cursor-pointer transition-colors\", className)}\n title=\"Who owns this signal inside Handled\"\n >\n {value}\n <span className=\"text-muted-foreground ml-0.5\">\n {open ? <ChevronUp size={12} /> : <ChevronDown size={12} />}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-64\">\n <DropdownMenuLabel className=\"flex flex-col gap-0.5\">\n <span className=\"text-[13px] font-semibold\">Assign signal owner</span>\n <span className=\"text-muted-foreground text-[11px] font-normal\">\n Who works this inside Handled, separate from the Salesforce account owner.\n </span>\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n {assignableOwners.map((o) => {\n const active = !!owner && (owner.id ? owner.id === o.id : owner.name === o.name)\n return (\n <DropdownMenuItem\n key={o.id ?? o.name}\n onSelect={() => onAssign?.(o)}\n className=\"gap-2\"\n >\n <OwnerAvatar person={o} size=\"default\" />\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate text-[13px] font-medium\">{o.name}</span>\n {o.role ? (\n <span className=\"text-muted-foreground truncate text-[11px]\">{o.role}</span>\n ) : null}\n </span>\n {active ? <Check size={14} className=\"text-foreground ml-auto\" /> : null}\n </DropdownMenuItem>\n )\n })}\n {owner && onUnassign ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem onSelect={() => onUnassign()} className=\"text-muted-foreground gap-2\">\n <UserMinus size={13} /> Unassign\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/* ── Account owner(s) (Salesforce, read-through) ─────────────────────────── */\n\nexport interface AccountOwnerChipProps {\n /** Salesforce account owners (RM, AE, …). Empty -> renders nothing. */\n owners: OwnerPerson[]\n className?: string\n}\n\nfunction AccountOwnerSummary({ person }: { person: OwnerPerson }) {\n return (\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate text-[13px] font-medium\">{person.name}</span>\n {person.role ? (\n <span className=\"text-muted-foreground truncate text-[11px]\">{person.role}</span>\n ) : null}\n {person.email ? (\n <span className=\"text-muted-foreground truncate text-[11px]\">{person.email}</span>\n ) : null}\n </span>\n )\n}\n\nfunction AccountOwnerSalesforceLink({ person }: { person: OwnerPerson }) {\n if (!person.href) return null\n\n return (\n <DropdownMenuItem asChild className=\"p-0\">\n <a\n href={person.href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n data-account-owner-salesforce-link=\"true\"\n className=\"text-foreground focus:bg-accent focus:text-accent-foreground flex items-center gap-1.5 rounded-sm px-2 py-1.5 text-[12px] font-medium outline-hidden transition-colors\"\n title={`Open ${person.name} in Salesforce`}\n >\n <ArrowUpRight size={12} className=\"text-muted-foreground\" />\n Open in Salesforce\n </a>\n </DropdownMenuItem>\n )\n}\n\nfunction AccountOwnerRow({ person }: { person: OwnerPerson }) {\n return (\n <>\n <DropdownMenuItem\n disabled\n data-owner-row=\"true\"\n className=\"items-start gap-2 py-1.5 data-[disabled]:opacity-100\"\n >\n <OwnerAvatar person={person} size=\"default\" />\n <AccountOwnerSummary person={person} />\n </DropdownMenuItem>\n <AccountOwnerSalesforceLink person={person} />\n </>\n )\n}\n\nfunction SalesforceReadOnlyHelper() {\n return (\n <div role=\"presentation\" className=\"text-muted-foreground flex items-start gap-1.5 px-2 py-1.5 text-[11px]\">\n <Info size={12} className=\"mt-0.5 shrink-0\" />\n <span>Read-only from Salesforce. Manage owners in Salesforce.</span>\n </div>\n )\n}\n\nfunction AccountOwnerChip({ owners, className }: AccountOwnerChipProps) {\n const [open, setOpen] = React.useState(false)\n if (!owners.length) return null\n const multi = owners.length > 1\n\n if (!multi) {\n const only = owners[0]\n\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"account-owner-chip\"\n className={cn(chipBase, \"hover:bg-muted cursor-pointer transition-colors\", className)}\n title={`Account owner in Salesforce — ${only.name}`}\n >\n <span className=\"inline-flex shrink-0 items-center\">\n <SalesforceMark />\n </span>\n <OwnerAvatar person={only} />\n <span className=\"text-foreground font-medium\">{only.name}</span>\n <span className=\"text-muted-foreground ml-0.5\">\n {open ? <ChevronUp size={12} /> : <ChevronDown size={12} />}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-72\">\n <DropdownMenuLabel className=\"flex items-center gap-1.5 text-[13px]\">\n <SalesforceMark />\n Account owner\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n <AccountOwnerRow person={only} />\n <DropdownMenuSeparator />\n <SalesforceReadOnlyHelper />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n }\n\n // Multiple owners: stacked avatars + ×N + a menu listing each.\n return (\n <DropdownMenu open={open} onOpenChange={setOpen}>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n data-slot=\"account-owner-chip\"\n data-multi=\"true\"\n className={cn(chipBase, \"hover:bg-muted cursor-pointer transition-colors\", className)}\n title=\"Account owners in Salesforce\"\n >\n <span className=\"inline-flex shrink-0 items-center\">\n <SalesforceMark />\n </span>\n <span className=\"flex -space-x-2\">\n {owners.map((o) => (\n <OwnerAvatar key={o.id ?? o.name} person={o} />\n ))}\n </span>\n <span className=\"text-foreground font-medium\">Account owners</span>\n <span className=\"bg-muted text-muted-foreground rounded px-1 text-[10px] font-semibold tabular-nums\">\n ×{owners.length}\n </span>\n <span className=\"text-muted-foreground ml-0.5\">\n {open ? <ChevronUp size={12} /> : <ChevronDown size={12} />}\n </span>\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-72\">\n <DropdownMenuLabel className=\"flex items-center gap-1.5 text-[13px]\">\n <SalesforceMark />\n {owners.length} account owners\n </DropdownMenuLabel>\n <DropdownMenuSeparator />\n {owners.map((o) => (\n <AccountOwnerRow key={o.id ?? o.name} person={o} />\n ))}\n <DropdownMenuSeparator />\n <SalesforceReadOnlyHelper />\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n\n/* ── Convenience composite ──────────────────────────────────────────────── */\n\nexport interface OwnerChipsProps extends SignalOwnerChipProps {\n /** Salesforce account owners (read-through). */\n accountOwners?: OwnerPerson[]\n}\n\nfunction OwnerChips({ accountOwners = [], className, ...signal }: OwnerChipsProps) {\n return (\n <>\n <SignalOwnerChip {...signal} className={className} />\n <AccountOwnerChip owners={accountOwners} className={className} />\n </>\n )\n}\n\nexport { SignalOwnerChip, AccountOwnerChip, OwnerChips }\n"],"mappings":"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","names":[]}
@@ -12,7 +12,7 @@ import { VariantProps } from 'class-variance-authority';
12
12
  */
13
13
  type PillStatus = "success" | "warning" | "error" | "neutral" | "info";
14
14
  declare const pillVariants: (props?: ({
15
- variant?: "error" | "default" | "secondary" | "destructive" | "outline" | "ghost" | "neutral" | "info" | "success" | "warning" | null | undefined;
15
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "error" | "neutral" | "info" | "success" | "warning" | null | undefined;
16
16
  } & class_variance_authority_types.ClassProp) | undefined) => string;
17
17
  interface PillProps extends React.ComponentProps<"span">, VariantProps<typeof pillVariants> {
18
18
  }
@@ -5,7 +5,7 @@ import { Tabs as Tabs$1 } from 'radix-ui';
5
5
 
6
6
  declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof Tabs$1.Root>): React.JSX.Element;
7
7
  declare const tabsListVariants: (props?: ({
8
- variant?: "line" | "default" | null | undefined;
8
+ variant?: "default" | "line" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare function TabsList({ className, variant, ...props }: React.ComponentProps<typeof Tabs$1.List> & VariantProps<typeof tabsListVariants>): React.JSX.Element;
11
11
  declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof Tabs$1.Trigger>): React.JSX.Element;
@@ -178,6 +178,8 @@ function VirtualizedDataTable({
178
178
  const newDir = activeSortColumn === sortKey ? activeSortDirection === "asc" ? "desc" : "asc" : "asc";
179
179
  onColumnSort(sortKey, newDir);
180
180
  } : void 0;
181
+ const headerDef = header.column.columnDef.header;
182
+ const headerTitle = typeof headerDef === "string" ? headerDef : void 0;
181
183
  return /* @__PURE__ */ jsxs(
182
184
  "div",
183
185
  {
@@ -198,10 +200,10 @@ function VirtualizedDataTable({
198
200
  "button",
199
201
  {
200
202
  type: "button",
201
- className: "flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors",
203
+ className: "flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors",
202
204
  onClick: handleHeaderClick,
203
205
  children: [
204
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: flexRender(header.column.columnDef.header, header.getContext()) }),
206
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-xs leading-4", title: headerTitle, children: flexRender(headerDef, header.getContext()) }),
205
207
  sortIcon
206
208
  ]
207
209
  }
@@ -209,20 +211,14 @@ function VirtualizedDataTable({
209
211
  "button",
210
212
  {
211
213
  type: "button",
212
- className: "flex min-w-0 flex-1 items-center gap-1 hover:text-foreground transition-colors",
214
+ className: "flex min-w-0 flex-1 items-center gap-1 text-xs leading-4 hover:text-foreground transition-colors",
213
215
  onClick: header.column.getToggleSortingHandler(),
214
216
  children: [
215
- /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: flexRender(
216
- header.column.columnDef.header,
217
- header.getContext()
218
- ) }),
217
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-xs leading-4", title: headerTitle, children: flexRender(headerDef, header.getContext()) }),
219
218
  header.column.getIsSorted() === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "w-3 h-3 shrink-0" }) : header.column.getIsSorted() === "desc" ? /* @__PURE__ */ jsx(ArrowDown, { className: "w-3 h-3 shrink-0" }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "w-3 h-3 shrink-0 opacity-40" })
220
219
  ]
221
220
  }
222
- ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: flexRender(
223
- header.column.columnDef.header,
224
- header.getContext()
225
- ) }),
221
+ ) : /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-xs leading-4", title: headerTitle, children: flexRender(headerDef, header.getContext()) }),
226
222
  (canServerSort || header.column.getCanSort() || header.column.getCanHide()) && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
227
223
  /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
228
224
  "button",
@@ -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 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":[]}
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 const headerDef = header.column.columnDef.header\n // When the header is a plain string, expose it as a native title\n // tooltip so truncated headers remain readable. Non-string\n // ReactNode headers render no title.\n const headerTitle =\n typeof headerDef === \"string\" ? headerDef : 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 text-xs leading-4 hover:text-foreground transition-colors\"\n onClick={handleHeaderClick}\n >\n <span className=\"min-w-0 truncate text-xs leading-4\" title={headerTitle}>\n {flexRender(headerDef, 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 text-xs leading-4 hover:text-foreground transition-colors\"\n onClick={header.column.getToggleSortingHandler()}\n >\n <span className=\"min-w-0 truncate text-xs leading-4\" title={headerTitle}>\n {flexRender(headerDef, header.getContext())}\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 text-xs leading-4\" title={headerTitle}>\n {flexRender(headerDef, header.getContext())}\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/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { cn } from './lib/utils.js';
2
+ export { getEntityColor } from './lib/entity-color.js';
2
3
  export { BRAND_GRAPHICS, BRAND_ICONS } from './lib/icons.js';
3
4
  export { ProfileLike, displayName, getInitials, shortName } from './lib/user-display.js';
4
5
  export { useIsMobile } from './hooks/use-mobile.js';
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { cn } from "./lib/utils.js";
2
+ import { getEntityColor } from "./lib/entity-color.js";
2
3
  import { BRAND_ICONS, BRAND_GRAPHICS } from "./lib/icons.js";
3
4
  import { displayName, getInitials, shortName } from "./lib/user-display.js";
4
5
  import { useIsMobile } from "./hooks/use-mobile.js";
@@ -128,6 +129,7 @@ export {
128
129
  SignalPriorityPopover,
129
130
  cn,
130
131
  displayName,
132
+ getEntityColor,
131
133
  getInitials,
132
134
  shortName,
133
135
  useIsMobile
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { BRAND_ICONS, BRAND_GRAPHICS } from \"./lib/icons\"\nexport { displayName, getInitials, shortName, type ProfileLike } from \"./lib/user-display\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/case-panel-email-composer\"\nexport * from \"./components/email-body\"\nexport * from \"./components/email-composer-row\"\nexport * from \"./components/email-display-helpers\"\nexport * from \"./components/email-preview-card\"\nexport * from \"./components/email-recipient-field\"\nexport * from \"./components/email-send-bar\"\nexport * from \"./components/case-panel-activity-timeline\"\nexport * from \"./components/case-panel-detail\"\nexport * from \"./components/case-panel-why\"\nexport { CollapsibleSection, type CollapsibleSectionProps } from \"./components/collapsible-section\"\nexport * from \"./components/comment-composer\"\nexport * from \"./components/compliance-badge\"\nexport * from \"./components/contact-chip\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/contextual-quick-action-launcher\"\nexport * from \"./components/conversation-panel\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-condition-filter\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/detail-drawer\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/empty-state\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/related-record-action-card\"\nexport { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from \"./components/feedback-primitives\"\nexport type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from \"./components/feedback-primitives\"\nexport { SignalPriorityPopover } from \"./components/signal-priority-popover\"\nexport type { SignalPriorityPopoverProps, SignalPriorityScoreDisplay, PriorityFactor } from \"./components/signal-priority-popover\"\nexport * from \"./components/filter-chip\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/inline-banner\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/days-open-cell\"\nexport * from \"./components/linked-entity-cell\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/kbd-hint\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/owner-chips\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/pill\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport * from \"./components/quick-segment\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/rich-text-toolbar\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-semantics\"\nexport * from \"./components/score-why-chips\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/step-timeline\"\nexport * from \"./components/sticky-action-bar\"\nexport * from \"./components/styled-bar-list\"\nexport { DraftFeedbackInline } from \"./components/draft-feedback-inline\"\nexport type { DraftFeedbackInlineProps } from \"./components/draft-feedback-inline\"\nexport { AccountContactsPopover, BrandIcon } from \"./components/account-contacts-popover\"\nexport type { AccountContactsPopoverProps } from \"./components/account-contacts-popover\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/switch\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/user-display\"\nexport * from \"./components/variable-autocomplete\"\nexport * from \"./components/view-mode-toggle\"\nexport * from \"./components/virtualized-data-table\"\nexport type { ColumnSizingState } from \"@tanstack/react-table\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,aAAa,sBAAsB;AAC5C,SAAS,aAAa,aAAa,iBAAmC;AAGtE,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,0BAAwD;AACjE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,gBAAgB,mBAAmB,eAAe,iBAAiB,6BAA6B;AAEzG,SAAS,6BAA6B;AAEtC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB,iBAAiB;AAElD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @handled-ai/design-system\n * UI components and utilities (shadcn-style, New York)\n */\n\n// Utilities\nexport { cn } from \"./lib/utils\"\nexport { getEntityColor } from \"./lib/entity-color\"\nexport { BRAND_ICONS, BRAND_GRAPHICS } from \"./lib/icons\"\nexport { displayName, getInitials, shortName, type ProfileLike } from \"./lib/user-display\"\n\n// Hooks\nexport { useIsMobile } from \"./hooks/use-mobile\"\n\n// Components (light — no recharts/nivo/three transitive deps)\nexport * from \"./components/activity-detail\"\nexport * from \"./components/activity-log\"\nexport * from \"./components/agent-popover\"\nexport * from \"./components/agent-widget\"\nexport * from \"./components/avatar\"\nexport * from \"./components/badge\"\nexport * from \"./components/button\"\nexport * from \"./components/card\"\nexport * from \"./components/case-panel-email-composer\"\nexport * from \"./components/email-body\"\nexport * from \"./components/email-composer-row\"\nexport * from \"./components/email-display-helpers\"\nexport * from \"./components/email-preview-card\"\nexport * from \"./components/email-recipient-field\"\nexport * from \"./components/email-send-bar\"\nexport * from \"./components/case-panel-activity-timeline\"\nexport * from \"./components/case-panel-detail\"\nexport * from \"./components/case-panel-why\"\nexport { CollapsibleSection, type CollapsibleSectionProps } from \"./components/collapsible-section\"\nexport * from \"./components/comment-composer\"\nexport * from \"./components/compliance-badge\"\nexport * from \"./components/contact-chip\"\nexport * from \"./components/contact-list\"\nexport * from \"./components/contextual-quick-action-launcher\"\nexport * from \"./components/conversation-panel\"\nexport * from \"./components/dashboard-cards\"\nexport * from \"./components/data-table\"\nexport * from \"./components/data-table-condition-filter\"\nexport * from \"./components/data-table-display\"\nexport * from \"./components/data-table-filter\"\nexport * from \"./components/data-table-quick-views\"\nexport * from \"./components/data-table-toolbar\"\nexport * from \"./components/detail-view\"\nexport * from \"./components/detail-drawer\"\nexport * from \"./components/dialog\"\nexport * from \"./components/dropdown-menu\"\nexport * from \"./components/empty-state\"\nexport * from \"./components/entity-panel\"\nexport * from \"./components/related-record-action-card\"\nexport { FeedbackFooter, FeedbackChipGroup, FeedbackInput, FeedbackActions, InlineFeedbackControl } from \"./components/feedback-primitives\"\nexport type { FeedbackFooterProps, FeedbackChipTree, FeedbackChipGroupProps, FeedbackInputProps, FeedbackActionsProps, FeedbackSubmitData, PersistedFeedbackData, InlineFeedbackControlProps } from \"./components/feedback-primitives\"\nexport { SignalPriorityPopover } from \"./components/signal-priority-popover\"\nexport type { SignalPriorityPopoverProps, SignalPriorityScoreDisplay, PriorityFactor } from \"./components/signal-priority-popover\"\nexport * from \"./components/filter-chip\"\nexport * from \"./components/inbox-row\"\nexport * from \"./components/inbox-toolbar\"\nexport * from \"./components/inline-banner\"\nexport * from \"./components/input\"\nexport * from \"./components/insights-filter-bar\"\nexport * from \"./components/days-open-cell\"\nexport * from \"./components/linked-entity-cell\"\nexport * from \"./components/item-list\"\nexport * from \"./components/item-list-display\"\nexport * from \"./components/item-list-filter\"\nexport * from \"./components/item-list-toolbar\"\nexport * from \"./components/kbd-hint\"\nexport * from \"./components/label\"\nexport * from \"./components/message\"\nexport * from \"./components/metric-card\"\nexport * from \"./components/owner-chips\"\nexport * from \"./components/performance-metrics-table\"\nexport * from \"./components/pill\"\nexport * from \"./components/preview-list\"\nexport * from \"./components/progress\"\nexport * from \"./components/quick-action-chat-area\"\nexport * from \"./components/quick-segment\"\nexport {\n QuickActionModal,\n type QuickActionPriority,\n type QuickActionTaskDraft,\n type QuickActionTemplate,\n} from \"./components/quick-action-modal\"\nexport * from \"./components/quick-action-sidebar-nav\"\nexport * from \"./components/recommended-actions-section\"\nexport * from \"./components/report-card\"\nexport * from \"./components/rich-text-toolbar\"\nexport * from \"./components/score-analysis-modal\"\nexport * from \"./components/score-breakdown\"\nexport * from \"./components/score-feedback\"\nexport * from \"./components/score-semantics\"\nexport * from \"./components/score-why-chips\"\nexport * from \"./components/score-ring\"\nexport * from \"./components/scroll-area\"\nexport * from \"./components/select\"\nexport * from \"./components/separator\"\nexport * from \"./components/sheet\"\nexport * from \"./components/sidebar\"\nexport * from \"./components/signal-feedback-inline\"\nexport * from \"./components/simple-data-table\"\nexport * from \"./components/skeleton\"\nexport * from \"./components/status-badge\"\nexport * from \"./components/step-timeline\"\nexport * from \"./components/sticky-action-bar\"\nexport * from \"./components/styled-bar-list\"\nexport { DraftFeedbackInline } from \"./components/draft-feedback-inline\"\nexport type { DraftFeedbackInlineProps } from \"./components/draft-feedback-inline\"\nexport { AccountContactsPopover, BrandIcon } from \"./components/account-contacts-popover\"\nexport type { AccountContactsPopoverProps } from \"./components/account-contacts-popover\"\nexport * from \"./components/suggested-actions\"\nexport * from \"./components/switch\"\nexport * from \"./components/table\"\nexport * from \"./components/tabs\"\nexport * from \"./components/textarea\"\nexport * from \"./components/timeline-activity\"\nexport * from \"./components/tooltip\"\nexport * from \"./components/user-display\"\nexport * from \"./components/variable-autocomplete\"\nexport * from \"./components/view-mode-toggle\"\nexport * from \"./components/virtualized-data-table\"\nexport type { ColumnSizingState } from \"@tanstack/react-table\"\n\n// Charts (re-exported for backward compatibility with root imports)\nexport * from \"./charts/index\"\n\n// Prototype template system (re-exported for backward compatibility)\nexport * from \"./prototype/prototype-config\"\nexport * from \"./prototype/prototype-shell\"\nexport * from \"./prototype/prototype-inbox-view\"\nexport * from \"./prototype/prototype-insights-view\"\nexport * from \"./prototype/prototype-accounts-view\"\nexport * from \"./prototype/prototype-admin-view\"\nexport * from \"./prototype/prototype-work-queue-view\"\n"],"mappings":"AAMA,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAC/B,SAAS,aAAa,sBAAsB;AAC5C,SAAS,aAAa,aAAa,iBAAmC;AAGtE,SAAS,mBAAmB;AAG5B,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,0BAAwD;AACjE,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,gBAAgB,mBAAmB,eAAe,iBAAiB,6BAA6B;AAEzG,SAAS,6BAA6B;AAEtC,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd;AAAA,EACE;AAAA,OAIK;AACP,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,SAAS,2BAA2B;AAEpC,SAAS,wBAAwB,iBAAiB;AAElD,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AAId,cAAc;AAGd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;AACd,cAAc;","names":[]}
@@ -0,0 +1,3 @@
1
+ declare function getEntityColor(name: string): string;
2
+
3
+ export { getEntityColor };
@@ -0,0 +1,19 @@
1
+ const COLORS = [
2
+ "bg-muted text-muted-foreground",
3
+ "bg-gray-100 text-gray-600",
4
+ "bg-zinc-100 text-zinc-600",
5
+ "bg-blue-50 text-blue-600",
6
+ "bg-indigo-50 text-indigo-600",
7
+ "bg-violet-50 text-violet-600"
8
+ ];
9
+ function getEntityColor(name) {
10
+ let hash = 0;
11
+ for (let i = 0; i < name.length; i += 1) {
12
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
13
+ }
14
+ return COLORS[Math.abs(hash) % COLORS.length];
15
+ }
16
+ export {
17
+ getEntityColor
18
+ };
19
+ //# sourceMappingURL=entity-color.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/entity-color.ts"],"sourcesContent":["/**\n * Deterministically maps an entity name to a muted Tailwind color class pair\n * (background + text) used for entity avatar/initial badges. The same input\n * always yields the same color so a given entity keeps a stable color across\n * the app.\n */\nconst COLORS = [\n \"bg-muted text-muted-foreground\",\n \"bg-gray-100 text-gray-600\",\n \"bg-zinc-100 text-zinc-600\",\n \"bg-blue-50 text-blue-600\",\n \"bg-indigo-50 text-indigo-600\",\n \"bg-violet-50 text-violet-600\",\n]\n\nexport function getEntityColor(name: string) {\n let hash = 0\n for (let i = 0; i < name.length; i += 1) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash)\n }\n return COLORS[Math.abs(hash) % COLORS.length]\n}\n"],"mappings":"AAMA,MAAM,SAAS;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,eAAe,MAAc;AAC3C,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,GAAG;AACvC,WAAO,KAAK,WAAW,CAAC,MAAM,QAAQ,KAAK;AAAA,EAC7C;AACA,SAAO,OAAO,KAAK,IAAI,IAAI,IAAI,OAAO,MAAM;AAC9C;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.20.34",
3
+ "version": "0.21.0",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -179,20 +179,6 @@ describe("ContextualQuickActionLauncher", () => {
179
179
  expect(onOpenChange).toHaveBeenCalledWith(false)
180
180
  })
181
181
 
182
- it("stamps an optional per-item testId on the rendered menu item", () => {
183
- render(
184
- <ContextualQuickActionLauncher
185
- contextLabel="Acme Corp"
186
- items={[{ id: "sync-work-item", label: "Sync", testId: "case-sync-button" }]}
187
- onSelect={() => {}}
188
- open
189
- />,
190
- )
191
-
192
- expect(screen.getByTestId("case-sync-button")).toBeTruthy()
193
- expect(screen.getByTestId("case-sync-button").textContent).toContain("Sync")
194
- })
195
-
196
182
  it("renders optional hint outside the menu", () => {
197
183
  render(
198
184
  <ContextualQuickActionLauncher
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import React from "react";
3
+ import { render } from "@testing-library/react";
4
+ import { LinkedEntityCell } from "../linked-entity-cell";
5
+ import { getEntityColor } from "../../lib/entity-color";
6
+
7
+ describe("LinkedEntityCell — base font size", () => {
8
+ it("name span includes text-sm by default (no href)", () => {
9
+ const { container } = render(<LinkedEntityCell name="Acme Corp" />);
10
+ const nameSpan = container.querySelector(
11
+ '[data-slot="linked-entity-cell-name"]',
12
+ )!;
13
+ expect(nameSpan.className).toContain("text-sm");
14
+ });
15
+
16
+ it("name link includes text-sm by default (with href)", () => {
17
+ const { container } = render(
18
+ <LinkedEntityCell name="Acme Corp" href="/accounts/1" />,
19
+ );
20
+ const link = container.querySelector(
21
+ '[data-slot="linked-entity-cell-link"]',
22
+ )!;
23
+ expect(link.className).toContain("text-sm");
24
+ });
25
+
26
+ it("nameClassName overrides the default size on the name span (twMerge removes text-sm)", () => {
27
+ const { container } = render(
28
+ <LinkedEntityCell name="Acme Corp" nameClassName="text-base" />,
29
+ );
30
+ const nameSpan = container.querySelector(
31
+ '[data-slot="linked-entity-cell-name"]',
32
+ )!;
33
+ const classes = nameSpan.className.split(/\s+/);
34
+ expect(classes).toContain("text-base");
35
+ // twMerge drops the conflicting default size
36
+ expect(classes).not.toContain("text-sm");
37
+ // default font-medium retained
38
+ expect(classes).toContain("font-medium");
39
+ });
40
+
41
+ it("nameClassName overrides the default size on the name link (twMerge removes text-sm)", () => {
42
+ const { container } = render(
43
+ <LinkedEntityCell
44
+ name="Acme Corp"
45
+ href="/accounts/1"
46
+ nameClassName="text-base"
47
+ />,
48
+ );
49
+ const link = container.querySelector(
50
+ '[data-slot="linked-entity-cell-link"]',
51
+ )!;
52
+ const classes = link.className.split(/\s+/);
53
+ expect(classes).toContain("text-base");
54
+ expect(classes).not.toContain("text-sm");
55
+ expect(classes).toContain("font-medium");
56
+ });
57
+ });
58
+
59
+ describe("LinkedEntityCell — avatar badge", () => {
60
+ it("renders no avatar badge by default", () => {
61
+ const { container } = render(<LinkedEntityCell name="Acme Corp" />);
62
+ expect(
63
+ container.querySelector('[data-slot="linked-entity-cell-avatar"]'),
64
+ ).toBeNull();
65
+ });
66
+
67
+ it("renders an avatar badge only when avatarLabel is passed, showing the first letter and a getEntityColor color class", () => {
68
+ const { container } = render(
69
+ <LinkedEntityCell name="Acme Corp" avatarLabel="Acme Corp" />,
70
+ );
71
+ const avatar = container.querySelector(
72
+ '[data-slot="linked-entity-cell-avatar"]',
73
+ )!;
74
+ expect(avatar).not.toBeNull();
75
+ expect(avatar.textContent).toBe("A");
76
+ // The full label seeds the color
77
+ const expectedColor = getEntityColor("Acme Corp");
78
+ for (const cls of expectedColor.split(/\s+/)) {
79
+ expect(avatar.className).toContain(cls);
80
+ }
81
+ });
82
+ });
83
+
84
+ describe("LinkedEntityCell — trailing action", () => {
85
+ it("renders trailingAction after the name without affecting the name href", () => {
86
+ const { container } = render(
87
+ <LinkedEntityCell
88
+ name="Acme Corp"
89
+ href="/accounts/1"
90
+ trailingAction={<a href="/sf">SF</a>}
91
+ />,
92
+ );
93
+ const link = container.querySelector(
94
+ '[data-slot="linked-entity-cell-link"]',
95
+ ) as HTMLAnchorElement;
96
+ // Name keeps its own href
97
+ expect(link.getAttribute("href")).toBe("/accounts/1");
98
+
99
+ const trailing = container.querySelector(
100
+ '[data-slot="linked-entity-cell-trailing"]',
101
+ )!;
102
+ expect(trailing).not.toBeNull();
103
+ expect(trailing.textContent).toBe("SF");
104
+
105
+ // Trailing slot comes after the name block in DOM order
106
+ const root = container.querySelector(
107
+ '[data-slot="linked-entity-cell"]',
108
+ )!;
109
+ const children = Array.from(root.children);
110
+ const nameBlockIdx = children.findIndex((c) => c.contains(link));
111
+ const trailingIdx = children.findIndex((c) =>
112
+ c.matches('[data-slot="linked-entity-cell-trailing"]'),
113
+ );
114
+ expect(trailingIdx).toBeGreaterThan(nameBlockIdx);
115
+ });
116
+ });
117
+
118
+ const KNOWN_PAIRS = [
119
+ "bg-muted text-muted-foreground",
120
+ "bg-gray-100 text-gray-600",
121
+ "bg-zinc-100 text-zinc-600",
122
+ "bg-blue-50 text-blue-600",
123
+ "bg-indigo-50 text-indigo-600",
124
+ "bg-violet-50 text-violet-600",
125
+ ];
126
+
127
+ describe("getEntityColor — stable palette", () => {
128
+ it("returns one of the six known color pairs", () => {
129
+ for (const name of ["Acme Corp", "Globex", "Initech", "Umbrella", "Stark Industries", "a"]) {
130
+ expect(KNOWN_PAIRS).toContain(getEntityColor(name));
131
+ }
132
+ });
133
+
134
+ it("returns the exact expected pair for representative names (guards against logic drift)", () => {
135
+ // Hardcoded snapshot of current output so accounts colors cannot shift.
136
+ expect(getEntityColor("Acme Corp")).toBe("bg-muted text-muted-foreground");
137
+ expect(getEntityColor("Globex")).toBe("bg-gray-100 text-gray-600");
138
+ expect(getEntityColor("Initech")).toBe("bg-muted text-muted-foreground");
139
+ expect(getEntityColor("Umbrella")).toBe("bg-muted text-muted-foreground");
140
+ expect(getEntityColor("Stark Industries")).toBe("bg-blue-50 text-blue-600");
141
+ expect(getEntityColor("a")).toBe("bg-gray-100 text-gray-600");
142
+ });
143
+ });
@@ -109,16 +109,6 @@ describe("SignalOwnerChip", () => {
109
109
  expect(cls).not.toContain("text-[13px]");
110
110
  });
111
111
 
112
- it("renders the owner avatar with a 1px ring (no thick ring overflow)", () => {
113
- const { container } = render(<SignalOwnerChip owner={dana} />);
114
- const avatar = container.querySelector('[data-slot="avatar"]')!;
115
- const cls = avatar.className;
116
- expect(cls).toContain("ring-background");
117
- expect(cls).toContain("ring-1");
118
- // The thick ring used to bleed past the chip border; it must be gone.
119
- expect(cls).not.toContain("ring-2");
120
- });
121
-
122
112
  it("renders a static span (no button) when read-only / no handlers", () => {
123
113
  const { container } = render(<SignalOwnerChip owner={dana} />);
124
114
  const el = container.querySelector('[data-slot="signal-owner-chip"]');