@ctrl-ui/react 0.8.7 → 0.8.9

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 (341) hide show
  1. package/dist/action-bar.d.ts +15 -7
  2. package/dist/action-bar.js +39 -19
  3. package/dist/activity.js +7 -15
  4. package/dist/audio-recorder.d.ts +14 -4
  5. package/dist/audio-recorder.js +82 -29
  6. package/dist/audio-visualizer.js +1 -1
  7. package/dist/blocks/coding-agent.js +39 -39
  8. package/dist/blocks/design-canvas.d.ts +5 -1
  9. package/dist/blocks/design-canvas.js +198 -92
  10. package/dist/blocks/discussion.d.ts +28 -0
  11. package/dist/blocks/discussion.js +67 -0
  12. package/dist/blocks/file-explorer-data.d.ts +1 -0
  13. package/dist/blocks/file-explorer-data.js +4 -1
  14. package/dist/blocks/file-explorer.d.ts +9 -1
  15. package/dist/blocks/file-explorer.js +461 -192
  16. package/dist/blocks/settings.d.ts +2 -1
  17. package/dist/blocks/settings.js +101 -79
  18. package/dist/blocks/team-chat.d.ts +12 -0
  19. package/dist/blocks/team-chat.js +26 -0
  20. package/dist/blocks/theme-toggle.js +23 -31
  21. package/dist/chat-composer-attachment.d.ts +4 -1
  22. package/dist/chat-composer-attachment.js +35 -10
  23. package/dist/chat-composer-editor/extensions/mention.d.ts +5 -1
  24. package/dist/chat-composer-editor/extensions/mention.js +59 -28
  25. package/dist/chat-composer-editor/ghost.js +2 -4
  26. package/dist/chat-composer-editor/types.d.ts +5 -0
  27. package/dist/chat-composer-editor.d.ts +1 -1
  28. package/dist/chat-composer-editor.js +57 -30
  29. package/dist/chat-composer.d.ts +13 -32
  30. package/dist/chat-composer.js +52 -12
  31. package/dist/chat-layout.d.ts +19 -0
  32. package/dist/chat-layout.js +91 -40
  33. package/dist/chat-message/social.d.ts +26 -0
  34. package/dist/chat-message/social.js +122 -0
  35. package/dist/chat-message.d.ts +28 -29
  36. package/dist/chat-message.js +106 -25
  37. package/dist/context.d.ts +2 -2
  38. package/dist/context.js +38 -23
  39. package/dist/dynamic-notification-canvas-loop.d.ts +24 -0
  40. package/dist/dynamic-notification-canvas-loop.js +113 -0
  41. package/dist/dynamic-notification-glass.js +23 -114
  42. package/dist/dynamic-notification-liquid.js +14 -118
  43. package/dist/dynamic-notification.d.ts +16 -8
  44. package/dist/dynamic-notification.js +85 -22
  45. package/dist/email/email-brand.d.ts +7 -4
  46. package/dist/email/email-brand.js +9 -6
  47. package/dist/email/email-code.d.ts +3 -2
  48. package/dist/email/email-code.js +11 -9
  49. package/dist/email/email-footer.d.ts +5 -3
  50. package/dist/email/email-footer.js +11 -5
  51. package/dist/email/email.d.ts +31 -9
  52. package/dist/email/email.js +50 -26
  53. package/dist/email/templates.d.ts +10 -9
  54. package/dist/email/templates.js +102 -38
  55. package/dist/email/theme.js +1 -1
  56. package/dist/environment-variables.d.ts +4 -2
  57. package/dist/environment-variables.js +52 -32
  58. package/dist/filter-bar/editor.js +20 -8
  59. package/dist/filter-bar/model.d.ts +1 -0
  60. package/dist/filter-bar/model.js +10 -1
  61. package/dist/filter-bar/motion.js +4 -4
  62. package/dist/filter-bar/parts.js +19 -8
  63. package/dist/filter-bar/types.d.ts +1 -0
  64. package/dist/filter-bar.js +19 -13
  65. package/dist/hooks/use-audio-recorder.js +12 -6
  66. package/dist/hooks/use-chat-composer.d.ts +19 -7
  67. package/dist/hooks/use-chat-composer.js +32 -5
  68. package/dist/hooks/use-chat-message.d.ts +28 -2
  69. package/dist/hooks/use-chat-message.js +41 -10
  70. package/dist/hooks/use-chat-thread-scroll.js +43 -7
  71. package/dist/hooks/use-color-area.d.ts +3 -3
  72. package/dist/hooks/use-color-area.js +13 -12
  73. package/dist/hooks/use-copy-to-clipboard.d.ts +2 -1
  74. package/dist/hooks/use-copy-to-clipboard.js +20 -20
  75. package/dist/hooks/use-dynamic-notification.d.ts +6 -2
  76. package/dist/hooks/use-dynamic-notification.js +26 -10
  77. package/dist/hooks/use-mobile.d.ts +3 -2
  78. package/dist/hooks/use-mobile.js +8 -4
  79. package/dist/hooks/use-textarea-trigger-menu.js +2 -1
  80. package/dist/hooks/use-trigger-menu.d.ts +54 -0
  81. package/dist/hooks/use-trigger-menu.js +102 -47
  82. package/dist/hooks/use-user-ask.d.ts +2 -0
  83. package/dist/hooks/use-user-ask.js +35 -41
  84. package/dist/inline-attachment.d.ts +6 -2
  85. package/dist/inline-attachment.js +36 -15
  86. package/dist/inline-citation.d.ts +12 -5
  87. package/dist/inline-citation.js +25 -16
  88. package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
  89. package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
  90. package/dist/knob-contracts/audio-visualizer-knobs.d.ts +1 -1
  91. package/dist/knob-contracts/audio-visualizer-knobs.js +2 -1
  92. package/dist/knob-contracts/avatar-knobs.d.ts +1 -1
  93. package/dist/knob-contracts/avatar-knobs.js +6 -1
  94. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  95. package/dist/knob-contracts/badge-knobs.js +2 -1
  96. package/dist/knob-contracts/breadcrumb-knobs.d.ts +4 -0
  97. package/dist/knob-contracts/breadcrumb-knobs.js +8 -0
  98. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  99. package/dist/knob-contracts/button-knobs.js +1 -0
  100. package/dist/knob-contracts/chat-message-knobs.d.ts +1 -1
  101. package/dist/knob-contracts/chat-message-knobs.js +8 -1
  102. package/dist/knob-contracts/code-diff-knobs.d.ts +1 -1
  103. package/dist/knob-contracts/code-diff-knobs.js +2 -0
  104. package/dist/knob-contracts/code-knobs.d.ts +1 -1
  105. package/dist/knob-contracts/code-knobs.js +2 -1
  106. package/dist/knob-contracts/dynamic-notification-knobs.d.ts +1 -1
  107. package/dist/knob-contracts/dynamic-notification-knobs.js +2 -0
  108. package/dist/knob-contracts/emoji-picker-knobs.d.ts +4 -0
  109. package/dist/knob-contracts/emoji-picker-knobs.js +10 -0
  110. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  111. package/dist/knob-contracts/field-knobs.js +5 -1
  112. package/dist/knob-contracts/filter-bar-knobs.d.ts +1 -1
  113. package/dist/knob-contracts/filter-bar-knobs.js +1 -2
  114. package/dist/knob-contracts/markdown-knobs.d.ts +1 -1
  115. package/dist/knob-contracts/markdown-knobs.js +3 -1
  116. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  117. package/dist/knob-contracts/page-layout-knobs.js +2 -1
  118. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  119. package/dist/knob-contracts/popup-knobs.js +27 -0
  120. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  121. package/dist/knob-contracts/range-knobs.js +1 -0
  122. package/dist/knob-contracts/rich-tooltip-knobs.d.ts +1 -1
  123. package/dist/knob-contracts/rich-tooltip-knobs.js +2 -1
  124. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  125. package/dist/knob-contracts/sidebar-knobs.js +27 -2
  126. package/dist/knob-contracts/spinner-knobs.d.ts +1 -1
  127. package/dist/knob-contracts/spinner-knobs.js +1 -1
  128. package/dist/knob-contracts/stepper-knobs.d.ts +1 -1
  129. package/dist/knob-contracts/stepper-knobs.js +4 -1
  130. package/dist/knob-contracts/switch-knobs.d.ts +1 -1
  131. package/dist/knob-contracts/switch-knobs.js +5 -0
  132. package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
  133. package/dist/knob-contracts/table-of-contents-knobs.js +2 -1
  134. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  135. package/dist/knob-contracts/tabs-knobs.js +1 -0
  136. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  137. package/dist/knob-contracts/tree-knobs.js +2 -1
  138. package/dist/lib/cn.js +0 -4
  139. package/dist/lib/contrast.d.ts +1 -0
  140. package/dist/lib/contrast.js +17 -5
  141. package/dist/lib/dropzone-validation.js +41 -28
  142. package/dist/lib/env-file.d.ts +1 -1
  143. package/dist/lib/env-file.js +5 -5
  144. package/dist/lib/motion.d.ts +4 -0
  145. package/dist/lib/motion.js +15 -0
  146. package/dist/lib/trigger-detect.js +6 -8
  147. package/dist/markdown-block.js +2 -1
  148. package/dist/popup-parts.d.ts +8 -0
  149. package/dist/popup-parts.js +43 -0
  150. package/dist/source-badge.js +3 -1
  151. package/dist/styles/code.css +0 -2
  152. package/dist/styles/effects.css +6 -32
  153. package/dist/styles/index.css +10 -5
  154. package/dist/styles/recipes/accordion.css +9 -6
  155. package/dist/styles/recipes/action-bar.css +0 -15
  156. package/dist/styles/recipes/activity.css +5 -11
  157. package/dist/styles/recipes/alert.css +4 -4
  158. package/dist/styles/recipes/audio-recorder.css +15 -3
  159. package/dist/styles/recipes/audio-visualizer.css +13 -2
  160. package/dist/styles/recipes/avatar.css +78 -1
  161. package/dist/styles/recipes/badge.css +14 -4
  162. package/dist/styles/recipes/breadcrumb.css +77 -0
  163. package/dist/styles/recipes/button-group.css +5 -7
  164. package/dist/styles/recipes/button-states.css +66 -0
  165. package/dist/styles/recipes/button.css +24 -83
  166. package/dist/styles/recipes/calendar.css +17 -23
  167. package/dist/styles/recipes/card.css +2 -2
  168. package/dist/styles/recipes/chat-composer-attachment.css +9 -3
  169. package/dist/styles/recipes/chat-composer.css +7 -10
  170. package/dist/styles/recipes/chat-layout.css +35 -0
  171. package/dist/styles/recipes/chat-message-social.css +123 -0
  172. package/dist/styles/recipes/chat-message.css +157 -37
  173. package/dist/styles/recipes/choice.css +18 -21
  174. package/dist/styles/recipes/code-diff.css +46 -33
  175. package/dist/styles/recipes/code.css +36 -16
  176. package/dist/styles/recipes/collapsible.css +7 -2
  177. package/dist/styles/recipes/color-picker-details.css +3 -4
  178. package/dist/styles/recipes/color-picker-values.css +9 -8
  179. package/dist/styles/recipes/color-picker.css +11 -2
  180. package/dist/styles/recipes/context.css +3 -3
  181. package/dist/styles/recipes/dockable-panel.css +7 -4
  182. package/dist/styles/recipes/dropzone.css +7 -8
  183. package/dist/styles/recipes/dynamic-notification-input.css +8 -5
  184. package/dist/styles/recipes/dynamic-notification-motion.css +12 -21
  185. package/dist/styles/recipes/dynamic-notification.css +40 -26
  186. package/dist/styles/recipes/emoji-picker.css +132 -0
  187. package/dist/styles/recipes/empty.css +5 -4
  188. package/dist/styles/recipes/environment-variables.css +4 -15
  189. package/dist/styles/recipes/field-parts.css +30 -24
  190. package/dist/styles/recipes/field.css +61 -26
  191. package/dist/styles/recipes/filter-bar.css +18 -31
  192. package/dist/styles/recipes/gradient-editor.css +18 -7
  193. package/dist/styles/recipes/infinite-canvas.css +0 -1
  194. package/dist/styles/recipes/inline-attachment.css +3 -3
  195. package/dist/styles/recipes/inline-citation.css +10 -4
  196. package/dist/styles/recipes/item.css +23 -9
  197. package/dist/styles/recipes/markdown-block.css +5 -4
  198. package/dist/styles/recipes/markdown.css +68 -26
  199. package/dist/styles/recipes/morphing-panel.css +8 -8
  200. package/dist/styles/recipes/page-layout.css +20 -12
  201. package/dist/styles/recipes/pagination.css +4 -5
  202. package/dist/styles/recipes/phone-input.css +12 -8
  203. package/dist/styles/recipes/popup-controls.css +166 -33
  204. package/dist/styles/recipes/popup-drawer.css +3 -14
  205. package/dist/styles/recipes/popup-lists.css +128 -27
  206. package/dist/styles/recipes/popup-motion.css +4 -45
  207. package/dist/styles/recipes/popup-toast.css +55 -26
  208. package/dist/styles/recipes/popup.css +139 -141
  209. package/dist/styles/recipes/range.css +54 -18
  210. package/dist/styles/recipes/resizable.css +2 -7
  211. package/dist/styles/recipes/rich-tooltip.css +24 -34
  212. package/dist/styles/recipes/scroll-area.css +8 -1
  213. package/dist/styles/recipes/sidebar-menu.css +166 -34
  214. package/dist/styles/recipes/sidebar-parts.css +192 -0
  215. package/dist/styles/recipes/sidebar.css +188 -128
  216. package/dist/styles/recipes/source-badge.css +1 -1
  217. package/dist/styles/recipes/spinner.css +8 -1
  218. package/dist/styles/recipes/stepper.css +31 -13
  219. package/dist/styles/recipes/switch.css +68 -23
  220. package/dist/styles/recipes/table-of-contents.css +9 -1
  221. package/dist/styles/recipes/table.css +14 -10
  222. package/dist/styles/recipes/tabs.css +17 -12
  223. package/dist/styles/recipes/task-list.css +7 -14
  224. package/dist/styles/recipes/thread-rail.css +42 -19
  225. package/dist/styles/recipes/timeline.css +2 -0
  226. package/dist/styles/recipes/toolbar.css +27 -25
  227. package/dist/styles/recipes/track-highlight.css +10 -7
  228. package/dist/styles/recipes/tree.css +31 -4
  229. package/dist/styles/recipes/user-ask-details.css +0 -1
  230. package/dist/styles/recipes/user-ask.css +4 -4
  231. package/dist/styles/theme.css +37 -16
  232. package/dist/task-list.d.ts +4 -1
  233. package/dist/task-list.js +24 -8
  234. package/dist/thread-rail.d.ts +1 -1
  235. package/dist/thread-rail.js +61 -19
  236. package/dist/transcript-divider.js +2 -1
  237. package/dist/ui/alert.d.ts +3 -1
  238. package/dist/ui/alert.js +14 -8
  239. package/dist/ui/autocomplete.js +3 -1
  240. package/dist/ui/avatar.d.ts +8 -0
  241. package/dist/ui/avatar.js +23 -3
  242. package/dist/ui/badge.js +1 -1
  243. package/dist/ui/breadcrumb.d.ts +27 -0
  244. package/dist/ui/breadcrumb.js +88 -0
  245. package/dist/ui/button.d.ts +2 -1
  246. package/dist/ui/button.js +1 -1
  247. package/dist/ui/calendar.js +2 -2
  248. package/dist/ui/card.d.ts +3 -1
  249. package/dist/ui/card.js +12 -7
  250. package/dist/ui/checkbox.d.ts +2 -0
  251. package/dist/ui/code-diff.d.ts +1 -1
  252. package/dist/ui/code-diff.js +71 -27
  253. package/dist/ui/code.d.ts +5 -3
  254. package/dist/ui/code.js +26 -27
  255. package/dist/ui/color-picker.d.ts +5 -2
  256. package/dist/ui/color-picker.js +84 -36
  257. package/dist/ui/combobox-disabled-selection.d.ts +1 -2
  258. package/dist/ui/combobox-disabled-selection.js +7 -9
  259. package/dist/ui/combobox.js +13 -5
  260. package/dist/ui/command.d.ts +8 -2
  261. package/dist/ui/command.js +30 -14
  262. package/dist/ui/context-menu.js +3 -0
  263. package/dist/ui/dialog.d.ts +2 -1
  264. package/dist/ui/dialog.js +3 -22
  265. package/dist/ui/dockable-panel-geometry.d.ts +1 -0
  266. package/dist/ui/dockable-panel-geometry.js +7 -1
  267. package/dist/ui/dockable-panel.d.ts +9 -6
  268. package/dist/ui/dockable-panel.js +63 -23
  269. package/dist/ui/dropdown-menu.js +4 -3
  270. package/dist/ui/dropzone.d.ts +2 -2
  271. package/dist/ui/dropzone.js +75 -16
  272. package/dist/ui/emoji-picker.d.ts +18 -0
  273. package/dist/ui/emoji-picker.js +136 -0
  274. package/dist/ui/empty.d.ts +3 -1
  275. package/dist/ui/empty.js +12 -7
  276. package/dist/ui/gradient-editor.d.ts +1 -1
  277. package/dist/ui/gradient-editor.js +37 -21
  278. package/dist/ui/infinite-canvas.d.ts +3 -2
  279. package/dist/ui/infinite-canvas.js +63 -19
  280. package/dist/ui/input-group.d.ts +0 -3
  281. package/dist/ui/input-group.js +3 -13
  282. package/dist/ui/input-otp.d.ts +1 -1
  283. package/dist/ui/input-otp.js +32 -15
  284. package/dist/ui/input.js +4 -4
  285. package/dist/ui/item.d.ts +2 -1
  286. package/dist/ui/item.js +11 -7
  287. package/dist/ui/live-status.d.ts +7 -0
  288. package/dist/ui/live-status.js +17 -0
  289. package/dist/ui/markdown-elements.d.ts +6 -0
  290. package/dist/ui/markdown-elements.js +31 -1
  291. package/dist/ui/menubar.js +2 -2
  292. package/dist/ui/morphing-panel.js +2 -1
  293. package/dist/ui/native-select.d.ts +1 -1
  294. package/dist/ui/native-select.js +24 -11
  295. package/dist/ui/page-layout.d.ts +2 -2
  296. package/dist/ui/page-layout.js +2 -4
  297. package/dist/ui/pagination.d.ts +7 -5
  298. package/dist/ui/pagination.js +19 -13
  299. package/dist/ui/phone-input.d.ts +1 -0
  300. package/dist/ui/phone-input.js +56 -58
  301. package/dist/ui/radio-group.d.ts +2 -0
  302. package/dist/ui/resizable.d.ts +3 -2
  303. package/dist/ui/resizable.js +5 -3
  304. package/dist/ui/responsive-dialog.d.ts +1 -1
  305. package/dist/ui/responsive-dialog.js +4 -21
  306. package/dist/ui/rich-tooltip.d.ts +10 -4
  307. package/dist/ui/rich-tooltip.js +81 -33
  308. package/dist/ui/scroll-area.d.ts +1 -1
  309. package/dist/ui/scroll-area.js +43 -28
  310. package/dist/ui/sidebar-menu.d.ts +5 -1
  311. package/dist/ui/sidebar-menu.js +34 -34
  312. package/dist/ui/sidebar-provider.d.ts +10 -1
  313. package/dist/ui/sidebar-provider.js +38 -13
  314. package/dist/ui/sidebar-resize-rail.js +7 -3
  315. package/dist/ui/sidebar.d.ts +11 -6
  316. package/dist/ui/sidebar.js +48 -24
  317. package/dist/ui/slider.js +31 -22
  318. package/dist/ui/spinner.d.ts +3 -2
  319. package/dist/ui/spinner.js +3 -2
  320. package/dist/ui/stepper.d.ts +8 -1
  321. package/dist/ui/stepper.js +74 -25
  322. package/dist/ui/switch.d.ts +1 -0
  323. package/dist/ui/table-of-contents.d.ts +2 -2
  324. package/dist/ui/table-of-contents.js +20 -8
  325. package/dist/ui/table.js +4 -2
  326. package/dist/ui/textarea.d.ts +1 -1
  327. package/dist/ui/textarea.js +20 -9
  328. package/dist/ui/timeline.d.ts +3 -1
  329. package/dist/ui/timeline.js +17 -9
  330. package/dist/ui/toast.d.ts +11 -5
  331. package/dist/ui/toast.js +100 -54
  332. package/dist/ui/toolbar.d.ts +2 -1
  333. package/dist/ui/toolbar.js +3 -1
  334. package/dist/ui/tooltip.js +3 -20
  335. package/dist/ui/tree.js +120 -80
  336. package/dist/ui/trigger-menu.js +1 -0
  337. package/dist/user-ask.d.ts +10 -5
  338. package/dist/user-ask.js +31 -11
  339. package/package.json +5 -1
  340. package/dist/hooks/use-keyboard-navigation.d.ts +0 -3
  341. package/dist/hooks/use-keyboard-navigation.js +0 -22
@@ -1,25 +1,35 @@
1
1
  "use client";
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
+ import { LiveStatus } from "../ui/live-status.js";
4
5
  import { ScrollArea } from "../ui/scroll-area.js";
5
- import { InputGroup, InputGroupAddon, InputGroupInput } from "../ui/input-group.js";
6
- import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
6
+ import { InputGroup, InputGroupAddon } from "../ui/input-group.js";
7
+ import { Input } from "../ui/input.js";
8
+ import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "../ui/empty.js";
7
9
  import { SidebarProvider, useSidebar } from "../ui/sidebar-provider.js";
8
10
  import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar-menu.js";
9
11
  import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarRail, SidebarTrigger } from "../ui/sidebar.js";
12
+ import { Drawer, DrawerBody, DrawerContent, DrawerTitle, DrawerTrigger } from "../ui/drawer.js";
10
13
  import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from "../ui/resizable.js";
11
- import { groupFileExplorerItems, resolveFileExplorer, searchFileExplorer } from "./file-explorer-data.js";
12
- import { useState } from "react";
14
+ import { groupFileExplorerItems, orderFileExplorerEntries, resolveFileExplorer, searchFileExplorer } from "./file-explorer-data.js";
15
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../ui/dropdown-menu.js";
16
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../ui/breadcrumb.js";
17
+ import { Fragment, useEffect, useId, useRef, useState } from "react";
13
18
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
- import { ChevronRightIcon, FileIcon, FolderIcon, SearchIcon, XIcon } from "lucide-react";
19
+ import { ChevronLeftIcon, ChevronRightIcon, FileIcon, FolderIcon, MoreHorizontalIcon, SearchIcon, XIcon } from "lucide-react";
15
20
  //#region src/blocks/file-explorer.tsx
16
- function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationId, selectedPath, defaultSelectedPath = [], searchValue, defaultSearchValue = "", onActiveLocationChange, onSelectedPathChange, onSearchValueChange, onOpenEntry, sidebarTop, sidebarFooter, headerActions, searchPlaceholder = "Search files", layout = "viewport", className, style, ...props }) {
21
+ function defaultSearchStatusLabel(count, query) {
22
+ if (count === 0) return `No results for “${query}”`;
23
+ return count === 1 ? "1 result" : `${count} results`;
24
+ }
25
+ function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationId, selectedPath, defaultSelectedPath = [], searchValue, defaultSearchValue = "", onActiveLocationChange, onSelectedPathChange, onSearchValueChange, onOpenEntry, sidebarTop, sidebarFooter, headerActions, headerMenuItems, searchPlaceholder = "Search files", searchLabel = "Search files", clearSearchLabel = "Clear search", searchResultsLabel = "Search results", searchStatusLabel = defaultSearchStatusLabel, backLabel = "Go back", moreActionsLabel = "More actions", detailsLabel = "Details", layout = "viewport", className, style, ...props }) {
17
26
  const [internalLocationId, setInternalLocationId] = useState(defaultActiveLocationId ?? locations[0]?.id);
18
27
  const [internalSelectedPath, setInternalSelectedPath] = useState(defaultSelectedPath);
19
28
  const [internalSearchValue, setInternalSearchValue] = useState(defaultSearchValue);
20
29
  const currentLocationId = activeLocationId ?? internalLocationId;
21
30
  const currentPath = selectedPath ?? internalSelectedPath;
22
31
  const query = searchValue ?? internalSearchValue;
32
+ const trimmedQuery = query.trim();
23
33
  const activeLocation = locations.find((location) => location.id === currentLocationId) ?? locations[0];
24
34
  const contained = layout === "contained";
25
35
  function changePath(path, entry) {
@@ -43,9 +53,7 @@ function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationI
43
53
  });
44
54
  const resolution = resolveFileExplorer(activeLocation, currentPath);
45
55
  const searchResults = searchFileExplorer(activeLocation, query);
46
- function selectEntry(entry, columnIndex) {
47
- changePath([...resolution.validPath.slice(0, columnIndex), entry.id], entry);
48
- }
56
+ const searchStatus = trimmedQuery ? searchStatusLabel(searchResults.length, trimmedQuery) : "";
49
57
  function selectSearchResult(result) {
50
58
  changePath(result.path, result.entry);
51
59
  changeQuery("");
@@ -58,50 +66,62 @@ function FileExplorerBlock({ locations, activeLocationId, defaultActiveLocationI
58
66
  ...style
59
67
  };
60
68
  return /* @__PURE__ */ jsxs(SidebarProvider, {
61
- className: cn("overflow-hidden bg-background text-foreground", contained ? "relative h-full min-h-0" : "h-svh min-h-svh", className),
69
+ layout,
70
+ className: cn("bg-background text-foreground", !contained && "h-svh overflow-hidden", className),
62
71
  style: providerStyle,
63
72
  ...props,
64
73
  children: [/* @__PURE__ */ jsx(FileExplorerSidebar, {
65
74
  locations,
66
75
  activeLocationId: activeLocation.id,
67
- contained,
68
76
  sidebarTop,
69
77
  sidebarFooter,
70
78
  onLocationSelect: changeLocation
71
79
  }), /* @__PURE__ */ jsxs(SidebarInset, {
72
- className: "h-full min-h-0 min-w-0 overflow-hidden",
80
+ className: "@container/explorer h-full min-h-0 min-w-0 overflow-hidden",
73
81
  children: [
74
82
  /* @__PURE__ */ jsx(FileExplorerHeader, {
75
83
  location: activeLocation,
84
+ headingAs: contained ? "h2" : "h1",
76
85
  breadcrumbs: resolution.breadcrumbs,
77
86
  query,
78
87
  searchPlaceholder,
88
+ searchLabel,
89
+ clearSearchLabel,
90
+ backLabel,
91
+ moreActionsLabel,
79
92
  canGoBack: resolution.validPath.length > 0,
80
93
  headerActions,
94
+ headerMenuItems,
81
95
  onBack: () => changePath(resolution.validPath.slice(0, -1), resolution.breadcrumbs.at(-2)?.entry),
82
96
  onBreadcrumbSelect: (breadcrumb) => changePath(breadcrumb.path, breadcrumb.entry),
83
97
  onQueryChange: changeQuery
84
98
  }),
85
99
  /* @__PURE__ */ jsx("div", {
86
100
  className: "min-h-0 flex-1 overflow-hidden",
87
- children: query.trim() ? /* @__PURE__ */ jsx(FileExplorerSearchResults, {
88
- query,
101
+ children: trimmedQuery ? /* @__PURE__ */ jsx(FileExplorerSearchResults, {
102
+ label: searchResultsLabel,
103
+ status: searchStatus,
104
+ clearSearchLabel,
89
105
  results: searchResults,
90
106
  onSelect: selectSearchResult,
91
- onOpen: openEntry
107
+ onOpen: openEntry,
108
+ onClear: () => changeQuery("")
92
109
  }) : /* @__PURE__ */ jsx(FileExplorerColumns, {
93
110
  columns: resolution.columns,
94
111
  selectedEntry: resolution.selectedEntry,
95
112
  selectedPath: resolution.validPath,
96
- onSelect: selectEntry,
113
+ headingAs: contained ? "h3" : "h2",
114
+ backLabel,
115
+ detailsLabel,
116
+ onNavigate: changePath,
97
117
  onOpen: openEntry
98
118
  })
99
119
  }),
100
120
  /* @__PURE__ */ jsx(FileExplorerStatusBar, {
101
121
  breadcrumbs: resolution.breadcrumbs,
102
- selectedEntry: resolution.selectedEntry,
103
- onBreadcrumbSelect: (breadcrumb) => changePath(breadcrumb.path, breadcrumb.entry)
104
- })
122
+ selectedEntry: resolution.selectedEntry
123
+ }),
124
+ /* @__PURE__ */ jsx(LiveStatus, { message: searchStatus })
105
125
  ]
106
126
  })]
107
127
  });
@@ -117,30 +137,25 @@ function FileExplorerUnavailable({ className, ...props }) {
117
137
  ] }) })
118
138
  });
119
139
  }
120
- function FileExplorerSidebar({ locations, activeLocationId, contained, sidebarTop, sidebarFooter, onLocationSelect }) {
140
+ function FileExplorerSidebar({ locations, activeLocationId, sidebarTop, sidebarFooter, onLocationSelect }) {
121
141
  const { setOpenMobile } = useSidebar();
122
142
  const groups = groupFileExplorerItems(locations);
123
143
  return /* @__PURE__ */ jsxs(Sidebar, {
124
144
  collapsible: "offcanvas",
125
- className: contained ? "absolute! inset-y-0! h-full border-r border-sidebar-border" : void 0,
126
145
  children: [
127
146
  sidebarTop ? /* @__PURE__ */ jsx(SidebarHeader, {
128
147
  className: "min-h-11 justify-center border-b border-sidebar-border px-3",
129
148
  children: sidebarTop
130
149
  }) : null,
131
- /* @__PURE__ */ jsx(SidebarContent, { children: groups.map((group) => /* @__PURE__ */ jsxs(SidebarGroup, { children: [group.label ? /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group.label }) : null, /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((location) => {
132
- const isActive = location.id === activeLocationId;
133
- return /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
134
- size: "sm",
135
- isActive,
136
- "aria-current": isActive ? "location" : void 0,
137
- onClick: () => {
138
- onLocationSelect(location);
139
- setOpenMobile(false);
140
- },
141
- children: [location.icon ?? /* @__PURE__ */ jsx(FolderIcon, {}), /* @__PURE__ */ jsx("span", { children: location.label })]
142
- }) }, location.id);
143
- }) })] }, group.label || "locations")) }),
150
+ /* @__PURE__ */ jsx(SidebarContent, { children: groups.map((group) => /* @__PURE__ */ jsxs(SidebarGroup, { children: [group.label ? /* @__PURE__ */ jsx(SidebarGroupLabel, { children: group.label }) : null, /* @__PURE__ */ jsx(SidebarMenu, { children: group.items.map((location) => /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(SidebarMenuButton, {
151
+ size: "sm",
152
+ isActive: location.id === activeLocationId,
153
+ onClick: () => {
154
+ onLocationSelect(location);
155
+ setOpenMobile(false);
156
+ },
157
+ children: [location.icon ?? /* @__PURE__ */ jsx(FolderIcon, {}), /* @__PURE__ */ jsx("span", { children: location.label })]
158
+ }) }, location.id)) })] }, group.label || "locations")) }),
144
159
  sidebarFooter ? /* @__PURE__ */ jsx(SidebarFooter, {
145
160
  className: "border-t border-sidebar-border",
146
161
  children: sidebarFooter
@@ -149,7 +164,7 @@ function FileExplorerSidebar({ locations, activeLocationId, contained, sidebarTo
149
164
  ]
150
165
  });
151
166
  }
152
- function FileExplorerHeader({ location, breadcrumbs, query, searchPlaceholder, canGoBack, headerActions, onBack, onBreadcrumbSelect, onQueryChange }) {
167
+ function FileExplorerHeader({ location, headingAs: Heading, breadcrumbs, query, searchPlaceholder, searchLabel, clearSearchLabel, backLabel, moreActionsLabel, canGoBack, headerActions, headerMenuItems, onBack, onBreadcrumbSelect, onQueryChange }) {
153
168
  return /* @__PURE__ */ jsxs("header", {
154
169
  className: "shrink-0 border-b border-border/70 bg-card/35",
155
170
  children: [/* @__PURE__ */ jsxs("div", {
@@ -159,33 +174,52 @@ function FileExplorerHeader({ location, breadcrumbs, query, searchPlaceholder, c
159
174
  /* @__PURE__ */ jsx(Button, {
160
175
  variant: "ghost",
161
176
  size: "xs",
177
+ className: "hidden @xl/explorer:inline-flex",
162
178
  disabled: !canGoBack,
163
- "aria-label": "Go back",
179
+ "aria-label": backLabel,
164
180
  onClick: onBack,
165
- children: /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-4 rotate-180" })
181
+ children: /* @__PURE__ */ jsx(ChevronLeftIcon, {
182
+ className: "size-4",
183
+ "data-icon-dir": "inline"
184
+ })
166
185
  }),
167
- /* @__PURE__ */ jsx("h1", {
186
+ /* @__PURE__ */ jsx(Heading, {
168
187
  className: "min-w-0 flex-1 truncate text-label font-semibold",
188
+ title: location.label,
169
189
  children: location.label
170
190
  }),
171
191
  headerActions ? /* @__PURE__ */ jsx("div", {
172
- className: "hidden shrink-0 items-center gap-1 sm:flex",
192
+ className: cn("shrink-0 items-center gap-1", headerMenuItems ? "hidden @2xl/explorer:flex" : "flex"),
173
193
  children: headerActions
174
194
  }) : null,
195
+ headerMenuItems ? /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsx(DropdownMenuTrigger, {
196
+ variant: "ghost",
197
+ size: "xs",
198
+ iconOnly: true,
199
+ "aria-label": moreActionsLabel,
200
+ className: headerActions ? "@2xl/explorer:hidden" : void 0,
201
+ children: /* @__PURE__ */ jsx(MoreHorizontalIcon, {
202
+ className: "size-3.5",
203
+ "aria-hidden": "true"
204
+ })
205
+ }), /* @__PURE__ */ jsx(DropdownMenuContent, {
206
+ align: "end",
207
+ children: headerMenuItems
208
+ })] }) : null,
175
209
  searchPlaceholder ? /* @__PURE__ */ jsxs(InputGroup, {
176
210
  size: "sm",
177
- className: "w-36 bg-background/60 md:w-48",
211
+ className: "flex-[0_1_9rem] bg-background/60 @xl/explorer:flex-[0_0_12rem]",
178
212
  children: [
179
213
  /* @__PURE__ */ jsx(InputGroupAddon, {
180
- className: "pl-2",
214
+ className: "ps-2",
181
215
  children: /* @__PURE__ */ jsx(SearchIcon, {
182
216
  className: "size-3.5",
183
217
  "aria-hidden": "true"
184
218
  })
185
219
  }),
186
- /* @__PURE__ */ jsx(InputGroupInput, {
220
+ /* @__PURE__ */ jsx(Input, {
187
221
  type: "search",
188
- "aria-label": searchPlaceholder,
222
+ "aria-label": searchLabel,
189
223
  placeholder: searchPlaceholder,
190
224
  value: query,
191
225
  onChange: (event) => onQueryChange(event.currentTarget.value)
@@ -194,8 +228,8 @@ function FileExplorerHeader({ location, breadcrumbs, query, searchPlaceholder, c
194
228
  variant: "ghost",
195
229
  size: "xs",
196
230
  iconOnly: true,
197
- className: "mr-1",
198
- "aria-label": "Clear search",
231
+ className: "me-1",
232
+ "aria-label": clearSearchLabel,
199
233
  onClick: () => onQueryChange(""),
200
234
  children: /* @__PURE__ */ jsx(XIcon, { className: "size-3.5" })
201
235
  }) : null
@@ -204,44 +238,218 @@ function FileExplorerHeader({ location, breadcrumbs, query, searchPlaceholder, c
204
238
  ]
205
239
  }), /* @__PURE__ */ jsx("div", {
206
240
  className: "px-3 pb-2",
207
- children: /* @__PURE__ */ jsx(FileExplorerBreadcrumbs, {
241
+ children: /* @__PURE__ */ jsx(FileExplorerAddressBar, {
208
242
  breadcrumbs,
209
- onSelect: onBreadcrumbSelect,
210
- variant: "address"
243
+ onSelect: onBreadcrumbSelect
211
244
  })
212
245
  })]
213
246
  });
214
247
  }
215
- function FileExplorerColumns({ columns, selectedEntry, selectedPath, onSelect, onOpen }) {
248
+ function FileExplorerColumns({ columns, selectedEntry, selectedPath, headingAs, backLabel, detailsLabel, onNavigate, onOpen }) {
249
+ const rootRef = useRef(null);
250
+ const optionElements = useRef(/* @__PURE__ */ new Map());
251
+ const pendingFocus = useRef(null);
252
+ const restoreFocus = useRef(false);
253
+ const backRef = useRef(null);
254
+ const [narrowRef, narrowDisplayed] = useDisplayed();
255
+ const [wideRef, wideDisplayed] = useDisplayed();
256
+ const [pinned, setPinned] = useState(null);
257
+ const lastIndex = columns.length - 1;
258
+ const activeIndex = pinned?.pathKey === JSON.stringify(selectedPath) ? Math.min(pinned.columnIndex, lastIndex) : lastIndex;
259
+ const activeColumn = columns[activeIndex];
260
+ const parentColumn = activeIndex > 0 ? columns[activeIndex - 1] : void 0;
216
261
  const paneCount = columns.length + 1;
217
262
  const columnSize = `${Math.max(18, Math.floor(68 / paneCount))}%`;
218
- return /* @__PURE__ */ jsxs(ResizablePanelGroup, {
219
- orientation: "horizontal",
220
- className: "rounded-none border-0 bg-card shadow-none",
221
- children: [columns.map((column, columnIndex) => /* @__PURE__ */ jsx(FileExplorerPanel, {
222
- defaultSize: columnSize,
223
- minSize: "16%",
224
- children: /* @__PURE__ */ jsx(FileExplorerColumnView, {
225
- column,
226
- columnIndex,
227
- onSelect,
228
- onOpen: (entry) => onOpen(entry, [...selectedPath.slice(0, columnIndex), entry.id])
229
- })
230
- }, column.id)), /* @__PURE__ */ jsx(ResizablePanel, {
231
- defaultSize: "32%",
232
- minSize: "20%",
233
- children: /* @__PURE__ */ jsx(FileExplorerPreview, { entry: selectedEntry })
263
+ useEffect(() => {
264
+ const target = pendingFocus.current;
265
+ if (target && focusOption(target)) pendingFocus.current = null;
266
+ if (!restoreFocus.current) return;
267
+ restoreFocus.current = false;
268
+ if (rootRef.current?.contains(document.activeElement)) return;
269
+ ([...optionElements.current.values()].find((option) => option.tabIndex === 0 && option.checkVisibility()) ?? backRef.current)?.focus();
270
+ });
271
+ function focusOption(target) {
272
+ for (const layout of ["narrow", "wide"]) {
273
+ const option = optionElements.current.get(`${layout}:${target.columnIndex}:${target.entryId}`);
274
+ if (option?.checkVisibility()) {
275
+ option.focus();
276
+ return true;
277
+ }
278
+ }
279
+ return false;
280
+ }
281
+ function moveTo(columnIndex, entry, path) {
282
+ onNavigate(path, entry);
283
+ setPinned({
284
+ pathKey: JSON.stringify(path),
285
+ columnIndex
286
+ });
287
+ const target = {
288
+ columnIndex,
289
+ entryId: entry.id
290
+ };
291
+ pendingFocus.current = focusOption(target) ? null : target;
292
+ }
293
+ function moveToParent(columnIndex) {
294
+ const parentId = selectedPath[columnIndex - 1];
295
+ const parent = columns[columnIndex - 1]?.entries.find((entry) => entry.id === parentId);
296
+ if (parent) moveTo(columnIndex - 1, parent, selectedPath.slice(0, columnIndex));
297
+ }
298
+ function select(columnIndex, entry) {
299
+ setPinned(null);
300
+ restoreFocus.current = true;
301
+ onNavigate([...selectedPath.slice(0, columnIndex), entry.id], entry);
302
+ }
303
+ function moveAlongInlineAxis(event, columnIndex, entry) {
304
+ if (!(event.key === "ArrowRight" !== (getComputedStyle(event.currentTarget).direction === "rtl"))) {
305
+ moveToParent(columnIndex);
306
+ return;
307
+ }
308
+ const child = entry.kind === "folder" ? orderFileExplorerEntries(entry.children ?? [])[0] : void 0;
309
+ if (child) moveTo(columnIndex + 1, child, [
310
+ ...selectedPath.slice(0, columnIndex),
311
+ entry.id,
312
+ child.id
313
+ ]);
314
+ }
315
+ function handleOptionKeyDown(event, columnIndex, entry) {
316
+ const ordered = orderFileExplorerEntries(columns[columnIndex]?.entries ?? []);
317
+ const index = ordered.indexOf(entry);
318
+ const base = selectedPath.slice(0, columnIndex);
319
+ let target;
320
+ switch (event.key) {
321
+ case "ArrowDown":
322
+ target = ordered[index + 1];
323
+ break;
324
+ case "ArrowUp":
325
+ target = ordered[index - 1];
326
+ break;
327
+ case "Home":
328
+ target = ordered[0];
329
+ break;
330
+ case "End":
331
+ target = ordered.at(-1);
332
+ break;
333
+ case "ArrowLeft":
334
+ case "ArrowRight":
335
+ event.preventDefault();
336
+ moveAlongInlineAxis(event, columnIndex, entry);
337
+ return;
338
+ case "Enter":
339
+ event.preventDefault();
340
+ onOpen(entry, [...base, entry.id]);
341
+ return;
342
+ default: return;
343
+ }
344
+ event.preventDefault();
345
+ if (target) moveTo(columnIndex, target, [...base, target.id]);
346
+ }
347
+ function renderColumn(column, columnIndex, layout) {
348
+ return /* @__PURE__ */ jsx(FileExplorerColumnView, {
349
+ column,
350
+ optionRef: (entryId) => (element) => {
351
+ const key = `${layout}:${columnIndex}:${entryId}`;
352
+ if (element) optionElements.current.set(key, element);
353
+ return () => {
354
+ optionElements.current.delete(key);
355
+ };
356
+ },
357
+ onSelect: (entry) => select(columnIndex, entry),
358
+ onOpen: (entry) => onOpen(entry, [...selectedPath.slice(0, columnIndex), entry.id]),
359
+ onOptionKeyDown: (event, entry) => handleOptionKeyDown(event, columnIndex, entry)
360
+ });
361
+ }
362
+ return /* @__PURE__ */ jsxs("div", {
363
+ ref: rootRef,
364
+ className: "h-full min-h-0",
365
+ children: [/* @__PURE__ */ jsx("div", {
366
+ ref: narrowRef,
367
+ className: "flex h-full min-h-0 flex-col @xl/explorer:hidden",
368
+ children: narrowDisplayed ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
369
+ className: "flex h-10 shrink-0 items-center gap-2 border-b border-border/70 px-2",
370
+ children: [parentColumn ? /* @__PURE__ */ jsxs(Button, {
371
+ variant: "ghost",
372
+ size: "xs",
373
+ className: "min-w-0 justify-start",
374
+ ref: backRef,
375
+ "aria-label": `${backLabel}: ${parentColumn.label}`,
376
+ onClick: () => moveToParent(activeIndex),
377
+ children: [/* @__PURE__ */ jsx(ChevronLeftIcon, {
378
+ className: "size-4",
379
+ "data-icon-dir": "inline"
380
+ }), /* @__PURE__ */ jsx("span", {
381
+ className: "truncate",
382
+ children: parentColumn.label
383
+ })]
384
+ }) : /* @__PURE__ */ jsx("span", {
385
+ className: "min-w-0 truncate px-1 text-label font-medium",
386
+ children: activeColumn?.label
387
+ }), /* @__PURE__ */ jsxs(Drawer, { children: [/* @__PURE__ */ jsx(DrawerTrigger, {
388
+ render: /* @__PURE__ */ jsx(Button, {
389
+ variant: "ghost",
390
+ size: "xs",
391
+ className: "ms-auto",
392
+ disabled: !selectedEntry
393
+ }),
394
+ children: detailsLabel
395
+ }), /* @__PURE__ */ jsx(DrawerContent, { children: selectedEntry ? /* @__PURE__ */ jsx(DrawerBody, { children: /* @__PURE__ */ jsx(FileExplorerPreviewContent, {
396
+ entry: selectedEntry,
397
+ title: /* @__PURE__ */ jsx(DrawerTitle, {
398
+ className: "mt-4 max-w-full truncate text-heading-4 font-semibold",
399
+ children: selectedEntry.name
400
+ })
401
+ }) }) : null })] })]
402
+ }), /* @__PURE__ */ jsx("div", {
403
+ className: "min-h-0 flex-1 bg-card",
404
+ children: activeColumn ? renderColumn(activeColumn, activeIndex, "narrow") : null
405
+ })] }) : null
406
+ }), /* @__PURE__ */ jsx("div", {
407
+ ref: wideRef,
408
+ className: "hidden h-full min-h-0 @xl/explorer:block",
409
+ children: wideDisplayed ? /* @__PURE__ */ jsxs(ResizablePanelGroup, {
410
+ orientation: "horizontal",
411
+ className: "rounded-none border-0 bg-card shadow-none",
412
+ children: [columns.map((column, columnIndex) => /* @__PURE__ */ jsx(FileExplorerPanel, {
413
+ defaultSize: columnSize,
414
+ minSize: "16%",
415
+ children: renderColumn(column, columnIndex, "wide")
416
+ }, column.id)), /* @__PURE__ */ jsx(ResizablePanel, {
417
+ defaultSize: "32%",
418
+ minSize: "20%",
419
+ children: /* @__PURE__ */ jsx(FileExplorerPreview, {
420
+ entry: selectedEntry,
421
+ headingAs
422
+ })
423
+ })]
424
+ }) : null
234
425
  })]
235
426
  });
236
427
  }
428
+ function useDisplayed() {
429
+ const ref = useRef(null);
430
+ const [displayed, setDisplayed] = useState(true);
431
+ useEffect(() => {
432
+ const element = ref.current;
433
+ if (!element) return;
434
+ const observer = new ResizeObserver(([entry]) => setDisplayed(Boolean(entry && entry.contentRect.width > 0)));
435
+ observer.observe(element);
436
+ return () => observer.disconnect();
437
+ }, []);
438
+ return [ref, displayed];
439
+ }
237
440
  function FileExplorerPanel({ children, ...props }) {
238
441
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ResizablePanel, {
239
442
  ...props,
240
443
  children
241
444
  }), /* @__PURE__ */ jsx(ResizableHandle, {})] });
242
445
  }
243
- function FileExplorerColumnView({ column, columnIndex, onSelect, onOpen }) {
446
+ function FileExplorerEntryIcon({ entry }) {
447
+ return entry.icon ?? (entry.kind === "folder" ? /* @__PURE__ */ jsx(FolderIcon, {}) : /* @__PURE__ */ jsx(FileIcon, { className: "text-muted-foreground" }));
448
+ }
449
+ function FileExplorerColumnView({ column, optionRef, onSelect, onOpen, onOptionKeyDown }) {
450
+ const groupId = useId();
244
451
  const groups = groupFileExplorerItems(column.entries);
452
+ const tabStopId = column.selectedId ?? groups[0]?.items[0]?.id;
245
453
  if (column.entries.length === 0) return /* @__PURE__ */ jsx(Empty, {
246
454
  className: "h-full rounded-none",
247
455
  children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
@@ -254,44 +462,62 @@ function FileExplorerColumnView({ column, columnIndex, onSelect, onOpen }) {
254
462
  className: "h-full",
255
463
  lockAxis: "x",
256
464
  children: /* @__PURE__ */ jsx("div", {
465
+ role: "listbox",
466
+ "aria-label": column.label,
257
467
  className: "grid gap-4 p-2",
258
- children: groups.map((group) => /* @__PURE__ */ jsxs("section", {
259
- className: "min-w-0",
260
- children: [group.label ? /* @__PURE__ */ jsx("h2", {
261
- className: "mb-1 px-2 text-caption font-semibold text-muted-foreground",
262
- children: group.label
263
- }) : null, /* @__PURE__ */ jsx("div", {
264
- className: "grid gap-0.5",
265
- children: group.items.map((entry) => {
266
- const isSelected = entry.id === column.selectedId;
267
- return /* @__PURE__ */ jsxs(Button, {
268
- variant: "quiet",
269
- size: "sm",
270
- active: isSelected,
271
- "aria-current": isSelected ? "page" : void 0,
272
- className: "w-full justify-start gap-2 px-2 text-left",
273
- title: entry.name,
274
- onClick: () => onSelect(entry, columnIndex),
275
- onDoubleClick: () => onOpen(entry),
276
- children: [
277
- /* @__PURE__ */ jsx("span", {
278
- className: "flex size-4 shrink-0 items-center justify-center text-[oklch(0.72_0.14_235)] [&>svg]:size-4",
279
- children: entry.icon ?? (entry.kind === "folder" ? /* @__PURE__ */ jsx(FolderIcon, {}) : /* @__PURE__ */ jsx(FileIcon, { className: "text-muted-foreground" }))
280
- }),
281
- /* @__PURE__ */ jsx("span", {
282
- className: "min-w-0 flex-1 truncate",
283
- children: entry.name
284
- }),
285
- entry.kind === "folder" ? /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-3.5 shrink-0 text-muted-foreground" }) : null
286
- ]
287
- }, entry.id);
288
- })
289
- })]
290
- }, group.label || "files"))
468
+ children: groups.map((group, groupIndex) => {
469
+ const labelId = `${groupId}-${groupIndex}`;
470
+ const options = group.items.map((entry) => {
471
+ const isSelected = entry.id === column.selectedId;
472
+ return /* @__PURE__ */ jsxs(Button, {
473
+ ref: optionRef(entry.id),
474
+ role: "option",
475
+ "aria-selected": isSelected,
476
+ tabIndex: entry.id === tabStopId ? 0 : -1,
477
+ variant: "quiet",
478
+ size: "sm",
479
+ active: isSelected,
480
+ className: "w-full justify-start gap-2 px-2 text-start",
481
+ title: entry.name,
482
+ onClick: () => onSelect(entry),
483
+ onDoubleClick: () => onOpen(entry),
484
+ onKeyDown: (event) => onOptionKeyDown(event, entry),
485
+ children: [
486
+ /* @__PURE__ */ jsx("span", {
487
+ className: "flex size-4 shrink-0 items-center justify-center text-info-text [&>svg]:size-4",
488
+ children: /* @__PURE__ */ jsx(FileExplorerEntryIcon, { entry })
489
+ }),
490
+ /* @__PURE__ */ jsx("span", {
491
+ className: "min-w-0 flex-1 truncate",
492
+ children: entry.name
493
+ }),
494
+ entry.kind === "folder" ? /* @__PURE__ */ jsx(ChevronRightIcon, {
495
+ className: "size-3.5 shrink-0 text-muted-foreground",
496
+ "data-icon-dir": "inline"
497
+ }) : null
498
+ ]
499
+ }, entry.id);
500
+ });
501
+ return group.label ? /* @__PURE__ */ jsxs("div", {
502
+ role: "group",
503
+ "aria-labelledby": labelId,
504
+ className: "grid min-w-0 gap-0.5",
505
+ children: [/* @__PURE__ */ jsx("div", {
506
+ role: "presentation",
507
+ id: labelId,
508
+ className: "mb-1 px-2 text-caption font-semibold text-muted-foreground",
509
+ children: group.label
510
+ }), options]
511
+ }, group.label) : /* @__PURE__ */ jsx("div", {
512
+ role: "presentation",
513
+ className: "grid min-w-0 gap-0.5",
514
+ children: options
515
+ }, "");
516
+ })
291
517
  })
292
518
  });
293
519
  }
294
- function FileExplorerPreview({ entry }) {
520
+ function FileExplorerPreview({ entry, headingAs: Heading }) {
295
521
  if (!entry) return /* @__PURE__ */ jsx(Empty, {
296
522
  className: "h-full rounded-none bg-background/45",
297
523
  children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
@@ -303,124 +529,167 @@ function FileExplorerPreview({ entry }) {
303
529
  return /* @__PURE__ */ jsx(ScrollArea, {
304
530
  className: "h-full bg-background/45",
305
531
  lockAxis: "x",
306
- children: /* @__PURE__ */ jsxs("div", {
307
- className: "mx-auto flex w-full max-w-lg flex-col items-center px-6 py-10 text-center",
308
- children: [
309
- /* @__PURE__ */ jsx("div", {
310
- className: "flex size-16 items-center justify-center rounded-[var(--radius-panel)] bg-foreground/6 text-[oklch(0.72_0.14_235)] shadow-sm ring-1 ring-foreground/8 [&>svg]:size-8",
311
- children: entry.icon ?? (entry.kind === "folder" ? /* @__PURE__ */ jsx(FolderIcon, {}) : /* @__PURE__ */ jsx(FileIcon, { className: "text-muted-foreground" }))
312
- }),
313
- /* @__PURE__ */ jsx("h2", {
314
- className: "mt-4 max-w-full truncate text-base font-semibold",
315
- children: entry.name
316
- }),
317
- entry.description ? /* @__PURE__ */ jsx("div", {
318
- className: "mt-1 text-caption leading-5 text-muted-foreground",
319
- children: entry.description
320
- }) : null,
321
- entry.details && entry.details.length > 0 ? /* @__PURE__ */ jsx("dl", {
322
- className: "mt-6 grid w-full grid-cols-[auto_1fr] gap-x-4 gap-y-2 border-t border-border/70 pt-4 text-left text-caption",
323
- children: entry.details.map((detail) => /* @__PURE__ */ jsxs("div", {
324
- className: "contents",
325
- children: [/* @__PURE__ */ jsx("dt", {
326
- className: "text-muted-foreground",
327
- children: detail.label
328
- }), /* @__PURE__ */ jsx("dd", {
329
- className: "min-w-0 truncate text-right text-foreground",
330
- children: detail.value
331
- })]
332
- }, detail.label))
333
- }) : null,
334
- entry.preview ? /* @__PURE__ */ jsx("div", {
335
- className: "mt-6 w-full text-left",
336
- children: entry.preview
337
- }) : null
338
- ]
532
+ children: /* @__PURE__ */ jsx(FileExplorerPreviewContent, {
533
+ entry,
534
+ className: "px-6 py-10",
535
+ title: /* @__PURE__ */ jsx(Heading, {
536
+ className: "mt-4 max-w-full truncate text-heading-4 font-semibold",
537
+ title: entry.name,
538
+ children: entry.name
539
+ })
339
540
  })
340
541
  });
341
542
  }
342
- function FileExplorerSearchResults({ query, results, onSelect, onOpen }) {
343
- if (results.length === 0) return /* @__PURE__ */ jsx(Empty, {
543
+ function FileExplorerPreviewContent({ entry, title, className }) {
544
+ return /* @__PURE__ */ jsxs("div", {
545
+ className: cn("mx-auto flex w-full max-w-lg flex-col items-center text-center", className),
546
+ children: [
547
+ /* @__PURE__ */ jsx("div", {
548
+ className: "flex size-16 items-center justify-center rounded-[var(--radius-panel)] bg-foreground/6 text-info-text shadow-sm ring-1 ring-foreground/8 [&>svg]:size-8",
549
+ children: /* @__PURE__ */ jsx(FileExplorerEntryIcon, { entry })
550
+ }),
551
+ title,
552
+ entry.description ? /* @__PURE__ */ jsx("div", {
553
+ className: "mt-1 text-caption text-muted-foreground",
554
+ children: entry.description
555
+ }) : null,
556
+ entry.details && entry.details.length > 0 ? /* @__PURE__ */ jsx("dl", {
557
+ className: "mt-6 grid w-full grid-cols-[auto_1fr] gap-x-4 gap-y-2 border-t border-border/70 pt-4 text-start text-caption",
558
+ children: entry.details.map((detail) => /* @__PURE__ */ jsxs("div", {
559
+ className: "contents",
560
+ children: [/* @__PURE__ */ jsx("dt", {
561
+ className: "text-muted-foreground",
562
+ children: detail.label
563
+ }), /* @__PURE__ */ jsx("dd", {
564
+ className: "min-w-0 truncate text-end text-foreground",
565
+ children: detail.value
566
+ })]
567
+ }, detail.label))
568
+ }) : null,
569
+ entry.preview ? /* @__PURE__ */ jsx("div", {
570
+ className: "mt-6 w-full text-start",
571
+ children: entry.preview
572
+ }) : null
573
+ ]
574
+ });
575
+ }
576
+ function FileExplorerSearchResults({ label, status, clearSearchLabel, results, onSelect, onOpen, onClear }) {
577
+ const optionElements = useRef([]);
578
+ const [activeIndex, setActiveIndex] = useState(0);
579
+ const tabStop = Math.min(activeIndex, results.length - 1);
580
+ if (results.length === 0) return /* @__PURE__ */ jsxs(Empty, {
344
581
  className: "h-full rounded-none",
345
- children: /* @__PURE__ */ jsxs(EmptyHeader, { children: [
582
+ children: [/* @__PURE__ */ jsxs(EmptyHeader, { children: [
346
583
  /* @__PURE__ */ jsx(EmptyMedia, { children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
347
- /* @__PURE__ */ jsxs(EmptyTitle, { children: [
348
- "No results for “",
349
- query,
350
- "”"
351
- ] }),
584
+ /* @__PURE__ */ jsx(EmptyTitle, { children: status }),
352
585
  /* @__PURE__ */ jsx(EmptyDescription, { children: "Try a shorter file or folder name." })
353
- ] })
586
+ ] }), /* @__PURE__ */ jsx(EmptyContent, { children: /* @__PURE__ */ jsx(Button, {
587
+ variant: "surface",
588
+ size: "sm",
589
+ onClick: onClear,
590
+ children: clearSearchLabel
591
+ }) })]
354
592
  });
593
+ function handleKeyDown(event, index, result) {
594
+ if (event.key === "Enter") {
595
+ event.preventDefault();
596
+ onOpen(result.entry, result.path);
597
+ return;
598
+ }
599
+ const next = {
600
+ ArrowDown: index + 1,
601
+ ArrowUp: index - 1,
602
+ Home: 0,
603
+ End: results.length - 1
604
+ }[event.key];
605
+ if (next === void 0) return;
606
+ event.preventDefault();
607
+ const target = Math.max(0, Math.min(next, results.length - 1));
608
+ setActiveIndex(target);
609
+ optionElements.current[target]?.focus();
610
+ }
355
611
  return /* @__PURE__ */ jsx(ScrollArea, {
356
612
  className: "h-full",
357
613
  lockAxis: "x",
358
614
  children: /* @__PURE__ */ jsxs("div", {
359
615
  className: "mx-auto grid w-full max-w-3xl gap-1 p-4",
360
- children: [/* @__PURE__ */ jsxs("div", {
361
- className: "mb-2 flex items-center justify-between px-2 text-caption text-muted-foreground",
362
- children: [/* @__PURE__ */ jsxs("span", { children: [
363
- results.length,
364
- " ",
365
- results.length === 1 ? "result" : "results"
366
- ] }), /* @__PURE__ */ jsxs("span", { children: ["Search: ", query] })]
367
- }), results.map((result) => /* @__PURE__ */ jsxs(Button, {
368
- variant: "quiet",
369
- size: "lg",
370
- className: "h-auto w-full justify-start gap-3 px-3 py-2 text-left",
371
- onClick: () => onSelect(result),
372
- onDoubleClick: () => onOpen(result.entry, result.path),
373
- children: [/* @__PURE__ */ jsx("span", {
374
- className: "flex size-8 shrink-0 items-center justify-center rounded-[var(--radius-control)] bg-foreground/6 text-[oklch(0.72_0.14_235)] [&>svg]:size-4",
375
- children: result.entry.icon ?? (result.entry.kind === "folder" ? /* @__PURE__ */ jsx(FolderIcon, {}) : /* @__PURE__ */ jsx(FileIcon, { className: "text-muted-foreground" }))
376
- }), /* @__PURE__ */ jsxs("span", {
377
- className: "min-w-0 flex-1",
616
+ children: [/* @__PURE__ */ jsx("div", {
617
+ className: "mb-2 px-2 text-caption text-muted-foreground tabular-nums",
618
+ children: status
619
+ }), /* @__PURE__ */ jsx("div", {
620
+ role: "listbox",
621
+ "aria-label": label,
622
+ className: "grid gap-1",
623
+ children: results.map((result, index) => /* @__PURE__ */ jsxs(Button, {
624
+ ref: (element) => {
625
+ optionElements.current[index] = element;
626
+ },
627
+ role: "option",
628
+ "aria-selected": index === tabStop,
629
+ tabIndex: index === tabStop ? 0 : -1,
630
+ variant: "quiet",
631
+ size: "lg",
632
+ className: "h-auto w-full justify-start gap-3 px-3 py-2 text-start",
633
+ onClick: () => onSelect(result),
634
+ onDoubleClick: () => onOpen(result.entry, result.path),
635
+ onFocus: () => setActiveIndex(index),
636
+ onKeyDown: (event) => handleKeyDown(event, index, result),
378
637
  children: [/* @__PURE__ */ jsx("span", {
379
- className: "block truncate text-label text-foreground",
380
- children: result.entry.name
381
- }), /* @__PURE__ */ jsx("span", {
382
- className: "block truncate text-caption text-muted-foreground",
383
- children: result.parents.join(" / ") || "Top level"
638
+ className: "flex size-8 shrink-0 items-center justify-center rounded-[var(--radius-control)] bg-foreground/6 text-info-text [&>svg]:size-4",
639
+ children: /* @__PURE__ */ jsx(FileExplorerEntryIcon, { entry: result.entry })
640
+ }), /* @__PURE__ */ jsxs("span", {
641
+ className: "min-w-0 flex-1",
642
+ children: [/* @__PURE__ */ jsx("span", {
643
+ className: "block truncate text-label text-foreground",
644
+ children: result.entry.name
645
+ }), /* @__PURE__ */ jsx("span", {
646
+ className: "block truncate text-caption text-muted-foreground",
647
+ children: result.parents.join(" / ") || "Top level"
648
+ })]
384
649
  })]
385
- })]
386
- }, result.path.join("/")))]
650
+ }, result.path.join("/")))
651
+ })]
387
652
  })
388
653
  });
389
654
  }
390
- function FileExplorerStatusBar({ breadcrumbs, selectedEntry, onBreadcrumbSelect }) {
655
+ function FileExplorerStatusBar({ breadcrumbs, selectedEntry }) {
656
+ const path = breadcrumbs.map((breadcrumb) => breadcrumb.label).join(" / ");
657
+ const itemCount = selectedEntry?.children?.length ?? 0;
658
+ const itemLabel = itemCount === 1 ? "1 item" : `${itemCount} items`;
391
659
  return /* @__PURE__ */ jsxs("footer", {
392
- className: "flex h-8 shrink-0 items-center gap-3 border-t border-border/70 bg-card/45 px-3",
393
- children: [/* @__PURE__ */ jsx(FileExplorerBreadcrumbs, {
394
- breadcrumbs,
395
- onSelect: onBreadcrumbSelect,
396
- variant: "status"
660
+ className: "flex h-8 shrink-0 items-center gap-3 border-t border-border/70 bg-card/45 px-3 text-micro text-muted-foreground",
661
+ children: [/* @__PURE__ */ jsx("span", {
662
+ className: "min-w-0 flex-1 truncate",
663
+ title: path,
664
+ children: path
397
665
  }), /* @__PURE__ */ jsx("span", {
398
- className: "ml-auto shrink-0 text-micro text-muted-foreground",
399
- children: selectedEntry?.kind === "folder" ? `${selectedEntry.children?.length ?? 0} items` : selectedEntry?.name ?? "No selection"
666
+ className: "shrink-0 tabular-nums",
667
+ children: selectedEntry?.kind === "folder" ? itemLabel : selectedEntry?.name ?? "No selection"
400
668
  })]
401
669
  });
402
670
  }
403
- function FileExplorerBreadcrumbs({ breadcrumbs, onSelect, variant }) {
404
- return /* @__PURE__ */ jsx("nav", {
405
- "aria-label": variant === "address" ? "Current folder" : "Selected path",
406
- className: cn("min-w-0 flex-1 overflow-hidden", variant === "address" && "rounded-[var(--radius-control)] border bg-background/55 px-2 py-1 shadow-inner"),
407
- children: /* @__PURE__ */ jsx("ol", {
408
- className: cn("flex min-w-0 flex-nowrap items-center gap-1 overflow-hidden text-caption text-muted-foreground", variant === "status" && "text-micro"),
671
+ function FileExplorerAddressBar({ breadcrumbs, onSelect }) {
672
+ return /* @__PURE__ */ jsx(Breadcrumb, {
673
+ "aria-label": "Current folder",
674
+ className: "min-w-0 overflow-hidden rounded-[var(--radius-control)] border bg-background/55 px-2 py-1 shadow-inner",
675
+ children: /* @__PURE__ */ jsx(BreadcrumbList, {
676
+ className: "min-w-0 flex-nowrap overflow-hidden",
409
677
  children: breadcrumbs.map((breadcrumb, index) => {
410
678
  const isCurrent = index === breadcrumbs.length - 1;
411
- return /* @__PURE__ */ jsxs("li", {
412
- className: "flex min-w-0 items-center gap-1",
413
- children: [index > 0 ? /* @__PURE__ */ jsx(ChevronRightIcon, {
414
- className: "size-3 shrink-0",
415
- "aria-hidden": "true"
416
- }) : null, /* @__PURE__ */ jsx("button", {
417
- type: "button",
418
- "aria-current": isCurrent ? "page" : void 0,
419
- className: cn("min-w-0 truncate rounded-[var(--radius-popup-item)] px-1 py-0.5 text-muted-foreground transition hover:bg-foreground/6 hover:text-foreground focus-visible:outline-(--focus-ring) focus-visible:outline-offset-(--focus-ring-offset) focus-visible:outline-(length:--focus-ring-width)", isCurrent && "text-foreground"),
679
+ return /* @__PURE__ */ jsxs(Fragment, { children: [index > 0 ? /* @__PURE__ */ jsx(BreadcrumbSeparator, {}) : null, /* @__PURE__ */ jsx(BreadcrumbItem, {
680
+ className: "min-w-0",
681
+ children: isCurrent ? /* @__PURE__ */ jsx(BreadcrumbPage, {
682
+ className: "min-w-0 truncate",
683
+ title: breadcrumb.label,
684
+ children: breadcrumb.label
685
+ }) : /* @__PURE__ */ jsx(BreadcrumbLink, {
686
+ render: /* @__PURE__ */ jsx("button", { type: "button" }),
687
+ className: "min-w-0 truncate",
688
+ title: breadcrumb.label,
420
689
  onClick: () => onSelect(breadcrumb),
421
690
  children: breadcrumb.label
422
- })]
423
- }, `${breadcrumb.id}:${breadcrumb.path.join("/")}`);
691
+ })
692
+ })] }, `${breadcrumb.id}:${breadcrumb.path.join("/")}`);
424
693
  })
425
694
  })
426
695
  });