@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
@@ -8,19 +8,19 @@ import {
8
8
  type ComponentPropsWithoutRef,
9
9
  type ComponentRef,
10
10
  type HTMLAttributes,
11
- type KeyboardEvent,
12
- type PointerEvent,
13
11
  type ReactNode,
14
12
  forwardRef,
15
- useCallback,
16
13
  useEffect,
17
14
  useRef,
18
15
  useState,
16
+ useSyncExternalStore,
19
17
  } from 'react';
20
18
 
19
+ import { ViewportBreakpoints } from '../../hooks/ui/use-content-breakpoint';
21
20
  import { useHeaderHeight } from '../../hooks/ui/use-header-height';
21
+ import { type PanelDefaultSize, useResizablePanelSize } from '../../hooks/ui/use-resizable-panel-size';
22
22
  import { cn } from '../../utils/cn';
23
- import { clamp } from '../../utils/common';
23
+ import { PanelResizeHandle } from './panel-resize-handle';
24
24
 
25
25
  /** Unified overlay backdrop — dimmed, no blur. Single source of truth for
26
26
  * every full-screen backdrop (Drawer, AppLayoutDrawer, MobileBurgerMenu,
@@ -138,181 +138,21 @@ type DrawerSide = 'right' | 'left' | 'top' | 'bottom';
138
138
 
139
139
  const HORIZONTAL_SIDES: ReadonlySet<DrawerSide> = new Set(['left', 'right']);
140
140
 
141
- function viewportSize(isHorizontal: boolean): number {
142
- if (typeof window === 'undefined') return 0;
143
- return isHorizontal ? window.innerWidth : window.innerHeight;
144
- }
145
-
146
- interface UseResizableSizeArgs {
147
- enabled: boolean;
148
- isHorizontal: boolean;
149
- minSize: number;
150
- maxSize: number;
151
- defaultSize: number;
152
- storageKey?: string;
153
- }
154
-
155
- function useResizableSize({ enabled, isHorizontal, minSize, maxSize, defaultSize, storageKey }: UseResizableSizeArgs) {
156
- const clampToViewport = useCallback(
157
- (value: number) => {
158
- const vp = viewportSize(isHorizontal);
159
- const effectiveMax = vp > 0 ? Math.min(maxSize, vp - 80) : maxSize;
160
- return clamp(value, minSize, Math.max(minSize, effectiveMax));
161
- },
162
- [isHorizontal, minSize, maxSize],
163
- );
164
-
165
- const readInitial = useCallback(() => {
166
- if (!enabled) return defaultSize;
167
- if (typeof window === 'undefined') return defaultSize;
168
- if (!storageKey) return clampToViewport(defaultSize);
169
- try {
170
- const raw = window.localStorage.getItem(storageKey);
171
- if (!raw) return clampToViewport(defaultSize);
172
- const parsed = parseFloat(raw);
173
- if (!Number.isFinite(parsed)) return clampToViewport(defaultSize);
174
- return clampToViewport(parsed);
175
- } catch {
176
- return clampToViewport(defaultSize);
177
- }
178
- }, [enabled, storageKey, defaultSize, clampToViewport]);
179
-
180
- const [size, setSizeRaw] = useState<number>(readInitial);
181
-
182
- const setSize = useCallback((next: number) => setSizeRaw(clampToViewport(next)), [clampToViewport]);
183
-
184
- useEffect(() => {
185
- if (!enabled || !storageKey || typeof window === 'undefined') return;
186
- try {
187
- window.localStorage.setItem(storageKey, String(Math.round(size)));
188
- } catch {
189
- // ignore quota / disabled-storage
190
- }
191
- }, [enabled, size, storageKey]);
192
-
193
- useEffect(() => {
194
- if (!enabled || typeof window === 'undefined') return undefined;
195
- const onResize = () => setSizeRaw(prev => clampToViewport(prev));
196
- window.addEventListener('resize', onResize);
197
- return () => window.removeEventListener('resize', onResize);
198
- }, [enabled, clampToViewport]);
199
-
200
- return { size, setSize };
201
- }
202
-
203
- interface DrawerResizeHandleProps {
204
- side: DrawerSide;
205
- size: number;
206
- minSize: number;
207
- maxSize: number;
208
- onSize: (next: number) => void;
209
- ariaLabel?: string;
210
- }
211
-
212
- function DrawerResizeHandle({ side, size, minSize, maxSize, onSize, ariaLabel }: DrawerResizeHandleProps) {
213
- const isHorizontal = HORIZONTAL_SIDES.has(side);
214
- const startRef = useRef<{ x: number; y: number; size: number } | null>(null);
215
-
216
- const direction = side === 'right' || side === 'bottom' ? -1 : 1;
217
-
218
- const handlePointerDown = (e: PointerEvent<HTMLDivElement>) => {
219
- if (e.button !== 0 && e.pointerType === 'mouse') return;
220
- e.preventDefault();
221
- startRef.current = { x: e.clientX, y: e.clientY, size };
222
- e.currentTarget.setPointerCapture(e.pointerId);
223
- document.body.style.cursor = isHorizontal ? 'col-resize' : 'row-resize';
224
- document.body.style.userSelect = 'none';
225
- };
226
-
227
- const handlePointerMove = (e: PointerEvent<HTMLDivElement>) => {
228
- const start = startRef.current;
229
- if (!start) return;
230
- const delta = isHorizontal ? e.clientX - start.x : e.clientY - start.y;
231
- onSize(start.size + delta * direction);
232
- };
233
-
234
- const endDrag = (e: PointerEvent<HTMLDivElement>) => {
235
- if (!startRef.current) return;
236
- startRef.current = null;
237
- try {
238
- e.currentTarget.releasePointerCapture(e.pointerId);
239
- } catch {
240
- // ignore — pointer may already be released
241
- }
242
- document.body.style.cursor = '';
243
- document.body.style.userSelect = '';
244
- };
245
-
246
- const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
247
- const step = e.shiftKey ? 40 : 16;
248
- if (isHorizontal) {
249
- if (e.key === 'ArrowLeft') {
250
- e.preventDefault();
251
- onSize(size + step * (side === 'right' ? 1 : -1));
252
- } else if (e.key === 'ArrowRight') {
253
- e.preventDefault();
254
- onSize(size + step * (side === 'right' ? -1 : 1));
255
- }
256
- } else {
257
- if (e.key === 'ArrowUp') {
258
- e.preventDefault();
259
- onSize(size + step * (side === 'bottom' ? 1 : -1));
260
- } else if (e.key === 'ArrowDown') {
261
- e.preventDefault();
262
- onSize(size + step * (side === 'bottom' ? -1 : 1));
263
- }
264
- }
265
- if (e.key === 'Home') {
266
- e.preventDefault();
267
- onSize(minSize);
268
- } else if (e.key === 'End') {
269
- e.preventDefault();
270
- onSize(maxSize);
271
- }
272
- };
273
-
274
- // Handle is rendered as a SIBLING of the panel (child of DialogPrimitive.Content)
275
- // so the panel can use `overflow-hidden` without clipping the handle.
276
- // Wrapper has `py-4 px-4` (or `md:py-4 md:px-4` for flush on desktop)
277
- // when the handle is visible — handle is desktop-only, where both flush
278
- // and non-flush wrappers carry that padding — so `top-4 bottom-4` (or
279
- // `left-4 right-4`) aligns the handle with the panel's bounds.
280
- const trackPosition =
281
- side === 'right'
282
- ? 'right-full top-4 bottom-4 w-3 items-center justify-end pr-1'
283
- : side === 'left'
284
- ? 'left-full top-4 bottom-4 w-3 items-center justify-start pl-1'
285
- : side === 'bottom'
286
- ? 'bottom-full left-4 right-4 h-3 justify-center items-end pb-1'
287
- : 'top-full left-4 right-4 h-3 justify-center items-start pt-1';
288
-
289
- const cursorClass = isHorizontal ? 'cursor-col-resize' : 'cursor-row-resize';
290
-
291
- const gripClass = isHorizontal ? 'h-10 w-1' : 'w-10 h-1';
292
-
293
- return (
294
- <div
295
- role="separator"
296
- tabIndex={0}
297
- aria-orientation={isHorizontal ? 'vertical' : 'horizontal'}
298
- aria-valuenow={Math.round(size)}
299
- aria-valuemin={minSize}
300
- aria-valuemax={maxSize}
301
- aria-label={ariaLabel ?? (isHorizontal ? 'Resize panel width' : 'Resize panel height')}
302
- onPointerDown={handlePointerDown}
303
- onPointerMove={handlePointerMove}
304
- onPointerUp={endDrag}
305
- onPointerCancel={endDrag}
306
- onKeyDown={handleKeyDown}
307
- className={cn(
308
- 'group absolute z-20 flex touch-none select-none',
309
- 'outline-none ring-0 focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0',
310
- trackPosition,
311
- cursorClass,
312
- )}
313
- >
314
- <div aria-hidden className={cn('rounded-full bg-ods-bg-surface', gripClass)} />
315
- </div>
141
+ const subscribeToResize = (onChange: () => void) => {
142
+ window.addEventListener('resize', onChange);
143
+ return () => window.removeEventListener('resize', onChange);
144
+ };
145
+ const subscribeToNothing = () => () => {};
146
+ const noExtent = () => 0;
147
+
148
+ /** The viewport's extent along the resize axis, in px. 0 on the server and
149
+ * while `enabled` is false, when nothing listens for resizes at all. A change
150
+ * on the other axis (a phone's URL bar collapsing) re-renders nothing. */
151
+ function useViewportExtent(enabled: boolean, isHorizontal: boolean): number {
152
+ return useSyncExternalStore(
153
+ enabled ? subscribeToResize : subscribeToNothing,
154
+ enabled ? (isHorizontal ? () => window.innerWidth : () => window.innerHeight) : noExtent,
155
+ noExtent,
316
156
  );
317
157
  }
318
158
 
@@ -326,9 +166,12 @@ interface DrawerContentBaseProps
326
166
  minSize?: number;
327
167
  /** Maximum allowed size (px) when resizable. Also clamped by viewport. */
328
168
  maxSize?: number;
329
- /** Initial size (px) when no localStorage entry exists. */
330
- defaultSize?: number;
331
- /** localStorage key for persisting the size across sessions. */
169
+ /** Size (px) while the user has not resized the panel. Never stored. Pass a
170
+ * function of the viewport's extent (0 on the server) for a default that
171
+ * tracks the window. */
172
+ defaultSize?: PanelDefaultSize;
173
+ /** localStorage key for the size the user chose with the handle. Only that
174
+ * choice is stored; see `useResizablePanelSize`. */
332
175
  storageKey?: string;
333
176
  /** Pixel breakpoint below which `resizable` is disabled and inline
334
177
  * size is not applied (so consumer CSS can render full-viewport).
@@ -400,8 +243,6 @@ const DrawerContent = forwardRef<ComponentRef<typeof DialogPrimitive.Content>, D
400
243
  ) => {
401
244
  const resolvedSide: DrawerSide = side ?? 'right';
402
245
  const isHorizontal = HORIZONTAL_SIDES.has(resolvedSide);
403
- const initialSize = defaultSize ?? (isHorizontal ? 560 : 480);
404
-
405
246
  const headerHeight = useHeaderHeight();
406
247
 
407
248
  const [isMobile, setIsMobile] = useState(false);
@@ -414,12 +255,21 @@ const DrawerContent = forwardRef<ComponentRef<typeof DialogPrimitive.Content>, D
414
255
  return () => mq.removeEventListener('change', update);
415
256
  }, [mobileBreakpoint]);
416
257
 
417
- const { size: resizedSize, setSize } = useResizableSize({
258
+ // The panel keeps 80px of the viewport free so the page behind it, and the
259
+ // resize handle, stay reachable.
260
+ const available = useViewportExtent(resizable, isHorizontal);
261
+ const panelRef = useRef<HTMLDivElement>(null);
262
+ const {
263
+ size: resizedSize,
264
+ setSize,
265
+ clampSize,
266
+ } = useResizablePanelSize({
418
267
  enabled: resizable,
419
- isHorizontal,
420
268
  minSize,
421
269
  maxSize,
422
- defaultSize: initialSize,
270
+ defaultSize: defaultSize ?? (isHorizontal ? 560 : 480),
271
+ available,
272
+ reserve: 80,
423
273
  storageKey,
424
274
  });
425
275
 
@@ -443,20 +293,24 @@ const DrawerContent = forwardRef<ComponentRef<typeof DialogPrimitive.Content>, D
443
293
  `overflow-hidden` so the drag track stays visible while the
444
294
  panel cleanly clips children to its rounded corners. */}
445
295
  {applyInlineSize ? (
446
- <DrawerResizeHandle
296
+ <PanelResizeHandle
297
+ variant="overlay"
447
298
  side={resolvedSide}
448
299
  size={resizedSize}
449
300
  minSize={minSize}
450
301
  maxSize={maxSize}
451
302
  onSize={setSize}
303
+ clampSize={clampSize}
304
+ panelRef={panelRef}
452
305
  ariaLabel={resizeAriaLabel}
453
306
  />
454
307
  ) : null}
455
308
  <div
309
+ ref={panelRef}
456
310
  className={cn(drawerPanelVariants({ side, flush, size }), className, panelClassName)}
457
311
  style={{ ...sizeStyle, ...panelStyle }}
458
312
  >
459
- {children}
313
+ <ViewportBreakpoints>{children}</ViewportBreakpoints>
460
314
  </div>
461
315
  </DialogPrimitive.Content>
462
316
  </DrawerPortal>
@@ -115,7 +115,7 @@ export function DurationInput({ value, onChange, invalid, className }: DurationI
115
115
  }
116
116
  }}
117
117
  className={cn(
118
- 'flex h-11 w-full items-center gap-[var(--spacing-system-xxs)] rounded-md border border-ods-border bg-ods-card px-[var(--spacing-system-sf)] transition-colors duration-200 md:h-12',
118
+ 'flex h-11 w-full items-center gap-[var(--spacing-system-xxs)] rounded-md border border-ods-border bg-ods-card px-[var(--spacing-system-sf)] transition-colors duration-200 content-md:h-12',
119
119
  'cursor-text focus-within:border-ods-accent',
120
120
  invalid && 'border-ods-error focus-within:border-ods-error',
121
121
  className,
@@ -7,7 +7,7 @@ import { cn } from '../../utils/cn';
7
7
  import { personInitials } from '../../utils/format';
8
8
 
9
9
  /** The design size, when a caller does not state one. */
10
- const ENTITY_IMAGE_SIZE = 'size-[52px] md:size-[60px]';
10
+ const ENTITY_IMAGE_SIZE = 'size-[52px] content-md:size-[60px]';
11
11
 
12
12
  export interface EntityImageProps {
13
13
  src?: string | null;
@@ -18,10 +18,10 @@ export interface EntityImageProps {
18
18
  * REPLACES the default size, for both the image and its initials fallback.
19
19
  *
20
20
  * Sizing through `className` does NOT work here and cannot be made to:
21
- * the default is responsive (`size-[52px] md:size-[60px]`), and
21
+ * the default is responsive (`size-[52px] content-md:size-[60px]`), and
22
22
  * tailwind-merge lets `size-*` override `w`/`h` but never the reverse — so a
23
23
  * caller passing `h-4 w-6` dropped the base `size-[52px]` and kept
24
- * `md:size-[60px]`, rendering 16px on a phone and 60px on a desktop. State
24
+ * `content-md:size-[60px]`, rendering 16px on a phone and 60px on a desktop. State
25
25
  * the size here instead; it is substituted, not merged.
26
26
  */
27
27
  sizeClassName?: string;
@@ -59,9 +59,9 @@ export interface FeatureCardGridProps {
59
59
  }
60
60
 
61
61
  const GRID_COLUMNS = {
62
- 2: 'grid-cols-1 md:grid-cols-2',
63
- 3: 'grid-cols-1 md:grid-cols-3',
64
- 4: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',
62
+ 2: 'grid-cols-1 content-md:grid-cols-2',
63
+ 3: 'grid-cols-1 content-md:grid-cols-3',
64
+ 4: 'grid-cols-1 content-md:grid-cols-2 content-lg:grid-cols-4',
65
65
  } as const;
66
66
 
67
67
  /**
@@ -75,10 +75,10 @@ function fourColumnBorders(index: number, count: number): string {
75
75
  if (index < count - 1) classes.push('border-b');
76
76
  const mdRows = Math.ceil(count / 2);
77
77
  const lgRows = Math.ceil(count / 4);
78
- classes.push(index % 2 === 0 && index < count - 1 ? 'md:border-r' : 'md:border-r-0');
79
- classes.push(Math.floor(index / 2) < mdRows - 1 ? 'md:border-b' : 'md:border-b-0');
80
- classes.push(index % 4 !== 3 && index < count - 1 ? 'lg:border-r' : 'lg:border-r-0');
81
- classes.push(Math.floor(index / 4) < lgRows - 1 ? 'lg:border-b' : 'lg:border-b-0');
78
+ classes.push(index % 2 === 0 && index < count - 1 ? 'content-md:border-r' : 'content-md:border-r-0');
79
+ classes.push(Math.floor(index / 2) < mdRows - 1 ? 'content-md:border-b' : 'content-md:border-b-0');
80
+ classes.push(index % 4 !== 3 && index < count - 1 ? 'content-lg:border-r' : 'content-lg:border-r-0');
81
+ classes.push(Math.floor(index / 4) < lgRows - 1 ? 'content-lg:border-b' : 'content-lg:border-b-0');
82
82
  return ` ${classes.join(' ')}`;
83
83
  }
84
84
 
@@ -117,7 +117,7 @@ export function FeatureCardGrid({
117
117
  if (!isLastInRow) {
118
118
  // On mobile (1 column), never show right border
119
119
  // On desktop (2/3 columns), show right border except for last in row
120
- classes += ' md:border-r border-ods-border';
120
+ classes += ' content-md:border-r border-ods-border';
121
121
  }
122
122
 
123
123
  // Bottom border logic
@@ -130,10 +130,10 @@ export function FeatureCardGrid({
130
130
 
131
131
  // Desktop: override mobile border, show bottom border for all rows except last
132
132
  if (!isLastRow) {
133
- classes += ' md:border-b border-ods-border';
133
+ classes += ' content-md:border-b border-ods-border';
134
134
  } else {
135
135
  // Last row on desktop - remove bottom border
136
- classes += ' md:border-b-0';
136
+ classes += ' content-md:border-b-0';
137
137
  }
138
138
 
139
139
  return classes;
@@ -17,7 +17,7 @@ export function FileManagerActionBar({
17
17
  className,
18
18
  }: FileManagerActionBarProps) {
19
19
  return (
20
- <div className={cn('flex flex-wrap items-center gap-4 md:justify-end', className)}>
20
+ <div className={cn('flex flex-wrap items-center gap-4 content-md:justify-end', className)}>
21
21
  <Button variant="outline" size="small-legacy" onClick={onNewFolder} leftIcon={<FolderPlus className="h-4 w-4" />}>
22
22
  New Folder
23
23
  </Button>
@@ -129,7 +129,7 @@ export function FileManager({
129
129
  return (
130
130
  <div className={cn('flex h-full flex-col bg-ods-bg', className)}>
131
131
  <div className="flex min-h-0 flex-1 flex-col space-y-6 py-2">
132
- <div className="mb-4 flex flex-col justify-between gap-4 md:flex-row">
132
+ <div className="mb-4 flex flex-col justify-between gap-4 content-md:flex-row">
133
133
  <FileManagerBreadcrumb items={breadcrumbItems} onItemClick={handleBreadcrumbClick} />
134
134
 
135
135
  {showActions && (
@@ -175,7 +175,14 @@ export function FilterModal({
175
175
  const isEmpty = !isLoading && !hasSort && !hasFilterGroups && !hasTagFilter && !dateFilter;
176
176
 
177
177
  return (
178
- <ModalV2 isOpen={isOpen} onClose={onClose} className={cn('max-h-[90vh] max-w-none', className)}>
178
+ <ModalV2
179
+ isOpen={isOpen}
180
+ onClose={onClose}
181
+ // Full width as the phone's bottom sheet. A wide window gets the medium dialog
182
+ // width instead: page content narrowed by a docked side panel shows the
183
+ // mobile filter button there too, and a window-wide sheet read as broken.
184
+ className={cn('max-h-[90vh] max-w-none md:max-w-[min(42rem,calc(100vw-2rem))]', className)}
185
+ >
179
186
  <ModalV2Header>
180
187
  <ModalV2Title>{title}</ModalV2Title>
181
188
  </ModalV2Header>
@@ -18,11 +18,11 @@ export function HighlightCard({ item, className = '' }: HighlightCardProps) {
18
18
  const desktopWidth = item.category.length * 14;
19
19
 
20
20
  return (
21
- <div className={`relative bg-ods-bg p-6 md:p-10 ${className}`}>
21
+ <div className={`relative bg-ods-bg p-6 content-md:p-10 ${className}`}>
22
22
  <div className="relative">
23
23
  {/* Mobile highlight */}
24
24
  <div
25
- className="absolute left-[80px] top-0 h-8 rounded-md md:hidden"
25
+ className="absolute left-[80px] top-0 h-8 rounded-md content-md:hidden"
26
26
  style={{
27
27
  backgroundColor: item.highlightBg,
28
28
  width: `${mobileWidth}px`,
@@ -30,7 +30,7 @@ export function HighlightCard({ item, className = '' }: HighlightCardProps) {
30
30
  />
31
31
  {/* Desktop highlight */}
32
32
  <div
33
- className="absolute left-[95px] top-0 hidden h-10 rounded-md md:block"
33
+ className="absolute left-[95px] top-0 hidden h-10 rounded-md content-md:block"
34
34
  style={{
35
35
  backgroundColor: item.highlightBg,
36
36
  width: `${desktopWidth}px`,
@@ -57,7 +57,7 @@ export function HighlightCardGrid({
57
57
  className = '',
58
58
  cardClassName = 'bg-ods-card border border-ods-border rounded-lg p-0 overflow-hidden',
59
59
  }: HighlightCardGridProps) {
60
- const gridCols = columns === 2 ? 'grid-cols-1 md:grid-cols-2' : 'grid-cols-1 md:grid-cols-3';
60
+ const gridCols = columns === 2 ? 'grid-cols-1 content-md:grid-cols-2' : 'grid-cols-1 content-md:grid-cols-3';
61
61
  const itemsPerRow = columns;
62
62
  const rows = Math.ceil(items.length / itemsPerRow);
63
63
 
@@ -68,7 +68,7 @@ export function HighlightCardGrid({
68
68
  if (!isLastInRow) {
69
69
  // On mobile (1 column), never show right border
70
70
  // On desktop (2/3 columns), show right border except for last in row
71
- classes += ' md:border-r border-ods-border';
71
+ classes += ' content-md:border-r border-ods-border';
72
72
  }
73
73
 
74
74
  // Bottom border logic
@@ -81,10 +81,10 @@ export function HighlightCardGrid({
81
81
 
82
82
  // Desktop: override mobile border, show bottom border for all rows except last
83
83
  if (!isLastRow) {
84
- classes += ' md:border-b border-ods-border';
84
+ classes += ' content-md:border-b border-ods-border';
85
85
  } else {
86
86
  // Last row on desktop - remove bottom border
87
- classes += ' md:border-b-0';
87
+ classes += ' content-md:border-b-0';
88
88
  }
89
89
 
90
90
  return classes;
@@ -14,7 +14,7 @@ export interface InfoCardRowProps {
14
14
  }
15
15
 
16
16
  const CELL =
17
- 'flex items-center gap-[var(--spacing-system-s)] min-w-0 p-[var(--spacing-system-xsf)] md:p-[var(--spacing-system-m)] min-h-[60px] md:min-h-[76px]';
17
+ 'flex items-center gap-[var(--spacing-system-s)] min-w-0 p-[var(--spacing-system-xsf)] content-md:p-[var(--spacing-system-m)] min-h-[60px] content-md:min-h-[76px]';
18
18
 
19
19
  function StatCell({ section, className }: { section: InfoCardRowSection; className?: string }) {
20
20
  return (
@@ -24,11 +24,13 @@ function StatCell({ section, className }: { section: InfoCardRowSection; classNa
24
24
  <div className="flex min-w-0 items-center gap-[var(--spacing-system-s)]">
25
25
  <p className="min-w-0 flex-1 truncate text-ods-text-primary text-h3">{section.caption}</p>
26
26
  {section.icon && (
27
- <div className="flex size-6 shrink-0 items-center justify-center md:hidden">{section.icon}</div>
27
+ <div className="flex size-6 shrink-0 items-center justify-center content-md:hidden">{section.icon}</div>
28
28
  )}
29
29
  </div>
30
30
  </div>
31
- {section.icon && <div className="hidden size-6 shrink-0 items-center justify-center md:flex">{section.icon}</div>}
31
+ {section.icon && (
32
+ <div className="hidden size-6 shrink-0 items-center justify-center content-md:flex">{section.icon}</div>
33
+ )}
32
34
  </div>
33
35
  );
34
36
  }
@@ -37,11 +39,13 @@ export function InfoCardRow({ lead, stats, className }: InfoCardRowProps) {
37
39
  return (
38
40
  <div
39
41
  className={cn(
40
- 'flex shrink-0 flex-col overflow-hidden rounded-md border border-ods-border bg-ods-card md:flex-row',
42
+ 'flex shrink-0 flex-col overflow-hidden rounded-md border border-ods-border bg-ods-card content-md:flex-row',
41
43
  className,
42
44
  )}
43
45
  >
44
- <div className={cn(CELL, 'border-b border-ods-border md:flex-1 md:border-b-0 md:border-r')}>
46
+ <div
47
+ className={cn(CELL, 'border-b border-ods-border content-md:flex-1 content-md:border-b-0 content-md:border-r')}
48
+ >
45
49
  {lead.icon && (
46
50
  <div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-sm border border-ods-border [&>*]:size-full [&_img]:object-cover">
47
51
  {lead.icon}
@@ -53,7 +57,7 @@ export function InfoCardRow({ lead, stats, className }: InfoCardRowProps) {
53
57
  </div>
54
58
  </div>
55
59
 
56
- <div className="flex md:flex-1">
60
+ <div className="flex content-md:flex-1">
57
61
  <StatCell section={stats[0]} className="border-r border-ods-border" />
58
62
  <StatCell section={stats[1]} />
59
63
  </div>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { type ReactNode, createContext, forwardRef, useContext, useRef, useEffect, useState } from 'react';
4
4
  import { RemoveScroll } from 'react-remove-scroll';
5
+ import { ViewportBreakpoints } from '../../hooks/ui/use-content-breakpoint';
5
6
  import { cn } from '../../utils/cn';
6
7
  import { XmarkIcon } from '../icons-v2-generated';
7
8
 
@@ -235,7 +236,9 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>(
235
236
  mirrored there as INSET_TRANSITION_MS — it delays the app's
236
237
  scroll-the-focused-field re-assert until this transition settles. */}
237
238
  <div
238
- className="fixed inset-0 z-[1300] flex items-end justify-center transition-[padding] duration-200 md:items-center"
239
+ // `ods-viewport-layer`: a modal follows the window, so a modal opened from a
240
+ // narrow content area takes the window's tokens, not the area's mobile ones.
241
+ className="ods-viewport-layer fixed inset-0 z-[1300] flex items-end justify-center transition-[padding] duration-200 md:items-center"
239
242
  style={{ paddingBottom: 'var(--of-keyboard-inset, 0px)' }}
240
243
  >
241
244
  <div
@@ -303,7 +306,9 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>(
303
306
  role="dialog"
304
307
  aria-modal="true"
305
308
  >
306
- <ModalContext.Provider value={{ onClose }}>{children}</ModalContext.Provider>
309
+ <ModalContext.Provider value={{ onClose }}>
310
+ <ViewportBreakpoints>{children}</ViewportBreakpoints>
311
+ </ModalContext.Provider>
307
312
  </div>
308
313
  </div>
309
314
  </RemoveScroll>
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { usePreventScroll } from '@react-aria/overlays';
4
4
  import { type ReactNode, forwardRef, useEffect } from 'react';
5
+ import { ViewportBreakpoints } from '../../hooks/ui/use-content-breakpoint';
5
6
  import { cn } from '../../utils/cn';
6
7
 
7
8
  /**
@@ -61,7 +62,7 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>(({ isOpen, onClose, childre
61
62
  if (!isOpen) return null;
62
63
 
63
64
  return (
64
- <div className="fixed inset-0 z-[1300] flex items-center justify-center">
65
+ <div className="ods-viewport-layer fixed inset-0 z-[1300] flex items-center justify-center">
65
66
  <div className="absolute inset-0 bg-ods-overlay" onClick={onClose} aria-hidden="true" />
66
67
  <div
67
68
  ref={ref}
@@ -72,7 +73,7 @@ const Modal = forwardRef<HTMLDivElement, ModalProps>(({ isOpen, onClose, childre
72
73
  role="dialog"
73
74
  aria-modal="true"
74
75
  >
75
- {children}
76
+ <ViewportBreakpoints>{children}</ViewportBreakpoints>
76
77
  </div>
77
78
  </div>
78
79
  );
@@ -25,13 +25,15 @@ const NoDataAction = React.forwardRef<HTMLDivElement, NoDataActionProps>(functio
25
25
  ref={ref}
26
26
  className={cn(
27
27
  'flex flex-1 items-center gap-[var(--spacing-system-s)] p-[var(--spacing-system-m)] text-ods-text-secondary',
28
- 'md:w-[224px] md:flex-none md:flex-col md:items-start md:gap-[var(--spacing-system-xs)]',
28
+ 'content-md:w-[224px] content-md:flex-none content-md:flex-col content-md:items-start content-md:gap-[var(--spacing-system-xs)]',
29
29
  className,
30
30
  )}
31
31
  {...props}
32
32
  >
33
33
  {icon && <span className={cn(noDataIconClasses, 'text-ods-text-primary')}>{icon}</span>}
34
- <span className="min-w-0 flex-1 break-words text-h6 md:w-full md:flex-none">{label ?? children}</span>
34
+ <span className="min-w-0 flex-1 break-words text-h6 content-md:w-full content-md:flex-none">
35
+ {label ?? children}
36
+ </span>
35
37
  </div>
36
38
  );
37
39
  });
@@ -61,7 +63,11 @@ const NoDataActions = React.forwardRef<HTMLDivElement, NoDataActionsProps>(funct
61
63
  return (
62
64
  <div
63
65
  ref={ref}
64
- className={cn(noDataActionsVariants({ variant }), 'flex w-full flex-col md:w-auto md:flex-row', className)}
66
+ className={cn(
67
+ noDataActionsVariants({ variant }),
68
+ 'flex w-full flex-col content-md:w-auto content-md:flex-row',
69
+ className,
70
+ )}
65
71
  {...props}
66
72
  >
67
73
  {blocks.map((block, index) => (
@@ -69,7 +75,7 @@ const NoDataActions = React.forwardRef<HTMLDivElement, NoDataActionsProps>(funct
69
75
  {index > 0 && (
70
76
  <div
71
77
  aria-hidden="true"
72
- className="shrink-0 self-stretch border-t border-ods-border md:border-l md:border-t-0"
78
+ className="shrink-0 self-stretch border-t border-ods-border content-md:border-l content-md:border-t-0"
73
79
  />
74
80
  )}
75
81
  {block}
@@ -7,7 +7,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
7
7
  // Leading-icon glyph scale: 16px on mobile, 24px on md+. Color is intentionally
8
8
  // omitted so the glyph inherits the current text color.
9
9
  export const noDataIconClasses =
10
- 'inline-flex shrink-0 items-center justify-center [&_svg]:h-4 [&_svg]:w-4 md:[&_svg]:h-6 md:[&_svg]:w-6';
10
+ 'inline-flex shrink-0 items-center justify-center [&_svg]:h-4 [&_svg]:w-4 content-md:[&_svg]:h-6 content-md:[&_svg]:w-6';
11
11
 
12
12
  // Surface for the info-block group container: a transparent, bordered panel with
13
13
  // rounded corners. `overflow-hidden` keeps only the outer blocks round — inner
@@ -65,7 +65,7 @@ const NoData = React.forwardRef<HTMLDivElement, NoDataProps>(function NoDataImpl
65
65
  leftIcon={buttonIcon}
66
66
  onClick={onButtonClick}
67
67
  {...buttonProps}
68
- className={cn('w-full md:w-auto', buttonProps?.className)}
68
+ className={cn('w-full content-md:w-auto', buttonProps?.className)}
69
69
  >
70
70
  {buttonLabel}
71
71
  </Button>
@@ -127,7 +127,7 @@ export function OrganizationCard({
127
127
  customFooter
128
128
  ) : footerStats && footerStats.length > 0 ? (
129
129
  <div className="flex w-full min-w-0 items-center justify-between gap-2">
130
- <div className="flex min-w-0 flex-shrink items-center gap-3 md:gap-4">
130
+ <div className="flex min-w-0 flex-shrink items-center gap-3 content-md:gap-4">
131
131
  {footerStats.map((stat, index) => (
132
132
  <div key={index} className="flex flex-shrink-0 items-center gap-1">
133
133
  {stat.icon}