From c3c63cb071c5f4c4fb1e27d7354c50f5c2a14fd6 Mon Sep 17 00:00:00 2001 From: robin_yang Date: Thu, 23 Mar 2023 00:57:41 +0800 Subject: [PATCH 1/4] :memo: update readme --- README.md | Bin 5552 -> 5634 bytes src/_test.tsx | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 426bed5308743dde6343bb75e55427476064dd6d..429f13efebc508d2c0941cd8eedda03e5899652d 100644 GIT binary patch delta 236 zcmdm>-K4W2g-NEIA)ld$A)6tMAqU7RXHZ}eskj*In9E(I$1pjENs8-n=jOmofsG(; z6O$gZ9)rT<^-QXadXq0RDKY^Czc59CC_iR75Ve3^WO4&@hH(KyBGA|phD?TJAO`V3 z>^z|Hi9j+1NINj(GNc08An|-4UA*}}vn!*$0fQNXF@re}8!?yyNlP#`V$f$WWH4Z` v0763s6QGAwr6|*qpUUO delta 162 zcmZqD*`U24g^5F?;$pC4E_c=B1}3S=E10whOWH4s10Ae!+Lk4pOO9lf56Cm4!K_AFB w0Lp;GOo6lkP>m5#+yV&Az&tacjK$=$tlE=*v3g8)VXK>bge_vT9QzYK0QYDmRsaA1 diff --git a/src/_test.tsx b/src/_test.tsx index 4e3a04d..b22835b 100644 --- a/src/_test.tsx +++ b/src/_test.tsx @@ -38,12 +38,12 @@ function App() { show modal { + onTabChange={() => { setEv(true) }} hiddenStyle={{ - height:'0px', - overflow:'hidden' + height: '0px', + overflow: 'hidden' }} tabContentVisible={ev} extSelector={'[aria-label="tab"]'} From d3aa4382321670397c380bc316fb9da24b009f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E5=B9=B3?= <756517553@qq.com> Date: Thu, 23 Mar 2023 10:55:21 +0800 Subject: [PATCH 2/4] :memo update readme --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd4bff9..27d390f 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,14 @@ -### contributors + +[![Stargazers repo roster for @RobinYang11/goji](https://reporoster.com/stars/RobinYang11/goji)](https://github.com/RobinYang11/goji/stargazers) + + +[![Forkers repo roster for @RobinYang11/goji](https://reporoster.com/forks/RobinYang11/goji)](https://github.com/RobinYang11/goji/network/members) + ### 前言 From 8a7c4c0b16ea864277690a5fa0970d067fed4c75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E5=B9=B3?= <756517553@qq.com> Date: Thu, 23 Mar 2023 10:58:43 +0800 Subject: [PATCH 3/4] :memo update readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index cd4bff9..a5dffda 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ + +[![Forkers repo roster for @RobinYang11/goji](https://reporoster.com/forks/RobinYang11/goji)](https://github.com/RobinYang11/goji/network/members) + ### contributors From 31557ce5be90a2854cb7c724a874cc2f5a4263c1 Mon Sep 17 00:00:00 2001 From: huangcunjie <2622428319@qq.com> Date: Fri, 24 Mar 2023 00:04:13 +0800 Subject: [PATCH 4/4] =?UTF-8?q?add=20react=20popover=E3=80=81table=20compo?= =?UTF-8?q?nents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 1 + src/react/App.tsx | 73 +++++++++++ src/react/components/Pagination/index.tsx | 74 +++++++++++ .../components/Pagination/styles.module.less | 26 ++++ src/react/components/Popover/index.tsx | 108 ++++++++++++++++ .../components/Popover/styles.module.less | 46 +++++++ src/react/components/Table/index.tsx | 119 ++++++++++++++++++ src/react/components/Table/styles.module.less | 63 ++++++++++ .../components/TableFilterPopover/index.tsx | 63 ++++++++++ .../TableFilterPopover/styles.module.less | 40 ++++++ src/react/index.tsx | 7 ++ webpack.config.js | 2 +- 12 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 src/react/App.tsx create mode 100644 src/react/components/Pagination/index.tsx create mode 100644 src/react/components/Pagination/styles.module.less create mode 100644 src/react/components/Popover/index.tsx create mode 100644 src/react/components/Popover/styles.module.less create mode 100644 src/react/components/Table/index.tsx create mode 100644 src/react/components/Table/styles.module.less create mode 100644 src/react/components/TableFilterPopover/index.tsx create mode 100644 src/react/components/TableFilterPopover/styles.module.less create mode 100644 src/react/index.tsx diff --git a/index.html b/index.html index 7f97143..571d0b8 100644 --- a/index.html +++ b/index.html @@ -26,6 +26,7 @@
left
right
+
\ No newline at end of file diff --git a/src/react/App.tsx b/src/react/App.tsx new file mode 100644 index 0000000..1f11280 --- /dev/null +++ b/src/react/App.tsx @@ -0,0 +1,73 @@ +import React, { memo, useState } from 'react' +import type { FC, ReactNode } from 'react' + +import Table from './components/Table' +import Popover from './components/Popover' + +interface IProps { + children?: ReactNode +} + +const hobby = ['乒乓球', '篮球', '足球', '羽毛球', '网球'] +const address = ['福州', '杭州', '江苏', '厦门', '深圳'] + +const data = () => { + return new Array>(72).fill({}).map((_, index) => ({ + name: `姓名${index + 1}`, + age: Math.floor(Math.random() * 100), + hobby: hobby[Math.floor(Math.random() * 5)], + address: address[Math.floor(Math.random() * 5)] + })) +} + +const cols = [ + { + title: '姓名', + dataIndex: 'name', + key: 'name' + }, + { + title: '年龄', + dataIndex: 'age', + key: 'age' + }, + { + title: '爱好', + dataIndex: 'hobby', + key: 'hobby' + }, + { + title: '住址', + dataIndex: 'address', + key: 'address' + } +] + +const App: FC = () => { + return ( +
+
+ + + + + + + +
+ +
+ +
+

Table组件

+

点击title栏进行排序(随机排序)

+

点击白色方块进行筛选

+

底部分页栏

+ + + + + ) +} + +export default memo(App) diff --git a/src/react/components/Pagination/index.tsx b/src/react/components/Pagination/index.tsx new file mode 100644 index 0000000..f41dcdb --- /dev/null +++ b/src/react/components/Pagination/index.tsx @@ -0,0 +1,74 @@ +import React, { memo, ReactElement, useMemo, useRef, useState } from 'react' +import type { FC, ReactNode } from 'react' + +import styles from './styles.module.less' + +interface IProps { + children?: ReactNode + pageNo: number + pageSize: number + totalPage: number + onChange?: (curPage: number) => void +} + +const Pagination: FC = props => { + const { totalPage, pageNo, pageSize, onChange } = props + + const [pageInfo, setPageInfo] = useState({ pageNo, pageSize }) + + const isCan = (pageNo: number, totalPage: number) => { + if (pageNo <= 0) return false + if (pageNo > totalPage) return false + return true + } + + const onPreOrNextPage = (type: 'next' | 'pre' = 'next') => { + const curPageNo = type === 'next' ? pageNo + 1 : pageNo - 1 + if (isCan(curPageNo, totalPage)) { + setPageInfo({ ...pageInfo, pageNo: curPageNo }) + onChange && onChange(curPageNo) + } + } + const onCurPageClick = (pageNo: number) => { + if (isCan(pageNo, totalPage)) { + setPageInfo({ ...pageInfo, pageNo }) + onChange && onChange(pageNo) + } + } + + const allPageEl = useMemo(() => { + const nodes = [] + for (let i = 0; i < totalPage; i++) { + const isActive = pageInfo.pageNo === i + 1 + const classnames = [ + styles.paginationItem, + isActive ? styles.active : '' + ].join(' ') + nodes.push( +
onCurPageClick(i + 1)}> + {i + 1} +
+ ) + } + return nodes + }, [totalPage, pageInfo]) + + return ( +
+
onPreOrNextPage('pre')}> + < +
+ <>{allPageEl} +
onPreOrNextPage()}> + > +
+
+ ) +} + +export default memo(Pagination) diff --git a/src/react/components/Pagination/styles.module.less b/src/react/components/Pagination/styles.module.less new file mode 100644 index 0000000..cb68788 --- /dev/null +++ b/src/react/components/Pagination/styles.module.less @@ -0,0 +1,26 @@ +.pagination { + display: flex; + margin-top: 8px; + gap: 8px; + + .paginationItem { + width: 30px; + height: 30px; + line-height: 30px; + text-align: center; + color: #d3d3d3; + background: #141414; + border: 1px solid #434343; + + &:hover { + cursor: pointer; + color: #177ddb; + border: 1px solid #177ddb; + } + } + + .paginationItem.active { + color: #177ddb; + border: 1px solid #177ddb; + } +} diff --git a/src/react/components/Popover/index.tsx b/src/react/components/Popover/index.tsx new file mode 100644 index 0000000..dfdecf2 --- /dev/null +++ b/src/react/components/Popover/index.tsx @@ -0,0 +1,108 @@ +import React, { CSSProperties, memo, useCallback, useState } from 'react' + +import type { FC, ReactNode } from 'react' + +import styles from './styles.module.less' + +interface IProps { + children?: ReactNode // 默认元素 + content?: ReactNode // 展示内容元素 + title?: string | ReactNode // 标题 + action?: string | ReactNode // 底部操作栏 + // position?: number // popover 偏移位置 + trigger?: 'hover' | 'click' // 如何触发 + placement?: 'top' | 'bottom' // popover 出现位置 +} + +/** + * TODO + * 1. 只能作用在行内元素,如果多个popover层叠排版会显示在最上层 + * 2. 如果元素设置了偏移,显示位置不对 + */ + +const Popover: FC = props => { + const { + children, + content, + title, + // position = 0, + trigger = 'hover', + placement = 'bottom' + } = props + const [show, setShow] = useState(false) + const [modalStyle, setModalStyle] = useState({}) + const childrenRef = useCallback((node: HTMLDivElement) => { + if (node !== null) { + let rect = node.getBoundingClientRect() + if (node.children[0]) { + rect = node.children[0].getBoundingClientRect() + } + setModalStyle(setupModalStyle(rect)) + } + }, []) + + const setupModalStyle = (rect: DOMRect) => { + const position: Record<'top' | 'bottom', CSSProperties> = { + top: { + transform: `translate(${0}px, ${-rect.bottom + 60}px)` + }, + bottom: { + transform: `translate(${0}px, ${16}px)` + } + } + return position[placement] + } + + /* 三角形位置 */ + const triangleClassNames = [ + styles.triangle, + placement === 'top' ? styles.triangleTop : styles.triangleBottom + ].join(' ') + + /* 事件处理 */ + const onMouseEnter = () => { + if (show) return + trigger == 'hover' && setShow(true) + } + const onMouseLeave = () => trigger === 'hover' && setShow(false) + + const onMouseClick = () => trigger === 'click' && setShow(!show) + + return ( +
e.stopPropagation()}> +
{ + e.stopPropagation() + onMouseEnter() + }} + onMouseLeave={e => { + e.stopPropagation() + onMouseLeave() + }} + onClick={e => { + e.stopPropagation() + onMouseClick() + }}> + {children} +
+
+
+ {title && ( +
{title}
+ )} + {content && ( +
+ {content} +
+ )} +
+
+
+ ) +} + +export default memo(Popover) diff --git a/src/react/components/Popover/styles.module.less b/src/react/components/Popover/styles.module.less new file mode 100644 index 0000000..5d9e35d --- /dev/null +++ b/src/react/components/Popover/styles.module.less @@ -0,0 +1,46 @@ +.popover { + position: relative; + + .children { + width: 100%; + } + + .modal { + position: absolute; + color: #d4d4d4; + background: #1f1f1f; + z-index: 1; + + .triangle { + position: absolute; + width: 0; + height: 0; + } + + .triangleTop { + bottom: -16px; + left: 8px; + border-top: 8px solid #1f1f1f; + border-right: 8px solid transparent; + border-bottom: 8px solid transparent; + border-left: 8px solid transparent; + } + + .triangleBottom { + top: -16px; + left: 8px; + border-top: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 8px solid #1f1f1f; + border-left: 8px solid transparent; + } + + .layout { + padding: 8px; + } + + .title { + border-bottom: 1px solid #303030; + } + } +} diff --git a/src/react/components/Table/index.tsx b/src/react/components/Table/index.tsx new file mode 100644 index 0000000..aa31f32 --- /dev/null +++ b/src/react/components/Table/index.tsx @@ -0,0 +1,119 @@ +import React, { memo, useEffect, useRef, useState } from 'react' +import type { FC, ReactNode } from 'react' + +import styles from './styles.module.less' +import TableFilterPopover from '../TableFilterPopover' +import Pagination from '../Pagination' + +type Cols = { + title: string + dataIndex: string + key: string +} + +interface IProps { + data: Record[] + cols: Cols[] + children?: ReactNode +} + +const Table: FC = props => { + const { data, cols } = props + const curData = useRef[]>([]) + const allRawData = useRef[][]>([]) + const [dataSource, setDataSource] = useState[]>([]) + const [pageInfo, setPageInfo] = useState({ pageSize: 10, pageNo: 1 }) + + const colList = cols.map((item, index) => ( +
+ )) + + const trList = dataSource.map((item, index) => ( + + {Object.entries(item).map(([key, value]) => ( + + ))} + + )) + + /* 初始化数据 */ + const setupData = (data: Record[]) => { + const newData: any[] = [] + let i = 1 + let total = 0 + while (total < data.length) { + newData.push(data.slice(total, 10 * i)) + i++ + total += 10 + } + curData.current = [...newData[0]] + allRawData.current = newData + } + + useEffect(() => { + setupData(data) + }, [data]) + + useEffect(() => { + curData.current = [...allRawData.current[pageInfo.pageNo - 1]] + setDataSource(allRawData.current[pageInfo.pageNo - 1]) + }, [pageInfo]) + + /* 随机排序 */ + const onSort = (dataIndex: string) => { + const values = dataSource.map((item, index) => ({ + index, + value: item[dataIndex] + })) + values.sort((a, b) => 0.5 - Math.random()) + setDataSource(values.map(item => dataSource[item.index])) + } + + /* 模糊查询 */ + const onSearch = (dataIndex: string, searchvalue: string) => { + const indexs: number[] = [] + const values = curData.current.map(item => item[dataIndex]) + for (let i = 0; i < values.length; i++) { + if (String(values[i]).indexOf(searchvalue) >= 0) { + indexs.push(i) + } + } + setDataSource(indexs.map(index => curData.current[index])) + } + + /* 重置 */ + const onRest = () => { + setDataSource([...curData.current]) + } + + return ( +
+
onSort(item.dataIndex)}> +
+
{item.title}
+ onSearch(item.dataIndex, value)} + onRest={() => onRest()}> +
+
+
+
{value}
+ + {colList} + + {trList} +
+ + { + console.log(e) + setPageInfo({ ...pageInfo, pageNo: e }) + }} + /> +
+ ) +} + +export default memo(Table) diff --git a/src/react/components/Table/styles.module.less b/src/react/components/Table/styles.module.less new file mode 100644 index 0000000..4a43ba9 --- /dev/null +++ b/src/react/components/Table/styles.module.less @@ -0,0 +1,63 @@ +.wrap { + color: #dddddd; + + .table { + border-collapse: collapse; + } + + thead { + background: #1d1d1d; + } + + tbody { + background: #141414; + } + + tr { + border-bottom: 1px solid #303030; + + &:hover { + cursor: pointer; + background: #262626; + } + } + + th { + padding: 8px; + height: 48px; + width: 200px; + line-height: 48px; + text-align: left; + border-right: 1px solid #262626; + } + + .header { + tr { + &:hover { + background: none; + } + } + + th { + &:hover { + cursor: pointer; + background: #262626; + } + } + } + + .filter { + display: flex; + justify-content: space-between; + align-items: center; + + .icon { + width: 10px; + height: 10px; + background: #ffffff; + &:hover { + background: #434343; + } + } + } +} diff --git a/src/react/components/TableFilterPopover/index.tsx b/src/react/components/TableFilterPopover/index.tsx new file mode 100644 index 0000000..6d569d7 --- /dev/null +++ b/src/react/components/TableFilterPopover/index.tsx @@ -0,0 +1,63 @@ +import React, { memo, useState } from 'react' + +import type { FC, ReactNode, MouseEvent } from 'react' + +import styles from './styles.module.less' +import Popover from '../Popover' + +type FilterProps = { + children: ReactNode + onSearch?: (value: string) => void + onRest?: () => void +} + +const TableFilterPopover: FC = props => { + const { children, onSearch, onRest } = props + + const [value, setValue] = useState('') + const handleSearch = (e: MouseEvent) => { + e.stopPropagation() + onSearch && onSearch(value) + } + + const handleRest = (e: MouseEvent) => { + e.stopPropagation() + onRest && onRest() + } + + return ( + { + e.stopPropagation() + setValue(e.target.value) + }} + placeholder='请输入搜索内容' + /> + } + content={ +
+ + +
+ }> + {children} +
+ ) +} + +export default memo(TableFilterPopover) diff --git a/src/react/components/TableFilterPopover/styles.module.less b/src/react/components/TableFilterPopover/styles.module.less new file mode 100644 index 0000000..94a1b2f --- /dev/null +++ b/src/react/components/TableFilterPopover/styles.module.less @@ -0,0 +1,40 @@ +.input { + height: 24px; + border: none; + color: #dedee4; + caret-color: #dedee4; + outline: 1px solid #dedee4; + background: #1f1f1f; + + &::placeholder { + color: #515e63; + } + + &:focus-within { + outline: 1px solid #177ddc; + } +} + +.searchBtn { + border: none; + color: #dedee4; + background: #177ddc; + margin-right: 12px; + + &:hover { + background: #095cb5; + cursor: pointer; + } +} + +.restBtn { + border: 1px solid #434343; + color: #dedee4; + background: transparent; + + &:hover { + border: 1px solid #095cb5; + color: #095cb5; + cursor: pointer; + } +} diff --git a/src/react/index.tsx b/src/react/index.tsx new file mode 100644 index 0000000..2e59d8b --- /dev/null +++ b/src/react/index.tsx @@ -0,0 +1,7 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement) + +root.render() diff --git a/webpack.config.js b/webpack.config.js index 33d6dc6..f40eb3f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -7,7 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const CompressionPlugin = require("compression-webpack-plugin"); const config = { - entry: "./src/_test.tsx", + entry: "./src/react/index.tsx", mode: process.env.NODE_ENV === "development" ? "development" : "production", output: { filename: "[name].js",