Add Chromebook compatibility fixes and optimizations

Implemented comprehensive Chromebook-specific fixes including viewport adjustments, enhanced touch and keyboard detection, improved scrolling and keyboard navigation hooks, and extensive CSS optimizations for better usability. Updated chat and dashboard interfaces for larger touch targets, better focus management, and responsive layouts. Added documentation in docs/CHROMEBOOK-FIXES.md and new hooks for Chromebook scroll and keyboard handling.
This commit is contained in:
NotII
2025-10-26 18:29:23 +00:00
parent 1fc29e6cbf
commit 130ecac208
27 changed files with 691 additions and 65 deletions

View File

@@ -15,6 +15,8 @@ import { getCookie, clientFetch } from "@/lib/api";
import { ImageViewerModal } from "@/components/modals/image-viewer-modal";
import BuyerOrderInfo from "./BuyerOrderInfo";
import { useIsTouchDevice } from "@/hooks/use-mobile";
import { useChromebookScroll, useSmoothScrollToBottom } from "@/hooks/use-chromebook-scroll";
import { useChromebookKeyboard, useChatFocus } from "@/hooks/use-chromebook-keyboard";
interface Message {
_id: string;
@@ -100,10 +102,18 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
const [selectedAttachmentIndex, setSelectedAttachmentIndex] = useState<number | null>(null);
const seenMessageIdsRef = useRef<Set<string>>(new Set());
const isTouchDevice = useIsTouchDevice();
const scrollContainerRef = useChromebookScroll();
const { scrollToBottom, scrollToBottomInstant } = useSmoothScrollToBottom();
useChromebookKeyboard();
const { focusMessageInput, focusNextMessage, focusPreviousMessage } = useChatFocus();
// Scroll to bottom utility functions
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
const scrollToBottomHandler = () => {
if (scrollContainerRef.current) {
scrollToBottom(scrollContainerRef.current);
} else {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
}
};
const isNearBottom = () => {
@@ -262,7 +272,7 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
// Scroll to bottom on initial load
setTimeout(() => {
scrollToBottom();
scrollToBottomHandler();
}, 100);
} catch (error) {
console.error("Error fetching chat data:", error);
@@ -363,13 +373,33 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
} else if (e.key === 'Escape') {
// Clear the input on Escape
setMessage('');
focusMessageInput();
} else if (e.key === 'ArrowUp' && message === '') {
// Load previous message on Arrow Up when input is empty
e.preventDefault();
const lastVendorMessage = [...messages].reverse().find(msg => msg.sender === 'vendor');
if (lastVendorMessage) {
setMessage(lastVendorMessage.content);
} else {
focusPreviousMessage();
}
} else if (e.key === 'ArrowDown' && message === '') {
// Focus next message
e.preventDefault();
focusNextMessage();
} else if (e.key === 'Tab') {
// Enhanced tab navigation for Chromebooks
e.preventDefault();
const focusableElements = document.querySelectorAll(
'button:not([disabled]), input:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
) as NodeListOf<HTMLElement>;
const currentIndex = Array.from(focusableElements).indexOf(document.activeElement as HTMLElement);
const nextIndex = e.shiftKey
? (currentIndex > 0 ? currentIndex - 1 : focusableElements.length - 1)
: (currentIndex < focusableElements.length - 1 ? currentIndex + 1 : 0);
focusableElements[nextIndex]?.focus();
}
};
@@ -601,11 +631,19 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
</div>
<div
className="flex-1 overflow-y-auto p-2 space-y-2 pb-[80px]"
ref={scrollContainerRef}
className={cn(
"flex-1 overflow-y-auto space-y-2 pb-[80px]",
isTouchDevice ? "p-3" : "p-2"
)}
role="log"
aria-label="Chat messages"
aria-live="polite"
aria-atomic="false"
style={{
WebkitOverflowScrolling: 'touch',
overscrollBehavior: 'contain'
}}
>
{chat.messages.length === 0 ? (
<div className="h-full flex items-center justify-center">
@@ -624,7 +662,8 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
>
<div
className={cn(
"max-w-[90%] rounded-lg p-3",
"max-w-[90%] rounded-lg chat-message",
isTouchDevice ? "p-4" : "p-3",
msg.sender === "vendor"
? "bg-primary text-primary-foreground"
: "bg-muted"
@@ -738,8 +777,8 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
placeholder="Type your message..."
disabled={sending}
className={cn(
"flex-1 text-base transition-all duration-200",
isTouchDevice ? "min-h-[48px] text-lg" : "min-h-[44px]"
"flex-1 text-base transition-all duration-200 form-input",
isTouchDevice ? "min-h-[52px] text-lg" : "min-h-[48px]"
)}
onKeyDown={handleKeyDown}
autoFocus
@@ -749,15 +788,23 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
autoComplete="off"
spellCheck="true"
maxLength={2000}
style={{
WebkitAppearance: 'none',
borderRadius: '0.5rem'
}}
/>
<Button
type="submit"
disabled={sending || !message.trim()}
aria-label={sending ? "Sending message" : "Send message"}
className={cn(
"transition-all duration-200",
isTouchDevice ? "min-h-[48px] min-w-[48px]" : "min-h-[44px] min-w-[44px]"
"transition-all duration-200 btn-chromebook",
isTouchDevice ? "min-h-[52px] min-w-[52px]" : "min-h-[48px] min-w-[48px]"
)}
style={{
WebkitAppearance: 'none',
touchAction: 'manipulation'
}}
>
{sending ? <RefreshCw className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
</Button>