Browse Source
feat: implement view paddings management with ViewPaddingsBridge and related components
master
feat: implement view paddings management with ViewPaddingsBridge and related components
master
10 changed files with 178 additions and 3 deletions
-
6src/app/globals.css
-
9src/app/layout.tsx
-
6src/app/providers.tsx
-
8src/components/ui/sticky-header.tsx
-
22src/components/utils/fixed-bottom.tsx
-
12src/components/utils/view-paddings-provider.tsx
-
14src/hooks/use-view-paddings.ts
-
92src/lib/view-paddings.ts
-
9src/types/window.d.ts
-
3src/view-paddings.ts
@ -0,0 +1,22 @@ |
|||
"use client"; |
|||
|
|||
import type { ReactNode } from "react"; |
|||
import { useViewPaddings } from "@/hooks/use-view-paddings"; |
|||
|
|||
type FixedBottomProps = { |
|||
children: ReactNode; |
|||
className?: string; |
|||
}; |
|||
|
|||
export function FixedBottom({ children, className }: FixedBottomProps) { |
|||
const { bottom } = useViewPaddings(); |
|||
|
|||
return ( |
|||
<div |
|||
style={{ paddingBottom: `${bottom}px` }} |
|||
className={className} |
|||
> |
|||
{children} |
|||
</div> |
|||
); |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
"use client"; |
|||
|
|||
import { useEffect } from "react"; |
|||
import { viewPaddingsBridge } from "@/lib/view-paddings"; |
|||
|
|||
export function ViewPaddingsProvider() { |
|||
useEffect(() => { |
|||
viewPaddingsBridge; |
|||
}, []); |
|||
|
|||
return null; |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
"use client"; |
|||
|
|||
import { useEffect, useState } from "react"; |
|||
import { viewPaddingsBridge } from "@/lib/view-paddings"; |
|||
|
|||
export function useViewPaddings() { |
|||
const [paddings, setPaddings] = useState(() => viewPaddingsBridge.getPaddings()); |
|||
|
|||
useEffect(() => { |
|||
return viewPaddingsBridge.subscribe(setPaddings); |
|||
}, []); |
|||
|
|||
return paddings; |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
interface ViewPaddings { |
|||
top: number; |
|||
bottom: number; |
|||
left: number; |
|||
right: number; |
|||
} |
|||
|
|||
class ViewPaddingsBridge { |
|||
private paddings: ViewPaddings = { top: 0, bottom: 0, left: 0, right: 0 }; |
|||
private listeners: Array<(paddings: ViewPaddings) => void> = []; |
|||
private flutterUnsubscribe?: () => void; |
|||
|
|||
constructor() { |
|||
this.init(); |
|||
} |
|||
|
|||
private init() { |
|||
if (typeof window === "undefined") return; |
|||
|
|||
this.setupFlutterListener(); |
|||
this.requestPaddings(); |
|||
} |
|||
|
|||
private setupFlutterListener() { |
|||
const win = window as Window & { |
|||
addFlutterResponseListener?: ( |
|||
listener: (event: any) => void, |
|||
) => () => void; |
|||
}; |
|||
|
|||
const attach = () => { |
|||
if (typeof win.addFlutterResponseListener !== "function") return false; |
|||
|
|||
this.flutterUnsubscribe = win.addFlutterResponseListener((event) => { |
|||
if (event.action === "get_view_paddings" && event.success && event.data) { |
|||
this.paddings = { |
|||
top: event.data.top || 0, |
|||
bottom: event.data.bottom || 0, |
|||
left: event.data.left || 0, |
|||
right: event.data.right || 0, |
|||
}; |
|||
this.applyPaddings(); |
|||
this.notifyListeners(); |
|||
} |
|||
}); |
|||
|
|||
return true; |
|||
}; |
|||
|
|||
if (!attach()) { |
|||
const interval = setInterval(() => { |
|||
if (attach()) clearInterval(interval); |
|||
}, 50); |
|||
} |
|||
} |
|||
|
|||
private requestPaddings() { |
|||
if (typeof window === "undefined") return; |
|||
|
|||
const app = (window as any).HabibApp; |
|||
if (app?.postMessage) { |
|||
app.postMessage(JSON.stringify({ action: "get_view_paddings" })); |
|||
} |
|||
} |
|||
|
|||
private applyPaddings() { |
|||
if (typeof document === "undefined") return; |
|||
|
|||
document.documentElement.style.setProperty("--safe-top", `${this.paddings.top}px`); |
|||
document.documentElement.style.setProperty("--safe-bottom", `${this.paddings.bottom}px`); |
|||
document.documentElement.style.setProperty("--safe-left", `${this.paddings.left}px`); |
|||
document.documentElement.style.setProperty("--safe-right", `${this.paddings.right}px`); |
|||
} |
|||
|
|||
private notifyListeners() { |
|||
this.listeners.forEach((listener) => listener(this.paddings)); |
|||
} |
|||
|
|||
public getPaddings(): ViewPaddings { |
|||
return { ...this.paddings }; |
|||
} |
|||
|
|||
public subscribe(listener: (paddings: ViewPaddings) => void): () => void { |
|||
this.listeners.push(listener); |
|||
return () => { |
|||
const index = this.listeners.indexOf(listener); |
|||
if (index >= 0) this.listeners.splice(index, 1); |
|||
}; |
|||
} |
|||
} |
|||
|
|||
export const viewPaddingsBridge = new ViewPaddingsBridge(); |
|||
@ -0,0 +1,3 @@ |
|||
export { useViewPaddings } from "./hooks/use-view-paddings"; |
|||
export { viewPaddingsBridge } from "./lib/view-paddings"; |
|||
export { FixedBottom } from "./components/utils/fixed-bottom"; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue