chore: sync feature into next

This commit is contained in:
thinkasany
2025-02-24 20:34:17 +08:00
30 changed files with 3424 additions and 542 deletions

View File

@@ -1,5 +1,5 @@
import { scan } from 'react-scan'; // import this BEFORE react
import React, { Suspense, useCallback, useEffect } from 'react';
import { Monitoring } from 'react-scan/monitoring';
import {
createCache,
extractStyle,
@@ -13,13 +13,7 @@ import { getSandpackCssText } from '@codesandbox/sandpack-react';
import { theme as antdTheme, App } from 'antd';
import type { MappingAlgorithm } from 'antd';
import type { DirectionType, ThemeConfig } from 'antd/es/config-provider';
import {
createSearchParams,
useOutlet,
useParams,
useSearchParams,
useServerInsertedHTML,
} from 'dumi';
import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi';
import { DarkContext } from '../../hooks/useDark';
import useLayoutState from '../../hooks/useLayoutState';
@@ -52,6 +46,10 @@ if (typeof window !== 'undefined') {
location.hash = `#${hashId.replace(/^components-/, '')}`;
}
}
scan({
enabled: process.env.NODE_ENV !== 'production',
log: true, // logs render info to console (default: false)
});
}
const getAlgorithm = (themes: ThemeName[] = []) =>
@@ -70,7 +68,6 @@ const getAlgorithm = (themes: ThemeName[] = []) =>
const GlobalLayout: React.FC = () => {
const outlet = useOutlet();
const { pathname } = useLocation();
const params = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const [{ theme = [], direction, isMobile, bannerVisible = false }, setSiteState] =
useLayoutState<SiteState>({
@@ -229,17 +226,7 @@ const GlobalLayout: React.FC = () => {
>
<SiteContext.Provider value={siteContextValue}>
<SiteThemeProvider theme={themeConfig}>
<HappyProvider disabled={!theme.includes('happy-work')}>
{content}
<Monitoring
apiKey="GhrCCNrHZHXlf4P6E03ntrFwhRLxJL30" // Safe to expose publically
url="https://monitoring.react-scan.com/api/v1/ingest"
commit={process.env.COMMIT_HASH}
branch={process.env.BRANCH}
params={params as Record<string, string>}
path={pathname}
/>
</HappyProvider>
<HappyProvider disabled={!theme.includes('happy-work')}>{content}</HappyProvider>
</SiteThemeProvider>
</SiteContext.Provider>
</StyleProvider>

View File

@@ -15,6 +15,29 @@ tag: vVERSION
---
## 5.24.2
`2025-02-24`
- Input
- 🐞 Fix Input with component token `inputFontSize` breaks the height of `controlHeight`. [#52865](https://github.com/ant-design/ant-design/pull/52865) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Input.Search has a border that is not aligned with the bottom of the search button when configure `variable` as `underlined`. [#52861](https://github.com/ant-design/ant-design/pull/52861) [@ustcfury](https://github.com/ustcfury)
- 🛠 Improve Input.OTP logic for create default state. [#52878](https://github.com/ant-design/ant-design/pull/52878) [@Dandelion-F](https://github.com/Dandelion-F)
- 🛠 Improve Input.OTP implementation for render separator. [#52841](https://github.com/ant-design/ant-design/pull/52841) [@li-jia-nan](https://github.com/li-jia-nan)
- Watermark
- 🐞 Fix Watermark may cause page unresponsive when re-rendering. [#52897](https://github.com/ant-design/ant-design/pull/52897) [@765477020](https://github.com/765477020)
- 🆕 Improve Watermark rendering logic to avoid disable it via developer tools and `hidden` attribute. [#52891](https://github.com/ant-design/ant-design/pull/52891) [@arronlai](https://github.com/arronlai)
- 🐞 Fix DatePicker.RangePicker arrow position not correctly when sometime reopened. [#52854](https://github.com/ant-design/ant-design/pull/52854) [@zombieJ](https://github.com/zombieJ)
- 🐞 Fix Layout.Sider content overflow issue when `collapsedWidth={0}`. [#52862](https://github.com/ant-design/ant-design/pull/52862) [@afc163](https://github.com/afc163)
- 🛠 Refactor Grid internal useBreakpoint logic to be same as other component, this will not affect usage. [#52870](https://github.com/ant-design/ant-design/pull/52870) [@zombieJ](https://github.com/zombieJ)
- 💄 Fix Button styles for hyperlink mode. [#52888](https://github.com/ant-design/ant-design/pull/52888) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 Fix Table sortable column headers could not wrap automatically. [#52899](https://github.com/ant-design/ant-design/pull/52899) [@765477020](https://github.com/765477020)
- ⚡️ Improve Menu re-rendering performance when pass function to `expandIcon` property. [#52863](https://github.com/ant-design/ant-design/pull/52863) [@wanpan11](https://github.com/wanpan11)
- ⚡️ Improve Carousel indicator animation performance. [#52881](https://github.com/ant-design/ant-design/pull/52881) [@li-jia-nan](https://github.com/li-jia-nan)
- RTL
- 💄 Fix DatePicker wrong icon direction for RTL mode. [#52896](https://github.com/ant-design/ant-design/pull/52896) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 Fix Dropdown wrong arrow direction of multi-level menu for RTL mode. [#52885](https://github.com/ant-design/ant-design/pull/52885) [@yellowryan](https://github.com/yellowryan)
## 5.24.1
`2025-02-17`

View File

@@ -15,6 +15,29 @@ tag: vVERSION
---
## 5.24.2
`2025-02-24`
- Input
- 🐞 修复 Input 配置 `inputFontSize` component token 时,`controlHeight` 会不生效的问题。[#52865](https://github.com/ant-design/ant-design/pull/52865) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Input.Search 在设置 `variant``underlined` 时下边框与搜索按钮底部没对齐的问题。[#52861](https://github.com/ant-design/ant-design/pull/52861) [@ustcfury](https://github.com/ustcfury)
- 🛠 优化 Input.OTP 的默认状态创建逻辑。[#52878](https://github.com/ant-design/ant-design/pull/52878) [@Dandelion-F](https://github.com/Dandelion-F)
- 🛠 优化 Input.OTP 的分隔符渲染实现。[#52841](https://github.com/ant-design/ant-design/pull/52841) [@li-jia-nan](https://github.com/li-jia-nan)
- Watermark
- 🐞 修复 Watermark 重新渲染时可能导致页面卡死的问题。[#52897](https://github.com/ant-design/ant-design/pull/52897) [@765477020](https://github.com/765477020)
- 🆕 调整 Watermark 渲染逻辑,防止通过开发者工具添加 `hidden` 属性来去掉水印。[#52891](https://github.com/ant-design/ant-design/pull/52891) [@arronlai](https://github.com/arronlai)
- 🐞 修复 DatePicker.RangePicker 在弹层重新打开的时候,有可能出现箭头位置不正确的问题。[#52854](https://github.com/ant-design/ant-design/pull/52854) [@zombieJ](https://github.com/zombieJ)
- 🐞 修复 Layout.Sider 当 `collapsedWidth={0}` 时的内容溢出的问题。[#52862](https://github.com/ant-design/ant-design/pull/52862) [@afc163](https://github.com/afc163)
- 🛠 重构 Grid 内部响应式逻辑以复用其他组件类似的逻辑,该更新不会于使用上有所变化。[#52870](https://github.com/ant-design/ant-design/pull/52870) [@zombieJ](https://github.com/zombieJ)
- 💄 修复 Button 超链接模式的样式。[#52888](https://github.com/ant-design/ant-design/pull/52888) [@DDDDD12138](https://github.com/DDDDD12138)
- 💄 修复 Table 可排序列头不自动换行的问题。[#52899](https://github.com/ant-design/ant-design/pull/52899) [@765477020](https://github.com/765477020)
- ⚡️ 优化 Menu 在 `expandIcon` 属性传入函数时重新渲染的性能。[#52863](https://github.com/ant-design/ant-design/pull/52863) [@wanpan11](https://github.com/wanpan11)
- ⚡️ 优化 Carousel 指示器的动画性能。[#52881](https://github.com/ant-design/ant-design/pull/52881) [@li-jia-nan](https://github.com/li-jia-nan)
- RTL
- 💄 修复 DatePicker 在 RTL 模式下图标方向错误的问题。[#52896](https://github.com/ant-design/ant-design/pull/52896) [@li-jia-nan](https://github.com/li-jia-nan)
- 💄 修复 Dropdown 在 RTL 模式下多级菜单箭头方向错误的问题。[#52885](https://github.com/ant-design/ant-design/pull/52885) [@yellowryan](https://github.com/yellowryan)
## 5.24.1
`2025-02-17`

View File

@@ -143,4 +143,4 @@ Common props ref[Common props](/docs/react/common-props)
### CSS Var doesn't work inside `<App component={false}>`
Make sure the App `component` is a legit React component string, so when you're turning on CSS variables, there's a container to hold the CSS class name.
Make sure the App `component` is a valid html tag, so when you're turning on CSS variables, there's a container to hold the CSS class name. If not set, it defaults to the `div` tag. If set to `false`, no additional DOM nodes will be created, and no default styles will be provided.

View File

@@ -144,4 +144,4 @@ export default () => {
### CSS Var 在 `<App component={false}>` 内不起作用
请确保 App 的 `component` 是一个有效的 React 组件字符串,以便在启用 CSS 变量时有一个容器来承载 CSS 类名。
请确保 App 的 `component` 是一个有效的 html 标签名,以便在启用 CSS 变量时有一个容器来承载 CSS 类名。如果不设置,则默认为 `div` 标签,如果设置为 `false`,则不会创建额外的 DOM 节点,也不会提供默认样式。

View File

@@ -14,6 +14,7 @@ export interface CheckboxOptionType<T = any> {
label: React.ReactNode;
value: T;
style?: React.CSSProperties;
className?: string; // 👈 5.25.0+
disabled?: boolean;
title?: string;
id?: string;
@@ -125,7 +126,7 @@ const CheckboxGroup = React.forwardRef(
value={option.value}
checked={value.includes(option.value)}
onChange={option.onChange}
className={`${groupPrefixCls}-item`}
className={classNames(`${groupPrefixCls}-item`, option.className)}
style={option.style}
title={option.title}
id={option.id}
@@ -145,6 +146,7 @@ const CheckboxGroup = React.forwardRef(
registerValue,
cancelValue,
};
const classString = classNames(
groupPrefixCls,
{

View File

@@ -1,6 +1,6 @@
import React from 'react';
import { Checkbox } from 'antd';
import type { GetProp } from 'antd';
import type { CheckboxOptionType, GetProp } from 'antd';
const onChange: GetProp<typeof Checkbox.Group, 'onChange'> = (checkedValues) => {
console.log('checked = ', checkedValues);
@@ -8,16 +8,16 @@ const onChange: GetProp<typeof Checkbox.Group, 'onChange'> = (checkedValues) =>
const plainOptions = ['Apple', 'Pear', 'Orange'];
const options = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange' },
const options: CheckboxOptionType<string>[] = [
{ label: 'Apple', value: 'Apple', className: 'label-1' },
{ label: 'Pear', value: 'Pear', className: 'label-2' },
{ label: 'Orange', value: 'Orange', className: 'label-3' },
];
const optionsWithDisabled = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange', disabled: false },
const optionsWithDisabled: CheckboxOptionType<string>[] = [
{ label: 'Apple', value: 'Apple', className: 'label-1' },
{ label: 'Pear', value: 'Pear', className: 'label-2' },
{ label: 'Orange', value: 'Orange', className: 'label-3', disabled: false },
];
const App: React.FC = () => (

View File

@@ -44,7 +44,7 @@ Common props ref[Common props](/docs/react/common-props)
| onBlur | Called when leaving the component | function() | - | |
| onFocus | Called when entering the component | function() | - | |
#### Checkbox Group
#### Checkbox.Group
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
@@ -53,6 +53,9 @@ Common props ref[Common props](/docs/react/common-props)
| name | The `name` property of all `input[type="checkbox"]` children | string | - | |
| options | Specifies options | string\[] \| number\[] \| Option\[] | \[] | |
| value | Used for setting the currently selected value | (string \| number \| boolean)\[] | \[] | |
| title | title of the option | `string` | - | |
| className | className of the option | `string` | - | 5.25.0 |
| style | styles of the option | `React.CSSProperties` | - | |
| onChange | The callback function that is triggered when the state changes | (checkedValue: T[]) => void | - | |
##### Option

View File

@@ -45,7 +45,7 @@ demo:
| onBlur | 失去焦点时的回调 | function() | - | |
| onFocus | 获得焦点时的回调 | function() | - | |
#### Checkbox Group
#### Checkbox.Group
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
@@ -54,6 +54,9 @@ demo:
| name | CheckboxGroup 下所有 `input[type="checkbox"]``name` 属性 | string | - | |
| options | 指定可选项 | string\[] \| number\[] \| Option\[] | \[] | |
| value | 指定选中的选项 | (string \| number \| boolean)\[] | \[] | |
| title | 选项的 title | `string` | - | |
| className | 选项的类名 | `string` | - | 5.25.0 |
| style | 选项的样式 | `React.CSSProperties` | - | |
| onChange | 变化时的回调函数 | (checkedValue: T[]) => void | - | |
##### Option

View File

@@ -5128,7 +5128,7 @@ Array [
type="file"
/>
<button
style="border: 0px; background: none;"
style="cursor: inherit; border: 0px; background: none;"
type="button"
>
<span

View File

@@ -2648,7 +2648,7 @@ Array [
type="file"
/>
<button
style="border:0;background:none"
style="color:inherit;cursor:inherit;border:0;background:none"
type="button"
>
<span

View File

@@ -103,7 +103,10 @@ const FormDisabledDemo: React.FC = () => {
</Form.Item>
<Form.Item label="Upload" valuePropName="fileList" getValueFromEvent={normFile}>
<Upload action="/upload.do" listType="picture-card">
<button style={{ border: 0, background: 'none' }} type="button">
<button
style={{ color: 'inherit', cursor: 'inherit', border: 0, background: 'none' }}
type="button"
>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</button>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
z// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders components/radio/demo/badge.tsx extend context correctly 1`] = `
<div

View File

@@ -6,15 +6,15 @@ import type { CheckboxGroupProps } from 'antd/es/checkbox';
const plainOptions: CheckboxGroupProps<string>['options'] = ['Apple', 'Pear', 'Orange'];
const options: CheckboxGroupProps<string>['options'] = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange', title: 'Orange' },
{ label: 'Apple', value: 'Apple', className: 'label-1' },
{ label: 'Pear', value: 'Pear', className: 'label-2' },
{ label: 'Orange', value: 'Orange', title: 'Orange', className: 'label-3' },
];
const optionsWithDisabled: CheckboxGroupProps<string>['options'] = [
{ label: 'Apple', value: 'Apple' },
{ label: 'Pear', value: 'Pear' },
{ label: 'Orange', value: 'Orange', disabled: true },
{ label: 'Apple', value: 'Apple', className: 'label-1' },
{ label: 'Pear', value: 'Pear', className: 'label-2' },
{ label: 'Orange', value: 'Orange', className: 'label-3', disabled: true },
];
const App: React.FC = () => {

View File

@@ -22,6 +22,7 @@ const App: React.FC = () => {
options={[
{
value: 1,
className: 'option-1',
label: (
<Flex gap="small" justify="center" align="center" vertical>
<LineChartOutlined style={{ fontSize: 18 }} />
@@ -31,6 +32,7 @@ const App: React.FC = () => {
},
{
value: 2,
className: 'option-2',
label: (
<Flex gap="small" justify="center" align="center" vertical>
<DotChartOutlined style={{ fontSize: 18 }} />
@@ -40,6 +42,7 @@ const App: React.FC = () => {
},
{
value: 3,
className: 'option-3',
label: (
<Flex gap="small" justify="center" align="center" vertical>
<BarChartOutlined style={{ fontSize: 18 }} />
@@ -49,6 +52,7 @@ const App: React.FC = () => {
},
{
value: 4,
className: 'option-4',
label: (
<Flex gap="small" justify="center" align="center" vertical>
<PieChartOutlined style={{ fontSize: 18 }} />

View File

@@ -98,6 +98,7 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>((props, ref
checked={value === option.value}
title={option.title}
style={option.style}
className={option.className} // 👈 5.25.0+
id={option.id}
required={option.required}
>

View File

@@ -70,7 +70,7 @@ Common props ref[Common props](/docs/react/common-props)
| disabled | Disable radio | boolean | false |
| value | According to value for comparison, to determine whether the selected | any | - |
### RadioGroup
### Radio.Group
Radio group can wrap a group of `Radio`
@@ -94,6 +94,7 @@ Radio group can wrap a group of `Radio`。
| label | The text used to display as the Radio option | `string` | - | 4.4.0 |
| value | The value associated with the Radio option | `string` \| `number` \| `boolean` | - | 4.4.0 |
| style | The style to apply to the Radio option | `React.CSSProperties` | - | 4.4.0 |
| className | className of the Radio option | `string` | - | 5.25.0 |
| disabled | Specifies whether the Radio option is disabled | `boolean` | `false` | 4.4.0 |
| title | Adds the Title attribute value | `string` | - | 4.4.0 |
| id | Adds the Radio Id attribute value | `string` | - | 4.4.0 |

View File

@@ -92,11 +92,12 @@ return (
### CheckboxOptionType
| Property | Description | Type | Default | Version |
| 属性 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| label | 用于作为 Radio 选项展示的文本 | `string` | - | 4.4.0 |
| value | 关联 Radio 选项的值 | `string` \| `number` \| `boolean` | - | 4.4.0 |
| style | 应用到 Radio 选项的 style | `React.CSSProperties` | - | 4.4.0 |
| className | Radio 选项的类名 | `string` | - | 5.25.0 |
| disabled | 指定 Radio 选项是否要禁用 | `boolean` | `false` | 4.4.0 |
| title | 添加 Title 属性值 | `string` | - | 4.4.0 |
| id | 添加 Radio Id 属性值 | `string` | - | 4.4.0 |

File diff suppressed because it is too large Load Diff

View File

@@ -1,138 +1,117 @@
import React from 'react';
import { Button, ConfigProvider, Tabs } from 'antd';
import { Button, ConfigProvider, Flex, Tabs } from 'antd';
const tabItems = Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
disabled: i === 2,
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
});
const sharedTabsProps = {
items: Array.from({ length: 2 }).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
};
}),
tabBarStyle: { background: 'red' },
};
const App: React.FC = () => (
<ConfigProvider
theme={{
components: {
Tabs: {
cardBg: '#f6ffed',
cardHeight: 60,
cardPadding: `20px`,
cardPaddingSM: `20px`,
cardPaddingLG: `20px`,
titleFontSize: 20,
titleFontSizeLG: 20,
titleFontSizeSM: 20,
inkBarColor: '#52C41A',
horizontalMargin: `0 0 12px 0`,
horizontalItemGutter: 12, // Fixed Value
horizontalItemPadding: `20px`,
horizontalItemPaddingSM: `20px`,
horizontalItemPaddingLG: `20px`,
verticalItemPadding: `8px`,
verticalItemMargin: `4px 0 0 0`,
itemColor: 'rgba(0,0,0,0.85)',
itemSelectedColor: '#389e0d',
itemHoverColor: '#d9f7be',
itemActiveColor: '#b7eb8f',
cardGutter: 12,
<>
<ConfigProvider
theme={{
components: {
Tabs: {
cardBg: '#f6ffed',
cardHeight: 60,
cardPadding: `20px`,
cardPaddingSM: `20px`,
cardPaddingLG: `20px`,
titleFontSize: 20,
titleFontSizeLG: 20,
titleFontSizeSM: 20,
inkBarColor: '#52C41A',
horizontalMargin: `0 0 12px 0`,
horizontalItemGutter: 12, // Fixed Value
horizontalItemPadding: `20px`,
horizontalItemPaddingSM: `20px`,
horizontalItemPaddingLG: `20px`,
verticalItemPadding: `8px`,
verticalItemMargin: `4px 0 0 0`,
itemColor: 'rgba(0,0,0,0.85)',
itemSelectedColor: '#389e0d',
itemHoverColor: '#d9f7be',
itemActiveColor: '#b7eb8f',
cardGutter: 12,
},
},
},
}}
>
<div>
<Tabs
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
})}
/>
<Tabs
tabPosition="left"
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
})}
/>
<Tabs
size="small"
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
})}
/>
<Tabs
size="large"
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
label: `Tab ${id}`,
key: id,
children: `Content of tab ${id}`,
};
})}
/>
<Tabs
defaultActiveKey="1"
centered
type="card"
items={Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
disabled: i === 2,
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
<Tabs
size="small"
defaultActiveKey="1"
centered
type="card"
items={Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
disabled: i === 2,
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
<Tabs
size="large"
defaultActiveKey="1"
centered
type="card"
items={Array.from({ length: 3 }).map((_, i) => {
const id = String(i + 1);
return {
disabled: i === 2,
label: `Tab ${id}`,
key: id,
children: `Content of Tab Pane ${id}`,
};
})}
/>
</div>
</ConfigProvider>
}}
>
<div>
<Tabs
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={tabItems}
/>
<Tabs
tabPosition="left"
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={tabItems}
/>
<Tabs
size="small"
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={tabItems}
/>
<Tabs
size="large"
defaultActiveKey="1"
tabBarExtraContent={<Button>Extra Action</Button>}
style={{ marginBottom: 32 }}
items={tabItems}
/>
<Tabs defaultActiveKey="1" centered type="card" items={tabItems} />
<Tabs size="small" defaultActiveKey="1" centered type="card" items={tabItems} />
<Tabs size="large" defaultActiveKey="1" centered type="card" items={tabItems} />
</div>
</ConfigProvider>
<ConfigProvider
theme={{
components: {
Tabs: {
cardHeight: 180,
cardPadding: '0px 0px 0px 0px',
cardPaddingSM: '0px 0px 0px 0px',
verticalItemPadding: '0px 0px',
borderRadiusLG: 0,
borderRadius: 0,
horizontalItemPadding: '0px 0px 0px 0px',
horizontalMargin: '0 0 0 0',
inkBarColor: '#ffa940',
},
},
}}
>
<Tabs size="small" type="editable-card" items={tabItems} />
</ConfigProvider>
<Flex align="flex-end">
<Tabs size="large" type="card" {...sharedTabsProps} />
<Tabs size="middle" type="card" {...sharedTabsProps} />
<Tabs size="small" type="editable-card" {...sharedTabsProps} />
<Tabs size="small" type="card" {...sharedTabsProps} />
</Flex>
</>
);
export default App;

View File

@@ -21,7 +21,17 @@ export interface ComponentToken {
* @desc 卡片标签页高度
* @descEN Height of card tab
*/
cardHeight: number | string;
cardHeight: number;
/**
* @desc 小尺寸卡片标签页高度
* @descEN Height of small card tab
*/
cardHeightSM: number;
/**
* @desc 大尺寸卡片标签页高度
* @descEN Height of large card tab
*/
cardHeightLG: number;
/**
* @desc 卡片标签页内间距
* @descEN Padding of card tab
@@ -593,10 +603,13 @@ const genSizeStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject =>
componentCls,
cardPaddingSM,
cardPaddingLG,
cardHeightSM,
cardHeightLG,
horizontalItemPaddingSM,
horizontalItemPaddingLG,
} = token;
return {
// >>>>> shared
[componentCls]: {
'&-small': {
[`> ${componentCls}-nav`]: {
@@ -612,17 +625,24 @@ const genSizeStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject =>
[`${componentCls}-tab`]: {
padding: horizontalItemPaddingLG,
fontSize: token.titleFontSizeLG,
lineHeight: token.lineHeightLG,
},
},
},
},
// >>>>> card
[`${componentCls}-card`]: {
// Small
[`&${componentCls}-small`]: {
[`> ${componentCls}-nav`]: {
[`${componentCls}-tab`]: {
padding: cardPaddingSM,
},
[`${componentCls}-nav-add`]: {
minWidth: cardHeightSM,
minHeight: cardHeightSM,
},
},
[`&${componentCls}-bottom`]: {
[`> ${componentCls}-nav ${componentCls}-tab`]: {
@@ -652,11 +672,16 @@ const genSizeStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject =>
},
},
// Large
[`&${componentCls}-large`]: {
[`> ${componentCls}-nav`]: {
[`${componentCls}-tab`]: {
padding: cardPaddingLG,
},
[`${componentCls}-nav-add`]: {
minWidth: cardHeightLG,
minHeight: cardHeightLG,
},
},
},
},
@@ -952,11 +977,11 @@ const genTabsStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject =>
[`${componentCls}-nav-add`]: {
minWidth: cardHeight,
minHeight: cardHeight,
marginLeft: {
_skip_check_: true,
value: cardGutter,
},
padding: unit(token.paddingXS),
background: 'transparent',
border: `${unit(token.lineWidth)} ${token.lineType} ${colorBorderSecondary}`,
borderRadius: `${unit(token.borderRadiusLG)} ${unit(token.borderRadiusLG)} 0 0`,
@@ -1024,18 +1049,31 @@ const genTabsStyle: GenerateStyle<TabsToken> = (token: TabsToken): CSSObject =>
};
export const prepareComponentToken: GetDefaultToken<'Tabs'> = (token) => {
const cardHeight = token.controlHeightLG;
const { cardHeight, cardHeightSM, cardHeightLG, controlHeight, controlHeightLG } = token;
const mergedCardHeight = cardHeight || controlHeightLG;
const mergedCardHeightSM = cardHeightSM || controlHeight;
// `controlHeight` missing XL variable, so we directly write it here:
const mergedCardHeightLG = cardHeightLG || controlHeightLG + 8;
return {
zIndexPopup: token.zIndexPopupBase + 50,
cardBg: token.colorFillAlter,
cardHeight,
// We can not pass this as valid value,
// Since `cardHeight` will lock nav add button height.
cardHeight: mergedCardHeight,
cardHeightSM: mergedCardHeightSM,
cardHeightLG: mergedCardHeightLG,
// Initialize with empty string, because cardPadding will be calculated with cardHeight by default.
cardPadding: `${
(cardHeight - Math.round(token.fontSize * token.lineHeight)) / 2 - token.lineWidth
(mergedCardHeight - token.fontHeight) / 2 - token.lineWidth
}px ${token.padding}px`,
cardPaddingSM: `${
(mergedCardHeightSM - token.fontHeight) / 2 - token.lineWidth
}px ${token.paddingXS}px`,
cardPaddingLG: `${
(mergedCardHeightLG - token.fontHeightLG) / 2 - token.lineWidth
}px ${token.padding}px`,
cardPaddingSM: `${token.paddingXXS * 1.5}px ${token.padding}px`,
cardPaddingLG: `${token.paddingXS}px ${token.padding}px ${token.paddingXXS * 1.5}px`,
titleFontSize: token.fontSize,
titleFontSizeLG: token.fontSizeLG,
titleFontSizeSM: token.fontSize,

View File

@@ -853,7 +853,7 @@ exports[`renders components/upload/demo/debug-disabled.tsx extend context correc
type="file"
/>
<button
style="border: 0px; background: none;"
style="cursor: inherit; border: 0px; background: none;"
type="button"
>
<span
@@ -1130,7 +1130,7 @@ exports[`renders components/upload/demo/debug-disabled.tsx extend context correc
type="file"
/>
<button
style="border: 0px; background: none;"
style="cursor: inherit; border: 0px; background: none;"
type="button"
>
<span

View File

@@ -805,7 +805,7 @@ exports[`renders components/upload/demo/debug-disabled.tsx correctly 1`] = `
type="file"
/>
<button
style="border:0;background:none"
style="color:inherit;cursor:inherit;border:0;background:none"
type="button"
>
<span
@@ -1062,7 +1062,7 @@ exports[`renders components/upload/demo/debug-disabled.tsx correctly 1`] = `
type="file"
/>
<button
style="border:0;background:none"
style="color:inherit;cursor:inherit;border:0;background:none"
type="button"
>
<span

View File

@@ -34,7 +34,10 @@ const fileList: UploadFile[] = [
const App: React.FC = () => {
const uploadButton = (
<button style={{ border: 0, background: 'none' }} type="button">
<button
style={{ color: 'inherit', cursor: 'inherit', border: 0, background: 'none' }}
type="button"
>
<PlusOutlined />
<div style={{ marginTop: 8 }}>Upload</div>
</button>

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import { UploadOutlined } from '@ant-design/icons';
import type { UploadFile, UploadProps } from 'antd';
import { Button, Form, message, Upload } from 'antd';
import { App, Button, Form, Upload } from 'antd';
interface OSSDataType {
dir: string;
@@ -17,26 +17,33 @@ interface AliyunOSSUploadProps {
onChange?: (fileList: UploadFile[]) => void;
}
const AliyunOSSUpload = ({ value, onChange }: AliyunOSSUploadProps) => {
const [OSSData, setOSSData] = useState<OSSDataType>();
// Mock get OSS api
// https://help.aliyun.com/document_detail/31988.html
const mockGetOSSData = () => ({
// Mock get OSS api
// https://help.aliyun.com/document_detail/31988.html
const mockOSSData = () => {
const mockData = {
dir: 'user-dir/',
expire: '1577811661',
host: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
accessId: 'c2hhb2RhaG9uZw==',
policy: 'eGl4aWhhaGFrdWt1ZGFkYQ==',
signature: 'ZGFob25nc2hhbw==',
});
};
return Promise.resolve(mockData);
};
const AliyunOSSUpload: React.FC<Readonly<AliyunOSSUploadProps>> = ({ value, onChange }) => {
const { message } = App.useApp();
const [OSSData, setOSSData] = useState<OSSDataType>();
const init = async () => {
try {
const result = await mockGetOSSData();
const result = await mockOSSData();
setOSSData(result);
} catch (error) {
message.error(error as string);
} catch (err) {
if (err instanceof Error) {
message.error(err.message);
}
}
};
@@ -51,10 +58,7 @@ const AliyunOSSUpload = ({ value, onChange }: AliyunOSSUploadProps) => {
const onRemove = (file: UploadFile) => {
const files = (value || []).filter((v) => v.url !== file.url);
if (onChange) {
onChange(files);
}
onChange?.(files);
};
const getExtraData: UploadProps['data'] = (file) => ({
@@ -65,7 +69,9 @@ const AliyunOSSUpload = ({ value, onChange }: AliyunOSSUploadProps) => {
});
const beforeUpload: UploadProps['beforeUpload'] = async (file) => {
if (!OSSData) return false;
if (!OSSData) {
return false;
}
const expire = Number(OSSData.expire) * 1000;
@@ -98,7 +104,7 @@ const AliyunOSSUpload = ({ value, onChange }: AliyunOSSUploadProps) => {
);
};
const App: React.FC = () => (
const Demo: React.FC = () => (
<Form labelCol={{ span: 4 }}>
<Form.Item label="Photos" name="photos">
<AliyunOSSUpload />
@@ -106,4 +112,4 @@ const App: React.FC = () => (
</Form>
);
export default App;
export default Demo;

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { spyElementPrototypes } from 'rc-util/lib/test/domHook';
import Watermark from '..';
import mountTest from '../../../tests/shared/mountTest';
@@ -89,12 +90,25 @@ describe('Watermark', () => {
});
it('MutationObserver should work properly', async () => {
let counter = 0;
const spyCanvas = spyElementPrototypes(HTMLCanvasElement, {
toDataURL(originDescriptor: any) {
counter += 1;
return originDescriptor.value.call(this);
},
});
const { container } = render(<Watermark className="watermark" content="MutationObserver" />);
const target = container.querySelector<HTMLDivElement>('.watermark div');
await waitFakeTimer();
expect(counter).toBe(1);
target?.remove();
await waitFakeTimer();
expect(counter).toBe(1);
expect(container).toMatchSnapshot();
spyCanvas.mockRestore();
});
describe('Observe the modification of style', () => {

View File

@@ -10,6 +10,7 @@ import WatermarkContext from './context';
import type { WatermarkContextProps } from './context';
import useClips, { FontGap } from './useClips';
import useRafDebounce from './useRafDebounce';
import useSingletonCache from './useSingletonCache';
import useWatermark from './useWatermark';
import { getPixelRatio, reRendering } from './utils';
@@ -165,6 +166,9 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
const getClips = useClips();
type ClipParams = Parameters<typeof getClips>;
const getClipsCache = useSingletonCache<ClipParams, ReturnType<typeof getClips>>();
const [watermarkInfo, setWatermarkInfo] = React.useState<[base64: string, contentWidth: number]>(
null!,
);
@@ -181,24 +185,19 @@ const Watermark: React.FC<WatermarkProps> = (props) => {
const drawCanvas = (
drawContent?: NonNullable<WatermarkProps['content']> | HTMLImageElement,
) => {
const [nextClips, clipWidth] = getClips(
const params: ClipParams = [
drawContent || '',
rotate,
ratio,
markWidth,
markHeight,
{
color,
fontSize,
fontStyle,
fontWeight,
fontFamily,
textAlign,
},
{ color, fontSize, fontStyle, fontWeight, fontFamily, textAlign },
gapX,
gapY,
);
] as const;
const result = getClipsCache(params, () => getClips(...params));
const [nextClips, clipWidth] = result;
setWatermarkInfo([nextClips, clipWidth]);
};

View File

@@ -0,0 +1,25 @@
import * as React from 'react';
import isEqual from 'rc-util/lib/isEqual';
export type GetCache<T, R> = (cacheKeys: T, callback: () => R) => R;
/**
* Singleton cache will only take latest `cacheParams` as key
* and return the result for callback matching.
*/
export default function useSingletonCache<T extends any[], R>(): GetCache<T, R> {
const cacheRef = React.useRef<[any[] | null, R | null]>([null, null]);
const getCache: GetCache<T, R> = (cacheKeys, callback) => {
const filteredKeys = cacheKeys.map((item) =>
item instanceof HTMLElement || isNaN(item) ? '' : item,
);
if (!isEqual(cacheRef.current[0], filteredKeys)) {
cacheRef.current = [filteredKeys, callback()];
}
return cacheRef.current[1]!;
};
return getCache;
}

View File

@@ -1,6 +1,6 @@
{
"name": "antd",
"version": "5.24.1",
"version": "5.24.2",
"description": "An enterprise-class UI design language and React components implementation",
"license": "MIT",
"funding": {
@@ -279,7 +279,7 @@
"ora": "^8.1.0",
"p-all": "^5.0.0",
"package-manager-detector": "^0.2.8",
"pixelmatch": "^6.0.0",
"pixelmatch": "^7.1.0",
"pngjs": "^7.0.0",
"prettier": "^3.4.1",
"pretty-format": "^29.7.0",
@@ -301,7 +301,7 @@
"react-intersection-observer": "^9.13.1",
"react-resizable": "^3.0.5",
"react-router-dom": "^7.0.1",
"react-scan": "^0.1.0",
"react-scan": "^0.1.3",
"react-sticky-box": "^2.0.5",
"regenerator-runtime": "^0.14.1",
"rehype-stringify": "^10.0.1",