@handled-ai/design-system 0.18.10 → 0.18.12

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 (43) hide show
  1. package/dist/components/account-contacts-popover.d.ts +5 -1
  2. package/dist/components/account-contacts-popover.js +25 -4
  3. package/dist/components/account-contacts-popover.js.map +1 -1
  4. package/dist/components/badge.d.ts +1 -1
  5. package/dist/components/button.d.ts +1 -1
  6. package/dist/components/data-table-condition-filter.d.ts +15 -3
  7. package/dist/components/data-table-condition-filter.js +199 -52
  8. package/dist/components/data-table-condition-filter.js.map +1 -1
  9. package/dist/components/data-table-filter.js +7 -8
  10. package/dist/components/data-table-filter.js.map +1 -1
  11. package/dist/components/entity-panel.d.ts +2 -1
  12. package/dist/components/entity-panel.js +52 -45
  13. package/dist/components/entity-panel.js.map +1 -1
  14. package/dist/components/pill.d.ts +1 -1
  15. package/dist/components/score-why-chips.d.ts +1 -1
  16. package/dist/components/signal-priority-popover.d.ts +1 -1
  17. package/dist/components/signal-priority-popover.js +4 -4
  18. package/dist/components/signal-priority-popover.js.map +1 -1
  19. package/dist/components/tabs.d.ts +1 -1
  20. package/dist/index.d.ts +2 -2
  21. package/dist/prototype/index.d.ts +1 -1
  22. package/dist/prototype/prototype-accounts-view.d.ts +1 -1
  23. package/dist/prototype/prototype-admin-view.d.ts +1 -1
  24. package/dist/prototype/prototype-config.d.ts +1 -1
  25. package/dist/prototype/prototype-inbox-view.d.ts +5 -3
  26. package/dist/prototype/prototype-inbox-view.js +11 -5
  27. package/dist/prototype/prototype-inbox-view.js.map +1 -1
  28. package/dist/prototype/prototype-insights-view.d.ts +1 -1
  29. package/dist/prototype/prototype-shell.d.ts +1 -1
  30. package/dist/{signal-priority-popover-BT6CPYNs.d.ts → signal-priority-popover-BEDoPsNE.d.ts} +6 -0
  31. package/package.json +1 -2
  32. package/src/components/__tests__/account-contacts-popover.test.tsx +79 -0
  33. package/src/components/__tests__/data-table-condition-filter.test.tsx +239 -7
  34. package/src/components/__tests__/entity-panel-header.test.tsx +44 -0
  35. package/src/components/__tests__/signal-priority-popover.test.tsx +30 -0
  36. package/src/components/account-contacts-popover.tsx +29 -1
  37. package/src/components/data-table-condition-filter.tsx +278 -68
  38. package/src/components/data-table-filter.tsx +6 -9
  39. package/src/components/entity-panel.tsx +56 -40
  40. package/src/components/signal-priority-popover.tsx +15 -4
  41. package/src/prototype/__tests__/detail-view-title-slots.test.tsx +15 -0
  42. package/src/prototype/prototype-config.ts +2 -0
  43. package/src/prototype/prototype-inbox-view.tsx +17 -5
@@ -12,11 +12,15 @@ interface AccountContactsPopoverProps {
12
12
  onSelectTo?: (contact: SuggestedContact) => void;
13
13
  onSelectCc?: (contact: SuggestedContact) => void;
14
14
  onSelectBcc?: (contact: SuggestedContact) => void;
15
+ /** Label for the default contact row action. Defaults to "Add" or "Switch" when onSelectSwitch is provided. */
16
+ defaultSelectLabel?: string;
17
+ /** Optional replacement-selection callback. When provided, row clicks call this instead of additive onSelect/onSelectTo. */
18
+ onSelectSwitch?: (contact: SuggestedContact) => void;
15
19
  onViewAll?: () => void;
16
20
  onOpenRecentActivity?: () => void;
17
21
  trigger: React.ReactNode;
18
22
  iconMap?: SuggestedActionsIconMap;
19
23
  }
20
- declare function AccountContactsPopover({ contacts, onSelect, onSelectTo, onSelectCc, onSelectBcc, onViewAll, onOpenRecentActivity, trigger, iconMap, }: AccountContactsPopoverProps): React.JSX.Element;
24
+ declare function AccountContactsPopover({ contacts, onSelect, onSelectTo, onSelectCc, onSelectBcc, defaultSelectLabel, onSelectSwitch, onViewAll, onOpenRecentActivity, trigger, iconMap, }: AccountContactsPopoverProps): React.JSX.Element;
21
25
 
22
26
  export { AccountContactsPopover, type AccountContactsPopoverProps, BrandIcon };
@@ -24,6 +24,8 @@ function AccountContactsPopover({
24
24
  onSelectTo,
25
25
  onSelectCc,
26
26
  onSelectBcc,
27
+ defaultSelectLabel,
28
+ onSelectSwitch,
27
29
  onViewAll,
28
30
  onOpenRecentActivity,
29
31
  trigger,
@@ -32,6 +34,15 @@ function AccountContactsPopover({
32
34
  const [open, setOpen] = React.useState(false);
33
35
  const triggerRef = React.useRef(null);
34
36
  const [popoverStyle, setPopoverStyle] = React.useState({});
37
+ const resolvedDefaultSelectLabel = defaultSelectLabel != null ? defaultSelectLabel : onSelectSwitch ? "Switch" : void 0;
38
+ const handleDefaultSelect = React.useCallback((contact) => {
39
+ if (onSelectSwitch) {
40
+ onSelectSwitch(contact);
41
+ } else {
42
+ (onSelectTo != null ? onSelectTo : onSelect)(contact);
43
+ }
44
+ setOpen(false);
45
+ }, [onSelect, onSelectSwitch, onSelectTo]);
35
46
  React.useEffect(() => {
36
47
  if (open && triggerRef.current) {
37
48
  const rect = triggerRef.current.getBoundingClientRect();
@@ -59,10 +70,8 @@ function AccountContactsPopover({
59
70
  "div",
60
71
  {
61
72
  role: "button",
62
- onClick: () => {
63
- (onSelectTo != null ? onSelectTo : onSelect)(c);
64
- setOpen(false);
65
- },
73
+ onClick: () => handleDefaultSelect(c),
74
+ "aria-label": resolvedDefaultSelectLabel ? `${resolvedDefaultSelectLabel} ${c.name}` : void 0,
66
75
  className: "flex items-center gap-3 w-full px-3 py-2 text-left hover:bg-muted/50 transition-colors cursor-pointer",
67
76
  children: [
68
77
  /* @__PURE__ */ jsx("div", { className: "w-7 h-7 rounded-full bg-muted flex items-center justify-center text-[10px] font-medium text-muted-foreground shrink-0", children: c.name.split(" ").map((n) => n[0]).join("") }),
@@ -95,6 +104,18 @@ function AccountContactsPopover({
95
104
  )
96
105
  ] }),
97
106
  /* @__PURE__ */ jsxs("div", { className: "ml-2 flex items-center gap-1.5 shrink-0", children: [
107
+ resolvedDefaultSelectLabel && /* @__PURE__ */ jsx(
108
+ "button",
109
+ {
110
+ type: "button",
111
+ onClick: (e) => {
112
+ e.stopPropagation();
113
+ handleDefaultSelect(c);
114
+ },
115
+ className: "h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40",
116
+ children: resolvedDefaultSelectLabel
117
+ }
118
+ ),
98
119
  onSelectTo && /* @__PURE__ */ jsx(
99
120
  "button",
100
121
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/account-contacts-popover.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n Clock,\n ExternalLink,\n} from \"lucide-react\"\nimport type { SuggestedContact, SuggestedActionsIconMap } from \"./suggested-actions\"\n\n// ---------------------------------------------------------------------------\n// BrandIcon\n// ---------------------------------------------------------------------------\n\nexport function BrandIcon({ src, alt, className }: { src: string; alt: string; className?: string }) {\n return (\n <img\n src={src}\n alt={alt}\n className={`${className ?? \"\"} object-contain`}\n draggable={false}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// AccountContactsPopover\n// ---------------------------------------------------------------------------\n\nexport interface AccountContactsPopoverProps {\n contacts: SuggestedContact[]\n onSelect: (contact: SuggestedContact) => void\n onSelectTo?: (contact: SuggestedContact) => void\n onSelectCc?: (contact: SuggestedContact) => void\n onSelectBcc?: (contact: SuggestedContact) => void\n onViewAll?: () => void\n onOpenRecentActivity?: () => void\n trigger: React.ReactNode\n iconMap?: SuggestedActionsIconMap\n}\n\nexport function AccountContactsPopover({\n contacts,\n onSelect,\n onSelectTo,\n onSelectCc,\n onSelectBcc,\n onViewAll,\n onOpenRecentActivity,\n trigger,\n iconMap,\n}: AccountContactsPopoverProps) {\n const [open, setOpen] = React.useState(false)\n const triggerRef = React.useRef<HTMLDivElement>(null)\n const [popoverStyle, setPopoverStyle] = React.useState<React.CSSProperties>({})\n\n React.useEffect(() => {\n if (open && triggerRef.current) {\n const rect = triggerRef.current.getBoundingClientRect()\n const popoverWidth = Math.min(448, window.innerWidth - 32)\n let left = rect.right - popoverWidth\n if (left < 16) left = 16\n if (left + popoverWidth > window.innerWidth - 16) left = window.innerWidth - 16 - popoverWidth\n const popoverHeight = 320;\n const spaceBelow = window.innerHeight - rect.bottom - 8;\n const spaceAbove = rect.top - 8;\n const placeAbove = spaceBelow < popoverHeight && spaceAbove > spaceBelow;\n const top = placeAbove ? rect.top - popoverHeight - 4 : rect.bottom + 4;\n setPopoverStyle({ position: \"fixed\", top: Math.max(8, top), left, maxHeight: placeAbove ? spaceAbove : spaceBelow })\n }\n }, [open])\n\n return (\n <div>\n <div ref={triggerRef} onClick={() => setOpen(!open)}>{trigger}</div>\n {open && (\n <>\n <div className=\"fixed inset-0 z-40\" onClick={() => setOpen(false)} />\n <div style={popoverStyle} className=\"fixed bg-background border border-border rounded-lg shadow-xl z-50 w-[28rem] max-w-[calc(100vw-2rem)] py-2 animate-in fade-in slide-in-from-top-1 duration-150 overflow-y-auto\">\n <div className=\"px-3 py-1.5 text-[11px] font-medium text-muted-foreground/60 uppercase tracking-wide\">\n Account Contacts\n </div>\n <div className=\"max-h-48 overflow-y-auto\">\n {contacts.map((c, i) => (\n <div\n key={i}\n role=\"button\"\n onClick={() => { (onSelectTo ?? onSelect)(c); setOpen(false) }}\n className=\"flex items-center gap-3 w-full px-3 py-2 text-left hover:bg-muted/50 transition-colors cursor-pointer\"\n >\n <div className=\"w-7 h-7 rounded-full bg-muted flex items-center justify-center text-[10px] font-medium text-muted-foreground shrink-0\">\n {c.name.split(\" \").map((n) => n[0]).join(\"\")}\n </div>\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <div className=\"truncate text-sm font-medium text-foreground\">{c.name}</div>\n <div className=\"truncate text-xs text-muted-foreground leading-tight\">\n {c.role} · {c.email ?? c.emails?.[0] ?? c.phone ?? c.phones?.[0] ?? \"\"}\n </div>\n {c.lastActivity && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onOpenRecentActivity?.()\n setOpen(false)\n }}\n className=\"mt-1.5 flex max-w-full items-center gap-1.5 overflow-hidden rounded-md border border-border/70 bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors\"\n >\n <Clock className=\"w-3 h-3 shrink-0\" />\n <span className=\"shrink-0 font-medium\">Last activity</span>\n <span className=\"shrink-0 text-muted-foreground/60\">·</span>\n <span className=\"shrink-0\">{c.lastActivity.date}</span>\n <span className=\"shrink-0 text-muted-foreground/60\">·</span>\n <span className=\"truncate capitalize\">{c.lastActivity.type}</span>\n </button>\n )}\n </div>\n <div className=\"ml-2 flex items-center gap-1.5 shrink-0\">\n {onSelectTo && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onSelectTo(c)\n setOpen(false)\n }}\n className=\"h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40\"\n >\n To\n </button>\n )}\n {onSelectCc && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onSelectCc(c)\n setOpen(false)\n }}\n className=\"h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40\"\n >\n Cc\n </button>\n )}\n {onSelectBcc && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onSelectBcc(c)\n setOpen(false)\n }}\n className=\"h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40\"\n >\n Bcc\n </button>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n if (c.salesforceUrl) {\n window.open(c.salesforceUrl, \"_blank\", \"noopener,noreferrer\")\n } else {\n onViewAll?.()\n }\n }}\n className=\"h-7 w-7 inline-flex items-center justify-center rounded-md border border-border bg-background hover:bg-muted/40 transition-colors shrink-0\"\n aria-label={`Open ${c.name} in Salesforce`}\n >\n {iconMap?.salesforce ? (\n <BrandIcon src={iconMap.salesforce} alt=\"Salesforce\" className=\"w-3.5 h-3.5\" />\n ) : (\n <ExternalLink className=\"w-3.5 h-3.5 text-muted-foreground\" />\n )}\n </button>\n </div>\n </div>\n ))}\n </div>\n {onViewAll && (\n <>\n <div className=\"h-px bg-border mx-3 my-1\" />\n <button\n onClick={() => { onViewAll(); setOpen(false) }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-left text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors\"\n >\n <ExternalLink className=\"w-3 h-3\" />\n View all contacts\n </button>\n </>\n )}\n </div>\n </>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/account-contacts-popover.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n Clock,\n ExternalLink,\n} from \"lucide-react\"\nimport type { SuggestedContact, SuggestedActionsIconMap } from \"./suggested-actions\"\n\n// ---------------------------------------------------------------------------\n// BrandIcon\n// ---------------------------------------------------------------------------\n\nexport function BrandIcon({ src, alt, className }: { src: string; alt: string; className?: string }) {\n return (\n <img\n src={src}\n alt={alt}\n className={`${className ?? \"\"} object-contain`}\n draggable={false}\n />\n )\n}\n\n// ---------------------------------------------------------------------------\n// AccountContactsPopover\n// ---------------------------------------------------------------------------\n\nexport interface AccountContactsPopoverProps {\n contacts: SuggestedContact[]\n onSelect: (contact: SuggestedContact) => void\n onSelectTo?: (contact: SuggestedContact) => void\n onSelectCc?: (contact: SuggestedContact) => void\n onSelectBcc?: (contact: SuggestedContact) => void\n /** Label for the default contact row action. Defaults to \"Add\" or \"Switch\" when onSelectSwitch is provided. */\n defaultSelectLabel?: string\n /** Optional replacement-selection callback. When provided, row clicks call this instead of additive onSelect/onSelectTo. */\n onSelectSwitch?: (contact: SuggestedContact) => void\n onViewAll?: () => void\n onOpenRecentActivity?: () => void\n trigger: React.ReactNode\n iconMap?: SuggestedActionsIconMap\n}\n\nexport function AccountContactsPopover({\n contacts,\n onSelect,\n onSelectTo,\n onSelectCc,\n onSelectBcc,\n defaultSelectLabel,\n onSelectSwitch,\n onViewAll,\n onOpenRecentActivity,\n trigger,\n iconMap,\n}: AccountContactsPopoverProps) {\n const [open, setOpen] = React.useState(false)\n const triggerRef = React.useRef<HTMLDivElement>(null)\n const [popoverStyle, setPopoverStyle] = React.useState<React.CSSProperties>({})\n const resolvedDefaultSelectLabel = defaultSelectLabel ?? (onSelectSwitch ? \"Switch\" : undefined)\n const handleDefaultSelect = React.useCallback((contact: SuggestedContact) => {\n if (onSelectSwitch) {\n onSelectSwitch(contact)\n } else {\n (onSelectTo ?? onSelect)(contact)\n }\n setOpen(false)\n }, [onSelect, onSelectSwitch, onSelectTo])\n\n React.useEffect(() => {\n if (open && triggerRef.current) {\n const rect = triggerRef.current.getBoundingClientRect()\n const popoverWidth = Math.min(448, window.innerWidth - 32)\n let left = rect.right - popoverWidth\n if (left < 16) left = 16\n if (left + popoverWidth > window.innerWidth - 16) left = window.innerWidth - 16 - popoverWidth\n const popoverHeight = 320;\n const spaceBelow = window.innerHeight - rect.bottom - 8;\n const spaceAbove = rect.top - 8;\n const placeAbove = spaceBelow < popoverHeight && spaceAbove > spaceBelow;\n const top = placeAbove ? rect.top - popoverHeight - 4 : rect.bottom + 4;\n setPopoverStyle({ position: \"fixed\", top: Math.max(8, top), left, maxHeight: placeAbove ? spaceAbove : spaceBelow })\n }\n }, [open])\n\n return (\n <div>\n <div ref={triggerRef} onClick={() => setOpen(!open)}>{trigger}</div>\n {open && (\n <>\n <div className=\"fixed inset-0 z-40\" onClick={() => setOpen(false)} />\n <div style={popoverStyle} className=\"fixed bg-background border border-border rounded-lg shadow-xl z-50 w-[28rem] max-w-[calc(100vw-2rem)] py-2 animate-in fade-in slide-in-from-top-1 duration-150 overflow-y-auto\">\n <div className=\"px-3 py-1.5 text-[11px] font-medium text-muted-foreground/60 uppercase tracking-wide\">\n Account Contacts\n </div>\n <div className=\"max-h-48 overflow-y-auto\">\n {contacts.map((c, i) => (\n <div\n key={i}\n role=\"button\"\n onClick={() => handleDefaultSelect(c)}\n aria-label={resolvedDefaultSelectLabel ? `${resolvedDefaultSelectLabel} ${c.name}` : undefined}\n className=\"flex items-center gap-3 w-full px-3 py-2 text-left hover:bg-muted/50 transition-colors cursor-pointer\"\n >\n <div className=\"w-7 h-7 rounded-full bg-muted flex items-center justify-center text-[10px] font-medium text-muted-foreground shrink-0\">\n {c.name.split(\" \").map((n) => n[0]).join(\"\")}\n </div>\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <div className=\"truncate text-sm font-medium text-foreground\">{c.name}</div>\n <div className=\"truncate text-xs text-muted-foreground leading-tight\">\n {c.role} · {c.email ?? c.emails?.[0] ?? c.phone ?? c.phones?.[0] ?? \"\"}\n </div>\n {c.lastActivity && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onOpenRecentActivity?.()\n setOpen(false)\n }}\n className=\"mt-1.5 flex max-w-full items-center gap-1.5 overflow-hidden rounded-md border border-border/70 bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors\"\n >\n <Clock className=\"w-3 h-3 shrink-0\" />\n <span className=\"shrink-0 font-medium\">Last activity</span>\n <span className=\"shrink-0 text-muted-foreground/60\">·</span>\n <span className=\"shrink-0\">{c.lastActivity.date}</span>\n <span className=\"shrink-0 text-muted-foreground/60\">·</span>\n <span className=\"truncate capitalize\">{c.lastActivity.type}</span>\n </button>\n )}\n </div>\n <div className=\"ml-2 flex items-center gap-1.5 shrink-0\">\n {resolvedDefaultSelectLabel && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n handleDefaultSelect(c)\n }}\n className=\"h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40\"\n >\n {resolvedDefaultSelectLabel}\n </button>\n )}\n {onSelectTo && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onSelectTo(c)\n setOpen(false)\n }}\n className=\"h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40\"\n >\n To\n </button>\n )}\n {onSelectCc && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onSelectCc(c)\n setOpen(false)\n }}\n className=\"h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40\"\n >\n Cc\n </button>\n )}\n {onSelectBcc && (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onSelectBcc(c)\n setOpen(false)\n }}\n className=\"h-6 rounded border border-border bg-background px-1.5 text-[10px] text-muted-foreground hover:text-foreground hover:bg-muted/40\"\n >\n Bcc\n </button>\n )}\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n if (c.salesforceUrl) {\n window.open(c.salesforceUrl, \"_blank\", \"noopener,noreferrer\")\n } else {\n onViewAll?.()\n }\n }}\n className=\"h-7 w-7 inline-flex items-center justify-center rounded-md border border-border bg-background hover:bg-muted/40 transition-colors shrink-0\"\n aria-label={`Open ${c.name} in Salesforce`}\n >\n {iconMap?.salesforce ? (\n <BrandIcon src={iconMap.salesforce} alt=\"Salesforce\" className=\"w-3.5 h-3.5\" />\n ) : (\n <ExternalLink className=\"w-3.5 h-3.5 text-muted-foreground\" />\n )}\n </button>\n </div>\n </div>\n ))}\n </div>\n {onViewAll && (\n <>\n <div className=\"h-px bg-border mx-3 my-1\" />\n <button\n onClick={() => { onViewAll(); setOpen(false) }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-left text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors\"\n >\n <ExternalLink className=\"w-3 h-3\" />\n View all contacts\n </button>\n </>\n )}\n </div>\n </>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const badgeVariants: (props?: ({
6
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
6
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | null | undefined;
7
7
  } & class_variance_authority_types.ClassProp) | undefined) => string;
8
8
  declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
9
9
  asChild?: boolean;
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
6
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
@@ -1,22 +1,33 @@
1
1
  import * as React from 'react';
2
2
 
3
3
  type ConditionOperator = "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "is_null" | "is_not_null";
4
+ interface ConditionOptionObject {
5
+ label: string;
6
+ value: string;
7
+ }
8
+ type ConditionFieldOption = string | ConditionOptionObject;
4
9
  interface ConditionFieldDef {
5
10
  /** Unique field key (e.g., "Account_Balance__c") */
6
11
  id: string;
7
12
  /** Display label (e.g., "Account Balance") */
8
13
  label: string;
9
14
  /** Field data type — determines which operators are available and how the value input renders */
10
- type: "text" | "number" | "currency" | "date";
15
+ type: "text" | "number" | "currency" | "date" | "select" | "multi_select";
11
16
  /** Allowed operators for this field. Defaults based on type if not provided. */
12
17
  operators?: ConditionOperator[];
18
+ /** Options used by select and multi-select fields. Strings use the same label and value. */
19
+ options?: ConditionFieldOption[];
20
+ /** Show a search box for option-backed value inputs. */
21
+ searchable?: boolean | {
22
+ threshold?: number;
23
+ };
13
24
  }
14
25
  interface ConditionFilterValue {
15
26
  /** Stable identity — used as React key to avoid stale-state bugs on removal */
16
27
  id: string;
17
28
  field: string;
18
29
  operator: ConditionOperator;
19
- value: string | number | null;
30
+ value: string | number | string[] | null;
20
31
  }
21
32
  interface DataTableConditionFilterProps {
22
33
  /** Available fields the user can filter on */
@@ -28,10 +39,11 @@ interface DataTableConditionFilterProps {
28
39
  className?: string;
29
40
  }
30
41
  declare const OPERATOR_LABELS: Record<ConditionOperator, string>;
42
+ declare function shouldShowOptionSearch(searchable: ConditionFieldDef["searchable"], optionCount: number, defaultThreshold?: number): boolean;
31
43
  declare const DEFAULT_OPERATORS: Record<ConditionFieldDef["type"], ConditionOperator[]>;
32
44
  /** Generate a stable unique ID for a new condition row. */
33
45
  declare function generateConditionId(): string;
34
46
  declare function getOperators(field: ConditionFieldDef): ConditionOperator[];
35
47
  declare function DataTableConditionFilter({ fields, conditions, onConditionsChange, className, }: DataTableConditionFilterProps): React.JSX.Element;
36
48
 
37
- export { type ConditionFieldDef, type ConditionFilterValue, type ConditionOperator, DEFAULT_OPERATORS, DataTableConditionFilter, type DataTableConditionFilterProps, OPERATOR_LABELS, generateConditionId, getOperators };
49
+ export { type ConditionFieldDef, type ConditionFieldOption, type ConditionFilterValue, type ConditionOperator, type ConditionOptionObject, DEFAULT_OPERATORS, DataTableConditionFilter, type DataTableConditionFilterProps, OPERATOR_LABELS, generateConditionId, getOperators, shouldShowOptionSearch };
@@ -23,14 +23,11 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
23
23
  import { jsx, jsxs } from "react/jsx-runtime";
24
24
  import * as React from "react";
25
25
  import {
26
- CalendarDays,
27
- DollarSign,
26
+ Check,
28
27
  Eye,
29
- Hash,
30
28
  MoreHorizontal,
31
29
  Plus,
32
- Trash2,
33
- Type
30
+ Trash2
34
31
  } from "lucide-react";
35
32
  import { cn } from "../lib/utils.js";
36
33
  import { Button } from "./button.js";
@@ -49,7 +46,7 @@ const OPERATOR_LABELS = {
49
46
  gte: "\u2265",
50
47
  lt: "<",
51
48
  lte: "\u2264",
52
- in: "contains",
49
+ in: "is any of",
53
50
  is_null: "is empty",
54
51
  is_not_null: "is not empty"
55
52
  };
@@ -63,11 +60,21 @@ const NUMERIC_OPERATORS = [
63
60
  "is_null",
64
61
  "is_not_null"
65
62
  ];
63
+ const NAV_KEYS = /* @__PURE__ */ new Set(["ArrowDown", "ArrowUp", "Enter", "Escape", "Tab"]);
64
+ function shouldShowOptionSearch(searchable, optionCount, defaultThreshold = 8) {
65
+ var _a;
66
+ if (searchable === true) return true;
67
+ if (searchable === false) return false;
68
+ const threshold = typeof searchable === "object" ? (_a = searchable.threshold) != null ? _a : defaultThreshold : defaultThreshold;
69
+ return optionCount > threshold;
70
+ }
66
71
  const DEFAULT_OPERATORS = {
67
72
  text: ["eq", "neq", "is_null", "is_not_null"],
68
73
  number: NUMERIC_OPERATORS,
69
74
  currency: NUMERIC_OPERATORS,
70
- date: ["gt", "gte", "lt", "lte", "eq", "neq", "is_null", "is_not_null"]
75
+ date: ["gt", "gte", "lt", "lte", "eq", "neq", "is_null", "is_not_null"],
76
+ select: ["eq", "neq", "is_null", "is_not_null"],
77
+ multi_select: ["in", "is_null", "is_not_null"]
71
78
  };
72
79
  function generateConditionId() {
73
80
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
@@ -77,7 +84,7 @@ function generateConditionId() {
77
84
  }
78
85
  function getOperators(field) {
79
86
  var _a;
80
- return ((_a = field.operators) != null ? _a : DEFAULT_OPERATORS[field.type]).filter((op) => op !== "in");
87
+ return (_a = field.operators) != null ? _a : DEFAULT_OPERATORS[field.type];
81
88
  }
82
89
  function isUnaryOperator(op) {
83
90
  return op === "is_null" || op === "is_not_null";
@@ -94,6 +101,13 @@ function createDraftCondition(field) {
94
101
  value: null
95
102
  };
96
103
  }
104
+ function normalizeConditionValue(value, field, operator) {
105
+ if (isUnaryOperator(operator)) return null;
106
+ if (field.type === "multi_select") {
107
+ return Array.isArray(value) ? value : null;
108
+ }
109
+ return Array.isArray(value) ? null : value;
110
+ }
97
111
  function normalizeCondition(condition, fields) {
98
112
  var _a;
99
113
  const field = (_a = fields.find((f) => f.id === condition.field)) != null ? _a : fields[0];
@@ -103,7 +117,7 @@ function normalizeCondition(condition, fields) {
103
117
  return __spreadProps(__spreadValues({}, condition), {
104
118
  field: field.id,
105
119
  operator,
106
- value: isUnaryOperator(operator) ? null : condition.value
120
+ value: normalizeConditionValue(condition.value, field, operator)
107
121
  });
108
122
  }
109
123
  function parseConditionValue(raw, fieldType) {
@@ -119,25 +133,164 @@ function isCompleteCondition(condition, fields) {
119
133
  if (!field) return false;
120
134
  if (!getOperators(field).includes(condition.operator)) return false;
121
135
  if (isUnaryOperator(condition.operator)) return true;
122
- return condition.value !== null && condition.value !== "";
136
+ if (field.type === "multi_select") {
137
+ return Array.isArray(condition.value) && condition.value.length > 0;
138
+ }
139
+ return condition.value !== null && condition.value !== "" && !Array.isArray(condition.value);
140
+ }
141
+ function getConditionValueSignature(value) {
142
+ return Array.isArray(value) ? JSON.stringify(value) : String(value);
123
143
  }
124
144
  function getConditionsSignature(conditions) {
125
- return conditions.map((condition) => `${condition.id}:${condition.field}:${condition.operator}:${String(condition.value)}`).join(";");
145
+ return conditions.map((condition) => `${condition.id}:${condition.field}:${condition.operator}:${getConditionValueSignature(condition.value)}`).join(";");
146
+ }
147
+ function normalizeFieldOptions(field) {
148
+ var _a;
149
+ return ((_a = field.options) != null ? _a : []).map(
150
+ (option) => typeof option === "string" ? { label: option, value: option } : option
151
+ );
126
152
  }
127
153
  function getFieldsSignature(fields) {
128
154
  return fields.map((field) => {
129
- var _a;
130
- return `${field.id}:${field.type}:${field.label}:${((_a = field.operators) != null ? _a : []).join("|")}`;
155
+ var _a, _b, _c;
156
+ const optionsSignature = normalizeFieldOptions(field).map((option) => `${option.label}:${option.value}`).join("|");
157
+ const searchSignature = typeof field.searchable === "object" ? `threshold:${(_a = field.searchable.threshold) != null ? _a : ""}` : String((_b = field.searchable) != null ? _b : "");
158
+ return `${field.id}:${field.type}:${field.label}:${((_c = field.operators) != null ? _c : []).join("|")}:${optionsSignature}:${searchSignature}`;
131
159
  }).join(";");
132
160
  }
133
161
  function getCommittedConditions(drafts, fields) {
134
162
  return drafts.map((condition) => normalizeCondition(condition, fields)).filter((condition) => isCompleteCondition(condition, fields));
135
163
  }
136
- function getFieldIcon(type) {
137
- if (type === "currency") return DollarSign;
138
- if (type === "number") return Hash;
139
- if (type === "date") return CalendarDays;
140
- return Type;
164
+ function getInputType(fieldType) {
165
+ if (fieldType === "number" || fieldType === "currency") return "number";
166
+ if (fieldType === "date") return "date";
167
+ return "text";
168
+ }
169
+ function getInputPlaceholder(fieldType) {
170
+ if (fieldType === "currency") return "Amount";
171
+ if (fieldType === "number") return "Enter number...";
172
+ if (fieldType === "date") return "";
173
+ return "Enter value...";
174
+ }
175
+ function SelectConditionValueInput({
176
+ condition,
177
+ fieldDef,
178
+ onSelectValueChange
179
+ }) {
180
+ const [query, setQuery] = React.useState("");
181
+ const options = normalizeFieldOptions(fieldDef);
182
+ const showSearch = shouldShowOptionSearch(fieldDef.searchable, options.length);
183
+ const normalizedQuery = showSearch ? query.trim().toLowerCase() : "";
184
+ const filteredOptions = normalizedQuery ? options.filter((option) => option.label.toLowerCase().includes(normalizedQuery)) : options;
185
+ React.useEffect(() => {
186
+ setQuery("");
187
+ }, [fieldDef.id]);
188
+ return /* @__PURE__ */ jsxs(
189
+ Select,
190
+ {
191
+ value: typeof condition.value === "string" ? condition.value : "",
192
+ onValueChange: onSelectValueChange,
193
+ children: [
194
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-full", size: "sm", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select value..." }) }),
195
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
196
+ showSearch ? /* @__PURE__ */ jsx("div", { className: "sticky top-0 z-10 border-b border-border bg-popover p-1.5", children: /* @__PURE__ */ jsx(
197
+ Input,
198
+ {
199
+ value: query,
200
+ onChange: (event) => setQuery(event.target.value),
201
+ onClick: (event) => event.stopPropagation(),
202
+ onKeyDown: (event) => {
203
+ if (!NAV_KEYS.has(event.key)) {
204
+ event.stopPropagation();
205
+ }
206
+ },
207
+ placeholder: "Search options...",
208
+ className: "h-7 text-xs"
209
+ }
210
+ ) }) : null,
211
+ filteredOptions.length > 0 ? filteredOptions.map((option) => /* @__PURE__ */ jsx(SelectItem, { value: option.value, children: option.label }, option.value)) : /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-xs text-muted-foreground", children: "No options" })
212
+ ] })
213
+ ]
214
+ }
215
+ );
216
+ }
217
+ function MultiSelectConditionValueInput({
218
+ condition,
219
+ fieldDef,
220
+ onMultiSelectValueToggle
221
+ }) {
222
+ const selectedValues = Array.isArray(condition.value) ? condition.value : [];
223
+ const options = normalizeFieldOptions(fieldDef);
224
+ return /* @__PURE__ */ jsx("div", { className: "max-h-28 overflow-y-auto rounded-md border border-border bg-background p-1", children: options.length > 0 ? options.map((option) => {
225
+ const checked = selectedValues.includes(option.value);
226
+ return /* @__PURE__ */ jsxs(
227
+ "button",
228
+ {
229
+ type: "button",
230
+ role: "checkbox",
231
+ "aria-checked": checked,
232
+ className: cn(
233
+ "flex w-full items-center gap-2 rounded-sm px-2 py-1 text-left text-xs hover:bg-muted",
234
+ checked && "text-brand-purple"
235
+ ),
236
+ onClick: (event) => {
237
+ event.stopPropagation();
238
+ onMultiSelectValueToggle(option.value);
239
+ },
240
+ children: [
241
+ /* @__PURE__ */ jsx(
242
+ "span",
243
+ {
244
+ className: cn(
245
+ "flex h-3.5 w-3.5 items-center justify-center rounded-sm border border-border",
246
+ checked && "border-brand-purple bg-brand-purple text-white"
247
+ ),
248
+ "aria-hidden": "true",
249
+ children: checked ? /* @__PURE__ */ jsx(Check, { className: "h-3 w-3" }) : null
250
+ }
251
+ ),
252
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: option.label })
253
+ ]
254
+ },
255
+ option.value
256
+ );
257
+ }) : /* @__PURE__ */ jsx("div", { className: "px-2 py-1 text-xs text-muted-foreground", children: "No options" }) });
258
+ }
259
+ function ScalarConditionValueInput({
260
+ condition,
261
+ fieldDef,
262
+ onValueChange,
263
+ onCommit
264
+ }) {
265
+ return /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
266
+ fieldDef.type === "currency" ? /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-2 text-sm text-muted-foreground", children: "$" }) : null,
267
+ /* @__PURE__ */ jsx(
268
+ Input,
269
+ {
270
+ type: getInputType(fieldDef.type),
271
+ value: condition.value != null && !Array.isArray(condition.value) ? String(condition.value) : "",
272
+ onChange: onValueChange,
273
+ onClick: (event) => event.stopPropagation(),
274
+ onKeyDown: (event) => {
275
+ event.stopPropagation();
276
+ if (event.key === "Enter") {
277
+ onCommit();
278
+ }
279
+ },
280
+ placeholder: getInputPlaceholder(fieldDef.type),
281
+ className: cn("h-8", fieldDef.type === "currency" && "pl-6")
282
+ }
283
+ )
284
+ ] });
285
+ }
286
+ function ConditionValueInput(props) {
287
+ if (props.fieldDef.type === "select") {
288
+ return /* @__PURE__ */ jsx(SelectConditionValueInput, __spreadValues({}, props));
289
+ }
290
+ if (props.fieldDef.type === "multi_select") {
291
+ return /* @__PURE__ */ jsx(MultiSelectConditionValueInput, __spreadValues({}, props));
292
+ }
293
+ return /* @__PURE__ */ jsx(ScalarConditionValueInput, __spreadValues({}, props));
141
294
  }
142
295
  function ConditionRow({
143
296
  condition,
@@ -151,7 +304,6 @@ function ConditionRow({
151
304
  const fieldDef = (_a = fields.find((f) => f.id === condition.field)) != null ? _a : fields[0];
152
305
  const operators = getOperators(fieldDef);
153
306
  const isUnary = isUnaryOperator(condition.operator);
154
- const FieldIcon = getFieldIcon(fieldDef.type);
155
307
  const handleFieldChange = (newFieldId) => {
156
308
  var _a2;
157
309
  const newFieldDef = (_a2 = fields.find((f) => f.id === newFieldId)) != null ? _a2 : fields[0];
@@ -165,7 +317,7 @@ function ConditionRow({
165
317
  const handleOperatorChange = (newOp) => {
166
318
  onChange(__spreadProps(__spreadValues({}, condition), {
167
319
  operator: newOp,
168
- value: isUnaryOperator(newOp) ? null : condition.value
320
+ value: normalizeConditionValue(condition.value, fieldDef, newOp)
169
321
  }));
170
322
  };
171
323
  const handleValueChange = (event) => {
@@ -173,6 +325,18 @@ function ConditionRow({
173
325
  value: parseConditionValue(event.target.value, fieldDef.type)
174
326
  }));
175
327
  };
328
+ const handleSelectValueChange = (value) => {
329
+ onChange(__spreadProps(__spreadValues({}, condition), {
330
+ value
331
+ }));
332
+ };
333
+ const handleMultiSelectValueToggle = (value) => {
334
+ const currentValues = Array.isArray(condition.value) ? condition.value : [];
335
+ const nextValues = currentValues.includes(value) ? currentValues.filter((currentValue) => currentValue !== value) : [...currentValues, value];
336
+ onChange(__spreadProps(__spreadValues({}, condition), {
337
+ value: nextValues
338
+ }));
339
+ };
176
340
  return /* @__PURE__ */ jsxs(
177
341
  "div",
178
342
  {
@@ -181,17 +345,8 @@ function ConditionRow({
181
345
  children: [
182
346
  /* @__PURE__ */ jsx("div", { className: "text-xs font-medium text-muted-foreground", children: index === 0 ? "Where" : "And" }),
183
347
  /* @__PURE__ */ jsxs(Select, { value: condition.field, onValueChange: handleFieldChange, children: [
184
- /* @__PURE__ */ jsxs(SelectTrigger, { className: "h-8 w-full justify-start gap-2", size: "sm", children: [
185
- /* @__PURE__ */ jsx(FieldIcon, { className: "h-3.5 w-3.5 text-muted-foreground" }),
186
- /* @__PURE__ */ jsx(SelectValue, { placeholder: fieldDef.label })
187
- ] }),
188
- /* @__PURE__ */ jsx(SelectContent, { children: fields.map((field) => {
189
- const Icon = getFieldIcon(field.type);
190
- return /* @__PURE__ */ jsx(SelectItem, { value: field.id, children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-2", children: [
191
- /* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5 text-muted-foreground" }),
192
- field.label
193
- ] }) }, field.id);
194
- }) })
348
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-8 w-full justify-start gap-2", size: "sm", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: fieldDef.label }) }),
349
+ /* @__PURE__ */ jsx(SelectContent, { children: fields.map((field) => /* @__PURE__ */ jsx(SelectItem, { value: field.id, children: field.label }, field.id)) })
195
350
  ] }),
196
351
  /* @__PURE__ */ jsxs(
197
352
  Select,
@@ -204,26 +359,17 @@ function ConditionRow({
204
359
  ]
205
360
  }
206
361
  ),
207
- isUnary ? /* @__PURE__ */ jsx("div", { className: "h-8 rounded-md border border-dashed border-border/70 bg-muted/30 px-3 py-1.5 text-xs text-muted-foreground", children: "No value needed" }) : /* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
208
- fieldDef.type === "currency" ? /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-2 text-sm text-muted-foreground", children: "$" }) : null,
209
- /* @__PURE__ */ jsx(
210
- Input,
211
- {
212
- type: fieldDef.type === "number" || fieldDef.type === "currency" ? "number" : fieldDef.type === "date" ? "date" : "text",
213
- value: condition.value != null ? String(condition.value) : "",
214
- onChange: handleValueChange,
215
- onClick: (event) => event.stopPropagation(),
216
- onKeyDown: (event) => {
217
- event.stopPropagation();
218
- if (event.key === "Enter") {
219
- onCommit();
220
- }
221
- },
222
- placeholder: fieldDef.type === "currency" ? "Amount" : fieldDef.type === "number" ? "Enter number..." : fieldDef.type === "date" ? "" : "Enter value...",
223
- className: cn("h-8", fieldDef.type === "currency" && "pl-6")
224
- }
225
- )
226
- ] }),
362
+ isUnary ? /* @__PURE__ */ jsx("div", { className: "h-8 rounded-md border border-dashed border-border/70 bg-muted/30 px-3 py-1.5 text-xs text-muted-foreground", children: "No value needed" }) : /* @__PURE__ */ jsx(
363
+ ConditionValueInput,
364
+ {
365
+ condition,
366
+ fieldDef,
367
+ onValueChange: handleValueChange,
368
+ onSelectValueChange: handleSelectValueChange,
369
+ onMultiSelectValueToggle: handleMultiSelectValueToggle,
370
+ onCommit
371
+ }
372
+ ),
227
373
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
228
374
  /* @__PURE__ */ jsx(
229
375
  Button,
@@ -419,6 +565,7 @@ export {
419
565
  DataTableConditionFilter,
420
566
  OPERATOR_LABELS,
421
567
  generateConditionId,
422
- getOperators
568
+ getOperators,
569
+ shouldShowOptionSearch
423
570
  };
424
571
  //# sourceMappingURL=data-table-condition-filter.js.map