@marimo-team/islands 0.20.5-dev9 → 0.20.5-dev90

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 (253) hide show
  1. package/dist/{Combination-Du-o_hC9.js → Combination-Dk6JxauT.js} +1 -1
  2. package/dist/{ConnectedDataExplorerComponent-DUS-zJoR.js → ConnectedDataExplorerComponent-Bh11efrC.js} +17 -17
  3. package/dist/{_baseIsEqual-5cAxzk6f.js → _baseIsEqual-CvgsjYoW.js} +38 -38
  4. package/dist/{_basePickBy-3JVb5wYm.js → _basePickBy-pTDW2_2A.js} +6 -6
  5. package/dist/{_baseUniq-DSSiFuIJ.js → _baseUniq-BUFhl85h.js} +1 -1
  6. package/dist/{any-language-editor-BL9o7y0_.js → any-language-editor-BIj11a2e.js} +19 -19
  7. package/dist/{architecture-7HQA4BMR-BxkNpYRp.js → architecture-7HQA4BMR-BmtmhGMc.js} +2 -2
  8. package/dist/{architectureDiagram-VXUJARFQ-DrJeyFHq.js → architectureDiagram-VXUJARFQ-Df0FNeBR.js} +14 -14
  9. package/dist/assets/__vite-browser-external-Us1ds95c.js +1 -0
  10. package/dist/assets/{worker-DUYMdbtA.js → worker-D10K3OOz.js} +2 -2
  11. package/dist/{blockDiagram-VD42YOAC-BJrP6qKc.js → blockDiagram-VD42YOAC-DszWqlLz.js} +7 -7
  12. package/dist/{button-KYalaJYu.js → button-DQpBib29.js} +24 -11
  13. package/dist/{c4Diagram-YG6GDRKO-Bo4gytQ5.js → c4Diagram-YG6GDRKO-Dyj8LoUX.js} +4 -4
  14. package/dist/{channel-IWLGkaBE.js → channel-CUFaIkTh.js} +1 -1
  15. package/dist/{check-C50jsehH.js → check-DpqPQmzz.js} +1 -1
  16. package/dist/{chunk-4F5CHEZ2-CxKDFd-t.js → chunk-4F5CHEZ2-CRwwZ2ED.js} +1 -1
  17. package/dist/{chunk-ABZYJK2D-CRwanrkd.js → chunk-ABZYJK2D-7QYXAAhe.js} +1 -1
  18. package/dist/{chunk-ATLVNIR6-CMMCMvOK.js → chunk-ATLVNIR6-pmHPAPSd.js} +1 -1
  19. package/dist/{chunk-B2363JML-e_W7KW1D.js → chunk-B2363JML-BuBMltZc.js} +1 -1
  20. package/dist/{chunk-B4BG7PRW-BNsHrGHG.js → chunk-B4BG7PRW-Dbta9cTX.js} +4 -4
  21. package/dist/{chunk-DI55MBZ5-DQeYbfMV.js → chunk-DI55MBZ5-DyKB35wC.js} +4 -4
  22. package/dist/{chunk-EXTU4WIE-CV_DQeaX.js → chunk-EXTU4WIE-BRFl4iNd.js} +1 -1
  23. package/dist/{chunk-FRFDVMJY-C7q09nvl.js → chunk-FRFDVMJY-Bk2LD5Te.js} +1 -1
  24. package/dist/{chunk-JA3XYJ7Z-Cmt--e0q.js → chunk-JA3XYJ7Z-BkrY9SdL.js} +2 -2
  25. package/dist/{chunk-JZLCHNYA-CkyMJnI9.js → chunk-JZLCHNYA-Bk_Lil-q.js} +4 -4
  26. package/dist/{chunk-N4CR4FBY-BJfHtJbD.js → chunk-N4CR4FBY-f5n6meOd.js} +5 -5
  27. package/dist/{chunk-PL6DKKU2-ChKBqnoD.js → chunk-PL6DKKU2-DiFkzMfM.js} +1 -1
  28. package/dist/{chunk-QN33PNHL-WOLIPUAJ.js → chunk-QN33PNHL-CXfJywHv.js} +1 -1
  29. package/dist/{chunk-QXUST7PY-DYuD50pU.js → chunk-QXUST7PY-D7-26sj3.js} +5 -5
  30. package/dist/{chunk-S3R3BYOJ-CsnX6RKs.js → chunk-S3R3BYOJ-BRT9vd1R.js} +3 -3
  31. package/dist/{chunk-SJTYNZTY-j6_1s5om.js → chunk-SJTYNZTY-BvVkbShU.js} +1 -1
  32. package/dist/{chunk-TCCFYFTB-DdLCbCTn.js → chunk-TCCFYFTB-DqxhgXG0.js} +31 -31
  33. package/dist/{chunk-TQ3KTPDO-CGsUIC73.js → chunk-TQ3KTPDO-CPkEruAA.js} +1 -1
  34. package/dist/{chunk-TZMSLE5B-B3eYTGCw.js → chunk-TZMSLE5B-DSfBOnzx.js} +1 -1
  35. package/dist/{chunk-UMXZTB3W--LdAK3Bv.js → chunk-UMXZTB3W-C4ypIY3V.js} +1 -1
  36. package/dist/{classDiagram-v2-WZHVMYZB-UTw37Gg8.js → classDiagram-2ON5EDUG-DphiMW3Y.js} +10 -10
  37. package/dist/{classDiagram-2ON5EDUG-C7C-oefv.js → classDiagram-v2-WZHVMYZB-BH1x5h4a.js} +10 -10
  38. package/dist/{clone-BJrS4PdE.js → clone-CEQ-pda1.js} +1 -1
  39. package/dist/{constants-D1Tbg_6B.js → constants-CytQ_3LM.js} +3 -3
  40. package/dist/{copy-oc-FcZzt.js → copy-BkBF0Xgk.js} +2 -2
  41. package/dist/{dagre-6UL2VRFP-BgsUhJrV.js → dagre-6UL2VRFP-DGEbtmgU.js} +12 -12
  42. package/dist/{dagre-CyZCGfV_.js → dagre-BVnNvbvD.js} +37 -37
  43. package/dist/{diagram-PSM6KHXK-BIUUOfKo.js → diagram-PSM6KHXK-CG_usglE.js} +15 -15
  44. package/dist/{diagram-QEK2KX5R-BFjolZQv.js → diagram-QEK2KX5R-CtGFEwzJ.js} +13 -13
  45. package/dist/{diagram-S2PKOQOG-4jfkWoZw.js → diagram-S2PKOQOG-ClKAGmbv.js} +13 -13
  46. package/dist/dist-B4MxkKHf.js +8 -0
  47. package/dist/{dist-De9X_Des.js → dist-B9EjSb9T.js} +1 -1
  48. package/dist/{dist-IW_ARJ3S.js → dist-BFxYppVR.js} +4 -4
  49. package/dist/{dist-D7ZGWV_9.js → dist-BGZ7TWS9.js} +3 -3
  50. package/dist/{dist-CwtEWuFb.js → dist-BSfYc7vq.js} +2 -2
  51. package/dist/{dist-DMS81OrU.js → dist-BUrWeMEP.js} +1 -1
  52. package/dist/dist-BYghZv6b.js +5 -0
  53. package/dist/dist-Be-uQhz5.js +6 -0
  54. package/dist/{dist-Ch_JuCvc.js → dist-BpMlUdNO.js} +3 -3
  55. package/dist/{dist-C6z8U-ms.js → dist-Bq5eYK43.js} +2 -2
  56. package/dist/{dist-BFL9TlzD.js → dist-Bq9zYwJs.js} +5 -5
  57. package/dist/{dist-7ZF--V_D.js → dist-C4K7pumm.js} +2 -2
  58. package/dist/{dist-Qjf6pcqK.js → dist-CAKwXCWI.js} +2 -2
  59. package/dist/dist-CB_xf0ju.js +5 -0
  60. package/dist/{dist-BwQHkjA9.js → dist-CDHl2i1x.js} +4 -4
  61. package/dist/dist-CK0qFAbF.js +8 -0
  62. package/dist/{dist-C4XMUaob.js → dist-CPlGUbk-.js} +2 -2
  63. package/dist/{dist-BT6_J2eq.js → dist-CSEWGuDq.js} +7 -2
  64. package/dist/dist-CYEk-qrr.js +8 -0
  65. package/dist/{dist-CYo3w-nC.js → dist-Cl5iM8xL.js} +3 -3
  66. package/dist/dist-CmKoWpMk.js +5 -0
  67. package/dist/{dist-I8MQW60_.js → dist-CseYuPtL.js} +2 -2
  68. package/dist/dist-D1nf4IQl.js +5 -0
  69. package/dist/{dist-CsqiXw7J.js → dist-D4gcY469.js} +2 -2
  70. package/dist/{dist-DUxS2paD.js → dist-D5NMgbbv.js} +2 -2
  71. package/dist/{dist-UYm1IE5s.js → dist-DERtJN02.js} +2 -2
  72. package/dist/{dist-CFToYDWO.js → dist-DEj2X26M.js} +2 -2
  73. package/dist/{dist-BuapEdlD.js → dist-DOoqn-VL.js} +70 -67
  74. package/dist/{dist-BLThQiU4.js → dist-DUretbKK.js} +2 -2
  75. package/dist/{dist-DEFZ7dnD.js → dist-D_-CGmlh.js} +2 -2
  76. package/dist/dist-Df3AcKpt.js +6 -0
  77. package/dist/dist-DgaFHt_I.js +5 -0
  78. package/dist/dist-Dk10C3ui.js +5 -0
  79. package/dist/{dist-D0f6Yrrb.js → dist-DodLQWPg.js} +1 -1
  80. package/dist/dist-DtyPVMHR.js +5 -0
  81. package/dist/{dist-Cb3cLT39.js → dist-HoZO6brh.js} +2 -2
  82. package/dist/{dist-Cqpjy6bK.js → dist-RNGn_-uD.js} +1 -1
  83. package/dist/{dist-BBcqvpvP.js → dist-Ux6dL_VB.js} +1 -1
  84. package/dist/{dist-B8Y11RWn.js → dist-WIWVvdBh.js} +2 -2
  85. package/dist/{dist-CB6qhQ8K.js → dist-gc9KgJuA.js} +1 -1
  86. package/dist/{dist-ovDpXuSB.js → dist-i-ud9aCA.js} +1 -1
  87. package/dist/dist-ko7WnHAO.js +5 -0
  88. package/dist/{dist-BTQbjEKU.js → dist-lNe4i1Nm.js} +1 -1
  89. package/dist/dist-of7gLRFK.js +8 -0
  90. package/dist/{erDiagram-Q2GNP2WA-Cq5Bz5lG.js → erDiagram-Q2GNP2WA-DPMseVVp.js} +10 -10
  91. package/dist/{error-banner-D0tXnwl4.js → error-banner-BctofTCP.js} +2 -2
  92. package/dist/{esm-BxMbHo0y.js → esm-BBkPJL8N.js} +29 -27
  93. package/dist/{flowDiagram-NV44I4VS-6WPJVFl7.js → flowDiagram-NV44I4VS-BpAIFwW7.js} +10 -10
  94. package/dist/{ganttDiagram-JELNMOA3-AfDhh9CI.js → ganttDiagram-JELNMOA3-DXYghZ9C.js} +3 -3
  95. package/dist/{gitGraph-G5XIXVHT-C0o6gecv.js → gitGraph-G5XIXVHT-ChHUSAop.js} +2 -2
  96. package/dist/{gitGraphDiagram-V2S2FVAM-BRSwuj0Q.js → gitGraphDiagram-V2S2FVAM-CBL-7g3_.js} +12 -12
  97. package/dist/{glide-data-editor-ByPNTNVG.js → glide-data-editor-DqxJOnJk.js} +63 -63
  98. package/dist/{graphlib-DZnBMcsX.js → graphlib-D18eZCT4.js} +10 -10
  99. package/dist/hasIn-B9AbGLj3.js +86 -0
  100. package/dist/{info-VBDWY6EO-Bzsods6X.js → info-VBDWY6EO-CwyXEo8E.js} +2 -2
  101. package/dist/{infoDiagram-HS3SLOUP-Cmxo6jKx.js → infoDiagram-HS3SLOUP-BXGbfBss.js} +12 -12
  102. package/dist/{isArrayLikeObject-Btu-i6_P.js → isArrayLikeObject-BrYl-ETg.js} +25 -26
  103. package/dist/{isEmpty-CZvUtYFp.js → isEmpty-C-xMag79.js} +2 -2
  104. package/dist/{isString-CBr7TEb7.js → isString-D-vNYDBA.js} +1 -1
  105. package/dist/{isSymbol-BuQsMXhk.js → isSymbol-Dyt2NSnN.js} +1 -1
  106. package/dist/{journeyDiagram-XKPGCS4Q-CKYr8cSR.js → journeyDiagram-XKPGCS4Q-D5BIjS4N.js} +3 -3
  107. package/dist/{kanban-definition-3W4ZIXB7-DVvAZzQD.js → kanban-definition-3W4ZIXB7-DhDkqxFB.js} +7 -7
  108. package/dist/{label-CV0KYhtH.js → label-BLDcDYdI.js} +6 -6
  109. package/dist/{loader-eJCvvApN.js → loader-DsE3MiYo.js} +2 -2
  110. package/dist/main.js +1673 -1163
  111. package/dist/{memoize-P1T1IGb9.js → memoize-Cs8aS5RW.js} +1 -1
  112. package/dist/merge-NuyC7LN7.js +51 -0
  113. package/dist/{mermaid-COOB_abB.js → mermaid-DkdSmFY8.js} +42 -42
  114. package/dist/{mermaid-parser.core-Cd-wu4tE.js → mermaid-parser.core-OkWZ8nr-.js} +8 -8
  115. package/dist/{min-CMDDtXJP.js → min-ECVRnCdn.js} +30 -30
  116. package/dist/{mindmap-definition-VGOIOE7T-1ExmnvYy.js → mindmap-definition-VGOIOE7T-BxQi78Vl.js} +9 -9
  117. package/dist/{now-BxlRp0OQ.js → now-BC2mX0ZT.js} +1 -1
  118. package/dist/{packet-DYOGHKS2-Bf1CvFco.js → packet-DYOGHKS2-C62XQjZh.js} +2 -2
  119. package/dist/{pie-VRWISCQL-LY_wbqji.js → pie-VRWISCQL-nfAKQJw3.js} +2 -2
  120. package/dist/{pieDiagram-ADFJNKIX-CJlIsdsU.js → pieDiagram-ADFJNKIX-DfSJXUHa.js} +13 -13
  121. package/dist/{purify.es-CyOIw8ru.js → purify.es-DGenX2XH.js} +67 -67
  122. package/dist/{quadrantDiagram-AYHSOK5B-BU78RiaH.js → quadrantDiagram-AYHSOK5B-CAcVWXc-.js} +2 -2
  123. package/dist/{radar-ZZBFDIW7-Ro3iXZCk.js → radar-ZZBFDIW7-lopS8_4j.js} +2 -2
  124. package/dist/{range-Dh0_-r8P.js → range-BKaWvVUE.js} +8 -8
  125. package/dist/reduce-CqQo8ppc.js +275 -0
  126. package/dist/{requirementDiagram-UZGBJVZJ-DACHtrFr.js → requirementDiagram-UZGBJVZJ-BU7dwzFM.js} +9 -9
  127. package/dist/{sankeyDiagram-TZEHDZUN-Bzg7_UWs.js → sankeyDiagram-TZEHDZUN-BVJnR4_b.js} +2 -2
  128. package/dist/{sequenceDiagram-WL72ISMW-agybEe9J.js → sequenceDiagram-WL72ISMW-CQcFQTwX.js} +4 -4
  129. package/dist/{slides-component-B0yK5GXP.js → slides-component-DwvL_HJi.js} +2 -2
  130. package/dist/{spec-Dq_reDGM.js → spec-CbYkiXG3.js} +5 -5
  131. package/dist/{stateDiagram-FKZM4ZOC-DehQAt8g.js → stateDiagram-FKZM4ZOC-Dx9AIGDe.js} +12 -12
  132. package/dist/{stateDiagram-v2-4FDKWEC3-8VzeREl9.js → stateDiagram-v2-4FDKWEC3-BIeUs-Ed.js} +10 -10
  133. package/dist/style.css +1 -1
  134. package/dist/{timeline-definition-IT6M3QCI-CdCfdaCF.js → timeline-definition-IT6M3QCI-D8B3p7ID.js} +2 -2
  135. package/dist/{toNumber-By7s5JC_.js → toNumber-CbZ70FdN.js} +2 -2
  136. package/dist/{toString-Ckpb50uw.js → toString-DbIAWQpF.js} +2 -2
  137. package/dist/{tooltip-CL8m4f9y.js → tooltip-SPkubVH3.js} +3 -3
  138. package/dist/{treemap-GDKQZRPO-DRxfDG65.js → treemap-GDKQZRPO-CkR-5ai2.js} +2 -2
  139. package/dist/{types-BwnzGcE4.js → types-0FB-N7AA.js} +519 -408
  140. package/dist/{uniq-cCc07Q8K.js → uniq-H2E5nMLq.js} +1 -1
  141. package/dist/{useAsyncData-B4hMFGnF.js → useAsyncData-D7-oahg5.js} +1 -1
  142. package/dist/{useDeepCompareMemoize-DuPhOXzr.js → useDeepCompareMemoize-DLS-bHHT.js} +5 -5
  143. package/dist/{useIframeCapabilities-CAt6D2EI.js → useIframeCapabilities-DFGZKWkO.js} +1 -1
  144. package/dist/{useTheme-BNYQnvu-.js → useTheme-D0rdoMBF.js} +6 -5
  145. package/dist/{vega-component-DouPy8AI.js → vega-component-CnO3mkFC.js} +11 -11
  146. package/dist/{xychartDiagram-PRI3JC2R-rEm_SIsC.js → xychartDiagram-PRI3JC2R-XO8FiQjU.js} +5 -5
  147. package/package.json +9 -9
  148. package/src/__mocks__/common.ts +41 -8
  149. package/src/__mocks__/requests.ts +1 -0
  150. package/src/components/chat/__tests__/useFileState.test.tsx +2 -3
  151. package/src/components/chat/acp/__tests__/context-utils.test.ts +2 -6
  152. package/src/components/datasources/components.tsx +3 -6
  153. package/src/components/datasources/datasources.tsx +8 -21
  154. package/src/components/editor/__tests__/data-attributes.test.tsx +2 -11
  155. package/src/components/editor/actions/types.ts +6 -1
  156. package/src/components/editor/actions/useNotebookActions.tsx +50 -13
  157. package/src/components/editor/cell/cell-context-menu.tsx +2 -6
  158. package/src/components/editor/chrome/types.ts +17 -0
  159. package/src/components/editor/connections/add-connection-dialog.tsx +27 -2
  160. package/src/components/editor/connections/database/__tests__/__snapshots__/as-code.test.ts.snap +105 -6
  161. package/src/components/editor/connections/database/__tests__/as-code.test.ts +101 -8
  162. package/src/components/editor/connections/database/as-code.ts +115 -25
  163. package/src/components/editor/connections/database/schemas.ts +49 -2
  164. package/src/components/editor/connections/storage/as-code.ts +1 -1
  165. package/src/components/editor/controls/command-palette.tsx +7 -0
  166. package/src/components/editor/controls/keyboard-shortcuts.tsx +3 -1
  167. package/src/components/editor/file-tree/__tests__/requesting-tree.test.ts +2 -3
  168. package/src/components/editor/file-tree/file-explorer.tsx +48 -62
  169. package/src/components/editor/file-tree/file-icons.tsx +132 -0
  170. package/src/components/editor/file-tree/file-viewer.tsx +1 -1
  171. package/src/components/editor/file-tree/tree-actions.tsx +107 -0
  172. package/src/components/editor/file-tree/types.ts +2 -96
  173. package/src/components/editor/header/filename-input.tsx +4 -1
  174. package/src/components/editor/navigation/__tests__/clipboard.test.ts +2 -4
  175. package/src/components/editor/output/console/ConsoleOutput.tsx +51 -2
  176. package/src/components/editor/output/console/__tests__/ConsoleOutput.test.tsx +97 -16
  177. package/src/components/editor/package-alert.tsx +4 -0
  178. package/src/components/icons/marimo-icons.tsx +2 -2
  179. package/src/components/pages/home-page.tsx +5 -5
  180. package/src/components/storage/__tests__/storage-snippets.test.ts +253 -0
  181. package/src/components/storage/components.tsx +0 -38
  182. package/src/components/storage/storage-file-viewer.tsx +1 -1
  183. package/src/components/storage/storage-inspector.tsx +66 -51
  184. package/src/components/storage/storage-snippets.ts +67 -0
  185. package/src/components/ui/command.tsx +2 -0
  186. package/src/core/ai/tools/__tests__/run-cells-tool.test.ts +206 -0
  187. package/src/core/ai/tools/run-cells-tool.ts +75 -40
  188. package/src/core/alerts/state.ts +1 -0
  189. package/src/core/cells/__tests__/cells.test.ts +62 -0
  190. package/src/core/cells/__tests__/session.test.ts +2 -7
  191. package/src/core/cells/cells.ts +25 -3
  192. package/src/core/cells/ids.ts +2 -1
  193. package/src/core/codemirror/compat/__tests__/jupyter.test.ts +2 -3
  194. package/src/core/codemirror/keymaps/vim.ts +32 -3
  195. package/src/core/codemirror/markdown/__tests__/commands.test.ts +2 -3
  196. package/src/core/config/__tests__/config-schema.test.ts +6 -2
  197. package/src/core/config/config-schema.ts +1 -0
  198. package/src/core/config/feature-flag.tsx +1 -1
  199. package/src/core/dom/ui-element-constants.ts +15 -0
  200. package/src/core/dom/ui-element.ts +3 -2
  201. package/src/core/export/__tests__/hooks.test.ts +3 -10
  202. package/src/core/hotkeys/__tests__/hotkeys.test.ts +64 -1
  203. package/src/core/hotkeys/hotkeys.ts +29 -3
  204. package/src/core/islands/bridge.ts +1 -0
  205. package/src/core/islands/components/web-components.tsx +2 -1
  206. package/src/core/network/__tests__/requests-network.test.ts +17 -0
  207. package/src/core/network/requests-lazy.ts +1 -0
  208. package/src/core/network/requests-network.ts +9 -0
  209. package/src/core/network/requests-static.ts +1 -0
  210. package/src/core/network/requests-toasting.tsx +1 -0
  211. package/src/core/network/types.ts +1 -0
  212. package/src/core/runtime/__tests__/runtime.test.ts +2 -8
  213. package/src/core/storage/__tests__/state.test.ts +1 -0
  214. package/src/core/wasm/bridge.ts +1 -0
  215. package/src/core/websocket/useMarimoKernelConnection.tsx +2 -0
  216. package/src/plugins/impl/DataTablePlugin.tsx +53 -3
  217. package/src/plugins/impl/FileBrowserPlugin.tsx +8 -5
  218. package/src/plugins/impl/__tests__/DataTablePlugin.test.tsx +2 -11
  219. package/src/plugins/impl/__tests__/DropdownPlugin.test.tsx +2 -11
  220. package/src/plugins/impl/anywidget/AnyWidgetPlugin.tsx +4 -1
  221. package/src/plugins/impl/data-explorer/ConnectedDataExplorerComponent.tsx +8 -1
  222. package/src/plugins/impl/mpl-interactive/MplInteractivePlugin.tsx +309 -0
  223. package/src/plugins/impl/mpl-interactive/__tests__/mpl-websocket-shim.test.ts +110 -0
  224. package/src/plugins/impl/mpl-interactive/mpl-websocket-shim.ts +57 -0
  225. package/src/plugins/impl/plotly/PlotlyPlugin.tsx +8 -2
  226. package/src/plugins/impl/vega/vega-component.tsx +7 -1
  227. package/src/plugins/impl/vega/vega.css +4 -11
  228. package/src/plugins/plugins.ts +2 -0
  229. package/src/utils/__tests__/copy.test.ts +129 -0
  230. package/src/utils/__tests__/download.test.tsx +12 -14
  231. package/src/utils/__tests__/filenames.test.ts +7 -0
  232. package/src/utils/__tests__/smartMatch.test.ts +61 -0
  233. package/src/utils/copy.ts +43 -0
  234. package/src/utils/filenames.ts +3 -0
  235. package/src/utils/smartMatch.ts +62 -0
  236. package/dist/_baseProperty-D1nWkRMz.js +0 -93
  237. package/dist/assets/__vite-browser-external-WSlCcXn_.js +0 -1
  238. package/dist/dist-BAeGo2rp.js +0 -5
  239. package/dist/dist-BqwCMSEa.js +0 -5
  240. package/dist/dist-Bum8FwTO.js +0 -6
  241. package/dist/dist-C0YiOwt_.js +0 -5
  242. package/dist/dist-C2uPv4iU.js +0 -5
  243. package/dist/dist-C5hOLsJN.js +0 -8
  244. package/dist/dist-C9NIAKMs.js +0 -8
  245. package/dist/dist-CCrzTtvk.js +0 -5
  246. package/dist/dist-CFS9i1rS.js +0 -8
  247. package/dist/dist-CyHZuhPH.js +0 -5
  248. package/dist/dist-CzcjWdIk.js +0 -6
  249. package/dist/dist-DaYyUSNC.js +0 -5
  250. package/dist/dist-DpDcJYNh.js +0 -8
  251. package/dist/dist-U_BfxcPn.js +0 -5
  252. package/dist/merge-CGQkMGzr.js +0 -51
  253. package/dist/reduce-BXFHs7IQ.js +0 -268
@@ -218,12 +218,59 @@ export const SnowflakeConnectionSchema = z
218
218
  optionRegex: ".*snowflake.*",
219
219
  }),
220
220
  ),
221
- username: usernameField(),
222
- password: passwordField(),
223
221
  role: z
224
222
  .string()
225
223
  .optional()
226
224
  .describe(FieldOptions.of({ label: "Role" })),
225
+ authType: z
226
+ .discriminatedUnion("type", [
227
+ z.object({
228
+ type: z.literal("Password"),
229
+ username: usernameField(),
230
+ password: passwordField(),
231
+ enable_mfa: z
232
+ .boolean()
233
+ .default(false)
234
+ .describe(FieldOptions.of({ label: "Enable MFA (Duo Push)" })),
235
+ }),
236
+ z.object({
237
+ type: z.literal("SSO (Browser)"),
238
+ username: usernameField(),
239
+ }),
240
+ z.object({
241
+ type: z.literal("Key Pair"),
242
+ username: usernameField(),
243
+ private_key_path: z
244
+ .string()
245
+ .nonempty()
246
+ .describe(
247
+ FieldOptions.of({
248
+ label: "Private Key Path",
249
+ placeholder: "/path/to/rsa_key.p8",
250
+ }),
251
+ ),
252
+ private_key_passphrase: z
253
+ .string()
254
+ .optional()
255
+ .describe(
256
+ FieldOptions.of({
257
+ label: "Private Key Passphrase",
258
+ inputType: "password",
259
+ optionRegex: ".*passphrase.*",
260
+ }),
261
+ ),
262
+ }),
263
+ z.object({
264
+ type: z.literal("OAuth / PAT"),
265
+ token: tokenField("Token", true),
266
+ }),
267
+ ])
268
+ .default({
269
+ type: "Password",
270
+ username: "username",
271
+ enable_mfa: false,
272
+ })
273
+ .describe(FieldOptions.of({ special: "tabs" })),
227
274
  })
228
275
  .describe(FieldOptions.of({ direction: "two-columns" }));
229
276
 
@@ -8,7 +8,7 @@ import { type StorageConnection, StorageConnectionSchema } from "./schemas";
8
8
  export type StorageLibrary = "obstore" | "fsspec";
9
9
 
10
10
  export const StorageLibraryDisplayNames: Record<StorageLibrary, string> = {
11
- obstore: "Obstore",
11
+ obstore: "obstore",
12
12
  fsspec: "fsspec",
13
13
  };
14
14
 
@@ -78,6 +78,7 @@ const CommandPalette = () => {
78
78
  return (
79
79
  <CommandItem
80
80
  disabled={props.disabled}
81
+ keywords={hotkey.additionalKeywords}
81
82
  onSelect={() => {
82
83
  addRecentCommand(shortcut);
83
84
  // Close first and then run the action, so the dialog doesn't steal focus
@@ -105,15 +106,18 @@ const CommandPalette = () => {
105
106
  handle,
106
107
  props = {},
107
108
  hotkey,
109
+ additionalKeywords,
108
110
  }: {
109
111
  label: string;
110
112
  handle: () => void;
111
113
  props?: { disabled?: boolean; tooltip?: React.ReactNode };
112
114
  hotkey?: HotkeyAction;
115
+ additionalKeywords?: string[];
113
116
  }) => {
114
117
  return (
115
118
  <CommandItem
116
119
  disabled={props.disabled}
120
+ keywords={additionalKeywords}
117
121
  onSelect={() => {
118
122
  addRecentCommand(label);
119
123
  setOpen(false);
@@ -163,6 +167,7 @@ const CommandPalette = () => {
163
167
  disabled: action.disabled,
164
168
  tooltip: action.tooltip,
165
169
  },
170
+ additionalKeywords: action.additionalKeywords,
166
171
  });
167
172
  }
168
173
  return null;
@@ -190,6 +195,7 @@ const CommandPalette = () => {
190
195
  label: action.label,
191
196
  handle: action.handleHeadless || action.handle,
192
197
  props: { disabled: action.disabled, tooltip: action.tooltip },
198
+ additionalKeywords: action.additionalKeywords,
193
199
  });
194
200
  })}
195
201
  {cellActions.map((action) => {
@@ -200,6 +206,7 @@ const CommandPalette = () => {
200
206
  label: `Cell > ${action.label}`,
201
207
  handle: action.handleHeadless || action.handle,
202
208
  props: { disabled: action.disabled, tooltip: action.tooltip },
209
+ additionalKeywords: action.additionalKeywords,
203
210
  });
204
211
  })}
205
212
  </CommandGroup>
@@ -174,7 +174,9 @@ export const KeyboardShortcuts: React.FC = () => {
174
174
  return;
175
175
  }
176
176
 
177
- let key = e.key.toLowerCase();
177
+ // Single character keys are always lowercase (e.g. "a", "b", "c")
178
+ // But we should preserve the original case for other keys (e.g. "Enter", "Escape")
179
+ let key = e.key.length === 1 ? e.key.toLowerCase() : e.key;
178
180
  // Handle edge cases
179
181
  if (e.key === " ") {
180
182
  key = "Space";
@@ -1,5 +1,6 @@
1
1
  /* Copyright 2026 Marimo. All rights reserved. */
2
2
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
3
+ import { MockModules } from "@/__mocks__/common";
3
4
  import { toast } from "@/components/ui/use-toast";
4
5
  import type { FilePath } from "@/utils/paths";
5
6
  import { RequestingTree } from "../requesting-tree";
@@ -9,9 +10,7 @@ const sendCreateFileOrFolder = vi.fn();
9
10
  const sendDeleteFileOrFolder = vi.fn();
10
11
  const sendRenameFileOrFolder = vi.fn();
11
12
 
12
- vi.mock("@/components/ui/use-toast", () => ({
13
- toast: vi.fn(),
14
- }));
13
+ vi.mock("@/components/ui/use-toast", () => MockModules.toast());
15
14
 
16
15
  describe("RequestingTree", () => {
17
16
  let requestingTree: RequestingTree;
@@ -6,8 +6,6 @@ import {
6
6
  ArrowLeftIcon,
7
7
  BetweenHorizontalStartIcon,
8
8
  BracesIcon,
9
- ChevronDownIcon,
10
- ChevronRightIcon,
11
9
  CopyIcon,
12
10
  CopyMinusIcon,
13
11
  DownloadIcon,
@@ -17,9 +15,7 @@ import {
17
15
  FilePlus2Icon,
18
16
  FolderPlusIcon,
19
17
  ListTreeIcon,
20
- MoreVerticalIcon,
21
18
  PlaySquareIcon,
22
- RefreshCcwIcon,
23
19
  Trash2Icon,
24
20
  UploadIcon,
25
21
  ViewIcon,
@@ -32,6 +28,18 @@ import {
32
28
  type TreeApi,
33
29
  } from "react-arborist";
34
30
  import useEvent from "react-use-event-hook";
31
+ import {
32
+ FILE_ICON,
33
+ FILE_ICON_COLOR,
34
+ type FileIconType,
35
+ guessFileIconType,
36
+ } from "@/components/editor/file-tree/file-icons";
37
+ import {
38
+ MENU_ITEM_ICON_CLASS,
39
+ MoreActionsButton,
40
+ RefreshIconButton,
41
+ TreeChevron,
42
+ } from "@/components/editor/file-tree/tree-actions";
35
43
  import { MarimoIcon, MarimoPlusIcon } from "@/components/icons/marimo-icons";
36
44
  import { Spinner } from "@/components/icons/spinner";
37
45
  import { useImperativeModal } from "@/components/modal/ImperativeModal";
@@ -65,12 +73,7 @@ import { useTreeDndManager } from "./dnd-wrapper";
65
73
  import { FileViewer } from "./file-viewer";
66
74
  import type { RequestingTree } from "./requesting-tree";
67
75
  import { openStateAtom, treeAtom } from "./state";
68
- import {
69
- FILE_TYPE_ICONS,
70
- type FileType,
71
- guessFileType,
72
- PYTHON_CODE_FOR_FILE_TYPE,
73
- } from "./types";
76
+ import { PYTHON_CODE_FOR_FILE_TYPE } from "./types";
74
77
  import { useFileExplorerUpload } from "./upload";
75
78
 
76
79
  const hiddenFilesState = atomWithStorage(
@@ -102,7 +105,10 @@ export const FileExplorer: React.FC<{
102
105
  const { isPending, error } = useAsyncData(() => tree.initialize(setData), []);
103
106
 
104
107
  const handleRefresh = useEvent(() => {
105
- tree.refreshAll(Object.keys(openState).filter((id) => openState[id]));
108
+ // Return the promise so callers can await refresh completion
109
+ return tree.refreshAll(
110
+ Object.keys(openState).filter((id) => openState[id]),
111
+ );
106
112
  });
107
113
 
108
114
  const handleHiddenFilesToggle = useEvent(() => {
@@ -322,16 +328,10 @@ const Toolbar = ({
322
328
  </button>
323
329
  </Tooltip>
324
330
  <input {...getInputProps({})} type="file" />
325
- <Tooltip content="Refresh">
326
- <Button
327
- data-testid="file-explorer-refresh-button"
328
- onClick={onRefresh}
329
- variant="text"
330
- size="xs"
331
- >
332
- <RefreshCcwIcon size={16} />
333
- </Button>
334
- </Tooltip>
331
+ <RefreshIconButton
332
+ data-testid="file-explorer-refresh-button"
333
+ onClick={onRefresh}
334
+ />
335
335
  <Tooltip content="Toggle hidden files">
336
336
  <Button
337
337
  data-testid="file-explorer-hidden-files-button"
@@ -421,11 +421,11 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
421
421
  useRequestClient();
422
422
  const disableFileDownloads = useAtomValue(disableFileDownloadsAtom);
423
423
 
424
- const fileType: FileType = node.data.isDirectory
424
+ const fileType: FileIconType = node.data.isDirectory
425
425
  ? "directory"
426
- : guessFileType(node.data.name);
426
+ : guessFileIconType(node.data.name);
427
427
 
428
- const Icon = FILE_TYPE_ICONS[fileType];
428
+ const Icon = FILE_ICON[fileType];
429
429
  const { openConfirm, openPrompt } = useImperativeModal();
430
430
  const { createNewCell } = useCellActions();
431
431
  const lastFocusedCellId = useLastFocusedCellId();
@@ -534,11 +534,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
534
534
  });
535
535
 
536
536
  const renderActions = () => {
537
- const iconProps = {
538
- size: 14,
539
- strokeWidth: 1.5,
540
- className: "mr-2",
541
- };
537
+ const ic = MENU_ITEM_ICON_CLASS;
542
538
  return (
543
539
  <DropdownMenuContent
544
540
  align="end"
@@ -548,7 +544,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
548
544
  >
549
545
  {!node.data.isDirectory && (
550
546
  <DropdownMenuItem onSelect={() => node.select()}>
551
- <ViewIcon {...iconProps} />
547
+ <ViewIcon className={ic} />
552
548
  Open file
553
549
  </DropdownMenuItem>
554
550
  )}
@@ -558,34 +554,34 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
558
554
  openFile({ path: node.data.path });
559
555
  }}
560
556
  >
561
- <ExternalLinkIcon {...iconProps} />
557
+ <ExternalLinkIcon className={ic} />
562
558
  Open file in external editor
563
559
  </DropdownMenuItem>
564
560
  )}
565
561
  {node.data.isDirectory && (
566
562
  <>
567
563
  <DropdownMenuItem onSelect={() => handleCreateNotebook()}>
568
- <MarimoPlusIcon {...iconProps} />
564
+ <MarimoPlusIcon className={ic} />
569
565
  Create notebook
570
566
  </DropdownMenuItem>
571
567
  <DropdownMenuItem onSelect={() => handleCreateFile()}>
572
- <FilePlus2Icon {...iconProps} />
568
+ <FilePlus2Icon className={ic} />
573
569
  Create file
574
570
  </DropdownMenuItem>
575
571
  <DropdownMenuItem onSelect={() => handleCreateFolder()}>
576
- <FolderPlusIcon {...iconProps} />
572
+ <FolderPlusIcon className={ic} />
577
573
  Create folder
578
574
  </DropdownMenuItem>
579
575
  <DropdownMenuSeparator />
580
576
  </>
581
577
  )}
582
578
  <DropdownMenuItem onSelect={() => node.edit()}>
583
- <Edit3Icon {...iconProps} />
579
+ <Edit3Icon className={ic} />
584
580
  Rename
585
581
  </DropdownMenuItem>
586
582
  {!node.data.isDirectory && (
587
583
  <DropdownMenuItem onSelect={handleDuplicate}>
588
- <CopyIcon {...iconProps} />
584
+ <CopyIcon className={ic} />
589
585
  Duplicate
590
586
  </DropdownMenuItem>
591
587
  )}
@@ -595,7 +591,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
595
591
  toast({ title: "Copied to clipboard" });
596
592
  }}
597
593
  >
598
- <ListTreeIcon {...iconProps} />
594
+ <ListTreeIcon className={ic} />
599
595
  Copy path
600
596
  </DropdownMenuItem>
601
597
  {tree && (
@@ -607,7 +603,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
607
603
  toast({ title: "Copied to clipboard" });
608
604
  }}
609
605
  >
610
- <ListTreeIcon {...iconProps} />
606
+ <ListTreeIcon className={ic} />
611
607
  Copy relative path
612
608
  </DropdownMenuItem>
613
609
  )}
@@ -620,7 +616,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
620
616
  handleInsertCode(pythonCode);
621
617
  }}
622
618
  >
623
- <BetweenHorizontalStartIcon {...iconProps} />
619
+ <BetweenHorizontalStartIcon className={ic} />
624
620
  Insert snippet for reading file
625
621
  </DropdownMenuItem>
626
622
  <DropdownMenuItem
@@ -635,7 +631,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
635
631
  await copyToClipboard(pythonCode);
636
632
  }}
637
633
  >
638
- <BracesIcon {...iconProps} />
634
+ <BracesIcon className={ic} />
639
635
  Copy snippet for reading file
640
636
  </DropdownMenuItem>
641
637
  {/* Not shown in WASM */}
@@ -643,7 +639,7 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
643
639
  <>
644
640
  <DropdownMenuSeparator />
645
641
  <DropdownMenuItem onSelect={handleOpenMarimoFile}>
646
- <PlaySquareIcon {...iconProps} />
642
+ <PlaySquareIcon className={ic} />
647
643
  Open notebook
648
644
  </DropdownMenuItem>
649
645
  </>
@@ -658,14 +654,14 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
658
654
  downloadBlob(new Blob([contents]), node.data.name);
659
655
  }}
660
656
  >
661
- <DownloadIcon {...iconProps} />
657
+ <DownloadIcon className={ic} />
662
658
  Download
663
659
  </DropdownMenuItem>
664
660
  <DropdownMenuSeparator />
665
661
  </>
666
662
  )}
667
663
  <DropdownMenuItem onSelect={handleDeleteFile} variant="danger">
668
- <Trash2Icon {...iconProps} />
664
+ <Trash2Icon className={ic} />
669
665
  Delete
670
666
  </DropdownMenuItem>
671
667
  </DropdownMenuContent>
@@ -699,7 +695,10 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
699
695
  {node.data.isMarimoFile ? (
700
696
  <MarimoIcon className="w-5 h-5 shrink-0 mr-2" strokeWidth={1.5} />
701
697
  ) : (
702
- <Icon className="w-5 h-5 shrink-0 mr-2" strokeWidth={1.5} />
698
+ <Icon
699
+ className={cn("w-5 h-5 shrink-0 mr-2", FILE_ICON_COLOR[fileType])}
700
+ strokeWidth={1.5}
701
+ />
703
702
  )}
704
703
  {node.isEditing ? (
705
704
  <Edit node={node} />
@@ -712,19 +711,10 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
712
711
  tabIndex={-1}
713
712
  onClick={(e) => e.stopPropagation()}
714
713
  >
715
- <Button
714
+ <MoreActionsButton
716
715
  data-testid="file-explorer-more-button"
717
- variant="text"
718
- tabIndex={-1}
719
- size="xs"
720
- className="mb-0"
721
- aria-label="More options"
722
- >
723
- <MoreVerticalIcon
724
- strokeWidth={2}
725
- className="w-5 h-5 hidden group-hover:block"
726
- />
727
- </Button>
716
+ iconClassName="w-5 h-5"
717
+ />
728
718
  </DropdownMenuTrigger>
729
719
  {renderActions()}
730
720
  </DropdownMenu>
@@ -735,14 +725,10 @@ const Node = ({ node, style, dragHandle }: NodeRendererProps<FileInfo>) => {
735
725
 
736
726
  const FolderArrow = ({ node }: { node: NodeApi<FileInfo> }) => {
737
727
  if (!node.data.isDirectory) {
738
- return <span className="w-5 h-5 shrink-0" />;
728
+ return <span className="w-4 h-4 shrink-0" />;
739
729
  }
740
730
 
741
- return node.isOpen ? (
742
- <ChevronDownIcon className="w-5 h-5 shrink-0" />
743
- ) : (
744
- <ChevronRightIcon className="w-5 h-5 shrink-0" />
745
- );
731
+ return <TreeChevron isExpanded={node.isOpen} className="w-4 h-4" />;
746
732
  };
747
733
 
748
734
  function openMarimoNotebook(
@@ -0,0 +1,132 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ FileAudioIcon,
5
+ FileCodeIcon,
6
+ FileIcon,
7
+ FileJsonIcon,
8
+ FileSpreadsheetIcon,
9
+ FileTextIcon,
10
+ FileVideoIcon,
11
+ FolderArchiveIcon,
12
+ FolderIcon,
13
+ ImageIcon,
14
+ type LucideIcon,
15
+ } from "lucide-react";
16
+ import { cn } from "@/utils/cn";
17
+
18
+ export type FileIconType =
19
+ | "directory"
20
+ | "python"
21
+ | "code"
22
+ | "json"
23
+ | "text"
24
+ | "image"
25
+ | "audio"
26
+ | "video"
27
+ | "data"
28
+ | "pdf"
29
+ | "zip"
30
+ | "unknown";
31
+
32
+ const EXT_TO_TYPE: Record<string, FileIconType> = {
33
+ py: "python",
34
+ // Text / docs
35
+ txt: "text",
36
+ md: "text",
37
+ qmd: "text",
38
+ log: "text",
39
+ // Images
40
+ png: "image",
41
+ jpg: "image",
42
+ jpeg: "image",
43
+ gif: "image",
44
+ svg: "image",
45
+ webp: "image",
46
+ // Data / spreadsheets
47
+ csv: "data",
48
+ parquet: "data",
49
+ arrow: "data",
50
+ xlsx: "data",
51
+ // JSON
52
+ json: "json",
53
+ // Code
54
+ js: "code",
55
+ ts: "code",
56
+ tsx: "code",
57
+ html: "code",
58
+ css: "code",
59
+ toml: "code",
60
+ yaml: "code",
61
+ yml: "code",
62
+ wasm: "code",
63
+ // Audio
64
+ mp3: "audio",
65
+ m4a: "audio",
66
+ ogg: "audio",
67
+ wav: "audio",
68
+ // Video
69
+ mp4: "video",
70
+ m4v: "video",
71
+ mpeg: "video",
72
+ webm: "video",
73
+ mkv: "video",
74
+ // PDF
75
+ pdf: "pdf",
76
+ // Archives
77
+ zip: "zip",
78
+ tar: "zip",
79
+ gz: "zip",
80
+ };
81
+
82
+ export function guessFileIconType(name: string): FileIconType {
83
+ const ext = name.split(".").pop()?.toLowerCase();
84
+ if (!ext) {
85
+ return "unknown";
86
+ }
87
+ return EXT_TO_TYPE[ext] ?? "unknown";
88
+ }
89
+
90
+ export const FILE_ICON: Record<FileIconType, LucideIcon> = {
91
+ directory: FolderIcon,
92
+ python: FileCodeIcon,
93
+ code: FileCodeIcon,
94
+ json: FileJsonIcon,
95
+ text: FileTextIcon,
96
+ image: ImageIcon,
97
+ audio: FileAudioIcon,
98
+ video: FileVideoIcon,
99
+ data: FileSpreadsheetIcon,
100
+ pdf: FileTextIcon,
101
+ zip: FolderArchiveIcon,
102
+ unknown: FileIcon,
103
+ };
104
+
105
+ export const FILE_ICON_COLOR: Record<FileIconType, string> = {
106
+ directory: "text-amber-500",
107
+ python: "text-blue-500",
108
+ code: "text-blue-500",
109
+ json: "text-blue-500",
110
+ text: "text-muted-foreground",
111
+ image: "text-purple-500",
112
+ audio: "text-orange-500",
113
+ video: "text-orange-500",
114
+ data: "text-green-500",
115
+ pdf: "text-red-500",
116
+ zip: "text-muted-foreground",
117
+ unknown: "text-muted-foreground",
118
+ };
119
+
120
+ /**
121
+ * Render a colored file-type icon for a given filename.
122
+ * Pass `className` to control size (defaults to `h-3.5 w-3.5`).
123
+ */
124
+ export function renderFileIcon(
125
+ name: string,
126
+ className?: string,
127
+ ): React.ReactNode {
128
+ const type = guessFileIconType(name);
129
+ const Icon = FILE_ICON[type];
130
+ const color = FILE_ICON_COLOR[type];
131
+ return <Icon className={cn("h-3.5 w-3.5 shrink-0", color, className)} />;
132
+ }
@@ -166,7 +166,7 @@ export const FileViewer: React.FC<Props> = ({ file, onOpenNotebook }) => {
166
166
  </Tooltip>
167
167
  <Tooltip content={renderShortcut("global.save")}>
168
168
  <Button
169
- variant={internalValue === data.contents ? "text" : "success"}
169
+ variant="text"
170
170
  size="xs"
171
171
  onClick={handleSaveFile}
172
172
  disabled={internalValue === data.contents}
@@ -0,0 +1,107 @@
1
+ /* Copyright 2026 Marimo. All rights reserved. */
2
+
3
+ import {
4
+ ChevronRightIcon,
5
+ MoreVerticalIcon,
6
+ RefreshCwIcon,
7
+ } from "lucide-react";
8
+ import React, { useCallback, useState } from "react";
9
+ import { Button } from "@/components/ui/button";
10
+ import { Tooltip } from "@/components/ui/tooltip";
11
+ import { cn } from "@/utils/cn";
12
+
13
+ /**
14
+ * Animated chevron for tree expand/collapse.
15
+ * Rotates 90° when `isExpanded` is true.
16
+ */
17
+ export const TreeChevron: React.FC<{
18
+ isExpanded: boolean;
19
+ className?: string;
20
+ }> = ({ isExpanded, className }) => (
21
+ <ChevronRightIcon
22
+ className={cn(
23
+ "shrink-0 transition-transform",
24
+ isExpanded && "rotate-90",
25
+ className,
26
+ )}
27
+ />
28
+ );
29
+
30
+ /**
31
+ * Refresh button that briefly spins its icon when clicked.
32
+ * Keeps spinning for at least 500ms, or until the onClick Promise resolves.
33
+ */
34
+ export const RefreshIconButton: React.FC<{
35
+ onClick: (e: React.MouseEvent) => void | Promise<void>;
36
+ tooltip?: string;
37
+ className?: string;
38
+ iconClassName?: string;
39
+ }> = ({ onClick, tooltip = "Refresh", className, iconClassName }) => {
40
+ const [isSpinning, setIsSpinning] = useState(false);
41
+
42
+ const handleClick = useCallback(
43
+ async (e: React.MouseEvent) => {
44
+ setIsSpinning(true);
45
+ // Artificially spin for 500ms to show the user that the button is working.
46
+ const minDelay = new Promise<void>((r) => setTimeout(r, 500));
47
+ try {
48
+ await Promise.all([onClick(e), minDelay]);
49
+ } finally {
50
+ setIsSpinning(false);
51
+ }
52
+ },
53
+ [onClick],
54
+ );
55
+
56
+ const button = (
57
+ <Button
58
+ variant="text"
59
+ size="xs"
60
+ className={className}
61
+ onClick={handleClick}
62
+ >
63
+ <RefreshCwIcon
64
+ className={cn(
65
+ "h-4 w-4",
66
+ iconClassName,
67
+ isSpinning && "animate-[spin_0.5s]",
68
+ )}
69
+ />
70
+ </Button>
71
+ );
72
+
73
+ return <Tooltip content={tooltip}>{button}</Tooltip>;
74
+ };
75
+
76
+ /**
77
+ * Three-dot menu trigger that fades in on row hover.
78
+ * Must be inside a `group` container.
79
+ * Forwards ref so it works with Radix `asChild`.
80
+ */
81
+ export const MoreActionsButton = React.forwardRef<
82
+ HTMLButtonElement,
83
+ {
84
+ onClick?: (e: React.MouseEvent) => void;
85
+ className?: string;
86
+ iconClassName?: string;
87
+ } & Omit<React.ComponentPropsWithoutRef<typeof Button>, "variant" | "size">
88
+ >(({ className, iconClassName, ...props }, ref) => (
89
+ <Button
90
+ ref={ref}
91
+ variant="text"
92
+ tabIndex={-1}
93
+ size="xs"
94
+ className={cn(
95
+ "mb-0 opacity-0 group-hover:opacity-100 transition-opacity",
96
+ className,
97
+ )}
98
+ aria-label="More options"
99
+ {...props}
100
+ >
101
+ <MoreVerticalIcon className={cn("w-4 h-4", iconClassName)} />
102
+ </Button>
103
+ ));
104
+ MoreActionsButton.displayName = "MoreActionsButton";
105
+
106
+ /** Standard class string for icons inside dropdown menu items. */
107
+ export const MENU_ITEM_ICON_CLASS = "h-3.5 w-3.5 mr-2";