@flamingo-stack/openframe-frontend-core 0.0.702 → 0.0.703-snapshot.20261006080901

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/{chunk-CYYTD5ZK.cjs → chunk-262YQFOS.cjs} +47 -47
  2. package/dist/{chunk-SAA7B3DA.js → chunk-2G546OND.js} +500 -67
  3. package/dist/{chunk-6JPQDYYY.js → chunk-2JDVL7UV.js} +2614 -2001
  4. package/dist/{chunk-ASN65P3F.cjs → chunk-4DQMTBMK.cjs} +15 -15
  5. package/dist/{chunk-CZ34HXDW.js → chunk-4KGXLVCF.js} +686 -725
  6. package/dist/{chunk-TCZCMD5Q.js → chunk-5HBHO5TB.js} +1 -1
  7. package/dist/{chunk-446KS5BG.js → chunk-5QJDM6UV.js} +5 -5
  8. package/dist/{chunk-UJJWTJ6S.js → chunk-6WQIUQUC.js} +30 -16
  9. package/dist/{chunk-YR6JPJF4.js → chunk-6XIF2Z7I.js} +4 -4
  10. package/dist/{chunk-YGRHITPY.cjs → chunk-7JIL3WOH.cjs} +24 -24
  11. package/dist/{chunk-RS7HWAYX.js → chunk-7LFIKJX2.js} +3 -3
  12. package/dist/{chunk-4OGAXAEG.cjs → chunk-ABU3TGXA.cjs} +3 -3
  13. package/dist/{chunk-3WSFIV5G.cjs → chunk-AMYY3PNL.cjs} +69 -55
  14. package/dist/{chunk-BRXOGCH3.cjs → chunk-BIZNSF3U.cjs} +3 -3
  15. package/dist/{chunk-2A4XES7R.cjs → chunk-BM5MVGH3.cjs} +3 -3
  16. package/dist/{chunk-IIMICN7L.cjs → chunk-C3BWIU7R.cjs} +4 -44
  17. package/dist/{chunk-LZNJH23A.cjs → chunk-C7XGRLT5.cjs} +12 -12
  18. package/dist/{chunk-6YCXA7CN.js → chunk-CH5232HV.js} +20 -19
  19. package/dist/{chunk-UIBSSVON.cjs → chunk-D7BTSVMS.cjs} +2 -2
  20. package/dist/{chunk-BO5ZHDA4.js → chunk-ERCV6HHJ.js} +7 -7
  21. package/dist/{chunk-5V6O73EH.js → chunk-FLKJYMVB.js} +3 -3
  22. package/dist/{chunk-Z5HDCZAS.js → chunk-FXENTYE5.js} +15 -15
  23. package/dist/{chunk-HIXGAY2L.cjs → chunk-HFJW24JJ.cjs} +121 -121
  24. package/dist/{chunk-HPJ4XS2L.js → chunk-HJAYVKWQ.js} +24 -24
  25. package/dist/{chunk-PLK6GHN7.js → chunk-I7DFUQMN.js} +10 -10
  26. package/dist/{chunk-KJ5LWATV.js → chunk-IHU5ANUY.js} +7 -7
  27. package/dist/{chunk-AEQANLBW.cjs → chunk-IZGDG5X6.cjs} +505 -72
  28. package/dist/{chunk-2V4Z44G6.js → chunk-JEYRXT5L.js} +17 -17
  29. package/dist/{chunk-B3LZJ6J3.cjs → chunk-L3IC42IF.cjs} +32 -32
  30. package/dist/{chunk-RTGPIHVO.cjs → chunk-LDPEQGKT.cjs} +2243 -1630
  31. package/dist/{chunk-FD4DBTFO.cjs → chunk-LTSTC3N5.cjs} +1253 -1292
  32. package/dist/{chunk-OGLCD3UL.js → chunk-MFFCUVWG.js} +6 -6
  33. package/dist/{chunk-3L7KCOGO.cjs → chunk-MGGGBHYD.cjs} +30 -30
  34. package/dist/{chunk-QFW3RUZS.cjs → chunk-MI3JUJ75.cjs} +33 -33
  35. package/dist/{chunk-EMCQGFXD.cjs → chunk-MMOODW57.cjs} +28 -27
  36. package/dist/{chunk-Q76ZTHNV.js → chunk-MP2C3LJ4.js} +1 -1
  37. package/dist/{chunk-E3DH543A.cjs → chunk-N3TO7JV3.cjs} +98 -98
  38. package/dist/{chunk-2T6QWNXU.js → chunk-NDESEPW5.js} +99 -11
  39. package/dist/{chunk-ETFFEA3J.js → chunk-NRLJUBCS.js} +7 -7
  40. package/dist/{chunk-HUOLB7ZN.cjs → chunk-OHFS7VOU.cjs} +9 -9
  41. package/dist/{chunk-MWUBBBWZ.js → chunk-QVSFBSWV.js} +10 -10
  42. package/dist/{chunk-MDBHZBW7.cjs → chunk-RKV5B7EU.cjs} +92 -4
  43. package/dist/{chunk-3G76LRGL.cjs → chunk-SYXQLETR.cjs} +29 -29
  44. package/dist/{chunk-FHM7GD5B.cjs → chunk-UK62YTH6.cjs} +28 -14
  45. package/dist/{chunk-HUMWQMCU.js → chunk-UT4YHQZW.js} +3 -3
  46. package/dist/{chunk-NGJCWEYX.cjs → chunk-VHCACDKP.cjs} +46 -46
  47. package/dist/{chunk-5FFNNJM7.js → chunk-VR2ZLFCP.js} +1 -1
  48. package/dist/{chunk-IZKOQVXO.js → chunk-VTASHWER.js} +14 -14
  49. package/dist/{chunk-64O4L7WN.js → chunk-WUJNYGO2.js} +11 -51
  50. package/dist/{chunk-JOY6HVPV.js → chunk-XATTBO5X.js} +25 -11
  51. package/dist/{chunk-L7KZA7ZD.cjs → chunk-XIFOBQRQ.cjs} +6 -6
  52. package/dist/{chunk-CXDJUKC6.cjs → chunk-XJKZ4IKT.cjs} +4 -4
  53. package/dist/{chunk-XCIFHW4E.cjs → chunk-XOYH2YQE.cjs} +2 -2
  54. package/dist/{chunk-HYFLTFFB.js → chunk-ZQFZSFQI.js} +2 -2
  55. package/dist/components/case-studies/index.cjs +24 -24
  56. package/dist/components/case-studies/index.js +10 -10
  57. package/dist/components/chat/chat-archive-page.d.ts +3 -3
  58. package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
  59. package/dist/components/chat/chat-nav-sidebar.d.ts +4 -0
  60. package/dist/components/chat/chat-nav-sidebar.d.ts.map +1 -1
  61. package/dist/components/chat/chat-panel-header.d.ts +3 -2
  62. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  63. package/dist/components/chat/embeddable-chat.d.ts +36 -2
  64. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  65. package/dist/components/chat/index.cjs +14 -6
  66. package/dist/components/chat/index.d.ts +4 -1
  67. package/dist/components/chat/index.d.ts.map +1 -1
  68. package/dist/components/chat/index.js +13 -5
  69. package/dist/components/chat/mingo-archive-list.d.ts +17 -0
  70. package/dist/components/chat/mingo-archive-list.d.ts.map +1 -0
  71. package/dist/components/chat/mingo-chat-header.d.ts +31 -0
  72. package/dist/components/chat/mingo-chat-header.d.ts.map +1 -0
  73. package/dist/components/chat/mingo-chat-rail.d.ts +54 -0
  74. package/dist/components/chat/mingo-chat-rail.d.ts.map +1 -0
  75. package/dist/components/chat/model-display.d.ts.map +1 -1
  76. package/dist/components/contact/contact-form.d.ts.map +1 -1
  77. package/dist/components/contact/index.cjs +11 -11
  78. package/dist/components/contact/index.js +10 -10
  79. package/dist/components/content-loading-container.d.ts +1 -1
  80. package/dist/components/content-loading-container.d.ts.map +1 -1
  81. package/dist/components/docs/index.cjs +14 -14
  82. package/dist/components/docs/index.js +13 -13
  83. package/dist/components/docs/skeletons.d.ts.map +1 -1
  84. package/dist/components/embeds/index.cjs +11 -11
  85. package/dist/components/embeds/index.js +10 -10
  86. package/dist/components/faq/index.cjs +12 -12
  87. package/dist/components/faq/index.js +11 -11
  88. package/dist/components/features/figma-prototype-viewer.d.ts.map +1 -1
  89. package/dist/components/features/index.cjs +10 -10
  90. package/dist/components/features/index.js +9 -9
  91. package/dist/components/features/media-gallery-manager.d.ts.map +1 -1
  92. package/dist/components/features/parallax-image-showcase.d.ts.map +1 -1
  93. package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
  94. package/dist/components/features/section-selector.d.ts.map +1 -1
  95. package/dist/components/help-center-pages/index.cjs +119 -119
  96. package/dist/components/help-center-pages/index.js +39 -39
  97. package/dist/components/help-center-pages/trust-center-sections.d.ts +2 -2
  98. package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
  99. package/dist/components/index.cjs +94 -86
  100. package/dist/components/index.js +32 -24
  101. package/dist/components/layout/article-detail-layout.d.ts +1 -1
  102. package/dist/components/layout/article-detail-layout.d.ts.map +1 -1
  103. package/dist/components/layout/page-container.d.ts.map +1 -1
  104. package/dist/components/layout/title-block.d.ts +2 -2
  105. package/dist/components/layout/title-block.d.ts.map +1 -1
  106. package/dist/components/loading/page-layout-skeleton.d.ts.map +1 -1
  107. package/dist/components/loading/unified-skeleton.d.ts.map +1 -1
  108. package/dist/components/meeting-scheduler/booking-form.d.ts +4 -4
  109. package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
  110. package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
  111. package/dist/components/meeting-scheduler/index.cjs +127 -127
  112. package/dist/components/meeting-scheduler/index.d.ts +2 -2
  113. package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
  114. package/dist/components/meeting-scheduler/index.js +50 -50
  115. package/dist/components/navigation/app-header.d.ts +4 -0
  116. package/dist/components/navigation/app-header.d.ts.map +1 -1
  117. package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
  118. package/dist/components/navigation/app-layout-side-panel.d.ts +142 -0
  119. package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -0
  120. package/dist/components/navigation/app-layout.d.ts +11 -1
  121. package/dist/components/navigation/app-layout.d.ts.map +1 -1
  122. package/dist/components/navigation/header-mingo-button.d.ts +4 -1
  123. package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
  124. package/dist/components/navigation/index.cjs +13 -13
  125. package/dist/components/navigation/index.d.ts +1 -0
  126. package/dist/components/navigation/index.d.ts.map +1 -1
  127. package/dist/components/navigation/index.js +12 -12
  128. package/dist/components/navigation/navigation-sidebar.d.ts +8 -1
  129. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  130. package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
  131. package/dist/components/onboarding-guides/index.cjs +14 -14
  132. package/dist/components/onboarding-guides/index.js +13 -13
  133. package/dist/components/persistent-filter-controls.d.ts.map +1 -1
  134. package/dist/components/related-content/index.cjs +7 -7
  135. package/dist/components/related-content/index.js +6 -6
  136. package/dist/components/shared/detail-page-skeleton.d.ts.map +1 -1
  137. package/dist/components/tickets/help-center-create-form.d.ts +4 -4
  138. package/dist/components/tickets/index.cjs +15 -15
  139. package/dist/components/tickets/index.js +14 -14
  140. package/dist/components/ui/announcement-bar-view.d.ts.map +1 -1
  141. package/dist/components/ui/breadcrumb.d.ts.map +1 -1
  142. package/dist/components/ui/checkbox-block.d.ts.map +1 -1
  143. package/dist/components/ui/dashboard-info-card.d.ts +1 -1
  144. package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
  145. package/dist/components/ui/data-table/data-table-body.d.ts +2 -2
  146. package/dist/components/ui/data-table/data-table-skeleton.d.ts +2 -2
  147. package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
  148. package/dist/components/ui/data-table/utils.d.ts +2 -2
  149. package/dist/components/ui/dialog.d.ts.map +1 -1
  150. package/dist/components/ui/drawer.d.ts +7 -3
  151. package/dist/components/ui/drawer.d.ts.map +1 -1
  152. package/dist/components/ui/entity-image.d.ts +2 -2
  153. package/dist/components/ui/file-manager/index.cjs +36 -36
  154. package/dist/components/ui/file-manager/index.js +3 -3
  155. package/dist/components/ui/filter-modal.d.ts.map +1 -1
  156. package/dist/components/ui/index.cjs +18 -10
  157. package/dist/components/ui/index.js +17 -9
  158. package/dist/components/ui/info-card-row.d.ts.map +1 -1
  159. package/dist/components/ui/menubar.d.ts +1 -1
  160. package/dist/components/ui/menubar.d.ts.map +1 -1
  161. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  162. package/dist/components/ui/modal.d.ts.map +1 -1
  163. package/dist/components/ui/no-data/no-data-actions.d.ts.map +1 -1
  164. package/dist/components/ui/no-data/no-data-styles.d.ts +1 -1
  165. package/dist/components/ui/no-data/no-data-styles.d.ts.map +1 -1
  166. package/dist/components/ui/page-actions.d.ts.map +1 -1
  167. package/dist/components/ui/panel-resize-handle.d.ts +25 -0
  168. package/dist/components/ui/panel-resize-handle.d.ts.map +1 -0
  169. package/dist/components/ui/sheet.d.ts.map +1 -1
  170. package/dist/components/ui/table/table-skeleton.d.ts +1 -1
  171. package/dist/components/ui/table/table-skeleton.d.ts.map +1 -1
  172. package/dist/hooks/index.cjs +23 -9
  173. package/dist/hooks/index.js +24 -10
  174. package/dist/hooks/ui/index.d.ts +1 -0
  175. package/dist/hooks/ui/index.d.ts.map +1 -1
  176. package/dist/hooks/ui/use-content-breakpoint.d.ts +32 -0
  177. package/dist/hooks/ui/use-content-breakpoint.d.ts.map +1 -0
  178. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts +2 -2
  179. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts.map +1 -1
  180. package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
  181. package/dist/hooks/ui/use-resizable-panel-size.d.ts +46 -0
  182. package/dist/hooks/ui/use-resizable-panel-size.d.ts.map +1 -0
  183. package/dist/index.cjs +39 -17
  184. package/dist/index.js +41 -19
  185. package/dist/styles/layout-steps.d.ts +30 -0
  186. package/dist/styles/layout-steps.d.ts.map +1 -0
  187. package/dist/tailwind.config.cjs +36 -11
  188. package/dist/tailwind.config.js +36 -11
  189. package/eslint-config/README.md +9 -0
  190. package/eslint-config/base.js +17 -1
  191. package/eslint-config/layout-variants.js +57 -0
  192. package/package.json +3 -1
  193. package/src/ODS_TOKEN_RULES.md +40 -0
  194. package/src/components/chat/chat-archive-page.tsx +9 -7
  195. package/src/components/chat/chat-nav-sidebar.tsx +10 -4
  196. package/src/components/chat/chat-panel-header.tsx +8 -5
  197. package/src/components/chat/embeddable-chat.tsx +686 -494
  198. package/src/components/chat/index.ts +4 -0
  199. package/src/components/chat/mingo-archive-list.tsx +108 -0
  200. package/src/components/chat/mingo-chat-header.tsx +104 -0
  201. package/src/components/chat/mingo-chat-rail.tsx +361 -0
  202. package/src/components/chat/model-display.tsx +8 -1
  203. package/src/components/contact/contact-form.tsx +11 -9
  204. package/src/components/content-loading-container.tsx +4 -2
  205. package/src/components/docs/doc-viewer.tsx +7 -7
  206. package/src/components/docs/skeletons.tsx +7 -4
  207. package/src/components/dynamic-skeleton.tsx +1 -1
  208. package/src/components/embeds/claude-embed.tsx +1 -1
  209. package/src/components/embeds/embed-viewer-frame.tsx +4 -4
  210. package/src/components/embeds/figma-embed.tsx +2 -2
  211. package/src/components/embeds/google-sheets-viewer.tsx +1 -1
  212. package/src/components/embeds/pdf-viewer.tsx +3 -3
  213. package/src/components/empty-state.tsx +6 -6
  214. package/src/components/faq/faq-section.tsx +2 -2
  215. package/src/components/faq-accordion.tsx +1 -1
  216. package/src/components/features/board/board-column-header.tsx +1 -1
  217. package/src/components/features/command-box.tsx +3 -3
  218. package/src/components/features/entity-video-section.tsx +2 -2
  219. package/src/components/features/figma-prototype-viewer.tsx +4 -1
  220. package/src/components/features/media-gallery-manager.tsx +3 -1
  221. package/src/components/features/parallax-image-showcase.tsx +9 -6
  222. package/src/components/features/policy-configuration-panel.tsx +9 -7
  223. package/src/components/features/provider-button.tsx +1 -1
  224. package/src/components/features/section-selector.tsx +11 -6
  225. package/src/components/features/select-button.tsx +4 -4
  226. package/src/components/features/seo-editor-preview.tsx +2 -2
  227. package/src/components/features/video-ratio-tabs.tsx +3 -3
  228. package/src/components/features/waitlist-form.tsx +1 -1
  229. package/src/components/help-center-pages/trust-center-page.tsx +1 -1
  230. package/src/components/help-center-pages/trust-center-sections.tsx +14 -14
  231. package/src/components/icon-display.tsx +1 -1
  232. package/src/components/layout/article-detail-layout.tsx +4 -2
  233. package/src/components/layout/list-page-layout.tsx +2 -2
  234. package/src/components/layout/page-container.tsx +26 -14
  235. package/src/components/layout/page-heading.tsx +1 -1
  236. package/src/components/layout/page-layout.tsx +1 -1
  237. package/src/components/layout/title-block.tsx +13 -13
  238. package/src/components/loading/card-skeleton.tsx +11 -11
  239. package/src/components/loading/category-vendor-selector-skeleton.tsx +1 -1
  240. package/src/components/loading/content-skeleton.tsx +6 -6
  241. package/src/components/loading/device-card-skeleton.tsx +1 -1
  242. package/src/components/loading/margin-report-skeleton.tsx +7 -7
  243. package/src/components/loading/msp-profile-form-skeleton.tsx +3 -3
  244. package/src/components/loading/organization-card-skeleton.tsx +2 -2
  245. package/src/components/loading/page-layout-skeleton.tsx +80 -68
  246. package/src/components/loading/unified-skeleton.tsx +16 -6
  247. package/src/components/media-carousel.tsx +3 -3
  248. package/src/components/meeting-scheduler/booking-form.tsx +9 -8
  249. package/src/components/meeting-scheduler/context-panel.tsx +8 -6
  250. package/src/components/meeting-scheduler/directory.tsx +4 -4
  251. package/src/components/meeting-scheduler/index.tsx +15 -15
  252. package/src/components/meeting-scheduler/slot-picker.tsx +12 -12
  253. package/src/components/navigation/app-header.tsx +6 -0
  254. package/src/components/navigation/app-layout-drawer.tsx +14 -237
  255. package/src/components/navigation/app-layout-side-panel.tsx +594 -0
  256. package/src/components/navigation/app-layout.tsx +69 -4
  257. package/src/components/navigation/header-mingo-button.tsx +13 -3
  258. package/src/components/navigation/index.ts +5 -0
  259. package/src/components/navigation/navigation-sidebar.tsx +17 -3
  260. package/src/components/navigation/sliding-sidebar.tsx +2 -1
  261. package/src/components/pagination.tsx +3 -3
  262. package/src/components/persistent-filter-controls.tsx +6 -2
  263. package/src/components/platform/ScriptArguments.tsx +1 -1
  264. package/src/components/platform/ScriptInfoSection.tsx +4 -4
  265. package/src/components/shared/article-author-byline.tsx +1 -1
  266. package/src/components/shared/delivery/delivery-lists.tsx +2 -2
  267. package/src/components/shared/delivery/delivery-row.tsx +7 -7
  268. package/src/components/shared/delivery/delivery-table.tsx +3 -3
  269. package/src/components/shared/detail-page-skeleton.tsx +11 -5
  270. package/src/components/shared/dev-section/dev-card-row.tsx +6 -6
  271. package/src/components/shared/dev-section/dev-section-view.tsx +1 -1
  272. package/src/components/shared/product-release/product-release-card-skeleton.tsx +8 -8
  273. package/src/components/shared/product-release/product-release-card.tsx +9 -9
  274. package/src/components/shared/product-release/product-releases-view.tsx +1 -1
  275. package/src/components/shared/product-release/release-detail-page.tsx +5 -5
  276. package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +1 -1
  277. package/src/components/shared/roadmap/roadmap-grid.tsx +3 -3
  278. package/src/components/tickets/help-center-card.tsx +1 -1
  279. package/src/components/tickets/help-center-create-form.tsx +12 -12
  280. package/src/components/ui/announcement-bar-view.tsx +2 -10
  281. package/src/components/ui/benefit-card.tsx +18 -18
  282. package/src/components/ui/brand-association-grid.tsx +4 -4
  283. package/src/components/ui/breadcrumb.tsx +4 -1
  284. package/src/components/ui/card.tsx +1 -1
  285. package/src/components/ui/checkbox-block.tsx +9 -7
  286. package/src/components/ui/content-loader.tsx +3 -3
  287. package/src/components/ui/cursor-pagination.tsx +3 -3
  288. package/src/components/ui/dashboard-info-card.tsx +13 -9
  289. package/src/components/ui/data-attribution.tsx +2 -2
  290. package/src/components/ui/data-table/data-table-body.tsx +2 -2
  291. package/src/components/ui/data-table/data-table-header.tsx +15 -15
  292. package/src/components/ui/data-table/data-table-skeleton.tsx +6 -6
  293. package/src/components/ui/data-table/utils.ts +5 -5
  294. package/src/components/ui/dialog.tsx +4 -1
  295. package/src/components/ui/drawer.tsx +43 -189
  296. package/src/components/ui/duration-input.tsx +1 -1
  297. package/src/components/ui/entity-image.tsx +3 -3
  298. package/src/components/ui/feature-card.tsx +10 -10
  299. package/src/components/ui/file-manager/file-manager-action-bar.tsx +1 -1
  300. package/src/components/ui/file-manager/file-manager.tsx +1 -1
  301. package/src/components/ui/filter-modal.tsx +8 -1
  302. package/src/components/ui/highlight-card.tsx +7 -7
  303. package/src/components/ui/info-card-row.tsx +10 -6
  304. package/src/components/ui/modal-v2.tsx +7 -2
  305. package/src/components/ui/modal.tsx +3 -2
  306. package/src/components/ui/no-data/no-data-actions.tsx +10 -4
  307. package/src/components/ui/no-data/no-data-styles.ts +1 -1
  308. package/src/components/ui/no-data/no-data.tsx +1 -1
  309. package/src/components/ui/organization-card.tsx +1 -1
  310. package/src/components/ui/page-actions.tsx +34 -5
  311. package/src/components/ui/panel-resize-handle.tsx +184 -0
  312. package/src/components/ui/progress-bar.tsx +2 -2
  313. package/src/components/ui/query-report-table/query-report-table.tsx +1 -1
  314. package/src/components/ui/search-input.tsx +2 -2
  315. package/src/components/ui/service-card.tsx +2 -2
  316. package/src/components/ui/sheet.tsx +2 -1
  317. package/src/components/ui/skeleton.tsx +3 -3
  318. package/src/components/ui/stacked-rows-panel.tsx +4 -4
  319. package/src/components/ui/tab-navigation.tsx +2 -2
  320. package/src/components/ui/tab-selector.tsx +2 -2
  321. package/src/components/ui/table/table-header.tsx +3 -3
  322. package/src/components/ui/table/table-skeleton.tsx +3 -3
  323. package/src/components/ui/table/utils.ts +5 -5
  324. package/src/components/ui/tag-search-input.tsx +3 -3
  325. package/src/components/ui/tags-manager.tsx +5 -5
  326. package/src/components/ui/test-run.tsx +2 -2
  327. package/src/components/ui/ticket-info-section.tsx +1 -1
  328. package/src/components/ui/ticket-notes-section.tsx +1 -1
  329. package/src/components/ui/ticket-status-config-row.tsx +15 -15
  330. package/src/hooks/ui/index.ts +1 -0
  331. package/src/hooks/ui/use-content-breakpoint.ts +58 -0
  332. package/src/hooks/ui/use-media-query.ts +3 -2
  333. package/src/hooks/ui/use-resizable-panel-size.ts +114 -0
  334. package/src/styles/index.css +2 -0
  335. package/src/styles/layout-steps.ts +14 -0
  336. package/src/styles/ods-colors.css +15 -0
  337. package/src/styles/ods-content-area-tokens.css +27 -0
  338. package/src/styles/ods-content-area.css +31 -0
  339. package/src/styles/ods-effects.css +4 -2
  340. package/src/styles/ods-responsive-tokens.css +14 -4
  341. package/tailwind.config.ts +42 -7
@@ -172,7 +172,7 @@ type StashedDetails = {
172
172
  /** Context-panel geometry — ONE definition for the loaded card and the
173
173
  * loading skeleton, which is what keeps the two footprint-identical. */
174
174
  const CONTEXT_PANEL_CLASS =
175
- 'p-[var(--spacing-system-l)] shrink-0 lg:w-[280px] border-b lg:border-b-0 lg:border-r border-ods-border lg:min-h-0 lg:overflow-y-auto';
175
+ 'p-[var(--spacing-system-l)] shrink-0 content-lg:w-[280px] border-b content-lg:border-b-0 content-lg:border-r border-ods-border content-lg:min-h-0 content-lg:overflow-y-auto';
176
176
 
177
177
  /**
178
178
  * Action panel: the elastic half, scrolling inside the fixed card.
@@ -182,7 +182,7 @@ const CONTEXT_PANEL_CLASS =
182
182
  * inset that divider from the card's edges instead of letting it run the full
183
183
  * width the way every other rule in this card does.
184
184
  */
185
- const ACTION_PANEL_CLASS = 'flex-1 min-w-0 flex flex-col md:min-h-0 lg:p-[var(--spacing-system-l)]';
185
+ const ACTION_PANEL_CLASS = 'flex-1 min-w-0 flex flex-col content-md:min-h-0 content-lg:p-[var(--spacing-system-l)]';
186
186
 
187
187
  /**
188
188
  * The action panel's inset for the steps that are ONE block — details,
@@ -195,12 +195,12 @@ const ACTION_PANEL_CLASS = 'flex-1 min-w-0 flex flex-col md:min-h-0 lg:p-[var(--
195
195
  * sections has to bring the same inset with it — without it the form ran edge
196
196
  * to edge on tablet, its inputs touching the card's border.
197
197
  *
198
- * `md:min-h-0 md:overflow-y-auto` comes with it: from `md` up the card states
198
+ * `content-md:min-h-0 content-md:overflow-y-auto` comes with it: from `md` up the card states
199
199
  * a height, and these steps have no inner scroller of their own the way the
200
200
  * times column does, so this is where a long form gets to scroll instead of
201
201
  * being cut off at the card's edge.
202
202
  */
203
- const PANEL_STEP_CLASS = 'p-[var(--spacing-system-l)] md:min-h-0 md:overflow-y-auto lg:p-0';
203
+ const PANEL_STEP_CLASS = 'p-[var(--spacing-system-l)] content-md:min-h-0 content-md:overflow-y-auto content-lg:p-0';
204
204
  /** A step's panel: the inset above on a column that fills the action side. */
205
205
  const STEP_PANEL_CLASS = cn('flex flex-1 flex-col', PANEL_STEP_CLASS);
206
206
 
@@ -248,7 +248,7 @@ const STEP_PANEL_CLASS = cn('flex flex-1 flex-col', PANEL_STEP_CLASS);
248
248
  * video take the leftover space at 16:9; a stand-in that just stacks fixed
249
249
  * blocks will overflow the shorter of the two.
250
250
  */
251
- export const MEETING_SCHEDULER_H = 'md:h-[34.375rem] lg:h-[23.75rem]';
251
+ export const MEETING_SCHEDULER_H = 'content-md:h-[34.375rem] content-lg:h-[23.75rem]';
252
252
 
253
253
  /**
254
254
  * The box for `flow="details-first"`'s CALENDAR and confirmation stages (and its
@@ -265,7 +265,7 @@ export const MEETING_SCHEDULER_H = 'md:h-[34.375rem] lg:h-[23.75rem]';
265
265
  *
266
266
  * Hosts read it through `SCHEDULER_FLOW_PRESETS[flow].height`, never directly.
267
267
  */
268
- export const MEETING_SCHEDULER_DETAILS_FIRST_H = 'h-[50.75rem] md:h-[39.875rem]';
268
+ export const MEETING_SCHEDULER_DETAILS_FIRST_H = 'h-[50.75rem] content-md:h-[39.875rem]';
269
269
 
270
270
  export type SchedulerFlow = 'slot-first' | 'details-first';
271
271
 
@@ -314,24 +314,24 @@ export const SCHEDULER_FLOW_PRESETS: Record<
314
314
  * it on a calendar sized to fit (no scroll) and a times list that scrolls on
315
315
  * its own.
316
316
  *
317
- * `md:flex md:flex-col` is what makes that possible at all. A stated height on
317
+ * `content-md:flex content-md:flex-col` is what makes that possible at all. A stated height on
318
318
  * a plain block only clips (this card is `overflow-hidden` for its corners) —
319
319
  * it is the flex column plus the inner wrapper's `min-h-0` that lets the
320
320
  * content shrink into the height instead of being cut off by it.
321
321
  */
322
322
  const CARD_CLASS = cn(
323
323
  'overflow-hidden rounded-md border border-ods-border bg-ods-card',
324
- 'md:flex md:flex-col',
324
+ 'content-md:flex content-md:flex-col',
325
325
  // The height is NOT baked in any more: it is chosen per flow at each render
326
326
  // site (`cardClass`), because details-first needs a taller box and both
327
327
  // constants are module scope.
328
328
  );
329
329
 
330
- /** Context strip over action panel, side by side from `lg`. `md:flex-1
331
- * md:min-h-0` is the pair that makes it exactly as tall as the card states —
330
+ /** Context strip over action panel, side by side from `lg`. `content-md:flex-1
331
+ * content-md:min-h-0` is the pair that makes it exactly as tall as the card states —
332
332
  * grow into a stated height that content does not reach, shrink into one it
333
333
  * overruns — so neither a short stage nor a long one changes the box. */
334
- const CARD_INNER_CLASS = 'flex flex-col md:min-h-0 md:flex-1 lg:h-full lg:flex-row';
334
+ const CARD_INNER_CLASS = 'flex flex-col content-md:min-h-0 content-md:flex-1 content-lg:h-full content-lg:flex-row';
335
335
 
336
336
  /** The two-line stages (load failure, "booked on HubSpot") — the SAME box as
337
337
  * the booking card, because these are stages of one widget in one slot and a
@@ -340,7 +340,7 @@ const CARD_INNER_CLASS = 'flex flex-col md:min-h-0 md:flex-1 lg:h-full lg:flex-r
340
340
  * outgrows it. */
341
341
  const CARD_DEGRADED_CLASS = cn(
342
342
  'flex flex-col items-start gap-[var(--spacing-system-m)] rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-lf)]',
343
- 'md:justify-center md:overflow-y-auto',
343
+ 'content-md:justify-center content-md:overflow-y-auto',
344
344
  );
345
345
 
346
346
  /**
@@ -723,7 +723,7 @@ export function HubSpotMeetingScheduler({
723
723
  const boxed = !formOnly;
724
724
  const cardClass = cn(
725
725
  CARD_CLASS,
726
- boxed ? preset.height : 'md:h-auto',
726
+ boxed ? preset.height : 'content-md:h-auto',
727
727
  detailsFirst && 'flex flex-col',
728
728
  formOnly && 'bg-ods-bg',
729
729
  className,
@@ -880,11 +880,11 @@ export function HubSpotMeetingScheduler({
880
880
  />
881
881
  </div>
882
882
  ) : (
883
- <div className="flex flex-col gap-[var(--spacing-system-m)] md:min-h-0 md:flex-1">
883
+ <div className="flex flex-col gap-[var(--spacing-system-m)] content-md:min-h-0 content-md:flex-1">
884
884
  {bookingError === 'SLOT_TAKEN' && (
885
885
  <Alert
886
886
  variant="warning"
887
- className="mx-[var(--spacing-system-l)] mt-[var(--spacing-system-l)] w-auto lg:m-0 lg:w-full"
887
+ className="mx-[var(--spacing-system-l)] mt-[var(--spacing-system-l)] w-auto content-lg:m-0 content-lg:w-full"
888
888
  >
889
889
  <AlertDescription>
890
890
  That time was just taken — pick another slot below. (If you already submitted, check your email for
@@ -110,7 +110,7 @@ function timeLabelInZone(ms: number, timeZone: string): string {
110
110
  * 48px targets and the COLUMN widens to house it, rather than the buttons
111
111
  * shrinking to fit a narrower column.
112
112
  */
113
- const CALENDAR_W = 'md:w-[19rem] md:shrink-0 lg:w-[16rem]';
113
+ const CALENDAR_W = 'content-md:w-[19rem] content-md:shrink-0 content-lg:w-[16rem]';
114
114
 
115
115
  /** Column heading ("Select Date & Time" / the chosen day) — body-weight
116
116
  * primary, 4px above its content, mirrored by both skeletons. */
@@ -150,7 +150,7 @@ const CHIP_GRID_CLASS = cn(
150
150
  // 700px card into a 1500px one and buries whatever the page puts below
151
151
  // it. This is the one nested scroller worth having: a small, obviously
152
152
  // bounded list with a visible edge, exactly as drawn.
153
- 'max-h-[9.75rem] md:max-h-none',
153
+ 'max-h-[9.75rem] content-md:max-h-none',
154
154
  // No height of its own, and none borrowed from the calendar either: the card
155
155
  // states one height, the panel and this row pass it down, and what is left
156
156
  // after the column heading IS the scroll box. A long day scrolls inside it.
@@ -188,24 +188,24 @@ const CHIP_CLASS = 'w-full px-[var(--spacing-system-xxs)]';
188
188
  * is worse than the thing it saves.
189
189
  */
190
190
  const SLOT_ROW_CLASS = cn(
191
- 'flex min-w-0 flex-col md:flex-row',
191
+ 'flex min-w-0 flex-col content-md:flex-row',
192
192
  // Default `stretch` at every width, deliberately. Stacked, that is what
193
193
  // makes each section span the card — an `items-start` here (it was here, to
194
194
  // stop a column growing taller than its month) collapses both sections to
195
195
  // their intrinsic WIDTH instead, which on a phone left the calendar and the
196
196
  // chips huddled against the left edge with half the card empty. The
197
197
  // calendar no longer needs protecting: its height is fixed, not derived.
198
- 'md:min-h-0 md:flex-1 lg:gap-[var(--spacing-system-lf)]',
198
+ 'content-md:min-h-0 content-md:flex-1 content-lg:gap-[var(--spacing-system-lf)]',
199
199
  );
200
200
 
201
201
  /** Shared by both columns: self-padded until the panel takes over at `lg`.
202
202
  * Widths are per-column — the calendar states one (`CALENDAR_W`) and the
203
203
  * times take what is left, rather than a 50/50 split that would size the
204
204
  * month by the card's width. */
205
- const COLUMN_BOX = 'p-[var(--spacing-system-l)] lg:p-0';
205
+ const COLUMN_BOX = 'p-[var(--spacing-system-l)] content-lg:p-0';
206
206
 
207
207
  /** The rule between the two, wherever the layout puts it. */
208
- const COLUMN_DIVIDER = 'border-b border-ods-border md:border-b-0 md:border-r lg:border-0';
208
+ const COLUMN_DIVIDER = 'border-b border-ods-border content-md:border-b-0 content-md:border-r content-lg:border-0';
209
209
 
210
210
  /** Deterministic month caption for a given offset (fixed en-US locale so
211
211
  * the SSR and client first-paint markup agree byte-for-byte). */
@@ -288,7 +288,7 @@ function DayCalendar({ monthOffset, onMonthOffsetChange, selectedDay, onSelectDa
288
288
  export function SlotPickerSkeleton({ monthOffset = 0 }: { monthOffset?: number }) {
289
289
  return (
290
290
  <div className={SLOT_ROW_CLASS}>
291
- <div className={cn(COLUMN_STACK, COLUMN_BOX, COLUMN_DIVIDER, CALENDAR_W, 'md:overflow-y-auto')}>
291
+ <div className={cn(COLUMN_STACK, COLUMN_BOX, COLUMN_DIVIDER, CALENDAR_W, 'content-md:overflow-y-auto')}>
292
292
  {/* Needs no data — the heading renders REAL in every loading state. */}
293
293
  <p className={COLUMN_HEADING_CLASS}>{SLOT_PICKER_HEADING}</p>
294
294
  <DayCalendar
@@ -299,7 +299,7 @@ export function SlotPickerSkeleton({ monthOffset = 0 }: { monthOffset?: number }
299
299
  slotsByDay={null}
300
300
  />
301
301
  </div>
302
- <div className={cn(COLUMN_STACK, COLUMN_BOX, 'min-w-0 md:flex-1')}>
302
+ <div className={cn(COLUMN_STACK, COLUMN_BOX, 'min-w-0 content-md:flex-1')}>
303
303
  <TimeChipsSkeleton />
304
304
  </div>
305
305
  </div>
@@ -320,8 +320,8 @@ export function TimeChipsSkeleton() {
320
320
  grid's track count follows the card width, so no fixed number
321
321
  lands flush and a partial last row is the honest shape anyway. */}
322
322
  {Array.from({ length: 16 }, (_, i) => (
323
- // h-11 md:h-12 — the loaded chip's own default-size height.
324
- <Skeleton key={i} className={cn(CHIP_CLASS, 'h-11 md:h-12')} />
323
+ // h-11 content-md:h-12 — the loaded chip's own default-size height.
324
+ <Skeleton key={i} className={cn(CHIP_CLASS, 'h-11 content-md:h-12')} />
325
325
  ))}
326
326
  </div>
327
327
  </>
@@ -367,7 +367,7 @@ export function SlotPicker({
367
367
 
368
368
  return (
369
369
  <div className={SLOT_ROW_CLASS}>
370
- <div className={cn(COLUMN_STACK, COLUMN_BOX, COLUMN_DIVIDER, CALENDAR_W, 'md:overflow-y-auto')}>
370
+ <div className={cn(COLUMN_STACK, COLUMN_BOX, COLUMN_DIVIDER, CALENDAR_W, 'content-md:overflow-y-auto')}>
371
371
  <p className={COLUMN_HEADING_CLASS}>{SLOT_PICKER_HEADING}</p>
372
372
  {/* Stays mounted through a month load — `slotsByDay: null` is what
373
373
  "nothing selectable yet" looks like, not an unmount. */}
@@ -379,7 +379,7 @@ export function SlotPicker({
379
379
  slotsByDay={isLoading ? null : slotsByDay}
380
380
  />
381
381
  </div>
382
- <div className={cn(COLUMN_STACK, COLUMN_BOX, 'min-w-0 md:flex-1')}>
382
+ <div className={cn(COLUMN_STACK, COLUMN_BOX, 'min-w-0 content-md:flex-1')}>
383
383
  {isLoading ? (
384
384
  <TimeChipsSkeleton />
385
385
  ) : selectedDay ? (
@@ -47,6 +47,10 @@ export interface AppHeaderProps {
47
47
  onMingoAI?: () => void;
48
48
  /** Whether the Mingo drawer is currently open (visually pressed state). */
49
49
  isMingoAIActive?: boolean;
50
+ /** Swap the Mingo logo for a close "X" while active, beside the wordmark
51
+ * too (icon-only mode always does). For a panel the button both opens and
52
+ * closes. */
53
+ mingoAICloseWhenActive?: boolean;
50
54
  // User block
51
55
  showUser?: boolean;
52
56
  userName?: string;
@@ -131,6 +135,7 @@ export const AppHeader = React.memo(function AppHeaderImpl({
131
135
  showMingoAI = false,
132
136
  onMingoAI,
133
137
  isMingoAIActive = false,
138
+ mingoAICloseWhenActive = false,
134
139
  showUser,
135
140
  userName,
136
141
  userEmail,
@@ -327,6 +332,7 @@ export const AppHeader = React.memo(function AppHeaderImpl({
327
332
  <HeaderMingoButton
328
333
  onClick={onMingoAI}
329
334
  isActive={isMingoAIActive}
335
+ closeWhenActive={mingoAICloseWhenActive}
330
336
  iconOnly={!isMdUp}
331
337
  disabled={disabled || !onMingoAI}
332
338
  className={cn(cellDivider, dimmedClass)}
@@ -6,9 +6,6 @@ import {
6
6
  type CSSProperties,
7
7
  type ComponentPropsWithoutRef,
8
8
  type ComponentRef,
9
- type KeyboardEvent,
10
- type PointerEvent,
11
- type RefObject,
12
9
  createContext,
13
10
  forwardRef,
14
11
  useCallback,
@@ -18,8 +15,8 @@ import {
18
15
  useState,
19
16
  } from 'react';
20
17
 
18
+ import { useResizablePanelSize } from '../../hooks/ui/use-resizable-panel-size';
21
19
  import { cn } from '../../utils/cn';
22
- import { clamp } from '../../utils/common';
23
20
  import {
24
21
  DrawerBody,
25
22
  DrawerDescription,
@@ -29,6 +26,7 @@ import {
29
26
  OVERLAY_BACKDROP_CLASS,
30
27
  type DrawerSide,
31
28
  } from '../ui/drawer';
29
+ import { PanelResizeHandle } from '../ui/panel-resize-handle';
32
30
  import {
33
31
  type AppLayoutDrawerHandle,
34
32
  useAppLayoutDrawerContainer,
@@ -216,25 +214,8 @@ const HORIZONTAL_SIDES: ReadonlySet<DrawerSide> = new Set(['left', 'right']);
216
214
  */
217
215
  const PERSIST_CLOSED_HOLD = 'data-[state=closed]:fill-mode-forwards';
218
216
 
219
- interface UseContainedResizableSizeArgs {
220
- enabled: boolean;
221
- isHorizontal: boolean;
222
- minSize: number;
223
- maxSize: number;
224
- defaultSize: number;
225
- storageKey?: string;
226
- container: HTMLElement | null;
227
- }
228
-
229
- function useContainedResizableSize({
230
- enabled,
231
- isHorizontal,
232
- minSize,
233
- maxSize,
234
- defaultSize,
235
- storageKey,
236
- container,
237
- }: UseContainedResizableSizeArgs) {
217
+ /** Room the portal container gives the panel along its resize axis, in px. */
218
+ function useContainerExtent(enabled: boolean, isHorizontal: boolean, container: HTMLElement | null): number {
238
219
  const [available, setAvailable] = useState(0);
239
220
 
240
221
  useEffect(() => {
@@ -248,216 +229,7 @@ function useContainedResizableSize({
248
229
  return () => ro.disconnect();
249
230
  }, [enabled, container, isHorizontal]);
250
231
 
251
- const clampToContainer = useCallback(
252
- (value: number) => {
253
- // Reserve 40px (the `system-m` outside-edge padding from the wrapper
254
- // plus a matching gap on the inside edge) so the panel sits symmetrically
255
- // inside the container. This also keeps the resize grip on-screen at
256
- // maximum extent.
257
- const effectiveMax = available > 0 ? Math.min(maxSize, available - 40) : maxSize;
258
- return clamp(value, minSize, Math.max(minSize, effectiveMax));
259
- },
260
- [available, minSize, maxSize],
261
- );
262
-
263
- const readInitial = useCallback(() => {
264
- if (!enabled) return defaultSize;
265
- if (typeof window === 'undefined') return defaultSize;
266
- if (!storageKey) return defaultSize;
267
- try {
268
- const raw = window.localStorage.getItem(storageKey);
269
- if (!raw) return defaultSize;
270
- const parsed = parseFloat(raw);
271
- if (!Number.isFinite(parsed)) return defaultSize;
272
- return parsed;
273
- } catch {
274
- return defaultSize;
275
- }
276
- }, [enabled, storageKey, defaultSize]);
277
-
278
- const [rawSize, setSizeRaw] = useState<number>(readInitial);
279
-
280
- // Re-clamp the stored size whenever the container resizes so a previously
281
- // saved size never overflows after the user shrinks the viewport.
282
- //
283
- // Clamped where it is READ rather than written back into state from an
284
- // effect: `available` is already state (the ResizeObserver above owns it), so
285
- // the container shrinking re-renders regardless — and deriving here means the
286
- // panel is never painted overflowing its container for the frame between the
287
- // resize and the corrective commit. Keeping the raw value also means widening
288
- // the viewport again restores the size the user actually chose, instead of
289
- // leaving it permanently trimmed to the narrowest width ever seen.
290
- const size = enabled ? clampToContainer(rawSize) : rawSize;
291
-
292
- const setSize = useCallback((next: number) => setSizeRaw(clampToContainer(next)), [clampToContainer]);
293
-
294
- useEffect(() => {
295
- if (!enabled || !storageKey || typeof window === 'undefined') return;
296
- try {
297
- window.localStorage.setItem(storageKey, String(Math.round(size)));
298
- } catch {
299
- // ignore quota / disabled-storage
300
- }
301
- }, [enabled, size, storageKey]);
302
-
303
- return { size, setSize, clampSize: clampToContainer };
304
- }
305
-
306
- /** Ends a drawer resize drag: commits the size it reached and restores the
307
- * page's cursor and text selection. No-op when no drag is in progress. */
308
- function finishDrag(
309
- startRef: RefObject<{ x: number; y: number; size: number } | null>,
310
- dragSizeRef: RefObject<number | null>,
311
- commit: (size: number) => void,
312
- ): void {
313
- if (!startRef.current) return;
314
- startRef.current = null;
315
- if (dragSizeRef.current !== null) commit(dragSizeRef.current);
316
- dragSizeRef.current = null;
317
- document.body.style.cursor = '';
318
- document.body.style.userSelect = '';
319
- }
320
-
321
- interface AppLayoutDrawerResizeHandleProps {
322
- side: DrawerSide;
323
- size: number;
324
- minSize: number;
325
- maxSize: number;
326
- onSize: (next: number) => void;
327
- /** Clamp a candidate size to the min/max and the container. */
328
- clampSize: (next: number) => number;
329
- /** The panel the size applies to — written directly while dragging. */
330
- panelRef: RefObject<HTMLDivElement | null>;
331
- ariaLabel?: string;
332
- }
333
-
334
- function AppLayoutDrawerResizeHandle({
335
- side,
336
- size,
337
- minSize,
338
- maxSize,
339
- onSize,
340
- clampSize,
341
- panelRef,
342
- ariaLabel,
343
- }: AppLayoutDrawerResizeHandleProps) {
344
- const isHorizontal = HORIZONTAL_SIDES.has(side);
345
- const startRef = useRef<{ x: number; y: number; size: number } | null>(null);
346
- // Size reached by the drag in progress, committed to React state on release.
347
- const dragSizeRef = useRef<number | null>(null);
348
- const onSizeRef = useRef(onSize);
349
- useEffect(() => {
350
- onSizeRef.current = onSize;
351
- }, [onSize]);
352
- // The handle can unmount mid-drag (a persist-mode drawer closing), and then
353
- // no pointer event ends the drag: end it here, or the panel keeps a width
354
- // React state never received and the page keeps the resize cursor.
355
- useEffect(() => () => finishDrag(startRef, dragSizeRef, onSizeRef.current), []);
356
-
357
- const direction = side === 'right' || side === 'bottom' ? -1 : 1;
358
-
359
- const handlePointerDown = (e: PointerEvent<HTMLDivElement>) => {
360
- if (e.button !== 0 && e.pointerType === 'mouse') return;
361
- e.preventDefault();
362
- startRef.current = { x: e.clientX, y: e.clientY, size };
363
- e.currentTarget.setPointerCapture(e.pointerId);
364
- document.body.style.cursor = isHorizontal ? 'col-resize' : 'row-resize';
365
- document.body.style.userSelect = 'none';
366
- };
367
-
368
- // The drag writes the panel's size straight to the DOM and commits it to
369
- // state once, on release. Committing every pointermove re-rendered the
370
- // drawer per event — ~16% of a drag's CPU in WebKit on top of the reflow the
371
- // resize itself costs. ResizeObserver consumers inside the panel still see
372
- // every change. A re-render of the drawer mid-drag (e.g. a streamed chunk)
373
- // does not undo the write: React diffs against its previous render, where
374
- // the size is unchanged, so it leaves the style alone.
375
- const handlePointerMove = (e: PointerEvent<HTMLDivElement>) => {
376
- const start = startRef.current;
377
- const panel = panelRef.current;
378
- if (!start || !panel) return;
379
- const delta = isHorizontal ? e.clientX - start.x : e.clientY - start.y;
380
- const next = clampSize(start.size + delta * direction);
381
- dragSizeRef.current = next;
382
- panel.style[isHorizontal ? 'width' : 'height'] = `${next}px`;
383
- e.currentTarget.setAttribute('aria-valuenow', String(Math.round(next)));
384
- };
385
-
386
- const endDrag = (e: PointerEvent<HTMLDivElement>) => {
387
- if (!startRef.current) return;
388
- finishDrag(startRef, dragSizeRef, onSize);
389
- try {
390
- e.currentTarget.releasePointerCapture(e.pointerId);
391
- } catch {
392
- // ignore — pointer may already be released
393
- }
394
- };
395
-
396
- const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
397
- const step = e.shiftKey ? 40 : 16;
398
- if (isHorizontal) {
399
- if (e.key === 'ArrowLeft') {
400
- e.preventDefault();
401
- onSize(size + step * (side === 'right' ? 1 : -1));
402
- } else if (e.key === 'ArrowRight') {
403
- e.preventDefault();
404
- onSize(size + step * (side === 'right' ? -1 : 1));
405
- }
406
- } else {
407
- if (e.key === 'ArrowUp') {
408
- e.preventDefault();
409
- onSize(size + step * (side === 'bottom' ? 1 : -1));
410
- } else if (e.key === 'ArrowDown') {
411
- e.preventDefault();
412
- onSize(size + step * (side === 'bottom' ? -1 : 1));
413
- }
414
- }
415
- if (e.key === 'Home') {
416
- e.preventDefault();
417
- onSize(minSize);
418
- } else if (e.key === 'End') {
419
- e.preventDefault();
420
- onSize(maxSize);
421
- }
422
- };
423
-
424
- const trackPosition =
425
- side === 'right'
426
- ? 'right-full top-4 bottom-4 w-6 items-center justify-end pr-1'
427
- : side === 'left'
428
- ? 'left-full top-4 bottom-4 w-6 items-center justify-start pl-1'
429
- : side === 'bottom'
430
- ? 'bottom-full left-4 right-4 h-6 justify-center items-end pb-1'
431
- : 'top-full left-4 right-4 h-6 justify-center items-start pt-1';
432
-
433
- const cursorClass = isHorizontal ? 'cursor-col-resize' : 'cursor-row-resize';
434
- const gripClass = isHorizontal ? 'h-10 w-1' : 'w-10 h-1';
435
-
436
- return (
437
- <div
438
- role="separator"
439
- tabIndex={0}
440
- aria-orientation={isHorizontal ? 'vertical' : 'horizontal'}
441
- aria-valuenow={Math.round(size)}
442
- aria-valuemin={minSize}
443
- aria-valuemax={maxSize}
444
- aria-label={ariaLabel ?? (isHorizontal ? 'Resize panel width' : 'Resize panel height')}
445
- onPointerDown={handlePointerDown}
446
- onPointerMove={handlePointerMove}
447
- onPointerUp={endDrag}
448
- onPointerCancel={endDrag}
449
- onLostPointerCapture={endDrag}
450
- onKeyDown={handleKeyDown}
451
- className={cn(
452
- 'group absolute z-20 flex touch-none select-none',
453
- 'outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
454
- trackPosition,
455
- cursorClass,
456
- )}
457
- >
458
- <div aria-hidden className={cn('rounded-full bg-ods-border', gripClass)} />
459
- </div>
460
- );
232
+ return available;
461
233
  }
462
234
 
463
235
  export interface AppLayoutDrawerContentProps
@@ -628,14 +400,18 @@ const AppLayoutDrawerContent = forwardRef<ComponentRef<typeof DialogPrimitive.Co
628
400
  }, [mobileBreakpoint]);
629
401
 
630
402
  const panelRef = useRef<HTMLDivElement>(null);
631
- const { size, setSize, clampSize } = useContainedResizableSize({
403
+ // Reserve 40px (the `system-m` outside-edge padding from the wrapper plus a
404
+ // matching gap on the inside edge) so the panel sits symmetrically inside
405
+ // the container and the resize grip stays on screen at maximum extent.
406
+ const available = useContainerExtent(resizable, isHorizontal, portalContainer);
407
+ const { size, setSize, clampSize } = useResizablePanelSize({
632
408
  enabled: resizable,
633
- isHorizontal,
634
409
  minSize,
635
410
  maxSize,
636
411
  defaultSize: initialSize,
412
+ available,
413
+ reserve: 40,
637
414
  storageKey,
638
- container: portalContainer,
639
415
  });
640
416
 
641
417
  const applyInlineSize = resizable && !isMobile;
@@ -707,7 +483,8 @@ const AppLayoutDrawerContent = forwardRef<ComponentRef<typeof DialogPrimitive.Co
707
483
  at the container edge. In persist mode it's also non-interactive
708
484
  while closed, so drop it entirely until the panel reopens. */}
709
485
  {applyInlineSize && (!persist || open) ? (
710
- <AppLayoutDrawerResizeHandle
486
+ <PanelResizeHandle
487
+ variant="inLayout"
711
488
  side={resolvedSide}
712
489
  size={size}
713
490
  minSize={minSize}