chore: merge master into feature

This commit is contained in:
栗嘉男 2023-02-23 15:05:18 +08:00
commit cd9ad445ea
50 changed files with 1576 additions and 895 deletions

View File

@ -6,7 +6,7 @@ Your pull requests will be merged after one of the collaborators approve.
Thank you!
-->
[[中文版模板 / Chinese template](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]
[[中文版模板 / Chinese template](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md?plain=1)]
### 🤔 This is a ...

View File

@ -6,7 +6,7 @@
请确保填写以下 pull request 的信息,谢谢!~
-->
[[English Template / 英文模板](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE.md)]
[[English Template / 英文模板](https://github.com/ant-design/ant-design/blob/master/.github/PULL_REQUEST_TEMPLATE.md?plain=1)]
### 🤔 这个变动的性质是?

View File

@ -1,9 +1,11 @@
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-rational-order'],
extends: [
'stylelint-config-standard',
'stylelint-prettier/recommended',
'stylelint-config-rational-order',
],
// 使用 stylelint 来 lint css in js? https://github.com/emotion-js/emotion/discussions/2694
plugins: ['stylelint-prettier'],
rules: {
'prettier/prettier': true,
'function-name-case': ['lower'],
'function-no-unknown': [
true,

View File

@ -53,7 +53,7 @@ export interface AffixState {
prevTarget: Window | HTMLElement | null;
}
class Affix extends React.Component<InternalAffixProps, AffixState> {
class InternalAffix extends React.Component<InternalAffixProps, AffixState> {
static contextType = ConfigContext;
state: AffixState = {
@ -293,9 +293,9 @@ class Affix extends React.Component<InternalAffixProps, AffixState> {
}
}
// just use in test
export type InternalAffixClass = Affix;
export type InternalAffixClass = InternalAffix;
const AffixFC = forwardRef<Affix, AffixProps>((props, ref) => {
const Affix = forwardRef<InternalAffix, AffixProps>((props, ref) => {
const { prefixCls: customizePrefixCls, rootClassName } = props;
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
const affixPrefixCls = getPrefixCls('affix', customizePrefixCls);
@ -308,11 +308,11 @@ const AffixFC = forwardRef<Affix, AffixProps>((props, ref) => {
rootClassName: classNames(rootClassName, hashId),
};
return wrapSSR(<Affix {...AffixProps} ref={ref} />);
return wrapSSR(<InternalAffix {...AffixProps} ref={ref} />);
});
if (process.env.NODE_ENV !== 'production') {
AffixFC.displayName = 'Affix';
Affix.displayName = 'Affix';
}
export default AffixFC;
export default Affix;

View File

@ -1,4 +1,5 @@
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import type { InternalAffixClass } from '.';
export type BindElement = HTMLElement | Window | null | undefined;
@ -51,7 +52,10 @@ export function getObserverEntities() {
return observerEntities;
}
export function addObserveTarget<T>(target: HTMLElement | Window | null, affix?: T): void {
export function addObserveTarget<T extends InternalAffixClass>(
target: HTMLElement | Window | null,
affix?: T,
): void {
if (!target) {
return;
}
@ -79,7 +83,7 @@ export function addObserveTarget<T>(target: HTMLElement | Window | null, affix?:
}
}
export function removeObserveTarget<T>(affix: T): void {
export function removeObserveTarget<T extends InternalAffixClass>(affix: T): void {
const observerEntity = observerEntities.find((oriObserverEntity) => {
const hasAffix = oriObserverEntity.affixList.some((item) => item === affix);
if (hasAffix) {

View File

@ -314,7 +314,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-pink"
class="ant-badge-status-dot ant-badge-color-pink"
/>
<span
class="ant-badge-status-text"
@ -331,7 +331,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-red"
class="ant-badge-status-dot ant-badge-color-red"
/>
<span
class="ant-badge-status-text"
@ -348,7 +348,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-yellow"
class="ant-badge-status-dot ant-badge-color-yellow"
/>
<span
class="ant-badge-status-text"
@ -365,7 +365,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-orange"
class="ant-badge-status-dot ant-badge-color-orange"
/>
<span
class="ant-badge-status-text"
@ -382,7 +382,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-cyan"
class="ant-badge-status-dot ant-badge-color-cyan"
/>
<span
class="ant-badge-status-text"
@ -399,7 +399,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-green"
class="ant-badge-status-dot ant-badge-color-green"
/>
<span
class="ant-badge-status-text"
@ -416,7 +416,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-blue"
class="ant-badge-status-dot ant-badge-color-blue"
/>
<span
class="ant-badge-status-text"
@ -433,7 +433,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-purple"
class="ant-badge-status-dot ant-badge-color-purple"
/>
<span
class="ant-badge-status-text"
@ -450,7 +450,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-geekblue"
class="ant-badge-status-dot ant-badge-color-geekblue"
/>
<span
class="ant-badge-status-text"
@ -467,7 +467,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-magenta"
class="ant-badge-status-dot ant-badge-color-magenta"
/>
<span
class="ant-badge-status-text"
@ -484,7 +484,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-volcano"
class="ant-badge-status-dot ant-badge-color-volcano"
/>
<span
class="ant-badge-status-text"
@ -501,7 +501,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-gold"
class="ant-badge-status-dot ant-badge-color-gold"
/>
<span
class="ant-badge-status-text"
@ -517,7 +517,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-lime"
class="ant-badge-status-dot ant-badge-color-lime"
/>
<span
class="ant-badge-status-text"
@ -633,7 +633,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
pink
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-pink"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-pink"
data-show="true"
title="44"
>
@ -673,7 +673,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
red
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-red"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-red"
data-show="true"
title="44"
>
@ -713,7 +713,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
yellow
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-yellow"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-yellow"
data-show="true"
title="44"
>
@ -753,7 +753,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
orange
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-orange"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-orange"
data-show="true"
title="44"
>
@ -793,7 +793,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
cyan
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-cyan"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-cyan"
data-show="true"
title="44"
>
@ -833,7 +833,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
green
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-green"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-green"
data-show="true"
title="44"
>
@ -873,7 +873,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
blue
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-blue"
data-show="true"
title="44"
>
@ -913,7 +913,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
purple
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-purple"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-purple"
data-show="true"
title="44"
>
@ -953,7 +953,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
geekblue
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-geekblue"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-geekblue"
data-show="true"
title="44"
>
@ -993,7 +993,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
magenta
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-magenta"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-magenta"
data-show="true"
title="44"
>
@ -1033,7 +1033,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
volcano
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-volcano"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-volcano"
data-show="true"
title="44"
>
@ -1073,7 +1073,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
gold
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-gold"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-gold"
data-show="true"
title="44"
>
@ -1113,7 +1113,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx extend co
lime
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-lime"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-lime"
data-show="true"
title="44"
>
@ -1330,7 +1330,7 @@ exports[`renders ./components/badge/demo/mix.tsx extend context correctly 1`] =
/>
</span>
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="5"
>
@ -1439,7 +1439,7 @@ exports[`renders ./components/badge/demo/mix.tsx extend context correctly 1`] =
/>
</span>
<sup
class="ant-scroll-number ant-badge-dot ant-badge-status-blue"
class="ant-scroll-number ant-badge-dot ant-badge-color-blue"
data-show="true"
/>
</span>
@ -1500,7 +1500,7 @@ exports[`renders ./components/badge/demo/mix.tsx extend context correctly 1`] =
class="ant-badge ant-badge-not-a-wrapper"
>
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="0"
>
@ -1565,7 +1565,7 @@ exports[`renders ./components/badge/demo/mix.tsx extend context correctly 1`] =
/>
</span>
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="0"
>

View File

@ -314,7 +314,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-pink"
class="ant-badge-status-dot ant-badge-color-pink"
/>
<span
class="ant-badge-status-text"
@ -331,7 +331,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-red"
class="ant-badge-status-dot ant-badge-color-red"
/>
<span
class="ant-badge-status-text"
@ -348,7 +348,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-yellow"
class="ant-badge-status-dot ant-badge-color-yellow"
/>
<span
class="ant-badge-status-text"
@ -365,7 +365,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-orange"
class="ant-badge-status-dot ant-badge-color-orange"
/>
<span
class="ant-badge-status-text"
@ -382,7 +382,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-cyan"
class="ant-badge-status-dot ant-badge-color-cyan"
/>
<span
class="ant-badge-status-text"
@ -399,7 +399,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-green"
class="ant-badge-status-dot ant-badge-color-green"
/>
<span
class="ant-badge-status-text"
@ -416,7 +416,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-blue"
class="ant-badge-status-dot ant-badge-color-blue"
/>
<span
class="ant-badge-status-text"
@ -433,7 +433,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-purple"
class="ant-badge-status-dot ant-badge-color-purple"
/>
<span
class="ant-badge-status-text"
@ -450,7 +450,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-geekblue"
class="ant-badge-status-dot ant-badge-color-geekblue"
/>
<span
class="ant-badge-status-text"
@ -467,7 +467,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-magenta"
class="ant-badge-status-dot ant-badge-color-magenta"
/>
<span
class="ant-badge-status-text"
@ -484,7 +484,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-volcano"
class="ant-badge-status-dot ant-badge-color-volcano"
/>
<span
class="ant-badge-status-text"
@ -501,7 +501,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-gold"
class="ant-badge-status-dot ant-badge-color-gold"
/>
<span
class="ant-badge-status-text"
@ -517,7 +517,7 @@ Array [
class="ant-badge ant-badge-status ant-badge-not-a-wrapper"
>
<span
class="ant-badge-status-dot ant-badge-status-lime"
class="ant-badge-status-dot ant-badge-color-lime"
/>
<span
class="ant-badge-status-text"
@ -633,7 +633,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
pink
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-pink"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-pink"
data-show="true"
title="44"
>
@ -673,7 +673,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
red
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-red"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-red"
data-show="true"
title="44"
>
@ -713,7 +713,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
yellow
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-yellow"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-yellow"
data-show="true"
title="44"
>
@ -753,7 +753,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
orange
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-orange"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-orange"
data-show="true"
title="44"
>
@ -793,7 +793,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
cyan
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-cyan"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-cyan"
data-show="true"
title="44"
>
@ -833,7 +833,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
green
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-green"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-green"
data-show="true"
title="44"
>
@ -873,7 +873,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
blue
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-blue"
data-show="true"
title="44"
>
@ -913,7 +913,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
purple
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-purple"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-purple"
data-show="true"
title="44"
>
@ -953,7 +953,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
geekblue
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-geekblue"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-geekblue"
data-show="true"
title="44"
>
@ -993,7 +993,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
magenta
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-magenta"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-magenta"
data-show="true"
title="44"
>
@ -1033,7 +1033,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
volcano
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-volcano"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-volcano"
data-show="true"
title="44"
>
@ -1073,7 +1073,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
gold
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-gold"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-gold"
data-show="true"
title="44"
>
@ -1113,7 +1113,7 @@ exports[`renders ./components/badge/demo/colorful-with-count-debug.tsx correctly
lime
</div>
<sup
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-status-lime"
class="ant-scroll-number ant-badge-count ant-badge-multiple-words ant-badge-color-lime"
data-show="true"
title="44"
>
@ -1330,7 +1330,7 @@ exports[`renders ./components/badge/demo/mix.tsx correctly 1`] = `
/>
</span>
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="5"
>
@ -1439,7 +1439,7 @@ exports[`renders ./components/badge/demo/mix.tsx correctly 1`] = `
/>
</span>
<sup
class="ant-scroll-number ant-badge-dot ant-badge-status-blue"
class="ant-scroll-number ant-badge-dot ant-badge-color-blue"
data-show="true"
/>
</span>
@ -1500,7 +1500,7 @@ exports[`renders ./components/badge/demo/mix.tsx correctly 1`] = `
class="ant-badge ant-badge-not-a-wrapper"
>
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="0"
>
@ -1565,7 +1565,7 @@ exports[`renders ./components/badge/demo/mix.tsx correctly 1`] = `
/>
</span>
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="0"
>

View File

@ -28,7 +28,7 @@ exports[`Badge render Badge status/color when contains children 1`] = `
>
<a />
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="5"
>
@ -232,7 +232,7 @@ exports[`Badge should display custom color and number is 0 1`] = `
class="ant-badge ant-badge-not-a-wrapper"
>
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-blue"
class="ant-scroll-number ant-badge-count ant-badge-color-blue"
data-show="true"
title="0"
>
@ -255,7 +255,7 @@ exports[`Badge should display custom color and number is 0 1`] = `
>
<div />
<sup
class="ant-scroll-number ant-badge-count ant-badge-status-green"
class="ant-scroll-number ant-badge-count ant-badge-color-green"
data-show="true"
title="0"
>

View File

@ -154,7 +154,7 @@ const Badge: CompoundedComponent = ({
const statusCls = classNames({
[`${prefixCls}-status-dot`]: hasStatus,
[`${prefixCls}-status-${status}`]: !!status,
[`${prefixCls}-status-${color}`]: isInternalColor,
[`${prefixCls}-color-${color}`]: isInternalColor,
});
const statusStyle: React.CSSProperties = {};
@ -214,7 +214,7 @@ const Badge: CompoundedComponent = ({
[`${prefixCls}-multiple-words`]:
!isDot && displayCount && displayCount.toString().length > 1,
[`${prefixCls}-status-${status}`]: !!status,
[`${prefixCls}-status-${color}`]: isInternalColor,
[`${prefixCls}-color-${color}`]: isInternalColor,
});
let scrollNumberStyle: React.CSSProperties = { ...mergedStyle };

View File

@ -73,8 +73,8 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
const ribbonPrefixCls = `${antCls}-ribbon`;
const ribbonWrapperPrefixCls = `${antCls}-ribbon-wrapper`;
const statusPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`${componentCls}-status-${colorKey}`]: {
const colorPreset = genPresetColor(token, (colorKey, { darkColor }) => ({
[`${componentCls}-color-${colorKey}`]: {
background: darkColor,
},
}));
@ -207,13 +207,13 @@ const genSharedBadgeStyle: GenerateStyle<BadgeToken> = (token: BadgeToken): CSSO
[`${componentCls}-status-warning`]: {
backgroundColor: token.colorWarning,
},
...statusPreset,
[`${componentCls}-status-text`]: {
marginInlineStart: marginXS,
color: token.colorText,
fontSize: token.fontSize,
},
},
...colorPreset,
[`${componentCls}-zoom-appear, ${componentCls}-zoom-enter`]: {
animationName: antZoomBadgeIn,
animationDuration: token.motionDurationSlow,

View File

@ -10,7 +10,11 @@ const BreadcrumbSeparator: CompoundedComponent = ({ children }) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('breadcrumb');
return <span className={`${prefixCls}-separator`}>{children || '/'}</span>;
return (
<li className={`${prefixCls}-separator`} aria-hidden="true">
{children || '/'}
</li>
);
};
BreadcrumbSeparator.__ANT_BREADCRUMB_SEPARATOR = true;

View File

@ -12,11 +12,12 @@ exports[`Breadcrumb filter React.Fragment 1`] = `
Location
</span>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
:
</span>
</li>
<li>
<a
class="ant-breadcrumb-link"
@ -25,11 +26,12 @@ exports[`Breadcrumb filter React.Fragment 1`] = `
Application Center
</a>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
/
</span>
</li>
</ol>
</nav>
`;

View File

@ -396,11 +396,12 @@ exports[`renders ./components/breadcrumb/demo/separator-component.tsx extend con
Location
</span>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
:
</span>
</li>
<li>
<a
class="ant-breadcrumb-link"
@ -409,11 +410,12 @@ exports[`renders ./components/breadcrumb/demo/separator-component.tsx extend con
Application Center
</a>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
/
</span>
</li>
<li>
<a
class="ant-breadcrumb-link"
@ -422,11 +424,12 @@ exports[`renders ./components/breadcrumb/demo/separator-component.tsx extend con
Application List
</a>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
/
</span>
</li>
<li>
<span
class="ant-breadcrumb-link"

View File

@ -224,11 +224,12 @@ exports[`renders ./components/breadcrumb/demo/separator-component.tsx correctly
Location
</span>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
:
</span>
</li>
<li>
<a
class="ant-breadcrumb-link"
@ -237,11 +238,12 @@ exports[`renders ./components/breadcrumb/demo/separator-component.tsx correctly
Application Center
</a>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
/
</span>
</li>
<li>
<a
class="ant-breadcrumb-link"
@ -250,11 +252,12 @@ exports[`renders ./components/breadcrumb/demo/separator-component.tsx correctly
Application List
</a>
</li>
<span
<li
aria-hidden="true"
class="ant-breadcrumb-separator"
>
/
</span>
</li>
<li>
<span
class="ant-breadcrumb-link"

View File

@ -1,9 +1,9 @@
import MockDate from 'mockdate';
import Dayjs from 'dayjs';
import 'dayjs/locale/zh-cn';
import MockDate from 'mockdate';
import { type PickerPanelProps } from 'rc-picker';
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
import type { Locale } from 'rc-picker/lib/interface';
import { type PickerPanelProps } from 'rc-picker';
import React from 'react';
import Calendar from '..';
import mountTest from '../../../tests/shared/mountTest';
@ -42,7 +42,7 @@ jest.mock('rc-picker', () => {
describe('Calendar', () => {
mountTest(Calendar);
rtlTest(Calendar, { mockDate: true });
rtlTest(Calendar, true);
function openSelect(wrapper: HTMLElement, className: string) {
fireEvent.mouseDown(wrapper.querySelector(className)!.querySelector('.ant-select-selector')!);

View File

@ -10,7 +10,7 @@ import type {
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
import CalendarHeader from './Header';
import enUS from './locale/en_US';
@ -236,60 +236,57 @@ function generateCalendar<DateType>(generateConfig: GenerateConfig<DateType>) {
[monthFullCellRender, monthCellRender],
);
return wrapSSR(
<LocaleReceiver componentName="Calendar" defaultLocale={getDefaultLocale}>
{(contextLocale) => (
<div
className={classNames(
calendarPrefixCls,
{
[`${calendarPrefixCls}-full`]: fullscreen,
[`${calendarPrefixCls}-mini`]: !fullscreen,
[`${calendarPrefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
)}
style={style}
>
{headerRender ? (
headerRender({
value: mergedValue,
type: mergedMode,
onChange: onInternalSelect,
onTypeChange: triggerModeChange,
})
) : (
<CalendarHeader
prefixCls={calendarPrefixCls}
value={mergedValue}
generateConfig={generateConfig}
mode={mergedMode}
fullscreen={fullscreen}
locale={contextLocale.lang}
validRange={validRange}
onChange={onInternalSelect}
onModeChange={triggerModeChange}
/>
)}
const contextLocale = useLocale('Calendar', getDefaultLocale);
<RCPickerPanel
value={mergedValue}
prefixCls={prefixCls}
locale={contextLocale.lang}
generateConfig={generateConfig}
dateRender={dateRender}
monthCellRender={(date) => monthRender(date, contextLocale.lang)}
onSelect={onInternalSelect}
mode={panelMode}
picker={panelMode}
disabledDate={mergedDisabledDate}
hideHeader
/>
</div>
return wrapSSR(
<div
className={classNames(
calendarPrefixCls,
{
[`${calendarPrefixCls}-full`]: fullscreen,
[`${calendarPrefixCls}-mini`]: !fullscreen,
[`${calendarPrefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
)}
</LocaleReceiver>,
style={style}
>
{headerRender ? (
headerRender({
value: mergedValue,
type: mergedMode,
onChange: onInternalSelect,
onTypeChange: triggerModeChange,
})
) : (
<CalendarHeader
prefixCls={calendarPrefixCls}
value={mergedValue}
generateConfig={generateConfig}
mode={mergedMode}
fullscreen={fullscreen}
locale={contextLocale?.lang}
validRange={validRange}
onChange={onInternalSelect}
onModeChange={triggerModeChange}
/>
)}
<RCPickerPanel
value={mergedValue}
prefixCls={prefixCls}
locale={contextLocale?.lang}
generateConfig={generateConfig}
dateRender={dateRender}
monthCellRender={(date) => monthRender(date, contextLocale?.lang)}
onSelect={onInternalSelect}
mode={panelMode}
picker={panelMode}
disabledDate={mergedDisabledDate}
hideHeader
/>
</div>,
);
};

View File

@ -3,27 +3,28 @@ import IconContext from '@ant-design/icons/lib/components/Context';
import { FormProvider as RcFormProvider } from 'rc-field-form';
import type { ValidateMessages } from 'rc-field-form/lib/interface';
import useMemo from 'rc-util/lib/hooks/useMemo';
import * as React from 'react';
import type { ReactElement } from 'react';
import * as React from 'react';
import type { Options } from 'scroll-into-view-if-needed';
import type { RequiredMark } from '../form/Form';
import type { Locale } from '../locale';
import LocaleProvider, { ANT_MARK } from '../locale';
import LocaleReceiver from '../locale/LocaleReceiver';
import type { LocaleContextProps } from '../locale/context';
import LocaleContext from '../locale/context';
import defaultLocale from '../locale/en_US';
import { DesignTokenContext } from '../theme/internal';
import defaultSeedToken from '../theme/themes/seed';
import warning from '../_util/warning';
import type { ConfigConsumerProps, CSPConfig, DirectionType, Theme, ThemeConfig } from './context';
import { ConfigConsumer, ConfigContext, defaultIconPrefixCls } from './context';
import { registerTheme } from './cssVariables';
import type { RenderEmptyHandler } from './defaultRenderEmpty';
import { DisabledContextProvider } from './DisabledContext';
import useConfig from './hooks/useConfig';
import useTheme from './hooks/useTheme';
import type { SizeType } from './SizeContext';
import SizeContext, { SizeContextProvider } from './SizeContext';
import useStyle from './style';
import useConfig from './hooks/useConfig';
import warning from '../_util/warning';
export {
type RenderEmptyHandler,
@ -317,17 +318,11 @@ const ConfigProvider: React.FC<ConfigProviderProps> & {
SizeContext: typeof SizeContext;
config: typeof setGlobalConfig;
useConfig: typeof useConfig;
} = (props) => (
<LocaleReceiver>
{(_, __, legacyLocale) => (
<ConfigConsumer>
{(context) => (
<ProviderChildren parentContext={context} legacyLocale={legacyLocale} {...props} />
)}
</ConfigConsumer>
)}
</LocaleReceiver>
);
} = (props) => {
const context = React.useContext<ConfigConsumerProps>(ConfigContext);
const antLocale = React.useContext<LocaleContextProps | undefined>(LocaleContext);
return <ProviderChildren parentContext={context} legacyLocale={antLocale!} {...props} />;
};
ConfigProvider.ConfigContext = ConfigContext;
ConfigProvider.SizeContext = SizeContext;

View File

@ -13,13 +13,13 @@ import { ConfigContext } from '../../config-provider';
import DisabledContext from '../../config-provider/DisabledContext';
import SizeContext from '../../config-provider/SizeContext';
import { FormItemInputContext } from '../../form/context';
import useLocale from '../../locale/useLocale';
import { useCompactItemContext } from '../../space/Compact';
import LocaleReceiver from '../../locale/LocaleReceiver';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
import warning from '../../_util/warning';
import enUS from '../locale/en_US';
import { getRangePlaceholder, transPlacement2DropdownAlign } from '../util';
import type { CommonPickerMethods, PickerComponentClass } from './interface';
import warning from '../../_util/warning';
import useStyle from '../style';
@ -102,57 +102,53 @@ export default function generateRangePicker<DateType>(generateConfig: GenerateCo
blur: () => innerRef.current?.blur(),
}));
return wrapSSR(
<LocaleReceiver componentName="DatePicker" defaultLocale={enUS}>
{(contextLocale) => {
const locale = { ...contextLocale, ...props.locale };
const contextLocale = useLocale('Calendar', enUS);
return (
<RCRangePicker<DateType>
separator={
<span aria-label="to" className={`${prefixCls}-separator`}>
<SwapRightOutlined />
</span>
}
disabled={mergedDisabled}
ref={innerRef}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
placeholder={getRangePlaceholder(locale, picker, placeholder)}
suffixIcon={suffixNode}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...restProps}
{...additionalOverrideProps}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls as string,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
hashId,
compactItemClassnames,
className,
)}
locale={locale.lang}
prefixCls={prefixCls}
getPopupContainer={customGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
dropdownClassName={classNames(hashId, popupClassName || dropdownClassName)}
/>
);
}}
</LocaleReceiver>,
const locale = { ...contextLocale, ...props.locale! };
return wrapSSR(
<RCRangePicker<DateType>
separator={
<span aria-label="to" className={`${prefixCls}-separator`}>
<SwapRightOutlined />
</span>
}
disabled={mergedDisabled}
ref={innerRef}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
placeholder={getRangePlaceholder(locale, picker, placeholder)}
suffixIcon={suffixNode}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...restProps}
{...additionalOverrideProps}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls as string,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
hashId,
compactItemClassnames,
className,
)}
locale={locale.lang}
prefixCls={prefixCls}
getPopupContainer={customGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
dropdownClassName={classNames(hashId, popupClassName || dropdownClassName)}
/>,
);
});

View File

@ -13,7 +13,7 @@ import { ConfigContext } from '../../config-provider';
import DisabledContext from '../../config-provider/DisabledContext';
import SizeContext from '../../config-provider/SizeContext';
import { FormItemInputContext } from '../../form/context';
import LocaleReceiver from '../../locale/LocaleReceiver';
import useLocale from '../../locale/useLocale';
import { useCompactItemContext } from '../../space/Compact';
import type { InputStatus } from '../../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../../_util/statusUtils';
@ -122,58 +122,54 @@ export default function generatePicker<DateType>(generateConfig: GenerateConfig<
</>
);
return wrapSSR(
<LocaleReceiver componentName="DatePicker" defaultLocale={enUS}>
{(contextLocale) => {
const locale = { ...contextLocale, ...props.locale };
const contextLocale = useLocale('DatePicker', enUS);
return (
<RCPicker<DateType>
ref={innerRef}
placeholder={getPlaceholder(locale, mergedPicker, placeholder)}
suffixIcon={suffixNode}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...additionalProps}
{...restProps}
{...additionalOverrideProps}
locale={locale!.lang}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls as string,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
hashId,
compactItemClassnames,
className,
rootClassName,
)}
prefixCls={prefixCls}
getPopupContainer={customizeGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
disabled={mergedDisabled}
dropdownClassName={classNames(
hashId,
rootClassName,
popupClassName || dropdownClassName,
)}
/>
);
}}
</LocaleReceiver>,
const locale = { ...contextLocale, ...props.locale! };
return wrapSSR(
<RCPicker<DateType>
ref={innerRef}
placeholder={getPlaceholder(locale, mergedPicker, placeholder)}
suffixIcon={suffixNode}
dropdownAlign={transPlacement2DropdownAlign(direction, placement)}
clearIcon={<CloseCircleFilled />}
prevIcon={<span className={`${prefixCls}-prev-icon`} />}
nextIcon={<span className={`${prefixCls}-next-icon`} />}
superPrevIcon={<span className={`${prefixCls}-super-prev-icon`} />}
superNextIcon={<span className={`${prefixCls}-super-next-icon`} />}
allowClear
transitionName={`${rootPrefixCls}-slide-up`}
{...additionalProps}
{...restProps}
{...additionalOverrideProps}
locale={locale!.lang}
className={classNames(
{
[`${prefixCls}-${mergedSize}`]: mergedSize,
[`${prefixCls}-borderless`]: !bordered,
},
getStatusClassNames(
prefixCls as string,
getMergedStatus(contextStatus, customStatus),
hasFeedback,
),
hashId,
compactItemClassnames,
className,
rootClassName,
)}
prefixCls={prefixCls}
getPopupContainer={customizeGetPopupContainer || getPopupContainer}
generateConfig={generateConfig}
components={Components}
direction={direction}
disabled={mergedDisabled}
dropdownClassName={classNames(
hashId,
rootClassName,
popupClassName || dropdownClassName,
)}
/>,
);
},
);

View File

@ -256,4 +256,15 @@ describe('Descriptions', () => {
);
expect(wrapper.container.firstChild).toMatchSnapshot();
});
it('should pass data-* and accessibility attributes', () => {
const { getByTestId } = render(
<Descriptions data-testid="test-id" data-id="12345" aria-describedby="some-label">
<Descriptions.Item label="banana">banana</Descriptions.Item>
</Descriptions>,
);
const container = getByTestId('test-id');
expect(container).toHaveAttribute('data-id', '12345');
expect(container).toHaveAttribute('aria-describedby', 'some-label');
});
});

View File

@ -130,6 +130,7 @@ function Descriptions({
size,
labelStyle,
contentStyle,
...restProps
}: DescriptionsProps) {
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const prefixCls = getPrefixCls('descriptions', customizePrefixCls);
@ -175,6 +176,7 @@ function Descriptions({
hashId,
)}
style={style}
{...restProps}
>
{(title || extra) && (
<div className={`${prefixCls}-header`}>

View File

@ -1,7 +1,7 @@
import classNames from 'classnames';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
import DefaultEmptyImg from './empty';
import SimpleEmptyImg from './simple';
@ -46,43 +46,39 @@ const Empty: CompoundedComponent = ({
const prefixCls = getPrefixCls('empty', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const locale = useLocale('Empty');
const des = typeof description !== 'undefined' ? description : locale?.description;
const alt = typeof des === 'string' ? des : 'empty';
let imageNode: React.ReactNode = null;
if (typeof image === 'string') {
imageNode = <img alt={alt} src={image} />;
} else {
imageNode = image;
}
return wrapSSR(
<LocaleReceiver componentName="Empty">
{(locale: TransferLocale) => {
const des = typeof description !== 'undefined' ? description : locale.description;
const alt = typeof des === 'string' ? des : 'empty';
let imageNode: React.ReactNode = null;
if (typeof image === 'string') {
imageNode = <img alt={alt} src={image} />;
} else {
imageNode = image;
}
return (
<div
className={classNames(
hashId,
prefixCls,
{
[`${prefixCls}-normal`]: image === simpleEmptyImg,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
)}
{...restProps}
>
<div className={`${prefixCls}-image`} style={imageStyle}>
{imageNode}
</div>
{des && <div className={`${prefixCls}-description`}>{des}</div>}
{children && <div className={`${prefixCls}-footer`}>{children}</div>}
</div>
);
}}
</LocaleReceiver>,
<div
className={classNames(
hashId,
prefixCls,
{
[`${prefixCls}-normal`]: image === simpleEmptyImg,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
)}
{...restProps}
>
<div className={`${prefixCls}-image`} style={imageStyle}>
{imageNode}
</div>
{des && <div className={`${prefixCls}-description`}>{des}</div>}
{children && <div className={`${prefixCls}-footer`}>{children}</div>}
</div>,
);
};

View File

@ -3,8 +3,8 @@ import classNames from 'classnames';
import * as React from 'react';
import type { ColProps } from '../grid/col';
import Col from '../grid/col';
import { useLocaleReceiver } from '../locale/LocaleReceiver';
import defaultLocale from '../locale/en_US';
import useLocale from '../locale/useLocale';
import type { TooltipProps } from '../tooltip';
import Tooltip from '../tooltip';
import type { FormContextProps } from './context';
@ -53,7 +53,7 @@ const FormItemLabel: React.FC<FormItemLabelProps & { required?: boolean; prefixC
requiredMark,
tooltip,
}) => {
const [formLocale] = useLocaleReceiver('Form');
const formLocale = useLocale('Form');
const {
vertical,

View File

@ -1,6 +1,6 @@
// locale-provider 文件夹的移除需要修改 @ant-design/tools 和 antd-img-crop
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
export * from '../locale/LocaleReceiver';
export * from '../locale/useLocale';
export default LocaleReceiver;
export default useLocale;

View File

@ -1,64 +0,0 @@
import * as React from 'react';
import type { Locale } from '.';
import type { LocaleContextProps } from './context';
import LocaleContext from './context';
import defaultLocaleData from './en_US';
export type LocaleComponentName = Exclude<keyof Locale, 'locale'>;
export interface LocaleReceiverProps<C extends LocaleComponentName = LocaleComponentName> {
componentName?: C;
defaultLocale?: Locale[C] | (() => Locale[C]);
children: (
locale: NonNullable<Locale[C]>,
localeCode: string,
fullLocale: Locale,
) => React.ReactElement;
}
const LocaleReceiver = <C extends LocaleComponentName = LocaleComponentName>(
props: LocaleReceiverProps<C>,
) => {
const { componentName = 'global' as C, defaultLocale, children } = props;
const antLocale = React.useContext<LocaleContextProps | undefined>(LocaleContext);
const getLocale = React.useMemo<NonNullable<Locale[C]>>(() => {
const locale = defaultLocale || defaultLocaleData[componentName];
const localeFromContext = antLocale?.[componentName] ?? {};
return {
...(locale instanceof Function ? locale() : locale),
...(localeFromContext || {}),
};
}, [componentName, defaultLocale, antLocale]);
const getLocaleCode = React.useMemo<string>(() => {
const localeCode = antLocale && antLocale.locale;
// Had use LocaleProvide but didn't set locale
if (antLocale && antLocale.exist && !localeCode) {
return defaultLocaleData.locale;
}
return localeCode!;
}, [antLocale]);
return children(getLocale, getLocaleCode, antLocale!);
};
export default LocaleReceiver;
export const useLocaleReceiver = <C extends LocaleComponentName = LocaleComponentName>(
componentName: C,
defaultLocale?: Locale[C] | (() => Locale[C]),
): [Locale[C]] => {
const antLocale = React.useContext<LocaleContextProps | undefined>(LocaleContext);
const getLocale = React.useMemo<NonNullable<Locale[C]>>(() => {
const locale = defaultLocale || defaultLocaleData[componentName];
const localeFromContext = antLocale?.[componentName] ?? {};
return {
...(typeof locale === 'function' ? locale() : locale),
...(localeFromContext || {}),
};
}, [componentName, defaultLocale, antLocale]);
return [getLocale];
};

View File

@ -0,0 +1,24 @@
import * as React from 'react';
import type { Locale } from '.';
import type { LocaleContextProps } from './context';
import LocaleContext from './context';
import defaultLocaleData from './en_US';
export type LocaleComponentName = Exclude<keyof Locale, 'locale'>;
const useLocale = <C extends LocaleComponentName = LocaleComponentName>(
componentName: C,
defaultLocale?: Locale[C] | (() => Locale[C]),
): Locale[C] => {
const antLocale = React.useContext<LocaleContextProps | undefined>(LocaleContext);
return React.useMemo<NonNullable<Locale[C]>>(() => {
const locale = defaultLocale || defaultLocaleData[componentName];
const localeFromContext = antLocale?.[componentName] ?? {};
return {
...(typeof locale === 'function' ? locale() : locale),
...(localeFromContext || {}),
};
}, [componentName, defaultLocale, antLocale]);
};
export default useLocale;

View File

@ -329,64 +329,58 @@ exports[`Menu all types must be available in the "items" syntax 1`] = `
`;
exports[`Menu rtl render component should be rendered correctly in RTL direction 1`] = `
HTMLCollection [
<ul
class="ant-menu ant-menu-root ant-menu-vertical ant-menu-light ant-menu-rtl"
data-menu-list="true"
dir="rtl"
role="menu"
tabindex="0"
<ul
class="ant-menu ant-menu-root ant-menu-vertical ant-menu-light ant-menu-rtl"
data-menu-list="true"
dir="rtl"
role="menu"
tabindex="0"
>
<li
class="ant-menu-item"
data-menu-id="rc-menu-uuid-test-tmp_key-0"
role="menuitem"
tabindex="-1"
>
<li
class="ant-menu-item"
data-menu-id="rc-menu-uuid-test-tmp_key-0"
<span
class="ant-menu-title-content"
/>
</li>
<li
class="ant-menu-item-group"
role="presentation"
>
<div
class="ant-menu-item-group-title"
role="presentation"
/>
<ul
class="ant-menu-item-group-list"
role="group"
/>
</li>
<li
class="ant-menu-submenu ant-menu-submenu-vertical"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-tmp_key-2-popup"
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
data-menu-id="rc-menu-uuid-test-tmp_key-2"
role="menuitem"
tabindex="-1"
>
<span
class="ant-menu-title-content"
/>
</li>
<li
class="ant-menu-item-group"
role="presentation"
>
<div
class="ant-menu-item-group-title"
role="presentation"
<i
class="ant-menu-submenu-arrow"
/>
<ul
class="ant-menu-item-group-list"
role="group"
/>
</li>
<li
class="ant-menu-submenu ant-menu-submenu-vertical"
role="none"
>
<div
aria-controls="rc-menu-uuid-test-tmp_key-2-popup"
aria-expanded="false"
aria-haspopup="true"
class="ant-menu-submenu-title"
data-menu-id="rc-menu-uuid-test-tmp_key-2"
role="menuitem"
tabindex="-1"
>
<span
class="ant-menu-title-content"
/>
<i
class="ant-menu-submenu-arrow"
/>
</div>
</li>
</ul>,
<div
aria-hidden="true"
style="display: none;"
/>,
]
</div>
</li>
</ul>
`;
exports[`Menu should controlled collapse work 1`] = `

View File

@ -5,12 +5,12 @@ import {
PieChartOutlined,
UserOutlined,
} from '@ant-design/icons';
import React, { useState, useMemo } from 'react';
import React, { useMemo, useState } from 'react';
import type { MenuProps, MenuRef } from '..';
import Menu from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render, act } from '../../../tests/utils';
import { act, fireEvent, render } from '../../../tests/utils';
import Layout from '../../layout';
import initCollapseMotion from '../../_util/motion';
import { noop } from '../../_util/warning';
@ -133,7 +133,7 @@ describe('Menu', () => {
</Menu>
);
rtlTest(RtlDemo, { componentName: 'menu' });
rtlTest(RtlDemo);
let div: HTMLDivElement;

View File

@ -15,8 +15,6 @@ import { wrapPromiseFn } from './util';
export { ArgsProps };
const methods: NoticeType[] = ['success', 'info', 'warning', 'error', 'loading'];
let message: GlobalMessage | null = null;
let act: (callback: VoidFunction) => Promise<void> | void = (callback) => callback();
@ -220,7 +218,6 @@ function flushNotice() {
// ==============================================================================
// == Export ==
// ==============================================================================
type MethodType = typeof methods[number];
function setMessageGlobalConfig(config: ConfigOptions) {
defaultGlobalConfig = {
@ -303,14 +300,26 @@ function destroy(key: React.Key) {
flushNotice();
}
const baseStaticMethods: {
interface BaseMethods {
open: (config: ArgsProps) => MessageType;
destroy: (key?: React.Key) => void;
config: typeof setMessageGlobalConfig;
useMessage: typeof useMessage;
/** @private Internal Component. Do not use in your production. */
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
} = {
}
interface MessageMethods {
info: TypeOpen;
success: TypeOpen;
error: TypeOpen;
warning: TypeOpen;
loading: TypeOpen;
}
const methods: (keyof MessageMethods)[] = ['success', 'info', 'warning', 'error', 'loading'];
const baseStaticMethods: BaseMethods = {
open,
destroy,
config: setMessageGlobalConfig,
@ -318,10 +327,9 @@ const baseStaticMethods: {
_InternalPanelDoNotUseOrYouWillBeFired: PurePanel,
};
const staticMethods: typeof baseStaticMethods & Record<MethodType, TypeOpen> =
baseStaticMethods as any;
const staticMethods = baseStaticMethods as MessageMethods & BaseMethods;
methods.forEach((type) => {
methods.forEach((type: keyof MessageMethods) => {
staticMethods[type] = (...args: Parameters<TypeOpen>) => typeOpen(type, args);
});

View File

@ -5,7 +5,7 @@ import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
import classNames from 'classnames';
import * as React from 'react';
import ConfigProvider from '../config-provider';
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
import ActionButton from '../_util/ActionButton';
import { getTransitionName } from '../_util/motion';
import warning from '../_util/warning';
@ -81,57 +81,53 @@ export function ConfirmContent(
const autoFocusButton = props.autoFocusButton === null ? false : props.autoFocusButton || 'ok';
return (
<LocaleReceiver componentName="Modal">
{(locale) => {
const mergedLocale = staticLocale || locale;
const locale = useLocale('Modal');
const cancelButton = mergedOkCancel && (
const mergedLocale = staticLocale || locale;
const cancelButton = mergedOkCancel && (
<ActionButton
actionFn={onCancel}
close={close}
autoFocus={autoFocusButton === 'cancel'}
buttonProps={cancelButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
>
{cancelText || mergedLocale?.cancelText}
</ActionButton>
);
return (
<div className={`${confirmPrefixCls}-body-wrapper`}>
<div className={`${confirmPrefixCls}-body`}>
{mergedIcon}
{props.title === undefined ? null : (
<span className={`${confirmPrefixCls}-title`}>{props.title}</span>
)}
<div className={`${confirmPrefixCls}-content`}>{props.content}</div>
</div>
{footer !== undefined ? (
footer
) : (
<div className={`${confirmPrefixCls}-btns`}>
{cancelButton}
<ActionButton
actionFn={onCancel}
type={okType}
actionFn={onOk}
close={close}
autoFocus={autoFocusButton === 'cancel'}
buttonProps={cancelButtonProps}
autoFocus={autoFocusButton === 'ok'}
buttonProps={okButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
>
{cancelText || mergedLocale?.cancelText}
{okText || (mergedOkCancel ? mergedLocale?.okText : mergedLocale?.justOkText)}
</ActionButton>
);
return (
<div className={`${confirmPrefixCls}-body-wrapper`}>
<div className={`${confirmPrefixCls}-body`}>
{mergedIcon}
{props.title === undefined ? null : (
<span className={`${confirmPrefixCls}-title`}>{props.title}</span>
)}
<div className={`${confirmPrefixCls}-content`}>{props.content}</div>
</div>
{footer !== undefined ? (
footer
) : (
<div className={`${confirmPrefixCls}-btns`}>
{cancelButton}
<ActionButton
type={okType}
actionFn={onOk}
close={close}
autoFocus={autoFocusButton === 'ok'}
buttonProps={okButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
>
{okText || (mergedOkCancel ? mergedLocale?.okText : mergedLocale?.justOkText)}
</ActionButton>
</div>
)}
</div>
);
}}
</LocaleReceiver>
</div>
)}
</div>
);
}
const ConfirmDialog = (props: ConfirmDialogProps) => {
const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {
const {
close,
zIndex,

View File

@ -9,7 +9,7 @@ import { NoCompactStyle } from '../space/Compact';
import { getTransitionName } from '../_util/motion';
import { canUseDocElement } from '../_util/styleChecker';
import warning from '../_util/warning';
import { renderCloseIcon, renderFooter } from './PurePanel';
import { Footer, renderCloseIcon } from './PurePanel';
import useStyle from './style';
type MousePosition = { x: number; y: number } | null;
@ -209,11 +209,13 @@ const Modal: React.FC<ModalProps> = (props) => {
prefixCls={prefixCls}
rootClassName={classNames(hashId, rootClassName)}
wrapClassName={wrapClassNameExtended}
footer={renderFooter({
...props,
onOk: handleOk,
onCancel: handleCancel,
})}
footer={
props.footer === null ? (
props.footer
) : (
<Footer {...props} onOk={handleOk} onCancel={handleCancel} />
)
}
visible={open ?? visible}
mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel}

View File

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import { Panel } from 'rc-dialog';
@ -6,10 +7,10 @@ import * as React from 'react';
import Button from '../button';
import { convertLegacyProps } from '../button/button';
import { ConfigContext } from '../config-provider';
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
import { ConfirmContent } from './ConfirmDialog';
import { getConfirmLocale } from './locale';
import type { ModalProps, ModalFuncProps } from './Modal';
import type { ModalFuncProps, ModalProps } from './Modal';
import useStyle from './style';
export interface PurePanelProps
@ -27,21 +28,24 @@ export function renderCloseIcon(prefixCls: string, closeIcon?: React.ReactNode)
);
}
export function renderFooter(
props: Pick<
ModalProps,
| 'footer'
| 'okText'
| 'okType'
| 'cancelText'
| 'confirmLoading'
| 'okButtonProps'
| 'cancelButtonProps'
> & {
onOk?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
onCancel?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
},
) {
interface FooterProps {
onOk?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
onCancel?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement>;
}
export const Footer: React.FC<
FooterProps &
Pick<
ModalProps,
| 'footer'
| 'okText'
| 'okType'
| 'cancelText'
| 'confirmLoading'
| 'okButtonProps'
| 'cancelButtonProps'
>
> = (props) => {
const {
okText,
okType = 'primary',
@ -54,30 +58,28 @@ export function renderFooter(
footer,
} = props;
return footer === undefined ? (
<LocaleReceiver componentName="Modal" defaultLocale={getConfirmLocale()}>
{(locale) => (
<>
<Button onClick={onCancel} {...cancelButtonProps}>
{cancelText || locale!.cancelText}
</Button>
<Button
{...convertLegacyProps(okType)}
loading={confirmLoading}
onClick={onOk}
{...okButtonProps}
>
{okText || locale!.okText}
</Button>
</>
)}
</LocaleReceiver>
) : (
footer
);
}
const locale = useLocale('Modal', getConfirmLocale());
export default function PurePanel(props: PurePanelProps) {
return footer === undefined ? (
<>
<Button onClick={onCancel} {...cancelButtonProps}>
{cancelText || locale?.cancelText}
</Button>
<Button
{...convertLegacyProps(okType)}
loading={confirmLoading}
onClick={onOk}
{...okButtonProps}
>
{okText || locale?.okText}
</Button>
</>
) : (
(footer as React.ReactElement)
);
};
const PurePanel: React.FC<PurePanelProps> = (props) => {
const {
prefixCls: customizePrefixCls,
className,
@ -117,7 +119,7 @@ export default function PurePanel(props: PurePanelProps) {
additionalProps = {
closable: closable ?? true,
title,
footer: renderFooter(props),
footer: props.footer === null ? props.footer : <Footer {...props} />,
children,
};
}
@ -138,4 +140,6 @@ export default function PurePanel(props: PurePanelProps) {
{...additionalProps}
/>
);
}
};
export default PurePanel;

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import { ConfigContext } from '../../config-provider';
import LocaleReceiver from '../../locale/LocaleReceiver';
import defaultLocale from '../../locale/en_US';
import useLocale from '../../locale/useLocale';
import ConfirmDialog from '../ConfirmDialog';
import type { ModalFuncProps } from '../Modal';
@ -46,24 +46,22 @@ const HookModal: React.ForwardRefRenderFunction<HookModalRef, HookModalProps> =
const mergedOkCancel = innerConfig.okCancel ?? innerConfig.type === 'confirm';
const contextLocale = useLocale('Modal', defaultLocale.Modal);
return (
<LocaleReceiver componentName="Modal" defaultLocale={defaultLocale.Modal}>
{(contextLocale) => (
<ConfirmDialog
prefixCls={prefixCls}
rootPrefixCls={rootPrefixCls}
{...innerConfig}
close={close}
open={open}
afterClose={afterClose}
okText={
innerConfig.okText || (mergedOkCancel ? contextLocale.okText : contextLocale.justOkText)
}
direction={direction}
cancelText={innerConfig.cancelText || contextLocale.cancelText}
/>
)}
</LocaleReceiver>
<ConfirmDialog
prefixCls={prefixCls}
rootPrefixCls={rootPrefixCls}
{...innerConfig}
close={close}
open={open}
afterClose={afterClose}
okText={
innerConfig.okText || (mergedOkCancel ? contextLocale?.okText : contextLocale?.justOkText)
}
direction={direction}
cancelText={innerConfig.cancelText || contextLocale?.cancelText}
/>
);
};

View File

@ -187,8 +187,6 @@ function flushNotice() {
// ==============================================================================
// == Export ==
// ==============================================================================
const methods = ['success', 'info', 'warning', 'error'] as const;
type MethodType = typeof methods[number];
function setNotificationGlobalConfig(config: GlobalConfigProps) {
defaultGlobalConfig = {
@ -218,14 +216,27 @@ function destroy(key: React.Key) {
flushNotice();
}
const baseStaticMethods: {
interface BaseMethods {
open: (config: ArgsProps) => void;
destroy: (key?: React.Key) => void;
config: any;
useNotification: typeof useNotification;
/** @private Internal Component. Do not use in your production. */
_InternalPanelDoNotUseOrYouWillBeFired: typeof PurePanel;
} = {
}
type StaticFn = (config: ArgsProps) => void;
interface NoticeMethods {
success: StaticFn;
info: StaticFn;
warning: StaticFn;
error: StaticFn;
}
const methods: (keyof NoticeMethods)[] = ['success', 'info', 'warning', 'error'];
const baseStaticMethods: BaseMethods = {
open,
destroy,
config: setNotificationGlobalConfig,
@ -233,15 +244,10 @@ const baseStaticMethods: {
_InternalPanelDoNotUseOrYouWillBeFired: PurePanel,
};
const staticMethods: typeof baseStaticMethods & Record<MethodType, (config: ArgsProps) => void> =
baseStaticMethods as any;
const staticMethods = baseStaticMethods as NoticeMethods & BaseMethods;
methods.forEach((type) => {
staticMethods[type] = (config) =>
open({
...config,
type,
});
methods.forEach((type: keyof NoticeMethods) => {
staticMethods[type] = (config) => open({ ...config, type });
});
// ==============================================================================

View File

@ -3,13 +3,13 @@ import DoubleRightOutlined from '@ant-design/icons/DoubleRightOutlined';
import LeftOutlined from '@ant-design/icons/LeftOutlined';
import RightOutlined from '@ant-design/icons/RightOutlined';
import classNames from 'classnames';
import type { PaginationProps as RcPaginationProps, PaginationLocale } from 'rc-pagination';
import type { PaginationLocale, PaginationProps as RcPaginationProps } from 'rc-pagination';
import RcPagination from 'rc-pagination';
import enUS from 'rc-pagination/lib/locale/en_US';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import useBreakpoint from '../grid/hooks/useBreakpoint';
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
import { MiddleSelect, MiniSelect } from './Select';
import useStyle from './style';
@ -90,44 +90,36 @@ const Pagination: React.FC<PaginationProps> = ({
[prevIcon, nextIcon] = [nextIcon, prevIcon];
[jumpPrevIcon, jumpNextIcon] = [jumpNextIcon, jumpPrevIcon];
}
return {
prevIcon,
nextIcon,
jumpPrevIcon,
jumpNextIcon,
};
return { prevIcon, nextIcon, jumpPrevIcon, jumpNextIcon };
};
return (
<LocaleReceiver componentName="Pagination" defaultLocale={enUS}>
{(contextLocale) => {
const locale = { ...contextLocale, ...customLocale };
const isSmall = size === 'small' || !!(xs && !size && responsive);
const selectPrefixCls = getPrefixCls('select', customizeSelectPrefixCls);
const extendedClassName = classNames(
{
[`${prefixCls}-mini`]: isSmall,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);
const contextLocale = useLocale('Pagination', enUS);
return wrapSSR(
<RcPagination
{...getIconsProps()}
{...restProps}
prefixCls={prefixCls}
selectPrefixCls={selectPrefixCls}
className={extendedClassName}
selectComponentClass={selectComponentClass || (isSmall ? MiniSelect : MiddleSelect)}
locale={locale}
showSizeChanger={mergedShowSizeChanger}
/>,
);
}}
</LocaleReceiver>
const locale = { ...contextLocale, ...customLocale };
const isSmall = size === 'small' || !!(xs && !size && responsive);
const selectPrefixCls = getPrefixCls('select', customizeSelectPrefixCls);
const extendedClassName = classNames(
{
[`${prefixCls}-mini`]: isSmall,
[`${prefixCls}-rtl`]: direction === 'rtl',
},
className,
rootClassName,
hashId,
);
return wrapSSR(
<RcPagination
{...getIconsProps()}
{...restProps}
prefixCls={prefixCls}
selectPrefixCls={selectPrefixCls}
className={extendedClassName}
selectComponentClass={selectComponentClass || (isSmall ? MiniSelect : MiddleSelect)}
locale={locale}
showSizeChanger={mergedShowSizeChanger}
/>,
);
};

View File

@ -1,15 +1,15 @@
import * as React from 'react';
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
import classNames from 'classnames';
import * as React from 'react';
import type { PopconfirmProps } from '.';
import Button from '../button';
import { convertLegacyProps } from '../button/button';
import ActionButton from '../_util/ActionButton';
import LocaleReceiver from '../locale/LocaleReceiver';
import defaultLocale from '../locale/en_US';
import { getRenderPropValue } from '../_util/getRenderPropValue';
import { ConfigContext } from '../config-provider';
import defaultLocale from '../locale/en_US';
import useLocale from '../locale/useLocale';
import PopoverPurePanel from '../popover/PurePanel';
import ActionButton from '../_util/ActionButton';
import { getRenderPropValue } from '../_util/getRenderPropValue';
import useStyle from './style';
@ -56,43 +56,41 @@ export const Overlay: React.FC<OverlayProps> = (props) => {
const { getPrefixCls } = React.useContext(ConfigContext);
const contextLocale = useLocale('Popconfirm', defaultLocale.Popconfirm);
return (
<LocaleReceiver componentName="Popconfirm" defaultLocale={defaultLocale.Popconfirm}>
{(contextLocale) => (
<div className={`${prefixCls}-inner-content`}>
<div className={`${prefixCls}-message`}>
{icon && <span className={`${prefixCls}-message-icon`}>{icon}</span>}
<div
className={classNames(`${prefixCls}-message-title`, {
[`${prefixCls}-message-title-only`]: !!description,
})}
>
{getRenderPropValue(title)}
</div>
</div>
{description && (
<div className={`${prefixCls}-description`}>{getRenderPropValue(description)}</div>
)}
<div className={`${prefixCls}-buttons`}>
{showCancel && (
<Button onClick={onCancel} size="small" {...cancelButtonProps}>
{cancelText ?? contextLocale.cancelText}
</Button>
)}
<ActionButton
buttonProps={{ size: 'small', ...convertLegacyProps(okType), ...okButtonProps }}
actionFn={onConfirm}
close={close}
prefixCls={getPrefixCls('btn')}
quitOnNullishReturnValue
emitEvent
>
{okText ?? contextLocale.okText}
</ActionButton>
</div>
<div className={`${prefixCls}-inner-content`}>
<div className={`${prefixCls}-message`}>
{icon && <span className={`${prefixCls}-message-icon`}>{icon}</span>}
<div
className={classNames(`${prefixCls}-message-title`, {
[`${prefixCls}-message-title-only`]: !!description,
})}
>
{getRenderPropValue(title)}
</div>
</div>
{description && (
<div className={`${prefixCls}-description`}>{getRenderPropValue(description)}</div>
)}
</LocaleReceiver>
<div className={`${prefixCls}-buttons`}>
{showCancel && (
<Button onClick={onCancel} size="small" {...cancelButtonProps}>
{cancelText ?? contextLocale?.cancelText}
</Button>
)}
<ActionButton
buttonProps={{ size: 'small', ...convertLegacyProps(okType), ...okButtonProps }}
actionFn={onConfirm}
close={close}
prefixCls={getPrefixCls('btn')}
quitOnNullishReturnValue
emitEvent
>
{okText ?? contextLocale?.okText}
</ActionButton>
</div>
</div>
);
};

View File

@ -1,16 +1,16 @@
import React, { useMemo, useContext } from 'react';
import { QRCodeCanvas } from 'qrcode.react';
import classNames from 'classnames';
import { ReloadOutlined } from '@ant-design/icons';
import { ConfigContext } from '../config-provider';
import LocaleReceiver from '../locale/LocaleReceiver';
import type { ConfigConsumerProps } from '../config-provider';
import type { QRCodeProps, QRPropsCanvas } from './interface';
import warning from '../_util/warning';
import useStyle from './style/index';
import Spin from '../spin';
import classNames from 'classnames';
import { QRCodeCanvas } from 'qrcode.react';
import React, { useContext, useMemo } from 'react';
import Button from '../button';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import useLocale from '../locale/useLocale';
import Spin from '../spin';
import theme from '../theme';
import warning from '../_util/warning';
import type { QRCodeProps, QRPropsCanvas } from './interface';
import useStyle from './style/index';
const { useToken } = theme;
@ -53,6 +53,8 @@ const QRCode: React.FC<QRCodeProps> = (props) => {
};
}, [errorLevel, color, icon, iconSize, size, value]);
const locale = useLocale('QRCode');
if (!value) {
if (process.env.NODE_ENV !== 'production') {
warning(false, 'QRCode', 'need to receive `value` props');
@ -73,28 +75,24 @@ const QRCode: React.FC<QRCodeProps> = (props) => {
});
return wrapSSR(
<LocaleReceiver componentName="QRCode">
{(locale) => (
<div style={{ ...style, width: size, height: size }} className={cls}>
{status !== 'active' && (
<div className={`${prefixCls}-mask`}>
{status === 'loading' && <Spin />}
{status === 'expired' && (
<>
<p className={`${prefixCls}-expired`}>{locale.expired}</p>
{typeof onRefresh === 'function' && (
<Button type="link" icon={<ReloadOutlined />} onClick={onRefresh}>
{locale.refresh}
</Button>
)}
</>
<div style={{ ...style, width: size, height: size }} className={cls}>
{status !== 'active' && (
<div className={`${prefixCls}-mask`}>
{status === 'loading' && <Spin />}
{status === 'expired' && (
<>
<p className={`${prefixCls}-expired`}>{locale?.expired}</p>
{typeof onRefresh === 'function' && (
<Button type="link" icon={<ReloadOutlined />} onClick={onRefresh}>
{locale?.refresh}
</Button>
)}
</div>
</>
)}
<QRCodeCanvas {...qrCodeProps} />
</div>
)}
</LocaleReceiver>,
<QRCodeCanvas {...qrCodeProps} />
</div>,
);
};

View File

@ -5,7 +5,7 @@ const App: React.FC = () => {
const [current, setCurrent] = useState(0);
const onChange = (value: number) => {
console.log('onChange:', current);
console.log('onChange:', value);
setCurrent(value);
};
const description = 'This is a description.';

View File

@ -1,10 +1,10 @@
/* eslint-disable import/prefer-default-export */
import type { CSSObject } from '@ant-design/cssinjs';
import type { DerivativeToken, AliasToken } from '../theme/internal';
import type { AliasToken, DerivativeToken } from '../theme/internal';
export { operationUnit } from './operationUnit';
export { roundedArrow } from './roundedArrow';
export { genPresetColor } from './presetColor';
export { roundedArrow } from './roundedArrow';
export const textEllipsis: CSSObject = {
overflow: 'hidden',

View File

@ -28,6 +28,7 @@ import type { SortState } from './hooks/useSorter';
import useSorter, { getSortData } from './hooks/useSorter';
import useTitleColumns from './hooks/useTitleColumns';
import type {
ColumnsType,
ColumnTitleProps,
ColumnType,
ExpandableConfig,
@ -35,15 +36,14 @@ import type {
FilterValue,
GetPopupContainer,
GetRowKey,
RefInternalTable,
SorterResult,
SortOrder,
TableAction,
TableCurrentDataSource,
TableLocale,
TableRowSelection,
ColumnsType,
TablePaginationConfig,
RefInternalTable,
TableRowSelection,
} from './interface';
import RcTable from './RcTable';
@ -114,6 +114,7 @@ function InternalTable<RecordType extends object = any>(
props: InternalTableProps<RecordType>,
ref: React.MutableRefObject<HTMLDivElement>,
) {
const { getPopupContainer: getContextPopupContainer } = React.useContext(ConfigContext);
const {
prefixCls: customizePrefixCls,
className,
@ -323,7 +324,7 @@ function InternalTable<RecordType extends object = any>(
dropdownPrefixCls,
mergedColumns,
onFilterChange,
getPopupContainer,
getPopupContainer: getPopupContainer || getContextPopupContainer,
});
const mergedData = getFilterData(sortedData, filterStates);
@ -408,7 +409,7 @@ function InternalTable<RecordType extends object = any>(
expandType,
childrenColumnName,
locale: tableLocale,
getPopupContainer,
getPopupContainer: getPopupContainer || getContextPopupContainer,
});
const internalRowClassName = (record: RecordType, index: number, indent: number) => {

View File

@ -1,5 +1,5 @@
import { ConfigProvider } from 'antd';
import React from 'react';
import React, { useRef } from 'react';
import type { TableProps } from '..';
import Table from '..';
import mountTest from '../../../tests/shared/mountTest';
@ -369,4 +369,36 @@ describe('Table', () => {
);
expect(container.firstChild).toMatchSnapshot();
});
it('support getPopupContainer inject by ConfigProvider', async () => {
const columns = [
{
title: 'title',
key: 'title',
dataIndex: 'title',
filters: [
{
text: 'filter',
value: 'filter',
},
],
},
];
const Demo = () => {
const wrapRef = useRef(null);
return (
<ConfigProvider getPopupContainer={wrapRef.current!}>
<div ref={wrapRef}>
<Table columns={columns} />
</div>
</ConfigProvider>
);
};
const { container } = render(<Demo />);
fireEvent.click(container.querySelector('.ant-table-filter-trigger')!);
await waitFakeTimer();
expect(container.querySelector('.ant-dropdown')).toBeTruthy();
});
});

View File

@ -5,7 +5,7 @@ import React from 'react';
import type { ButtonProps } from '../button';
import Button from '../button';
import defaultLocale from '../locale/en_US';
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
import type { TourStepProps } from './interface';
function isValidNode(node: ReactNode): boolean {
@ -92,52 +92,50 @@ const TourPanel: React.FC<TourPanelProps> = ({ stepProps, current, type, indicat
ghost: mergedType === 'primary',
};
const contextLocale = useLocale('Tour', defaultLocale.Tour);
return (
<LocaleReceiver componentName="Tour" defaultLocale={defaultLocale.Tour}>
{(contextLocale) => (
<div
className={classNames(
mergedType === 'primary' ? `${prefixCls}-primary` : '',
className,
`${prefixCls}-content`,
)}
>
{arrow && <div className={`${prefixCls}-arrow`} key="arrow" />}
<div className={`${prefixCls}-inner`}>
<CloseOutlined className={`${prefixCls}-close`} onClick={onClose} />
{coverNode}
{headerNode}
{descriptionNode}
<div className={`${prefixCls}-footer`}>
{total > 1 && <div className={`${prefixCls}-indicators`}>{mergeIndicatorNode}</div>}
<div className={`${prefixCls}-buttons`}>
{current !== 0 ? (
<Button
{...secondaryBtnProps}
{...prevButtonProps}
onClick={prevBtnClick}
size="small"
className={classNames(`${prefixCls}-prev-btn`, prevButtonProps?.className)}
>
{prevButtonProps?.children ?? contextLocale.Previous}
</Button>
) : null}
<Button
type={mainBtnType}
{...nextButtonProps}
onClick={nextBtnClick}
size="small"
className={classNames(`${prefixCls}-next-btn`, nextButtonProps?.className)}
>
{nextButtonProps?.children ??
(isLastStep ? contextLocale.Finish : contextLocale.Next)}
</Button>
</div>
</div>
<div
className={classNames(
mergedType === 'primary' ? `${prefixCls}-primary` : '',
className,
`${prefixCls}-content`,
)}
>
{arrow && <div className={`${prefixCls}-arrow`} key="arrow" />}
<div className={`${prefixCls}-inner`}>
<CloseOutlined className={`${prefixCls}-close`} onClick={onClose} />
{coverNode}
{headerNode}
{descriptionNode}
<div className={`${prefixCls}-footer`}>
{total > 1 && <div className={`${prefixCls}-indicators`}>{mergeIndicatorNode}</div>}
<div className={`${prefixCls}-buttons`}>
{current !== 0 ? (
<Button
{...secondaryBtnProps}
{...prevButtonProps}
onClick={prevBtnClick}
size="small"
className={classNames(`${prefixCls}-prev-btn`, prevButtonProps?.className)}
>
{prevButtonProps?.children ?? contextLocale?.Previous}
</Button>
) : null}
<Button
type={mainBtnType}
{...nextButtonProps}
onClick={nextBtnClick}
size="small"
className={classNames(`${prefixCls}-next-btn`, nextButtonProps?.className)}
>
{nextButtonProps?.children ??
(isLastStep ? contextLocale?.Finish : contextLocale?.Next)}
</Button>
</div>
</div>
)}
</LocaleReceiver>
</div>
</div>
);
};

View File

@ -3,8 +3,8 @@ import classNames from 'classnames';
import * as React from 'react';
import type { KeyWiseTransferItem } from '.';
import Checkbox from '../checkbox';
import LocaleReceiver from '../locale/LocaleReceiver';
import defaultLocale from '../locale/en_US';
import useLocale from '../locale/useLocale';
import TransButton from '../_util/transButton';
type ListItemProps<RecordType> = {
@ -43,45 +43,42 @@ const ListItem = <RecordType extends KeyWiseTransferItem>(props: ListItemProps<R
title = String(renderedText);
}
const contextLocale = useLocale('Transfer', defaultLocale.Transfer);
const liProps: React.HTMLAttributes<HTMLLIElement> = { className, title };
const labelNode = <span className={`${prefixCls}-content-item-text`}>{renderedEl}</span>;
if (showRemove) {
return (
<li {...liProps}>
{labelNode}
<TransButton
disabled={disabled || item.disabled}
className={`${prefixCls}-content-item-remove`}
aria-label={contextLocale?.remove}
onClick={() => {
onRemove?.(item);
}}
>
<DeleteOutlined />
</TransButton>
</li>
);
}
// Default click to select
liProps.onClick = disabled || item.disabled ? undefined : () => onClick(item);
return (
<LocaleReceiver componentName="Transfer" defaultLocale={defaultLocale.Transfer}>
{(contextLocale) => {
const liProps: React.HTMLAttributes<HTMLLIElement> = { className, title };
const labelNode = <span className={`${prefixCls}-content-item-text`}>{renderedEl}</span>;
// Show remove
if (showRemove) {
return (
<li {...liProps}>
{labelNode}
<TransButton
disabled={disabled || item.disabled}
className={`${prefixCls}-content-item-remove`}
aria-label={contextLocale.remove}
onClick={() => {
onRemove?.(item);
}}
>
<DeleteOutlined />
</TransButton>
</li>
);
}
// Default click to select
liProps.onClick = disabled || item.disabled ? undefined : () => onClick(item);
return (
<li {...liProps}>
<Checkbox
className={`${prefixCls}-checkbox`}
checked={checked}
disabled={disabled || item.disabled}
/>
{labelNode}
</li>
);
}}
</LocaleReceiver>
<li {...liProps}>
<Checkbox
className={`${prefixCls}-checkbox`}
checked={checked}
disabled={disabled || item.disabled}
/>
{labelNode}
</li>
);
};

View File

@ -7,7 +7,7 @@ import DefaultRenderEmpty from '../config-provider/defaultRenderEmpty';
import type { FormItemStatusContextProps } from '../form/context';
import { FormItemInputContext } from '../form/context';
import defaultLocale from '../locale/en_US';
import LocaleReceiver from '../locale/LocaleReceiver';
import useLocale from '../locale/useLocale';
import type { InputStatus } from '../_util/statusUtils';
import { getMergedStatus, getStatusClassNames } from '../_util/statusUtils';
import { groupDisabledKeysMap, groupKeysMap } from '../_util/transKeys';
@ -367,78 +367,76 @@ const Transfer = <RecordType extends TransferItem = TransferItem>(
hashId,
);
const contextLocale = useLocale('Transfer', defaultLocale.Transfer);
const listLocale = getLocale(contextLocale!);
const [leftTitle, rightTitle] = getTitles(listLocale);
return wrapSSR(
<LocaleReceiver componentName="Transfer" defaultLocale={defaultLocale.Transfer}>
{(contextLocale) => {
const listLocale = getLocale(contextLocale);
const [leftTitle, rightTitle] = getTitles(listLocale);
return (
<div className={cls} style={style}>
<List<KeyWise<RecordType>>
prefixCls={`${prefixCls}-list`}
titleText={leftTitle}
dataSource={leftDataSource}
filterOption={filterOption}
style={handleListStyle('left')}
checkedKeys={sourceSelectedKeys}
handleFilter={leftFilter}
handleClear={handleLeftClear}
onItemSelect={onLeftItemSelect}
onItemSelectAll={onLeftItemSelectAll}
render={render}
showSearch={showSearch}
renderList={children}
footer={footer}
onScroll={handleLeftScroll}
disabled={disabled}
direction={dir === 'rtl' ? 'right' : 'left'}
showSelectAll={showSelectAll}
selectAllLabel={selectAllLabels[0]}
pagination={mergedPagination}
{...listLocale}
/>
<Operation
className={`${prefixCls}-operation`}
rightActive={rightActive}
rightArrowText={operations[0]}
moveToRight={moveToRight}
leftActive={leftActive}
leftArrowText={operations[1]}
moveToLeft={moveToLeft}
style={operationStyle}
disabled={disabled}
direction={dir}
oneWay={oneWay}
/>
<List<KeyWise<RecordType>>
prefixCls={`${prefixCls}-list`}
titleText={rightTitle}
dataSource={rightDataSource}
filterOption={filterOption}
style={handleListStyle('right')}
checkedKeys={targetSelectedKeys}
handleFilter={rightFilter}
handleClear={handleRightClear}
onItemSelect={onRightItemSelect}
onItemSelectAll={onRightItemSelectAll}
onItemRemove={onRightItemRemove}
render={render}
showSearch={showSearch}
renderList={children}
footer={footer}
onScroll={handleRightScroll}
disabled={disabled}
direction={dir === 'rtl' ? 'left' : 'right'}
showSelectAll={showSelectAll}
selectAllLabel={selectAllLabels[1]}
showRemove={oneWay}
pagination={mergedPagination}
{...listLocale}
/>
</div>
);
}}
</LocaleReceiver>,
<div className={cls} style={style}>
<List<KeyWise<RecordType>>
prefixCls={`${prefixCls}-list`}
titleText={leftTitle}
dataSource={leftDataSource}
filterOption={filterOption}
style={handleListStyle('left')}
checkedKeys={sourceSelectedKeys}
handleFilter={leftFilter}
handleClear={handleLeftClear}
onItemSelect={onLeftItemSelect}
onItemSelectAll={onLeftItemSelectAll}
render={render}
showSearch={showSearch}
renderList={children}
footer={footer}
onScroll={handleLeftScroll}
disabled={disabled}
direction={dir === 'rtl' ? 'right' : 'left'}
showSelectAll={showSelectAll}
selectAllLabel={selectAllLabels[0]}
pagination={mergedPagination}
{...listLocale}
/>
<Operation
className={`${prefixCls}-operation`}
rightActive={rightActive}
rightArrowText={operations[0]}
moveToRight={moveToRight}
leftActive={leftActive}
leftArrowText={operations[1]}
moveToLeft={moveToLeft}
style={operationStyle}
disabled={disabled}
direction={dir}
oneWay={oneWay}
/>
<List<KeyWise<RecordType>>
prefixCls={`${prefixCls}-list`}
titleText={rightTitle}
dataSource={rightDataSource}
filterOption={filterOption}
style={handleListStyle('right')}
checkedKeys={targetSelectedKeys}
handleFilter={rightFilter}
handleClear={handleRightClear}
onItemSelect={onRightItemSelect}
onItemSelectAll={onRightItemSelectAll}
onItemRemove={onRightItemRemove}
render={render}
showSearch={showSearch}
renderList={children}
footer={footer}
onScroll={handleRightScroll}
disabled={disabled}
direction={dir === 'rtl' ? 'left' : 'right'}
showSelectAll={showSelectAll}
selectAllLabel={selectAllLabels[1]}
showRemove={oneWay}
pagination={mergedPagination}
{...listLocale}
/>
</div>,
);
};

View File

@ -12,11 +12,11 @@ import omit from 'rc-util/lib/omit';
import { composeRef } from 'rc-util/lib/ref';
import * as React from 'react';
import { ConfigContext } from '../../config-provider';
import { useLocaleReceiver } from '../../locale/LocaleReceiver';
import TransButton from '../../_util/transButton';
import { isStyleSupport } from '../../_util/styleChecker';
import useLocale from '../../locale/useLocale';
import type { TooltipProps } from '../../tooltip';
import Tooltip from '../../tooltip';
import { isStyleSupport } from '../../_util/styleChecker';
import TransButton from '../../_util/transButton';
import Editable from '../Editable';
import useMergedConfig from '../hooks/useMergedConfig';
import useUpdatedEffect from '../hooks/useUpdatedEffect';
@ -135,7 +135,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
...restProps
} = props;
const { getPrefixCls, direction } = React.useContext(ConfigContext);
const textLocale = useLocaleReceiver('Text')[0]!; // Force TS get this
const textLocale = useLocale('Text');
const typographyRef = React.useRef<HTMLElement>(null);
const editIconRef = React.useRef<HTMLDivElement>(null);
@ -408,7 +408,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
if (symbol) {
expandContent = symbol;
} else {
expandContent = textLocale.expand;
expandContent = textLocale?.expand;
}
return (
@ -416,7 +416,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
key="expand"
className={`${prefixCls}-expand`}
onClick={onExpandClick}
aria-label={textLocale.expand}
aria-label={textLocale?.expand}
>
{expandContent}
</a>
@ -429,7 +429,7 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
const { icon, tooltip } = editConfig;
const editTitle = toArray(tooltip)[0] || textLocale.edit;
const editTitle = toArray(tooltip)[0] || textLocale?.edit;
const ariaLabel = typeof editTitle === 'string' ? editTitle : '';
return triggerType.includes('icon') ? (
@ -456,9 +456,9 @@ const Base = React.forwardRef<HTMLElement, BlockProps>((props, ref) => {
const iconNodes = toList(icon);
const copyTitle = copied
? getNode(tooltipNodes[1], textLocale.copied)
: getNode(tooltipNodes[0], textLocale.copy);
const systemStr = copied ? textLocale.copied : textLocale.copy;
? getNode(tooltipNodes[1], textLocale?.copied)
: getNode(tooltipNodes[0], textLocale?.copy);
const systemStr = copied ? textLocale?.copied : textLocale?.copy;
const ariaLabel = typeof copyTitle === 'string' ? copyTitle : systemStr;
return (

View File

@ -6,8 +6,8 @@ import * as React from 'react';
import { flushSync } from 'react-dom';
import { ConfigContext } from '../config-provider';
import DisabledContext from '../config-provider/DisabledContext';
import LocaleReceiver from '../locale/LocaleReceiver';
import defaultLocale from '../locale/en_US';
import useLocale from '../locale/useLocale';
import warning from '../_util/warning';
import type { RcFile, ShowUploadListInterface, UploadChangeParam, UploadFile } from './interface';
import { UploadProps } from './interface';
@ -339,48 +339,46 @@ const InternalUpload: React.ForwardRefRenderFunction<unknown, UploadProps> = (pr
const [wrapSSR, hashId] = useStyle(prefixCls);
const renderUploadList = (button?: React.ReactNode, buttonVisible?: boolean) =>
showUploadList ? (
<LocaleReceiver componentName="Upload" defaultLocale={defaultLocale.Upload}>
{(contextLocale) => {
const {
showRemoveIcon,
showPreviewIcon,
showDownloadIcon,
removeIcon,
previewIcon,
downloadIcon,
} =
typeof showUploadList === 'boolean' ? ({} as ShowUploadListInterface) : showUploadList;
return (
<UploadList
prefixCls={prefixCls}
listType={listType}
items={mergedFileList}
previewFile={previewFile}
onPreview={onPreview}
onDownload={onDownload}
onRemove={handleRemove}
showRemoveIcon={!mergedDisabled && showRemoveIcon}
showPreviewIcon={showPreviewIcon}
showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon}
previewIcon={previewIcon}
downloadIcon={downloadIcon}
iconRender={iconRender}
locale={{ ...contextLocale, ...propLocale }}
isImageUrl={isImageUrl}
progress={progress}
appendAction={button}
appendActionVisible={buttonVisible}
itemRender={itemRender}
/>
);
}}
</LocaleReceiver>
) : (
button
const contextLocale = useLocale('Upload', defaultLocale.Upload);
const {
showRemoveIcon,
showPreviewIcon,
showDownloadIcon,
removeIcon,
previewIcon,
downloadIcon,
} = typeof showUploadList === 'boolean' ? ({} as ShowUploadListInterface) : showUploadList;
const renderUploadList = (button?: React.ReactNode, buttonVisible?: boolean) => {
if (!showUploadList) {
return button;
}
return (
<UploadList
prefixCls={prefixCls}
listType={listType}
items={mergedFileList}
previewFile={previewFile}
onPreview={onPreview}
onDownload={onDownload}
onRemove={handleRemove}
showRemoveIcon={!mergedDisabled && showRemoveIcon}
showPreviewIcon={showPreviewIcon}
showDownloadIcon={showDownloadIcon}
removeIcon={removeIcon}
previewIcon={previewIcon}
downloadIcon={downloadIcon}
iconRender={iconRender}
locale={{ ...contextLocale, ...propLocale }}
isImageUrl={isImageUrl}
progress={progress}
appendAction={button}
appendActionVisible={buttonVisible}
itemRender={itemRender}
/>
);
};
const rtlCls = {
[`${prefixCls}-rtl`]: direction === 'rtl',

View File

@ -0,0 +1,335 @@
---
title: 'Repost: How to submit a riddle'
date: 2023-02-22
author: afc163
---
There are some common mistake when submitting an issue to the community for the first time, making it difficult for maintainers to help solve problems. Repost an old article, hoping helps for submit issue after laughing :)
> Original link: [How to submit a issue which never can be answered to open source projects](https://zhuanlan.zhihu.com/p/25795393)
---
As a developer, I use and participate in many open source projects. In the open source community, questions and answers are the most interesting part. Some issue with fully communication and some others are not. There are many fascinating and useful commonalities in the way people ask questions. I've distilled them in the hope that they will help those like me who are curious and willing to go out of their way to annoy the maintainers of open source projects.
Here are thirteen tips on how to ask questions which never can be answered:
## 1. Cherish your words
Compress the number of bytes in the question, so that the other party does not think you are long-winded. Use the simplest words to describe your problem, refine keywords, and simplify the lengthy process and tedious details.
#### 😈 'Good Way'
```
Style compilation error
```
#### 👼 'Bad Way'
```
Import xxx.css into my project, and an error occurred during compilation. The error message is as follows:
Module build failed: SyntaxError: Unexpected token
I import it like this:
import 'xxx.css';
balalalala.....
```
## 2. Slow down
If the maintainer answers you, usually they will ask for further information. Remember not to reply in a hurry, that will make you look like a workaholic (bubble by the computer all the time, waiting pitifully for a reply). You still have other lives, drink a cup of coffee and reply after ten days and a half months. Believe me, they will quickly lose patience and close the question, or get depressed because they can't close it for a while.
#### 😈 'Good Way'
```
You: When using Button, I find that the console reports an error, and the prompt is as follows.
Maintainer (within 2 days): I can't reproduce your example, can you provide a reproducible example?
Maintainer (3 days later): @you
Maintainer (one week later): ping~
You (two weeks later): Whoops sorry for the late reply, here is my code.
```
#### 👼 'Bad Way'
```
You: When using Button, I find that the console reports an error, and the prompt is as follows.
Maintainer (within 2 days): I can't reproduce your example, can you provide a reproducible example?
You (in 2 days): Maybe my situation is a bit different, here is the reproduce code.
```
## 3. A big package
Introducing open source modules in a medium or large project is prone to strange problems. There are dozens of files and hundreds of business modules, and the project schedule is tight. It is too hard to check one by one. It is better to hire someone else, and quickly pack a package and send it to the other party.
#### 😈 'Good Way'
```
I have a problem with the front-end component of my database project, here is my code, can anyone help me?
Attachment: db-service-app.rar (434MB)
```
#### 👼 'Bad Way'
```
There is a front-end component problem in my project, I simplified the code,
It is found that the xxx component and the yyy component are used at the same time. Here is a simple reproduction example.
Attachment: component-xxx-yyy-bug.zip (10KB)
```
## 4. To be continued
Always hold back, don't finish the sentence at once, make your question full of mystery, and fully mobilize the reader's curiosity.
#### 😈 'Good Way'
```
You: My code is wrong and I don't know what to do?
You: I have a problem here, can someone help me?
are u there?
```
#### 👼 'Bad Way'
```
You: I used the latest version of xxx just released, and the following error occurred in the console...
I call it like this...
My code repository is here...
```
## 5. Mess with formatting
Never, never format code. You are not an artist, and beautifying the format is not your specialty. Your energy should be used in project development, and you don't have time to learn formatting syntax. As for whether the other party can understand, you don't need to care.
#### 😈 'Good Way'
```tsx
renderBatchButton() {
return(
<Dropdown overlay={this. renderExportMenu("2")}>
export warehouse order
);
}
renderExportMenu(category) {
let exportFile=({key})=>{
console. log(key)
}
let items=[];
if(this.props.global.template_list){
items=this.props.global.template_list.map((item)=>{
if(category===item.category){
return <Menu.Item key={item.id}>{item.name}</Menu.Item>;
}
});
}
```
#### 👼 'Bad Way'
```tsx
import { Menu } from 'antd';
import React from 'react';
const Demo: React.FC = () => {
const [collapsed, setCollapsed] = useState<boolean>(false);
const toggle = () => setCollapsed(!collapsed);
return <Menu>...</Menu>;
};
export default Demo;
```
## 6. Missing key information
The project code always runs well at the beginning, but when you do a certain operation, or change some code, or in a special environment, a problem occurs. This difference is often the key point of the problem, just keep it in your mind and don't say it easily.
#### 😈 'Good Way'
```
You: My code is wrong.
Maintainer: I have tried various methods but have not reproduced it, please provide the reproduction?
You (much later): Oh! I have this problem in chrome 35.
```
#### 👼 'Bad Way'
```
You: My code is wrong in chrome 35.
Maintainer: Ok, I reproduced it too, I'll see how to fix it.
```
## 7. Providing wrong information
Sometimes you need to do some misleading, intentionally or unintentionally, in short, making difficulties is your strong point
#### 😈 'Good Way'
```
You: My code is wrong.
Maintainer: What version are you using?
You: 0.8.4 (actually 0.8.3 locally)
Maintainer: Are you sure, 0.8.4 should have fixed this issue. I'll take another look...
```
#### 👼 'Bad Way'
```
You: My code is broken in version 0.8.3.
Maintainer: 0.8.4 should have fixed this problem, and upgrading to the new version will solve it.
```
## 8. Feel free to vent your emotions
Open source projects cause bugs in your project, cause you to work overtime on Saturday night, make your missing the party and someone must be responsible. Your work and life are ruined by them, and don't make it easier for them.
#### 😈 'Good Way'
```
This project sucks, it is full of pitfalls to use, and the documentation is too simple. It is really open source to do so.
```
#### 👼 'Bad Way'
```
This project has many details and the documentation is not perfect. Is there any improvement plan?
I have collected the following specific questions and hope to continue to improve them.
```
## 9. Think big
Try asking a question with an ambitious goal, and only those grandmotherly maintainers will try to answer you (which is unlikely to happen). And because you showed unpreparedness and extreme ignorance in all technical details, the other party's answer can't satisfy you.
#### 😈 'Good Way'
```
How to package and release?
```
#### 👼 'Bad Way'
```
I want to develop a front-end single-page project, the back-end is php, and the architecture is completely separated from the front-end.
I have a problem when I try to use xxx to build a package... (50 words omitted) What should I do at this point?
```
## 10. Freedom of expression
The maintainers of many open source projects are arrogant, pedantic, freaks who like to set all kinds of rules. For example, they often provide weird question templates and ask you to fill in the blanks in a long and smelly form. Once you don't do what they say, they will see you as a troublemaker and judge you. How can you stand these constraints, write whatever you want, let them and their templates go to hell!
#### 😈 'Good Way'
```
Call `xxx.close` not trigger popup close, please solve it
```
#### 👼 'Bad Way'
```
The popup of the xxx component is not closed
- Version used: 1.0.0
- Browser: Chrome 56.0987
- OS: Windows 10
## what have you done?
I introduced the component xxx, the code is as follows, I clicked on the component to open the popup, and did the following operations.
## What are you expecting?
Overlays should be turned off.
## What is the actual situation?
The popup closes briefly and then pops up again.
[GIF screenshot]
## Reproducible online demo
https://codesandbox.io/xxx
```
## 11. DDOS the maintainer
Repeat the questions you asked in different places to deepen the other party's impression and subvert the other party's imagination!
#### 😈 'Good Way'
```
Question 1: An error is reported when sending a request: `405 Method not allowed`.
Question 2: Hello, I have the problem of `405 Method not allowed` here.
Question 3: Request 405 error, what should I do?
Question n:...
```
#### 👼 'Bad Way'
```
Problem 1: An error is reported when sending a request: `405 Method not allowed`
You: +1 I had this problem too.
```
## 12. Surprise
Even if you know that there is an official channel, it is recommended to ask the maintainer in other ways: Twitter, Facebook, private Email, personal blog, their friends and so on. go to all the places you can find him to ask questions.
#### 😈 'Good Way'
```
Private message of unfollowed people: Hello, our project uses your framework, I would like to ask, can the xxx component get the focus? for keyboard switching
```
#### 👼 'Bad Way'
```
Official channel: Hello, our project uses your framework. I would like to ask, can the xxx component get the focus? for keyboard switching
```
## 13. High level strike
Raise your question to a higher level, take the moral high ground and make accusations, making they unable to argue.
#### 😈 'Good Way'
```
It turns out that the teams of big companies are like this, dont they test well? Its a shame to take this thing out, its just a KPI product, and I dont care about it after the promotion.
```
#### 👼 'Bad Way'
```
Although this project is a product of a large company, it has disadvantages compared with competing products in the following aspects, and I personally do not recommend using it.
```
## Summarize
All in all, maintainers of open source projects always want to see problems happen when they try to answer and solve problems, **don't let them succeed**. Also, most of them have OCD about unclosed questions, try to create as many of them as possible.

View File

@ -0,0 +1,361 @@
---
title: 转载-如何提交无法解答的问题
date: 2023-02-22
author: afc163
---
初次到社区提交 issue 时会遇到一些常见的错误,而使得维护者很难帮助解决问题。转载一篇旧文章,希望在博君一笑的同时也能帮助到大家 :)
> 原文链接:[如何向开源项目提交无法解答的问题](https://zhuanlan.zhihu.com/p/25795393)
---
作为一名互联网开发者,本人使用和参与过许多开源项目。开源社区里,提问和回答是最有趣的组成部分,有些你来我往,有些则石沉大海。人们提问的方式有许多迷人和实用的共通之处。我把它们提炼出来,希望能帮助到那些像我一样充满了好奇心、且愿意付诸行动去惹恼开源项目维护者的人们。
以下是『如何提出无法解答的问题』的十三个小技巧:
## 1. 惜字如金
『言多必失,不如闷声发大财』
压缩问题的字节数,不要让对方觉得你啰嗦。用最简单的字词描述你的问题,提炼关键字,简化掉冗长的过程和繁琐的细节。
#### 😈 “正确示范”
```
样式编译报错
```
#### 👼 “错误示范”
```
在我的项目里引入了 xxx.css编译时出错了报错信息如下
Module build failed: SyntaxError: Unexpected token
我是这么引用的:
import 'xxx.css';
balalalala .....
```
## 2. 缓兵之计
『和他成为长期笔友。』
如果维护者答复你了,通常他们会索要进一步的信息。记住不要着急回复,那样显得你像个工作狂(时时刻刻泡在电脑边,可怜巴巴的等待回复)。你还有其他生活,喝杯咖啡,聊个微信,隔上十天半个月再回复。相信我,他们很快会失去耐心而关掉这个问题,或者因为一时关不了而心情郁闷。
#### 😈 “正确示范”
```
你:使用 Button 时发现控制台报错,提示如下。
维护者(两天内):我没有重现出你的例子,可以提供一份可重现的示例么?
维护者(三天后):@你
维护者一周后ping~
你(两周后):哎呀抱歉,没有及时回复,我的代码在这里。
```
#### 👼 “错误示范”
```
你:使用 Button 时发现控制台报错,提示如下。
维护者(两天内):我没有重现出你的例子,可以提供一份可重现的示例么?
你(两天内):可能我的情况有些不同,这里是重现代码。
```
## 3. 夹带私货
『我哪有时间排查,这绝对是你的锅。』
在一个中型或者大型项目中引入开源模块容易遇到奇怪的问题。几十个文件上百个业务模块,项目工期又紧张,一一排查太辛苦了,还是另请高明吧,赶紧打个包发给对方。
#### 😈 “正确示范”
```
我的数据库项目出现了一个前端组件问题,这里是我的代码,有人能帮我看看么?
附件db-service-app.rar (434MB)
```
#### 👼 “错误示范”
```
我的项目里出现了一个前端组件问题,我简化了一下代码,
发现是 xxx 组件和 yyy 组件同时使用时出现的,这里有个简单的重现例子。
附件component-xxx-yyy-bug.zip (10KB)
```
## 4. 卖个关子
『欲知后事如何,且听下回分解』
总是留个后手,不要一次性把话说完,让你的问题充满神秘感,充分调动起读者的好奇心。
#### 😈 “正确示范”
```
你:我的代码出错了,不知道该怎么办?
你:我这里有一个问题,有人能帮我解决么?
你:在吗?
```
#### 👼 “错误示范”
```
你:我使用了刚刚发布的 xxx 最新版本,控制台出现如下错误...
我是这么调用的...
我的代码仓库在这里...
```
## 5. 弄乱格式
『怕他轻易看懂我的问题,我必须要做点什么』
从来,永远不要格式化问题。你又不是美工,美化格式不是你的特长。你的精力要用在项目开发中,也没有时间去学习什么格式化语法。至于对方能不能看明白,你才不需要关心。
#### 😈 “正确示范”
```tsx
renderBatchButton() {
return(
<Dropdown overlay={this.renderExportMenu("2")}>
导出出库单
);
}
renderExportMenu(category) {
let exportFile=({key})=>{
console.log(key)
}
let items=[];
if(this.props.global.template_list){
items=this.props.global.template_list.map((item)=>{
if(category===item.category){
return <Menu.Item key={item.id}>{item.name}</Menu.Item>;
}
});
}
```
#### 👼 “错误示范”
```tsx
import { Menu } from 'antd';
import React from 'react';
const Demo: React.FC = () => {
const [collapsed, setCollapsed] = useState<boolean>(false);
const toggle = () => setCollapsed(!collapsed);
return <Menu>...</Menu>;
};
export default Demo;
```
## 6. 遗漏关键信息
『诶?我忘了说我没插电源了么?』
项目代码一开始总是跑的好好的,你做了某个操作、或改动了某些代码、或者在一个特殊的环境下,问题出现了。 这个区别往往是问题的关键,把它留在心里就好,不要轻易说出来。
#### 😈 “正确示范”
```
你:我的代码出错了。
维护者:我尝试了各种方式都没有重现出来,麻烦提供下重现?
你(很久以后):哦!我是在 chrome 35 中出现的这个问题。
```
#### 👼 “错误示范”
```
你:我的代码在 chrome 35 出错了。
维护者:好的,我也重现了,我看看怎么修复。
```
## 7. 提供错误的信息
『在错误的信息上解决问题才能体现你牛逼嘛!哈哈哈』
有时候需要做一些误导,有意或者无意,总之制造困难是你的强项
#### 😈 “正确示范”
```
你:我的代码出错了。
维护者:你使用了什么版本?
0.8.4(实际上本地是 0.8.3
维护者你确定么0.8.4 应该已经修复过这个问题。我再看看...
```
#### 👼 “错误示范”
```
你:我的代码在 0.8.3 版本里出错了。
维护者0.8.4 应该已经修复过这个问题,升级到新版即可解决。
```
## 8. 尽情宣泄情绪
『你们把我项目搞挂了,狗屎!』
开源项目导致了你的项目出现 BUG导致了你周六晚上还要加班导致了男友/女友抱怨你不理他/她,这必须要有人负责。你的工作和生活被他们毁了,也别让他们好过。
#### 😈 “正确示范”
```
这个项目烂透了,用起来全是坑,文档也太简略了,这样做开源真是呵呵了
```
#### 👼 “错误示范”
```
这个项目有很多细节问题,文档也不完善,请问有改进的计划么?
我收集了以下具体问题,希望持续完善。
```
## 9. 构思宏伟蓝图
『我要造一台汽车,该怎么做?』
尝试问一个具有宏大目标的问题,只有那些祖母般慈祥的维护者才会尝试回答你(这简直不可能发生)。而且由于你表现出了在所有技术细节上的毫无准备以及极端无知,对方的回答也没办法让你满意。
#### 😈 “正确示范”
```
请问怎么打包发布?
```
#### 👼 “错误示范”
```
我要开发一个前端单页项目,后端是 php架构是前后端完全分离的方式。
我尝试使用 xxx 进行打包构建时遇到一个问题...(省略五十字)请问这时我应该做什么?
```
## 10. 自由发挥
『八股文的时代早就过去了!』
很多开源项目的维护者都是傲慢、迂腐、喜欢设定各种规矩的怪胎。例如他们常常会提供奇怪的问题模板,让你在一个又臭又长的表单里填空。一旦你不按他们说的来,他们就会视你为捣乱分子,把你批判一番。你哪里受得了这些拘束,想怎么写就怎么写,让他们和他们的模板都见鬼去吧!
#### 😈 “正确示范”
```
调用 `xxx.close` 没有关闭浮层,求解决
```
#### 👼 “错误示范”
```
xxx 组件浮层没有关闭
- 使用版本1.0.0
- 浏览器Chrome 56.0987
- 操作系统Windows 10
## 你做了什么?
我引入了组件 xxx代码如下我点击组件后打开浮层做了如下操作。
## 你期待的是什么?
浮层应该关闭。
## 实际上的情况是?
浮层短暂关闭后又再次弹出。
[GIF截图]
## 可重现的在线演示
https://codesandbox.io/xxx
```
## 11. 重复提问
『重要的事要说三遍』
在不同的地方重复你提过的问题,加深对方的印象,颠覆对方的想象!
#### 😈 “正确示范”
```
问题一:发请求时报错:`405 Method not allowd`。
问题二:您好,我这里出现了 `405 Method not allowd` 的问题。
问题三:请求 405 错误,请问我该怎么办?
问题 n...
```
#### 👼 “错误示范”
```
问题一:发请求时报错:`405 Method not allowd`
你:+1 我也出现了这个问题。
```
## 12. 出其不意
『到全世界提问,到他们想不到的地方提问』
即使你知道有官方渠道也推荐用其他方式向维护者提问微博、Twitter、知乎私信、知乎评论区、Email、微信、个人博客、蚂蚁森林、朋友圈今日头条娱乐版的评论区……到一切你能找到他的地方去提问。
#### 😈 “正确示范”
```
未关注人私信:你好,我们项目用的是你们的框架,我想问下可以让 xxx 组件获取到焦点吗?因为要做键盘切换
```
#### 👼 “错误示范”
```
官方渠道:你好,我们项目用的是你们的框架,我想问下可以让 xxx 组件获取到焦点吗?因为要做键盘切换
```
## 13. 上纲上线
『接连便是难懂的话,什么"KPI""绩效""弃坑"之类,引得众人都哄笑起来』
把你的问题拔高一个层次,站在道德高地进行指责,让他们便百口莫辩。
#### 😈 “正确示范”
```
原来大公司团队也就这样啊,都不好好测试的么?就这玩意还好意思拿出来,就是个 KPI 产物,晋升完就不管了。
```
#### 👼 “错误示范”
```
这个项目虽然是大公司的产品,在以下方面比起竞品还有劣势,个人不建议使用。
```
## 总结
总而言之,开源项目的维护者在尝试解答和解决问题时,总是希望能亲眼看到问题发生,**不要让他们得逞**。另外,他们大多对未关闭的问题有强迫症,尽量多制造一些这样的问题。

View File

@ -279,7 +279,7 @@
"stylelint": "^15.1.0",
"stylelint-config-rational-order": "^0.1.2",
"stylelint-config-standard": "^30.0.0",
"stylelint-prettier": "^2.0.0",
"stylelint-prettier": "^3.0.0",
"sylvanas": "^0.6.1",
"terser": "^5.16.1",
"ts-node": "^10.8.2",

View File

@ -1,18 +1,12 @@
import React from 'react';
import dayjs from 'dayjs';
import MockDate from 'mockdate';
import { render } from '../utils';
import React from 'react';
import ConfigProvider from '../../components/config-provider';
import { render } from '../utils';
interface TestOptions {
mockDate?: boolean;
componentName?: string;
}
function rtlTest(Component: React.ComponentType, { mockDate, componentName }: TestOptions = {}) {
describe(`rtl render`, () => {
it(`component should be rendered correctly in RTL direction`, () => {
const isArray = componentName && ['menu'].includes(componentName);
const rtlTest = (Component: React.ComponentType, mockDate = false) => {
describe('rtl render', () => {
it('component should be rendered correctly in RTL direction', () => {
if (mockDate) {
MockDate.set(dayjs('2000-09-28').valueOf());
}
@ -21,13 +15,13 @@ function rtlTest(Component: React.ComponentType, { mockDate, componentName }: Te
<Component />
</ConfigProvider>,
);
expect(isArray ? container.children : container.firstChild).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
if (mockDate) {
MockDate.reset();
}
});
});
}
};
// eslint-disable-next-line jest/no-export
export default rtlTest;