feat(zhst/meta): 全量迁移antd5.17.4

This commit is contained in:
NICE CODE BY DEV 2024-06-05 11:17:48 +08:00
parent e5b176c148
commit 357a4c677a
1867 changed files with 520625 additions and 234938 deletions

View File

@ -16,7 +16,7 @@
#root .dumi-default-sidebar {
width: 188px;
overflow-y: hidden;
overflow-y: scroll;
/* stylelint-disable-next-line rule-empty-line-before */
&:hover {
overflow-y: auto;

View File

@ -40,5 +40,5 @@ export default defineConfig({
monorepoRedirect: {
srcDir: ['packages', 'src'],
peerDeps: true,
},
}
});

View File

@ -82,5 +82,12 @@
},
"authors": [
"dev<710328466@qq.com>"
]
],
"dependencies": {
"@ant-design/happy-work-theme": "^1.0.0",
"@zhst/meta": "workspace:^",
"rc-rate": "~2.12.0",
"react-fast-marquee": "^1.6.4",
"react-infinite-scroll-component": "^6.1.0"
}
}

View File

@ -4,6 +4,7 @@ import BoxPanel from './components/boxPanel';
import type { BoxPanelProps } from './components/boxPanel';
import './index.less'
import classNames from 'classnames';
export interface BoxSelectTreeProps extends BoxPanelProps {
onTabChange?: (e: any) => void
tabsProps?: TabsProps

View File

@ -17,7 +17,6 @@ const demo = () => {
}
const onBoxBatchDelete = () => {
console.log('盒子批量删除', checkedKeys)
modal.warning({
content: (
<div>

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,6 @@
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; }
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
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); }
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
import { cloneDeep, dataURLToBlob, get, isNull } from "@zhst/func";
var proto = {
Common: {

View File

@ -1,9 +1,4 @@
function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
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 _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 _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import React, { useRef, useState, useEffect, forwardRef, useImperativeHandle, useContext } from 'react';
// @ts-ignore
import { generateImg, get, addEventListenerWrapper } from '@zhst/func';

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,4 @@
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; }
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); }
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
//@ts-nocheck
import { isObject, isNumber, assign, isFunction } from '@zhst/func';
import { hasClass, addClass, removeClass } from 'rc-util/lib/Dom/class';

View File

@ -1,9 +1,4 @@
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; }
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); }
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
//@ts-nocheck
import { isNumber, get, addEventListenerWrapper } from '@zhst/func';
import { addClass, removeClass } from 'rc-util/lib/Dom/class.js';

View File

@ -1,17 +1,11 @@
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); }
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
var _excluded = ["x", "y"],
_excluded2 = ["x", "y"],
_excluded3 = ["x", "y", "w", "h", "image"],
_excluded4 = ["x", "y"],
_excluded5 = ["x", "y"],
_excluded6 = ["x", "y", "w", "h"];
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; }
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 _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
//@ts-nocheck
import * as turf from '@turf/turf';
import { AXIS_TYPE_ORIGIN, AXIS_TYPE_CANVAS, AXIS_TYPE_IMAGE } from "./constants";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,5 @@
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 _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
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 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; }
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); }
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
// @ts-nocheck
import { isNil, isArray, isFunction } from '@zhst/func';
import * as turf from '@turf/turf';

File diff suppressed because one or more lines are too long

View File

@ -1,22 +1,12 @@
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
import _assertThisInitialized from "@babel/runtime/helpers/esm/assertThisInitialized";
import _inherits from "@babel/runtime/helpers/esm/inherits";
import _createSuper from "@babel/runtime/helpers/esm/createSuper";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
var _excluded = ["className", "autoPlay", "config", "onCreate", "playId"];
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; }
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); }
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); }
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; } catch (e) { return false; } }
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
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); }
import React, { Component } from 'react';
import flvjs from 'flv.js';
import { isEqual } from '@zhst/func';

View File

@ -1,6 +1,5 @@
import _objectWithoutProperties from "@babel/runtime/helpers/esm/objectWithoutProperties";
var _excluded = ["className", "style", "showSlider"];
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import React from 'react';
import classNames from 'classnames';
import { Slider, ConfigProvider } from 'antd';

View File

@ -6,6 +6,7 @@ export { default as CropperImage } from "./cropperImage";
export { default as AttachImage } from "./attachImage";
export { default as RelatedImage } from "./relatedImage";
export { default as VideoPlayer } from "./VideoPlayer";
// antd
export { default as Tabs } from "./tabs";
export { default as Tree } from "./tree";
export { default as message } from "./message";

View File

@ -61,8 +61,6 @@
"@types/react": "^18.2.46",
"@types/react-copy-to-clipboard": "^5.0.7",
"@types/react-dom": "^18.2.18",
"@types/react-highlight-words": "^0.16.7",
"@types/react-resizable": "^3.0.7",
"@types/semver": "^7.5.6",
"@types/tar": "^6.1.10",
"@types/throttle-debounce": "^5.0.2",
@ -77,12 +75,15 @@
},
"dependencies": {
"@ant-design/colors": "^7.0.2",
"@ant-design/cssinjs": "^1.18.2",
"@ant-design/icons": "^5.2.6",
"@ctrl/tinycolor": "^4.0.2",
"@ant-design/cssinjs": "^1.19.1",
"@ant-design/icons": "^5.3.7",
"@ant-design/react-slick": "~1.1.2",
"@babel/runtime": "^7.24.5",
"@ctrl/tinycolor": "^3.6.1",
"@rc-component/color-picker": "~1.5.3",
"@rc-component/mutate-observer": "^1.1.0",
"@rc-component/tour": "^1.12.3",
"@rc-component/trigger": "^1.18.2",
"@rc-component/tour": "~1.15.0",
"@rc-component/trigger": "^2.2.0",
"@turf/boolean-point-in-polygon": "^6.5.0",
"@turf/turf": "^6.5.0",
"@types/downloadjs": "^1.4.6",
@ -90,7 +91,6 @@
"@zhst/func": "workspace:^",
"@zhst/hooks": "workspace:^",
"@zhst/icon": "workspace:^",
"@zhst/meta": "workspace:^",
"antd": "^5.12.5",
"antd-img-crop": "^4.21.0",
"antd-style": "^3.6.1",
@ -101,44 +101,47 @@
"fabric": "^5.3.0",
"flv.js": "^1.6.2",
"lunar-typescript": "^1.7.3",
"qrcode.react": "^3.1.0",
"rc-align": "^4.0.15",
"rc-cascader": "~3.20.0",
"rc-checkbox": "~3.1.0",
"rc-collapse": "~3.7.2",
"rc-dialog": "~9.3.4",
"rc-drawer": "~6.5.2",
"rc-dropdown": "~4.1.0",
"rc-field-form": "~1.41.0",
"rc-image": "~7.5.1",
"rc-input": "~1.3.11",
"rc-input-number": "~8.4.0",
"rc-mentions": "~2.9.1",
"rc-menu": "~9.12.4",
"rc-motion": "^2.9.0",
"rc-notification": "~5.3.0",
"rc-cascader": "~3.26.0",
"rc-checkbox": "~3.3.0",
"rc-collapse": "~3.7.3",
"rc-dialog": "~9.4.0",
"rc-drawer": "~7.1.0",
"rc-dropdown": "~4.2.0",
"rc-field-form": "~2.0.1",
"rc-image": "~7.6.0",
"rc-input": "~1.5.1",
"rc-input-number": "~9.1.0",
"rc-mentions": "~2.13.1",
"rc-menu": "~9.14.0",
"rc-motion": "^2.9.1",
"rc-notification": "~5.4.0",
"rc-pagination": "~4.0.4",
"rc-picker": "~3.14.6",
"rc-progress": "~3.5.1",
"rc-picker": "~4.5.0",
"rc-progress": "~4.0.0",
"rc-rate": "~2.12.0",
"rc-resize-observer": "^1.4.0",
"rc-segmented": "~2.2.2",
"rc-select": "~14.10.0",
"rc-slider": "~10.5.0",
"rc-segmented": "~2.3.0",
"rc-select": "~14.14.0",
"rc-slider": "~10.6.2",
"rc-steps": "~6.0.1",
"rc-switch": "~4.1.0",
"rc-table": "~7.36.1",
"rc-tabs": "~12.14.1",
"rc-textarea": "~1.5.3",
"rc-tooltip": "~6.1.3",
"rc-tree": "~5.8.2",
"rc-tree-select": "~5.15.0",
"rc-table": "~7.45.7",
"rc-tabs": "~15.1.0",
"rc-textarea": "~1.7.0",
"rc-tooltip": "~6.2.0",
"rc-tree": "~5.8.7",
"rc-tree-select": "~5.21.0",
"rc-upload": "~4.5.2",
"rc-util": "^5.38.1",
"rc-util": "^5.41.0",
"rc-virtual-list": "^3.14.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-draggable": "^4.4.6",
"scroll-into-view-if-needed": "^3.1.0",
"swiper": "^11.1.1"
"swiper": "^11.1.1",
"throttle-debounce": "^5.0.0"
},
"peerDependencies": {
"react": ">=16.9.0",

View File

@ -1,6 +1,6 @@
import React, { useRef, useState } from 'react';
import { CompareImage, Space } from '@zhst/meta'
import { CompareImage } from '@zhst/meta'
export default () => {
const ref = useRef(null)
@ -10,26 +10,24 @@ export default () => {
})
return (
<Space>
<CompareImage
label="目标图"
url={data.url}
score={data.score}
openRoll
ref={ref}
onPre={() => {
setData({
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
score: '0.8'
})
}}
onNext={() => {
setData({
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
score: '0.4'
})
}}
/>
</Space>
<CompareImage
label="目标图"
url={data.url}
score={data.score}
openRoll
ref={ref}
onPre={() => {
setData({
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
score: '0.8'
})
}}
onNext={() => {
setData({
url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png',
score: '0.4'
})
}}
/>
)
}

View File

@ -0,0 +1,78 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`antd exports modules correctly 1`] = `
[
"Affix",
"Alert",
"Anchor",
"App",
"AutoComplete",
"Avatar",
"BackTop",
"Badge",
"Breadcrumb",
"Button",
"Calendar",
"Card",
"Carousel",
"Cascader",
"Checkbox",
"Col",
"Collapse",
"ColorPicker",
"ConfigProvider",
"DatePicker",
"Descriptions",
"Divider",
"Drawer",
"Dropdown",
"Empty",
"Flex",
"FloatButton",
"Form",
"Grid",
"Image",
"Input",
"InputNumber",
"Layout",
"List",
"Mentions",
"Menu",
"Modal",
"Pagination",
"Popconfirm",
"Popover",
"Progress",
"QRCode",
"Radio",
"Rate",
"Result",
"Row",
"Segmented",
"Select",
"Skeleton",
"Slider",
"Space",
"Spin",
"Statistic",
"Steps",
"Switch",
"Table",
"Tabs",
"Tag",
"TimePicker",
"Timeline",
"Tooltip",
"Tour",
"Transfer",
"Tree",
"TreeSelect",
"Typography",
"Upload",
"Watermark",
"message",
"notification",
"theme",
"version",
]
`;

View File

@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SetUp.Test diff of React 18 & React 17 1`] = `
NodeList [
<div>
bamboo
little
</div>,
<div />,
]
`;

View File

@ -0,0 +1,35 @@
const fs = require('fs');
const path = require('path');
const blogList = [
'check-conduct',
'contributor-development-maintenance-guide',
'css-in-js',
'extract-ssr',
'getContainer',
'github-actions-workflow',
'issue-helper',
'mock-project-build',
'modal-hook-order',
'testing-migrate',
'render-times',
'to-be-collaborator',
'tooltip-align',
'tree-shaking',
'why-not-static',
].map((blogName) => path.join(__dirname, `../../docs/blog/${blogName}.en-US.md`));
describe('blog', () => {
it('should not include Chinese in en-US blog', () => {
blogList.forEach((blog) => {
fs.readFile(blog, (err: NodeJS.ErrnoException | null, data: Buffer) => {
if (err) {
return;
}
const includeChinese = /[\u4E00-\u9FA5]/.test(data.toString());
expect(includeChinese).toBe(false);
});
});
});
});

View File

@ -0,0 +1,13 @@
const OLD_NODE_ENV = process.env.NODE_ENV;
process.env.NODE_ENV = 'development';
const antd = require('..');
describe('antd', () => {
afterAll(() => {
process.env.NODE_ENV = OLD_NODE_ENV;
});
it('exports modules correctly', () => {
expect(Object.keys(antd)).toMatchSnapshot();
});
});

View File

@ -0,0 +1,53 @@
import * as React from 'react';
import { globSync } from 'glob';
import { renderToString } from 'react-dom/server';
import type { Options } from '../../tests/shared/demoTest';
(global as any).testConfig = {};
jest.mock('../../tests/shared/demoTest', () => {
function fakeDemoTest(name: string, option: Options = {}) {
(global as any).testConfig[name] = option;
}
fakeDemoTest.rootPropsTest = () => {};
return fakeDemoTest;
});
describe('node', () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date('2016-11-22'));
});
// Find the component exist demo test file
const files = globSync(`./components/*/__tests__/demo.test.@(j|t)s?(x)`);
files.forEach((componentTestFile) => {
const componentName = componentTestFile.match(/components\/([^/]*)\//)![1];
// Test for ssr
describe(componentName, () => {
const demoList = globSync(`./components/${componentName}/demo/*.tsx`).filter(
(file) => !file.includes('_semantic'),
);
// Use mock to get config
require(`../../${componentTestFile}`); // eslint-disable-line global-require, import/no-dynamic-require
const option = (global as any).testConfig?.[componentName];
demoList.forEach((demoFile) => {
const skip: string[] = option?.skip || [];
const test = skip.some((skipMarkdown) => demoFile.includes(skipMarkdown)) ? it.skip : it;
test(demoFile, () => {
const Demo = require(`../../${demoFile}`).default; // eslint-disable-line global-require, import/no-dynamic-require
expect(() => {
renderToString(<Demo />);
}).not.toThrow();
});
});
});
});
});

View File

@ -0,0 +1,15 @@
import * as React from 'react';
import { render } from '../../tests/utils';
describe('SetUp.Test', () => {
it('diff of React 18 & React 17', () => {
const { container } = render(
<>
<div>{['bamboo', '', 'little']}</div>
<div>{['', '']}</div>
</>,
);
expect(container.childNodes).toMatchSnapshot();
});
});

View File

@ -1,5 +1,6 @@
---
category: Components
toc: content
title: Util 工具类
subtitle: 工具类
description: 辅助开发,提供一些常用的工具方法。

View File

@ -0,0 +1,197 @@
import React, { useEffect, useRef } from 'react';
import Affix from '..';
import accessibilityTest from '../../../tests/shared/accessibilityTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render, triggerResize, waitFakeTimer } from '../../../tests/utils';
import Button from '../../button';
const events: Partial<Record<keyof HTMLElementEventMap, (ev: Partial<Event>) => void>> = {};
interface AffixProps {
offsetTop?: number;
offsetBottom?: number;
style?: React.CSSProperties;
onChange?: () => void;
onTestUpdatePosition?: () => void;
}
const AffixMounter: React.FC<AffixProps> = (props) => {
const container = useRef<HTMLDivElement>(null);
useEffect(() => {
if (container.current) {
container.current.addEventListener = jest
.fn()
.mockImplementation((event: keyof HTMLElementEventMap, cb: (ev: Event) => void) => {
(events as any)[event] = cb;
});
}
}, []);
return (
<div ref={container} className="container">
<Affix className="fixed" target={() => container.current} {...props}>
<Button type="primary">Fixed at the top of container</Button>
</Affix>
</div>
);
};
describe('Affix Render', () => {
rtlTest(Affix as any);
accessibilityTest(Affix as any);
const domMock = jest.spyOn(HTMLElement.prototype, 'getBoundingClientRect');
const classRect: Record<string, DOMRect> = { container: { top: 0, bottom: 100 } as DOMRect };
beforeEach(() => {
jest.useFakeTimers();
});
beforeAll(() => {
domMock.mockImplementation(function fn(this: HTMLElement) {
return classRect[this.className] || { top: 0, bottom: 0 };
});
});
afterEach(() => {
jest.useRealTimers();
jest.clearAllTimers();
});
afterAll(() => {
domMock.mockRestore();
});
const movePlaceholder = async (top: number) => {
classRect.fixed = { top, bottom: top } as DOMRect;
if (events.scroll == null) {
throw new Error('scroll should be set');
}
events.scroll({ type: 'scroll' });
await waitFakeTimer();
};
it('Anchor render perfectly', async () => {
const { container } = render(<AffixMounter />);
await waitFakeTimer();
await movePlaceholder(0);
expect(container.querySelector('.ant-affix')).toBeFalsy();
await movePlaceholder(-100);
expect(container.querySelector('.ant-affix')).toBeTruthy();
await movePlaceholder(0);
expect(container.querySelector('.ant-affix')).toBeFalsy();
});
it('Anchor correct render when target is null', async () => {
render(<Affix target={() => null}>test</Affix>);
await waitFakeTimer();
});
it('support offsetBottom', async () => {
const { container } = render(<AffixMounter offsetBottom={0} />);
await waitFakeTimer();
await movePlaceholder(300);
expect(container.querySelector('.ant-affix')).toBeTruthy();
await movePlaceholder(0);
expect(container.querySelector('.ant-affix')).toBeFalsy();
await movePlaceholder(300);
expect(container.querySelector('.ant-affix')).toBeTruthy();
});
it('updatePosition when offsetTop changed', async () => {
const onChange = jest.fn();
const { container, rerender } = render(<AffixMounter offsetTop={0} onChange={onChange} />);
await waitFakeTimer();
await movePlaceholder(-100);
expect(onChange).toHaveBeenLastCalledWith(true);
expect(container.querySelector('.ant-affix')).toHaveStyle({ top: 0 });
rerender(<AffixMounter offsetTop={10} onChange={onChange} />);
await waitFakeTimer();
expect(container.querySelector('.ant-affix')).toHaveStyle({ top: `10px` });
});
describe('updatePosition when target changed', () => {
it('function change', () => {
document.body.innerHTML = `<div id="mounter" />`;
const target = document.getElementById('mounter');
const getTarget = () => target;
const { container, rerender } = render(<Affix target={getTarget}>{null}</Affix>);
rerender(<Affix target={() => null}>{null}</Affix>);
expect(container.querySelector(`div[aria-hidden="true"]`)).toBeNull();
expect(container.querySelector('.ant-affix')?.getAttribute('style')).toBeUndefined();
});
it('check position change before measure', async () => {
const { container } = render(
<>
<Affix offsetTop={10}>
<Button>top</Button>
</Affix>
<Affix offsetBottom={10}>
<Button>bottom</Button>
</Affix>
</>,
);
await waitFakeTimer();
await movePlaceholder(1000);
expect(container.querySelector<HTMLDivElement>('.ant-affix')).toBeTruthy();
});
it('do not measure when hidden', async () => {
const { container, rerender } = render(<AffixMounter offsetBottom={0} />);
await waitFakeTimer();
const affixStyleEle = container.querySelector('.ant-affix');
const firstAffixStyle = affixStyleEle ? affixStyleEle.getAttribute('style') : null;
rerender(<AffixMounter offsetBottom={0} style={{ display: 'none' }} />);
await waitFakeTimer();
const secondAffixStyle = affixStyleEle ? affixStyleEle.getAttribute('style') : null;
expect(firstAffixStyle).toEqual(secondAffixStyle);
});
});
describe('updatePosition when size changed', () => {
it('add class automatically', async () => {
document.body.innerHTML = '<div id="mounter" />';
const { container } = render(<AffixMounter offsetBottom={0} />, {
container: document.getElementById('mounter')!,
});
await waitFakeTimer();
await movePlaceholder(300);
expect(container.querySelector(`div[aria-hidden="true"]`)).toBeTruthy();
expect(container.querySelector('.ant-affix')?.getAttribute('style')).toBeTruthy();
});
// Trigger inner and outer element for the two <ResizeObserver>s.
['.ant-btn', '.fixed'].forEach((selector) => {
it(`trigger listener when size change: ${selector}`, async () => {
const updateCalled = jest.fn();
const { container } = render(
<AffixMounter offsetBottom={0} onTestUpdatePosition={updateCalled} />,
{ container: document.getElementById('mounter')! },
);
updateCalled.mockReset();
triggerResize(container.querySelector(selector)!);
await waitFakeTimer();
expect(updateCalled).toHaveBeenCalled();
});
});
});
});

View File

@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Affix Render rtl render component should be rendered correctly in RTL direction 1`] = `
<div>
<div
class=""
/>
</div>
`;

View File

@ -0,0 +1,116 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/affix/demo/basic.tsx extend context correctly 1`] = `
Array [
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Affix top
</span>
</button>
</div>
</div>,
<br />,
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Affix bottom
</span>
</button>
</div>
</div>,
]
`;
exports[`renders components/affix/demo/basic.tsx extend context correctly 2`] = `[]`;
exports[`renders components/affix/demo/debug.tsx extend context correctly 1`] = `
<div
style="height: 10000px;"
>
<div>
Top
</div>
<div>
<div
class=""
>
<div
style="background: red;"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Affix top
</span>
</button>
</div>
</div>
</div>
<div>
Bottom
</div>
</div>
`;
exports[`renders components/affix/demo/debug.tsx extend context correctly 2`] = `[]`;
exports[`renders components/affix/demo/on-change.tsx extend context correctly 1`] = `
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
120px to affix top
</span>
</button>
</div>
</div>
`;
exports[`renders components/affix/demo/on-change.tsx extend context correctly 2`] = `[]`;
exports[`renders components/affix/demo/target.tsx extend context correctly 1`] = `
<div
style="width: 100%; height: 100px; overflow: auto; border: 1px solid #40a9ff;"
>
<div
style="width: 100%; height: 1000px;"
>
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Fixed at the top of container
</span>
</button>
</div>
</div>
</div>
</div>
`;
exports[`renders components/affix/demo/target.tsx extend context correctly 2`] = `[]`;

View File

@ -0,0 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/affix/demo/basic.tsx correctly 1`] = `
Array [
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Affix top
</span>
</button>
</div>
</div>,
<br />,
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Affix bottom
</span>
</button>
</div>
</div>,
]
`;
exports[`renders components/affix/demo/debug.tsx correctly 1`] = `
<div
style="height:10000px"
>
<div>
Top
</div>
<div>
<div
class=""
>
<div
style="background:red"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Affix top
</span>
</button>
</div>
</div>
</div>
<div>
Bottom
</div>
</div>
`;
exports[`renders components/affix/demo/on-change.tsx correctly 1`] = `
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-default"
type="button"
>
<span>
120px to affix top
</span>
</button>
</div>
</div>
`;
exports[`renders components/affix/demo/target.tsx correctly 1`] = `
<div
style="width:100%;height:100px;overflow:auto;border:1px solid #40a9ff"
>
<div
style="width:100%;height:1000px"
>
<div>
<div
class=""
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Fixed at the top of container
</span>
</button>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,3 @@
import { extendTest } from '../../../tests/shared/demoTest';
extendTest('affix');

View File

@ -0,0 +1,35 @@
import * as React from 'react';
import { spyElementPrototype } from 'rc-util/lib/test/domHook';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('affix', {
testRootProps: false,
});
rootPropsTest(
'affix',
(Affix, props) => (
<Affix {...props} className="fixed" target={() => document.querySelector('#holder')}>
Bamboo
</Affix>
),
{
beforeRender: () => {
spyElementPrototype(HTMLElement, 'getBoundingClientRect', function getBoundingClientRect() {
// @ts-ignore
if (this.id === 'holder') {
return { top: 0, bottom: 100 };
}
// @ts-ignore
if (this.className === 'fixed') {
return { top: -100, bottom: -100 };
}
return { top: 0, bottom: 0 };
});
},
findRootElements: () => document.querySelectorAll('.ant-affix'),
expectCount: 1,
},
);

View File

@ -0,0 +1,5 @@
import { imageDemoTest } from '../../../tests/shared/imageTest';
describe('Affix image', () => {
imageDemoTest('affix');
});

View File

@ -0,0 +1,7 @@
## zh-CN
最简单的用法。
## en-US
The simplest usage.

View File

@ -0,0 +1,24 @@
import React from 'react';
import { Affix, Button } from 'antd';
const App: React.FC = () => {
const [top, setTop] = React.useState<number>(100);
const [bottom, setBottom] = React.useState<number>(100);
return (
<>
<Affix offsetTop={top}>
<Button type="primary" onClick={() => setTop(top + 10)}>
Affix top
</Button>
</Affix>
<br />
<Affix offsetBottom={bottom}>
<Button type="primary" onClick={() => setBottom(bottom + 10)}>
Affix bottom
</Button>
</Affix>
</>
);
};
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
DEBUG
## en-US
DEBUG

View File

@ -0,0 +1,22 @@
import React, { useState } from 'react';
import { Affix, Button } from 'antd';
const App: React.FC = () => {
const [top, setTop] = useState(10);
return (
<div style={{ height: 10000 }}>
<div>Top</div>
<Affix offsetTop={top}>
<div style={{ background: 'red' }}>
<Button type="primary" onClick={() => setTop(top + 10)}>
Affix top
</Button>
</div>
</Affix>
<div>Bottom</div>
</div>
);
};
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
可以获得是否固定的状态。
## en-US
Callback with affixed state.

View File

@ -0,0 +1,10 @@
import React from 'react';
import { Affix, Button } from 'antd';
const App: React.FC = () => (
<Affix offsetTop={120} onChange={(affixed) => console.log(affixed)}>
<Button>120px to affix top</Button>
</Affix>
);
export default App;

View File

@ -0,0 +1,19 @@
## zh-CN
`target` 设置 `Affix` 需要监听其滚动事件的元素,默认为 `window`
## en-US
Set a `target` for 'Affix', which is listen to scroll event of target element (default is `window`).
<style>
#components-affix-demo-target .scrollable-container {
height: 100px;
overflow-y: scroll;
}
#components-affix-demo-target .background {
padding-top: 60px;
height: 300px;
background-image: url('https://zos.alipayobjects.com/rmsportal/RmjwQiJorKyobvI.jpg');
}
</style>

View File

@ -0,0 +1,29 @@
import React from 'react';
import { Affix, Button } from 'antd';
const containerStyle: React.CSSProperties = {
width: '100%',
height: 100,
overflow: 'auto',
border: '1px solid #40a9ff',
};
const style: React.CSSProperties = {
width: '100%',
height: 1000,
};
const App: React.FC = () => {
const [container, setContainer] = React.useState<HTMLDivElement | null>(null);
return (
<div style={containerStyle} ref={setContainer}>
<div style={style}>
<Affix target={() => container}>
<Button type="primary">Fixed at the top of container</Button>
</Affix>
</div>
</div>
);
};
export default App;

View File

@ -0,0 +1,280 @@
import React from 'react';
import classNames from 'classnames';
import ResizeObserver from 'rc-resize-observer';
import omit from 'rc-util/lib/omit';
import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import useStyle from './style';
import { getFixedBottom, getFixedTop, getTargetRect } from './utils';
const TRIGGER_EVENTS = [
'resize',
'scroll',
'touchstart',
'touchmove',
'touchend',
'pageshow',
'load',
] as const;
function getDefaultTarget() {
return typeof window !== 'undefined' ? window : null;
}
// Affix
export interface AffixProps {
/** Triggered when the specified offset is reached from the top of the window */
offsetTop?: number;
/** Triggered when the specified offset is reached from the bottom of the window */
offsetBottom?: number;
style?: React.CSSProperties;
/** Callback function triggered when fixed state changes */
onChange?: (affixed?: boolean) => void;
/** Set the element that Affix needs to listen to its scroll event, the value is a function that returns the corresponding DOM element */
target?: () => Window | HTMLElement | null;
prefixCls?: string;
className?: string;
rootClassName?: string;
children: React.ReactNode;
}
const AFFIX_STATUS_NONE = 0;
const AFFIX_STATUS_PREPARE = 1;
type AffixStatus = typeof AFFIX_STATUS_NONE | typeof AFFIX_STATUS_PREPARE;
interface AffixState {
affixStyle?: React.CSSProperties;
placeholderStyle?: React.CSSProperties;
status: AffixStatus;
lastAffix: boolean;
prevTarget: Window | HTMLElement | null;
}
export interface AffixRef {
updatePosition: ReturnType<typeof throttleByAnimationFrame>;
}
const Affix = React.forwardRef<AffixRef, AffixProps>((props, ref) => {
const {
style,
offsetTop,
offsetBottom,
prefixCls,
className,
rootClassName,
children,
target,
onChange,
} = props;
const { getPrefixCls, getTargetContainer } = React.useContext<ConfigConsumerProps>(ConfigContext);
const affixPrefixCls = getPrefixCls('affix', prefixCls);
const [lastAffix, setLastAffix] = React.useState(false);
const [affixStyle, setAffixStyle] = React.useState<React.CSSProperties>();
const [placeholderStyle, setPlaceholderStyle] = React.useState<React.CSSProperties>();
const status = React.useRef<AffixStatus>(AFFIX_STATUS_NONE);
const prevTarget = React.useRef<Window | HTMLElement | null>(null);
const prevListener = React.useRef<EventListener>();
const placeholderNodeRef = React.useRef<HTMLDivElement>(null);
const fixedNodeRef = React.useRef<HTMLDivElement>(null);
const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const targetFunc = target ?? getTargetContainer ?? getDefaultTarget;
const internalOffsetTop = offsetBottom === undefined && offsetTop === undefined ? 0 : offsetTop;
// =================== Measure ===================
const measure = () => {
if (
status.current !== AFFIX_STATUS_PREPARE ||
!fixedNodeRef.current ||
!placeholderNodeRef.current ||
!targetFunc
) {
return;
}
const targetNode = targetFunc();
if (targetNode) {
const newState: Partial<AffixState> = {
status: AFFIX_STATUS_NONE,
};
const placeholderRect = getTargetRect(placeholderNodeRef.current);
if (
placeholderRect.top === 0 &&
placeholderRect.left === 0 &&
placeholderRect.width === 0 &&
placeholderRect.height === 0
) {
return;
}
const targetRect = getTargetRect(targetNode);
const fixedTop = getFixedTop(placeholderRect, targetRect, internalOffsetTop);
const fixedBottom = getFixedBottom(placeholderRect, targetRect, offsetBottom);
if (fixedTop !== undefined) {
newState.affixStyle = {
position: 'fixed',
top: fixedTop,
width: placeholderRect.width,
height: placeholderRect.height,
};
newState.placeholderStyle = {
width: placeholderRect.width,
height: placeholderRect.height,
};
} else if (fixedBottom !== undefined) {
newState.affixStyle = {
position: 'fixed',
bottom: fixedBottom,
width: placeholderRect.width,
height: placeholderRect.height,
};
newState.placeholderStyle = {
width: placeholderRect.width,
height: placeholderRect.height,
};
}
newState.lastAffix = !!newState.affixStyle;
if (lastAffix !== newState.lastAffix) {
onChange?.(newState.lastAffix);
}
status.current = newState.status!;
setAffixStyle(newState.affixStyle);
setPlaceholderStyle(newState.placeholderStyle);
setLastAffix(newState.lastAffix);
}
};
const prepareMeasure = () => {
status.current = AFFIX_STATUS_PREPARE;
measure();
if (process.env.NODE_ENV === 'test') {
(props as any)?.onTestUpdatePosition?.();
}
};
const updatePosition = throttleByAnimationFrame(() => {
prepareMeasure();
});
const lazyUpdatePosition = throttleByAnimationFrame(() => {
// Check position change before measure to make Safari smooth
if (targetFunc && affixStyle) {
const targetNode = targetFunc();
if (targetNode && placeholderNodeRef.current) {
const targetRect = getTargetRect(targetNode);
const placeholderRect = getTargetRect(placeholderNodeRef.current);
const fixedTop = getFixedTop(placeholderRect, targetRect, internalOffsetTop);
const fixedBottom = getFixedBottom(placeholderRect, targetRect, offsetBottom);
if (
(fixedTop !== undefined && affixStyle.top === fixedTop) ||
(fixedBottom !== undefined && affixStyle.bottom === fixedBottom)
) {
return;
}
}
}
// Directly call prepare measure since it's already throttled.
prepareMeasure();
});
const addListeners = () => {
const listenerTarget = targetFunc?.();
if (!listenerTarget) {
return;
}
TRIGGER_EVENTS.forEach((eventName) => {
if (prevListener.current) {
prevTarget.current?.removeEventListener(eventName, prevListener.current);
}
listenerTarget?.addEventListener(eventName, lazyUpdatePosition);
});
prevTarget.current = listenerTarget;
prevListener.current = lazyUpdatePosition;
};
const removeListeners = () => {
if (timer.current) {
clearTimeout(timer.current);
timer.current = null;
}
const newTarget = targetFunc?.();
TRIGGER_EVENTS.forEach((eventName) => {
newTarget?.removeEventListener(eventName, lazyUpdatePosition);
if (prevListener.current) {
prevTarget.current?.removeEventListener(eventName, prevListener.current);
}
});
updatePosition.cancel();
lazyUpdatePosition.cancel();
};
React.useImperativeHandle(ref, () => ({ updatePosition }));
// mount & unmount
React.useEffect(() => {
// [Legacy] Wait for parent component ref has its value.
// We should use target as directly element instead of function which makes element check hard.
timer.current = setTimeout(addListeners);
return () => removeListeners();
}, []);
React.useEffect(() => {
addListeners();
}, [target, affixStyle]);
React.useEffect(() => {
updatePosition();
}, [target, offsetTop, offsetBottom]);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(affixPrefixCls);
const rootCls = classNames(rootClassName, hashId, affixPrefixCls, cssVarCls);
const mergedCls = classNames({ [rootCls]: affixStyle });
let otherProps = omit(props, [
'prefixCls',
'offsetTop',
'offsetBottom',
'target',
'onChange',
'rootClassName',
]);
if (process.env.NODE_ENV === 'test') {
otherProps = omit(otherProps, ['onTestUpdatePosition' as any]);
}
return wrapCSSVar(
<ResizeObserver onResize={updatePosition}>
<div style={style} className={className} ref={placeholderNodeRef} {...otherProps}>
{affixStyle && <div style={placeholderStyle} aria-hidden="true" />}
<div className={mergedCls} ref={fixedNodeRef} style={affixStyle}>
<ResizeObserver onResize={updatePosition}>{children}</ResizeObserver>
</div>
</div>
</ResizeObserver>,
);
});
if (process.env.NODE_ENV !== 'production') {
Affix.displayName = 'Affix';
}
export default Affix;

View File

@ -0,0 +1,63 @@
---
category: Components
toc: content
title: Affix
subtitle: 固钉
description: 将页面元素钉在可视范围。
cover: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*YSm4RI3iOJ8AAAAAAAAAAAAADrJ8AQ/original
coverDark: https://mdn.alipayobjects.com/huamei_7uahnr/afts/img/A*03dxS64LxeQAAAAAAAAAAAAADrJ8AQ/original
demo:
cols: 2
group:
title: 其他
order: 7
---
## 何时使用
当内容区域比较长,需要滚动页面时,这部分内容对应的操作或者导航需要在滚动范围内始终展现。常用于侧边菜单和按钮组合。
页面可视范围过小时,慎用此功能以免出现遮挡页面内容的情况。
> 开发者注意事项:
>
> 自 `5.10.0` 起,由于 Affix 组件由 class 重构为 FC之前获取 `ref` 并调用内部实例方法的写法都会失效。
## 代码演示
<!-- prettier-ignore -->
<code src="./demo/basic.tsx">基本</code>
<code src="./demo/on-change.tsx">固定状态改变的回调</code>
<code src="./demo/target.tsx">滚动容器</code>
<code src="./demo/debug.tsx" debug>调整浏览器大小,观察 Affix 容器是否发生变化。跟随变化为正常。#17678</code>
## API
通用属性参考:[通用属性](/docs/react/common-props)
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| offsetBottom | 距离窗口底部达到指定偏移量后触发 | number | - |
| offsetTop | 距离窗口顶部达到指定偏移量后触发 | number | 0 |
| target | 设置 `Affix` 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 | () => HTMLElement | () => window |
| onChange | 固定状态改变时触发的回调函数 | (affixed?: boolean) => void | - |
**注意:**`Affix` 内的元素不要使用绝对定位,如需要绝对定位的效果,可以直接设置 `Affix` 为绝对定位:
```jsx
<Affix style={{ position: 'absolute', top: y, left: x }}>...</Affix>
```
## FAQ
### Affix 使用 `target` 绑定容器时,元素会跑到容器外。
从性能角度考虑,我们只监听容器滚动事件。如果希望任意滚动,你可以在窗体添加滚动监听:<https://codesandbox.io/s/stupefied-maxwell-ophqnm?file=/index.js>
相关 issue[#3938](https://github.com/ant-design/ant-design/issues/3938) [#5642](https://github.com/ant-design/ant-design/issues/5642) [#16120](https://github.com/ant-design/ant-design/issues/16120)
### Affix 在水平滚动容器中使用时, 元素 `left` 位置不正确。
Affix 一般只适用于单向滚动的区域,只支持在垂直滚动容器中使用。如果希望在水平容器中使用,你可以考虑使用 原生 `position: sticky` 实现。
相关 issue: [#29108](https://github.com/ant-design/ant-design/issues/29108)

View File

@ -0,0 +1,30 @@
import type { CSSObject } from '@ant-design/cssinjs';
import type { FullToken, GenerateStyle, GetDefaultToken } from '../../theme/internal';
import { genStyleHooks } from '../../theme/internal';
export interface ComponentToken {
zIndexPopup: number;
}
interface AffixToken extends FullToken<'Affix'> {
//
}
// ============================== Shared ==============================
const genSharedAffixStyle: GenerateStyle<AffixToken> = (token): CSSObject => {
const { componentCls } = token;
return {
[componentCls]: {
position: 'fixed',
zIndex: token.zIndexPopup,
},
};
};
export const prepareComponentToken: GetDefaultToken<'Affix'> = (token) => ({
zIndexPopup: token.zIndexBase + 10,
});
// ============================== Export ==============================
export default genStyleHooks('Affix', genSharedAffixStyle, prepareComponentToken);

View File

@ -0,0 +1,32 @@
export type BindElement = HTMLElement | Window | null | undefined;
export function getTargetRect(target: BindElement): DOMRect {
return target !== window
? (target as HTMLElement).getBoundingClientRect()
: ({ top: 0, bottom: window.innerHeight } as DOMRect);
}
export function getFixedTop(placeholderRect: DOMRect, targetRect: DOMRect, offsetTop?: number) {
if (
offsetTop !== undefined &&
Math.round(targetRect.top) > Math.round(placeholderRect.top) - offsetTop
) {
return offsetTop + targetRect.top;
}
return undefined;
}
export function getFixedBottom(
placeholderRect: DOMRect,
targetRect: DOMRect,
offsetBottom?: number,
) {
if (
offsetBottom !== undefined &&
Math.round(targetRect.bottom) < Math.round(placeholderRect.bottom) + offsetBottom
) {
const targetBottomOffset = window.innerHeight - targetRect.bottom;
return offsetBottom + targetBottomOffset;
}
return undefined;
}

View File

@ -7,20 +7,26 @@ import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
import classNames from 'classnames';
import CSSMotion from 'rc-motion';
import { composeRef } from 'rc-util/lib/ref';
import pickAttrs from 'rc-util/lib/pickAttrs';
import type { ClosableType } from '../_util/hooks/useClosable';
import { replaceElement } from '../_util/reactNode';
import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider';
import useStyle from './style';
export interface AlertRef {
nativeElement: HTMLDivElement;
}
export interface AlertProps {
/** Type of Alert styles, options:`success`, `info`, `warning`, `error` */
type?: 'success' | 'info' | 'warning' | 'error';
/** Whether Alert can be closed */
closable?: boolean;
closable?: ClosableType;
/**
* @deprecated please use `closeIcon` instead.
* @deprecated please use `closable.closeIcon` instead.
* Close text to show
*/
closeText?: React.ReactNode;
@ -42,12 +48,13 @@ export interface AlertProps {
rootClassName?: string;
banner?: boolean;
icon?: React.ReactNode;
/** Custom closeIcon */
closeIcon?: boolean | React.ReactNode;
closeIcon?: React.ReactNode;
action?: React.ReactNode;
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
onMouseLeave?: React.MouseEventHandler<HTMLDivElement>;
onClick?: React.MouseEventHandler<HTMLDivElement>;
id?: string;
}
const iconMapFilled = {
@ -77,25 +84,32 @@ const IconNode: React.FC<IconNodeProps> = (props) => {
return React.createElement(iconType, { className: `${prefixCls}-icon` });
};
interface CloseIconProps {
type CloseIconProps = {
isClosable: boolean;
prefixCls: AlertProps['prefixCls'];
closeIcon: AlertProps['closeIcon'];
handleClose: AlertProps['onClose'];
}
ariaProps: React.AriaAttributes;
};
const CloseIcon: React.FC<CloseIconProps> = (props) => {
const { isClosable, prefixCls, closeIcon, handleClose } = props;
const CloseIconNode: React.FC<CloseIconProps> = (props) => {
const { isClosable, prefixCls, closeIcon, handleClose, ariaProps } = props;
const mergedCloseIcon =
closeIcon === true || closeIcon === undefined ? <CloseOutlined /> : closeIcon;
return isClosable ? (
<button type="button" onClick={handleClose} className={`${prefixCls}-close-icon`} tabIndex={0}>
<button
type="button"
onClick={handleClose}
className={`${prefixCls}-close-icon`}
tabIndex={0}
{...ariaProps}
>
{mergedCloseIcon}
</button>
) : null;
};
const Alert: React.FC<AlertProps> = (props) => {
const Alert = React.forwardRef<AlertRef, AlertProps>((props, ref) => {
const {
description,
prefixCls: customizePrefixCls,
@ -113,6 +127,7 @@ const Alert: React.FC<AlertProps> = (props) => {
closeText,
closeIcon,
action,
id,
...otherProps
} = props;
@ -120,10 +135,15 @@ const Alert: React.FC<AlertProps> = (props) => {
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Alert');
warning.deprecated(!closeText, 'closeText', 'closeIcon');
warning.deprecated(!closeText, 'closeText', 'closable.closeIcon');
}
const ref = React.useRef<HTMLDivElement>(null);
const internalRef = React.useRef<HTMLDivElement>(null);
React.useImperativeHandle(ref, () => ({
nativeElement: internalRef.current!,
}));
const { getPrefixCls, direction, alert } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('alert', customizePrefixCls);
@ -143,7 +163,8 @@ const Alert: React.FC<AlertProps> = (props) => {
}, [props.type, banner]);
// closeable when closeText or closeIcon is assigned
const isClosable = React.useMemo(() => {
const isClosable = React.useMemo<boolean>(() => {
if (typeof closable === 'object' && closable.closeIcon) return true;
if (closeText) {
return true;
}
@ -151,8 +172,12 @@ const Alert: React.FC<AlertProps> = (props) => {
return closable;
}
// should be true when closeIcon is 0 or ''
return closeIcon !== false && closeIcon !== null && closeIcon !== undefined;
}, [closeText, closeIcon, closable]);
if (closeIcon !== false && closeIcon !== null && closeIcon !== undefined) {
return true;
}
return !!alert?.closable;
}, [closeText, closeIcon, closable, alert?.closable]);
// banner mode defaults to Icon
const isShowIcon = banner && showIcon === undefined ? true : showIcon;
@ -175,6 +200,32 @@ const Alert: React.FC<AlertProps> = (props) => {
const restProps = pickAttrs(otherProps, { aria: true, data: true });
const mergedCloseIcon = React.useMemo(() => {
if (typeof closable === 'object' && closable.closeIcon) {
return closable.closeIcon;
}
if (closeText) {
return closeText;
}
if (closeIcon !== undefined) {
return closeIcon;
}
if (typeof alert?.closable === 'object' && alert?.closable?.closeIcon) {
return alert?.closable?.closeIcon;
}
return alert?.closeIcon;
}, [closeIcon, closable, closeText, alert?.closeIcon]);
const mergedAriaProps = React.useMemo<React.AriaAttributes>(() => {
const merged = closable ?? alert?.closable;
if (typeof merged === 'object') {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { closeIcon: _, ...ariaProps } = merged;
return ariaProps;
}
return {};
}, [closable, alert?.closable]);
return wrapCSSVar(
<CSSMotion
visible={!closed}
@ -184,9 +235,10 @@ const Alert: React.FC<AlertProps> = (props) => {
onLeaveStart={(node) => ({ maxHeight: node.offsetHeight })}
onLeaveEnd={afterClose}
>
{({ className: motionClassName, style: motionStyle }) => (
{({ className: motionClassName, style: motionStyle }, setRef) => (
<div
ref={ref}
id={id}
ref={composeRef(internalRef, setRef)}
data-show={!closed}
className={classNames(alertCls, motionClassName)}
style={{ ...alert?.style, ...style, ...motionStyle }}
@ -209,17 +261,18 @@ const Alert: React.FC<AlertProps> = (props) => {
{description ? <div className={`${prefixCls}-description`}>{description}</div> : null}
</div>
{action ? <div className={`${prefixCls}-action`}>{action}</div> : null}
<CloseIcon
<CloseIconNode
isClosable={isClosable}
prefixCls={prefixCls}
closeIcon={closeText || closeIcon}
closeIcon={mergedCloseIcon}
handleClose={handleClose}
ariaProps={mergedAriaProps}
/>
</div>
)}
</CSSMotion>,
);
};
});
if (process.env.NODE_ENV !== 'production') {
Alert.displayName = 'Alert';

View File

@ -1,10 +1,12 @@
import * as React from 'react';
import Alert from './Alert';
interface ErrorBoundaryProps {
message?: React.ReactNode;
description?: React.ReactNode;
children?: React.ReactNode;
id?: string;
}
interface ErrorBoundaryStates {
@ -27,7 +29,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
}
render() {
const { message, description, children } = this.props;
const { message, description, id, children } = this.props;
const { error, info } = this.state;
const componentStack = info && info.componentStack ? info.componentStack : null;
const errorMessage = typeof message === 'undefined' ? (error || '').toString() : message;
@ -35,6 +37,7 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
if (error) {
return (
<Alert
id={id}
type="error"
message={errorMessage}
description={

View File

@ -583,6 +583,57 @@ exports[`renders components/alert/demo/closable.tsx extend context correctly 1`]
</button>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon"
data-show="true"
role="alert"
>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Error Text
</div>
<div
class="ant-alert-description"
>
Error Description Error Description Error Description Error Description Error Description Error Description
</div>
</div>
<button
aria-label="close"
class="ant-alert-close-icon"
tabindex="0"
type="button"
>
<span
aria-label="close-square"
class="anticon anticon-close-square"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-square"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 112c17.7 0 32 14.3 32 32v736c0 17.7-14.3 32-32 32H144c-17.7 0-32-14.3-32-32V144c0-17.7 14.3-32 32-32zM639.98 338.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
`;

View File

@ -573,6 +573,57 @@ exports[`renders components/alert/demo/closable.tsx correctly 1`] = `
</button>
</div>
</div>
<div
class="ant-space-item"
>
<div
class="ant-alert ant-alert-error ant-alert-with-description ant-alert-no-icon"
data-show="true"
role="alert"
>
<div
class="ant-alert-content"
>
<div
class="ant-alert-message"
>
Error Text
</div>
<div
class="ant-alert-description"
>
Error Description Error Description Error Description Error Description Error Description Error Description
</div>
</div>
<button
aria-label="close"
class="ant-alert-close-icon"
tabindex="0"
type="button"
>
<span
aria-label="close-square"
class="anticon anticon-close-square"
role="img"
>
<svg
aria-hidden="true"
data-icon="close-square"
fill="currentColor"
fill-rule="evenodd"
focusable="false"
height="1em"
viewBox="64 64 896 896"
width="1em"
>
<path
d="M880 112c17.7 0 32 14.3 32 32v736c0 17.7-14.3 32-32 32H144c-17.7 0-32-14.3-32-32V144c0-17.7 14.3-32 32-32zM639.98 338.82h-.04l-.08.06L512 466.75 384.14 338.88c-.04-.05-.06-.06-.08-.06a.12.12 0 00-.07 0c-.03 0-.05.01-.09.05l-45.02 45.02a.2.2 0 00-.05.09.12.12 0 000 .07v.02a.27.27 0 00.06.06L466.75 512 338.88 639.86c-.05.04-.06.06-.06.08a.12.12 0 000 .07c0 .03.01.05.05.09l45.02 45.02a.2.2 0 00.09.05.12.12 0 00.07 0c.02 0 .04-.01.08-.05L512 557.25l127.86 127.87c.04.04.06.05.08.05a.12.12 0 00.07 0c.03 0 .05-.01.09-.05l45.02-45.02a.2.2 0 00.05-.09.12.12 0 000-.07v-.02a.27.27 0 00-.05-.06L557.25 512l127.87-127.86c.04-.04.05-.06.05-.08a.12.12 0 000-.07c0-.03-.01-.05-.05-.09l-45.02-45.02a.2.2 0 00-.09-.05.12.12 0 00-.07 0z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
`;

View File

@ -1,13 +1,15 @@
import React from 'react';
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 { act, render, screen, waitFakeTimer } from '../../../tests/utils';
import Button from '../../button';
import Popconfirm from '../../popconfirm';
import Tooltip from '../../tooltip';
import type { AlertRef } from '../Alert';
const { ErrorBoundary } = Alert;
@ -24,6 +26,7 @@ describe('Alert', () => {
});
it('should show close button and could be closed', async () => {
const errSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
const onClose = jest.fn();
render(
<Alert
@ -34,13 +37,14 @@ describe('Alert', () => {
/>,
);
await userEvent.click(screen.getByRole('button', { name: /close/i }));
act(() => {
await act(async () => {
await userEvent.click(screen.getByRole('button', { name: /close/i }));
jest.runAllTimers();
});
expect(onClose).toHaveBeenCalledTimes(1);
expect(errSpy).not.toHaveBeenCalled();
errSpy.mockRestore();
});
it('custom action', () => {
@ -106,11 +110,9 @@ describe('Alert', () => {
await userEvent.hover(screen.getByRole('alert'));
act(() => {
jest.runAllTimers();
});
await waitFakeTimer();
expect(screen.getByRole('tooltip')).toBeInTheDocument();
expect(document.querySelector<HTMLDivElement>('.ant-tooltip')).toBeInTheDocument();
});
it('could be used with Popconfirm', async () => {
@ -154,6 +156,31 @@ describe('Alert', () => {
expect(container.querySelector('.ant-alert-close-icon')).toBeFalsy();
});
it('close button should be support aria-* by closable', () => {
const { container, rerender } = render(<Alert />);
expect(container.querySelector('*[aria-label]')).toBeFalsy();
rerender(<Alert closable={{ 'aria-label': 'Close' }} closeIcon="CloseIcon" />);
expect(container.querySelector('[aria-label="Close"]')).toBeTruthy();
rerender(<Alert closable={{ 'aria-label': 'Close' }} closeText="CloseText" />);
expect(container.querySelector('[aria-label="Close"]')).toBeTruthy();
rerender(<Alert closable={{ 'aria-label': 'Close', closeIcon: 'CloseIconProp' }} />);
expect(container.querySelector('[aria-label="Close"]')).toBeTruthy();
});
it('close button should be support custom icon by closable', () => {
const { container, rerender } = render(<Alert />);
expect(container.querySelector('.ant-alert-close-icon')).toBeFalsy();
rerender(<Alert closable={{ closeIcon: 'CloseBtn' }} />);
expect(container.querySelector('.ant-alert-close-icon')?.textContent).toBe('CloseBtn');
rerender(<Alert closable={{ closeIcon: 'CloseBtn' }} closeIcon="CloseBtn2" />);
expect(container.querySelector('.ant-alert-close-icon')?.textContent).toBe('CloseBtn');
rerender(<Alert closable={{ closeIcon: 'CloseBtn' }} closeText="CloseBtn3" />);
expect(container.querySelector('.ant-alert-close-icon')?.textContent).toBe('CloseBtn');
rerender(<Alert closeText="CloseBtn2" />);
expect(container.querySelector('.ant-alert-close-icon')?.textContent).toBe('CloseBtn2');
rerender(<Alert closeIcon="CloseBtn3" />);
expect(container.querySelector('.ant-alert-close-icon')?.textContent).toBe('CloseBtn3');
});
it('should warning when using closeText', () => {
resetWarned();
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
@ -161,11 +188,20 @@ describe('Alert', () => {
const { container } = render(<Alert closeText="close" />);
expect(warnSpy).toHaveBeenCalledWith(
`Warning: [antd: Alert] \`closeText\` is deprecated. Please use \`closeIcon\` instead.`,
`Warning: [antd: Alert] \`closeText\` is deprecated. Please use \`closable.closeIcon\` instead.`,
);
expect(container.querySelector('.ant-alert-close-icon')?.textContent).toBe('close');
warnSpy.mockRestore();
});
it('should support id and ref', () => {
const alertRef = React.createRef<AlertRef>();
const { container } = render(<Alert id="test-id" ref={alertRef} />);
const element = container.querySelector<HTMLDivElement>('#test-id');
expect(element).toBeTruthy();
expect(alertRef.current?.nativeElement).toBeTruthy();
expect(alertRef.current?.nativeElement).toBe(element);
});
});

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Alert, Button, Space } from '@zhst/meta';
import { Alert, Button, Space } from 'antd';
const App: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Alert, Space } from '@zhst/meta';
import { Alert, Space } from 'antd';
const App: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Alert } from '@zhst/meta';
import { Alert } from 'antd';
const App: React.FC = () => <Alert message="Success Text" type="success" />;

View File

@ -1,5 +1,6 @@
import React from 'react';
import { Alert, Space } from '@zhst/meta';
import { CloseSquareFilled } from '@ant-design/icons';
import { Alert, Space } from 'antd';
const onClose = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
console.log(e, 'I was closed.');
@ -20,6 +21,16 @@ const App: React.FC = () => (
closable
onClose={onClose}
/>
<Alert
message="Error Text"
description="Error Description Error Description Error Description Error Description Error Description Error Description"
type="error"
closable={{
'aria-label': 'close',
closeIcon: <CloseSquareFilled />,
}}
onClose={onClose}
/>
</Space>
);

View File

@ -1,6 +1,6 @@
import { SmileOutlined } from '@ant-design/icons';
import React from 'react';
import { Alert, ConfigProvider } from '@zhst/meta';
import { SmileOutlined } from '@ant-design/icons';
import { Alert, ConfigProvider } from 'antd';
const icon = <SmileOutlined />;

View File

@ -1,6 +1,6 @@
import React from 'react';
import { SmileOutlined } from '@ant-design/icons';
import { Alert, Space } from '@zhst/meta';
import { Alert, Space } from 'antd';
const icon = <SmileOutlined />;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Alert, Space } from '@zhst/meta';
import { Alert, Space } from 'antd';
const App: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Alert, Button } from '@zhst/meta';
import { Alert, Button } from 'antd';
const { ErrorBoundary } = Alert;
const ThrowError: React.FC = () => {

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Alert, Space } from '@zhst/meta';
import { Alert, Space } from 'antd';
const App: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Alert } from 'antd';
import Marquee from 'react-fast-marquee';
import { Alert } from '@zhst/meta';
const App: React.FC = () => (
<Alert

View File

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Alert, Switch, Space } from '@zhst/meta';
import { Alert, Space, Switch } from 'antd';
const App: React.FC = () => {
const [visible, setVisible] = useState(true);

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Alert, Space } from '@zhst/meta';
import { Alert, Space } from 'antd';
const App: React.FC = () => (
<Space direction="vertical" style={{ width: '100%' }}>

View File

@ -0,0 +1,39 @@
import React from 'react';
import BehaviorMap from '../../../.dumi/theme/common/BehaviorMap';
const BehaviorPattern: React.FC = () => (
<BehaviorMap
data={{
id: '200000004',
label: '了解页面/模块内需要关注的提示',
children: [
{
id: '500000061',
label: '了解提示信息',
targetType: 'mvp',
children: [
{
id: '707000085',
label: '了解提示内容',
link: 'components-alert-index-tab-design-demo-content',
},
{
id: '707000086',
label: '了解提示类型',
link: 'components-alert-index-tab-design-demo-type',
},
],
},
{
id: '200000005',
label: '针对提示进行操作',
targetType: 'extension',
link: 'components-alert-index-tab-design-demo-action',
},
],
}}
/>
);
export default BehaviorPattern;

View File

@ -0,0 +1,102 @@
import React from 'react';
import { Alert, Flex, Typography } from 'antd';
const Demo = () => {
const [expandA, setExpandA] = React.useState(false);
const [expandB, setExpandB] = React.useState(true);
return (
<Flex gap="large" vertical style={{ maxWidth: 600 }}>
<Flex gap="middle" vertical>
<div></div>
<Alert showIcon closable message="你好!欢迎使用专业版,你可以根据自身需求添加业务模块。" />
<Alert
showIcon
closable
message="帮助信息"
description="你好,由于你的良好信用,我们决定赠送你三个月产品会员,欲了解会员特权与活动请进首页会员专区查看。"
/>
</Flex>
<Flex gap="middle" vertical>
<div>/</div>
<Alert
showIcon
closable
message={
<div>
<Typography.Paragraph ellipsis={!expandA && { rows: 2 }} style={{ marginBottom: 8 }}>
2使2使2使2使2使
</Typography.Paragraph>
<Typography.Link onClick={() => setExpandA((prev) => !prev)}>
{expandA ? '收起' : '展开更多'}
</Typography.Link>
</div>
}
style={{ alignItems: 'baseline' }}
/>
<Alert
showIcon
closable
message={
<div>
<Typography.Paragraph ellipsis={!expandB && { rows: 2 }} style={{ marginBottom: 8 }}>
2使2使2使2使2使
</Typography.Paragraph>
<Typography.Link onClick={() => setExpandB((prev) => !prev)}>
{expandB ? '收起' : '展开更多'}
</Typography.Link>
</div>
}
style={{ alignItems: 'baseline' }}
/>
</Flex>
<Flex gap="middle" vertical>
<div></div>
<Alert
showIcon
closable
message="提示信息不超过一行时,按钮放在信息右侧。"
action={<Typography.Link></Typography.Link>}
/>
<Alert
showIcon
closable
message={
<div>
<Typography.Paragraph style={{ marginBottom: 8 }}>
</Typography.Paragraph>
<Flex gap={8}>
<Typography.Link>1</Typography.Link>
<Typography.Link>2</Typography.Link>
</Flex>
</div>
}
style={{ alignItems: 'baseline' }}
/>
<Alert
showIcon
closable
message="提示标题"
description={
<div>
<Typography.Paragraph style={{ marginBottom: 8 }}>
</Typography.Paragraph>
<Flex gap={8}>
<Typography.Link>1</Typography.Link>
<Typography.Link>2</Typography.Link>
</Flex>
</div>
}
/>
<Typography.Paragraph type="secondary">
使Link
Button线
</Typography.Paragraph>
</Flex>
</Flex>
);
};
export default Demo;

View File

@ -0,0 +1,14 @@
import React from 'react';
import { Alert, Flex } from 'antd';
const Demo = () => (
<Flex gap="middle" vertical style={{ maxWidth: 600 }}>
<Alert message="你好!欢迎使用专业版,你可以根据自身需求添加业务模块。" />
<Alert
message="帮助信息"
description="你好,由于你的良好信用,我们决定赠送你三个月产品会员,欲了解会员特权与活动请进首页会员专区查看。"
/>
</Flex>
);
export default Demo;

View File

@ -0,0 +1,61 @@
import React from 'react';
import { Alert, Flex } from 'antd';
const Demo = () => (
<Flex gap="large" vertical style={{ maxWidth: 600 }}>
<Flex gap="middle" vertical>
<div></div>
<Alert
showIcon
type="success"
message="恭喜!你所提交的信息已经审核通过,如有问题请联系客服。"
/>
<Alert
showIcon
type="success"
message="已成功!"
description="你所提交的信息已经审核通过,请及时跟进申请状况。如有问题,请联系审核人员或在线客服。"
/>
</Flex>
<Flex gap="middle" vertical>
<div></div>
<Alert
showIcon
type="info"
message="你好!欢迎使用专业版,你可以根据自身需求添加业务模块。"
/>
<Alert
showIcon
type="info"
message="帮助信息"
description="你好,由于你的良好信用,我们决定赠送你三个月产品会员,欲了解会员特权与活动请进首页会员专区查看。"
/>
</Flex>
<Flex gap="middle" vertical>
<div></div>
<Alert
showIcon
type="warning"
message="系统将于 15 : 00 - 17 : 00 进行升级,请及时保存你的资料!"
/>
<Alert
showIcon
type="warning"
message="请注意"
description="你所提交的信息已经审核失败,可以进入个人信箱查看原因,如有疑问,请联系客服人员。"
/>
</Flex>
<Flex gap="middle" vertical>
<div></div>
<Alert showIcon type="error" message="系统错误,请稍后重试。" />
<Alert
showIcon
type="error"
message="出错了!"
description="你的账户会员使用权限将在3天后到期请及时跟进申请状况。如有问题请联系审核人员。"
/>
</Flex>
</Flex>
);
export default Demo;

View File

@ -1,11 +1,9 @@
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> & {
type CompoundedComponent = typeof InternalAlert & {
ErrorBoundary: typeof ErrorBoundary;
};

View File

@ -1,7 +1,9 @@
---
category: Components
subtitle: 警告提示
toc: content
title: Alert 警告提示
subtitle: 警告提示
description: 警告提示,展现需要关注的信息。
demo:
cols: 2
group:
@ -9,8 +11,6 @@ group:
order: 6
---
警告提示,展现需要关注的信息。
## 何时使用
- 当某个页面需要向用户显示警告的信息时。
@ -25,7 +25,7 @@ group:
<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/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>
@ -41,7 +41,7 @@ group:
| action | 自定义操作项 | ReactNode | - | 4.9.0 |
| afterClose | 关闭动画结束后触发的回调函数 | () => void | - | |
| banner | 是否用作顶部公告 | boolean | false | |
| closeIcon | 自定义关闭 Icon>=5.7.0: 设置为 `null``false` 时隐藏关闭按钮 | boolean \| ReactNode | `<CloseOutlined />` | |
| closable | 可关闭配置,>=5.15.0: 支持 `aria-*` | boolean \| ({ closeIcon?: React.ReactNode } & React.AriaAttributes) | `false` | |
| description | 警告提示的辅助性文字介绍 | ReactNode | - | |
| icon | 自定义图标,`showIcon` 为 true 时有效 | ReactNode | - | |
| message | 警告提示内容 | ReactNode | - | |

View File

@ -25,7 +25,6 @@ export interface ComponentToken {
withDescriptionIconSize: number;
}
// @ts-ignore
type AlertToken = FullToken<'Alert'> & {
// Custom token here
};
@ -55,13 +54,10 @@ export const genBaseStyle: GenerateStyle<AlertToken> = (token: AlertToken): CSSO
lineHeight,
borderRadiusLG: borderRadius,
motionEaseInOutCirc,
// @ts-ignore
withDescriptionIconSize,
colorText,
colorTextHeading,
// @ts-ignore
withDescriptionPadding,
// @ts-ignore
defaultPadding,
} = token;
@ -242,7 +238,7 @@ export const genActionStyle: GenerateStyle<AlertToken> = (token: AlertToken): CS
},
};
};
// @ts-ignore
export const prepareComponentToken: GetDefaultToken<'Alert'> = (token) => {
const paddingHorizontal = 12; // Fixed value here.
return {
@ -253,7 +249,6 @@ export const prepareComponentToken: GetDefaultToken<'Alert'> = (token) => {
};
export default genStyleHooks(
// @ts-ignore
'Alert',
(token) => [genBaseStyle(token), genTypeStyle(token), genActionStyle(token)],
prepareComponentToken,

View File

@ -0,0 +1,368 @@
import * as React from 'react';
import classNames from 'classnames';
import { useEvent } from 'rc-util';
import scrollIntoView from 'scroll-into-view-if-needed';
import getScroll from '../_util/getScroll';
import scrollTo from '../_util/scrollTo';
import { devUseWarning } from '../_util/warning';
import Affix from '../affix';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import useCSSVarCls from '../config-provider/hooks/useCSSVarCls';
import type { AnchorLinkBaseProps } from './AnchorLink';
import AnchorLink from './AnchorLink';
import AnchorContext from './context';
import useStyle from './style';
export interface AnchorLinkItemProps extends AnchorLinkBaseProps {
key: React.Key;
children?: AnchorLinkItemProps[];
}
export type AnchorContainer = HTMLElement | Window;
function getDefaultContainer() {
return window;
}
function getOffsetTop(element: HTMLElement, container: AnchorContainer): number {
if (!element.getClientRects().length) {
return 0;
}
const rect = element.getBoundingClientRect();
if (rect.width || rect.height) {
if (container === window) {
container = element.ownerDocument!.documentElement!;
return rect.top - container.clientTop;
}
return rect.top - (container as HTMLElement).getBoundingClientRect().top;
}
return rect.top;
}
const sharpMatcherRegex = /#([\S ]+)$/;
interface Section {
link: string;
top: number;
}
export interface AnchorProps {
prefixCls?: string;
className?: string;
rootClassName?: string;
style?: React.CSSProperties;
/**
* @deprecated Please use `items` instead.
*/
children?: React.ReactNode;
offsetTop?: number;
bounds?: number;
affix?: boolean;
showInkInFixed?: boolean;
getContainer?: () => AnchorContainer;
/** Return customize highlight anchor */
getCurrentAnchor?: (activeLink: string) => string;
onClick?: (
e: React.MouseEvent<HTMLElement>,
link: { title: React.ReactNode; href: string },
) => void;
/** Scroll to target offset value, if none, it's offsetTop prop value or 0. */
targetOffset?: number;
/** Listening event when scrolling change active link */
onChange?: (currentActiveLink: string) => void;
items?: AnchorLinkItemProps[];
direction?: AnchorDirection;
replace?: boolean;
}
export interface AnchorState {
activeLink: null | string;
}
export interface AnchorDefaultProps extends AnchorProps {
prefixCls: string;
affix: boolean;
showInkInFixed: boolean;
getContainer: () => AnchorContainer;
}
export type AnchorDirection = 'vertical' | 'horizontal';
export interface AntAnchor {
registerLink: (link: string) => void;
unregisterLink: (link: string) => void;
activeLink: string | null;
scrollTo: (link: string) => void;
onClick?: (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
link: { title: React.ReactNode; href: string },
) => void;
direction: AnchorDirection;
}
const Anchor: React.FC<AnchorProps> = (props) => {
const {
rootClassName,
prefixCls: customPrefixCls,
className,
style,
offsetTop,
affix = true,
showInkInFixed = false,
children,
items,
direction: anchorDirection = 'vertical',
bounds,
targetOffset,
onClick,
onChange,
getContainer,
getCurrentAnchor,
replace,
} = props;
// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Anchor');
warning.deprecated(!children, 'Anchor children', 'items');
warning(
!(anchorDirection === 'horizontal' && items?.some((n) => 'children' in n)),
'usage',
'`Anchor items#children` is not supported when `Anchor` direction is horizontal.',
);
}
const [links, setLinks] = React.useState<string[]>([]);
const [activeLink, setActiveLink] = React.useState<string | null>(null);
const activeLinkRef = React.useRef<string | null>(activeLink);
const wrapperRef = React.useRef<HTMLDivElement>(null);
const spanLinkNode = React.useRef<HTMLSpanElement>(null);
const animating = React.useRef<boolean>(false);
const { direction, anchor, getTargetContainer, getPrefixCls } =
React.useContext<ConfigConsumerProps>(ConfigContext);
const prefixCls = getPrefixCls('anchor', customPrefixCls);
const rootCls = useCSSVarCls(prefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls);
const getCurrentContainer = getContainer ?? getTargetContainer ?? getDefaultContainer;
const dependencyListItem: React.DependencyList[number] = JSON.stringify(links);
const registerLink = useEvent<AntAnchor['registerLink']>((link) => {
if (!links.includes(link)) {
setLinks((prev) => [...prev, link]);
}
});
const unregisterLink = useEvent<AntAnchor['unregisterLink']>((link) => {
if (links.includes(link)) {
setLinks((prev) => prev.filter((i) => i !== link));
}
});
const updateInk = () => {
const linkNode = wrapperRef.current?.querySelector<HTMLElement>(
`.${prefixCls}-link-title-active`,
);
if (linkNode && spanLinkNode.current) {
const { style: inkStyle } = spanLinkNode.current;
const horizontalAnchor = anchorDirection === 'horizontal';
inkStyle.top = horizontalAnchor ? '' : `${linkNode.offsetTop + linkNode.clientHeight / 2}px`;
inkStyle.height = horizontalAnchor ? '' : `${linkNode.clientHeight}px`;
inkStyle.left = horizontalAnchor ? `${linkNode.offsetLeft}px` : '';
inkStyle.width = horizontalAnchor ? `${linkNode.clientWidth}px` : '';
if (horizontalAnchor) {
scrollIntoView(linkNode, { scrollMode: 'if-needed', block: 'nearest' });
}
}
};
const getInternalCurrentAnchor = (_links: string[], _offsetTop = 0, _bounds = 5): string => {
const linkSections: Section[] = [];
const container = getCurrentContainer();
_links.forEach((link) => {
const sharpLinkMatch = sharpMatcherRegex.exec(link?.toString());
if (!sharpLinkMatch) {
return;
}
const target = document.getElementById(sharpLinkMatch[1]);
if (target) {
const top = getOffsetTop(target, container);
if (top <= _offsetTop + _bounds) {
linkSections.push({ link, top });
}
}
});
if (linkSections.length) {
const maxSection = linkSections.reduce((prev, curr) => (curr.top > prev.top ? curr : prev));
return maxSection.link;
}
return '';
};
const setCurrentActiveLink = useEvent((link: string) => {
// FIXME: Seems a bug since this compare is not equals
// `activeLinkRef` is parsed value which will always trigger `onChange` event.
if (activeLinkRef.current === link) {
return;
}
// https://github.com/ant-design/ant-design/issues/30584
const newLink = typeof getCurrentAnchor === 'function' ? getCurrentAnchor(link) : link;
setActiveLink(newLink);
activeLinkRef.current = newLink;
// onChange should respect the original link (which may caused by
// window scroll or user click), not the new link
onChange?.(link);
});
const handleScroll = React.useCallback(() => {
if (animating.current) {
return;
}
const currentActiveLink = getInternalCurrentAnchor(
links,
targetOffset !== undefined ? targetOffset : offsetTop || 0,
bounds,
);
setCurrentActiveLink(currentActiveLink);
}, [dependencyListItem, targetOffset, offsetTop]);
const handleScrollTo = React.useCallback<(link: string) => void>(
(link) => {
setCurrentActiveLink(link);
const sharpLinkMatch = sharpMatcherRegex.exec(link);
if (!sharpLinkMatch) {
return;
}
const targetElement = document.getElementById(sharpLinkMatch[1]);
if (!targetElement) {
return;
}
const container = getCurrentContainer();
const scrollTop = getScroll(container, true);
const eleOffsetTop = getOffsetTop(targetElement, container);
let y = scrollTop + eleOffsetTop;
y -= targetOffset !== undefined ? targetOffset : offsetTop || 0;
animating.current = true;
scrollTo(y, {
getContainer: getCurrentContainer,
callback() {
animating.current = false;
},
});
},
[targetOffset, offsetTop],
);
const wrapperClass = classNames(
hashId,
cssVarCls,
rootCls,
rootClassName,
`${prefixCls}-wrapper`,
{
[`${prefixCls}-wrapper-horizontal`]: anchorDirection === 'horizontal',
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
anchor?.className,
);
const anchorClass = classNames(prefixCls, {
[`${prefixCls}-fixed`]: !affix && !showInkInFixed,
});
const inkClass = classNames(`${prefixCls}-ink`, {
[`${prefixCls}-ink-visible`]: activeLink,
});
const wrapperStyle: React.CSSProperties = {
maxHeight: offsetTop ? `calc(100vh - ${offsetTop}px)` : '100vh',
...anchor?.style,
...style,
};
const createNestedLink = (options?: AnchorLinkItemProps[]) =>
Array.isArray(options)
? options.map((item) => (
<AnchorLink replace={replace} {...item} key={item.key}>
{anchorDirection === 'vertical' && createNestedLink(item.children)}
</AnchorLink>
))
: null;
const anchorContent = (
<div ref={wrapperRef} className={wrapperClass} style={wrapperStyle}>
<div className={anchorClass}>
<span className={inkClass} ref={spanLinkNode} />
{'items' in props ? createNestedLink(items) : children}
</div>
</div>
);
React.useEffect(() => {
const scrollContainer = getCurrentContainer();
handleScroll();
scrollContainer?.addEventListener('scroll', handleScroll);
return () => {
scrollContainer?.removeEventListener('scroll', handleScroll);
};
}, [dependencyListItem]);
React.useEffect(() => {
if (typeof getCurrentAnchor === 'function') {
setCurrentActiveLink(getCurrentAnchor(activeLinkRef.current || ''));
}
}, [getCurrentAnchor]);
React.useEffect(() => {
updateInk();
}, [anchorDirection, getCurrentAnchor, dependencyListItem, activeLink]);
const memoizedContextValue = React.useMemo<AntAnchor>(
() => ({
registerLink,
unregisterLink,
scrollTo: handleScrollTo,
activeLink,
onClick,
direction: anchorDirection,
}),
[activeLink, onClick, handleScrollTo, anchorDirection],
);
return wrapCSSVar(
<AnchorContext.Provider value={memoizedContextValue}>
{affix ? (
<Affix offsetTop={offsetTop} target={getCurrentContainer}>
{anchorContent}
</Affix>
) : (
anchorContent
)}
</AnchorContext.Provider>,
);
};
if (process.env.NODE_ENV !== 'production') {
Anchor.displayName = 'Anchor';
}
export default Anchor;

View File

@ -0,0 +1,94 @@
import * as React from 'react';
import classNames from 'classnames';
import { devUseWarning } from '../_util/warning';
import { ConfigContext } from '../config-provider';
import type { AntAnchor } from './Anchor';
import AnchorContext from './context';
export interface AnchorLinkBaseProps {
prefixCls?: string;
href: string;
target?: string;
title: React.ReactNode;
className?: string;
replace?: boolean;
}
export interface AnchorLinkProps extends AnchorLinkBaseProps {
children?: React.ReactNode;
}
const AnchorLink: React.FC<AnchorLinkProps> = (props) => {
const {
href,
title,
prefixCls: customizePrefixCls,
children,
className,
target,
replace,
} = props;
const context = React.useContext<AntAnchor | undefined>(AnchorContext);
const { registerLink, unregisterLink, scrollTo, onClick, activeLink, direction } = context || {};
React.useEffect(() => {
registerLink?.(href);
return () => {
unregisterLink?.(href);
};
}, [href]);
const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
onClick?.(e, { title, href });
scrollTo?.(href);
if (replace) {
e.preventDefault();
window.location.replace(href);
}
};
// =================== Warning =====================
if (process.env.NODE_ENV !== 'production') {
const warning = devUseWarning('Anchor.Link');
warning(
!children || direction !== 'horizontal',
'usage',
'`Anchor.Link children` is not supported when `Anchor` direction is horizontal',
);
}
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('anchor', customizePrefixCls);
const active = activeLink === href;
const wrapperClassName = classNames(`${prefixCls}-link`, className, {
[`${prefixCls}-link-active`]: active,
});
const titleClassName = classNames(`${prefixCls}-link-title`, {
[`${prefixCls}-link-title-active`]: active,
});
return (
<div className={wrapperClassName}>
<a
className={titleClassName}
href={href}
title={typeof title === 'string' ? title : ''}
target={target}
onClick={handleClick}
>
{title}
</a>
{direction !== 'horizontal' ? children : null}
</div>
);
};
export default AnchorLink;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,163 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Anchor Render render items and ignore jsx children 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Item Basic Demo"
>
Item Basic Demo
</a>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Anchor Render renders items correctly 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Item Basic Demo"
>
Item Basic Demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`Anchor Render renders items correctly#horizontal 1`] = `
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Item Basic Demo"
>
Item Basic Demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
</div>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,782 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/anchor/demo/basic.tsx extend context correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height: 100vh; background: rgba(255, 0, 0, 0.02);"
/>
<div
id="part-2"
style="height: 100vh; background: rgba(0, 255, 0, 0.02);"
/>
<div
id="part-3"
style="height: 100vh; background: rgba(0, 0, 255, 0.02);"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink ant-anchor-ink-visible"
style="top: 0px; height: 0px;"
/>
<div
class="ant-anchor-link ant-anchor-link-active"
>
<a
class="ant-anchor-link-title ant-anchor-link-title-active"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/basic.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/component-token.tsx extend context correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height: 100vh; background: rgba(255, 0, 0, 0.02);"
/>
<div
id="part-2"
style="height: 100vh; background: rgba(0, 255, 0, 0.02);"
/>
<div
id="part-3"
style="height: 100vh; background: rgba(0, 0, 255, 0.02);"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink ant-anchor-ink-visible"
style="top: 0px; height: 0px;"
/>
<div
class="ant-anchor-link ant-anchor-link-active"
>
<a
class="ant-anchor-link-title ant-anchor-link-title-active"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/component-token.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/customizeHighlight.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink ant-anchor-ink-visible"
style="top: 0px; height: 0px;"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link ant-anchor-link-active"
>
<a
class="ant-anchor-link-title ant-anchor-link-title-active"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/customizeHighlight.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/horizontal.tsx extend context correctly 1`] = `
Array [
<div
style="padding: 20px;"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper ant-anchor-wrapper-horizontal"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink ant-anchor-ink-visible"
style="left: 0px; width: 0px;"
/>
<div
class="ant-anchor-link ant-anchor-link-active"
>
<a
class="ant-anchor-link-title ant-anchor-link-title-active"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>,
<div>
<div
id="part-1"
style="width: 100vw; height: 100vh; text-align: center; background: rgba(0, 255, 0, 0.02);"
/>
<div
id="part-2"
style="width: 100vw; height: 100vh; text-align: center; background: rgba(0, 0, 255, 0.02);"
/>
<div
id="part-3"
style="width: 100vw; height: 100vh; text-align: center; background: rgb(255, 251, 233);"
/>
</div>,
]
`;
exports[`renders components/anchor/demo/horizontal.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/legacy-anchor.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/legacy-anchor.tsx extend context correctly 2`] = `
[
"Warning: [antd: Anchor] \`Anchor children\` is deprecated. Please use \`items\` instead.",
]
`;
exports[`renders components/anchor/demo/onChange.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/onChange.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/onClick.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/onClick.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/replace.tsx extend context correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height: 100vh; background: rgba(255, 0, 0, 0.02);"
/>
<div
id="part-2"
style="height: 100vh; background: rgba(0, 255, 0, 0.02);"
/>
<div
id="part-3"
style="height: 100vh; background: rgba(0, 0, 255, 0.02);"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink ant-anchor-ink-visible"
style="top: 0px; height: 0px;"
/>
<div
class="ant-anchor-link ant-anchor-link-active"
>
<a
class="ant-anchor-link-title ant-anchor-link-title-active"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/replace.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/static.tsx extend context correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/static.tsx extend context correctly 2`] = `[]`;
exports[`renders components/anchor/demo/targetOffset.tsx extend context correctly 1`] = `
<div>
<div
class="ant-row"
>
<div
class="ant-col ant-col-18"
>
<div
id="part-1"
style="height: 100vh; background: rgba(255, 0, 0, 0.02); margin-top: 30vh;"
>
Part 1
</div>
<div
id="part-2"
style="height: 100vh; background: rgba(0, 255, 0, 0.02);"
>
Part 2
</div>
<div
id="part-3"
style="height: 100vh; background: rgba(0, 0, 255, 0.02);"
>
Part 3
</div>
</div>
<div
class="ant-col ant-col-6"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height: 100vh;"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink ant-anchor-ink-visible"
style="top: 0px; height: 0px;"
/>
<div
class="ant-anchor-link ant-anchor-link-active"
>
<a
class="ant-anchor-link-title ant-anchor-link-title-active"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
style="height: 30vh; background: rgba(0, 0, 0, 0.85); position: fixed; top: 0px; left: 0px; width: 75%; color: rgb(255, 255, 255);"
>
<div>
Fixed Top Block
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/targetOffset.tsx extend context correctly 2`] = `[]`;

View File

@ -0,0 +1,752 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/anchor/demo/basic.tsx correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height:100vh;background:rgba(255,0,0,0.02)"
/>
<div
id="part-2"
style="height:100vh;background:rgba(0,255,0,0.02)"
/>
<div
id="part-3"
style="height:100vh;background:rgba(0,0,255,0.02)"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/component-token.tsx correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height:100vh;background:rgba(255,0,0,0.02)"
/>
<div
id="part-2"
style="height:100vh;background:rgba(0,255,0,0.02)"
/>
<div
id="part-3"
style="height:100vh;background:rgba(0,0,255,0.02)"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/customizeHighlight.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/horizontal.tsx correctly 1`] = `
Array [
<div
style="padding:20px"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper ant-anchor-wrapper-horizontal"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>,
<div>
<div
id="part-1"
style="width:100vw;height:100vh;text-align:center;background:rgba(0,255,0,0.02)"
/>
<div
id="part-2"
style="width:100vw;height:100vh;text-align:center;background:rgba(0,0,255,0.02)"
/>
<div
id="part-3"
style="width:100vw;height:100vh;text-align:center;background:#FFFBE9"
/>
</div>,
]
`;
exports[`renders components/anchor/demo/legacy-anchor.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/onChange.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/onClick.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/replace.tsx correctly 1`] = `
<div
class="ant-row"
>
<div
class="ant-col ant-col-16"
>
<div
id="part-1"
style="height:100vh;background:rgba(255,0,0,0.02)"
/>
<div
id="part-2"
style="height:100vh;background:rgba(0,255,0,0.02)"
/>
<div
id="part-3"
style="height:100vh;background:rgba(0,0,255,0.02)"
/>
</div>
<div
class="ant-col ant-col-8"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/static.tsx correctly 1`] = `
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor ant-anchor-fixed"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-basic"
title="Basic demo"
>
Basic demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#components-anchor-demo-static"
title="Static demo"
>
Static demo
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#api"
title="API"
>
API
</a>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#anchor-props"
title="Anchor Props"
>
Anchor Props
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#link-props"
title="Link Props"
>
Link Props
</a>
</div>
</div>
</div>
</div>
`;
exports[`renders components/anchor/demo/targetOffset.tsx correctly 1`] = `
<div>
<div
class="ant-row"
>
<div
class="ant-col ant-col-18"
>
<div
id="part-1"
style="height:100vh;background:rgba(255,0,0,0.02);margin-top:30vh"
>
Part 1
</div>
<div
id="part-2"
style="height:100vh;background:rgba(0,255,0,0.02)"
>
Part 2
</div>
<div
id="part-3"
style="height:100vh;background:rgba(0,0,255,0.02)"
>
Part 3
</div>
</div>
<div
class="ant-col ant-col-6"
>
<div>
<div
class=""
>
<div
class="ant-anchor-wrapper"
style="max-height:100vh"
>
<div
class="ant-anchor"
>
<span
class="ant-anchor-ink"
/>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-1"
title="Part 1"
>
Part 1
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-2"
title="Part 2"
>
Part 2
</a>
</div>
<div
class="ant-anchor-link"
>
<a
class="ant-anchor-link-title"
href="#part-3"
title="Part 3"
>
Part 3
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
style="height:30vh;background:rgba(0,0,0,0.85);position:fixed;top:0;left:0;width:75%;color:#FFF"
>
<div>
Fixed Top Block
</div>
</div>
</div>
`;

View File

@ -0,0 +1,41 @@
import React, { memo, useContext } from 'react';
import { fireEvent, pureRender } from '../../../tests/utils';
import Anchor from '../Anchor';
import AnchorContext from '../context';
let innerCount = 0;
let outerCount = 0;
const handleClick = () => {
outerCount++;
};
// we use'memo' here in order to only render inner component while context changed.
const CacheInner: React.FC = memo(() => {
innerCount++;
// subscribe locale context
useContext(AnchorContext);
return null;
});
const CacheOuter: React.FC = memo(() => (
<>
<button type="button" onClick={handleClick} id="parent_btn">
Click
</button>
<Anchor affix={false}>
<CacheInner />
</Anchor>
</>
));
it("Rendering on Anchor without changed won't trigger rendering on child component.", () => {
const { container, unmount } = pureRender(<CacheOuter />);
expect(outerCount).toBe(0);
expect(innerCount).toBe(2);
fireEvent.click(container.querySelector('#parent_btn')!);
expect(outerCount).toBe(1);
expect(innerCount).toBe(2);
unmount();
});

View File

@ -0,0 +1,3 @@
import { extendTest } from '../../../tests/shared/demoTest';
extendTest('anchor');

View File

@ -0,0 +1,26 @@
import * as React from 'react';
import demoTest, { rootPropsTest } from '../../../tests/shared/demoTest';
demoTest('anchor', {
testRootProps: false,
});
rootPropsTest(
'anchor',
(Anchor, props) => (
<Anchor
{...props}
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
]}
/>
),
{
findRootElements: () => document.querySelector('.ant-anchor-wrapper')!,
},
);

View File

@ -0,0 +1,5 @@
import { imageDemoTest } from '../../../tests/shared/imageTest';
describe('Anchor image', () => {
imageDemoTest('anchor', { onlyViewport: true });
});

View File

@ -0,0 +1,7 @@
import * as React from 'react';
import type { AntAnchor } from './Anchor';
const AnchorContext = React.createContext<AntAnchor | undefined>(undefined);
export default AnchorContext;

View File

@ -0,0 +1,7 @@
## zh-CN
最简单的用法。
## en-US
The simplest usage.

View File

@ -0,0 +1,35 @@
import React from 'react';
import { Anchor, Col, Row } from 'antd';
const App: React.FC = () => (
<Row>
<Col span={16}>
<div id="part-1" style={{ height: '100vh', background: 'rgba(255,0,0,0.02)' }} />
<div id="part-2" style={{ height: '100vh', background: 'rgba(0,255,0,0.02)' }} />
<div id="part-3" style={{ height: '100vh', background: 'rgba(0,0,255,0.02)' }} />
</Col>
<Col span={8}>
<Anchor
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
{
key: 'part-2',
href: '#part-2',
title: 'Part 2',
},
{
key: 'part-3',
href: '#part-3',
title: 'Part 3',
},
]}
/>
</Col>
</Row>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
Component Token Debug.
## en-US
Component Token Debug

View File

@ -0,0 +1,46 @@
import React from 'react';
import { Anchor, Col, ConfigProvider, Row } from 'antd';
/** Test usage. Do not use in your production. */
export default () => (
<ConfigProvider
theme={{
components: {
Anchor: {
linkPaddingBlock: 100,
linkPaddingInlineStart: 50,
},
},
}}
>
<Row>
<Col span={16}>
<div id="part-1" style={{ height: '100vh', background: 'rgba(255,0,0,0.02)' }} />
<div id="part-2" style={{ height: '100vh', background: 'rgba(0,255,0,0.02)' }} />
<div id="part-3" style={{ height: '100vh', background: 'rgba(0,0,255,0.02)' }} />
</Col>
<Col span={8}>
<Anchor
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
{
key: 'part-2',
href: '#part-2',
title: 'Part 2',
},
{
key: 'part-3',
href: '#part-3',
title: 'Part 3',
},
]}
/>
</Col>
</Row>
</ConfigProvider>
);

View File

@ -0,0 +1,7 @@
## zh-CN
自定义锚点高亮。
## en-US
Customize the anchor highlight.

View File

@ -0,0 +1,42 @@
import React from 'react';
import { Anchor } from 'antd';
const getCurrentAnchor = () => '#components-anchor-demo-static';
const App: React.FC = () => (
<Anchor
affix={false}
getCurrentAnchor={getCurrentAnchor}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
横向 Anchor。
## en-US
Horizontally aligned anchors

View File

@ -0,0 +1,55 @@
import React from 'react';
import { Anchor } from 'antd';
const App: React.FC = () => (
<>
<div style={{ padding: '20px' }}>
<Anchor
direction="horizontal"
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
{
key: 'part-2',
href: '#part-2',
title: 'Part 2',
},
{
key: 'part-3',
href: '#part-3',
title: 'Part 3',
},
]}
/>
</div>
<div>
<div
id="part-1"
style={{
width: '100vw',
height: '100vh',
textAlign: 'center',
background: 'rgba(0,255,0,0.02)',
}}
/>
<div
id="part-2"
style={{
width: '100vw',
height: '100vh',
textAlign: 'center',
background: 'rgba(0,0,255,0.02)',
}}
/>
<div
id="part-3"
style={{ width: '100vw', height: '100vh', textAlign: 'center', background: '#FFFBE9' }}
/>
</div>
</>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
调试使用
## en-US
Debug usage

View File

@ -0,0 +1,17 @@
import React from 'react';
import { Anchor } from 'antd';
const { Link } = Anchor;
const App: React.FC = () => (
<Anchor affix={false}>
<Link href="#components-anchor-demo-basic" title="Basic demo" />
<Link href="#components-anchor-demo-static" title="Static demo" />
<Link href="#api" title="API">
<Link href="#anchor-props" title="Anchor Props" />
<Link href="#link-props" title="Link Props" />
</Link>
</Anchor>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
监听锚点链接改变
## en-US
Listening for anchor link change.

View File

@ -0,0 +1,44 @@
import React from 'react';
import { Anchor } from 'antd';
const onChange = (link: string) => {
console.log('Anchor:OnChange', link);
};
const App: React.FC = () => (
<Anchor
affix={false}
onChange={onChange}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
点击锚点不记录历史。
## en-US
Clicking on an anchor does not record history.

View File

@ -0,0 +1,51 @@
import React from 'react';
import { Anchor } from 'antd';
const handleClick = (
e: React.MouseEvent<HTMLElement>,
link: {
title: React.ReactNode;
href: string;
},
) => {
e.preventDefault();
console.log(link);
};
const App: React.FC = () => (
<Anchor
affix={false}
onClick={handleClick}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
替换浏览器历史记录中的路径,后退按钮将返回到上一页而不是上一个锚点。
## en-US
Replace path in browser history, so back button returns to previous page instead of previous anchor item.

View File

@ -0,0 +1,36 @@
import React from 'react';
import { Anchor, Col, Row } from 'antd';
const App: React.FC = () => (
<Row>
<Col span={16}>
<div id="part-1" style={{ height: '100vh', background: 'rgba(255,0,0,0.02)' }} />
<div id="part-2" style={{ height: '100vh', background: 'rgba(0,255,0,0.02)' }} />
<div id="part-3" style={{ height: '100vh', background: 'rgba(0,0,255,0.02)' }} />
</Col>
<Col span={8}>
<Anchor
replace
items={[
{
key: 'part-1',
href: '#part-1',
title: 'Part 1',
},
{
key: 'part-2',
href: '#part-2',
title: 'Part 2',
},
{
key: 'part-3',
href: '#part-3',
title: 'Part 3',
},
]}
/>
</Col>
</Row>
);
export default App;

View File

@ -0,0 +1,7 @@
## zh-CN
不浮动,状态不随页面滚动变化。
## en-US
Do not change state when page is scrolling.

View File

@ -0,0 +1,39 @@
import React from 'react';
import { Anchor } from 'antd';
const App: React.FC = () => (
<Anchor
affix={false}
items={[
{
key: '1',
href: '#components-anchor-demo-basic',
title: 'Basic demo',
},
{
key: '2',
href: '#components-anchor-demo-static',
title: 'Static demo',
},
{
key: '3',
href: '#api',
title: 'API',
children: [
{
key: '4',
href: '#anchor-props',
title: 'Anchor Props',
},
{
key: '5',
href: '#link-props',
title: 'Link Props',
},
],
},
]}
/>
);
export default App;

Some files were not shown because too many files have changed in this diff Show More