This is an automated email from the ASF dual-hosted git repository. shuai pushed a commit to branch feat/1.4.3/ui in repository https://gitbox.apache.org/repos/asf/incubator-answer.git
commit 58f2191d51ad41d14be69e2fc5b7fe1b7f38a3b7 Author: shuai <[email protected]> AuthorDate: Mon Dec 16 11:00:34 2024 +0800 fix: Top list UI optimization --- ui/src/common/color.scss | 2 +- ui/src/components/PinList/index.tsx | 60 +++++++++++ ui/src/components/QuestionList/index.tsx | 137 +++++++++++++------------ ui/src/components/QuestionListLoader/index.tsx | 22 +++- ui/src/components/SideNav/index.tsx | 4 +- ui/src/components/index.ts | 2 + 6 files changed, 155 insertions(+), 72 deletions(-) diff --git a/ui/src/common/color.scss b/ui/src/common/color.scss index a2dd55fc..e82624fe 100644 --- a/ui/src/common/color.scss +++ b/ui/src/common/color.scss @@ -37,7 +37,7 @@ --an-editor-toolbar-hover: #f8f9fa; --ans-editor-toolbar-focus: #dae0e5; --an-editor-placeholder-color: #6c757d; - --an-side-nav-link-hover-color: black; + --an-side-nav-link-hover-color: rgba(0, 0, 0, .85); --an-invite-answer-item-active: #e9ecef; --an-alert-exist-color: #055160; } diff --git a/ui/src/components/PinList/index.tsx b/ui/src/components/PinList/index.tsx new file mode 100644 index 00000000..059ca422 --- /dev/null +++ b/ui/src/components/PinList/index.tsx @@ -0,0 +1,60 @@ +import { FC } from 'react'; +import { ListGroup, Stack, Card } from 'react-bootstrap'; +import { NavLink } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; + +import { Counts } from '@/components'; +import { pathFactory } from '@/router/pathFactory'; + +interface IProps { + data: any[]; +} + +const PinList: FC<IProps> = ({ data }) => { + const { t } = useTranslation('translation', { keyPrefix: 'question' }); + if (!data?.length) return null; + + return ( + <ListGroup.Item className="py-3 px-0 border-start-0 border-end-0"> + <Stack + direction="horizontal" + gap={3} + className="overflow-x-auto align-items-stretch"> + {data.map((item) => { + return ( + <Card + key={item.id} + style={{ + minWidth: '238px', + width: `${100 / data.length}%`, + }}> + <Card.Body> + <h6 className="text-wrap text-break"> + <NavLink + to={pathFactory.questionLanding(item.id, item.url_title)} + className="link-dark text-truncate-2"> + {item.title} + {item.status === 2 ? ` [${t('closed')}]` : ''} + </NavLink> + </h6> + + <Counts + data={{ + votes: item.vote_count, + answers: item.answer_count, + views: item.view_count, + }} + isAccepted={item.accepted_answer_id >= 1} + showViews={false} + className="mt-2 mt-md-0 small text-secondary" + /> + </Card.Body> + </Card> + ); + })} + </Stack> + </ListGroup.Item> + ); +}; + +export default PinList; diff --git a/ui/src/components/QuestionList/index.tsx b/ui/src/components/QuestionList/index.tsx index 8796a975..256dbfb4 100644 --- a/ui/src/components/QuestionList/index.tsx +++ b/ui/src/components/QuestionList/index.tsx @@ -32,7 +32,7 @@ import { QueryGroup, QuestionListLoader, Counts, - Icon, + PinList, } from '@/components'; import * as Type from '@/common/interface'; import { useSkeletonControl } from '@/hooks'; @@ -69,6 +69,13 @@ const QuestionList: FC<Props> = ({ const pageSize = 20; const count = data?.count || 0; const orderKeys = orderList || QUESTION_ORDER_KEYS; + const pinData = + source === 'questions' + ? data?.list?.filter((v) => v.pin === 2).slice(0, 3) + : []; + const renderData = data?.list?.filter( + (v) => pinData.findIndex((p) => p.id === v.id) === -1, + ); return ( <div> @@ -90,74 +97,72 @@ const QuestionList: FC<Props> = ({ {isSkeletonShow ? ( <QuestionListLoader /> ) : ( - data?.list?.map((li) => { - return ( - <ListGroup.Item - key={li.id} - className="bg-transparent py-3 px-2 border-start-0 border-end-0"> - <div className="d-flex flex-wrap text-secondary small mb-12"> - <BaseUserCard - data={li.operator} - className="me-1" - avatarClass="me-2" - /> - • - <FormatTime - time={ - curOrder === 'active' ? li.operated_at : li.created_at - } - className="text-secondary ms-1 flex-shrink-0" - preFix={ - curOrder === 'active' ? t(li.operation_type) : t('asked') - } - /> - </div> - <h5 className="text-wrap text-break"> - {li.pin === 2 && ( - <Icon - name="pin-fill" + <> + <PinList data={pinData} /> + {renderData?.map((li) => { + return ( + <ListGroup.Item + key={li.id} + className="bg-transparent py-3 px-2 border-start-0 border-end-0"> + <div className="d-flex flex-wrap text-secondary small mb-12"> + <BaseUserCard + data={li.operator} className="me-1" - title={t('pinned', { keyPrefix: 'btns' })} + avatarClass="me-2" + /> + • + <FormatTime + time={ + curOrder === 'active' ? li.operated_at : li.created_at + } + className="text-secondary ms-1 flex-shrink-0" + preFix={ + curOrder === 'active' + ? t(li.operation_type) + : t('asked') + } /> - )} - <NavLink - to={pathFactory.questionLanding(li.id, li.url_title)} - className="link-dark"> - {li.title} - {li.status === 2 ? ` [${t('closed')}]` : ''} - </NavLink> - </h5> - <p className="mb-2 small text-body text-truncate-2"> - {li.description} - </p> + </div> + <h5 className="text-wrap text-break"> + <NavLink + to={pathFactory.questionLanding(li.id, li.url_title)} + className="link-dark"> + {li.title} + {li.status === 2 ? ` [${t('closed')}]` : ''} + </NavLink> + </h5> + <p className="mb-2 small text-body text-truncate-2"> + {li.description} + </p> - <div className="question-tags mb-2"> - {Array.isArray(li.tags) - ? li.tags.map((tag, index) => { - return ( - <Tag - key={tag.slug_name} - className={`${li.tags.length - 1 === index ? '' : 'me-1'}`} - data={tag} - /> - ); - }) - : null} - </div> - <div className="small text-secondary"> - <Counts - data={{ - votes: li.vote_count, - answers: li.answer_count, - views: li.view_count, - }} - isAccepted={li.accepted_answer_id >= 1} - className="mt-2 mt-md-0" - /> - </div> - </ListGroup.Item> - ); - }) + <div className="question-tags mb-2"> + {Array.isArray(li.tags) + ? li.tags.map((tag, index) => { + return ( + <Tag + key={tag.slug_name} + className={`${li.tags.length - 1 === index ? '' : 'me-1'}`} + data={tag} + /> + ); + }) + : null} + </div> + <div className="small text-secondary"> + <Counts + data={{ + votes: li.vote_count, + answers: li.answer_count, + views: li.view_count, + }} + isAccepted={li.accepted_answer_id >= 1} + className="mt-2 mt-md-0" + /> + </div> + </ListGroup.Item> + ); + })} + </> )} </ListGroup> {count <= 0 && !isLoading && <Empty />} diff --git a/ui/src/components/QuestionListLoader/index.tsx b/ui/src/components/QuestionListLoader/index.tsx index 049cb6d0..e76aa00d 100644 --- a/ui/src/components/QuestionListLoader/index.tsx +++ b/ui/src/components/QuestionListLoader/index.tsx @@ -30,22 +30,36 @@ const Index: FC<Props> = ({ count = 10 }) => { <> {list.map((v) => ( <ListGroupItem - className="bg-transparent py-3 px-0 border-start-0 border-end-0 placeholder-glow" + className="bg-transparent py-3 px-2 border-start-0 border-end-0 placeholder-glow" key={v}> <div - className="placeholder w-100 h5 align-top" + className="placeholder h5 align-top d-block" + style={{ height: '21px', width: '35%' }} + /> + + <div + className="placeholder w-75 h5 align-top" style={{ height: '24px' }} /> <div - className="placeholder w-75 d-block align-top mb-2" + className="placeholder w-100 d-block align-top mb-2" + style={{ height: '21px' }} + /> + <div + className="placeholder w-100 d-block align-top mb-2" style={{ height: '21px' }} /> <div - className="placeholder w-50 align-top" + className="placeholder w-50 align-top mb-2" style={{ height: '24px' }} /> + + <div + className="placeholder w-25 align-top d-block" + style={{ height: '21px' }} + /> </ListGroupItem> ))} </> diff --git a/ui/src/components/SideNav/index.tsx b/ui/src/components/SideNav/index.tsx index e2acf127..4af827f5 100644 --- a/ui/src/components/SideNav/index.tsx +++ b/ui/src/components/SideNav/index.tsx @@ -81,6 +81,7 @@ const Index: FC = () => { </div> {can_revision && ( <NavLink to="/review" className="nav-link"> + <Icon name="shield-fill-check" className="me-2" /> <span>{t('header.nav.review')}</span> <span className="float-end"> {revision > 99 ? '99+' : revision > 0 ? revision : ''} @@ -90,7 +91,8 @@ const Index: FC = () => { {userInfo?.role_id === 2 ? ( <NavLink to="/admin" className="nav-link"> - {t('header.nav.admin')} + <Icon name="gear-fill" className="me-2" /> + <span>{t('header.nav.admin')}</span> </NavLink> ) : null} </> diff --git a/ui/src/components/index.ts b/ui/src/components/index.ts index 44dc1516..400c6b6f 100644 --- a/ui/src/components/index.ts +++ b/ui/src/components/index.ts @@ -61,6 +61,7 @@ import SideNav from './SideNav'; import PluginRender from './PluginRender'; import HighlightText from './HighlightText'; import CardBadge from './CardBadge'; +import PinList from './PinList'; export { Avatar, @@ -109,5 +110,6 @@ export { PluginRender, HighlightText, CardBadge, + PinList, }; export type { EditorRef, JSONSchema, UISchema };
