This is an automated email from the ASF dual-hosted git repository.
shuai pushed a commit to branch ui-optimization
in repository https://gitbox.apache.org/repos/asf/answer.git
The following commit(s) were added to refs/heads/ui-optimization by this push:
new 1131b50d fix: Navigation style optimization
1131b50d is described below
commit 1131b50d0e1fe1813b932cbe1b6597e35302f1da
Author: shuai <[email protected]>
AuthorDate: Thu Apr 10 15:47:16 2025 +0800
fix: Navigation style optimization
---
i18n/en_US.yaml | 2 +-
i18n/zh_CN.yaml | 2 +-
.../Header/components/NavItems/index.tsx | 8 +-
.../Header/components/SearchInput/index.tsx | 59 +++++
ui/src/components/Header/index.scss | 28 +-
ui/src/components/Header/index.tsx | 284 ++++++++-------------
ui/src/components/MobileSideNav/index.scss | 4 +-
7 files changed, 198 insertions(+), 189 deletions(-)
diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml
index 12f46a33..484d6d29 100644
--- a/i18n/en_US.yaml
+++ b/i18n/en_US.yaml
@@ -1474,7 +1474,7 @@ ui:
signup: Sign up
logout: Log out
verify: Verify
- add_question: Add question
+ create: Create
approve: Approve
reject: Reject
skip: Skip
diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml
index 20c0a24c..29b78d0c 100644
--- a/i18n/zh_CN.yaml
+++ b/i18n/zh_CN.yaml
@@ -1441,7 +1441,7 @@ ui:
signup: 注册
logout: 退出
verify: 验证
- add_question: 我要提问
+ create: 创建
approve: 批准
reject: 拒绝
skip: 跳过
diff --git a/ui/src/components/Header/components/NavItems/index.tsx
b/ui/src/components/Header/components/NavItems/index.tsx
index badd1b79..3dbed4e4 100644
--- a/ui/src/components/Header/components/NavItems/index.tsx
+++ b/ui/src/components/Header/components/NavItems/index.tsx
@@ -54,7 +54,7 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
<NavLink
to="/users/notifications/inbox"
title={t('inbox', { keyPrefix: 'notifications' })}
- className="icon-link nav-link d-flex align-items-center
justify-content-center p-0 me-3 position-relative">
+ className="icon-link nav-link d-flex align-items-center
justify-content-center p-0 me-2 position-relative">
<Icon name="bell-fill" className="fs-4" />
{(redDot?.inbox || 0) > 0 && (
<div className="unread-dot bg-danger">
@@ -68,7 +68,7 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
<NavLink
to="/users/notifications/achievement"
title={t('achievement', { keyPrefix: 'notifications' })}
- className="icon-link nav-link d-flex align-items-center
justify-content-center p-0 me-3 position-relative">
+ className="icon-link nav-link d-flex align-items-center
justify-content-center p-0 me-2 position-relative">
<Icon name="trophy-fill" className="fs-4" />
{(redDot?.achievement || 0) > 0 && (
<div className="unread-dot bg-danger">
@@ -95,7 +95,7 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
/>
</Dropdown.Toggle>
- <Dropdown.Menu>
+ <Dropdown.Menu className="position-absolute">
<Dropdown.Item
href={`${REACT_BASE_PATH}/users/${userInfo.username}`}
onClick={handleLinkClick}>
@@ -137,7 +137,7 @@ const Index: FC<Props> = ({ redDot, userInfo, logOut }) => {
</Nav>
</Dropdown.Toggle>
- <Dropdown.Menu>
+ <Dropdown.Menu className="position-absolute">
{ucAgent.agent_info.url ? (
<Dropdown.Item href={ucAgent.agent_info.url}>
{ucAgent.agent_info.name}
diff --git a/ui/src/components/Header/components/SearchInput/index.tsx
b/ui/src/components/Header/components/SearchInput/index.tsx
new file mode 100644
index 00000000..11dacff2
--- /dev/null
+++ b/ui/src/components/Header/components/SearchInput/index.tsx
@@ -0,0 +1,59 @@
+import { FC, useState, useEffect } from 'react';
+import { Form, FormControl } from 'react-bootstrap';
+import { useSearchParams, useNavigate, useLocation } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+
+import { Icon } from '@/components';
+
+const SearchInput: FC<{ className?: string }> = ({ className }) => {
+ const { t } = useTranslation('translation', { keyPrefix: 'header' });
+ const navigate = useNavigate();
+ const location = useLocation();
+ const [urlSearch] = useSearchParams();
+ const q = urlSearch.get('q');
+ const [searchStr, setSearch] = useState('');
+ const handleInput = (val) => {
+ setSearch(val);
+ };
+ const handleSearch = (evt) => {
+ evt.preventDefault();
+ if (!searchStr) {
+ return;
+ }
+ const searchUrl = `/search?q=${encodeURIComponent(searchStr)}`;
+ navigate(searchUrl);
+ };
+
+ useEffect(() => {
+ if (q && location.pathname === '/search') {
+ handleInput(q);
+ }
+ }, [q]);
+
+ useEffect(() => {
+ // clear search input when navigate to other page
+ if (location.pathname !== '/search' && searchStr) {
+ setSearch('');
+ }
+ }, [location.pathname]);
+ return (
+ <Form
+ action="/search"
+ className={`w-100 position-relative mx-auto ${className}`}
+ onSubmit={handleSearch}>
+ <div className="search-wrap" onClick={handleSearch}>
+ <Icon name="search" className="search-icon" />
+ </div>
+ <FormControl
+ type="search"
+ placeholder={t('search.placeholder')}
+ className="placeholder-search"
+ value={searchStr}
+ name="q"
+ onChange={(e) => handleInput(e.target.value)}
+ />
+ </Form>
+ );
+};
+
+export default SearchInput;
diff --git a/ui/src/components/Header/index.scss
b/ui/src/components/Header/index.scss
index b6943eb5..9f2dd799 100644
--- a/ui/src/components/Header/index.scss
+++ b/ui/src/components/Header/index.scss
@@ -21,7 +21,6 @@
@import 'bootstrap/scss/variables';
#header {
z-index: 1041;
- transform: translate3d(0, 0, 0);
--bs-navbar-padding-y: 0.75rem;
background: var(--bs-primary);
box-shadow:
@@ -31,13 +30,30 @@
max-height: 2rem;
}
+ .create-icon {
+ color: var(--bs-nav-link-color);
+ }
+
.nav-link {
&.icon-link {
- width: 36px;
- height: 36px;
+ width: 38px;
+ height: 38px;
}
}
+ .nav-text {
+ flex: 1;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ padding: 0 10px;
+ min-width: 0;
+ }
+
+ .search-mobile {
+ color: var(--bs-nav-link-color);
+ }
+
.answer-navBar {
font-size: 1rem;
padding: 0.25rem 0.5rem;
@@ -99,15 +115,15 @@
}
}
- .maxw-400 {
- max-width: 400px;
+ .maxw-560 {
+ max-width: 560px;
}
.search-wrap {
position: absolute;
top: 0;
left: 0;
- padding: 11px 13px;
+ padding: 10px 13px;
line-height: 1;
}
}
diff --git a/ui/src/components/Header/index.tsx
b/ui/src/components/Header/index.tsx
index a1928b36..58144808 100644
--- a/ui/src/components/Header/index.tsx
+++ b/ui/src/components/Header/index.tsx
@@ -18,15 +18,9 @@
*/
import { FC, memo, useState, useEffect } from 'react';
-import { Navbar, Nav, Form, FormControl, Col } from 'react-bootstrap';
+import { Navbar, Nav, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
-import {
- useSearchParams,
- Link,
- useNavigate,
- useLocation,
- useMatch,
-} from 'react-router-dom';
+import { Link, NavLink, useLocation, useMatch } from 'react-router-dom';
import classnames from 'classnames';
@@ -43,23 +37,22 @@ import { logout, useQueryNotificationStatus } from
'@/services';
import { Icon, MobileSideNav } from '@/components';
import NavItems from './components/NavItems';
+import SearchInput from './components/SearchInput';
import './index.scss';
const Header: FC = () => {
- const navigate = useNavigate();
const location = useLocation();
- const [urlSearch] = useSearchParams();
- const q = urlSearch.get('q');
const { user, clear: clearUserStore } = loggedUserInfoStore();
const { t } = useTranslation();
- const [searchStr, setSearch] = useState('');
const siteInfo = siteInfoStore((state) => state.siteInfo);
const brandingInfo = brandingStore((state) => state.branding);
const loginSetting = loginSettingStore((state) => state.login);
const { updateReview } = sideNavStore();
const { data: redDot } = useQueryNotificationStatus();
const [showMobileSideNav, setShowMobileSideNav] = useState(false);
+
+ const [showMobileSearchInput, setShowMobileSearchInput] = useState(false);
/**
* Automatically append `tag` information when creating a question
*/
@@ -76,18 +69,6 @@ const Header: FC = () => {
});
}, [redDot]);
- const handleInput = (val) => {
- setSearch(val);
- };
- const handleSearch = (evt) => {
- evt.preventDefault();
- if (!searchStr) {
- return;
- }
- const searchUrl = `/search?q=${encodeURIComponent(searchStr)}`;
- navigate(searchUrl);
- };
-
const handleLogout = async (evt) => {
evt.preventDefault();
await logout();
@@ -96,16 +77,7 @@ const Header: FC = () => {
};
useEffect(() => {
- if (q && location.pathname === '/search') {
- handleInput(q);
- }
- }, [q]);
-
- useEffect(() => {
- // clear search input when navigate to other page
- if (location.pathname !== '/search' && searchStr) {
- setSearch('');
- }
+ setShowMobileSearchInput(false);
setShowMobileSideNav(false);
}, [location.pathname]);
@@ -119,6 +91,7 @@ const Header: FC = () => {
const handleResize = () => {
if (window.innerWidth >= 1199.9) {
setShowMobileSideNav(false);
+ setShowMobileSearchInput(false);
}
};
@@ -129,151 +102,112 @@ const Header: FC = () => {
}, []);
return (
- <>
- <Navbar
- variant={navbarStyle === 'theme-colored' ? 'dark' : ''}
- expand="xl"
- className={classnames('sticky-top', navbarStyle)}
- id="header">
- <div className="w-100 d-flex align-items-center px-3">
- <Navbar.Toggle
- className="answer-navBar me-2"
+ <Navbar
+ variant={navbarStyle === 'theme-colored' ? 'dark' : ''}
+ expand="xl"
+ className={classnames('sticky-top', navbarStyle)}
+ id="header">
+ <div className="w-100 d-flex align-items-center px-3">
+ <Navbar.Toggle
+ className="answer-navBar me-2"
+ onClick={() => {
+ setShowMobileSideNav(!showMobileSideNav);
+ setShowMobileSearchInput(false);
+ }}
+ />
+
+ <Navbar.Brand
+ to="/"
+ as={Link}
+ className="lh-1 me-0 me-sm-5 p-0 nav-text">
+ {brandingInfo.logo ? (
+ <>
+ <img
+ className="d-none d-xl-block logo me-0"
+ src={brandingInfo.logo}
+ alt={siteInfo.name}
+ />
+
+ <img
+ className="xl-none logo me-0"
+ src={brandingInfo.mobile_logo || brandingInfo.logo}
+ alt={siteInfo.name}
+ />
+ </>
+ ) : (
+ <span>{siteInfo.name}</span>
+ )}
+ </Navbar.Brand>
+
+ <SearchInput className="d-none d-lg-block maxw-560" />
+
+ <Nav className="d-block d-lg-none me-2">
+ <Button
+ variant="link"
onClick={() => {
- setShowMobileSideNav(!showMobileSideNav);
+ setShowMobileSideNav(false);
+ setShowMobileSearchInput(!showMobileSearchInput);
}}
- />
-
- <div className="d-flex justify-content-between align-items-center
nav-grow flex-nowrap">
- <Navbar.Brand to="/" as={Link} className="lh-1 me-0 me-sm-5 p-0">
- {brandingInfo.logo ? (
- <>
- <img
- className="d-none d-xl-block logo me-0"
- src={brandingInfo.logo}
- alt={siteInfo.name}
- />
-
- <img
- className="xl-none logo me-0"
- src={brandingInfo.mobile_logo || brandingInfo.logo}
- alt={siteInfo.name}
- />
- </>
- ) : (
- <span>{siteInfo.name}</span>
- )}
- </Navbar.Brand>
-
- {/* mobile nav */}
- <div className="d-flex xl-none align-items-center flex-lg-nowrap">
- {user?.username ? (
- <NavItems
- redDot={redDot}
- userInfo={user}
- logOut={(e) => handleLogout(e)}
- />
- ) : (
- <>
- <Link
- className={classnames('me-2 btn btn-link', {
- 'link-light': navbarStyle === 'theme-colored',
- 'link-primary': navbarStyle !== 'theme-colored',
- })}
- onClick={() => floppyNavigation.storageLoginRedirect()}
- to={userCenter.getLoginUrl()}>
- {t('btns.login')}
- </Link>
- {loginSetting.allow_new_registrations && (
- <Link
- className={classnames(
- 'btn',
- navbarStyle === 'theme-colored'
- ? 'btn-light'
- : 'btn-primary',
- )}
- to={userCenter.getSignUpUrl()}>
- {t('btns.signup')}
- </Link>
- )}
- </>
- )}
- </div>
- </div>
-
- <div className="d-none d-xl-flex flex-grow-1 me-auto">
- <Col lg={8} className="d-none d-xl-block ps-0">
- <Form
- action="/search"
- className="w-100 maxw-400 position-relative"
- onSubmit={handleSearch}>
- <div className="search-wrap" onClick={handleSearch}>
- <Icon name="search" className="search-icon" />
- </div>
- <FormControl
- type="search"
- placeholder="sddfsdf"
- className="placeholder-search"
- value={searchStr}
- name="q"
- onChange={(e) => handleInput(e.target.value)}
- />
- </Form>
- </Col>
-
- {/* pc nav */}
- <Col
- lg={4}
- className="d-none d-xl-flex justify-content-start
justify-content-sm-end">
- {user?.username ? (
- <Nav className="d-flex align-items-center flex-lg-nowrap">
- <Nav.Item className="me-3">
- <Link
- to={askUrl}
- className={classnames('text-capitalize text-nowrap btn',
{
- 'btn-light': navbarStyle !== 'theme-light',
- 'btn-primary': navbarStyle === 'theme-light',
- })}>
- {t('btns.add_question')}
- </Link>
- </Nav.Item>
-
- <NavItems
- redDot={redDot}
- userInfo={user}
- logOut={handleLogout}
- />
- </Nav>
- ) : (
- <>
- <Link
- className={classnames('me-2 btn btn-link', {
- 'link-light': navbarStyle === 'theme-colored',
- 'link-primary': navbarStyle !== 'theme-colored',
- })}
- onClick={() => floppyNavigation.storageLoginRedirect()}
- to={userCenter.getLoginUrl()}>
- {t('btns.login')}
- </Link>
- {loginSetting.allow_new_registrations && (
- <Link
- className={classnames(
- 'btn',
- navbarStyle === 'theme-colored'
- ? 'btn-light'
- : 'btn-primary',
- )}
- to={userCenter.getSignUpUrl()}>
- {t('btns.signup')}
- </Link>
- )}
- </>
- )}
- </Col>
- </div>
+ className="p-0 btn-no-border icon-link nav-link d-flex
align-items-center justify-content-center">
+ <Icon name="search" className="lh-1 fs-4" />
+ </Button>
+ </Nav>
+
+ {/* pc nav */}
+ {user?.username ? (
+ <Nav className="d-flex align-items-center flex-nowrap flex-row
ms-auto">
+ <Nav.Item className="me-2 d-block d-xl-none">
+ <NavLink
+ to={askUrl}
+ className="d-block icon-link nav-link text-center">
+ <Icon name="plus-lg" className="lh-1 fs-4" />
+ </NavLink>
+ </Nav.Item>
+
+ <Nav.Item className="me-2 d-none d-xl-block">
+ <NavLink
+ to={askUrl}
+ className="nav-link d-flex align-items-center text-capitalize
text-nowrap">
+ <Icon name="plus-lg" className="me-2 lh-1 fs-4" />
+ <span>{t('btns.create')}</span>
+ </NavLink>
+ </Nav.Item>
+
+ <NavItems redDot={redDot} userInfo={user} logOut={handleLogout} />
+ </Nav>
+ ) : (
+ <>
+ <Link
+ className={classnames('me-2 btn btn-link', {
+ 'link-light': navbarStyle === 'theme-colored',
+ 'link-primary': navbarStyle !== 'theme-colored',
+ })}
+ onClick={() => floppyNavigation.storageLoginRedirect()}
+ to={userCenter.getLoginUrl()}>
+ {t('btns.login')}
+ </Link>
+ {loginSetting.allow_new_registrations && (
+ <Link
+ className={classnames(
+ 'btn',
+ navbarStyle === 'theme-colored' ? 'btn-light' :
'btn-primary',
+ )}
+ to={userCenter.getSignUpUrl()}>
+ {t('btns.signup')}
+ </Link>
+ )}
+ </>
+ )}
+ </div>
+
+ {showMobileSearchInput && (
+ <div className="w-100 px-3 mt-2 d-block d-lg-none">
+ <SearchInput />
</div>
- </Navbar>
+ )}
+
<MobileSideNav show={showMobileSideNav} onHide={setShowMobileSideNav} />
- </>
+ </Navbar>
);
};
diff --git a/ui/src/components/MobileSideNav/index.scss
b/ui/src/components/MobileSideNav/index.scss
index fbb7c945..983e6825 100644
--- a/ui/src/components/MobileSideNav/index.scss
+++ b/ui/src/components/MobileSideNav/index.scss
@@ -1,7 +1,7 @@
#mobileSideNav {
- top: 60px !important;
+ top: 62px !important;
width: 240px !important;
- height: calc(100vh - 60px) !important;
+ height: calc(100vh - 62px) !important;
overflow-y: auto;
flex: none;
}