Files
cozsweet-frontend-nextjs/src/hooks/use-visibility.ts
T
admin 8cab34864e feat(ui): migrate Flutter UI library to Next.js 16 (App Router)
Migrate all 87 Dart files from /Users/chase/Documents/cozsweet/lib/ui/
to TypeScript React components, replacing Flutter-native features with
Web equivalents and reimplementing the BLoC pattern with React
Context + useReducer.

Highlights
- 1:1 BLoC -> Context+useReducer+side-effects mapping for auth, chat,
  user, and sidebar features (4 providers wired in root-providers.tsx)
- 500px MobileShell primitive eliminates per-screen layout boilerplate
- Headless Dialog primitive (createPortal + ESC + scroll lock) powers
  quota / pwa-install / username / pronouns / subscription / other-options
  dialogs
- New integrations/: facebook-sdk, google-identity, pwa-install,
  media-recorder (Web Speech API + MediaRecorder), browser-detect,
  chat-websocket (singleton), message-queue (throttled send)
- CSS Modules + existing design tokens (no new styling system);
  extended with breakpoints.css and dimensions.css
- Custom hooks: useBreakpoint (useSyncExternalStore),
  useFullVisibility (IntersectionObserver), usePwaInstall,
  usePullToRefresh
- Vitest + jsdom; 15 unit tests (chat-reducer, auth-validators)
- React 19.2.4, Next.js 16.2.7, Zod 4, lottie-react, classnames

Workarounds
- next.config.ts: experimental.staticGenerationRetryCount = 0 to
  bypass Next.js 16.2.7 /_global-error workStore prerender bug
- src/app/global-error.tsx: required Client Component
- src/types/globals.d.ts: centralized window.google / FB /
  SpeechRecognition declarations

Routes (/, /splash, /auth, /chat, /chat/deviceid/[deviceId],
/sidebar) all build and lint clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-09 13:39:49 +08:00

43 lines
1.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
/**
* 元素全可见性 hook
*
* 原始 Dart: `visibility_detector` 包的 `VisibilityDetector`chat_screen 用法:
* onVisibilityChanged: (info) { if (info.visibleFraction == 1.0) ... })。
*
* 用 IntersectionObserverthreshold=1.0 触发 `isIntersecting` 即视为「完全可见」。
* 返回 `[isVisible, ref]`,与 Flutter `VisibilityDetector` 的语义对齐。
*
* 用法:
* const [isVisible, ref] = useFullVisibility();
* useEffect(() => { dispatch(isVisible ? Visible() : Invisible()); }, [isVisible]);
*/
import { useEffect, useRef, useState } from "react";
export function useFullVisibility(
threshold = 1.0,
): readonly [boolean, React.RefObject<HTMLDivElement | null>] {
const ref = useRef<HTMLDivElement | null>(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el || typeof IntersectionObserver === "undefined") return;
const observer = new IntersectionObserver(
(entries) => {
const entry = entries[0];
if (!entry) return;
setVisible(
entry.isIntersecting && entry.intersectionRatio >= threshold,
);
},
{ threshold: [threshold] },
);
observer.observe(el);
return () => observer.disconnect();
}, [threshold]);
return [visible, ref] as const;
}