frappe-ui 1.0.0-beta.2 → 1.0.0-beta.20

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 (636) hide show
  1. package/experimental/FloatingWindow/FloatingWindow.cy.ts +328 -0
  2. package/experimental/FloatingWindow/FloatingWindow.vue +303 -0
  3. package/experimental/FloatingWindow/index.ts +8 -0
  4. package/experimental/FloatingWindow/types.ts +31 -0
  5. package/experimental/FloatingWindow/useFloatingWindow.ts +236 -0
  6. package/experimental/MultiEmailInput/MultiEmailInput.cy.ts +155 -0
  7. package/experimental/MultiEmailInput/MultiEmailInput.vue +472 -0
  8. package/experimental/MultiEmailInput/index.ts +17 -0
  9. package/experimental/MultiEmailInput/stories/AsyncSuggestions.vue +54 -0
  10. package/experimental/MultiEmailInput/stories/Basic.vue +18 -0
  11. package/experimental/MultiEmailInput/stories/CustomChip.vue +39 -0
  12. package/experimental/MultiEmailInput/stories/Labeling.vue +28 -0
  13. package/experimental/MultiEmailInput/types.ts +138 -0
  14. package/experimental/MultiEmailInput/utils.ts +60 -0
  15. package/experimental.ts +12 -0
  16. package/frappe/Billing/SignupBanner.vue +1 -1
  17. package/frappe/Billing/TrialBanner.vue +1 -1
  18. package/frappe/DataImport/DataImport.vue +1 -1
  19. package/frappe/DataImport/DataImportList.vue +1 -1
  20. package/frappe/DataImport/ImportSteps.vue +6 -6
  21. package/frappe/DataImport/MappingStep.vue +1 -1
  22. package/frappe/DataImport/PreviewStep.vue +9 -9
  23. package/frappe/DataImport/TemplateModal.vue +1 -1
  24. package/frappe/DataImport/UploadStep.vue +5 -5
  25. package/frappe/Filter/Filter.vue +1 -1
  26. package/frappe/Help/HelpModal.vue +1 -1
  27. package/frappe/Link/stories/Suffix.vue +2 -2
  28. package/frappe/Onboarding/GettingStartedBanner.vue +1 -1
  29. package/frappe/drive/components/AppSwitcher.vue +1 -1
  30. package/frappe/drive/components/InfoDialog.vue +3 -3
  31. package/frappe/drive/components/MoveDialog.vue +2 -2
  32. package/frappe/drive/components/Select/Select.vue +2 -2
  33. package/frappe/drive/components/ShareDialog.vue +1 -1
  34. package/frappe/telemetry/index.ts +88 -14
  35. package/frappe/telemetry/pulse.ts +78 -164
  36. package/frappe/telemetry/telemetry.test.ts +13 -79
  37. package/icons/IconPicker.vue +1 -1
  38. package/package.json +104 -34
  39. package/src/components/Alert/Alert.md +4 -0
  40. package/src/components/Alert/Alert.vue +4 -4
  41. package/src/components/Autocomplete/Autocomplete.vue +6 -6
  42. package/src/components/Avatar/Avatar.md +4 -0
  43. package/src/components/Avatar/Avatar.vue +3 -3
  44. package/src/components/Badge/Badge.api.md +1 -1
  45. package/src/components/Badge/Badge.cy.ts +34 -16
  46. package/src/components/Badge/Badge.md +20 -4
  47. package/src/components/Badge/Badge.vue +60 -45
  48. package/src/components/Badge/stories/CallMeta.vue +35 -0
  49. package/src/components/Badge/stories/EventStatus.vue +16 -0
  50. package/src/components/Badge/stories/ListStatus.vue +91 -0
  51. package/src/components/Badge/stories/Reactions.vue +33 -0
  52. package/src/components/Badge/stories/RowTags.vue +39 -0
  53. package/src/components/Badge/types.ts +1 -2
  54. package/src/components/Breadcrumbs/Breadcrumbs.md +4 -0
  55. package/src/components/Breadcrumbs/Breadcrumbs.vue +3 -3
  56. package/src/components/Button/Button.api.md +37 -32
  57. package/src/components/Button/Button.cy.ts +18 -2
  58. package/src/components/Button/Button.md +20 -8
  59. package/src/components/Button/Button.vue +304 -284
  60. package/src/components/Button/stories/InlineActions.vue +10 -0
  61. package/src/components/Button/stories/LiveClassCard.vue +34 -0
  62. package/src/components/Button/stories/SectionAction.vue +17 -0
  63. package/src/components/Button/stories/SectionControls.vue +28 -0
  64. package/src/components/Button/stories/SelectionToolbar.vue +20 -0
  65. package/src/components/Button/stories/StackedActions.vue +10 -0
  66. package/src/components/Button/types.ts +38 -32
  67. package/src/components/Calendar/Calendar.vue +1 -1
  68. package/src/components/Calendar/CalendarDaily.vue +1 -1
  69. package/src/components/Calendar/CalendarMonthEvent.vue +1 -1
  70. package/src/components/Calendar/CalendarMonthly.vue +2 -2
  71. package/src/components/Calendar/CalendarWeekDayEvent.vue +2 -2
  72. package/src/components/Calendar/CalendarWeekly.vue +2 -2
  73. package/src/components/Calendar/EventModalContent.vue +6 -6
  74. package/src/components/Calendar/ShowMoreCalendarEvent.vue +1 -1
  75. package/src/components/Calendar/stories/CustomHeader.vue +1 -1
  76. package/src/components/Card.vue +2 -2
  77. package/src/components/Charts/ECharts.vue +1 -1
  78. package/src/components/Charts/NumberChart.vue +7 -7
  79. package/src/components/Charts/eChartOptions.ts +6 -6
  80. package/src/components/Checkbox/Checkbox.cy.ts +10 -2
  81. package/src/components/Checkbox/Checkbox.md +28 -0
  82. package/src/components/Checkbox/Checkbox.vue +8 -22
  83. package/src/components/Checkbox/stories/Group.vue +27 -0
  84. package/src/components/Checkbox/stories/HorizontalGroup.vue +24 -0
  85. package/src/components/Checkbox/stories/SettingRow.vue +23 -0
  86. package/src/components/Checkbox/stories/WithDescription.vue +27 -0
  87. package/src/components/CodeEditor/CodeEditor.api.md +140 -0
  88. package/src/components/CodeEditor/CodeEditor.cy.ts +160 -0
  89. package/src/components/CodeEditor/CodeEditor.md +104 -0
  90. package/src/components/CodeEditor/CodeEditor.vue +556 -0
  91. package/src/components/CodeEditor/CodePreview.test.ts +49 -0
  92. package/src/components/CodeEditor/CodePreview.vue +45 -0
  93. package/src/components/CodeEditor/index.ts +17 -0
  94. package/src/components/CodeEditor/languages.ts +63 -0
  95. package/src/components/CodeEditor/stories/Default.vue +16 -0
  96. package/src/components/CodeEditor/stories/Labeling.vue +25 -0
  97. package/src/components/CodeEditor/stories/MaxHeight.vue +24 -0
  98. package/src/components/CodeEditor/stories/Preview.vue +36 -0
  99. package/src/components/CodeEditor/stories/Sizes.vue +15 -0
  100. package/src/components/CodeEditor/stories/Variants.vue +13 -0
  101. package/src/components/CodeEditor/theme.ts +272 -0
  102. package/src/components/CodeEditor/types.ts +69 -0
  103. package/src/components/Combobox/Combobox.api.md +21 -21
  104. package/src/components/Combobox/Combobox.cy.ts +54 -14
  105. package/src/components/Combobox/Combobox.md +4 -0
  106. package/src/components/Combobox/Combobox.vue +233 -247
  107. package/src/components/Combobox/ComboboxResults.vue +1 -1
  108. package/src/components/Combobox/stories/Clearable.vue +2 -2
  109. package/src/components/Combobox/stories/CreateNew.vue +1 -1
  110. package/src/components/Combobox/stories/Footer.vue +10 -2
  111. package/src/components/Combobox/stories/InDialog.vue +1 -1
  112. package/src/components/Combobox/types.ts +8 -29
  113. package/src/components/CommandPalette/CommandPaletteItem.vue +1 -1
  114. package/src/components/ContextMenu/ContextMenu.api.md +52 -0
  115. package/src/components/ContextMenu/ContextMenu.cy.ts +191 -0
  116. package/src/components/ContextMenu/ContextMenu.md +25 -0
  117. package/src/components/ContextMenu/ContextMenu.vue +106 -0
  118. package/src/components/ContextMenu/index.ts +2 -0
  119. package/src/components/ContextMenu/stories/FileList.vue +105 -0
  120. package/src/components/ContextMenu/stories/Groups.vue +95 -0
  121. package/src/components/ContextMenu/stories/Simple.vue +72 -0
  122. package/src/components/ContextMenu/types.ts +39 -0
  123. package/src/components/DatePicker/CalendarPanel.vue +128 -86
  124. package/src/components/DatePicker/DatePicker.vue +3 -9
  125. package/src/components/DatePicker/DateRangePicker.vue +1 -7
  126. package/src/components/DatePicker/DateTimePicker.cy.ts +4 -2
  127. package/src/components/DatePicker/DateTimePicker.vue +3 -9
  128. package/src/components/DatePicker/composables.ts +15 -37
  129. package/src/components/DatePicker/stories/Range.vue +1 -1
  130. package/src/components/DatePicker/types.ts +1 -1
  131. package/src/components/DesktopShell/DesktopShell.api.md +32 -0
  132. package/src/components/DesktopShell/DesktopShell.cy.ts +39 -0
  133. package/src/components/DesktopShell/DesktopShell.md +31 -0
  134. package/src/components/DesktopShell/DesktopShell.vue +58 -0
  135. package/src/components/DesktopShell/index.ts +3 -0
  136. package/src/components/DesktopShell/stories/Default.vue +97 -0
  137. package/src/components/DesktopShell/types.ts +2 -0
  138. package/src/components/Dialog/Dialog.md +4 -0
  139. package/src/components/Dialog/Dialog.vue +8 -7
  140. package/src/components/Dialog/stories/CommandPalette.vue +1 -1
  141. package/src/components/Dialog/stories/Wizard.vue +1 -1
  142. package/src/components/Divider/Divider.api.md +1 -1
  143. package/src/components/Divider/Divider.md +4 -0
  144. package/src/components/Dropdown/Dropdown.api.md +1 -31
  145. package/src/components/Dropdown/Dropdown.md +10 -6
  146. package/src/components/Dropdown/Dropdown.vue +33 -47
  147. package/src/components/Dropdown/stories/{05_KebabMenu.vue → KebabMenu.vue} +3 -1
  148. package/src/components/Dropdown/stories/{06_UserMenu.vue → UserMenu.vue} +1 -1
  149. package/src/components/Dropdown/types.ts +25 -189
  150. package/src/components/Duration/Duration.api.md +92 -0
  151. package/src/components/Duration/Duration.cy.ts +183 -0
  152. package/src/components/Duration/Duration.md +98 -0
  153. package/src/components/Duration/Duration.vue +114 -0
  154. package/src/components/Duration/duration.ts +185 -0
  155. package/src/components/Duration/index.ts +8 -0
  156. package/src/components/Duration/stories/Default.vue +10 -0
  157. package/src/components/Duration/stories/Formats.vue +16 -0
  158. package/src/components/Duration/stories/RealWorld.vue +50 -0
  159. package/src/components/Duration/stories/Sizes.vue +15 -0
  160. package/src/components/Duration/stories/States.vue +21 -0
  161. package/src/components/Duration/types.ts +52 -0
  162. package/src/components/ErrorMessage/ErrorMessage.cy.ts +1 -1
  163. package/src/components/ErrorMessage/ErrorMessage.md +4 -0
  164. package/src/components/ErrorMessage/ErrorMessage.vue +1 -1
  165. package/src/components/FormControl/FormControl.md +4 -0
  166. package/src/components/FormControl/stories/RealForm.vue +3 -3
  167. package/src/components/FormLabel.vue +5 -8
  168. package/src/components/HoverCard/HoverCard.api.md +101 -0
  169. package/src/components/HoverCard/HoverCard.cy.ts +103 -0
  170. package/src/components/HoverCard/HoverCard.md +52 -0
  171. package/src/components/HoverCard/HoverCard.vue +91 -0
  172. package/src/components/HoverCard/index.ts +2 -0
  173. package/src/components/HoverCard/stories/Arrow.vue +28 -0
  174. package/src/components/HoverCard/stories/Delays.vue +16 -0
  175. package/src/components/HoverCard/stories/Example.vue +28 -0
  176. package/src/components/HoverCard/types.ts +47 -0
  177. package/src/components/Icon/Icon.cy.ts +78 -0
  178. package/src/components/Icon/Icon.vue +50 -0
  179. package/src/components/Icon/index.ts +1 -0
  180. package/src/components/InputLabeling/InputDescription.vue +1 -1
  181. package/src/components/InputLabeling/InputError.vue +1 -1
  182. package/src/components/InputLabeling/InputLabel.vue +21 -7
  183. package/src/components/InputLabeling/RequiredIndicator.vue +1 -1
  184. package/src/components/ItemListRow/ItemListRow.vue +1 -1
  185. package/src/components/ItemListRow/stories/RowStates.vue +1 -1
  186. package/src/components/KeyboardShortcut.vue +4 -8
  187. package/src/components/KeyboardShortcutsModal/KeyboardShortcutsModal.vue +2 -2
  188. package/src/components/ListFilter/ListFilter.vue +2 -2
  189. package/src/components/ListItem.vue +1 -1
  190. package/src/components/ListView/ListEmptyState.vue +1 -1
  191. package/src/components/ListView/ListGroupHeader.vue +2 -2
  192. package/src/components/ListView/ListRow.vue +1 -1
  193. package/src/components/ListView/ListSelectBanner.vue +1 -1
  194. package/src/components/ListView/stories/CustomList.vue +1 -1
  195. package/src/components/ListView/stories/GroupedRows.vue +1 -1
  196. package/src/components/LoadingIndicator.vue +13 -22
  197. package/src/components/LoadingText.vue +3 -13
  198. package/src/components/{Dropdown/DropdownMenuList.vue → Menu/Menu.vue} +110 -75
  199. package/src/components/{Dropdown/DropdownMenuItemContent.vue → Menu/MenuItemContent.vue} +23 -33
  200. package/src/components/{Dropdown/DropdownRenderContent.vue → Menu/MenuRenderContent.vue} +1 -1
  201. package/src/components/Menu/types.ts +225 -0
  202. package/src/components/{Dropdown → Menu}/utils.ts +36 -40
  203. package/src/components/MobileNav/MobileNav.api.md +71 -0
  204. package/src/components/MobileNav/MobileNav.cy.ts +89 -0
  205. package/src/components/MobileNav/MobileNav.md +34 -0
  206. package/src/components/MobileNav/MobileNav.vue +15 -0
  207. package/src/components/MobileNav/MobileNavItem.vue +125 -0
  208. package/src/components/MobileNav/index.ts +4 -0
  209. package/src/components/MobileNav/stories/Default.vue +40 -0
  210. package/src/components/MobileNav/types.ts +33 -0
  211. package/src/components/MobileShell/MobileShell.api.md +23 -0
  212. package/src/components/MobileShell/MobileShell.cy.ts +25 -0
  213. package/src/components/MobileShell/MobileShell.md +24 -0
  214. package/src/components/MobileShell/MobileShell.vue +41 -0
  215. package/src/components/MobileShell/index.ts +3 -0
  216. package/src/components/MobileShell/stories/Default.vue +75 -0
  217. package/src/components/MobileShell/types.ts +7 -0
  218. package/src/components/MonthPicker/MonthPicker.vue +1 -1
  219. package/src/components/MultiSelect/MultiSelect.api.md +9 -9
  220. package/src/components/MultiSelect/MultiSelect.md +42 -10
  221. package/src/components/MultiSelect/MultiSelect.vue +199 -227
  222. package/src/components/MultiSelect/MultiSelectResults.vue +1 -1
  223. package/src/components/MultiSelect/stories/Members.vue +1 -1
  224. package/src/components/MultiSelect/stories/TagsTrigger.vue +3 -3
  225. package/src/components/MultiSelect/types.ts +3 -3
  226. package/src/components/PageHeader/PageHeader.api.md +113 -0
  227. package/src/components/PageHeader/PageHeader.cy.ts +68 -0
  228. package/src/components/PageHeader/PageHeader.md +25 -0
  229. package/src/components/PageHeader/PageHeader.vue +13 -0
  230. package/src/components/PageHeader/PageHeaderBackButton.vue +29 -0
  231. package/src/components/PageHeader/PageHeaderBase.vue +51 -0
  232. package/src/components/PageHeader/PageHeaderMobile.vue +68 -0
  233. package/src/components/PageHeader/PageHeaderMobileTitle.vue +16 -0
  234. package/src/components/PageHeader/PageHeaderTarget.vue +13 -0
  235. package/src/components/PageHeader/index.ts +6 -0
  236. package/src/components/PageHeader/stories/Default.vue +19 -0
  237. package/src/components/PageHeader/stories/Mobile.vue +24 -0
  238. package/src/components/PageHeader/target.ts +19 -0
  239. package/src/components/PageHeader/types.ts +19 -0
  240. package/src/components/Password/Password.md +4 -0
  241. package/src/components/Password/Password.vue +2 -2
  242. package/src/components/Pill/Pill.api.md +99 -0
  243. package/src/components/Pill/Pill.md +20 -0
  244. package/src/components/Pill/Pill.vue +161 -0
  245. package/src/components/Pill/index.ts +10 -0
  246. package/src/components/Pill/stories/BrowserEdges.vue +42 -0
  247. package/src/components/Pill/stories/Sizes.vue +49 -0
  248. package/src/components/Pill/stories/Variants.vue +53 -0
  249. package/src/components/Pill/types.ts +43 -0
  250. package/src/components/Popover/Popover.api.md +126 -41
  251. package/src/components/Popover/Popover.cy.ts +425 -41
  252. package/src/components/Popover/Popover.md +99 -4
  253. package/src/components/Popover/Popover.vue +288 -215
  254. package/src/components/Popover/index.ts +8 -1
  255. package/src/components/Popover/stories/Arrow.vue +16 -0
  256. package/src/components/Popover/stories/Bare.vue +20 -0
  257. package/src/components/Popover/stories/Click.vue +7 -4
  258. package/src/components/Popover/stories/Controlled.vue +22 -0
  259. package/src/components/Popover/stories/Dismissible.vue +19 -0
  260. package/src/components/Popover/stories/MatchTriggerWidth.vue +16 -0
  261. package/src/components/Popover/stories/SideAlign.vue +17 -0
  262. package/src/components/Popover/types.ts +101 -23
  263. package/src/components/Progress/Progress.cy.ts +2 -2
  264. package/src/components/Progress/Progress.md +4 -0
  265. package/src/components/Progress/Progress.vue +4 -4
  266. package/src/components/Rail/Rail.api.md +92 -0
  267. package/src/components/Rail/Rail.cy.ts +84 -0
  268. package/src/components/Rail/Rail.md +30 -0
  269. package/src/components/Rail/Rail.vue +16 -0
  270. package/src/components/Rail/RailItem.vue +134 -0
  271. package/src/components/Rail/RailItemBadge.vue +64 -0
  272. package/src/components/Rail/index.ts +2 -0
  273. package/src/components/Rail/stories/Default.vue +44 -0
  274. package/src/components/Rail/types.ts +37 -0
  275. package/src/components/Rating/Rating.md +4 -0
  276. package/src/components/Rating/Rating.vue +5 -8
  277. package/src/components/Rating/stories/CustomSlot.vue +2 -2
  278. package/src/components/ScrollArea/ScrollArea.api.md +59 -0
  279. package/src/components/ScrollArea/ScrollArea.vue +43 -0
  280. package/src/components/ScrollArea/ScrollBar.vue +95 -0
  281. package/src/components/ScrollArea/index.ts +2 -0
  282. package/src/components/Select/Select.cy.ts +16 -15
  283. package/src/components/Select/Select.md +4 -0
  284. package/src/components/Select/Select.vue +217 -203
  285. package/src/components/Select/stories/Example.vue +4 -3
  286. package/src/components/Select/stories/States.vue +1 -1
  287. package/src/components/Select/stories/TriggerSlots.vue +2 -2
  288. package/src/components/Select/utils.ts +4 -3
  289. package/src/components/SettingsDialog/SettingsBody.vue +23 -0
  290. package/src/components/SettingsDialog/SettingsContent.vue +5 -0
  291. package/src/components/SettingsDialog/SettingsDialog.api.md +273 -0
  292. package/src/components/SettingsDialog/SettingsDialog.cy.ts +126 -0
  293. package/src/components/SettingsDialog/SettingsDialog.md +43 -0
  294. package/src/components/SettingsDialog/SettingsDialog.vue +72 -0
  295. package/src/components/SettingsDialog/SettingsHeader.vue +36 -0
  296. package/src/components/SettingsDialog/SettingsNavGroup.vue +20 -0
  297. package/src/components/SettingsDialog/SettingsNavItem.vue +33 -0
  298. package/src/components/SettingsDialog/SettingsPanel.cy.ts +77 -0
  299. package/src/components/SettingsDialog/SettingsPanel.vue +26 -0
  300. package/src/components/SettingsDialog/SettingsRow.vue +52 -0
  301. package/src/components/SettingsDialog/SettingsSidebar.vue +16 -0
  302. package/src/components/SettingsDialog/index.ts +10 -0
  303. package/src/components/SettingsDialog/stories/Default.vue +99 -0
  304. package/src/components/SettingsDialog/stories/PanelBasic.vue +52 -0
  305. package/src/components/SettingsDialog/stories/panels/NotificationsPanel.vue +57 -0
  306. package/src/components/SettingsDialog/stories/panels/PreferencesPanel.vue +57 -0
  307. package/src/components/SettingsDialog/stories/panels/ProfilePanel.vue +38 -0
  308. package/src/components/SettingsDialog/stories/panels/UsersPanel.vue +83 -0
  309. package/src/components/SettingsDialog/types.ts +21 -0
  310. package/src/components/Sidebar/Sidebar.api.md +112 -69
  311. package/src/components/Sidebar/Sidebar.cy.ts +127 -76
  312. package/src/components/Sidebar/Sidebar.md +47 -3
  313. package/src/components/Sidebar/Sidebar.vue +64 -56
  314. package/src/components/Sidebar/SidebarCollapseToggle.vue +23 -0
  315. package/src/components/Sidebar/SidebarHeader.vue +2 -2
  316. package/src/components/Sidebar/SidebarItem.vue +130 -97
  317. package/src/components/Sidebar/SidebarItemIcon.vue +25 -0
  318. package/src/components/Sidebar/SidebarLabel.vue +34 -0
  319. package/src/components/Sidebar/SidebarSection.vue +29 -14
  320. package/src/components/Sidebar/index.ts +13 -2
  321. package/src/components/Sidebar/stories/Collapsed.vue +40 -0
  322. package/src/components/Sidebar/stories/Default.vue +153 -0
  323. package/src/components/Sidebar/stories/Legacy.vue +36 -0
  324. package/src/components/Sidebar/types.ts +97 -20
  325. package/src/components/Skeleton/Skeleton.cy.ts +21 -0
  326. package/src/components/Skeleton/Skeleton.md +21 -0
  327. package/src/components/Skeleton/Skeleton.vue +6 -0
  328. package/src/components/Skeleton/index.ts +1 -0
  329. package/src/components/Skeleton/stories/Card.vue +12 -0
  330. package/src/components/Skeleton/stories/List.vue +15 -0
  331. package/src/components/Skeleton/stories/Text.vue +11 -0
  332. package/src/components/Slider/Slider.api.md +1 -1
  333. package/src/components/Slider/Slider.cy.ts +29 -0
  334. package/src/components/Slider/Slider.md +10 -0
  335. package/src/components/Slider/Slider.vue +37 -9
  336. package/src/components/Slider/stories/NegativeValues.vue +12 -0
  337. package/src/components/Slider/types.ts +1 -1
  338. package/src/components/Spinner/Spinner.api.md +32 -0
  339. package/src/components/Spinner/Spinner.cy.ts +87 -0
  340. package/src/components/Spinner/Spinner.md +21 -0
  341. package/src/components/Spinner/Spinner.vue +168 -0
  342. package/src/components/Spinner/index.ts +2 -0
  343. package/src/components/Spinner/stories/InContext.vue +49 -0
  344. package/src/components/Spinner/stories/Sizes.vue +12 -0
  345. package/src/components/Spinner/stories/Themes.vue +10 -0
  346. package/src/components/Spinner/stories/Track.vue +12 -0
  347. package/src/components/Spinner/types.ts +14 -0
  348. package/src/components/Switch/Switch.md +4 -0
  349. package/src/components/Switch/Switch.vue +6 -23
  350. package/src/components/TabButtons/TabButtons.api.md +52 -2
  351. package/src/components/TabButtons/TabButtons.cy.ts +18 -5
  352. package/src/components/TabButtons/TabButtons.md +49 -0
  353. package/src/components/TabButtons/TabButtons.vue +206 -109
  354. package/src/components/TabButtons/index.ts +11 -1
  355. package/src/components/TabButtons/stories/PrefixSuffix.vue +65 -0
  356. package/src/components/TabButtons/stories/Sizes.vue +26 -0
  357. package/src/components/TabButtons/stories/Variants.vue +40 -0
  358. package/src/components/TabButtons/stories/Vertical.vue +64 -0
  359. package/src/components/TabButtons/types.ts +49 -0
  360. package/src/components/Tabs/Tabs.md +4 -0
  361. package/src/components/Tabs/Tabs.vue +1 -1
  362. package/src/components/TextEditor/TextEditor.cy.ts +1 -1
  363. package/src/components/TextEditor/TextEditor.vue +5 -2
  364. package/src/components/TextEditor/components/CodeBlockComponent.vue +4 -194
  365. package/src/components/TextEditor/components/FontColor.vue +1 -1
  366. package/src/components/TextEditor/components/LinkPopup.vue +1 -1
  367. package/src/components/TextEditor/components/MediaNodeView.vue +17 -17
  368. package/src/components/TextEditor/components/MentionList.vue +1 -1
  369. package/src/components/TextEditor/components/TextEditorBubbleMenu.vue +14 -5
  370. package/src/components/TextEditor/components/TextEditorFixedMenu.vue +3 -3
  371. package/src/components/TextEditor/components/{Menu.vue → TextEditorMenu.vue} +3 -3
  372. package/src/components/TextEditor/extensions/iframe/InsertIframe.vue +2 -2
  373. package/src/components/TextEditor/extensions/iframe/iframe-extension.ts +62 -19
  374. package/src/components/TextEditor/extensions/image-group/ImageGroupNodeView.vue +1 -1
  375. package/src/components/TextEditor/extensions/image-group/ImageGroupUploadDialog.vue +4 -4
  376. package/src/components/TextEditor/extensions/suggestion/SuggestionList.vue +1 -1
  377. package/src/components/TextEditor/extensions/toc-node/TocNodeView.vue +1 -1
  378. package/src/components/TextEditor/hljs-github.css +197 -0
  379. package/src/components/TextInput/TextInput.cy.ts +8 -3
  380. package/src/components/TextInput/TextInput.md +4 -0
  381. package/src/components/TextInput/TextInput.vue +11 -15
  382. package/src/components/TextInput/stories/Slots.vue +1 -1
  383. package/src/components/Textarea/Textarea.cy.ts +1 -1
  384. package/src/components/Textarea/Textarea.md +4 -0
  385. package/src/components/Textarea/Textarea.vue +11 -15
  386. package/src/components/ThemeSwitcher/ThemePreview.vue +74 -0
  387. package/src/components/ThemeSwitcher/ThemeSwitcher.api.md +82 -0
  388. package/src/components/ThemeSwitcher/ThemeSwitcher.cy.ts +88 -0
  389. package/src/components/ThemeSwitcher/ThemeSwitcher.md +70 -0
  390. package/src/components/ThemeSwitcher/ThemeSwitcher.vue +142 -0
  391. package/src/components/ThemeSwitcher/index.ts +2 -0
  392. package/src/components/ThemeSwitcher/previews/PreviewWindow.vue +74 -0
  393. package/src/components/ThemeSwitcher/stories/Branded.vue +12 -0
  394. package/src/components/ThemeSwitcher/stories/Default.vue +7 -0
  395. package/src/components/ThemeSwitcher/stories/Menu.vue +53 -0
  396. package/src/components/ThemeSwitcher/stories/Toggle.vue +25 -0
  397. package/src/components/ThemeSwitcher/types.ts +35 -0
  398. package/src/components/TimePicker/TimePicker.api.md +67 -15
  399. package/src/components/TimePicker/TimePicker.cy.ts +130 -14
  400. package/src/components/TimePicker/TimePicker.md +6 -0
  401. package/src/components/TimePicker/TimePicker.vue +74 -19
  402. package/src/components/TimePicker/stories/Labeling.vue +21 -0
  403. package/src/components/TimePicker/stories/Range.vue +1 -1
  404. package/src/components/TimePicker/stories/SizesAndVariants.vue +23 -0
  405. package/src/components/TimePicker/stories/TwentyFour.vue +1 -1
  406. package/src/components/TimePicker/types.ts +15 -3
  407. package/src/components/TimePicker/utils.ts +82 -21
  408. package/src/components/Toast/Toast.md +1 -1
  409. package/src/components/Toast/Toast.sanitize.test.ts +50 -0
  410. package/src/components/Toast/Toast.test.ts +5 -0
  411. package/src/components/Toast/Toast.vue +7 -7
  412. package/src/components/Toast/ToastProvider.vue +18 -9
  413. package/src/components/Toast/stories/CustomIcon.vue +1 -1
  414. package/src/components/Toast/toast.ts +32 -8
  415. package/src/components/Tooltip/Tooltip.api.md +43 -5
  416. package/src/components/Tooltip/Tooltip.cy.ts +48 -0
  417. package/src/components/Tooltip/Tooltip.md +10 -0
  418. package/src/components/Tooltip/Tooltip.vue +20 -5
  419. package/src/components/Tooltip/TooltipBubble.vue +2 -2
  420. package/src/components/Tooltip/TooltipProvider.vue +53 -0
  421. package/src/components/Tooltip/index.ts +1 -0
  422. package/src/components/Tooltip/stories/Group.vue +22 -0
  423. package/src/components/Tooltip/stories/Slots.vue +2 -2
  424. package/src/components/Tooltip/types.ts +1 -1
  425. package/src/components/Tree/Tree.api.md +76 -18
  426. package/src/components/Tree/Tree.md +144 -2
  427. package/src/components/Tree/Tree.vue +276 -97
  428. package/src/components/Tree/TreeItem.vue +263 -0
  429. package/src/components/Tree/index.ts +9 -1
  430. package/src/components/Tree/stories/DragDrop.vue +94 -0
  431. package/src/components/Tree/stories/Example.vue +11 -34
  432. package/src/components/Tree/stories/ExpandAll.vue +48 -0
  433. package/src/components/Tree/stories/Guides.vue +49 -0
  434. package/src/components/Tree/stories/ListView.vue +74 -0
  435. package/src/components/Tree/tree.cy.ts +218 -90
  436. package/src/components/Tree/types.ts +114 -24
  437. package/src/components/Tree/useTreeDragDrop.ts +203 -0
  438. package/src/components/Tree/useTreeKeyboard.ts +109 -0
  439. package/src/components/VueGridLayout/Layout.vue +1 -1
  440. package/src/components/shared/picker/PickerShell.vue +20 -15
  441. package/src/components/shared/popover/PopoverPanel.vue +67 -0
  442. package/src/components/shared/popover/popoverPanel.css +102 -0
  443. package/src/components/shared/selection/utils.ts +6 -6
  444. package/src/composables/useScreenSize.ts +46 -0
  445. package/src/composables/useScrollContainer.ts +108 -0
  446. package/src/data-fetching/docStore.test.ts +73 -0
  447. package/src/data-fetching/docStore.ts +16 -3
  448. package/src/data-fetching/useDoc/useDoc.test.ts +59 -0
  449. package/src/data-fetching/useDoc/useDoc.ts +29 -13
  450. package/src/index.ts +19 -1
  451. package/src/molecules/editor/Editor.cy.ts +6 -1
  452. package/src/molecules/editor/EditorBubbleMenu.vue +1 -1
  453. package/src/molecules/editor/EditorContent.vue +48 -7
  454. package/src/molecules/editor/EditorFixedMenu.vue +2 -1
  455. package/src/molecules/editor/EditorFloatingMenu.vue +1 -1
  456. package/src/molecules/editor/EditorTableMenu.vue +303 -0
  457. package/src/molecules/editor/MenuItems.vue +81 -69
  458. package/src/molecules/editor/commands.ts +10 -2
  459. package/src/molecules/editor/components/AttachmentNodeView.vue +115 -0
  460. package/src/molecules/editor/components/EditorDropZone.vue +105 -0
  461. package/src/molecules/editor/components/EditorPopover.vue +89 -0
  462. package/src/molecules/editor/components/MediaNodeView.vue +156 -49
  463. package/src/molecules/editor/components/MediaResizeHandles.vue +38 -0
  464. package/src/molecules/editor/components/MediaToolbar.vue +108 -54
  465. package/src/molecules/editor/components/TableContextMenu.vue +144 -0
  466. package/src/molecules/editor/components/UploadProgressIndicator.vue +33 -0
  467. package/src/molecules/editor/components/VideoControls.vue +208 -0
  468. package/src/molecules/editor/components/font-color/ColorSwatchGrid.vue +3 -5
  469. package/src/molecules/editor/components/font-color/fontColorController.ts +28 -23
  470. package/src/molecules/editor/components/font-color/swatches.ts +1 -1
  471. package/src/molecules/editor/components/media-node-view-controller.ts +4 -4
  472. package/src/molecules/editor/components/media-node-view-utils.test.ts +0 -13
  473. package/src/molecules/editor/components/media-node-view-utils.ts +0 -10
  474. package/src/molecules/editor/components/table-color/tableCellColorController.ts +80 -0
  475. package/src/molecules/editor/components/table-size-picker/TableSizePicker.vue +92 -0
  476. package/src/molecules/editor/components/table-size-picker/tableSizePickerController.ts +40 -0
  477. package/src/molecules/editor/composables/useEditorFileDrop.ts +117 -0
  478. package/src/molecules/editor/composables/useFloatingPopup.ts +46 -2
  479. package/src/molecules/editor/composables/useNodeViewResize.test.ts +60 -11
  480. package/src/molecules/editor/composables/useNodeViewResize.ts +80 -21
  481. package/src/molecules/editor/composables/useTableCellColorState.ts +78 -0
  482. package/src/molecules/editor/composables/useWindowFileDragging.ts +68 -0
  483. package/src/molecules/editor/extensions/attachment/attachment-engine.ts +250 -0
  484. package/src/molecules/editor/extensions/attachment/attachment-extension.ts +248 -0
  485. package/src/molecules/editor/extensions/attachment/attachment.flow.test.ts +80 -0
  486. package/src/molecules/editor/extensions/attachment/attachment.roundtrip.test.ts +50 -0
  487. package/src/molecules/editor/extensions/attachment/index.ts +5 -0
  488. package/src/molecules/editor/extensions/code-block/CodeBlockComponent.css +6 -26
  489. package/src/molecules/editor/extensions/code-block/CodeBlockComponent.vue +107 -22
  490. package/src/molecules/editor/extensions/content-paste/content-paste-extension.ts +5 -3
  491. package/src/molecules/editor/extensions/iframe/IframeInsertDialog.vue +34 -7
  492. package/src/molecules/editor/extensions/iframe/IframeNodeView.vue +54 -48
  493. package/src/molecules/editor/extensions/iframe/iframe-allowlist.ts +33 -4
  494. package/src/molecules/editor/extensions/iframe/iframe-commands.ts +49 -5
  495. package/src/molecules/editor/extensions/iframe/iframe-embed-utils.ts +30 -0
  496. package/src/molecules/editor/extensions/iframe/iframe-extension.ts +4 -4
  497. package/src/molecules/editor/extensions/iframe/iframeInsertDialogController.ts +12 -1
  498. package/src/molecules/editor/extensions/iframe/index.ts +4 -4
  499. package/src/molecules/editor/extensions/iframe/useIframeDialog.ts +25 -13
  500. package/src/molecules/editor/extensions/image/image-extension.ts +29 -11
  501. package/src/molecules/editor/extensions/image-group/ImageGroupGrid.vue +27 -11
  502. package/src/molecules/editor/extensions/image-group/ImageGroupGridCell.vue +55 -11
  503. package/src/molecules/editor/extensions/image-group/ImageGroupNodeView.vue +64 -31
  504. package/src/molecules/editor/extensions/image-group/ImageGroupUploadDialog.vue +84 -40
  505. package/src/molecules/editor/extensions/image-group/image-group-extension.ts +4 -3
  506. package/src/molecules/editor/extensions/image-group/useImageGroupDialog.ts +116 -17
  507. package/src/molecules/editor/extensions/link/LinkEditorPopup.vue +214 -71
  508. package/src/molecules/editor/extensions/link/link-click-plugin.ts +10 -15
  509. package/src/molecules/editor/extensions/link/link-commands.ts +24 -3
  510. package/src/molecules/editor/extensions/link/link-extension.ts +11 -2
  511. package/src/molecules/editor/extensions/link/link-popup-controller.test.ts +49 -0
  512. package/src/molecules/editor/extensions/link/link-popup-controller.ts +67 -2
  513. package/src/molecules/editor/extensions/link/link-shortcut-plugin.ts +7 -2
  514. package/src/molecules/editor/extensions/media-drop/media-drop-extension.ts +177 -0
  515. package/src/molecules/editor/extensions/shared/media-dimensions.ts +36 -3
  516. package/src/molecules/editor/extensions/shared/media-node-ops.ts +17 -0
  517. package/src/molecules/editor/extensions/shared/media-plugin.test.ts +60 -0
  518. package/src/molecules/editor/extensions/shared/media-plugin.ts +7 -37
  519. package/src/molecules/editor/extensions/shared/media-upload-engine.test.ts +39 -2
  520. package/src/molecules/editor/extensions/shared/media-upload-engine.ts +104 -10
  521. package/src/molecules/editor/extensions/shared/media-upload-state.ts +55 -0
  522. package/src/molecules/editor/extensions/shared/media-upload-types.ts +52 -1
  523. package/src/molecules/editor/extensions/shared/suggestion-helpers.ts +20 -0
  524. package/src/molecules/editor/extensions/shared/suggestion-types.ts +7 -0
  525. package/src/molecules/editor/extensions/shared/upload-types.ts +1 -0
  526. package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.test.ts +38 -0
  527. package/src/molecules/editor/extensions/slash-commands/slash-commands-extension.ts +119 -55
  528. package/src/molecules/editor/extensions/suggestion/SuggestionList.vue +62 -22
  529. package/src/molecules/editor/extensions/suggestion/SuggestionListItem.vue +1 -1
  530. package/src/molecules/editor/extensions/suggestion/createSuggestionExtension.ts +4 -0
  531. package/src/molecules/editor/extensions/table/drag-scroll.ts +154 -0
  532. package/src/molecules/editor/extensions/table/table-cell-color.ts +188 -0
  533. package/src/molecules/editor/extensions/table/table-navigation.ts +560 -0
  534. package/src/molecules/editor/extensions/table/table-selection-overlay.ts +137 -0
  535. package/src/molecules/editor/extensions/toc-node/TocNodeView.vue +1 -1
  536. package/src/molecules/editor/extensions/video/video-extension.ts +72 -5
  537. package/src/molecules/editor/extensions.ts +176 -8
  538. package/src/molecules/editor/index.ts +7 -0
  539. package/src/molecules/editor/kits.test.ts +21 -1
  540. package/src/molecules/editor/kits.ts +56 -33
  541. package/src/molecules/editor/menu.test.ts +3 -1
  542. package/src/molecules/editor/menu.ts +180 -18
  543. package/src/molecules/editor/stories/Comment.vue +4 -4
  544. package/src/molecules/editor/stories/Inline.vue +1 -1
  545. package/src/molecules/editor/stories/Primitives.vue +1 -1
  546. package/src/molecules/editor/stories/RichText.vue +1 -1
  547. package/src/molecules/editor/style.css +80 -3
  548. package/src/molecules/editor/useEditor.test.ts +4 -0
  549. package/src/molecules/editor/useEditor.ts +7 -3
  550. package/src/molecules/list/List.cy.ts +305 -0
  551. package/src/molecules/list/List.vue +42 -0
  552. package/src/molecules/list/ListCell.vue +15 -0
  553. package/src/molecules/list/ListHeader.vue +26 -0
  554. package/src/molecules/list/ListHeaderCell.vue +26 -0
  555. package/src/molecules/list/ListHeaderCellSort.vue +72 -0
  556. package/src/molecules/list/ListRow.vue +48 -0
  557. package/src/molecules/list/ListRowBase.vue +79 -0
  558. package/src/molecules/list/ListRows.vue +82 -0
  559. package/src/molecules/list/index.ts +23 -0
  560. package/src/molecules/list/list-context.ts +28 -0
  561. package/src/molecules/list/list.api.md +223 -0
  562. package/src/molecules/list/list.md +91 -0
  563. package/src/molecules/list/stories/Columns.vue +103 -0
  564. package/src/molecules/list/stories/Feed.vue +105 -0
  565. package/src/molecules/list/stories/Virtual.vue +31 -0
  566. package/src/molecules/list/style.css +140 -0
  567. package/src/molecules/list/types.ts +66 -0
  568. package/src/molecules/list/useVirtualRows.ts +80 -0
  569. package/src/resources/documentResource.js +32 -4
  570. package/src/resources/documentResource.test.ts +111 -0
  571. package/src/utils/config.ts +15 -1
  572. package/src/utils/dialog.cy.ts +9 -9
  573. package/src/utils/fileSize.ts +36 -0
  574. package/src/utils/fileUploadHandler.ts +49 -5
  575. package/src/utils/frappeRequest.js +21 -0
  576. package/src/utils/frappeRequest.test.ts +64 -0
  577. package/src/utils/plugin.js +19 -0
  578. package/src/utils/request.js +1 -0
  579. package/src/utils/theme.ts +48 -57
  580. package/src/utils/useFileUpload.ts +68 -22
  581. package/tailwind/audit-token-drift.cjs +176 -0
  582. package/tailwind/colorPalette.js +69 -8
  583. package/tailwind/colors.json +1115 -397
  584. package/tailwind/figma-tokens-to-theme.js +364 -0
  585. package/tailwind/generated/colors.json +1217 -0
  586. package/tailwind/generated/effects.json +41 -0
  587. package/tailwind/generated/radius.json +20 -0
  588. package/tailwind/generated/typography.json +479 -0
  589. package/tailwind/migrate-tokens-v2.js +453 -0
  590. package/tailwind/migrate-tokens-v2.test.js +55 -0
  591. package/tailwind/plugin.js +220 -15
  592. package/tailwind/tokens.js +35 -37
  593. package/vitepress/components/Docs/CommandPalette.vue +214 -0
  594. package/vitepress/components/Docs/Demo.vue +60 -0
  595. package/vitepress/components/Docs/EmitsTable.vue +99 -0
  596. package/vitepress/components/Docs/MobileNavSheet.vue +148 -0
  597. package/vitepress/components/Docs/OnThisPage.vue +74 -0
  598. package/vitepress/components/Docs/PrevNextBtns.vue +58 -0
  599. package/vitepress/components/Docs/PropsTable.vue +185 -0
  600. package/vitepress/components/Docs/Search.vue +34 -0
  601. package/vitepress/components/Docs/Sidebar.vue +91 -0
  602. package/vitepress/components/Docs/SlotsTable.vue +99 -0
  603. package/vitepress/components/Docs/index.ts +13 -0
  604. package/vitepress/components/Docs/sidebarList.ts +33 -0
  605. package/vitepress/components/Layout.vue +58 -0
  606. package/vitepress/components/Navbar.vue +126 -0
  607. package/vitepress/components/index.ts +3 -0
  608. package/vitepress/composables/index.ts +3 -0
  609. package/vitepress/composables/useTheme.ts +39 -0
  610. package/vitepress/css/fonts/Inter-Italic.var.woff2 +0 -0
  611. package/vitepress/css/fonts/Inter.var.woff2 +0 -0
  612. package/vitepress/css/style.css +198 -0
  613. package/vitepress/index.d.ts +23 -0
  614. package/vitepress/index.node.ts +175 -0
  615. package/vitepress/index.ts +17 -0
  616. package/vitepress/plugins/colocatedComponentDocs.ts +164 -0
  617. package/vitepress/plugins/componentTransformer.ts +286 -0
  618. package/vitepress/plugins/index.ts +12 -0
  619. package/vitepress/state.ts +10 -0
  620. package/vitepress/theme/index.ts +21 -0
  621. package/frappe/telemetry/utils.ts +0 -15
  622. package/src/components/Badge/stories/Themes.vue +0 -10
  623. package/src/components/Badge/stories/Variants.vue +0 -10
  624. package/src/components/Button/stories/Icons.vue +0 -27
  625. package/src/components/Button/stories/Sizes.vue +0 -11
  626. package/src/components/Button/stories/Themes.vue +0 -10
  627. package/src/components/Button/stories/Variants.vue +0 -10
  628. package/src/components/Popover/stories/Hover.vue +0 -14
  629. package/src/components/Sidebar/stories/Example.vue +0 -76
  630. package/src/components/Spinner.vue +0 -3
  631. package/src/components/TabButtons/TabButtons.story.vue +0 -22
  632. /package/src/components/Dropdown/stories/{02_Shortcuts.vue → Shortcuts.vue} +0 -0
  633. /package/src/components/Dropdown/stories/{01_Simple.vue → Simple.vue} +0 -0
  634. /package/src/components/Dropdown/stories/{03_Submenus.vue → Submenus.vue} +0 -0
  635. /package/src/components/Dropdown/stories/{04_Switches.vue → Switches.vue} +0 -0
  636. /package/src/components/{Dropdown/DropdownRenderContentAsChild.vue → Menu/MenuRenderContentAsChild.vue} +0 -0
@@ -0,0 +1,328 @@
1
+ import { defineComponent, h, ref } from 'vue'
2
+ import FloatingWindow from './FloatingWindow.vue'
3
+ import type { WindowMode } from './types'
4
+
5
+ // A controlled host that surfaces the bound mode as text, so the v-model sync is
6
+ // observable, and forwards a storageKey for the persistence tests. The default
7
+ // slot is tagged so we can assert it collapses in the tray.
8
+ function controlledWindow(
9
+ options: {
10
+ initialMode?: WindowMode
11
+ storageKey?: string | null
12
+ title?: string
13
+ } = {},
14
+ ) {
15
+ return defineComponent({
16
+ setup() {
17
+ const mode = ref<WindowMode>(options.initialMode ?? 'docked')
18
+ return { mode }
19
+ },
20
+ render() {
21
+ return [
22
+ h('span', { 'data-cy': 'mode' }, this.mode),
23
+ h(
24
+ FloatingWindow,
25
+ {
26
+ title: options.title ?? 'Messages',
27
+ mode: this.mode,
28
+ 'onUpdate:mode': (value: WindowMode) => (this.mode = value),
29
+ storageKey: options.storageKey ?? null,
30
+ },
31
+ { default: () => h('div', { 'data-cy': 'body' }, 'Conversation') },
32
+ ),
33
+ ]
34
+ },
35
+ })
36
+ }
37
+
38
+ // Simulate a pointer resize drag on a named edge/corner handle. Only the delta
39
+ // matters: startResize records the pointerdown position, then the window-level
40
+ // pointermove/pointerup (bubbled up from <body>) drive the resize loop.
41
+ function dragResize(
42
+ name: string,
43
+ from: { x: number; y: number },
44
+ delta: { x: number; y: number },
45
+ ) {
46
+ cy.get(`[data-resize=${name}]`).trigger('pointerdown', {
47
+ eventConstructor: 'PointerEvent',
48
+ button: 0,
49
+ clientX: from.x,
50
+ clientY: from.y,
51
+ })
52
+ cy.get('body').trigger('pointermove', {
53
+ eventConstructor: 'PointerEvent',
54
+ clientX: from.x + delta.x,
55
+ clientY: from.y + delta.y,
56
+ })
57
+ cy.get('body').trigger('pointerup', { eventConstructor: 'PointerEvent' })
58
+ }
59
+
60
+ describe('FloatingWindow', () => {
61
+ beforeEach(() => {
62
+ // Deterministic geometry: the default panel (460×520) fits with room to
63
+ // spare, so nothing clamps to the viewport.
64
+ cy.viewport(1200, 800)
65
+ localStorage.clear()
66
+ // The single-detached registry lives in module scope; clear its body flag
67
+ // so one test never bleeds into the next.
68
+ document.body.classList.remove('has-floating-window')
69
+ })
70
+
71
+ it('renders docked by default with the window body visible', () => {
72
+ cy.mount(controlledWindow())
73
+
74
+ cy.get('[data-cy=mode]').should('have.text', 'docked')
75
+ cy.get('.floating-window')
76
+ .should('exist')
77
+ .and('have.attr', 'data-state', 'docked')
78
+ cy.get('[data-cy=body]').should('be.visible')
79
+ // Docked offers pop-out and minimize, never a close.
80
+ cy.get('[aria-label="Pop out"]').should('exist')
81
+ cy.get('[aria-label="Close"]').should('not.exist')
82
+ })
83
+
84
+ it('walks docked → floating → minimized → floating → docked from the chrome', () => {
85
+ cy.mount(controlledWindow())
86
+
87
+ // Pop out: detaches, casts a shadow, and surfaces the resize grip.
88
+ cy.get('[aria-label="Pop out"]').click()
89
+ cy.get('[data-cy=mode]').should('have.text', 'floating')
90
+ cy.get('.floating-window').should('have.attr', 'data-state', 'floating')
91
+ cy.get('[aria-label="Resize window"]').should('exist')
92
+ cy.get('[data-cy=body]').should('be.visible')
93
+
94
+ // Minimize: collapses to the tray, hiding the body, narrowed to 320px.
95
+ cy.get('[aria-label="Minimize"]').click()
96
+ cy.get('[data-cy=mode]').should('have.text', 'minimized')
97
+ cy.get('[data-cy=body]').should('not.be.visible')
98
+ cy.get('.floating-window').invoke('outerWidth').should('eq', 320)
99
+
100
+ // Expand back out of the tray.
101
+ cy.get('[aria-label="Expand"]').click()
102
+ cy.get('[data-cy=mode]').should('have.text', 'floating')
103
+ cy.get('[data-cy=body]').should('be.visible')
104
+
105
+ // Close: docks back into the host layout.
106
+ cy.get('[aria-label="Close"]').click()
107
+ cy.get('[data-cy=mode]').should('have.text', 'docked')
108
+ cy.get('.floating-window').should('have.attr', 'data-state', 'docked')
109
+ })
110
+
111
+ it('lets the host drive the window through v-model:mode', () => {
112
+ const Host = defineComponent({
113
+ setup() {
114
+ const mode = ref<WindowMode>('docked')
115
+ return { mode }
116
+ },
117
+ render() {
118
+ return [
119
+ h(
120
+ 'button',
121
+ { 'data-cy': 'go-float', onClick: () => (this.mode = 'floating') },
122
+ 'float',
123
+ ),
124
+ h(FloatingWindow, {
125
+ title: 'Messages',
126
+ mode: this.mode,
127
+ 'onUpdate:mode': (value: WindowMode) => (this.mode = value),
128
+ }),
129
+ ]
130
+ },
131
+ })
132
+
133
+ cy.mount(Host)
134
+ cy.get('.floating-window').should('have.attr', 'data-state', 'docked')
135
+
136
+ // Host flips the bound mode; the window detaches to match.
137
+ cy.get('[data-cy=go-float]').click()
138
+ cy.get('.floating-window').should('have.attr', 'data-state', 'floating')
139
+ cy.get('[aria-label="Close"]').should('exist')
140
+ })
141
+
142
+ describe('persistence', () => {
143
+ const KEY = 'fw:test:persist'
144
+
145
+ it('restores a saved mode on mount and adopts it into v-model', () => {
146
+ // Seed a floating session, then mount with the bound mode still at its
147
+ // docked default. The restored mode must win and propagate up, or a
148
+ // controlled host would render docked over a floating window.
149
+ localStorage.setItem(
150
+ KEY,
151
+ JSON.stringify({
152
+ mode: 'floating',
153
+ rect: { x: 120, y: 120, width: 500, height: 400 },
154
+ }),
155
+ )
156
+
157
+ cy.mount(controlledWindow({ storageKey: KEY, initialMode: 'docked' }))
158
+
159
+ cy.get('[data-cy=mode]').should('have.text', 'floating')
160
+ cy.get('.floating-window').should('have.attr', 'data-state', 'floating')
161
+ cy.get('.floating-window').invoke('outerWidth').should('eq', 500)
162
+ })
163
+
164
+ it('writes mode changes back to storage', () => {
165
+ cy.mount(controlledWindow({ storageKey: KEY }))
166
+
167
+ cy.get('[aria-label="Pop out"]').click()
168
+ cy.get('[data-cy=mode]').should('have.text', 'floating')
169
+ cy.wrap(null).then(() => {
170
+ const saved = JSON.parse(localStorage.getItem(KEY) as string)
171
+ expect(saved.mode).to.equal('floating')
172
+ })
173
+ })
174
+ })
175
+
176
+ it('pins the previously detached window when a second one pops out', () => {
177
+ // Only one window may float at a time; popping a second one out docks the
178
+ // first via the shared activeDocker registry.
179
+ const TwoWindows = defineComponent({
180
+ render() {
181
+ return [
182
+ h(FloatingWindow, { title: 'Window A' }),
183
+ h(FloatingWindow, { title: 'Window B' }),
184
+ ]
185
+ },
186
+ })
187
+
188
+ cy.mount(TwoWindows)
189
+
190
+ cy.contains('.floating-window', 'Window A')
191
+ .find('[aria-label="Pop out"]')
192
+ .click()
193
+ cy.contains('.floating-window', 'Window A').should(
194
+ 'have.attr',
195
+ 'data-state',
196
+ 'floating',
197
+ )
198
+
199
+ cy.contains('.floating-window', 'Window B')
200
+ .find('[aria-label="Pop out"]')
201
+ .click()
202
+
203
+ // B is now floating; A was pinned back into the docked layout.
204
+ cy.contains('.floating-window', 'Window B').should(
205
+ 'have.attr',
206
+ 'data-state',
207
+ 'floating',
208
+ )
209
+ cy.contains('.floating-window', 'Window A')
210
+ .should('have.attr', 'data-state', 'docked')
211
+ .find('[aria-label="Pop out"]')
212
+ .should('exist')
213
+ })
214
+
215
+ describe('keyboard accessibility', () => {
216
+ it('names every window control for assistive tech', () => {
217
+ cy.mount(controlledWindow())
218
+
219
+ cy.get('[aria-label="Minimize"]').should('exist')
220
+ cy.get('[aria-label="Pop out"]').should('exist')
221
+
222
+ cy.get('[aria-label="Pop out"]').click()
223
+ cy.get('[aria-label="Close"]').should('exist')
224
+ cy.get('[aria-label="Resize window"]').should('exist')
225
+ })
226
+
227
+ it('expands from the tray via a focusable, named control', () => {
228
+ cy.mount(controlledWindow())
229
+
230
+ cy.get('[aria-label="Pop out"]').click()
231
+ cy.get('[aria-label="Minimize"]').click()
232
+ cy.get('[data-cy=mode]').should('have.text', 'minimized')
233
+ cy.get('[data-cy=body]').should('not.be.visible')
234
+
235
+ // Reachable by keyboard and named; being a native <button> it activates on
236
+ // Enter/Space. Clicking exercises the same handler that activation fires.
237
+ cy.get('[aria-label="Expand"]')
238
+ .should('be.visible')
239
+ .focus()
240
+ .should('be.focused')
241
+ .click()
242
+
243
+ cy.get('[data-cy=mode]').should('have.text', 'floating')
244
+ cy.get('[data-cy=body]').should('be.visible')
245
+ })
246
+
247
+ it('resizes with the arrow keys on the grip, clamped to the minimum', () => {
248
+ cy.mount(controlledWindow())
249
+ cy.get('[aria-label="Pop out"]').click()
250
+ cy.get('.floating-window').invoke('outerWidth').should('eq', 460)
251
+
252
+ // One arrow step is 16px.
253
+ cy.get('[aria-label="Resize window"]')
254
+ .focus()
255
+ .trigger('keydown', { key: 'ArrowRight' })
256
+ cy.get('.floating-window').invoke('outerWidth').should('eq', 476)
257
+
258
+ // Shrinking past the 380px floor stops at the floor, never below.
259
+ cy.get('[aria-label="Resize window"]').then(($grip) => {
260
+ for (let i = 0; i < 20; i++) {
261
+ cy.wrap($grip).trigger('keydown', { key: 'ArrowLeft' })
262
+ }
263
+ })
264
+ cy.get('.floating-window').invoke('outerWidth').should('eq', 380)
265
+ })
266
+ })
267
+
268
+ describe('pointercancel cleanup', () => {
269
+ it('stops resize and removes move listener when resize is cancelled by the browser', () => {
270
+ cy.mount(controlledWindow())
271
+ cy.get('[aria-label="Pop out"]').click()
272
+ cy.get('.floating-window').invoke('outerWidth').should('eq', 460)
273
+
274
+ // Start resize, then cancel (OS gesture / context-menu interrupt).
275
+ cy.get('[data-resize=se]').trigger('pointerdown', {
276
+ eventConstructor: 'PointerEvent',
277
+ button: 0,
278
+ clientX: 800,
279
+ clientY: 700,
280
+ })
281
+ cy.get('body').trigger('pointercancel', { eventConstructor: 'PointerEvent' })
282
+ // Move after cancel must be a no-op (listener torn down).
283
+ cy.get('body').trigger('pointermove', {
284
+ eventConstructor: 'PointerEvent',
285
+ clientX: 900,
286
+ clientY: 800,
287
+ })
288
+ cy.get('.floating-window').invoke('outerWidth').should('eq', 460)
289
+ })
290
+ })
291
+
292
+ describe('edge and corner resize', () => {
293
+ // Floating default is 460×520, parked bottom-right in the 1200×800 viewport:
294
+ // x = 1200 - 460 - 24 = 716, y = 800 - 520 - 24 = 256.
295
+ const START_X = 716
296
+ const START_Y = 256
297
+
298
+ it('grows from the bottom-right corner, leaving the origin put', () => {
299
+ cy.mount(controlledWindow())
300
+ cy.get('[aria-label="Pop out"]').click()
301
+
302
+ dragResize('se', { x: START_X + 460, y: START_Y + 520 }, { x: 40, y: 30 })
303
+
304
+ cy.get('.floating-window').should(($el) => {
305
+ const r = $el[0].getBoundingClientRect()
306
+ expect(r.width).to.be.closeTo(500, 1)
307
+ expect(r.height).to.be.closeTo(550, 1)
308
+ expect(r.left).to.be.closeTo(START_X, 1)
309
+ expect(r.top).to.be.closeTo(START_Y, 1)
310
+ })
311
+ })
312
+
313
+ it('grows from the top-left corner, shifting the origin to anchor the opposite edge', () => {
314
+ cy.mount(controlledWindow())
315
+ cy.get('[aria-label="Pop out"]').click()
316
+
317
+ dragResize('nw', { x: START_X, y: START_Y }, { x: -40, y: -30 })
318
+
319
+ cy.get('.floating-window').should(($el) => {
320
+ const r = $el[0].getBoundingClientRect()
321
+ expect(r.width).to.be.closeTo(500, 1)
322
+ expect(r.height).to.be.closeTo(550, 1)
323
+ expect(r.left).to.be.closeTo(START_X - 40, 1)
324
+ expect(r.top).to.be.closeTo(START_Y - 30, 1)
325
+ })
326
+ })
327
+ })
328
+ })
@@ -0,0 +1,303 @@
1
+ <template>
2
+ <!--
3
+ Teleport to <body> in every detached mode so the panel escapes any host
4
+ layout's overflow/stacking contexts. `disabled` keeps it in-flow while
5
+ docked. Teleport moves the node without remounting, so content inside (an
6
+ editor, a form) never loses focus or state.
7
+ -->
8
+ <Teleport to="body" :disabled="isDocked">
9
+ <div
10
+ ref="panel"
11
+ v-bind="$attrs"
12
+ :data-state="mode"
13
+ class="floating-window relative flex flex-col rounded-lg border border-outline-gray-2 bg-surface-elevation-1"
14
+ :class="isDocked ? '' : 'shadow-2xl'"
15
+ :style="style"
16
+ >
17
+ <!-- Title bar, doubles as the drag handle while floating. A host-supplied
18
+ `header` slot replaces it entirely (and becomes the drag handle);
19
+ otherwise the built-in chrome renders. -->
20
+ <div
21
+ ref="handle"
22
+ class="flex select-none items-center justify-between gap-2"
23
+ :class="[
24
+ isFloating ? 'cursor-move' : '',
25
+ $slots.header ? '' : 'gap-2 px-2.5 py-1.5',
26
+ ]"
27
+ @pointerdown="startDrag"
28
+ >
29
+ <!-- Host-owned header: takes the whole row, supplies its own controls. -->
30
+ <div v-if="$slots.header" class="min-w-0 flex-1">
31
+ <slot
32
+ name="header"
33
+ v-bind="{ mode, dock, float, minimize, expandFromTray }"
34
+ />
35
+ </div>
36
+
37
+ <!-- Built-in chrome -->
38
+ <template v-else>
39
+ <div class="flex min-w-0 items-center gap-2">
40
+ <span class="truncate text-sm font-medium text-ink-gray-8">
41
+ {{ title }}
42
+ </span>
43
+ </div>
44
+
45
+ <div class="flex shrink-0 items-center gap-1">
46
+ <slot name="actions" v-bind="{ mode, dock, float, minimize }" />
47
+ <!-- Minimize control, hidden when the window is not minimizable. In
48
+ the tray it's already minimized, so offer to expand it back out;
49
+ everywhere else offer to minimize. -->
50
+ <template v-if="minimizable">
51
+ <Button
52
+ v-if="isMinimized"
53
+ variant="ghost"
54
+ label="Expand"
55
+ tooltip="Expand"
56
+ @click="expandFromTray"
57
+ >
58
+ <template #icon><LucideMaximize2 class="h-4 w-4" /></template>
59
+ </Button>
60
+ <Button
61
+ v-else
62
+ variant="ghost"
63
+ label="Minimize"
64
+ tooltip="Minimize"
65
+ @click="minimize"
66
+ >
67
+ <template #icon><LucideMinus class="h-4 w-4" /></template>
68
+ </Button>
69
+ </template>
70
+ <!-- Expand to a floating window when docked; close (dock back) once
71
+ detached. The close (X) action always sits last. -->
72
+ <Button
73
+ variant="ghost"
74
+ :label="isDocked ? 'Pop out' : 'Close'"
75
+ :tooltip="isDocked ? 'Pop out' : 'Close'"
76
+ @click="isDocked ? float() : dock()"
77
+ >
78
+ <template #icon>
79
+ <component
80
+ :is="isDocked ? LucideMaximize2 : LucideX"
81
+ class="h-4 w-4"
82
+ />
83
+ </template>
84
+ </Button>
85
+ </div>
86
+ </template>
87
+ </div>
88
+
89
+ <!-- Body, collapsed in the tray (minimized) -->
90
+ <div v-show="!isMinimized" class="min-h-0 flex-1 overflow-auto">
91
+ <slot :mode="mode" />
92
+ </div>
93
+
94
+ <!-- Pinned footer (composer / action bar), collapsed in the tray. -->
95
+ <div v-if="$slots.footer" v-show="!isMinimized" class="shrink-0">
96
+ <slot name="footer" :mode="mode" />
97
+ </div>
98
+
99
+ <!-- Resize zones straddling each edge and corner (floating only), so the
100
+ window can be dragged from any side. They're hidden from assistive
101
+ tech; the bottom-right corner is rendered as a focusable button that
102
+ also resizes with the arrow keys, the keyboard counterpart. -->
103
+ <template v-if="isFloating">
104
+ <div
105
+ v-for="handle in resizeHandles"
106
+ :key="handle.name"
107
+ :data-resize="handle.name"
108
+ aria-hidden="true"
109
+ class="absolute"
110
+ :class="handle.class"
111
+ @pointerdown.prevent="startResize($event, handle.dir)"
112
+ />
113
+ <button
114
+ type="button"
115
+ data-resize="se"
116
+ aria-label="Resize window"
117
+ class="absolute -bottom-1 -right-1 z-10 size-4 cursor-nwse-resize rounded-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-outline-gray-3"
118
+ @pointerdown.prevent="startResize($event, { x: 1, y: 1 })"
119
+ @keydown="onResizeKey"
120
+ />
121
+ </template>
122
+ </div>
123
+ </Teleport>
124
+ </template>
125
+
126
+ <script setup lang="ts">
127
+ import { computed, ref, watch } from 'vue'
128
+ import LucideX from '~icons/lucide/x'
129
+ import LucideMinus from '~icons/lucide/minus'
130
+ import LucideMaximize2 from '~icons/lucide/maximize-2'
131
+ import { Button } from '#components/Button'
132
+ import { useFloatingWindow } from './useFloatingWindow'
133
+ import type { ResizeDir, WindowMode } from './types'
134
+
135
+ // The template root is <Teleport>, which can't receive fallthrough attributes,
136
+ // so forward them (class, style, etc.) onto the panel element explicitly.
137
+ defineOptions({ inheritAttrs: false })
138
+
139
+ /** Window state. Use `v-model:mode` to drive it from the host. */
140
+ const modeModel = defineModel<WindowMode>('mode', { default: 'docked' })
141
+
142
+ const props = withDefaults(
143
+ defineProps<{
144
+ /** Title shown in the built-in title bar. */
145
+ title?: string
146
+ /**
147
+ * localStorage key to persist `{ mode, rect }` under. When `null`,
148
+ * geometry resets every session.
149
+ */
150
+ storageKey?: string | null
151
+ /**
152
+ * Whether the window can collapse to the bottom-right tray. When `false`,
153
+ * the minimize control is hidden, leaving only dock and pop-out, for
154
+ * composer-style windows that should never minimize.
155
+ */
156
+ minimizable?: boolean
157
+ }>(),
158
+ {
159
+ title: '',
160
+ storageKey: null,
161
+ minimizable: true,
162
+ },
163
+ )
164
+
165
+ defineSlots<{
166
+ /** The scrollable window body. Receives the current `mode`. */
167
+ default?: (props: { mode: WindowMode }) => any
168
+ /**
169
+ * Pinned region below the scrollable body, for a composer or action bar.
170
+ * Hidden, along with the body, while minimized.
171
+ */
172
+ footer?: (props: { mode: WindowMode }) => any
173
+ /**
174
+ * Replaces the built-in title bar entirely. Becomes the drag handle while
175
+ * floating and must supply its own controls from the slot props.
176
+ */
177
+ header?: (props: {
178
+ mode: WindowMode
179
+ dock: () => void
180
+ float: () => void
181
+ minimize: () => void
182
+ expandFromTray: () => void
183
+ }) => any
184
+ /** Extra controls inserted before the built-in window buttons. */
185
+ actions?: (props: {
186
+ mode: WindowMode
187
+ dock: () => void
188
+ float: () => void
189
+ minimize: () => void
190
+ }) => any
191
+ }>()
192
+
193
+ const panel = ref<HTMLElement | null>(null)
194
+ const handle = ref<HTMLElement | null>(null)
195
+
196
+ const {
197
+ mode,
198
+ style,
199
+ startDrag,
200
+ startResize,
201
+ resizeBy,
202
+ dock,
203
+ float,
204
+ minimize,
205
+ expandFromTray,
206
+ setMode,
207
+ } = useFloatingWindow(panel, handle, {
208
+ initialMode: modeModel.value,
209
+ storageKey: props.storageKey,
210
+ })
211
+
212
+ const isDocked = computed(() => mode.value === 'docked')
213
+ const isFloating = computed(() => mode.value === 'floating')
214
+ const isMinimized = computed(() => mode.value === 'minimized')
215
+
216
+ interface ResizeHandle {
217
+ name: string
218
+ dir: ResizeDir
219
+ class: string
220
+ }
221
+
222
+ // Invisible zones straddling each edge and three corners. They extend slightly
223
+ // past the panel so they sit over the shadow, not the body's scrollbar. Corners
224
+ // come after edges so they layer on top and win the cursor where they meet. The
225
+ // bottom-right corner is rendered separately, as the keyboard-capable button.
226
+ const resizeHandles: ResizeHandle[] = [
227
+ {
228
+ name: 'n',
229
+ dir: { x: 0, y: -1 },
230
+ class: '-top-1 inset-x-0 h-2 cursor-ns-resize',
231
+ },
232
+ {
233
+ name: 's',
234
+ dir: { x: 0, y: 1 },
235
+ class: '-bottom-1 inset-x-0 h-2 cursor-ns-resize',
236
+ },
237
+ {
238
+ name: 'w',
239
+ dir: { x: -1, y: 0 },
240
+ class: '-left-1 inset-y-0 w-2 cursor-ew-resize',
241
+ },
242
+ {
243
+ name: 'e',
244
+ dir: { x: 1, y: 0 },
245
+ class: '-right-1 inset-y-0 w-2 cursor-ew-resize',
246
+ },
247
+ {
248
+ name: 'nw',
249
+ dir: { x: -1, y: -1 },
250
+ class: '-top-1 -left-1 size-4 z-10 cursor-nwse-resize',
251
+ },
252
+ {
253
+ name: 'ne',
254
+ dir: { x: 1, y: -1 },
255
+ class: '-top-1 -right-1 size-4 z-10 cursor-nesw-resize',
256
+ },
257
+ {
258
+ name: 'sw',
259
+ dir: { x: -1, y: 1 },
260
+ class: '-bottom-1 -left-1 size-4 z-10 cursor-nesw-resize',
261
+ },
262
+ ]
263
+
264
+ // Keyboard resize: each arrow nudges the panel by a fixed step, the keyboard
265
+ // counterpart to dragging the bottom-right corner.
266
+ const RESIZE_STEP = 16
267
+ const RESIZE_DELTAS: Record<string, [number, number]> = {
268
+ ArrowRight: [RESIZE_STEP, 0],
269
+ ArrowLeft: [-RESIZE_STEP, 0],
270
+ ArrowDown: [0, RESIZE_STEP],
271
+ ArrowUp: [0, -RESIZE_STEP],
272
+ }
273
+
274
+ function onResizeKey(event: KeyboardEvent) {
275
+ const delta = RESIZE_DELTAS[event.key]
276
+ if (!delta) return
277
+ event.preventDefault()
278
+ resizeBy(...delta)
279
+ }
280
+
281
+ // The composable owns `mode` (it restores from storage), so mirror it into the
282
+ // v-model in both directions without looping. Immediate so a host's
283
+ // `v-model:mode` adopts a storage-restored mode on mount, not just on the next
284
+ // change.
285
+ watch(mode, (value) => (modeModel.value = value), { immediate: true })
286
+ watch(modeModel, (value) => value !== mode.value && setMode(value))
287
+ </script>
288
+
289
+ <!--
290
+ Global, intentionally unscoped. While a window is detached it's fixed at
291
+ z-index 50; reka-ui teleports popovers (dropdowns, autocompletes, menus) to
292
+ <body> and copies their content z-index (which is `auto` for most
293
+ components) onto the positioning wrapper, so they render *behind* the window
294
+ and become unclickable. Lift the popover layer to the app's popover z (100,
295
+ matching NestedPopover). `!important` is required to beat reka's inline
296
+ `z-index: auto`. Scoped to `.has-floating-window` so it only applies while a
297
+ window is open; docked windows are in normal flow and need no override.
298
+ -->
299
+ <style>
300
+ body.has-floating-window [data-reka-popper-content-wrapper] {
301
+ z-index: 100 !important;
302
+ }
303
+ </style>
@@ -0,0 +1,8 @@
1
+ export { default as FloatingWindow } from './FloatingWindow.vue'
2
+ export { useFloatingWindow } from './useFloatingWindow'
3
+ export type {
4
+ WindowMode,
5
+ Rect,
6
+ ResizeDir,
7
+ FloatingWindowOptions,
8
+ } from './types'
@@ -0,0 +1,31 @@
1
+ /** The three window states a FloatingWindow can be in. */
2
+ export type WindowMode = 'docked' | 'floating' | 'minimized'
3
+
4
+ /** Position + size of a floating panel, in viewport pixels. */
5
+ export interface Rect {
6
+ x: number
7
+ y: number
8
+ width: number
9
+ height: number
10
+ }
11
+
12
+ /**
13
+ * Which edges a resize drag pulls. `-1` pulls the top/left edge (and moves the
14
+ * panel so the opposite edge stays put), `1` the bottom/right edge, `0` leaves
15
+ * that axis fixed. A corner sets both axes.
16
+ */
17
+ export interface ResizeDir {
18
+ x: -1 | 0 | 1
19
+ y: -1 | 0 | 1
20
+ }
21
+
22
+ /** Options for the headless useFloatingWindow composable. */
23
+ export interface FloatingWindowOptions {
24
+ /** Initial window state. */
25
+ initialMode?: WindowMode
26
+ /**
27
+ * localStorage key to persist `{ mode, rect }` under. When null, geometry
28
+ * resets every session.
29
+ */
30
+ storageKey?: string | null
31
+ }