@brainfish-ai/components 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/dist/alert-dialog.d.ts +6 -2
  2. package/dist/button.d.ts +4 -2
  3. package/dist/chat-search.d.ts +14 -1
  4. package/dist/confirm-dialog.d.ts +3 -1
  5. package/dist/convos.d.ts +3 -0
  6. package/dist/esm/chunks/ChatSearch.bblH7kYY.js +95 -0
  7. package/dist/esm/chunks/ChatSearch.bblH7kYY.js.map +1 -0
  8. package/dist/esm/chunks/Conversation.CuRp-tJL.js +22 -0
  9. package/dist/esm/chunks/{Conversation.BriXFYqU.js.map → Conversation.CuRp-tJL.js.map} +1 -1
  10. package/dist/esm/chunks/FormattedMessage.XNMN23hm.js +23 -0
  11. package/dist/esm/chunks/FormattedMessage.XNMN23hm.js.map +1 -0
  12. package/dist/esm/chunks/MermaidDiagram.PRgXQ5Yh.js +2 -0
  13. package/dist/esm/chunks/{MermaidDiagram.xQ0CVFOI.js.map → MermaidDiagram.PRgXQ5Yh.js.map} +1 -1
  14. package/dist/esm/chunks/_commonjsHelpers.lGe4XDVY.js +2 -0
  15. package/dist/esm/chunks/_commonjsHelpers.lGe4XDVY.js.map +1 -0
  16. package/dist/esm/chunks/button.D_2SonNs.js +3 -0
  17. package/dist/esm/chunks/button.D_2SonNs.js.map +1 -0
  18. package/dist/esm/chunks/chart.BDL2tf-S.js +10 -0
  19. package/dist/esm/chunks/{chart.4ZbtBMmR.js.map → chart.BDL2tf-S.js.map} +1 -1
  20. package/dist/esm/chunks/chat-logo.CqPppEb9.js +3 -0
  21. package/dist/esm/chunks/chat-logo.CqPppEb9.js.map +1 -0
  22. package/dist/esm/chunks/combobox.MyoPH18G.js +6 -0
  23. package/dist/esm/chunks/{combobox.CJKym3Z1.js.map → combobox.MyoPH18G.js.map} +1 -1
  24. package/dist/esm/chunks/dark.DuW7JuAk.js +2 -0
  25. package/dist/esm/chunks/{dark.Cq2RCgy4.js.map → dark.DuW7JuAk.js.map} +1 -1
  26. package/dist/esm/chunks/data-table.CJOR-1Kf.js +5 -0
  27. package/dist/esm/chunks/{data-table.DbcAYxMY.js.map → data-table.CJOR-1Kf.js.map} +1 -1
  28. package/dist/esm/chunks/date-picker.C2VT_rZ9.js +4 -0
  29. package/dist/esm/chunks/{date-picker._cBTpdEK.js.map → date-picker.C2VT_rZ9.js.map} +1 -1
  30. package/dist/esm/chunks/extends.DPdBf6DS.js +2 -0
  31. package/dist/esm/chunks/extends.DPdBf6DS.js.map +1 -0
  32. package/dist/esm/chunks/feature-flags.DOcVlPHk.js +3 -0
  33. package/dist/esm/chunks/{feature-flags.DeDEcnd1.js.map → feature-flags.DOcVlPHk.js.map} +1 -1
  34. package/dist/esm/chunks/feedback.CLMuSvsg.js +13 -0
  35. package/dist/esm/chunks/{feedback.W2OzN-5r.js.map → feedback.CLMuSvsg.js.map} +1 -1
  36. package/dist/esm/chunks/file-upload-status.D8RhMcbO.js +8 -0
  37. package/dist/esm/chunks/{file-upload-status.DP2iuttI.js.map → file-upload-status.D8RhMcbO.js.map} +1 -1
  38. package/dist/esm/chunks/filters.BHp3ukNW.js +22 -0
  39. package/dist/esm/chunks/{filters.-7vSLEQ2.js.map → filters.BHp3ukNW.js.map} +1 -1
  40. package/dist/esm/chunks/font-picker.B9GPXyK4.js +6 -0
  41. package/dist/esm/chunks/{font-picker.DisEoE8a.js.map → font-picker.B9GPXyK4.js.map} +1 -1
  42. package/dist/esm/chunks/formatDate.D2xEZm8f.js +2 -0
  43. package/dist/esm/chunks/{formatDate.CWN6IFKq.js.map → formatDate.D2xEZm8f.js.map} +1 -1
  44. package/dist/esm/chunks/formatNumber.DhVn228t.js +2 -0
  45. package/dist/esm/chunks/{formatNumber.Bm2k8QrT.js.map → formatNumber.DhVn228t.js.map} +1 -1
  46. package/dist/esm/chunks/generating-star.BN9p_FDu.js +7 -0
  47. package/dist/esm/chunks/generating-star.BN9p_FDu.js.map +1 -0
  48. package/dist/esm/chunks/header-nav.DdOXbPSM.js +10 -0
  49. package/dist/esm/chunks/{header-nav.b4hvOsKc.js.map → header-nav.DdOXbPSM.js.map} +1 -1
  50. package/dist/esm/chunks/header-pane.DrVjpN5S.js +20 -0
  51. package/dist/esm/chunks/{header-pane.BFXHXxVn.js.map → header-pane.DrVjpN5S.js.map} +1 -1
  52. package/dist/esm/chunks/hooks.BQTKhHSv.js +2 -0
  53. package/dist/esm/chunks/hooks.BQTKhHSv.js.map +1 -0
  54. package/dist/esm/chunks/index.uF4ME3WQ.js +4 -0
  55. package/dist/esm/chunks/{index.BqibIWDw.js.map → index.uF4ME3WQ.js.map} +1 -1
  56. package/dist/esm/chunks/input-with-tags.DLv9e0XI.js +5 -0
  57. package/dist/esm/chunks/{input-with-tags.tg2nhPFv.js.map → input-with-tags.DLv9e0XI.js.map} +1 -1
  58. package/dist/esm/chunks/logo.CketsPBx.js +5 -0
  59. package/dist/esm/chunks/{logo.D5BMN6Db.js.map → logo.CketsPBx.js.map} +1 -1
  60. package/dist/esm/chunks/primary.CMQbo1GJ.js +2 -0
  61. package/dist/esm/chunks/{primary.CtiRZbqq.js.map → primary.CMQbo1GJ.js.map} +1 -1
  62. package/dist/esm/chunks/review-list.Cn5bw-lP.js +6 -0
  63. package/dist/esm/chunks/review-list.Cn5bw-lP.js.map +1 -0
  64. package/dist/esm/chunks/sidebar.DsEgGwJU.js +25 -0
  65. package/dist/esm/chunks/sidebar.DsEgGwJU.js.map +1 -0
  66. package/dist/esm/chunks/simpleSelect.DK1qZSXM.js +3 -0
  67. package/dist/esm/chunks/{simpleSelect.B1rktKkt.js.map → simpleSelect.DK1qZSXM.js.map} +1 -1
  68. package/dist/esm/chunks/status-badge.BLB0pWDn.js +3 -0
  69. package/dist/esm/chunks/status-badge.BLB0pWDn.js.map +1 -0
  70. package/dist/esm/chunks/trend-value.BPBDBsk2.js +3 -0
  71. package/dist/esm/chunks/{trend-value.COSukPwk.js.map → trend-value.BPBDBsk2.js.map} +1 -1
  72. package/dist/esm/chunks/two-level-combobox.DJYP--W9.js +8 -0
  73. package/dist/esm/chunks/{two-level-combobox.BXs2z9u5.js.map → two-level-combobox.DJYP--W9.js.map} +1 -1
  74. package/dist/esm/chunks/useChartDateFormatters.Dx2h5AAm.js +2 -0
  75. package/dist/esm/chunks/{useChartDateFormatters.DS9ASgFO.js.map → useChartDateFormatters.Dx2h5AAm.js.map} +1 -1
  76. package/dist/esm/chunks/utils.C6Qu-kwd.js +2 -0
  77. package/dist/esm/chunks/{utils.Cwtlq8dh.js.map → utils.C6Qu-kwd.js.map} +1 -1
  78. package/dist/esm/colors.js +1 -169
  79. package/dist/esm/colors.js.map +1 -1
  80. package/dist/esm/components/article-suggestions-banner.js +4 -53
  81. package/dist/esm/components/article-suggestions-banner.js.map +1 -1
  82. package/dist/esm/components/articles-coverage.js +4 -116
  83. package/dist/esm/components/articles-coverage.js.map +1 -1
  84. package/dist/esm/components/articles-updated.js +4 -74
  85. package/dist/esm/components/articles-updated.js.map +1 -1
  86. package/dist/esm/components/breadcrumbs.js +3 -13
  87. package/dist/esm/components/breadcrumbs.js.map +1 -1
  88. package/dist/esm/components/chart-area-linear.js +6 -66
  89. package/dist/esm/components/chart-area-linear.js.map +1 -1
  90. package/dist/esm/components/chart-radial-stacked.js +2 -48
  91. package/dist/esm/components/chart-radial-stacked.js.map +1 -1
  92. package/dist/esm/components/chat-search.js +1 -1
  93. package/dist/esm/components/combobox.js +1 -1
  94. package/dist/esm/components/confirm-dialog.js +2 -47
  95. package/dist/esm/components/confirm-dialog.js.map +1 -1
  96. package/dist/esm/components/conversation.js +1 -1
  97. package/dist/esm/components/convos.js +27 -607
  98. package/dist/esm/components/convos.js.map +1 -1
  99. package/dist/esm/components/data-table.js +1 -1
  100. package/dist/esm/components/date-picker.js +1 -1
  101. package/dist/esm/components/discoveries-created.js +4 -64
  102. package/dist/esm/components/discoveries-created.js.map +1 -1
  103. package/dist/esm/components/feedback.js +1 -1
  104. package/dist/esm/components/file-upload.js +1 -1
  105. package/dist/esm/components/filter.js +1 -1
  106. package/dist/esm/components/font-picker.js +1 -1
  107. package/dist/esm/components/generating-star.js +1 -1
  108. package/dist/esm/components/input-with-tags.js +1 -1
  109. package/dist/esm/components/logo.js +1 -1
  110. package/dist/esm/components/markdown.js +1 -2
  111. package/dist/esm/components/markdown.js.map +1 -1
  112. package/dist/esm/components/metric-card.js +3 -29
  113. package/dist/esm/components/metric-card.js.map +1 -1
  114. package/dist/esm/components/select.js +1 -1
  115. package/dist/esm/components/trend-value.js +1 -1
  116. package/dist/esm/components/two-level-combobox.js +1 -1
  117. package/dist/esm/components/ui/accordion.js +7 -46
  118. package/dist/esm/components/ui/accordion.js.map +1 -1
  119. package/dist/esm/components/ui/alert-dialog.js +3 -114
  120. package/dist/esm/components/ui/alert-dialog.js.map +1 -1
  121. package/dist/esm/components/ui/alert.js +4 -103
  122. package/dist/esm/components/ui/alert.js.map +1 -1
  123. package/dist/esm/components/ui/avatar.js +7 -89
  124. package/dist/esm/components/ui/avatar.js.map +1 -1
  125. package/dist/esm/components/ui/badge.js +2 -26
  126. package/dist/esm/components/ui/badge.js.map +1 -1
  127. package/dist/esm/components/ui/breadcrumb.js +4 -60
  128. package/dist/esm/components/ui/breadcrumb.js.map +1 -1
  129. package/dist/esm/components/ui/button-group.js +4 -88
  130. package/dist/esm/components/ui/button-group.js.map +1 -1
  131. package/dist/esm/components/ui/button.js +1 -5
  132. package/dist/esm/components/ui/button.js.map +1 -1
  133. package/dist/esm/components/ui/calendar.js +2 -20
  134. package/dist/esm/components/ui/calendar.js.map +1 -1
  135. package/dist/esm/components/ui/card.js +1 -55
  136. package/dist/esm/components/ui/card.js.map +1 -1
  137. package/dist/esm/components/ui/collapsible.js +1 -33
  138. package/dist/esm/components/ui/collapsible.js.map +1 -1
  139. package/dist/esm/components/ui/combobox.js +1 -1
  140. package/dist/esm/components/ui/command.js +2 -79
  141. package/dist/esm/components/ui/command.js.map +1 -1
  142. package/dist/esm/components/ui/dialog.js +4 -60
  143. package/dist/esm/components/ui/dialog.js.map +1 -1
  144. package/dist/esm/components/ui/div-button.js +2 -61
  145. package/dist/esm/components/ui/div-button.js.map +1 -1
  146. package/dist/esm/components/ui/dropdown-menu.js +3 -114
  147. package/dist/esm/components/ui/dropdown-menu.js.map +1 -1
  148. package/dist/esm/components/ui/icon.js +2 -25
  149. package/dist/esm/components/ui/icon.js.map +1 -1
  150. package/dist/esm/components/ui/input.js +4 -47
  151. package/dist/esm/components/ui/input.js.map +1 -1
  152. package/dist/esm/components/ui/item.js +11 -140
  153. package/dist/esm/components/ui/item.js.map +1 -1
  154. package/dist/esm/components/ui/label.js +1 -19
  155. package/dist/esm/components/ui/label.js.map +1 -1
  156. package/dist/esm/components/ui/popover.js +1 -31
  157. package/dist/esm/components/ui/popover.js.map +1 -1
  158. package/dist/esm/components/ui/progress.js +2 -22
  159. package/dist/esm/components/ui/progress.js.map +1 -1
  160. package/dist/esm/components/ui/scroll-area.js +2 -32
  161. package/dist/esm/components/ui/scroll-area.js.map +1 -1
  162. package/dist/esm/components/ui/select.js +5 -66
  163. package/dist/esm/components/ui/select.js.map +1 -1
  164. package/dist/esm/components/ui/separator.js +1 -23
  165. package/dist/esm/components/ui/separator.js.map +1 -1
  166. package/dist/esm/components/ui/sheet.js +3 -62
  167. package/dist/esm/components/ui/sheet.js.map +1 -1
  168. package/dist/esm/components/ui/spinner.js +2 -17
  169. package/dist/esm/components/ui/spinner.js.map +1 -1
  170. package/dist/esm/components/ui/switch.js +2 -26
  171. package/dist/esm/components/ui/switch.js.map +1 -1
  172. package/dist/esm/components/ui/table.js +1 -82
  173. package/dist/esm/components/ui/table.js.map +1 -1
  174. package/dist/esm/components/ui/textarea.js +1 -33
  175. package/dist/esm/components/ui/textarea.js.map +1 -1
  176. package/dist/esm/components/ui/tooltip.js +3 -31
  177. package/dist/esm/components/ui/tooltip.js.map +1 -1
  178. package/dist/esm/global.css +1 -1
  179. package/dist/esm/index.js +1 -53
  180. package/dist/esm/index.js.map +1 -1
  181. package/dist/esm/layouts/full-layout.js +1 -1
  182. package/dist/esm/layouts/header-nav.js +1 -1
  183. package/dist/esm/layouts/sidebar.js +1 -1
  184. package/dist/esm/logos/microsoft-logo.js +6 -66
  185. package/dist/esm/logos/microsoft-logo.js.map +1 -1
  186. package/dist/esm/logos/microsoft-teams-logo.js +12 -112
  187. package/dist/esm/logos/microsoft-teams-logo.js.map +1 -1
  188. package/dist/esm/logos/slack-logo.js +3 -39
  189. package/dist/esm/logos/slack-logo.js.map +1 -1
  190. package/dist/esm/scenes/knowledge-review.js +17 -380
  191. package/dist/esm/scenes/knowledge-review.js.map +1 -1
  192. package/dist/esm/tailwind.preset.js +1 -1526
  193. package/dist/esm/tailwind.preset.js.map +1 -1
  194. package/dist/index.d.ts +32 -5
  195. package/dist/logo.d.ts +9 -0
  196. package/dist/stats.html +47 -46
  197. package/package.json +17 -18
  198. package/tailwind.preset.ts +1 -0
  199. package/dist/esm/chunks/ChatSearch.CeQrTOVx.js +0 -6825
  200. package/dist/esm/chunks/ChatSearch.CeQrTOVx.js.map +0 -1
  201. package/dist/esm/chunks/Conversation.BriXFYqU.js +0 -831
  202. package/dist/esm/chunks/FormattedMessage.CRbM-hF6.js +0 -39715
  203. package/dist/esm/chunks/FormattedMessage.CRbM-hF6.js.map +0 -1
  204. package/dist/esm/chunks/MermaidDiagram.xQ0CVFOI.js +0 -50
  205. package/dist/esm/chunks/_commonjsHelpers.BFTU3MAI.js +0 -8
  206. package/dist/esm/chunks/_commonjsHelpers.BFTU3MAI.js.map +0 -1
  207. package/dist/esm/chunks/button.DQL6gCAt.js +0 -48
  208. package/dist/esm/chunks/button.DQL6gCAt.js.map +0 -1
  209. package/dist/esm/chunks/chart.4ZbtBMmR.js +0 -199
  210. package/dist/esm/chunks/combobox.CJKym3Z1.js +0 -95
  211. package/dist/esm/chunks/dark.Cq2RCgy4.js +0 -18
  212. package/dist/esm/chunks/data-table.DbcAYxMY.js +0 -102
  213. package/dist/esm/chunks/date-picker._cBTpdEK.js +0 -26
  214. package/dist/esm/chunks/extends.mO86zOh3.js +0 -12
  215. package/dist/esm/chunks/extends.mO86zOh3.js.map +0 -1
  216. package/dist/esm/chunks/feature-flags.DeDEcnd1.js +0 -22
  217. package/dist/esm/chunks/feedback.W2OzN-5r.js +0 -214
  218. package/dist/esm/chunks/file-upload-status.DP2iuttI.js +0 -141
  219. package/dist/esm/chunks/filters.-7vSLEQ2.js +0 -565
  220. package/dist/esm/chunks/font-picker.DisEoE8a.js +0 -181
  221. package/dist/esm/chunks/formatDate.CWN6IFKq.js +0 -952
  222. package/dist/esm/chunks/formatNumber.Bm2k8QrT.js +0 -10
  223. package/dist/esm/chunks/generating-star.DMDPNTaM.js +0 -1501
  224. package/dist/esm/chunks/generating-star.DMDPNTaM.js.map +0 -1
  225. package/dist/esm/chunks/header-nav.b4hvOsKc.js +0 -197
  226. package/dist/esm/chunks/header-pane.BFXHXxVn.js +0 -559
  227. package/dist/esm/chunks/hooks.BWVaVAT-.js +0 -343
  228. package/dist/esm/chunks/hooks.BWVaVAT-.js.map +0 -1
  229. package/dist/esm/chunks/index.BqibIWDw.js +0 -137
  230. package/dist/esm/chunks/input-with-tags.tg2nhPFv.js +0 -110
  231. package/dist/esm/chunks/logo.D5BMN6Db.js +0 -191
  232. package/dist/esm/chunks/primary.CtiRZbqq.js +0 -18
  233. package/dist/esm/chunks/review-list.BtSnfpSc.js +0 -117
  234. package/dist/esm/chunks/review-list.BtSnfpSc.js.map +0 -1
  235. package/dist/esm/chunks/sidebar.BamKohb5.js +0 -803
  236. package/dist/esm/chunks/sidebar.BamKohb5.js.map +0 -1
  237. package/dist/esm/chunks/simpleSelect.B1rktKkt.js +0 -23
  238. package/dist/esm/chunks/status-badge.eFJ1PYeb.js +0 -18
  239. package/dist/esm/chunks/status-badge.eFJ1PYeb.js.map +0 -1
  240. package/dist/esm/chunks/trend-value.COSukPwk.js +0 -51
  241. package/dist/esm/chunks/two-level-combobox.BXs2z9u5.js +0 -132
  242. package/dist/esm/chunks/useChartDateFormatters.DS9ASgFO.js +0 -11
  243. package/dist/esm/chunks/utils.Cwtlq8dh.js +0 -45
@@ -1 +1 @@
1
- {"version":3,"file":"file-upload-status.DP2iuttI.js","sources":["../../../src/components/file-upload/utils.tsx","../../../src/components/file-upload/file-upload.tsx","../../../src/components/file-upload/file-upload-status.tsx"],"sourcesContent":["export const formatFileSize = (bytes: number) => {\n if (bytes === 0) return '0 Bytes';\n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n};\n","import React from 'react';\nimport { useDropzone } from 'react-dropzone';\nimport { CloudArrowUp } from '@phosphor-icons/react';\n\nimport type { FileUploadProps } from './types';\nimport { formatFileSize } from './utils';\n\nimport { Card } from '@/components/ui/card';\nimport { cn } from '@/lib/utils';\n\nexport function FileUpload({\n onDropAccepted,\n onDropRejected,\n accept = {\n 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.mp4'],\n 'application/pdf': ['.pdf'],\n 'text/*': ['.txt', '.csv'],\n },\n maxSize = 5 * 1024 * 1024, // 5MB\n maxFiles = 5,\n multiple = true,\n className,\n disabled = false,\n title = 'Drag & drop files here, or click to select',\n description = '',\n supportsMessage = '',\n children,\n}: FileUploadProps) {\n const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({\n onDropAccepted,\n onDropRejected,\n accept,\n maxSize,\n multiple,\n disabled,\n maxFiles,\n });\n\n return (\n <div data-testid=\"file-upload\" className=\"w-full space-y-4\">\n <div className={cn('group', className, 'focus-visible:outline-none')} {...getRootProps()}>\n <Card\n className={cn(\n 'custom-dashed-border shadow-none p-8 text-center cursor-pointer',\n 'group-focus:custom-dashed-border-hover',\n 'group-hover:custom-dashed-border-hover',\n isDragActive && !isDragReject && 'border-primary bg-primary/5',\n isDragReject && 'border-destructive bg-destructive/5',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n >\n <input {...getInputProps()} />\n <div className=\"flex flex-col items-center gap-4\">\n <CloudArrowUp\n className={cn(\n 'size-12',\n isDragActive && !isDragReject && 'text-primary',\n isDragReject && 'text-destructive',\n )}\n />\n <div className=\"space-y-2\">\n <p className=\"text-lg font-medium\">\n {isDragActive ? (isDragReject ? 'Some files will be rejected' : 'Drop files here') : title}\n </p>\n {description && <p className=\"text-sm\">{description}</p>}\n {supportsMessage ? (\n <p className=\"text-sm\">{supportsMessage}</p>\n ) : (\n <p className=\"text-sm\">\n {`Supports: ${Object.values(accept).flat().join(', ')} • Max size: ${formatFileSize(maxSize)}${\n maxFiles > 1 ? ` • Max files: ${maxFiles}` : ''\n }`}\n </p>\n )}\n </div>\n </div>\n </Card>\n </div>\n {children}\n </div>\n );\n}\n","import React, { useState, useEffect, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport { CaretUp, CaretDown, FileVideo, CheckCircle, Circle } from '@phosphor-icons/react';\n\nimport type { UploadState } from './types';\nimport { formatFileSize } from './utils';\n\nimport { Card, CardContent } from '@/components/ui/card';\nimport { Button } from '@/components/ui/button';\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';\nimport { cn } from '@/lib/utils';\n\nexport interface ProcessingStep {\n id: string;\n label: string;\n status: 'completed' | 'current' | 'pending' | 'error';\n}\n\ninterface FileUploadStatusProps {\n uploads: UploadState[];\n className?: string;\n timeRemaining?: string;\n onCancelUpload?: (e: React.MouseEvent<HTMLButtonElement>) => void;\n steps?: ProcessingStep[];\n title?: string;\n}\n\nconst CheckCircleIcon = React.memo<{ className?: string }>(({ className }) => {\n return (\n <div className={cn('relative', className)}>\n <CheckCircle className=\"absolute size-6 text-green-400\" weight=\"fill\" />\n <CheckCircle className=\"absolute size-6 text-black\" weight=\"regular\" />\n </div>\n );\n});\n\nCheckCircleIcon.displayName = 'CheckCircleIcon';\n\nexport const FileUploadStatus: React.FC<FileUploadStatusProps> = ({\n uploads,\n className,\n onCancelUpload,\n steps = [\n { id: 'upload', label: 'Uploading video', status: 'current' },\n { id: 'analyze', label: 'Analyzing content', status: 'pending' },\n { id: 'create', label: 'Creating help articles', status: 'pending' },\n ],\n timeRemaining,\n title = 'Video Processing',\n}) => {\n const [mounted, setMounted] = useState(false);\n const [isOpen, setIsOpen] = useState(true);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n const cancelButtonLabel = useCallback((uploadState: UploadState) => {\n if (uploadState.status === 'success') return 'Uploaded';\n if (uploadState.status === 'error') return 'Retry upload';\n\n return 'Cancel upload';\n }, []);\n\n if (!mounted || uploads.length === 0) return null;\n\n // Ensure we're in a browser environment\n if (typeof document === 'undefined') return null;\n\n const statusCard = (\n <Collapsible\n open={isOpen}\n onOpenChange={setIsOpen}\n className={cn(\n 'fixed bottom-3 right-3 w-96 shadow-lg border z-50 transition-all duration-200 h-auto p-0 gap-4 bg-dark-100 rounded-lg overflow-hidden',\n className,\n )}\n >\n <CollapsibleTrigger asChild>\n <div className=\"flex items-center justify-between py-4 px-6 bg-foreground text-background cursor-pointer\">\n <div className=\"flex flex-col gap-0\">\n <span className=\"font-semibold\">{title}</span>\n {timeRemaining && <span className=\"text-sm text-muted-foreground\">{timeRemaining}</span>}\n </div>\n <Button variant=\"ghost\" size=\"sm\" className=\"size-6 p-0\">\n {isOpen ? <CaretDown className=\"size-4\" /> : <CaretUp className=\"size-4\" />}\n </Button>\n </div>\n </CollapsibleTrigger>\n <CollapsibleContent>\n {uploads.map((upload) => (\n <Card key={upload.id} className={cn('rounded-none p-0')}>\n <CardContent className=\"p-0\">\n {/* File Info */}\n <div className=\"px-6 py-4 border-b\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 bg-yellow-400 border border-dark rounded-lg flex items-center justify-center text-dark shrink-0\">\n <FileVideo className=\"size-6\" />\n </div>\n <div className=\"flex-1 flex flex-col gap-0\">\n <h3 className=\"text-base font-medium flex items-center gap-1 whitespace-nowrap\">\n <span className=\"truncate max-w-56\">{upload.file.name}</span>\n <span className=\"shrink-0\"> • {formatFileSize(upload.file.size)}</span>\n </h3>\n <Button\n variant=\"dark\"\n size=\"sm\"\n className=\"self-start h-6 text-xs bg-foreground text-background\"\n onClick={onCancelUpload}\n data-id={upload.id}\n disabled={upload.status === 'success' || upload.progress === 100}\n data-status={upload.status}\n >\n {cancelButtonLabel(upload)}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Steps */}\n <ol className=\"py-2\">\n {steps.map((step) => (\n <li\n key={step.id}\n className={cn(\n 'flex items-center gap-4 px-6 py-3 relative',\n 'after:absolute after:w-[1px] after:bg-foreground after:top-[38px] after:-bottom-[10px] after:left-[35px]',\n 'last-of-type:after:hidden',\n )}\n >\n {step.status === 'completed' ? (\n <CheckCircleIcon className=\"size-6\" />\n ) : (\n <Circle className=\"size-6\" />\n )}\n <span className=\"flex items-center gap-1\">\n {`${step.label} ${step.id === 'upload' ? `— ${Math.round(upload.progress)}%` : ''}`}\n </span>\n </li>\n ))}\n </ol>\n </CardContent>\n </Card>\n ))}\n </CollapsibleContent>\n </Collapsible>\n );\n\n return createPortal(statusCard, document.body);\n};\n"],"names":["React"],"mappings":"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"}
1
+ {"version":3,"file":"file-upload-status.D8RhMcbO.js","sources":["../../../src/components/file-upload/utils.tsx","../../../src/components/file-upload/file-upload.tsx","../../../src/components/file-upload/file-upload-status.tsx"],"sourcesContent":["export const formatFileSize = (bytes: number) => {\n if (bytes === 0) return '0 Bytes';\n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return Number.parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];\n};\n","import React from 'react';\nimport { useDropzone } from 'react-dropzone';\nimport { CloudArrowUp } from '@phosphor-icons/react';\n\nimport type { FileUploadProps } from './types';\nimport { formatFileSize } from './utils';\n\nimport { Card } from '@/components/ui/card';\nimport { cn } from '@/lib/utils';\n\nexport function FileUpload({\n onDropAccepted,\n onDropRejected,\n accept = {\n 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.mp4'],\n 'application/pdf': ['.pdf'],\n 'text/*': ['.txt', '.csv'],\n },\n maxSize = 5 * 1024 * 1024, // 5MB\n maxFiles = 5,\n multiple = true,\n className,\n disabled = false,\n title = 'Drag & drop files here, or click to select',\n description = '',\n supportsMessage = '',\n children,\n}: FileUploadProps) {\n const { getRootProps, getInputProps, isDragActive, isDragReject } = useDropzone({\n onDropAccepted,\n onDropRejected,\n accept,\n maxSize,\n multiple,\n disabled,\n maxFiles,\n });\n\n return (\n <div data-testid=\"file-upload\" className=\"w-full space-y-4\">\n <div className={cn('group', className, 'focus-visible:outline-none')} {...getRootProps()}>\n <Card\n className={cn(\n 'custom-dashed-border shadow-none p-8 text-center cursor-pointer',\n 'group-focus:custom-dashed-border-hover',\n 'group-hover:custom-dashed-border-hover',\n isDragActive && !isDragReject && 'border-primary bg-primary/5',\n isDragReject && 'border-destructive bg-destructive/5',\n disabled && 'cursor-not-allowed opacity-50',\n )}\n >\n <input {...getInputProps()} />\n <div className=\"flex flex-col items-center gap-4\">\n <CloudArrowUp\n className={cn(\n 'size-12',\n isDragActive && !isDragReject && 'text-primary',\n isDragReject && 'text-destructive',\n )}\n />\n <div className=\"space-y-2\">\n <p className=\"text-lg font-medium\">\n {isDragActive ? (isDragReject ? 'Some files will be rejected' : 'Drop files here') : title}\n </p>\n {description && <p className=\"text-sm\">{description}</p>}\n {supportsMessage ? (\n <p className=\"text-sm\">{supportsMessage}</p>\n ) : (\n <p className=\"text-sm\">\n {`Supports: ${Object.values(accept).flat().join(', ')} • Max size: ${formatFileSize(maxSize)}${\n maxFiles > 1 ? ` • Max files: ${maxFiles}` : ''\n }`}\n </p>\n )}\n </div>\n </div>\n </Card>\n </div>\n {children}\n </div>\n );\n}\n","import React, { useState, useEffect, useCallback } from 'react';\nimport { createPortal } from 'react-dom';\nimport { CaretUp, CaretDown, FileVideo, CheckCircle, Circle } from '@phosphor-icons/react';\n\nimport type { UploadState } from './types';\nimport { formatFileSize } from './utils';\n\nimport { Card, CardContent } from '@/components/ui/card';\nimport { Button } from '@/components/ui/button';\nimport { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';\nimport { cn } from '@/lib/utils';\n\nexport interface ProcessingStep {\n id: string;\n label: string;\n status: 'completed' | 'current' | 'pending' | 'error';\n}\n\ninterface FileUploadStatusProps {\n uploads: UploadState[];\n className?: string;\n timeRemaining?: string;\n onCancelUpload?: (e: React.MouseEvent<HTMLButtonElement>) => void;\n steps?: ProcessingStep[];\n title?: string;\n}\n\nconst CheckCircleIcon = React.memo<{ className?: string }>(({ className }) => {\n return (\n <div className={cn('relative', className)}>\n <CheckCircle className=\"absolute size-6 text-green-400\" weight=\"fill\" />\n <CheckCircle className=\"absolute size-6 text-black\" weight=\"regular\" />\n </div>\n );\n});\n\nCheckCircleIcon.displayName = 'CheckCircleIcon';\n\nexport const FileUploadStatus: React.FC<FileUploadStatusProps> = ({\n uploads,\n className,\n onCancelUpload,\n steps = [\n { id: 'upload', label: 'Uploading video', status: 'current' },\n { id: 'analyze', label: 'Analyzing content', status: 'pending' },\n { id: 'create', label: 'Creating help articles', status: 'pending' },\n ],\n timeRemaining,\n title = 'Video Processing',\n}) => {\n const [mounted, setMounted] = useState(false);\n const [isOpen, setIsOpen] = useState(true);\n\n useEffect(() => {\n setMounted(true);\n }, []);\n\n const cancelButtonLabel = useCallback((uploadState: UploadState) => {\n if (uploadState.status === 'success') return 'Uploaded';\n if (uploadState.status === 'error') return 'Retry upload';\n\n return 'Cancel upload';\n }, []);\n\n if (!mounted || uploads.length === 0) return null;\n\n // Ensure we're in a browser environment\n if (typeof document === 'undefined') return null;\n\n const statusCard = (\n <Collapsible\n open={isOpen}\n onOpenChange={setIsOpen}\n className={cn(\n 'fixed bottom-3 right-3 w-96 shadow-lg border z-50 transition-all duration-200 h-auto p-0 gap-4 bg-dark-100 rounded-lg overflow-hidden',\n className,\n )}\n >\n <CollapsibleTrigger asChild>\n <div className=\"flex items-center justify-between py-4 px-6 bg-foreground text-background cursor-pointer\">\n <div className=\"flex flex-col gap-0\">\n <span className=\"font-semibold\">{title}</span>\n {timeRemaining && <span className=\"text-sm text-muted-foreground\">{timeRemaining}</span>}\n </div>\n <Button variant=\"ghost\" size=\"sm\" className=\"size-6 p-0\">\n {isOpen ? <CaretDown className=\"size-4\" /> : <CaretUp className=\"size-4\" />}\n </Button>\n </div>\n </CollapsibleTrigger>\n <CollapsibleContent>\n {uploads.map((upload) => (\n <Card key={upload.id} className={cn('rounded-none p-0')}>\n <CardContent className=\"p-0\">\n {/* File Info */}\n <div className=\"px-6 py-4 border-b\">\n <div className=\"flex items-center gap-4\">\n <div className=\"size-12 bg-yellow-400 border border-dark rounded-lg flex items-center justify-center text-dark shrink-0\">\n <FileVideo className=\"size-6\" />\n </div>\n <div className=\"flex-1 flex flex-col gap-0\">\n <h3 className=\"text-base font-medium flex items-center gap-1 whitespace-nowrap\">\n <span className=\"truncate max-w-56\">{upload.file.name}</span>\n <span className=\"shrink-0\"> • {formatFileSize(upload.file.size)}</span>\n </h3>\n <Button\n variant=\"dark\"\n size=\"sm\"\n className=\"self-start h-6 text-xs bg-foreground text-background\"\n onClick={onCancelUpload}\n data-id={upload.id}\n disabled={upload.status === 'success' || upload.progress === 100}\n data-status={upload.status}\n >\n {cancelButtonLabel(upload)}\n </Button>\n </div>\n </div>\n </div>\n\n {/* Steps */}\n <ol className=\"py-2\">\n {steps.map((step) => (\n <li\n key={step.id}\n className={cn(\n 'flex items-center gap-4 px-6 py-3 relative',\n 'after:absolute after:w-[1px] after:bg-foreground after:top-[38px] after:-bottom-[10px] after:left-[35px]',\n 'last-of-type:after:hidden',\n )}\n >\n {step.status === 'completed' ? (\n <CheckCircleIcon className=\"size-6\" />\n ) : (\n <Circle className=\"size-6\" />\n )}\n <span className=\"flex items-center gap-1\">\n {`${step.label} ${step.id === 'upload' ? `— ${Math.round(upload.progress)}%` : ''}`}\n </span>\n </li>\n ))}\n </ol>\n </CardContent>\n </Card>\n ))}\n </CollapsibleContent>\n </Collapsible>\n );\n\n return createPortal(statusCard, document.body);\n};\n"],"names":["formatFileSize","bytes","i","Math","floor","log","Number","parseFloat","pow","toFixed","FileUpload","onDropAccepted","onDropRejected","accept","maxSize","maxFiles","multiple","className","disabled","title","description","supportsMessage","children","getRootProps","getInputProps","isDragActive","isDragReject","useDropzone","React","createElement","cn","Card","CloudArrowUp","Object","values","flat","join","CheckCircleIcon","memo","CheckCircle","weight","displayName","FileUploadStatus","uploads","onCancelUpload","steps","id","label","status","timeRemaining","mounted","setMounted","useState","isOpen","setIsOpen","useEffect","cancelButtonLabel","useCallback","uploadState","length","document","statusCard","Collapsible","open","onOpenChange","CollapsibleTrigger","asChild","Button","variant","size","CaretDown","CaretUp","CollapsibleContent","map","upload","key","CardContent","FileVideo","file","name","onClick","progress","step","Circle","round","createPortal","body"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import e,{useRef as t,useState as l,forwardRef as r,useImperativeHandle as a,useEffect as n}from"react";import{Check as o,XCircle as s,Plus as c}from"@phosphor-icons/react";import{createId as u}from"@paralleldrive/cuid2";import{B as i}from"./button.D_2SonNs.js";import{Input as m}from"../components/ui/input.js";import{ButtonGroup as d}from"../components/ui/button-group.js";import{DropdownMenu as p,DropdownMenuTrigger as f,DropdownMenuContent as v,DropdownMenuItem as b}from"../components/ui/dropdown-menu.js";import{Popover as g,PopoverTrigger as h,PopoverContent as E}from"../components/ui/popover.js";import{c as y}from"./utils.C6Qu-kwd.js";var N=/* @__PURE__ */(e=>(e.IS="is",e.IS_NOT="is not",e.CONTAINS="contains",e.DOES_NOT_CONTAIN="does not contain",e.STARTS_WITH="starts with",e))(N||{});const S=({inputValue:l,setInputValue:r,customValuePlaceholder:a,customValueButtonLabel:n,onSubmit:o,handleKeyDown:s})=>{const c=t(null);/* @__PURE__ */
2
+ return e.createElement("form",{className:"p-3",onSubmit:e=>{e.preventDefault(),o()}},
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
+ /* @__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
+ return e.createElement("div",{className:"py-1",role:"listbox","aria-multiselectable":"true"},/* @__PURE__ */e.createElement("div",{"aria-live":"polite","aria-atomic":"true",className:"sr-only"},m),a.map((t,l)=>/* @__PURE__ */e.createElement("button",{key:t.id,ref:e=>p.current[l]=e,type:"button",className:"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",onClick:()=>v(t.id),onKeyDown:e=>E(e,l),role:"option","aria-selected":g(t.id)},g(t.id)&&/* @__PURE__ */e.createElement(o,{size:14,className:"mr-2"}),!g(t.id)&&/* @__PURE__ */e.createElement("span",{className:"mr-2 w-3.5"}),t.label)),/* @__PURE__ */e.createElement("div",{className:"border-t border-border mt-1 pt-2 px-2 pb-2"},/* @__PURE__ */e.createElement(i,{ref:f,onClick:b,size:"sm",className:"w-full",disabled:0===c.length,onKeyDown:e=>E(e,a.length)},"Apply (",c.length," selected)")))},x=({valueOptions:l,isValueSelected:r,onValueSelect:a})=>{const n=t([]),s=e=>{n.current[e]?.focus()};/* @__PURE__ */
6
+ return e.createElement("div",{className:"py-1",role:"listbox"},l.map((t,c)=>/* @__PURE__ */e.createElement("button",{key:t.id,ref:e=>n.current[c]=e,type:"button",className:"relative flex w-full text-left cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",onClick:()=>a(t.id),onKeyDown:e=>((e,t)=>{switch(e.key){case"ArrowDown":{e.preventDefault();const r=Math.min(t+1,l.length-1);s(r);break}case"ArrowUp":{e.preventDefault();const l=Math.max(t-1,0);s(l);break}case"Enter":case"Spacebar":e.preventDefault(),a(l[t].id)}})(e,c)},r(t.id)&&/* @__PURE__ */e.createElement(o,{size:14,className:"mr-2"}),!r(t.id)&&/* @__PURE__ */e.createElement("span",{className:"mr-2 w-3.5"}),t.label)))},C=r(({filter:r,sourceOptions:c,rules:u,onSourceChange:m,onRemoveFilter:y,onFilterChange:C,selectPlaceholder:D="Select a value",customValuePlaceholder:k="Enter custom value",customValueButtonLabel:O="Done",sourceFirstMessage:V="Please select a source first"},A)=>{const[F,T]=l({id:r.id,source:r.source,operator:r.operator||N.IS,value:r.value}),[z,I]=l(""),[K,j]=l(null),_=t(null),B=t(null),M=t(null);a(A,()=>({focus:()=>{B.current?.focus()}}));const P=F.source?u[F.source]:void 0,R=P?.operators||[],L=P?.multiSelect||!1;let $=P?.inputType||"dropdown";F.operator!==N.CONTAINS&&F.operator!==N.DOES_NOT_CONTAIN||($="custom");const q=P?.getValueOptions?P.getValueOptions():[],{getSelectedValues:U,isValueSelected:H,getDisplayValue:W}=((e,t,l)=>{const r=()=>Array.isArray(e.value)?e.value:e.value?[e.value]:[];return{getSelectedValues:r,isValueSelected:e=>r().includes(e),getDisplayValue:()=>{const e=r();if(0===e.length)return l;if(1===e.length){const l=t.find(t=>t.id===e[0]);return l?.label||e[0]}return`${e.length} selected`}}})(F,q,D);n(()=>{if("value"===K&&"custom"===$){const e=setTimeout(()=>_.current?.focus(),50);return()=>clearTimeout(e)}},[K,$]),n(()=>{T({id:r.id,source:r.source,operator:r.operator||N.IS,value:r.value})},[r]);const G=e=>{const t=Array.isArray(e.value)?e.value.length>0:!!e.value?.trim();e.source&&e.operator&&t&&C(e)},J=(e,t)=>{const l={...F,[e]:t};if("source"===e){const e=u[t];l.operator=e?.operators[0]?.id,l.value=e?.multiSelect?[]:""}if("operator"===e&&F.source){const e=u[F.source];"dropdown"===e?.inputType&&(l.value=e?.multiSelect?[]:"")}T(l),G(l),L&&"value"===e||j(null)};/* @__PURE__ */
7
+ return e.createElement(d,{ref:M,orientation:"horizontal",className:"rounded-full border border-border divide-x-2 divide-dark-500"},
8
+ /* @__PURE__ */e.createElement(p,{open:"source"===K,onOpenChange:e=>j(e?"source":null),modal:!1},
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)),
10
+ /* @__PURE__ */e.createElement(v,{align:"start",className:"w-48"},c.map(t=>/* @__PURE__ */e.createElement(b,{key:t.id,className:"flex items-center",onSelect:()=>{J("source",t.id),m?.(t.id)}},t.id===F.source&&/* @__PURE__ */e.createElement(o,{size:14,className:"mr-2"}),t.label)))),
11
+ /* @__PURE__ */e.createElement(p,{open:"operator"===K,onOpenChange:e=>j(e?"operator":null),modal:!1},
12
+ /* @__PURE__ */e.createElement(f,{asChild:!0},/* @__PURE__ */e.createElement(i,{variant:"ghost",size:"xs"},R.find(e=>e.id===F.operator)?.label||D)),
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
+ /* @__PURE__ */e.createElement(g,{open:"value"===K,onOpenChange:e=>j(e?"value":null)},
15
+ /* @__PURE__ */e.createElement(h,{asChild:!0},/* @__PURE__ */e.createElement(i,{variant:"ghost",size:"xs",className:"font-bold"},W())),
16
+ /* @__PURE__ */e.createElement(E,{align:"start",className:"w-64 max-h-[min(320px,50vh)] overflow-y-auto p-0"},"custom"===$&&/* @__PURE__ */e.createElement("div",{className:"p-3"},/* @__PURE__ */e.createElement(S,{inputValue:z,setInputValue:I,customValuePlaceholder:k,customValueButtonLabel:O,onSubmit:()=>{if(z.trim()){const e={...F,value:z.trim()};T(e),I(""),j(null),e.source&&e.operator&&e.value&&C(e)}},handleKeyDown:(e,t)=>{if("Escape"!==e.key){if("Tab"===e.key)if(t&&!e.shiftKey){e.preventDefault();const t=document.querySelector("[data-custom-submit-button]");t?.focus()}else t||(e.preventDefault(),_.current?.focus())}else j(null)}})),"dropdown"===$&&/* @__PURE__ */e.createElement(e.Fragment,null,F.source?/* @__PURE__ */e.createElement(e.Fragment,null,L?/* @__PURE__ */e.createElement(w,{internalFilter:F,valueOptions:q,onConfirm:e=>{const t={...F,value:e};T(t);t.source&&t.operator&&e.length>0&&C(t),j(null)},onClose:()=>j(null)}):/* @__PURE__ */e.createElement(x,{valueOptions:q,isValueSelected:H,onValueSelect:e=>{const t=U();let l;l=L?t.includes(e)?t.filter(t=>t!==e):[...t,e]:[e];const r={...F,value:L?l:l[0]||""};T(r),G(r),L||j(null)}})):/* @__PURE__ */e.createElement("div",{className:"p-3 text-sm text-gray-500"},V)))),
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
+ /* @__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
+ 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
+ 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
+ /* @__PURE__ */e.createElement(c,{size:16}),"Add filter"))};export{C as F,N as O,D as a};
22
+ //# sourceMappingURL=filters.BHp3ukNW.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"filters.-7vSLEQ2.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","React"],"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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"}
@@ -0,0 +1,6 @@
1
+ import*as e from"react";import{Check as t,SpinnerGap as o}from"@phosphor-icons/react";import{c as a}from"./utils.C6Qu-kwd.js";import{CommandItem as n}from"../components/ui/command.js";import{C as r}from"./combobox.MyoPH18G.js";const s="brainfish-google-fonts-cache",l="brainfish-google-fonts-cache-timestamp",c=/* @__PURE__ */new Set,i=()=>{try{const e=localStorage.getItem(l);if(!e)return null;if(Date.now()-parseInt(e,10)>864e5)return localStorage.removeItem(s),localStorage.removeItem(l),null;const t=localStorage.getItem(s);return t?JSON.parse(t):null}catch{return null}},m=e=>{try{localStorage.setItem(s,JSON.stringify(e)),localStorage.setItem(l,Date.now().toString())}catch{}},f=e=>{if(c.has(e))return;const t=document.createElement("link");t.href=`https://fonts.googleapis.com/css2?family=${e.replace(/ /g,"+")}&display=swap`,t.rel="stylesheet",document.head.appendChild(t),c.add(e)},u=()=>{try{localStorage.removeItem(s),localStorage.removeItem(l)}catch{}};function d({apiKey:t,maxFonts:o=500,onError:a}){const[n,r]=e.useState([]),[s,l]=e.useState(!0),c=e.useRef(a);return e.useEffect(()=>{c.current=a},[a]),e.useEffect(()=>{(async()=>{const e=i();if(e&&e.length>0)return r(e.slice(0,o)),void l(!1);try{const e=await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${t}&sort=popularity`);if(!e.ok)throw new Error(`Failed to fetch fonts: ${e.statusText}`);const a=(await e.json()).items.map(e=>({value:e.family,label:e.family,category:e.category,variants:e.variants}));m(a),r(a.slice(0,o))}catch(a){const e=a instanceof Error?a:new Error("Failed to fetch fonts");console.error("FontPicker: Failed to fetch Google Fonts",e),c.current?.(e)}finally{l(!1)}})()},[t,o]),{fonts:n,isLoading:s,loadFont:f}}const g=e.memo(function({font:o,isSelected:r,onSelect:s}){const l=e.useRef(null),[c,i]=e.useState(!1);e.useEffect(()=>{const e=new IntersectionObserver(([t])=>{t.isIntersecting&&(i(!0),e.disconnect())},{threshold:.1});return l.current&&e.observe(l.current),()=>e.disconnect()},[]),e.useEffect(()=>{c&&f(o.value)},[c,o.value]);const m=`Select ${o.label} font${o.category?`, ${o.category} category`:""}`;/* @__PURE__ */
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.B9GPXyK4.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"font-picker.DisEoE8a.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":["FontPickerItem"],"mappings":"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"}
1
+ {"version":3,"file":"font-picker.B9GPXyK4.js","sources":["../../../src/components/font-picker/font-utils.ts","../../../src/components/font-picker/use-font-options.ts","../../../src/components/font-picker/font-picker-item.tsx","../../../src/components/font-picker/font-picker.tsx"],"sourcesContent":["import { FontOption } from './types';\n\n// Cache keys for localStorage\nconst FONT_CACHE_KEY = 'brainfish-google-fonts-cache';\nconst FONT_CACHE_TIMESTAMP_KEY = 'brainfish-google-fonts-cache-timestamp';\nconst FONT_CACHE_DURATION_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n// Cache for loaded font stylesheets to prevent duplicate loads\nconst loadedFonts = new Set<string>();\n\n/**\n * Get cached fonts from localStorage\n */\nexport const getCachedFonts = (): FontOption[] | null => {\n try {\n const timestamp = localStorage.getItem(FONT_CACHE_TIMESTAMP_KEY);\n if (!timestamp) return null;\n\n const cacheAge = Date.now() - parseInt(timestamp, 10);\n if (cacheAge > FONT_CACHE_DURATION_MS) {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n\n return null;\n }\n\n const cached = localStorage.getItem(FONT_CACHE_KEY);\n if (!cached) return null;\n\n return JSON.parse(cached) as FontOption[];\n } catch {\n return null;\n }\n};\n\n/**\n * Save fonts to localStorage cache\n */\nexport const setCachedFonts = (fonts: FontOption[]): void => {\n try {\n localStorage.setItem(FONT_CACHE_KEY, JSON.stringify(fonts));\n localStorage.setItem(FONT_CACHE_TIMESTAMP_KEY, Date.now().toString());\n } catch {\n // localStorage might be full or unavailable, ignore\n }\n};\n\n/**\n * Load a Google Font stylesheet dynamically\n */\nexport const loadFont = (fontFamily: string): void => {\n if (loadedFonts.has(fontFamily)) return;\n\n const link = document.createElement('link');\n link.href = `https://fonts.googleapis.com/css2?family=${fontFamily.replace(/ /g, '+')}&display=swap`;\n link.rel = 'stylesheet';\n document.head.appendChild(link);\n loadedFonts.add(fontFamily);\n};\n\n/**\n * Clear the font cache from localStorage\n * Useful for forcing a fresh fetch of fonts\n */\nexport const clearFontCache = (): void => {\n try {\n localStorage.removeItem(FONT_CACHE_KEY);\n localStorage.removeItem(FONT_CACHE_TIMESTAMP_KEY);\n } catch {\n // localStorage might be unavailable, ignore\n }\n};\n","import * as React from 'react';\n\nimport { FontOption } from './types';\nimport { getCachedFonts, setCachedFonts, loadFont } from './font-utils';\n\nexport interface UseFontOptionsProps {\n apiKey: string;\n maxFonts?: number;\n onError?: (error: Error) => void;\n}\n\nexport interface UseFontOptionsReturn {\n fonts: FontOption[];\n isLoading: boolean;\n loadFont: typeof loadFont;\n}\n\n/**\n * Hook to fetch and manage Google Fonts options\n * Handles caching, loading state, and font stylesheet loading\n */\nexport function useFontOptions({ apiKey, maxFonts = 500, onError }: UseFontOptionsProps): UseFontOptionsReturn {\n const [fonts, setFonts] = React.useState<FontOption[]>([]);\n const [isLoading, setIsLoading] = React.useState(true);\n\n // Stabilize onError callback reference to prevent effect re-runs when\n // consumers pass inline functions (which create new references on every render)\n const onErrorRef = React.useRef(onError);\n React.useEffect(() => {\n onErrorRef.current = onError;\n }, [onError]);\n\n React.useEffect(() => {\n const fetchFonts = async () => {\n // Check cache first\n const cachedFonts = getCachedFonts();\n if (cachedFonts && cachedFonts.length > 0) {\n setFonts(cachedFonts.slice(0, maxFonts));\n setIsLoading(false);\n\n return;\n }\n\n try {\n const response = await fetch(`https://www.googleapis.com/webfonts/v1/webfonts?key=${apiKey}&sort=popularity`);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch fonts: ${response.statusText}`);\n }\n\n const data = await response.json();\n\n const fontOptions: FontOption[] = data.items.map((fontItem: { family: string; category: string; variants: string[] }) => ({\n value: fontItem.family,\n label: fontItem.family,\n category: fontItem.category,\n variants: fontItem.variants,\n }));\n\n // Cache all fonts, but only display up to maxFonts\n setCachedFonts(fontOptions);\n setFonts(fontOptions.slice(0, maxFonts));\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch fonts');\n\n console.error('FontPicker: Failed to fetch Google Fonts', error);\n onErrorRef.current?.(error);\n } finally {\n setIsLoading(false);\n }\n };\n\n void fetchFonts();\n }, [apiKey, maxFonts]);\n\n return { fonts, isLoading, loadFont };\n}\n","import * as React from 'react';\nimport { Check } from '@phosphor-icons/react';\n\nimport { FontOption } from './types';\nimport { loadFont } from './font-utils';\n\nimport { cn } from '@/lib/utils';\nimport { CommandItem } from '@/components/ui/command';\n\nexport interface FontPickerItemProps {\n font: FontOption;\n isSelected: boolean;\n onSelect: () => void;\n}\n\n/**\n * Individual font item in the dropdown list\n * Loads font on mount for preview using Intersection Observer\n * Memoized to prevent unnecessary re-renders\n */\nexport const FontPickerItem = React.memo(function FontPickerItem({ font, isSelected, onSelect }: FontPickerItemProps) {\n const itemRef = React.useRef<HTMLDivElement>(null);\n const [isVisible, setIsVisible] = React.useState(false);\n\n // Use Intersection Observer to lazy-load fonts\n React.useEffect(() => {\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true);\n observer.disconnect();\n }\n },\n { threshold: 0.1 },\n );\n\n if (itemRef.current) {\n observer.observe(itemRef.current);\n }\n\n return () => observer.disconnect();\n }, []);\n\n // Load font when visible\n React.useEffect(() => {\n if (isVisible) {\n loadFont(font.value);\n }\n }, [isVisible, font.value]);\n\n // Build accessible label\n const ariaLabel = `Select ${font.label} font${font.category ? `, ${font.category} category` : ''}`;\n\n return (\n <CommandItem\n ref={itemRef}\n value={font.label}\n onSelect={onSelect}\n className=\"cursor-pointer\"\n aria-label={ariaLabel}\n aria-selected={isSelected}\n >\n <span style={{ fontFamily: isVisible ? font.value : undefined }} className=\"flex-1 truncate\">\n {font.label}\n </span>\n {font.category && <span className=\"text-xs text-muted-foreground capitalize\">{font.category}</span>}\n <Check className={cn('ml-2 h-4 w-4', isSelected ? 'opacity-100' : 'opacity-0')} />\n </CommandItem>\n );\n});\n","import * as React from 'react';\nimport { SpinnerGap } from '@phosphor-icons/react';\n\nimport { useFontOptions } from './use-font-options';\nimport { FontPickerItem } from './font-picker-item';\n\nimport { Combobox } from '@/components/combobox/combobox';\n\nexport interface FontPickerProps {\n /** Currently selected font family */\n value?: string;\n /** Callback when font selection changes */\n onChange?: (fontFamily: string) => void;\n /** Google Fonts API key (required) */\n apiKey: string;\n /** Placeholder text when no font is selected */\n placeholder?: string;\n /** Text shown in search input */\n searchPlaceholder?: string;\n /** Text shown when no fonts match the search */\n noResultsText?: string;\n /** Text shown while fonts are loading */\n loadingText?: string;\n /** Whether the picker is disabled */\n disabled?: boolean;\n /** Additional CSS classes for the trigger button */\n className?: string;\n /** Width of the dropdown (defaults to trigger width) */\n dropdownWidth?: number;\n /** Maximum number of fonts to display (for performance) */\n maxFonts?: number;\n /** Callback when an error occurs fetching fonts */\n onError?: (error: Error) => void;\n}\n\n/**\n * FontPicker - A searchable dropdown for selecting Google Fonts\n *\n * @example\n * ```tsx\n * const [font, setFont] = useState('Inter');\n *\n * <FontPicker\n * apiKey=\"YOUR_API_KEY\"\n * value={font}\n * onChange={setFont}\n * placeholder=\"Select a font\"\n * />\n * ```\n */\nexport function FontPicker({\n value,\n onChange,\n apiKey,\n placeholder = 'Select a font...',\n searchPlaceholder = 'Search fonts...',\n noResultsText = 'No fonts found.',\n loadingText = 'Loading fonts...',\n disabled = false,\n className,\n dropdownWidth,\n maxFonts = 500,\n onError,\n}: FontPickerProps) {\n const { fonts, isLoading, loadFont } = useFontOptions({ apiKey, maxFonts, onError });\n\n // Load font stylesheet when selected\n React.useEffect(() => {\n if (value) {\n loadFont(value);\n }\n }, [value, loadFont]);\n\n // fonts already have value/label which Combobox requires\n const options = fonts;\n\n return (\n <Combobox\n options={options}\n value={value}\n onChange={onChange}\n defaultValueLabel={placeholder}\n placeholder={searchPlaceholder}\n noResultsLabel={noResultsText}\n disabled={disabled || isLoading}\n className={className}\n dropdownWidth={dropdownWidth}\n renderTrigger={({ selectedItem }) => {\n if (isLoading) {\n return (\n <span className=\"flex items-center gap-2 text-muted-foreground\">\n <SpinnerGap className=\"h-4 w-4 animate-spin\" />\n {loadingText}\n </span>\n );\n }\n\n if (selectedItem) {\n return <span style={{ fontFamily: selectedItem.value }}>{selectedItem.label}</span>;\n }\n\n return <span className=\"text-muted-foreground\">{placeholder}</span>;\n }}\n renderItem={({ item, isSelected, onSelect }) => (\n <FontPickerItem font={item} isSelected={isSelected} onSelect={onSelect} />\n )}\n />\n );\n}\n\nexport default FontPicker;\n"],"names":["FONT_CACHE_KEY","FONT_CACHE_TIMESTAMP_KEY","loadedFonts","Set","getCachedFonts","timestamp","localStorage","getItem","Date","now","parseInt","removeItem","cached","JSON","parse","setCachedFonts","fonts","setItem","stringify","toString","loadFont","fontFamily","has","link","document","createElement","href","replace","rel","head","appendChild","add","clearFontCache","useFontOptions","apiKey","maxFonts","onError","setFonts","React","useState","isLoading","setIsLoading","onErrorRef","useRef","useEffect","current","async","cachedFonts","length","slice","response","fetch","ok","Error","statusText","fontOptions","json","items","map","fontItem","value","family","label","category","variants","err","error","console","fetchFonts","FontPickerItem","memo","font","isSelected","onSelect","itemRef","isVisible","setIsVisible","observer","IntersectionObserver","entry","isIntersecting","disconnect","threshold","observe","ariaLabel","CommandItem","ref","className","style","Check","cn","FontPicker","onChange","placeholder","searchPlaceholder","noResultsText","loadingText","disabled","dropdownWidth","options","Combobox","defaultValueLabel","noResultsLabel","renderTrigger","selectedItem","SpinnerGap","renderItem","item"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const t=86400,n=31556952,e=2629746,o=Symbol.for("constructDateFrom");function r(t,n){return"function"==typeof t?t(n):t&&"object"==typeof t&&o in t?t[o](n):t instanceof Date?new t.constructor(n):new Date(n)}function u(t,...n){const e=r.bind(null,t||n.find(t=>"object"==typeof t));return n.map(e)}function a(t,n){return r(t,t)}function c(t){const n=a(t),e=new Date(Date.UTC(n.getFullYear(),n.getMonth(),n.getDate(),n.getHours(),n.getMinutes(),n.getSeconds(),n.getMilliseconds()));return e.setUTCFullYear(n.getFullYear()),+t-+e}function i(t,n){const e=a(t);return e.setHours(0,0,0,0),e}function s(t,n,e){const[o,r]=u(e?.in,t,n),a=i(o),s=i(r),f=+a-c(a),l=+s-c(s);return Math.round((f-l)/864e5)}function f(t,n,e){const[o,r]=u(e?.in,t,n);return 12*(o.getFullYear()-r.getFullYear())+(o.getMonth()-r.getMonth())}function l(t,n){const e=a(t);return Math.trunc(e.getMonth()/3)+1}function g(t,n,e){const[o,r]=u(e?.in,t,n);return 4*(o.getFullYear()-r.getFullYear())+(l(o)-l(r))}let h={};function M(){return h}function d(t,n){const e=M(),o=e.weekStartsOn??e.locale?.options?.weekStartsOn??0,r=a(t),u=r.getDay(),c=(u<o?7:0)+u-o;return r.setDate(r.getDate()-c),r.setHours(0,0,0,0),r}function m(t,n,e){const[o,r]=u(e?.in,t,n),a=d(o),i=d(r),s=+a-c(a),f=+i-c(i);return Math.round((s-f)/6048e5)}function y(t,n,e){const[o,r]=u(e?.in,t,n);return o.getFullYear()-r.getFullYear()}function D(t){return n=>{const e=(t?Math[t]:Math.trunc)(n);return 0===e?0:e}}function b(t,n,e){const[o,r]=u(e?.in,t,n),a=(+o-+r)/36e5;return D(e?.roundingMethod)(a)}function F(t,n){return+a(t)-+a(n)}function S(t,n,e){const o=F(t,n)/6e4;return D(e?.roundingMethod)(o)}function p(t,n,e){const o=F(t,n)/1e3;return D(e?.roundingMethod)(o)}function w(t){return t||(t="undefined"!=typeof navigator?navigator.language:"en-US"),t}function Y(o,r){const a=w(r);return function(o,r,a){let c,i=0;const[l,h]=u(a?.in,o,r);if(a?.unit)c=a?.unit,"second"===c?i=p(l,h):"minute"===c?i=S(l,h):"hour"===c?i=b(l,h):"day"===c?i=s(l,h):"week"===c?i=m(l,h):"month"===c?i=f(l,h):"quarter"===c?i=g(l,h):"year"===c&&(i=y(l,h));else{const o=p(l,h);Math.abs(o)<60?(i=p(l,h),c="second"):Math.abs(o)<3600?(i=S(l,h),c="minute"):Math.abs(o)<t&&Math.abs(s(l,h))<1?(i=b(l,h),c="hour"):Math.abs(o)<604800&&(i=s(l,h))&&Math.abs(i)<7?c="day":Math.abs(o)<e?(i=m(l,h),c="week"):Math.abs(o)<7889238?(i=f(l,h),c="month"):Math.abs(o)<n&&g(l,h)<4?(i=g(l,h),c="quarter"):(i=y(l,h),c="year")}return new Intl.RelativeTimeFormat(a?.locale,{numeric:"auto",...a}).format(i,c)}(o,/* @__PURE__ */new Date,{locale:a,style:"short"})}function U(t,n){const e=w(n);return t.toLocaleDateString(e,{month:"short",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit",hour12:!0})}function k(t,n="en-US"){return"string"==typeof t?t:t instanceof Date?t.toLocaleDateString(n,{month:"short",day:"numeric"}):""}function v(t,n="en-US"){return"string"==typeof t?t:t instanceof Date?t.toLocaleDateString(n,{month:"long",day:"numeric",year:"numeric"}):""}export{v as a,Y as b,U as c,k as f};
2
+ //# sourceMappingURL=formatDate.D2xEZm8f.js.map