fix: observer chat message container width
- Added useElementWidth hook to observe and track the width of a referenced element using ResizeObserver. - Updated ChatInterface and WelcomeScreen components to utilize the new hook for responsive layout adjustments. - Enhanced AIDisplayEntriesPart and AIDisplayFeedsPart components with improved styling for better alignment and spacing. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
parent
9f2769b34c
commit
aada1b8b65
|
|
@ -39,11 +39,11 @@ const SingleEntryCard = ({ entry }: { entry: SingleEntry }) => {
|
|||
target={undefined}
|
||||
siteUrl={entry.url ?? undefined}
|
||||
/>
|
||||
<h3 className="text-text line-clamp-2 flex-1 font-semibold leading-tight">
|
||||
<h3 className="text-text line-clamp-2 flex-1 text-left font-semibold leading-tight">
|
||||
{entry.title || "Untitled Entry"}
|
||||
</h3>
|
||||
</div>
|
||||
<i className="i-mgc-external-link-cute-re text-text-tertiary shrink-0 opacity-60 transition-opacity group-hover:opacity-100" />
|
||||
<i className="i-mgc-external-link-cute-re text-text-tertiary ml-2 shrink-0 opacity-60 transition-opacity group-hover:opacity-100" />
|
||||
</div>
|
||||
</button>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ const AIDisplayFeedPartBase = ({
|
|||
}}
|
||||
siteUrl={siteUrl!}
|
||||
/>
|
||||
<h3 className="text-text line-clamp-2 flex-1 font-semibold leading-tight">
|
||||
<h3 className="text-text line-clamp-2 flex-1 text-left font-semibold leading-tight">
|
||||
{title || "Untitled Feed"}
|
||||
</h3>
|
||||
</div>
|
||||
<i className="i-mgc-external-link-cute-re text-text-tertiary shrink-0 opacity-60 transition-opacity group-hover:opacity-100" />
|
||||
<i className="i-mgc-external-link-cute-re text-text-tertiary ml-2 shrink-0 opacity-60 transition-opacity group-hover:opacity-100" />
|
||||
</div>
|
||||
|
||||
{/* Description */}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useFocusable } from "@follow/components/common/Focusable/hooks.js"
|
||||
import { ScrollArea } from "@follow/components/ui/scroll-area/ScrollArea.js"
|
||||
import { useElementWidth } from "@follow/hooks"
|
||||
import { getCategoryFeedIds } from "@follow/store/subscription/getter"
|
||||
import { usePrefetchSummary } from "@follow/store/summary/hooks"
|
||||
import { tracker } from "@follow/tracker"
|
||||
|
|
@ -10,7 +11,7 @@ import type { EditorState, LexicalEditor } from "lexical"
|
|||
import { AnimatePresence } from "motion/react"
|
||||
import { nanoid } from "nanoid"
|
||||
import type { FC, RefObject } from "react"
|
||||
import { startTransition, Suspense, use, useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||
import { Suspense, use, useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||
import { useEventCallback, useEventListener } from "usehooks-ts"
|
||||
|
||||
import { useAISettingKey } from "~/atoms/settings/ai"
|
||||
|
|
@ -391,26 +392,8 @@ export const ChatInterface = (props: ChatInterfaceProps) => (
|
|||
|
||||
const Messages: FC<{ contentRef?: RefObject<HTMLDivElement> }> = ({ contentRef }) => {
|
||||
const messages = useMessages()
|
||||
|
||||
const [messageContainerWidth, setMessageContainerWidth] = useState<number>(0)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!contentRef) return
|
||||
|
||||
const setMessageContainerWidthTransition = () =>
|
||||
startTransition(() => {
|
||||
setMessageContainerWidth(contentRef.current?.clientWidth ?? 0)
|
||||
})
|
||||
setMessageContainerWidthTransition()
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
setMessageContainerWidthTransition()
|
||||
})
|
||||
resizeObserver.observe(contentRef.current)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [contentRef])
|
||||
const fallbackRef = useRef<HTMLDivElement>(null)
|
||||
const messageContainerWidth = useElementWidth(contentRef ?? fallbackRef)
|
||||
return (
|
||||
<div
|
||||
ref={contentRef}
|
||||
|
|
@ -421,18 +404,19 @@ const Messages: FC<{ contentRef?: RefObject<HTMLDivElement> }> = ({ contentRef }
|
|||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{messages.map((message, index) => {
|
||||
const isLastMessage = index === messages.length - 1
|
||||
return (
|
||||
<Suspense key={message.id}>
|
||||
{message.role === "user" ? (
|
||||
<UserChatMessage message={message} />
|
||||
) : (
|
||||
<AIChatMessage message={message} isLastMessage={isLastMessage} />
|
||||
)}
|
||||
</Suspense>
|
||||
)
|
||||
})}
|
||||
{!!messageContainerWidth &&
|
||||
messages.map((message, index) => {
|
||||
const isLastMessage = index === messages.length - 1
|
||||
return (
|
||||
<Suspense key={message.id}>
|
||||
{message.role === "user" ? (
|
||||
<UserChatMessage message={message} />
|
||||
) : (
|
||||
<AIChatMessage message={message} isLastMessage={isLastMessage} />
|
||||
)}
|
||||
</Suspense>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Folo } from "@follow/components/icons/folo.js"
|
||||
import { ScrollArea } from "@follow/components/ui/scroll-area/ScrollArea.js"
|
||||
import { FeedViewType } from "@follow/constants"
|
||||
import { useElementWidth } from "@follow/hooks"
|
||||
import { clsx } from "@follow/utils"
|
||||
import type { EditorState, LexicalEditor } from "lexical"
|
||||
import { AnimatePresence, m } from "motion/react"
|
||||
|
|
@ -174,6 +175,9 @@ const TimelineSummarySection = () => {
|
|||
|
||||
const { t } = useTranslation("ai")
|
||||
|
||||
const messageContainerRef = useRef<HTMLDivElement>(null)
|
||||
const messageContainerWidth = useElementWidth(messageContainerRef)
|
||||
|
||||
const isLoading = status === "streaming"
|
||||
const isError = status === "error"
|
||||
return (
|
||||
|
|
@ -196,7 +200,16 @@ const TimelineSummarySection = () => {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-text flex select-text flex-col gap-2 text-sm leading-6">
|
||||
<div
|
||||
className="text-text flex select-text flex-col gap-2 text-sm leading-6"
|
||||
ref={messageContainerRef}
|
||||
style={
|
||||
{
|
||||
"--ai-chat-message-container-width": `${messageContainerWidth}px`,
|
||||
opacity: messageContainerWidth > 0 ? 1 : 0,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
{isError ? (
|
||||
<p className="text-text text-sm font-medium">{t("timeline_summary.error")}</p>
|
||||
) : hasContent && message ? (
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ export * from "./useAnyPointDown"
|
|||
export * from "./useControlled"
|
||||
export * from "./useCountDown"
|
||||
export * from "./useDark"
|
||||
export * from "./useElementWidth"
|
||||
export * from "./useInputComposition"
|
||||
export * from "./useInterval"
|
||||
export * from "./useIsOnline"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
import type { RefObject } from "react"
|
||||
import { startTransition, useLayoutEffect, useState } from "react"
|
||||
|
||||
/**
|
||||
* Hook to track the width of an element using ResizeObserver
|
||||
* @param ref - RefObject pointing to the element to observe
|
||||
* @returns The current width of the element (0 if element is not available)
|
||||
*/
|
||||
export function useElementWidth<T extends HTMLElement>(ref: RefObject<T | null>): number {
|
||||
const [width, setWidth] = useState<number>(0)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!ref.current) return
|
||||
|
||||
const updateWidth = (newWidth: number) => {
|
||||
startTransition(() => {
|
||||
setWidth(newWidth)
|
||||
})
|
||||
}
|
||||
|
||||
// Set initial width
|
||||
updateWidth(ref.current.clientWidth)
|
||||
|
||||
// Create ResizeObserver to track width changes
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
if (ref.current) {
|
||||
updateWidth(ref.current.clientWidth)
|
||||
}
|
||||
})
|
||||
|
||||
resizeObserver.observe(ref.current)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [ref])
|
||||
|
||||
return width
|
||||
}
|
||||
Loading…
Reference in New Issue