@khanacademy/perseus 0.4.0 → 0.4.2

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/dist/strings.js CHANGED
@@ -50,8 +50,6 @@ module.exports = [
50
50
  i18n._("Click on the tiles to change the lights.", {}),
51
51
  i18n._("Click to add points", {}),
52
52
  i18n._("Click to add vertices", {}),
53
- i18n._("Close", {}),
54
- i18n._("Closes the tooltip: ", {}),
55
53
  i18n._("Correct", {}),
56
54
  i18n._("Correct (but you crossed it out)", {}),
57
55
  i18n._("Correct (selected)", {}),
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Core Perseus API (includes renderers and widgets)",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "0.4.0",
6
+ "version": "0.4.2",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -23,19 +23,20 @@
23
23
  "dependencies": {
24
24
  "@khanacademy/kas": "^0.2.5",
25
25
  "@khanacademy/kmath": "^0.0.6",
26
- "@khanacademy/math-input": "^0.5.6",
26
+ "@khanacademy/math-input": "^0.6.1",
27
27
  "@khanacademy/perseus-linter": "^0.2.1",
28
28
  "@khanacademy/pure-markdown": "^0.1.1",
29
29
  "@khanacademy/simple-markdown": "^0.8.4",
30
- "@khanacademy/wonder-blocks-form": "^2.4.5",
31
30
  "mathquill": "git+https://git@github.com/Khan/mathquill.git#a9ae54e057c5c1acc8244a5627acbff29901d992"
32
31
  },
33
32
  "devDependencies": {
34
33
  "@khanacademy/wonder-blocks-button": "^2.11.5",
34
+ "@khanacademy/wonder-blocks-clickable": "^2.2.6",
35
35
  "@khanacademy/wonder-blocks-color": "^1.1.20",
36
36
  "@khanacademy/wonder-blocks-core": "^4.3.0",
37
37
  "@khanacademy/wonder-blocks-data": "^7.0.0",
38
38
  "@khanacademy/wonder-blocks-dropdown": "^2.6.7",
39
+ "@khanacademy/wonder-blocks-form": "^2.4.5",
39
40
  "@khanacademy/wonder-blocks-i18n": "^1.2.3",
40
41
  "@khanacademy/wonder-blocks-layout": "^1.4.9",
41
42
  "@khanacademy/wonder-blocks-link": "^3.8.5",
@@ -62,10 +63,12 @@
62
63
  },
63
64
  "peerDependencies": {
64
65
  "@khanacademy/wonder-blocks-button": "^2.11.5",
66
+ "@khanacademy/wonder-blocks-clickable": "^2.2.6",
65
67
  "@khanacademy/wonder-blocks-color": "^1.1.20",
66
68
  "@khanacademy/wonder-blocks-core": "^4.3.0",
67
69
  "@khanacademy/wonder-blocks-data": "^7.0.0",
68
70
  "@khanacademy/wonder-blocks-dropdown": "^2.6.7",
71
+ "@khanacademy/wonder-blocks-form": "^2.4.5",
69
72
  "@khanacademy/wonder-blocks-i18n": "^1.2.3",
70
73
  "@khanacademy/wonder-blocks-layout": "^1.4.9",
71
74
  "@khanacademy/wonder-blocks-link": "^3.8.5",
@@ -38,22 +38,6 @@ type HighlightTooltipProps = {|
38
38
  class HighlightTooltip extends React.PureComponent<HighlightTooltipProps> {
39
39
  _tooltip: ?React.ElementRef<typeof NewTooltip>;
40
40
 
41
- componentDidUpdate(oldProps: HighlightTooltipProps) {
42
- // If the highlight focus might've changed, then tell the tooltip to
43
- // remeasure itself. For "Add highlight", this happens when an existing
44
- // selection is updated, e.g., by using the Shift+RightArrow shortcut.
45
- // For "Remove highlight", this can happen when we quickly switch from
46
- // targeting one highlight to another: we reuse the same
47
- // HighlightTooltip instance, for improved performance.
48
- if (
49
- this.props.focusNode !== oldProps.focusNode ||
50
- this.props.focusOffset !== oldProps.focusOffset ||
51
- this.props.offsetParent !== oldProps.offsetParent
52
- ) {
53
- this._tooltip?.remeasure();
54
- }
55
- }
56
-
57
41
  _getFocusRect(): ?Rect {
58
42
  const {focusNode, focusOffset, offsetParent} = this.props;
59
43
 
@@ -1,110 +1,3 @@
1
1
  # `NewTooltip`: KA tooltip component
2
2
 
3
- Across the Khan Academy webapp, we use a shared "tooltip" UI element, to visually annotate page elements with additional information. This React component, `NewTooltip`, is designed to satisfy all of our tooltip needs! 😄
4
-
5
- **Warning: This component is WIP!** We're in the process of rolling out this tooltip to eventually replace the old `Tooltip` component. `NewTooltip` is currently is only used at a select few call sites. [Scroll down for details.](#warning-this-component-is-wip)
6
-
7
- ## Usage
8
-
9
- To point a tooltip at an element, wrap it in a `NewTooltip` component:
10
-
11
- ```jsx
12
- <NewTooltip content={i18n._("You should click on this!")}>
13
- <button><$_>Assign to students</$_></button>
14
- </NewTooltip>
15
- ```
16
-
17
- This will produce a tooltip that says "You should click on this!" when you hover over the wrapped button.
18
-
19
- You can also provide more advanced content:
20
-
21
- ```jsx
22
- const content = <div className={css(styles.tooltipContent)}>
23
- <p><$_>You should click on this!</$_></p>
24
- <p><$_>It will help you!</$_></p>
25
- </div>;
26
-
27
- <NewTooltip content={content}>
28
- <button>Assign to students</button>
29
- </NewTooltip>
30
- ```
31
-
32
- There are also many styling, positioning, and behavioral options for the tooltip! See `types.js` for the list of options, and the [React Sandbox][fixtures] to see them in action.
33
-
34
- ## Accessibility
35
-
36
- Unlike some other tooltip components on the web, this tooltip is a _visual_ element rather than a semantic element. Its use cases are relatively diverse:
37
-
38
- * Explaining a UI control
39
- * Illustrating a relationship between two permanent UI elements
40
- * Showing a transient UI element without disrupting the flow of the document
41
- * Prompting the user to take a secondary or blocking action (tooltip) before taking the main action (target)
42
-
43
- The key commonality is that the tooltip and its target are somehow _related_… but that's about it.
44
-
45
- **So, be sure to add your own ARIA attributes to tooltip targets and content!** Only you know the semantic relationship between your tooltip and target, so it's up to you to express it.
46
-
47
- ## Package structure
48
-
49
- The two most important files in this package are:
50
-
51
- * `new-tooltip.jsx`: The entry point to our tooltip code. Manages high-level visibility state. Renders the tooltip's target element inline, and renders a `TooltipPortal` to get the tooltip content outside of any `overflow: hidden` containers.
52
- * `types.js`: Defines the props for `NewTooltip`.
53
-
54
- We also have a number of supporting files, as well:
55
-
56
- * React components
57
- * `tooltip-portal.jsx`: Simulates a [React 16 portal][react-portal]. Creates a node near the root of the document, and renders a `TooltipPositioner` there, instead of inline with the target element.
58
- * `tooltip-positioner.jsx`: Manages the position of the tooltip UI. Computes the position of the target element, and watches for changes. Renders a `TooltipBubble` and `TooltipArrow`, positioned to point at the target element.
59
- * `tooltip-bubble.jsx`: The main tooltip "bubble" element. Renders the tooltip content.
60
- * `tooltip-arrow.jsx`: The little triangle that points from the tooltip bubble to the target element.
61
- * Library files:
62
- * `util.js`: Miscellaneous utility functions.
63
-
64
- Note that these components are relatively tightly coupled—for example, rather than specifying their individual data requirements, each tooltip component just forwards the entire set of `tooltipProps` to its child tooltip components. This enables faster iteration, but means that the child components aren't especially reusable.
65
-
66
- ## Warning: This component is WIP!
67
-
68
- This is `NewTooltip`, a fork of the old `Tooltip`, in which we've built a brand new layout algorithm to address some key UX issues. Someday, `NewTooltip` will replace `Tooltip`, hooray! [See ADR #33 for details.][adr-33]
69
-
70
- `NewTooltip` is probably _feature-ready_ for most use cases on Khan Academy, but **general usage is not yet advised**. We're still developing our plan for the "Wonder Blocks" shared component rollout, and we want to keep call sites well-constrained until we know more. But, if you _need_ the features of `NewTooltip` today, maybe we can figure something out! Hit up the [#reusable-components][reusable-components] Slack channel.
71
-
72
- But, if you need a feature that hasn't been ported yet, maybe you'll want to continue to use `Tooltip`. (Or port the feature into `NewTooltip`? 😍)
73
-
74
- Here's the overall behavioral comparison right now. Especially valuable features are marked with a star (⭐️).
75
-
76
- | Feature | `NewTooltip` | `Tooltip` |
77
- |----------------------------------------------|--------------|-----------|
78
- | ⭐️ **Basic tooltip functionality** | ✅ | ✅ |
79
- | ⭐️ **Escape `overflow: hidden` containers** | ✅ | ✅ |
80
- | ⭐️ **Attach to any side of the target** | ✅ | ✅ |
81
- | ⭐️ **Configurable colors** | ✅ | ✅ |
82
- | ⭐️ **Dismiss on click** | ✅ | ✅ |
83
- | ⭐️ **Only show once per user lifetime** | ✅ | ✅ |
84
- | Tweak position via the `offset` prop | ✅ | ✅ |
85
- | Arrow center-aligned with bubble | ✅ | ✅ |
86
- | ⭐️ **Auto-shift position to avoid wrapping** | ✅ | ❌ |
87
- | ⭐️ **Auto-reposition during scrolling** | ✅ | ❌ |
88
- | ⭐️ **Update content without remounting** | ✅ | ❌ |
89
- | ⭐️ **Media queries to show/hide tooltip** | ✅ | ❌ |
90
- | Can specify offset from the _arrow_ | ✅ | ❌ |
91
- | Can specify margin around tooltip | ✅ | ❌ |
92
- | ⭐️ **Can be used inside a modal** | ✅ | ✅ |
93
- | Can target `position: fixed` elements | ❌ | ✅ |
94
- | Auto-choose top or bottom anchoring | ❌ | ✅ |
95
- | Try to left/right align tooltip with target | ❌ | ✅ |
96
- | Manual positioning (left/top) | ❌ | ✅ |
97
- | Dismiss on scroll/interaction/timer | ❌ | ✅ |
98
- | Show tooltip when target gains focus | ❌ | ✅ |
99
-
100
- > NOTE(mdr): It's possible that some of the old `Tooltip` features are also "especially valuable", and I just don't know about it yet! So far we've only used `NewTooltip` at GTP call sites, and they haven't needed many of these features, so it's not clear to me how broadly they're actually used. (We'll probably have to support most of the missing options eventually, but maybe not all, in case some of their use cases were addressed by the general positioning improvements?)
101
-
102
- Eventually, we'll roll out `NewTooltip` to all KA call sites, and upgrade it to support everything that the old `Tooltip` call sites need. So, no worries if you end up adding new `Tooltip` call sites; we'll help upgrade them soon! [For more details, see ADR #33.][adr-33]
103
-
104
- [adr-33]: https://docs.google.com/document/d/1B4pMGWp344e96RmIbnsmUBiTbrizdbOvipe-9I_1iZQ/edit
105
- [fixtures]: http://localhost:8081/react-sandbox/components/new-tooltip-package/new-tooltip.jsx.fixture.js
106
- [react-portal]: https://reactjs.org/docs/portals.html
107
- [reusable-components]: https://khanacademy.slack.com/messages/C4PE1QM5Y/
108
-
109
- Code-Owner: Frontend Infrastructure
110
- Crowdin-Category: _other_
3
+ This is a wrapper around [Wonderblock's popover component](https://github.com/Khan/wonder-blocks). It is API compatiable with Perseus and remains for compatiability and legacy reasons. If you are anyone but the Perseus group at Khan Academy please use the wonderblocks component directly.
@@ -1,35 +1,15 @@
1
1
  /* eslint-disable react/no-unsafe */
2
2
  // @flow
3
3
  /**
4
- * This component has been obsoleted by the Wonder Blocks tooltip (https://wonder-blocks.netlify.app/#tooltip-1).
5
- * It is no longer a WIP. The Wonder Blocks tooltip should be used in future development.
6
- *
7
- * This top-level component primarily manages high-level visibility state. It
8
- * renders the tooltip's target element inline, and renders a TooltipPortal
9
- * to get the tooltip content outside of any `overflow: hidden` containers.
10
- *
11
- * For usage information and help navigating this package, see `README.md`.
12
- * For prop type information, see `types.js`.
13
- *
14
- * Public methods:
15
- * - `remeasure()`: Re-measure the target element's position, and update the
16
- * tooltip's position. This is helpful to call when the parent is aware of
17
- * a change in the target element's position, which might not be clear from
18
- * the events that the tooltip is watching (e.g., scrolls).
4
+ * This component is a wrapper around Wonderblock's tooltip with the API
5
+ * that perseus expects
19
6
  */
20
- import {StyleSheet, css} from "aphrodite";
21
- import PropTypes from "prop-types";
7
+
8
+ import {Popover, PopoverContentCore} from "@khanacademy/wonder-blocks-popover";
22
9
  import * as React from "react";
23
10
 
24
- import {getDependencies} from "../../dependencies.js";
25
- import {Errors, Log} from "../../logging/log.js";
26
- import {PerseusError} from "../../perseus-error.js";
27
11
  import {colors} from "../../styles/global-styles.js";
28
- import {createVisibilityObserver} from "../visibility-observer/visibility-observer.js";
29
-
30
- import TooltipPortal from "./tooltip-portal.jsx";
31
12
 
32
- import type {VisibilityObserver} from "../visibility-observer/visibility-observer.js";
33
13
  import type {NewTooltipProps} from "./types.js";
34
14
 
35
15
  type NewTooltipState = {|
@@ -37,28 +17,11 @@ type NewTooltipState = {|
37
17
  dismissed: boolean,
38
18
  // Whether the target element is currently being hovered.
39
19
  hovered: boolean,
40
- // Whether the target element is currently visible, relative to the "root
41
- // element" where we're mounting the tooltip.
42
- //
43
- // NOTE(mdr): This can be true (element is visible), false (element is not
44
- // visible), or null (unknown).
45
- targetElementIsVisible: ?boolean,
46
20
  |};
47
21
 
48
22
  type Props = {|
49
23
  ...NewTooltipProps,
50
24
 
51
- // The wrapped content that serves as the target of the tooltip.
52
- //
53
- // This must be a single React element, with block-like styles. (e.g.,
54
- // `display: block`, `display: inline-block`, `display: flex`, etc.)
55
- //
56
- // Unfortunately, we can't reliably track the position of inline elements,
57
- // and we can't wrap your children in a block-like element without
58
- // potentially disrupting your call site's layout.
59
- //
60
- // So, your element will need to be block-like. Sorry for the extra
61
- // constraint! (For details, see the `render` method of `NewTooltip`).
62
25
  children: React$Element<any>,
63
26
  |};
64
27
 
@@ -88,218 +51,27 @@ export const DefaultPropValues: DefaultProps = Object.freeze({
88
51
  showOnMount: false,
89
52
  });
90
53
 
91
- class NewTooltip extends React.PureComponent<Props, NewTooltipState> {
92
- // The root element, where we'll mount tooltips.
93
- _rootElement: HTMLElement;
94
-
95
- // The wrapper element that we created, to listen to mouse events.
96
- _wrapper: ?HTMLElement;
97
-
98
- // The target element itself: the child of the wrapper, provided by the
99
- // caller.
100
- _targetElement: ?HTMLElement;
101
-
102
- // Our child TooltipPortal instance. Used to pass down `remeasure` calls.
103
- _tooltipPortal: ?TooltipPortal;
104
-
105
- // The VisibilityObserver, notifying us when the element enters or exits
106
- // total invisibility. Will be null iff the browser doesn't support the
107
- // necessary APIs.
108
- _visibilityObserver: VisibilityObserver;
109
-
110
- // Whether this component is mounted.
111
- _isMounted: boolean;
112
-
113
- // A timeout used in `componentDidMount`.
114
- _targetElementMountTimeout: ?TimeoutID;
115
-
54
+ class NewTooltip extends React.Component<Props, NewTooltipState> {
116
55
  static defaultProps: DefaultProps = DefaultPropValues;
117
56
 
118
57
  constructor(props: Props) {
119
58
  super(props);
120
59
 
121
- this._isMounted = false;
122
-
123
60
  this.state = {
124
61
  dismissed: props.dismissed != null ? props.dismissed : false,
125
62
  hovered: false,
126
- targetElementIsVisible: false,
127
63
  };
128
64
  }
129
65
 
130
- componentDidMount() {
131
- this._isMounted = true;
132
-
133
- this._registerVisibilityObserver();
134
- }
135
-
136
66
  UNSAFE_componentWillReceiveProps(nextProps: Props) {
137
- if (nextProps.dismissed != null) {
138
- // We're serving as a "controlled component": our dismissed state
139
- // always matches the incoming props.
67
+ if (Object.keys(nextProps).includes("dismissed")) {
140
68
  this.setState({dismissed: nextProps.dismissed});
141
69
  }
142
70
  }
143
71
 
144
- UNSAFE_componentWillUpdate() {
145
- // The component may receive a new root element via the context.
146
- // For example, we may get a modal root element that has become
147
- // available.
148
-
149
- this._registerVisibilityObserver();
150
- }
151
-
152
- componentWillUnmount() {
153
- this._isMounted = false;
154
- if (this._targetElementMountTimeout) {
155
- // TODO(jeff, WEB-1378): Use Wonder Blocks Timing API.
156
- // eslint-disable-next-line no-restricted-syntax
157
- clearTimeout(this._targetElementMountTimeout);
158
- }
159
- this._visibilityObserver.disconnect();
160
- }
161
-
162
- _findRootElement(): any {
163
- // In the Khan Academy webapp, the best place to mount is the
164
- // #outer-wrapper element, because it's our outermost scroll container.
165
- // But, if we're for some reason in a page without an #outer-wrapper,
166
- // then mounting at the body element is probably a good guess.
167
- // Also, if there is a modal anywhere in the context, it should be the
168
- // root because it obscures the visibility of the higher containers.
169
- //
170
- // HACK(davidflanagan):
171
- // Missions use a modal-like ../../tasks-package/task-container.jsx
172
- // component that does not work like our standard modal, so we handle
173
- // that with a special hardcoded case element id. When this component
174
- // is converted to a wonder block, we should upgrade Modal to use
175
- // the new context mechanism instead of the legacy mechanism and
176
- // replace this mission-task-container id with context.
177
- return (
178
- this.context.modalContainerElement ||
179
- document.getElementById("mission-task-container") ||
180
- document.getElementById("outer-wrapper") ||
181
- document.body
182
- );
183
- }
184
-
185
- // When this component mounts inside a modal, we don't have the modal
186
- // container in the context yet. That comes during the next cycle. If the
187
- // tooltip is in the modal, it will be observing with respect to the app
188
- // root for a moment (and won't display because it is obscured) and then
189
- // it will get called again on the next update because the context changes.
190
- // When it sees that the root is different, it will unregister the first
191
- // and register the new one.
192
- _registerVisibilityObserver() {
193
- const rootElement = this._findRootElement();
194
-
195
- if (!rootElement) {
196
- throw new PerseusError(
197
- "Failed to mount NewTooltip: " +
198
- "we need a #outer-wrapper or document.body element.",
199
- Errors.Internal,
200
- );
201
- }
202
-
203
- if (rootElement === this._rootElement) {
204
- return;
205
- }
206
-
207
- this._rootElement = rootElement;
208
-
209
- if (this._visibilityObserver) {
210
- this._visibilityObserver.disconnect();
211
- }
212
-
213
- // Create a VisibilityObserver, and start by assuming the target
214
- // element is invisible. On mount, we'll start observing the
215
- // target, and check whether it's visible.
216
- this._visibilityObserver = createVisibilityObserver(
217
- rootElement,
218
- this._handleVisibilityChange,
219
- );
220
-
221
- // Find our target element. We do this by walking down the DOM tree,
222
- // until we find a node that doesn't have `data-tooltip-wrapper` on it.
223
- //
224
- // (This also enables us to compose tooltips: if you wrap an element
225
- // in multiple tooltips, we'll skip *all* the wrapper elements, and
226
- // find the correct element.)
227
- //
228
- // HACK(mdr): I'm not entirely satisfied with this :/ The whole tooltip
229
- // composition "API" is a bit of a hack to support different
230
- // tooltip props based on media query, and a *robust* way of
231
- // specifying that would probably be preferable... but, in the
232
- // meantime, I'd rather not invent new styling patterns (there's
233
- // already enough fragmentation!), and instead write some
234
- // well-encapsulated hacks that keep the public-facing API simple
235
- // and low on new concepts.
236
- const wrapper = this._wrapper;
237
- if (!wrapper) {
238
- throw new PerseusError(
239
- "NewTooltip expected a wrapper element after mount",
240
- Errors.Internal,
241
- );
242
- }
243
- let targetElement: HTMLElement = wrapper;
244
- while (targetElement.hasAttribute("data-tooltip-wrapper")) {
245
- targetElement = (targetElement.childNodes[0]: any);
246
- if (!targetElement) {
247
- throw new PerseusError(
248
- "NewTooltip expected wrapper element to have a child",
249
- Errors.Internal,
250
- );
251
- }
252
- }
253
- this._targetElement = targetElement;
254
-
255
- // Now that we've mounted, update our TooltipPortal (by re-rendering)
256
- // and our VisibilityObserver, so they can be aware of our new target
257
- // element.
258
- // TODO(jeff, WEB-1378): Use Wonder Blocks Timing API.
259
- // eslint-disable-next-line no-restricted-syntax
260
- this._targetElementMountTimeout = setTimeout(() => {
261
- const targetElement = this._targetElement;
262
- if (!targetElement) {
263
- throw new PerseusError(
264
- "NewTooltip should have a target element after mounting.",
265
- Errors.Internal,
266
- );
267
- }
268
-
269
- // While we're here, let's also check whether the element is
270
- // block-like, as is required for position tracking to work. If
271
- // not, warn the developer - but in prod, skip this, because
272
- // it's slow.
273
- if (getDependencies().isDevServer) {
274
- if (getComputedStyle(targetElement).display === "inline") {
275
- Log.error(
276
- "Warning: The children of a NewTooltip element " +
277
- "must be block-like, but this element seems " +
278
- "to have `display: inline`. This could break " +
279
- "position tracking.",
280
- Errors.Internal,
281
- );
282
- Log.log("NewTooltip Target Element:", {
283
- targetElement: targetElement.outerHTML,
284
- });
285
- }
286
- }
287
-
288
- this._visibilityObserver.setTargetElement(targetElement);
289
-
290
- this.forceUpdate();
291
- }, 0);
292
- }
293
-
294
72
  _shouldShowTooltip(): boolean {
295
73
  const {toggleOnHover, showOnMount} = this.props;
296
- const {dismissed, targetElementIsVisible} = this.state;
297
-
298
- // If the target element isn't visible, then we shouldn't show the
299
- // tooltip, regardless of configuration.
300
- if (targetElementIsVisible === false) {
301
- return false;
302
- }
74
+ const {dismissed} = this.state;
303
75
 
304
76
  // If the tooltip was dismissed, or we're not sure yet, don't show it.
305
77
  if (dismissed) {
@@ -322,78 +94,62 @@ class NewTooltip extends React.PureComponent<Props, NewTooltipState> {
322
94
  return false;
323
95
  }
324
96
 
325
- _handleVisibilityChange: (targetElementIsVisible: boolean) => void = (
326
- targetElementIsVisible: boolean,
327
- ) => {
328
- this.setState({targetElementIsVisible});
329
- };
330
-
331
- _handleMouseEnter: () => void = () => {
332
- this.setState({hovered: true});
333
- };
334
-
335
- _handleMouseLeave: () => void = () => {
336
- this.setState({hovered: false});
337
- };
338
-
339
97
  _handleDismiss: () => void = () => {
340
98
  if (this.props.dismissed == null) {
341
99
  // We're serving as an "uncontrolled component": our dismissed
342
100
  // state is managed by us, not by incoming props.
343
101
  this.setState({dismissed: true});
344
102
  }
103
+
345
104
  this.props.onDismiss && this.props.onDismiss();
346
105
  };
347
106
 
348
- remeasure() {
349
- this._tooltipPortal && this._tooltipPortal.remeasure();
350
- }
107
+ _popoverContentColor: () => "white" | "blue" | "darkBlue" = () => {
108
+ const {inverted, color} = this.props;
109
+ if (inverted) {
110
+ return "darkBlue";
111
+ } else if (color === "blue") {
112
+ return "blue";
113
+ }
351
114
 
352
- render(): React.Element<"div"> {
353
- const {children, toggleOnHover} = this.props;
354
- const {children: _, ...portalProps} = this.props;
115
+ return "white";
116
+ };
355
117
 
118
+ render(): React.Element<any> {
119
+ const {side, children, content, dismissOnClickClose} = this.props;
120
+ const {onMouseEnter, onMouseLeave} = this.props;
356
121
  return (
357
- <div
358
- className={css(styles.targetElement)}
359
- onMouseEnter={
360
- toggleOnHover ? this._handleMouseEnter : undefined
361
- }
362
- onMouseLeave={
363
- toggleOnHover ? this._handleMouseLeave : undefined
122
+ <Popover
123
+ content={
124
+ <PopoverContentCore
125
+ color={this._popoverContentColor()}
126
+ closeButtonVisible={dismissOnClickClose}
127
+ style={{}}
128
+ >
129
+ {content}
130
+ </PopoverContentCore>
364
131
  }
365
- ref={(node) => (this._wrapper = node)}
366
- data-tooltip-wrapper
367
- data-test-id="question-id"
132
+ placement={side}
133
+ opened={this._shouldShowTooltip()}
134
+ onClose={this._handleDismiss}
135
+ dismissEnabled={false}
136
+ testId="question-id"
368
137
  >
369
- {children}
370
- {this._targetElement && this._shouldShowTooltip() && (
371
- <TooltipPortal
372
- tooltipProps={portalProps}
373
- dismiss={this._handleDismiss}
374
- rootElement={this._rootElement}
375
- targetElement={this._targetElement}
376
- isAboveModal={!!this.context.modalContainerElement}
377
- ref={(node) => (this._tooltipPortal = node)}
378
- />
379
- )}
380
- </div>
138
+ <div
139
+ onMouseEnter={() => {
140
+ onMouseEnter && onMouseEnter();
141
+ this.setState({hovered: true});
142
+ }}
143
+ onMouseLeave={() => {
144
+ onMouseLeave && onMouseLeave();
145
+ this.setState({hovered: false});
146
+ }}
147
+ >
148
+ {children}
149
+ </div>
150
+ </Popover>
381
151
  );
382
152
  }
383
153
  }
384
154
 
385
- NewTooltip.contextTypes = {
386
- modalContainerElement: PropTypes.instanceOf(HTMLElement),
387
- };
388
-
389
- const styles = StyleSheet.create({
390
- targetElement: {
391
- // NOTE(mdr): By setting this container to `display: inline`, we avoid
392
- // disrupting the call site's layout! Yay! Unfortunately, this
393
- // requires other hacks in order to do position tracking; see the
394
- // `cloneElement` call in `render`.
395
- display: "inline",
396
- },
397
- });
398
-
399
155
  export default NewTooltip;