UNPKG

@nutui/nutui-react

Version:

京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序

347 lines (346 loc) 12.3 kB
import React__default, { useRef, useState, useMemo, useEffect } from "react"; import { Notice, Close } from "@nutui/icons-react"; import classNames from "classnames"; import { g as getRect } from "./use-client-rect.js"; import { C as ComponentDefaults } from "./typings.js"; import { useRtl } from "./ConfigProvider.js"; const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { align: "left", direction: "horizontal", list: [], duration: 1e3, height: 40, content: "", closeable: false, wrap: false, leftIcon: React__default.createElement(Notice, { width: 16, height: 16 }), rightIcon: null, right: null, delay: 1, scrollable: null, speed: 50 }); const NoticeBar = (props) => { const rtl = useRtl(); const { children, className, style, align, direction, list, duration, height, content, closeable, wrap, leftIcon, rightIcon, right, delay, scrollable, speed, close, click, onClose, onClick, onItemClick } = Object.assign(Object.assign({}, defaultProps), props); const classPrefix = "nut-noticebar"; const wrapRef = useRef(null); const contentRef = useRef(null); const [showNoticeBar, SetShowNoticeBar] = useState(true); const scrollList = useRef([]); const [wrapWidth, SetWrapWidth] = useState(0); const [firstRound, SetFirstRound] = useState(true); const [animationDuration, SetAnimationDuration] = useState(0); const [offsetWidth, SetOffsetW] = useState(0); const [animationClass, SetAnimationClass] = useState(""); const [animate, SetAnimate] = useState(false); const [timer, SetTimer] = useState(0); const [isCanScroll, SetIsCanScroll] = useState(null); const isVertical = direction === "vertical"; const [rect, setRect] = useState(null); let active = 0; const [ready, setReady] = useState(false); const container = useRef(null); const innerRef = useRef(null); const swiperRef = useRef({ moving: false, autoplayTimer: null, width: 0, height: 0, offset: 0, size: 0 }); const [childOffset, setChildOffset] = useState([]); const [offset, setOffset] = useState(0); const { childs, childCount } = useMemo(() => { let childCount2 = 0; const childs2 = React__default.Children.map(children, (child) => { if (!React__default.isValidElement(child)) return null; childCount2++; return child; }); return { childs: childs2, childCount: childCount2 }; }, [children]); let trackSize = childCount * Number(height); const minOffset = (() => { if (rect) { const base = isVertical ? rect.height : rect.width; return base - Number(height) * childCount; } return 0; })(); useEffect(() => { if (isVertical) { if (children) { scrollList.current = [].concat(childs); } else { scrollList.current = [].concat(list); startRollEasy(); } } else { initScrollWrap(); } return () => { clearInterval(timer); }; }, []); useEffect(() => { initScrollWrap(); }, [content]); useEffect(() => { if (list && list.length) { scrollList.current = [].concat(list); } }, [list]); const initScrollWrap = (value) => { if (showNoticeBar === false) { return; } setTimeout(() => { if (!wrapRef.current || !contentRef.current) { return; } const wrapW = getRect(wrapRef.current).width; const offsetW = getRect(contentRef.current).width; const canScroll = align === "left" && scrollable == null ? offsetW > wrapW : scrollable; SetIsCanScroll(canScroll); if (canScroll) { SetWrapWidth(wrapW); SetOffsetW(offsetW); SetAnimationDuration(offsetW / speed); SetAnimationClass("play"); } else { SetAnimationClass(""); } }, 0); }; const handleClick = (event) => { click && click(event); onClick && onClick(event); }; const onClickIcon = (event) => { event.stopPropagation(); SetShowNoticeBar(!closeable); close && close(event); onClose && onClose(event); }; const onAnimationEnd = () => { SetFirstRound(false); setTimeout(() => { SetAnimationDuration((offsetWidth + wrapWidth) / speed); SetAnimationClass("play-infinite"); }, 0); }; const time = height / speed / 4 < 1 ? Number((height / speed / 4).toFixed(1)) * 1e3 : ~~(height / speed / 4) * 1e3; const startRollEasy = () => { showhorseLamp(); const timerCurr = window.setInterval(showhorseLamp, time + Number(duration)); SetTimer(timerCurr); }; const showhorseLamp = () => { SetAnimate(true); setTimeout(() => { scrollList.current.push(scrollList.current[0]); scrollList.current.shift(); SetAnimate(false); }, time); }; const handleClickIcon = (event) => { event.stopPropagation(); SetShowNoticeBar(!closeable); close && close(event); onClose && onClose(event); }; const isEllipsis = () => { if (isCanScroll == null && align === "left") { return wrap; } return !isCanScroll && !wrap; }; const contentStyle = { animationDelay: `${firstRound ? delay : 0}s`, animationDuration: `${animationDuration}s`, transform: `translateX(${firstRound ? 0 : `${rtl ? -wrapWidth : wrapWidth}px`})` }; const barStyle = { height: isVertical ? `${height}px` : "" }; const duringTime = height / speed / 4 < 1 ? Number((height / speed / 4).toFixed(1)) : ~~(height / speed / 4); const noDuring = height / speed < 1 ? (height / speed).toFixed(1) : ~~(height / speed); const horseLampStyle = { transition: animate ? `all ${duringTime === 0 ? noDuring : duringTime}s` : "", marginTop: animate ? `-${height}px` : "" }; const init = (active2 = 0) => { const rects = getRect(container === null || container === void 0 ? void 0 : container.current); const _active = Math.max(Math.min(childCount - 1, active2), 0); const _height = rects.height; trackSize = childCount * Number(_height); const targetOffset = getOffset(_active); swiperRef.current.moving = true; if (ready) { swiperRef.current.moving = false; } active2 = _active; setRect(rects); setOffset(targetOffset); setReady(true); }; useEffect(() => { if (ready) { stopAutoPlay(); autoplay(); } return () => { setReady(false); }; }, [ready]); useEffect(() => { if (isVertical && children) { init(); stopAutoPlay(); autoplay(); } }, [children, container === null || container === void 0 ? void 0 : container.current]); const stopAutoPlay = () => { clearTimeout(swiperRef.current.autoplayTimer); swiperRef.current.autoplayTimer = null; }; const autoplay = () => { if (childCount <= 1) return; stopAutoPlay(); swiperRef.current.autoplayTimer = setTimeout(() => { next(); autoplay(); }, Number(duration) + 100 * speed); }; const move = ({ pace = 0, offset: offset2 = 0 }) => { if (childCount <= 1) return; const targetActive = getActive(pace); const targetOffset = getOffset(targetActive, offset2); if (Array.isArray(children) && children[0] && targetOffset !== minOffset) { const rightBound = targetOffset < minOffset; childOffset[0] = rightBound ? trackSize : 0; } if (Array.isArray(children) && children[childCount - 1] && targetOffset !== 0) { const leftBound = targetOffset > 0; childOffset[childCount - 1] = leftBound ? -trackSize : 0; } setChildOffset(childOffset); active = targetActive; setOffset(targetOffset); getStyle(targetOffset); }; const next = () => { resettPosition(); requestFrame(() => { requestFrame(() => { swiperRef.current.moving = false; move({ pace: 1 }); }); }); }; const handleItemClick = (event, value) => { onItemClick && onItemClick(event, value); }; const getStyle = (moveOffset = offset) => { const target = innerRef.current; if (!target) { return; } let _offset = 0; const val = rect.height - height; _offset = moveOffset + Number(active === childCount - 1 && val / 2); target.style.transitionDuration = `${swiperRef.current.moving ? 0 : duration}ms`; target.style.height = `${Number(height) * childCount}px`; target.style.transform = `translate3D(0,${_offset}px,0)`; }; const itemStyle = (index) => { const style2 = {}; if (height) { style2.height = `${height}px`; style2.lineHeight = `${height}px`; } const offset2 = childOffset[index]; if (offset2) { style2.transform = `translate3D(0,${offset2}px,0)`; } return style2; }; const getActive = (pace) => { if (pace) { const _active = active + pace; return range(_active, -1, childCount); } return active; }; const getOffset = (active2, offset2 = 0) => { const currentPosition = active2 * Number(height); const targetOffset = offset2 - currentPosition; return targetOffset; }; const requestFrame = (fn) => { window.requestAnimationFrame.call(window, fn); }; const range = (num, min, max) => { return Math.min(Math.max(num, min), max); }; const resettPosition = () => { swiperRef.current.moving = true; if (active <= -1) { move({ pace: childCount }); } if (active >= childCount) { move({ pace: -childCount }); } }; const noticebarClass = classNames({ [`${classPrefix}-box`]: true, [`${classPrefix}-box-wrapable`]: wrap, [`${classPrefix}-box-${align}`]: true }); const cls = classNames(classPrefix, className); useEffect(() => { return () => { stopAutoPlay(); }; }, []); return React__default.createElement( "div", { className: cls, style }, showNoticeBar && direction === "horizontal" ? React__default.createElement( "div", { className: noticebarClass, style: barStyle, onClick: handleClick }, leftIcon ? React__default.createElement("div", { className: "nut-noticebar-box-left-icon" }, leftIcon) : null, React__default.createElement( "div", { ref: wrapRef, className: "nut-noticebar-box-wrap" }, React__default.createElement( "div", { ref: contentRef, className: `nut-noticebar-box-wrap-content ${animationClass} ${isEllipsis() ? "nut-ellipsis" : ""}`, style: contentStyle, onAnimationEnd }, children, content ) ), right ? React__default.createElement("div", { className: "nut-noticebar-box-right" }, right) : null, closeable || rightIcon ? React__default.createElement("div", { className: "nut-noticebar-box-right-icon", onClick: onClickIcon }, rightIcon || React__default.createElement(Close, { width: 12, height: 12 })) : null ) : null, showNoticeBar && scrollList.current.length > 0 && isVertical ? React__default.createElement( "div", { className: "nut-noticebar-vertical", style: barStyle, ref: container, onClick: handleClick }, leftIcon ? React__default.createElement("div", { className: "nut-noticebar-box-left-icon" }, leftIcon) : null, children ? React__default.createElement("div", { className: "nut-noticebar-box-wrap", ref: innerRef }, scrollList.current.map((item, index) => { return React__default.createElement("div", { style: itemStyle(index), key: index, onClick: (e) => { handleItemClick(e, item); } }, item); })) : React__default.createElement("div", { className: "nut-noticebar-box-horseLamp-list", style: horseLampStyle }, scrollList.current.map((item, index) => { return ( // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions React__default.createElement("div", { className: "nut-noticebar-box-horseLamp-list-item", style: { height }, key: index, onClick: (e) => { handleItemClick(e, item); } }, item) ); })), React__default.createElement("div", { className: "nut-noticebar-box-right-icon", onClick: (e) => { handleClickIcon(e); } }, rightIcon || (closeable ? React__default.createElement(Close, { width: 12, height: 12 }) : null)) ) : null ); }; NoticeBar.displayName = "NutNoticeBar"; export { NoticeBar as default };