@brainfish-ai/components 0.29.0 → 0.30.1

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 (53) hide show
  1. package/dist/esm/chunks/{ChatSearch.DIrtg9k_.js → ChatSearch.C1LGcLbg.js} +2 -2
  2. package/dist/esm/chunks/{ChatSearch.DIrtg9k_.js.map → ChatSearch.C1LGcLbg.js.map} +1 -1
  3. package/dist/esm/chunks/{Conversation.BRyKMDlg.js → Conversation.Biqpt5FT.js} +6 -6
  4. package/dist/esm/chunks/{Conversation.BRyKMDlg.js.map → Conversation.Biqpt5FT.js.map} +1 -1
  5. package/dist/esm/chunks/{FormattedMessage.XNMN23hm.js → FormattedMessage.CrcIzpCM.js} +17 -17
  6. package/dist/esm/chunks/{FormattedMessage.XNMN23hm.js.map → FormattedMessage.CrcIzpCM.js.map} +1 -1
  7. package/dist/esm/chunks/ai-logo.Do__PIO3.js +7 -0
  8. package/dist/esm/chunks/ai-logo.Do__PIO3.js.map +1 -0
  9. package/dist/esm/chunks/{combobox.MyoPH18G.js → combobox.DJCyKFxV.js} +4 -4
  10. package/dist/esm/chunks/{combobox.MyoPH18G.js.map → combobox.DJCyKFxV.js.map} +1 -1
  11. package/dist/esm/chunks/{filters.BHp3ukNW.js → filters.DPv-zTEC.js} +3 -3
  12. package/dist/esm/chunks/{filters.BHp3ukNW.js.map → filters.DPv-zTEC.js.map} +1 -1
  13. package/dist/esm/chunks/{font-picker.B9GPXyK4.js → font-picker.BE03shJT.js} +2 -2
  14. package/dist/esm/chunks/{font-picker.B9GPXyK4.js.map → font-picker.BE03shJT.js.map} +1 -1
  15. package/dist/esm/chunks/header-nav.Bo_GNqXn.js +14 -0
  16. package/dist/esm/chunks/header-nav.Bo_GNqXn.js.map +1 -0
  17. package/dist/esm/chunks/{header-pane.1-3lMndU.js → header-pane.hQNeFYWQ.js} +2 -2
  18. package/dist/esm/chunks/{header-pane.1-3lMndU.js.map → header-pane.hQNeFYWQ.js.map} +1 -1
  19. package/dist/esm/chunks/logo.DtwFGMBj.js +5 -0
  20. package/dist/esm/chunks/logo.DtwFGMBj.js.map +1 -0
  21. package/dist/esm/chunks/{status-badge.BLB0pWDn.js → status-badge.Cf3R4nVr.js} +3 -3
  22. package/dist/esm/chunks/{status-badge.BLB0pWDn.js.map → status-badge.Cf3R4nVr.js.map} +1 -1
  23. package/dist/esm/chunks/{suggestion-card.DOmXIRGW.js → suggestion-card.6ETnT7EM.js} +2 -2
  24. package/dist/esm/chunks/{suggestion-card.DOmXIRGW.js.map → suggestion-card.6ETnT7EM.js.map} +1 -1
  25. package/dist/esm/chunks/{two-level-combobox.DJYP--W9.js → two-level-combobox.DQpxBbg3.js} +3 -3
  26. package/dist/esm/chunks/{two-level-combobox.DJYP--W9.js.map → two-level-combobox.DQpxBbg3.js.map} +1 -1
  27. package/dist/esm/components/articles-updated.js +2 -2
  28. package/dist/esm/components/chat-search.js +1 -1
  29. package/dist/esm/components/combobox.js +1 -1
  30. package/dist/esm/components/conversation.js +1 -1
  31. package/dist/esm/components/convos.js +1 -1
  32. package/dist/esm/components/discoveries-created.js +2 -2
  33. package/dist/esm/components/filter.js +1 -1
  34. package/dist/esm/components/font-picker.js +1 -1
  35. package/dist/esm/components/logo.js +1 -1
  36. package/dist/esm/components/markdown.js +1 -1
  37. package/dist/esm/components/metric-card.js +3 -3
  38. package/dist/esm/components/two-level-combobox.js +1 -1
  39. package/dist/esm/components/ui/combobox.js +1 -1
  40. package/dist/esm/components/ui/item.js +5 -5
  41. package/dist/esm/global.css +1 -1
  42. package/dist/esm/index.js +1 -1
  43. package/dist/esm/layouts/full-layout.js +1 -1
  44. package/dist/esm/layouts/header-nav.js +1 -1
  45. package/dist/esm/scenes/knowledge-review.js +8 -8
  46. package/dist/index.d.ts +9 -1
  47. package/dist/logo.d.ts +9 -1
  48. package/dist/stats.html +1 -1
  49. package/package.json +1 -1
  50. package/dist/esm/chunks/header-nav.C9r4A44a.js +0 -10
  51. package/dist/esm/chunks/header-nav.C9r4A44a.js.map +0 -1
  52. package/dist/esm/chunks/logo.CGKfN1U6.js +0 -5
  53. package/dist/esm/chunks/logo.CGKfN1U6.js.map +0 -1
@@ -0,0 +1,7 @@
1
+ import*as e from"react";const t=["M32.2871 15.1692C26.8054 10.5534 22.0839 5.46043 18.1727 0C15.205 1.4739 12.4494 3.30417 9.964 5.43479C14.4641 11.8179 19.8322 17.6418 25.8951 22.7243C31.7253 27.5902 40.485 27.5703 46.2998 22.6915C52.4297 17.5217 57.7468 11.7747 62.1676 5.57625C59.6755 3.41629 56.9081 1.55954 53.9242 0.0641588C50.0224 5.50027 45.32 10.5716 39.8685 15.1692C37.6822 16.994 34.4734 16.994 32.2871 15.1692Z","M56.6639 31.8406C61.3305 26.4186 66.4795 21.7485 72 17.8798C70.5099 14.9444 68.6595 12.2188 66.5054 9.76043C60.0522 14.2115 54.1641 19.5213 49.0257 25.5182C44.1063 31.285 44.1264 39.9493 49.0589 45.7009C54.2856 51.7641 60.0958 57.0233 66.3624 61.3961C68.5461 58.9311 70.4233 56.1939 71.9351 53.2424C66.4392 49.3831 61.3122 44.7318 56.6639 39.3396C54.8191 37.177 54.8191 34.0032 56.6639 31.8406Z","M40.0024 56.8308C45.4841 61.4466 50.2056 66.5396 54.1168 72C57.0845 70.5261 59.8401 68.6958 62.3255 66.5652C57.8254 60.1821 52.4573 54.3582 46.3944 49.2757C40.5642 44.4098 31.8045 44.4297 25.9897 49.3085C19.8598 54.4783 14.5427 60.2253 10.1219 66.4237C12.614 68.5837 15.3813 70.4405 18.3653 71.9358C22.2671 66.4997 26.9695 61.4284 32.421 56.8308C34.6073 55.006 37.8161 55.006 40.0024 56.8308Z","M15.3361 39.3159C10.6695 44.7379 5.52049 49.408 0 53.2767C1.49011 56.2122 3.34051 58.9378 5.49457 61.3961C11.9478 56.945 17.8359 51.6352 22.9743 45.6383C27.8937 39.8716 27.8736 31.2072 22.9411 25.4556C17.7144 19.3924 11.9042 14.1332 5.63758 9.76043C3.45386 12.2255 1.57669 14.9627 0.0648629 17.9141C5.56077 21.7735 10.6878 26.4247 15.3361 31.8169C17.1809 33.9795 17.1809 37.1533 15.3361 39.3159Z"],r={dark:"#262626",light:"#FAFAFA"};function n({variant:n,gradientId:l}){if("yellowfin"===n)/* @__PURE__ */
2
+ return e.createElement(e.Fragment,null,/* @__PURE__ */e.createElement("defs",null,/* @__PURE__ */e.createElement("linearGradient",{id:l,x1:"5.37931",y1:"0",x2:"50.046",y2:"65.8568",gradientUnits:"userSpaceOnUse"},/* @__PURE__ */e.createElement("stop",{offset:"0.0544031",stopColor:"#1868DB"}),/* @__PURE__ */e.createElement("stop",{offset:"0.5",stopColor:"#357DE8"}),/* @__PURE__ */e.createElement("stop",{offset:"0.802885",stopColor:"#7EE2B8"}),/* @__PURE__ */e.createElement("stop",{offset:"1",stopColor:"#B4EE2B"}))),t.map((t,r)=>/* @__PURE__ */e.createElement("path",{key:r,d:t,fill:`url(#${l})`})));const a=r[n];/* @__PURE__ */
3
+ return e.createElement(e.Fragment,null,t.map((t,r)=>/* @__PURE__ */e.createElement("path",{key:r,d:t,fill:a})))}const l=e.forwardRef(({variant:t,width:r=72,height:l=72,...a},o)=>{const C=`ai-logo-gradient-${e.useId()}`;/* @__PURE__ */
4
+ return e.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",...a,width:r,height:l,ref:o,viewBox:"0 0 72 72",role:"img",fill:"none"},
5
+ /* @__PURE__ */e.createElement("title",null,"BrainfishAI logo"),
6
+ /* @__PURE__ */e.createElement(n,{variant:t,gradientId:C}))});l.displayName="AILogo";export{l as A};
7
+ //# sourceMappingURL=ai-logo.Do__PIO3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-logo.Do__PIO3.js","sources":["../../../src/components/logo/ai-logo.tsx"],"sourcesContent":["import * as React from 'react';\n\nexport type AILogoVariant = 'dark' | 'light' | 'yellowfin';\n\nconst PATH_DATA = [\n 'M32.2871 15.1692C26.8054 10.5534 22.0839 5.46043 18.1727 0C15.205 1.4739 12.4494 3.30417 9.964 5.43479C14.4641 11.8179 19.8322 17.6418 25.8951 22.7243C31.7253 27.5902 40.485 27.5703 46.2998 22.6915C52.4297 17.5217 57.7468 11.7747 62.1676 5.57625C59.6755 3.41629 56.9081 1.55954 53.9242 0.0641588C50.0224 5.50027 45.32 10.5716 39.8685 15.1692C37.6822 16.994 34.4734 16.994 32.2871 15.1692Z',\n 'M56.6639 31.8406C61.3305 26.4186 66.4795 21.7485 72 17.8798C70.5099 14.9444 68.6595 12.2188 66.5054 9.76043C60.0522 14.2115 54.1641 19.5213 49.0257 25.5182C44.1063 31.285 44.1264 39.9493 49.0589 45.7009C54.2856 51.7641 60.0958 57.0233 66.3624 61.3961C68.5461 58.9311 70.4233 56.1939 71.9351 53.2424C66.4392 49.3831 61.3122 44.7318 56.6639 39.3396C54.8191 37.177 54.8191 34.0032 56.6639 31.8406Z',\n 'M40.0024 56.8308C45.4841 61.4466 50.2056 66.5396 54.1168 72C57.0845 70.5261 59.8401 68.6958 62.3255 66.5652C57.8254 60.1821 52.4573 54.3582 46.3944 49.2757C40.5642 44.4098 31.8045 44.4297 25.9897 49.3085C19.8598 54.4783 14.5427 60.2253 10.1219 66.4237C12.614 68.5837 15.3813 70.4405 18.3653 71.9358C22.2671 66.4997 26.9695 61.4284 32.421 56.8308C34.6073 55.006 37.8161 55.006 40.0024 56.8308Z',\n 'M15.3361 39.3159C10.6695 44.7379 5.52049 49.408 0 53.2767C1.49011 56.2122 3.34051 58.9378 5.49457 61.3961C11.9478 56.945 17.8359 51.6352 22.9743 45.6383C27.8937 39.8716 27.8736 31.2072 22.9411 25.4556C17.7144 19.3924 11.9042 14.1332 5.63758 9.76043C3.45386 12.2255 1.57669 14.9627 0.0648629 17.9141C5.56077 21.7735 10.6878 26.4247 15.3361 31.8169C17.1809 33.9795 17.1809 37.1533 15.3361 39.3159Z',\n] as const;\n\nconst VARIANT_FILLS: Record<Exclude<AILogoVariant, 'yellowfin'>, string> = {\n dark: '#262626',\n light: '#FAFAFA',\n};\n\ntype AILogoPathsProps = {\n variant: AILogoVariant;\n gradientId: string;\n};\n\nfunction AILogoPaths({ variant, gradientId }: AILogoPathsProps) {\n if (variant === 'yellowfin') {\n return (\n <>\n <defs>\n <linearGradient id={gradientId} x1=\"5.37931\" y1=\"0\" x2=\"50.046\" y2=\"65.8568\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0.0544031\" stopColor=\"#1868DB\" />\n <stop offset=\"0.5\" stopColor=\"#357DE8\" />\n <stop offset=\"0.802885\" stopColor=\"#7EE2B8\" />\n <stop offset=\"1\" stopColor=\"#B4EE2B\" />\n </linearGradient>\n </defs>\n {PATH_DATA.map((d, i) => (\n <path key={i} d={d} fill={`url(#${gradientId})`} />\n ))}\n </>\n );\n }\n\n const fill = VARIANT_FILLS[variant];\n\n return (\n <>\n {PATH_DATA.map((d, i) => (\n <path key={i} d={d} fill={fill} />\n ))}\n </>\n );\n}\n\nexport interface AILogoProps extends React.SVGProps<SVGSVGElement> {\n variant: AILogoVariant;\n}\n\nexport const AILogo = React.forwardRef<SVGSVGElement, AILogoProps>(\n ({ variant, width = 72, height = 72, ...props }, ref) => {\n const id = React.useId();\n const gradientId = `ai-logo-gradient-${id}`;\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n {...props}\n width={width}\n height={height}\n ref={ref}\n viewBox=\"0 0 72 72\"\n role=\"img\"\n fill=\"none\"\n >\n <title>BrainfishAI logo</title>\n <AILogoPaths variant={variant} gradientId={gradientId} />\n </svg>\n );\n },\n);\n\nAILogo.displayName = 'AILogo';\n"],"names":["PATH_DATA","VARIANT_FILLS","dark","light","AILogoPaths","variant","gradientId","React","createElement","Fragment","id","x1","y1","x2","y2","gradientUnits","offset","stopColor","map","d","i","key","fill","AILogo","forwardRef","width","height","props","ref","useId","xmlns","viewBox","role","displayName"],"mappings":"wBAIA,MAAMA,EAAY,CAChB,uYACA,6YACA,2YACA,+YAGIC,EAAqE,CACzEC,KAAM,UACNC,MAAO,WAQT,SAASC,GAAYC,QAAEA,EAAAC,WAASA,IAC9B,GAAgB,cAAZD;AACF,OACEE,EAAAC,cAAAD,EAAAE,SAAA,oBACEF,EAAAC,cAAC,OAAA,oBACCD,EAAAC,cAAC,iBAAA,CAAeE,GAAIJ,EAAYK,GAAG,UAAUC,GAAG,IAAIC,GAAG,SAASC,GAAG,UAAUC,cAAc,iCACzFR,EAAAC,cAAC,OAAA,CAAKQ,OAAO,YAAYC,UAAU,2BACnCV,EAAAC,cAAC,OAAA,CAAKQ,OAAO,MAAMC,UAAU,2BAC7BV,EAAAC,cAAC,OAAA,CAAKQ,OAAO,WAAWC,UAAU,2BAClCV,EAAAC,cAAC,OAAA,CAAKQ,OAAO,IAAIC,UAAU,cAG9BjB,EAAUkB,IAAI,CAACC,EAAGC,mBACjBb,EAAAC,cAAC,OAAA,CAAKa,IAAKD,EAAGD,IAAMG,KAAM,QAAQhB,SAM1C,MAAMgB,EAAOrB,EAAcI;AAE3B,OACEE,EAAAC,cAAAD,EAAAE,SAAA,KACGT,EAAUkB,IAAI,CAACC,EAAGC,mBACjBb,EAAAC,cAAC,OAAA,CAAKa,IAAKD,EAAGD,IAAMG,UAI5B,CAMO,MAAMC,EAAShB,EAAMiB,WAC1B,EAAGnB,UAASoB,QAAQ,GAAIC,SAAS,MAAOC,GAASC,KAC/C,MACMtB,EAAa,oBADRC,EAAMsB;AAGjB,OACEtB,EAAAC,cAAC,MAAA,CACCsB,MAAM,gCACFH,EACJF,QACAC,SACAE,MACAG,QAAQ,YACRC,KAAK,MACLV,KAAK;eAELf,EAAAC,cAAC,aAAM;eACPD,EAAAC,cAACJ,EAAA,CAAYC,UAAkBC,kBAMvCiB,EAAOU,YAAc"}
@@ -1,6 +1,6 @@
1
- import*as e from"react";import{CaretUpDown as t,Check as a}from"@phosphor-icons/react";import{c as l}from"./utils.C6Qu-kwd.js";import{B as n}from"./button.D_2SonNs.js";import{Command as o,CommandInput as r,CommandList as s,CommandEmpty as c,CommandGroup as m,CommandItem as u}from"../components/ui/command.js";import{Popover as i,PopoverTrigger as p,PopoverContent as d}from"../components/ui/popover.js";function f({options:f,defaultValueLabel:E,placeholder:b,noResultsLabel:v,className:h,onOpenChange:y,onChange:g,dropdownWidth:S,value:j,disabled:C=!1,renderItem:N,renderValue:w,renderTrigger:x,appendToBody:B=!0}){const[O,T]=e.useState(!1),[k,I]=e.useState(""),L=void 0!==j?j:k,R=e.useRef(null),[V,W]=e.useState(null),F=f.find(e=>e.value===L),$=e=>{const t=e===L?"":e;void 0===j&&I(t),T(!1),g?.(t)},q=F?w?w(F):F.label:E;/* @__PURE__ */
2
- return e.createElement(i,{open:O,onOpenChange:e=>{e&&R.current&&W(S||R.current.offsetWidth),T(e),y?.(e)}},/* @__PURE__ */e.createElement(p,{asChild:!0},/* @__PURE__ */e.createElement(n,{ref:R,variant:"outline",role:"combobox","aria-expanded":O,disabled:C,className:l("w-full justify-between",h)},x?x({selectedItem:F,isOpen:O}):q,
1
+ import*as e from"react";import{CaretUpDown as t,Check as a}from"@phosphor-icons/react";import{c as l}from"./utils.C6Qu-kwd.js";import{B as n}from"./button.D_2SonNs.js";import{Command as o,CommandInput as r,CommandList as s,CommandEmpty as c,CommandGroup as m,CommandItem as u}from"../components/ui/command.js";import{Popover as i,PopoverTrigger as p,PopoverContent as d}from"../components/ui/popover.js";function f({options:f,defaultValueLabel:E,placeholder:b,noResultsLabel:v,className:h,onOpenChange:y,onChange:g,dropdownWidth:S,value:j,disabled:C=!1,renderItem:N,renderValue:w,renderTrigger:x,appendToBody:B=!0}){const[O,T]=e.useState(!1),[k,I]=e.useState(""),L=void 0!==j?j:k,R=e.useRef(null),[V,W]=e.useState(null),q=f.find(e=>e.value===L),F=e=>{const t=e===L?"":e;void 0===j&&I(t),T(!1),g?.(t)},$=q?w?w(q):q.label:E;/* @__PURE__ */
2
+ return e.createElement(i,{open:O,onOpenChange:e=>{e&&R.current&&W(S||R.current.offsetWidth),T(e),y?.(e)}},/* @__PURE__ */e.createElement(p,{asChild:!0},/* @__PURE__ */e.createElement(n,{ref:R,variant:"outline",role:"combobox","aria-expanded":O,disabled:C,className:l("w-full justify-between",h)},x?x({selectedItem:q,isOpen:O}):$,
3
3
  /* @__PURE__ */e.createElement(t,{className:"opacity-50"}))),/* @__PURE__ */e.createElement(d,{appendToBody:B,className:"p-0",style:{width:V?`${V}px`:"auto"},align:"start"},
4
- /* @__PURE__ */e.createElement(o,null,/* @__PURE__ */e.createElement(r,{placeholder:b}),/* @__PURE__ */e.createElement(s,null,/* @__PURE__ */e.createElement(c,null,v),/* @__PURE__ */e.createElement(m,null,f.map(t=>{const n=L===t.value;return N?/* @__PURE__ */e.createElement(e.Fragment,{key:t.value},N({item:t,isSelected:n,onSelect:()=>$(t.value)})):/* @__PURE__ */e.createElement(u,{key:t.value,value:t.label,onSelect:e=>{const t=f.find(t=>t.label===e);$(t?.value||"")}},t.label,
4
+ /* @__PURE__ */e.createElement(o,null,/* @__PURE__ */e.createElement(r,{placeholder:b}),/* @__PURE__ */e.createElement(s,null,/* @__PURE__ */e.createElement(c,null,v),/* @__PURE__ */e.createElement(m,null,f.map(t=>{const n=L===t.value;return N?/* @__PURE__ */e.createElement(e.Fragment,{key:t.value},N({item:t,isSelected:n,onSelect:()=>F(t.value)})):/* @__PURE__ */e.createElement(u,{key:t.value,value:t.label,onSelect:e=>{const t=f.find(t=>t.label===e);F(t?.value||"")}},t.label,
5
5
  /* @__PURE__ */e.createElement(a,{className:l("ml-auto",n?"opacity-100":"opacity-0")}))}))))))}export{f as C};
6
- //# sourceMappingURL=combobox.MyoPH18G.js.map
6
+ //# sourceMappingURL=combobox.DJCyKFxV.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"combobox.MyoPH18G.js","sources":["../../../src/components/combobox/combobox.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Check, CaretUpDown } from '@phosphor-icons/react';\n\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\n\nexport interface ComboboxItem {\n value: string;\n label: string;\n icon?: React.ReactNode;\n /** Additional data that can be passed to custom renderers */\n\n data?: any;\n}\n\nexport interface ComboboxRenderItemProps<T extends ComboboxItem = ComboboxItem> {\n item: T;\n isSelected: boolean;\n onSelect: () => void;\n}\n\nexport interface ComboboxProps<T extends ComboboxItem = ComboboxItem> {\n options: T[];\n defaultValueLabel: string;\n placeholder: string;\n noResultsLabel: string;\n className?: string;\n dropdownWidth?: number;\n value?: string;\n /** Whether the combobox is disabled */\n disabled?: boolean;\n /** Callback on open state change */\n onOpenChange?: (open: boolean) => void;\n /** Callback on value change */\n onChange?: (value: string) => void;\n /** Custom render function for each item in the dropdown */\n renderItem?: (props: ComboboxRenderItemProps<T>) => React.ReactNode;\n /** Custom render function for the selected value in the trigger button */\n renderValue?: (item: T) => React.ReactNode;\n /** Custom render function for the entire trigger button content */\n renderTrigger?: (props: { selectedItem: T | undefined; isOpen: boolean }) => React.ReactNode;\n /**\n * When false, the dropdown stays in the React tree instead of portaling to body.\n * Use inside modals/dialogs so the list is not detached from the dialog layer.\n */\n appendToBody?: boolean;\n}\n\nexport function Combobox<T extends ComboboxItem = ComboboxItem>({\n options,\n defaultValueLabel,\n placeholder,\n noResultsLabel,\n className,\n onOpenChange,\n onChange,\n dropdownWidth,\n value: controlledValue,\n disabled = false,\n renderItem,\n renderValue,\n renderTrigger,\n appendToBody = true,\n}: ComboboxProps<T>) {\n const [open, setOpen] = React.useState(false);\n const [internalValue, setInternalValue] = React.useState('');\n\n // Use controlled value if provided, otherwise use internal state\n const value = controlledValue !== undefined ? controlledValue : internalValue;\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const [triggerWidth, setTriggerWidth] = React.useState<number | null>(null);\n\n const selectedItem = options.find((option) => option.value === value);\n\n // Update width when popover opens\n const handleOpenChange = (isOpen: boolean) => {\n if (isOpen && triggerRef.current) {\n setTriggerWidth(dropdownWidth ? dropdownWidth : triggerRef.current.offsetWidth);\n }\n setOpen(isOpen);\n onOpenChange?.(isOpen);\n };\n\n const handleSelect = (selectedValue: string) => {\n const newValue = selectedValue === value ? '' : selectedValue;\n\n // Update internal state only if not controlled\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n }\n\n setOpen(false);\n onChange?.(newValue);\n };\n\n // Default trigger content\n const defaultTriggerContent = selectedItem\n ? renderValue\n ? renderValue(selectedItem)\n : selectedItem.label\n : defaultValueLabel;\n\n return (\n <Popover open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger asChild>\n <Button\n ref={triggerRef}\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n disabled={disabled}\n className={cn('w-full justify-between', className)}\n >\n {renderTrigger ? renderTrigger({ selectedItem, isOpen: open }) : defaultTriggerContent}\n <CaretUpDown className=\"opacity-50\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent\n appendToBody={appendToBody}\n className=\"p-0\"\n style={{\n width: triggerWidth ? `${triggerWidth}px` : 'auto',\n }}\n align=\"start\"\n >\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{noResultsLabel}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => {\n const isSelected = value === option.value;\n\n if (renderItem) {\n return (\n <React.Fragment key={option.value}>\n {renderItem({\n item: option,\n isSelected,\n onSelect: () => handleSelect(option.value),\n })}\n </React.Fragment>\n );\n }\n\n return (\n <CommandItem\n key={option.value}\n value={option.label}\n onSelect={(currentLabel) => {\n const selectedOption = options.find((opt) => opt.label === currentLabel);\n\n handleSelect(selectedOption?.value || '');\n }}\n >\n {option.label}\n <Check className={cn('ml-auto', isSelected ? 'opacity-100' : 'opacity-0')} />\n </CommandItem>\n );\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["Combobox","options","defaultValueLabel","placeholder","noResultsLabel","className","onOpenChange","onChange","dropdownWidth","value","controlledValue","disabled","renderItem","renderValue","renderTrigger","appendToBody","open","setOpen","React","useState","internalValue","setInternalValue","triggerRef","useRef","triggerWidth","setTriggerWidth","selectedItem","find","option","handleSelect","selectedValue","newValue","defaultTriggerContent","label","createElement","Popover","isOpen","current","offsetWidth","PopoverTrigger","asChild","Button","ref","variant","role","cn","CaretUpDown","PopoverContent","style","width","align","Command","CommandInput","CommandList","CommandEmpty","CommandGroup","map","isSelected","Fragment","key","item","onSelect","CommandItem","currentLabel","selectedOption","opt","Check"],"mappings":"oZAkDO,SAASA,GAAgDC,QAC9DA,EAAAC,kBACAA,EAAAC,YACAA,EAAAC,eACAA,EAAAC,UACAA,EAAAC,aACAA,EAAAC,SACAA,EAAAC,cACAA,EACAC,MAAOC,EAAAC,SACPA,GAAW,EAAAC,WACXA,EAAAC,YACAA,EAAAC,cACAA,EAAAC,aACAA,GAAe,IAEf,MAAOC,EAAMC,GAAWC,EAAMC,UAAS,IAChCC,EAAeC,GAAoBH,EAAMC,SAAS,IAGnDV,OAA4B,IAApBC,EAAgCA,EAAkBU,EAC1DE,EAAaJ,EAAMK,OAA0B,OAC5CC,EAAcC,GAAmBP,EAAMC,SAAwB,MAEhEO,EAAezB,EAAQ0B,KAAMC,GAAWA,EAAOnB,QAAUA,GAWzDoB,EAAgBC,IACpB,MAAMC,EAAWD,IAAkBrB,EAAQ,GAAKqB,OAGxB,IAApBpB,GACFW,EAAiBU,GAGnBd,GAAQ,GACRV,IAAWwB,IAIPC,EAAwBN,EAC1Bb,EACEA,EAAYa,GACZA,EAAaO,MACf/B;AAEJ,OACEgB,EAAAgB,cAACC,GAAQnB,OAAYV,aA5BG8B,IACpBA,GAAUd,EAAWe,SACvBZ,EAAgBjB,GAAgCc,EAAWe,QAAQC,aAErErB,EAAQmB,GACR9B,IAAe8B,oBAwBblB,EAAAgB,cAACK,EAAA,CAAeC,SAAO,kBACrBtB,EAAAgB,cAACO,EAAA,CACCC,IAAKpB,EACLqB,QAAQ,UACRC,KAAK,WACL,gBAAe5B,EACfL,WACAN,UAAWwC,EAAG,yBAA0BxC,IAEvCS,EAAgBA,EAAc,CAAEY,eAAcU,OAAQpB,IAAUgB;eACjEd,EAAAgB,cAACY,EAAA,CAAYzC,UAAU,gCAG3Ba,EAAAgB,cAACa,EAAA,CACChC,eACAV,UAAU,MACV2C,MAAO,CACLC,MAAOzB,EAAe,GAAGA,MAAmB,QAE9C0B,MAAM;+BAELC,EAAA,oBACCjC,EAAAgB,cAACkB,EAAA,CAAajD,+BACde,EAAAgB,cAACmB,EAAA,oBACCnC,EAAAgB,cAACoB,EAAA,KAAclD,kBACfc,EAAAgB,cAACqB,OACEtD,EAAQuD,IAAK5B,IACZ,MAAM6B,EAAahD,IAAUmB,EAAOnB,MAEpC,OAAIG,iCAECM,EAAMwC,SAAN,CAAeC,IAAK/B,EAAOnB,OACzBG,EAAW,CACVgD,KAAMhC,EACN6B,aACAI,SAAU,IAAMhC,EAAaD,EAAOnB,yBAO1CS,EAAAgB,cAAC4B,EAAA,CACCH,IAAK/B,EAAOnB,MACZA,MAAOmB,EAAOK,MACd4B,SAAWE,IACT,MAAMC,EAAiB/D,EAAQ0B,KAAMsC,GAAQA,EAAIhC,QAAU8B,GAE3DlC,EAAamC,GAAgBvD,OAAS,MAGvCmB,EAAOK;eACRf,EAAAgB,cAACgC,GAAM7D,UAAWwC,EAAG,UAAWY,EAAa,cAAgB,sBAUjF"}
1
+ {"version":3,"file":"combobox.DJCyKFxV.js","sources":["../../../src/components/combobox/combobox.tsx"],"sourcesContent":["import * as React from 'react';\nimport { Check, CaretUpDown } from '@phosphor-icons/react';\n\nimport { cn } from '@/lib/utils';\nimport { Button } from '@/components/ui/button';\nimport { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from '@/components/ui/command';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\n\nexport interface ComboboxItem {\n value: string;\n label: string;\n icon?: React.ReactNode;\n /** Additional data that can be passed to custom renderers */\n\n data?: any;\n}\n\nexport interface ComboboxRenderItemProps<T extends ComboboxItem = ComboboxItem> {\n item: T;\n isSelected: boolean;\n onSelect: () => void;\n}\n\nexport interface ComboboxProps<T extends ComboboxItem = ComboboxItem> {\n options: T[];\n defaultValueLabel: string;\n placeholder: string;\n noResultsLabel: string;\n className?: string;\n dropdownWidth?: number;\n value?: string;\n /** Whether the combobox is disabled */\n disabled?: boolean;\n /** Callback on open state change */\n onOpenChange?: (open: boolean) => void;\n /** Callback on value change */\n onChange?: (value: string) => void;\n /** Custom render function for each item in the dropdown */\n renderItem?: (props: ComboboxRenderItemProps<T>) => React.ReactNode;\n /** Custom render function for the selected value in the trigger button */\n renderValue?: (item: T) => React.ReactNode;\n /** Custom render function for the entire trigger button content */\n renderTrigger?: (props: { selectedItem: T | undefined; isOpen: boolean }) => React.ReactNode;\n /**\n * When false, the dropdown stays in the React tree instead of portaling to body.\n * Use inside modals/dialogs so the list is not detached from the dialog layer.\n */\n appendToBody?: boolean;\n}\n\nexport function Combobox<T extends ComboboxItem = ComboboxItem>({\n options,\n defaultValueLabel,\n placeholder,\n noResultsLabel,\n className,\n onOpenChange,\n onChange,\n dropdownWidth,\n value: controlledValue,\n disabled = false,\n renderItem,\n renderValue,\n renderTrigger,\n appendToBody = true,\n}: ComboboxProps<T>) {\n const [open, setOpen] = React.useState(false);\n const [internalValue, setInternalValue] = React.useState('');\n\n // Use controlled value if provided, otherwise use internal state\n const value = controlledValue !== undefined ? controlledValue : internalValue;\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const [triggerWidth, setTriggerWidth] = React.useState<number | null>(null);\n\n const selectedItem = options.find((option) => option.value === value);\n\n // Update width when popover opens\n const handleOpenChange = (isOpen: boolean) => {\n if (isOpen && triggerRef.current) {\n setTriggerWidth(dropdownWidth ? dropdownWidth : triggerRef.current.offsetWidth);\n }\n setOpen(isOpen);\n onOpenChange?.(isOpen);\n };\n\n const handleSelect = (selectedValue: string) => {\n const newValue = selectedValue === value ? '' : selectedValue;\n\n // Update internal state only if not controlled\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n }\n\n setOpen(false);\n onChange?.(newValue);\n };\n\n // Default trigger content\n const defaultTriggerContent = selectedItem\n ? renderValue\n ? renderValue(selectedItem)\n : selectedItem.label\n : defaultValueLabel;\n\n return (\n <Popover open={open} onOpenChange={handleOpenChange}>\n <PopoverTrigger asChild>\n <Button\n ref={triggerRef}\n variant=\"outline\"\n role=\"combobox\"\n aria-expanded={open}\n disabled={disabled}\n className={cn('w-full justify-between', className)}\n >\n {renderTrigger ? renderTrigger({ selectedItem, isOpen: open }) : defaultTriggerContent}\n <CaretUpDown className=\"opacity-50\" />\n </Button>\n </PopoverTrigger>\n <PopoverContent\n appendToBody={appendToBody}\n className=\"p-0\"\n style={{\n width: triggerWidth ? `${triggerWidth}px` : 'auto',\n }}\n align=\"start\"\n >\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{noResultsLabel}</CommandEmpty>\n <CommandGroup>\n {options.map((option) => {\n const isSelected = value === option.value;\n\n if (renderItem) {\n return (\n <React.Fragment key={option.value}>\n {renderItem({\n item: option,\n isSelected,\n onSelect: () => handleSelect(option.value),\n })}\n </React.Fragment>\n );\n }\n\n return (\n <CommandItem\n key={option.value}\n value={option.label}\n onSelect={(currentLabel) => {\n const selectedOption = options.find((opt) => opt.label === currentLabel);\n\n handleSelect(selectedOption?.value || '');\n }}\n >\n {option.label}\n <Check className={cn('ml-auto', isSelected ? 'opacity-100' : 'opacity-0')} />\n </CommandItem>\n );\n })}\n </CommandGroup>\n </CommandList>\n </Command>\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["Combobox","options","defaultValueLabel","placeholder","noResultsLabel","className","onOpenChange","onChange","dropdownWidth","value","controlledValue","disabled","renderItem","renderValue","renderTrigger","appendToBody","open","setOpen","React","useState","internalValue","setInternalValue","triggerRef","useRef","triggerWidth","setTriggerWidth","selectedItem","find","option","handleSelect","selectedValue","newValue","defaultTriggerContent","label","createElement","Popover","isOpen","current","offsetWidth","PopoverTrigger","asChild","Button","ref","variant","role","cn","CaretUpDown","PopoverContent","style","width","align","Command","CommandInput","CommandList","CommandEmpty","CommandGroup","map","isSelected","Fragment","key","item","onSelect","CommandItem","currentLabel","selectedOption","opt","Check"],"mappings":"oZAkDO,SAASA,GAAgDC,QAC9DA,EAAAC,kBACAA,EAAAC,YACAA,EAAAC,eACAA,EAAAC,UACAA,EAAAC,aACAA,EAAAC,SACAA,EAAAC,cACAA,EACAC,MAAOC,EAAAC,SACPA,GAAW,EAAAC,WACXA,EAAAC,YACAA,EAAAC,cACAA,EAAAC,aACAA,GAAe,IAEf,MAAOC,EAAMC,GAAWC,EAAMC,UAAS,IAChCC,EAAeC,GAAoBH,EAAMC,SAAS,IAGnDV,OAA4B,IAApBC,EAAgCA,EAAkBU,EAC1DE,EAAaJ,EAAMK,OAA0B,OAC5CC,EAAcC,GAAmBP,EAAMC,SAAwB,MAEhEO,EAAezB,EAAQ0B,KAAMC,GAAWA,EAAOnB,QAAUA,GAWzDoB,EAAgBC,IACpB,MAAMC,EAAWD,IAAkBrB,EAAQ,GAAKqB,OAGxB,IAApBpB,GACFW,EAAiBU,GAGnBd,GAAQ,GACRV,IAAWwB,IAIPC,EAAwBN,EAC1Bb,EACEA,EAAYa,GACZA,EAAaO,MACf/B;AAEJ,OACEgB,EAAAgB,cAACC,GAAQnB,OAAYV,aA5BG8B,IACpBA,GAAUd,EAAWe,SACvBZ,EAAgBjB,GAAgCc,EAAWe,QAAQC,aAErErB,EAAQmB,GACR9B,IAAe8B,oBAwBblB,EAAAgB,cAACK,EAAA,CAAeC,SAAO,kBACrBtB,EAAAgB,cAACO,EAAA,CACCC,IAAKpB,EACLqB,QAAQ,UACRC,KAAK,WACL,gBAAe5B,EACfL,WACAN,UAAWwC,EAAG,yBAA0BxC,IAEvCS,EAAgBA,EAAc,CAAEY,eAAcU,OAAQpB,IAAUgB;eACjEd,EAAAgB,cAACY,EAAA,CAAYzC,UAAU,gCAG3Ba,EAAAgB,cAACa,EAAA,CACChC,eACAV,UAAU,MACV2C,MAAO,CACLC,MAAOzB,EAAe,GAAGA,MAAmB,QAE9C0B,MAAM;+BAELC,EAAA,oBACCjC,EAAAgB,cAACkB,EAAA,CAAajD,+BACde,EAAAgB,cAACmB,EAAA,oBACCnC,EAAAgB,cAACoB,EAAA,KAAclD,kBACfc,EAAAgB,cAACqB,OACEtD,EAAQuD,IAAK5B,IACZ,MAAM6B,EAAahD,IAAUmB,EAAOnB,MAEpC,OAAIG,iCAECM,EAAMwC,SAAN,CAAeC,IAAK/B,EAAOnB,OACzBG,EAAW,CACVgD,KAAMhC,EACN6B,aACAI,SAAU,IAAMhC,EAAaD,EAAOnB,yBAO1CS,EAAAgB,cAAC4B,EAAA,CACCH,IAAK/B,EAAOnB,MACZA,MAAOmB,EAAOK,MACd4B,SAAWE,IACT,MAAMC,EAAiB/D,EAAQ0B,KAAMsC,GAAQA,EAAIhC,QAAU8B,GAE3DlC,EAAamC,GAAgBvD,OAAS,MAGvCmB,EAAOK;eACRf,EAAAgB,cAACgC,GAAM7D,UAAWwC,EAAG,UAAWY,EAAa,cAAgB,sBAUjF"}
@@ -3,7 +3,7 @@ return e.createElement("form",{className:"p-3",onSubmit:e=>{e.preventDefault(),o
3
3
  /* @__PURE__ */e.createElement(m,{ref:c,type:"text",value:l,onChange:e=>r(e.target.value),placeholder:a,className:"mb-2",onKeyDown:e=>s(e,!0)}),
4
4
  /* @__PURE__ */e.createElement(i,{type:"submit",variant:"dark",className:"w-full",onKeyDown:e=>s(e,!1),"data-custom-submit-button":!0},n))},w=({internalFilter:r,valueOptions:a,onConfirm:n,onClose:s})=>{const[c,u]=l(()=>{return e=r.value,Array.isArray(e)?e:e?[e]:[];var e}),[m,d]=l(""),p=t([]),f=t(null),v=e=>{const t=a.find(t=>t.id===e),l=c.includes(e);u(r=>{const a=r.includes(e)?r.filter(t=>t!==e):[...r,e];if(t){const e=l?"deselected":"selected";d(`${t.label} ${e}.`)}return a})},b=()=>{n(c),s()},g=e=>c.includes(e),h=e=>{e<a.length?p.current[e]?.focus():f.current?.focus()},E=(e,t)=>{switch(e.key){case"ArrowDown":{e.preventDefault();const l=Math.min(t+1,a.length);h(l);break}case"ArrowUp":{e.preventDefault();const l=Math.max(t-1,0);h(l);break}case"Enter":case"Spacebar":e.preventDefault(),t<a.length?v(a[t].id):b();break;case"Escape":e.preventDefault(),s()}};/* @__PURE__ */
5
5
  return e.createElement("div",{className:"py-1",role:"listbox","aria-multiselectable":"true"},/* @__PURE__ */e.createElement("div",{"aria-live":"polite","aria-atomic":"true",className:"sr-only"},m),a.map((t,l)=>/* @__PURE__ */e.createElement("button",{key:t.id,ref:e=>p.current[l]=e,type:"button",className:"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",onClick:()=>v(t.id),onKeyDown:e=>E(e,l),role:"option","aria-selected":g(t.id)},g(t.id)&&/* @__PURE__ */e.createElement(o,{size:14,className:"mr-2"}),!g(t.id)&&/* @__PURE__ */e.createElement("span",{className:"mr-2 w-3.5"}),t.label)),/* @__PURE__ */e.createElement("div",{className:"border-t border-border mt-1 pt-2 px-2 pb-2"},/* @__PURE__ */e.createElement(i,{ref:f,onClick:b,size:"sm",className:"w-full",disabled:0===c.length,onKeyDown:e=>E(e,a.length)},"Apply (",c.length," selected)")))},x=({valueOptions:l,isValueSelected:r,onValueSelect:a})=>{const n=t([]),s=e=>{n.current[e]?.focus()};/* @__PURE__ */
6
- return e.createElement("div",{className:"py-1",role:"listbox"},l.map((t,c)=>/* @__PURE__ */e.createElement("button",{key:t.id,ref:e=>n.current[c]=e,type:"button",className:"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",onClick:()=>a(t.id),onKeyDown:e=>((e,t)=>{switch(e.key){case"ArrowDown":{e.preventDefault();const r=Math.min(t+1,l.length-1);s(r);break}case"ArrowUp":{e.preventDefault();const l=Math.max(t-1,0);s(l);break}case"Enter":case"Spacebar":e.preventDefault(),a(l[t].id)}})(e,c)},r(t.id)&&/* @__PURE__ */e.createElement(o,{size:14,className:"mr-2"}),!r(t.id)&&/* @__PURE__ */e.createElement("span",{className:"mr-2 w-3.5"}),t.label)))},C=r(({filter:r,sourceOptions:c,rules:u,onSourceChange:m,onRemoveFilter:y,onFilterChange:C,selectPlaceholder:D="Select a value",customValuePlaceholder:k="Enter custom value",customValueButtonLabel:O="Done",sourceFirstMessage:V="Please select a source first"},A)=>{const[F,T]=l({id:r.id,source:r.source,operator:r.operator||N.IS,value:r.value}),[z,I]=l(""),[K,j]=l(null),_=t(null),B=t(null),M=t(null);a(A,()=>({focus:()=>{B.current?.focus()}}));const P=F.source?u[F.source]:void 0,R=P?.operators||[],L=P?.multiSelect||!1;let $=P?.inputType||"dropdown";F.operator!==N.CONTAINS&&F.operator!==N.DOES_NOT_CONTAIN||($="custom");const q=P?.getValueOptions?P.getValueOptions():[],{getSelectedValues:U,isValueSelected:H,getDisplayValue:W}=((e,t,l)=>{const r=()=>Array.isArray(e.value)?e.value:e.value?[e.value]:[];return{getSelectedValues:r,isValueSelected:e=>r().includes(e),getDisplayValue:()=>{const e=r();if(0===e.length)return l;if(1===e.length){const l=t.find(t=>t.id===e[0]);return l?.label||e[0]}return`${e.length} selected`}}})(F,q,D);n(()=>{if("value"===K&&"custom"===$){const e=setTimeout(()=>_.current?.focus(),50);return()=>clearTimeout(e)}},[K,$]),n(()=>{T({id:r.id,source:r.source,operator:r.operator||N.IS,value:r.value})},[r]);const G=e=>{const t=Array.isArray(e.value)?e.value.length>0:!!e.value?.trim();e.source&&e.operator&&t&&C(e)},J=(e,t)=>{const l={...F,[e]:t};if("source"===e){const e=u[t];l.operator=e?.operators[0]?.id,l.value=e?.multiSelect?[]:""}if("operator"===e&&F.source){const e=u[F.source];"dropdown"===e?.inputType&&(l.value=e?.multiSelect?[]:"")}T(l),G(l),L&&"value"===e||j(null)};/* @__PURE__ */
6
+ return e.createElement("div",{className:"py-1",role:"listbox"},l.map((t,c)=>/* @__PURE__ */e.createElement("button",{key:t.id,ref:e=>n.current[c]=e,type:"button",className:"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",onClick:()=>a(t.id),onKeyDown:e=>((e,t)=>{switch(e.key){case"ArrowDown":{e.preventDefault();const r=Math.min(t+1,l.length-1);s(r);break}case"ArrowUp":{e.preventDefault();const l=Math.max(t-1,0);s(l);break}case"Enter":case"Spacebar":e.preventDefault(),a(l[t].id)}})(e,c)},r(t.id)&&/* @__PURE__ */e.createElement(o,{size:14,className:"mr-2"}),!r(t.id)&&/* @__PURE__ */e.createElement("span",{className:"mr-2 w-3.5"}),t.label)))},C=r(({filter:r,sourceOptions:c,rules:u,onSourceChange:m,onRemoveFilter:y,onFilterChange:C,selectPlaceholder:D="Select a value",customValuePlaceholder:k="Enter custom value",customValueButtonLabel:O="Done",sourceFirstMessage:V="Please select a source first"},A)=>{const[F,T]=l({id:r.id,source:r.source,operator:r.operator||N.IS,value:r.value}),[z,I]=l(""),[K,j]=l(null),_=t(null),B=t(null),M=t(null);a(A,()=>({focus:()=>{B.current?.focus()}}));const P=F.source?u[F.source]:void 0,R=P?.operators||[],q=P?.multiSelect||!1;let L=P?.inputType||"dropdown";F.operator!==N.CONTAINS&&F.operator!==N.DOES_NOT_CONTAIN||(L="custom");const $=P?.getValueOptions?P.getValueOptions():[],{getSelectedValues:U,isValueSelected:H,getDisplayValue:W}=((e,t,l)=>{const r=()=>Array.isArray(e.value)?e.value:e.value?[e.value]:[];return{getSelectedValues:r,isValueSelected:e=>r().includes(e),getDisplayValue:()=>{const e=r();if(0===e.length)return l;if(1===e.length){const l=t.find(t=>t.id===e[0]);return l?.label||e[0]}return`${e.length} selected`}}})(F,$,D);n(()=>{if("value"===K&&"custom"===L){const e=setTimeout(()=>_.current?.focus(),50);return()=>clearTimeout(e)}},[K,L]),n(()=>{T({id:r.id,source:r.source,operator:r.operator||N.IS,value:r.value})},[r]);const G=e=>{const t=Array.isArray(e.value)?e.value.length>0:!!e.value?.trim();e.source&&e.operator&&t&&C(e)},J=(e,t)=>{const l={...F,[e]:t};if("source"===e){const e=u[t];l.operator=e?.operators[0]?.id,l.value=e?.multiSelect?[]:""}if("operator"===e&&F.source){const e=u[F.source];"dropdown"===e?.inputType&&(l.value=e?.multiSelect?[]:"")}T(l),G(l),q&&"value"===e||j(null)};/* @__PURE__ */
7
7
  return e.createElement(d,{ref:M,orientation:"horizontal",className:"rounded-full border border-border divide-x-2 divide-dark-500"},
8
8
  /* @__PURE__ */e.createElement(p,{open:"source"===K,onOpenChange:e=>j(e?"source":null),modal:!1},
9
9
  /* @__PURE__ */e.createElement(f,{asChild:!0},/* @__PURE__ */e.createElement(i,{ref:B,variant:"ghost",size:"xs",className:"rounded-l-full font-bold"},c.find(e=>e.id===F.source)?.label||c[0]?.label||D)),
@@ -13,10 +13,10 @@ return e.createElement(d,{ref:M,orientation:"horizontal",className:"rounded-full
13
13
  /* @__PURE__ */e.createElement(v,{align:"start",className:"w-48"},R.map(t=>/* @__PURE__ */e.createElement(b,{key:t.id,className:"flex items-center",onSelect:()=>J("operator",t.id)},t.id===F.operator&&/* @__PURE__ */e.createElement(o,{size:14,className:"mr-2"}),t.label)))),
14
14
  /* @__PURE__ */e.createElement(g,{open:"value"===K,onOpenChange:e=>j(e?"value":null)},
15
15
  /* @__PURE__ */e.createElement(h,{asChild:!0},/* @__PURE__ */e.createElement(i,{variant:"ghost",size:"xs",className:"font-bold"},W())),
16
- /* @__PURE__ */e.createElement(E,{align:"start",className:"w-64 max-h-[min(320px,50vh)] overflow-y-auto p-0"},"custom"===$&&/* @__PURE__ */e.createElement("div",{className:"p-3"},/* @__PURE__ */e.createElement(S,{inputValue:z,setInputValue:I,customValuePlaceholder:k,customValueButtonLabel:O,onSubmit:()=>{if(z.trim()){const e={...F,value:z.trim()};T(e),I(""),j(null),e.source&&e.operator&&e.value&&C(e)}},handleKeyDown:(e,t)=>{if("Escape"!==e.key){if("Tab"===e.key)if(t&&!e.shiftKey){e.preventDefault();const t=document.querySelector("[data-custom-submit-button]");t?.focus()}else t||(e.preventDefault(),_.current?.focus())}else j(null)}})),"dropdown"===$&&/* @__PURE__ */e.createElement(e.Fragment,null,F.source?/* @__PURE__ */e.createElement(e.Fragment,null,L?/* @__PURE__ */e.createElement(w,{internalFilter:F,valueOptions:q,onConfirm:e=>{const t={...F,value:e};T(t);t.source&&t.operator&&e.length>0&&C(t),j(null)},onClose:()=>j(null)}):/* @__PURE__ */e.createElement(x,{valueOptions:q,isValueSelected:H,onValueSelect:e=>{const t=U();let l;l=L?t.includes(e)?t.filter(t=>t!==e):[...t,e]:[e];const r={...F,value:L?l:l[0]||""};T(r),G(r),L||j(null)}})):/* @__PURE__ */e.createElement("div",{className:"p-3 text-sm text-gray-500"},V)))),
16
+ /* @__PURE__ */e.createElement(E,{align:"start",className:"w-64 max-h-[min(320px,50vh)] overflow-y-auto p-0"},"custom"===L&&/* @__PURE__ */e.createElement("div",{className:"p-3"},/* @__PURE__ */e.createElement(S,{inputValue:z,setInputValue:I,customValuePlaceholder:k,customValueButtonLabel:O,onSubmit:()=>{if(z.trim()){const e={...F,value:z.trim()};T(e),I(""),j(null),e.source&&e.operator&&e.value&&C(e)}},handleKeyDown:(e,t)=>{if("Escape"!==e.key){if("Tab"===e.key)if(t&&!e.shiftKey){e.preventDefault();const t=document.querySelector("[data-custom-submit-button]");t?.focus()}else t||(e.preventDefault(),_.current?.focus())}else j(null)}})),"dropdown"===L&&/* @__PURE__ */e.createElement(e.Fragment,null,F.source?/* @__PURE__ */e.createElement(e.Fragment,null,q?/* @__PURE__ */e.createElement(w,{internalFilter:F,valueOptions:$,onConfirm:e=>{const t={...F,value:e};T(t);t.source&&t.operator&&e.length>0&&C(t),j(null)},onClose:()=>j(null)}):/* @__PURE__ */e.createElement(x,{valueOptions:$,isValueSelected:H,onValueSelect:e=>{const t=U();let l;l=q?t.includes(e)?t.filter(t=>t!==e):[...t,e]:[e];const r={...F,value:q?l:l[0]||""};T(r),G(r),q||j(null)}})):/* @__PURE__ */e.createElement("div",{className:"p-3 text-sm text-gray-500"},V)))),
17
17
  /* @__PURE__ */e.createElement(i,{type:"button",variant:"ghost",size:"xs",onClick:()=>{T({id:r.id}),j(null),y(r.id)},className:"p-2 hover:bg-black/10 rounded-none rounded-r-full","aria-label":"Remove filter"},
18
18
  /* @__PURE__ */e.createElement(s,{size:16,weight:"fill"})))});C.displayName="Filter";const D=({filters:r,onFilterChange:a,onFilterRemove:o,onFilterAdd:s,sourceOptions:m,rules:d,className:p})=>{const[f,v]=l(r||[]),b=t({});n(()=>{v(r||[])},[r]);const g=f.map(e=>e.source).filter(Boolean),h=m.filter(e=>d[e.id]?.multiSelect?!g.includes(e.id):m),E=h.length>0;/* @__PURE__ */
19
19
  return e.createElement("fieldset",{className:y("flex flex-wrap gap-2",p)},/* @__PURE__ */e.createElement("legend",{className:"sr-only"},"Filters"),f?.map(t=>{const l=m.filter(e=>!g.includes(e.id)||e.id===t.source);/* @__PURE__ */
20
20
  return e.createElement(C,{key:t.id,ref:e=>{b.current[t.id]=e},filter:t,sourceOptions:l,rules:d,onRemoveFilter:()=>(e=>{const t=f.filter(t=>t.id!==e.id);v(t),o(e.id)})(t),onFilterChange:a})}),E&&/* @__PURE__ */e.createElement(i,{onClick:()=>{if(!E)return;const e={id:u(),source:h[0].id,operator:N.IS,value:d[h[0].id]?.multiSelect?[]:""},t=[...f,e];v(t),requestAnimationFrame(()=>{b.current[e.id]?.focus()}),s?.(e)},variant:"ghost",className:"border border-foreground rounded-full gap-1",size:"sm",title:"Add filter"},
21
21
  /* @__PURE__ */e.createElement(c,{size:16}),"Add filter"))};export{C as F,N as O,D as a};
22
- //# sourceMappingURL=filters.BHp3ukNW.js.map
22
+ //# sourceMappingURL=filters.DPv-zTEC.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filters.BHp3ukNW.js","sources":["../../../src/components/filter/types.ts","../../../src/components/filter/hooks.ts","../../../src/components/filter/components/CustomValueInput.tsx","../../../src/components/filter/components/MultiSelectDropdown.tsx","../../../src/components/filter/components/SingleSelectDropdown.tsx","../../../src/components/filter/filter.tsx","../../../src/components/filter/filters.tsx"],"sourcesContent":["// Types\nexport interface FilterOption {\n id: string;\n label: string;\n}\n\nexport enum Operator {\n IS = 'is',\n IS_NOT = 'is not',\n CONTAINS = 'contains',\n DOES_NOT_CONTAIN = 'does not contain',\n STARTS_WITH = 'starts with',\n}\n\nexport interface FilterValue {\n id: string | number;\n source?: string;\n operator?: Operator;\n value?: string | string[];\n}\n\n/**\n * Filter rules for each source\n * How this works:\n * - You can define the rules for each source\n * - The rules are used to determine the available operators and value type\n * - The input type is used to determine the type of input displayed in when the dropdown is opened\n * - The getValueOptions function is used to determine the available options for the value\n * - multiSelect determines if multiple values can be selected for dropdown type\n *\n * @example\n * {\n * 'sourceId': {\n * operators: [\n * { id: 'is', label: 'is' },\n * { id: 'is not', label: 'is not' },\n * ],\n * inputType: 'dropdown' | 'custom',\n * multiSelect?: boolean,\n * getValueOptions?: () => FilterOption[],\n * },\n * }\n */\nexport interface FilterRules {\n [sourceId: string]: {\n operators: FilterOption[];\n inputType: 'dropdown' | 'custom';\n multiSelect?: boolean;\n getValueOptions?: () => FilterOption[];\n };\n}\n\nexport interface FilterProps {\n filter: FilterValue;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n onSourceChange?: (sourceId: string) => void;\n onRemoveFilter: (id: string | number) => void;\n onFilterChange: (filter: FilterValue) => void;\n selectPlaceholder?: string;\n customValuePlaceholder?: string;\n customValueButtonLabel?: string;\n sourceFirstMessage?: string;\n}\n","import { FilterValue } from './types';\n\nexport const useMultiSelectHelpers = (\n internalFilter: FilterValue,\n valueOptions: Array<{ id: string; label: string }>,\n selectPlaceholder: string,\n) => {\n const getSelectedValues = (): string[] => {\n if (Array.isArray(internalFilter.value)) {\n return internalFilter.value;\n }\n\n return internalFilter.value ? [internalFilter.value] : [];\n };\n\n const isValueSelected = (valueId: string): boolean => {\n const selectedValues = getSelectedValues();\n\n return selectedValues.includes(valueId);\n };\n\n const getDisplayValue = (): string => {\n const selectedValues = getSelectedValues();\n if (selectedValues.length === 0) return selectPlaceholder;\n if (selectedValues.length === 1) {\n const option = valueOptions.find((opt) => opt.id === selectedValues[0]);\n\n return option?.label || selectedValues[0];\n }\n\n return `${selectedValues.length} selected`;\n };\n\n return {\n getSelectedValues,\n isValueSelected,\n getDisplayValue,\n };\n};\n","import React, { useRef } from 'react';\n\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\n\ninterface CustomValueInputProps {\n inputValue: string;\n setInputValue: (value: string) => void;\n customValuePlaceholder: string;\n customValueButtonLabel: string;\n onSubmit: () => void;\n handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => void;\n}\n\nexport const CustomValueInput: React.FC<CustomValueInputProps> = ({\n inputValue,\n setInputValue,\n customValuePlaceholder,\n customValueButtonLabel,\n onSubmit,\n handleKeyDown,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n\n return (\n <form\n className=\"p-3\"\n onSubmit={(e) => {\n e.preventDefault();\n onSubmit();\n }}\n >\n <Input\n ref={inputRef}\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder={customValuePlaceholder}\n className=\"mb-2\"\n onKeyDown={(e) => handleKeyDown(e, true)}\n />\n <Button\n type=\"submit\"\n variant=\"dark\"\n className=\"w-full\"\n onKeyDown={(e) => handleKeyDown(e, false)}\n data-custom-submit-button\n >\n {customValueButtonLabel}\n </Button>\n </form>\n );\n};\n","import React, { useState, useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterValue, FilterOption } from '../types';\n\nimport { Button } from '@/components/ui/button';\n\ninterface MultiSelectDropdownProps {\n internalFilter: FilterValue;\n valueOptions: FilterOption[];\n onConfirm: (selectedValues: string[]) => void;\n onClose: () => void;\n}\n\nconst getInitialValues = (value: FilterValue['value']): string[] => {\n if (Array.isArray(value)) {\n return value;\n } else if (value) {\n return [value];\n }\n\n return [];\n};\n\nexport const MultiSelectDropdown: React.FC<MultiSelectDropdownProps> = ({\n internalFilter,\n valueOptions,\n onConfirm,\n onClose,\n}) => {\n const [pendingValues, setPendingValues] = useState<string[]>(() => getInitialValues(internalFilter.value));\n const [announcement, setAnnouncement] = useState<string>('');\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const applyButtonRef = useRef<HTMLButtonElement>(null);\n\n const handleValueToggle = (valueId: string) => {\n const option = valueOptions.find((opt) => opt.id === valueId);\n const isCurrentlySelected = pendingValues.includes(valueId);\n\n setPendingValues((prev) => {\n const newValues = prev.includes(valueId) ? prev.filter((id) => id !== valueId) : [...prev, valueId];\n\n // Announce the change\n if (option) {\n const action = isCurrentlySelected ? 'deselected' : 'selected';\n setAnnouncement(`${option.label} ${action}.`);\n }\n\n return newValues;\n });\n };\n\n const handleConfirm = () => {\n onConfirm(pendingValues);\n onClose();\n };\n\n const isValueSelected = (valueId: string): boolean => {\n return pendingValues.includes(valueId);\n };\n\n const focusButton = (index: number) => {\n if (index < valueOptions.length) {\n buttonRefs.current[index]?.focus();\n } else {\n applyButtonRef.current?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n if (index < valueOptions.length) {\n // If the user is selecting an option\n handleValueToggle(valueOptions[index].id);\n } else {\n // If the user is selecting the apply button\n handleConfirm();\n }\n break;\n case 'Escape':\n e.preventDefault();\n onClose();\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\" aria-multiselectable=\"true\">\n {/* Screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {announcement}\n </div>\n\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => handleValueToggle(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n role=\"option\"\n aria-selected={isValueSelected(option.id)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n\n <div className=\"border-t border-border mt-1 pt-2 px-2 pb-2\">\n <Button\n ref={applyButtonRef}\n onClick={handleConfirm}\n size=\"sm\"\n className=\"w-full\"\n disabled={pendingValues.length === 0}\n onKeyDown={(e) => handleKeyDown(e, valueOptions.length)}\n >\n Apply ({pendingValues.length} selected)\n </Button>\n </div>\n </div>\n );\n};\n","import React, { useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterOption } from '../types';\n\ninterface SingleSelectDropdownProps {\n valueOptions: FilterOption[];\n isValueSelected: (valueId: string) => boolean;\n onValueSelect: (valueId: string) => void;\n}\n\nexport const SingleSelectDropdown: React.FC<SingleSelectDropdownProps> = ({\n valueOptions,\n isValueSelected,\n onValueSelect,\n}) => {\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const focusButton = (index: number) => {\n buttonRefs.current[index]?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length - 1);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n onValueSelect(valueOptions[index].id);\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\">\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => onValueSelect(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n </div>\n );\n};\n","import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';\nimport { Check, XCircle } from '@phosphor-icons/react';\n\nimport { FilterProps, FilterValue, Operator } from './types';\nimport { useMultiSelectHelpers } from './hooks';\nimport { CustomValueInput } from './components/CustomValueInput';\nimport { MultiSelectDropdown } from './components/MultiSelectDropdown';\nimport { SingleSelectDropdown } from './components/SingleSelectDropdown';\n\nimport { Button } from '@/components/ui/button';\nimport { ButtonGroup } from '@/components/ui/button-group';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\n\nconst Filter = forwardRef<{ focus: () => void }, FilterProps>(\n (\n {\n filter,\n sourceOptions,\n rules,\n onSourceChange,\n onRemoveFilter,\n onFilterChange,\n selectPlaceholder = 'Select a value',\n customValuePlaceholder = 'Enter custom value',\n customValueButtonLabel = 'Done',\n sourceFirstMessage = 'Please select a source first',\n }: FilterProps,\n ref,\n ) => {\n // State\n const [internalFilter, setInternalFilter] = useState<FilterValue>({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n const [inputValue, setInputValue] = useState('');\n const [openDropdownType, setOpenDropdownType] = useState<'source' | 'operator' | 'value' | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const sourceButtonRef = useRef<HTMLButtonElement>(null);\n const divRef = useRef<HTMLDivElement>(null);\n\n // Expose focus method\n useImperativeHandle(ref, () => ({\n focus: () => {\n sourceButtonRef.current?.focus();\n },\n }));\n\n // Get current rules for selected source\n const currentRules = internalFilter.source ? rules[internalFilter.source] : undefined;\n const operatorOptions = currentRules?.operators || [];\n const isMultiSelect = currentRules?.multiSelect || false;\n let inputType = currentRules?.inputType || 'dropdown';\n if (internalFilter.operator === Operator.CONTAINS || internalFilter.operator === Operator.DOES_NOT_CONTAIN) {\n inputType = 'custom';\n }\n const valueOptions = currentRules?.getValueOptions ? currentRules.getValueOptions() : [];\n\n // Multi-select helpers\n const { getSelectedValues, isValueSelected, getDisplayValue } = useMultiSelectHelpers(\n internalFilter,\n valueOptions,\n selectPlaceholder,\n );\n\n // Auto-focus input when dropdown opens and custom input is needed\n useEffect(() => {\n if (openDropdownType === 'value' && inputType === 'custom') {\n const timeoutId = setTimeout(() => inputRef.current?.focus(), 50);\n\n return () => clearTimeout(timeoutId);\n }\n\n return undefined;\n }, [openDropdownType, inputType]);\n\n // Sync with external state\n useEffect(() => {\n setInternalFilter({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n }, [filter]);\n\n // Helper function to check if filter is complete and trigger onChange\n const checkAndTriggerChange = (newFilter: FilterValue) => {\n const hasValue = Array.isArray(newFilter.value) ? newFilter.value.length > 0 : !!newFilter.value?.trim();\n\n const isComplete = newFilter.source && newFilter.operator && hasValue;\n\n if (isComplete) {\n onFilterChange(newFilter);\n }\n };\n\n // Handle field updates\n const updateFilterField = (field: keyof FilterValue, value: string) => {\n const newFilter: FilterValue = { ...internalFilter, [field]: value };\n\n // Special handling for source changes\n if (field === 'source') {\n const sourceRules = rules[value];\n newFilter.operator = sourceRules?.operators[0]?.id as Operator;\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n // If operator changes, also clear value if inputType is dropdown\n if (field === 'operator') {\n if (internalFilter.source) {\n const sourceRules = rules[internalFilter.source];\n if (sourceRules?.inputType === 'dropdown') {\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n }\n }\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n if (!isMultiSelect || field !== 'value') {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select value changes\n const handleValueToggle = (valueId: string) => {\n const selectedValues = getSelectedValues();\n let newValues: string[];\n\n if (isMultiSelect) {\n if (selectedValues.includes(valueId)) {\n newValues = selectedValues.filter((id) => id !== valueId);\n } else {\n newValues = [...selectedValues, valueId];\n }\n } else {\n newValues = [valueId];\n }\n\n const newFilter = {\n ...internalFilter,\n value: isMultiSelect ? newValues : newValues[0] || '',\n };\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n // Close dropdown for single select, keep open for multi-select\n if (!isMultiSelect) {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select confirmation\n const handleMultiSelectConfirm = (selectedValues: string[]) => {\n const newFilter = {\n ...internalFilter,\n value: selectedValues,\n };\n\n setInternalFilter(newFilter);\n\n // Trigger onChange if filter is complete\n const isComplete = newFilter.source && newFilter.operator && selectedValues.length > 0;\n if (isComplete) onFilterChange(newFilter);\n\n setOpenDropdownType(null);\n };\n\n // Handle custom value submission\n const handleCustomValueSubmit = () => {\n if (inputValue.trim()) {\n const newFilter = { ...internalFilter, value: inputValue.trim() };\n setInternalFilter(newFilter);\n setInputValue('');\n setOpenDropdownType(null);\n if (newFilter.source && newFilter.operator && newFilter.value) {\n onFilterChange(newFilter);\n }\n }\n };\n\n // Tab trap handlers\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => {\n if (e.key === 'Escape') {\n setOpenDropdownType(null);\n\n return;\n }\n if (e.key === 'Tab') {\n if (isInput && !e.shiftKey) {\n e.preventDefault();\n const button = document.querySelector('[data-custom-submit-button]') as HTMLButtonElement;\n button?.focus();\n } else if (!isInput) {\n e.preventDefault();\n inputRef.current?.focus();\n }\n }\n };\n\n return (\n <ButtonGroup\n ref={divRef}\n orientation=\"horizontal\"\n className=\"rounded-full border border-border divide-x-2 divide-dark-500\"\n >\n {/* Source Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'source'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'source' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button ref={sourceButtonRef} variant=\"ghost\" size=\"xs\" className=\"rounded-l-full font-bold\">\n {sourceOptions.find((opt) => opt.id === internalFilter.source)?.label ||\n sourceOptions[0]?.label ||\n selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {sourceOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => {\n updateFilterField('source', option.id);\n onSourceChange?.(option.id);\n }}\n >\n {option.id === internalFilter.source && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Operator Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'operator'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'operator' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\">\n {operatorOptions.find((opt) => opt.id === internalFilter.operator)?.label || selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {operatorOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => updateFilterField('operator', option.id)}\n >\n {option.id === internalFilter.operator && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Value Popover */}\n <Popover\n open={openDropdownType === 'value'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'value' : null)}\n >\n <PopoverTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\" className=\"font-bold\">\n {getDisplayValue()}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-64 max-h-[min(320px,50vh)] overflow-y-auto p-0\">\n {inputType === 'custom' && (\n <div className=\"p-3\">\n <CustomValueInput\n inputValue={inputValue}\n setInputValue={setInputValue}\n customValuePlaceholder={customValuePlaceholder}\n customValueButtonLabel={customValueButtonLabel}\n onSubmit={handleCustomValueSubmit}\n handleKeyDown={handleKeyDown}\n />\n </div>\n )}\n {inputType === 'dropdown' && (\n <>\n {internalFilter.source ? (\n <>\n {isMultiSelect ? (\n <MultiSelectDropdown\n internalFilter={internalFilter}\n valueOptions={valueOptions}\n onConfirm={handleMultiSelectConfirm}\n onClose={() => setOpenDropdownType(null)}\n />\n ) : (\n <SingleSelectDropdown\n valueOptions={valueOptions}\n isValueSelected={isValueSelected}\n onValueSelect={handleValueToggle}\n />\n )}\n </>\n ) : (\n <div className=\"p-3 text-sm text-gray-500\">{sourceFirstMessage}</div>\n )}\n </>\n )}\n </PopoverContent>\n </Popover>\n\n {/* Remove button */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => {\n setInternalFilter({ id: filter.id });\n setOpenDropdownType(null);\n onRemoveFilter(filter.id);\n }}\n className=\"p-2 hover:bg-black/10 rounded-none rounded-r-full\"\n aria-label=\"Remove filter\"\n >\n <XCircle size={16} weight=\"fill\" />\n </Button>\n </ButtonGroup>\n );\n },\n);\n\nFilter.displayName = 'Filter';\n\nexport { Filter };\n","import React, { useState, useEffect, useRef } from 'react';\nimport { Plus } from '@phosphor-icons/react';\nimport { createId } from '@paralleldrive/cuid2';\n\nimport { Filter, FilterOption, FilterRules, FilterValue, Operator } from './index';\nimport { Button } from '../ui/button';\n\nimport { cn } from '@/lib/utils';\n\ninterface FiltersProps extends React.HTMLAttributes<HTMLDivElement> {\n filters: FilterValue[];\n onFilterChange: (filter: FilterValue) => void;\n onFilterRemove: (filterId: string | number) => void;\n onFilterAdd?: (filter: FilterValue) => void;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n}\n\nconst Filters = ({\n filters,\n onFilterChange,\n onFilterRemove,\n onFilterAdd,\n sourceOptions,\n rules,\n className,\n}: FiltersProps) => {\n const [internalFilters, setInternalFilters] = useState<FilterValue[]>(filters || []);\n const filterRefs = useRef<{ [key: string]: { focus: () => void } | null }>({});\n\n // Sync internal state with props\n useEffect(() => {\n setInternalFilters(filters || []);\n }, [filters]);\n\n // Get sources that are already in use\n const usedSources = internalFilters.map((filter) => filter.source).filter(Boolean);\n\n // Get available sources that haven't been used yet\n const availableSources = sourceOptions.filter((option) => {\n // If the source has multiSelect enabled, filter out used sources\n if (rules[option.id]?.multiSelect) {\n return !usedSources.includes(option.id);\n }\n\n // If multiSelect is false or undefined, allow the source to be used multiple times\n return sourceOptions;\n });\n\n // Check if we can add more filters\n const canAddFilter = availableSources.length > 0;\n\n const handleAddFilter = () => {\n if (!canAddFilter) return;\n\n const newFilter = {\n id: createId(),\n source: availableSources[0].id,\n operator: Operator.IS,\n value: rules[availableSources[0].id]?.multiSelect ? [] : '',\n };\n\n const newFilters = [...internalFilters, newFilter];\n setInternalFilters(newFilters);\n\n // Focus the new filter after it's rendered\n requestAnimationFrame(() => {\n filterRefs.current[newFilter.id]?.focus();\n });\n\n // Notify parent if callback provided\n onFilterAdd?.(newFilter);\n };\n\n const handleRemoveFilter = (filter: FilterValue) => {\n const newFilters = internalFilters.filter((f) => f.id !== filter.id);\n setInternalFilters(newFilters);\n onFilterRemove(filter.id);\n };\n\n return (\n <fieldset className={cn('flex flex-wrap gap-2', className)}>\n <legend className=\"sr-only\">Filters</legend>\n {internalFilters?.map((filter) => {\n // For each filter, include available sources plus its own current source\n const filterSourceOptions = sourceOptions.filter(\n (option) =>\n !usedSources.includes(option.id) || // Available sources\n option.id === filter.source, // Or its own current source\n );\n\n return (\n <Filter\n key={filter.id}\n ref={(ref) => {\n filterRefs.current[filter.id] = ref;\n }}\n filter={filter}\n sourceOptions={filterSourceOptions}\n rules={rules}\n onRemoveFilter={() => handleRemoveFilter(filter)}\n onFilterChange={onFilterChange}\n />\n );\n })}\n {canAddFilter && (\n <Button\n onClick={handleAddFilter}\n variant=\"ghost\"\n className=\"border border-foreground rounded-full gap-1\"\n size=\"sm\"\n title=\"Add filter\"\n >\n <Plus size={16} />\n Add filter\n </Button>\n )}\n </fieldset>\n );\n};\n\nexport { Filters };\n"],"names":["Operator","CustomValueInput","inputValue","setInputValue","customValuePlaceholder","customValueButtonLabel","onSubmit","handleKeyDown","inputRef","useRef","React","createElement","className","e","preventDefault","Input","ref","type","value","onChange","target","placeholder","onKeyDown","Button","variant","MultiSelectDropdown","internalFilter","valueOptions","onConfirm","onClose","pendingValues","setPendingValues","useState","getInitialValues","Array","isArray","announcement","setAnnouncement","buttonRefs","applyButtonRef","handleValueToggle","valueId","option","find","opt","id","isCurrentlySelected","includes","prev","newValues","filter","action","label","handleConfirm","isValueSelected","focusButton","index","length","current","focus","key","nextIndex","Math","min","prevIndex","max","role","map","el","onClick","Check","size","disabled","SingleSelectDropdown","onValueSelect","Filter","forwardRef","sourceOptions","rules","onSourceChange","onRemoveFilter","onFilterChange","selectPlaceholder","sourceFirstMessage","setInternalFilter","source","operator","IS","openDropdownType","setOpenDropdownType","sourceButtonRef","divRef","useImperativeHandle","currentRules","operatorOptions","operators","isMultiSelect","multiSelect","inputType","CONTAINS","DOES_NOT_CONTAIN","getValueOptions","getSelectedValues","getDisplayValue","selectedValues","useMultiSelectHelpers","useEffect","timeoutId","setTimeout","clearTimeout","checkAndTriggerChange","newFilter","hasValue","trim","updateFilterField","field","sourceRules","ButtonGroup","orientation","DropdownMenu","open","onOpenChange","modal","DropdownMenuTrigger","asChild","DropdownMenuContent","align","DropdownMenuItem","onSelect","Popover","PopoverTrigger","PopoverContent","isInput","shiftKey","button","document","querySelector","XCircle","weight","displayName","Filters","filters","onFilterRemove","onFilterAdd","internalFilters","setInternalFilters","filterRefs","usedSources","Boolean","availableSources","canAddFilter","cn","filterSourceOptions","newFilters","f","handleRemoveFilter","createId","requestAnimationFrame","title","Plus"],"mappings":"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"}
1
+ {"version":3,"file":"filters.DPv-zTEC.js","sources":["../../../src/components/filter/types.ts","../../../src/components/filter/hooks.ts","../../../src/components/filter/components/CustomValueInput.tsx","../../../src/components/filter/components/MultiSelectDropdown.tsx","../../../src/components/filter/components/SingleSelectDropdown.tsx","../../../src/components/filter/filter.tsx","../../../src/components/filter/filters.tsx"],"sourcesContent":["// Types\nexport interface FilterOption {\n id: string;\n label: string;\n}\n\nexport enum Operator {\n IS = 'is',\n IS_NOT = 'is not',\n CONTAINS = 'contains',\n DOES_NOT_CONTAIN = 'does not contain',\n STARTS_WITH = 'starts with',\n}\n\nexport interface FilterValue {\n id: string | number;\n source?: string;\n operator?: Operator;\n value?: string | string[];\n}\n\n/**\n * Filter rules for each source\n * How this works:\n * - You can define the rules for each source\n * - The rules are used to determine the available operators and value type\n * - The input type is used to determine the type of input displayed in when the dropdown is opened\n * - The getValueOptions function is used to determine the available options for the value\n * - multiSelect determines if multiple values can be selected for dropdown type\n *\n * @example\n * {\n * 'sourceId': {\n * operators: [\n * { id: 'is', label: 'is' },\n * { id: 'is not', label: 'is not' },\n * ],\n * inputType: 'dropdown' | 'custom',\n * multiSelect?: boolean,\n * getValueOptions?: () => FilterOption[],\n * },\n * }\n */\nexport interface FilterRules {\n [sourceId: string]: {\n operators: FilterOption[];\n inputType: 'dropdown' | 'custom';\n multiSelect?: boolean;\n getValueOptions?: () => FilterOption[];\n };\n}\n\nexport interface FilterProps {\n filter: FilterValue;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n onSourceChange?: (sourceId: string) => void;\n onRemoveFilter: (id: string | number) => void;\n onFilterChange: (filter: FilterValue) => void;\n selectPlaceholder?: string;\n customValuePlaceholder?: string;\n customValueButtonLabel?: string;\n sourceFirstMessage?: string;\n}\n","import { FilterValue } from './types';\n\nexport const useMultiSelectHelpers = (\n internalFilter: FilterValue,\n valueOptions: Array<{ id: string; label: string }>,\n selectPlaceholder: string,\n) => {\n const getSelectedValues = (): string[] => {\n if (Array.isArray(internalFilter.value)) {\n return internalFilter.value;\n }\n\n return internalFilter.value ? [internalFilter.value] : [];\n };\n\n const isValueSelected = (valueId: string): boolean => {\n const selectedValues = getSelectedValues();\n\n return selectedValues.includes(valueId);\n };\n\n const getDisplayValue = (): string => {\n const selectedValues = getSelectedValues();\n if (selectedValues.length === 0) return selectPlaceholder;\n if (selectedValues.length === 1) {\n const option = valueOptions.find((opt) => opt.id === selectedValues[0]);\n\n return option?.label || selectedValues[0];\n }\n\n return `${selectedValues.length} selected`;\n };\n\n return {\n getSelectedValues,\n isValueSelected,\n getDisplayValue,\n };\n};\n","import React, { useRef } from 'react';\n\nimport { Button } from '@/components/ui/button';\nimport { Input } from '@/components/ui/input';\n\ninterface CustomValueInputProps {\n inputValue: string;\n setInputValue: (value: string) => void;\n customValuePlaceholder: string;\n customValueButtonLabel: string;\n onSubmit: () => void;\n handleKeyDown: (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => void;\n}\n\nexport const CustomValueInput: React.FC<CustomValueInputProps> = ({\n inputValue,\n setInputValue,\n customValuePlaceholder,\n customValueButtonLabel,\n onSubmit,\n handleKeyDown,\n}) => {\n const inputRef = useRef<HTMLInputElement>(null);\n\n return (\n <form\n className=\"p-3\"\n onSubmit={(e) => {\n e.preventDefault();\n onSubmit();\n }}\n >\n <Input\n ref={inputRef}\n type=\"text\"\n value={inputValue}\n onChange={(e) => setInputValue(e.target.value)}\n placeholder={customValuePlaceholder}\n className=\"mb-2\"\n onKeyDown={(e) => handleKeyDown(e, true)}\n />\n <Button\n type=\"submit\"\n variant=\"dark\"\n className=\"w-full\"\n onKeyDown={(e) => handleKeyDown(e, false)}\n data-custom-submit-button\n >\n {customValueButtonLabel}\n </Button>\n </form>\n );\n};\n","import React, { useState, useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterValue, FilterOption } from '../types';\n\nimport { Button } from '@/components/ui/button';\n\ninterface MultiSelectDropdownProps {\n internalFilter: FilterValue;\n valueOptions: FilterOption[];\n onConfirm: (selectedValues: string[]) => void;\n onClose: () => void;\n}\n\nconst getInitialValues = (value: FilterValue['value']): string[] => {\n if (Array.isArray(value)) {\n return value;\n } else if (value) {\n return [value];\n }\n\n return [];\n};\n\nexport const MultiSelectDropdown: React.FC<MultiSelectDropdownProps> = ({\n internalFilter,\n valueOptions,\n onConfirm,\n onClose,\n}) => {\n const [pendingValues, setPendingValues] = useState<string[]>(() => getInitialValues(internalFilter.value));\n const [announcement, setAnnouncement] = useState<string>('');\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n const applyButtonRef = useRef<HTMLButtonElement>(null);\n\n const handleValueToggle = (valueId: string) => {\n const option = valueOptions.find((opt) => opt.id === valueId);\n const isCurrentlySelected = pendingValues.includes(valueId);\n\n setPendingValues((prev) => {\n const newValues = prev.includes(valueId) ? prev.filter((id) => id !== valueId) : [...prev, valueId];\n\n // Announce the change\n if (option) {\n const action = isCurrentlySelected ? 'deselected' : 'selected';\n setAnnouncement(`${option.label} ${action}.`);\n }\n\n return newValues;\n });\n };\n\n const handleConfirm = () => {\n onConfirm(pendingValues);\n onClose();\n };\n\n const isValueSelected = (valueId: string): boolean => {\n return pendingValues.includes(valueId);\n };\n\n const focusButton = (index: number) => {\n if (index < valueOptions.length) {\n buttonRefs.current[index]?.focus();\n } else {\n applyButtonRef.current?.focus();\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n if (index < valueOptions.length) {\n // If the user is selecting an option\n handleValueToggle(valueOptions[index].id);\n } else {\n // If the user is selecting the apply button\n handleConfirm();\n }\n break;\n case 'Escape':\n e.preventDefault();\n onClose();\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\" aria-multiselectable=\"true\">\n {/* Screen reader announcements */}\n <div aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only\">\n {announcement}\n </div>\n\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => handleValueToggle(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n role=\"option\"\n aria-selected={isValueSelected(option.id)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n\n <div className=\"border-t border-border mt-1 pt-2 px-2 pb-2\">\n <Button\n ref={applyButtonRef}\n onClick={handleConfirm}\n size=\"sm\"\n className=\"w-full\"\n disabled={pendingValues.length === 0}\n onKeyDown={(e) => handleKeyDown(e, valueOptions.length)}\n >\n Apply ({pendingValues.length} selected)\n </Button>\n </div>\n </div>\n );\n};\n","import React, { useRef } from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FilterOption } from '../types';\n\ninterface SingleSelectDropdownProps {\n valueOptions: FilterOption[];\n isValueSelected: (valueId: string) => boolean;\n onValueSelect: (valueId: string) => void;\n}\n\nexport const SingleSelectDropdown: React.FC<SingleSelectDropdownProps> = ({\n valueOptions,\n isValueSelected,\n onValueSelect,\n}) => {\n const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);\n\n const focusButton = (index: number) => {\n buttonRefs.current[index]?.focus();\n };\n\n const handleKeyDown = (e: React.KeyboardEvent, index: number) => {\n switch (e.key) {\n case 'ArrowDown': {\n e.preventDefault();\n const nextIndex = Math.min(index + 1, valueOptions.length - 1);\n focusButton(nextIndex);\n break;\n }\n case 'ArrowUp': {\n e.preventDefault();\n const prevIndex = Math.max(index - 1, 0);\n focusButton(prevIndex);\n break;\n }\n case 'Enter':\n case 'Spacebar':\n e.preventDefault();\n onValueSelect(valueOptions[index].id);\n break;\n }\n };\n\n return (\n <div className=\"py-1\" role=\"listbox\">\n {valueOptions.map((option, index) => (\n <button\n key={option.id}\n ref={(el) => (buttonRefs.current[index] = el)}\n type=\"button\"\n className=\"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground\"\n onClick={() => onValueSelect(option.id)}\n onKeyDown={(e) => handleKeyDown(e, index)}\n >\n {isValueSelected(option.id) && <Check size={14} className=\"mr-2\" />}\n {!isValueSelected(option.id) && <span className=\"mr-2 w-3.5\" />}\n {option.label}\n </button>\n ))}\n </div>\n );\n};\n","import React, { useState, useEffect, forwardRef, useRef, useImperativeHandle } from 'react';\nimport { Check, XCircle } from '@phosphor-icons/react';\n\nimport { FilterProps, FilterValue, Operator } from './types';\nimport { useMultiSelectHelpers } from './hooks';\nimport { CustomValueInput } from './components/CustomValueInput';\nimport { MultiSelectDropdown } from './components/MultiSelectDropdown';\nimport { SingleSelectDropdown } from './components/SingleSelectDropdown';\n\nimport { Button } from '@/components/ui/button';\nimport { ButtonGroup } from '@/components/ui/button-group';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu';\nimport { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';\n\nconst Filter = forwardRef<{ focus: () => void }, FilterProps>(\n (\n {\n filter,\n sourceOptions,\n rules,\n onSourceChange,\n onRemoveFilter,\n onFilterChange,\n selectPlaceholder = 'Select a value',\n customValuePlaceholder = 'Enter custom value',\n customValueButtonLabel = 'Done',\n sourceFirstMessage = 'Please select a source first',\n }: FilterProps,\n ref,\n ) => {\n // State\n const [internalFilter, setInternalFilter] = useState<FilterValue>({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n const [inputValue, setInputValue] = useState('');\n const [openDropdownType, setOpenDropdownType] = useState<'source' | 'operator' | 'value' | null>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const sourceButtonRef = useRef<HTMLButtonElement>(null);\n const divRef = useRef<HTMLDivElement>(null);\n\n // Expose focus method\n useImperativeHandle(ref, () => ({\n focus: () => {\n sourceButtonRef.current?.focus();\n },\n }));\n\n // Get current rules for selected source\n const currentRules = internalFilter.source ? rules[internalFilter.source] : undefined;\n const operatorOptions = currentRules?.operators || [];\n const isMultiSelect = currentRules?.multiSelect || false;\n let inputType = currentRules?.inputType || 'dropdown';\n if (internalFilter.operator === Operator.CONTAINS || internalFilter.operator === Operator.DOES_NOT_CONTAIN) {\n inputType = 'custom';\n }\n const valueOptions = currentRules?.getValueOptions ? currentRules.getValueOptions() : [];\n\n // Multi-select helpers\n const { getSelectedValues, isValueSelected, getDisplayValue } = useMultiSelectHelpers(\n internalFilter,\n valueOptions,\n selectPlaceholder,\n );\n\n // Auto-focus input when dropdown opens and custom input is needed\n useEffect(() => {\n if (openDropdownType === 'value' && inputType === 'custom') {\n const timeoutId = setTimeout(() => inputRef.current?.focus(), 50);\n\n return () => clearTimeout(timeoutId);\n }\n\n return undefined;\n }, [openDropdownType, inputType]);\n\n // Sync with external state\n useEffect(() => {\n setInternalFilter({\n id: filter.id,\n source: filter.source,\n operator: filter.operator || Operator.IS,\n value: filter.value,\n });\n }, [filter]);\n\n // Helper function to check if filter is complete and trigger onChange\n const checkAndTriggerChange = (newFilter: FilterValue) => {\n const hasValue = Array.isArray(newFilter.value) ? newFilter.value.length > 0 : !!newFilter.value?.trim();\n\n const isComplete = newFilter.source && newFilter.operator && hasValue;\n\n if (isComplete) {\n onFilterChange(newFilter);\n }\n };\n\n // Handle field updates\n const updateFilterField = (field: keyof FilterValue, value: string) => {\n const newFilter: FilterValue = { ...internalFilter, [field]: value };\n\n // Special handling for source changes\n if (field === 'source') {\n const sourceRules = rules[value];\n newFilter.operator = sourceRules?.operators[0]?.id as Operator;\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n // If operator changes, also clear value if inputType is dropdown\n if (field === 'operator') {\n if (internalFilter.source) {\n const sourceRules = rules[internalFilter.source];\n if (sourceRules?.inputType === 'dropdown') {\n newFilter.value = sourceRules?.multiSelect ? [] : '';\n }\n }\n }\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n if (!isMultiSelect || field !== 'value') {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select value changes\n const handleValueToggle = (valueId: string) => {\n const selectedValues = getSelectedValues();\n let newValues: string[];\n\n if (isMultiSelect) {\n if (selectedValues.includes(valueId)) {\n newValues = selectedValues.filter((id) => id !== valueId);\n } else {\n newValues = [...selectedValues, valueId];\n }\n } else {\n newValues = [valueId];\n }\n\n const newFilter = {\n ...internalFilter,\n value: isMultiSelect ? newValues : newValues[0] || '',\n };\n\n setInternalFilter(newFilter);\n checkAndTriggerChange(newFilter);\n\n // Close dropdown for single select, keep open for multi-select\n if (!isMultiSelect) {\n setOpenDropdownType(null);\n }\n };\n\n // Handle multi-select confirmation\n const handleMultiSelectConfirm = (selectedValues: string[]) => {\n const newFilter = {\n ...internalFilter,\n value: selectedValues,\n };\n\n setInternalFilter(newFilter);\n\n // Trigger onChange if filter is complete\n const isComplete = newFilter.source && newFilter.operator && selectedValues.length > 0;\n if (isComplete) onFilterChange(newFilter);\n\n setOpenDropdownType(null);\n };\n\n // Handle custom value submission\n const handleCustomValueSubmit = () => {\n if (inputValue.trim()) {\n const newFilter = { ...internalFilter, value: inputValue.trim() };\n setInternalFilter(newFilter);\n setInputValue('');\n setOpenDropdownType(null);\n if (newFilter.source && newFilter.operator && newFilter.value) {\n onFilterChange(newFilter);\n }\n }\n };\n\n // Tab trap handlers\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement | HTMLButtonElement>, isInput: boolean) => {\n if (e.key === 'Escape') {\n setOpenDropdownType(null);\n\n return;\n }\n if (e.key === 'Tab') {\n if (isInput && !e.shiftKey) {\n e.preventDefault();\n const button = document.querySelector('[data-custom-submit-button]') as HTMLButtonElement;\n button?.focus();\n } else if (!isInput) {\n e.preventDefault();\n inputRef.current?.focus();\n }\n }\n };\n\n return (\n <ButtonGroup\n ref={divRef}\n orientation=\"horizontal\"\n className=\"rounded-full border border-border divide-x-2 divide-dark-500\"\n >\n {/* Source Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'source'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'source' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button ref={sourceButtonRef} variant=\"ghost\" size=\"xs\" className=\"rounded-l-full font-bold\">\n {sourceOptions.find((opt) => opt.id === internalFilter.source)?.label ||\n sourceOptions[0]?.label ||\n selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {sourceOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => {\n updateFilterField('source', option.id);\n onSourceChange?.(option.id);\n }}\n >\n {option.id === internalFilter.source && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Operator Dropdown */}\n <DropdownMenu\n open={openDropdownType === 'operator'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'operator' : null)}\n modal={false}\n >\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\">\n {operatorOptions.find((opt) => opt.id === internalFilter.operator)?.label || selectPlaceholder}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-48\">\n {operatorOptions.map((option) => (\n <DropdownMenuItem\n key={option.id}\n className=\"flex items-center\"\n onSelect={() => updateFilterField('operator', option.id)}\n >\n {option.id === internalFilter.operator && <Check size={14} className=\"mr-2\" />}\n {option.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n\n {/* Value Popover */}\n <Popover\n open={openDropdownType === 'value'}\n onOpenChange={(open) => setOpenDropdownType(open ? 'value' : null)}\n >\n <PopoverTrigger asChild>\n <Button variant=\"ghost\" size=\"xs\" className=\"font-bold\">\n {getDisplayValue()}\n </Button>\n </PopoverTrigger>\n <PopoverContent align=\"start\" className=\"w-64 max-h-[min(320px,50vh)] overflow-y-auto p-0\">\n {inputType === 'custom' && (\n <div className=\"p-3\">\n <CustomValueInput\n inputValue={inputValue}\n setInputValue={setInputValue}\n customValuePlaceholder={customValuePlaceholder}\n customValueButtonLabel={customValueButtonLabel}\n onSubmit={handleCustomValueSubmit}\n handleKeyDown={handleKeyDown}\n />\n </div>\n )}\n {inputType === 'dropdown' && (\n <>\n {internalFilter.source ? (\n <>\n {isMultiSelect ? (\n <MultiSelectDropdown\n internalFilter={internalFilter}\n valueOptions={valueOptions}\n onConfirm={handleMultiSelectConfirm}\n onClose={() => setOpenDropdownType(null)}\n />\n ) : (\n <SingleSelectDropdown\n valueOptions={valueOptions}\n isValueSelected={isValueSelected}\n onValueSelect={handleValueToggle}\n />\n )}\n </>\n ) : (\n <div className=\"p-3 text-sm text-gray-500\">{sourceFirstMessage}</div>\n )}\n </>\n )}\n </PopoverContent>\n </Popover>\n\n {/* Remove button */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => {\n setInternalFilter({ id: filter.id });\n setOpenDropdownType(null);\n onRemoveFilter(filter.id);\n }}\n className=\"p-2 hover:bg-black/10 rounded-none rounded-r-full\"\n aria-label=\"Remove filter\"\n >\n <XCircle size={16} weight=\"fill\" />\n </Button>\n </ButtonGroup>\n );\n },\n);\n\nFilter.displayName = 'Filter';\n\nexport { Filter };\n","import React, { useState, useEffect, useRef } from 'react';\nimport { Plus } from '@phosphor-icons/react';\nimport { createId } from '@paralleldrive/cuid2';\n\nimport { Filter, FilterOption, FilterRules, FilterValue, Operator } from './index';\nimport { Button } from '../ui/button';\n\nimport { cn } from '@/lib/utils';\n\ninterface FiltersProps extends React.HTMLAttributes<HTMLDivElement> {\n filters: FilterValue[];\n onFilterChange: (filter: FilterValue) => void;\n onFilterRemove: (filterId: string | number) => void;\n onFilterAdd?: (filter: FilterValue) => void;\n sourceOptions: FilterOption[];\n rules: FilterRules;\n}\n\nconst Filters = ({\n filters,\n onFilterChange,\n onFilterRemove,\n onFilterAdd,\n sourceOptions,\n rules,\n className,\n}: FiltersProps) => {\n const [internalFilters, setInternalFilters] = useState<FilterValue[]>(filters || []);\n const filterRefs = useRef<{ [key: string]: { focus: () => void } | null }>({});\n\n // Sync internal state with props\n useEffect(() => {\n setInternalFilters(filters || []);\n }, [filters]);\n\n // Get sources that are already in use\n const usedSources = internalFilters.map((filter) => filter.source).filter(Boolean);\n\n // Get available sources that haven't been used yet\n const availableSources = sourceOptions.filter((option) => {\n // If the source has multiSelect enabled, filter out used sources\n if (rules[option.id]?.multiSelect) {\n return !usedSources.includes(option.id);\n }\n\n // If multiSelect is false or undefined, allow the source to be used multiple times\n return sourceOptions;\n });\n\n // Check if we can add more filters\n const canAddFilter = availableSources.length > 0;\n\n const handleAddFilter = () => {\n if (!canAddFilter) return;\n\n const newFilter = {\n id: createId(),\n source: availableSources[0].id,\n operator: Operator.IS,\n value: rules[availableSources[0].id]?.multiSelect ? [] : '',\n };\n\n const newFilters = [...internalFilters, newFilter];\n setInternalFilters(newFilters);\n\n // Focus the new filter after it's rendered\n requestAnimationFrame(() => {\n filterRefs.current[newFilter.id]?.focus();\n });\n\n // Notify parent if callback provided\n onFilterAdd?.(newFilter);\n };\n\n const handleRemoveFilter = (filter: FilterValue) => {\n const newFilters = internalFilters.filter((f) => f.id !== filter.id);\n setInternalFilters(newFilters);\n onFilterRemove(filter.id);\n };\n\n return (\n <fieldset className={cn('flex flex-wrap gap-2', className)}>\n <legend className=\"sr-only\">Filters</legend>\n {internalFilters?.map((filter) => {\n // For each filter, include available sources plus its own current source\n const filterSourceOptions = sourceOptions.filter(\n (option) =>\n !usedSources.includes(option.id) || // Available sources\n option.id === filter.source, // Or its own current source\n );\n\n return (\n <Filter\n key={filter.id}\n ref={(ref) => {\n filterRefs.current[filter.id] = ref;\n }}\n filter={filter}\n sourceOptions={filterSourceOptions}\n rules={rules}\n onRemoveFilter={() => handleRemoveFilter(filter)}\n onFilterChange={onFilterChange}\n />\n );\n })}\n {canAddFilter && (\n <Button\n onClick={handleAddFilter}\n variant=\"ghost\"\n className=\"border border-foreground rounded-full gap-1\"\n size=\"sm\"\n title=\"Add filter\"\n >\n <Plus size={16} />\n Add filter\n </Button>\n )}\n </fieldset>\n );\n};\n\nexport { Filters };\n"],"names":["Operator","CustomValueInput","inputValue","setInputValue","customValuePlaceholder","customValueButtonLabel","onSubmit","handleKeyDown","inputRef","useRef","React","createElement","className","e","preventDefault","Input","ref","type","value","onChange","target","placeholder","onKeyDown","Button","variant","MultiSelectDropdown","internalFilter","valueOptions","onConfirm","onClose","pendingValues","setPendingValues","useState","getInitialValues","Array","isArray","announcement","setAnnouncement","buttonRefs","applyButtonRef","handleValueToggle","valueId","option","find","opt","id","isCurrentlySelected","includes","prev","newValues","filter","action","label","handleConfirm","isValueSelected","focusButton","index","length","current","focus","key","nextIndex","Math","min","prevIndex","max","role","map","el","onClick","Check","size","disabled","SingleSelectDropdown","onValueSelect","Filter","forwardRef","sourceOptions","rules","onSourceChange","onRemoveFilter","onFilterChange","selectPlaceholder","sourceFirstMessage","setInternalFilter","source","operator","IS","openDropdownType","setOpenDropdownType","sourceButtonRef","divRef","useImperativeHandle","currentRules","operatorOptions","operators","isMultiSelect","multiSelect","inputType","CONTAINS","DOES_NOT_CONTAIN","getValueOptions","getSelectedValues","getDisplayValue","selectedValues","useMultiSelectHelpers","useEffect","timeoutId","setTimeout","clearTimeout","checkAndTriggerChange","newFilter","hasValue","trim","updateFilterField","field","sourceRules","ButtonGroup","orientation","DropdownMenu","open","onOpenChange","modal","DropdownMenuTrigger","asChild","DropdownMenuContent","align","DropdownMenuItem","onSelect","Popover","PopoverTrigger","PopoverContent","isInput","shiftKey","button","document","querySelector","XCircle","weight","displayName","Filters","filters","onFilterRemove","onFilterAdd","internalFilters","setInternalFilters","filterRefs","usedSources","Boolean","availableSources","canAddFilter","cn","filterSourceOptions","newFilters","f","handleRemoveFilter","createId","requestAnimationFrame","title","Plus"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import*as e from"react";import{Check as t,SpinnerGap as o}from"@phosphor-icons/react";import{c as a}from"./utils.C6Qu-kwd.js";import{CommandItem as n}from"../components/ui/command.js";import{C as r}from"./combobox.MyoPH18G.js";const s="brainfish-google-fonts-cache",l="brainfish-google-fonts-cache-timestamp",c=/* @__PURE__ */new Set,i=()=>{try{const e=localStorage.getItem(l);if(!e)return null;if(Date.now()-parseInt(e,10)>864e5)return localStorage.removeItem(s),localStorage.removeItem(l),null;const t=localStorage.getItem(s);return t?JSON.parse(t):null}catch{return null}},m=e=>{try{localStorage.setItem(s,JSON.stringify(e)),localStorage.setItem(l,Date.now().toString())}catch{}},f=e=>{if(c.has(e))return;const t=document.createElement("link");t.href=`https://fonts.googleapis.com/css2?family=${e.replace(/ /g,"+")}&display=swap`,t.rel="stylesheet",document.head.appendChild(t),c.add(e)},u=()=>{try{localStorage.removeItem(s),localStorage.removeItem(l)}catch{}};function d({apiKey:t,maxFonts:o=500,onError:a}){const[n,r]=e.useState([]),[s,l]=e.useState(!0),c=e.useRef(a);return e.useEffect(()=>{c.current=a},[a]),e.useEffect(()=>{(async()=>{const e=i();if(e&&e.length>0)return r(e.slice(0,o)),void l(!1);try{const e=await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${t}&sort=popularity`);if(!e.ok)throw new Error(`Failed to fetch fonts: ${e.statusText}`);const a=(await e.json()).items.map(e=>({value:e.family,label:e.family,category:e.category,variants:e.variants}));m(a),r(a.slice(0,o))}catch(a){const e=a instanceof Error?a:new Error("Failed to fetch fonts");console.error("FontPicker: Failed to fetch Google Fonts",e),c.current?.(e)}finally{l(!1)}})()},[t,o]),{fonts:n,isLoading:s,loadFont:f}}const g=e.memo(function({font:o,isSelected:r,onSelect:s}){const l=e.useRef(null),[c,i]=e.useState(!1);e.useEffect(()=>{const e=new IntersectionObserver(([t])=>{t.isIntersecting&&(i(!0),e.disconnect())},{threshold:.1});return l.current&&e.observe(l.current),()=>e.disconnect()},[]),e.useEffect(()=>{c&&f(o.value)},[c,o.value]);const m=`Select ${o.label} font${o.category?`, ${o.category} category`:""}`;/* @__PURE__ */
1
+ import*as e from"react";import{Check as t,SpinnerGap as o}from"@phosphor-icons/react";import{c as a}from"./utils.C6Qu-kwd.js";import{CommandItem as n}from"../components/ui/command.js";import{C as r}from"./combobox.DJCyKFxV.js";const s="brainfish-google-fonts-cache",l="brainfish-google-fonts-cache-timestamp",c=/* @__PURE__ */new Set,i=()=>{try{const e=localStorage.getItem(l);if(!e)return null;if(Date.now()-parseInt(e,10)>864e5)return localStorage.removeItem(s),localStorage.removeItem(l),null;const t=localStorage.getItem(s);return t?JSON.parse(t):null}catch{return null}},m=e=>{try{localStorage.setItem(s,JSON.stringify(e)),localStorage.setItem(l,Date.now().toString())}catch{}},f=e=>{if(c.has(e))return;const t=document.createElement("link");t.href=`https://fonts.googleapis.com/css2?family=${e.replace(/ /g,"+")}&display=swap`,t.rel="stylesheet",document.head.appendChild(t),c.add(e)},u=()=>{try{localStorage.removeItem(s),localStorage.removeItem(l)}catch{}};function d({apiKey:t,maxFonts:o=500,onError:a}){const[n,r]=e.useState([]),[s,l]=e.useState(!0),c=e.useRef(a);return e.useEffect(()=>{c.current=a},[a]),e.useEffect(()=>{(async()=>{const e=i();if(e&&e.length>0)return r(e.slice(0,o)),void l(!1);try{const e=await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${t}&sort=popularity`);if(!e.ok)throw new Error(`Failed to fetch fonts: ${e.statusText}`);const a=(await e.json()).items.map(e=>({value:e.family,label:e.family,category:e.category,variants:e.variants}));m(a),r(a.slice(0,o))}catch(a){const e=a instanceof Error?a:new Error("Failed to fetch fonts");console.error("FontPicker: Failed to fetch Google Fonts",e),c.current?.(e)}finally{l(!1)}})()},[t,o]),{fonts:n,isLoading:s,loadFont:f}}const g=e.memo(function({font:o,isSelected:r,onSelect:s}){const l=e.useRef(null),[c,i]=e.useState(!1);e.useEffect(()=>{const e=new IntersectionObserver(([t])=>{t.isIntersecting&&(i(!0),e.disconnect())},{threshold:.1});return l.current&&e.observe(l.current),()=>e.disconnect()},[]),e.useEffect(()=>{c&&f(o.value)},[c,o.value]);const m=`Select ${o.label} font${o.category?`, ${o.category} category`:""}`;/* @__PURE__ */
2
2
  return e.createElement(n,{ref:l,value:o.label,onSelect:s,className:"cursor-pointer","aria-label":m,"aria-selected":r},
3
3
  /* @__PURE__ */e.createElement("span",{style:{fontFamily:c?o.value:void 0},className:"flex-1 truncate"},o.label),o.category&&/* @__PURE__ */e.createElement("span",{className:"text-xs text-muted-foreground capitalize"},o.category),
4
4
  /* @__PURE__ */e.createElement(t,{className:a("ml-2 h-4 w-4",r?"opacity-100":"opacity-0")}))});function p({value:t,onChange:a,apiKey:n,placeholder:s="Select a font...",searchPlaceholder:l="Search fonts...",noResultsText:c="No fonts found.",loadingText:i="Loading fonts...",disabled:m=!1,className:f,dropdownWidth:u,maxFonts:p=500,onError:h}){const{fonts:y,isLoading:S,loadFont:E}=d({apiKey:n,maxFonts:p,onError:h});e.useEffect(()=>{t&&E(t)},[t,E]);const v=y;/* @__PURE__ */
5
5
  return e.createElement(r,{options:v,value:t,onChange:a,defaultValueLabel:s,placeholder:l,noResultsLabel:c,disabled:m||S,className:f,dropdownWidth:u,renderTrigger:({selectedItem:t})=>S?/* @__PURE__ */e.createElement("span",{className:"flex items-center gap-2 text-muted-foreground"},/* @__PURE__ */e.createElement(o,{className:"h-4 w-4 animate-spin"}),i):t?/* @__PURE__ */e.createElement("span",{style:{fontFamily:t.value}},t.label):/* @__PURE__ */e.createElement("span",{className:"text-muted-foreground"},s),renderItem:({item:t,isSelected:o,onSelect:a})=>/* @__PURE__ */e.createElement(g,{font:t,isSelected:o,onSelect:a})})}export{p as F,g as a,u as c,i as g,f as l,m as s,d as u};
6
- //# sourceMappingURL=font-picker.B9GPXyK4.js.map
6
+ //# sourceMappingURL=font-picker.BE03shJT.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"font-picker.B9GPXyK4.js","sources":["../../../src/components/font-picker/font-utils.ts","../../../src/components/font-picker/use-font-options.ts","../../../src/components/font-picker/font-picker-item.tsx","../../../src/components/font-picker/font-picker.tsx"],"sourcesContent":["import { FontOption } from './types';\n\n// Cache keys for localStorage\nconst FONT_CACHE_KEY = 'brainfish-google-fonts-cache';\nconst FONT_CACHE_TIMESTAMP_KEY = 'brainfish-google-fonts-cache-timestamp';\nconst FONT_CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n// Cache for loaded font stylesheets to prevent duplicate loads\nconst loadedFonts = new Set<string>();\n\n/**\n * Get cached fonts from localStorage\n */\nexport const getCachedFonts = (): FontOption[] | null => {\n try {\n const timestamp = localStorage.getItem(FONT_CACHE_TIMESTAMP_KEY);\n if (!timestamp) return null;\n\n const cacheAge = Date.now() - parseInt(timestamp, 10);\n if (cacheAge > FONT_CACHE_DURATION_MS) {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n\n return null;\n }\n\n const cached = localStorage.getItem(FONT_CACHE_KEY);\n if (!cached) return null;\n\n return JSON.parse(cached) as FontOption[];\n } catch {\n return null;\n }\n};\n\n/**\n * Save fonts to localStorage cache\n */\nexport const setCachedFonts = (fonts: FontOption[]): void => {\n try {\n localStorage.setItem(FONT_CACHE_KEY, JSON.stringify(fonts));\n localStorage.setItem(FONT_CACHE_TIMESTAMP_KEY, Date.now().toString());\n } catch {\n // localStorage might be full or unavailable, ignore\n }\n};\n\n/**\n * Load a Google Font stylesheet dynamically\n */\nexport const loadFont = (fontFamily: string): void => {\n if (loadedFonts.has(fontFamily)) return;\n\n const link = document.createElement('link');\n link.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}&display=swap`;\n link.rel = 'stylesheet';\n document.head.appendChild(link);\n loadedFonts.add(fontFamily);\n};\n\n/**\n * Clear the font cache from localStorage\n * Useful for forcing a fresh fetch of fonts\n */\nexport const clearFontCache = (): void => {\n try {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n } catch {\n // localStorage might be unavailable, ignore\n }\n};\n","import * as React from 'react';\n\nimport { FontOption } from './types';\nimport { getCachedFonts, setCachedFonts, loadFont } from './font-utils';\n\nexport interface UseFontOptionsProps {\n apiKey: string;\n maxFonts?: number;\n onError?: (error: Error) => void;\n}\n\nexport interface UseFontOptionsReturn {\n fonts: FontOption[];\n isLoading: boolean;\n loadFont: typeof loadFont;\n}\n\n/**\n * Hook to fetch and manage Google Fonts options\n * Handles caching, loading state, and font stylesheet loading\n */\nexport function useFontOptions({ apiKey, maxFonts = 500, onError }: UseFontOptionsProps): UseFontOptionsReturn {\n const [fonts, setFonts] = React.useState<FontOption[]>([]);\n const [isLoading, setIsLoading] = React.useState(true);\n\n // Stabilize onError callback reference to prevent effect re-runs when\n // consumers pass inline functions (which create new references on every render)\n const onErrorRef = React.useRef(onError);\n React.useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n React.useEffect(() => {\n const fetchFonts = async () => {\n // Check cache first\n const cachedFonts = getCachedFonts();\n if (cachedFonts && cachedFonts.length > 0) {\n setFonts(cachedFonts.slice(0, maxFonts));\n setIsLoading(false);\n\n return;\n }\n\n try {\n const response = await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}&sort=popularity`);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch fonts: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n const fontOptions: FontOption[] = data.items.map((fontItem: { family: string; category: string; variants: string[] }) => ({\n value: fontItem.family,\n label: fontItem.family,\n category: fontItem.category,\n variants: fontItem.variants,\n }));\n\n // Cache all fonts, but only display up to maxFonts\n setCachedFonts(fontOptions);\n setFonts(fontOptions.slice(0, maxFonts));\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch fonts');\n\n console.error('FontPicker: Failed to fetch Google Fonts', error);\n onErrorRef.current?.(error);\n } finally {\n setIsLoading(false);\n }\n };\n\n void fetchFonts();\n }, [apiKey, maxFonts]);\n\n return { fonts, isLoading, loadFont };\n}\n","import * as React from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FontOption } from './types';\nimport { loadFont } from './font-utils';\n\nimport { cn } from '@/lib/utils';\nimport { CommandItem } from '@/components/ui/command';\n\nexport interface FontPickerItemProps {\n font: FontOption;\n isSelected: boolean;\n onSelect: () => void;\n}\n\n/**\n * Individual font item in the dropdown list\n * Loads font on mount for preview using Intersection Observer\n * Memoized to prevent unnecessary re-renders\n */\nexport const FontPickerItem = React.memo(function FontPickerItem({ font, isSelected, onSelect }: FontPickerItemProps) {\n const itemRef = React.useRef<HTMLDivElement>(null);\n const [isVisible, setIsVisible] = React.useState(false);\n\n // Use Intersection Observer to lazy-load fonts\n React.useEffect(() => {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true);\n observer.disconnect();\n }\n },\n { threshold: 0.1 },\n );\n\n if (itemRef.current) {\n observer.observe(itemRef.current);\n }\n\n return () => observer.disconnect();\n }, []);\n\n // Load font when visible\n React.useEffect(() => {\n if (isVisible) {\n loadFont(font.value);\n }\n }, [isVisible, font.value]);\n\n // Build accessible label\n const ariaLabel = `Select ${font.label} font${font.category ? `, ${font.category} category` : ''}`;\n\n return (\n <CommandItem\n ref={itemRef}\n value={font.label}\n onSelect={onSelect}\n className=\"cursor-pointer\"\n aria-label={ariaLabel}\n aria-selected={isSelected}\n >\n <span style={{ fontFamily: isVisible ? font.value : undefined }} className=\"flex-1 truncate\">\n {font.label}\n </span>\n {font.category && <span className=\"text-xs text-muted-foreground capitalize\">{font.category}</span>}\n <Check className={cn('ml-2 h-4 w-4', isSelected ? 'opacity-100' : 'opacity-0')} />\n </CommandItem>\n );\n});\n","import * as React from 'react';\nimport { SpinnerGap } from '@phosphor-icons/react';\n\nimport { useFontOptions } from './use-font-options';\nimport { FontPickerItem } from './font-picker-item';\n\nimport { Combobox } from '@/components/combobox/combobox';\n\nexport interface FontPickerProps {\n /** Currently selected font family */\n value?: string;\n /** Callback when font selection changes */\n onChange?: (fontFamily: string) => void;\n /** Google Fonts API key (required) */\n apiKey: string;\n /** Placeholder text when no font is selected */\n placeholder?: string;\n /** Text shown in search input */\n searchPlaceholder?: string;\n /** Text shown when no fonts match the search */\n noResultsText?: string;\n /** Text shown while fonts are loading */\n loadingText?: string;\n /** Whether the picker is disabled */\n disabled?: boolean;\n /** Additional CSS classes for the trigger button */\n className?: string;\n /** Width of the dropdown (defaults to trigger width) */\n dropdownWidth?: number;\n /** Maximum number of fonts to display (for performance) */\n maxFonts?: number;\n /** Callback when an error occurs fetching fonts */\n onError?: (error: Error) => void;\n}\n\n/**\n * FontPicker - A searchable dropdown for selecting Google Fonts\n *\n * @example\n * ```tsx\n * const [font, setFont] = useState('Inter');\n *\n * <FontPicker\n * apiKey=\"YOUR_API_KEY\"\n * value={font}\n * onChange={setFont}\n * placeholder=\"Select a font\"\n * />\n * ```\n */\nexport function FontPicker({\n value,\n onChange,\n apiKey,\n placeholder = 'Select a font...',\n searchPlaceholder = 'Search fonts...',\n noResultsText = 'No fonts found.',\n loadingText = 'Loading fonts...',\n disabled = false,\n className,\n dropdownWidth,\n maxFonts = 500,\n onError,\n}: FontPickerProps) {\n const { fonts, isLoading, loadFont } = useFontOptions({ apiKey, maxFonts, onError });\n\n // Load font stylesheet when selected\n React.useEffect(() => {\n if (value) {\n loadFont(value);\n }\n }, [value, loadFont]);\n\n // fonts already have value/label which Combobox requires\n const options = fonts;\n\n return (\n <Combobox\n options={options}\n value={value}\n onChange={onChange}\n defaultValueLabel={placeholder}\n placeholder={searchPlaceholder}\n noResultsLabel={noResultsText}\n disabled={disabled || isLoading}\n className={className}\n dropdownWidth={dropdownWidth}\n renderTrigger={({ selectedItem }) => {\n if (isLoading) {\n return (\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <SpinnerGap className=\"h-4 w-4 animate-spin\" />\n {loadingText}\n </span>\n );\n }\n\n if (selectedItem) {\n return <span style={{ fontFamily: selectedItem.value }}>{selectedItem.label}</span>;\n }\n\n return <span className=\"text-muted-foreground\">{placeholder}</span>;\n }}\n renderItem={({ item, isSelected, onSelect }) => (\n <FontPickerItem font={item} isSelected={isSelected} onSelect={onSelect} />\n )}\n />\n );\n}\n\nexport default FontPicker;\n"],"names":["FONT_CACHE_KEY","FONT_CACHE_TIMESTAMP_KEY","loadedFonts","Set","getCachedFonts","timestamp","localStorage","getItem","Date","now","parseInt","removeItem","cached","JSON","parse","setCachedFonts","fonts","setItem","stringify","toString","loadFont","fontFamily","has","link","document","createElement","href","replace","rel","head","appendChild","add","clearFontCache","useFontOptions","apiKey","maxFonts","onError","setFonts","React","useState","isLoading","setIsLoading","onErrorRef","useRef","useEffect","current","async","cachedFonts","length","slice","response","fetch","ok","Error","statusText","fontOptions","json","items","map","fontItem","value","family","label","category","variants","err","error","console","fetchFonts","FontPickerItem","memo","font","isSelected","onSelect","itemRef","isVisible","setIsVisible","observer","IntersectionObserver","entry","isIntersecting","disconnect","threshold","observe","ariaLabel","CommandItem","ref","className","style","Check","cn","FontPicker","onChange","placeholder","searchPlaceholder","noResultsText","loadingText","disabled","dropdownWidth","options","Combobox","defaultValueLabel","noResultsLabel","renderTrigger","selectedItem","SpinnerGap","renderItem","item"],"mappings":"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"}
1
+ {"version":3,"file":"font-picker.BE03shJT.js","sources":["../../../src/components/font-picker/font-utils.ts","../../../src/components/font-picker/use-font-options.ts","../../../src/components/font-picker/font-picker-item.tsx","../../../src/components/font-picker/font-picker.tsx"],"sourcesContent":["import { FontOption } from './types';\n\n// Cache keys for localStorage\nconst FONT_CACHE_KEY = 'brainfish-google-fonts-cache';\nconst FONT_CACHE_TIMESTAMP_KEY = 'brainfish-google-fonts-cache-timestamp';\nconst FONT_CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n// Cache for loaded font stylesheets to prevent duplicate loads\nconst loadedFonts = new Set<string>();\n\n/**\n * Get cached fonts from localStorage\n */\nexport const getCachedFonts = (): FontOption[] | null => {\n try {\n const timestamp = localStorage.getItem(FONT_CACHE_TIMESTAMP_KEY);\n if (!timestamp) return null;\n\n const cacheAge = Date.now() - parseInt(timestamp, 10);\n if (cacheAge > FONT_CACHE_DURATION_MS) {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n\n return null;\n }\n\n const cached = localStorage.getItem(FONT_CACHE_KEY);\n if (!cached) return null;\n\n return JSON.parse(cached) as FontOption[];\n } catch {\n return null;\n }\n};\n\n/**\n * Save fonts to localStorage cache\n */\nexport const setCachedFonts = (fonts: FontOption[]): void => {\n try {\n localStorage.setItem(FONT_CACHE_KEY, JSON.stringify(fonts));\n localStorage.setItem(FONT_CACHE_TIMESTAMP_KEY, Date.now().toString());\n } catch {\n // localStorage might be full or unavailable, ignore\n }\n};\n\n/**\n * Load a Google Font stylesheet dynamically\n */\nexport const loadFont = (fontFamily: string): void => {\n if (loadedFonts.has(fontFamily)) return;\n\n const link = document.createElement('link');\n link.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}&display=swap`;\n link.rel = 'stylesheet';\n document.head.appendChild(link);\n loadedFonts.add(fontFamily);\n};\n\n/**\n * Clear the font cache from localStorage\n * Useful for forcing a fresh fetch of fonts\n */\nexport const clearFontCache = (): void => {\n try {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n } catch {\n // localStorage might be unavailable, ignore\n }\n};\n","import * as React from 'react';\n\nimport { FontOption } from './types';\nimport { getCachedFonts, setCachedFonts, loadFont } from './font-utils';\n\nexport interface UseFontOptionsProps {\n apiKey: string;\n maxFonts?: number;\n onError?: (error: Error) => void;\n}\n\nexport interface UseFontOptionsReturn {\n fonts: FontOption[];\n isLoading: boolean;\n loadFont: typeof loadFont;\n}\n\n/**\n * Hook to fetch and manage Google Fonts options\n * Handles caching, loading state, and font stylesheet loading\n */\nexport function useFontOptions({ apiKey, maxFonts = 500, onError }: UseFontOptionsProps): UseFontOptionsReturn {\n const [fonts, setFonts] = React.useState<FontOption[]>([]);\n const [isLoading, setIsLoading] = React.useState(true);\n\n // Stabilize onError callback reference to prevent effect re-runs when\n // consumers pass inline functions (which create new references on every render)\n const onErrorRef = React.useRef(onError);\n React.useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n React.useEffect(() => {\n const fetchFonts = async () => {\n // Check cache first\n const cachedFonts = getCachedFonts();\n if (cachedFonts && cachedFonts.length > 0) {\n setFonts(cachedFonts.slice(0, maxFonts));\n setIsLoading(false);\n\n return;\n }\n\n try {\n const response = await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}&sort=popularity`);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch fonts: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n const fontOptions: FontOption[] = data.items.map((fontItem: { family: string; category: string; variants: string[] }) => ({\n value: fontItem.family,\n label: fontItem.family,\n category: fontItem.category,\n variants: fontItem.variants,\n }));\n\n // Cache all fonts, but only display up to maxFonts\n setCachedFonts(fontOptions);\n setFonts(fontOptions.slice(0, maxFonts));\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch fonts');\n\n console.error('FontPicker: Failed to fetch Google Fonts', error);\n onErrorRef.current?.(error);\n } finally {\n setIsLoading(false);\n }\n };\n\n void fetchFonts();\n }, [apiKey, maxFonts]);\n\n return { fonts, isLoading, loadFont };\n}\n","import * as React from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FontOption } from './types';\nimport { loadFont } from './font-utils';\n\nimport { cn } from '@/lib/utils';\nimport { CommandItem } from '@/components/ui/command';\n\nexport interface FontPickerItemProps {\n font: FontOption;\n isSelected: boolean;\n onSelect: () => void;\n}\n\n/**\n * Individual font item in the dropdown list\n * Loads font on mount for preview using Intersection Observer\n * Memoized to prevent unnecessary re-renders\n */\nexport const FontPickerItem = React.memo(function FontPickerItem({ font, isSelected, onSelect }: FontPickerItemProps) {\n const itemRef = React.useRef<HTMLDivElement>(null);\n const [isVisible, setIsVisible] = React.useState(false);\n\n // Use Intersection Observer to lazy-load fonts\n React.useEffect(() => {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true);\n observer.disconnect();\n }\n },\n { threshold: 0.1 },\n );\n\n if (itemRef.current) {\n observer.observe(itemRef.current);\n }\n\n return () => observer.disconnect();\n }, []);\n\n // Load font when visible\n React.useEffect(() => {\n if (isVisible) {\n loadFont(font.value);\n }\n }, [isVisible, font.value]);\n\n // Build accessible label\n const ariaLabel = `Select ${font.label} font${font.category ? `, ${font.category} category` : ''}`;\n\n return (\n <CommandItem\n ref={itemRef}\n value={font.label}\n onSelect={onSelect}\n className=\"cursor-pointer\"\n aria-label={ariaLabel}\n aria-selected={isSelected}\n >\n <span style={{ fontFamily: isVisible ? font.value : undefined }} className=\"flex-1 truncate\">\n {font.label}\n </span>\n {font.category && <span className=\"text-xs text-muted-foreground capitalize\">{font.category}</span>}\n <Check className={cn('ml-2 h-4 w-4', isSelected ? 'opacity-100' : 'opacity-0')} />\n </CommandItem>\n );\n});\n","import * as React from 'react';\nimport { SpinnerGap } from '@phosphor-icons/react';\n\nimport { useFontOptions } from './use-font-options';\nimport { FontPickerItem } from './font-picker-item';\n\nimport { Combobox } from '@/components/combobox/combobox';\n\nexport interface FontPickerProps {\n /** Currently selected font family */\n value?: string;\n /** Callback when font selection changes */\n onChange?: (fontFamily: string) => void;\n /** Google Fonts API key (required) */\n apiKey: string;\n /** Placeholder text when no font is selected */\n placeholder?: string;\n /** Text shown in search input */\n searchPlaceholder?: string;\n /** Text shown when no fonts match the search */\n noResultsText?: string;\n /** Text shown while fonts are loading */\n loadingText?: string;\n /** Whether the picker is disabled */\n disabled?: boolean;\n /** Additional CSS classes for the trigger button */\n className?: string;\n /** Width of the dropdown (defaults to trigger width) */\n dropdownWidth?: number;\n /** Maximum number of fonts to display (for performance) */\n maxFonts?: number;\n /** Callback when an error occurs fetching fonts */\n onError?: (error: Error) => void;\n}\n\n/**\n * FontPicker - A searchable dropdown for selecting Google Fonts\n *\n * @example\n * ```tsx\n * const [font, setFont] = useState('Inter');\n *\n * <FontPicker\n * apiKey=\"YOUR_API_KEY\"\n * value={font}\n * onChange={setFont}\n * placeholder=\"Select a font\"\n * />\n * ```\n */\nexport function FontPicker({\n value,\n onChange,\n apiKey,\n placeholder = 'Select a font...',\n searchPlaceholder = 'Search fonts...',\n noResultsText = 'No fonts found.',\n loadingText = 'Loading fonts...',\n disabled = false,\n className,\n dropdownWidth,\n maxFonts = 500,\n onError,\n}: FontPickerProps) {\n const { fonts, isLoading, loadFont } = useFontOptions({ apiKey, maxFonts, onError });\n\n // Load font stylesheet when selected\n React.useEffect(() => {\n if (value) {\n loadFont(value);\n }\n }, [value, loadFont]);\n\n // fonts already have value/label which Combobox requires\n const options = fonts;\n\n return (\n <Combobox\n options={options}\n value={value}\n onChange={onChange}\n defaultValueLabel={placeholder}\n placeholder={searchPlaceholder}\n noResultsLabel={noResultsText}\n disabled={disabled || isLoading}\n className={className}\n dropdownWidth={dropdownWidth}\n renderTrigger={({ selectedItem }) => {\n if (isLoading) {\n return (\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <SpinnerGap className=\"h-4 w-4 animate-spin\" />\n {loadingText}\n </span>\n );\n }\n\n if (selectedItem) {\n return <span style={{ fontFamily: selectedItem.value }}>{selectedItem.label}</span>;\n }\n\n return <span className=\"text-muted-foreground\">{placeholder}</span>;\n }}\n renderItem={({ item, isSelected, onSelect }) => (\n <FontPickerItem font={item} isSelected={isSelected} onSelect={onSelect} />\n )}\n />\n );\n}\n\nexport default FontPicker;\n"],"names":["FONT_CACHE_KEY","FONT_CACHE_TIMESTAMP_KEY","loadedFonts","Set","getCachedFonts","timestamp","localStorage","getItem","Date","now","parseInt","removeItem","cached","JSON","parse","setCachedFonts","fonts","setItem","stringify","toString","loadFont","fontFamily","has","link","document","createElement","href","replace","rel","head","appendChild","add","clearFontCache","useFontOptions","apiKey","maxFonts","onError","setFonts","React","useState","isLoading","setIsLoading","onErrorRef","useRef","useEffect","current","async","cachedFonts","length","slice","response","fetch","ok","Error","statusText","fontOptions","json","items","map","fontItem","value","family","label","category","variants","err","error","console","fetchFonts","FontPickerItem","memo","font","isSelected","onSelect","itemRef","isVisible","setIsVisible","observer","IntersectionObserver","entry","isIntersecting","disconnect","threshold","observe","ariaLabel","CommandItem","ref","className","style","Check","cn","FontPicker","onChange","placeholder","searchPlaceholder","noResultsText","loadingText","disabled","dropdownWidth","options","Combobox","defaultValueLabel","noResultsLabel","renderTrigger","selectedItem","SpinnerGap","renderItem","item"],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import*as e from"react";import{Crown as t,Copy as a,SidebarSimple as l,Lightning as r,Question as n}from"@phosphor-icons/react";import{L as o}from"./logo.DtwFGMBj.js";import{B as s}from"./button.D_2SonNs.js";import{DropdownMenu as i,DropdownMenuTrigger as c,DropdownMenuContent as m,DropdownMenuSeparator as d,DropdownMenuGroup as p,DropdownMenuSub as u,DropdownMenuSubTrigger as g,DropdownMenuPortal as f,DropdownMenuSubContent as E,DropdownMenuRadioGroup as h,DropdownMenuRadioItem as b,DropdownMenuShortcut as x,DropdownMenuItem as C}from"../components/ui/dropdown-menu.js";import{Avatar as v,AvatarImage as k,AvatarFallback as w,AvatarBadge as y}from"../components/ui/avatar.js";import{c as N}from"./utils.C6Qu-kwd.js";import{u as _}from"./feature-flags.DOcVlPHk.js";import{A as B}from"./ai-logo.Do__PIO3.js";const A=e.forwardRef(({children:t,size:a=48,className:l,...r},n)=>{const o=e.useId(),s=e.useId();/* @__PURE__ */
2
+ return e.createElement("div",{ref:n,className:N("relative shrink-0",l),style:{width:a,height:a},...r},/* @__PURE__ */e.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",style:{position:"absolute",inset:0,width:"100%",height:"100%"},viewBox:"0 0 25.2 25.2",fill:"none","aria-hidden":"true"},
3
+ /* @__PURE__ */e.createElement("path",{d:"M1.1687 5.09221C1.44983 3.03551 3.03409 1.43747 5.09056 1.15464C7.11115 0.876739 9.82098 0.6 12.6 0.6C15.3637 0.6 18.059 0.873703 20.076 1.15005C22.1498 1.43417 23.7418 3.05368 24.0222 5.12794C24.3077 7.23947 24.6 10.0294 24.6 12.6C24.6 15.1875 24.3038 17.9971 24.0166 20.1135C23.7379 22.1667 22.1732 23.7746 20.1207 24.0584C18.1434 24.3319 15.4723 24.6 12.6 24.6C9.71155 24.6 7.02648 24.3288 5.04597 24.0538C3.01073 23.7712 1.45383 22.185 1.17438 20.1493C0.890004 18.0777 0.6 15.3026 0.6 12.6C0.6 9.9154 0.886154 7.15926 1.1687 5.09221Z",fill:`url(#${o})`,stroke:`url(#${s})`,strokeOpacity:"0.7",strokeWidth:"1.2"}),
4
+ /* @__PURE__ */e.createElement("defs",null,/* @__PURE__ */e.createElement("linearGradient",{id:o,x1:"2.3931",y1:"0.6",x2:"17.282",y2:"22.5523",gradientUnits:"userSpaceOnUse"},/* @__PURE__ */e.createElement("stop",{offset:"0.0544031",stopColor:"#1868DB"}),/* @__PURE__ */e.createElement("stop",{offset:"0.5",stopColor:"#357DE8"}),/* @__PURE__ */e.createElement("stop",{offset:"0.802885",stopColor:"#7EE2B8"}),/* @__PURE__ */e.createElement("stop",{offset:"1",stopColor:"#B4EE2B"})),/* @__PURE__ */e.createElement("linearGradient",{id:s,x1:"2.3931",y1:"0.6",x2:"17.282",y2:"22.5523",gradientUnits:"userSpaceOnUse"},/* @__PURE__ */e.createElement("stop",{stopColor:"#B4EE2B"}),/* @__PURE__ */e.createElement("stop",{offset:"0.266033",stopColor:"#7EE2B8"}),/* @__PURE__ */e.createElement("stop",{offset:"0.740217",stopColor:"#357DE8"}),/* @__PURE__ */e.createElement("stop",{offset:"0.945597",stopColor:"#1868DB"})))),/* @__PURE__ */e.createElement("span",{className:"absolute inset-[20%] [&>*]:h-full [&>*]:w-full",style:{filter:`drop-shadow(0px ${(.04*a).toFixed(2)}px ${(.05*a).toFixed(2)}px rgba(22,27,53,0.55))`}},t??/* @__PURE__ */e.createElement(B,{variant:"light"})))});function z({accountPhoto:l,accountName:r,accountDropdownItems:n,teams:o,side:_="bottom",align:B="end",isSuperAdmin:A=!1,...z}){const S=o?.length>1||A,D=S&&o?.find(e=>e.isCurrent)?.name||"",j=r[0],I=Array.isArray(n[0])?n:[n],O=e.useCallback((e,t)=>a=>{a.stopPropagation(),t?t(e):navigator.clipboard.writeText(e)},[]),T=(t,l,r)=>t&&/* @__PURE__ */e.createElement("div",{className:"flex w-full items-center gap-1 text-xs text-subtlest ml-7"},t,null!=l&&/* @__PURE__ */e.createElement(s,{variant:"ghost",size:"icon",className:"size-4 shrink-0 p-0 [&>svg]:!size-3",onClick:O(l,r),"aria-label":"Copy ID"},
5
+ /* @__PURE__ */e.createElement(a,{className:"size-3"})));/* @__PURE__ */
6
+ return e.createElement(i,{...z},/* @__PURE__ */e.createElement(c,{asChild:!0},/* @__PURE__ */e.createElement(s,{size:"default",variant:"ghost",className:"rounded-full p-[1px] border border-dark-300 relative","aria-label":"Toggle account dropdown"},
7
+ /* @__PURE__ */e.createElement(v,null,/* @__PURE__ */e.createElement(k,{src:l,alt:`${r}'s account`,className:N("block rounded-full border border-background",A&&"border-2 border-solid border-red-700 inset-1")}),/* @__PURE__ */e.createElement(w,null,j),A&&/* @__PURE__ */e.createElement(y,{className:"bg-red-700 text-white"},/* @__PURE__ */e.createElement(t,null))),S&&/* @__PURE__ */e.createElement("span",{className:"text-xs text-default font-semibold pr-1"},D))),/* @__PURE__ */e.createElement(m,{side:_,align:B,className:"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]"},I.map((t,a)=>/* @__PURE__ */e.createElement(e.Fragment,{key:a},a>0&&/* @__PURE__ */e.createElement(d,null),/* @__PURE__ */e.createElement(p,null,t.map(t=>"submenu"===t.type?/* @__PURE__ */e.createElement(u,{key:t.id},/* @__PURE__ */e.createElement(g,{secondaryRow:T(t.secondaryLabel,t.copyValue,t.onCopy)},t.icon,t.label),/* @__PURE__ */e.createElement(f,null,/* @__PURE__ */e.createElement(E,{className:"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]"},null!=t.value&&t.onValueChange?/* @__PURE__ */e.createElement(h,{value:t.value,onValueChange:t.onValueChange},t.items.map(t=>/* @__PURE__ */e.createElement(b,{key:t.id,value:t.value},t.icon,t.label,null!=t.shortcut&&/* @__PURE__ */e.createElement(x,null,t.shortcut)))):t.items.map(t=>/* @__PURE__ */e.createElement(C,{key:t.id,onClick:t.onClick},t.icon,t.label,null!=t.shortcut&&/* @__PURE__ */e.createElement(x,null,t.shortcut)))))):/* @__PURE__ */e.createElement(C,{key:t.id,onClick:t.onClick,secondaryRow:T(t.secondaryLabel,t.copyValue,t.onCopy)},t.icon,t.label,null!=t.shortcut&&/* @__PURE__ */e.createElement(x,null,t.shortcut))))))))}A.displayName="AIBadge";const S=60,D=e.forwardRef(function({className:t,isSideNavOpen:a=!1,onSideNavigationToggle:i,onHelpClick:c,onBrainfishNativeClick:m,accountName:d,accountPhoto:p,accountDropdownItems:u,teams:g,side:f,align:E,showSuggestionAction:h,suggestionAction:b,onSuggestionActionClick:x,onLogoNav:C,isHomePath:v=!1,showSidebarToggle:k=!0,sidebarToggleRef:w,isSuperAdmin:y,isViewer:B,...S},D){const j=h&&!!b&&!!x,I=_("platform.brainfish-native.enabled",!1),O=!B;/* @__PURE__ */
8
+ return e.createElement("header",{ref:D,...S,className:N("px-4 py-3 relative",t)},/* @__PURE__ */e.createElement("nav",{"aria-label":"Account navigation",className:"flex justify-between items-center"},/* @__PURE__ */e.createElement("div",{className:"flex items-center gap-2"},/* @__PURE__ */e.createElement("a",{href:"/",onClick:e=>{!(0!==e.button||e.metaKey||e.ctrlKey||e.shiftKey||e.altKey)&&(e.preventDefault(),C())},"aria-current":v?"page":void 0},
9
+ /* @__PURE__ */e.createElement(o,{variant:"full",color:v?"fill":"outline",width:130,height:20,className:"hidden md:block"}),
10
+ /* @__PURE__ */e.createElement(o,{variant:"mark",color:v?"fill":"outline",width:37,height:20,className:"block md:hidden"})),k&&/* @__PURE__ */e.createElement(s,{ref:w,size:"icon",variant:"ghost","aria-label":"Toggle side navigation",className:"[&_svg]:size-6",onClick:i},
11
+ /* @__PURE__ */e.createElement(l,{"aria-hidden":"true",weight:a?"fill":"regular"}))),/* @__PURE__ */e.createElement("div",{className:"flex items-center gap-4"},j&&/* @__PURE__ */e.createElement(s,{variant:"suggestion",onClick:x},/* @__PURE__ */e.createElement(r,{weight:"fill","aria-hidden":"true"})," ",b),O&&/* @__PURE__ */e.createElement(s,{size:"icon",variant:"ghost","aria-label":"Open help",className:"[&_svg]:size-6",onClick:c},/* @__PURE__ */e.createElement(n,{"aria-hidden":"true"})),I&&/* @__PURE__ */e.createElement("button",{type:"button","aria-label":"Open Ask Brainfish",className:"flex items-center gap-2 rounded border border-dark-300 bg-surface py-1 pl-1 pr-1.5 cursor-pointer hover:bg-dark-200 active:bg-dark-400 focus-visible:outline-none",onClick:m},
12
+ /* @__PURE__ */e.createElement(A,{size:24}),
13
+ /* @__PURE__ */e.createElement("span",{className:"text-xs font-normal text-dark-900 whitespace-nowrap"},"Ask Brainfish")),/* @__PURE__ */e.createElement(z,{accountName:d,accountPhoto:p,accountDropdownItems:u,teams:g,side:f,align:E,isSuperAdmin:y}))))});export{S as H,D as a};
14
+ //# sourceMappingURL=header-nav.Bo_GNqXn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-nav.Bo_GNqXn.js","sources":["../../../src/components/ai-button/ai-badge.tsx","../../../src/layouts/header-nav/account-dropdown.tsx","../../../src/layouts/header-nav/header-nav.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '@/lib/utils';\nimport { AILogo } from '@/components/logo/ai-logo';\n\nconst SQUIRCLE_PATH =\n 'M1.1687 5.09221C1.44983 3.03551 3.03409 1.43747 5.09056 1.15464C7.11115 0.876739 9.82098 0.6 12.6 0.6C15.3637 0.6 18.059 0.873703 20.076 1.15005C22.1498 1.43417 23.7418 3.05368 24.0222 5.12794C24.3077 7.23947 24.6 10.0294 24.6 12.6C24.6 15.1875 24.3038 17.9971 24.0166 20.1135C23.7379 22.1667 22.1732 23.7746 20.1207 24.0584C18.1434 24.3319 15.4723 24.6 12.6 24.6C9.71155 24.6 7.02648 24.3288 5.04597 24.0538C3.01073 23.7712 1.45383 22.185 1.17438 20.1493C0.890004 18.0777 0.6 15.3026 0.6 12.6C0.6 9.9154 0.886154 7.15926 1.1687 5.09221Z';\n\nexport interface AIBadgeProps extends React.HTMLAttributes<HTMLDivElement> {\n children?: React.ReactNode;\n size?: number;\n}\n\nexport const AIBadge = React.forwardRef<HTMLDivElement, AIBadgeProps>(\n ({ children, size = 48, className, ...props }, ref) => {\n const fillId = React.useId();\n const strokeId = React.useId();\n\n return (\n <div ref={ref} className={cn('relative shrink-0', className)} style={{ width: size, height: size }} {...props}>\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n style={{ position: 'absolute', inset: 0, width: '100%', height: '100%' }}\n viewBox=\"0 0 25.2 25.2\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d={SQUIRCLE_PATH}\n fill={`url(#${fillId})`}\n stroke={`url(#${strokeId})`}\n strokeOpacity=\"0.7\"\n strokeWidth=\"1.2\"\n />\n <defs>\n <linearGradient id={fillId} x1=\"2.3931\" y1=\"0.6\" x2=\"17.282\" y2=\"22.5523\" gradientUnits=\"userSpaceOnUse\">\n <stop offset=\"0.0544031\" stopColor=\"#1868DB\" />\n <stop offset=\"0.5\" stopColor=\"#357DE8\" />\n <stop offset=\"0.802885\" stopColor=\"#7EE2B8\" />\n <stop offset=\"1\" stopColor=\"#B4EE2B\" />\n </linearGradient>\n <linearGradient id={strokeId} x1=\"2.3931\" y1=\"0.6\" x2=\"17.282\" y2=\"22.5523\" gradientUnits=\"userSpaceOnUse\">\n <stop stopColor=\"#B4EE2B\" />\n <stop offset=\"0.266033\" stopColor=\"#7EE2B8\" />\n <stop offset=\"0.740217\" stopColor=\"#357DE8\" />\n <stop offset=\"0.945597\" stopColor=\"#1868DB\" />\n </linearGradient>\n </defs>\n </svg>\n <span\n className=\"absolute inset-[20%] [&>*]:h-full [&>*]:w-full\"\n style={{\n filter: `drop-shadow(0px ${(size * 0.04).toFixed(2)}px ${(size * 0.05).toFixed(2)}px rgba(22,27,53,0.55))`,\n }}\n >\n {children ?? <AILogo variant=\"light\" />}\n </span>\n </div>\n );\n },\n);\n\nAIBadge.displayName = 'AIBadge';\n","import * as React from 'react';\nimport { Copy, Crown } from '@phosphor-icons/react';\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuGroup,\n DropdownMenuItem,\n DropdownMenuPortal,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuSeparator,\n DropdownMenuShortcut,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from '../../components/ui/dropdown-menu';\nimport { Avatar, AvatarFallback, AvatarImage, AvatarBadge } from '../../components/ui/avatar';\nimport { Button } from '../../components/ui/button';\n\nimport { cn } from '@/lib/utils';\n\nexport type AccountDropdownActionItem = {\n type?: 'action';\n id: string;\n label: string;\n icon?: React.ReactNode;\n onClick: () => void;\n /** Second line (e.g. \"ID: xyz\"); copy button shown when copyValue is set. */\n secondaryLabel?: React.ReactNode;\n copyValue?: string;\n onCopy?: (value: string) => void;\n /** Keyboard shortcut hint (e.g. \"⌘K\") shown on the right. */\n shortcut?: string;\n};\n\nexport type AccountDropdownSubOption = {\n id: string;\n label: string;\n icon?: React.ReactNode;\n /** Required for radio mode. */\n value?: string;\n /** Used in plain (non-radio) mode. */\n onClick?: () => void;\n /** Keyboard shortcut hint (e.g. \"⌘K\") shown on the right. */\n shortcut?: string;\n};\n\n/** Sub-option with a required `value`, used in radio-mode submenus. */\nexport type AccountDropdownRadioSubOption = Omit<AccountDropdownSubOption, 'value' | 'onClick'> & {\n value: string;\n};\n\ntype AccountDropdownSubmenuBase = {\n type: 'submenu';\n id: string;\n label: string;\n icon?: React.ReactNode;\n /** Second line on the submenu trigger (e.g. \"ID: xyz\"); copy button when copyValue is set. */\n secondaryLabel?: React.ReactNode;\n copyValue?: string;\n onCopy?: (value: string) => void;\n};\n\nexport type AccountDropdownSubmenuItem = AccountDropdownSubmenuBase &\n (\n | {\n /** The currently selected value. When provided with onValueChange, renders items as a radio group with selection indicators. */\n value: string;\n onValueChange: (value: string) => void;\n items: AccountDropdownRadioSubOption[];\n }\n | {\n value?: never;\n onValueChange?: never;\n items: AccountDropdownSubOption[];\n }\n );\n\nexport type AccountDropdownItem = AccountDropdownActionItem | AccountDropdownSubmenuItem;\n\nexport type AccountDropdownTeam = {\n id: string;\n name: string;\n isCurrent: boolean;\n logoUrl?: string;\n};\n\nexport interface AccountDropdownProps extends React.ComponentPropsWithoutRef<typeof DropdownMenu> {\n accountPhoto: string;\n accountName: string;\n /** Pass a flat array for a single group, or an array of arrays for multiple groups separated by dividers. */\n accountDropdownItems: AccountDropdownItem[] | AccountDropdownItem[][];\n teams: AccountDropdownTeam[];\n /** Side the dropdown opens on relative to the trigger. Defaults to 'bottom'. */\n side?: React.ComponentPropsWithoutRef<typeof DropdownMenuContent>['side'];\n /** Alignment of the dropdown relative to the trigger. Defaults to 'end'. */\n align?: React.ComponentPropsWithoutRef<typeof DropdownMenuContent>['align'];\n isSuperAdmin?: boolean;\n}\n\nexport function AccountDropdown({\n accountPhoto,\n accountName,\n accountDropdownItems,\n teams,\n side = 'bottom',\n align = 'end',\n isSuperAdmin = false,\n ...props\n}: AccountDropdownProps) {\n const showTeamName = teams?.length > 1 || isSuperAdmin;\n const currentTeamName = (showTeamName && teams?.find((el) => el.isCurrent)?.name) || '';\n const initial = accountName[0];\n\n const groups: AccountDropdownItem[][] = Array.isArray(accountDropdownItems[0])\n ? (accountDropdownItems as AccountDropdownItem[][])\n : [accountDropdownItems as AccountDropdownItem[]];\n\n const handleCopy = React.useCallback((value: string, onCopy?: (value: string) => void) => {\n return (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onCopy) onCopy(value);\n else navigator.clipboard.writeText(value);\n };\n }, []);\n\n const renderCopyRow = (\n secondaryLabel: React.ReactNode,\n copyValue: string | undefined,\n onCopy?: (value: string) => void,\n ) =>\n secondaryLabel && (\n <div className=\"flex w-full items-center gap-1 text-xs text-subtlest ml-7\">\n {secondaryLabel}\n {copyValue != null && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-4 shrink-0 p-0 [&>svg]:!size-3\"\n onClick={handleCopy(copyValue, onCopy)}\n aria-label=\"Copy ID\"\n >\n <Copy className=\"size-3\" />\n </Button>\n )}\n </div>\n );\n\n return (\n <DropdownMenu {...props}>\n <DropdownMenuTrigger asChild>\n <Button\n size=\"default\"\n variant=\"ghost\"\n className=\"rounded-full p-[1px] border border-dark-300 relative\"\n aria-label=\"Toggle account dropdown\"\n >\n <Avatar>\n <AvatarImage\n src={accountPhoto}\n alt={`${accountName}'s account`}\n className={cn(\n 'block rounded-full border border-background',\n isSuperAdmin && 'border-2 border-solid border-red-700 inset-1',\n )}\n />\n <AvatarFallback>{initial}</AvatarFallback>\n {isSuperAdmin && (\n <AvatarBadge className=\"bg-red-700 text-white\">\n <Crown />\n </AvatarBadge>\n )}\n </Avatar>\n {showTeamName && <span className=\"text-xs text-default font-semibold pr-1\">{currentTeamName}</span>}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n side={side}\n align={align}\n className=\"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]\"\n >\n {groups.map((group, groupIndex) => (\n <React.Fragment key={groupIndex}>\n {groupIndex > 0 && <DropdownMenuSeparator />}\n <DropdownMenuGroup>\n {group.map((item) =>\n item.type === 'submenu' ? (\n <DropdownMenuSub key={item.id}>\n <DropdownMenuSubTrigger\n secondaryRow={renderCopyRow(item.secondaryLabel, item.copyValue, item.onCopy)}\n >\n {item.icon}\n {item.label}\n </DropdownMenuSubTrigger>\n <DropdownMenuPortal>\n <DropdownMenuSubContent className=\"border-none shadow-[2px_4px_12px_0px_rgba(0,0,0,0.14)] dark:shadow-[2px_4px_12px_0px_rgba(255,255,255,0.14)]\">\n {item.value != null && item.onValueChange ? (\n <DropdownMenuRadioGroup value={item.value} onValueChange={item.onValueChange}>\n {item.items.map((option) => (\n <DropdownMenuRadioItem key={option.id} value={option.value}>\n {option.icon}\n {option.label}\n {option.shortcut != null && (\n <DropdownMenuShortcut>{option.shortcut}</DropdownMenuShortcut>\n )}\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n ) : (\n item.items.map((option) => (\n <DropdownMenuItem key={option.id} onClick={option.onClick}>\n {option.icon}\n {option.label}\n {option.shortcut != null && (\n <DropdownMenuShortcut>{option.shortcut}</DropdownMenuShortcut>\n )}\n </DropdownMenuItem>\n ))\n )}\n </DropdownMenuSubContent>\n </DropdownMenuPortal>\n </DropdownMenuSub>\n ) : (\n <DropdownMenuItem\n key={item.id}\n onClick={item.onClick}\n secondaryRow={renderCopyRow(item.secondaryLabel, item.copyValue, item.onCopy)}\n >\n {item.icon}\n {item.label}\n {item.shortcut != null && <DropdownMenuShortcut>{item.shortcut}</DropdownMenuShortcut>}\n </DropdownMenuItem>\n ),\n )}\n </DropdownMenuGroup>\n </React.Fragment>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n","import * as React from 'react';\nimport { Question, SidebarSimple, Lightning } from '@phosphor-icons/react';\n\nimport { Logo } from '../../components/logo';\nimport { AIBadge } from '../../components/ai-button';\nimport { Button } from '../../components/ui/button';\nimport { AccountDropdown, AccountDropdownProps } from './account-dropdown';\n\nimport { cn } from '@/lib/utils';\nimport { useBooleanFlagValue } from '@/lib/feature-flags';\n\ninterface HeaderNavProps extends React.ComponentPropsWithoutRef<'header'>, Omit<AccountDropdownProps, 'dir'> {\n isSideNavOpen?: boolean;\n onSideNavigationToggle: () => void;\n onHelpClick: () => void;\n onBrainfishNativeClick: () => void;\n onLogoNav: () => void;\n isHomePath?: boolean;\n showSuggestionAction?: boolean;\n suggestionAction?: React.ReactNode;\n onSuggestionActionClick?: () => void;\n showSidebarToggle?: boolean;\n isSuperAdmin?: boolean;\n isViewer?: boolean;\n /** Ref for the sidebar toggle button; used by layout to move focus here when sidebar is collapsed. */\n sidebarToggleRef?: React.Ref<HTMLButtonElement>;\n}\n\nexport const HEADER_NAV_HEIGHT = 60;\n\nexport const HeaderNav = React.forwardRef<HTMLElement, HeaderNavProps>(function HeaderNav(\n {\n className,\n isSideNavOpen = false,\n onSideNavigationToggle,\n onHelpClick,\n onBrainfishNativeClick,\n accountName,\n accountPhoto,\n accountDropdownItems,\n teams,\n side,\n align,\n showSuggestionAction,\n suggestionAction,\n onSuggestionActionClick,\n onLogoNav,\n isHomePath = false,\n showSidebarToggle = true,\n sidebarToggleRef,\n isSuperAdmin,\n isViewer,\n ...props\n },\n ref,\n) {\n const hasSuggestionAction = showSuggestionAction && !!suggestionAction && !!onSuggestionActionClick;\n const brainfishNativeEnabled = useBooleanFlagValue('platform.brainfish-native.enabled', false);\n const isHelpButtonEnabled = !isViewer;\n\n return (\n <header ref={ref} {...props} className={cn('px-4 py-3 relative', className)}>\n <nav aria-label=\"Account navigation\" className=\"flex justify-between items-center\">\n <div className=\"flex items-center gap-2\">\n <a\n href=\"/\"\n onClick={(e) => {\n const isPlainLeftClick = e.button === 0 && !e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey;\n\n if (isPlainLeftClick) {\n e.preventDefault();\n onLogoNav();\n }\n }}\n aria-current={isHomePath ? 'page' : undefined}\n >\n <Logo\n variant=\"full\"\n color={isHomePath ? 'fill' : 'outline'}\n width={130}\n height={20}\n className=\"hidden md:block\"\n />\n <Logo\n variant=\"mark\"\n color={isHomePath ? 'fill' : 'outline'}\n width={37}\n height={20}\n className=\"block md:hidden\"\n />\n </a>\n {showSidebarToggle && (\n <Button\n ref={sidebarToggleRef}\n size=\"icon\"\n variant=\"ghost\"\n aria-label=\"Toggle side navigation\"\n className=\"[&_svg]:size-6\"\n onClick={onSideNavigationToggle}\n >\n <SidebarSimple aria-hidden=\"true\" weight={isSideNavOpen ? 'fill' : 'regular'} />\n </Button>\n )}\n </div>\n <div className=\"flex items-center gap-4\">\n {hasSuggestionAction && (\n <Button variant=\"suggestion\" onClick={onSuggestionActionClick}>\n <Lightning weight=\"fill\" aria-hidden=\"true\" /> {suggestionAction}\n </Button>\n )}\n {isHelpButtonEnabled && (\n <Button size=\"icon\" variant=\"ghost\" aria-label=\"Open help\" className=\"[&_svg]:size-6\" onClick={onHelpClick}>\n <Question aria-hidden=\"true\" />\n </Button>\n )}\n {brainfishNativeEnabled && (\n <button\n type=\"button\"\n aria-label=\"Open Ask Brainfish\"\n className=\"flex items-center gap-2 rounded border border-dark-300 bg-surface py-1 pl-1 pr-1.5 cursor-pointer hover:bg-dark-200 active:bg-dark-400 focus-visible:outline-none\"\n onClick={onBrainfishNativeClick}\n >\n <AIBadge size={24} />\n <span className=\"text-xs font-normal text-dark-900 whitespace-nowrap\">Ask Brainfish</span>\n </button>\n )}\n <AccountDropdown\n accountName={accountName}\n accountPhoto={accountPhoto}\n accountDropdownItems={accountDropdownItems}\n teams={teams}\n side={side}\n align={align}\n isSuperAdmin={isSuperAdmin}\n />\n </div>\n </nav>\n </header>\n );\n});\n"],"names":["AIBadge","React","forwardRef","children","size","className","props","ref","fillId","useId","strokeId","cn","style","width","height","createElement","xmlns","position","inset","viewBox","fill","d","stroke","strokeOpacity","strokeWidth","id","x1","y1","x2","y2","gradientUnits","offset","stopColor","filter","toFixed","AILogo","variant","AccountDropdown","accountPhoto","accountName","accountDropdownItems","teams","side","align","isSuperAdmin","showTeamName","length","currentTeamName","find","el","isCurrent","name","initial","groups","Array","isArray","handleCopy","useCallback","value","onCopy","e","stopPropagation","navigator","clipboard","writeText","renderCopyRow","secondaryLabel","copyValue","Button","onClick","Copy","DropdownMenu","DropdownMenuTrigger","asChild","Avatar","AvatarImage","src","alt","AvatarFallback","AvatarBadge","Crown","DropdownMenuContent","map","group","groupIndex","Fragment","key","DropdownMenuSeparator","DropdownMenuGroup","item","type","DropdownMenuSub","DropdownMenuSubTrigger","secondaryRow","icon","label","DropdownMenuPortal","DropdownMenuSubContent","onValueChange","DropdownMenuRadioGroup","items","option","DropdownMenuRadioItem","shortcut","DropdownMenuShortcut","DropdownMenuItem","displayName","HEADER_NAV_HEIGHT","HeaderNav","isSideNavOpen","onSideNavigationToggle","onHelpClick","onBrainfishNativeClick","showSuggestionAction","suggestionAction","onSuggestionActionClick","onLogoNav","isHomePath","showSidebarToggle","sidebarToggleRef","isViewer","hasSuggestionAction","brainfishNativeEnabled","useBooleanFlagValue","isHelpButtonEnabled","href","button","metaKey","ctrlKey","shiftKey","altKey","preventDefault","Logo","color","SidebarSimple","weight","Lightning","Question"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import e,{useContext as a,useState as t,useMemo as r,useRef as n,useEffect as l,useCallback as i,isValidElement as o}from"react";import{useMediaQuery as s}from"usehooks-ts";import{a as d,H as c}from"./header-nav.C9r4A44a.js";import{cva as u}from"class-variance-authority";import{c as m}from"./utils.C6Qu-kwd.js";import{ScrollArea as h}from"../components/ui/scroll-area.js";import{F as f}from"./feature-flags.DOcVlPHk.js";import{Item as v}from"../components/ui/item.js";import{S as g}from"./sidebar.HOrQaimG.js";const p=e.createContext(null),b=e.createContext(null);function w({value:a,children:t}){/* @__PURE__ */
1
+ import e,{useContext as a,useState as t,useMemo as r,useRef as n,useEffect as l,useCallback as i,isValidElement as o}from"react";import{useMediaQuery as s}from"usehooks-ts";import{a as d,H as c}from"./header-nav.Bo_GNqXn.js";import{cva as u}from"class-variance-authority";import{c as m}from"./utils.C6Qu-kwd.js";import{ScrollArea as h}from"../components/ui/scroll-area.js";import{F as f}from"./feature-flags.DOcVlPHk.js";import{Item as v}from"../components/ui/item.js";import{S as g}from"./sidebar.HOrQaimG.js";const p=e.createContext(null),b=e.createContext(null);function w({value:a,children:t}){/* @__PURE__ */
2
2
  return e.createElement(b.Provider,{value:a},t)}function y(){return e.useContext(b)}function N(){const a=e.useContext(b);if(!a)throw new Error("useRightSidebar must be used within a <FullLayout> component.");return{isOpen:a.isRightSidebarOpen,open:a.openRightSidebar,close:a.closeRightSidebar,toggle:a.toggleRightSidebar}}function E({children:a}){/* @__PURE__ */
3
3
  return e.createElement(e.Fragment,null,a)}function S({children:a}){/* @__PURE__ */
4
4
  return e.createElement(e.Fragment,null,a)}function x(a){const{onBrainfishNativeClick:t,...r}=a,n=y();if(!n)return null;/* @__PURE__ */
@@ -17,4 +17,4 @@ return e.createElement(f,{flags:t},/* @__PURE__ */e.createElement(I,{...r}))},{M
17
17
  /* @__PURE__ */e.createElement("svg",{className:"size-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},/* @__PURE__ */e.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 6h16M4 12h16M4 18h16"}))),
18
18
  /* @__PURE__ */e.createElement("div",{"data-slot":"full-layout-header-center",className:"flex-1 min-w-0"},n),null!=t&&/* @__PURE__ */e.createElement("div",{"data-slot":"full-layout-header-trigger-right",className:"ml-auto"},/* @__PURE__ */e.createElement("button",{type:"button",onClick:t,className:"p-2 hover:bg-gray-100 rounded-lg transition-colors","aria-label":"Toggle chat"},
19
19
  /* @__PURE__ */e.createElement("svg",{className:"size-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24"},/* @__PURE__ */e.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"}))))));B.displayName="FullLayoutHeader";export{j as F,B as a,R as b,p as c,w as d,k as e,N as f,y as u};
20
- //# sourceMappingURL=header-pane.1-3lMndU.js.map
20
+ //# sourceMappingURL=header-pane.hQNeFYWQ.js.map