diff --git a/ReadMe.md b/ReadMe.md index f6b9177..2392068 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -43,6 +43,7 @@ A **[React][1] advanced components library** based on [TypeScript][2] & [Bootstr 18. [Share Box](source/ShareBox.tsx) 19. [Overlay Box](source/OverlayBox.tsx) 20. [Dialog](source/Dialog.tsx) +21. [User Rank](source/UserRank.tsx) #### Data components diff --git a/package.json b/package.json index f2ae671..99fa91a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "idea-react", - "version": "2.0.0-rc.4", + "version": "2.0.0-rc.5", "license": "LGPL-3.0-or-later", "author": "shiy2008@gmail.com", "description": "A React advanced components library based on TypeScript & Bootstrap, built by idea2app remote developers team.", diff --git a/preview/content.tsx b/preview/content.tsx index 7182961..04388c0 100644 --- a/preview/content.tsx +++ b/preview/content.tsx @@ -1,6 +1,6 @@ import { observable } from 'mobx'; import { observer } from 'mobx-react'; -import { PureComponent } from 'react'; +import { Component } from 'react'; import { Button, Form, Image, Modal } from 'react-bootstrap'; import { formToJSON, sleep } from 'web-utility'; @@ -9,6 +9,7 @@ import { CodeBlock, Dialog, DialogClose, + HorizontalMarquee, Icon, Loading, MonthCalendar, @@ -20,12 +21,14 @@ import { ShareBox, SpinnerButton, TimeDistance, - TypeEcho + TypeEcho, + UserRankView, + VerticalMarquee } from '../source'; import { CodeExample, Section } from './utility'; @observer -export class Content extends PureComponent { +export class Content extends Component { @observable accessor pageIndex = 1; @@ -92,6 +95,22 @@ export class Content extends PureComponent { +
+ + + {'idea2app '.repeat(15).trim()} + + +
+ +
+ + + + + +
+
@@ -219,6 +238,27 @@ export class Content extends PureComponent { {showLoading && 加载中...}
+ +
+ + ({ + id: index + 1, + name, + avatar: `https://github.com/${name}.png`, + website: `https://github.com/${name}`, + score: 100 - index + }))} + linkOf={({ id }) => `/user/${id}`} + /> + +
); } diff --git a/source/HorizontalMarquee/index.tsx b/source/HorizontalMarquee/index.tsx index 0dbe84c..e6b83fb 100644 --- a/source/HorizontalMarquee/index.tsx +++ b/source/HorizontalMarquee/index.tsx @@ -1,18 +1,19 @@ -import { FC, PropsWithChildren } from 'react'; +import { FC, HTMLAttributes } from 'react'; import * as style from './index.module.less'; -export type HorizontalMarqueeProps = PropsWithChildren< - Partial> ->; +export type HorizontalMarqueeProps = HTMLAttributes & + Partial>; export const HorizontalMarquee: FC = ({ + className = '', children, maxWidth = '100%', duration, - height + height, + ...props }) => ( -
+
= ({ size, ...props }) => ( - >; + +export const UserAddress: FC = ({ email, website }) => ( +
+ {email && ( + + + + )} + {website && ( + + + + )} +
+); diff --git a/source/UserRank/index.module.less b/source/UserRank/index.module.less new file mode 100644 index 0000000..ffc07f1 --- /dev/null +++ b/source/UserRank/index.module.less @@ -0,0 +1,227 @@ +@keyframes suspension { + 0% { + position: relative; + top: 0px; + } + 50% { + position: relative; + top: -0.8rem; + } + 100% { + position: relative; + top: 0px; + } +} + +.topUserRow { + --logo-image: url('https://github.com/idea2app.png'); + --title-background-image: url('https://hackathon-api.static.kaiyuanshe.cn/6342619375fa1817e0f56ce1/2022/10/09/rrrr.png'); + a { + color: inherit; + text-decoration: none; + } + .imgBox { + width: 2.1rem; + &:hover img { + transition: all 0.3s; + transform: scale(1.1); + } + } + .showTitle { + position: relative; + height: 4.8rem; + padding-top: 1.2rem; + z-index: 4; + background-image: var(--title-background-image); + background-attachment: fixed; + background-size: 21rem auto; + margin-top: 1.5rem; + &::before { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 0.3rem; + background-color: rgba(161, 160, 152, 0.7); + } + .showMedal { + position: absolute; + top: -1.2rem; + left: 5%; + i { + width: 1.7rem; + height: 1.7rem; + background-image: var(--logo-image), + linear-gradient(#ffd83a, #fdeba0); + background-blend-mode: lighten; + background-position: center; + background-size: 1.5rem 1.5rem; + background-repeat: no-repeat; + border: 0.1rem solid #ffd83a; + box-shadow: 0 0 0.5rem 0 #ffd83a; + &:first-child { + border-color: #d8d8d8; + box-shadow: 0 0 0.5rem 0 #d8d8d8; + background-image: var(--logo-image), + linear-gradient(#d8d8d8, #eeeeee); + } + &:last-child { + border-color: #fab36e; + box-shadow: 0 0 0.5rem 0 #fab36e; + background-image: var(--logo-image), + linear-gradient(#fab36e, #fbe7d3); + } + } + } + h3 { + position: relative; + z-index: 2; + font-size: 2rem; + letter-spacing: 0.4rem; + width: 10rem; + cursor: default; + text-shadow: + 0px 1px 0px #c0c0c0, + 0px 2px 0px #b0b0b0, + 0px 3px 0px #a0a0a0, + 0px 4px 0px #909090, + 0px 5px 10px rgba(0, 0, 0, 0.6); + color: transparent; + font-family: "'book antiqua', palatino, serif"; + &::after { + content: attr(data-text); + display: block; + position: absolute; + z-index: 1; + top: 0; + left: 0; + width: 10rem; + margin: 0 auto; + text-shadow: none; + background-image: linear-gradient( + to right, + #d8d8d8 10%, + #ffd83a 60%, + #fab36e 75% + ); + background-clip: text; + } + } + } + .topUserUl { + z-index: 5; + li { + text-align: center; + cursor: default; + .showBox { + min-height: 6rem; + letter-spacing: 1px; + z-index: 3; + } + .imgBox { + z-index: 2; + animation: 3.5s suspension ease-in-out infinite; + transition: all 0.6s; + } + &:nth-child(1) { + order: 2; + padding-top: 0.3rem; + .showBox { + color: #ffd83a; + border-bottom: 1.5rem solid #ffd83a; + text-shadow: + 0px 1px 0px #969653, + 0px 2px 5px rgba(240, 219, 100, 0.4); + i { + width: 2.5rem; + height: 2.6rem; + background-image: var(--logo-image), + linear-gradient(#ffd83a, #fdeba0); + background-blend-mode: lighten; + background-position: center; + background-size: 2.5rem auto; + background-repeat: no-repeat; + } + } + .imgBox { + width: 6.4rem; + animation-delay: 3.5s; + } + } + &:nth-child(2) { + order: 1; + padding-top: 1rem; + .showBox { + color: #d8d8d8; + border-bottom: 1.2rem solid #d8d8d8; + text-shadow: 0px 1px 0px #d9cdcd 0px 2px 5px + rgba(133, 132, 130, 0.4); + i { + background-image: var(--logo-image), + linear-gradient(#d8d8d8, #eeeeee); + } + } + .imgBox { + width: 6rem; + animation-delay: 1.4s; + } + } + &:nth-child(3) { + order: 3; + padding-top: 1.5rem; + .showBox { + border-bottom: 1rem solid #fab36e; + color: #fab36e; + text-shadow: + 0px 1px 0px #aa8865, + 0px 2px 5px rgba(167, 129, 67, 0.4); + i { + background-image: var(--logo-image), + linear-gradient(#fab36e, #fbe7d3); + } + } + .imgBox { + width: 5.6rem; + animation-delay: 2.4s; + } + } + &:hover .imgBox { + animation-play-state: paused; + } + } + } + .topUserList { + tr { + &::before { + content: ''; + position: absolute; + display: block; + right: 0; + bottom: 0; + width: 0; + height: 2px; + background: #9fc2ef; + transition: all 0.5s; + } + &:hover::before { + left: 0; + width: 100%; + } + } + td { + cursor: default; + background-color: transparent; + .usernameBox { + font-size: 0; + &:hover { + .imgBox > img { + transform: scale(1.2); + } + } + } + } + } +} diff --git a/source/UserRank/index.tsx b/source/UserRank/index.tsx new file mode 100644 index 0000000..c06a053 --- /dev/null +++ b/source/UserRank/index.tsx @@ -0,0 +1,120 @@ +import { PureComponent } from 'react'; +import { Badge, Col, Image, Row, Table } from 'react-bootstrap'; + +import { UserAddress, UserAddressProps } from './Address'; +import * as style from './index.module.less'; + +export interface UserRank extends UserAddressProps { + id: number | string; + name: string; + avatar?: string; + score: number; +} + +export interface UserRankViewProps { + title: string; + rank: UserRank[]; + linkOf?: (user: UserRank) => string; +} + +export class UserRankView extends PureComponent { + renderMedal = (user: UserRank) => ( + +
+ {user.name} +
+
+
+ + + {user.name} + + {user.score} +
+ +
+ + ); + + renderRow = (user: UserRank, index: number, { length }: UserRank[]) => ( + + + + {(index + 4 + '').padStart(length, '0')} + + + +
+ {user.name} +
+ + {user.name} + + + {user.score} + + + + + ); + + render() { + const { title, rank = [] } = this.props; + + return ( + + +
+
+ + + +
+

+ {title} +

+
+ + {rank.slice(0, 3).map(this.renderMedal)} + + + + + {rank.slice(3, 10).map(this.renderRow)} +
+ +
+ ); + } +} diff --git a/source/VerticalMarquee/index.module.less b/source/VerticalMarquee/index.module.less index ffaacdb..6441614 100644 --- a/source/VerticalMarquee/index.module.less +++ b/source/VerticalMarquee/index.module.less @@ -27,7 +27,6 @@ animation-iteration-count: infinite; animation-timing-function: linear; font-size: 0; - max-width: 100%; &:hover { animation-play-state: paused; .scrollItem { diff --git a/source/VerticalMarquee/index.tsx b/source/VerticalMarquee/index.tsx index b852846..3ac6031 100644 --- a/source/VerticalMarquee/index.tsx +++ b/source/VerticalMarquee/index.tsx @@ -1,18 +1,26 @@ -import { FC, PropsWithChildren } from 'react'; +import { FC, HTMLAttributes } from 'react'; import * as style from './index.module.less'; -export type VerticalMarqueeProps = PropsWithChildren<{ duration?: string }>; +export interface VerticalMarqueeProps extends HTMLAttributes { + duration?: string; +} export const VerticalMarquee: FC = ({ + className = '', children, - duration + duration, + ...props }) => ( -
-
{children}
+
+
+
+ {children} +
+
); diff --git a/source/index.ts b/source/index.ts index cfd86f4..1c76f57 100644 --- a/source/index.ts +++ b/source/index.ts @@ -19,4 +19,5 @@ export * from './SpinnerButton'; export * from './TableSpinner'; export * from './TimeDistance'; export * from './TypeEcho'; +export * from './UserRank'; export * from './VerticalMarquee';