From b5e245e0fb938ac05cb2239f20cecb140fbfb5cf Mon Sep 17 00:00:00 2001 From: colinislit Date: Sat, 27 Dec 2025 14:00:47 +0100 Subject: [PATCH] feat(swift): voeg scrolling functionaliteit toe aan chat (E2.S3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E2.S3 - ChatPanel scrolling (5 SP) - Scrollable message list met refs (scrollContainerRef, messagesEndRef) - Auto-scroll naar laatste message bij nieuwe berichten - Scroll-lock detection (100px threshold) - "Scroll naar beneden" button tijdens scroll-lock - Initial scroll to bottom bij mount Scrolling Logic: - useRef voor scroll container en messages end tracking - useState voor isScrolledUp en showScrollButton state - handleScroll() detecteert wanneer user omhoog scrollt - scrollToBottom() met smooth/auto behavior support - Auto-scroll alleen wanneer NIET scrolled up (scroll-lock) Scroll Button: - ArrowDown icon (Lucide) - Positioned absolute bottom-4 right-4 - Shadow + hover effects, smooth transitions - Verschijnt alleen bij scroll-lock en messages present - Aria-label voor accessibility Testing: - 12 demo messages voor scrolling behavior test - Conversatie flow: notitie, zoeken, overdracht - Scroll threshold: 100px vanaf bottom Components Updated: - components/swift/chat/chat-panel.tsx (+68 regels) Epic 2 Progress: 3/5 stories compleet (10 SP / 13 SP = 77%) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- components/swift/chat/chat-panel.tsx | 137 ++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 11 deletions(-) diff --git a/components/swift/chat/chat-panel.tsx b/components/swift/chat/chat-panel.tsx index 349da16..ce4fcae 100644 --- a/components/swift/chat/chat-panel.tsx +++ b/components/swift/chat/chat-panel.tsx @@ -3,20 +3,30 @@ /** * Chat Panel (v3.0) * - * Chat interface met message list en input. - * Toont chat messages met verschillende types (user, assistant, system, error). + * Chat interface met scrollable message list, auto-scroll, en scroll-lock detection. * * Epic: E2 (Chat Panel & Messages) - * Story: E2.S2 (ChatMessage component) - testing + * Story: E2.S3 (ChatPanel component - scrolling) */ +import { useRef, useEffect, useState, useCallback } from 'react'; +import { ArrowDown } from 'lucide-react'; import { ChatMessage } from './chat-message'; import { useSwiftStore } from '@/stores/swift-store'; +import { cn } from '@/lib/utils'; export function ChatPanel() { const chatMessages = useSwiftStore((s) => s.chatMessages); - // Demo messages for testing E2.S2 (will be removed in E2.S3) + // Refs for scrolling + const scrollContainerRef = useRef(null); + const messagesEndRef = useRef(null); + + // Scroll-lock state + const [isScrolledUp, setIsScrolledUp] = useState(false); + const [showScrollButton, setShowScrollButton] = useState(false); + + // Demo messages for testing scrolling behavior (will be removed when chat input is implemented) const demoMessages = chatMessages.length === 0 ? [ { id: '1', @@ -27,34 +37,120 @@ export function ChatPanel() { { id: '2', type: 'user' as const, - content: 'Notitie voor Jan: medicatie gegeven om 14:00', + content: 'Hoi, ik wil een notitie maken', timestamp: new Date(), }, { id: '3', type: 'assistant' as const, - content: 'Ik begrijp dat je een notitie wilt maken voor Jan over medicatie. Ik open een dagnotitie voor je waarin je dit kunt vastleggen.', + content: 'Natuurlijk! Voor welke patiënt wil je een notitie maken?', timestamp: new Date(), }, { id: '4', - type: 'error' as const, - content: 'Er ging iets mis met de verbinding. Probeer het opnieuw.', + type: 'user' as const, + content: 'Notitie voor Jan: medicatie gegeven om 14:00', + timestamp: new Date(), + }, + { + id: '5', + type: 'assistant' as const, + content: 'Ik begrijp dat je een notitie wilt maken voor Jan over medicatie. Ik open een dagnotitie voor je waarin je dit kunt vastleggen.', + timestamp: new Date(), + }, + { + id: '6', + type: 'user' as const, + content: 'Zoek Marie van den Berg', + timestamp: new Date(), + }, + { + id: '7', + type: 'assistant' as const, + content: 'Ik zoek Marie van den Berg voor je op in het systeem.', + timestamp: new Date(), + }, + { + id: '8', + type: 'user' as const, + content: 'Maak overdracht voor deze dienst', + timestamp: new Date(), + }, + { + id: '9', + type: 'assistant' as const, + content: 'Ik maak een overdracht voor je. Welke dienst bedoel je precies? Nacht, ochtend, middag of avond?', + timestamp: new Date(), + }, + { + id: '10', + type: 'user' as const, + content: 'Avonddienst', + timestamp: new Date(), + }, + { + id: '11', + type: 'assistant' as const, + content: 'Prima! Ik open een overdracht voor de avonddienst. Je kunt hier alle relevante informatie voor de overdracht invullen.', + timestamp: new Date(), + }, + { + id: '12', + type: 'system' as const, + content: 'Dit is een lange conversatie om scrolling te testen', timestamp: new Date(), }, ] : chatMessages; const hasMessages = demoMessages.length > 0; + // Scroll to bottom function + const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => { + messagesEndRef.current?.scrollIntoView({ behavior }); + setIsScrolledUp(false); + setShowScrollButton(false); + }, []); + + // Detect scroll position (scroll-lock detection) + const handleScroll = useCallback(() => { + if (!scrollContainerRef.current) return; + + const { scrollTop, scrollHeight, clientHeight } = scrollContainerRef.current; + const isNearBottom = scrollHeight - scrollTop - clientHeight < 100; // 100px threshold + + setIsScrolledUp(!isNearBottom); + + // Show scroll button only if scrolled up AND there are messages + setShowScrollButton(!isNearBottom && hasMessages); + }, [hasMessages]); + + // Auto-scroll to latest message when new message arrives (unless user scrolled up) + useEffect(() => { + if (!isScrolledUp && hasMessages) { + scrollToBottom('smooth'); + } + }, [demoMessages.length, isScrolledUp, hasMessages, scrollToBottom]); + + // Initial scroll to bottom on mount + useEffect(() => { + scrollToBottom('auto'); + }, [scrollToBottom]); + return (
- {/* Chat messages area */} -
+ {/* Chat messages area - scrollable */} +
{hasMessages ? (
{demoMessages.map((message) => ( ))} + {/* Invisible element to scroll to */} +
) : (
@@ -75,6 +171,25 @@ export function ChatPanel() {
)} + + {/* Scroll to bottom button */} + {showScrollButton && ( + + )}
{/* Chat input - placeholder */} @@ -94,7 +209,7 @@ export function ChatPanel() {

- E2.S2 compleet: ChatMessage component werkend ✓ + E2.S3 in progress: Auto-scroll en scroll-lock werkend ✓