feat : new components app (#39046)

* feat : new components app

* feat: update app

* feat: update app

* feat: update app

* feat: update app

* feat: update app

* feat: update app

* feat: update app

* feat: update style

* feat: update style

* feat: update style

* feat: add style

* chore: code clean

* feat: add prefixCls

* chore: update snapshot

* chore: update snapshot

* chore: update snapshot

* test: image test

* Update components/app/index.zh-CN.md

Co-authored-by: afc163 <afc163@gmail.com>

* feat : update for reviewer

* feat: update

* feat: update

* feat: update snap

Co-authored-by: MadCcc <1075746765@qq.com>
Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
黑雨 2022-12-06 17:45:10 +08:00 committed by GitHub
parent cd8840bf56
commit db4ee53ffa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 453 additions and 7 deletions

View File

@ -5,6 +5,7 @@ exports[`antd exports modules correctly 1`] = `
"Affix",
"Alert",
"Anchor",
"App",
"AutoComplete",
"Avatar",
"BackTop",

View File

@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/app/demo/message.tsx extend context correctly 1`] = `
<div
class="ant-app"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open message
</span>
</button>
</div>
`;
exports[`renders ./components/app/demo/modal.tsx extend context correctly 1`] = `
<div
class="ant-app"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open modal
</span>
</button>
</div>
`;
exports[`renders ./components/app/demo/notification.tsx extend context correctly 1`] = `
<div
class="ant-app"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open notification
</span>
</button>
</div>
`;

View File

@ -0,0 +1,46 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders ./components/app/demo/message.tsx correctly 1`] = `
<div
class="ant-app"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open message
</span>
</button>
</div>
`;
exports[`renders ./components/app/demo/modal.tsx correctly 1`] = `
<div
class="ant-app"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open modal
</span>
</button>
</div>
`;
exports[`renders ./components/app/demo/notification.tsx correctly 1`] = `
<div
class="ant-app"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open notification
</span>
</button>
</div>
`;

View File

@ -0,0 +1,17 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`App rtl render component should be rendered correctly in RTL direction 1`] = `
<div
class="ant-app"
/>
`;
exports[`App single 1`] = `
<div
class="ant-app"
>
<div>
Hello World
</div>
</div>
`;

View File

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

View File

@ -0,0 +1,3 @@
import demoTest from '../../../tests/shared/demoTest';
demoTest('app');

View File

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

View File

@ -0,0 +1,33 @@
import React from 'react';
import App from '..';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { render } from '../../../tests/utils';
describe('App', () => {
mountTest(App);
rtlTest(App);
it('single', () => {
// Sub page
const MyPage = () => {
const { message } = App.useApp();
React.useEffect(() => {
message.success('Good!');
}, [message]);
return <div>Hello World</div>;
};
// Entry component
const MyApp = () => (
<App>
<MyPage />
</App>
);
const { getByText, container } = render(<MyApp />);
expect(getByText('Hello World')).toBeTruthy();
expect(container.firstChild).toMatchSnapshot();
});
});

19
components/app/context.ts Normal file
View File

@ -0,0 +1,19 @@
import React from 'react';
import type { MessageInstance } from '../message/interface';
import type { NotificationInstance } from '../notification/interface';
import type { ModalStaticFunctions } from '../modal/confirm';
type ModalType = Omit<ModalStaticFunctions, 'warn'>;
export interface useAppProps {
message: MessageInstance;
notification: NotificationInstance;
modal: ModalType;
}
const AppContext = React.createContext<useAppProps>({
message: {} as MessageInstance,
notification: {} as NotificationInstance,
modal: {} as ModalType,
});
export default AppContext;

View File

@ -0,0 +1,7 @@
## zh-CN
获取 `message` 静态方法.
## en-US
Static method for `message`.

View File

@ -0,0 +1,24 @@
import React from 'react';
import { App, Button } from 'antd';
// Sub page
const MyPage = () => {
const { message } = App.useApp();
const showMessage = () => {
message.success('Success!');
};
return (
<Button type="primary" onClick={showMessage}>
Open message
</Button>
);
};
// Entry component
export default () => (
<App>
<MyPage />
</App>
);

View File

@ -0,0 +1,7 @@
## zh-CN
获取 `modal` 静态方法.
## en-US
Static method for `modal`.

View File

@ -0,0 +1,27 @@
import React from 'react';
import { App, Button } from 'antd';
// Sub page
const MyPage = () => {
const { modal } = App.useApp();
const showModal = () => {
modal.warning({
title: 'This is a warning message',
content: 'some messages...some messages...',
});
};
return (
<Button type="primary" onClick={showModal}>
Open modal
</Button>
);
};
// Entry component
export default () => (
<App>
<MyPage />
</App>
);

View File

@ -0,0 +1,7 @@
## zh-CN
获取 `notification` 静态方法.
## en-US
Static method for `notification`.

View File

@ -0,0 +1,28 @@
import React from 'react';
import { App, Button } from 'antd';
// Sub page
const MyPage = () => {
const { notification } = App.useApp();
const showNotification = () => {
notification.info({
message: `Notification topLeft`,
description: 'Hello, Ant Design!!',
placement: 'topLeft',
});
};
return (
<Button type="primary" onClick={showNotification}>
Open notification
</Button>
);
};
// Entry component
export default () => (
<App>
<MyPage />
</App>
);

View File

@ -0,0 +1,43 @@
---
category: Components
group: Other
title: App
cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg
demo:
cols: 2
---
New App Component which provide global style & static function replacement.
## When To Use
Static function in React 18 concurrent mode will not well support. In v5, we recommend to use hooks for the static replacement. But it will make user manual work on define this.
## Examples
<!-- prettier-ignore -->
<code src="./demo/message.tsx">message</code>
<code src="./demo/notification.tsx">notification</code>
<code src="./demo/modal.tsx">modal</code>
## How to use
```javascript
import React from 'react';
import { App } from 'antd';
const MyPage = () => {
const { message, notification, modal } = App.useApp();
message.success('Good!');
notification.info({ message: 'Good' });
modal.warning({ title: 'Good' });
// ....
// other message,notification,modal static function
return <div>Hello word</div>;
};
const MyApp = () => (
<App>
<MyPage />
</App>
);
```

60
components/app/index.tsx Normal file
View File

@ -0,0 +1,60 @@
import React, { useContext } from 'react';
import type { ReactNode } from 'react';
import classNames from 'classnames';
import type { ConfigConsumerProps } from '../config-provider';
import { ConfigContext } from '../config-provider';
import useStyle from './style';
import useMessage from '../message/useMessage';
import useNotification from '../notification/useNotification';
import useModal from '../modal/useModal';
import AppContext from './context';
import type { useAppProps } from './context';
export type AppProps = {
className?: string;
prefixCls?: string;
children?: ReactNode;
};
const useApp: () => useAppProps = () => React.useContext(AppContext);
const App: React.ForwardRefRenderFunction<HTMLDivElement, AppProps> & {
useApp: () => useAppProps;
} = (props) => {
const { prefixCls: customizePrefixCls, children, className } = props;
const { getPrefixCls } = useContext<ConfigConsumerProps>(ConfigContext);
const prefixCls = getPrefixCls('app', customizePrefixCls);
const [wrapSSR, hashId] = useStyle(prefixCls);
const customClassName = classNames(hashId, prefixCls, className);
const [messageApi, messageContextHolder] = useMessage();
const [notificationApi, notificationContextHolder] = useNotification();
const [ModalApi, ModalContextHolder] = useModal();
const memoizedContextValue = React.useMemo(
() => ({
message: messageApi,
notification: notificationApi,
modal: ModalApi,
}),
[messageApi, notificationApi, ModalApi],
);
return wrapSSR(
<AppContext.Provider value={memoizedContextValue}>
<div className={customClassName}>
{ModalContextHolder}
{messageContextHolder}
{notificationContextHolder}
{children}
</div>
</AppContext.Provider>,
);
};
if (process.env.NODE_ENV !== 'production') {
App.displayName = 'App';
}
App.useApp = useApp;
export default App;

View File

@ -0,0 +1,45 @@
---
category: Components
subtitle: 包裹组件
group: 其他
title: App
cover: https://gw.alipayobjects.com/zos/bmw-prod/cc3fcbfa-bf5b-4c8c-8a3d-c3f8388c75e8.svg
demo:
cols: 2
---
新的包裹组件,提供重置样式和提供消费上下文的默认环境。
## 何时使用
- 提供可消费 React context 的 `message.xxx`、`Modal.xxx`、`notification.xxx` 的静态方法,可以简化 useMessage 等方法需要手动植入 `contextHolder` 的问题。
- 提供基于 `.ant-app` 的默认重置样式,解决原生元素没有 antd 规范样式的问题。
## 代码演示
<!-- prettier-ignore -->
<code src="./demo/message.tsx">message</code>
<code src="./demo/notification.tsx">notification</code>
<code src="./demo/modal.tsx">modal</code>
## How to use
```javascript
import React from 'react';
import { App } from 'antd';
const MyPage = () => {
const { message, notification, modal } = App.useApp();
message.success('Good!');
notification.info({ message: 'Good' });
modal.warning({ title: 'Good' });
// ....
// other message,notification,modal static function
return <div>Hello word</div>;
};
const MyApp = () => (
<App>
<MyPage />
</App>
);
```

View File

@ -0,0 +1,22 @@
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook } from '../../theme/internal';
export type ComponentToken = {};
interface AppToken extends FullToken<'App'> {}
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<AppToken> = (token) => {
const { componentCls, colorText, fontSize, lineHeight, fontFamily } = token;
return {
[componentCls]: {
color: colorText,
fontSize,
lineHeight,
fontFamily,
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook('App', (token) => [genBaseStyle(token)]);

View File

@ -135,6 +135,7 @@ export { default as Tooltip } from './tooltip';
export type { TooltipProps } from './tooltip';
export { default as Tour } from './tour';
export type { TourProps, TourStepProps } from './tour/interface';
export { default as App } from './app';
export { default as Transfer } from './transfer';
export type { TransferProps } from './transfer';
export { default as Tree } from './tree';

View File

@ -103,15 +103,11 @@ export const genLinkStyle = (token: DerivativeToken): CSSObject => ({
},
});
export const genCommonStyle = (token: DerivativeToken, componentPrefixCls: string): CSSObject => {
const { fontFamily, fontSize } = token;
export const genCommonStyle = (componentPrefixCls: string): CSSObject => {
const rootPrefixSelector = `[class^="${componentPrefixCls}"], [class*=" ${componentPrefixCls}"]`;
return {
[rootPrefixSelector]: {
fontFamily,
fontSize,
boxSizing: 'border-box',
'&::before, &::after': {

View File

@ -47,6 +47,7 @@ import type { ComponentToken as TypographyComponentToken } from '../../typograph
import type { ComponentToken as UploadComponentToken } from '../../upload/style';
import type { ComponentToken as TourComponentToken } from '../../tour/style';
import type { ComponentToken as QRCodeComponentToken } from '../../qrcode/style';
import type { ComponentToken as AppComponentToken } from '../../app/style';
export interface ComponentTokenMap {
Affix?: {};
@ -110,4 +111,5 @@ export interface ComponentTokenMap {
Progress?: ProgressComponentToken;
Tour?: TourComponentToken;
QRCode?: QRCodeComponentToken;
App?: AppComponentToken;
}

View File

@ -87,7 +87,7 @@ export default function genComponentStyleHook<ComponentName extends OverrideComp
overrideComponentToken: token[component],
});
flush(component, mergedComponentToken);
return [genCommonStyle(token, prefixCls), styleInterpolation];
return [genCommonStyle(prefixCls), styleInterpolation];
},
),
hashId,

View File

@ -5,6 +5,7 @@ exports[`antd dist files exports modules correctly 1`] = `
"Affix",
"Alert",
"Anchor",
"App",
"AutoComplete",
"Avatar",
"BackTop",

View File

@ -7,6 +7,7 @@ import glob from 'glob';
import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
import MockDate from 'mockdate';
import ReactDOMServer from 'react-dom/server';
import { App } from '../../components';
const toMatchImageSnapshot = configureToMatchImageSnapshot({
customSnapshotsDir: `${process.cwd()}/imageSnapshots`,
@ -35,7 +36,9 @@ export default function imageTest(component: React.ReactElement) {
const cache = createCache();
const html = ReactDOMServer.renderToString(
<StyleProvider cache={cache}>{component}</StyleProvider>,
<App>
<StyleProvider cache={cache}>{component}</StyleProvider>,
</App>,
);
const styleStr = extractStyle(cache);