mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 22:36:31 +08:00
Merge pull request #29104 from ant-design/master
This commit is contained in:
commit
074f2228cb
1
.github/workflows/make-release.yml
vendored
1
.github/workflows/make-release.yml
vendored
@ -8,6 +8,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: make release
|
||||
if: github.event.ref_type == 'tag'
|
||||
uses: actions-cool/release-helper@v1
|
||||
with:
|
||||
token: ${{ secrets.ANT_BOT_TOKEN }}
|
||||
|
@ -9,7 +9,7 @@ Wrap Affix around another component to make it stick the viewport.
|
||||
|
||||
## When To Use
|
||||
|
||||
On longer web pages, its helpful for some content to stick to the viewport. This is common for menus and actions.
|
||||
On longer web pages, it's helpful to stick component into the viewport. This is common for menus and actions.
|
||||
|
||||
Please note that Affix should not cover other content on the page, especially when the size of the viewport is small.
|
||||
|
||||
|
@ -38,6 +38,7 @@ function renderNumberList(position: number, className: string) {
|
||||
export interface ScrollNumberProps {
|
||||
prefixCls?: string;
|
||||
className?: string;
|
||||
motionClassName?: string;
|
||||
count?: string | number | null;
|
||||
children?: React.ReactElement<HTMLElement>;
|
||||
component?: string;
|
||||
@ -56,6 +57,7 @@ const ScrollNumber: React.FC<ScrollNumberProps> = ({
|
||||
prefixCls: customizePrefixCls,
|
||||
count: customizeCount,
|
||||
className,
|
||||
motionClassName,
|
||||
style,
|
||||
title,
|
||||
show,
|
||||
@ -124,7 +126,7 @@ const ScrollNumber: React.FC<ScrollNumberProps> = ({
|
||||
...restProps,
|
||||
'data-show': show,
|
||||
style,
|
||||
className: classNames(prefixCls, className),
|
||||
className: classNames(prefixCls, className, motionClassName),
|
||||
title: title as string,
|
||||
};
|
||||
|
||||
@ -173,7 +175,7 @@ const ScrollNumber: React.FC<ScrollNumberProps> = ({
|
||||
}
|
||||
if (children) {
|
||||
return cloneElement(children, oriProps => ({
|
||||
className: classNames(`${prefixCls}-custom-component`, oriProps?.className),
|
||||
className: classNames(`${prefixCls}-custom-component`, oriProps?.className, motionClassName),
|
||||
}));
|
||||
}
|
||||
return React.createElement(component as any, newProps, numberNode);
|
||||
|
@ -1337,6 +1337,38 @@ exports[`renders ./components/badge/demo/no-wrapper.md correctly 1`] = `
|
||||
</sup>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
>
|
||||
<span
|
||||
class="ant-badge ant-badge-not-a-wrapper"
|
||||
>
|
||||
<span
|
||||
aria-label="clock-circle"
|
||||
class="anticon anticon-clock-circle ant-scroll-number-custom-component"
|
||||
role="img"
|
||||
style="color:#f5222d"
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
data-icon="clock-circle"
|
||||
fill="currentColor"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
viewBox="64 64 896 896"
|
||||
width="1em"
|
||||
>
|
||||
<path
|
||||
d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"
|
||||
/>
|
||||
<path
|
||||
d="M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="ant-space-item"
|
||||
style="margin-right:8px"
|
||||
|
@ -17,6 +17,7 @@ Used in standalone when children is empty.
|
||||
|
||||
```jsx
|
||||
import { Badge, Space, Switch } from 'antd';
|
||||
import { ClockCircleOutlined } from '@ant-design/icons';
|
||||
|
||||
const Demo = () => {
|
||||
const [show, setShow] = React.useState(true);
|
||||
@ -30,6 +31,7 @@ const Demo = () => {
|
||||
}}
|
||||
/>
|
||||
<Badge count={show ? 25 : 0} />
|
||||
<Badge count={show ? <ClockCircleOutlined style={{ color: '#f5222d' }} /> : 0} />
|
||||
<Badge count={show ? 4 : 0} className="site-badge-count-4" />
|
||||
<Badge
|
||||
className="site-badge-count-109"
|
||||
@ -45,8 +47,8 @@ ReactDOM.render(<Demo />, mountNode);
|
||||
|
||||
```css
|
||||
.site-badge-count-4 .ant-badge-count {
|
||||
background-color: #fff;
|
||||
color: #999;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 0 0 1px #d9d9d9 inset;
|
||||
}
|
||||
```
|
||||
|
@ -76,6 +76,13 @@ const Badge: CompoundedComponent = ({
|
||||
return (isEmpty || (isZero && !showZero)) && !showAsDot;
|
||||
}, [mergedCount, isZero, showZero, showAsDot]);
|
||||
|
||||
// Count should be cache in case hidden change it
|
||||
const countRef = useRef(count);
|
||||
if (!isHidden) {
|
||||
countRef.current = count;
|
||||
}
|
||||
const livingCount = countRef.current;
|
||||
|
||||
// We need cache count since remove motion should not change count display
|
||||
const displayCountRef = useRef(mergedCount);
|
||||
if (!isHidden) {
|
||||
@ -111,7 +118,8 @@ const Badge: CompoundedComponent = ({
|
||||
// =============================== Render ===============================
|
||||
// >>> Title
|
||||
const titleNode =
|
||||
title ?? (typeof count === 'string' || typeof count === 'number' ? count : undefined);
|
||||
title ??
|
||||
(typeof livingCount === 'string' || typeof livingCount === 'number' ? livingCount : undefined);
|
||||
|
||||
// >>> Status Text
|
||||
const statusTextNode =
|
||||
@ -119,9 +127,9 @@ const Badge: CompoundedComponent = ({
|
||||
|
||||
// >>> Display Component
|
||||
const displayNode =
|
||||
!count || typeof count !== 'object'
|
||||
!livingCount || typeof livingCount !== 'object'
|
||||
? undefined
|
||||
: cloneElement(count, oriProps => ({
|
||||
: cloneElement(livingCount, oriProps => ({
|
||||
style: {
|
||||
...mergedStyle,
|
||||
...oriProps.style,
|
||||
@ -192,11 +200,14 @@ const Badge: CompoundedComponent = ({
|
||||
scrollNumberStyle.background = color;
|
||||
}
|
||||
|
||||
console.log('===>', isDot, scrollNumberCls);
|
||||
|
||||
return (
|
||||
<ScrollNumber
|
||||
prefixCls={scrollNumberPrefixCls}
|
||||
show={!isHidden}
|
||||
className={classNames(motionClassName, scrollNumberCls)}
|
||||
motionClassName={motionClassName}
|
||||
className={scrollNumberCls}
|
||||
count={displayCount}
|
||||
title={titleNode}
|
||||
style={scrollNumberStyle}
|
||||
|
@ -148,6 +148,11 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.@{number-prefix-cls}-custom-component {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.@{number-prefix-cls}-custom-component,
|
||||
.@{ant-prefix}-scroll-number {
|
||||
position: relative;
|
||||
top: auto;
|
||||
|
@ -236,7 +236,7 @@ Provide linkage between forms. If a sub form with `name` prop update, it will au
|
||||
| getFieldError | Get the error messages by the field name | (name: [NamePath](#NamePath)) => string\[] | |
|
||||
| getFieldInstance | Get field instance | (name: [NamePath](#NamePath)) => any | 4.4.0 |
|
||||
| getFieldsError | Get the error messages by the fields name. Return as an array | (nameList?: [NamePath](#NamePath)\[]) => FieldError\[] | |
|
||||
| getFieldsValue | Get values by a set of field names. Return according to the corresponding structure | (nameList?: [NamePath](#NamePath)\[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any | |
|
||||
| getFieldsValue | Get values by a set of field names. Return according to the corresponding structure. Default return mounted field value, but you can use `getFieldsValue(true)` to get all values | (nameList?: [NamePath](#NamePath)\[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any | |
|
||||
| getFieldValue | Get the value by the field name | (name: [NamePath](#NamePath)) => any | |
|
||||
| isFieldsTouched | Check if fields have been operated. Check if all fields is touched when `allTouched` is `true` | (nameList?: [NamePath](#NamePath)\[], allTouched?: boolean) => boolean | |
|
||||
| isFieldTouched | Check if a field has been operated | (name: [NamePath](#NamePath)) => boolean | |
|
||||
|
@ -235,7 +235,7 @@ Form.List 渲染表单相关操作函数。
|
||||
| getFieldError | 获取对应字段名的错误信息 | (name: [NamePath](#NamePath)) => string\[] | |
|
||||
| getFieldInstance | 获取对应字段实例 | (name: [NamePath](#NamePath)) => any | 4.4.0 |
|
||||
| getFieldsError | 获取一组字段名对应的错误信息,返回为数组形式 | (nameList?: [NamePath](#NamePath)\[]) => FieldError\[] | |
|
||||
| getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回 | (nameList?: [NamePath](#NamePath)\[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any | |
|
||||
| getFieldsValue | 获取一组字段名对应的值,会按照对应结构返回。默认返回现存字段值,当调用 `getFieldsValue(true)` 时返回所有值 | (nameList?: [NamePath](#NamePath)\[], filterFunc?: (meta: { touched: boolean, validating: boolean }) => boolean) => any | |
|
||||
| getFieldValue | 获取对应字段名的值 | (name: [NamePath](#NamePath)) => any | |
|
||||
| isFieldsTouched | 检查一组字段是否被用户操作过,`allTouched` 为 `true` 时检查是否所有字段都被操作过 | (nameList?: [NamePath](#NamePath)\[], allTouched?: boolean) => boolean | |
|
||||
| isFieldTouched | 检查对应字段是否被用户操作过 | (name: [NamePath](#NamePath)) => boolean | |
|
||||
|
@ -17,7 +17,6 @@ export interface TextAreaProps extends RcTextAreaProps {
|
||||
allowClear?: boolean;
|
||||
bordered?: boolean;
|
||||
showCount?: boolean | ShowCountProps;
|
||||
maxLength?: number;
|
||||
size?: SizeType;
|
||||
}
|
||||
|
||||
@ -130,7 +129,7 @@ const TextArea = React.forwardRef<TextAreaRef, TextAreaProps>(
|
||||
|
||||
// Only show text area wrapper when needed
|
||||
if (showCount) {
|
||||
const valueLength = [...val].length;
|
||||
const valueLength = Math.min(val.length, maxLength ?? Infinity);
|
||||
|
||||
let dataCount = '';
|
||||
if (typeof showCount === 'object') {
|
||||
|
@ -144,6 +144,18 @@ describe('TextArea', () => {
|
||||
expect(textarea.prop('data-count')).toBe('5 / 5');
|
||||
});
|
||||
|
||||
it('should minimize value between emoji length and maxLength', () => {
|
||||
const wrapper = mount(<TextArea maxLength={1} showCount value="👀" />);
|
||||
const textarea = wrapper.find('.ant-input-textarea');
|
||||
expect(wrapper.find('textarea').prop('value')).toBe('👀');
|
||||
expect(textarea.prop('data-count')).toBe('1 / 1');
|
||||
|
||||
// fix: 当 maxLength 长度为 2 的时候,输入 emoji 之后 showCount 会显示 1/2,但是不能再输入了
|
||||
const wrapper1 = mount(<TextArea maxLength={2} showCount value="👀" />);
|
||||
const textarea1 = wrapper1.find('.ant-input-textarea');
|
||||
expect(textarea1.prop('data-count')).toBe('2 / 2');
|
||||
});
|
||||
|
||||
// 修改TextArea value截取规则后新增单测
|
||||
it('slice emoji', () => {
|
||||
const wrapper = mount(<TextArea maxLength={5} showCount value="1234😂" />);
|
||||
|
@ -153,7 +153,7 @@ function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
||||
);
|
||||
const mergedSize = customizeSize || size;
|
||||
const tableLocale = { ...contextLocale.Table, ...locale } as TableLocale;
|
||||
const rawData: RecordType[] = dataSource || EMPTY_LIST;
|
||||
const rawData: readonly RecordType[] = dataSource || EMPTY_LIST;
|
||||
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
const prefixCls = getPrefixCls('table', customizePrefixCls);
|
||||
|
@ -2,14 +2,14 @@ import * as React from 'react';
|
||||
import { Key, GetRowKey } from '../interface';
|
||||
|
||||
interface MapCache<RecordType> {
|
||||
data?: RecordType[];
|
||||
data?: readonly RecordType[];
|
||||
childrenColumnName?: string;
|
||||
kvMap?: Map<Key, RecordType>;
|
||||
getRowKey?: Function;
|
||||
}
|
||||
|
||||
export default function useLazyKVMap<RecordType>(
|
||||
data: RecordType[],
|
||||
data: readonly RecordType[],
|
||||
childrenColumnName: string,
|
||||
getRowKey: GetRowKey<RecordType>,
|
||||
) {
|
||||
@ -25,7 +25,7 @@ export default function useLazyKVMap<RecordType>(
|
||||
const kvMap = new Map<Key, RecordType>();
|
||||
|
||||
/* eslint-disable no-inner-declarations */
|
||||
function dig(records: RecordType[]) {
|
||||
function dig(records: readonly RecordType[]) {
|
||||
records.forEach((record, index) => {
|
||||
const rowKey = getRowKey(record, index);
|
||||
kvMap.set(rowKey, record);
|
||||
|
@ -245,7 +245,7 @@ function generateSorterInfo<RecordType>(
|
||||
}
|
||||
|
||||
export function getSortData<RecordType>(
|
||||
data: RecordType[],
|
||||
data: readonly RecordType[],
|
||||
sortStates: SortState<RecordType>[],
|
||||
childrenColumnName: string,
|
||||
): RecordType[] {
|
||||
|
@ -83,7 +83,7 @@ const columns = [
|
||||
| tableLayout | The [table-layout](https://developer.mozilla.org/en-US/docs/Web/CSS/table-layout) attribute of table element | - \| `auto` \| `fixed` | -<hr />`fixed` when header/columns are fixed, or using `column.ellipsis` | |
|
||||
| title | Table title renderer | function(currentPageData) | - | |
|
||||
| onChange | Callback executed when pagination, filters or sorter is changed | function(pagination, filters, sorter, extra: { currentDataSource: \[], action: `paginate` \| `sort` \| `filter` }) | - | |
|
||||
| onHeaderRow | Set props on per header row | function(column, index) | - | |
|
||||
| onHeaderRow | Set props on per header row | function(columns, index) | - | |
|
||||
| onRow | Set props on per row | function(record, index) | - | |
|
||||
|
||||
#### onRow usage
|
||||
@ -101,7 +101,7 @@ Same as `onRow` `onHeaderRow` `onCell` `onHeaderCell`
|
||||
onMouseLeave: event => {}, // mouse leave row
|
||||
};
|
||||
}}
|
||||
onHeaderRow={column => {
|
||||
onHeaderRow={(columns, index) => {
|
||||
return {
|
||||
onClick: () => {}, // click header row
|
||||
};
|
||||
|
@ -90,7 +90,7 @@ const columns = [
|
||||
| tableLayout | 表格元素的 [table-layout](https://developer.mozilla.org/zh-CN/docs/Web/CSS/table-layout) 属性,设为 `fixed` 表示内容不会影响列的布局 | - \| `auto` \| `fixed` | 无<hr />固定表头/列或使用了 `column.ellipsis` 时,默认值为 `fixed` | |
|
||||
| title | 表格标题 | function(currentPageData) | - | |
|
||||
| onChange | 分页、排序、筛选变化时触发 | function(pagination, filters, sorter, extra: { currentDataSource: \[], action: `paginate` \| `sort` \| `filter` }) | - | |
|
||||
| onHeaderRow | 设置头部行属性 | function(column, index) | - | |
|
||||
| onHeaderRow | 设置头部行属性 | function(columns, index) | - | |
|
||||
| onRow | 设置行属性 | function(record, index) | - | |
|
||||
|
||||
#### onRow 用法
|
||||
@ -108,7 +108,7 @@ const columns = [
|
||||
onMouseLeave: event => {},
|
||||
};
|
||||
}}
|
||||
onHeaderRow={column => {
|
||||
onHeaderRow={(columns, index) => {
|
||||
return {
|
||||
onClick: () => {}, // 点击表头行
|
||||
};
|
||||
|
@ -176,7 +176,7 @@ export interface TableCurrentDataSource<RecordType> {
|
||||
export interface SorterResult<RecordType> {
|
||||
column?: ColumnType<RecordType>;
|
||||
order?: SortOrder;
|
||||
field?: Key | Key[];
|
||||
field?: Key | readonly Key[];
|
||||
columnKey?: Key;
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ export function getColumnKey<RecordType>(column: ColumnType<RecordType>, default
|
||||
return column.key;
|
||||
}
|
||||
if (column.dataIndex) {
|
||||
return Array.isArray(column.dataIndex) ? column.dataIndex.join('.') : column.dataIndex;
|
||||
return (Array.isArray(column.dataIndex) ? column.dataIndex.join('.') : column.dataIndex) as Key;
|
||||
}
|
||||
|
||||
return defaultKey;
|
||||
|
@ -109,6 +109,7 @@ Basic text writing, including headings, body text, lists, and more.
|
||||
expandable: boolean,
|
||||
suffix: string,
|
||||
symbol: ReactNode,
|
||||
tooltip: boolean | ReactNode,
|
||||
onExpand: function(event),
|
||||
onEllipsis: function(ellipsis),
|
||||
}
|
||||
@ -117,8 +118,8 @@ Basic text writing, including headings, body text, lists, and more.
|
||||
| --- | --- | --- | --- | --- |
|
||||
| expandable | Whether to be expandable | boolean | - | |
|
||||
| rows | Max rows of content | number | - | |
|
||||
| suffix | Suffix of ellipsis content | ReactNode | - | |
|
||||
| symbol | Custom `...` symbol of ellipsis | ReactNode | `...` | |
|
||||
| suffix | Suffix of ellipsis content | string | - | |
|
||||
| symbol | Custom description of ellipsis | ReactNode | `Expand` | |
|
||||
| tooltip | Show tooltip when ellipsis | boolean \| ReactNode | - | 4.11.0 |
|
||||
| onEllipsis | Called when enter or leave ellipsis state | function(ellipsis) | - | 4.2.0 |
|
||||
| onExpand | Called when expand content | function(event) | - | |
|
||||
|
@ -109,6 +109,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
|
||||
expandable: boolean,
|
||||
suffix: string,
|
||||
symbol: ReactNode,
|
||||
tooltip: boolean | ReactNode,
|
||||
onExpand: function(event),
|
||||
onEllipsis: function(ellipsis),
|
||||
}
|
||||
@ -117,8 +118,8 @@ cover: https://gw.alipayobjects.com/zos/alicdn/GOM1KQ24O/Typography.svg
|
||||
| ---------- | -------------------- | -------------------- | ------ | ------ |
|
||||
| expandable | 是否可展开 | boolean | - | |
|
||||
| rows | 最多显示的行数 | number | - | |
|
||||
| suffix | 自定义省略内容后缀 | ReactNode | - | |
|
||||
| symbol | 自定义省略符号 | ReactNode | `...` | |
|
||||
| suffix | 自定义省略内容后缀 | string | - | |
|
||||
| symbol | 自定义展开描述文案 | ReactNode | `展开` | |
|
||||
| tooltip | 省略时,展示提示信息 | boolean \| ReactNode | - | 4.11.0 |
|
||||
| onEllipsis | 触发省略时的回调 | function(ellipsis) | - | 4.2.0 |
|
||||
| onExpand | 点击展开时的回调 | function(event) | - | |
|
||||
|
@ -97,7 +97,9 @@ ReactDOM.render(<LinksList />, mountNode);
|
||||
|
||||
- Hacknews: [Show HN: Antd – A set of high-quality React components](https://news.ycombinator.com/item?id=13053137)
|
||||
- Alligator: [Crafting Beautiful UIs in React Using Ant Design](https://alligator.io/react/beautiful-uis-ant-design/)
|
||||
- Hackernoon: [Interesting JavaScript Libraries born in China](https://hackernoon.com/interesting-javascript-libraries-born-in-china-d50d1bb81355)
|
||||
- [Introduction to Ant Design](https://blog.logrocket.com/introduction-to-ant-design/)
|
||||
- [Build a React App with Ant Design Principles](https://developer.okta.com/blog/2020/09/16/ant-design-react-app)
|
||||
- [Meet Antd, an enterprise React UI library](https://medium.com/javascript-in-plain-english/antd-library-what-why-useful-or-not-5fec225b639d)
|
||||
|
||||
## How to Contribute
|
||||
|
||||
|
@ -98,8 +98,10 @@ ReactDOM.render(<LinksList />, mountNode);
|
||||
- 知乎:[如何评价 Ant Design 这个项目?](https://www.zhihu.com/question/33629737)
|
||||
- Hacknews: [Show HN: Antd – A set of high-quality React components](https://news.ycombinator.com/item?id=13053137)
|
||||
- Alligator: [Crafting Beautiful UIs in React Using Ant Design](https://alligator.io/react/beautiful-uis-ant-design/)
|
||||
- Hackernoon: [Interesting JavaScript Libraries born in China](https://hackernoon.com/interesting-javascript-libraries-born-in-china-d50d1bb81355)
|
||||
- [漫谈 Material Design & Ant Design](http://dwbbb.com/blog/MaterialDesignAntDesign/)
|
||||
- [Introduction to Ant Design](https://blog.logrocket.com/introduction-to-ant-design/)
|
||||
- [Build a React App with Ant Design Principles](https://developer.okta.com/blog/2020/09/16/ant-design-react-app)
|
||||
- [Meet Antd, an enterprise React UI library](https://medium.com/javascript-in-plain-english/antd-library-what-why-useful-or-not-5fec225b639d)
|
||||
|
||||
## 如何贡献
|
||||
|
||||
|
@ -112,7 +112,7 @@
|
||||
"@ant-design/colors": "^5.0.0",
|
||||
"@ant-design/icons": "^4.4.0",
|
||||
"@ant-design/react-slick": "~0.28.1",
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"array-tree-filter": "^2.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"copy-to-clipboard": "^3.2.0",
|
||||
@ -140,7 +140,7 @@
|
||||
"rc-slider": "~9.7.1",
|
||||
"rc-steps": "~4.1.0",
|
||||
"rc-switch": "~3.2.0",
|
||||
"rc-table": "~7.12.0",
|
||||
"rc-table": "~7.13.0",
|
||||
"rc-tabs": "~11.7.0",
|
||||
"rc-textarea": "~0.3.0",
|
||||
"rc-tooltip": "~5.0.0",
|
||||
|
Loading…
Reference in New Issue
Block a user