diff --git a/.dumirc.ts b/.dumirc.ts
index 594242f..6c3f3e2 100644
--- a/.dumirc.ts
+++ b/.dumirc.ts
@@ -1,6 +1,9 @@
import { defineConfig } from 'dumi';
import path from 'path';
+const basePath = process.env.GH_PAGES ? '/notification/' : '/';
+const publicPath = basePath;
+
export default defineConfig({
alias: {
'@rc-component/notification$': path.resolve('src'),
@@ -11,4 +14,7 @@ export default defineConfig({
name: 'Notification',
logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4',
},
+ outputPath: 'docs-dist',
+ base: basePath,
+ publicPath,
});
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..758659a
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: ant-design
+open_collective: ant-design
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 89942c1..757f744 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -1,12 +1,12 @@
-name: "CodeQL"
+name: 'CodeQL'
on:
push:
- branches: [ "master" ]
+ branches: ['master']
pull_request:
- branches: [ "master" ]
+ branches: ['master']
schedule:
- - cron: "4 14 * * 2"
+ - cron: '4 14 * * 2'
jobs:
analyze:
@@ -20,22 +20,24 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ javascript ]
+ language: [javascript]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ with:
+ persist-credentials: false
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e
with:
languages: ${{ matrix.language }}
queries: +security-and-quality
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e
with:
- category: "/language:${{ matrix.language }}"
+ category: '/language:${{ matrix.language }}'
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
deleted file mode 100644
index f860ff1..0000000
--- a/.github/workflows/main.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: ✅ test
-on: [push, pull_request]
-jobs:
- test:
- uses: react-component/rc-test/.github/workflows/test.yml@main
- secrets: inherit
diff --git a/.github/workflows/react-component-ci.yml b/.github/workflows/react-component-ci.yml
new file mode 100644
index 0000000..9503b96
--- /dev/null
+++ b/.github/workflows/react-component-ci.yml
@@ -0,0 +1,8 @@
+name: ✅ test
+on: [push, pull_request]
+permissions:
+ contents: read
+jobs:
+ test:
+ uses: react-component/rc-test/.github/workflows/test-utoo.yml@main
+ secrets: inherit
diff --git a/.github/workflows/react-doctor.yml b/.github/workflows/react-doctor.yml
new file mode 100644
index 0000000..f68281c
--- /dev/null
+++ b/.github/workflows/react-doctor.yml
@@ -0,0 +1,27 @@
+name: React Doctor
+
+on:
+ pull_request:
+ types: [opened, synchronize, reopened, ready_for_review]
+ push:
+ branches: [master]
+
+permissions:
+ contents: read
+ pull-requests: write
+ issues: write
+ statuses: write
+
+concurrency:
+ group: react-doctor-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ react-doctor:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ with:
+ fetch-depth: 0
+ persist-credentials: false
+ - uses: millionco/react-doctor@0b4f4f4bd248a154e64eb508a48347f71154b3f3
diff --git a/.github/workflows/surge-preview.yml b/.github/workflows/surge-preview.yml
new file mode 100644
index 0000000..cb6c8c4
--- /dev/null
+++ b/.github/workflows/surge-preview.yml
@@ -0,0 +1,52 @@
+name: Surge Preview
+
+on:
+ pull_request:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+ pull-requests: write
+ checks: write
+ statuses: write
+
+jobs:
+ preview:
+ runs-on: ubuntu-latest
+ env:
+ PREVIEW: true
+ steps:
+ - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
+ with:
+ persist-credentials: false
+ - name: Check Surge token
+ id: surge-token
+ env:
+ SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }}
+ run: |
+ if [ -n "$SURGE_TOKEN" ]; then
+ echo "enabled=true" >> "$GITHUB_OUTPUT"
+ else
+ echo "enabled=false" >> "$GITHUB_OUTPUT"
+ fi
+ - name: Build preview
+ if: ${{ steps.surge-token.outputs.enabled == 'true' }}
+ run: |
+ npm install
+ npm run build
+ - uses: afc163/surge-preview@bf90a5a86111f6311ca42f0a5a0f80fb0fb03cec
+ if: ${{ steps.surge-token.outputs.enabled == 'true' }}
+ env:
+ SURGE_TOKEN: ${{ secrets.SURGE_TOKEN }}
+ with:
+ surge_token: ${{ env.SURGE_TOKEN }}
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ dist: docs-dist
+ failOnError: false
+ setCommitStatus: false
+ - name: Skip Surge preview
+ if: ${{ steps.surge-token.outputs.enabled != 'true' }}
+ run: echo "SURGE_TOKEN is not configured; skip Surge preview."
diff --git a/.gitignore b/.gitignore
index 552c5b9..a82d792 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,6 +28,7 @@ build
lib
es
coverage
+docs-dist/
yarn.lock
package-lock.json
pnpm-lock.yaml
@@ -43,5 +44,6 @@ pnpm-lock.yaml
.dumi/tmp-production
bun.lockb
+.vercel
.claude/skills/tmp-*
diff --git a/README.md b/README.md
index 8d30871..a9881b9 100644
--- a/README.md
+++ b/README.md
@@ -1,194 +1,215 @@
-# @rc-component/notification
+
+
@rc-component/notification
+
Part of the Ant Design ecosystem.
+
🔔 Hook-based React notification primitives for stacked, animated, and accessible notices.
+
-React Notification UI Component
+English | 简体中文
-[![NPM version][npm-image]][npm-url] [](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Test coverage][coveralls-image]][coveralls-url] [![npm download][download-image]][download-url] [![bundle size][bundlephobia-image]][bundlephobia-url]
-[npm-image]: http://img.shields.io/npm/v/@rc-component/notification.svg?style=flat-square
-[npm-url]: http://npmjs.org/package/@rc-component/notification
-[github-actions-image]: https://github.com/react-component/notification/workflows/CI/badge.svg
-[github-actions-url]: https://github.com/react-component/notification/actions
-[coveralls-image]: https://img.shields.io/coveralls/react-component/notification.svg?style=flat-square
-[coveralls-url]: https://coveralls.io/r/react-component/notification?branch=master
-[download-image]: https://img.shields.io/npm/dm/@rc-component/notification.svg?style=flat-square
-[download-url]: https://npmjs.org/package/@rc-component/notification
-[bundlephobia-url]: https://bundlephobia.com/result?p=@rc-component/notification
-[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/notification
+
+
+[![NPM version][npm-image]][npm-url]
+[![npm download][download-image]][download-url]
+[![build status][github-actions-image]][github-actions-url]
+[![Codecov][codecov-image]][codecov-url]
+[![bundle size][bundlephobia-image]][bundlephobia-url]
+[![dumi][dumi-image]][dumi-url]
+
+
+
+
+## Highlights
+
+- `useNotification` hook returning an API and React holder element.
+- Top, bottom, left, and right placements with max-count limiting.
+- Closable notices, duration timers, progress display, hover pause, and stacked layout.
+- Custom motion, semantic `classNames` / `styles`, progress component override, and provider-level classes.
+- TypeScript definitions for notification config, API, list config, and progress props.
+- Used by Ant Design as the shared notification foundation.
## Install
-[](https://npmjs.org/package/@rc-component/notification)
+```bash
+npm install @rc-component/notification
+```
## Usage
-```js
-import Notification from '@rc-component/notification';
+```tsx | pure
+import { useNotification } from '@rc-component/notification';
+
+export default () => {
+ const [api, holder] = useNotification();
+
+ return (
+ <>
+ {holder}
+ {
+ api.open({
+ key: 'welcome',
+ title: 'Notification',
+ description: 'This notice is rendered by @rc-component/notification.',
+ closable: true,
+ });
+ }}
+ >
+ Open
+
+ >
+ );
+};
+```
+
+```tsx | pure
+import { NotificationProvider, useNotification } from '@rc-component/notification';
-Notification.newInstance({}, (notification) => {
- notification.notice({
- content: 'content',
+const Demo = () => {
+ const [api, holder] = useNotification({
+ placement: 'topRight',
+ maxCount: 3,
+ showProgress: true,
+ pauseOnHover: true,
+ stack: true,
});
-});
-```
-## Compatibility
+ return (
+ <>
+ {holder}
+ api.open({ title: 'Queued' })}>
+ Add
+
+ >
+ );
+};
+
+export default () => (
+
+
+
+);
+```
-| Browser | Supported Version |
-| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
-| [ Firefox](http://godban.github.io/browsers-support-badges/) | last 2 versions |
-| [ Chrome](http://godban.github.io/browsers-support-badges/) | last 2 versions |
-| [ Safari](http://godban.github.io/browsers-support-badges/) | last 2 versions |
-| [ Electron](http://godban.github.io/browsers-support-badges/) | last 2 versions |
+## Examples
-## Example
+Run the local dumi site:
-http://localhost:8001
+```bash
+npm install
+npm start
+```
-online example: https://notification-react-component.vercel.app
+Then open `http://localhost:8000`.
## API
-### Notification.newInstance(props, (notification) => void) => void
-
-props details:
-
-
-
-
- name
- type
- default
- description
-
-
-
-
- prefixCls
- String
-
- prefix class name for notification container
-
-
- style
- Object
- {'top': 65, left: '50%'}
- additional style for notification container.
-
-
- getContainer
- getContainer(): HTMLElement
-
- function returning html node which will act as notification container
-
-
- maxCount
- number
-
- max notices show, drop first notice if exceed limit
-
-
-
-
-### notification.notice(props)
-
-props details:
-
-
-
-
- name
- type
- default
- description
-
-
-
-
- content
- React.Element
-
- content of notice
-
-
- key
- String
-
- id of this notice
-
-
- closable
- Boolean | { closeIcon: ReactNode, onClose: VoidFunction }
-
- whether show close button
-
-
- onClose
- Function
-
- called when notice close
-
-
- duration
- number | false
- 4.5
- The delay for automatic closing in seconds. Set to 0 or false to not close automatically.
-
-
- showProgress
- boolean
- false
- show with progress bar for auto-closing notification
-
-
- pauseOnHover
- boolean
- true
- keep the timer running or not on hover
-
-
- style
- Object
- { right: '50%' }
- additional style for single notice node.
-
-
- closeIcon
- ReactNode
-
- specific the close icon.
-
-
- props
- Object
-
- An object that can contain data-*, aria-*, or role props, to be put on the notification div. This currently only allows data-testid instead of data-* in TypeScript. See https://github.com/microsoft/TypeScript/issues/28960.
-
-
-
-
-### notification.removeNotice(key:string)
-
-remove single notice with specified key
-
-### notification.destroy()
-
-destroy current notification
-
-## Test Case
+### useNotification
+```ts
+const [api, holder] = useNotification(config);
```
-npm test
-npm run chrome-test
+
+| Property | Type | Default | Description |
+| ------------------- | ------------------------------------------------------------ | --------------------- | ----------------------------------------------------------- |
+| className | `(placement: Placement) => string` | - | Class name for each placement container. |
+| classNames | `NotificationClassNames` | - | Semantic class names for notice and list slots. |
+| closable | `boolean \| { closeIcon?: ReactNode; onClose?: () => void }` | - | Shared closable config for opened notices. |
+| components | `{ progress?: ComponentType }` | - | Component overrides. |
+| duration | `number \| false \| null` | `4.5` | Auto-close delay in seconds. Use `false` or `0` to disable. |
+| getContainer | `() => HTMLElement \| ShadowRoot` | `() => document.body` | Notification portal container. |
+| maxCount | `number` | - | Maximum notices to keep. Oldest notices are dropped. |
+| motion | `CSSMotionProps \| (placement) => CSSMotionProps` | - | Motion config for notice transitions. |
+| pauseOnHover | `boolean` | `true` | Pause auto-close timer while hovering. |
+| placement | `Placement` | `topRight` | Default placement for opened notices. |
+| prefixCls | `string` | `rc-notification` | Class name prefix. |
+| renderNotifications | `(node, info) => ReactElement` | - | Customize the rendered notification tree. |
+| showProgress | `boolean` | `false` | Show auto-close progress for opened notices. |
+| stack | `boolean \| StackConfig` | `false` | Enable stacked notification layout. |
+| style | `(placement: Placement) => CSSProperties` | - | Inline style for each placement container. |
+| styles | `NotificationStyles` | - | Semantic styles for notice and list slots. |
+| onAllRemoved | `() => void` | - | Triggered after all notices are removed. |
+
+### NotificationAPI
+
+| Method | Type | Description |
+| --------- | --------------------------------------------------- | ------------------------ |
+| `open` | `(config: Partial) => void` | Open or update a notice. |
+| `close` | `(key: React.Key) => void` | Close a notice by key. |
+| `destroy` | `() => void` | Remove all notices. |
+
+### NotificationListConfig
+
+| Property | Type | Default | Description |
+| ------------ | ------------------------------------------------------------ | ---------- | ------------------------------------------------------------ |
+| actions | `ReactNode` | - | Extra action content. |
+| className | `string` | - | Class name for the notice. |
+| classNames | `NotificationClassNames` | - | Semantic class names for notice slots. |
+| closable | `boolean \| { closeIcon?: ReactNode; onClose?: () => void }` | - | Whether the notice can be closed. |
+| components | `{ progress?: ComponentType }` | - | Component overrides for this notice. |
+| description | `ReactNode` | - | Notice description content. |
+| duration | `number \| false \| null` | `4.5` | Auto-close delay in seconds. |
+| icon | `ReactNode` | - | Notice icon. |
+| key | `React.Key` | - | Unique notice key. Opening with the same key updates it. |
+| offset | `number` | - | Offset used by stacked positioning. |
+| pauseOnHover | `boolean` | `true` | Pause this notice while hovering. |
+| placement | `Placement` | `topRight` | Notice placement. |
+| props | `HTMLAttributes & Record` | - | Extra props passed to the notice root. |
+| role | `string` | - | ARIA role for the notice. |
+| showProgress | `boolean` | `false` | Show auto-close progress. |
+| style | `CSSProperties` | - | Inline style for the notice. |
+| styles | `NotificationStyles` | - | Semantic styles for notice slots. |
+| title | `ReactNode` | - | Notice title content. |
+| onClick | `MouseEventHandler` | - | Triggered when the notice is clicked. |
+| onClose | `() => void` | - | Triggered when the notice closes. Prefer `closable.onClose`. |
+| onMouseEnter | `MouseEventHandler` | - | Triggered when mouse enters the notice. |
+| onMouseLeave | `MouseEventHandler` | - | Triggered when mouse leaves the notice. |
+
+### Types
+
+```ts
+type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight';
+
+interface StackConfig {
+ threshold?: number;
+ offset?: number;
+}
```
-## Coverage
+## Development
-```
+```bash
+npm install
+npm start
+npm test
+npm run tsc
npm run coverage
+npm run compile
+npm run build
```
-open coverage/ dir
+## Release
+
+```bash
+npm run prepublishOnly
+```
+
+The release flow is handled by `@rc-component/np` through the `rc-np` command after the package build.
## License
-@rc-component/notification is released under the MIT license.
+@rc-component/notification is released under the [MIT](./LICENSE.md) license.
+
+[npm-image]: https://img.shields.io/npm/v/@rc-component/notification.svg?style=flat-square
+[npm-url]: https://npmjs.org/package/@rc-component/notification
+[github-actions-image]: https://github.com/react-component/notification/actions/workflows/react-component-ci.yml/badge.svg
+[github-actions-url]: https://github.com/react-component/notification/actions/workflows/react-component-ci.yml
+[codecov-image]: https://img.shields.io/codecov/c/github/react-component/notification/master.svg?style=flat-square
+[codecov-url]: https://app.codecov.io/gh/react-component/notification
+[download-image]: https://img.shields.io/npm/dm/@rc-component/notification.svg?style=flat-square
+[download-url]: https://npmjs.org/package/@rc-component/notification
+[bundlephobia-url]: https://bundlephobia.com/package/@rc-component/notification
+[bundlephobia-image]: https://img.shields.io/bundlephobia/minzip/@rc-component/notification?style=flat-square
+[dumi-url]: https://github.com/umijs/dumi
+[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 0000000..774aba5
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,215 @@
+
+
@rc-component/notification
+
Ant Design 生态的一部分。
+
🔔 React 通知组件,支持堆叠、位置、动画和全局调用。
+
+
+English | 简体中文
+
+
+
+
+[![NPM version][npm-image]][npm-url]
+[![npm download][download-image]][download-url]
+[![build status][github-actions-image]][github-actions-url]
+[![Codecov][codecov-image]][codecov-url]
+[![bundle size][bundlephobia-image]][bundlephobia-url]
+[![dumi][dumi-image]][dumi-url]
+
+
+
+
+## 特性
+
+- `useNotification` 钩子返回 API 和 React 持有者元素。
+- 具有最大数量限制的顶部、底部、左侧和右侧展示位置。
+- 可关闭通知、持续计时器、进度显示、悬停暂停和堆叠布局。
+- 支持动画自定义、语义化 `classNames` / `styles` 、细节组件覆盖和 Provider 级类。
+- 通知配置、API、列表配置和进度属性的 TypeScript 定义。
+- 被 Ant Design 用作共享的 notification 基础能力。
+
+## 安装
+
+```bash
+npm install @rc-component/notification
+```
+
+## 使用
+
+```tsx | pure
+import { useNotification } from '@rc-component/notification';
+
+export default () => {
+ const [api, holder] = useNotification();
+
+ return (
+ <>
+ {holder}
+ {
+ api.open({
+ key: 'welcome',
+ title: 'Notification',
+ description: 'This notice is rendered by @rc-component/notification.',
+ closable: true,
+ });
+ }}
+ >
+ Open
+
+ >
+ );
+};
+```
+
+```tsx | pure
+import { NotificationProvider, useNotification } from '@rc-component/notification';
+
+const Demo = () => {
+ const [api, holder] = useNotification({
+ placement: 'topRight',
+ maxCount: 3,
+ showProgress: true,
+ pauseOnHover: true,
+ stack: true,
+ });
+
+ return (
+ <>
+ {holder}
+ api.open({ title: 'Queued' })}>
+ Add
+
+ >
+ );
+};
+
+export default () => (
+
+
+
+);
+```
+
+## 示例
+
+运行本地 dumi 站点:
+
+```bash
+npm install
+npm start
+```
+
+然后打开 `http://localhost:8000`。
+
+## API
+
+### useNotification
+
+```ts
+const [api, holder] = useNotification(config);
+```
+
+| 参数 | 类型 | 默认值 | 说明 |
+| ------------------- | ------------------------------------------------------------ | --------------------- | ----------------------------------------------------------- |
+| className | `(placement: Placement) => string` | - | 每个放置容器的类名。 |
+| classNames | `NotificationClassNames` | - | 通知和列表槽的语义类名。 |
+| closable | `boolean \| { closeIcon?: ReactNode; onClose?: () => void }` | - | 打开通知的共享可关闭配置。 |
+| components | `{ progress?: ComponentType }` | - | 组件覆盖。 |
+| duration | `number \| false \| null` | `4.5` | 自动关闭延迟以秒为单位。使用 `false` 或 `0` 禁用。 |
+| getContainer | `() => HTMLElement \| ShadowRoot` | `() => document.body` | 通知门户容器。 |
+| maxCount | `number` | - | 保留的最大通知数。最旧的通知被删除。 |
+| motion | `CSSMotionProps \| (placement) => CSSMotionProps` | - | 通知过渡动画配置。 |
+| pauseOnHover | `boolean` | `true` | 悬停时暂停自动关闭计时器。 |
+| placement | `Placement` | `topRight` | 打开通知的默认位置。 |
+| prefixCls | `string` | `rc-notification` | 类名前缀。 |
+| renderNotifications | `(node, info) => ReactElement` | - | 自定义呈现的通知树。 |
+| showProgress | `boolean` | `false` | 显示打开通知的自动关闭进度。 |
+| stack | `boolean \| StackConfig` | `false` | 启用堆叠通知布局。 |
+| style | `(placement: Placement) => CSSProperties` | - | 每个放置容器的内联样式。 |
+| styles | `NotificationStyles` | - | 通知和列表槽的语义样式。 |
+| onAllRemoved | `() => void` | - | 删除所有通知后触发。 |
+
+### NotificationAPI
+
+| Method | 类型 | 说明 |
+| --------- | --------------------------------------------------- | ------------------------ |
+| `open` | `(config: Partial) => void` | 打开或更新通知。 |
+| `close` | `(key: React.Key) => void` | 通过按键关闭通知。 |
+| `destroy` | `() => void` | 删除所有通知。 |
+
+### NotificationListConfig
+
+| 参数 | 类型 | 默认值 | 说明 |
+| ------------ | ------------------------------------------------------------ | ---------- | ------------------------------------------------------------ |
+| actions | `ReactNode` | - | 额外的动作内容。 |
+| className | `string` | - | 通知的类名。 |
+| classNames | `NotificationClassNames` | - | 通知槽的语义类名。 |
+| closable | `boolean \| { closeIcon?: ReactNode; onClose?: () => void }` | - | 是否可以关闭通知。 |
+| components | `{ progress?: ComponentType }` | - | 组件覆盖此通知。 |
+| description | `ReactNode` | - | 注意说明内容。 |
+| duration | `number \| false \| null` | `4.5` | 自动关闭延迟以秒为单位。 |
+| icon | `ReactNode` | - | 通知图标。 |
+| key | `React.Key` | - | 独特的通知键。使用相同的密钥打开会更新它。 |
+| offset | `number` | - | 堆叠定位使用的偏移量。 |
+| pauseOnHover | `boolean` | `true` | 悬停时暂停此通知。 |
+| placement | `Placement` | `topRight` | 通知位置。 |
+| props | `HTMLAttributes & Record` | - | 额外的 props 传递到通知根。 |
+| role | `string` | - | ARIA 的作用为通知。 |
+| showProgress | `boolean` | `false` | 显示自动关闭进度。 |
+| style | `CSSProperties` | - | 通知的内联样式。 |
+| styles | `NotificationStyles` | - | 通知槽的语义样式。 |
+| title | `ReactNode` | - | 注意标题内容。 |
+| onClick | `MouseEventHandler` | - | 单击通知时触发。 |
+| onClose | `() => void` | - | 通知关闭时触发。更喜欢 `closable.onClose`。 |
+| onMouseEnter | `MouseEventHandler` | - | 当鼠标进入通知时触发。 |
+| onMouseLeave | `MouseEventHandler` | - | 当鼠标离开通知时触发。 |
+
+### Types
+
+```ts
+type Placement = 'top' | 'topLeft' | 'topRight' | 'bottom' | 'bottomLeft' | 'bottomRight';
+
+interface StackConfig {
+ threshold?: number;
+ offset?: number;
+}
+```
+
+## 本地开发
+
+```bash
+npm install
+npm start
+npm test
+npm run tsc
+npm run coverage
+npm run compile
+npm run build
+```
+
+## 发布
+
+```bash
+npm run prepublishOnly
+```
+
+包构建完成后,发布流程由 `@rc-component/np` 通过 `rc-np` 命令处理。
+
+## 许可证
+
+@rc-component/notification 基于 [MIT](./LICENSE.md) 许可证发布。
+
+[npm-image]: https://img.shields.io/npm/v/@rc-component/notification.svg?style=flat-square
+[npm-url]: https://npmjs.org/package/@rc-component/notification
+[github-actions-image]: https://github.com/react-component/notification/actions/workflows/react-component-ci.yml/badge.svg
+[github-actions-url]: https://github.com/react-component/notification/actions/workflows/react-component-ci.yml
+[codecov-image]: https://img.shields.io/codecov/c/github/react-component/notification/master.svg?style=flat-square
+[codecov-url]: https://app.codecov.io/gh/react-component/notification
+[download-image]: https://img.shields.io/npm/dm/@rc-component/notification.svg?style=flat-square
+[download-url]: https://npmjs.org/package/@rc-component/notification
+[bundlephobia-url]: https://bundlephobia.com/package/@rc-component/notification
+[bundlephobia-image]: https://img.shields.io/bundlephobia/minzip/@rc-component/notification?style=flat-square
+[dumi-url]: https://github.com/umijs/dumi
+[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square
diff --git a/now.json b/now.json
deleted file mode 100644
index 53f3828..0000000
--- a/now.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "version": 2,
- "name": "rc-notification",
- "builds": [
- {
- "src": "package.json",
- "use": "@now/static-build",
- "config": { "distDir": "dist" }
- }
- ]
-}
diff --git a/package.json b/package.json
index 1f9a974..471dc2c 100644
--- a/package.json
+++ b/package.json
@@ -36,15 +36,18 @@
"typings": "es/index.d.ts",
"scripts": {
"start": "dumi dev",
- "build": "dumi build",
- "docs:deploy": "gh-pages -d .doc",
+ "build": "npm run docs:build",
+ "docs:build": "dumi build",
+ "docs:deploy": "gh-pages -d docs-dist",
"compile": "father build && lessc assets/index.less assets/index.css",
+ "gh-pages": "cross-env GH_PAGES=1 npm run docs:build && npm run docs:deploy",
"prepublishOnly": "npm run compile && rc-np",
"lint": "eslint src/ docs/examples/ --ext .tsx,.ts,.jsx,.js",
+ "prettier": "prettier --write --ignore-unknown .",
"test": "vitest --watch=false",
"test:watch": "vitest",
+ "tsc": "tsc --noEmit",
"coverage": "vitest run --coverage",
- "now-build": "npm run build",
"prepare": "husky install"
},
"dependencies": {
@@ -53,31 +56,32 @@
"clsx": "^2.1.1"
},
"devDependencies": {
- "@rc-component/father-plugin": "^2.0.4",
- "@rc-component/np": "^1.0.3",
- "@testing-library/jest-dom": "^6.0.0",
+ "@rc-component/father-plugin": "^2.2.0",
+ "@rc-component/np": "^1.0.4",
+ "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^15.0.7",
- "@types/node": "^24.5.2",
- "@types/react": "^18.0.0",
- "@types/react-dom": "^18.0.0",
+ "@types/node": "^26.0.1",
+ "@types/react": "^18.3.31",
+ "@types/react-dom": "^18.3.7",
"@types/testing-library__jest-dom": "^6.0.0",
- "@typescript-eslint/eslint-plugin": "^5.59.7",
- "@typescript-eslint/parser": "^5.59.7",
+ "@typescript-eslint/eslint-plugin": "^5.62.0",
+ "@typescript-eslint/parser": "^5.62.0",
"@umijs/fabric": "^4.0.1",
- "@vitest/coverage-v8": "^0.34.2",
- "dumi": "^2.1.0",
+ "@vitest/coverage-v8": "^4.1.9",
+ "cross-env": "^10.1.0",
+ "dumi": "^2.4.35",
"eslint": "^8.57.1",
- "father": "^4.0.0",
- "gh-pages": "^3.1.0",
- "husky": "^8.0.3",
- "jsdom": "^24.0.0",
- "less": "^4.2.0",
- "lint-staged": "^14.0.1",
- "prettier": "^3.0.2",
- "react": "^18.0.0",
- "react-dom": "^18.0.0",
- "typescript": "^5.4.5",
- "vitest": "^0.34.2"
+ "father": "^4.6.23",
+ "gh-pages": "^6.3.0",
+ "husky": "^9.1.7",
+ "jsdom": "^29.1.1",
+ "less": "^4.6.7",
+ "lint-staged": "^16.4.0",
+ "prettier": "^3.9.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "typescript": "^5.9.3",
+ "vitest": "^4.1.9"
},
"peerDependencies": {
"react": ">=18.0.0",
@@ -85,8 +89,7 @@
},
"lint-staged": {
"**/*.{js,jsx,tsx,ts,md,json}": [
- "prettier --write",
- "git add"
+ "prettier --write --ignore-unknown"
]
}
}
diff --git a/tests/index.test.tsx b/tests/index.test.tsx
index 204ea6e..f80f35d 100644
--- a/tests/index.test.tsx
+++ b/tests/index.test.tsx
@@ -41,6 +41,10 @@ describe('Notification.Basic', () => {
}
}
+ function expectContentStyle(selector: string, content: string) {
+ expect((document.querySelector(selector) as HTMLElement).style.content).toBe(`"${content}"`);
+ }
+
it('works', () => {
const { instance, unmount } = renderDemo();
@@ -77,6 +81,21 @@ describe('Notification.Basic', () => {
expect(document.querySelector('.test-icon').textContent).toEqual('test-close-icon');
});
+ it('works with disabled close button', () => {
+ const { instance } = renderDemo();
+
+ act(() => {
+ instance.open({
+ description: 1
,
+ closable: false,
+ duration: 0,
+ });
+ });
+
+ expect(document.querySelector('.test')).toBeTruthy();
+ expect(document.querySelector('.rc-notification-notice-close')).toBeFalsy();
+ });
+
it('works with multi instance', () => {
const { instance } = renderDemo();
@@ -637,7 +656,7 @@ describe('Notification.Basic', () => {
it('should style work', () => {
const { instance } = renderDemo({
style: () => ({
- content: 'little',
+ content: '"little"',
}),
});
@@ -645,9 +664,7 @@ describe('Notification.Basic', () => {
instance.open({});
});
- expect(document.querySelector('.rc-notification')).toHaveStyle({
- content: 'little',
- });
+ expectContentStyle('.rc-notification', 'little');
});
it('should open style and className work', () => {
@@ -656,15 +673,13 @@ describe('Notification.Basic', () => {
act(() => {
instance.open({
style: {
- content: 'little',
+ content: '"little"',
},
className: 'bamboo',
});
});
- expect(document.querySelector('.rc-notification-notice')).toHaveStyle({
- content: 'little',
- });
+ expectContentStyle('.rc-notification-notice', 'little');
expect(document.querySelector('.rc-notification-notice')).toHaveClass('bamboo');
});
@@ -706,7 +721,7 @@ describe('Notification.Basic', () => {
description: 'little',
styles: {
section: {
- content: 'light',
+ content: '"light"',
},
},
classNames: {
@@ -715,9 +730,7 @@ describe('Notification.Basic', () => {
});
});
- expect(document.querySelector('.rc-notification-notice-section')).toHaveStyle({
- content: 'light',
- });
+ expectContentStyle('.rc-notification-notice-section', 'light');
expect(document.querySelector('.rc-notification-notice-section')).toHaveClass('section-class');
});
@@ -729,7 +742,7 @@ describe('Notification.Basic', () => {
icon: ,
styles: {
wrapper: {
- content: 'little',
+ content: '"little"',
},
},
classNames: {
@@ -738,9 +751,7 @@ describe('Notification.Basic', () => {
});
});
- expect(document.querySelector('.rc-notification-notice-wrapper')).toHaveStyle({
- content: 'little',
- });
+ expectContentStyle('.rc-notification-notice-wrapper', 'little');
expect(document.querySelector('.rc-notification-notice-wrapper')).toHaveClass('bamboo');
});
@@ -777,16 +788,16 @@ describe('Notification.Basic', () => {
},
styles: {
title: {
- content: 'global-title',
+ content: '"global-title"',
},
description: {
- content: 'global-description',
+ content: '"global-description"',
},
actions: {
- content: 'global-actions',
+ content: '"global-actions"',
},
icon: {
- content: 'global-icon',
+ content: '"global-icon"',
},
},
});
@@ -824,24 +835,24 @@ describe('Notification.Basic', () => {
'global-title',
'notice-title',
);
+ expectContentStyle('.rc-notification-notice-title', 'global-title');
expect(document.querySelector('.rc-notification-notice-title')).toHaveStyle({
- content: 'global-title',
marginTop: '1px',
});
expect(document.querySelector('.rc-notification-notice-description')).toHaveClass(
'global-description',
'notice-description',
);
+ expectContentStyle('.rc-notification-notice-description', 'global-description');
expect(document.querySelector('.rc-notification-notice-description')).toHaveStyle({
- content: 'global-description',
marginRight: '2px',
});
expect(document.querySelector('.rc-notification-notice-actions')).toHaveClass(
'global-actions',
'notice-actions',
);
+ expectContentStyle('.rc-notification-notice-actions', 'global-actions');
expect(document.querySelector('.rc-notification-notice-actions')).toHaveStyle({
- content: 'global-actions',
marginBottom: '3px',
});
expect(document.querySelector('.actions')).toBeFalsy();
@@ -849,8 +860,8 @@ describe('Notification.Basic', () => {
'global-icon',
'notice-icon',
);
+ expectContentStyle('.rc-notification-notice-icon', 'global-icon');
expect(document.querySelector('.rc-notification-notice-icon')).toHaveStyle({
- content: 'global-icon',
marginLeft: '4px',
});
});
@@ -875,10 +886,10 @@ describe('Notification.Basic', () => {
},
styles: {
list: {
- content: 'root-list',
+ content: '"root-list"',
},
listContent: {
- content: 'little',
+ content: '"little"',
},
},
});
@@ -887,13 +898,9 @@ describe('Notification.Basic', () => {
instance.open({});
});
- expect(document.querySelector('.rc-notification-list')).toHaveStyle({
- content: 'root-list',
- });
+ expectContentStyle('.rc-notification-list', 'root-list');
expect(document.querySelector('.rc-notification-list')).toHaveClass('root-list');
- expect(document.querySelector('.rc-notification-list-content')).toHaveStyle({
- content: 'little',
- });
+ expectContentStyle('.rc-notification-list-content', 'little');
expect(document.querySelector('.rc-notification-list-content')).toHaveClass('bamboo');
});
diff --git a/tsconfig.json b/tsconfig.json
index 19a30b9..5c36c9d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,10 +8,39 @@
"skipLibCheck": true,
"esModuleInterop": true,
"paths": {
- "@/*": ["src/*"],
- "@@/*": [".dumi/tmp/*"],
- "rc-notification": ["src/index.tsx"]
+ "@/*": [
+ "src/*"
+ ],
+ "@@/*": [
+ ".dumi/tmp/*"
+ ],
+ "@rc-component/notification": [
+ "src/index.ts"
+ ],
+ "rc-notification": [
+ "src/index.ts"
+ ]
},
- "types": ["vitest/globals"]
- }
+ "types": [
+ "vitest/globals"
+ ],
+ "ignoreDeprecations": "5.0"
+ },
+ "include": [
+ "src",
+ "docs",
+ "tests",
+ ".dumirc.ts",
+ ".fatherrc.ts",
+ "vitest.config.ts",
+ "vitest-setup.ts"
+ ],
+ "exclude": [
+ "node_modules",
+ "lib",
+ "es",
+ "dist",
+ "docs-dist",
+ ".dumi"
+ ]
}
diff --git a/vercel.json b/vercel.json
new file mode 100644
index 0000000..5f9139e
--- /dev/null
+++ b/vercel.json
@@ -0,0 +1,6 @@
+{
+ "framework": "umijs",
+ "installCommand": "npm install",
+ "buildCommand": "npm run build",
+ "outputDirectory": "docs-dist"
+}