@flanksource/clicky-ui 0.2.19 → 0.2.21

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 (423) hide show
  1. package/dist/ai.cjs +26 -0
  2. package/dist/ai.cjs.map +1 -0
  3. package/dist/ai.d.ts +2 -0
  4. package/dist/ai.d.ts.map +1 -0
  5. package/dist/ai.js +26 -0
  6. package/dist/ai.js.map +1 -0
  7. package/dist/chat.cjs +7 -0
  8. package/dist/chat.cjs.map +1 -1
  9. package/dist/chat.js +7 -0
  10. package/dist/chat.js.map +1 -1
  11. package/dist/components/FilterBar.cjs +49 -18
  12. package/dist/components/FilterBar.cjs.map +1 -1
  13. package/dist/components/FilterBar.d.ts +0 -2
  14. package/dist/components/FilterBar.d.ts.map +1 -1
  15. package/dist/components/FilterBar.js +50 -19
  16. package/dist/components/FilterBar.js.map +1 -1
  17. package/dist/components/SearchInput.cjs +63 -0
  18. package/dist/components/SearchInput.cjs.map +1 -0
  19. package/dist/components/SearchInput.d.ts +31 -0
  20. package/dist/components/SearchInput.d.ts.map +1 -0
  21. package/dist/components/SearchInput.js +63 -0
  22. package/dist/components/SearchInput.js.map +1 -0
  23. package/dist/components/SegmentedControl.cjs +88 -0
  24. package/dist/components/SegmentedControl.cjs.map +1 -0
  25. package/dist/components/SegmentedControl.d.ts +38 -0
  26. package/dist/components/SegmentedControl.d.ts.map +1 -0
  27. package/dist/components/SegmentedControl.js +88 -0
  28. package/dist/components/SegmentedControl.js.map +1 -0
  29. package/dist/components/Switch.cjs +51 -0
  30. package/dist/components/Switch.cjs.map +1 -0
  31. package/dist/components/Switch.d.ts +23 -0
  32. package/dist/components/Switch.d.ts.map +1 -0
  33. package/dist/components/Switch.js +51 -0
  34. package/dist/components/Switch.js.map +1 -0
  35. package/dist/components/TimeRange.cjs +53 -45
  36. package/dist/components/TimeRange.cjs.map +1 -1
  37. package/dist/components/TimeRange.d.ts.map +1 -1
  38. package/dist/components/TimeRange.js +53 -45
  39. package/dist/components/TimeRange.js.map +1 -1
  40. package/dist/components/TreePickerField.cjs +5 -1
  41. package/dist/components/TreePickerField.cjs.map +1 -1
  42. package/dist/components/TreePickerField.d.ts.map +1 -1
  43. package/dist/components/TreePickerField.js +5 -1
  44. package/dist/components/TreePickerField.js.map +1 -1
  45. package/dist/components/filter-bar-utils.cjs +5 -0
  46. package/dist/components/filter-bar-utils.cjs.map +1 -1
  47. package/dist/components/filter-bar-utils.d.ts +2 -0
  48. package/dist/components/filter-bar-utils.d.ts.map +1 -1
  49. package/dist/components/filter-bar-utils.js +5 -0
  50. package/dist/components/filter-bar-utils.js.map +1 -1
  51. package/dist/components/json-schema-form-fields.cjs +8 -2
  52. package/dist/components/json-schema-form-fields.cjs.map +1 -1
  53. package/dist/components/json-schema-form-fields.d.ts.map +1 -1
  54. package/dist/components/json-schema-form-fields.js +8 -2
  55. package/dist/components/json-schema-form-fields.js.map +1 -1
  56. package/dist/components/json-schema-form-resolve.cjs +1 -1
  57. package/dist/components/json-schema-form-resolve.cjs.map +1 -1
  58. package/dist/components/json-schema-form-resolve.js +1 -1
  59. package/dist/components/json-schema-form-resolve.js.map +1 -1
  60. package/dist/components.cjs +15 -1
  61. package/dist/components.cjs.map +1 -1
  62. package/dist/components.d.ts +8 -1
  63. package/dist/components.d.ts.map +1 -1
  64. package/dist/components.js +15 -1
  65. package/dist/components.js.map +1 -1
  66. package/dist/data/Clicky.cjs +33 -44
  67. package/dist/data/Clicky.cjs.map +1 -1
  68. package/dist/data/Clicky.d.ts +0 -9
  69. package/dist/data/Clicky.d.ts.map +1 -1
  70. package/dist/data/Clicky.js +31 -42
  71. package/dist/data/Clicky.js.map +1 -1
  72. package/dist/data/DataTable.cjs +73 -50
  73. package/dist/data/DataTable.cjs.map +1 -1
  74. package/dist/data/DataTable.d.ts +4 -0
  75. package/dist/data/DataTable.d.ts.map +1 -1
  76. package/dist/data/DataTable.js +73 -50
  77. package/dist/data/DataTable.js.map +1 -1
  78. package/dist/data/Icon.cjs +2 -5
  79. package/dist/data/Icon.cjs.map +1 -1
  80. package/dist/data/Icon.d.ts +1 -21
  81. package/dist/data/Icon.d.ts.map +1 -1
  82. package/dist/data/Icon.js +3 -6
  83. package/dist/data/Icon.js.map +1 -1
  84. package/dist/data/MatrixTable.cjs +4 -4
  85. package/dist/data/MatrixTable.cjs.map +1 -1
  86. package/dist/data/MatrixTable.js +4 -4
  87. package/dist/data/MatrixTable.js.map +1 -1
  88. package/dist/data/RadialGauge.cjs +76 -0
  89. package/dist/data/RadialGauge.cjs.map +1 -0
  90. package/dist/data/RadialGauge.d.ts +24 -0
  91. package/dist/data/RadialGauge.d.ts.map +1 -0
  92. package/dist/data/RadialGauge.js +76 -0
  93. package/dist/data/RadialGauge.js.map +1 -0
  94. package/dist/data/StatusBreakdown.cjs +0 -4
  95. package/dist/data/StatusBreakdown.cjs.map +1 -1
  96. package/dist/data/StatusBreakdown.d.ts +0 -2
  97. package/dist/data/StatusBreakdown.d.ts.map +1 -1
  98. package/dist/data/StatusBreakdown.js +1 -5
  99. package/dist/data/StatusBreakdown.js.map +1 -1
  100. package/dist/data/Timeline.cjs +61 -0
  101. package/dist/data/Timeline.cjs.map +1 -0
  102. package/dist/data/Timeline.d.ts +36 -0
  103. package/dist/data/Timeline.d.ts.map +1 -0
  104. package/dist/data/Timeline.js +61 -0
  105. package/dist/data/Timeline.js.map +1 -0
  106. package/dist/data/TimeseriesCoreBars.cjs +2 -25
  107. package/dist/data/TimeseriesCoreBars.cjs.map +1 -1
  108. package/dist/data/TimeseriesCoreBars.d.ts +0 -19
  109. package/dist/data/TimeseriesCoreBars.d.ts.map +1 -1
  110. package/dist/data/TimeseriesCoreBars.js +2 -25
  111. package/dist/data/TimeseriesCoreBars.js.map +1 -1
  112. package/dist/data/TimeseriesCoreBars.model.cjs +27 -0
  113. package/dist/data/TimeseriesCoreBars.model.cjs.map +1 -0
  114. package/dist/data/TimeseriesCoreBars.model.d.ts +20 -0
  115. package/dist/data/TimeseriesCoreBars.model.d.ts.map +1 -0
  116. package/dist/data/TimeseriesCoreBars.model.js +27 -0
  117. package/dist/data/TimeseriesCoreBars.model.js.map +1 -0
  118. package/dist/data/TimeseriesPanel.axes.cjs.map +1 -1
  119. package/dist/data/TimeseriesPanel.axes.d.ts +1 -1
  120. package/dist/data/TimeseriesPanel.axes.d.ts.map +1 -1
  121. package/dist/data/TimeseriesPanel.axes.js.map +1 -1
  122. package/dist/data/TimeseriesPanel.cjs +4 -54
  123. package/dist/data/TimeseriesPanel.cjs.map +1 -1
  124. package/dist/data/TimeseriesPanel.d.ts +2 -138
  125. package/dist/data/TimeseriesPanel.d.ts.map +1 -1
  126. package/dist/data/TimeseriesPanel.js +2 -52
  127. package/dist/data/TimeseriesPanel.js.map +1 -1
  128. package/dist/data/TimeseriesPanel.model.cjs +53 -0
  129. package/dist/data/TimeseriesPanel.model.cjs.map +1 -0
  130. package/dist/data/TimeseriesPanel.model.d.ts +88 -0
  131. package/dist/data/TimeseriesPanel.model.d.ts.map +1 -0
  132. package/dist/data/TimeseriesPanel.model.js +53 -0
  133. package/dist/data/TimeseriesPanel.model.js.map +1 -0
  134. package/dist/data/Tree.cjs +1 -1
  135. package/dist/data/Tree.cjs.map +1 -1
  136. package/dist/data/Tree.js +1 -1
  137. package/dist/data/Tree.js.map +1 -1
  138. package/dist/data/ai/ChatFab.cjs +29 -0
  139. package/dist/data/ai/ChatFab.cjs.map +1 -0
  140. package/dist/data/ai/ChatFab.d.ts +12 -0
  141. package/dist/data/ai/ChatFab.d.ts.map +1 -0
  142. package/dist/data/ai/ChatFab.js +29 -0
  143. package/dist/data/ai/ChatFab.js.map +1 -0
  144. package/dist/data/ai/ChatWindow.cjs +178 -0
  145. package/dist/data/ai/ChatWindow.cjs.map +1 -0
  146. package/dist/data/ai/ChatWindow.d.ts +38 -0
  147. package/dist/data/ai/ChatWindow.d.ts.map +1 -0
  148. package/dist/data/ai/ChatWindow.js +156 -0
  149. package/dist/data/ai/ChatWindow.js.map +1 -0
  150. package/dist/data/ai/ChatWindowManager.cjs +139 -0
  151. package/dist/data/ai/ChatWindowManager.cjs.map +1 -0
  152. package/dist/data/ai/ChatWindowManager.d.ts +12 -0
  153. package/dist/data/ai/ChatWindowManager.d.ts.map +1 -0
  154. package/dist/data/ai/ChatWindowManager.js +139 -0
  155. package/dist/data/ai/ChatWindowManager.js.map +1 -0
  156. package/dist/data/ai/ContextBadges.cjs +74 -0
  157. package/dist/data/ai/ContextBadges.cjs.map +1 -0
  158. package/dist/data/ai/ContextBadges.d.ts +16 -0
  159. package/dist/data/ai/ContextBadges.d.ts.map +1 -0
  160. package/dist/data/ai/ContextBadges.js +74 -0
  161. package/dist/data/ai/ContextBadges.js.map +1 -0
  162. package/dist/data/ai/ThreadPicker.cjs +148 -0
  163. package/dist/data/ai/ThreadPicker.cjs.map +1 -0
  164. package/dist/data/ai/ThreadPicker.d.ts +37 -0
  165. package/dist/data/ai/ThreadPicker.d.ts.map +1 -0
  166. package/dist/data/ai/ThreadPicker.js +148 -0
  167. package/dist/data/ai/ThreadPicker.js.map +1 -0
  168. package/dist/data/ai/ToolPreferences.cjs +80 -0
  169. package/dist/data/ai/ToolPreferences.cjs.map +1 -0
  170. package/dist/data/ai/ToolPreferences.d.ts +19 -0
  171. package/dist/data/ai/ToolPreferences.d.ts.map +1 -0
  172. package/dist/data/ai/ToolPreferences.js +80 -0
  173. package/dist/data/ai/ToolPreferences.js.map +1 -0
  174. package/dist/data/ai/chat-window-context.cjs +78 -0
  175. package/dist/data/ai/chat-window-context.cjs.map +1 -0
  176. package/dist/data/ai/chat-window-context.d.ts +57 -0
  177. package/dist/data/ai/chat-window-context.d.ts.map +1 -0
  178. package/dist/data/ai/chat-window-context.js +78 -0
  179. package/dist/data/ai/chat-window-context.js.map +1 -0
  180. package/dist/data/ai/context.cjs +43 -0
  181. package/dist/data/ai/context.cjs.map +1 -0
  182. package/dist/data/ai/context.d.ts +35 -0
  183. package/dist/data/ai/context.d.ts.map +1 -0
  184. package/dist/data/ai/context.js +43 -0
  185. package/dist/data/ai/context.js.map +1 -0
  186. package/dist/data/ai/index.d.ts +10 -0
  187. package/dist/data/ai/index.d.ts.map +1 -0
  188. package/dist/data/cache-browser/CacheDetailPanel.cjs +15 -0
  189. package/dist/data/cache-browser/CacheDetailPanel.cjs.map +1 -1
  190. package/dist/data/cache-browser/CacheDetailPanel.d.ts.map +1 -1
  191. package/dist/data/cache-browser/CacheDetailPanel.js +15 -0
  192. package/dist/data/cache-browser/CacheDetailPanel.js.map +1 -1
  193. package/dist/data/cache-browser/cache-browser.fixtures.d.ts +10 -0
  194. package/dist/data/cache-browser/cache-browser.fixtures.d.ts.map +1 -0
  195. package/dist/data/cache-browser/download.cjs +13 -0
  196. package/dist/data/cache-browser/download.cjs.map +1 -0
  197. package/dist/data/cache-browser/download.d.ts +3 -0
  198. package/dist/data/cache-browser/download.d.ts.map +1 -0
  199. package/dist/data/cache-browser/download.js +13 -0
  200. package/dist/data/cache-browser/download.js.map +1 -0
  201. package/dist/data/chat/Chat.cjs +44 -3
  202. package/dist/data/chat/Chat.cjs.map +1 -1
  203. package/dist/data/chat/Chat.d.ts +5 -2
  204. package/dist/data/chat/Chat.d.ts.map +1 -1
  205. package/dist/data/chat/Chat.fixtures.cjs +86 -0
  206. package/dist/data/chat/Chat.fixtures.cjs.map +1 -0
  207. package/dist/data/chat/Chat.fixtures.d.ts +4 -0
  208. package/dist/data/chat/Chat.fixtures.d.ts.map +1 -1
  209. package/dist/data/chat/Chat.fixtures.js +86 -0
  210. package/dist/data/chat/Chat.fixtures.js.map +1 -0
  211. package/dist/data/chat/Chat.js +45 -4
  212. package/dist/data/chat/Chat.js.map +1 -1
  213. package/dist/data/chat/ContextUsage.cjs +99 -0
  214. package/dist/data/chat/ContextUsage.cjs.map +1 -0
  215. package/dist/data/chat/ContextUsage.d.ts +19 -0
  216. package/dist/data/chat/ContextUsage.d.ts.map +1 -0
  217. package/dist/data/chat/ContextUsage.js +99 -0
  218. package/dist/data/chat/ContextUsage.js.map +1 -0
  219. package/dist/data/chat/Conversation.cjs +47 -2
  220. package/dist/data/chat/Conversation.cjs.map +1 -1
  221. package/dist/data/chat/Conversation.d.ts +8 -2
  222. package/dist/data/chat/Conversation.d.ts.map +1 -1
  223. package/dist/data/chat/Conversation.js +47 -2
  224. package/dist/data/chat/Conversation.js.map +1 -1
  225. package/dist/data/chat/ModelSelector.cjs +28 -14
  226. package/dist/data/chat/ModelSelector.cjs.map +1 -1
  227. package/dist/data/chat/ModelSelector.d.ts +5 -4
  228. package/dist/data/chat/ModelSelector.d.ts.map +1 -1
  229. package/dist/data/chat/ModelSelector.js +28 -14
  230. package/dist/data/chat/ModelSelector.js.map +1 -1
  231. package/dist/data/chat/clickyOperationsToTools.cjs +21 -1
  232. package/dist/data/chat/clickyOperationsToTools.cjs.map +1 -1
  233. package/dist/data/chat/clickyOperationsToTools.d.ts +5 -4
  234. package/dist/data/chat/clickyOperationsToTools.d.ts.map +1 -1
  235. package/dist/data/chat/clickyOperationsToTools.js +21 -1
  236. package/dist/data/chat/clickyOperationsToTools.js.map +1 -1
  237. package/dist/data/chat/index.d.ts +4 -1
  238. package/dist/data/chat/index.d.ts.map +1 -1
  239. package/dist/data/chat/provider-icons.cjs +15 -0
  240. package/dist/data/chat/provider-icons.cjs.map +1 -0
  241. package/dist/data/chat/provider-icons.d.ts +7 -0
  242. package/dist/data/chat/provider-icons.d.ts.map +1 -0
  243. package/dist/data/chat/provider-icons.js +15 -0
  244. package/dist/data/chat/provider-icons.js.map +1 -0
  245. package/dist/data/chat/types.cjs.map +1 -1
  246. package/dist/data/chat/types.d.ts +41 -9
  247. package/dist/data/chat/types.d.ts.map +1 -1
  248. package/dist/data/chat/types.js.map +1 -1
  249. package/dist/data/clicky-parse.cjs +42 -0
  250. package/dist/data/clicky-parse.cjs.map +1 -0
  251. package/dist/data/clicky-parse.d.ts +11 -0
  252. package/dist/data/clicky-parse.d.ts.map +1 -0
  253. package/dist/data/clicky-parse.js +42 -0
  254. package/dist/data/clicky-parse.js.map +1 -0
  255. package/dist/data/diagnostics/FrameSourceWindow.cjs +0 -4
  256. package/dist/data/diagnostics/FrameSourceWindow.cjs.map +1 -1
  257. package/dist/data/diagnostics/FrameSourceWindow.d.ts +1 -11
  258. package/dist/data/diagnostics/FrameSourceWindow.d.ts.map +1 -1
  259. package/dist/data/diagnostics/FrameSourceWindow.js +1 -5
  260. package/dist/data/diagnostics/FrameSourceWindow.js.map +1 -1
  261. package/dist/data/diagnostics/FrameSourceWindow.utils.cjs +7 -0
  262. package/dist/data/diagnostics/FrameSourceWindow.utils.cjs.map +1 -0
  263. package/dist/data/diagnostics/FrameSourceWindow.utils.d.ts +12 -0
  264. package/dist/data/diagnostics/FrameSourceWindow.utils.d.ts.map +1 -0
  265. package/dist/data/diagnostics/FrameSourceWindow.utils.js +7 -0
  266. package/dist/data/diagnostics/FrameSourceWindow.utils.js.map +1 -0
  267. package/dist/data/diagnostics/JvmStackTrace.cjs +2 -1
  268. package/dist/data/diagnostics/JvmStackTrace.cjs.map +1 -1
  269. package/dist/data/diagnostics/JvmStackTrace.d.ts.map +1 -1
  270. package/dist/data/diagnostics/JvmStackTrace.js +2 -1
  271. package/dist/data/diagnostics/JvmStackTrace.js.map +1 -1
  272. package/dist/data/icon-fallback.cjs +12 -0
  273. package/dist/data/icon-fallback.cjs.map +1 -0
  274. package/dist/data/icon-fallback.d.ts +19 -0
  275. package/dist/data/icon-fallback.d.ts.map +1 -0
  276. package/dist/data/icon-fallback.js +12 -0
  277. package/dist/data/icon-fallback.js.map +1 -0
  278. package/dist/data/status-breakdown-utils.cjs +7 -0
  279. package/dist/data/status-breakdown-utils.cjs.map +1 -0
  280. package/dist/data/status-breakdown-utils.d.ts +4 -0
  281. package/dist/data/status-breakdown-utils.d.ts.map +1 -0
  282. package/dist/data/status-breakdown-utils.js +7 -0
  283. package/dist/data/status-breakdown-utils.js.map +1 -0
  284. package/dist/data/test-runner/TestRunSummary.cjs +36 -0
  285. package/dist/data/test-runner/TestRunSummary.cjs.map +1 -1
  286. package/dist/data/test-runner/TestRunSummary.d.ts +7 -1
  287. package/dist/data/test-runner/TestRunSummary.d.ts.map +1 -1
  288. package/dist/data/test-runner/TestRunSummary.js +36 -0
  289. package/dist/data/test-runner/TestRunSummary.js.map +1 -1
  290. package/dist/data/test-runner/TestRunner.cjs +5 -3
  291. package/dist/data/test-runner/TestRunner.cjs.map +1 -1
  292. package/dist/data/test-runner/TestRunner.d.ts +13 -1
  293. package/dist/data/test-runner/TestRunner.d.ts.map +1 -1
  294. package/dist/data/test-runner/TestRunner.js +5 -3
  295. package/dist/data/test-runner/TestRunner.js.map +1 -1
  296. package/dist/data/version-info.cjs +3 -3
  297. package/dist/data/version-info.js +3 -3
  298. package/dist/data.cjs +19 -6
  299. package/dist/data.cjs.map +1 -1
  300. package/dist/data.d.ts +13 -5
  301. package/dist/data.d.ts.map +1 -1
  302. package/dist/data.js +18 -5
  303. package/dist/data.js.map +1 -1
  304. package/dist/index.cjs +51 -7
  305. package/dist/index.cjs.map +1 -1
  306. package/dist/index.js +50 -6
  307. package/dist/index.js.map +1 -1
  308. package/dist/layout/AppShell.cjs +179 -29
  309. package/dist/layout/AppShell.cjs.map +1 -1
  310. package/dist/layout/AppShell.d.ts +5 -5
  311. package/dist/layout/AppShell.d.ts.map +1 -1
  312. package/dist/layout/AppShell.js +179 -29
  313. package/dist/layout/AppShell.js.map +1 -1
  314. package/dist/layout/AppSidebar.cjs +239 -0
  315. package/dist/layout/AppSidebar.cjs.map +1 -0
  316. package/dist/layout/AppSidebar.d.ts +62 -0
  317. package/dist/layout/AppSidebar.d.ts.map +1 -0
  318. package/dist/layout/AppSidebar.js +239 -0
  319. package/dist/layout/AppSidebar.js.map +1 -0
  320. package/dist/overlay/DropdownMenu.cjs.map +1 -1
  321. package/dist/overlay/DropdownMenu.d.ts.map +1 -1
  322. package/dist/overlay/DropdownMenu.js.map +1 -1
  323. package/dist/overlay/Modal.cjs +4 -2
  324. package/dist/overlay/Modal.cjs.map +1 -1
  325. package/dist/overlay/Modal.d.ts +1 -1
  326. package/dist/overlay/Modal.d.ts.map +1 -1
  327. package/dist/overlay/Modal.js +4 -2
  328. package/dist/overlay/Modal.js.map +1 -1
  329. package/dist/overlay/Toast.cjs +89 -0
  330. package/dist/overlay/Toast.cjs.map +1 -0
  331. package/dist/overlay/Toast.d.ts +14 -0
  332. package/dist/overlay/Toast.d.ts.map +1 -0
  333. package/dist/overlay/Toast.js +89 -0
  334. package/dist/overlay/Toast.js.map +1 -0
  335. package/dist/overlay/toast-context.cjs +12 -0
  336. package/dist/overlay/toast-context.cjs.map +1 -0
  337. package/dist/overlay/toast-context.d.ts +24 -0
  338. package/dist/overlay/toast-context.d.ts.map +1 -0
  339. package/dist/overlay/toast-context.js +12 -0
  340. package/dist/overlay/toast-context.js.map +1 -0
  341. package/dist/rpc/AcceptPicker.cjs +81 -58
  342. package/dist/rpc/AcceptPicker.cjs.map +1 -1
  343. package/dist/rpc/AcceptPicker.d.ts.map +1 -1
  344. package/dist/rpc/AcceptPicker.js +81 -58
  345. package/dist/rpc/AcceptPicker.js.map +1 -1
  346. package/dist/rpc/CommandForm.cjs +5 -35
  347. package/dist/rpc/CommandForm.cjs.map +1 -1
  348. package/dist/rpc/CommandForm.d.ts +0 -3
  349. package/dist/rpc/CommandForm.d.ts.map +1 -1
  350. package/dist/rpc/CommandForm.js +2 -32
  351. package/dist/rpc/CommandForm.js.map +1 -1
  352. package/dist/rpc/CommandOutput.cjs +2 -1
  353. package/dist/rpc/CommandOutput.cjs.map +1 -1
  354. package/dist/rpc/CommandOutput.d.ts.map +1 -1
  355. package/dist/rpc/CommandOutput.js +2 -1
  356. package/dist/rpc/CommandOutput.js.map +1 -1
  357. package/dist/rpc/EntityExplorerApp.cjs +28 -83
  358. package/dist/rpc/EntityExplorerApp.cjs.map +1 -1
  359. package/dist/rpc/EntityExplorerApp.d.ts +1 -4
  360. package/dist/rpc/EntityExplorerApp.d.ts.map +1 -1
  361. package/dist/rpc/EntityExplorerApp.js +13 -68
  362. package/dist/rpc/EntityExplorerApp.js.map +1 -1
  363. package/dist/rpc/OperationCatalog.cjs +2 -2
  364. package/dist/rpc/OperationCatalog.cjs.map +1 -1
  365. package/dist/rpc/OperationCatalog.d.ts +1 -1
  366. package/dist/rpc/OperationCatalog.d.ts.map +1 -1
  367. package/dist/rpc/OperationCatalog.js +1 -1
  368. package/dist/rpc/OperationCatalog.js.map +1 -1
  369. package/dist/rpc/OperationCommandPage.cjs +6 -4
  370. package/dist/rpc/OperationCommandPage.cjs.map +1 -1
  371. package/dist/rpc/OperationCommandPage.d.ts.map +1 -1
  372. package/dist/rpc/OperationCommandPage.js +3 -1
  373. package/dist/rpc/OperationCommandPage.js.map +1 -1
  374. package/dist/rpc/RouterProvider.cjs +10 -0
  375. package/dist/rpc/RouterProvider.cjs.map +1 -0
  376. package/dist/rpc/RouterProvider.d.ts +10 -0
  377. package/dist/rpc/RouterProvider.d.ts.map +1 -0
  378. package/dist/rpc/RouterProvider.js +10 -0
  379. package/dist/rpc/RouterProvider.js.map +1 -0
  380. package/dist/rpc/apiClient.cjs +4 -4
  381. package/dist/rpc/apiClient.cjs.map +1 -1
  382. package/dist/rpc/apiClient.js +4 -4
  383. package/dist/rpc/apiClient.js.map +1 -1
  384. package/dist/rpc/command-form-utils.cjs +43 -0
  385. package/dist/rpc/command-form-utils.cjs.map +1 -0
  386. package/dist/rpc/command-form-utils.d.ts +5 -0
  387. package/dist/rpc/command-form-utils.d.ts.map +1 -0
  388. package/dist/rpc/command-form-utils.js +43 -0
  389. package/dist/rpc/command-form-utils.js.map +1 -0
  390. package/dist/rpc/commandHref.cjs +62 -0
  391. package/dist/rpc/commandHref.cjs.map +1 -0
  392. package/dist/rpc/commandHref.d.ts +7 -0
  393. package/dist/rpc/commandHref.d.ts.map +1 -0
  394. package/dist/rpc/commandHref.js +62 -0
  395. package/dist/rpc/commandHref.js.map +1 -0
  396. package/dist/rpc/formMetadata.cjs +1 -1
  397. package/dist/rpc/formMetadata.cjs.map +1 -1
  398. package/dist/rpc/formMetadata.js +1 -1
  399. package/dist/rpc/formMetadata.js.map +1 -1
  400. package/dist/rpc/router.cjs +83 -0
  401. package/dist/rpc/router.cjs.map +1 -0
  402. package/dist/rpc/router.d.ts +24 -0
  403. package/dist/rpc/router.d.ts.map +1 -0
  404. package/dist/rpc/router.js +83 -0
  405. package/dist/rpc/router.js.map +1 -0
  406. package/dist/rpc/rpc-story-fixtures/orders.fixture.d.ts +3 -0
  407. package/dist/rpc/rpc-story-fixtures/orders.fixture.d.ts.map +1 -0
  408. package/dist/rpc/rpc-story-fixtures/services.fixture.d.ts +3 -0
  409. package/dist/rpc/rpc-story-fixtures/services.fixture.d.ts.map +1 -0
  410. package/dist/rpc/rpc-story-fixtures/surface-fixture.d.ts +24 -0
  411. package/dist/rpc/rpc-story-fixtures/surface-fixture.d.ts.map +1 -0
  412. package/dist/rpc/rpc-story-fixtures/widgets.fixture.d.ts +3 -0
  413. package/dist/rpc/rpc-story-fixtures/widgets.fixture.d.ts.map +1 -0
  414. package/dist/rpc/rpc-story.fixtures.d.ts +16 -0
  415. package/dist/rpc/rpc-story.fixtures.d.ts.map +1 -0
  416. package/dist/rpc.cjs +17 -0
  417. package/dist/rpc.cjs.map +1 -1
  418. package/dist/rpc.d.ts +5 -0
  419. package/dist/rpc.d.ts.map +1 -1
  420. package/dist/rpc.js +18 -1
  421. package/dist/rpc.js.map +1 -1
  422. package/dist/styles.css +1 -1
  423. package/package.json +11 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppSidebar.cjs","sources":["../../src/layout/AppSidebar.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiSidebar } from \"../icons\";\nimport type { RenderLink } from \"../rpc/EndpointList\";\nimport { useRouter } from \"../rpc/router\";\n\n// AppSidebar / AppLayout are a router-agnostic application shell: a collapsible\n// left navigation rail driven by declarative sections, plus a flex layout that\n// pairs it with an optional header and the routed content. Navigation and active\n// state come from the RouterAdapter (see ../rpc/router) — by default a plain\n// <a href={to}> browser adapter, or the host's react-router/memory adapter via\n// RouterProvider. The `pathname` and `renderLink` props still override the\n// adapter for cases where a rail's active state is independent of the URL.\n//\n// A section may carry flat `items` and/or collapsible `groups`. Groups are for\n// high-cardinality clusters (e.g. provider surfaces) that benefit from being\n// folded away; their open/closed state persists per `group.key`.\n\nexport interface AppNavItem {\n /** Stable key; also used as the React key. */\n key: string;\n title: string;\n /** Destination passed to renderLink (or href for external items). */\n to: string;\n icon?: ReactNode;\n /** Render a plain anchor (new-tab style) instead of a routed link. */\n external?: boolean;\n /** Optional trailing adornment (count, status dot, etc.). */\n badge?: ReactNode;\n}\n\nexport interface AppNavGroup {\n /** Stable key; also the persistence key for collapsed state. */\n key: string;\n label: string;\n icon?: ReactNode;\n /** Collapsed on first render until the user toggles it. */\n defaultCollapsed?: boolean;\n items?: AppNavItem[];\n /** Nested groups (one extra level, e.g. Providers > Xero). */\n groups?: AppNavGroup[];\n}\n\nexport interface AppNavSection {\n /** Section heading; omitted for the leading section. */\n label?: string;\n items?: AppNavItem[];\n /** Collapsible groups rendered after the flat items. */\n groups?: AppNavGroup[];\n}\n\nexport interface AppSidebarProps {\n sections: AppNavSection[];\n /** Current path for active-state. Defaults to the RouterAdapter's pathname. */\n pathname?: string;\n /** Render routed links. Defaults to the RouterAdapter's renderLink (a plain\n * <a href={to}> unless a RouterProvider supplies a router Link). */\n renderLink?: RenderLink;\n /** Branding shown at the top of the expanded rail (logo/title). */\n header?: ReactNode;\n /** Pinned to the bottom of the rail (theme switcher, version, etc.). */\n footer?: ReactNode;\n /** localStorage key persisting the rail collapsed state. */\n collapsedStorageKey?: string;\n /** localStorage key persisting per-group collapsed state. */\n groupCollapsedStorageKey?: string;\n /** Override the default prefix-match active test. */\n isActive?: (pathname: string, to: string) => boolean;\n className?: string;\n}\n\nconst DEFAULT_COLLAPSE_KEY = \"clicky-ui:app-sidebar:collapsed\";\nconst DEFAULT_GROUP_COLLAPSE_KEY = \"clicky-ui:app-sidebar:groups\";\n\nfunction defaultIsActive(pathname: string, to: string): boolean {\n if (to === \"/\") return pathname === \"/\";\n return pathname === to || pathname.startsWith(`${to}/`);\n}\n\nfunction useCollapsed(storageKey: string): [boolean, () => void] {\n const [collapsed, setCollapsed] = useState<boolean>(() => {\n if (typeof window === \"undefined\") return false;\n return window.localStorage.getItem(storageKey) === \"true\";\n });\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n window.localStorage.setItem(storageKey, String(collapsed));\n }, [collapsed, storageKey]);\n return [collapsed, () => setCollapsed((c) => !c)];\n}\n\ninterface GroupState {\n isCollapsed: (key: string, fallback: boolean) => boolean;\n toggle: (key: string, fallback: boolean) => void;\n}\n\nfunction readGroupState(storageKey: string): Record<string, boolean> {\n if (typeof window === \"undefined\") return {};\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return {};\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, boolean>) : {};\n } catch {\n return {};\n }\n}\n\nfunction useGroupCollapsed(storageKey: string): GroupState {\n const [state, setState] = useState<Record<string, boolean>>(() => readGroupState(storageKey));\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch {\n // Sidebar state is a UX nicety; private-mode storage failures are safe to swallow.\n }\n }, [state, storageKey]);\n return {\n isCollapsed: (key, fallback) => state[key] ?? fallback,\n toggle: (key, fallback) => setState((prev) => ({ ...prev, [key]: !(prev[key] ?? fallback) })),\n };\n}\n\nfunction flattenGroupItems(groups: AppNavGroup[]): AppNavItem[] {\n const out: AppNavItem[] = [];\n for (const group of groups) {\n if (group.items) out.push(...group.items);\n if (group.groups) out.push(...flattenGroupItems(group.groups));\n }\n return out;\n}\n\nexport function AppSidebar({\n sections,\n pathname: pathnameProp,\n renderLink: renderLinkProp,\n header,\n footer,\n collapsedStorageKey = DEFAULT_COLLAPSE_KEY,\n groupCollapsedStorageKey = DEFAULT_GROUP_COLLAPSE_KEY,\n isActive = defaultIsActive,\n className,\n}: AppSidebarProps) {\n const router = useRouter();\n const pathname = pathnameProp ?? router.pathname;\n const renderLink = renderLinkProp ?? router.renderLink;\n const [collapsed, toggleCollapsed] = useCollapsed(collapsedStorageKey);\n const groupState = useGroupCollapsed(groupCollapsedStorageKey);\n return (\n <nav\n className={cn(\n \"flex shrink-0 flex-col overflow-hidden border-r border-border bg-muted/30 transition-[width] duration-200\",\n collapsed ? \"w-16\" : \"w-64\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex shrink-0 items-center border-b border-border py-3\",\n collapsed ? \"justify-center px-2\" : \"justify-between px-4\",\n )}\n >\n {!collapsed && <div className=\"min-w-0 flex-1\">{header}</div>}\n <button\n type=\"button\"\n onClick={toggleCollapsed}\n aria-label={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n title={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\"\n >\n <Icon icon={UiSidebar} className=\"h-4 w-4\" />\n </button>\n </div>\n\n <div className={cn(\"flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto py-2\", collapsed ? \"px-2\" : \"px-3\")}>\n {sections.map((section, i) => (\n <div key={section.label ?? `section-${i}`} className=\"flex flex-col\">\n {section.label && <SectionLabel collapsed={collapsed}>{section.label}</SectionLabel>}\n {section.items?.map((item) => (\n <SidebarItem\n key={item.key}\n item={item}\n active={isActive(pathname, item.to)}\n collapsed={collapsed}\n renderLink={renderLink}\n />\n ))}\n {section.groups?.map((group) =>\n collapsed ? (\n flattenGroupItems([group]).map((item) => (\n <SidebarItem\n key={item.key}\n item={item}\n active={isActive(pathname, item.to)}\n collapsed\n renderLink={renderLink}\n />\n ))\n ) : (\n <SidebarGroup\n key={group.key}\n group={group}\n pathname={pathname}\n isActive={isActive}\n renderLink={renderLink}\n groupState={groupState}\n />\n ),\n )}\n </div>\n ))}\n </div>\n\n {footer && <div className={cn(\"mt-auto border-t border-border py-3\", collapsed ? \"px-2\" : \"px-4\")}>{footer}</div>}\n </nav>\n );\n}\n\nfunction SidebarGroup({\n group,\n pathname,\n isActive,\n renderLink,\n groupState,\n}: {\n group: AppNavGroup;\n pathname: string;\n isActive: (pathname: string, to: string) => boolean;\n renderLink: RenderLink;\n groupState: GroupState;\n}) {\n const fallback = group.defaultCollapsed ?? false;\n const collapsed = groupState.isCollapsed(group.key, fallback);\n return (\n <div className=\"flex flex-col\">\n <button\n type=\"button\"\n onClick={() => groupState.toggle(group.key, fallback)}\n aria-expanded={!collapsed}\n className=\"flex items-center gap-2 rounded-md px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground transition-colors hover:bg-accent/40\"\n >\n {group.icon && <span className=\"flex h-4 w-4 shrink-0 items-center justify-center\">{group.icon}</span>}\n <span className=\"flex-1 truncate text-left\">{group.label}</span>\n <span aria-hidden className=\"text-[0.65rem] opacity-60\">\n {collapsed ? \"▸\" : \"▾\"}\n </span>\n </button>\n {!collapsed && (\n <div className=\"ml-2 mt-0.5 flex flex-col gap-0.5 border-l border-border/60 pl-2\">\n {group.items?.map((item) => (\n <SidebarItem\n key={item.key}\n item={item}\n active={isActive(pathname, item.to)}\n collapsed={false}\n renderLink={renderLink}\n />\n ))}\n {group.groups?.map((sub) => (\n <SidebarGroup\n key={sub.key}\n group={sub}\n pathname={pathname}\n isActive={isActive}\n renderLink={renderLink}\n groupState={groupState}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction SidebarItem({\n item,\n active,\n collapsed,\n renderLink,\n}: {\n item: AppNavItem;\n active: boolean;\n collapsed: boolean;\n renderLink: RenderLink;\n}) {\n const className = cn(\n \"flex items-center gap-2.5 rounded-md px-3 py-1.5 text-[13px] text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\",\n collapsed && \"justify-center px-0\",\n active && \"bg-accent font-medium text-accent-foreground\",\n );\n const title = collapsed ? item.title : undefined;\n const children = (\n <>\n {item.icon && <span className=\"flex h-4 w-4 shrink-0 items-center justify-center\">{item.icon}</span>}\n {!collapsed && <span className=\"flex-1 truncate\">{item.title}</span>}\n {!collapsed && item.badge}\n </>\n );\n\n if (item.external) {\n return (\n <a href={item.to} className={className} title={title} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n }\n return renderLink({ key: item.key, to: item.to, className, children, ...(title ? { title } : {}) });\n}\n\nfunction SectionLabel({ children, collapsed }: { children: ReactNode; collapsed: boolean }) {\n if (collapsed) {\n return <div className=\"mx-2 mb-1 mt-3 border-t border-border first:mt-2\" />;\n }\n return (\n <div className=\"mb-0.5 mt-3 px-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground first:mt-2\">\n {children}\n </div>\n );\n}\n\nexport interface AppLayoutProps {\n sidebar: ReactNode;\n header?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\n/** Flex shell pairing the sidebar rail with an optional header and the routed\n * content area. The content area scrolls independently of the rail. */\nexport function AppLayout({ sidebar, header, children, className }: AppLayoutProps) {\n return (\n <div className={cn(\"flex h-full min-h-0 w-full overflow-hidden bg-background\", className)}>\n {sidebar}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {header}\n <main className=\"min-h-0 min-w-0 flex-1 overflow-y-auto\">{children}</main>\n </div>\n </div>\n );\n}\n"],"names":["useState","useEffect","router","useRouter","jsxs","cn","jsx","Icon","UiSidebar","Fragment"],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { ReactNode } from 'react';
2
+ import { RenderLink } from '../rpc/EndpointList';
3
+ export interface AppNavItem {
4
+ /** Stable key; also used as the React key. */
5
+ key: string;
6
+ title: string;
7
+ /** Destination passed to renderLink (or href for external items). */
8
+ to: string;
9
+ icon?: ReactNode;
10
+ /** Render a plain anchor (new-tab style) instead of a routed link. */
11
+ external?: boolean;
12
+ /** Optional trailing adornment (count, status dot, etc.). */
13
+ badge?: ReactNode;
14
+ }
15
+ export interface AppNavGroup {
16
+ /** Stable key; also the persistence key for collapsed state. */
17
+ key: string;
18
+ label: string;
19
+ icon?: ReactNode;
20
+ /** Collapsed on first render until the user toggles it. */
21
+ defaultCollapsed?: boolean;
22
+ items?: AppNavItem[];
23
+ /** Nested groups (one extra level, e.g. Providers > Xero). */
24
+ groups?: AppNavGroup[];
25
+ }
26
+ export interface AppNavSection {
27
+ /** Section heading; omitted for the leading section. */
28
+ label?: string;
29
+ items?: AppNavItem[];
30
+ /** Collapsible groups rendered after the flat items. */
31
+ groups?: AppNavGroup[];
32
+ }
33
+ export interface AppSidebarProps {
34
+ sections: AppNavSection[];
35
+ /** Current path for active-state. Defaults to the RouterAdapter's pathname. */
36
+ pathname?: string;
37
+ /** Render routed links. Defaults to the RouterAdapter's renderLink (a plain
38
+ * <a href={to}> unless a RouterProvider supplies a router Link). */
39
+ renderLink?: RenderLink;
40
+ /** Branding shown at the top of the expanded rail (logo/title). */
41
+ header?: ReactNode;
42
+ /** Pinned to the bottom of the rail (theme switcher, version, etc.). */
43
+ footer?: ReactNode;
44
+ /** localStorage key persisting the rail collapsed state. */
45
+ collapsedStorageKey?: string;
46
+ /** localStorage key persisting per-group collapsed state. */
47
+ groupCollapsedStorageKey?: string;
48
+ /** Override the default prefix-match active test. */
49
+ isActive?: (pathname: string, to: string) => boolean;
50
+ className?: string;
51
+ }
52
+ export declare function AppSidebar({ sections, pathname: pathnameProp, renderLink: renderLinkProp, header, footer, collapsedStorageKey, groupCollapsedStorageKey, isActive, className, }: AppSidebarProps): import("react/jsx-runtime").JSX.Element;
53
+ export interface AppLayoutProps {
54
+ sidebar: ReactNode;
55
+ header?: ReactNode;
56
+ children: ReactNode;
57
+ className?: string;
58
+ }
59
+ /** Flex shell pairing the sidebar rail with an optional header and the routed
60
+ * content area. The content area scrolls independently of the rail. */
61
+ export declare function AppLayout({ sidebar, header, children, className }: AppLayoutProps): import("react/jsx-runtime").JSX.Element;
62
+ //# sourceMappingURL=AppSidebar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppSidebar.d.ts","sourceRoot":"","sources":["../../src/layout/AppSidebar.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAetD,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,SAAS,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,gEAAgE;IAChE,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,UAAU,EAAE,CAAC;IACrB,wDAAwD;IACxD,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;yEACqE;IACrE,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,mEAAmE;IACnE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,wEAAwE;IACxE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,4DAA4D;IAC5D,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,qDAAqD;IACrD,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgED,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,QAAQ,EAAE,YAAY,EACtB,UAAU,EAAE,cAAc,EAC1B,MAAM,EACN,MAAM,EACN,mBAA0C,EAC1C,wBAAqD,EACrD,QAA0B,EAC1B,SAAS,GACV,EAAE,eAAe,2CA0EjB;AAwGD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;uEACuE;AACvE,wBAAgB,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,cAAc,2CAUjF"}
@@ -0,0 +1,239 @@
1
+ import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { cn } from "../lib/utils.js";
4
+ import { Icon } from "../data/Icon.js";
5
+ import { useRouter } from "../rpc/router.js";
6
+ import { UiSidebar } from "../icons/components/UiSidebar.js";
7
+ const DEFAULT_COLLAPSE_KEY = "clicky-ui:app-sidebar:collapsed";
8
+ const DEFAULT_GROUP_COLLAPSE_KEY = "clicky-ui:app-sidebar:groups";
9
+ function defaultIsActive(pathname, to) {
10
+ if (to === "/") return pathname === "/";
11
+ return pathname === to || pathname.startsWith(`${to}/`);
12
+ }
13
+ function useCollapsed(storageKey) {
14
+ const [collapsed, setCollapsed] = useState(() => {
15
+ if (typeof window === "undefined") return false;
16
+ return window.localStorage.getItem(storageKey) === "true";
17
+ });
18
+ useEffect(() => {
19
+ if (typeof window === "undefined") return;
20
+ window.localStorage.setItem(storageKey, String(collapsed));
21
+ }, [collapsed, storageKey]);
22
+ return [collapsed, () => setCollapsed((c) => !c)];
23
+ }
24
+ function readGroupState(storageKey) {
25
+ if (typeof window === "undefined") return {};
26
+ try {
27
+ const raw = window.localStorage.getItem(storageKey);
28
+ if (!raw) return {};
29
+ const parsed = JSON.parse(raw);
30
+ return parsed && typeof parsed === "object" ? parsed : {};
31
+ } catch {
32
+ return {};
33
+ }
34
+ }
35
+ function useGroupCollapsed(storageKey) {
36
+ const [state, setState] = useState(() => readGroupState(storageKey));
37
+ useEffect(() => {
38
+ if (typeof window === "undefined") return;
39
+ try {
40
+ window.localStorage.setItem(storageKey, JSON.stringify(state));
41
+ } catch {
42
+ }
43
+ }, [state, storageKey]);
44
+ return {
45
+ isCollapsed: (key, fallback) => state[key] ?? fallback,
46
+ toggle: (key, fallback) => setState((prev) => ({ ...prev, [key]: !(prev[key] ?? fallback) }))
47
+ };
48
+ }
49
+ function flattenGroupItems(groups) {
50
+ const out = [];
51
+ for (const group of groups) {
52
+ if (group.items) out.push(...group.items);
53
+ if (group.groups) out.push(...flattenGroupItems(group.groups));
54
+ }
55
+ return out;
56
+ }
57
+ function AppSidebar({
58
+ sections,
59
+ pathname: pathnameProp,
60
+ renderLink: renderLinkProp,
61
+ header,
62
+ footer,
63
+ collapsedStorageKey = DEFAULT_COLLAPSE_KEY,
64
+ groupCollapsedStorageKey = DEFAULT_GROUP_COLLAPSE_KEY,
65
+ isActive = defaultIsActive,
66
+ className
67
+ }) {
68
+ const router = useRouter();
69
+ const pathname = pathnameProp ?? router.pathname;
70
+ const renderLink = renderLinkProp ?? router.renderLink;
71
+ const [collapsed, toggleCollapsed] = useCollapsed(collapsedStorageKey);
72
+ const groupState = useGroupCollapsed(groupCollapsedStorageKey);
73
+ return /* @__PURE__ */ jsxs(
74
+ "nav",
75
+ {
76
+ className: cn(
77
+ "flex shrink-0 flex-col overflow-hidden border-r border-border bg-muted/30 transition-[width] duration-200",
78
+ collapsed ? "w-16" : "w-64",
79
+ className
80
+ ),
81
+ children: [
82
+ /* @__PURE__ */ jsxs(
83
+ "div",
84
+ {
85
+ className: cn(
86
+ "flex shrink-0 items-center border-b border-border py-3",
87
+ collapsed ? "justify-center px-2" : "justify-between px-4"
88
+ ),
89
+ children: [
90
+ !collapsed && /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: header }),
91
+ /* @__PURE__ */ jsx(
92
+ "button",
93
+ {
94
+ type: "button",
95
+ onClick: toggleCollapsed,
96
+ "aria-label": collapsed ? "Expand sidebar" : "Collapse sidebar",
97
+ title: collapsed ? "Expand sidebar" : "Collapse sidebar",
98
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
99
+ children: /* @__PURE__ */ jsx(Icon, { icon: UiSidebar, className: "h-4 w-4" })
100
+ }
101
+ )
102
+ ]
103
+ }
104
+ ),
105
+ /* @__PURE__ */ jsx("div", { className: cn("flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto py-2", collapsed ? "px-2" : "px-3"), children: sections.map((section, i) => {
106
+ var _a, _b;
107
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
108
+ section.label && /* @__PURE__ */ jsx(SectionLabel, { collapsed, children: section.label }),
109
+ (_a = section.items) == null ? void 0 : _a.map((item) => /* @__PURE__ */ jsx(
110
+ SidebarItem,
111
+ {
112
+ item,
113
+ active: isActive(pathname, item.to),
114
+ collapsed,
115
+ renderLink
116
+ },
117
+ item.key
118
+ )),
119
+ (_b = section.groups) == null ? void 0 : _b.map(
120
+ (group) => collapsed ? flattenGroupItems([group]).map((item) => /* @__PURE__ */ jsx(
121
+ SidebarItem,
122
+ {
123
+ item,
124
+ active: isActive(pathname, item.to),
125
+ collapsed: true,
126
+ renderLink
127
+ },
128
+ item.key
129
+ )) : /* @__PURE__ */ jsx(
130
+ SidebarGroup,
131
+ {
132
+ group,
133
+ pathname,
134
+ isActive,
135
+ renderLink,
136
+ groupState
137
+ },
138
+ group.key
139
+ )
140
+ )
141
+ ] }, section.label ?? `section-${i}`);
142
+ }) }),
143
+ footer && /* @__PURE__ */ jsx("div", { className: cn("mt-auto border-t border-border py-3", collapsed ? "px-2" : "px-4"), children: footer })
144
+ ]
145
+ }
146
+ );
147
+ }
148
+ function SidebarGroup({
149
+ group,
150
+ pathname,
151
+ isActive,
152
+ renderLink,
153
+ groupState
154
+ }) {
155
+ var _a, _b;
156
+ const fallback = group.defaultCollapsed ?? false;
157
+ const collapsed = groupState.isCollapsed(group.key, fallback);
158
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col", children: [
159
+ /* @__PURE__ */ jsxs(
160
+ "button",
161
+ {
162
+ type: "button",
163
+ onClick: () => groupState.toggle(group.key, fallback),
164
+ "aria-expanded": !collapsed,
165
+ className: "flex items-center gap-2 rounded-md px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground transition-colors hover:bg-accent/40",
166
+ children: [
167
+ group.icon && /* @__PURE__ */ jsx("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center", children: group.icon }),
168
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-left", children: group.label }),
169
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "text-[0.65rem] opacity-60", children: collapsed ? "▸" : "▾" })
170
+ ]
171
+ }
172
+ ),
173
+ !collapsed && /* @__PURE__ */ jsxs("div", { className: "ml-2 mt-0.5 flex flex-col gap-0.5 border-l border-border/60 pl-2", children: [
174
+ (_a = group.items) == null ? void 0 : _a.map((item) => /* @__PURE__ */ jsx(
175
+ SidebarItem,
176
+ {
177
+ item,
178
+ active: isActive(pathname, item.to),
179
+ collapsed: false,
180
+ renderLink
181
+ },
182
+ item.key
183
+ )),
184
+ (_b = group.groups) == null ? void 0 : _b.map((sub) => /* @__PURE__ */ jsx(
185
+ SidebarGroup,
186
+ {
187
+ group: sub,
188
+ pathname,
189
+ isActive,
190
+ renderLink,
191
+ groupState
192
+ },
193
+ sub.key
194
+ ))
195
+ ] })
196
+ ] });
197
+ }
198
+ function SidebarItem({
199
+ item,
200
+ active,
201
+ collapsed,
202
+ renderLink
203
+ }) {
204
+ const className = cn(
205
+ "flex items-center gap-2.5 rounded-md px-3 py-1.5 text-[13px] text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground",
206
+ collapsed && "justify-center px-0",
207
+ active && "bg-accent font-medium text-accent-foreground"
208
+ );
209
+ const title = collapsed ? item.title : void 0;
210
+ const children = /* @__PURE__ */ jsxs(Fragment, { children: [
211
+ item.icon && /* @__PURE__ */ jsx("span", { className: "flex h-4 w-4 shrink-0 items-center justify-center", children: item.icon }),
212
+ !collapsed && /* @__PURE__ */ jsx("span", { className: "flex-1 truncate", children: item.title }),
213
+ !collapsed && item.badge
214
+ ] });
215
+ if (item.external) {
216
+ return /* @__PURE__ */ jsx("a", { href: item.to, className, title, target: "_blank", rel: "noopener noreferrer", children });
217
+ }
218
+ return renderLink({ key: item.key, to: item.to, className, children, ...title ? { title } : {} });
219
+ }
220
+ function SectionLabel({ children, collapsed }) {
221
+ if (collapsed) {
222
+ return /* @__PURE__ */ jsx("div", { className: "mx-2 mb-1 mt-3 border-t border-border first:mt-2" });
223
+ }
224
+ return /* @__PURE__ */ jsx("div", { className: "mb-0.5 mt-3 px-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground first:mt-2", children });
225
+ }
226
+ function AppLayout({ sidebar, header, children, className }) {
227
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex h-full min-h-0 w-full overflow-hidden bg-background", className), children: [
228
+ sidebar,
229
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [
230
+ header,
231
+ /* @__PURE__ */ jsx("main", { className: "min-h-0 min-w-0 flex-1 overflow-y-auto", children })
232
+ ] })
233
+ ] });
234
+ }
235
+ export {
236
+ AppLayout,
237
+ AppSidebar
238
+ };
239
+ //# sourceMappingURL=AppSidebar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppSidebar.js","sources":["../../src/layout/AppSidebar.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { UiSidebar } from \"../icons\";\nimport type { RenderLink } from \"../rpc/EndpointList\";\nimport { useRouter } from \"../rpc/router\";\n\n// AppSidebar / AppLayout are a router-agnostic application shell: a collapsible\n// left navigation rail driven by declarative sections, plus a flex layout that\n// pairs it with an optional header and the routed content. Navigation and active\n// state come from the RouterAdapter (see ../rpc/router) — by default a plain\n// <a href={to}> browser adapter, or the host's react-router/memory adapter via\n// RouterProvider. The `pathname` and `renderLink` props still override the\n// adapter for cases where a rail's active state is independent of the URL.\n//\n// A section may carry flat `items` and/or collapsible `groups`. Groups are for\n// high-cardinality clusters (e.g. provider surfaces) that benefit from being\n// folded away; their open/closed state persists per `group.key`.\n\nexport interface AppNavItem {\n /** Stable key; also used as the React key. */\n key: string;\n title: string;\n /** Destination passed to renderLink (or href for external items). */\n to: string;\n icon?: ReactNode;\n /** Render a plain anchor (new-tab style) instead of a routed link. */\n external?: boolean;\n /** Optional trailing adornment (count, status dot, etc.). */\n badge?: ReactNode;\n}\n\nexport interface AppNavGroup {\n /** Stable key; also the persistence key for collapsed state. */\n key: string;\n label: string;\n icon?: ReactNode;\n /** Collapsed on first render until the user toggles it. */\n defaultCollapsed?: boolean;\n items?: AppNavItem[];\n /** Nested groups (one extra level, e.g. Providers > Xero). */\n groups?: AppNavGroup[];\n}\n\nexport interface AppNavSection {\n /** Section heading; omitted for the leading section. */\n label?: string;\n items?: AppNavItem[];\n /** Collapsible groups rendered after the flat items. */\n groups?: AppNavGroup[];\n}\n\nexport interface AppSidebarProps {\n sections: AppNavSection[];\n /** Current path for active-state. Defaults to the RouterAdapter's pathname. */\n pathname?: string;\n /** Render routed links. Defaults to the RouterAdapter's renderLink (a plain\n * <a href={to}> unless a RouterProvider supplies a router Link). */\n renderLink?: RenderLink;\n /** Branding shown at the top of the expanded rail (logo/title). */\n header?: ReactNode;\n /** Pinned to the bottom of the rail (theme switcher, version, etc.). */\n footer?: ReactNode;\n /** localStorage key persisting the rail collapsed state. */\n collapsedStorageKey?: string;\n /** localStorage key persisting per-group collapsed state. */\n groupCollapsedStorageKey?: string;\n /** Override the default prefix-match active test. */\n isActive?: (pathname: string, to: string) => boolean;\n className?: string;\n}\n\nconst DEFAULT_COLLAPSE_KEY = \"clicky-ui:app-sidebar:collapsed\";\nconst DEFAULT_GROUP_COLLAPSE_KEY = \"clicky-ui:app-sidebar:groups\";\n\nfunction defaultIsActive(pathname: string, to: string): boolean {\n if (to === \"/\") return pathname === \"/\";\n return pathname === to || pathname.startsWith(`${to}/`);\n}\n\nfunction useCollapsed(storageKey: string): [boolean, () => void] {\n const [collapsed, setCollapsed] = useState<boolean>(() => {\n if (typeof window === \"undefined\") return false;\n return window.localStorage.getItem(storageKey) === \"true\";\n });\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n window.localStorage.setItem(storageKey, String(collapsed));\n }, [collapsed, storageKey]);\n return [collapsed, () => setCollapsed((c) => !c)];\n}\n\ninterface GroupState {\n isCollapsed: (key: string, fallback: boolean) => boolean;\n toggle: (key: string, fallback: boolean) => void;\n}\n\nfunction readGroupState(storageKey: string): Record<string, boolean> {\n if (typeof window === \"undefined\") return {};\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) return {};\n const parsed = JSON.parse(raw);\n return parsed && typeof parsed === \"object\" ? (parsed as Record<string, boolean>) : {};\n } catch {\n return {};\n }\n}\n\nfunction useGroupCollapsed(storageKey: string): GroupState {\n const [state, setState] = useState<Record<string, boolean>>(() => readGroupState(storageKey));\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch {\n // Sidebar state is a UX nicety; private-mode storage failures are safe to swallow.\n }\n }, [state, storageKey]);\n return {\n isCollapsed: (key, fallback) => state[key] ?? fallback,\n toggle: (key, fallback) => setState((prev) => ({ ...prev, [key]: !(prev[key] ?? fallback) })),\n };\n}\n\nfunction flattenGroupItems(groups: AppNavGroup[]): AppNavItem[] {\n const out: AppNavItem[] = [];\n for (const group of groups) {\n if (group.items) out.push(...group.items);\n if (group.groups) out.push(...flattenGroupItems(group.groups));\n }\n return out;\n}\n\nexport function AppSidebar({\n sections,\n pathname: pathnameProp,\n renderLink: renderLinkProp,\n header,\n footer,\n collapsedStorageKey = DEFAULT_COLLAPSE_KEY,\n groupCollapsedStorageKey = DEFAULT_GROUP_COLLAPSE_KEY,\n isActive = defaultIsActive,\n className,\n}: AppSidebarProps) {\n const router = useRouter();\n const pathname = pathnameProp ?? router.pathname;\n const renderLink = renderLinkProp ?? router.renderLink;\n const [collapsed, toggleCollapsed] = useCollapsed(collapsedStorageKey);\n const groupState = useGroupCollapsed(groupCollapsedStorageKey);\n return (\n <nav\n className={cn(\n \"flex shrink-0 flex-col overflow-hidden border-r border-border bg-muted/30 transition-[width] duration-200\",\n collapsed ? \"w-16\" : \"w-64\",\n className,\n )}\n >\n <div\n className={cn(\n \"flex shrink-0 items-center border-b border-border py-3\",\n collapsed ? \"justify-center px-2\" : \"justify-between px-4\",\n )}\n >\n {!collapsed && <div className=\"min-w-0 flex-1\">{header}</div>}\n <button\n type=\"button\"\n onClick={toggleCollapsed}\n aria-label={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n title={collapsed ? \"Expand sidebar\" : \"Collapse sidebar\"}\n className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\"\n >\n <Icon icon={UiSidebar} className=\"h-4 w-4\" />\n </button>\n </div>\n\n <div className={cn(\"flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto py-2\", collapsed ? \"px-2\" : \"px-3\")}>\n {sections.map((section, i) => (\n <div key={section.label ?? `section-${i}`} className=\"flex flex-col\">\n {section.label && <SectionLabel collapsed={collapsed}>{section.label}</SectionLabel>}\n {section.items?.map((item) => (\n <SidebarItem\n key={item.key}\n item={item}\n active={isActive(pathname, item.to)}\n collapsed={collapsed}\n renderLink={renderLink}\n />\n ))}\n {section.groups?.map((group) =>\n collapsed ? (\n flattenGroupItems([group]).map((item) => (\n <SidebarItem\n key={item.key}\n item={item}\n active={isActive(pathname, item.to)}\n collapsed\n renderLink={renderLink}\n />\n ))\n ) : (\n <SidebarGroup\n key={group.key}\n group={group}\n pathname={pathname}\n isActive={isActive}\n renderLink={renderLink}\n groupState={groupState}\n />\n ),\n )}\n </div>\n ))}\n </div>\n\n {footer && <div className={cn(\"mt-auto border-t border-border py-3\", collapsed ? \"px-2\" : \"px-4\")}>{footer}</div>}\n </nav>\n );\n}\n\nfunction SidebarGroup({\n group,\n pathname,\n isActive,\n renderLink,\n groupState,\n}: {\n group: AppNavGroup;\n pathname: string;\n isActive: (pathname: string, to: string) => boolean;\n renderLink: RenderLink;\n groupState: GroupState;\n}) {\n const fallback = group.defaultCollapsed ?? false;\n const collapsed = groupState.isCollapsed(group.key, fallback);\n return (\n <div className=\"flex flex-col\">\n <button\n type=\"button\"\n onClick={() => groupState.toggle(group.key, fallback)}\n aria-expanded={!collapsed}\n className=\"flex items-center gap-2 rounded-md px-3 py-1.5 text-[11px] font-semibold uppercase tracking-[0.14em] text-muted-foreground transition-colors hover:bg-accent/40\"\n >\n {group.icon && <span className=\"flex h-4 w-4 shrink-0 items-center justify-center\">{group.icon}</span>}\n <span className=\"flex-1 truncate text-left\">{group.label}</span>\n <span aria-hidden className=\"text-[0.65rem] opacity-60\">\n {collapsed ? \"▸\" : \"▾\"}\n </span>\n </button>\n {!collapsed && (\n <div className=\"ml-2 mt-0.5 flex flex-col gap-0.5 border-l border-border/60 pl-2\">\n {group.items?.map((item) => (\n <SidebarItem\n key={item.key}\n item={item}\n active={isActive(pathname, item.to)}\n collapsed={false}\n renderLink={renderLink}\n />\n ))}\n {group.groups?.map((sub) => (\n <SidebarGroup\n key={sub.key}\n group={sub}\n pathname={pathname}\n isActive={isActive}\n renderLink={renderLink}\n groupState={groupState}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction SidebarItem({\n item,\n active,\n collapsed,\n renderLink,\n}: {\n item: AppNavItem;\n active: boolean;\n collapsed: boolean;\n renderLink: RenderLink;\n}) {\n const className = cn(\n \"flex items-center gap-2.5 rounded-md px-3 py-1.5 text-[13px] text-muted-foreground transition-colors hover:bg-accent hover:text-accent-foreground\",\n collapsed && \"justify-center px-0\",\n active && \"bg-accent font-medium text-accent-foreground\",\n );\n const title = collapsed ? item.title : undefined;\n const children = (\n <>\n {item.icon && <span className=\"flex h-4 w-4 shrink-0 items-center justify-center\">{item.icon}</span>}\n {!collapsed && <span className=\"flex-1 truncate\">{item.title}</span>}\n {!collapsed && item.badge}\n </>\n );\n\n if (item.external) {\n return (\n <a href={item.to} className={className} title={title} target=\"_blank\" rel=\"noopener noreferrer\">\n {children}\n </a>\n );\n }\n return renderLink({ key: item.key, to: item.to, className, children, ...(title ? { title } : {}) });\n}\n\nfunction SectionLabel({ children, collapsed }: { children: ReactNode; collapsed: boolean }) {\n if (collapsed) {\n return <div className=\"mx-2 mb-1 mt-3 border-t border-border first:mt-2\" />;\n }\n return (\n <div className=\"mb-0.5 mt-3 px-3 text-[10px] font-semibold uppercase tracking-[0.18em] text-muted-foreground first:mt-2\">\n {children}\n </div>\n );\n}\n\nexport interface AppLayoutProps {\n sidebar: ReactNode;\n header?: ReactNode;\n children: ReactNode;\n className?: string;\n}\n\n/** Flex shell pairing the sidebar rail with an optional header and the routed\n * content area. The content area scrolls independently of the rail. */\nexport function AppLayout({ sidebar, header, children, className }: AppLayoutProps) {\n return (\n <div className={cn(\"flex h-full min-h-0 w-full overflow-hidden bg-background\", className)}>\n {sidebar}\n <div className=\"flex min-w-0 flex-1 flex-col\">\n {header}\n <main className=\"min-h-0 min-w-0 flex-1 overflow-y-auto\">{children}</main>\n </div>\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.cjs","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => setOpen(false);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useEffect","useFloating","autoUpdate","offset","flip","shift","useClick","useDismiss","useRole","useListNavigation","useInteractions","cn","jsx","jsxs","Button","Icon","UiChevronDown","FloatingPortal","FloatingFocusManager"],"mappings":"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"}
1
+ {"version":3,"file":"DropdownMenu.cjs","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n // useDismiss is React-tree aware: a press inside anything rendered through the\n // menu's `children` render-prop — including a Modal that portals to\n // document.body — propagates via React's synthetic events to the floating\n // element and counts as \"inside\", so it won't dismiss the menu (which would\n // unmount that child Modal). A Modal owned by a sibling/parent instead of this\n // menu correctly does dismiss the menu, which is harmless since closing the\n // menu does not unmount a Modal it doesn't own.\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => setOpen(false);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":["useState","useRef","useEffect","useFloating","autoUpdate","offset","flip","shift","useClick","useDismiss","useRole","useListNavigation","useInteractions","cn","jsx","jsxs","Button","Icon","UiChevronDown","FloatingPortal","FloatingFocusManager"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/overlay/DropdownMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAgBpE,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAG9D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qBAAqB;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACpC,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,yDAAyD;IACzD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACpC,wDAAwD;IACxD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC,oDAAoD;IACpD,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,kFAAkF;IAClF,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IAChD,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,kDAAkD;IAClD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,WAAmB,EACnB,OAAmB,EACnB,IAAW,EACX,OAAO,EACP,KAAK,EACL,QAAQ,EACR,KAAe,EACf,YAAY,EACZ,KAAK,EACL,SAAS,EACT,SAAS,EACT,aAAa,GACd,EAAE,iBAAiB,2CA8HnB"}
1
+ {"version":3,"file":"DropdownMenu.d.ts","sourceRoot":"","sources":["../../src/overlay/DropdownMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAgBpE,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,EAAQ,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAG9D,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qBAAqB;IACrB,KAAK,EAAE,SAAS,CAAC;IACjB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACpC,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,yDAAyD;IACzD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,+EAA+E;IAC/E,IAAI,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAAC;IACpC,wDAAwD;IACxD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,uDAAuD;IACvD,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC,oDAAoD;IACpD,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3B,kFAAkF;IAClF,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,oEAAoE;IACpE,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IAChD,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,kDAAkD;IAClD,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACvC,kEAAkE;IAClE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,IAAI,EACJ,WAAmB,EACnB,OAAmB,EACnB,IAAW,EACX,OAAO,EACP,KAAK,EACL,QAAQ,EACR,KAAe,EACf,YAAY,EACZ,KAAK,EACL,SAAS,EACT,SAAS,EACT,aAAa,GACd,EAAE,iBAAiB,2CAqInB"}
@@ -1 +1 @@
1
- {"version":3,"file":"DropdownMenu.js","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => setOpen(false);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DropdownMenu.js","sources":["../../src/overlay/DropdownMenu.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport {\n autoUpdate,\n flip,\n FloatingFocusManager,\n FloatingPortal,\n offset,\n shift,\n useClick,\n useDismiss,\n useFloating,\n useInteractions,\n useListNavigation,\n useRole,\n} from \"@floating-ui/react\";\nimport { cn } from \"../lib/utils\";\nimport { Button, type ButtonProps } from \"../components/button\";\nimport { Icon, type StaticIconComponent } from \"../data/Icon\";\nimport { UiChevronDown } from \"../icons\";\n\nexport type DropdownMenuItem = {\n /** Visible label. */\n label: ReactNode;\n /** Called when the item is chosen. */\n onSelect: () => void;\n /** Iconify name or imported icon component rendered before the label. */\n icon?: string | StaticIconComponent;\n /** Browser tooltip for the item. */\n title?: string;\n /** Disable selection. */\n disabled?: boolean;\n};\n\nexport type DropdownMenuProps = {\n /** Trigger label. Ignored when `trigger` is provided. */\n label?: ReactNode;\n /** Icon shown before the trigger label. Ignored when `trigger` is provided. */\n icon?: string | StaticIconComponent;\n /** Hide the trailing chevron on the default trigger. */\n hideChevron?: boolean;\n /** Variant forwarded to the default Button trigger. */\n variant?: ButtonProps[\"variant\"];\n /** Size forwarded to the default Button trigger. */\n size?: ButtonProps[\"size\"];\n /** Fully custom trigger. Receives no props; the menu wraps it with open state. */\n trigger?: ReactNode;\n /** Declarative menu items. Provide this or `children`, not both. */\n items?: DropdownMenuItem[];\n /** Custom menu content. Use `closeMenu` to dismiss after a selection. */\n children?: (closeMenu: () => void) => ReactNode;\n /** Horizontal alignment of the menu relative to the trigger. */\n align?: \"left\" | \"right\";\n /** Notified whenever the menu opens or closes. */\n onOpenChange?: (open: boolean) => void;\n /** Browser tooltip / accessible label for the default trigger. */\n title?: string;\n /** Accessible label (`aria-label`) for the floating menu element itself. */\n menuLabel?: string;\n /** Classes applied to the wrapper. */\n className?: string;\n /** Classes applied to the floating menu. */\n menuClassName?: string;\n};\n\nexport function DropdownMenu({\n label,\n icon,\n hideChevron = false,\n variant = \"outline\",\n size = \"sm\",\n trigger,\n items,\n children,\n align = \"right\",\n onOpenChange,\n title,\n menuLabel,\n className,\n menuClassName,\n}: DropdownMenuProps) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number | null>(null);\n const listRef = useRef<(HTMLElement | null)[]>([]);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n useEffect(() => {\n onOpenChangeRef.current?.(open);\n }, [open]);\n\n const { refs, floatingStyles, context } = useFloating<HTMLDivElement>({\n open,\n onOpenChange: setOpen,\n // bottom-end / bottom-start reproduce the previous right/left alignment.\n placement: align === \"right\" ? \"bottom-end\" : \"bottom-start\",\n whileElementsMounted: autoUpdate,\n // offset(4) ≈ the previous mt-1; flip opens upward at the bottom edge and\n // shift slides the menu back on-screen at the horizontal edges.\n middleware: [offset(4), flip({ padding: 8 }), shift({ padding: 8 })],\n });\n\n const click = useClick(context);\n // useDismiss is React-tree aware: a press inside anything rendered through the\n // menu's `children` render-prop — including a Modal that portals to\n // document.body — propagates via React's synthetic events to the floating\n // element and counts as \"inside\", so it won't dismiss the menu (which would\n // unmount that child Modal). A Modal owned by a sibling/parent instead of this\n // menu correctly does dismiss the menu, which is harmless since closing the\n // menu does not unmount a Modal it doesn't own.\n const dismiss = useDismiss(context);\n const role = useRole(context, { role: \"menu\" });\n const listNav = useListNavigation(context, {\n listRef,\n activeIndex,\n onNavigate: setActiveIndex,\n loop: true,\n });\n\n const { getReferenceProps, getFloatingProps, getItemProps } = useInteractions([\n click,\n dismiss,\n role,\n listNav,\n ]);\n\n const closeMenu = () => setOpen(false);\n\n return (\n <div className={cn(\"relative inline-flex\", className)}>\n {trigger ? (\n <span\n ref={refs.setReference}\n className=\"inline-flex\"\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {trigger}\n </span>\n ) : (\n <Button\n ref={refs.setReference as React.Ref<HTMLButtonElement>}\n variant={variant}\n size={size}\n title={title}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n {...getReferenceProps()}\n >\n {icon && <Icon {...(typeof icon === \"string\" ? { name: icon } : { icon })} />}\n {label}\n {!hideChevron && <Icon icon={UiChevronDown} />}\n </Button>\n )}\n {open && (\n <FloatingPortal>\n <FloatingFocusManager context={context} modal={false}>\n <div\n ref={refs.setFloating}\n role=\"menu\"\n aria-label={menuLabel}\n style={floatingStyles}\n className={cn(\n \"z-[9999] min-w-[8rem] rounded-md border border-border bg-popover py-1 shadow-md\",\n menuClassName,\n )}\n {...getFloatingProps()}\n >\n {children\n ? children(closeMenu)\n : items?.map((item, i) => (\n <button\n key={i}\n ref={(node) => {\n listRef.current[i] = node;\n }}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={i === activeIndex ? 0 : -1}\n disabled={item.disabled}\n title={item.title}\n className=\"flex w-full items-center gap-2 px-3 py-1.5 text-left text-xs text-popover-foreground hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50\"\n {...getItemProps({\n onClick: () => {\n item.onSelect();\n closeMenu();\n },\n onKeyDown: (event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n item.onSelect();\n closeMenu();\n }\n },\n })}\n >\n {item.icon && (\n <Icon\n {...(typeof item.icon === \"string\"\n ? { name: item.icon }\n : { icon: item.icon })}\n />\n )}\n {item.label}\n </button>\n ))}\n </div>\n </FloatingFocusManager>\n </FloatingPortal>\n )}\n </div>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const react = require("react");
5
+ const reactDom = require("react-dom");
5
6
  const utils = require("../lib/utils.cjs");
6
7
  const Icon = require("../data/Icon.cjs");
7
8
  const button = require("../components/button.cjs");
@@ -75,7 +76,7 @@ function Modal({
75
76
  };
76
77
  }, [open]);
77
78
  if (!open) return null;
78
- return /* @__PURE__ */ jsxRuntime.jsx(
79
+ const overlay = /* @__PURE__ */ jsxRuntime.jsx(
79
80
  "div",
80
81
  {
81
82
  className: utils.cn(
@@ -83,7 +84,7 @@ function Modal({
83
84
  // Nested modals dim less so the dialog they opened over stays visible.
84
85
  depth === 0 ? "bg-black/40" : "bg-black/20"
85
86
  ),
86
- style: { zIndex: 50 + depth * 10 },
87
+ style: { zIndex: 1e4 + depth * 10 },
87
88
  onClick: closeOnBackdrop ? requestClose : void 0,
88
89
  role: "presentation",
89
90
  children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -146,6 +147,7 @@ function Modal({
146
147
  )
147
148
  }
148
149
  );
150
+ return typeof document !== "undefined" ? reactDom.createPortal(overlay, document.body) : null;
149
151
  }
150
152
  function ConfirmClosePrompt({
151
153
  options,
@@ -1 +1 @@
1
- {"version":3,"file":"Modal.cjs","sources":["../../src/overlay/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { Button } from \"../components/button\";\nimport { UiClose, UiFullscreen, UiFullscreenFilled } from \"../icons\";\nimport { useModalStack } from \"./modalStack\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\n/** Copy for the discard-confirmation prompt shown before a guarded close. */\nexport type ConfirmCloseOptions = {\n /** Heading of the confirmation prompt. */\n title?: ReactNode;\n /** Body explaining what is lost on close. */\n message?: ReactNode;\n /** Label of the button that proceeds with closing. */\n confirmLabel?: string;\n /** Label of the button that dismisses the prompt and keeps the modal open. */\n cancelLabel?: string;\n};\n\nexport type ModalProps = {\n /** Controls whether the modal is mounted. */\n open: boolean;\n /** Called when the modal requests to close. */\n onClose: () => void;\n /**\n * Guard every close path (close button, Escape, backdrop) behind a discard\n * confirmation. `true` uses the default copy; pass options to customise it.\n * `onClose` only fires once the user confirms. Set this while the modal holds\n * unsaved edits and clear it (or pass `false`) once there is nothing to lose.\n */\n confirmClose?: boolean | ConfirmCloseOptions;\n /** Header title and accessible dialog label when it is a string. */\n title?: ReactNode;\n /**\n * Secondary header content rendered on its own row directly beneath the title.\n * The title stays a single row alongside the expand/close buttons, so a tall\n * subtitle (e.g. a tab switcher) never pushes those buttons down.\n */\n subtitle?: ReactNode;\n /** Width/height preset for the dialog. */\n size?: ModalSize;\n /** Close when the backdrop is clicked. Off by default; close via Escape or the close button. */\n closeOnBackdrop?: boolean;\n /** Close when Escape is pressed. */\n closeOnEsc?: boolean;\n /** Hide the close button. */\n hideClose?: boolean;\n /** Show an expand/restore button. */\n expandable?: boolean;\n /** Classes applied to the dialog panel. */\n className?: string;\n /** Extra header content rendered before the expand/close buttons. */\n headerSlot?: ReactNode;\n /** Footer content pinned below the scrollable body. */\n footer?: ReactNode;\n /** Modal body content. */\n children: ReactNode;\n};\n\nconst sizeClass: Record<ModalSize, string> = {\n sm: \"max-w-sm max-h-[90vh]\",\n md: \"max-w-md max-h-[90vh]\",\n lg: \"max-w-2xl max-h-[90vh]\",\n xl: \"max-w-4xl max-h-[90vh]\",\n full: \"max-w-[95vw] max-h-[95vh]\",\n};\n\nconst DEFAULT_CONFIRM: Required<ConfirmCloseOptions> = {\n title: \"Discard changes?\",\n message: \"You have unsaved changes. Closing now will discard them.\",\n confirmLabel: \"Discard\",\n cancelLabel: \"Keep editing\",\n};\n\nexport function Modal({\n open,\n onClose,\n confirmClose = false,\n title,\n subtitle,\n size = \"md\",\n closeOnBackdrop = false,\n closeOnEsc = true,\n hideClose = false,\n expandable = true,\n className,\n headerSlot,\n footer,\n children,\n}: ModalProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const { isTop, depth } = useModalStack(open);\n const [expanded, setExpanded] = useState(false);\n // When confirmClose is active, a close request opens this prompt instead of\n // closing outright; onClose only fires once the user confirms the discard.\n const [confirming, setConfirming] = useState(false);\n\n // requestClose is the single entry point for every close affordance. With the\n // guard on, it surfaces the confirmation; otherwise it closes immediately.\n const requestClose = () => {\n if (confirmClose) setConfirming(true);\n else onClose();\n };\n\n useEffect(() => {\n if (!open) {\n setExpanded(false);\n setConfirming(false);\n }\n }, [open]);\n\n // A close guard that disappears mid-prompt (changes saved/reverted) should\n // not leave a stale confirmation dialog up.\n useEffect(() => {\n if (!confirmClose) setConfirming(false);\n }, [confirmClose]);\n\n useEffect(() => {\n // Only the topmost modal handles Escape so a keypress closes one nested\n // layer at a time instead of every open modal at once.\n if (!open || !closeOnEsc || !isTop) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n // While the prompt is up, Escape dismisses the prompt rather than the modal.\n if (confirming) setConfirming(false);\n else requestClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, closeOnEsc, isTop, confirming, confirmClose, onClose]);\n\n useEffect(() => {\n if (!open) return;\n const prev = document.activeElement as HTMLElement | null;\n dialogRef.current?.focus();\n return () => prev?.focus?.();\n }, [open]);\n\n if (!open) return null;\n\n return (\n <div\n className={cn(\n \"fixed inset-0 flex items-center justify-center\",\n // Nested modals dim less so the dialog they opened over stays visible.\n depth === 0 ? \"bg-black/40\" : \"bg-black/20\",\n )}\n style={{ zIndex: 50 + depth * 10 }}\n onClick={closeOnBackdrop ? requestClose : undefined}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === \"string\" ? title : undefined}\n className={cn(\n \"relative bg-background border border-border rounded-lg shadow-xl w-full flex flex-col\",\n expanded ? sizeClass.full : sizeClass[size],\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || subtitle || headerSlot || expandable || !hideClose) && (\n <div className=\"px-density-4 py-density-3 border-b border-border\">\n <div className=\"flex items-center gap-density-2\">\n {title ? (\n <h2 className=\"text-sm font-semibold flex-1\">{title}</h2>\n ) : (\n <span className=\"flex-1\" />\n )}\n {headerSlot}\n {expandable && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n aria-label={expanded ? \"Restore size\" : \"Expand to fullscreen\"}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={expanded ? UiFullscreenFilled : UiFullscreen} />\n </button>\n )}\n {!hideClose && (\n <button\n type=\"button\"\n onClick={requestClose}\n aria-label=\"Close\"\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiClose} />\n </button>\n )}\n </div>\n {subtitle ? <div className=\"mt-density-2\">{subtitle}</div> : null}\n </div>\n )}\n <div className=\"flex-1 overflow-auto px-density-4 py-density-3\">{children}</div>\n {footer && <div className=\"px-density-4 py-density-3 border-t border-border\">{footer}</div>}\n {confirming && (\n <ConfirmClosePrompt\n options={confirmClose === true ? DEFAULT_CONFIRM : { ...DEFAULT_CONFIRM, ...confirmClose }}\n onConfirm={() => {\n setConfirming(false);\n onClose();\n }}\n onCancel={() => setConfirming(false)}\n />\n )}\n </div>\n </div>\n );\n}\n\nfunction ConfirmClosePrompt({\n options,\n onConfirm,\n onCancel,\n}: {\n options: Required<ConfirmCloseOptions>;\n onConfirm: () => void;\n onCancel: () => void;\n}) {\n return (\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-density-4\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label={typeof options.title === \"string\" ? options.title : \"Confirm close\"}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"w-full max-w-sm rounded-lg border border-border bg-background p-density-4 shadow-xl\">\n <h3 className=\"text-sm font-semibold\">{options.title}</h3>\n <p className=\"mt-density-2 text-sm text-muted-foreground\">{options.message}</p>\n <div className=\"mt-density-4 flex justify-end gap-density-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onCancel}>\n {options.cancelLabel}\n </Button>\n <Button variant=\"destructive\" size=\"sm\" onClick={onConfirm}>\n {options.confirmLabel}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"names":["useRef","useModalStack","useState","useEffect","_a","jsx","cn","jsxs","Icon","UiFullscreenFilled","UiFullscreen","UiClose","Button"],"mappings":"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"}
1
+ {"version":3,"file":"Modal.cjs","sources":["../../src/overlay/Modal.tsx"],"sourcesContent":["import { useEffect, useRef, useState, type ReactNode } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { cn } from \"../lib/utils\";\nimport { Icon } from \"../data/Icon\";\nimport { Button } from \"../components/button\";\nimport { UiClose, UiFullscreen, UiFullscreenFilled } from \"../icons\";\nimport { useModalStack } from \"./modalStack\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"full\";\n\n/** Copy for the discard-confirmation prompt shown before a guarded close. */\nexport type ConfirmCloseOptions = {\n /** Heading of the confirmation prompt. */\n title?: ReactNode;\n /** Body explaining what is lost on close. */\n message?: ReactNode;\n /** Label of the button that proceeds with closing. */\n confirmLabel?: string;\n /** Label of the button that dismisses the prompt and keeps the modal open. */\n cancelLabel?: string;\n};\n\nexport type ModalProps = {\n /** Controls whether the modal is mounted. */\n open: boolean;\n /** Called when the modal requests to close. */\n onClose: () => void;\n /**\n * Guard every close path (close button, Escape, backdrop) behind a discard\n * confirmation. `true` uses the default copy; pass options to customise it.\n * `onClose` only fires once the user confirms. Set this while the modal holds\n * unsaved edits and clear it (or pass `false`) once there is nothing to lose.\n */\n confirmClose?: boolean | ConfirmCloseOptions;\n /** Header title and accessible dialog label when it is a string. */\n title?: ReactNode;\n /**\n * Secondary header content rendered on its own row directly beneath the title.\n * The title stays a single row alongside the expand/close buttons, so a tall\n * subtitle (e.g. a tab switcher) never pushes those buttons down.\n */\n subtitle?: ReactNode;\n /** Width/height preset for the dialog. */\n size?: ModalSize;\n /** Close when the backdrop is clicked. Off by default; close via Escape or the close button. */\n closeOnBackdrop?: boolean;\n /** Close when Escape is pressed. */\n closeOnEsc?: boolean;\n /** Hide the close button. */\n hideClose?: boolean;\n /** Show an expand/restore button. */\n expandable?: boolean;\n /** Classes applied to the dialog panel. */\n className?: string;\n /** Extra header content rendered before the expand/close buttons. */\n headerSlot?: ReactNode;\n /** Footer content pinned below the scrollable body. */\n footer?: ReactNode;\n /** Modal body content. */\n children: ReactNode;\n};\n\nconst sizeClass: Record<ModalSize, string> = {\n sm: \"max-w-sm max-h-[90vh]\",\n md: \"max-w-md max-h-[90vh]\",\n lg: \"max-w-2xl max-h-[90vh]\",\n xl: \"max-w-4xl max-h-[90vh]\",\n full: \"max-w-[95vw] max-h-[95vh]\",\n};\n\nconst DEFAULT_CONFIRM: Required<ConfirmCloseOptions> = {\n title: \"Discard changes?\",\n message: \"You have unsaved changes. Closing now will discard them.\",\n confirmLabel: \"Discard\",\n cancelLabel: \"Keep editing\",\n};\n\nexport function Modal({\n open,\n onClose,\n confirmClose = false,\n title,\n subtitle,\n size = \"md\",\n closeOnBackdrop = false,\n closeOnEsc = true,\n hideClose = false,\n expandable = true,\n className,\n headerSlot,\n footer,\n children,\n}: ModalProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const { isTop, depth } = useModalStack(open);\n const [expanded, setExpanded] = useState(false);\n // When confirmClose is active, a close request opens this prompt instead of\n // closing outright; onClose only fires once the user confirms the discard.\n const [confirming, setConfirming] = useState(false);\n\n // requestClose is the single entry point for every close affordance. With the\n // guard on, it surfaces the confirmation; otherwise it closes immediately.\n const requestClose = () => {\n if (confirmClose) setConfirming(true);\n else onClose();\n };\n\n useEffect(() => {\n if (!open) {\n setExpanded(false);\n setConfirming(false);\n }\n }, [open]);\n\n // A close guard that disappears mid-prompt (changes saved/reverted) should\n // not leave a stale confirmation dialog up.\n useEffect(() => {\n if (!confirmClose) setConfirming(false);\n }, [confirmClose]);\n\n useEffect(() => {\n // Only the topmost modal handles Escape so a keypress closes one nested\n // layer at a time instead of every open modal at once.\n if (!open || !closeOnEsc || !isTop) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== \"Escape\") return;\n // While the prompt is up, Escape dismisses the prompt rather than the modal.\n if (confirming) setConfirming(false);\n else requestClose();\n };\n document.addEventListener(\"keydown\", onKey);\n return () => document.removeEventListener(\"keydown\", onKey);\n }, [open, closeOnEsc, isTop, confirming, confirmClose, onClose]);\n\n useEffect(() => {\n if (!open) return;\n const prev = document.activeElement as HTMLElement | null;\n dialogRef.current?.focus();\n return () => prev?.focus?.();\n }, [open]);\n\n if (!open) return null;\n\n const overlay = (\n <div\n className={cn(\n \"fixed inset-0 flex items-center justify-center\",\n // Nested modals dim less so the dialog they opened over stays visible.\n depth === 0 ? \"bg-black/40\" : \"bg-black/20\",\n )}\n // Above the z-[9999] popover/overlay layer (dropdowns, tooltips, toasts) so a\n // modal opened from one of them stacks on top; +depth keeps nested modals\n // above their parent.\n style={{ zIndex: 10000 + depth * 10 }}\n onClick={closeOnBackdrop ? requestClose : undefined}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={typeof title === \"string\" ? title : undefined}\n className={cn(\n \"relative bg-background border border-border rounded-lg shadow-xl w-full flex flex-col\",\n expanded ? sizeClass.full : sizeClass[size],\n className,\n )}\n onClick={(e) => e.stopPropagation()}\n >\n {(title || subtitle || headerSlot || expandable || !hideClose) && (\n <div className=\"px-density-4 py-density-3 border-b border-border\">\n <div className=\"flex items-center gap-density-2\">\n {title ? (\n <h2 className=\"text-sm font-semibold flex-1\">{title}</h2>\n ) : (\n <span className=\"flex-1\" />\n )}\n {headerSlot}\n {expandable && (\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n aria-label={expanded ? \"Restore size\" : \"Expand to fullscreen\"}\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={expanded ? UiFullscreenFilled : UiFullscreen} />\n </button>\n )}\n {!hideClose && (\n <button\n type=\"button\"\n onClick={requestClose}\n aria-label=\"Close\"\n className=\"text-muted-foreground hover:text-foreground\"\n >\n <Icon icon={UiClose} />\n </button>\n )}\n </div>\n {subtitle ? <div className=\"mt-density-2\">{subtitle}</div> : null}\n </div>\n )}\n <div className=\"flex-1 overflow-auto px-density-4 py-density-3\">{children}</div>\n {footer && <div className=\"px-density-4 py-density-3 border-t border-border\">{footer}</div>}\n {confirming && (\n <ConfirmClosePrompt\n options={confirmClose === true ? DEFAULT_CONFIRM : { ...DEFAULT_CONFIRM, ...confirmClose }}\n onConfirm={() => {\n setConfirming(false);\n onClose();\n }}\n onCancel={() => setConfirming(false)}\n />\n )}\n </div>\n </div>\n );\n\n // Render into document.body so the fixed-position overlay escapes any ancestor\n // transform/overflow (e.g. a dropdown menu positioned via a CSS transform),\n // which would otherwise trap position:fixed inside that ancestor's box and clip\n // the modal to it.\n return typeof document !== \"undefined\" ? createPortal(overlay, document.body) : null;\n}\n\nfunction ConfirmClosePrompt({\n options,\n onConfirm,\n onCancel,\n}: {\n options: Required<ConfirmCloseOptions>;\n onConfirm: () => void;\n onCancel: () => void;\n}) {\n return (\n <div\n className=\"absolute inset-0 z-10 flex items-center justify-center rounded-lg bg-background/80 p-density-4\"\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-label={typeof options.title === \"string\" ? options.title : \"Confirm close\"}\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"w-full max-w-sm rounded-lg border border-border bg-background p-density-4 shadow-xl\">\n <h3 className=\"text-sm font-semibold\">{options.title}</h3>\n <p className=\"mt-density-2 text-sm text-muted-foreground\">{options.message}</p>\n <div className=\"mt-density-4 flex justify-end gap-density-2\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onCancel}>\n {options.cancelLabel}\n </Button>\n <Button variant=\"destructive\" size=\"sm\" onClick={onConfirm}>\n {options.confirmLabel}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"names":["useRef","useModalStack","useState","useEffect","_a","jsx","cn","jsxs","Icon","UiFullscreenFilled","UiFullscreen","UiClose","createPortal","Button"],"mappings":"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"}