@flamingo-stack/openframe-frontend-core 0.0.410-snapshot.20260711165901 → 0.0.410

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 (124) hide show
  1. package/dist/{chunk-SOLI7V4I.js → chunk-5HOII3WB.js} +4 -4
  2. package/dist/{chunk-4FNDOTW2.js → chunk-7Q52HP45.js} +77 -139
  3. package/dist/chunk-7Q52HP45.js.map +1 -0
  4. package/dist/{chunk-ZZWFS7CD.cjs → chunk-A3RW7M6B.cjs} +29 -29
  5. package/dist/{chunk-ZZWFS7CD.cjs.map → chunk-A3RW7M6B.cjs.map} +1 -1
  6. package/dist/{chunk-G2G7HLEM.cjs → chunk-CUN4CJUN.cjs} +3 -3
  7. package/dist/{chunk-G2G7HLEM.cjs.map → chunk-CUN4CJUN.cjs.map} +1 -1
  8. package/dist/{chunk-NJSBYUJZ.js → chunk-E4J5KKHH.js} +3 -3
  9. package/dist/{chunk-VTTPNZX3.cjs → chunk-F4KE5AZO.cjs} +40 -40
  10. package/dist/{chunk-VTTPNZX3.cjs.map → chunk-F4KE5AZO.cjs.map} +1 -1
  11. package/dist/{chunk-B2BERJ4M.js → chunk-IK5AR6QG.js} +6 -6
  12. package/dist/{chunk-YVJ2XBFU.js → chunk-IVM47EGI.js} +398 -389
  13. package/dist/chunk-IVM47EGI.js.map +1 -0
  14. package/dist/{chunk-MHNBDSWH.js → chunk-J6M2G6TD.js} +6 -6
  15. package/dist/{chunk-BRHCGXOH.cjs → chunk-KPGKNUJF.cjs} +40 -40
  16. package/dist/{chunk-BRHCGXOH.cjs.map → chunk-KPGKNUJF.cjs.map} +1 -1
  17. package/dist/{chunk-XVPWVG6N.cjs → chunk-KRLBVBYU.cjs} +18 -18
  18. package/dist/{chunk-XVPWVG6N.cjs.map → chunk-KRLBVBYU.cjs.map} +1 -1
  19. package/dist/{chunk-XBC5AES6.js → chunk-KZPVN7AZ.js} +2 -2
  20. package/dist/{chunk-HXRULNWA.js → chunk-L6OPVKVR.js} +5 -5
  21. package/dist/{chunk-FXZO4EKN.js → chunk-LQWYXIKB.js} +5 -5
  22. package/dist/{chunk-H4ISMWHG.cjs → chunk-NEMUCNCG.cjs} +73 -73
  23. package/dist/{chunk-H4ISMWHG.cjs.map → chunk-NEMUCNCG.cjs.map} +1 -1
  24. package/dist/{chunk-DGIF3YDV.js → chunk-NO2KT42M.js} +2 -2
  25. package/dist/{chunk-6AB36PRE.js → chunk-O5W26SCL.js} +2 -2
  26. package/dist/{chunk-3U4IZF7H.cjs → chunk-RL344I7V.cjs} +7 -7
  27. package/dist/{chunk-3U4IZF7H.cjs.map → chunk-RL344I7V.cjs.map} +1 -1
  28. package/dist/{chunk-MF5CVIAC.cjs → chunk-RPGFIUN3.cjs} +32 -32
  29. package/dist/{chunk-MF5CVIAC.cjs.map → chunk-RPGFIUN3.cjs.map} +1 -1
  30. package/dist/{chunk-BKYV2ALJ.cjs → chunk-SOALFYU6.cjs} +591 -582
  31. package/dist/chunk-SOALFYU6.cjs.map +1 -0
  32. package/dist/{chunk-3SNDISEN.cjs → chunk-STGWQFYT.cjs} +30 -92
  33. package/dist/chunk-STGWQFYT.cjs.map +1 -0
  34. package/dist/{chunk-VXM2GCKS.cjs → chunk-T6ZUBXPA.cjs} +12 -12
  35. package/dist/{chunk-VXM2GCKS.cjs.map → chunk-T6ZUBXPA.cjs.map} +1 -1
  36. package/dist/{chunk-SMLAYAIW.cjs → chunk-V3EZQWHQ.cjs} +5 -5
  37. package/dist/{chunk-SMLAYAIW.cjs.map → chunk-V3EZQWHQ.cjs.map} +1 -1
  38. package/dist/{chunk-Z4RKJOFH.js → chunk-VUW4G6SU.js} +3 -3
  39. package/dist/{chunk-SZ27SV6T.cjs → chunk-WGOATV3P.cjs} +73 -73
  40. package/dist/{chunk-SZ27SV6T.cjs.map → chunk-WGOATV3P.cjs.map} +1 -1
  41. package/dist/{chunk-RALT6D4H.cjs → chunk-YRKPMYI3.cjs} +15 -15
  42. package/dist/{chunk-RALT6D4H.cjs.map → chunk-YRKPMYI3.cjs.map} +1 -1
  43. package/dist/{chunk-TYCWPT33.js → chunk-ZF5O3KUQ.js} +3 -3
  44. package/dist/{chunk-SAJM3PDE.js → chunk-ZMFQWMM7.js} +2 -2
  45. package/dist/components/case-studies/index.cjs +9 -9
  46. package/dist/components/case-studies/index.js +3 -3
  47. package/dist/components/chat/index.cjs +3 -3
  48. package/dist/components/chat/index.js +2 -2
  49. package/dist/components/contact/index.cjs +4 -4
  50. package/dist/components/contact/index.js +3 -3
  51. package/dist/components/docs/index.cjs +6 -6
  52. package/dist/components/docs/index.js +5 -5
  53. package/dist/components/embeds/index.cjs +4 -4
  54. package/dist/components/embeds/index.js +3 -3
  55. package/dist/components/faq/index.cjs +5 -5
  56. package/dist/components/faq/index.js +4 -4
  57. package/dist/components/features/index.cjs +3 -3
  58. package/dist/components/features/index.js +2 -2
  59. package/dist/components/help-center-pages/index.cjs +22 -22
  60. package/dist/components/help-center-pages/index.js +13 -13
  61. package/dist/components/index.cjs +134 -136
  62. package/dist/components/index.cjs.map +1 -1
  63. package/dist/components/index.js +11 -13
  64. package/dist/components/index.js.map +1 -1
  65. package/dist/components/navigation/header.d.ts.map +1 -1
  66. package/dist/components/navigation/index.cjs +3 -5
  67. package/dist/components/navigation/index.cjs.map +1 -1
  68. package/dist/components/navigation/index.d.ts +1 -1
  69. package/dist/components/navigation/index.d.ts.map +1 -1
  70. package/dist/components/navigation/index.js +2 -4
  71. package/dist/components/navigation/mobile-burger-menu.d.ts.map +1 -1
  72. package/dist/components/navigation/mobile-nav-panel.d.ts +0 -2
  73. package/dist/components/navigation/mobile-nav-panel.d.ts.map +1 -1
  74. package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
  75. package/dist/components/onboarding-guides/index.cjs +7 -7
  76. package/dist/components/onboarding-guides/index.js +6 -6
  77. package/dist/components/related-content/index.cjs +5 -5
  78. package/dist/components/related-content/index.js +4 -4
  79. package/dist/components/tickets/index.cjs +6 -6
  80. package/dist/components/tickets/index.js +5 -5
  81. package/dist/components/ui/index.cjs +3 -3
  82. package/dist/components/ui/index.js +2 -2
  83. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  84. package/dist/components/ui/modal.d.ts.map +1 -1
  85. package/dist/hooks/index.cjs +2 -6
  86. package/dist/hooks/index.cjs.map +1 -1
  87. package/dist/hooks/index.js +1 -5
  88. package/dist/hooks/ui/index.d.ts +0 -2
  89. package/dist/hooks/ui/index.d.ts.map +1 -1
  90. package/dist/hooks/ui/use-header-height.d.ts +1 -10
  91. package/dist/hooks/ui/use-header-height.d.ts.map +1 -1
  92. package/dist/index.cjs +3 -9
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.js +2 -8
  95. package/package.json +1 -1
  96. package/src/components/navigation/header.tsx +0 -5
  97. package/src/components/navigation/index.ts +1 -1
  98. package/src/components/navigation/mobile-burger-menu.tsx +29 -16
  99. package/src/components/navigation/mobile-nav-panel.tsx +35 -32
  100. package/src/components/navigation/sliding-sidebar.tsx +9 -29
  101. package/src/components/ui/modal-v2.tsx +6 -7
  102. package/src/components/ui/modal.tsx +9 -7
  103. package/src/hooks/ui/index.ts +0 -6
  104. package/src/hooks/ui/use-header-height.ts +2 -17
  105. package/dist/chunk-3SNDISEN.cjs.map +0 -1
  106. package/dist/chunk-4FNDOTW2.js.map +0 -1
  107. package/dist/chunk-BKYV2ALJ.cjs.map +0 -1
  108. package/dist/chunk-YVJ2XBFU.js.map +0 -1
  109. package/dist/hooks/ui/use-focus-trap.d.ts +0 -32
  110. package/dist/hooks/ui/use-focus-trap.d.ts.map +0 -1
  111. package/src/hooks/ui/use-focus-trap.ts +0 -110
  112. package/src/stories/MobileNavPanel.stories.tsx +0 -150
  113. /package/dist/{chunk-SOLI7V4I.js.map → chunk-5HOII3WB.js.map} +0 -0
  114. /package/dist/{chunk-NJSBYUJZ.js.map → chunk-E4J5KKHH.js.map} +0 -0
  115. /package/dist/{chunk-B2BERJ4M.js.map → chunk-IK5AR6QG.js.map} +0 -0
  116. /package/dist/{chunk-MHNBDSWH.js.map → chunk-J6M2G6TD.js.map} +0 -0
  117. /package/dist/{chunk-XBC5AES6.js.map → chunk-KZPVN7AZ.js.map} +0 -0
  118. /package/dist/{chunk-HXRULNWA.js.map → chunk-L6OPVKVR.js.map} +0 -0
  119. /package/dist/{chunk-FXZO4EKN.js.map → chunk-LQWYXIKB.js.map} +0 -0
  120. /package/dist/{chunk-DGIF3YDV.js.map → chunk-NO2KT42M.js.map} +0 -0
  121. /package/dist/{chunk-6AB36PRE.js.map → chunk-O5W26SCL.js.map} +0 -0
  122. /package/dist/{chunk-Z4RKJOFH.js.map → chunk-VUW4G6SU.js.map} +0 -0
  123. /package/dist/{chunk-TYCWPT33.js.map → chunk-ZF5O3KUQ.js.map} +0 -0
  124. /package/dist/{chunk-SAJM3PDE.js.map → chunk-ZMFQWMM7.js.map} +0 -0
@@ -1,10 +1,8 @@
1
1
  "use client"
2
2
 
3
- import React, { useCallback, useRef } from 'react'
4
- import { usePreventScroll } from '@react-aria/overlays'
3
+ import React, { useCallback, useEffect } from 'react'
5
4
  import { NavigationSidebarConfig, NavigationSidebarItem } from '../../types/navigation'
6
5
  import { cn } from '../../utils'
7
- import { useFocusTrap } from '../../hooks/ui/use-focus-trap'
8
6
  import { Logout02Icon, PenEditIcon, UserSearchIcon } from '../icons-v2-generated'
9
7
  import { Button, SquareAvatar } from '../ui'
10
8
  import { OVERLAY_BACKDROP_CLASS } from '../ui/drawer'
@@ -49,12 +47,32 @@ export const MobileBurgerMenu = React.memo(function MobileBurgerMenu({
49
47
  onLogout,
50
48
  disabled = false,
51
49
  }: MobileBurgerMenuProps) {
52
- const panelRef = useRef<HTMLDivElement>(null)
50
+ // Prevent body scroll when menu is open
51
+ useEffect(() => {
52
+ if (isOpen) {
53
+ document.body.style.overflow = 'hidden'
54
+ } else {
55
+ document.body.style.overflow = 'unset'
56
+ }
57
+
58
+ return () => {
59
+ document.body.style.overflow = 'unset'
60
+ }
61
+ }, [isOpen])
53
62
 
54
- // Shared ref-counted, iOS-aware scroll lock (react-aria) while open.
55
- usePreventScroll({ isDisabled: !isOpen })
56
- // Initial focus, Tab containment, Escape-to-close, guarded focus restore.
57
- useFocusTrap(panelRef, isOpen, { onEscape: onClose })
63
+ // Handle escape key
64
+ useEffect(() => {
65
+ const handleKeyDown = (e: KeyboardEvent) => {
66
+ if (e.key === 'Escape' && isOpen) {
67
+ onClose()
68
+ }
69
+ }
70
+
71
+ if (isOpen) {
72
+ document.addEventListener('keydown', handleKeyDown)
73
+ return () => document.removeEventListener('keydown', handleKeyDown)
74
+ }
75
+ }, [isOpen, onClose])
58
76
 
59
77
  const handleItemClick = useCallback((item: NavigationSidebarItem) => {
60
78
  if (item.onClick) {
@@ -147,7 +165,7 @@ export const MobileBurgerMenu = React.memo(function MobileBurgerMenu({
147
165
  className={cn(
148
166
  "absolute inset-0 z-[100] md:hidden",
149
167
  OVERLAY_BACKDROP_CLASS,
150
- "transition-all duration-300 motion-reduce:transition-none",
168
+ "transition-all duration-300",
151
169
  isOpen ? "opacity-100" : "opacity-0 pointer-events-none"
152
170
  )}
153
171
  style={{ top: HEADER_HEIGHT }}
@@ -158,12 +176,11 @@ export const MobileBurgerMenu = React.memo(function MobileBurgerMenu({
158
176
  {/* Menu Panel - positioned below header with slide-down animation. `absolute`
159
177
  for the same reason as the backdrop above (anchors below the topBar). */}
160
178
  <div
161
- ref={panelRef}
162
179
  className={cn(
163
180
  "absolute left-0 right-0 z-[101] md:hidden",
164
181
  "flex flex-col",
165
182
  "bg-ods-bg border-b border-ods-border",
166
- "transition-all duration-300 ease-out motion-reduce:transition-none",
183
+ "transition-all duration-300 ease-out",
167
184
  "overflow-hidden",
168
185
  isOpen
169
186
  ? "opacity-100 translate-y-0"
@@ -176,13 +193,9 @@ export const MobileBurgerMenu = React.memo(function MobileBurgerMenu({
176
193
  role="dialog"
177
194
  aria-modal="true"
178
195
  aria-label="Mobile navigation menu"
179
- tabIndex={-1}
180
- // Stays mounted while hidden (opacity-0) — inert keeps its items out
181
- // of the tab order when closed.
182
- inert={!isOpen || undefined}
183
196
  >
184
197
  {/* Scrollable Content */}
185
- <div className="flex-1 overflow-y-auto overflow-x-hidden overscroll-contain p-4 flex flex-col gap-4">
198
+ <div className="flex-1 overflow-y-auto overflow-x-hidden p-4 flex flex-col gap-4">
186
199
  {/* User Card */}
187
200
  {user && (
188
201
  <div
@@ -1,18 +1,11 @@
1
1
  "use client"
2
2
 
3
- import React, { useRef } from 'react'
4
- import { usePreventScroll } from '@react-aria/overlays'
3
+ import React, { useEffect, useRef } from 'react'
5
4
  import { cn } from '../../utils'
6
5
  import { MobileNavConfig, NavigationItem } from '../../types/navigation'
7
6
  import { Button } from '../ui/button'
8
- import { OVERLAY_BACKDROP_CLASS } from '../ui/drawer'
9
- import { useFocusTrap } from '../../hooks/ui/use-focus-trap'
10
- import { useHeaderHeight } from '../../hooks/ui/use-header-height'
11
7
  import { X } from 'lucide-react'
12
8
 
13
- /** DOM id of the panel — referenced by the header hamburger's `aria-controls`. */
14
- export const MOBILE_NAV_PANEL_ID = 'mobile-nav-panel'
15
-
16
9
  export interface MobileNavPanelProps {
17
10
  isOpen: boolean
18
11
  config: MobileNavConfig
@@ -21,13 +14,32 @@ export interface MobileNavPanelProps {
21
14
  export function MobileNavPanel({ isOpen, config }: MobileNavPanelProps) {
22
15
  const panelRef = useRef<HTMLDivElement>(null)
23
16
 
24
- // Shared ref-counted, iOS-aware scroll lock (react-aria) one counter with
25
- // the modals and chat overlays, restores prior styles on release.
26
- usePreventScroll({ isDisabled: !isOpen })
27
- // Initial focus, Tab containment, Escape-to-close, guarded focus restore.
28
- useFocusTrap(panelRef, isOpen, { onEscape: config.onClose })
29
- // The panel is layout-mounted on every page — observers only while open.
30
- const headerHeight = useHeaderHeight(64, { enabled: isOpen })
17
+ // Prevent body scroll when menu is open - using the original working approach
18
+ useEffect(() => {
19
+ if (isOpen) {
20
+ document.body.style.overflow = 'hidden'
21
+ } else {
22
+ document.body.style.overflow = 'unset'
23
+ }
24
+
25
+ return () => {
26
+ document.body.style.overflow = 'unset'
27
+ }
28
+ }, [isOpen])
29
+
30
+ // Handle escape key
31
+ useEffect(() => {
32
+ const handleKeyDown = (e: KeyboardEvent) => {
33
+ if (e.key === 'Escape' && isOpen) {
34
+ config.onClose?.()
35
+ }
36
+ }
37
+
38
+ if (isOpen) {
39
+ document.addEventListener('keydown', handleKeyDown)
40
+ return () => document.removeEventListener('keydown', handleKeyDown)
41
+ }
42
+ }, [isOpen, config])
31
43
 
32
44
  if (!isOpen) return null
33
45
 
@@ -87,30 +99,21 @@ export function MobileNavPanel({ isOpen, config }: MobileNavPanelProps) {
87
99
  <>
88
100
  {/* Backdrop - closes nav when clicked outside */}
89
101
  <div
90
- className={cn("fixed inset-0 z-[9998]", OVERLAY_BACKDROP_CLASS)}
102
+ className="fixed inset-0 z-[9998] bg-black/50"
91
103
  onClick={config.onClose}
92
104
  />
93
105
 
94
- {/* Navigation Panel - top-anchored floating card */}
106
+ {/* Navigation Panel - modal style like the original working version */}
95
107
  <div
96
108
  ref={panelRef}
97
- id={MOBILE_NAV_PANEL_ID}
98
- role="dialog"
99
- aria-modal="true"
100
- aria-label="Navigation menu"
101
- tabIndex={-1}
102
- style={{ '--mobile-nav-top': `${headerHeight + 16}px` } as React.CSSProperties}
103
109
  className={cn(
104
110
  "fixed z-[9999] rounded-lg shadow-xl",
105
111
  config.className ? "" : "bg-ods-card border border-ods-border",
106
- // Responsive positioning and sizing
112
+ // Responsive positioning and sizing - matching original working version
107
113
  "right-2 left-2 md:left-auto",
108
114
  "md:right-6 md:w-[400px] md:max-w-[calc(100vw-3rem)]",
109
- // Small-viewport (svh) sizing anchored below the measured header
110
- // 100vh overflowed under mobile browser chrome, leaving the bottom
111
- // entries and footer unreachable while the body was scroll-locked.
112
- "top-[var(--mobile-nav-top)]",
113
- "max-h-[calc(100svh-var(--mobile-nav-top)-50px)] md:max-h-[calc(100svh-var(--mobile-nav-top)-8px)]",
115
+ // Height constraints with proper mobile spacing - increased top position
116
+ "top-20 max-h-[calc(100vh-130px)] md:max-h-[calc(100vh-88px)]",
114
117
  "flex flex-col",
115
118
  config.className || ""
116
119
  )}
@@ -129,7 +132,7 @@ export function MobileNavPanel({ isOpen, config }: MobileNavPanelProps) {
129
132
  </div>
130
133
 
131
134
  {/* Scrollable content area */}
132
- <div className="flex-1 overflow-y-auto overflow-x-hidden overscroll-contain px-2 py-2">
135
+ <div className="flex-1 overflow-y-auto overflow-x-hidden px-2 py-2">
133
136
  <div className="flex flex-col space-y-1">
134
137
  {config.sections.map((section, index) => (
135
138
  <div key={index}>
@@ -146,9 +149,9 @@ export function MobileNavPanel({ isOpen, config }: MobileNavPanelProps) {
146
149
  </div>
147
150
  </div>
148
151
 
149
- {/* Footer with action button - fixed at bottom, clears the iOS home indicator */}
152
+ {/* Footer with action button - fixed at bottom */}
150
153
  {config.footer && (
151
- <div className="border-t border-ods-border p-4 pb-[max(1rem,env(safe-area-inset-bottom))] flex-shrink-0">
154
+ <div className="border-t border-ods-border p-4 flex-shrink-0">
152
155
  {config.footer}
153
156
  </div>
154
157
  )}
@@ -1,14 +1,11 @@
1
1
  "use client"
2
2
 
3
- import React, { useState, useEffect, useRef } from 'react'
4
- import { motion, AnimatePresence, useReducedMotion } from 'framer-motion'
5
- import { usePreventScroll } from '@react-aria/overlays'
3
+ import React, { useState, useEffect } from 'react'
4
+ import { motion, AnimatePresence } from 'framer-motion'
6
5
  import { cn } from '../../utils'
7
6
  import { useHeaderHeight } from '../../hooks/ui'
8
- import { useFocusTrap } from '../../hooks/ui/use-focus-trap'
9
7
  import { SlidingSidebarConfig, NavigationItem } from '../../types/navigation'
10
8
  import { Button } from '../ui/button'
11
- import { OVERLAY_BACKDROP_CLASS } from '../ui/drawer'
12
9
 
13
10
  export interface SlidingSidebarProps {
14
11
  config: SlidingSidebarConfig
@@ -18,15 +15,6 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
18
15
  const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set())
19
16
  const [mounted, setMounted] = useState(false)
20
17
  const headerHeight = useHeaderHeight()
21
- const asideRef = useRef<HTMLElement>(null)
22
- const prefersReducedMotion = useReducedMotion()
23
-
24
- // Shared ref-counted scroll lock (react-aria) while the drawer is open.
25
- usePreventScroll({ isDisabled: !config.isOpen })
26
- // Escape + initial focus + guarded restore. `contain: false`: the z-50
27
- // header stays visible and clickable ABOVE the open drawer (see the z-tier
28
- // comment below), so this is a NON-modal dialog — Tab may leave freely.
29
- useFocusTrap(asideRef, config.isOpen, { onEscape: config.onClose, contain: false })
30
18
 
31
19
  useEffect(() => { setMounted(true) }, [])
32
20
 
@@ -182,7 +170,7 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
182
170
  duration: 0.3,
183
171
  ease: [0.4, 0, 0.2, 1]
184
172
  }}
185
- className={cn("fixed inset-0 z-[45]", OVERLAY_BACKDROP_CLASS)}
173
+ className="fixed inset-0 bg-black/50 z-[45]"
186
174
  onClick={() => config.onClose()}
187
175
  />
188
176
  )}
@@ -190,26 +178,18 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
190
178
 
191
179
  {/* Sliding Sidebar */}
192
180
  <motion.aside
193
- ref={asideRef}
194
181
  initial={false}
195
182
  animate={{
196
183
  x: config.isOpen ? 0 : (config.position === 'right' ? "100%" : "-100%")
197
184
  }}
198
- transition={prefersReducedMotion ? { duration: 0 } : {
199
- type: "spring",
200
- damping: 25,
185
+ transition={{
186
+ type: "spring",
187
+ damping: 25,
201
188
  stiffness: 300,
202
189
  mass: 0.8,
203
190
  restDelta: 0.01,
204
191
  velocity: config.isOpen ? 5 : -5
205
192
  }}
206
- tabIndex={-1}
207
- // Stays mounted translated off-screen — inert keeps its items out of
208
- // the tab order while "closed". Non-modal dialog: no aria-modal (the
209
- // header above remains reachable by design).
210
- inert={!config.isOpen || undefined}
211
- role={config.isOpen ? 'dialog' : undefined}
212
- aria-label={config.isOpen ? 'Navigation sidebar' : undefined}
213
193
  className={cn(
214
194
  "fixed top-0 bottom-0 z-[46] w-72 bg-ods-card border-ods-border flex flex-col shadow-xl",
215
195
  config.position === 'right' ? "right-0 border-l" : "left-0 border-r",
@@ -220,13 +200,13 @@ export function SlidingSidebar({ config }: SlidingSidebarProps) {
220
200
  <div className="flex-shrink-0" style={{ height: `${headerHeight}px` }} />
221
201
 
222
202
  {/* Navigation */}
223
- <nav className="flex-1 p-4 space-y-2 overflow-y-auto overscroll-contain">
203
+ <nav className="flex-1 p-4 space-y-2 overflow-y-auto">
224
204
  {config.items.map(item => renderMenuItem(item))}
225
205
  </nav>
226
206
 
227
- {/* Footer - bottom padding clears the iOS home indicator */}
207
+ {/* Footer */}
228
208
  {config.footer && (
229
- <div className="p-4 pb-[max(1rem,env(safe-area-inset-bottom))] border-t border-ods-border">
209
+ <div className="p-4 border-t border-ods-border">
230
210
  {config.footer}
231
211
  </div>
232
212
  )}
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as React from "react"
4
4
  import { useEffect, useState } from "react"
5
- import { usePreventScroll } from "@react-aria/overlays"
6
5
  import { XmarkIcon } from "../icons-v2-generated"
7
6
  import { cn } from "../../utils/cn"
8
7
 
@@ -53,11 +52,6 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
53
52
  return () => clearTimeout(timeout)
54
53
  }, [isOpen])
55
54
 
56
- // Shared ref-counted scroll lock (react-aria) — restores prior styles on
57
- // release instead of clobbering to 'unset'.
58
- usePreventScroll({ isDisabled: !isOpen })
59
-
60
- // Escape key (document-level: top-of-stack semantics for modals)
61
55
  useEffect(() => {
62
56
  const handleKeyDown = (event: KeyboardEvent) => {
63
57
  if (event.key === 'Escape') {
@@ -66,8 +60,13 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
66
60
  }
67
61
 
68
62
  if (isOpen) {
63
+ document.body.style.overflow = 'hidden'
69
64
  document.addEventListener('keydown', handleKeyDown)
70
- return () => document.removeEventListener('keydown', handleKeyDown)
65
+
66
+ return () => {
67
+ document.body.style.overflow = 'unset'
68
+ document.removeEventListener('keydown', handleKeyDown)
69
+ }
71
70
  }
72
71
  }, [isOpen, onClose])
73
72
 
@@ -2,7 +2,6 @@
2
2
 
3
3
  import * as React from "react"
4
4
  import { useEffect } from "react"
5
- import { usePreventScroll } from "@react-aria/overlays"
6
5
  import { cn } from "../../utils/cn"
7
6
 
8
7
  interface ModalProps {
@@ -35,11 +34,7 @@ interface ModalFooterProps {
35
34
  /** @deprecated Use ModalV2 from './modal-v2' instead. */
36
35
  const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
37
36
  ({ isOpen, onClose, children, className }, ref) => {
38
- // Shared ref-counted scroll lock (react-aria) — restores prior styles on
39
- // release instead of clobbering to 'unset'.
40
- usePreventScroll({ isDisabled: !isOpen })
41
-
42
- // Handle Escape key (document-level: top-of-stack semantics for modals)
37
+ // Handle Escape key and scroll blocking
43
38
  useEffect(() => {
44
39
  const handleKeyDown = (event: KeyboardEvent) => {
45
40
  if (event.key === 'Escape') {
@@ -48,8 +43,15 @@ const Modal = React.forwardRef<HTMLDivElement, ModalProps>(
48
43
  }
49
44
 
50
45
  if (isOpen) {
46
+ // Block background scrolling
47
+ document.body.style.overflow = 'hidden'
51
48
  document.addEventListener('keydown', handleKeyDown)
52
- return () => document.removeEventListener('keydown', handleKeyDown)
49
+
50
+ return () => {
51
+ // Restore scrolling
52
+ document.body.style.overflow = 'unset'
53
+ document.removeEventListener('keydown', handleKeyDown)
54
+ }
53
55
  }
54
56
  }, [isOpen, onClose])
55
57
 
@@ -2,7 +2,6 @@
2
2
  export * from './use-auto-limit-tags'
3
3
  export * from './use-debounce'
4
4
  export * from './use-deferred-error'
5
- export * from './use-focus-trap'
6
5
  export * from './use-header-height'
7
6
  export * from './use-horizontal-scrollbar'
8
7
  export * from './use-image-edge-color'
@@ -15,8 +14,3 @@ export * from './use-search'
15
14
  export * from './use-table-pagination'
16
15
  export * from './use-throttle'
17
16
  export * from './use-window-size'
18
-
19
- // Shared ref-counted, iOS-aware body scroll lock (react-aria). Re-exported so
20
- // hub/app consumers have one import path; lib internals may import the
21
- // package directly (npm dedupes to one module instance either way).
22
- export { usePreventScroll } from '@react-aria/overlays'
@@ -8,25 +8,10 @@ import { useEffect, useState } from 'react'
8
8
  * Useful for offsetting fixed/absolute-positioned panels so they don't
9
9
  * overlap the header.
10
10
  */
11
- export function useHeaderHeight(
12
- defaultHeight = 64,
13
- options: {
14
- /**
15
- * When false, no observers are attached and the default height is
16
- * returned — for consumers that stay mounted on every page but only need
17
- * the measurement while an overlay is open (avoids an always-on
18
- * document-wide MutationObserver + forced reflow).
19
- * @default true
20
- */
21
- enabled?: boolean
22
- } = {}
23
- ): number {
24
- const { enabled = true } = options
11
+ export function useHeaderHeight(defaultHeight = 64): number {
25
12
  const [height, setHeight] = useState(defaultHeight)
26
13
 
27
14
  useEffect(() => {
28
- if (!enabled) return
29
-
30
15
  const measure = () => {
31
16
  let total = 0
32
17
  const header = document.querySelector('header')
@@ -64,7 +49,7 @@ export function useHeaderHeight(
64
49
  resizeObserver.disconnect()
65
50
  mutationObserver.disconnect()
66
51
  }
67
- }, [defaultHeight, enabled])
52
+ }, [defaultHeight])
68
53
 
69
54
  return height
70
55
  }