diff --git a/.dumi/app.tsx b/.dumi/app.tsx new file mode 100644 index 00000000..32140877 --- /dev/null +++ b/.dumi/app.tsx @@ -0,0 +1,10 @@ +import { Navigate } from 'dumi'; +import * as React from 'react'; + +export function patchClientRoutes({ routes }) { + routes[0].children.unshift({ + id: 'demo-redirect', + path: '/demo', + element: , + }); +} diff --git a/.dumi/tsconfig.json b/.dumi/tsconfig.json new file mode 100644 index 00000000..79711a82 --- /dev/null +++ b/.dumi/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../tsconfig.json", + "include": [ + "**/*" + ] +} \ No newline at end of file diff --git a/.dumirc.ts b/.dumirc.ts new file mode 100644 index 00000000..e6e98c80 --- /dev/null +++ b/.dumirc.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'dumi'; +import path from 'path'; + +const isProdSite = + // 不是预览模式 同时是生产环境 + process.env.PREVIEW !== 'true' && process.env.NODE_ENV === 'production'; + +const name = 'tooltip'; + +export default defineConfig({ + alias: { + 'rc-tooltip$': path.resolve('src'), + 'rc-tooltip/es': path.resolve('src'), + }, + mfsu: false, + favicons: ['https://avatars0.githubusercontent.com/u/9441414?s=200&v=4'], + themeConfig: { + name: 'Tooltip', + logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4', + }, + base: isProdSite ? `/${name}/` : '/', + publicPath: isProdSite ? `/${name}/` : '/', +}); diff --git a/.fatherrc.js b/.fatherrc.js index 912aa0aa..4ddbafd1 100644 --- a/.fatherrc.js +++ b/.fatherrc.js @@ -1,9 +1,5 @@ -export default { - cjs: 'babel', - esm: { type: 'babel', importLibToEs: true }, - preCommit: { - eslint: true, - prettier: true, - }, - runtimeHelpers: true, -}; +import { defineConfig } from 'father'; + +export default defineConfig({ + plugins: ['@rc-component/father-plugin'], +}); \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..cb16808a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,23 @@ +version: 2 +updates: +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + time: "21:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: "@types/react" + versions: + - 17.0.0 + - 17.0.1 + - 17.0.2 + - 17.0.3 + - dependency-name: "@types/react-dom" + versions: + - 17.0.0 + - 17.0.1 + - 17.0.2 + - dependency-name: less + versions: + - 4.1.0 diff --git a/.github/workflows/react-component-ci.yml b/.github/workflows/react-component-ci.yml index 432a3fb3..467f037f 100644 --- a/.github/workflows/react-component-ci.yml +++ b/.github/workflows/react-component-ci.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/setup-node@v1 with: - node-version: '12' + node-version: '14' - name: cache package-lock.json uses: actions/cache@v2 @@ -43,7 +43,7 @@ jobs: - name: install if: steps.node_modules_cache_id.outputs.cache-hit != 'true' run: npm ci - + lint: runs-on: ubuntu-latest steps: @@ -66,7 +66,7 @@ jobs: run: npm run lint needs: setup - + compile: runs-on: ubuntu-latest steps: @@ -89,7 +89,7 @@ jobs: run: npm run compile needs: setup - + coverage: runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 15f4631d..35fad322 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,8 @@ package-lock.json .umi .umi-production .umi-test -.env.local \ No newline at end of file +.env.local + +# dumi +.dumi/tmp +.dumi/tmp-production \ No newline at end of file diff --git a/.umirc.ts b/.umirc.ts deleted file mode 100644 index f75dbd21..00000000 --- a/.umirc.ts +++ /dev/null @@ -1,19 +0,0 @@ -// more config: https://d.umijs.org/config -import { defineConfig } from 'dumi'; - -export default defineConfig({ - title: 'rc-tooltip', - favicon: - 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4', - logo: - 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4', - outputPath: '.doc', - exportStatic: {}, - styles: [ - ` - .markdown table { - width: auto !important; - } - `, - ] -}); \ No newline at end of file diff --git a/README.md b/README.md index af2d2a96..42c4d808 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,31 @@ React Tooltip -[![NPM version][npm-image]][npm-url] [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Test coverage][coveralls-image]][coveralls-url] [![gemnasium deps][gemnasium-image]][gemnasium-url] [![node version][node-image]][node-url] [![npm download][download-image]][download-url] - -[npm-image]: https://img.shields.io/npm/v/rc-tooltip.svg?style=flat-square -[npm-url]: https://npmjs.org/package/rc-tooltip +[![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] + +[npm-image]: http://img.shields.io/npm/v/rc-tooltip.svg?style=flat-square +[npm-url]: http://npmjs.org/package/rc-tooltip +[travis-image]: https://img.shields.io/travis/react-component/tooltip/master?style=flat-square +[travis-url]: https://travis-ci.com/react-component/tooltip [github-actions-image]: https://github.com/react-component/tooltip/workflows/CI/badge.svg [github-actions-url]: https://github.com/react-component/tooltip/actions -[coveralls-image]: https://img.shields.io/coveralls/react-component/tooltip.svg?style=flat-square -[coveralls-url]: https://coveralls.io/r/react-component/tooltip?branch=master -[gemnasium-image]: https://img.shields.io/gemnasium/react-component/tooltip.svg?style=flat-square -[gemnasium-url]: https://gemnasium.com/react-component/tooltip -[node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square -[node-url]: https://nodejs.org/download/ +[codecov-image]: https://img.shields.io/codecov/c/github/react-component/tooltip/master.svg?style=flat-square +[codecov-url]: https://app.codecov.io/gh/react-component/tooltip +[david-url]: https://david-dm.org/react-component/tooltip +[david-image]: https://david-dm.org/react-component/tooltip/status.svg?style=flat-square +[david-dev-url]: https://david-dm.org/react-component/tooltip?type=dev +[david-dev-image]: https://david-dm.org/react-component/tooltip/dev-status.svg?style=flat-square [download-image]: https://img.shields.io/npm/dm/rc-tooltip.svg?style=flat-square [download-url]: https://npmjs.org/package/rc-tooltip +[bundlephobia-url]: https://bundlephobia.com/package/rc-tooltip +[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-tooltip +[dumi-url]: https://github.com/umijs/dumi +[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square ## Screenshot @@ -24,9 +35,8 @@ React Tooltip ## Browsers support | [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | -| --------- | --------- | --------- | --------- | --------- | -| IE 8 + ✔ | Firefox 31.0+ ✔ | Chrome 31.0+ ✔ | Safari 7.0+ ✔ | Opera 30.0+ ✔ | - +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| IE 8 + ✔ | Firefox 31.0+ ✔ | Chrome 31.0+ ✔ | Safari 7.0+ ✔ | Opera 30.0+ ✔ | ## Install @@ -47,153 +57,41 @@ ReactDOM.render( tooltip}> hover , - container + container, ); ``` ## Examples `npm start` and then go to - + -Online examples: +Online demo: ## API ### Props - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
nametypedefaultdescription
overlayClassNamestringadditional className added to popup overlay
triggerstring | string[]['hover']which actions cause tooltip shown. enum of 'hover','click','focus'
mouseEnterDelaynumber0delay time to show when mouse enter.unit: s.
mouseLeaveDelaynumber0.1delay time to hide when mouse leave.unit: s.
overlayStyleObjectadditional style of overlay node
prefixClsStringrc-tooltipprefix class name
transitionNameString|Objectsame as https://github.com/react-component/animate
onVisibleChangeFunctioncall when visible is changed
afterVisibleChangeFunctioncall after visible is changed
visiblebooleanwhether tooltip is visible
defaultVisiblebooleanwhether tooltip is visible initially
placementStringone of ['left','right','top','bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight']
alignObject: alignConfig of [dom-align](https://github.com/yiminghe/dom-align)value will be merged into placement's config
onPopupAlignfunction(popupDomNode, align)callback when popup node is aligned
overlayReact.Element | () => React.Elementpopup content
overlayInnerStyleObjectset overlay inner style
arrowContentReact.Nodenullarrow content
getTooltipContainerfunctionFunction returning html node which will act as tooltip container. By default the tooltip attaches to the body. If you want to change the container, simply return a new element.
destroyTooltipOnHideboolean | { keepParent: boolean }falsewhether destroy tooltip when tooltip is hidden.In general, destroyTooltipOnHide will only remove itself instead of parent container of it.Parent container will be removed include tooltip when keepParent is true
idStringId which gets attached to the tooltip content. Can be used with aria-describedby to achieve Screenreader-Support.
+| name | type | default | description | +| -------------------- | ----------------------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| trigger | string \| string\[] | 'hover' | which actions cause tooltip shown. enum of 'hover','click','focus' | +| visible | boolean | false | whether tooltip is visible | +| defaultVisible | boolean | false | whether tooltip is visible by default | +| placement | string | 'right' | tooltip placement. enum of 'top','left','right','bottom', 'topLeft', 'topRight', 'bottomLeft', 'bottomRight', 'leftTop', 'leftBottom', 'rightTop', 'rightBottom' | +| motion | object | | Config popup motion. Please ref demo for example | +| onVisibleChange | (visible: boolean) => void; | | Callback when visible change | +| afterVisibleChange | (visible: boolean) => void; | | Callback after visible change | +| overlay | ReactNode \| () => ReactNode | | tooltip overlay content | +| overlayStyle | object | | style of tooltip overlay | +| overlayClassName | string | | className of tooltip overlay | +| prefixCls | string | 'rc-tooltip' | prefix class name of tooltip | +| mouseEnterDelay | number | 0 | delay time (in second) before tooltip shows when mouse enter | +| mouseLeaveDelay | number | 0.1 | delay time (in second) before tooltip hides when mouse leave | +| getTooltipContainer | (triggerNode: HTMLElement) => HTMLElement | () => document.body | get container of tooltip, default to body | +| destroyTooltipOnHide | boolean | false | destroy tooltip when it is hidden | +| align | object | | align config of tooltip. Please ref demo for usage example | +| showArrow | boolean \| object | false | whether to show arrow of tooltip | +| zIndex | number | | config popup tooltip zIndex | ## Important Note @@ -202,6 +100,7 @@ Online examples: ## Accessibility For accessibility purpose you can use the `id` prop to link your tooltip with another element. For example attaching it to an input element: + ```js ``` + If you do it like this, a screenreader would read the content of your tooltip if you focus the input element. **NOTE:** `role="tooltip"` is also added to expose the purpose of the tooltip element to a screenreader. diff --git a/assets/bootstrap_white.less b/assets/bootstrap_white.less index 07b47858..e123dd12 100644 --- a/assets/bootstrap_white.less +++ b/assets/bootstrap_white.less @@ -69,7 +69,8 @@ &-placement-top &-arrow, &-placement-topLeft &-arrow, &-placement-topRight &-arrow{ - bottom: -@tooltip-arrow-width + @tooltip-shadow-width; + // bottom: -@tooltip-arrow-width + @tooltip-shadow-width; + transform: translate(-50%, @tooltip-arrow-width - @tooltip-shadow-width); margin-left: -@tooltip-arrow-width; border-width: @tooltip-arrow-width @tooltip-arrow-width 0; border-top-color: @tooltip-arrow-color; @@ -103,6 +104,7 @@ margin-top: -@tooltip-arrow-width; border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0; border-right-color: @tooltip-arrow-color; + transform: translateX(calc(-100% + @tooltip-shadow-width)); } &-placement-right &-arrow-inner, @@ -134,6 +136,7 @@ margin-top: -@tooltip-arrow-width; border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width; border-left-color: @tooltip-arrow-color; + transform: translateX(calc(100% - @tooltip-shadow-width)); } &-placement-left &-arrow-inner, @@ -161,7 +164,8 @@ &-placement-bottom &-arrow, &-placement-bottomLeft &-arrow, &-placement-bottomRight &-arrow { - top: -@tooltip-arrow-width + @tooltip-shadow-width;; + // top: -@tooltip-arrow-width + @tooltip-shadow-width;; + transform: translate(-50%, -@tooltip-arrow-width + @tooltip-shadow-width); margin-left: -@tooltip-arrow-width; border-width: 0 @tooltip-arrow-width @tooltip-arrow-width; border-bottom-color: @tooltip-arrow-color; diff --git a/docs/demo/arrowContent.md b/docs/demo/arrowContent.md index b1b21f09..e8013bd3 100644 --- a/docs/demo/arrowContent.md +++ b/docs/demo/arrowContent.md @@ -1,3 +1,8 @@ -## arrowContent +--- +title: arrowContent +nav: + title: Demo + path: /demo +--- - \ No newline at end of file + \ No newline at end of file diff --git a/docs/demo/formError.md b/docs/demo/formError.md index 51a3480d..e3bf2e8d 100644 --- a/docs/demo/formError.md +++ b/docs/demo/formError.md @@ -1,3 +1,8 @@ -## formError +--- +title: formError +nav: + title: Demo + path: /demo +--- - \ No newline at end of file + \ No newline at end of file diff --git a/docs/demo/onVisibleChange.md b/docs/demo/onVisibleChange.md index 989b373e..4f30b05a 100644 --- a/docs/demo/onVisibleChange.md +++ b/docs/demo/onVisibleChange.md @@ -1,3 +1,8 @@ -## onVisibleChange +--- +title: onVisibleChange +nav: + title: Demo + path: /demo +--- - \ No newline at end of file + \ No newline at end of file diff --git a/docs/demo/placement.md b/docs/demo/placement.md index e2952931..192ec92e 100644 --- a/docs/demo/placement.md +++ b/docs/demo/placement.md @@ -1,3 +1,8 @@ -## placement +--- +title: placement +nav: + title: Demo + path: /demo +--- - \ No newline at end of file + \ No newline at end of file diff --git a/docs/demo/point.md b/docs/demo/point.md new file mode 100644 index 00000000..073a89bc --- /dev/null +++ b/docs/demo/point.md @@ -0,0 +1,8 @@ +--- +title: Point +nav: + title: Demo + path: /demo +--- + + \ No newline at end of file diff --git a/docs/demo/showArrow.md b/docs/demo/showArrow.md new file mode 100644 index 00000000..7925fe57 --- /dev/null +++ b/docs/demo/showArrow.md @@ -0,0 +1,8 @@ +--- +title: showArrow +nav: + title: Demo + path: /demo +--- + + diff --git a/docs/demo/simple.md b/docs/demo/simple.md index 51d9e8f6..23f3e343 100644 --- a/docs/demo/simple.md +++ b/docs/demo/simple.md @@ -1,3 +1,8 @@ -## simple +--- +title: simple +nav: + title: Demo + path: /demo +--- - \ No newline at end of file + \ No newline at end of file diff --git a/docs/examples/placement.tsx b/docs/examples/placement.tsx index 40c6188e..987141a7 100644 --- a/docs/examples/placement.tsx +++ b/docs/examples/placement.tsx @@ -1,9 +1,10 @@ import React from 'react'; import Tooltip from 'rc-tooltip'; import '../../assets/bootstrap.less'; +import Popup from '../../src/Popup'; const text = Tooltip Text; -const styles = { +const styles: React.CSSProperties = { display: 'table-cell', height: '60px', width: '80px', @@ -18,74 +19,87 @@ const rowStyle = { }; const Test = () => ( -
-
- - - Left - - - - - Top - - - - - Bottom - - - - - Right - - + <> + - + ); export default Test; diff --git a/docs/examples/point.tsx b/docs/examples/point.tsx new file mode 100644 index 00000000..d59ce6a2 --- /dev/null +++ b/docs/examples/point.tsx @@ -0,0 +1,56 @@ +import Tooltip from 'rc-tooltip'; +import React from 'react'; +import '../../assets/bootstrap_white.less'; + +const text = Tooltip Text; + +const Test = () => { + const scrollRef = React.useRef(); + + return ( +
+
+
+ } + > +
+ Hover Me +
+
+
+
+
+ ); +}; + +export default Test; diff --git a/docs/examples/showArrow.tsx b/docs/examples/showArrow.tsx new file mode 100644 index 00000000..882f6bfa --- /dev/null +++ b/docs/examples/showArrow.tsx @@ -0,0 +1,91 @@ +import React from 'react'; +import Tooltip from 'rc-tooltip'; +import '../../assets/bootstrap_white.less'; + +const text = Tooltip Text; +const styles = { + display: 'table-cell', + height: '60px', + width: '80px', + textAlign: 'center', + background: '#f6f6f6', + verticalAlign: 'middle', + border: '5px solid white', +}; + +const rowStyle = { + display: 'table-row', +}; + +const Test = () => ( + +); + +export default Test; diff --git a/docs/index.md b/docs/index.md index 53a98a37..4c79a7b9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,8 @@ --- -title: rc-tooltip +hero: + title: rc-tooltip + description: React Tooltip --- + diff --git a/package.json b/package.json index 0e99715f..454c6d0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-tooltip", - "version": "5.1.1", + "version": "6.1.2", "description": "React Tooltip", "keywords": [ "react", @@ -17,53 +17,52 @@ "url": "git@github.com:react-component/tooltip.git" }, "license": "MIT", + "maintainers": [ + "yiminghe@gmail.com" + ], + "main": "lib/index", + "module": "es/index", "files": [ - "dist", "lib", "es", "assets/*.css", "assets/*.less" ], - "main": "lib/index", - "module": "es/index", "scripts": { - "start": "dumi dev", - "docs:build": "dumi build", - "docs:deploy": "gh-pages -d .doc", "compile": "father build && lessc assets/bootstrap.less assets/bootstrap.css && lessc assets/bootstrap_white.less assets/bootstrap_white.css", - "coverage": "father test --coverage", + "prepare": "dumi setup", + "docs:build": "dumi build", + "docs:deploy": "gh-pages -d dist", "lint": "eslint src/ --ext .tsx,.ts,.jsx,.js", - "now-build": "npm run build", "prepublishOnly": "npm run compile && np --no-cleanup --yolo --no-publish", - "test": "father test" + "postpublish": "npm run docs:build && npm run docs:deploy", + "start": "dumi dev", + "test": "rc-test" }, "dependencies": { "@babel/runtime": "^7.11.2", - "rc-trigger": "^5.0.0" + "@rc-component/trigger": "^1.18.0", + "classnames": "^2.3.1" }, "devDependencies": { - "@types/jest": "^26.0.0", - "@types/react": "^16.9.9", - "@types/react-dom": "^17.0.9", + "@rc-component/father-plugin": "^1.0.0", + "@testing-library/react": "^13.4.0", + "@types/jest": "^29.4.0", + "@types/react": "^18.0.26", + "@types/react-dom": "^18.0.10", "@types/warning": "^3.0.0", "cross-env": "^7.0.0", - "dumi": "^1.1.0", - "enzyme": "^3.10.0", - "enzyme-adapter-react-16": "^1.15.1", - "enzyme-to-json": "^3.4.3", + "dumi": "^2.2.13", "eslint": "^7.1.0", - "father": "^2.23.1", - "father-build": "^1.18.6", + "father": "^4.0.0", "gh-pages": "^3.1.0", - "less": "^3.11.1", + "less": "^4.1.1", "np": "^7.1.0", - "react": "^16.10.2", - "react-dom": "^16.10.2", + "rc-test": "^7.0.9", + "react": "^18.2.0", + "react-dom": "^18.2.0", "typescript": "^4.0.5" }, - "maintainers": [ - "yiminghe@gmail.com" - ], "peerDependencies": { "react": ">=16.9.0", "react-dom": ">=16.9.0" diff --git a/src/Content.tsx b/src/Content.tsx deleted file mode 100644 index b91f1731..00000000 --- a/src/Content.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import * as React from 'react'; - -export interface ContentProps { - prefixCls?: string; - overlay: (() => React.ReactNode) | React.ReactNode; - id: string; - overlayInnerStyle?: React.CSSProperties; -} - -const Content = (props: ContentProps) => { - const { overlay, prefixCls, id, overlayInnerStyle } = props; - - return ( - - ); -}; - -export default Content; diff --git a/src/Popup.tsx b/src/Popup.tsx new file mode 100644 index 00000000..cbaa9cf3 --- /dev/null +++ b/src/Popup.tsx @@ -0,0 +1,23 @@ +import classNames from 'classnames'; +import * as React from 'react'; + +export interface ContentProps { + prefixCls?: string; + children: (() => React.ReactNode) | React.ReactNode; + id?: string; + overlayInnerStyle?: React.CSSProperties; + className?: string; + style?: React.CSSProperties; +} + +export default function Popup(props: ContentProps) { + const { children, prefixCls, id, overlayInnerStyle, className, style } = props; + + return ( +
+ +
+ ); +} diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx index 4948c32d..944f7119 100644 --- a/src/Tooltip.tsx +++ b/src/Tooltip.tsx @@ -1,12 +1,22 @@ +import type { ArrowType, TriggerProps, TriggerRef } from '@rc-component/trigger'; +import Trigger from '@rc-component/trigger'; +import type { ActionType, AlignType, AnimationType } from '@rc-component/trigger/lib/interface'; import * as React from 'react'; -import { useRef, useImperativeHandle, forwardRef } from 'react'; -import Trigger from 'rc-trigger'; -import type { TriggerProps } from 'rc-trigger'; -import type { AlignType, AnimationType, ActionType } from 'rc-trigger/lib/interface'; +import { forwardRef, useImperativeHandle, useRef } from 'react'; import { placements } from './placements'; -import Content from './Content'; +import Popup from './Popup'; -export interface TooltipProps extends Pick { +export interface TooltipProps + extends Pick< + TriggerProps, + | 'onPopupAlign' + | 'builtinPlacements' + | 'fresh' + | 'children' + | 'mouseLeaveDelay' + | 'mouseEnterDelay' + | 'prefixCls' + > { trigger?: ActionType | ActionType[]; defaultVisible?: boolean; visible?: boolean; @@ -22,25 +32,22 @@ export interface TooltipProps extends Pick React.ReactNode) | React.ReactNode; overlayStyle?: React.CSSProperties; overlayClassName?: string; - prefixCls?: string; - mouseEnterDelay?: number; - mouseLeaveDelay?: number; getTooltipContainer?: (node: HTMLElement) => HTMLElement; - destroyTooltipOnHide?: - | boolean - | { - keepParent?: boolean; - }; + destroyTooltipOnHide?: boolean; align?: AlignType; + showArrow?: boolean | ArrowType; arrowContent?: React.ReactNode; id?: string; - children?: React.ReactElement; - popupVisible?: boolean; overlayInnerStyle?: React.CSSProperties; zIndex?: number; } -const Tooltip = (props: TooltipProps, ref) => { +export interface TooltipRef { + nativeElement: HTMLElement; + forceAlign: VoidFunction; +} + +const Tooltip = (props: TooltipProps, ref: React.Ref) => { const { overlayClassName, trigger = ['hover'], @@ -60,42 +67,26 @@ const Tooltip = (props: TooltipProps, ref) => { defaultVisible, getTooltipContainer, overlayInnerStyle, + arrowContent, + overlay, + id, + showArrow = true, ...restProps } = props; - const domRef = useRef(null); - useImperativeHandle(ref, () => domRef.current); + const triggerRef = useRef(null); + useImperativeHandle(ref, () => triggerRef.current); - const extraProps = { ...restProps }; + const extraProps: Partial = { ...restProps }; if ('visible' in props) { extraProps.popupVisible = props.visible; } - const getPopupElement = () => { - const { arrowContent = null, overlay, id } = props; - return [ -
- {arrowContent} -
, - , - ]; - }; - - let destroyTooltip = false; - let autoDestroy = false; - if (typeof destroyTooltipOnHide === 'boolean') { - destroyTooltip = destroyTooltipOnHide; - } else if (destroyTooltipOnHide && typeof destroyTooltipOnHide === 'object') { - const { keepParent } = destroyTooltipOnHide; - destroyTooltip = keepParent === true; - autoDestroy = keepParent === false; - } + const getPopupElement = () => ( + + {overlay} + + ); return ( { action={trigger} builtinPlacements={placements} popupPlacement={placement} - ref={domRef} + ref={triggerRef} popupAlign={align} getPopupContainer={getTooltipContainer} onPopupVisibleChange={onVisibleChange} @@ -114,11 +105,11 @@ const Tooltip = (props: TooltipProps, ref) => { popupAnimation={animation} popupMotion={motion} defaultPopupVisible={defaultVisible} - destroyPopupOnHide={destroyTooltip} - autoDestroy={autoDestroy} + autoDestroy={destroyTooltipOnHide} mouseLeaveDelay={mouseLeaveDelay} popupStyle={overlayStyle} mouseEnterDelay={mouseEnterDelay} + arrow={showArrow} {...extraProps} > {children} diff --git a/src/index.tsx b/src/index.tsx index d8851cb0..6da28f5a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,3 +1,7 @@ +import Popup from './Popup'; import Tooltip from './Tooltip'; +export type { TooltipRef } from './Tooltip'; +export { Popup }; + export default Tooltip; diff --git a/src/placements.tsx b/src/placements.tsx index 789dce36..6371e889 100644 --- a/src/placements.tsx +++ b/src/placements.tsx @@ -1,82 +1,84 @@ -import { BuildInPlacements } from 'rc-trigger'; +import type { BuildInPlacements } from '@rc-component/trigger'; -const autoAdjustOverflow = { - adjustX: 1, +const autoAdjustOverflowTopBottom = { + shiftX: 64, adjustY: 1, }; +const autoAdjustOverflowLeftRight = { adjustX: 1, shiftY: true }; + const targetOffset = [0, 0]; export const placements: BuildInPlacements = { left: { points: ['cr', 'cl'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowLeftRight, offset: [-4, 0], targetOffset, }, right: { points: ['cl', 'cr'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowLeftRight, offset: [4, 0], targetOffset, }, top: { points: ['bc', 'tc'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowTopBottom, offset: [0, -4], targetOffset, }, bottom: { points: ['tc', 'bc'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowTopBottom, offset: [0, 4], targetOffset, }, topLeft: { points: ['bl', 'tl'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowTopBottom, offset: [0, -4], targetOffset, }, leftTop: { points: ['tr', 'tl'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowLeftRight, offset: [-4, 0], targetOffset, }, topRight: { points: ['br', 'tr'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowTopBottom, offset: [0, -4], targetOffset, }, rightTop: { points: ['tl', 'tr'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowLeftRight, offset: [4, 0], targetOffset, }, bottomRight: { points: ['tr', 'br'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowTopBottom, offset: [0, 4], targetOffset, }, rightBottom: { points: ['bl', 'br'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowLeftRight, offset: [4, 0], targetOffset, }, bottomLeft: { points: ['tl', 'bl'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowTopBottom, offset: [0, 4], targetOffset, }, leftBottom: { points: ['br', 'bl'], - overflow: autoAdjustOverflow, + overflow: autoAdjustOverflowLeftRight, offset: [-4, 0], targetOffset, }, diff --git a/tests/__mocks__/@rc-component/trigger.js b/tests/__mocks__/@rc-component/trigger.js new file mode 100644 index 00000000..887c966d --- /dev/null +++ b/tests/__mocks__/@rc-component/trigger.js @@ -0,0 +1,3 @@ +import Trigger from '@rc-component/trigger/lib/mock'; + +export default Trigger; diff --git a/tests/__mocks__/rc-trigger.js b/tests/__mocks__/rc-trigger.js deleted file mode 100644 index 32303075..00000000 --- a/tests/__mocks__/rc-trigger.js +++ /dev/null @@ -1,3 +0,0 @@ -import Trigger from 'rc-trigger/lib/mock'; - -export default Trigger; diff --git a/tests/index.test.js b/tests/index.test.js deleted file mode 100644 index 30e2ebc0..00000000 --- a/tests/index.test.js +++ /dev/null @@ -1,173 +0,0 @@ -import React from 'react'; -import { mount } from 'enzyme'; -import Tooltip from '../src'; - -const verifyContent = (wrapper, content) => { - expect(wrapper.find('.x-content').text()).toBe(content); - expect(wrapper.find('Trigger').instance().getPopupDomNode()).toBeTruthy(); - wrapper.find('.target').simulate('click'); - expect(wrapper.find('.rc-tooltip').hostNodes().hasClass('rc-tooltip-hidden')).toBe(true); -}; - -describe('rc-tooltip', () => { - window.requestAnimationFrame = window.setTimeout; - window.cancelAnimationFrame = window.clearTimeout; - - describe('shows and hides itself on click', () => { - it('using an element overlay', () => { - const wrapper = mount( - Tooltip content} - > -
Click this
-
, - ); - wrapper.find('.target').simulate('click'); - verifyContent(wrapper, 'Tooltip content'); - }); - - it('using a function overlay', () => { - const wrapper = mount( - Tooltip content} - > -
Click this
-
, - ); - wrapper.find('.target').simulate('click'); - verifyContent(wrapper, 'Tooltip content'); - }); - - // https://github.com/ant-design/ant-design/pull/23155 - it('using style inner style', () => { - const wrapper = mount( - Tooltip content} - overlayInnerStyle={{ background: 'red' }} - > -
Click this
-
, - ); - wrapper.find('.target').simulate('click'); - expect(wrapper.find('.rc-tooltip-inner').props().style).toEqual({ background: 'red' }); - }); - - it('access of ref', () => { - const domRef = React.createRef(); - mount( - Tooltip content} - ref={domRef} - > -
Click this
-
, - ); - expect(domRef.current).toBeTruthy(); - }); - }); - describe('destroyTooltipOnHide', () => { - const destroyVerifyContent = (wrapper, content) => { - wrapper.find('.target').simulate('click'); - expect(wrapper.find('.x-content').text()).toBe(content); - expect(wrapper.find('Trigger').instance().getPopupDomNode()).toBeTruthy(); - wrapper.find('.target').simulate('click'); - }; - it('default value', () => { - const wrapper = mount( - Tooltip content} - > -
Click this
-
, - ); - wrapper.find('.target').simulate('click'); - verifyContent(wrapper, 'Tooltip content'); - }); - it('should only remove tooltip when value is true', () => { - const wrapper = mount( - Tooltip content} - > -
Click this
-
, - ); - destroyVerifyContent(wrapper, 'Tooltip content'); - expect(wrapper.html()).toBe('
Click this
'); - }); - it('should only remove tooltip when keepParent is true', () => { - const wrapper = mount( - Tooltip content} - > -
Click this
-
, - ); - destroyVerifyContent(wrapper, 'Tooltip content'); - expect(wrapper.html()).toBe('
Click this
'); - }); - it('should remove tooltip and container when keepParent is false', () => { - const wrapper = mount( - Tooltip content} - > -
Click this
-
, - ); - destroyVerifyContent(wrapper, 'Tooltip content'); - expect(wrapper.html()).toBe('
Click this
'); - }); - }); - - // This is only test for motion pass to internal rc-trigger - // It's safe to remove since meaningless to rc-tooltip if refactor - it('should motion props work', () => { - const wrapper = mount( - - Bamboo - , - ); - - expect(wrapper.find('Trigger').props().popupMotion).toEqual({ motionName: 'bamboo-is-light' }); - }); - - it('zIndex', () => { - jest.useFakeTimers(); - - const wrapper = mount( - -
Light
-
, - ); - wrapper.find('.target').simulate('click'); - - jest.runAllTimers(); - wrapper.update(); - - expect(wrapper.find('div.rc-tooltip').prop('style')).toEqual( - expect.objectContaining({ - zIndex: 903, - }), - ); - - jest.useRealTimers(); - }); -}); diff --git a/tests/index.test.tsx b/tests/index.test.tsx new file mode 100644 index 00000000..d167d0cd --- /dev/null +++ b/tests/index.test.tsx @@ -0,0 +1,254 @@ +import { act, fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import Tooltip, { TooltipRef } from '../src'; + +const verifyContent = (wrapper: HTMLElement, content: string) => { + expect(wrapper.querySelector('.x-content').textContent).toBe(content); + fireEvent.click(wrapper.querySelector('.target')); + expect(wrapper.querySelector('.rc-tooltip').classList.contains('rc-tooltip-hidden')).toBe(true); +}; + +describe('rc-tooltip', () => { + window.requestAnimationFrame = window.setTimeout; + window.cancelAnimationFrame = window.clearTimeout; + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + async function waitFakeTimers() { + for (let i = 0; i < 100; i += 1) { + await act(async () => { + jest.advanceTimersByTime(100); + await Promise.resolve(); + }); + } + } + + describe('shows and hides itself on click', () => { + it('using an element overlay', () => { + const { container } = render( + Tooltip content} + > +
Click this
+
, + ); + + fireEvent.click(container.querySelector('.target')); + + verifyContent(container, 'Tooltip content'); + }); + + it('using a function overlay', () => { + const { container } = render( + Tooltip content} + > +
Click this
+
, + ); + fireEvent.click(container.querySelector('.target')); + verifyContent(container, 'Tooltip content'); + }); + + // https://github.com/ant-design/ant-design/pull/23155 + it('using style inner style', () => { + const { container } = render( + Tooltip content} + overlayInnerStyle={{ background: 'red' }} + > +
Click this
+
, + ); + fireEvent.click(container.querySelector('.target')); + expect( + (container.querySelector('.rc-tooltip-inner') as HTMLElement).style.background, + ).toEqual('red'); + }); + + it('access of ref', () => { + const domRef = React.createRef(); + render( + Tooltip content} + ref={domRef} + > +
Click this
+
, + ); + expect(domRef.current).toBeTruthy(); + }); + }); + + describe('destroyTooltipOnHide', () => { + const destroyVerifyContent = async (wrapper: HTMLElement, content: string) => { + fireEvent.click(wrapper.querySelector('.target')); + await waitFakeTimers(); + + expect(wrapper.querySelector('.x-content').textContent).toBe(content); + + fireEvent.click(wrapper.querySelector('.target')); + await waitFakeTimers(); + }; + it('default value', () => { + const { container } = render( + Tooltip content} + > +
Click this
+
, + ); + fireEvent.click(container.querySelector('.target')); + verifyContent(container, 'Tooltip content'); + }); + + it('should only remove tooltip when value is true', async () => { + const { container } = render( + Tooltip content} + > +
Click this
+
, + ); + await destroyVerifyContent(container, 'Tooltip content'); + expect(document.querySelector('.x-content')).toBeFalsy(); + }); + }); + + it('zIndex', () => { + jest.useFakeTimers(); + + const { container } = render( + +
Light
+
, + ); + fireEvent.click(container.querySelector('.target')); + + jest.runAllTimers(); + + expect((container.querySelector('div.rc-tooltip') as HTMLElement).style.zIndex).toBe('903'); + + jest.useRealTimers(); + }); + + describe('showArrow', () => { + it('should show tooltip arrow default', () => { + const { container } = render( + Tooltip content} + > +
Click this
+
, + ); + fireEvent.click(container.querySelector('.target')); + expect(container.querySelector('.rc-tooltip-arrow')).toBeTruthy(); + }); + it('should show tooltip arrow when showArrow is true', () => { + const { container } = render( + Tooltip content} + showArrow + > +
Click this
+
, + ); + fireEvent.click(container.querySelector('.target')); + expect(container.querySelector('.rc-tooltip-arrow')).toBeTruthy(); + }); + it('should show tooltip arrow when showArrow is object', () => { + const { container } = render( + Tooltip content} + showArrow={{ + className: 'abc', + }} + > +
Click this
+
, + ); + fireEvent.click(container.querySelector('.target')); + expect(container.querySelector('.rc-tooltip-arrow')).toBeTruthy(); + expect(container.querySelector('.rc-tooltip-arrow').classList.contains('abc')).toBeTruthy(); + }); + it('should hide tooltip arrow when showArrow is false', () => { + const { container } = render( + Tooltip content} + showArrow={false} + > +
Click this
+
, + ); + fireEvent.click(container.querySelector('.target')); + expect(container.querySelector('.rc-tooltip').classList).not.toContain( + 'rc-tooltip-show-arrow', + ); + expect(container.querySelector('.rc-tooltip-arrow')).toBeFalsy(); + }); + }); + + it('visible', () => { + const App = () => { + const [open, setOpen] = React.useState(false); + return ( + Tooltip content} visible={open}> +
{ + setOpen(true); + }} + /> + + ); + }; + const { container } = render(); + + expect(container.querySelector('.x-content')).toBeFalsy(); + + fireEvent.click(container.querySelector('.target')); + expect(container.querySelector('.x-content')).toBeTruthy(); + }); + + it('ref support nativeElement', () => { + const nodeRef = React.createRef(); + + const { container } = render( + }> +