diff --git a/.eslintrc.json b/.eslintrc.json index 71a3c22..d69a953 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -15,7 +15,7 @@ "project": "./tsconfigRules.json" }, "rules": { - "comma-dangle": "warn", + // "comma-dangle": "warn", "object-curly-newline": 0, // 大括号之后有换行符 "react/prop-types": 0, // 未在props中声明该变量 "react-hooks/rules-of-hooks": "warn", @@ -81,7 +81,8 @@ "jsx-a11y/mouse-events-have-key-events": 0, "no-unused-expressions": "off", "@typescript-eslint/no-unused-expressions": "warn", - "no-nested-ternary": "off" + "no-nested-ternary": "off", + "jsx-a11y/alt-text": "off" }, "settings": { "import/ignore": ["node_modules"] diff --git a/package.json b/package.json index 02c097b..740f3bd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "licht-ui", "author": "style'me", "private": false, - "version": "0.1.6", + "version": "0.1.8", "type": "module", "main": "./dist/licht-ui.umd.js", "module": "./dist/licht-ui.es.js", diff --git a/packages/Image/image.tsx b/packages/Image/image.tsx index cdd89f2..618990b 100644 --- a/packages/Image/image.tsx +++ b/packages/Image/image.tsx @@ -1,39 +1,70 @@ import React, { CSSProperties } from 'react'; import './style.scss'; import classNames from 'classnames'; +import Preview from './preview'; export type ImageProps = { - src: string; - alt?:string - className?: string; - style?: CSSProperties; - fit?:'fill'| 'contain'| 'cover'| 'none'| 'scale-down', + style?: CSSProperties; + className?: string; + src: string; + alt?: string; + width?: string; + height?: string; + fit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down'; + preview?: boolean; + previewList?: Array; + toIndex?: number; }; - -const Image:React.FC=(props:ImageProps)=>{ - const {src,alt,className ,style,fit}=props; - - - const ImageClass = classNames({ - [className || '']: !!className, - }); - - const newStyle:CSSProperties={ - ...style, - objectFit:fit - +export default function Image(props: ImageProps): JSX.Element { + const { style, className, src, alt, width, height, fit, preview, previewList, toIndex } = props; + const [previewShow, setPreviewShow] = React.useState(false); + const [imgList, setImgList] = React.useState>([]) + const imageClass = classNames({ + 'mzl_image': true, + [className || '']: !!className, + }); + const imageStyle = { + ...style, + width, + height, + objectFit: fit, + cursor: preview ? 'pointer' : 'default', + } + const handlerPreviewClick = (e: React.MouseEvent) => { + if (preview) { + if (previewList && previewList.length > 1) { + setImgList(previewList); + } else { + setImgList([e.currentTarget.src]); + } + setPreviewShow(true); } - - return ( - {alt} - ) + } + const closePreview = () => { + setPreviewShow(false); + } + return ( + <> + {alt} handlerPreviewClick(e)} /> + { + preview && + } + + ); } - Image.defaultProps = { - alt: '', - className:'', - style:{}, - fit:'contain' - + style: '', + className: '', + alt: '图片加载失败', + width: '100%', + height: '100%', + fit: 'cover', + preview: false, + previewList: [], + toIndex: 0, }; -export default Image \ No newline at end of file diff --git a/packages/Image/index.ts b/packages/Image/index.ts index 40f60c4..9f1fcda 100644 --- a/packages/Image/index.ts +++ b/packages/Image/index.ts @@ -1,2 +1,4 @@ import Image from './image'; + +export type { ImageProps } from './image'; export default Image; diff --git a/packages/Image/preview.tsx b/packages/Image/preview.tsx new file mode 100644 index 0000000..28c80b8 --- /dev/null +++ b/packages/Image/preview.tsx @@ -0,0 +1,169 @@ +import React from "react"; +import { Image, Mask } from ".."; +import './style.scss'; + +export type PreviewProps = { + previewList?: Array; + show?: boolean; + closePreview?: () => void; + locateIndex?: number; +}; +export default function Preview(props: PreviewProps): JSX.Element { + const { previewList, show, closePreview, locateIndex } = props; + const [index, setIndex] = React.useState(locateIndex); + const [styles, setStyles] = React.useState({ + transform: 'scale3d(1, 1, 1) rotate(0deg)', + }) + const [params, setParams] = React.useState({ + scale: 1, + rotate: 0, + }) + const handlerClose = () => { + closePreview && closePreview() + setTimeout(() => { + setStyles({ + transform: 'scale3d(1, 1, 1) rotate(0deg)', + }) + setParams({ + scale: 1, + rotate: 0, + }) + }, 250) + }; + // console.log('previewList', previewList) + // 左旋转 + const leftRotate = () => { + setParams({ + ...params, + rotate: params.rotate -= 90, + }) + setStyles({ + transform: `scale3d(${params.scale}, ${params.scale}, 1) rotate(${params.rotate}deg)`, + }) + }; + // 右旋转 + const rightRotate = () => { + setParams({ + ...params, + rotate: params.rotate += 90, + }) + setStyles({ + transform: `scale3d(${params.scale}, ${params.scale}, 1) rotate(${params.rotate}deg)`, + }) + }; + // 放大 + const scalebig = () => { + setParams({ + ...params, + scale: params.scale += 0.5, + }) + setStyles({ + transform: `scale3d(${params.scale}, ${params.scale}, 1) rotate(${params.rotate}deg)`, + }) + }; + // 缩小 + const scalesmall = () => { + setParams({ + ...params, + scale: params.scale <= 0.5 ? 0.5 : params.scale -= 0.5, + }) + setStyles({ + transform: `scale3d(${params.scale}, ${params.scale}, 1) rotate(${params.rotate}deg)`, + }) + }; + // 监测鼠标滚轮事件进行放大缩小 + const handlerWheel = (e: any) => { + if (e.deltaY > 0) { + scalesmall() + } else { + scalebig() + } + }; + // 绑定鼠标滚动事件 + React.useEffect(() => { + document.addEventListener('wheel', handlerWheel, false); + return () => { + document.removeEventListener('wheel', handlerWheel, false); + }; + }, []); + // 上一张 + const previous = () => { + setStyles({ + transform: 'scale3d(1, 1, 1) rotate(0deg)', + }) + setParams({ + scale: 1, + rotate: 0, + }) + if ((index as number) >= (previewList as Array).length || index === 0) { + setIndex(0) + return + } + setIndex((index as number) - 1) + }; + // 下一张 + const next = () => { + setStyles({ + transform: 'scale3d(1, 1, 1) rotate(0deg)', + }) + setParams({ + scale: 1, + rotate: 0, + }) + if ((index as number) >= (previewList as Array).length - 1) { + setIndex((previewList as Array).length - 1) + return + } + setIndex((index as number) + 1) + } + React.useEffect(() => { + if (show) { + setIndex(locateIndex) + } + }, [show]) + return ( + +
+
{(index as number) + 1}/{previewList?.length}
+
e.stopPropagation()}> +
  • + +
  • +
  • + +
  • +
  • +
  • +
  • +
    +
    = (previewList as Array).length || index === 0 ? 'noCursor' : ''].join(' ')} + onClick={(e) => { e.stopPropagation(); previous() }} + > + +
    +
    = (previewList as Array).length - 1 ? 'noCursor' : ''].join(' ')} + onClick={(e) => { e.stopPropagation(); next() }} + > + +
    +
    +
    e.stopPropagation()}> + )[(index as number)]} + style={styles} + /> +
    +
    +
    +
    + + ) +} +Preview.defaultProps = { + previewList: [], + show: false, + closePreview: () => { }, + locateIndex: 0 +} \ No newline at end of file diff --git a/packages/Image/style.scss b/packages/Image/style.scss index 0574e43..33edcf9 100644 --- a/packages/Image/style.scss +++ b/packages/Image/style.scss @@ -1,63 +1,113 @@ -:root { - --mzl_border-color: rgb(220, 223, 230); - --mzl_border-margin: 24px; - --mzl_border-padding: 20px; - --mzl_border-hasChildren-margin: 10px; - --mzl_border-vertical-margin: 0.8rem; +.mzl_image { + transition: transform .3s cubic-bezier(.215, .61, .355, 1) 0s; + max-width: 100%; } -.mzl_divider { - border-top: 1px solid var(--mzl_border-color); - height: 1px; - padding: 0 var(--mzl_border-padding); - margin: var(--mzl_border-margin) 0; - - display: flex; - align-items: center; +.previewImgBox { + width: 100%; + height: 100%; position: relative; - box-sizing: border-box; - &_hasChildren { - &_left { - justify-content: start; - } - &_center { + + .equinox { + width: 100%; + height: 46px; + position: absolute; + top: 0; + left: 0; + display: flex; + align-items: center; + justify-content: center; + z-index: 999; + color: #fff; + font-size: 15px; + } + + .preview_head { + width: 100%; + height: 46px; + background: rgba(0, 0, 0, 0.2); + display: flex; + align-items: center; + color: #fff; + justify-content: end; + gap: 15px; + position: absolute; + z-index: 1000; + user-select: none; + + li { + width: 42px; + height: 46px; + display: flex; + align-items: center; justify-content: center; + cursor: pointer; + transition: all 0.1s ease-in-out; + user-select: none; + + &:hover { + background-color: rgba(0, 0, 0, 0.4); + } + + i { + font-size: 19px; + } } - &_right { - justify-content: end; + } + + .switch { + width: 40px; + height: 40px; + background: rgba(0, 0, 0, 0.15); + display: flex; + align-items: center; + justify-content: center; + color: #fff; + border-radius: 50%; + cursor: pointer; + position: absolute; + top: 50%; + margin-top: -20px; + z-index: 1000; + + i { + font-size: 18px; } - & .mzl_divider_children { - border-left: var(--mzl_border-hasChildren-margin) solid #fff; - border-right: var(--mzl_border-hasChildren-margin) solid #fff; - background-color: #fff; + &:hover { + background: rgba(0, 0, 0, 0.3); } } - &_solid { - border-top: 1px solid var(--mzl_border-color); + .previous { + left: 10px; } - &_dotted { - border-top: 1px dotted var(--mzl_border-color); - } - &_dashed { - border-top: 1px dashed var(--mzl_border-color); + + .next { + right: 10px; } - &_groove { - border-top: 1px groove var(--mzl_border-color); + + .noCursor { + cursor: not-allowed; + background: none; + + i { + color: rgba(255, 255, 255, .6); + } + + &:hover { + background: rgba(0, 0, 0, 0); + } } - &_vertical { - border-left: 1px solid var(--mzl_border-color); - border-top: 0; - border-right: 0; - border-bottom: 0; - display: inline-block; - height: 1rem; - margin: 0 var(--mzl_border-vertical-margin); - padding: 0; - position: relative; - top: 0.2rem; - width: 0; + .preview_inner_box { + display: flex; + align-items: center; + justify-content: center; + height: calc(100% - 46px); + + div { + max-width: 1200px; + } } -} +} \ No newline at end of file diff --git a/packages/Mask/index.ts b/packages/Mask/index.ts new file mode 100644 index 0000000..2b3f64e --- /dev/null +++ b/packages/Mask/index.ts @@ -0,0 +1,4 @@ +import Mask from './mask'; + +export type { MaskProps } from './mask'; +export default Mask; diff --git a/packages/Mask/mask.tsx b/packages/Mask/mask.tsx new file mode 100644 index 0000000..c624d0f --- /dev/null +++ b/packages/Mask/mask.tsx @@ -0,0 +1,51 @@ +import React, { CSSProperties } from 'react'; +import './style.scss'; +import classNames from 'classnames'; +import { CSSTransition } from 'react-transition-group'; + +export type MaskProps = { + style?: CSSProperties; + className?: string; + zIndex?: number; + visible: boolean; + children?: React.ReactNode; + background?: string; + maskClick?: (e: React.MouseEvent) => void; +}; +export default function Mask(props: MaskProps): JSX.Element { + const { style, className, zIndex, visible, children, background, maskClick } = props; + const [showMask, setShowMask] = React.useState(false); + const maskClass = classNames({ + 'mzl_mask': true, + [className || '']: !!className, + }); + const maskStyle = { + ...style, + zIndex, + background, + } + React.useEffect(() => { + setShowMask(visible); + }, [visible]); + return ( + +
    maskClick && maskClick(e)}> +
    {children}
    +
    +
    + + ); +} +Mask.defaultProps = { + style: '', + className: '', + zIndex: 1000, + children: null, + background: 'rgba(0,0,0,0.5)', + maskClick: () => { }, +}; diff --git a/packages/Mask/style.scss b/packages/Mask/style.scss new file mode 100644 index 0000000..e624828 --- /dev/null +++ b/packages/Mask/style.scss @@ -0,0 +1,37 @@ +.mzl_mask { + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + + .childrenBox { + width: 100%; + height: 100%; + overflow: hidden; + } +} + +.maskModal-enter { + opacity: 0; +} + +.maskModal-enter-active { + opacity: 1; + transition: all 200ms ease-in-out; +} + +.maskModal-exit { + opacity: 1; +} + +.maskModal-exit-active { + opacity: 0; + transition: all 200ms ease-in-out; +} \ No newline at end of file diff --git a/packages/Notification/index.ts b/packages/Notification/index.ts index 1982888..5dbe4ef 100644 --- a/packages/Notification/index.ts +++ b/packages/Notification/index.ts @@ -1,3 +1,4 @@ import Notification from './notification'; -export type { NotificationProps } from './notification'; + +export type { NotificationItemProps } from './notification'; export default Notification; diff --git a/packages/Notification/notification.tsx b/packages/Notification/notification.tsx index 9898b8f..608db6f 100644 --- a/packages/Notification/notification.tsx +++ b/packages/Notification/notification.tsx @@ -1,154 +1,268 @@ -import React, { useEffect } from 'react'; -import ReactDOM from 'react-dom/client'; -import classNames from 'classnames'; -import './style.scss'; - -interface config { - message: string | React.ReactNode; - description: string | React.ReactNode; - onClick?: () => void; - onClose?: () => void; - className?: string; - duration?: number | null; // null 或 0时不关闭 - style?: {}; - placement?: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; - bottom?: number; // 消息从底部弹出时,距离底部的位置,单位像素 - top?: number; // 消息从顶部弹出时,距离顶部的位置,单位像素 - closeIcon?: React.ReactNode; // 自定义关闭图标 - btn?: React.ReactNode; // 自定义关闭按钮 - icon?: React.ReactNode; // 自定义图标 - type?: 'info' | 'success' | 'warning' | 'error'; -} -export interface NotificationProps extends config {} - -const el = document.createElement('div'); -const wrapper = document.createElement('div'); -el.className = 'mzl_notification'; -wrapper.className = 'mzl_notification_wrapper'; -el.append(wrapper); - -function Notification(props: NotificationProps): JSX.Element { - const { - message, - description, - className = '', - style = {}, - placement = 'topLeft', - type = '', - bottom = 24, - top = 24, - closeIcon = , - btn, - icon = null, - duration = 4.5, - onClick, - onClose, - } = props; - - const icons = { - info: 'm-icon-prompt-filling', - success: 'm-icon-success-filling', - warning: 'm-icon-warning-filling', - error: 'm-icon-delete-filling', - }; - useEffect(() => { - duration && - setTimeout(() => { - document.body.removeChild(el); - }, duration * 1000); - }, []); - - const onCloseNotification = ( - e: React.MouseEvent - ) => { - document.body.removeChild(el); - e.stopPropagation(); - onClose && onClose(); - }; - - const onClickNotification = () => { - onClick && onClick(); - }; - return ( -
    - {icon && {icon}} - {!icon && type && ( - - - - )} -
    -
    - {message} - onCloseNotification(e)} - className="mzl_notification_close" - > - {closeIcon} - -
    -
    {description}
    - {btn &&
    {btn}
    } -
    -
    - ); -} -const notification = { - open: (config: NotificationProps) => { - if (document.querySelector('.mzl_notification')) return; - document.body.appendChild(el); - const root = document.querySelector('.mzl_notification_wrapper'); - return ReactDOM.createRoot(root as HTMLElement).render( - - ); - }, - success: (config: NotificationProps) => { - if (document.querySelector('.mzl_notification')) return; - document.body.appendChild(el); - const root = document.querySelector('.mzl_notification_wrapper'); - return ReactDOM.createRoot(root as HTMLElement).render( - - ); - }, - info: (config: NotificationProps) => { - if (document.querySelector('.mzl_notification')) return; - document.body.appendChild(el); - const root = document.querySelector('.mzl_notification_wrapper'); - return ReactDOM.createRoot(root as HTMLElement).render( - - ); - }, - warning: (config: NotificationProps) => { - if (document.querySelector('.mzl_notification')) return; - document.body.appendChild(el); - const root = document.querySelector('.mzl_notification_wrapper'); - return ReactDOM.createRoot(root as HTMLElement).render( - - ); - }, - error: (config: NotificationProps) => { - if (document.querySelector('.mzl_notification')) return; - document.body.appendChild(el); - const root = document.querySelector('.mzl_notification_wrapper'); - return ReactDOM.createRoot(root as HTMLElement).render( - - ); - }, -}; - -export default notification; +import React, { useEffect, useState } from 'react'; +import ReactDOM from 'react-dom/client'; +import classNames from 'classnames'; +import { CSSTransition } from 'react-transition-group'; +import './style.scss'; + +type TypeProps = 'info' | 'success' | 'warning' | 'error'; +interface Config { + message: string | React.ReactNode; + description: string | React.ReactNode; + onClick?: () => void; + onClose?: () => void; + className?: string; + duration?: number | null; // null 或 0时不关闭 + style?: Record; + placement?: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; + bottom?: number; // 消息从底部弹出时,距离底部的位置,单位像素 + top?: number; // 消息从顶部弹出时,距离顶部的位置,单位像素 + closeIcon?: React.ReactNode; // 自定义关闭图标 + btn?: React.ReactNode; // 自定义关闭按钮 + icon?: React.ReactNode; // 自定义图标 + type?: TypeProps; + isGlobal?: boolean; +} +export type NotificationItemProps = Config; +interface GlobalProps { + duration?: number | null; // null 或 0时不关闭 + placement?: 'topLeft' | 'topRight' | 'bottomLeft' | 'bottomRight'; + bottom?: number; // 消息从底部弹出时,距离底部的位置,单位像素 + top?: number; // 消息从顶部弹出时,距离顶部的位置,单位像素 + closeIcon?: React.ReactNode; // 自定义关闭图标 +} +type Func = (config: NotificationItemProps) => void; +interface ApiProps { + open: Func; + success: Func; + info: Func; + warning: Func; + error: Func; +} +type ContextHolderProps = string; +interface NotificationProps extends ApiProps { + config: Func; + useNotification: () => [ApiProps, ContextHolderProps]; +} + +const el = document.createElement('div'); +const wrapper = document.createElement('div'); +el.className = 'mzl_notification_container'; +document.body.appendChild(el); +wrapper.className = 'mzl_notification_wrapper'; +el.appendChild(wrapper); + +let globalParams: GlobalProps = {}; + +function NotificationItem(props: NotificationItemProps): JSX.Element { + const { + message, + description, + className = '', + style = {}, + placement, + type = '', + bottom, + top, + closeIcon, + btn, + icon = null, + duration, + onClick, + onClose, + isGlobal = false, + } = props; + const [isOpen, setIsOpen] = useState(false); + let timer: number | null = null; + + const icons = { + info: 'm-icon-prompt-filling', + success: 'm-icon-success-filling', + warning: 'm-icon-warning-filling', + error: 'm-icon-delete-filling', + }; + + const getTime = () => { + if (duration === 0 || duration === null || duration) return duration; + if ( + globalParams.duration === 0 || + globalParams.duration === null || + globalParams.duration + ) + return globalParams.duration; + return 4.5; + }; + + useEffect(() => { + setIsOpen(true); + if (isGlobal) { + globalParams = { bottom, closeIcon, duration, placement, top }; + } + + const time = getTime(); + if (time) { + // eslint-disable-next-line react-hooks/exhaustive-deps + timer = setTimeout(() => { + const ele = document.querySelector('.mzl_notification'); + if (ele) { + document.querySelector('.mzl_notification_wrapper')?.removeChild(ele); + } + setIsOpen(false); + }, time * 1000); + } + }, []); + + const onCloseNotification = ( + e: React.MouseEvent + ) => { + setIsOpen(false); + const ele = document.querySelector('.mzl_notification'); + if (ele) { + document.querySelector('.mzl_notification_wrapper')?.removeChild(ele); + } + if (timer) { + clearTimeout(timer); + } + e.stopPropagation(); + if (onClose) { + onClose(); + } + }; + + const onClickNotification = () => { + if (onClick) onClick(); + }; + + const getPlacement = () => { + return placement || globalParams.placement || 'topLeft'; + }; + const getPosition = (pos: 'top' | 'bottom') => { + const position = getPlacement(); + if (position === `${pos}Left` || position === `${pos}Right`) { + return props[pos] || globalParams[pos] || '24px'; + } + return ''; + }; + + return ( + <> + {isGlobal && null} + {!isGlobal && ( + +
    + {icon && {icon}} + {!icon && type && ( + + + + )} +
    +
    + {message} + onCloseNotification(e)} + className="mzl_notification_close" + > + {closeIcon || globalParams.closeIcon || ( + + )} + +
    +
    {description}
    + {btn &&
    {btn}
    } +
    +
    +
    + )} + + ); +} + +NotificationItem.defaultProps = { + onClick: () => {}, + onClose: () => {}, + className: '', + duration: undefined, + style: {}, + placement: '', + bottom: 0, + top: 0, + closeIcon: null, + btn: null, + icon: null, + type: '', + isGlobal: false, +}; + +function popNotification(config: NotificationItemProps, type: TypeProps) { + const root = document.createElement('div'); + root.className = 'mzl_notification'; + ReactDOM.createRoot(root).render( + + ); + const wrapperEl = document.querySelector('.mzl_notification_wrapper'); + if (wrapperEl) { + wrapper.appendChild(root); + } +} +const Notification: NotificationProps = { + config: (config: GlobalProps) => { + const root = document.createElement('div'); + root.className = 'mzl_notification'; + ReactDOM.createRoot(root).render( + + ); + }, + useNotification: () => { + const api: ApiProps = { + open: () => {}, + success: () => {}, + info: () => {}, + warning: () => {}, + error: () => {}, + }; + Object.keys(Notification) + .filter((item) => item !== 'useNotification') + .forEach((item) => { + api[item as keyof ApiProps] = + Notification[item as keyof NotificationProps]; + }); + const contextHolder = ''; + return [api, contextHolder]; + }, + open: (config: NotificationItemProps) => { + popNotification(config, 'info'); + }, + success: (config: NotificationItemProps) => { + popNotification(config, 'success'); + }, + info: (config: NotificationItemProps) => { + popNotification(config, 'info'); + }, + warning: (config: NotificationItemProps) => { + popNotification(config, 'warning'); + }, + error: (config: NotificationItemProps) => { + popNotification(config, 'error'); + }, +}; + +export default Notification; diff --git a/packages/Notification/style.scss b/packages/Notification/style.scss index 240b3f4..d630d02 100644 --- a/packages/Notification/style.scss +++ b/packages/Notification/style.scss @@ -1,98 +1,144 @@ -.mzl_notification { - position: absolute; - left: 0; - top: 0; +.mzl_notification_container { + position: absolute; + left: 0; + top: 0; + pointer-events: none; } .mzl_notification_wrapper { - position: relative; - width: 100vw; - height: 100vh; - - .mzl_demo_notification { - position: absolute; - width: 384px; - padding: 20px 24px; - background-color: $empty-color; - border-radius: 8px; - box-shadow: 0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), 0 9px 28px 8px rgb(0 0 0 / 5%); + position: relative; + width: 100vw; + height: 100vh; + + .mzl_demo_notification { + position: absolute; + width: 384px; + padding: 20px 24px; + background-color: $empty-color; + border-radius: 8px; + box-shadow: 0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), + 0 9px 28px 8px rgb(0 0 0 / 5%); + display: flex; + justify-content: space-between; + align-items: flex-start; + pointer-events: all; + + .mzl_notification_icon { + margin-right: 12px; + margin-top: -6px; + + i { + font-size: 28px; + } + + .m-icon-prompt-filling { + color: $primary-color; + } + + .m-icon-success-filling { + color: $success-color; + } + + .m-icon-warning-filling { + color: $warning-color; + } + + .m-icon-delete-filling { + color: $error-color; + } + } + + .mzl_notification_content { + flex: 1; + .mzl_notification_title_wrapper { display: flex; justify-content: space-between; - align-items: flex-start; - - .mzl_notification_icon { - margin-right: 12px; - margin-top: -6px; - - i { - font-size: 28px; - } - - .m-icon-prompt-filling { - color: $primary-color; - } - - .m-icon-success-filling { - color: $success-color; - } - - .m-icon-warning-filling { - color: $warning-color; - } - - .m-icon-delete-filling { - color: $error-color; - } + align-items: center; + margin-bottom: 8px; + + .mzl_notification_title { + font-size: 16px; } - - .mzl_notification_content { - flex: 1; - .mzl_notification_title_wrapper { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; - - .mzl_notification_title { - font-size: 16px; - } - - .mzl_notification_close { - cursor: pointer; - .m-icon-close { - font-size: 16px; - } - } - } - - .mzl_notification_description { - font-size: 14px; - } - - .mzl_notification_operation{ - margin-top: 8px; - display: flex; - justify-content: end; - } + + .mzl_notification_close { + cursor: pointer; + .m-icon-close { + font-size: 16px; + } } + } + + .mzl_notification_description { + font-size: 14px; + } + + .mzl_notification_operation { + margin-top: 8px; + display: flex; + justify-content: end; + } } - - .mzl_demo_notification_topLeft { - top: 24px; - left: 24px; - } - - .mzl_demo_notification_topRight { - top: 24px; - right: 24px; - } - - .mzl_demo_notification_bottomLeft { - bottom: 24px; - left: 24px; - } - - .mzl_demo_notification_bottomRight { - bottom: 24px; - right: 24px; - } -} \ No newline at end of file + } + + .mzl_demo_notification_topLeft { + top: 24px; + left: 24px; + } + + .mzl_demo_notification_topRight { + top: 24px; + right: 24px; + } + + .mzl_demo_notification_bottomLeft { + bottom: 24px; + left: 24px; + } + + .mzl_demo_notification_bottomRight { + bottom: 24px; + right: 24px; + } +} + +.notification-topLeft-enter, +.notification-bottomLeft-enter { + opacity: 0; + transform: translateX(-100%); +} + +.notification-topRight-enter, +.notification-bottomRight-enter { + opacity: 0; + transform: translateX(100%); +} + +.notification-topLeft-enter-active, +.notification-bottomLeft-enter-active, +.notification-topRight-enter-active, +.notification-bottomRight-enter-active { + opacity: 1; + transform: translateX(0); + transition: all 200ms ease-in-out; +} + +.notification-topLeft-exit, +.notification-bottomLeft-exit, +.notification-topRight-exit, +.notification-bottomRight-exit { + opacity: 1; + transform: translateX(0); +} + +.notification-topLeft-exit-active, +.notification-bottomLeft-exit-active { + opacity: 0; + transform: translateX(-100%); + transition: all 300ms ease-in-out; +} + +.notification-topRight-exit-active, +.notification-bottomRight-exit-active { + opacity: 0; + transform: translateX(100%); + transition: all 300ms ease-in-out; +} diff --git a/packages/Popover/Modal.ts b/packages/Popover/Modal.ts index 7fab5f6..54672fe 100644 --- a/packages/Popover/Modal.ts +++ b/packages/Popover/Modal.ts @@ -18,11 +18,7 @@ class Modal extends Component { }; } - componentDidUpdate( - prevProps: Readonly, - prevState: Readonly, - snapshot?: any - ): void { + componentDidUpdate(): void { const { popupContainer, showModal } = this.props; const { modalRoot } = this.state; if (!showModal) { diff --git a/packages/Popover/index.ts b/packages/Popover/index.ts index d25b93f..93bc337 100644 --- a/packages/Popover/index.ts +++ b/packages/Popover/index.ts @@ -1,3 +1,4 @@ import Popover from './popover'; + export type { PopoverProps } from './popover'; export default Popover; diff --git a/packages/Popover/popover.tsx b/packages/Popover/popover.tsx index 4adaee1..7941de5 100644 --- a/packages/Popover/popover.tsx +++ b/packages/Popover/popover.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import Modal from './Modal'; import PopoverItem from './popoverItem'; import './style.scss'; + export interface PopoverProps { children: React.ReactNode; content: string | React.ReactNode; @@ -40,10 +41,10 @@ function Popover(props: PopoverProps): JSX.Element { getPopupContainer(popoverComponentRef?.current || document.body)) || document.body; - let componentLeft: number, - componentTop: number, - componentWidth: number, - componentHeight: number; + let componentLeft: number; + let componentTop: number; + let componentWidth: number; + let componentHeight: number; const getPos = () => { const popoverComponentOffset = @@ -53,6 +54,27 @@ function Popover(props: PopoverProps): JSX.Element { componentHeight = popoverComponentRef.current?.clientHeight || 0; componentWidth = popoverComponentRef.current?.clientWidth || 0; }; + // 执行onOpenChange事件 + const onPopoverChange = (e: boolean): void => { + if (onOpenChange) { + onOpenChange(!e); + } + }; + // Hidden变化回调 + const onChangeHidden = (e: boolean): void => { + setIsHidden(e); + onPopoverChange(e); + }; + // 弹出气泡卡片 + const openPopover = (): void => { + onChangeHidden(false); + setPopOffset({ + top: componentTop, + left: componentLeft, + clientHeight: componentHeight, + clientWidth: componentWidth, + }); + }; useEffect(() => { if (visible !== undefined) { onChangeHidden(!visible); @@ -70,28 +92,12 @@ function Popover(props: PopoverProps): JSX.Element { getPos(); }, [isHidden]); - // 弹出气泡卡片 - const openPopover = (): void => { - onChangeHidden(false); - setPopOffset({ - top: componentTop, - left: componentLeft, - clientHeight: componentHeight, - clientWidth: componentWidth, - }); - }; - // 关闭气泡卡片 const closePopover = (): void => { onChangeHidden(true); }; - // 执行onOpenChange事件 - const onPopoverChange = (e: boolean): void => { - onOpenChange && onOpenChange(!e); - }; - - const onHoverOpen = (e: React.MouseEvent): void => { + const onHoverOpen = (): void => { // if (visible || visible === undefined) { // openPopover(); // } @@ -108,15 +114,15 @@ function Popover(props: PopoverProps): JSX.Element { closePopover(); }; - const onFocusOpen = (e: React.FocusEvent): void => { + const onFocusOpen = (): void => { openPopover(); }; - const onFocusClose = (e: React.FocusEvent): void => { + const onFocusClose = (): void => { if (visible) return; closePopover(); }; - const onClick = (e: React.MouseEvent): void => { + const onClick = (): void => { if (isHidden) { openPopover(); } else { @@ -125,12 +131,6 @@ function Popover(props: PopoverProps): JSX.Element { } }; - // Hidden变化回调 - const onChangeHidden = (e: boolean): void => { - setIsHidden(e); - onPopoverChange(e); - }; - return ( <>
    {}, + trigger: 'hover', + getPopupContainer: () => {}, +}; + +export default React.memo(Popover); diff --git a/packages/Popover/popoverItem.tsx b/packages/Popover/popoverItem.tsx index 3d68783..b0ab4d1 100644 --- a/packages/Popover/popoverItem.tsx +++ b/packages/Popover/popoverItem.tsx @@ -1,8 +1,9 @@ import React, { ReactNode, useRef, useEffect, useState } from 'react'; import classNames from 'classnames'; +import { CSSTransition } from 'react-transition-group'; import './style.scss'; -interface popOffsetProps { +interface PopOffsetProps { left: number; top: number; clientHeight: number; @@ -12,10 +13,10 @@ export interface PopoverProps { content: string | ReactNode; title: string; visible: boolean | undefined; - popOffset: popOffsetProps; + popOffset: PopOffsetProps; isHidden: boolean; placement?: string; - color?: string; + // color?: string; changeHidden: (e: boolean) => void; trigger: 'hover' | 'focus' | 'click'; containerDom: HTMLElement; @@ -95,35 +96,44 @@ function PopoverItem(props: PopoverProps): JSX.Element { changeHidden(true); }; return ( -
    popoverMouseLeave(e)} +
    popoverMouseLeave(e)} >
    -
    -
    {title}
    -
    {content}
    + > +
    +
    +
    {title}
    +
    {content}
    +
    -
    +
    ); } - +PopoverItem.defaultProps = { + placement: 'left', +}; export default React.memo(PopoverItem); diff --git a/packages/Popover/style.scss b/packages/Popover/style.scss index 5e28f1d..a53bb1d 100644 --- a/packages/Popover/style.scss +++ b/packages/Popover/style.scss @@ -1,68 +1,88 @@ .mzl_demo_popover { - width: inherit; - display: inline-block; - position: relative; + width: inherit; + display: inline-block; + position: relative; } .mzl_popover { - width: fit-content; - min-width: 177px; - display: inline-block; - background-color: #fff; + width: fit-content; + min-width: 177px; + display: inline-block; + background-color: #fff; - .mzl_popover_content_left { - padding-right: 10px; - } - .mzl_popover_content_right { - padding-left: 10px; - } - .mzl_popover_content_top { - padding-bottom: 10px; - } - .mzl_popover_content_bottom { - padding-top: 10px; - } - .mzl_popover_arrow { - position: absolute; - width: 0; - height: 0; - // box-shadow: 3px 3px 7px red; // 只针对盒模型 - filter: drop-shadow(0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), 0 9px 28px 8px rgb(0 0 0 / 5%)); - } - .mzl_popover_arrow_left { - border-left: 8px solid #fff; - border-top: 8px solid transparent; - border-bottom: 8px solid transparent; - } - .mzl_popover_arrow_right { - border-right: 8px solid #fff; - border-top: 8px solid transparent; - border-bottom: 8px solid transparent; - } + .mzl_popover_content_left { + padding-right: 10px; + } + .mzl_popover_content_right { + padding-left: 10px; + } + .mzl_popover_content_top { + padding-bottom: 10px; + } + .mzl_popover_content_bottom { + padding-top: 10px; + } + .mzl_popover_arrow { + position: absolute; + width: 0; + height: 0; + /* box-shadow: 3px 3px 7px red; 只针对盒模型 */ + filter: drop-shadow( + 0 6px 16px 0 rgb(0 0 0 / 8%), + 0 3px 6px -4px rgb(0 0 0 / 12%), + 0 9px 28px 8px rgb(0 0 0 / 5%) + ); + } + .mzl_popover_arrow_left { + border-left: 8px solid #fff; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + } + .mzl_popover_arrow_right { + border-right: 8px solid #fff; + border-top: 8px solid transparent; + border-bottom: 8px solid transparent; + } - .mzl_popover_arrow_top { - border-top: 8px solid #fff; - border-left: 8px solid transparent; - border-right: 8px solid transparent; - } + .mzl_popover_arrow_top { + border-top: 8px solid #fff; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + } - .mzl_popover_arrow_bottom { - border-bottom: 8px solid #fff; - border-left: 8px solid transparent; - border-right: 8px solid transparent; - } - .mzl_popover_inner { - padding: 12px; - box-shadow: 0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), 0 9px 28px 8px rgb(0 0 0 / 5%); - border-radius: 8px; + .mzl_popover_arrow_bottom { + border-bottom: 8px solid #fff; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + } + .mzl_popover_inner { + padding: 12px; + box-shadow: 0 6px 16px 0 rgb(0 0 0 / 8%), 0 3px 6px -4px rgb(0 0 0 / 12%), + 0 9px 28px 8px rgb(0 0 0 / 5%); + border-radius: 8px; - .mzl_popover_title { - font-weight: 600; - margin-bottom: 8px; - } + .mzl_popover_title { + font-weight: 600; + margin-bottom: 8px; } + } } .mzl_popover_hidden { - display: none; - left: -333px; - top: -666px; -} \ No newline at end of file + display: none; + left: -333px; + top: -666px; +} + +.popover-enter { + opacity: 0; +} +.popover-enter-active { + opacity: 1; + transition: all 150ms ease-in-out; +} +.popover-exit { + opacity: 1; +} +.popover-exit-active { + opacity: 0; + transition: all 150ms ease-in-out; +} diff --git a/packages/Space/space.tsx b/packages/Space/space.tsx index 32a7145..a73f697 100644 --- a/packages/Space/space.tsx +++ b/packages/Space/space.tsx @@ -7,24 +7,26 @@ export type SpaceProps = { className?: string; children: React.ReactNode; direction?: 'horizontal' | 'vertical'; - gap?: string, - align?: string, + gap?: string; + align?: string; }; function Space(props: SpaceProps): JSX.Element { const { style, className, children, direction, gap, align } = props; const spaceClass = classNames({ - 'mzl_space': true, + mzl_space: true, [className || '']: !!className, }); const spaceStyle = { ...style, - 'flexFlow': direction === 'vertical' ? 'column wrap' : 'wrap', - 'gap': gap, - 'align-items': direction === 'vertical' ? align : 'baseline', - 'justify-content': direction === 'horizontal' ? align : 'baseline', - } + flexFlow: direction === 'vertical' ? 'column wrap' : 'wrap', + gap: gap, + alignItems: direction === 'vertical' ? align : 'baseline', + justifyContent: direction === 'horizontal' ? align : 'baseline', + }; return ( -
    {children}
    +
    + {children} +
    ); } Space.defaultProps = { diff --git a/packages/index.ts b/packages/index.ts index 01aaba7..697b36b 100644 --- a/packages/index.ts +++ b/packages/index.ts @@ -18,4 +18,5 @@ export { default as LoadingBar } from './LoadingBar'; export { default as Empty } from './Empty'; export { default as Tag } from './Tag'; export { default as Popover } from './Popover'; -export { default as notification } from './Notification'; +export { default as Notification } from './Notification'; +export { default as Mask } from './Mask'; diff --git a/src/demo/image/api.tsx b/src/demo/image/api.tsx deleted file mode 100644 index 579d9f2..0000000 --- a/src/demo/image/api.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React from 'react'; -import { Table } from '../../../packages'; - -export default function Api() { - const columns = [ - { - title: "参数", - field: "arguments", - }, - { - title: "说明", - field: "description", - }, - { - title: '类型', - field: 'type', - }, - { - title: '可选值', - field: 'optional', - width: 450, - }, - { - title: '默认值', - field: 'default', - width: 80, - }, - ]; - - const dataSource = [ - { - arguments: 'src', - description: '图片源地址,同原生属性一致', - type: string, - optional: -- , - default: '--', - }, - { - arguments: 'fit', - description: '确定图片如何适应容器框,同原生', - type: string, - optional: "'fill' | 'contain' | 'cover' | 'none' | 'scale-down'", - default: 'contain', - }, - { - arguments: 'alt', - description: '原生属性 alt', - type: string, - optional: "-", - default: '-', - }, - ]; - - return ( -
    -

    API

    - - - ); -} \ No newline at end of file diff --git a/src/demo/image/demo1.tsx b/src/demo/image/demo1.tsx index 8be1790..6153c3b 100644 --- a/src/demo/image/demo1.tsx +++ b/src/demo/image/demo1.tsx @@ -1,17 +1,18 @@ -import React from 'react'; -import { Image } from '../../../packages'; +import React from "react"; +import { Image } from "../../../packages"; -export default function Demo1() { - const fits = ['fill', 'contain', 'cover', 'none', 'scale-down'] - return ( -
    - {fits.map((item: any, index: number) => { - return
    -

    {item}

    - -
    - })} -
    +export default function Demo() { + const fits = ['cover', 'contain', 'fill', 'none', 'scale-down'] + const src = "https://img2.baidu.com/it/u=2745938939,87326468&fm=253&fmt=auto&app=120&f=JPEG?w=1024&h=640" + return ( +
    + {fits.map((item: any, index: number) => { + return
    +

    {item}

    + +
    + })} +
    - ) -} + ) +} \ No newline at end of file diff --git a/src/demo/image/demo2.tsx b/src/demo/image/demo2.tsx new file mode 100644 index 0000000..30b7061 --- /dev/null +++ b/src/demo/image/demo2.tsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Image } from "../../../packages"; + +export default function Demo() { + const src = "https://img0.baidu.com/it/u=1960352606,3294699366&fm=253&fmt=auto&app=120&f=JPEG?w=1200&h=675" + return ( + + ) +} \ No newline at end of file diff --git a/src/demo/image/demo3.tsx b/src/demo/image/demo3.tsx new file mode 100644 index 0000000..b652a45 --- /dev/null +++ b/src/demo/image/demo3.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { Space, Image } from "../../../packages"; + +export default function Demo() { + const imgList = [ + 'https://img1.baidu.com/it/u=47162254,1618159860&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800', + 'https://img0.baidu.com/it/u=1042709427,4147685100&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500', + 'https://img0.baidu.com/it/u=4189562873,1342857029&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800', + 'https://img0.baidu.com/it/u=3308057334,4059906751&fm=253&fmt=auto&app=120&f=JPEG?w=658&h=987', + 'https://img1.baidu.com/it/u=3713898695,3041023958&fm=253&fmt=auto&app=120&f=JPEG?w=1280&h=800', + 'https://img1.baidu.com/it/u=1610481605,2160356402&fm=253&fmt=auto&app=120&f=JPEG?w=889&h=500', + 'https://img2.baidu.com/it/u=2641437459,1970317776&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500' + ] + return ( + + { + imgList.map((item: any, index: number) => { + return + }) + } + + ) +} \ No newline at end of file diff --git a/src/demo/mask/api.tsx b/src/demo/mask/api.tsx new file mode 100644 index 0000000..85f6208 --- /dev/null +++ b/src/demo/mask/api.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { Table } from '../../../packages'; + +export default function Api() { + const columns: any = [ + { + title: '参数', + field: 'arguments', + }, + { + title: '说明', + field: 'description', + }, + { + title: '类型', + field: 'type', + }, + { + title: '可选值', + field: 'optional', + width: 250, + }, + { + title: '默认值', + field: 'default', + width: 180, + }, + ] + const dataSource: any = [ + { + arguments: 'style', + description: '自定义Mask样式', + type: CSSProperties, + optional: '-', + default: '-', + }, + { + arguments: 'className', + description: '自定义Mask类名', + type: string, + optional: '-', + default: '-', + }, + { + arguments: 'zIndex', + description: 'Mask层级', + type: string, + optional: '-', + default: '1000', + }, + { + arguments: 'visible', + description: '是否显示遮罩层', + type: boolean, + optional: true | false, + default: 'false', + }, + { + arguments: 'children', + description: '遮罩层子集内容', + type: ReactNode, + optional: '-', + default: '-', + }, + { + arguments: 'background', + description: '遮罩层背景色', + type: string, + optional: '-', + default: 'rgba(0,0,0,0.5)', + }, + { + arguments: 'maskClick', + description: '遮罩层点击事件', + type: {'(e: React.MouseEvent) => void'}, + optional: '-', + default: '-', + }, + ] + return ( +
    +

    API 说明

    + {/*

    API 说明

    */} +
    + + ); +} diff --git a/src/demo/mask/demo1.tsx b/src/demo/mask/demo1.tsx new file mode 100644 index 0000000..74fe70c --- /dev/null +++ b/src/demo/mask/demo1.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Mask, Button } from '../../../packages'; + +export default function Demo() { + const [visible, setVisible] = React.useState(false); + return ( + <> + setVisible(false)} /> + + + ) +} \ No newline at end of file diff --git a/src/demo/mask/demo2.tsx b/src/demo/mask/demo2.tsx new file mode 100644 index 0000000..0d53698 --- /dev/null +++ b/src/demo/mask/demo2.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { Mask, Button } from '../../../packages'; + +export default function Demo() { + const [visible, setVisible] = React.useState(false); + return ( + <> + setVisible(false)} background="rgba(0,0,0,.8)" /> + + + ) +} \ No newline at end of file diff --git a/src/demo/mask/demo3.tsx b/src/demo/mask/demo3.tsx new file mode 100644 index 0000000..d83d16d --- /dev/null +++ b/src/demo/mask/demo3.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Mask, Button, Table } from '../../../packages'; +import { example } from '../../utils/example'; + +export default function Demo() { + const [visible, setVisible] = React.useState(false); + const style = { + width: '500px', + height: '500px', + background: '#fff', + borderRadius: '10px', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + margin: '100px auto' + } + return ( + <> + setVisible(false)}> +
    e.stopPropagation()}> + +
    +
    + + + ) +} \ No newline at end of file diff --git a/src/demo/notification/api.tsx b/src/demo/notification/api.tsx index d9327e7..8375b61 100644 --- a/src/demo/notification/api.tsx +++ b/src/demo/notification/api.tsx @@ -1,157 +1,214 @@ -import React from 'react'; -import { Table } from '../../../packages'; - -function Api() { - const columns: any = [ - { - title: '参数', - field: 'arguments', - }, - { - title: '说明', - field: 'description', - }, - { - title: '类型', - field: 'type', - }, - { - title: '可选值', - field: 'optional', - }, - { - title: '默认值', - field: 'default', - width: 80, - }, - ]; - const dataSource: any = [ - { - arguments: 'message', - description: '提醒框标题 (必填)', - type: string | ReactNode, - optional: '-', - default: '-', - }, - { - arguments: 'description', - description: '提醒框内容 (必填)', - type: string | ReactNode, - optional: '-', - default: '-', - }, - { - arguments: 'placement', - description: '弹出位置', - type: string, - optional: ( - - topLeft | topRight | bottomLeft | bottomRight - - ), - default: 'topLeft', - }, - { - arguments: 'duration', - description: '默认 4.5 秒后自动关闭,配置为 0 或 null 则不自动关闭', - type: number | null, - optional: '-', - default: '4.5', - }, - { - arguments: 'className', - description: '自定义 CSS class', - type: string, - optional: '-', - default: '-', - }, - { - arguments: 'style', - description: '自定义内联样式', - type: {'{ }'}, - optional: CSSProperties, - default: '-', - }, - { - arguments: 'bottom', - description: '消息从底部弹出时,距离底部的位置,单位像素', - type: number, - optional: '-', - default: '24', - }, - { - arguments: 'top', - description: '消息从顶部弹出时,距离顶部的位置,单位像素', - type: number, - optional: '-', - default: '24', - }, - { - arguments: 'closeIcon', - description: '自定义关闭图标', - type: ReactNode, - optional: '-', - default: '-', - }, - { - arguments: 'icon', - description: '自定义图标', - type: ReactNode, - optional: '-', - default: '-', - }, - { - arguments: 'btn', - description: '自定义关闭按钮', - type: ReactNode, - optional: '-', - default: '-', - }, - { - arguments: 'onClick', - description: '点击通知时触发的回调函数', - type: () => void, - optional: '-', - default: '-', - }, - { - arguments: 'onClose', - description: '点击通知时触发的回调函数', - type: () => void, - optional: '-', - default: '-', - }, - ]; - const typeList = ['success', 'error', 'info', 'warning', 'open']; - return ( -
    -

    API 说明

    -
      - {typeList.map((item) => ( -
    • - - notification.{item}(config) - -
    • - ))} -
    -

    config 参数如下:

    -
    - - ); -} - -export default Api; +import React from 'react'; +import { Table } from '../../../packages'; + +function Api() { + const columns: any = [ + { + title: '参数', + field: 'arguments', + }, + { + title: '说明', + field: 'description', + }, + { + title: '类型', + field: 'type', + }, + { + title: '可选值', + field: 'optional', + }, + { + title: '默认值', + field: 'default', + width: 80, + }, + ]; + const dataSource: any = [ + { + arguments: 'message', + description: '提醒框标题 (必填)', + type: string | ReactNode, + optional: '-', + default: '-', + }, + { + arguments: 'description', + description: '提醒框内容 (必填)', + type: string | ReactNode, + optional: '-', + default: '-', + }, + { + arguments: 'placement', + description: '弹出位置', + type: string, + optional: ( + + topLeft | topRight | bottomLeft | bottomRight + + ), + default: 'topLeft', + }, + { + arguments: 'duration', + description: '默认 4.5 秒后自动关闭,配置为 0 或 null 则不自动关闭', + type: number | null, + optional: '-', + default: '4.5', + }, + { + arguments: 'className', + description: '自定义 CSS class', + type: string, + optional: '-', + default: '-', + }, + { + arguments: 'style', + description: '自定义内联样式', + type: {'{ }'}, + optional: CSSProperties, + default: '-', + }, + { + arguments: 'bottom', + description: '消息从底部弹出时,距离底部的位置,单位像素', + type: number, + optional: '-', + default: '24', + }, + { + arguments: 'top', + description: '消息从顶部弹出时,距离顶部的位置,单位像素', + type: number, + optional: '-', + default: '24', + }, + { + arguments: 'closeIcon', + description: '自定义关闭图标', + type: ReactNode, + optional: '-', + default: '-', + }, + { + arguments: 'icon', + description: '自定义图标', + type: ReactNode, + optional: '-', + default: '-', + }, + { + arguments: 'btn', + description: '自定义关闭按钮', + type: ReactNode, + optional: '-', + default: '-', + }, + { + arguments: 'onClick', + description: '点击通知时触发的回调函数', + type: () => void, + optional: '-', + default: '-', + }, + { + arguments: 'onClose', + description: '点击通知时触发的回调函数', + type: () => void, + optional: '-', + default: '-', + }, + ]; + + const globalDataSource: any = [ + { + arguments: 'placement', + description: '弹出位置', + type: string, + optional: ( + + topLeft | topRight | bottomLeft | bottomRight + + ), + default: 'topLeft', + }, + { + arguments: 'duration', + description: '默认 4.5 秒后自动关闭,配置为 0 或 null 则不自动关闭', + type: number | null, + optional: '-', + default: '4.5', + }, + { + arguments: 'bottom', + description: '消息从底部弹出时,距离底部的位置,单位像素', + type: number, + optional: '-', + default: '24', + }, + { + arguments: 'top', + description: '消息从顶部弹出时,距离顶部的位置,单位像素', + type: number, + optional: '-', + default: '24', + }, + { + arguments: 'closeIcon', + description: '自定义关闭图标', + type: ReactNode, + optional: '-', + default: '-', + }, + ]; + const typeList = ['success', 'error', 'info', 'warning', 'open']; + const liStyle = { listStyle: 'circle', margin: '12px 0' }; + const codeStyle = { + backgroundColor: 'rgba(0, 0, 0, 0.04)', + border: '1px solid rgba(5, 5, 5, 0.06)', + borderRadius: '3px', + padding: '0.2em 0.4em', + fontSize: '10px', + }; + const code = + "notification.config({\n placement: 'bottomRight',\n bottom: 50,\n duration: 3,\n})"; + return ( +
    +

    API 说明

    +
      + {typeList.map((item) => ( +
    • + notification.{item}(config) +
    • + ))} +
    +

    config 参数如下:

    +
    +

    还提供了一个全局配置方法,在调用前提前配置,全局一次生效。

    +
      +
    • + notification.config(options) +
    • +
    +
    +        
    +          {code}
    +        
    +      
    +
    + + ); +} + +export default Api; diff --git a/src/demo/notification/demo0.tsx b/src/demo/notification/demo0.tsx new file mode 100644 index 0000000..da02e25 --- /dev/null +++ b/src/demo/notification/demo0.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { Button, Notification } from '../../../packages'; + +function Demo() { + const [api, contextHolder] = Notification.useNotification(); + const openNotification = () => { + api.info({ + message: 'Notification Title', + description: + 'This is the content of the notification. This is the content of the notification. This is the content of the notification.', + onClick: () => { + console.log('Notification Clicked!'); + }, + onClose: () => { + console.log('Notification Closed!'); + }, + }); + }; + return ( + <> +
    {contextHolder}
    + + + ); +} + +export default Demo; diff --git a/src/demo/notification/demo1.tsx b/src/demo/notification/demo1.tsx index 3b3cff2..24e33af 100644 --- a/src/demo/notification/demo1.tsx +++ b/src/demo/notification/demo1.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Button, notification } from '../../../packages'; +import { Button, Notification } from '../../../packages'; function Demo() { const openNotification = () => { - notification.open({ + Notification.open({ message: 'Notification Title', description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.', diff --git a/src/demo/notification/demo2.tsx b/src/demo/notification/demo2.tsx index 0ab19d0..3d5153f 100644 --- a/src/demo/notification/demo2.tsx +++ b/src/demo/notification/demo2.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Button, notification, Space } from '../../../packages'; +import { Button, Notification, Space } from '../../../packages'; function Demo() { const openNotification = (placement) => { - notification.open({ + Notification.open({ message: 'Notification Title', description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.', diff --git a/src/demo/notification/demo3.tsx b/src/demo/notification/demo3.tsx index 5347612..ef98500 100644 --- a/src/demo/notification/demo3.tsx +++ b/src/demo/notification/demo3.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Button, notification } from '../../../packages'; +import { Button, Notification } from '../../../packages'; function Demo() { const openNotification = () => { - notification.open({ + Notification.open({ message: 'Notification Title', description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.', diff --git a/src/demo/notification/demo4.tsx b/src/demo/notification/demo4.tsx index 8db8a4b..07750f3 100644 --- a/src/demo/notification/demo4.tsx +++ b/src/demo/notification/demo4.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Button, notification, Space } from '../../../packages'; +import { Button, Notification, Space } from '../../../packages'; function Demo() { const openNotification = (type) => { - notification[type]({ + Notification[type]({ message: 'Notification Title', description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.', diff --git a/src/demo/notification/demo5.tsx b/src/demo/notification/demo5.tsx index d8b5148..65ca21d 100644 --- a/src/demo/notification/demo5.tsx +++ b/src/demo/notification/demo5.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Button, notification, Space } from '../../../packages'; +import { Button, Notification, Space } from '../../../packages'; function Demo() { const openNotification = () => { - notification.open({ + Notification.open({ message: 'Notification Title', description: 'This is the content of the notification. This is the content of the notification. This is the content of the notification.', @@ -18,8 +18,9 @@ function Demo() { ; +} + +export default Demo; diff --git a/src/demo/popover/api.tsx b/src/demo/popover/api.tsx index 7f642a4..0f9ed33 100644 --- a/src/demo/popover/api.tsx +++ b/src/demo/popover/api.tsx @@ -76,7 +76,9 @@ function Api() { description: '触发行为', type: string, optional: ( - 'hover' | 'focus' | 'click' + + 'hover' | 'focus' | 'click' + ), default: 'hover', }, diff --git a/src/demo/popover/demo3.tsx b/src/demo/popover/demo3.tsx index e338c34..0be9559 100644 --- a/src/demo/popover/demo3.tsx +++ b/src/demo/popover/demo3.tsx @@ -6,7 +6,21 @@ function Demo() { const hide = () => { setVisible(false); }; - const content = Close; + const content = ( + + ); const onOpenChange = (e) => { setVisible(e); }; diff --git a/src/pages/packages/image.tsx b/src/pages/packages/image.tsx index 87f0f58..57bfa5a 100644 --- a/src/pages/packages/image.tsx +++ b/src/pages/packages/image.tsx @@ -1,25 +1,39 @@ /* @name:"Image 图片" - @group:"通用" + @group:"数据" */ import React from 'react'; import Title from '../../components/title'; import InstanceView from '../../layout/instanceView'; import Demo1 from '../../demo/image/demo1'; -import Api from '../../demo/image/api'; +import Demo2 from '../../demo/image/demo2'; +import Demo3 from '../../demo/image/demo3'; -export default function InputPage() { +export default function DemoPage() { return (
    - + <Title title="Image" notes="图片" desc="图片容器" /> <InstanceView subtitle="基本使用" subnotes="" demo={<Demo1 />} - subdesc="可通过fit确定图片如何适应到容器框,同原生 object-fit。" + subdesc="" path="image/demo1" /> - <Api /> + <InstanceView + subtitle="图片预览" + subnotes="" + demo={<Demo2 />} + subdesc="设置preview属性为true,点击图片即可预览" + path="image/demo2" + /> + <InstanceView + subtitle="多图预览" + subnotes="" + demo={<Demo3 />} + subdesc="设置previewList数组即可实现多图预览" + path="image/demo3" + /> </div> ); -} +} \ No newline at end of file diff --git a/src/pages/packages/mask.tsx b/src/pages/packages/mask.tsx new file mode 100644 index 0000000..366e90b --- /dev/null +++ b/src/pages/packages/mask.tsx @@ -0,0 +1,41 @@ +/* + @name:"Mask 遮罩" + @group:"通用" +*/ +import React from 'react'; +import Title from '../../components/title'; +import InstanceView from '../../layout/instanceView'; +import Demo1 from '../../demo/mask/demo1'; +import Demo2 from '../../demo/mask/demo2'; +import Demo3 from '../../demo/mask/demo3'; +import Api from '../../demo/mask/api'; + +export default function DemoPage() { + return ( + <div> + <Title title="Mask" notes="遮罩" desc="页面遮罩层" /> + <InstanceView + subtitle="基本示例" + subnotes="" + demo={<Demo1 />} + subdesc="" + path="mask/demo1" + /> + <InstanceView + subtitle="自定义Mask的背景颜色" + subnotes="" + demo={<Demo2 />} + subdesc="" + path="mask/demo2" + /> + <InstanceView + subtitle="定义Mask内的内容" + subnotes="" + demo={<Demo3 />} + subdesc="" + path="mask/demo3" + /> + <Api /> + </div> + ); +} \ No newline at end of file diff --git a/src/pages/packages/notification.tsx b/src/pages/packages/notification.tsx index b32d6f6..c07c576 100644 --- a/src/pages/packages/notification.tsx +++ b/src/pages/packages/notification.tsx @@ -1,77 +1,93 @@ -/* - @name:"Notification 通知提醒框" - @group:"反馈" -*/ -import React from 'react'; -import Title from '../../components/title'; -import InstanceView from '../../layout/instanceView'; -import Demo1 from '../../demo/notification/demo1'; -import Demo2 from '../../demo/notification/demo2'; -import Demo3 from '../../demo/notification/demo3'; -import Demo4 from '../../demo/notification/demo4'; -import Demo5 from '../../demo/notification/demo5'; -import Demo6 from '../../demo/notification/demo6'; -import Demo7 from '../../demo/notification/Demo7'; -import Api from '../../demo/notification/api'; - -export default function NotificationPage() { - return ( - <> - <Title - title="Notification" - notes="通知提醒框" - desc="全局展示通知提醒信息。" - /> - <InstanceView - subtitle="基本使用" - subnotes="" - demo={<Demo1 />} - subdesc="最简单的用法, 4.5 秒后自动关闭。" - path="notification/demo1" - /> - <InstanceView - subtitle="不同的弹出位置" - subnotes="" - demo={<Demo2 />} - subdesc="可以通过设置placement属性来控制弹出的位置。" - path="notification/demo2" - /> - <InstanceView - subtitle="自动关闭的延时" - subnotes="" - demo={<Demo3 />} - subdesc="自定义通知框自动关闭的延时,默认 4.5s,取消自动关闭只要将duration设为 0或null 即可。" - path="notification/demo3" - /> - <InstanceView - subtitle="带有图标的通知提醒框" - subnotes="" - demo={<Demo4 />} - subdesc="通知提醒框左侧有图标。" - path="notification/demo4" - /> - <InstanceView - subtitle="自定义按钮" - subnotes="" - demo={<Demo5 />} - subdesc="使用btn自定义关闭按钮的样式和文字。" - path="notification/demo5" - /> - <InstanceView - subtitle="自定义图标" - subnotes="" - demo={<Demo6 />} - subdesc="图标可以被自定义" - path="notification/demo6" - /> - <InstanceView - subtitle="自定义样式" - subnotes="" - demo={<Demo7 />} - subdesc="使用 style 和 className 来定义样式。" - path="notification/demo7" - /> - <Api /> - </> - ); -} +/* + @name:"Notification 通知提醒框" + @group:"反馈" +*/ +import React from 'react'; +import Title from '../../components/title'; +import InstanceView from '../../layout/instanceView'; +import Demo0 from '../../demo/notification/demo0'; +import Demo1 from '../../demo/notification/demo1'; +import Demo2 from '../../demo/notification/demo2'; +import Demo3 from '../../demo/notification/demo3'; +import Demo4 from '../../demo/notification/demo4'; +import Demo5 from '../../demo/notification/demo5'; +import Demo6 from '../../demo/notification/demo6'; +import Demo7 from '../../demo/notification/demo7'; +// import Demo8 from '../../demo/notification/demo8'; 打开可开启全局配置,为不影响其他demo,故隐藏 +import Api from '../../demo/notification/api'; + +export default function NotificationPage() { + return ( + <> + <Title + title="Notification" + notes="通知提醒框" + desc="全局展示通知提醒信息。" + /> + <InstanceView + subtitle="Hooks调用" + subnotes="" + demo={<Demo0 />} + subdesc="" + path="notification/demo0" + /> + <InstanceView + subtitle="基本使用" + subnotes="" + demo={<Demo1 />} + subdesc="最简单的用法, 4.5 秒后自动关闭。" + path="notification/demo1" + /> + <InstanceView + subtitle="不同的弹出位置" + subnotes="" + demo={<Demo2 />} + subdesc="可以通过设置placement属性来控制弹出的位置。" + path="notification/demo2" + /> + <InstanceView + subtitle="自动关闭的延时" + subnotes="" + demo={<Demo3 />} + subdesc="自定义通知框自动关闭的延时,默认 4.5s,取消自动关闭只要将duration设为 0或null 即可。" + path="notification/demo3" + /> + <InstanceView + subtitle="带有图标的通知提醒框" + subnotes="" + demo={<Demo4 />} + subdesc="通知提醒框左侧有图标。" + path="notification/demo4" + /> + <InstanceView + subtitle="自定义按钮" + subnotes="" + demo={<Demo5 />} + subdesc="使用btn自定义关闭按钮的样式和文字。" + path="notification/demo5" + /> + <InstanceView + subtitle="自定义图标" + subnotes="" + demo={<Demo6 />} + subdesc="图标可以被自定义" + path="notification/demo6" + /> + <InstanceView + subtitle="自定义样式" + subnotes="" + demo={<Demo7 />} + subdesc="使用 style 和 className 来定义样式。" + path="notification/demo7" + /> + {/* <InstanceView + subtitle="全局配置" + subnotes="" + demo={<Demo8 />} + subdesc="在调用前提前配置,全局一次生效" + path="notification/demo8" + /> */} + <Api /> + </> + ); +} diff --git a/src/pages/public/index.tsx b/src/pages/public/index.tsx index 63ad0ac..e41a3bd 100644 --- a/src/pages/public/index.tsx +++ b/src/pages/public/index.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useState, useRef } from 'react'; import { Route, Routes, useNavigate, useLocation } from 'react-router-dom'; import RouterPages from '../../router/pages/page'; import Menu from '../../components/menu'; -import '../../styles/custom.scss' -import { Backtop } from '../../../packages'; +import '../../styles/custom.scss'; +import { Backtop, Notification } from '../../../packages'; // 二级路由 export default function Index() { @@ -16,7 +16,7 @@ export default function Index() { const [menuIndex, setMenuIndex] = useState<any>( sessionStorage.getItem('menuIndex') ? sessionStorage.getItem('menuIndex') - : '0-0' || '0-0', + : '0-0' || '0-0' ); useEffect(() => { // 遍历menuList排除group===undefined的其他group相同的数据分组,放在同一个数组 @@ -35,8 +35,8 @@ export default function Index() { }); setTimeout(() => { setMenuData(arr); - }, 150) - }, 250) + }, 150); + }, 250); }, [menuList]); const handlerClick = (item: object, v: any, index: any) => { setMenuIndex(index); @@ -44,9 +44,24 @@ export default function Index() { sessionStorage.setItem('menuIndex', index); }; useEffect(() => { - setMenuIndex(sessionStorage.getItem('menuIndex') ? sessionStorage.getItem('menuIndex') : '0-0'); + setMenuIndex( + sessionStorage.getItem('menuIndex') + ? sessionStorage.getItem('menuIndex') + : '0-0' + ); (contentRef.current as HTMLDivElement).scrollTop = 0; - }, [location.pathname]) + }, [location.pathname]); + + useEffect(() => { + // 全局配置notification + // Notification.config({ + // bottom: 100, + // top: 300, + // duration: 4.5, + // placement: 'topRight', + // closeIcon: <i className="m-icon-success-filling" />, + // }); + }, []); return ( <div className="layoutBox"> <div className="menuBox"> @@ -56,29 +71,33 @@ export default function Index() { <div className="leftMenuBox"> {menuData && menuData.length ? menuData.map((item: any, index: number) => ( - <div key={index} className="groupItemBox"> - <p>{item.group}</p> - <div className="menuItem"> - {item.children.length - ? item.children.map((v: any, i: number) => ( - <li - key={i} - className={menuIndex === `${index}-${i}` ? 'active' : ''} - onClick={() => handlerClick(item, v, `${index}-${i}`)} - > - {v.name} - </li> - )) - : null} + <div key={index} className="groupItemBox"> + <p>{item.group}</p> + <div className="menuItem"> + {item.children.length + ? item.children.map((v: any, i: number) => ( + <li + key={i} + className={ + menuIndex === `${index}-${i}` ? 'active' : '' + } + onClick={() => + handlerClick(item, v, `${index}-${i}`) + } + > + {v.name} + </li> + )) + : null} + </div> </div> - </div> - )) + )) : null} </div> <div className="contentBox" ref={contentRef}> - { - location.pathname !== '/docs/react/backtop' ? <Backtop target=".contentBox" /> : null - } + {location.pathname !== '/docs/react/backtop' ? ( + <Backtop target=".contentBox" /> + ) : null} <Routes> {routerPage.map((item: any, index: number) => ( diff --git a/src/utils/example.js b/src/utils/example.js new file mode 100644 index 0000000..39138b2 --- /dev/null +++ b/src/utils/example.js @@ -0,0 +1,50 @@ +export const example = { + columns: [ + { + title: '姓名', + field: 'name', + align: 'left', + }, + { + title: '年龄', + field: 'age', + }, + { + title: '性别', + field: 'six', + }, + { + title: '学历', + field: 'gon', + }, + { + title: '住址', + field: 'add', + width: 300, + ellipsis: true, + }, + ], + dataSource: [ + { + name: '张三', + age: 18, + six: '男', + gon: '本科', + add: '北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区北京市朝阳区', + }, + { + name: '李四', + age: 18, + six: '男', + gon: '本科', + add: '北京市朝阳区', + }, + { + name: '王五', + age: 18, + six: '女', + gon: '硕士', + add: '北京市朝阳区', + }, + ], +}; diff --git a/src/utils/log.ts b/src/utils/log.ts index 24aba0c..d749bc1 100644 --- a/src/utils/log.ts +++ b/src/utils/log.ts @@ -216,4 +216,31 @@ export const updateLog = [ }, ], }, + { + version: '0.1.7', + time: '2022-12-21', + log: [ + { + title: '新增', + content: [ + '新增 Notification 通知提醒框组件', + '新增 Popover 气泡卡片组件', + ], + }, + { + title: '优化', + content: ['优化 Input 输入框组件'], + }, + ], + }, + { + version: '0.1.8', + time: '2023-01-13', + log: [ + { + title: '新增', + content: ['新增 Image 图片组件', '新增 Mask 遮罩组件'], + }, + ], + }, ]; diff --git a/src/utils/utils.tsx b/src/utils/utils.tsx index 9cdfa0b..7370210 100644 --- a/src/utils/utils.tsx +++ b/src/utils/utils.tsx @@ -42,4 +42,22 @@ export const getRouter = () => { }) }) return router +} +// 获取body偏移量 +export const getElOffSet = (curEle) => { + let totalLeft = null; + let totalTop = null; + let par = curEle.offsetParent; + totalLeft += curEle.offsetLeft; + totalTop += curEle.offsetTop; + while (par) { + if (navigator.userAgent.indexOf("MSIE 8.0") === -1) { + totalTop += par.clientTop; + totalLeft += par.clientLeft; + } + totalTop += par.offsetTop; + totalLeft += par.offsetLeft; + par = par.offsetParent; + } + return { left: totalLeft, top: totalTop }; } \ No newline at end of file