fix: 新增message、popover、slider、card、calendar组件
This commit is contained in:
parent
315d075c96
commit
43741393f7
@ -1,13 +1,32 @@
|
||||
#root .dumi-default-doc-layout .dumi-default-features-item {
|
||||
text-align: center;
|
||||
}
|
||||
/* stylelint-disable-next-line rule-empty-line-before */
|
||||
#root .dumi-default-doc-layout > main {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
#root .dumi-default-header {
|
||||
box-shadow: 2px 1px 16px #dfdfdf;
|
||||
}
|
||||
|
||||
#root .dumi-default-sidebar {
|
||||
width: 188px;
|
||||
overflow-y: hidden;
|
||||
/* stylelint-disable-next-line rule-empty-line-before */
|
||||
&:hover {
|
||||
overflow-y: auto;
|
||||
}
|
||||
/* stylelint-disable-next-line rule-empty-line-before */
|
||||
dd a {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
#root .dumi-default-navbar li {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#root .dumi-default-footer {
|
||||
display: none;
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { defineConfig } from 'father';
|
||||
|
||||
export default defineConfig({
|
||||
// more father config: https://github.com/umijs/father/blob/master/docs/config.md
|
||||
esm: { output: 'dist' },
|
||||
extraBabelPlugins: [
|
||||
[
|
||||
'import',
|
||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -2,13 +2,16 @@
|
||||
"cSpell.words": [
|
||||
"ahooks",
|
||||
"antd",
|
||||
"commitlint",
|
||||
"COMPATER",
|
||||
"constract",
|
||||
"dumi",
|
||||
"favicons",
|
||||
"flvjs",
|
||||
"indicatorsize",
|
||||
"lambo",
|
||||
"remuxer",
|
||||
"stylelint",
|
||||
"transmuxer",
|
||||
"zhst"
|
||||
]
|
||||
|
@ -13,7 +13,7 @@
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"add": "pnpm run build && pnpm changeset add",
|
||||
"add": "pnpm changeset add",
|
||||
"build": "pnpm --filter=@zhst/* run build",
|
||||
"build:watch": "father dev",
|
||||
"dev": "dumi dev",
|
||||
|
@ -1,5 +1,14 @@
|
||||
# @zhst/biz
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.6.0
|
||||
- @zhst/func@0.6.0
|
||||
- @zhst/meta@0.7.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/biz",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"description": "业务库",
|
||||
"keywords": [
|
||||
"business",
|
||||
|
@ -1,5 +1,11 @@
|
||||
# @zhst/biz
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- feat: 变更
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -1,6 +1,12 @@
|
||||
# @zhst/constants
|
||||
|
||||
:::info{title=开发中}
|
||||
稍等...
|
||||
:::
|
||||
|
||||
## 介绍
|
||||
|
||||
业务库
|
||||
静态变量库
|
||||
|
||||
## 安装
|
||||
|
||||
@ -11,5 +17,4 @@
|
||||
```js
|
||||
import React from 'react';
|
||||
import { TYPE } from '@zhst/constants'
|
||||
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/constants",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"description": "常量库",
|
||||
"keywords": [
|
||||
"constants",
|
||||
|
@ -1,5 +1,16 @@
|
||||
# @zhst/utils
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- feat: 变更
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/request@0.6.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -325,7 +325,6 @@ export var generateImg = function generateImg(_imgKey) {
|
||||
if (/(http|https):\/\/([\w.]+\/?)\S*/ig.test(imgKey)) {
|
||||
return imgKey;
|
||||
}
|
||||
console.log('imgKey', imgKey);
|
||||
try {
|
||||
if (matchS3Prefix(imgKey)) {
|
||||
imgKey = base64DecodeImageKey(imgKey);
|
||||
|
@ -7,4 +7,7 @@ export * from "./number";
|
||||
export * from "./time";
|
||||
export * from "./utils";
|
||||
export * from "./camera";
|
||||
export * from "./math";
|
||||
export * from "./math";
|
||||
export var a = function a(data) {
|
||||
console.log('data', data);
|
||||
};
|
@ -1,3 +1,4 @@
|
||||
var _ALGORITHM_VERSION;
|
||||
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
||||
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
||||
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
||||
@ -8,8 +9,8 @@ function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symb
|
||||
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
|
||||
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
||||
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); }
|
||||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
||||
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
|
||||
function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
|
||||
import { cloneDeep, get, isNull } from 'lodash-es';
|
||||
import { dataURLToBlob } from "../file";
|
||||
var proto = {
|
||||
@ -21,7 +22,7 @@ var proto = {
|
||||
}
|
||||
}
|
||||
};
|
||||
export var ALGORITHM_VERSION = _defineProperty(_defineProperty(_defineProperty({}, '7', '形体'), '4', '人脸'), '6', '非机动车');
|
||||
export var ALGORITHM_VERSION = (_ALGORITHM_VERSION = {}, _defineProperty(_ALGORITHM_VERSION, '7', '形体'), _defineProperty(_ALGORITHM_VERSION, '4', '人脸'), _defineProperty(_ALGORITHM_VERSION, '6', '非机动车'), _ALGORITHM_VERSION);
|
||||
export var algorithmVersions = _toConsumableArray(Object.keys(ALGORITHM_VERSION));
|
||||
export var getBikeExtendRect = function getBikeExtendRect(rect, maxW) {
|
||||
var newRect = _objectSpread({}, rect);
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/func",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"description": "函数合集",
|
||||
"keywords": [
|
||||
"hooks"
|
||||
|
@ -281,8 +281,6 @@ export const generateImg: (imgKey: string, host?: string) => string = (_imgKey,
|
||||
return imgKey;
|
||||
}
|
||||
|
||||
console.log('imgKey', imgKey)
|
||||
|
||||
try {
|
||||
if (matchS3Prefix(imgKey)) {
|
||||
imgKey = base64DecodeImageKey(imgKey);
|
||||
|
@ -8,3 +8,7 @@ export * from './time'
|
||||
export * from './utils'
|
||||
export * from './camera'
|
||||
export * from './math'
|
||||
|
||||
export const a = (data: string) => {
|
||||
console.log('data', data)
|
||||
}
|
||||
|
@ -1,5 +1,16 @@
|
||||
# @zhst/hooks
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- feat: 变更
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/func@0.6.0
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -1,4 +0,0 @@
|
||||
export declare const useUnActivateWrapper: (cb: () => void) => void;
|
||||
export declare const useActivateWrapper: (cb: any) => null;
|
||||
export declare const useActivateState: () => boolean;
|
||||
export default useActivateWrapper;
|
@ -6,6 +6,7 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
|
||||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { useActivate, useUnactivate } from 'react-activation';
|
||||
// @ts-ignore
|
||||
import { noop, isFunction } from '@zhst/func';
|
||||
import { useLatest, useMount, useUnmount } from 'ahooks';
|
||||
export var useUnActivateWrapper = function useUnActivateWrapper(cb) {
|
||||
|
0
packages/hooks/es/useSocket/index.d.ts
vendored
0
packages/hooks/es/useSocket/index.d.ts
vendored
@ -1,4 +0,0 @@
|
||||
export declare const useUnActivateWrapper: (cb: () => void) => void;
|
||||
export declare const useActivateWrapper: (cb: any) => null;
|
||||
export declare const useActivateState: () => boolean;
|
||||
export default useActivateWrapper;
|
0
packages/hooks/lib/useSocket/index.d.ts
vendored
0
packages/hooks/lib/useSocket/index.d.ts
vendored
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/hooks",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"description": "hooks合集",
|
||||
"keywords": [
|
||||
"hooks"
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { useActivate, useUnactivate } from 'react-activation';
|
||||
// @ts-ignore
|
||||
import { noop, isFunction } from '@zhst/func';
|
||||
import { useLatest, useMount, useUnmount } from 'ahooks';
|
||||
|
||||
|
@ -1,5 +1,15 @@
|
||||
# @zhst/biz
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.6.0
|
||||
- @zhst/func@0.6.0
|
||||
- @zhst/meta@0.7.0
|
||||
- @zhst/biz@0.5.1
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/material",
|
||||
"version": "0.5.0",
|
||||
"version": "0.5.1",
|
||||
"description": "物料库",
|
||||
"keywords": [
|
||||
"business",
|
||||
|
@ -1,5 +1,18 @@
|
||||
# @zhst/utils
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- feat: 变更
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- @zhst/hooks@0.6.0
|
||||
- @zhst/func@0.6.0
|
||||
- @zhst/meta@0.7.0
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
@ -10,12 +10,11 @@
|
||||
|
||||
```jsx
|
||||
import React from 'react';
|
||||
import { Button } from 'antd'
|
||||
import { doubleClick } from '@zhst/meta'
|
||||
import { Button } from '@zhst/meta'
|
||||
|
||||
export default () => {
|
||||
return (
|
||||
<Button type="primary" onClick={() => doubleClick()} >按我啊!</Button>
|
||||
<Button type="primary" onClick={() => console.log('ok')} >按我啊!</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
@ -24,9 +24,11 @@ getExtendRect,
|
||||
// @ts-ignore
|
||||
getTransformRect,
|
||||
// @ts-ignore
|
||||
getRotateImg, getTransforms, addEventListenerWrapper, getFileByRect } from '@zhst/func';
|
||||
getRotateImg, getTransforms, addEventListenerWrapper, getFileByRect
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { Button, Empty } from 'antd';
|
||||
import { Button, Empty } from '..';
|
||||
import Icon from "../iconfont";
|
||||
import { Cropper, Viewer, EVENT_VIEWER_TRANSFORM_CHANGE, EVENT_VIEWER_READY, EVENT_CROP_START, EVENT_CROP_END } from "../ImageEditor";
|
||||
import BtnGroup from "./components/BtnGroup";
|
||||
|
@ -5,6 +5,7 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
|
||||
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
||||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
||||
import React, { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react';
|
||||
// @ts-ignore
|
||||
import { generateImg, get, addEventListenerWrapper } from '@zhst/func';
|
||||
import { useUpdateEffect } from '@zhst/hooks';
|
||||
import Button from "../button";
|
||||
|
@ -14,12 +14,14 @@ function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len
|
||||
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
|
||||
function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
|
||||
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
|
||||
import { noop, get, addEventListenerWrapper, dataURLToBlob, nextTick, toRealNumber, getTransforms, formatDurationTime } from '@zhst/func';
|
||||
import { noop, get, addEventListenerWrapper, dataURLToBlob, nextTick, toRealNumber, getTransforms, formatDurationTime
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { useLatest, useUpdateEffect, useFullscreen, useUnmount } from '@zhst/hooks';
|
||||
import classNames from 'classnames';
|
||||
import download from 'downloadjs';
|
||||
import { Button, message } from 'antd';
|
||||
import { Button, message } from '..';
|
||||
import Icon from "../iconfont";
|
||||
import { Cropper, EVENT_CROP_START, EVENT_CROP_END } from "../ImageEditor";
|
||||
import FlvPlayer, { FLV_EVENT } from "./components/FlvPlayer";
|
||||
|
@ -19,6 +19,7 @@ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol"
|
||||
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
||||
import React, { Component } from 'react';
|
||||
import flvjs from 'flv.js';
|
||||
// @ts-ignore
|
||||
import { isEqual } from '@zhst/func';
|
||||
export var FLV_EVENT = flvjs.Events;
|
||||
var VideoPlayer = /*#__PURE__*/function (_Component) {
|
||||
|
@ -4,14 +4,16 @@ export { default as BigImagePreview } from "./BigImagePreview";
|
||||
export { default as VideoPlayer } from "./VideoPlayer";
|
||||
export { default as Tabs } from "./tabs";
|
||||
export { default as Button } from "./button";
|
||||
export { default as message } from "./message";
|
||||
export { default as Space } from "./space";
|
||||
export { default as Slider } from "./slider";
|
||||
export { default as Switch } from "./switch";
|
||||
export { default as Grid } from "./grid";
|
||||
export { default as Row } from "./row";
|
||||
export { default as Col } from "./col";
|
||||
export { default as TimePicker } from "./time-picker";
|
||||
export { default as DatePicker } from "./date-picker";
|
||||
export { default as Calender } from "./calendar";
|
||||
export { default as Calendar } from "./calendar";
|
||||
export { default as Empty } from "./empty";
|
||||
export { default as Form } from "./form";
|
||||
export { default as Select } from "./select";
|
||||
@ -29,4 +31,14 @@ export { default as Divider } from "./divider";
|
||||
export { default as Descriptions } from "./descriptions";
|
||||
export { default as Flex } from "./flex";
|
||||
export { default as Score } from "./score";
|
||||
export { default as Progress } from "./progress";
|
||||
export { default as Progress } from "./progress";
|
||||
export { default as theme } from "./theme";
|
||||
export { default as Badge } from "./badge";
|
||||
export { default as Alert } from "./alert";
|
||||
export { default as Popover } from "./popover";
|
||||
export { default as Avatar } from "./avatar";
|
||||
export { default as Card } from "./card";
|
||||
export { default as Skeleton } from "./skeleton";
|
||||
export { default as Tooltip } from "./tooltip";
|
||||
export { default as Tour } from "./tour";
|
||||
export { default as Segmented } from "./segmented";
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zhst/meta",
|
||||
"version": "0.6.0",
|
||||
"version": "0.7.0",
|
||||
"description": "原子组件",
|
||||
"keywords": [
|
||||
"meta",
|
||||
@ -81,6 +81,7 @@
|
||||
"@ant-design/icons": "^5.2.6",
|
||||
"@ctrl/tinycolor": "^4.0.2",
|
||||
"@rc-component/mutate-observer": "^1.1.0",
|
||||
"@rc-component/tour": "^1.12.3",
|
||||
"@rc-component/trigger": "^1.18.2",
|
||||
"@turf/boolean-point-in-polygon": "^6.5.0",
|
||||
"@turf/turf": "^6.5.0",
|
||||
@ -96,6 +97,7 @@
|
||||
"dayjs": "^1.11.10",
|
||||
"downloadjs": "^1.4.7",
|
||||
"flv.js": "^1.6.2",
|
||||
"lunar-typescript": "^1.7.3",
|
||||
"rc-align": "^4.0.15",
|
||||
"rc-cascader": "~3.20.0",
|
||||
"rc-checkbox": "~3.1.0",
|
||||
|
@ -18,9 +18,10 @@ import {
|
||||
getTransforms,
|
||||
addEventListenerWrapper,
|
||||
getFileByRect
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { Button, Empty } from 'antd';
|
||||
import { Button, Empty } from '..';
|
||||
import { type Rect, type IScreenshotButtonProp, type AlignType, type IOdRectOrigin } from '@zhst/types'
|
||||
import Icon from '../iconfont';
|
||||
import {
|
||||
|
68
packages/meta/src/BigImagePreview/demo/base.tsx
Normal file
68
packages/meta/src/BigImagePreview/demo/base.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { Button, Space } from 'antd'
|
||||
import { BigImagePreview } from '@zhst/meta'
|
||||
|
||||
|
||||
const props = {
|
||||
heigth: '500px',
|
||||
data: {
|
||||
imageKey: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||||
odRect:{
|
||||
"x":0.553125,"y":0.29722223,"w":0.048958335,"h":0.2462963
|
||||
},
|
||||
attachImg: [
|
||||
{
|
||||
"url": "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png","label": "形体"
|
||||
},{
|
||||
"url": "https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png",
|
||||
"label": "人脸"
|
||||
}
|
||||
],
|
||||
score: 0.815207, // 人脸质量分
|
||||
showScore: true, // 人脸质量分
|
||||
cameraPosition: 'string', // 摄像头位置
|
||||
time: '2022-01-01', // 摄像头拍摄时间
|
||||
objects: [
|
||||
{
|
||||
"infoOnSource": {
|
||||
"bboxInFrame": {
|
||||
"bboxRatio": {
|
||||
"x": 0.5519352,
|
||||
"y": 0.2965385,
|
||||
"w": 0.05185461,
|
||||
"h": 0.24698898
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"infoOnSource": {
|
||||
"bboxInFrame": {
|
||||
"bboxRatio": {
|
||||
"x": 0.58543766,
|
||||
"y": 0.3203356,
|
||||
"w": 0.052037954,
|
||||
"h": 0.2664015
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const imgRef = useRef(null)
|
||||
|
||||
return (
|
||||
<Space size={[8, 16]} direction="vertical">
|
||||
<BigImagePreview {...props} ref={imgRef} />
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => imgRef.current?.setShowCrop(true)}>编辑</Button>
|
||||
<Button onClick={() => imgRef.current?.handleChangeIndex(1)}>下一张</Button>
|
||||
<Button onClick={() => imgRef.current?.setShowCrop(false)}>取消</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
group: 通用
|
||||
group: 媒体
|
||||
category: Components
|
||||
subtitle: 大图预览组件
|
||||
title: BigImagePreview 大图预览组件
|
||||
@ -7,180 +7,7 @@ title: BigImagePreview 大图预览组件
|
||||
|
||||
# BigImagePreview 大图预览组件
|
||||
|
||||
```jsx
|
||||
import React, { useState, useRef } from 'react';
|
||||
import { Button, Space } from 'antd'
|
||||
import { BigImagePreview } from '@zhst/meta'
|
||||
|
||||
|
||||
const props = {
|
||||
heigth: '500px',
|
||||
dataSource: [
|
||||
{
|
||||
imageKey: 'http://10.0.0.120:30003/file/singer-20240110/1/5/1744894622934503424.jpg',
|
||||
odRect:{
|
||||
"x":0.5445312,
|
||||
"y":0.19166666,
|
||||
"w":0.08671875,
|
||||
"h":0.40138888
|
||||
},
|
||||
attachImg: [
|
||||
{
|
||||
"url": "http://10.0.0.120:30003/file/singer-20240110/1/5/1744894622695428096.jpg","label": "形体"
|
||||
},{
|
||||
"url": "http://10.0.0.120:30003/file/singer-20240110/1/5/1744894588427964418.jpg",
|
||||
"label": "人脸"
|
||||
}
|
||||
],
|
||||
score: '0.6', // 人脸质量分
|
||||
showScore: true, // 人脸质量分
|
||||
cameraPosition: 'string', // 摄像头位置
|
||||
time: '2022-01-01', // 摄像头拍摄时间
|
||||
objects: [
|
||||
{
|
||||
"objectIndex": {
|
||||
"objectId": "1746832189053474816",
|
||||
"solutionId": "0",
|
||||
"deviceId": "0",
|
||||
"fragmentId": "0"
|
||||
},
|
||||
"objectType": "OBJECT_TYPE_PEDESTRAIN",
|
||||
"sourceObjectId": "0",
|
||||
"level": 0,
|
||||
"confidence": 0.881164,
|
||||
"frameInfo": {
|
||||
"frameId": "0",
|
||||
"frameTimestamp": "1705312223057",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"originWidth": 0,
|
||||
"originHeight": 0,
|
||||
"offsetTime": "0",
|
||||
"skipNumber": "0"
|
||||
},
|
||||
"infoOnSource": {
|
||||
"bboxInFrame": {
|
||||
"bboxRatio": {
|
||||
"x": 0.61418945,
|
||||
"y": 0.34309354,
|
||||
"w": 0.067661405,
|
||||
"h": 0.34659258
|
||||
},
|
||||
},
|
||||
"countInSource": 0,
|
||||
"indexInSource": 0
|
||||
},
|
||||
"qualityScore": 0,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
imageKey: 'http://10.0.0.120:30003/file/singer-20240115/1/9/1746795581994436608.jpg',
|
||||
odRect:{
|
||||
"x":0.553125,"y":0.29722223,"w":0.048958335,"h":0.2462963
|
||||
},
|
||||
attachImg: [
|
||||
{
|
||||
"url": "http://10.0.0.120:30003/file/singer-20240115/1/9/1746795581163964416.jpg","label": "形体"
|
||||
},{
|
||||
"url": "http://10.0.0.120:30003/file/singer-20240115/1/9/1746795546867140608.jpg",
|
||||
"label": "人脸"
|
||||
}
|
||||
],
|
||||
score: 0.815207, // 人脸质量分
|
||||
showScore: true, // 人脸质量分
|
||||
cameraPosition: 'string', // 摄像头位置
|
||||
time: '2022-01-01', // 摄像头拍摄时间
|
||||
objects: [
|
||||
{
|
||||
"objectIndex": {
|
||||
"objectId": "1746816737430472704",
|
||||
"solutionId": "0",
|
||||
"deviceId": "0",
|
||||
"fragmentId": "0"
|
||||
},
|
||||
"objectType": "OBJECT_TYPE_PEDESTRAIN",
|
||||
"sourceObjectId": "0",
|
||||
"frameInfo": {
|
||||
"frameId": "0",
|
||||
"frameTimestamp": "1705308539109",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"originWidth": 0,
|
||||
"originHeight": 0,
|
||||
"offsetTime": "0",
|
||||
"skipNumber": "0"
|
||||
},
|
||||
"infoOnSource": {
|
||||
"bboxInFrame": {
|
||||
"bboxRatio": {
|
||||
"x": 0.5519352,
|
||||
"y": 0.2965385,
|
||||
"w": 0.05185461,
|
||||
"h": 0.24698898
|
||||
},
|
||||
},
|
||||
"countInSource": 0,
|
||||
"indexInSource": 0
|
||||
},
|
||||
"qualityScore": 0,
|
||||
},
|
||||
{
|
||||
"objectIndex": {
|
||||
"objectId": "1746816737430472705",
|
||||
"solutionId": "0",
|
||||
"deviceId": "0",
|
||||
"fragmentId": "0"
|
||||
},
|
||||
"objectType": "OBJECT_TYPE_PEDESTRAIN",
|
||||
"sourceObjectId": "0",
|
||||
"level": 0,
|
||||
"confidence": 0.9310699,
|
||||
"frameInfo": {
|
||||
"frameId": "0",
|
||||
"frameTimestamp": "1705308539109",
|
||||
"width": 0,
|
||||
"height": 0,
|
||||
"originWidth": 0,
|
||||
"originHeight": 0,
|
||||
"offsetTime": "0",
|
||||
"skipNumber": "0"
|
||||
},
|
||||
"infoOnSource": {
|
||||
"bboxInFrame": {
|
||||
"bboxRatio": {
|
||||
"x": 0.58543766,
|
||||
"y": 0.3203356,
|
||||
"w": 0.052037954,
|
||||
"h": 0.2664015
|
||||
},
|
||||
},
|
||||
"countInSource": 0,
|
||||
"indexInSource": 0
|
||||
},
|
||||
"qualityScore": 0,
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const imgRef = useRef(null)
|
||||
|
||||
return (
|
||||
<Space size={[8, 16]} direction="vertical">
|
||||
<BigImagePreview {...props} ref={imgRef} />
|
||||
<Space>
|
||||
<Button type="primary" onClick={() => imgRef.current?.setShowCrop(true)}>编辑</Button>
|
||||
<Button onClick={() => imgRef.current?.handleChangeIndex(1)}>下一张</Button>
|
||||
<Button onClick={() => imgRef.current?.setShowCrop(false)}>取消</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
```
|
||||
<code src="./demo/base.tsx">基本</code>
|
||||
|
||||
## API
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useRef, useState, useEffect, forwardRef, useImperativeHandle } from 'react'
|
||||
// @ts-ignore
|
||||
import { generateImg, get, addEventListenerWrapper } from '@zhst/func';
|
||||
import { useUpdateEffect } from '@zhst/hooks';
|
||||
import Button from '../button';
|
||||
|
30
packages/meta/src/CompareImage/demo/basic.tsx
Normal file
30
packages/meta/src/CompareImage/demo/basic.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { CompareImage, Space, Button } from '@zhst/meta'
|
||||
|
||||
const props = {
|
||||
label: "目标图",
|
||||
openRoll:true,
|
||||
dataSource:[
|
||||
{
|
||||
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||||
score: '0.5'
|
||||
},
|
||||
{
|
||||
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
|
||||
score: '0.8'
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const ref = useRef(null)
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Button onClick={() => ref.current?.handleIndexChange(-1)}>上一张</Button>
|
||||
<Button onClick={() => ref.current?.handleIndexChange(1)}>下一张</Button>
|
||||
<CompareImage {...props} ref={ref} />
|
||||
</Space>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
group: 通用
|
||||
group: 媒体
|
||||
category: Components
|
||||
subtitle: 图片预览
|
||||
title: CompareImage 图片预览
|
||||
@ -7,37 +7,7 @@ title: CompareImage 图片预览
|
||||
|
||||
# CompareImage 图片预览
|
||||
|
||||
```jsx
|
||||
import React, { useRef } from 'react';
|
||||
import { CompareImage, Space, Button } from '@zhst/meta'
|
||||
|
||||
const props = {
|
||||
label: "目标图",
|
||||
openRoll:true,
|
||||
dataSource:[
|
||||
{
|
||||
url: 'http://10.0.0.120:30003/file/public/public_20240110/file/60f26318-19fc-40b3-b249-91bd2a4da3f3.jpg',
|
||||
score: '0.5'
|
||||
},
|
||||
{
|
||||
url: 'http://10.0.0.120:30003/file/public/public_20240110/file/7c9b33a8-5106-4982-9d58-a593ad8acbf6.jpg',
|
||||
score: '0.8'
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default () => {
|
||||
const ref = useRef(null)
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Button onClick={() => ref.current?.handleIndexChange(-1)}>上一张</Button>
|
||||
<Button onClick={() => ref.current?.handleIndexChange(1)}>下一张</Button>
|
||||
<CompareImage {...props} ref={ref} />
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
```
|
||||
<code src="./demo/basic.tsx">基本</code>
|
||||
|
||||
## API
|
||||
|
||||
|
@ -8,13 +8,14 @@ import {
|
||||
toRealNumber,
|
||||
getTransforms,
|
||||
formatDurationTime
|
||||
// @ts-ignore
|
||||
} from '@zhst/func';
|
||||
import Align from 'rc-align';
|
||||
import { Rect, IScreenshotButtonProp, AlignType } from '@zhst/types'
|
||||
import { useLatest, useUpdateEffect, useFullscreen, useUnmount } from '@zhst/hooks';
|
||||
import classNames from 'classnames';
|
||||
import download from 'downloadjs';
|
||||
import { Button, message } from 'antd';
|
||||
import { Button, message } from '..';
|
||||
import Icon from '../iconfont';
|
||||
import {
|
||||
Cropper,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import React, { Component, CSSProperties } from 'react';
|
||||
import flvjs from 'flv.js';
|
||||
// @ts-ignore
|
||||
import { isEqual } from '@zhst/func';
|
||||
|
||||
export const FLV_EVENT = flvjs.Events;
|
||||
|
18
packages/meta/src/VideoPlayer/demo/basic.tsx
Normal file
18
packages/meta/src/VideoPlayer/demo/basic.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { VideoPlayer, Space, Button } from '@zhst/meta'
|
||||
|
||||
export default () => {
|
||||
const videoRef = useRef(null)
|
||||
const [url, setUrl] = useState(null)
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Space>
|
||||
<Button onClick={() => setUrl('ws://10.0.0.7:9033/flv/File/test/test_h264_1.mp4.flv?ip=127.0.0.1')}>播放</Button>
|
||||
<Button onClick={() => videoRef.current?.setShowCrop(true)}>截图</Button>
|
||||
<Button onClick={() => videoRef.current?.setShowCrop(false)}>取消</Button>
|
||||
</Space>
|
||||
{url && <VideoPlayer ref={videoRef} url={url} />}
|
||||
</Space>
|
||||
)
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
group: 通用
|
||||
group: 媒体
|
||||
category: Components
|
||||
subtitle: 视频播放
|
||||
title: VideoPlayer 视频播放
|
||||
@ -13,24 +13,7 @@ demo:
|
||||
播放进度条操作还未完善,在播放结束和拖拽的过程中都有一定程度卡顿,目前逻辑待优化
|
||||
:::
|
||||
|
||||
```jsx
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { VideoPlayer, Space, Button } from '@zhst/meta'
|
||||
|
||||
export default () => {
|
||||
const videoRef = useRef(null)
|
||||
const [url, setUrl] = useState(null)
|
||||
|
||||
return (
|
||||
<Space>
|
||||
<Button onClick={() => setUrl('ws://10.0.0.7:9033/flv/File/test/test_h264_1.mp4.flv?ip=127.0.0.1')}>播放</Button>
|
||||
<Button onClick={() => videoRef.current?.setShowCrop(true)}>截图</Button>
|
||||
<Button onClick={() => videoRef.current?.setShowCrop(false)}>取消</Button>
|
||||
<VideoPlayer ref={videoRef} url={url} />
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
```
|
||||
<code src="./demo/basic.tsx">基础</code>
|
||||
|
||||
<code src="./demo/multiple.tsx">测试视频播放压力</code>
|
||||
|
||||
|
228
packages/meta/src/alert/Alert.tsx
Normal file
228
packages/meta/src/alert/Alert.tsx
Normal file
@ -0,0 +1,228 @@
|
||||
import type { ReactElement } from 'react';
|
||||
import * as React from 'react';
|
||||
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
|
||||
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
|
||||
import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
|
||||
import classNames from 'classnames';
|
||||
import CSSMotion from 'rc-motion';
|
||||
import pickAttrs from 'rc-util/lib/pickAttrs';
|
||||
|
||||
import { replaceElement } from '../_util/reactNode';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import useStyle from './style';
|
||||
|
||||
export interface AlertProps {
|
||||
/** Type of Alert styles, options:`success`, `info`, `warning`, `error` */
|
||||
type?: 'success' | 'info' | 'warning' | 'error';
|
||||
/** Whether Alert can be closed */
|
||||
closable?: boolean;
|
||||
/**
|
||||
* @deprecated please use `closeIcon` instead.
|
||||
* Close text to show
|
||||
*/
|
||||
closeText?: React.ReactNode;
|
||||
/** Content of Alert */
|
||||
message?: React.ReactNode;
|
||||
/** Additional content of Alert */
|
||||
description?: React.ReactNode;
|
||||
/** Callback when close Alert */
|
||||
onClose?: React.MouseEventHandler<HTMLButtonElement>;
|
||||
/** Trigger when animation ending of Alert */
|
||||
afterClose?: () => void;
|
||||
/** Whether to show icon */
|
||||
showIcon?: boolean;
|
||||
/** https://www.w3.org/TR/2014/REC-html5-20141028/dom.html#aria-role-attribute */
|
||||
role?: string;
|
||||
style?: React.CSSProperties;
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
banner?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
/** Custom closeIcon */
|
||||
closeIcon?: boolean | React.ReactNode;
|
||||
action?: React.ReactNode;
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const iconMapFilled = {
|
||||
success: CheckCircleFilled,
|
||||
info: InfoCircleFilled,
|
||||
error: CloseCircleFilled,
|
||||
warning: ExclamationCircleFilled,
|
||||
};
|
||||
|
||||
interface IconNodeProps {
|
||||
type: AlertProps['type'];
|
||||
icon: AlertProps['icon'];
|
||||
prefixCls: AlertProps['prefixCls'];
|
||||
description: AlertProps['description'];
|
||||
}
|
||||
|
||||
const IconNode: React.FC<IconNodeProps> = (props) => {
|
||||
const { icon, prefixCls, type } = props;
|
||||
const iconType = iconMapFilled[type!] || null;
|
||||
if (icon) {
|
||||
return replaceElement(icon, <span className={`${prefixCls}-icon`}>{icon}</span>, () => ({
|
||||
className: classNames(`${prefixCls}-icon`, {
|
||||
[(icon as ReactElement).props.className]: (icon as ReactElement).props.className,
|
||||
}),
|
||||
})) as ReactElement;
|
||||
}
|
||||
return React.createElement(iconType, { className: `${prefixCls}-icon` });
|
||||
};
|
||||
|
||||
interface CloseIconProps {
|
||||
isClosable: boolean;
|
||||
prefixCls: AlertProps['prefixCls'];
|
||||
closeIcon: AlertProps['closeIcon'];
|
||||
handleClose: AlertProps['onClose'];
|
||||
}
|
||||
|
||||
const CloseIcon: React.FC<CloseIconProps> = (props) => {
|
||||
const { isClosable, prefixCls, closeIcon, handleClose } = props;
|
||||
const mergedCloseIcon =
|
||||
closeIcon === true || closeIcon === undefined ? <CloseOutlined /> : closeIcon;
|
||||
return isClosable ? (
|
||||
<button type="button" onClick={handleClose} className={`${prefixCls}-close-icon`} tabIndex={0}>
|
||||
{mergedCloseIcon}
|
||||
</button>
|
||||
) : null;
|
||||
};
|
||||
|
||||
const Alert: React.FC<AlertProps> = (props) => {
|
||||
const {
|
||||
description,
|
||||
prefixCls: customizePrefixCls,
|
||||
message,
|
||||
banner,
|
||||
className,
|
||||
rootClassName,
|
||||
style,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onClick,
|
||||
afterClose,
|
||||
showIcon,
|
||||
closable,
|
||||
closeText,
|
||||
closeIcon,
|
||||
action,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const [closed, setClosed] = React.useState(false);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Alert');
|
||||
warning.deprecated(!closeText, 'closeText', 'closeIcon');
|
||||
}
|
||||
|
||||
const ref = React.useRef<HTMLDivElement>(null);
|
||||
const { getPrefixCls, direction, alert } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('alert', customizePrefixCls);
|
||||
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
|
||||
|
||||
const handleClose = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setClosed(true);
|
||||
props.onClose?.(e);
|
||||
};
|
||||
|
||||
const type = React.useMemo<AlertProps['type']>(() => {
|
||||
if (props.type !== undefined) {
|
||||
return props.type;
|
||||
}
|
||||
// banner mode defaults to 'warning'
|
||||
return banner ? 'warning' : 'info';
|
||||
}, [props.type, banner]);
|
||||
|
||||
// closeable when closeText or closeIcon is assigned
|
||||
const isClosable = React.useMemo(() => {
|
||||
if (closeText) {
|
||||
return true;
|
||||
}
|
||||
if (typeof closable === 'boolean') {
|
||||
return closable;
|
||||
}
|
||||
// should be true when closeIcon is 0 or ''
|
||||
return closeIcon !== false && closeIcon !== null && closeIcon !== undefined;
|
||||
}, [closeText, closeIcon, closable]);
|
||||
|
||||
// banner mode defaults to Icon
|
||||
const isShowIcon = banner && showIcon === undefined ? true : showIcon;
|
||||
|
||||
const alertCls = classNames(
|
||||
prefixCls,
|
||||
`${prefixCls}-${type}`,
|
||||
{
|
||||
[`${prefixCls}-with-description`]: !!description,
|
||||
[`${prefixCls}-no-icon`]: !isShowIcon,
|
||||
[`${prefixCls}-banner`]: !!banner,
|
||||
[`${prefixCls}-rtl`]: direction === 'rtl',
|
||||
},
|
||||
alert?.className,
|
||||
className,
|
||||
rootClassName,
|
||||
cssVarCls,
|
||||
hashId,
|
||||
);
|
||||
|
||||
const restProps = pickAttrs(otherProps, { aria: true, data: true });
|
||||
|
||||
return wrapCSSVar(
|
||||
<CSSMotion
|
||||
visible={!closed}
|
||||
motionName={`${prefixCls}-motion`}
|
||||
motionAppear={false}
|
||||
motionEnter={false}
|
||||
onLeaveStart={(node) => ({ maxHeight: node.offsetHeight })}
|
||||
onLeaveEnd={afterClose}
|
||||
>
|
||||
{({ className: motionClassName, style: motionStyle }) => (
|
||||
<div
|
||||
ref={ref}
|
||||
data-show={!closed}
|
||||
className={classNames(alertCls, motionClassName)}
|
||||
style={{ ...alert?.style, ...style, ...motionStyle }}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onClick={onClick}
|
||||
role="alert"
|
||||
{...restProps}
|
||||
>
|
||||
{isShowIcon ? (
|
||||
<IconNode
|
||||
description={description}
|
||||
icon={props.icon}
|
||||
prefixCls={prefixCls}
|
||||
type={type}
|
||||
/>
|
||||
) : null}
|
||||
<div className={`${prefixCls}-content`}>
|
||||
{message ? <div className={`${prefixCls}-message`}>{message}</div> : null}
|
||||
{description ? <div className={`${prefixCls}-description`}>{description}</div> : null}
|
||||
</div>
|
||||
{action ? <div className={`${prefixCls}-action`}>{action}</div> : null}
|
||||
<CloseIcon
|
||||
isClosable={isClosable}
|
||||
prefixCls={prefixCls}
|
||||
closeIcon={closeText || closeIcon}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</CSSMotion>,
|
||||
);
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Alert.displayName = 'Alert';
|
||||
}
|
||||
|
||||
export default Alert;
|
50
packages/meta/src/alert/ErrorBoundary.tsx
Normal file
50
packages/meta/src/alert/ErrorBoundary.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import * as React from 'react';
|
||||
import Alert from './Alert';
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
message?: React.ReactNode;
|
||||
description?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryStates {
|
||||
error?: Error | null;
|
||||
info?: {
|
||||
componentStack?: string;
|
||||
};
|
||||
}
|
||||
|
||||
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryStates> {
|
||||
state = {
|
||||
error: undefined,
|
||||
info: {
|
||||
componentStack: '',
|
||||
},
|
||||
};
|
||||
|
||||
componentDidCatch(error: Error | null, info: object) {
|
||||
this.setState({ error, info });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { message, description, children } = this.props;
|
||||
const { error, info } = this.state;
|
||||
const componentStack = info && info.componentStack ? info.componentStack : null;
|
||||
const errorMessage = typeof message === 'undefined' ? (error || '').toString() : message;
|
||||
const errorDescription = typeof description === 'undefined' ? componentStack : description;
|
||||
if (error) {
|
||||
return (
|
||||
<Alert
|
||||
type="error"
|
||||
message={errorMessage}
|
||||
description={
|
||||
<pre style={{ fontSize: '0.9em', overflowX: 'auto' }}>{errorDescription}</pre>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
File diff suppressed because it is too large
Load Diff
1598
packages/meta/src/alert/__tests__/__snapshots__/demo.test.ts.snap
Normal file
1598
packages/meta/src/alert/__tests__/__snapshots__/demo.test.ts.snap
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,88 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Alert custom action 1`] = `
|
||||
<div
|
||||
class="ant-alert ant-alert-success"
|
||||
data-show="true"
|
||||
role="alert"
|
||||
>
|
||||
<span
|
||||
aria-label="check-circle"
|
||||
class="anticon anticon-check-circle ant-alert-icon"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="check-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<div
|
||||
class="ant-alert-content"
|
||||
>
|
||||
<div
|
||||
class="ant-alert-message"
|
||||
>
|
||||
Success Tips
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ant-alert-action"
|
||||
>
|
||||
<button
|
||||
class="ant-btn ant-btn-text ant-btn-sm"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
UNDO
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="ant-alert-close-icon"
|
||||
tabindex="0"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-label="close"
|
||||
class="anticon anticon-close"
|
||||
role="img"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="close"
|
||||
fill="currentColor"
|
||||
fill-rule="evenodd"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M799.86 166.31c.02 0 .04.02.08.06l57.69 57.7c.04.03.05.05.06.08a.12.12 0 010 .06c0 .03-.02.05-.06.09L569.93 512l287.7 287.7c.04.04.05.06.06.09a.12.12 0 010 .07c0 .02-.02.04-.06.08l-57.7 57.69c-.03.04-.05.05-.07.06a.12.12 0 01-.07 0c-.03 0-.05-.02-.09-.06L512 569.93l-287.7 287.7c-.04.04-.06.05-.09.06a.12.12 0 01-.07 0c-.02 0-.04-.02-.08-.06l-57.69-57.7c-.04-.03-.05-.05-.06-.07a.12.12 0 010-.07c0-.03.02-.05.06-.09L454.07 512l-287.7-287.7c-.04-.04-.05-.06-.06-.09a.12.12 0 010-.07c0-.02.02-.04.06-.08l57.7-57.69c.03-.04.05-.05.07-.06a.12.12 0 01.07 0c.03 0 .05.02.09.06L512 454.07l287.7-287.7c.04-.04.06-.05.09-.06a.12.12 0 01.07 0z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Alert rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<div
|
||||
class="ant-alert ant-alert-info ant-alert-no-icon ant-alert-rtl"
|
||||
data-show="true"
|
||||
role="alert"
|
||||
>
|
||||
<div
|
||||
class="ant-alert-content"
|
||||
/>
|
||||
</div>
|
||||
`;
|
3
packages/meta/src/alert/__tests__/demo-extend.test.ts
Normal file
3
packages/meta/src/alert/__tests__/demo-extend.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('alert', { skip: ['loop-banner.tsx', 'component-token.tsx'] });
|
3
packages/meta/src/alert/__tests__/demo.test.ts
Normal file
3
packages/meta/src/alert/__tests__/demo.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import demoTest from '../../../tests/shared/demoTest';
|
||||
|
||||
demoTest('alert', { skip: ['loop-banner.tsx', 'component-token.tsx'] });
|
5
packages/meta/src/alert/__tests__/image.test.ts
Normal file
5
packages/meta/src/alert/__tests__/image.test.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('Alert image', () => {
|
||||
imageDemoTest('alert');
|
||||
});
|
171
packages/meta/src/alert/__tests__/index.test.tsx
Normal file
171
packages/meta/src/alert/__tests__/index.test.tsx
Normal file
@ -0,0 +1,171 @@
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { resetWarned } from 'rc-util/lib/warning';
|
||||
import React from 'react';
|
||||
import Alert from '..';
|
||||
import accessibilityTest from '../../../tests/shared/accessibilityTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { act, render, screen } from '../../../tests/utils';
|
||||
import Button from '../../button';
|
||||
import Popconfirm from '../../popconfirm';
|
||||
import Tooltip from '../../tooltip';
|
||||
|
||||
const { ErrorBoundary } = Alert;
|
||||
|
||||
describe('Alert', () => {
|
||||
rtlTest(Alert);
|
||||
accessibilityTest(Alert);
|
||||
|
||||
beforeAll(() => {
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('should show close button and could be closed', async () => {
|
||||
const onClose = jest.fn();
|
||||
render(
|
||||
<Alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
closable
|
||||
onClose={onClose}
|
||||
/>,
|
||||
);
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /close/i }));
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(onClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('custom action', () => {
|
||||
const { container } = render(
|
||||
<Alert
|
||||
message="Success Tips"
|
||||
type="success"
|
||||
showIcon
|
||||
action={
|
||||
<Button size="small" type="text">
|
||||
UNDO
|
||||
</Button>
|
||||
}
|
||||
closable
|
||||
/>,
|
||||
);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should sets data attributes on alert when pass attributes to props', () => {
|
||||
render(
|
||||
<Alert data-test="test-id" data-id="12345" aria-describedby="some-label" message={null} />,
|
||||
);
|
||||
const alert = screen.getByRole('alert');
|
||||
expect(alert).toHaveAttribute('data-test', 'test-id');
|
||||
expect(alert).toHaveAttribute('data-id', '12345');
|
||||
expect(alert).toHaveAttribute('aria-describedby', 'some-label');
|
||||
});
|
||||
|
||||
it('sets role attribute on input', () => {
|
||||
render(<Alert role="status" message={null} />);
|
||||
|
||||
expect(screen.getByRole('status')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show error as ErrorBoundary when children have error', () => {
|
||||
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
expect(warnSpy).toHaveBeenCalledTimes(0);
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line react/jsx-no-undef
|
||||
const ThrowError = () => <NotExisted />;
|
||||
render(
|
||||
<ErrorBoundary>
|
||||
<ThrowError />
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(
|
||||
'ReferenceError: NotExisted is not defined',
|
||||
);
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('could be used with Tooltip', async () => {
|
||||
render(
|
||||
<Tooltip title="xxx" mouseEnterDelay={0}>
|
||||
<Alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
/>
|
||||
</Tooltip>,
|
||||
);
|
||||
|
||||
await userEvent.hover(screen.getByRole('alert'));
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(screen.getByRole('tooltip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('could be used with Popconfirm', async () => {
|
||||
render(
|
||||
<Popconfirm title="xxx">
|
||||
<Alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
/>
|
||||
</Popconfirm>,
|
||||
);
|
||||
await userEvent.click(screen.getByRole('alert'));
|
||||
|
||||
act(() => {
|
||||
jest.runAllTimers();
|
||||
});
|
||||
|
||||
expect(screen.getByRole('tooltip')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('could accept none react element icon', () => {
|
||||
render(<Alert message="Success Tips" type="success" showIcon icon="icon" />);
|
||||
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(/success tips/i);
|
||||
expect(screen.getByRole('alert')).toHaveTextContent(/icon/i);
|
||||
});
|
||||
|
||||
it('should not render message div when no message', () => {
|
||||
const { container } = render(<Alert description="description" />);
|
||||
expect(!!container.querySelector('.ant-alert-message')).toBe(false);
|
||||
});
|
||||
|
||||
it('close button should be hidden when closeIcon setting to null or false', () => {
|
||||
const { container, rerender } = render(<Alert closeIcon={null} />);
|
||||
expect(container.querySelector('.ant-alert-close-icon')).toBeFalsy();
|
||||
rerender(<Alert closeIcon={false} />);
|
||||
expect(container.querySelector('.ant-alert-close-icon')).toBeFalsy();
|
||||
rerender(<Alert closeIcon />);
|
||||
expect(container.querySelector('.ant-alert-close-icon')).toBeTruthy();
|
||||
rerender(<Alert />);
|
||||
expect(container.querySelector('.ant-alert-close-icon')).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should warning when using closeText', () => {
|
||||
resetWarned();
|
||||
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
|
||||
const { container } = render(<Alert closeText="close" />);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
`Warning: [antd: Alert] \`closeText\` is deprecated. Please use \`closeIcon\` instead.`,
|
||||
);
|
||||
|
||||
expect(container.querySelector('.ant-alert-close-icon')?.textContent).toBe('close');
|
||||
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
7
packages/meta/src/alert/demo/action.md
Normal file
7
packages/meta/src/alert/demo/action.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
可以在右上角自定义操作项。
|
||||
|
||||
## en-US
|
||||
|
||||
Custom action.
|
59
packages/meta/src/alert/demo/action.tsx
Normal file
59
packages/meta/src/alert/demo/action.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import React from 'react';
|
||||
import { Alert, Button, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert
|
||||
message="Success Tips"
|
||||
type="success"
|
||||
showIcon
|
||||
action={
|
||||
<Button size="small" type="text">
|
||||
UNDO
|
||||
</Button>
|
||||
}
|
||||
closable
|
||||
/>
|
||||
<Alert
|
||||
message="Error Text"
|
||||
showIcon
|
||||
description="Error Description Error Description Error Description Error Description"
|
||||
type="error"
|
||||
action={
|
||||
<Button size="small" danger>
|
||||
Detail
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Alert
|
||||
message="Warning Text"
|
||||
type="warning"
|
||||
action={
|
||||
<Space>
|
||||
<Button type="text" size="small" ghost>
|
||||
Done
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
closable
|
||||
/>
|
||||
<Alert
|
||||
message="Info Text"
|
||||
description="Info Description Info Description Info Description Info Description"
|
||||
type="info"
|
||||
action={
|
||||
<Space direction="vertical">
|
||||
<Button size="small" type="primary">
|
||||
Accept
|
||||
</Button>
|
||||
<Button size="small" danger ghost>
|
||||
Decline
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
closable
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/banner.md
Normal file
7
packages/meta/src/alert/demo/banner.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
页面顶部通告形式,默认有图标且 `type` 为 'warning'。
|
||||
|
||||
## en-US
|
||||
|
||||
Display Alert as a banner at top of page.
|
17
packages/meta/src/alert/demo/banner.tsx
Normal file
17
packages/meta/src/alert/demo/banner.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Alert, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert message="Warning text" banner />
|
||||
<Alert
|
||||
message="Very long warning text warning text text text text text text text"
|
||||
banner
|
||||
closable
|
||||
/>
|
||||
<Alert showIcon={false} message="Warning text without icon" banner />
|
||||
<Alert type="error" message="Error text" banner />
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/basic.md
Normal file
7
packages/meta/src/alert/demo/basic.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
最简单的用法,适用于简短的警告提示。
|
||||
|
||||
## en-US
|
||||
|
||||
The simplest usage for short messages.
|
6
packages/meta/src/alert/demo/basic.tsx
Normal file
6
packages/meta/src/alert/demo/basic.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Alert } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => <Alert message="Success Text" type="success" />;
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/closable.md
Normal file
7
packages/meta/src/alert/demo/closable.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
显示关闭按钮,点击可关闭警告提示。
|
||||
|
||||
## en-US
|
||||
|
||||
To show close button.
|
26
packages/meta/src/alert/demo/closable.tsx
Normal file
26
packages/meta/src/alert/demo/closable.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Alert, Space } from '@zhst/meta';
|
||||
|
||||
const onClose = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||
console.log(e, 'I was closed.');
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert
|
||||
message="Warning Text Warning Text Warning TextW arning Text Warning Text Warning TextWarning Text"
|
||||
type="warning"
|
||||
closable
|
||||
onClose={onClose}
|
||||
/>
|
||||
<Alert
|
||||
message="Error Text"
|
||||
description="Error Description Error Description Error Description Error Description Error Description Error Description"
|
||||
type="error"
|
||||
closable
|
||||
onClose={onClose}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/component-token.md
Normal file
7
packages/meta/src/alert/demo/component-token.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
自定义组件 Token。
|
||||
|
||||
## en-US
|
||||
|
||||
Custom component token.
|
28
packages/meta/src/alert/demo/component-token.tsx
Normal file
28
packages/meta/src/alert/demo/component-token.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import { Alert, ConfigProvider } from '@zhst/meta';
|
||||
|
||||
const icon = <SmileOutlined />;
|
||||
|
||||
const App: React.FC = () => (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Alert: {
|
||||
withDescriptionIconSize: 32,
|
||||
withDescriptionPadding: 16,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Alert
|
||||
icon={icon}
|
||||
message="Success Tips"
|
||||
description="Detailed description and advices about successful copywriting."
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/custom-icon.md
Normal file
7
packages/meta/src/alert/demo/custom-icon.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
可口的图标让信息类型更加醒目。
|
||||
|
||||
## en-US
|
||||
|
||||
A relevant icon makes information clearer and more friendly.
|
45
packages/meta/src/alert/demo/custom-icon.tsx
Normal file
45
packages/meta/src/alert/demo/custom-icon.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from 'react';
|
||||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import { Alert, Space } from '@zhst/meta';
|
||||
|
||||
const icon = <SmileOutlined />;
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert icon={icon} message="showIcon = false" type="success" />
|
||||
<Alert icon={icon} message="Success Tips" type="success" showIcon />
|
||||
<Alert icon={icon} message="Informational Notes" type="info" showIcon />
|
||||
<Alert icon={icon} message="Warning" type="warning" showIcon />
|
||||
<Alert icon={icon} message="Error" type="error" showIcon />
|
||||
<Alert
|
||||
icon={icon}
|
||||
message="Success Tips"
|
||||
description="Detailed description and advices about successful copywriting."
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
<Alert
|
||||
icon={icon}
|
||||
message="Informational Notes"
|
||||
description="Additional description and informations about copywriting."
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
<Alert
|
||||
icon={icon}
|
||||
message="Warning"
|
||||
description="This is a warning notice about copywriting."
|
||||
type="warning"
|
||||
showIcon
|
||||
/>
|
||||
<Alert
|
||||
icon={icon}
|
||||
message="Error"
|
||||
description="This is an error message about copywriting."
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/description.md
Normal file
7
packages/meta/src/alert/demo/description.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
含有辅助性文字介绍的警告提示。
|
||||
|
||||
## en-US
|
||||
|
||||
Additional description for alert message.
|
29
packages/meta/src/alert/demo/description.tsx
Normal file
29
packages/meta/src/alert/demo/description.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { Alert, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert
|
||||
message="Success Text"
|
||||
description="Success Description Success Description Success Description"
|
||||
type="success"
|
||||
/>
|
||||
<Alert
|
||||
message="Info Text"
|
||||
description="Info Description Info Description Info Description Info Description"
|
||||
type="info"
|
||||
/>
|
||||
<Alert
|
||||
message="Warning Text"
|
||||
description="Warning Description Warning Description Warning Description Warning Description"
|
||||
type="warning"
|
||||
/>
|
||||
<Alert
|
||||
message="Error Text"
|
||||
description="Error Description Error Description Error Description Error Description"
|
||||
type="error"
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/error-boundary.md
Normal file
7
packages/meta/src/alert/demo/error-boundary.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
友好的 [React 错误处理](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) 包裹组件。
|
||||
|
||||
## en-US
|
||||
|
||||
ErrorBoundary Component for making error handling easier in [React](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html).
|
27
packages/meta/src/alert/demo/error-boundary.tsx
Normal file
27
packages/meta/src/alert/demo/error-boundary.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Alert, Button } from '@zhst/meta';
|
||||
|
||||
const { ErrorBoundary } = Alert;
|
||||
const ThrowError: React.FC = () => {
|
||||
const [error, setError] = useState<Error>();
|
||||
const onClick = () => {
|
||||
setError(new Error('An Uncaught Error'));
|
||||
};
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
return (
|
||||
<Button danger onClick={onClick}>
|
||||
Click me to throw a error
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<ErrorBoundary>
|
||||
<ThrowError />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/icon.md
Normal file
7
packages/meta/src/alert/demo/icon.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
可口的图标让信息类型更加醒目。
|
||||
|
||||
## en-US
|
||||
|
||||
A relevant icon will make information clearer and more friendly.
|
38
packages/meta/src/alert/demo/icon.tsx
Normal file
38
packages/meta/src/alert/demo/icon.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
import { Alert, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert message="Success Tips" type="success" showIcon />
|
||||
<Alert message="Informational Notes" type="info" showIcon />
|
||||
<Alert message="Warning" type="warning" showIcon closable />
|
||||
<Alert message="Error" type="error" showIcon />
|
||||
<Alert
|
||||
message="Success Tips"
|
||||
description="Detailed description and advice about successful copywriting."
|
||||
type="success"
|
||||
showIcon
|
||||
/>
|
||||
<Alert
|
||||
message="Informational Notes"
|
||||
description="Additional description and information about copywriting."
|
||||
type="info"
|
||||
showIcon
|
||||
/>
|
||||
<Alert
|
||||
message="Warning"
|
||||
description="This is a warning notice about copywriting."
|
||||
type="warning"
|
||||
showIcon
|
||||
closable
|
||||
/>
|
||||
<Alert
|
||||
message="Error"
|
||||
description="This is an error message about copywriting."
|
||||
type="error"
|
||||
showIcon
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/loop-banner.md
Normal file
7
packages/meta/src/alert/demo/loop-banner.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
配合 [react-text-loop-next](https://npmjs.com/package/react-text-loop-next) 或 [react-fast-marquee](https://npmjs.com/package/react-fast-marquee) 实现消息轮播通知栏。
|
||||
|
||||
## en-US
|
||||
|
||||
Show a loop banner by using with [react-text-loop-next](https://npmjs.com/package/react-text-loop-next) or [react-fast-marquee](https://npmjs.com/package/react-fast-marquee).
|
16
packages/meta/src/alert/demo/loop-banner.tsx
Normal file
16
packages/meta/src/alert/demo/loop-banner.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import Marquee from 'react-fast-marquee';
|
||||
import { Alert } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Alert
|
||||
banner
|
||||
message={
|
||||
<Marquee pauseOnHover gradient={false}>
|
||||
I can be a React component, multiple React components, or just some text.
|
||||
</Marquee>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/smooth-closed.md
Normal file
7
packages/meta/src/alert/demo/smooth-closed.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
平滑、自然的卸载提示。
|
||||
|
||||
## en-US
|
||||
|
||||
Smoothly unmount Alert upon close.
|
22
packages/meta/src/alert/demo/smooth-closed.tsx
Normal file
22
packages/meta/src/alert/demo/smooth-closed.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Alert, Switch, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [visible, setVisible] = useState(true);
|
||||
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
{visible && (
|
||||
<Alert message="Alert Message Text" type="success" closable afterClose={handleClose} />
|
||||
)}
|
||||
<p>click the close button to see the effect</p>
|
||||
<Switch onChange={setVisible} checked={visible} disabled={visible} />
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
7
packages/meta/src/alert/demo/style.md
Normal file
7
packages/meta/src/alert/demo/style.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
共有四种样式 `success`、`info`、`warning`、`error`。
|
||||
|
||||
## en-US
|
||||
|
||||
There are 4 types of Alert: `success`, `info`, `warning`, `error`.
|
13
packages/meta/src/alert/demo/style.tsx
Normal file
13
packages/meta/src/alert/demo/style.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import React from 'react';
|
||||
import { Alert, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Alert message="Success Text" type="success" />
|
||||
<Alert message="Info Text" type="info" />
|
||||
<Alert message="Warning Text" type="warning" />
|
||||
<Alert message="Error Text" type="error" />
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
16
packages/meta/src/alert/index.ts
Normal file
16
packages/meta/src/alert/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type React from 'react';
|
||||
import type { AlertProps } from './Alert';
|
||||
import InternalAlert from './Alert';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
export type { AlertProps } from './Alert';
|
||||
|
||||
type CompoundedComponent = React.FC<AlertProps> & {
|
||||
ErrorBoundary: typeof ErrorBoundary;
|
||||
};
|
||||
|
||||
const Alert = InternalAlert as CompoundedComponent;
|
||||
|
||||
Alert.ErrorBoundary = ErrorBoundary;
|
||||
|
||||
export default Alert;
|
57
packages/meta/src/alert/index.zh-CN.md
Normal file
57
packages/meta/src/alert/index.zh-CN.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
category: Components
|
||||
subtitle: 警告提示
|
||||
title: Alert 警告提示
|
||||
demo:
|
||||
cols: 2
|
||||
group:
|
||||
title: 反馈
|
||||
order: 6
|
||||
---
|
||||
|
||||
警告提示,展现需要关注的信息。
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 当某个页面需要向用户显示警告的信息时。
|
||||
- 非浮层的静态展现形式,始终展现,不会自动消失,用户可以点击关闭。
|
||||
|
||||
## 代码演示
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
<code src="./demo/basic.tsx">基本</code>
|
||||
<code src="./demo/style.tsx">四种样式</code>
|
||||
<code src="./demo/closable.tsx">可关闭的警告提示</code>
|
||||
<code src="./demo/description.tsx">含有辅助性文字介绍</code>
|
||||
<code src="./demo/icon.tsx">图标</code>
|
||||
<code src="./demo/banner.tsx" iframe="250">顶部公告</code>
|
||||
<!-- <code src="./demo/loop-banner.tsx">轮播的公告</code> -->
|
||||
<code src="./demo/smooth-closed.tsx">平滑地卸载</code>
|
||||
<code src="./demo/error-boundary.tsx">React 错误处理</code>
|
||||
<code src="./demo/custom-icon.tsx" debug>自定义图标</code>
|
||||
<code src="./demo/action.tsx">操作</code>
|
||||
<code src="./demo/component-token.tsx" debug>组件 Token</code>
|
||||
|
||||
## API
|
||||
|
||||
通用属性参考:[通用属性](/docs/react/common-props)
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| action | 自定义操作项 | ReactNode | - | 4.9.0 |
|
||||
| afterClose | 关闭动画结束后触发的回调函数 | () => void | - | |
|
||||
| banner | 是否用作顶部公告 | boolean | false | |
|
||||
| closeIcon | 自定义关闭 Icon,>=5.7.0: 设置为 `null` 或 `false` 时隐藏关闭按钮 | boolean \| ReactNode | `<CloseOutlined />` | |
|
||||
| description | 警告提示的辅助性文字介绍 | ReactNode | - | |
|
||||
| icon | 自定义图标,`showIcon` 为 true 时有效 | ReactNode | - | |
|
||||
| message | 警告提示内容 | ReactNode | - | |
|
||||
| showIcon | 是否显示辅助图标 | boolean | false,`banner` 模式下默认值为 true | |
|
||||
| type | 指定警告提示的样式,有四种选择 `success`、`info`、`warning`、`error` | string | `info`,`banner` 模式下默认值为 `warning` | |
|
||||
| onClose | 关闭时触发的回调函数 | (e: MouseEvent) => void | - | |
|
||||
|
||||
### Alert.ErrorBoundary
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| description | 自定义错误内容,如果未指定会展示报错堆栈 | ReactNode | {{ error stack }} | |
|
||||
| message | 自定义错误标题,如果未指定会展示原生报错信息 | ReactNode | {{ error }} | |
|
260
packages/meta/src/alert/style/index.ts
Normal file
260
packages/meta/src/alert/style/index.ts
Normal file
@ -0,0 +1,260 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { CSSObject } from '@ant-design/cssinjs';
|
||||
import { unit } from '@ant-design/cssinjs';
|
||||
|
||||
import { resetComponent } from '../../style';
|
||||
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
|
||||
import { genStyleHooks } from '../../theme/internal';
|
||||
|
||||
export interface ComponentToken {
|
||||
// Component token here
|
||||
/**
|
||||
* @desc 默认内间距
|
||||
* @descEN Default padding
|
||||
*/
|
||||
defaultPadding: CSSProperties['padding'];
|
||||
/**
|
||||
* @desc 带有描述的内间距
|
||||
* @descEN Padding with description
|
||||
*/
|
||||
withDescriptionPadding: CSSProperties['padding'];
|
||||
/**
|
||||
* @desc 带有描述时的图标尺寸
|
||||
* @descEN Icon size with description
|
||||
*/
|
||||
withDescriptionIconSize: number;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
type AlertToken = FullToken<'Alert'> & {
|
||||
// Custom token here
|
||||
};
|
||||
|
||||
const genAlertTypeStyle = (
|
||||
bgColor: string,
|
||||
borderColor: string,
|
||||
iconColor: string,
|
||||
token: AlertToken,
|
||||
alertCls: string,
|
||||
): CSSObject => ({
|
||||
background: bgColor,
|
||||
border: `${unit(token.lineWidth)} ${token.lineType} ${borderColor}`,
|
||||
[`${alertCls}-icon`]: {
|
||||
color: iconColor,
|
||||
},
|
||||
});
|
||||
|
||||
export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
|
||||
const {
|
||||
componentCls,
|
||||
motionDurationSlow: duration,
|
||||
marginXS,
|
||||
marginSM,
|
||||
fontSize,
|
||||
fontSizeLG,
|
||||
lineHeight,
|
||||
borderRadiusLG: borderRadius,
|
||||
motionEaseInOutCirc,
|
||||
// @ts-ignore
|
||||
withDescriptionIconSize,
|
||||
colorText,
|
||||
colorTextHeading,
|
||||
// @ts-ignore
|
||||
withDescriptionPadding,
|
||||
// @ts-ignore
|
||||
defaultPadding,
|
||||
} = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
...resetComponent(token),
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
padding: defaultPadding,
|
||||
wordWrap: 'break-word',
|
||||
borderRadius,
|
||||
|
||||
[`&${componentCls}-rtl`]: {
|
||||
direction: 'rtl',
|
||||
},
|
||||
|
||||
[`${componentCls}-content`]: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
|
||||
[`${componentCls}-icon`]: {
|
||||
marginInlineEnd: marginXS,
|
||||
lineHeight: 0,
|
||||
},
|
||||
|
||||
[`&-description`]: {
|
||||
display: 'none',
|
||||
fontSize,
|
||||
lineHeight,
|
||||
},
|
||||
|
||||
'&-message': {
|
||||
color: colorTextHeading,
|
||||
},
|
||||
|
||||
[`&${componentCls}-motion-leave`]: {
|
||||
overflow: 'hidden',
|
||||
opacity: 1,
|
||||
transition: `max-height ${duration} ${motionEaseInOutCirc}, opacity ${duration} ${motionEaseInOutCirc},
|
||||
padding-top ${duration} ${motionEaseInOutCirc}, padding-bottom ${duration} ${motionEaseInOutCirc},
|
||||
margin-bottom ${duration} ${motionEaseInOutCirc}`,
|
||||
},
|
||||
|
||||
[`&${componentCls}-motion-leave-active`]: {
|
||||
maxHeight: 0,
|
||||
marginBottom: '0 !important',
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
opacity: 0,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-with-description`]: {
|
||||
alignItems: 'flex-start',
|
||||
padding: withDescriptionPadding,
|
||||
[`${componentCls}-icon`]: {
|
||||
marginInlineEnd: marginSM,
|
||||
fontSize: withDescriptionIconSize,
|
||||
lineHeight: 0,
|
||||
},
|
||||
|
||||
[`${componentCls}-message`]: {
|
||||
display: 'block',
|
||||
marginBottom: marginXS,
|
||||
color: colorTextHeading,
|
||||
fontSize: fontSizeLG,
|
||||
},
|
||||
|
||||
[`${componentCls}-description`]: {
|
||||
display: 'block',
|
||||
color: colorText,
|
||||
},
|
||||
},
|
||||
|
||||
[`${componentCls}-banner`]: {
|
||||
marginBottom: 0,
|
||||
border: '0 !important',
|
||||
borderRadius: 0,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const genTypeStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
|
||||
const {
|
||||
componentCls,
|
||||
|
||||
colorSuccess,
|
||||
colorSuccessBorder,
|
||||
colorSuccessBg,
|
||||
|
||||
colorWarning,
|
||||
colorWarningBorder,
|
||||
colorWarningBg,
|
||||
|
||||
colorError,
|
||||
colorErrorBorder,
|
||||
colorErrorBg,
|
||||
|
||||
colorInfo,
|
||||
colorInfoBorder,
|
||||
colorInfoBg,
|
||||
} = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
'&-success': genAlertTypeStyle(
|
||||
colorSuccessBg,
|
||||
colorSuccessBorder,
|
||||
colorSuccess,
|
||||
token,
|
||||
componentCls,
|
||||
),
|
||||
'&-info': genAlertTypeStyle(colorInfoBg, colorInfoBorder, colorInfo, token, componentCls),
|
||||
'&-warning': genAlertTypeStyle(
|
||||
colorWarningBg,
|
||||
colorWarningBorder,
|
||||
colorWarning,
|
||||
token,
|
||||
componentCls,
|
||||
),
|
||||
'&-error': {
|
||||
...genAlertTypeStyle(colorErrorBg, colorErrorBorder, colorError, token, componentCls),
|
||||
[`${componentCls}-description > pre`]: {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSObject => {
|
||||
const {
|
||||
componentCls,
|
||||
iconCls,
|
||||
motionDurationMid,
|
||||
marginXS,
|
||||
fontSizeIcon,
|
||||
colorIcon,
|
||||
colorIconHover,
|
||||
} = token;
|
||||
|
||||
return {
|
||||
[componentCls]: {
|
||||
[`&-action`]: {
|
||||
marginInlineStart: marginXS,
|
||||
},
|
||||
|
||||
[`${componentCls}-close-icon`]: {
|
||||
marginInlineStart: marginXS,
|
||||
padding: 0,
|
||||
overflow: 'hidden',
|
||||
fontSize: fontSizeIcon,
|
||||
lineHeight: unit(fontSizeIcon),
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
outline: 'none',
|
||||
cursor: 'pointer',
|
||||
|
||||
[`${iconCls}-close`]: {
|
||||
color: colorIcon,
|
||||
transition: `color ${motionDurationMid}`,
|
||||
'&:hover': {
|
||||
color: colorIconHover,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'&-close-text': {
|
||||
color: colorIcon,
|
||||
transition: `color ${motionDurationMid}`,
|
||||
'&:hover': {
|
||||
color: colorIconHover,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
// @ts-ignore
|
||||
export const prepareComponentToken: GetDefaultToken<'Alert'> = (token) => {
|
||||
const paddingHorizontal = 12; // Fixed value here.
|
||||
return {
|
||||
withDescriptionIconSize: token.fontSizeHeading3,
|
||||
defaultPadding: `${token.paddingContentVerticalSM}px ${paddingHorizontal}px`,
|
||||
withDescriptionPadding: `${token.paddingMD}px ${token.paddingContentHorizontalLG}px`,
|
||||
};
|
||||
};
|
||||
|
||||
export default genStyleHooks(
|
||||
// @ts-ignore
|
||||
'Alert',
|
||||
(token) => [genBaseStyle(token), genTypeStyle(token), genActionStyle(token)],
|
||||
prepareComponentToken,
|
||||
);
|
13
packages/meta/src/avatar/AvatarContext.ts
Normal file
13
packages/meta/src/avatar/AvatarContext.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import type { ScreenSizeMap } from '../_util/responsiveObserver';
|
||||
|
||||
export type AvatarSize = 'large' | 'small' | 'default' | number | ScreenSizeMap;
|
||||
|
||||
export interface AvatarContextType {
|
||||
size?: AvatarSize;
|
||||
shape?: 'circle' | 'square';
|
||||
}
|
||||
|
||||
const AvatarContext = React.createContext<AvatarContextType>({});
|
||||
|
||||
export default AvatarContext;
|
224
packages/meta/src/avatar/__tests__/Avatar.test.tsx
Normal file
224
packages/meta/src/avatar/__tests__/Avatar.test.tsx
Normal file
@ -0,0 +1,224 @@
|
||||
import React, { useState } from 'react';
|
||||
import Avatar from '..';
|
||||
import mountTest from '../../../tests/shared/mountTest';
|
||||
import rtlTest from '../../../tests/shared/rtlTest';
|
||||
import { fireEvent, render } from '../../../tests/utils';
|
||||
import useBreakpoint from '../../grid/hooks/useBreakpoint';
|
||||
import ConfigProvider from '../../config-provider';
|
||||
|
||||
jest.mock('../../grid/hooks/useBreakpoint');
|
||||
|
||||
describe('Avatar Render', () => {
|
||||
mountTest(Avatar);
|
||||
rtlTest(Avatar);
|
||||
|
||||
const sizes = { xs: 24, sm: 32, md: 40, lg: 64, xl: 80, xxl: 100 };
|
||||
let originOffsetWidth: PropertyDescriptor['get'];
|
||||
beforeAll(() => {
|
||||
// Mock offsetHeight
|
||||
originOffsetWidth = Object.getOwnPropertyDescriptor(HTMLElement.prototype, 'offsetWidth')?.get;
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
|
||||
get() {
|
||||
if (this.className === 'ant-avatar-string') {
|
||||
return 100;
|
||||
}
|
||||
return 80;
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Restore Mock offsetHeight
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
|
||||
get: originOffsetWidth,
|
||||
});
|
||||
});
|
||||
|
||||
it('Render long string correctly', () => {
|
||||
const { container } = render(<Avatar>TestString</Avatar>);
|
||||
expect(container.querySelectorAll('.ant-avatar-string').length).toBe(1);
|
||||
});
|
||||
|
||||
it('should render fallback string correctly', () => {
|
||||
const div = global.document.createElement('div');
|
||||
global.document.body.appendChild(div);
|
||||
const { container } = render(<Avatar src="http://error.url">Fallback</Avatar>);
|
||||
fireEvent.error(container.querySelector('img')!);
|
||||
const children = container.querySelectorAll('.ant-avatar-string');
|
||||
expect(children.length).toBe(1);
|
||||
expect(children[0].innerHTML).toBe('Fallback');
|
||||
global.document.body.removeChild(div);
|
||||
});
|
||||
|
||||
it('should handle onError correctly', () => {
|
||||
const LOAD_FAILURE_SRC = 'http://error.url/';
|
||||
const LOAD_SUCCESS_SRC = 'https://api.dicebear.com/7.x/pixel-art/svg';
|
||||
const Foo: React.FC = () => {
|
||||
const [avatarSrc, setAvatarSrc] = useState<typeof LOAD_FAILURE_SRC | typeof LOAD_SUCCESS_SRC>(
|
||||
LOAD_FAILURE_SRC,
|
||||
);
|
||||
const onError = (): boolean => {
|
||||
setAvatarSrc(LOAD_SUCCESS_SRC);
|
||||
return false;
|
||||
};
|
||||
return <Avatar src={avatarSrc} onError={onError} />;
|
||||
};
|
||||
const { container } = render(<Foo />);
|
||||
expect(container.querySelector('img')?.src).toBe(LOAD_FAILURE_SRC);
|
||||
// mock img load Error, since jsdom do not load resource by default
|
||||
// https://github.com/jsdom/jsdom/issues/1816
|
||||
fireEvent.error(container.querySelector('img')!);
|
||||
expect(container.querySelector('img')?.src).toBe(LOAD_SUCCESS_SRC);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should show image on success after a failure state', () => {
|
||||
const LOAD_FAILURE_SRC = 'http://error.url';
|
||||
const LOAD_SUCCESS_SRC = 'https://api.dicebear.com/7.x/pixel-art/svg';
|
||||
|
||||
const div = global.document.createElement('div');
|
||||
global.document.body.appendChild(div);
|
||||
|
||||
// simulate error src url
|
||||
const { container, rerender } = render(<Avatar src={LOAD_FAILURE_SRC}>Fallback</Avatar>);
|
||||
|
||||
fireEvent.error(container.querySelector('img')!);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
expect(container.querySelectorAll('.ant-avatar-string').length).toBe(1);
|
||||
// children should show, when image load error without onError return false
|
||||
expect(container.querySelector<HTMLDivElement>('.ant-avatar-string')?.style).not.toHaveProperty(
|
||||
'opacity',
|
||||
0,
|
||||
);
|
||||
|
||||
// simulate successful src url
|
||||
rerender(<Avatar src={LOAD_SUCCESS_SRC}>Fallback</Avatar>);
|
||||
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
expect(container.querySelectorAll('.ant-avatar-image').length).toBe(1);
|
||||
|
||||
global.document.body.removeChild(div);
|
||||
});
|
||||
|
||||
it('should calculate scale of avatar children correctly', () => {
|
||||
const { container, rerender } = render(<Avatar>Avatar</Avatar>);
|
||||
expect(container.querySelector('.ant-avatar-string')).toMatchSnapshot();
|
||||
|
||||
Object.defineProperty(HTMLElement.prototype, 'offsetWidth', {
|
||||
get() {
|
||||
return this.className === 'ant-avatar-string' ? 100 : 40;
|
||||
},
|
||||
});
|
||||
|
||||
rerender(<Avatar>xx</Avatar>);
|
||||
expect(container.querySelector('.ant-avatar-string')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should calculate scale of avatar children correctly with gap', () => {
|
||||
const { container } = render(<Avatar gap={2}>Avatar</Avatar>);
|
||||
expect(container.querySelector('.ant-avatar-string')).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should warning when pass a string as icon props', () => {
|
||||
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
render(<Avatar size={64} icon="aa" />);
|
||||
expect(warnSpy).not.toHaveBeenCalled();
|
||||
|
||||
render(<Avatar size={64} icon="user" />);
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
`Warning: [antd: Avatar] \`icon\` is using ReactNode instead of string naming in v4. Please check \`user\` at https://ant.design/components/icon`,
|
||||
);
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('support size is number', () => {
|
||||
const { container } = render(<Avatar size={100}>TestString</Avatar>);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
});
|
||||
|
||||
Object.entries(sizes).forEach(([key, value]) => {
|
||||
it(`adjusts component size to ${value} when window size is ${key}`, () => {
|
||||
(useBreakpoint as any).mockReturnValue({ [key]: true });
|
||||
const { container } = render(<Avatar size={sizes} />);
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it('support onMouseEnter', () => {
|
||||
const onMouseEnter = jest.fn();
|
||||
const { container } = render(<Avatar {...{ onMouseEnter }}>TestString</Avatar>);
|
||||
fireEvent.mouseEnter(container.firstChild!);
|
||||
expect(onMouseEnter).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fallback', () => {
|
||||
const div = global.document.createElement('div');
|
||||
global.document.body.appendChild(div);
|
||||
const { container } = render(
|
||||
<Avatar shape="circle" src="http://error.url">
|
||||
A
|
||||
</Avatar>,
|
||||
);
|
||||
fireEvent.error(container.querySelector('img')!);
|
||||
expect(container.firstChild).toMatchSnapshot();
|
||||
global.document.body.removeChild(div);
|
||||
});
|
||||
|
||||
it('should exist crossorigin attribute', () => {
|
||||
const LOAD_SUCCESS_SRC = 'https://api.dicebear.com/7.x/pixel-art/svg';
|
||||
const crossOrigin = 'anonymous';
|
||||
const { container } = render(
|
||||
<Avatar src={LOAD_SUCCESS_SRC} crossOrigin={crossOrigin}>
|
||||
crossorigin
|
||||
</Avatar>,
|
||||
);
|
||||
expect(container.querySelector('img')?.crossOrigin).toBeTruthy();
|
||||
expect(container.querySelector('img')?.crossOrigin).toEqual(crossOrigin);
|
||||
});
|
||||
|
||||
it('should not exist crossorigin attribute', () => {
|
||||
const LOAD_SUCCESS_SRC = 'https://api.dicebear.com/7.x/pixel-art/svg';
|
||||
const { container } = render(<Avatar src={LOAD_SUCCESS_SRC}>crossorigin</Avatar>);
|
||||
expect(container.querySelector('img')?.crossOrigin).toBeFalsy();
|
||||
expect(container.querySelector('img')?.crossOrigin).toEqual('');
|
||||
});
|
||||
|
||||
it('clickable', () => {
|
||||
const onClick = jest.fn();
|
||||
const { container } = render(<Avatar onClick={onClick}>TestString</Avatar>);
|
||||
fireEvent.click(container.querySelector('.ant-avatar-string')!);
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('Avatar.Group support shape props', () => {
|
||||
const { container } = render(
|
||||
<Avatar.Group shape="square">
|
||||
<Avatar>A</Avatar>
|
||||
<Avatar shape="circle">B</Avatar>
|
||||
<Avatar>C</Avatar>
|
||||
<Avatar shape="circle">D</Avatar>
|
||||
</Avatar.Group>,
|
||||
);
|
||||
const avatars = container?.querySelectorAll<HTMLSpanElement>('.ant-avatar-group .ant-avatar');
|
||||
expect(avatars?.[0]).toHaveClass('ant-avatar-square');
|
||||
expect(avatars?.[1]).toHaveClass('ant-avatar-circle');
|
||||
expect(avatars?.[2]).toHaveClass('ant-avatar-square');
|
||||
expect(avatars?.[3]).toHaveClass('ant-avatar-circle');
|
||||
});
|
||||
|
||||
it('should apply the componentSize of CP', () => {
|
||||
const { container } = render(
|
||||
<>
|
||||
<ConfigProvider componentSize="small">
|
||||
<Avatar>test</Avatar>
|
||||
</ConfigProvider>
|
||||
<ConfigProvider componentSize="large">
|
||||
<Avatar>test</Avatar>
|
||||
</ConfigProvider>
|
||||
</>,
|
||||
);
|
||||
expect(container.querySelector('.ant-avatar-sm')).toBeTruthy();
|
||||
expect(container.querySelector('.ant-avatar-lg')).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,183 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Avatar Render adjusts component size to 24 when window size is xs 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="width: 24px; height: 24px; line-height: 24px; font-size: 18px;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.32) translateX(-50%);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render adjusts component size to 32 when window size is sm 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="width: 32px; height: 32px; line-height: 32px; font-size: 18px;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.32) translateX(-50%);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render adjusts component size to 40 when window size is md 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="width: 40px; height: 40px; line-height: 40px; font-size: 18px;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.32) translateX(-50%);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render adjusts component size to 64 when window size is lg 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="width: 64px; height: 64px; line-height: 64px; font-size: 18px;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.32) translateX(-50%);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render adjusts component size to 80 when window size is xl 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="width: 80px; height: 80px; line-height: 80px; font-size: 18px;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.32) translateX(-50%);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render adjusts component size to 100 when window size is xxl 1`] = `
|
||||
<div>
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="width: 100px; height: 100px; line-height: 100px; font-size: 18px;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.32) translateX(-50%);"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render fallback 1`] = `
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(1) translateX(-50%);"
|
||||
>
|
||||
A
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render rtl render component should be rendered correctly in RTL direction 1`] = `
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.72) translateX(-50%);"
|
||||
/>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render should calculate scale of avatar children correctly 1`] = `
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.72) translateX(-50%);"
|
||||
>
|
||||
Avatar
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render should calculate scale of avatar children correctly 2`] = `
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.72) translateX(-50%);"
|
||||
>
|
||||
xx
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render should calculate scale of avatar children correctly with gap 1`] = `
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(0.36) translateX(-50%);"
|
||||
>
|
||||
Avatar
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render should handle onError correctly 1`] = `
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle ant-avatar-image"
|
||||
>
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/pixel-art/svg"
|
||||
/>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render should show image on success after a failure state 1`] = `
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="transform: scale(1) translateX(-50%);"
|
||||
>
|
||||
Fallback
|
||||
</span>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render should show image on success after a failure state 2`] = `
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle ant-avatar-image"
|
||||
>
|
||||
<img
|
||||
src="https://api.dicebear.com/7.x/pixel-art/svg"
|
||||
/>
|
||||
</span>
|
||||
`;
|
||||
|
||||
exports[`Avatar Render support size is number 1`] = `
|
||||
<span
|
||||
class="ant-avatar ant-avatar-circle"
|
||||
style="width: 100px; height: 100px; line-height: 100px; font-size: 18px;"
|
||||
>
|
||||
<span
|
||||
class="ant-avatar-string"
|
||||
style="line-height: 100px; transform: scale(0.32) translateX(-50%);"
|
||||
>
|
||||
TestString
|
||||
</span>
|
||||
</span>
|
||||
`;
|
File diff suppressed because it is too large
Load Diff
1065
packages/meta/src/avatar/__tests__/__snapshots__/demo.test.tsx.snap
Normal file
1065
packages/meta/src/avatar/__tests__/__snapshots__/demo.test.tsx.snap
Normal file
File diff suppressed because it is too large
Load Diff
3
packages/meta/src/avatar/__tests__/demo-extend.test.ts
Normal file
3
packages/meta/src/avatar/__tests__/demo-extend.test.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { extendTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
extendTest('avatar');
|
17
packages/meta/src/avatar/__tests__/demo.test.tsx
Normal file
17
packages/meta/src/avatar/__tests__/demo.test.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import * as React from 'react';
|
||||
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
|
||||
|
||||
demoTest('avatar');
|
||||
|
||||
rootPropsTest(
|
||||
'avatar',
|
||||
(Avatar, props) => (
|
||||
<Avatar.Group {...props} maxCount={1}>
|
||||
<Avatar>Bamboo</Avatar>
|
||||
<Avatar>Light</Avatar>
|
||||
</Avatar.Group>
|
||||
),
|
||||
{
|
||||
name: 'Avatar.Group',
|
||||
},
|
||||
);
|
5
packages/meta/src/avatar/__tests__/image.test.ts
Normal file
5
packages/meta/src/avatar/__tests__/image.test.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { imageDemoTest } from '../../../tests/shared/imageTest';
|
||||
|
||||
describe('Avatar image', () => {
|
||||
imageDemoTest('avatar');
|
||||
});
|
259
packages/meta/src/avatar/avatar.tsx
Normal file
259
packages/meta/src/avatar/avatar.tsx
Normal file
@ -0,0 +1,259 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import ResizeObserver from 'rc-resize-observer';
|
||||
import { composeRef } from 'rc-util/lib/ref';
|
||||
|
||||
import type { Breakpoint } from '../_util/responsiveObserver';
|
||||
import { responsiveArray } from '../_util/responsiveObserver';
|
||||
import { devUseWarning } from '../_util/warning';
|
||||
import { ConfigContext } from '../config-provider';
|
||||
import useSize from '../config-provider/hooks/useSize';
|
||||
import useBreakpoint from '../grid/hooks/useBreakpoint';
|
||||
import type { AvatarContextType, AvatarSize } from './AvatarContext';
|
||||
import AvatarContext from './AvatarContext';
|
||||
import useStyle from './style';
|
||||
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
|
||||
|
||||
export interface AvatarProps {
|
||||
/** Shape of avatar, options: `circle`, `square` */
|
||||
shape?: 'circle' | 'square';
|
||||
/*
|
||||
* Size of avatar, options: `large`, `small`, `default`
|
||||
* or a custom number size
|
||||
* */
|
||||
size?: AvatarSize;
|
||||
gap?: number;
|
||||
/** Src of image avatar */
|
||||
src?: React.ReactNode;
|
||||
/** Srcset of image avatar */
|
||||
srcSet?: string;
|
||||
draggable?: boolean | 'true' | 'false';
|
||||
/** Icon to be used in avatar */
|
||||
icon?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
rootClassName?: string;
|
||||
children?: React.ReactNode;
|
||||
alt?: string;
|
||||
crossOrigin?: '' | 'anonymous' | 'use-credentials';
|
||||
onClick?: (e?: React.MouseEvent<HTMLElement>) => void;
|
||||
/* callback when img load error */
|
||||
/* return false to prevent Avatar show default fallback behavior, then you can do fallback by yourself */
|
||||
onError?: () => boolean;
|
||||
}
|
||||
|
||||
const InternalAvatar: React.ForwardRefRenderFunction<HTMLSpanElement, AvatarProps> = (
|
||||
props,
|
||||
ref,
|
||||
) => {
|
||||
const [scale, setScale] = React.useState(1);
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const [isImgExist, setIsImgExist] = React.useState(true);
|
||||
|
||||
const avatarNodeRef = React.useRef<HTMLSpanElement>(null);
|
||||
const avatarChildrenRef = React.useRef<HTMLSpanElement>(null);
|
||||
const avatarNodeMergeRef = composeRef<HTMLSpanElement>(ref, avatarNodeRef);
|
||||
|
||||
const { getPrefixCls, avatar } = React.useContext(ConfigContext);
|
||||
|
||||
const avatarCtx = React.useContext<AvatarContextType>(AvatarContext);
|
||||
|
||||
const setScaleParam = () => {
|
||||
if (!avatarChildrenRef.current || !avatarNodeRef.current) {
|
||||
return;
|
||||
}
|
||||
const childrenWidth = avatarChildrenRef.current.offsetWidth; // offsetWidth avoid affecting be transform scale
|
||||
const nodeWidth = avatarNodeRef.current.offsetWidth;
|
||||
// denominator is 0 is no meaning
|
||||
if (childrenWidth !== 0 && nodeWidth !== 0) {
|
||||
const { gap = 4 } = props;
|
||||
if (gap * 2 < nodeWidth) {
|
||||
setScale(nodeWidth - gap * 2 < childrenWidth ? (nodeWidth - gap * 2) / childrenWidth : 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsImgExist(true);
|
||||
setScale(1);
|
||||
}, [props.src]);
|
||||
|
||||
React.useEffect(setScaleParam, [props.gap]);
|
||||
|
||||
const handleImgLoadError = () => {
|
||||
const { onError } = props;
|
||||
const errorFlag = onError?.();
|
||||
if (errorFlag !== false) {
|
||||
setIsImgExist(false);
|
||||
}
|
||||
};
|
||||
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
shape,
|
||||
size: customSize,
|
||||
src,
|
||||
srcSet,
|
||||
icon,
|
||||
className,
|
||||
rootClassName,
|
||||
alt,
|
||||
draggable,
|
||||
children,
|
||||
crossOrigin,
|
||||
...others
|
||||
} = props;
|
||||
|
||||
const size = useSize((ctxSize) => customSize ?? avatarCtx?.size ?? ctxSize ?? 'default');
|
||||
|
||||
const needResponsive = Object.keys(typeof size === 'object' ? size || {} : {}).some((key) =>
|
||||
['xs', 'sm', 'md', 'lg', 'xl', 'xxl'].includes(key),
|
||||
);
|
||||
const screens = useBreakpoint(needResponsive);
|
||||
const responsiveSizeStyle = React.useMemo<React.CSSProperties>(() => {
|
||||
if (typeof size !== 'object') {
|
||||
return {};
|
||||
}
|
||||
|
||||
const currentBreakpoint: Breakpoint = responsiveArray.find((screen) => screens[screen])!;
|
||||
|
||||
const currentSize = size[currentBreakpoint];
|
||||
|
||||
return currentSize
|
||||
? {
|
||||
width: currentSize,
|
||||
height: currentSize,
|
||||
lineHeight: `${currentSize}px`,
|
||||
fontSize: currentSize && (icon || children) ? currentSize / 2 : 18,
|
||||
}
|
||||
: {};
|
||||
}, [screens, size]);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const warning = devUseWarning('Avatar');
|
||||
|
||||
warning(
|
||||
!(typeof icon === 'string' && icon.length > 2),
|
||||
'breaking',
|
||||
`\`icon\` is using ReactNode instead of string naming in v4. Please check \`${icon}\` at https://ant.design/components/icon`,
|
||||
);
|
||||
}
|
||||
|
||||
const prefixCls = getPrefixCls('avatar', customizePrefixCls);
|
||||
const rootCls = useCSSVarCls(prefixCls);
|
||||
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
|
||||
|
||||
const sizeCls = classNames({
|
||||
[`${prefixCls}-lg`]: size === 'large',
|
||||
[`${prefixCls}-sm`]: size === 'small',
|
||||
});
|
||||
|
||||
const hasImageElement = React.isValidElement(src);
|
||||
|
||||
const mergedShape = shape || avatarCtx?.shape || 'circle';
|
||||
|
||||
const classString = classNames(
|
||||
prefixCls,
|
||||
sizeCls,
|
||||
avatar?.className,
|
||||
`${prefixCls}-${mergedShape}`,
|
||||
{
|
||||
[`${prefixCls}-image`]: hasImageElement || (src && isImgExist),
|
||||
[`${prefixCls}-icon`]: !!icon,
|
||||
},
|
||||
cssVarCls,
|
||||
rootCls,
|
||||
className,
|
||||
rootClassName,
|
||||
hashId,
|
||||
);
|
||||
|
||||
const sizeStyle: React.CSSProperties =
|
||||
typeof size === 'number'
|
||||
? {
|
||||
width: size,
|
||||
height: size,
|
||||
lineHeight: `${size}px`,
|
||||
fontSize: icon ? size / 2 : 18,
|
||||
}
|
||||
: {};
|
||||
|
||||
let childrenToRender: React.ReactNode;
|
||||
if (typeof src === 'string' && isImgExist) {
|
||||
childrenToRender = (
|
||||
<img
|
||||
src={src}
|
||||
draggable={draggable}
|
||||
srcSet={srcSet}
|
||||
onError={handleImgLoadError}
|
||||
alt={alt}
|
||||
crossOrigin={crossOrigin}
|
||||
/>
|
||||
);
|
||||
} else if (hasImageElement) {
|
||||
childrenToRender = src;
|
||||
} else if (icon) {
|
||||
childrenToRender = icon;
|
||||
} else if (mounted || scale !== 1) {
|
||||
const transformString = `scale(${scale}) translateX(-50%)`;
|
||||
const childrenStyle: React.CSSProperties = {
|
||||
msTransform: transformString,
|
||||
WebkitTransform: transformString,
|
||||
transform: transformString,
|
||||
};
|
||||
|
||||
const sizeChildrenStyle: React.CSSProperties =
|
||||
typeof size === 'number'
|
||||
? {
|
||||
lineHeight: `${size}px`,
|
||||
}
|
||||
: {};
|
||||
|
||||
childrenToRender = (
|
||||
<ResizeObserver onResize={setScaleParam}>
|
||||
<span
|
||||
className={`${prefixCls}-string`}
|
||||
ref={avatarChildrenRef}
|
||||
style={{ ...sizeChildrenStyle, ...childrenStyle }}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</ResizeObserver>
|
||||
);
|
||||
} else {
|
||||
childrenToRender = (
|
||||
<span className={`${prefixCls}-string`} style={{ opacity: 0 }} ref={avatarChildrenRef}>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// The event is triggered twice from bubbling up the DOM tree.
|
||||
// see https://codesandbox.io/s/kind-snow-9lidz
|
||||
delete others.onError;
|
||||
delete others.gap;
|
||||
|
||||
return wrapCSSVar(
|
||||
<span
|
||||
{...others}
|
||||
style={{ ...sizeStyle, ...responsiveSizeStyle, ...avatar?.style, ...others.style }}
|
||||
className={classString}
|
||||
ref={avatarNodeMergeRef}
|
||||
>
|
||||
{childrenToRender}
|
||||
</span>,
|
||||
);
|
||||
};
|
||||
|
||||
const Avatar = React.forwardRef<HTMLSpanElement, AvatarProps>(InternalAvatar);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
Avatar.displayName = 'Avatar';
|
||||
}
|
||||
|
||||
export default Avatar;
|
7
packages/meta/src/avatar/demo/badge.md
Normal file
7
packages/meta/src/avatar/demo/badge.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
通常用于消息提示。
|
||||
|
||||
## en-US
|
||||
|
||||
Usually used for reminders and notifications.
|
16
packages/meta/src/avatar/demo/badge.tsx
Normal file
16
packages/meta/src/avatar/demo/badge.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import { Avatar, Badge, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space size={24}>
|
||||
<Badge count={1}>
|
||||
<Avatar shape="square" icon={<UserOutlined />} />
|
||||
</Badge>
|
||||
<Badge dot>
|
||||
<Avatar shape="square" icon={<UserOutlined />} />
|
||||
</Badge>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/avatar/demo/basic.md
Normal file
7
packages/meta/src/avatar/demo/basic.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
头像有三种尺寸,两种形状可选。
|
||||
|
||||
## en-US
|
||||
|
||||
Three sizes and two shapes are available.
|
22
packages/meta/src/avatar/demo/basic.tsx
Normal file
22
packages/meta/src/avatar/demo/basic.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import { UserOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import { Avatar, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space direction="vertical" size={16}>
|
||||
<Space wrap size={16}>
|
||||
<Avatar size={64} icon={<UserOutlined />} />
|
||||
<Avatar size="large" icon={<UserOutlined />} />
|
||||
<Avatar icon={<UserOutlined />} />
|
||||
<Avatar size="small" icon={<UserOutlined />} />
|
||||
</Space>
|
||||
<Space wrap size={16}>
|
||||
<Avatar shape="square" size={64} icon={<UserOutlined />} />
|
||||
<Avatar shape="square" size="large" icon={<UserOutlined />} />
|
||||
<Avatar shape="square" icon={<UserOutlined />} />
|
||||
<Avatar shape="square" size="small" icon={<UserOutlined />} />
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/avatar/demo/comonent-token.md
Normal file
7
packages/meta/src/avatar/demo/comonent-token.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
Component Token Debug.
|
||||
|
||||
## en-US
|
||||
|
||||
Component Token Debug
|
51
packages/meta/src/avatar/demo/component-token.tsx
Normal file
51
packages/meta/src/avatar/demo/component-token.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
import { AntDesignOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import React from 'react';
|
||||
import { Avatar, Badge, ConfigProvider, Space, Tooltip } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
components: {
|
||||
Avatar: {
|
||||
containerSize: 60,
|
||||
containerSizeLG: 30,
|
||||
containerSizeSM: 16,
|
||||
|
||||
textFontSize: 18,
|
||||
textFontSizeLG: 28,
|
||||
textFontSizeSM: 12,
|
||||
|
||||
borderRadius: 10,
|
||||
groupOverlapping: -10,
|
||||
groupBorderColor: '#eee',
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Space>
|
||||
<Avatar shape="circle" src="http://abc.com/not-exist.jpg">
|
||||
A
|
||||
</Avatar>
|
||||
</Space>
|
||||
<Space>
|
||||
<Avatar.Group maxCount={2} maxStyle={{ color: '#f56a00', backgroundColor: '#fde3cf' }}>
|
||||
<Avatar src="https://api.dicebear.com/7.x/miniavs/svg?seed=2" />
|
||||
<Avatar style={{ backgroundColor: '#f56a00' }}>K</Avatar>
|
||||
<Tooltip title="Ant User" placement="top">
|
||||
<Avatar style={{ backgroundColor: '#87d068' }} icon={<UserOutlined />} />
|
||||
</Tooltip>
|
||||
<Avatar style={{ backgroundColor: '#1890ff' }} icon={<AntDesignOutlined />} />
|
||||
</Avatar.Group>
|
||||
</Space>
|
||||
<Space>
|
||||
<Badge count={1}>
|
||||
<Avatar shape="square" icon={<UserOutlined />} />
|
||||
</Badge>
|
||||
<Badge dot>
|
||||
<Avatar shape="square" icon={<UserOutlined />} />
|
||||
</Badge>
|
||||
</Space>
|
||||
</ConfigProvider>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/avatar/demo/dynamic.md
Normal file
7
packages/meta/src/avatar/demo/dynamic.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
对于字符型的头像,当字符串较长时,字体大小可以根据头像宽度自动调整。也可使用 `gap` 来设置字符距离左右两侧边界单位像素。
|
||||
|
||||
## en-US
|
||||
|
||||
For letter type Avatar, when the letters are too long to display, the font size can be automatically adjusted according to the width of the Avatar. You can also use `gap` to set the unit distance between left and right sides.
|
43
packages/meta/src/avatar/demo/dynamic.tsx
Normal file
43
packages/meta/src/avatar/demo/dynamic.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Avatar, Button } from '@zhst/meta';
|
||||
|
||||
const UserList = ['U', 'Lucy', 'Tom', 'Edward'];
|
||||
const ColorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
|
||||
const GapList = [4, 3, 2, 1];
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [user, setUser] = useState(UserList[0]);
|
||||
const [color, setColor] = useState(ColorList[0]);
|
||||
const [gap, setGap] = useState(GapList[0]);
|
||||
|
||||
const changeUser = () => {
|
||||
const index = UserList.indexOf(user);
|
||||
setUser(index < UserList.length - 1 ? UserList[index + 1] : UserList[0]);
|
||||
setColor(index < ColorList.length - 1 ? ColorList[index + 1] : ColorList[0]);
|
||||
};
|
||||
|
||||
const changeGap = () => {
|
||||
const index = GapList.indexOf(gap);
|
||||
setGap(index < GapList.length - 1 ? GapList[index + 1] : GapList[0]);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Avatar style={{ backgroundColor: color, verticalAlign: 'middle' }} size="large" gap={gap}>
|
||||
{user}
|
||||
</Avatar>
|
||||
<Button
|
||||
size="small"
|
||||
style={{ margin: '0 16px', verticalAlign: 'middle' }}
|
||||
onClick={changeUser}
|
||||
>
|
||||
ChangeUser
|
||||
</Button>
|
||||
<Button size="small" style={{ verticalAlign: 'middle' }} onClick={changeGap}>
|
||||
changeGap
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
7
packages/meta/src/avatar/demo/fallback.md
Normal file
7
packages/meta/src/avatar/demo/fallback.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
图片不存在时,如果 `src` 本身是个 ReactElement,会尝试回退到 `src`,否则尝试回退到 `icon`,最后回退到显示 `children`。
|
||||
|
||||
## en-US
|
||||
|
||||
When the image does not exist, if `src` is a ReactElement, it will try to fall back to `src`, otherwise it will try to fall back to `icon`, and finally fall back to displaying `children`.
|
15
packages/meta/src/avatar/demo/fallback.tsx
Normal file
15
packages/meta/src/avatar/demo/fallback.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Avatar, Space } from '@zhst/meta';
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Space>
|
||||
<Avatar shape="circle" src="http://abc.com/not-exist.jpg">
|
||||
A
|
||||
</Avatar>
|
||||
<Avatar shape="circle" src="http://abc.com/not-exist.jpg">
|
||||
ABC
|
||||
</Avatar>
|
||||
</Space>
|
||||
);
|
||||
|
||||
export default App;
|
7
packages/meta/src/avatar/demo/group.md
Normal file
7
packages/meta/src/avatar/demo/group.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
头像组合展现。
|
||||
|
||||
## en-US
|
||||
|
||||
Avatar group display.
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user