@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.
- package/dist/{chunk-SOLI7V4I.js → chunk-5HOII3WB.js} +4 -4
- package/dist/{chunk-4FNDOTW2.js → chunk-7Q52HP45.js} +77 -139
- package/dist/chunk-7Q52HP45.js.map +1 -0
- package/dist/{chunk-ZZWFS7CD.cjs → chunk-A3RW7M6B.cjs} +29 -29
- package/dist/{chunk-ZZWFS7CD.cjs.map → chunk-A3RW7M6B.cjs.map} +1 -1
- package/dist/{chunk-G2G7HLEM.cjs → chunk-CUN4CJUN.cjs} +3 -3
- package/dist/{chunk-G2G7HLEM.cjs.map → chunk-CUN4CJUN.cjs.map} +1 -1
- package/dist/{chunk-NJSBYUJZ.js → chunk-E4J5KKHH.js} +3 -3
- package/dist/{chunk-VTTPNZX3.cjs → chunk-F4KE5AZO.cjs} +40 -40
- package/dist/{chunk-VTTPNZX3.cjs.map → chunk-F4KE5AZO.cjs.map} +1 -1
- package/dist/{chunk-B2BERJ4M.js → chunk-IK5AR6QG.js} +6 -6
- package/dist/{chunk-YVJ2XBFU.js → chunk-IVM47EGI.js} +398 -389
- package/dist/chunk-IVM47EGI.js.map +1 -0
- package/dist/{chunk-MHNBDSWH.js → chunk-J6M2G6TD.js} +6 -6
- package/dist/{chunk-BRHCGXOH.cjs → chunk-KPGKNUJF.cjs} +40 -40
- package/dist/{chunk-BRHCGXOH.cjs.map → chunk-KPGKNUJF.cjs.map} +1 -1
- package/dist/{chunk-XVPWVG6N.cjs → chunk-KRLBVBYU.cjs} +18 -18
- package/dist/{chunk-XVPWVG6N.cjs.map → chunk-KRLBVBYU.cjs.map} +1 -1
- package/dist/{chunk-XBC5AES6.js → chunk-KZPVN7AZ.js} +2 -2
- package/dist/{chunk-HXRULNWA.js → chunk-L6OPVKVR.js} +5 -5
- package/dist/{chunk-FXZO4EKN.js → chunk-LQWYXIKB.js} +5 -5
- package/dist/{chunk-H4ISMWHG.cjs → chunk-NEMUCNCG.cjs} +73 -73
- package/dist/{chunk-H4ISMWHG.cjs.map → chunk-NEMUCNCG.cjs.map} +1 -1
- package/dist/{chunk-DGIF3YDV.js → chunk-NO2KT42M.js} +2 -2
- package/dist/{chunk-6AB36PRE.js → chunk-O5W26SCL.js} +2 -2
- package/dist/{chunk-3U4IZF7H.cjs → chunk-RL344I7V.cjs} +7 -7
- package/dist/{chunk-3U4IZF7H.cjs.map → chunk-RL344I7V.cjs.map} +1 -1
- package/dist/{chunk-MF5CVIAC.cjs → chunk-RPGFIUN3.cjs} +32 -32
- package/dist/{chunk-MF5CVIAC.cjs.map → chunk-RPGFIUN3.cjs.map} +1 -1
- package/dist/{chunk-BKYV2ALJ.cjs → chunk-SOALFYU6.cjs} +591 -582
- package/dist/chunk-SOALFYU6.cjs.map +1 -0
- package/dist/{chunk-3SNDISEN.cjs → chunk-STGWQFYT.cjs} +30 -92
- package/dist/chunk-STGWQFYT.cjs.map +1 -0
- package/dist/{chunk-VXM2GCKS.cjs → chunk-T6ZUBXPA.cjs} +12 -12
- package/dist/{chunk-VXM2GCKS.cjs.map → chunk-T6ZUBXPA.cjs.map} +1 -1
- package/dist/{chunk-SMLAYAIW.cjs → chunk-V3EZQWHQ.cjs} +5 -5
- package/dist/{chunk-SMLAYAIW.cjs.map → chunk-V3EZQWHQ.cjs.map} +1 -1
- package/dist/{chunk-Z4RKJOFH.js → chunk-VUW4G6SU.js} +3 -3
- package/dist/{chunk-SZ27SV6T.cjs → chunk-WGOATV3P.cjs} +73 -73
- package/dist/{chunk-SZ27SV6T.cjs.map → chunk-WGOATV3P.cjs.map} +1 -1
- package/dist/{chunk-RALT6D4H.cjs → chunk-YRKPMYI3.cjs} +15 -15
- package/dist/{chunk-RALT6D4H.cjs.map → chunk-YRKPMYI3.cjs.map} +1 -1
- package/dist/{chunk-TYCWPT33.js → chunk-ZF5O3KUQ.js} +3 -3
- package/dist/{chunk-SAJM3PDE.js → chunk-ZMFQWMM7.js} +2 -2
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +3 -3
- package/dist/components/chat/index.js +2 -2
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +6 -6
- package/dist/components/docs/index.js +5 -5
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +5 -5
- package/dist/components/faq/index.js +4 -4
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +22 -22
- package/dist/components/help-center-pages/index.js +13 -13
- package/dist/components/index.cjs +134 -136
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -13
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/header.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +3 -5
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.d.ts +1 -1
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +2 -4
- package/dist/components/navigation/mobile-burger-menu.d.ts.map +1 -1
- package/dist/components/navigation/mobile-nav-panel.d.ts +0 -2
- package/dist/components/navigation/mobile-nav-panel.d.ts.map +1 -1
- package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
- package/dist/components/onboarding-guides/index.cjs +7 -7
- package/dist/components/onboarding-guides/index.js +6 -6
- package/dist/components/related-content/index.cjs +5 -5
- package/dist/components/related-content/index.js +4 -4
- package/dist/components/tickets/index.cjs +6 -6
- package/dist/components/tickets/index.js +5 -5
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/components/ui/modal-v2.d.ts.map +1 -1
- package/dist/components/ui/modal.d.ts.map +1 -1
- package/dist/hooks/index.cjs +2 -6
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +1 -5
- package/dist/hooks/ui/index.d.ts +0 -2
- package/dist/hooks/ui/index.d.ts.map +1 -1
- package/dist/hooks/ui/use-header-height.d.ts +1 -10
- package/dist/hooks/ui/use-header-height.d.ts.map +1 -1
- package/dist/index.cjs +3 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -8
- package/package.json +1 -1
- package/src/components/navigation/header.tsx +0 -5
- package/src/components/navigation/index.ts +1 -1
- package/src/components/navigation/mobile-burger-menu.tsx +29 -16
- package/src/components/navigation/mobile-nav-panel.tsx +35 -32
- package/src/components/navigation/sliding-sidebar.tsx +9 -29
- package/src/components/ui/modal-v2.tsx +6 -7
- package/src/components/ui/modal.tsx +9 -7
- package/src/hooks/ui/index.ts +0 -6
- package/src/hooks/ui/use-header-height.ts +2 -17
- package/dist/chunk-3SNDISEN.cjs.map +0 -1
- package/dist/chunk-4FNDOTW2.js.map +0 -1
- package/dist/chunk-BKYV2ALJ.cjs.map +0 -1
- package/dist/chunk-YVJ2XBFU.js.map +0 -1
- package/dist/hooks/ui/use-focus-trap.d.ts +0 -32
- package/dist/hooks/ui/use-focus-trap.d.ts.map +0 -1
- package/src/hooks/ui/use-focus-trap.ts +0 -110
- package/src/stories/MobileNavPanel.stories.tsx +0 -150
- /package/dist/{chunk-SOLI7V4I.js.map → chunk-5HOII3WB.js.map} +0 -0
- /package/dist/{chunk-NJSBYUJZ.js.map → chunk-E4J5KKHH.js.map} +0 -0
- /package/dist/{chunk-B2BERJ4M.js.map → chunk-IK5AR6QG.js.map} +0 -0
- /package/dist/{chunk-MHNBDSWH.js.map → chunk-J6M2G6TD.js.map} +0 -0
- /package/dist/{chunk-XBC5AES6.js.map → chunk-KZPVN7AZ.js.map} +0 -0
- /package/dist/{chunk-HXRULNWA.js.map → chunk-L6OPVKVR.js.map} +0 -0
- /package/dist/{chunk-FXZO4EKN.js.map → chunk-LQWYXIKB.js.map} +0 -0
- /package/dist/{chunk-DGIF3YDV.js.map → chunk-NO2KT42M.js.map} +0 -0
- /package/dist/{chunk-6AB36PRE.js.map → chunk-O5W26SCL.js.map} +0 -0
- /package/dist/{chunk-Z4RKJOFH.js.map → chunk-VUW4G6SU.js.map} +0 -0
- /package/dist/{chunk-TYCWPT33.js.map → chunk-ZF5O3KUQ.js.map} +0 -0
- /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,
|
|
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
|
-
|
|
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
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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=
|
|
102
|
+
className="fixed inset-0 z-[9998] bg-black/50"
|
|
91
103
|
onClick={config.onClose}
|
|
92
104
|
/>
|
|
93
105
|
|
|
94
|
-
{/* Navigation Panel -
|
|
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
|
-
//
|
|
110
|
-
|
|
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
|
|
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
|
|
152
|
+
{/* Footer with action button - fixed at bottom */}
|
|
150
153
|
{config.footer && (
|
|
151
|
-
<div className="border-t border-ods-border p-4
|
|
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
|
|
4
|
-
import { motion, AnimatePresence
|
|
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=
|
|
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={
|
|
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
|
|
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
|
|
207
|
+
{/* Footer */}
|
|
228
208
|
{config.footer && (
|
|
229
|
-
<div className="p-4
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
package/src/hooks/ui/index.ts
CHANGED
|
@@ -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
|
|
52
|
+
}, [defaultHeight])
|
|
68
53
|
|
|
69
54
|
return height
|
|
70
55
|
}
|