feat: 优化列表滚动和输入框交互体验
- 纪念日/提醒列表添加动态底部填充,避免被 AI 输入框遮挡 - AI 输入框在弹窗和右键菜单打开时自动隐藏 - 优化输入框样式,提升在浅色背景上的可见度 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5f1c6208df
commit
864051a65b
@ -27,9 +27,10 @@ import { notifications } from '@mantine/notifications';
|
||||
|
||||
interface FloatingAIChatProps {
|
||||
onEventCreated?: () => void;
|
||||
hidden?: boolean;
|
||||
}
|
||||
|
||||
export function FloatingAIChat({ onEventCreated }: FloatingAIChatProps) {
|
||||
export function FloatingAIChat({ onEventCreated, hidden = false }: FloatingAIChatProps) {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [message, setMessage] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
@ -212,6 +213,11 @@ export function FloatingAIChat({ onEventCreated }: FloatingAIChatProps) {
|
||||
return editForm.type === 'anniversary' ? '纪念日' : '提醒';
|
||||
};
|
||||
|
||||
// 当 hidden 为 true 时,不渲染组件
|
||||
if (hidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 遮罩层 */}
|
||||
@ -246,13 +252,13 @@ export function FloatingAIChat({ onEventCreated }: FloatingAIChatProps) {
|
||||
<Paper
|
||||
style={{
|
||||
width: '100%',
|
||||
background: isFocused ? 'var(--mantine-color-body)' : 'rgba(255, 255, 255, 0.4)',
|
||||
backdropFilter: isFocused ? 'none' : 'blur(8px)',
|
||||
background: isFocused ? 'var(--mantine-color-body)' : 'rgba(255, 255, 255, 0.75)',
|
||||
backdropFilter: isFocused ? 'none' : 'blur(12px)',
|
||||
borderRadius: 16,
|
||||
boxShadow: isFocused
|
||||
? '0 8px 32px rgba(0, 0, 0, 0.12)'
|
||||
: '0 2px 12px rgba(0, 0, 0, 0.06)',
|
||||
border: '1px solid var(--mantine-color-gray-2)',
|
||||
: '0 2px 12px rgba(0, 0, 0, 0.08)',
|
||||
border: '1px solid rgba(0, 0, 0, 0.08)',
|
||||
transition: 'all 0.3s ease',
|
||||
overflow: 'visible',
|
||||
}}
|
||||
@ -523,8 +529,9 @@ export function FloatingAIChat({ onEventCreated }: FloatingAIChatProps) {
|
||||
styles={{
|
||||
input: {
|
||||
borderRadius: 12,
|
||||
borderColor: 'var(--mantine-color-gray-2)',
|
||||
background: isFocused ? 'rgba(255, 255, 255, 0.95)' : 'rgba(255, 255, 255, 0.6)',
|
||||
borderColor: 'var(--mantine-color-gray-3)',
|
||||
background: isFocused ? 'rgba(255, 255, 255, 0.95)' : 'rgba(0, 0, 0, 0.06)',
|
||||
color: '#1a1a1a',
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
transition: 'all 0.3s ease',
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useMemo, useRef, useState, useEffect } from 'react';
|
||||
import { Stack, Text, Paper, Group, Button, Box } from '@mantine/core';
|
||||
import { IconPlus } from '@tabler/icons-react';
|
||||
import { AnniversaryCard } from './AnniversaryCard';
|
||||
@ -29,6 +29,35 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
||||
const anniversaries = events.filter((e) => e.type === 'anniversary');
|
||||
const showHolidays = useAppStore((state) => state.settings?.showHolidays ?? true);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [bottomPadding, setBottomPadding] = useState(0);
|
||||
|
||||
// 检测列表是否可滚动,如果是则添加底部填充避免被 AI 输入框遮挡
|
||||
useEffect(() => {
|
||||
const updatePadding = () => {
|
||||
const container = scrollContainerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
// 判断是否可滚动(内容高度 > 容器高度)
|
||||
const isScrollable = container.scrollHeight > container.clientHeight;
|
||||
// 底部填充高度:AI 输入框高度(约50px) + 底部间距(48px) + 额外缓冲(20px)
|
||||
setBottomPadding(isScrollable ? 120 : 0);
|
||||
};
|
||||
|
||||
// 延迟执行确保 DOM 完全渲染
|
||||
const timer = setTimeout(updatePadding, 100);
|
||||
|
||||
// 监听容器和内容变化
|
||||
const container = scrollContainerRef.current;
|
||||
if (container) {
|
||||
const observer = new ResizeObserver(updatePadding);
|
||||
observer.observe(container);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}
|
||||
return () => clearTimeout(timer);
|
||||
}, [events]);
|
||||
|
||||
// 滚动条样式 - 仅在悬停时显示
|
||||
const scrollbarStyle = `
|
||||
@ -179,7 +208,7 @@ export function AnniversaryList({ events, onEventClick, onAddClick }: Anniversar
|
||||
ref={scrollContainerRef}
|
||||
onWheel={handleWheel}
|
||||
className="anniversary-scroll"
|
||||
style={{ flex: 1, overflowY: 'auto', minHeight: 0 }}
|
||||
style={{ flex: 1, overflowY: 'auto', minHeight: 0, paddingBottom: bottomPadding }}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
{/* 内置节假日 */}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useMemo, useRef, useState, useEffect } from 'react';
|
||||
import {
|
||||
Stack,
|
||||
Text,
|
||||
@ -43,6 +43,35 @@ export function ReminderList({
|
||||
}: ReminderListProps) {
|
||||
const navigate = useNavigate();
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [bottomPadding, setBottomPadding] = useState(0);
|
||||
|
||||
// 检测列表是否可滚动,如果是则添加底部填充避免被 AI 输入框遮挡
|
||||
useEffect(() => {
|
||||
const updatePadding = () => {
|
||||
const container = scrollContainerRef.current;
|
||||
if (!container) return;
|
||||
|
||||
// 判断是否可滚动(内容高度 > 容器高度)
|
||||
const isScrollable = container.scrollHeight > container.clientHeight;
|
||||
// 底部填充高度:AI 输入框高度(约50px) + 底部间距(48px) + 额外缓冲(20px)
|
||||
setBottomPadding(isScrollable ? 120 : 0);
|
||||
};
|
||||
|
||||
// 延迟执行确保 DOM 完全渲染
|
||||
const timer = setTimeout(updatePadding, 100);
|
||||
|
||||
// 监听容器和内容变化
|
||||
const container = scrollContainerRef.current;
|
||||
if (container) {
|
||||
const observer = new ResizeObserver(updatePadding);
|
||||
observer.observe(container);
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
clearTimeout(timer);
|
||||
};
|
||||
}
|
||||
return () => clearTimeout(timer);
|
||||
}, [events]);
|
||||
|
||||
// 分类折叠状态
|
||||
const [collapsed, setCollapsed] = useState({
|
||||
@ -278,6 +307,7 @@ export function ReminderList({
|
||||
flex: 1,
|
||||
overflowY: 'auto',
|
||||
minHeight: 0,
|
||||
paddingBottom: bottomPadding,
|
||||
}}
|
||||
>
|
||||
<Stack gap="xs">
|
||||
|
||||
@ -22,6 +22,7 @@ import { FixedCalendar } from '../components/common/FixedCalendar';
|
||||
import { TimePicker } from '../components/common/TimePicker';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAppStore } from '../stores';
|
||||
import { useContextMenuStore } from '../stores/contextMenu';
|
||||
import { AnniversaryList } from '../components/anniversary/AnniversaryList';
|
||||
import { ReminderList } from '../components/reminder/ReminderList';
|
||||
import { NoteEditor } from '../components/note/NoteEditor';
|
||||
@ -34,6 +35,7 @@ export function HomePage() {
|
||||
const navigate = useNavigate();
|
||||
const user = useAppStore((state) => state.user);
|
||||
const logout = useAppStore((state) => state.logout);
|
||||
const openEventId = useContextMenuStore((state) => state.openEventId);
|
||||
const checkAuth = useAppStore((state) => state.checkAuth);
|
||||
const events = useAppStore((state) => state.events);
|
||||
const fetchEvents = useAppStore((state) => state.fetchEvents);
|
||||
@ -391,8 +393,6 @@ export function HomePage() {
|
||||
onDateChange={handleDateChange}
|
||||
onPriorityChange={handlePriorityChange}
|
||||
/>
|
||||
{/* 底部空白区域 - 避免被 AI 输入框遮挡 */}
|
||||
<Box style={{ height: 120 }} />
|
||||
</div>
|
||||
|
||||
{/* Right column - Note */}
|
||||
@ -402,7 +402,7 @@ export function HomePage() {
|
||||
</div>
|
||||
|
||||
{/* AI Chat - Floating */}
|
||||
<FloatingAIChat onEventCreated={handleAIEventCreated} />
|
||||
<FloatingAIChat onEventCreated={handleAIEventCreated} hidden={opened || !!openEventId} />
|
||||
|
||||
{/* Add/Edit Event Modal */}
|
||||
<Modal
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user