@brainfish-ai/components 0.30.5 → 0.30.7

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 (71) hide show
  1. package/dist/chat-search.d.ts +11 -1
  2. package/dist/conversation.d.ts +4 -2
  3. package/dist/esm/ChatSearch.css +1 -1
  4. package/dist/esm/chunks/{ChatSearch.BLZiMiJ3.js → ChatSearch.B0vv3DDb.js} +89 -84
  5. package/dist/esm/chunks/ChatSearch.B0vv3DDb.js.map +1 -0
  6. package/dist/esm/chunks/Conversation.UzfPizn5.js +29 -0
  7. package/dist/esm/chunks/Conversation.UzfPizn5.js.map +1 -0
  8. package/dist/esm/chunks/ConversationStatus.BhdnM1uS.js +2 -0
  9. package/dist/esm/chunks/ConversationStatus.BhdnM1uS.js.map +1 -0
  10. package/dist/esm/chunks/{FormattedMessage.CrcIzpCM.js → FormattedMessage.D_knqVl-.js} +13 -13
  11. package/dist/esm/chunks/{FormattedMessage.CrcIzpCM.js.map → FormattedMessage.D_knqVl-.js.map} +1 -1
  12. package/dist/esm/chunks/{ai-logo.Do__PIO3.js → ai-logo.Bh2bhNcG.js} +7 -6
  13. package/dist/esm/chunks/ai-logo.Bh2bhNcG.js.map +1 -0
  14. package/dist/esm/chunks/{combobox.DJCyKFxV.js → combobox.MyoPH18G.js} +4 -4
  15. package/dist/esm/chunks/{combobox.DJCyKFxV.js.map → combobox.MyoPH18G.js.map} +1 -1
  16. package/dist/esm/chunks/{data-table.CJOR-1Kf.js → data-table.bjvbLk2u.js} +2 -2
  17. package/dist/esm/chunks/{data-table.CJOR-1Kf.js.map → data-table.bjvbLk2u.js.map} +1 -1
  18. package/dist/esm/chunks/{filters.DPv-zTEC.js → filters.BHp3ukNW.js} +3 -3
  19. package/dist/esm/chunks/{filters.DPv-zTEC.js.map → filters.BHp3ukNW.js.map} +1 -1
  20. package/dist/esm/chunks/{font-picker.BE03shJT.js → font-picker.YAE99idL.js} +6 -6
  21. package/dist/esm/chunks/{font-picker.BE03shJT.js.map → font-picker.YAE99idL.js.map} +1 -1
  22. package/dist/esm/chunks/formatDate.D2xEZm8f.js.map +1 -1
  23. package/dist/esm/chunks/{header-nav.Bo_GNqXn.js → header-nav.BfblRKoS.js} +6 -6
  24. package/dist/esm/chunks/{header-nav.Bo_GNqXn.js.map → header-nav.BfblRKoS.js.map} +1 -1
  25. package/dist/esm/chunks/{header-pane.hQNeFYWQ.js → header-pane.CMHPm65L.js} +2 -2
  26. package/dist/esm/chunks/{header-pane.hQNeFYWQ.js.map → header-pane.CMHPm65L.js.map} +1 -1
  27. package/dist/esm/chunks/{sidebar.HOrQaimG.js → sidebar.cdQIaDi5.js} +7 -7
  28. package/dist/esm/chunks/{sidebar.HOrQaimG.js.map → sidebar.cdQIaDi5.js.map} +1 -1
  29. package/dist/esm/chunks/{simpleSelect.DK1qZSXM.js → simpleSelect.CEUUfl3O.js} +3 -3
  30. package/dist/esm/chunks/{simpleSelect.DK1qZSXM.js.map → simpleSelect.CEUUfl3O.js.map} +1 -1
  31. package/dist/esm/chunks/{two-level-combobox.DQpxBbg3.js → two-level-combobox.DJYP--W9.js} +3 -3
  32. package/dist/esm/chunks/{two-level-combobox.DQpxBbg3.js.map → two-level-combobox.DJYP--W9.js.map} +1 -1
  33. package/dist/esm/components/articles-coverage.js +3 -3
  34. package/dist/esm/components/chart-radial-stacked.js +2 -2
  35. package/dist/esm/components/chat-search.js +1 -1
  36. package/dist/esm/components/combobox.js +1 -1
  37. package/dist/esm/components/conversation.js +1 -1
  38. package/dist/esm/components/convos.js +1 -1
  39. package/dist/esm/components/data-table.js +1 -1
  40. package/dist/esm/components/discoveries-created.js +2 -2
  41. package/dist/esm/components/filter.js +1 -1
  42. package/dist/esm/components/font-picker.js +1 -1
  43. package/dist/esm/components/logo.js +1 -1
  44. package/dist/esm/components/markdown.js +1 -1
  45. package/dist/esm/components/metric-card.js +2 -2
  46. package/dist/esm/components/select.js +1 -1
  47. package/dist/esm/components/two-level-combobox.js +1 -1
  48. package/dist/esm/components/ui/combobox.js +1 -1
  49. package/dist/esm/components/ui/label.js +1 -1
  50. package/dist/esm/components/ui/popover.js +1 -1
  51. package/dist/esm/components/ui/separator.js +1 -1
  52. package/dist/esm/components/ui/spinner.js +2 -2
  53. package/dist/esm/components/ui/switch.js +2 -2
  54. package/dist/esm/global.css +1 -1
  55. package/dist/esm/index.js +1 -1
  56. package/dist/esm/layouts/full-layout.js +1 -1
  57. package/dist/esm/layouts/header-nav.js +1 -1
  58. package/dist/esm/layouts/sidebar.js +1 -1
  59. package/dist/esm/logos/microsoft-logo.js +2 -2
  60. package/dist/esm/scenes/knowledge-review.js +8 -8
  61. package/dist/esm/tailwind.preset.js +1 -1
  62. package/dist/esm/tailwind.preset.js.map +1 -1
  63. package/dist/index.d.ts +23 -4
  64. package/dist/logo.d.ts +8 -1
  65. package/dist/stats.html +1 -1
  66. package/package.json +4 -4
  67. package/tailwind.preset.ts +2 -0
  68. package/dist/esm/chunks/ChatSearch.BLZiMiJ3.js.map +0 -1
  69. package/dist/esm/chunks/Conversation.Biqpt5FT.js +0 -29
  70. package/dist/esm/chunks/Conversation.Biqpt5FT.js.map +0 -1
  71. package/dist/esm/chunks/ai-logo.Do__PIO3.js.map +0 -1
@@ -1,7 +1,8 @@
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"},
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:a}){if("yellowfin"===n)/* @__PURE__ */
2
+ return e.createElement(e.Fragment,null,/* @__PURE__ */e.createElement("defs",null,/* @__PURE__ */e.createElement("linearGradient",{id:a,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(#${a})`})));if("monochrome"===n)/* @__PURE__ */
3
+ return e.createElement(e.Fragment,null,t.map((t,r)=>/* @__PURE__ */e.createElement("path",{key:r,d:t})));const l=r[n];/* @__PURE__ */
4
+ return e.createElement(e.Fragment,null,t.map((t,r)=>/* @__PURE__ */e.createElement("path",{key:r,d:t,fill:l})))}const a=e.forwardRef(({variant:t,width:r=72,height:a=72,...l},o)=>{const C=`ai-logo-gradient-${e.useId()}`;/* @__PURE__ */
5
+ return e.createElement("svg",{xmlns:"http://www.w3.org/2000/svg",...l,width:r,height:a,ref:o,viewBox:"0 0 72 72",role:"img",fill:"none"},
5
6
  /* @__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
7
+ /* @__PURE__ */e.createElement(n,{variant:t,gradientId:C}))});a.displayName="AILogo";export{a as A,n as a};
8
+ //# sourceMappingURL=ai-logo.Bh2bhNcG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-logo.Bh2bhNcG.js","sources":["../../../src/components/logo/ai-logo.tsx"],"sourcesContent":["import * as React from 'react';\n\nexport type AILogoVariant = 'dark' | 'light' | 'yellowfin' | 'monochrome';\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' | 'monochrome'>, string> = {\n dark: '#262626',\n light: '#FAFAFA',\n};\n\ntype AILogoPathsProps = {\n variant: AILogoVariant;\n gradientId: string;\n};\n\nexport function 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 if (variant === 'monochrome') {\n return (\n <>\n {PATH_DATA.map((d, i) => (\n <path key={i} d={d} />\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,EAAoF,CACxFC,KAAM,UACNC,MAAO,WAQF,SAASC,GAAYC,QAAEA,EAAAC,WAASA,IACrC,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,GAAgB,eAAZD;AACF,OACEE,EAAAC,cAAAD,EAAAE,SAAA,KACGT,EAAUkB,IAAI,CAACC,EAAGC,mBACjBb,EAAAC,cAAC,OAAA,CAAKa,IAAKD,EAAGD,QAMtB,MAAMG,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),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}):$,
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,
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:()=>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,
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,
5
5
  /* @__PURE__ */e.createElement(a,{className:l("ml-auto",n?"opacity-100":"opacity-0")}))}))))))}export{f as C};
6
- //# sourceMappingURL=combobox.DJCyKFxV.js.map
6
+ //# sourceMappingURL=combobox.MyoPH18G.js.map
@@ -1 +1 @@
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"}
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,5 +1,5 @@
1
- import e,{useCallback as t}from"react";import{useReactTable as a,getSortedRowModel as n,getExpandedRowModel as l,getFilteredRowModel as s,getPaginationRowModel as r,getCoreRowModel as o,flexRender as i}from"@tanstack/react-table";import{AnimatePresence as d,motion as m}from"framer-motion";import{TableRow as c,TableCell as p,Table as g,TableHeader as u,TableHead as h,TableBody as E}from"../components/ui/table.js";import{c as f}from"./utils.C6Qu-kwd.js";const x=({className:t,...a})=>/* @__PURE__ */e.createElement("div",{className:f("animate-original-pulse rounded-md bg-muted",t),...a}),w={initial:{height:0},animate:{height:"auto",transition:{height:{type:"spring",stiffness:450,damping:20}}},exit:{height:0,transition:{height:{duration:.1,ease:"linear"}}}};function N({columns:N,data:b,getRowCanExpand:y=()=>!0,className:C,expandeRowClassName:k,isLoading:R=!1,skeletonRowCount:S=10,...v}){const[M,$]=e.useState({}),[z,D]=e.useState([]);e.useEffect(()=>{$({})},[b]);const F=a({data:b,columns:N,getCoreRowModel:o(),state:{expanded:M,sorting:z},onExpandedChange:$,onSortingChange:D,getRowCanExpand:N.some(e=>e.expandedContent)?y:void 0,getPaginationRowModel:r(),getFilteredRowModel:s(),getExpandedRowModel:l(),getSortedRowModel:n()}),I=t(()=>Array.from({length:S}).map((t,a)=>/* @__PURE__ */e.createElement(c,{key:`skeleton-${a}`},N.map((t,n)=>/* @__PURE__ */e.createElement(p,{key:`skeleton-cell-${a}-${n}`,className:"first:pl-4 last:pr-4 py-4",style:{width:t.size}},
1
+ import e,{useCallback as t}from"react";import{useReactTable as a,getSortedRowModel as n,getExpandedRowModel as l,getFilteredRowModel as r,getPaginationRowModel as s,getCoreRowModel as o,flexRender as i}from"@tanstack/react-table";import{AnimatePresence as d,motion as m}from"framer-motion";import{TableRow as c,TableCell as p,Table as g,TableHeader as u,TableHead as h,TableBody as E}from"../components/ui/table.js";import{c as f}from"./utils.C6Qu-kwd.js";const x=({className:t,...a})=>/* @__PURE__ */e.createElement("div",{className:f("animate-original-pulse rounded-md bg-muted",t),...a}),w={initial:{height:0},animate:{height:"auto",transition:{height:{type:"spring",stiffness:450,damping:20}}},exit:{height:0,transition:{height:{duration:.1,ease:"linear"}}}};function N({columns:N,data:b,getRowCanExpand:y=()=>!0,className:C,expandeRowClassName:k,isLoading:R=!1,skeletonRowCount:S=10,...v}){const[M,$]=e.useState({}),[z,D]=e.useState([]);e.useEffect(()=>{$({})},[b]);const F=a({data:b,columns:N,getCoreRowModel:o(),state:{expanded:M,sorting:z},onExpandedChange:$,onSortingChange:D,getRowCanExpand:N.some(e=>e.expandedContent)?y:void 0,getPaginationRowModel:s(),getFilteredRowModel:r(),getExpandedRowModel:l(),getSortedRowModel:n()}),I=t(()=>Array.from({length:S}).map((t,a)=>/* @__PURE__ */e.createElement(c,{key:`skeleton-${a}`},N.map((t,n)=>/* @__PURE__ */e.createElement(p,{key:`skeleton-cell-${a}-${n}`,className:"first:pl-4 last:pr-4 py-4",style:{width:t.size}},
2
2
  /* @__PURE__ */e.createElement(x,{className:"h-4 w-full"}))))),[S,N]),V=t(()=>F.getRowModel().rows.map(t=>/* @__PURE__ */e.createElement(e.Fragment,{key:t.original.id||t.id},/* @__PURE__ */e.createElement(c,{"data-state":t.getIsSelected()&&"selected",className:f(t.getIsExpanded()&&"border-b-0")},t.getVisibleCells().map(t=>/* @__PURE__ */e.createElement(p,{key:t.id,className:"first:pl-4 last:pr-4 py-4",style:{width:t.column.getSize()}},i(t.column.columnDef.cell,t.getContext())))),/* @__PURE__ */e.createElement(d,{initial:!1},t.getIsExpanded()&&(()=>{const a=N.find(e=>e.expandedContent);return a?.expandedContent?/* @__PURE__ */e.createElement(e.Fragment,null,/* @__PURE__ */e.createElement(c,{"aria-hidden":!0,role:"presentation",className:"border-none"},/* @__PURE__ */e.createElement(p,{colSpan:t.getVisibleCells().length,className:"p-0 inset-0 h-[1px] w-full"},/* @__PURE__ */e.createElement("div",{className:"custom-dashed-border-b absolute inset-0 h-[1px]"}))),/* @__PURE__ */e.createElement(c,{key:`${t.id}-expanded`},/* @__PURE__ */e.createElement(p,{colSpan:t.getVisibleCells().length,className:f("first:pl-4 last:pr-4 py-4 overflow-hidden",k)},
3
3
  /* @__PURE__ */e.createElement(m.div,{variants:w,initial:"initial",animate:"animate",exit:"exit"},a.expandedContent(t))))):null})()))),[F,N,k]),j=F.getRowModel().rows?.length;/* @__PURE__ */
4
4
  return e.createElement("div",{className:f("overflow-hidden rounded-md border border-border bg-card",C),...v},/* @__PURE__ */e.createElement(g,{className:"overflow-hidden"},/* @__PURE__ */e.createElement(u,null,F.getHeaderGroups().map(t=>/* @__PURE__ */e.createElement(c,{key:t.id},t.headers.map(t=>/* @__PURE__ */e.createElement(h,{key:t.id,className:"space-x-4 first:pl-4 last:pr-4 py-4 text-current",style:{width:t.getSize()}},t.isPlaceholder?null:i(t.column.columnDef.header,t.getContext())))))),/* @__PURE__ */e.createElement(E,null,R?I():j?V():/* @__PURE__ */e.createElement(c,null,/* @__PURE__ */e.createElement(p,{colSpan:N.length,className:"h-24 text-center"},"No results.")))))}export{N as D};
5
- //# sourceMappingURL=data-table.CJOR-1Kf.js.map
5
+ //# sourceMappingURL=data-table.bjvbLk2u.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"data-table.CJOR-1Kf.js","sources":["../../../src/components/data-table/data-table.tsx"],"sourcesContent":["import React, { useCallback } from 'react';\nimport {\n ColumnDef,\n flexRender,\n getCoreRowModel,\n useReactTable,\n ExpandedState,\n SortingState,\n getPaginationRowModel,\n getFilteredRowModel,\n getExpandedRowModel,\n getSortedRowModel,\n Row,\n} from '@tanstack/react-table';\nimport { motion, AnimatePresence } from 'framer-motion';\n\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table';\n\nimport { cn } from '@/lib/utils';\n\n// Skeleton component for loading state\nconst Skeleton = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn('animate-original-pulse rounded-md bg-muted', className)} {...props} />\n);\n\n// Framer Motion Accordion Variants for bouncy accordion effect\nconst accordionVariants = {\n initial: {\n height: 0,\n },\n animate: {\n height: 'auto',\n transition: {\n height: {\n type: 'spring',\n stiffness: 450, // increase to make it more stiff (less bouncy), therefore increasing the speed of the animation\n damping: 20, // increase to make it more dampened and slow\n },\n },\n },\n exit: {\n height: 0,\n transition: {\n height: {\n duration: 0.1,\n ease: 'linear',\n },\n },\n },\n};\n\nexport type ExtendedColumnDef<TData, TValue = unknown> = ColumnDef<TData, TValue> & {\n expandedContent?: (row: Row<TData>) => React.ReactNode;\n};\n\nexport interface DataTableProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {\n columns: ExtendedColumnDef<TData, TValue>[];\n data: TData[];\n getRowCanExpand?: (row: Row<TData>) => boolean;\n className?: string;\n expandeRowClassName?: string;\n isLoading?: boolean;\n skeletonRowCount?: number;\n}\n\nexport function DataTable<TData extends Record<string, any> & { id?: string | number }, TValue>({\n columns,\n data,\n getRowCanExpand = () => true,\n className,\n expandeRowClassName,\n isLoading = false,\n skeletonRowCount = 10,\n ...props\n}: DataTableProps<TData, TValue>) {\n const [expanded, setExpanded] = React.useState<ExpandedState>({});\n const [sorting, setSorting] = React.useState<SortingState>([]);\n\n // Reset expanded state when data changes. Collapse all rows when data changes.\n React.useEffect(() => {\n setExpanded({});\n }, [data]);\n\n const table = useReactTable({\n data,\n columns,\n getCoreRowModel: getCoreRowModel(),\n state: {\n expanded,\n sorting,\n },\n onExpandedChange: setExpanded,\n onSortingChange: setSorting,\n getRowCanExpand: columns.some((col) => col.expandedContent) ? getRowCanExpand : undefined,\n getPaginationRowModel: getPaginationRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n getExpandedRowModel: getExpandedRowModel(),\n getSortedRowModel: getSortedRowModel(),\n });\n\n // Render the skeleton rows for loading state\n const renderSkeletonRows = useCallback(() => {\n return Array.from({ length: skeletonRowCount }).map((_, index) => (\n <TableRow key={`skeleton-${index}`}>\n {columns.map((column, colIndex) => (\n <TableCell\n key={`skeleton-cell-${index}-${colIndex}`}\n className=\"first:pl-4 last:pr-4 py-4\"\n style={{ width: column.size }}\n >\n <Skeleton className=\"h-4 w-full\" />\n </TableCell>\n ))}\n </TableRow>\n ));\n }, [skeletonRowCount, columns]);\n\n // Render the table rows\n const renderTableRows = useCallback(() => {\n return table.getRowModel().rows.map((row) => (\n <React.Fragment key={row.original.id || row.id}>\n <TableRow data-state={row.getIsSelected() && 'selected'} className={cn(row.getIsExpanded() && 'border-b-0')}>\n {row.getVisibleCells().map((cell) => (\n <TableCell key={cell.id} className=\"first:pl-4 last:pr-4 py-4\" style={{ width: cell.column.getSize() }}>\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </TableCell>\n ))}\n </TableRow>\n <AnimatePresence initial={false}>\n {row.getIsExpanded() &&\n (() => {\n // Find the first column with expandedContent\n const expandedColumn = columns.find((col) => col.expandedContent);\n\n return expandedColumn?.expandedContent ? (\n <>\n <TableRow aria-hidden role=\"presentation\" className=\"border-none\">\n <TableCell colSpan={row.getVisibleCells().length} className=\"p-0 inset-0 h-[1px] w-full\">\n <div className=\"custom-dashed-border-b absolute inset-0 h-[1px]\" />\n </TableCell>\n </TableRow>\n <TableRow key={`${row.id}-expanded`}>\n <TableCell\n colSpan={row.getVisibleCells().length}\n className={cn('first:pl-4 last:pr-4 py-4 overflow-hidden', expandeRowClassName)}\n >\n <motion.div variants={accordionVariants} initial=\"initial\" animate=\"animate\" exit=\"exit\">\n {expandedColumn.expandedContent(row)}\n </motion.div>\n </TableCell>\n </TableRow>\n </>\n ) : null;\n })()}\n </AnimatePresence>\n </React.Fragment>\n ));\n }, [table, columns, expandeRowClassName]);\n\n const hasResults = table.getRowModel().rows?.length;\n\n return (\n <div className={cn('overflow-hidden rounded-md border border-border bg-card', className)} {...props}>\n <Table className=\"overflow-hidden\">\n <TableHeader>\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n className=\"space-x-4 first:pl-4 last:pr-4 py-4 text-current\"\n style={{ width: header.getSize() }}\n >\n {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {isLoading ? (\n renderSkeletonRows()\n ) : hasResults ? (\n renderTableRows()\n ) : (\n <TableRow>\n <TableCell colSpan={columns.length} className=\"h-24 text-center\">\n No results.\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n );\n}\n"],"names":["Skeleton","className","props","React","createElement","cn","accordionVariants","initial","height","animate","transition","type","stiffness","damping","exit","duration","ease","DataTable","columns","data","getRowCanExpand","expandeRowClassName","isLoading","skeletonRowCount","expanded","setExpanded","useState","sorting","setSorting","useEffect","table","useReactTable","getCoreRowModel","state","onExpandedChange","onSortingChange","some","col","expandedContent","getPaginationRowModel","getFilteredRowModel","getExpandedRowModel","getSortedRowModel","renderSkeletonRows","useCallback","Array","from","length","map","_","index","TableRow","key","column","colIndex","TableCell","style","width","size","renderTableRows","getRowModel","rows","row","Fragment","original","id","getIsSelected","getIsExpanded","getVisibleCells","cell","getSize","flexRender","columnDef","getContext","AnimatePresence","expandedColumn","find","role","colSpan","motion","div","variants","hasResults","Table","TableHeader","getHeaderGroups","headerGroup","headers","header","TableHead","isPlaceholder","TableBody"],"mappings":"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"}
1
+ {"version":3,"file":"data-table.bjvbLk2u.js","sources":["../../../src/components/data-table/data-table.tsx"],"sourcesContent":["import React, { useCallback } from 'react';\nimport {\n ColumnDef,\n flexRender,\n getCoreRowModel,\n useReactTable,\n ExpandedState,\n SortingState,\n getPaginationRowModel,\n getFilteredRowModel,\n getExpandedRowModel,\n getSortedRowModel,\n Row,\n} from '@tanstack/react-table';\nimport { motion, AnimatePresence } from 'framer-motion';\n\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table';\n\nimport { cn } from '@/lib/utils';\n\n// Skeleton component for loading state\nconst Skeleton = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (\n <div className={cn('animate-original-pulse rounded-md bg-muted', className)} {...props} />\n);\n\n// Framer Motion Accordion Variants for bouncy accordion effect\nconst accordionVariants = {\n initial: {\n height: 0,\n },\n animate: {\n height: 'auto',\n transition: {\n height: {\n type: 'spring',\n stiffness: 450, // increase to make it more stiff (less bouncy), therefore increasing the speed of the animation\n damping: 20, // increase to make it more dampened and slow\n },\n },\n },\n exit: {\n height: 0,\n transition: {\n height: {\n duration: 0.1,\n ease: 'linear',\n },\n },\n },\n};\n\nexport type ExtendedColumnDef<TData, TValue = unknown> = ColumnDef<TData, TValue> & {\n expandedContent?: (row: Row<TData>) => React.ReactNode;\n};\n\nexport interface DataTableProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {\n columns: ExtendedColumnDef<TData, TValue>[];\n data: TData[];\n getRowCanExpand?: (row: Row<TData>) => boolean;\n className?: string;\n expandeRowClassName?: string;\n isLoading?: boolean;\n skeletonRowCount?: number;\n}\n\nexport function DataTable<TData extends Record<string, any> & { id?: string | number }, TValue>({\n columns,\n data,\n getRowCanExpand = () => true,\n className,\n expandeRowClassName,\n isLoading = false,\n skeletonRowCount = 10,\n ...props\n}: DataTableProps<TData, TValue>) {\n const [expanded, setExpanded] = React.useState<ExpandedState>({});\n const [sorting, setSorting] = React.useState<SortingState>([]);\n\n // Reset expanded state when data changes. Collapse all rows when data changes.\n React.useEffect(() => {\n setExpanded({});\n }, [data]);\n\n const table = useReactTable({\n data,\n columns,\n getCoreRowModel: getCoreRowModel(),\n state: {\n expanded,\n sorting,\n },\n onExpandedChange: setExpanded,\n onSortingChange: setSorting,\n getRowCanExpand: columns.some((col) => col.expandedContent) ? getRowCanExpand : undefined,\n getPaginationRowModel: getPaginationRowModel(),\n getFilteredRowModel: getFilteredRowModel(),\n getExpandedRowModel: getExpandedRowModel(),\n getSortedRowModel: getSortedRowModel(),\n });\n\n // Render the skeleton rows for loading state\n const renderSkeletonRows = useCallback(() => {\n return Array.from({ length: skeletonRowCount }).map((_, index) => (\n <TableRow key={`skeleton-${index}`}>\n {columns.map((column, colIndex) => (\n <TableCell\n key={`skeleton-cell-${index}-${colIndex}`}\n className=\"first:pl-4 last:pr-4 py-4\"\n style={{ width: column.size }}\n >\n <Skeleton className=\"h-4 w-full\" />\n </TableCell>\n ))}\n </TableRow>\n ));\n }, [skeletonRowCount, columns]);\n\n // Render the table rows\n const renderTableRows = useCallback(() => {\n return table.getRowModel().rows.map((row) => (\n <React.Fragment key={row.original.id || row.id}>\n <TableRow data-state={row.getIsSelected() && 'selected'} className={cn(row.getIsExpanded() && 'border-b-0')}>\n {row.getVisibleCells().map((cell) => (\n <TableCell key={cell.id} className=\"first:pl-4 last:pr-4 py-4\" style={{ width: cell.column.getSize() }}>\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\n </TableCell>\n ))}\n </TableRow>\n <AnimatePresence initial={false}>\n {row.getIsExpanded() &&\n (() => {\n // Find the first column with expandedContent\n const expandedColumn = columns.find((col) => col.expandedContent);\n\n return expandedColumn?.expandedContent ? (\n <>\n <TableRow aria-hidden role=\"presentation\" className=\"border-none\">\n <TableCell colSpan={row.getVisibleCells().length} className=\"p-0 inset-0 h-[1px] w-full\">\n <div className=\"custom-dashed-border-b absolute inset-0 h-[1px]\" />\n </TableCell>\n </TableRow>\n <TableRow key={`${row.id}-expanded`}>\n <TableCell\n colSpan={row.getVisibleCells().length}\n className={cn('first:pl-4 last:pr-4 py-4 overflow-hidden', expandeRowClassName)}\n >\n <motion.div variants={accordionVariants} initial=\"initial\" animate=\"animate\" exit=\"exit\">\n {expandedColumn.expandedContent(row)}\n </motion.div>\n </TableCell>\n </TableRow>\n </>\n ) : null;\n })()}\n </AnimatePresence>\n </React.Fragment>\n ));\n }, [table, columns, expandeRowClassName]);\n\n const hasResults = table.getRowModel().rows?.length;\n\n return (\n <div className={cn('overflow-hidden rounded-md border border-border bg-card', className)} {...props}>\n <Table className=\"overflow-hidden\">\n <TableHeader>\n {table.getHeaderGroups().map((headerGroup) => (\n <TableRow key={headerGroup.id}>\n {headerGroup.headers.map((header) => (\n <TableHead\n key={header.id}\n className=\"space-x-4 first:pl-4 last:pr-4 py-4 text-current\"\n style={{ width: header.getSize() }}\n >\n {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}\n </TableHead>\n ))}\n </TableRow>\n ))}\n </TableHeader>\n <TableBody>\n {isLoading ? (\n renderSkeletonRows()\n ) : hasResults ? (\n renderTableRows()\n ) : (\n <TableRow>\n <TableCell colSpan={columns.length} className=\"h-24 text-center\">\n No results.\n </TableCell>\n </TableRow>\n )}\n </TableBody>\n </Table>\n </div>\n );\n}\n"],"names":["Skeleton","className","props","React","createElement","cn","accordionVariants","initial","height","animate","transition","type","stiffness","damping","exit","duration","ease","DataTable","columns","data","getRowCanExpand","expandeRowClassName","isLoading","skeletonRowCount","expanded","setExpanded","useState","sorting","setSorting","useEffect","table","useReactTable","getCoreRowModel","state","onExpandedChange","onSortingChange","some","col","expandedContent","getPaginationRowModel","getFilteredRowModel","getExpandedRowModel","getSortedRowModel","renderSkeletonRows","useCallback","Array","from","length","map","_","index","TableRow","key","column","colIndex","TableCell","style","width","size","renderTableRows","getRowModel","rows","row","Fragment","original","id","getIsSelected","getIsExpanded","getVisibleCells","cell","getSize","flexRender","columnDef","getContext","AnimatePresence","expandedColumn","find","role","colSpan","motion","div","variants","hasResults","Table","TableHeader","getHeaderGroups","headerGroup","headers","header","TableHead","isPlaceholder","TableBody"],"mappings":"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"}
@@ -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||[],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__ */
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__ */
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"===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)))),
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)))),
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.DPv-zTEC.js.map
22
+ //# sourceMappingURL=filters.BHp3ukNW.js.map
@@ -1 +1 @@
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
+ {"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,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.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
- return e.createElement(n,{ref:l,value:o.label,onSelect:s,className:"cursor-pointer","aria-label":m,"aria-selected":r},
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
- /* @__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
- 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.BE03shJT.js.map
1
+ import*as e from"react";import{Check as t,SpinnerGap as a}from"@phosphor-icons/react";import{c as o}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:a=500,onError:o}){const[n,r]=e.useState([]),[s,l]=e.useState(!0),c=e.useRef(o);return e.useEffect(()=>{c.current=o},[o]),e.useEffect(()=>{(async()=>{const e=i();if(e&&e.length>0)return r(e.slice(0,a)),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 o=(await e.json()).items.map(e=>({value:e.family,label:e.family,category:e.category,variants:e.variants}));m(o),r(o.slice(0,a))}catch(o){const e=o instanceof Error?o:new Error("Failed to fetch fonts");console.error("FontPicker: Failed to fetch Google Fonts",e),c.current?.(e)}finally{l(!1)}})()},[t,a]),{fonts:n,isLoading:s,loadFont:f}}const g=e.memo(function({font:a,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(a.value)},[c,a.value]);const m=`Select ${a.label} font${a.category?`, ${a.category} category`:""}`;/* @__PURE__ */
2
+ return e.createElement(n,{ref:l,value:a.label,onSelect:s,className:"cursor-pointer","aria-label":m,"aria-selected":r},
3
+ /* @__PURE__ */e.createElement("span",{style:{fontFamily:c?a.value:void 0},className:"flex-1 truncate"},a.label),a.category&&/* @__PURE__ */e.createElement("span",{className:"text-xs text-muted-foreground capitalize"},a.category),
4
+ /* @__PURE__ */e.createElement(t,{className:o("ml-2 h-4 w-4",r?"opacity-100":"opacity-0")}))});function p({value:t,onChange:o,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
+ return e.createElement(r,{options:v,value:t,onChange:o,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(a,{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:a,onSelect:o})=>/* @__PURE__ */e.createElement(g,{font:t,isSelected:a,onSelect:o})})}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.YAE99idL.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"font-picker.YAE99idL.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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"}