fix(ui): center Config Panel action bar and move ProviderNav to bottom

This commit is contained in:
LTbinglingfeng
2026-02-06 03:13:13 +08:00
parent adcf0b6582
commit f53d333198
6 changed files with 104 additions and 58 deletions

View File

@@ -241,6 +241,37 @@ export function MainLayout() {
};
}, []);
// 将主内容区的中心点写入 CSS 变量,供底部浮层(如配置面板操作栏)对齐到内容区而非整窗
useLayoutEffect(() => {
const updateContentCenter = () => {
const el = contentRef.current;
if (!el) return;
const rect = el.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
document.documentElement.style.setProperty('--content-center-x', `${centerX}px`);
};
updateContentCenter();
const resizeObserver =
typeof ResizeObserver !== 'undefined' && contentRef.current
? new ResizeObserver(updateContentCenter)
: null;
if (resizeObserver && contentRef.current) {
resizeObserver.observe(contentRef.current);
}
window.addEventListener('resize', updateContentCenter);
return () => {
if (resizeObserver) {
resizeObserver.disconnect();
}
window.removeEventListener('resize', updateContentCenter);
};
}, []);
// 5秒后自动收起品牌名称
useEffect(() => {
brandCollapseTimer.current = setTimeout(() => {

View File

@@ -2,25 +2,34 @@
.navContainer {
position: fixed;
right: 24px;
top: 50%;
transform: translateY(-50%);
left: var(--content-center-x, 50%);
bottom: calc(12px + env(safe-area-inset-bottom));
transform: translateX(-50%);
z-index: 50;
pointer-events: auto;
width: fit-content;
max-width: calc(100vw - 24px);
}
.navList {
position: relative;
display: flex;
flex-direction: column;
gap: 8px;
padding: 12px 8px;
display: inline-flex;
flex-direction: row;
gap: 6px;
padding: 10px 12px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 16px;
border-radius: 999px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
overflow-x: auto;
scrollbar-width: none;
max-width: inherit;
&::-webkit-scrollbar {
display: none;
}
}
.indicator {
@@ -29,7 +38,7 @@
left: 0;
pointer-events: none;
opacity: 0;
border-radius: 10px;
border-radius: 999px;
background: rgba(59, 130, 246, 0.15);
box-shadow: inset 0 0 0 2px var(--primary-color);
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1),
@@ -58,9 +67,10 @@
padding: 0;
border: none;
background: transparent;
border-radius: 10px;
border-radius: 999px;
cursor: pointer;
transition: background-color 0.2s ease, transform 0.15s ease;
flex: 0 0 auto;
&:hover {
background: rgba(0, 0, 0, 0.06);
@@ -80,8 +90,8 @@
}
.icon {
width: 28px;
height: 28px;
width: 24px;
height: 24px;
object-fit: contain;
}
@@ -110,42 +120,20 @@
}
}
// 小屏幕改为底部横向浮层
// 小屏幕进一步收紧尺寸
@media (max-width: 1200px) {
.navContainer {
top: auto;
right: auto;
left: 50%;
bottom: calc(12px + env(safe-area-inset-bottom));
transform: translateX(-50%);
width: fit-content;
max-width: calc(100vw - 24px);
max-width: calc(100vw - 16px);
}
.navList {
display: inline-flex;
flex-direction: row;
gap: 6px;
padding: 8px 10px;
border-radius: 999px;
overflow-x: auto;
scrollbar-width: none;
max-width: inherit;
&::-webkit-scrollbar {
display: none;
}
}
.indicator {
border-radius: 999px;
}
.navItem {
width: 36px;
height: 36px;
border-radius: 999px;
flex: 0 0 auto;
}
.icon {

View File

@@ -41,6 +41,7 @@ export function ProviderNav() {
const [activeProvider, setActiveProvider] = useState<ProviderId | null>(null);
const contentScrollerRef = useRef<HTMLElement | null>(null);
const navListRef = useRef<HTMLDivElement | null>(null);
const navContainerRef = useRef<HTMLDivElement | null>(null);
const itemRefs = useRef<Record<ProviderId, HTMLButtonElement | null>>({
gemini: null,
codex: null,
@@ -170,6 +171,31 @@ export function ProviderNav() {
updateIndicator(activeProvider);
}, [activeProvider, shouldShow, updateIndicator]);
// Expose overlay height to the page, so it can reserve bottom padding and avoid being covered.
useLayoutEffect(() => {
if (!shouldShow) return;
const el = navContainerRef.current;
if (!el) return;
const updateHeight = () => {
const height = el.getBoundingClientRect().height;
document.documentElement.style.setProperty('--provider-nav-height', `${height}px`);
};
updateHeight();
window.addEventListener('resize', updateHeight);
const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(updateHeight);
ro?.observe(el);
return () => {
ro?.disconnect();
window.removeEventListener('resize', updateHeight);
document.documentElement.style.removeProperty('--provider-nav-height');
};
}, [shouldShow]);
const scrollToProvider = (providerId: ProviderId) => {
const container = getScrollContainer();
const element = document.getElementById(`provider-${providerId}`);
@@ -204,7 +230,7 @@ export function ProviderNav() {
}, [activeProvider, shouldShow, updateIndicator]);
const navContent = (
<div className={styles.navContainer}>
<div className={styles.navContainer} ref={navContainerRef}>
<div className={styles.navList} ref={navListRef}>
<div
className={[