From 61490b0383ce55b287fd47f0905ce72e8adee96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=8B=E5=AE=8B?= <353833373@qq.com> Date: Thu, 28 Oct 2021 17:42:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20elevator=E7=BB=84=E4=BB=B6=E5=BC=80?= =?UTF-8?q?=E5=8F=91=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config.json | 28 ++-- src/packages/elevator/demo.tsx | 187 +++++++++++++++++++++++ src/packages/elevator/doc.md | 221 ++++++++++++++++++++++++++++ src/packages/elevator/elevator.scss | 71 +++++++++ src/packages/elevator/elevator.tsx | 198 +++++++++++++++++++++++++ src/packages/elevator/index.ts | 2 + src/packages/nutui.react.ts | 2 + 7 files changed, 700 insertions(+), 9 deletions(-) create mode 100644 src/packages/elevator/demo.tsx create mode 100644 src/packages/elevator/doc.md create mode 100644 src/packages/elevator/elevator.scss create mode 100644 src/packages/elevator/elevator.tsx create mode 100644 src/packages/elevator/index.ts diff --git a/src/config.json b/src/config.json index 301ea16..9ac31ab 100644 --- a/src/config.json +++ b/src/config.json @@ -232,15 +232,15 @@ "show": true, "author": "swag~jun" }, - { - "version": "1.0.0", - "name": "CircleProgress", - "type": "component", - "cName": "进度条", - "desc": "展示操作或任务的当前进度。", - "sort": 7, - "show": true, - "author": "swag~jun" + { + "version": "1.0.0", + "name": "CircleProgress", + "type": "component", + "cName": "进度条", + "desc": "展示操作或任务的当前进度。", + "sort": 7, + "show": true, + "author": "swag~jun" } ] }, @@ -276,6 +276,16 @@ "sort": 3, "show": true, "author": "swag~jun" + }, + { + "version": "1.0.0", + "name": "Elevator", + "type": "component", + "cName": "电梯楼层", + "desc": "用于列表快速定位以及索引的显示", + "sort": 4, + "show": true, + "author": "songsong" } ] }, diff --git a/src/packages/elevator/demo.tsx b/src/packages/elevator/demo.tsx new file mode 100644 index 0000000..0881b66 --- /dev/null +++ b/src/packages/elevator/demo.tsx @@ -0,0 +1,187 @@ +import React from 'react' +import { Elevator } from './elevator' + +const ElevatorDemo = () => { + const dataList = [ + { + title: 'A', + list: [ + { + name: '安徽', + id: 1, + }, + ], + }, + { + title: 'B', + list: [ + { + name: '北京', + id: 2, + }, + ], + }, + { + title: 'G', + list: [ + { + name: '广西', + id: 3, + }, + { + name: '广东', + id: 4, + }, + ], + }, + { + title: 'H', + list: [ + { + name: '湖南', + id: 5, + }, + { + name: '湖北', + id: 6, + }, + , + { + name: '琥珀', + id: 7, + }, + ], + }, + ] + const dataList2 = [ + { + num: '一', + list: [ + { + name: '北京', + id: 1, + }, + { + name: '上海', + id: 2, + }, + { + name: '深圳', + id: 3, + }, + { + name: '广州', + id: 4, + }, + { + name: '杭州', + id: 5, + }, + ], + }, + { + num: '二', + list: [ + { + name: '成都', + id: 6, + }, + { + name: '西安', + id: 7, + }, + { + name: '天津', + id: 8, + }, + { + name: '武汉', + id: 9, + }, + { + name: '长沙', + id: 10, + }, + { + name: '重庆', + id: 11, + }, + { + name: '苏州', + id: 12, + }, + { + name: '南京', + id: 13, + }, + ], + }, + { + num: '三', + list: [ + { + name: '西宁', + id: 14, + }, + { + name: '兰州', + id: 15, + }, + { + name: '石家庄', + id: 16, + }, + { + name: '秦皇岛', + id: 17, + }, + { + name: '大连', + id: 18, + }, + { + name: '哈尔滨', + id: 19, + }, + { + name: '长春', + id: 20, + }, + { + name: '太原', + id: 21, + }, + ], + }, + ] + const clickItem = (key: string, item: any) => { + console.log(key, JSON.stringify(item)) + } + + const clickIndex = (key: string) => { + console.log(key) + } + return ( + <> +
+

基础用法

+ clickItem(key, item)} + clickIndex={(key: string) => clickIndex(key)} + > +

自定义索引key

+ clickItem(key, item)} + clickIndex={(key: string) => clickIndex(key)} + > +
+ + ) +} + +export default ElevatorDemo diff --git a/src/packages/elevator/doc.md b/src/packages/elevator/doc.md new file mode 100644 index 0000000..5e41f16 --- /dev/null +++ b/src/packages/elevator/doc.md @@ -0,0 +1,221 @@ +# Elevator 组件 + +### 介绍 + +用于列表快速定位以及索引的显示 + +### 安装 + +## 代码演示 + +### 基础用法 + +```tsx +const dataList = [ + { + title: 'A', + list: [ + { + name: '安徽', + id: 1, + }, + ], + }, + { + title: 'B', + list: [ + { + name: '北京', + id: 2, + }, + ], + }, + { + title: 'G', + list: [ + { + name: '广西', + id: 3, + }, + { + name: '广东', + id: 4, + }, + ], + }, + { + title: 'H', + list: [ + { + name: '湖南', + id: 5, + }, + { + name: '湖北', + id: 6, + }, + , + { + name: '琥珀', + id: 7, + }, + ], + }, +] +const clickItem = (key: string, item: any) => { + console.log(key, JSON.stringify(item)) +} + +const clickIndex = (key: string) => { + console.log(key) +} +``` + +```tsx + clickItem(key, item)} + clickIndex={(key: string) => clickIndex(key)} +> +``` + +### 自定义索引 + +## API + +```tsx +const dataList = [ + { + num: '一', + list: [ + { + name: '北京', + id: 1, + }, + { + name: '上海', + id: 2, + }, + { + name: '深圳', + id: 3, + }, + { + name: '广州', + id: 4, + }, + { + name: '杭州', + id: 5, + }, + ], + }, + { + num: '二', + list: [ + { + name: '成都', + id: 6, + }, + { + name: '西安', + id: 7, + }, + { + name: '天津', + id: 8, + }, + { + name: '武汉', + id: 9, + }, + { + name: '长沙', + id: 10, + }, + { + name: '重庆', + id: 11, + }, + { + name: '苏州', + id: 12, + }, + { + name: '南京', + id: 13, + }, + ], + }, + { + num: '三', + list: [ + { + name: '西宁', + id: 14, + }, + { + name: '兰州', + id: 15, + }, + { + name: '石家庄', + id: 16, + }, + { + name: '秦皇岛', + id: 17, + }, + { + name: '大连', + id: 18, + }, + { + name: '哈尔滨', + id: 19, + }, + { + name: '长春', + id: 20, + }, + { + name: '太原', + id: 21, + }, + ], + }, +] +const clickItem = (key: string, item: any) => { + console.log(key, JSON.stringify(item)) +} + +const clickIndex = (key: string) => { + console.log(key) +} +``` + +```tsx + clickItem(key, item)} + clickIndex={(key: string) => clickIndex(key)} +> +``` + +### Props + +| 字段 | 说明 | 类型 | 默认值 | +| --------- | -------------- | ----------------------------------------------------------- | --------------------- | +| height | 电梯区域的高度 | Number、String | `200px` | +| acceptKey | 索引 key 值 | String | `title` | +| indexList | 索引列表 | Array(item 需包含 id、name 属性, name 支持传入 html 结构) | `[{id: 0, name: ''}]` | + +### Event + +| 名称 | 说明 | 回调参数 | +| ---------- | -------- | -------------------------------------- | +| clickItem | 点击内容 | key: string, item: { id: 0, name: '' } | +| clickIndex | 点击索引 | key: string | diff --git a/src/packages/elevator/elevator.scss b/src/packages/elevator/elevator.scss new file mode 100644 index 0000000..da6cd73 --- /dev/null +++ b/src/packages/elevator/elevator.scss @@ -0,0 +1,71 @@ +.nut-elevator { + width: 100%; + display: block; + position: relative; + &__list { + display: block; + overflow: auto; + &__item { + display: block; + font-size: 12px; + color: #333; + &__code { + display: inline-flex; + position: relative; + height: 35px; + line-height: 35px; + font-size: 14px; + color: #1a1a1a; + padding: 0 20px; + font-weight: 500; + &::after { + content: ' '; + width: 100%; + height: 1px; + position: absolute; + left: 0; + bottom: 7px; + background-color: #eeeff2; + } + } + &__name { + display: flex; + align-items: center; + padding: 0 20px; + height: 30px; + line-height: 30px; + } + } + } + &__code--current { + position: absolute; + right: 60px; + top: 50%; + transform: translateY(-50%); + width: 45px; + height: 45px; + line-height: 45px; + border-radius: 50%; + background: $white; + box-shadow: 0 3px 3px 1px rgba(240, 240, 240, 1); + text-align: center; + } + &__bars { + position: absolute; + right: 8px; + top: 50%; + transform: translateY(-50%); + padding: 15px 0; + background-color: #eeeff2; + border-radius: 6px; + text-align: center; + z-index: 10; + &__inner { + &__item { + display: block; + padding: 3px; + font-size: 10px; + } + } + } +} diff --git a/src/packages/elevator/elevator.tsx b/src/packages/elevator/elevator.tsx new file mode 100644 index 0000000..ae8e5b8 --- /dev/null +++ b/src/packages/elevator/elevator.tsx @@ -0,0 +1,198 @@ +import React, { FunctionComponent, useRef, useEffect, useState } from 'react' +import './elevator.scss' +import bem from '@/utils/bem' + +export interface ElevatorProps { + height: number | string + acceptKey: string + indexList: any + className: string + style: React.CSSProperties + clickItem: (key: string, item: ElevatorData) => void + clickIndex: (key: string) => void +} +const defaultProps = { + height: '200px', + acceptKey: 'title', + indexList: [], + className: '', +} as ElevatorProps +interface ElevatorData { + name: string + id: number | string + [key: string]: string | number +} +export const Elevator: FunctionComponent< + Partial & React.HTMLAttributes +> = (props) => { + const { height, acceptKey, indexList, className, clickItem, clickIndex, ...rest } = { + ...defaultProps, + ...props, + } + const b = bem('elevator') + const spaceHeight = 23 + const listview = useRef(null) + const initData = { + anchorIndex: 0, + listHeight: [] as number[], + listGroup: [] as HTMLElement[], + } + const touchState = useRef({ + y1: 0, + y2: 0, + }) + const [currentIndex, setCurrentIndex] = useState(0) + const [scrollStart, setScrollStart] = useState(false) + const state = useRef(initData) + //重置滚动参数 + const resetScrollState = () => { + state.current.anchorIndex = 0 + setCurrentIndex(0) + setScrollStart(false) + touchState.current = { + y1: 0, + y2: 0, + } + } + + const getData = (el: HTMLElement, name: string): string | void => { + const prefix = 'data-' + return el.getAttribute(prefix + name) as string + } + + const calculateHeight = () => { + let height = 0 + + state.current.listHeight.push(height) + for (let i = 0; i < state.current.listGroup.length; i++) { + let item = state.current.listGroup[i] + height += item.clientHeight + state.current.listHeight.push(height) + } + } + + const scrollTo = (index: number) => { + if (!index && index !== 0) { + return + } + + if (!state.current.listHeight.length) { + calculateHeight() + } + if (index < 0) index = 0 + + if (index > state.current.listHeight.length - 2) index = state.current.listHeight.length - 2 + + setCurrentIndex(index) + if (listview.current) { + listview.current.scrollTo(0, state.current.listHeight[index]) + } + } + + const touchMove = (e: React.TouchEvent) => { + let firstTouch = e.touches[0] + touchState.current.y2 = firstTouch.pageY + let delta = ((touchState.current.y2 - touchState.current.y1) / spaceHeight) | 0 + const cacheIndex = state.current.anchorIndex + delta + + setCurrentIndex(cacheIndex) + scrollTo(cacheIndex) + } + + const touchEnd = () => { + resetScrollState() + } + + const touchStart = (e: React.TouchEvent) => { + setScrollStart(true) + let index = Number(getData(e.target as HTMLElement, 'index')) + let firstTouch = e.touches[0] + touchState.current.y1 = firstTouch.pageY + state.current.anchorIndex = +index + setCurrentIndex((currentIndex) => currentIndex + index) + scrollTo(index) + + const target = e.currentTarget as HTMLElement + + target.removeEventListener('touchmove', touchMove, false) + target.removeEventListener('touchend', touchEnd, false) + target.addEventListener('touchmove', touchMove, false) + target.addEventListener('touchend', touchEnd, false) + } + + const handleClickItem = (key: string, item: ElevatorData) => { + clickItem && clickItem(key, item) + } + + const handleClickIndex = (key: string) => { + clickIndex && clickIndex(key) + } + + const setListGroup = () => { + const els = listview.current.querySelectorAll('.nut-elevator__list__item') + + els.forEach((el: HTMLLIElement) => { + if (el != null && !state.current.listGroup.includes(el)) { + state.current.listGroup.push(el) + } + }) + } + useEffect(() => { + if (listview.current) { + setListGroup() + } + }, [listview.current]) + + return ( +
+
+ {indexList.map((item: any) => { + return ( +
+
{item[acceptKey]}
+ <> + {item.list.map((subitem: any) => { + return ( +
handleClickItem(item[acceptKey], subitem)} + > + {subitem.name} +
+ ) + })} + +
+ ) + })} +
+ {indexList.length && scrollStart ? ( +
{indexList[currentIndex][acceptKey]}
+ ) : null} +
touchStart(event)}> +
+ {indexList.map((item: any, index: number) => { + return ( +
handleClickIndex(item[acceptKey])} + > + {item[acceptKey]} +
+ ) + })} +
+
+
+ ) +} + +Elevator.defaultProps = defaultProps +Elevator.displayName = 'NutElevator' diff --git a/src/packages/elevator/index.ts b/src/packages/elevator/index.ts new file mode 100644 index 0000000..0cb817a --- /dev/null +++ b/src/packages/elevator/index.ts @@ -0,0 +1,2 @@ +import { Elevator } from './elevator' +export default Elevator diff --git a/src/packages/nutui.react.ts b/src/packages/nutui.react.ts index d75f7a2..b6a1c51 100644 --- a/src/packages/nutui.react.ts +++ b/src/packages/nutui.react.ts @@ -15,6 +15,7 @@ import Steps from './steps' import NavBar from './navbar' import Tabbar from './tabbar' import InputNumber from './inputnumber' +import Elevator from './elevator' import Rate from './rate' import Uploader from './uploader' import Input from './input' @@ -40,6 +41,7 @@ export { NavBar, Tabbar, InputNumber, + Elevator, Rate, Uploader, Input, -- GitLab