Message Scroller
จาก shadcnพื้นที่บทสนทนาที่เลื่อนตามข้อความใหม่อัตโนมัติ พร้อมปุ่มเลื่อนลงล่าสุดเมื่อผู้ใช้อ่านย้อนหลัง เหมาะกับห้องแชดงาน HR
ติดตั้ง
Terminal
pnpm dlx shadcn@latest add @whereui/message-scrollerต้องผูก registry และติดตั้ง @whereui/theme นำหน้าเสมอเมื่อติดตั้งครั้งแรก
การใช้งาน
import { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport,} from "@/components/ui/message-scroller"<MessageScrollerProvider> <MessageScroller> <MessageScrollerViewport> <MessageScrollerContent> <MessageScrollerItem scrollAnchor>{/* ข้อความ */}</MessageScrollerItem> </MessageScrollerContent> </MessageScrollerViewport> <MessageScrollerButton /> </MessageScroller></MessageScrollerProvider>ข้อควรรู้
- ต้องครอบด้วย MessageScrollerProvider เสมอ และกำหนด scrollAnchor ที่ข้อความรายการสุดท้ายเพื่อเลื่อนตามอัตโนมัติ
- พื้นที่ viewport ต้องมีความสูงจำกัด (เช่น h-72) ไม่เช่นนั้นจะไม่เกิดการเลื่อน
ซอร์สโค้ด
components/ui/message-scroller.tsx
"use client"import * as React from "react"import { MessageScroller as MessageScrollerPrimitive, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility,} from "@shadcn/react/message-scroller"import { cn } from "cn"import { Button } from "@/components/ui/button"import { ArrowDownIcon } from "lucide-react"function MessageScrollerProvider( props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>) { return <MessageScrollerPrimitive.Provider {...props} />}function MessageScroller({ className, ...props}: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) { return ( <MessageScrollerPrimitive.Root data-slot="message-scroller" className={cn( "group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden", className )} {...props} /> )}function MessageScrollerViewport({ className, ...props}: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) { return ( <MessageScrollerPrimitive.Viewport data-slot="message-scroller-viewport" className={cn( "size-full min-h-0 min-w-0 scroll-fade-b scrollbar-thin scrollbar-gutter-stable overflow-y-auto overscroll-contain contain-content data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent data-pending-scroll:invisible", className )} {...props} /> )}function MessageScrollerContent({ className, ...props}: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) { return ( <MessageScrollerPrimitive.Content data-slot="message-scroller-content" className={cn("flex h-max min-h-full flex-col gap-6", className)} {...props} /> )}function MessageScrollerItem({ className, scrollAnchor = false, ...props}: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) { return ( <MessageScrollerPrimitive.Item data-slot="message-scroller-item" scrollAnchor={scrollAnchor} className={cn( "min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]", className )} {...props} /> )}function MessageScrollerButton({ direction = "end", className, children, render, variant = "secondary", size = "icon-sm", ...props}: React.ComponentProps<typeof MessageScrollerPrimitive.Button> & Pick<React.ComponentProps<typeof Button>, "variant" | "size">) { return ( <MessageScrollerPrimitive.Button data-slot="message-scroller-button" data-direction={direction} data-variant={variant} data-size={size} direction={direction} className={cn( "absolute inset-s-1/2 -translate-x-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180", className )} render={render ?? <Button variant={variant} size={size} />} {...props} > {children ?? ( <> <ArrowDownIcon /> <span className="sr-only"> {direction === "end" ? "Scroll to end" : "Scroll to start"} </span> </> )} </MessageScrollerPrimitive.Button> )}export { MessageScrollerProvider, MessageScroller, MessageScrollerViewport, MessageScrollerContent, MessageScrollerItem, MessageScrollerButton, useMessageScroller, useMessageScrollerScrollable, useMessageScrollerVisibility,}