From daa623d2df7b3bd1ab086095d019bf7ba005adeb Mon Sep 17 00:00:00 2001 From: elvoisin <95469923+elvoisin@users.noreply.github.com> Date: Thu, 16 Oct 2025 18:18:30 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8(front)=20retour=20ui=20global=20(#?= =?UTF-8?q?80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 👷(front) fix scroll fix scroll + fix background model selector * 🎨(front) retour ui global retour ui robin : scroll, searchbar, margin --- CHANGELOG.md | 3 +- .../src/assets/logo/logo-assistant.svg | 34 ++-- .../src/components/CustomToast.tsx | 22 +-- .../src/components/ToastProvider.tsx | 7 +- .../quick-search/QuickSearchInput.tsx | 14 +- .../chat/components/AttachmentList.tsx | 39 +++- .../src/features/chat/components/Chat.tsx | 185 +++++++++++++----- .../chat/components/FeedbackButtons.tsx | 102 +++++++--- .../features/chat/components/InputChat.tsx | 38 ++-- .../features/chat/components/SourceItem.tsx | 40 +++- .../chat/components/SourceItemList.tsx | 27 ++- .../chat/components/ToolInvocationItem.tsx | 4 +- .../src/features/chat/hooks/index.ts | 1 + .../src/features/chat/hooks/useChatScroll.ts | 47 +---- .../features/chat/hooks/useSourceMetadata.ts | 117 +++++++++++ .../features/chat/stores/useScrollStore.ts | 11 ++ .../src/features/header/components/Header.tsx | 2 +- .../components/ConversationItemActions.tsx | 2 +- .../components/LeftPanelConversationItem.tsx | 1 + .../apps/conversations/src/pages/globals.css | 29 ++- 20 files changed, 511 insertions(+), 214 deletions(-) create mode 100644 src/frontend/apps/conversations/src/features/chat/hooks/useSourceMetadata.ts create mode 100644 src/frontend/apps/conversations/src/features/chat/stores/useScrollStore.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b6a3db..c0da30a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,11 @@ and this project adheres to ## [Unreleased] -- 👷(front) fix button scrollDown +- 🎨(front) retour ui global ### Changed +- 👷(front) fix button scrollDown - 💥(front) disable input when error occurred - 👷(front) fix scroll - 🐛(front) fix left panel status + fix scroll diff --git a/src/frontend/apps/conversations/src/assets/logo/logo-assistant.svg b/src/frontend/apps/conversations/src/assets/logo/logo-assistant.svg index 112c202..8ad8fa9 100644 --- a/src/frontend/apps/conversations/src/assets/logo/logo-assistant.svg +++ b/src/frontend/apps/conversations/src/assets/logo/logo-assistant.svg @@ -1,28 +1,28 @@ - - + + - - + + - - - - - - - - - - + + + + + + + + + + - + - + - + diff --git a/src/frontend/apps/conversations/src/components/CustomToast.tsx b/src/frontend/apps/conversations/src/components/CustomToast.tsx index 3100fa1..81b9e7f 100644 --- a/src/frontend/apps/conversations/src/components/CustomToast.tsx +++ b/src/frontend/apps/conversations/src/components/CustomToast.tsx @@ -69,7 +69,7 @@ export const Toast = ({ const iconToUse = icon || config.icon; useEffect(() => { - const timer = setTimeout(() => setIsVisible(true), 100); + setIsVisible(true); const autoCloseTimer = setTimeout(() => { setIsLeaving(true); @@ -77,7 +77,6 @@ export const Toast = ({ }, duration); return () => { - clearTimeout(timer); clearTimeout(autoCloseTimer); }; }, [id, duration, onClose]); @@ -89,21 +88,18 @@ export const Toast = ({ border: 1px solid ${config.borderColor}; color: ${config.color}; border-radius: 4px; - padding: 8px 12px; + padding: ${isVisible && !isLeaving ? '8px 12px' : '0 12px'}; width: auto; - margin-bottom: 8px; box-shadow: 0 6px 18px 0 rgba(0, 0, 145, 0.05); - transform: translateY(${isVisible ? '0' : '100%'}); - opacity: ${isVisible ? '1' : '0'}; - transition: all 0.3s ease-in-out; + opacity: ${isVisible && !isLeaving ? '1' : '0'}; + transform: translateY(${isVisible && !isLeaving ? '0' : '-3px'}); + max-height: ${isVisible && !isLeaving ? '100px' : '0px'}; + transition: + padding 0.2s ease, + opacity 0.2s ease, + transform 0.2s ease; position: relative; overflow: hidden; - - ${isLeaving && - css` - transform: translateY(-100%); - opacity: 0; - `} `} > diff --git a/src/frontend/apps/conversations/src/components/ToastProvider.tsx b/src/frontend/apps/conversations/src/components/ToastProvider.tsx index 8412b4c..84e2ff5 100644 --- a/src/frontend/apps/conversations/src/components/ToastProvider.tsx +++ b/src/frontend/apps/conversations/src/components/ToastProvider.tsx @@ -50,7 +50,7 @@ export const ToastProvider = ({ children }: ToastProviderProps) => { const id = Math.random().toString(36).substr(2, 9); const newToast: ToastItem = { id, message, type, icon, duration }; - setToasts((prev) => [...prev, newToast]); + setToasts((prev) => [newToast, ...prev]); }, [], ); @@ -75,13 +75,14 @@ export const ToastProvider = ({ children }: ToastProviderProps) => { left: 50%; transform: translateX(-50%); z-index: 1000; - display: inline-block; + display: flex; flex-direction: column; - gap: 8px; width: auto; pointer-events: none; & > * { pointer-events: auto; + margin-bottom: 8px; + transition: transform 0.3s ease-out, margin 0.3s ease-out; } `} > diff --git a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx index 78a50a4..59f36c0 100644 --- a/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx +++ b/src/frontend/apps/conversations/src/components/quick-search/QuickSearchInput.tsx @@ -80,7 +80,13 @@ export const QuickSearchInput = ({ border: 1px solid #CFD5DE; background-color: #eff1f5; padding: 6px 8px; - &:focus-within, &:hover { + cursor: pointer !important; + transition: border-color 0.2s ease; + &:hover { + border: 2px solid #CFD5DE; + padding: 5px 7px; + } + &:focus-within { border: 2px solid #3E5DE7; padding: 5px 7px; } @@ -124,9 +130,11 @@ export const QuickSearchInput = ({ right: 10px; border-radius: 4px; padding: 7px; - background-color: #e4e6ea; z-index: 1000; - &:focus { + &:hover { + background-color: #e4e6ea; + } + &:focus-visible { outline: 2px solid #3E5DE7; outline-offset: 2px; }`} diff --git a/src/frontend/apps/conversations/src/features/chat/components/AttachmentList.tsx b/src/frontend/apps/conversations/src/features/chat/components/AttachmentList.tsx index fbaa0ab..29136cf 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/AttachmentList.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/AttachmentList.tsx @@ -1,4 +1,7 @@ -import { Box, BoxButton, Icon, Text } from '@/components'; +import { useTranslation } from 'react-i18next'; +import { css } from 'styled-components'; + +import { Box, Icon, Text } from '@/components'; // Define Attachment type locally (mirroring backend structure) export interface Attachment { @@ -18,6 +21,8 @@ export const AttachmentList = ({ onRemove, isReadOnly = false, }: AttachmentListProps) => { + const { t } = useTranslation(); + if (!attachments || attachments.length === 0) { return null; } @@ -52,9 +57,9 @@ export const AttachmentList = ({ $background="var(--c--theme--colors--greyscale-050)" $minWidth="200px" $direction="row" - $gap="xs" + $gap="8px" $align="center" - $padding="xs" + $padding={{ all: '4px', left: 'xs' }} $css={` border-radius: 4px; `} @@ -66,7 +71,6 @@ export const AttachmentList = ({ $height="22px" $direction="row" $align="center" - $margin={{ right: 'xs' }} $justify="center" $css={` flex-shrink: 0; @@ -101,12 +105,33 @@ export const AttachmentList = ({ {name} {!isReadOnly && onRemove && ( - { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + removeAttachment(); + } + }} + $css={css` + display: block; + padding: 4px; + border-radius: 4px; + cursor: pointer; + &:hover { + background-color: #e1e3e7 !important; + } + &:focus-visible { + outline: 2px solid #3e5de7; + outline-offset: 2px; + } + `} > - + )} diff --git a/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx index 4a549d3..189e7dd 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/Chat.tsx @@ -32,8 +32,10 @@ import { ToolInvocationItem } from '@/features/chat/components/ToolInvocationIte import { useClipboard } from '@/hook'; import { useResponsiveStore } from '@/stores'; +import { useSourceMetadataCache } from '../hooks'; import { useChatPreferencesStore } from '../stores/useChatPreferencesStore'; import { usePendingChatStore } from '../stores/usePendingChatStore'; +import { useScrollStore } from '../stores/useScrollStore'; // Define Attachment type locally (mirroring backend structure) export interface Attachment { @@ -112,7 +114,29 @@ export const Chat = ({ title: string; message: string; } | null>(null); + + const { setIsAtTop } = useScrollStore(); + + // Gérer le scroll pour mettre à jour l'état du header + useEffect(() => { + const handleScroll = () => { + if (chatContainerRef.current) { + const scrollTop = chatContainerRef.current.scrollTop; + setIsAtTop(scrollTop <= 5); + } + }; + + const container = chatContainerRef.current; + if (container) { + container.addEventListener('scroll', handleScroll, { passive: true }); + handleScroll(); // Vérifier la position initiale + + return () => container.removeEventListener('scroll', handleScroll); + } + }, [setIsAtTop]); + const [isSourceOpen, setIsSourceOpen] = useState(null); + const { prefetchMetadata, getMetadata } = useSourceMetadataCache(); const [initialConversationMessages, setInitialConversationMessages] = useState(undefined); @@ -126,7 +150,7 @@ export const Chat = ({ const [streamingMessageHeight, setStreamingMessageHeight] = useState< number | null >(null); - const [userScrolledUp, _setUserScrolledUp] = useState(false); + const lastUserMessageIdRef = useRef(null); const { mutate: createChatConversation } = useCreateChatConversation(); @@ -201,6 +225,20 @@ export const Chat = ({ void handleSubmit(event); }; + // Précharger les métadonnées des sources dès que les messages arrivent + useEffect(() => { + messages.forEach((message) => { + if (message.parts) { + const sourceParts = message.parts.filter( + (part): part is SourceUIPart => part.type === 'source', + ); + sourceParts.forEach((part) => { + void prefetchMetadata(part.source.url); + }); + } + }); + }, [messages, prefetchMetadata]); + const openSources = (messageId: string) => { if (isSourceOpen === messageId) { setIsSourceOpen(null); @@ -236,9 +274,7 @@ export const Chat = ({ const userMessageHeight = (lastUserMessageElement as HTMLElement) .offsetHeight; - const thinkingHeight = 90; - const availableHeight = - containerHeight - userMessageHeight - thinkingHeight; + const availableHeight = containerHeight - userMessageHeight - 38; if (streamingMessageHeight !== availableHeight) { setStreamingMessageHeight(availableHeight); @@ -248,41 +284,48 @@ export const Chat = ({ } }, [messages, streamingMessageHeight]); + // Détecter l'arrivée d'un nouveau message user et retirer la hauteur de l'ancien useEffect(() => { - if (chatContainerRef.current && messages.length > 0) { - const _userMessages = messages.filter((msg) => msg.role === 'user'); - const assistantMessages = messages.filter( - (msg) => msg.role === 'assistant', - ); - const lastMessage = messages[messages.length - 1]; + const userMessages = messages.filter((msg) => msg.role === 'user'); + const lastUserMessage = userMessages[userMessages.length - 1]; - // Gérer la hauteur de streaming - if ( - lastMessage && - lastMessage.role === 'user' && - assistantMessages.length > 0 && - status === 'ready' - ) { - // Nouveau message user détecté, réinitialiser la hauteur + if ( + lastUserMessage && + lastUserMessage.id !== lastUserMessageIdRef.current + ) { + if (lastUserMessageIdRef.current !== null) { setStreamingMessageHeight(null); - } else if (status === 'streaming' || status === 'submitted') { - // Calculer la hauteur pendant le streaming - calculateStreamingHeight(); } + lastUserMessageIdRef.current = lastUserMessage.id; } - }, [ - messages, - status, - hasInitialized, - calculateStreamingHeight, - userScrolledUp, - ]); + }, [messages]); + // Calculer la hauteur pendant submitted/streaming useEffect(() => { - if (status === 'submitted') { - scrollToBottom(); + if (status === 'submitted' || status === 'streaming') { + calculateStreamingHeight(); } - }, [status, scrollToBottom]); + }, [status, calculateStreamingHeight]); + + // Scroller vers la question au moment du submit + useEffect(() => { + if (status !== 'submitted' || !chatContainerRef.current) { + return; + } + + const lastUserMessage = messages.filter((msg) => msg.role === 'user').pop(); + if (!lastUserMessage) { + return; + } + + requestAnimationFrame(() => { + const messageElement = chatContainerRef.current?.querySelector( + `[data-message-id="${lastUserMessage.id}"]`, + ); + + messageElement?.scrollIntoView({ block: 'start', behavior: 'smooth' }); + }); + }, [status, messages]); // Synchronize conversationId state with prop when it changes (e.g., after navigation) useEffect(() => { @@ -482,12 +525,13 @@ export const Chat = ({ $padding={{ all: 'base', left: '13px', bottom: '0', right: '0' }} $width="100%" $flex={1} - $overflow="scroll" + $overflow="auto" $css={` flex-basis: auto; flex-grow: 1; position: relative; margin-bottom: 0; + padding-bottom: 20px; height: ${messages.length > 0 ? 'calc(100vh - 62px)' : '0'}; max-height: ${messages.length > 0 ? 'calc(100vh - 62px)' : '0'}; `} @@ -495,12 +539,15 @@ export const Chat = ({ {messages.length > 0 && ( {messages.map((message, index) => { + const isLastMessage = index === messages.length - 1; const isLastAssistantMessageInConversation = message.role === 'assistant' && index === messages.findLastIndex((msg) => msg.role === 'assistant'); - const _shouldApplyStreamingHeight = - isLastAssistantMessageInConversation && streamingMessageHeight; + const shouldApplyStreamingHeight = + isLastAssistantMessageInConversation && + isLastMessage && + streamingMessageHeight; const isCurrentlyStreaming = isLastAssistantMessageInConversation && (status === 'streaming' || status === 'submitted'); @@ -543,7 +590,8 @@ export const Chat = ({ $background={`${message.role === 'user' ? '#EEF1F4' : 'white'}`} $css={` display: inline-block; - float: right;`} + float: right; + ${shouldApplyStreamingHeight ? `min-height: ${streamingMessageHeight}px;` : ''}`} > {/* Message content */} {message.content && ( @@ -581,6 +629,34 @@ export const Chat = ({ )} + {isCurrentlyStreaming && + isLastAssistantMessageInConversation && + status === 'streaming' && + message.parts?.some( + (part) => + part.type === 'tool-invocation' && + part.toolInvocation.toolName !== + 'document_parsing', + ) && ( + + + + {t('Search...')} + + + )} + {message.parts ?.filter( (part) => @@ -610,6 +686,7 @@ export const Chat = ({ key={`tool-invocation-${partIndex}`} toolInvocation={part.toolInvocation} status={status} + hideSearchLoader={true} /> ) : null, )} @@ -696,9 +773,10 @@ export const Chat = ({ $weight="500" $size="12px" > - {t('Show') + - ` ${sourceCount} ` + - t('sources')} + {t('Show')} {sourceCount}{' '} + {sourceCount !== 1 + ? t('sources') + : t('source')} ); @@ -717,14 +795,26 @@ export const Chat = ({ )} - {message.parts && isSourceOpen === message.id && ( - { + const sourceParts = message.parts.filter( (part): part is SourceUIPart => part.type === 'source', - )} - /> - )} + ); + return ( + + + + ); + })()} @@ -735,12 +825,15 @@ export const Chat = ({ {status !== 'ready' && status !== 'streaming' && status !== 'error' && ( diff --git a/src/frontend/apps/conversations/src/features/chat/components/FeedbackButtons.tsx b/src/frontend/apps/conversations/src/features/chat/components/FeedbackButtons.tsx index 3d1f321..3dd9d60 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/FeedbackButtons.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/FeedbackButtons.tsx @@ -1,8 +1,8 @@ import { Button } from '@openfun/cunningham-react'; -import React from 'react'; +import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { useToast } from '@/components'; +import { Box, useToast } from '@/components'; import { scoreMessage } from '@/features/chat/api/useScoreMessage'; interface FeedbackButtonsProps { @@ -12,34 +12,68 @@ interface FeedbackButtonsProps { const ThumbUp = () => ( ); const ThumbDown = () => ( + +); + +const ThumbDownFilled = () => ( + + + +); + +const ThumbUpFilled = () => ( + + ); @@ -50,6 +84,9 @@ export const FeedbackButtons = ({ }: FeedbackButtonsProps) => { const { t } = useTranslation(); const { showToast } = useToast(); + const [selectedFeedback, setSelectedFeedback] = useState< + 'positive' | 'negative' | null + >(null); const handleScore = async (value: 'positive' | 'negative') => { if (!conversationId) { @@ -63,29 +100,32 @@ export const FeedbackButtons = ({ value, }); - showToast( - 'success', - value === 'positive' - ? t('Positive feedback sent') - : t('Negative feedback sent'), - 'check_circle', - 3000, - ); + setSelectedFeedback(value); } catch (error) { + setSelectedFeedback(value); console.error('Error sending feedback:', error); showToast('error', t('Failed to send feedback'), 'error', 3000); } }; return ( - <> + ); }; diff --git a/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx b/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx index 1f11a90..4734bab 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/InputChat.tsx @@ -162,9 +162,8 @@ export const InputChat = ({ {messagesLength === 0 && ( {t('What is on your mind?')} @@ -212,24 +211,24 @@ export const InputChat = ({ }} style={{ width: '100%' }} > - + ); }; + +SourceItemListComponent.displayName = 'SourceItemList'; + +export const SourceItemList = React.memo(SourceItemListComponent); diff --git a/src/frontend/apps/conversations/src/features/chat/components/ToolInvocationItem.tsx b/src/frontend/apps/conversations/src/features/chat/components/ToolInvocationItem.tsx index c9b4d2a..de5a712 100644 --- a/src/frontend/apps/conversations/src/features/chat/components/ToolInvocationItem.tsx +++ b/src/frontend/apps/conversations/src/features/chat/components/ToolInvocationItem.tsx @@ -7,11 +7,13 @@ import { Box, Loader, Text } from '@/components'; interface ToolInvocationItemProps { toolInvocation: ToolInvocation; status?: string; + hideSearchLoader?: boolean; } export const ToolInvocationItem: React.FC = ({ toolInvocation, status, + hideSearchLoader = false, }) => { const { t } = useTranslation(); @@ -54,7 +56,7 @@ export const ToolInvocationItem: React.FC = ({ return ( <> - {status === 'streaming' && ( + {status === 'streaming' && !hideSearchLoader && ( = { - current: null, -}; - -export const setChatContainerRef = ( - ref: React.RefObject, -) => { - chatContainerRef = ref; -}; +import { useScrollStore } from '../stores/useScrollStore'; export const useChatScroll = () => { - const [isAtTop, setIsAtTop] = useState(true); - - useEffect(() => { - const handleScroll = () => { - if (chatContainerRef.current) { - const scrollTop = chatContainerRef.current.scrollTop; - const newIsAtTop = scrollTop <= 5; - setIsAtTop(newIsAtTop); - } - }; - - // Attendre que le conteneur soit disponible - const checkContainer = () => { - if (chatContainerRef.current) { - const container = chatContainerRef.current; - container.addEventListener('scroll', handleScroll, { passive: true }); - - // Vérifier la position initiale - handleScroll(); - - return () => { - container.removeEventListener('scroll', handleScroll); - }; - } else { - // Réessayer après un court délai si le conteneur n'est pas encore disponible - const timeoutId = setTimeout(checkContainer, 100); - return () => clearTimeout(timeoutId); - } - }; - - return checkContainer(); - }, []); - + const { isAtTop } = useScrollStore(); return { isAtTop }; }; diff --git a/src/frontend/apps/conversations/src/features/chat/hooks/useSourceMetadata.ts b/src/frontend/apps/conversations/src/features/chat/hooks/useSourceMetadata.ts new file mode 100644 index 0000000..50d85aa --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/hooks/useSourceMetadata.ts @@ -0,0 +1,117 @@ +import { useState } from 'react'; + +interface SourceMetadata { + title: string | null; + favicon: string | null; + loading: boolean; + error: boolean; +} + +// Cache global pour éviter de refetch les mêmes URLs +const metadataCache = new Map(); + +export const useSourceMetadataCache = () => { + const [cache, setCache] = + useState>(metadataCache); + + const prefetchMetadata = async (url: string) => { + // Si déjà en cache, ne rien faire + if (metadataCache.has(url)) { + return; + } + + // Marquer comme en cours de chargement + metadataCache.set(url, { + title: null, + favicon: null, + loading: true, + error: false, + }); + setCache(new Map(metadataCache)); + + try { + if (!url.startsWith('http')) { + metadataCache.set(url, { + title: url, + favicon: '📄', + loading: false, + error: false, + }); + setCache(new Map(metadataCache)); + return; + } + + const parser = new DOMParser(); + + let response; + try { + response = await fetch(url, { + mode: 'cors', + headers: { + 'User-Agent': 'Mozilla/5.0 (compatible; ChatBot/1.0)', + }, + }); + } catch { + // Si CORS échoue, utiliser juste le hostname + metadataCache.set(url, { + title: new URL(url).hostname, + favicon: null, + loading: false, + error: false, + }); + setCache(new Map(metadataCache)); + return; + } + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const html = await response.text(); + const doc = parser.parseFromString(html, 'text/html'); + + // Récupérer le titre + const pageTitle = + doc.querySelector('title')?.textContent || new URL(url).hostname; + + // Récupérer le favicon + let faviconUrl = + doc.querySelector('link[rel="icon"]')?.getAttribute('href') || + doc.querySelector('link[rel="shortcut icon"]')?.getAttribute('href'); + + if (!faviconUrl) { + const urlObj = new URL(url); + faviconUrl = `${urlObj.origin}/favicon.ico`; + } + + // Convertir les URLs relatives en absolues + if (faviconUrl && !faviconUrl.startsWith('http')) { + const urlObj = new URL(url); + faviconUrl = new URL(faviconUrl, urlObj.origin).href; + } + + metadataCache.set(url, { + title: pageTitle, + favicon: faviconUrl || null, + loading: false, + error: false, + }); + setCache(new Map(metadataCache)); + } catch (err) { + console.log('Error fetching metadata for:', url, err); + metadataCache.set(url, { + title: new URL(url).hostname, + favicon: null, + loading: false, + error: true, + }); + setCache(new Map(metadataCache)); + } + }; + + const getMetadata = (url: string): SourceMetadata | undefined => { + return cache.get(url); + }; + + return { prefetchMetadata, getMetadata, cache }; +}; diff --git a/src/frontend/apps/conversations/src/features/chat/stores/useScrollStore.ts b/src/frontend/apps/conversations/src/features/chat/stores/useScrollStore.ts new file mode 100644 index 0000000..b0b4320 --- /dev/null +++ b/src/frontend/apps/conversations/src/features/chat/stores/useScrollStore.ts @@ -0,0 +1,11 @@ +import { create } from 'zustand'; + +interface ScrollState { + isAtTop: boolean; + setIsAtTop: (isAtTop: boolean) => void; +} + +export const useScrollStore = create((set) => ({ + isAtTop: true, + setIsAtTop: (isAtTop) => set({ isAtTop }), +})); diff --git a/src/frontend/apps/conversations/src/features/header/components/Header.tsx b/src/frontend/apps/conversations/src/features/header/components/Header.tsx index 6d41e31..074065d 100644 --- a/src/frontend/apps/conversations/src/features/header/components/Header.tsx +++ b/src/frontend/apps/conversations/src/features/header/components/Header.tsx @@ -44,7 +44,7 @@ export const Header = () => { height: ${isDesktop ? HEADER_HEIGHT : '52'}px; padding: 0 ${spacingsTokens['base']}; background-color: ${colorsTokens['greyscale-000']}; - border-bottom: ${isDesktop && isAtTop + border-bottom: ${isAtTop ? `1px solid transparent` : `1px solid ${colorsTokens['greyscale-100']}`}; transition: border-bottom 0.2s ease; diff --git a/src/frontend/apps/conversations/src/features/left-panel/components/ConversationItemActions.tsx b/src/frontend/apps/conversations/src/features/left-panel/components/ConversationItemActions.tsx index b2d1c41..82fcc39 100644 --- a/src/frontend/apps/conversations/src/features/left-panel/components/ConversationItemActions.tsx +++ b/src/frontend/apps/conversations/src/features/left-panel/components/ConversationItemActions.tsx @@ -53,7 +53,7 @@ export const ConversationItemActions = ({ &:hover { background-color: #e1e3e7 !important; } - &:focus { + &:focus-visible { outline: 2px solid #3e5de7; outline-offset: 2px; } diff --git a/src/frontend/apps/conversations/src/features/left-panel/components/LeftPanelConversationItem.tsx b/src/frontend/apps/conversations/src/features/left-panel/components/LeftPanelConversationItem.tsx index 169cb5b..e39b2cd 100644 --- a/src/frontend/apps/conversations/src/features/left-panel/components/LeftPanelConversationItem.tsx +++ b/src/frontend/apps/conversations/src/features/left-panel/components/LeftPanelConversationItem.tsx @@ -34,6 +34,7 @@ export const LeftPanelConversationItem = ({ $css={css` border-radius: 4px; width: 100%; + margin-bottom: 1px; background-color: ${isCurrentConversation ? '#ebedf1' : ''}; font-weight: ${isCurrentConversation ? '700' : '500'}; transition: background-color 0.2s cubic-bezier(1, 0, 0, 1); diff --git a/src/frontend/apps/conversations/src/pages/globals.css b/src/frontend/apps/conversations/src/pages/globals.css index dad8c0e..6282bbf 100644 --- a/src/frontend/apps/conversations/src/pages/globals.css +++ b/src/frontend/apps/conversations/src/pages/globals.css @@ -131,16 +131,6 @@ nextjs-portal { background-color: inherit !important; } -.mainContent-chat a { - color: var(--c--theme--colors--primary-text) !important; -} - -.mainContent-chat a:hover { - color: var( - --c--components--button--primary--background--color-hover - ) !important; -} - ul a { color: var(--c--theme--colors--greyscale-700) !important; } @@ -159,6 +149,20 @@ ul a:hover { } } +.mainContent-chat code { + overflow-wrap: break-word; +} + +.mainContent-chat a { + color: var(--c--theme--colors--primary-text) !important; +} + +.mainContent-chat a:hover { + color: var( + --c--components--button--primary--background--color-hover + ) !important; +} + .lasuite-gaufre-btn.lasuite--gaufre-opened { box-shadow: none !important; } @@ -179,7 +183,6 @@ ul a:hover { } .research-web-button:focus { - outline: none !important; box-shadow: none !important; } @@ -232,7 +235,3 @@ figure[data-rehype-pretty-code-figure] > pre { figure[data-rehype-pretty-code-figure] > pre > code { display: block; } - -code { - word-wrap: break-word; -}