diff --git a/.dumi/app.tsx b/.dumi/app.tsx new file mode 100644 index 0000000..3214087 --- /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 0000000..79711a8 --- /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 0000000..e6e98c8 --- /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 912aa0a..4ddbafd 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 0000000..cb16808 --- /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 432a3fb..467f037 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 15f4631..35fad32 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 f75dbd2..0000000 --- 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 af2d2a9..42c4d80 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 07b4785..e123dd1 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 b1b21f0..e8013bd 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 51a3480..e3bf2e8 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 989b373..4f30b05 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 e295293..192ec92 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 0000000..073a89b --- /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 0000000..7925fe5 --- /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 51d9e8f..23f3e34 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 40c6188..987141a 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 0000000..d59ce6a --- /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 0000000..882f6bf --- /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 53a98a3..4c79a7b 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 0e99715..454c6d0 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 b91f173..0000000 --- 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 0000000..cbaa9cf --- /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 4948c32..944f711 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 d8851cb..6da28f5 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 789dce3..6371e88 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 0000000..887c966 --- /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 3230307..0000000 --- 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 30e2ebc..0000000 --- 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 0000000..d167d0c --- /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( + }> +