Skip to content

Commit

Permalink
feat(notify):add containerSelector and className prop support (#2046)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tinsson authored Apr 16, 2024
1 parent 11266c9 commit a29c78d
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 28 deletions.
58 changes: 58 additions & 0 deletions packages/zent/__tests__/notify.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,62 @@ describe('Notify component', () => {
jest.advanceTimersByTime(1800);
expect(document.querySelectorAll('.zent-notify').length).toBe(0);
});

it('custom container selector', () => {
const div = document.createElement('div');
div.className = 'custom-container';
document.body.appendChild(div);

const cb = jest.fn();
Notify.info('test info', 3000, cb, '.custom-container');
jest.advanceTimersByTime(1000);

expect(
document.querySelectorAll('.custom-container > .zent-notify-container')
.length
).toBe(1);

Notify.error('test error', 3000);
jest.advanceTimersByTime(1000);
expect(
document.querySelectorAll('body > .zent-notify-container').length
).toBe(1);

Notify.error('test error', 3000, cb, '.empty-class');
jest.advanceTimersByTime(1000);
expect(
document.querySelectorAll('.empty-class > .zent-notify-container').length
).toBe(0);
});

it('custom container class name', () => {
const cb = jest.fn();
Notify.error('test error', 3000, cb, '', 'test-custom-container');
jest.advanceTimersByTime(1000);

expect(
document.querySelectorAll('.zent-notify-container.test-custom-container')
.length
).toBe(1);
});

it('Global default container selector', () => {
const div = document.createElement('div');
div.className = 'custom-container';
document.body.appendChild(div);

Notify.config({ containerSelector: '.custom-container' });
Notify.error('test error', 2000);
jest.advanceTimersByTime(1000);

expect(
document.querySelectorAll('.custom-container > .zent-notify-container')
.length
).toBe(1);
});

it('react node content', () => {
Notify.error(<div>test error</div>);
expect(document.querySelectorAll('.zent-notify').length).toBe(1);
});
});
4 changes: 4 additions & 0 deletions packages/zent/assets/notify.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ $transition-duration: 160ms;
top: 80px;
left: 50%;
transform: translateX(-50%);

&.zent-notify-container-custom {
position: absolute;
}
}

.zent-notify {
Expand Down
82 changes: 67 additions & 15 deletions packages/zent/src/notify/Notify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import NotifyContent from './NotifyContent';

let index = 0;
let durationDefault = 3500;
let containerSelectorDefault = 'body';
const containerList = {};
const notifyContainerClass = 'zent-notify-container';

Expand Down Expand Up @@ -52,18 +53,44 @@ const closeAllNotify = () => {
};

/**
* 创建承载notify portal的容器
* 添加className,需要检查是否存在
* @param {HTMLElement} node dom节点
* @param {String} className class名称
*/
const createNotifyContainerNode = (): HTMLElement => {
const addClassName = (node: HTMLElement, className: string) => {
if (node.classList && !node.classList.contains(className)) {
node.classList.add(className);
}
};

/**
* 创建承载notify portal的容器,containerSelector可以自定义notify的挂载位置
*/
const createNotifyContainerNode = (
containerSelector,
className
): HTMLElement => {
const rootContainerSelector = containerSelector || containerSelectorDefault;

let notifyContainerNode = document.querySelector<HTMLElement>(
'.zent-notify-container'
`${rootContainerSelector} > .${notifyContainerClass}`
);

const rootContainer =
document.querySelector<HTMLElement>(rootContainerSelector) || document.body;

if (!notifyContainerNode) {
const bodyNode = document.body;
const div = createElement('div');
div.className = notifyContainerClass;
notifyContainerNode = bodyNode.appendChild(div);
notifyContainerNode = rootContainer.appendChild(div);
}

if (className) {
addClassName(notifyContainerNode, className);
}

if (rootContainerSelector !== 'body') {
addClassName(notifyContainerNode, 'zent-notify-container-custom');
}

return notifyContainerNode;
Expand All @@ -75,17 +102,24 @@ const createNotifyContainerNode = (): HTMLElement => {
* @param {[type]} duration 显示时长
* @param {[type]} status notify状态
* @param {Function} callback notify消失时回调
* @param {[String]} containerSelector 自定义父容器挂载节点
* @param {[String]} className 自定义样式类
*/
const show = (
text: ReactNode,
duration?: number,
status?: string,
callback?: () => void
callback?: () => void,
containerSelector?: string,
className?: string
) => {
if (!isBrowser) return null;

const container = createElement('div');
const notifyContainerNode = createNotifyContainerNode();
const notifyContainerNode = createNotifyContainerNode(
containerSelector,
className
);
const props: any = {
text,
status,
Expand Down Expand Up @@ -117,33 +151,48 @@ const show = (
export function success(
text: ReactNode,
duration?: number,
callback?: () => void
callback?: () => void,
containerSelector?: string,
className?: string
) {
return show(text, duration, 'success', callback);
return show(
text,
duration,
'success',
callback,
containerSelector,
className
);
}

export function warn(
text: ReactNode,
duration?: number,
callback?: () => void
callback?: () => void,
containerSelector?: string,
className?: string
) {
return show(text, duration, 'warn', callback);
return show(text, duration, 'warn', callback, containerSelector, className);
}

export function error(
text: ReactNode,
duration?: number,
callback?: () => void
callback?: () => void,
containerSelector?: string,
className?: string
) {
return show(text, duration, 'error', callback);
return show(text, duration, 'error', callback, containerSelector, className);
}

export function info(
text: ReactNode,
duration?: number,
callback?: () => void
callback?: () => void,
containerSelector?: string,
className?: string
) {
return show(text, duration, 'info', callback);
return show(text, duration, 'info', callback, containerSelector, className);
}

export function clear(containerId) {
Expand All @@ -158,4 +207,7 @@ export function config(options) {
if (options.duration) {
durationDefault = options.duration;
}
if (options.containerSelector) {
containerSelectorDefault = options.containerSelector;
}
}
19 changes: 13 additions & 6 deletions packages/zent/src/notify/README_en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ Display a notification at top of the viewport.

### API

- `Notify.info(text: node, duration?: number, callback?: () => ()): number`
- `Notify.success(text: node, duration?: number, callback?: () => ()): number`
- `Notify.warn(text: node, duration?: number, callback?: () => ()): number`
- `Notify.error(text: node, duration?: number, callback?: () => ()): number`
- `Notify.info(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`
- `Notify.success(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`
- `Notify.warn(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`
- `Notify.error(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`

`Notify.info`, `Notify.success`, `Notify.warn` and `Notify.error` return an id, which can be used by `Notify.clear(id)` to close the specific notify instance;

Expand All @@ -26,11 +26,18 @@ Display a notification at top of the viewport.
| text | notify message | node | `''` |
| duration | duration | number | `3500` |
| callback | customize callabck when notify closes | func | |
| containerSelector `v9.12.14` | notify's parent node CSS selector | string | `'body'` |
| className `v9.12.14` | Custom class name | string | |

- `Notify.clear(number?: id): void`

If no `id` is passed to `Notify.clear`, it will close all notify instances that are active.

- `Notify.config(options): void`
- `Notify.config(options: Options): void`

`duration` is the only supported parameter in `options`, it is used to set `Notify` duration globally.
### Options

| Property | Description | Type | Default |
| ----------- | ------------ | ------ | ------ |
| duration | global duration | number | `3500` |
| containerSelector `v9.12.14` | notify's parent node CSS selector | string | `'body'` |
19 changes: 13 additions & 6 deletions packages/zent/src/notify/README_zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ group: 反馈

### API

- `Notify.info(text: node, duration?: number, callback?: () => ()): number`
- `Notify.success(text: node, duration?: number, callback?: () => ()): number`
- `Notify.warn(text: node, duration?: number, callback?: () => ()): number`
- `Notify.error(text: node, duration?: number, callback?: () => ()): number`
- `Notify.info(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`
- `Notify.success(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`
- `Notify.warn(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`
- `Notify.error(text: node, duration?: number, callback?: () => (), containerSelector?: string, className?: string): number`

`Notify.info``Notify.success``Notify.warn``Notify.error` 方法会返回一个 `id`,这个 `id` 可以作为 `Notify.clear(id)` 的入参,用于关闭指定notify。

Expand All @@ -27,11 +27,18 @@ group: 反馈
| text | 通知文案 | node | `''` |
| duration | 持续时间 | number | `3500` |
| callback | 关闭时的回调 | func | |
| containerSelector `v9.12.14` | 提示组件的父节点CSS selector | string | `'body'` |
| className `v9.12.14` | 自定义类名 | string | |

- `Notify.clear(number: id): void`

如果 `Notify.clear` 调用时没有传入 `id` 参数,所有当前未关闭的实例都会被关闭。

- `Notify.config(options): void`
- `Notify.config(options: Options): void`

`options` 当前只支持一个设置:`duration`,可以用来全局设置 `Notify` 的关闭延迟时间。
### Options

| 参数 | 说明 | 类型 | 默认值 |
| ----------- | ------------ | ------ | ------ |
| duration | 全局持续时间 | number | `3500` |
| containerSelector `v9.12.14` | 提示组件的父节点CSS selector | string | `'body'` |
32 changes: 32 additions & 0 deletions packages/zent/src/notify/demos/custom-container-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
order: 7
zh-CN:
title: 自定义父节点CSS selector
name: 自定义父节点

en-US:
title: Custom notify parent container css selector
name: custom notify content container

---

```jsx
import { Notify, Button } from 'zent';

function customContent() {
Notify.success('{i18n.name}', '', () => {}, '#custom-container');
}

const relativeStyle = {
position: 'relative'
}

ReactDOM.render(
<div>
<Button onClick={customContent}>{i18n.name}</Button>
<div id="custom-container" style={relativeStyle}></div>
</div>
, mountNode
);

```
36 changes: 36 additions & 0 deletions packages/zent/src/notify/demos/global-container-selector.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@

---
order: 8
zh-CN:
title: 通过 config 调整全局挂载父节点
name: 调整挂载父节点
reset: 重置挂载父节点
info: 常规提示
en-US:
title: Adjust the global default container selector through Notify.config
name: Set global container selector to custom
reset: Reset global container selector config
info: info

---

```jsx
import { Notify, Button } from 'zent';

const relativeStyle = {
position: 'relative'
}

ReactDOM.render(
<div>
<Button onClick={() => Notify.config({ containerSelector: '#global-custom-container' })}>{i18n.name}</Button>
<Button onClick={() => Notify.config({ containerSelector: 'body' })}>{i18n.reset}</Button>
<div id="global-custom-container" style={relativeStyle}></div>
<br />
<br />
<Button onClick={() => Notify.info('{i18n.info}')}>{i18n.info}</Button>
</div>
, mountNode
);

```
3 changes: 2 additions & 1 deletion packages/zent/src/notify/demos/global-duration.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

---
order: 2
zh-CN:
Expand Down Expand Up @@ -26,7 +27,7 @@ ReactDOM.render(
<Button onClick={() => Notify.config({ duration: 2000 })}>2s</Button>
<Button onClick={() => Notify.config({ duration: 3000 })}>3s</Button>
<br />
<br />
<br />
<Button onClick={() => Notify.info('{i18n.info}')}>{i18n.info}</Button>
<Button onClick={() => Notify.success('{i18n.success}')}>{i18n.success}</Button>
<Button onClick={() => Notify.warn('{i18n.warn}')}>{i18n.warn}</Button>
Expand Down

0 comments on commit a29c78d

Please sign in to comment.