From 47c909dda4fff246d149e11d2e7cb2f69abbc29f Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Wed, 30 Jul 2025 12:00:49 +0800 Subject: [PATCH] feat(TabContainer, PinnedMinapps): enhance tab navigation and improve minapp switch indicator - Added a new handleTabClick function to streamline tab navigation and hide the minapp popup when a tab is clicked. - Implemented an animation for the minapp switch indicator in the TopNavbarOpenedMinappTabs component, improving visual feedback during tab switching. - Refactored the rendering of minapp items to use Tooltip for better accessibility and user experience. - Removed unnecessary StyledLink components to simplify the structure of the navigation items. --- .../src/components/Tab/TabContainer.tsx | 15 +- .../src/components/app/PinnedMinapps.tsx | 141 ++++++++---------- 2 files changed, 69 insertions(+), 87 deletions(-) diff --git a/src/renderer/src/components/Tab/TabContainer.tsx b/src/renderer/src/components/Tab/TabContainer.tsx index 37c6b74cfb..35c80fc5e6 100644 --- a/src/renderer/src/components/Tab/TabContainer.tsx +++ b/src/renderer/src/components/Tab/TabContainer.tsx @@ -127,6 +127,11 @@ const TabsContainer: React.FC = ({ children }) => { navigate(lastSettingsPath) } + const handleTabClick = (tab: Tab) => { + hideMinappPopup() + navigate(tab.path) + } + return ( @@ -134,15 +139,7 @@ const TabsContainer: React.FC = ({ children }) => { .filter((tab) => !specialTabs.includes(tab.id)) .map((tab) => { return ( - { - hideMinappPopup() - // 我不确定这个还需不需要,从Siderbar那边复制过来的 - // await modelGenerating() - navigate(tab.path) - }}> + handleTabClick(tab)}> {tab.id && {getTabIcon(tab.id)}} {getTitleLabel(tab.id)} diff --git a/src/renderer/src/components/app/PinnedMinapps.tsx b/src/renderer/src/components/app/PinnedMinapps.tsx index 8102bd3772..213a1e163c 100644 --- a/src/renderer/src/components/app/PinnedMinapps.tsx +++ b/src/renderer/src/components/app/PinnedMinapps.tsx @@ -27,6 +27,27 @@ export const TopNavbarOpenedMinappTabs: FC = () => { return () => clearTimeout(timer) }, [openedKeepAliveMinapps]) + // animation for minapp switch indicator + useEffect(() => { + const iconDefaultWidth = 30 // 22px icon + 8px gap + const iconDefaultOffset = 10 // initial offset + const container = document.querySelector('.TopNavContainer') as HTMLElement + const activeIcon = document.querySelector('.TopNavContainer .opened-active') as HTMLElement + + let indicatorLeft = 0, + indicatorBottom = 0 + if (minappShow && activeIcon && container) { + indicatorLeft = activeIcon.offsetLeft + activeIcon.offsetWidth / 2 - 4 // 4 is half of the indicator's width (8px) + indicatorBottom = 0 + } else { + indicatorLeft = + ((keepAliveMinapps.length > 0 ? keepAliveMinapps.length : 1) / 2) * iconDefaultWidth + iconDefaultOffset - 4 + indicatorBottom = -50 + } + container?.style.setProperty('--indicator-left', `${indicatorLeft}px`) + container?.style.setProperty('--indicator-bottom', `${indicatorBottom}px`) + }, [currentMinappId, keepAliveMinapps, minappShow]) + const handleOnClick = (app: MinAppType) => { if (minappShow && currentMinappId === app.id) { hideMinappPopup() @@ -43,6 +64,7 @@ export const TopNavbarOpenedMinappTabs: FC = () => { return ( 0 ? 'var(--color-list-item)' : 'transparent' }}> {keepAliveMinapps.map((app) => { @@ -62,22 +84,22 @@ export const TopNavbarOpenedMinappTabs: FC = () => { } } ] + const isActive = minappShow && currentMinappId === app.id return ( - + handleOnClick(app)} - theme={theme}> + theme={theme} + className={`${isActive ? 'opened-active' : ''}`}> - {app.name} - + ) })} @@ -158,16 +180,14 @@ export const SidebarOpenedMinappTabs: FC = () => { return ( - - - handleOnClick(app)} - className={`${isActive ? 'opened-active' : ''}`}> - - - - + + handleOnClick(app)} + className={`${isActive ? 'opened-active' : ''}`}> + + + ) })} @@ -201,16 +221,14 @@ export const SidebarPinnedApps: FC = () => { const isActive = minappShow && currentMinappId === app.id return ( - - - openMinappKeepAlive(app)} - className={`${isActive ? 'active' : ''} ${openedKeepAliveMinapps.some((item) => item.id === app.id) ? 'opened-minapp' : ''}`}> - - - - + + openMinappKeepAlive(app)} + className={`${isActive ? 'active' : ''} ${openedKeepAliveMinapps.some((item) => item.id === app.id) ? 'opened-minapp' : ''}`}> + + + ) }} @@ -239,16 +257,10 @@ const Icon = styled.div<{ theme: string }>` background-color: ${({ theme }) => (theme === 'dark' ? 'var(--color-black)' : 'var(--color-white)')}; opacity: 0.8; cursor: pointer; - .icon { - color: var(--color-icon-white); - } } &.active { background-color: ${({ theme }) => (theme === 'dark' ? 'var(--color-black)' : 'var(--color-white)')}; border: 0.5px solid var(--color-border); - .icon { - color: var(--color-primary); - } } @keyframes borderBreath { @@ -279,14 +291,6 @@ const Icon = styled.div<{ theme: string }>` } ` -const StyledLink = styled.div` - text-decoration: none; - -webkit-app-region: none; - &* { - user-select: none; - } -` - const Divider = styled.div` width: 50%; margin: 8px 0; @@ -330,64 +334,45 @@ const TopNavContainer = styled.div` display: flex; align-items: center; padding: 2px; - gap: 6px; + gap: 4px; background-color: var(--color-list-item); border-radius: 20px; margin: 0 5px; + position: relative; + overflow: hidden; + + &::after { + content: ''; + position: absolute; + left: var(--indicator-left, 0); + bottom: var(--indicator-bottom, 0); + width: 8px; + height: 4px; + background-color: var(--color-primary); + transition: + left 0.3s cubic-bezier(0.4, 0, 0.2, 1), + bottom 0.3s ease-in-out; + border-radius: 2px; + } ` const TopNavMenus = styled.div` display: flex; align-items: center; gap: 8px; - padding: 0 2px; height: 100%; ` const TopNavIcon = styled(Icon)` width: 22px; height: 22px; - - .icon { - width: 22px; - height: 22px; - } - - &.opened-active { - background-color: ${({ theme }) => (theme === 'dark' ? 'var(--color-black)' : 'var(--color-white)')}; - border: 0.5px solid var(--color-border); - border-radius: 25%; - .icon { - color: var(--color-primary); - } - } -` - -const TopNavLabel = styled.div` - display: flex; - align-items: center; - justify-content: center; - padding: 0 4px; ` const TopNavItemContainer = styled.div` display: flex; - padding: 4px 2px; transition: border 0.2s ease; border-radius: 18px; - /* 避免布局偏移 */ - border: 1px solid transparent; - - &:hover { - border-bottom: 2px solid var(--color-primary); - opacity: 0.8; - cursor: pointer; - } - - &.opened-active { - border: 1px solid var(--color-primary); - .icon { - color: var(--color-primary); - } - } + cursor: pointer; + border-radius: 50%; + padding: 2px; `