@cypress-design/react-tooltip 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @cypress-design/react-tooltip
2
+
3
+ ## 0.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#30](https://github.com/cypress-io/cypress-design/pull/30) [`7194ec4`](https://github.com/cypress-io/cypress-design/commit/7194ec428b96cdb5b66e4f23f63379000db19e08) Thanks [@elevatebart](https://github.com/elevatebart)! - creation of tooltips component
package/ReadMe.md ADDED
@@ -0,0 +1,28 @@
1
+ # Tooltip
2
+
3
+ ## Summary
4
+
5
+ Use the tooltip component to display a message when the user hovers over an element.
6
+ For accessibility, it also triggers on focus.
7
+
8
+ To allow focus, one has to add `tabIndex="0"` to the Tooltip element.
9
+
10
+ ```jsx
11
+ const comp = () => (
12
+ <Tooltip tabIndex="0">
13
+ <span>Focus me</span>
14
+ </Tooltip>
15
+ )
16
+ ```
17
+
18
+ ## install
19
+
20
+ ```bash
21
+ npm install @cypress-design/react-tooltip
22
+ ```
23
+
24
+ or with yarn
25
+
26
+ ```bash
27
+ yarn add @cypress-design/react-tooltip
28
+ ```
@@ -0,0 +1,43 @@
1
+ import { Placement } from '@floating-ui/react-dom-interactions'
2
+ import * as React from 'react'
3
+ import Tooltip from './Tooltip'
4
+
5
+ export default ({
6
+ color = 'light',
7
+ placement,
8
+ tabIndex,
9
+ }: {
10
+ color?: 'light' | 'dark'
11
+ placement?: Placement
12
+ tabIndex?: number
13
+ } = {}) => (
14
+ <div className="center flex flex-col items-center gap-20">
15
+ <Tooltip
16
+ className="bg-red-200 p-4 w-1/2"
17
+ popper={<div className="bg-jade-200 text-gray-800 p-4">Popover</div>}
18
+ color={color}
19
+ placement={placement}
20
+ tabIndex={tabIndex}
21
+ >
22
+ <div>Hover Me (dynamic: {placement?.toString()})</div>
23
+ </Tooltip>
24
+ {(['top', 'right', 'bottom', 'left', 'top-start'] as const).map(
25
+ (placement) => (
26
+ <Tooltip
27
+ className="bg-indigo-200 p-4 w-1/2"
28
+ placement={placement}
29
+ key={placement}
30
+ color={color}
31
+ tabIndex={tabIndex}
32
+ popper={
33
+ <div className="bg-jade-200 text-gray-800 p-4">
34
+ Popover ({placement})
35
+ </div>
36
+ }
37
+ >
38
+ <div>Hover Me ({placement})</div>
39
+ </Tooltip>
40
+ )
41
+ )}
42
+ </div>
43
+ )
@@ -0,0 +1,43 @@
1
+ import { ArgsTable, Canvas, Meta, Story, Description } from '@storybook/addon-docs'
2
+ import { version } from "./package.json"
3
+ import changelog from "./CHANGELOG.md"
4
+ import TooltipStory from './Tooltip.rootstory';
5
+
6
+ import { Tooltip } from './Tooltip'
7
+
8
+ <h1>Tooltip<span className="text-lg font-normal"> - v{version}</span></h1>
9
+
10
+ <Meta
11
+ title="Tooltip"
12
+ component={ Tooltip }
13
+ argTypes={{
14
+ color: {
15
+ control: { type: 'select' },
16
+ },
17
+ placement: {
18
+ control: { type: 'select' },
19
+ },
20
+ }}
21
+ parameters={{
22
+ design: {
23
+ type: "figma",
24
+ url: 'https://www.figma.com/file/1WJ3GVQyMV5e7xVxPg3yID/Design-System?node-id=893%3A0',
25
+ }
26
+ }}
27
+ />
28
+
29
+ A tooltip is used to provide more information to help a user complete a task.
30
+
31
+ <Canvas withSource="none">
32
+ <Story name="Tooltip">
33
+ {(args) => {
34
+ return <TooltipStory {...args} />
35
+ }}
36
+ </Story>
37
+ </Canvas>
38
+
39
+ <ArgsTable />
40
+
41
+ <Description>
42
+ {changelog.replace(/^# .+/, '## Changelog')}
43
+ </Description>
package/Tooltip.tsx ADDED
@@ -0,0 +1,167 @@
1
+ import clsx from 'clsx'
2
+ import * as React from 'react'
3
+ import {
4
+ Placement,
5
+ offset,
6
+ flip,
7
+ arrow,
8
+ autoUpdate,
9
+ useFloating,
10
+ useInteractions,
11
+ useHover,
12
+ useFocus,
13
+ useRole,
14
+ useDismiss,
15
+ safePolygon,
16
+ FloatingPortal,
17
+ } from '@floating-ui/react-dom-interactions'
18
+
19
+ export interface TooltipProps {
20
+ placement?: Placement
21
+ color?: 'light' | 'dark'
22
+ className?: string
23
+ tabIndex?: number | string
24
+ children?: React.ReactNode
25
+ popper?: React.ReactNode
26
+ }
27
+
28
+ const ROTATE_MAP = {
29
+ bottom: 180,
30
+ left: 270,
31
+ top: 0,
32
+ right: 90,
33
+ }
34
+
35
+ export const Tooltip: React.FC<
36
+ TooltipProps & React.HTMLProps<HTMLDivElement>
37
+ > = ({ placement, color = 'light', className, children, popper, ...rest }) => {
38
+ const arrowRef = React.useRef(null)
39
+ const [open, setOpen] = React.useState(false)
40
+
41
+ const [container] = React.useState(() => {
42
+ // This will be executed only on the initial render
43
+ // https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
44
+ return document.createElement('div')
45
+ })
46
+
47
+ React.useEffect(() => {
48
+ document.body.appendChild(container)
49
+ return () => {
50
+ document.body.removeChild(container)
51
+ }
52
+ }, [])
53
+
54
+ const {
55
+ x,
56
+ y,
57
+ reference,
58
+ floating,
59
+ strategy,
60
+ placement: calculatedPlacement,
61
+ context,
62
+ middlewareData: { arrow: { x: arrowX, y: arrowY } = {} },
63
+ } = useFloating({
64
+ placement,
65
+ open,
66
+ onOpenChange: setOpen,
67
+ middleware: [flip(), offset(0), arrow({ element: arrowRef, padding: 24 })],
68
+ whileElementsMounted: autoUpdate,
69
+ })
70
+
71
+ const { getReferenceProps, getFloatingProps } = useInteractions([
72
+ useHover(context, {
73
+ handleClose: safePolygon(),
74
+ }),
75
+ useFocus(context),
76
+ useRole(context, { role: 'tooltip' }),
77
+ useDismiss(context),
78
+ ])
79
+
80
+ const placementSide = calculatedPlacement.split('-')[0]
81
+ const arrowRotate = ROTATE_MAP[placementSide]
82
+
83
+ let arrowXRule = 'left',
84
+ arrowYRule = 'top'
85
+
86
+ if (arrowX && arrowY) {
87
+ arrowXRule = 'left'
88
+ arrowYRule = 'top'
89
+ } else if (arrowX) {
90
+ arrowXRule = 'left'
91
+ arrowYRule = placementSide === 'top' ? 'bottom' : 'top'
92
+ } else if (arrowY) {
93
+ arrowXRule = placementSide === 'left' ? 'right' : 'left'
94
+ arrowYRule = 'top'
95
+ }
96
+
97
+ return (
98
+ <>
99
+ <div
100
+ {...getReferenceProps()}
101
+ ref={reference}
102
+ className={className}
103
+ {...rest}
104
+ >
105
+ {children}
106
+ </div>
107
+ <FloatingPortal>
108
+ {open && (
109
+ <div
110
+ {...getFloatingProps({
111
+ ref: floating,
112
+ style: {
113
+ position: strategy,
114
+ top: y ?? '',
115
+ left: x ?? '',
116
+ padding: '16px',
117
+ },
118
+ })}
119
+ >
120
+ <svg
121
+ ref={arrowRef}
122
+ viewBox="0 0 48 48"
123
+ width="24"
124
+ height="24"
125
+ className={clsx('absolute', [
126
+ color === 'dark' && 'stroke-gray-800 fill-gray-900',
127
+ color === 'light' && 'stroke-gray-100 fill-white',
128
+ ])}
129
+ style={{
130
+ transform: `rotate(${arrowRotate}deg)`,
131
+ filter:
132
+ placementSide === 'bottom' || color === 'dark'
133
+ ? undefined
134
+ : 'drop-shadow(0 1px 1px rgba(225, 227, 237, .8))',
135
+ [arrowXRule]: `${arrowX ?? -6}px`,
136
+ [arrowYRule]: `${arrowY ?? -6}px`,
137
+ }}
138
+ fill="none"
139
+ >
140
+ <rect x="0" y="0" width="48" height="4" strokeWidth="0" />
141
+ <path
142
+ d="M 0 3 C 12 3 18 18 24 18 C 30 18 36 3 48 3"
143
+ stroke-width="2"
144
+ />
145
+ </svg>
146
+ <div
147
+ className={clsx(
148
+ 'rounded shadow border p-8px text-16px leading-24px min-w-160px text-center',
149
+ [
150
+ color === 'dark' &&
151
+ 'bg-gray-900 shadow-gray-800 border-gray-800',
152
+ color === 'light' &&
153
+ 'bg-white shadow-gray-100 border-gray-100',
154
+ (!x || !y) && 'invisible',
155
+ ]
156
+ )}
157
+ >
158
+ {popper}
159
+ </div>
160
+ </div>
161
+ )}
162
+ </FloatingPortal>
163
+ </>
164
+ )
165
+ }
166
+
167
+ export default Tooltip
@@ -0,0 +1,14 @@
1
+ /// <reference types="cypress" />
2
+
3
+ import * as React from 'react'
4
+ import { mount } from 'cypress/react'
5
+ import TooltipStory from './Tooltip.rootstory'
6
+ import assertions from '../assertions'
7
+
8
+ describe('Tooltip', { viewportHeight: 800, viewportWidth: 800 }, () => {
9
+ function mountStory(options: Parameters<typeof TooltipStory>[0] = {}) {
10
+ mount(<TooltipStory {...options} tabIndex={1} />)
11
+ }
12
+
13
+ assertions(mountStory)
14
+ })
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import { Placement } from '@floating-ui/react-dom-interactions';
3
+ export interface TooltipProps {
4
+ placement?: Placement;
5
+ color?: 'light' | 'dark';
6
+ className?: string;
7
+ tabIndex?: number | string;
8
+ children?: React.ReactNode;
9
+ popper?: React.ReactNode;
10
+ }
11
+ export declare const Tooltip: React.FC<TooltipProps & React.HTMLProps<HTMLDivElement>>;
12
+ export default Tooltip;
13
+ //# sourceMappingURL=Tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Tooltip.d.ts","sourceRoot":"","sources":["../Tooltip.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EACL,SAAS,EAaV,MAAM,qCAAqC,CAAA;AAE5C,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC1B,MAAM,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CACzB;AASD,eAAO,MAAM,OAAO,EAAE,KAAK,CAAC,EAAE,CAC5B,YAAY,GAAG,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAiI/C,CAAA;AAED,eAAe,OAAO,CAAA"}
@@ -0,0 +1,2 @@
1
+ export { default } from './Tooltip';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA"}