@dr.pogodin/react-utils 1.25.5 → 1.26.0
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/babel.config.js +3 -1
- package/bin/build.js +25 -5
- package/build/development/client/getInj.js +9 -8
- package/build/development/client/getInj.js.map +1 -1
- package/build/development/client/index.js +6 -3
- package/build/development/client/index.js.map +1 -1
- package/build/development/client/init.js +4 -0
- package/build/development/client/init.js.map +1 -1
- package/build/development/index.js +168 -42
- package/build/development/index.js.map +1 -1
- package/build/development/server/Cache.js +13 -13
- package/build/development/server/Cache.js.map +1 -1
- package/build/development/server/index.js +16 -24
- package/build/development/server/index.js.map +1 -1
- package/build/development/server/renderer.js +56 -44
- package/build/development/server/renderer.js.map +1 -1
- package/build/development/server/server.js +9 -5
- package/build/development/server/server.js.map +1 -1
- package/build/development/server/utils/errors.js +12 -9
- package/build/development/server/utils/errors.js.map +1 -1
- package/build/development/server/utils/index.js +2 -2
- package/build/development/server/utils/index.js.map +1 -1
- package/build/development/shared/components/Button/index.js +8 -7
- package/build/development/shared/components/Button/index.js.map +1 -1
- package/build/development/shared/components/Checkbox/index.js +25 -28
- package/build/development/shared/components/Checkbox/index.js.map +1 -1
- package/build/development/shared/components/Dropdown/index.js +25 -19
- package/build/development/shared/components/Dropdown/index.js.map +1 -1
- package/build/development/shared/components/GenericLink/index.js +44 -37
- package/build/development/shared/components/GenericLink/index.js.map +1 -1
- package/build/development/shared/components/Input/index.js +8 -9
- package/build/development/shared/components/Input/index.js.map +1 -1
- package/build/development/shared/components/Link.js +10 -9
- package/build/development/shared/components/Link.js.map +1 -1
- package/build/development/shared/components/MetaTags.js +22 -18
- package/build/development/shared/components/MetaTags.js.map +1 -1
- package/build/development/shared/components/Modal/index.js +17 -18
- package/build/development/shared/components/Modal/index.js.map +1 -1
- package/build/development/shared/components/NavLink.js +10 -9
- package/build/development/shared/components/NavLink.js.map +1 -1
- package/build/development/shared/components/PageLayout/index.js +17 -20
- package/build/development/shared/components/PageLayout/index.js.map +1 -1
- package/build/development/shared/components/ScalableRect/index.js +27 -7
- package/build/development/shared/components/ScalableRect/index.js.map +1 -1
- package/build/development/shared/components/Throbber/index.js +14 -23
- package/build/development/shared/components/Throbber/index.js.map +1 -1
- package/build/development/shared/components/WithTooltip/Tooltip.js +20 -24
- package/build/development/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/development/shared/components/WithTooltip/index.js +11 -11
- package/build/development/shared/components/WithTooltip/index.js.map +1 -1
- package/build/development/shared/components/YouTubeVideo/index.js +16 -15
- package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/development/shared/components/index.js +2 -2
- package/build/development/shared/components/index.js.map +1 -1
- package/build/development/shared/utils/config.js +1 -2
- package/build/development/shared/utils/config.js.map +1 -1
- package/build/development/shared/utils/globalState.js +15 -0
- package/build/development/shared/utils/globalState.js.map +1 -0
- package/build/development/shared/utils/index.js +13 -11
- package/build/development/shared/utils/index.js.map +1 -1
- package/build/development/shared/utils/isomorphy/buildInfo.js +7 -3
- package/build/development/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/development/shared/utils/isomorphy/environment-check.js +2 -4
- package/build/development/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/development/shared/utils/isomorphy/index.js.map +1 -1
- package/build/development/shared/utils/jest/E2eSsrEnv.js +35 -28
- package/build/development/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/development/shared/utils/jest/global.js +17 -0
- package/build/development/shared/utils/jest/global.js.map +1 -0
- package/build/development/shared/utils/jest/index.js +18 -11
- package/build/development/shared/utils/jest/index.js.map +1 -1
- package/build/development/shared/utils/splitComponent.js +25 -34
- package/build/development/shared/utils/splitComponent.js.map +1 -1
- package/build/development/shared/utils/time.js +16 -13
- package/build/development/shared/utils/time.js.map +1 -1
- package/build/development/shared/utils/webpack.js +3 -3
- package/build/development/shared/utils/webpack.js.map +1 -1
- package/build/development/web.bundle.js +92 -82
- package/build/production/client/getInj.js +1 -1
- package/build/production/client/getInj.js.map +1 -1
- package/build/production/client/index.js +4 -4
- package/build/production/client/index.js.map +1 -1
- package/build/production/client/init.js +3 -1
- package/build/production/client/init.js.map +1 -1
- package/build/production/index.js +1 -1
- package/build/production/index.js.map +1 -1
- package/build/production/server/Cache.js +7 -8
- package/build/production/server/Cache.js.map +1 -1
- package/build/production/server/index.js +5 -4
- package/build/production/server/index.js.map +1 -1
- package/build/production/server/renderer.js +32 -30
- package/build/production/server/renderer.js.map +1 -1
- package/build/production/server/server.js +7 -5
- package/build/production/server/server.js.map +1 -1
- package/build/production/server/utils/errors.js +9 -10
- package/build/production/server/utils/errors.js.map +1 -1
- package/build/production/server/utils/index.js +1 -1
- package/build/production/server/utils/index.js.map +1 -1
- package/build/production/shared/components/Button/index.js +4 -4
- package/build/production/shared/components/Button/index.js.map +1 -1
- package/build/production/shared/components/Checkbox/index.js +12 -12
- package/build/production/shared/components/Checkbox/index.js.map +1 -1
- package/build/production/shared/components/Dropdown/index.js +11 -11
- package/build/production/shared/components/Dropdown/index.js.map +1 -1
- package/build/production/shared/components/GenericLink/index.js +25 -20
- package/build/production/shared/components/GenericLink/index.js.map +1 -1
- package/build/production/shared/components/Input/index.js +7 -7
- package/build/production/shared/components/Input/index.js.map +1 -1
- package/build/production/shared/components/Link.js +2 -2
- package/build/production/shared/components/Link.js.map +1 -1
- package/build/production/shared/components/MetaTags.js +10 -10
- package/build/production/shared/components/MetaTags.js.map +1 -1
- package/build/production/shared/components/Modal/index.js +2 -2
- package/build/production/shared/components/Modal/index.js.map +1 -1
- package/build/production/shared/components/NavLink.js +1 -1
- package/build/production/shared/components/NavLink.js.map +1 -1
- package/build/production/shared/components/PageLayout/index.js +2 -2
- package/build/production/shared/components/PageLayout/index.js.map +1 -1
- package/build/production/shared/components/ScalableRect/index.js +7 -3
- package/build/production/shared/components/ScalableRect/index.js.map +1 -1
- package/build/production/shared/components/Throbber/index.js +2 -2
- package/build/production/shared/components/Throbber/index.js.map +1 -1
- package/build/production/shared/components/WithTooltip/Tooltip.js +13 -13
- package/build/production/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/production/shared/components/WithTooltip/index.js +3 -3
- package/build/production/shared/components/WithTooltip/index.js.map +1 -1
- package/build/production/shared/components/YouTubeVideo/index.js +7 -7
- package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/production/shared/components/index.js +1 -1
- package/build/production/shared/components/index.js.map +1 -1
- package/build/production/shared/utils/config.js +1 -1
- package/build/production/shared/utils/config.js.map +1 -1
- package/build/production/shared/utils/globalState.js +3 -0
- package/build/production/shared/utils/globalState.js.map +1 -0
- package/build/production/shared/utils/index.js +3 -3
- package/build/production/shared/utils/index.js.map +1 -1
- package/build/production/shared/utils/isomorphy/buildInfo.js +7 -4
- package/build/production/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/production/shared/utils/isomorphy/environment-check.js +2 -2
- package/build/production/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/production/shared/utils/isomorphy/index.js.map +1 -1
- package/build/production/shared/utils/jest/E2eSsrEnv.js +9 -9
- package/build/production/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/production/shared/utils/jest/global.js +2 -0
- package/build/production/shared/utils/jest/global.js.map +1 -0
- package/build/production/shared/utils/jest/index.js +6 -6
- package/build/production/shared/utils/jest/index.js.map +1 -1
- package/build/production/shared/utils/splitComponent.js +16 -16
- package/build/production/shared/utils/splitComponent.js.map +1 -1
- package/build/production/shared/utils/time.js +12 -8
- package/build/production/shared/utils/time.js.map +1 -1
- package/build/production/shared/utils/webpack.js +3 -3
- package/build/production/shared/utils/webpack.js.map +1 -1
- package/build/production/web.bundle.js +1 -1
- package/build/production/web.bundle.js.map +1 -1
- package/build/types-code/client/getInj.d.ts +3 -0
- package/build/types-code/client/index.d.ts +11 -0
- package/build/types-code/client/init.d.ts +9 -0
- package/build/types-code/index.d.ts +10 -0
- package/build/types-code/server/Cache.d.ts +37 -0
- package/build/types-code/server/index.d.ts +145 -0
- package/build/types-code/server/renderer.d.ts +106 -0
- package/build/types-code/server/server.d.ts +41 -0
- package/build/types-code/server/utils/errors.d.ts +104 -0
- package/build/types-code/server/utils/index.d.ts +1 -0
- package/build/types-code/shared/components/Button/index.d.ts +27 -0
- package/build/types-code/shared/components/Checkbox/index.d.ts +21 -0
- package/build/types-code/shared/components/Dropdown/index.d.ts +23 -0
- package/build/types-code/shared/components/GenericLink/index.d.ts +61 -0
- package/build/types-code/shared/components/Input/index.d.ts +11 -0
- package/build/types-code/shared/components/Link.d.ts +12 -0
- package/build/types-code/shared/components/MetaTags.d.ts +68 -0
- package/build/types-code/shared/components/Modal/index.d.ts +26 -0
- package/build/types-code/shared/components/NavLink.d.ts +5 -0
- package/build/types-code/shared/components/PageLayout/index.d.ts +16 -0
- package/build/types-code/shared/components/ScalableRect/index.d.ts +19 -0
- package/build/types-code/shared/components/Throbber/index.d.ts +9 -0
- package/build/types-code/shared/components/WithTooltip/Tooltip.d.ts +23 -0
- package/build/types-code/shared/components/WithTooltip/index.d.ts +17 -0
- package/build/types-code/shared/components/YouTubeVideo/index.d.ts +13 -0
- package/build/types-code/shared/components/index.d.ts +16 -0
- package/build/types-code/shared/utils/config.d.ts +2 -0
- package/build/types-code/shared/utils/globalState.d.ts +20 -0
- package/build/types-code/shared/utils/index.d.ts +52 -0
- package/build/types-code/shared/utils/isomorphy/buildInfo.d.ts +23 -0
- package/build/types-code/shared/utils/isomorphy/environment-check.d.ts +11 -0
- package/build/types-code/shared/utils/isomorphy/index.d.ts +20 -0
- package/build/types-code/shared/utils/jest/E2eSsrEnv.d.ts +31 -0
- package/build/types-code/shared/utils/jest/global.d.ts +12 -0
- package/build/types-code/shared/utils/jest/index.d.ts +85 -0
- package/build/types-code/shared/utils/splitComponent.d.ts +41 -0
- package/build/types-code/shared/utils/time.d.ts +62 -0
- package/build/types-code/shared/utils/webpack.d.ts +18 -0
- package/build/types-scss/__tests__/js/config/publicPath support/__assets__/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/config/stylename-generation/__assets__/MockPackageA/TestComponent/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/config/stylename-generation/__assets__/MockPackageB/TestComponent/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/config/stylename-generation/__assets__/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/shared/components/NavLink/styles.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/ComponentA/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/ComponentB/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/ComponentC/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/js/shared/utils/splitComponent/__assets__/SampleScene/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/config/publicPath support/__assets__/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/config/stylename-generation/__assets__/MockPackageA/TestComponent/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/config/stylename-generation/__assets__/MockPackageB/TestComponent/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/config/stylename-generation/__assets__/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/shared/components/NavLink/styles.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/ComponentA/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/ComponentB/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/ComponentC/style.scss.d.ts +1 -0
- package/build/types-scss/__tests__/ts/shared/utils/splitComponent/__assets__/SampleScene/style.scss.d.ts +1 -0
- package/build/types-scss/src/shared/components/Button/style.scss.d.ts +6 -0
- package/build/types-scss/src/shared/components/Checkbox/theme.scss.d.ts +6 -0
- package/build/types-scss/src/shared/components/Dropdown/theme.scss.d.ts +9 -0
- package/build/types-scss/src/shared/components/GenericLink/style.scss.d.ts +1 -0
- package/build/types-scss/src/shared/components/Input/theme.scss.d.ts +6 -0
- package/build/types-scss/src/shared/components/Modal/base-theme.scss.d.ts +5 -0
- package/build/types-scss/src/shared/components/PageLayout/base-theme.scss.d.ts +6 -0
- package/build/types-scss/src/shared/components/ScalableRect/style.scss.d.ts +2 -0
- package/build/types-scss/src/shared/components/Throbber/theme.scss.d.ts +6 -0
- package/build/types-scss/src/shared/components/WithTooltip/default-theme.scss.d.ts +7 -0
- package/build/types-scss/src/shared/components/YouTubeVideo/base.scss.d.ts +5 -0
- package/build/types-scss/src/shared/components/YouTubeVideo/throbber.scss.d.ts +4 -0
- package/config/eslint/jest.json +3 -2
- package/config/eslint/typescript.js +34 -0
- package/config/jest/default.js +3 -3
- package/package.json +75 -33
- package/src/client/getInj.ts +43 -0
- package/src/client/index.tsx +40 -0
- package/src/client/init.ts +47 -0
- package/src/index.ts +58 -0
- package/src/server/Cache.ts +68 -0
- package/src/server/index.ts +230 -0
- package/src/server/renderer.tsx +604 -0
- package/src/server/server.ts +309 -0
- package/src/server/utils/errors.ts +135 -0
- package/src/server/utils/index.ts +3 -0
- package/src/shared/components/Button/index.tsx +146 -0
- package/src/shared/components/Button/style.scss +53 -0
- package/src/shared/components/Checkbox/index.tsx +71 -0
- package/src/shared/components/Checkbox/theme.scss +43 -0
- package/src/shared/components/Dropdown/index.tsx +144 -0
- package/src/shared/components/Dropdown/theme.scss +63 -0
- package/src/shared/components/GenericLink/index.tsx +157 -0
- package/src/shared/components/GenericLink/style.scss +3 -0
- package/src/shared/components/Input/index.tsx +59 -0
- package/src/shared/components/Input/theme.scss +27 -0
- package/src/shared/components/Link.tsx +21 -0
- package/src/shared/components/MetaTags.tsx +170 -0
- package/src/shared/components/Modal/base-theme.scss +38 -0
- package/src/shared/components/Modal/index.tsx +144 -0
- package/src/shared/components/Modal/styles.scss +5 -0
- package/src/shared/components/NavLink.tsx +13 -0
- package/src/shared/components/PageLayout/base-theme.scss +30 -0
- package/src/shared/components/PageLayout/index.tsx +76 -0
- package/src/shared/components/ScalableRect/index.tsx +84 -0
- package/src/shared/components/ScalableRect/style.scss +10 -0
- package/src/shared/components/Throbber/index.tsx +43 -0
- package/src/shared/components/Throbber/theme.scss +26 -0
- package/src/shared/components/WithTooltip/Tooltip.tsx +353 -0
- package/src/shared/components/WithTooltip/default-theme.scss +36 -0
- package/src/shared/components/WithTooltip/index.tsx +204 -0
- package/src/shared/components/YouTubeVideo/base.scss +13 -0
- package/src/shared/components/YouTubeVideo/index.tsx +96 -0
- package/src/shared/components/YouTubeVideo/throbber.scss +11 -0
- package/src/shared/components/index.ts +17 -0
- package/src/shared/utils/config.ts +21 -0
- package/src/shared/utils/globalState.ts +29 -0
- package/src/shared/utils/index.ts +105 -0
- package/src/shared/utils/isomorphy/buildInfo.ts +54 -0
- package/src/shared/utils/isomorphy/environment-check.ts +18 -0
- package/src/shared/utils/isomorphy/index.ts +38 -0
- package/src/shared/utils/jest/E2eSsrEnv.ts +250 -0
- package/src/shared/utils/jest/global.ts +19 -0
- package/src/shared/utils/jest/index.tsx +157 -0
- package/src/shared/utils/splitComponent.tsx +255 -0
- package/src/shared/utils/time.ts +118 -0
- package/src/shared/utils/webpack.ts +45 -0
- package/src/styles/_global/reset.css +52 -0
- package/src/styles/global.scss +11 -0
- package/tsconfig.configs.json +18 -0
- package/tsconfig.json +27 -0
- package/tsconfig.types.json +53 -0
- package/typed-scss-modules.config.ts +9 -0
- package/types.d.ts +37 -0
- package/{webpack.config.js → webpack.config.ts} +7 -3
- package/config/babel/node-ssr.js +0 -85
- package/config/babel/webpack.js +0 -122
- package/config/webpack/app-base.js +0 -330
- package/config/webpack/app-development.js +0 -80
- package/config/webpack/app-production.js +0 -60
- package/config/webpack/lib-base.js +0 -155
- package/config/webpack/lib-development.js +0 -45
- package/config/webpack/lib-production.js +0 -44
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The actual tooltip component. It is rendered outside the regular document
|
|
3
|
+
* hierarchy, and with sub-components managed without React to achieve the best
|
|
4
|
+
* performance during animation.
|
|
5
|
+
*/
|
|
6
|
+
/* global document, window */
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
type ReactNode,
|
|
10
|
+
forwardRef,
|
|
11
|
+
useEffect,
|
|
12
|
+
useImperativeHandle,
|
|
13
|
+
useRef,
|
|
14
|
+
useState,
|
|
15
|
+
} from 'react';
|
|
16
|
+
|
|
17
|
+
import { createPortal } from 'react-dom';
|
|
18
|
+
|
|
19
|
+
import PT from 'prop-types';
|
|
20
|
+
|
|
21
|
+
/* Valid placements of the rendered tooltip. They will be overriden when
|
|
22
|
+
* necessary to fit the tooltip within the viewport. */
|
|
23
|
+
export enum PLACEMENTS {
|
|
24
|
+
ABOVE_CURSOR = 'ABOVE_CURSOR',
|
|
25
|
+
ABOVE_ELEMENT = 'ABOVE_ELEMENT',
|
|
26
|
+
BELOW_CURSOR = 'BELOW_CURSOR',
|
|
27
|
+
BELOW_ELEMENT = 'BELOW_ELEMENT',
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const ARROW_STYLE_DOWN = [
|
|
31
|
+
'border-bottom-color:transparent',
|
|
32
|
+
'border-left-color:transparent',
|
|
33
|
+
'border-right-color:transparent',
|
|
34
|
+
].join(';');
|
|
35
|
+
|
|
36
|
+
const ARROW_STYLE_UP = [
|
|
37
|
+
'border-top-color:transparent',
|
|
38
|
+
'border-left-color:transparent',
|
|
39
|
+
'border-right-color:transparent',
|
|
40
|
+
].join(';');
|
|
41
|
+
|
|
42
|
+
type ComponentsT = {
|
|
43
|
+
container: HTMLDivElement;
|
|
44
|
+
arrow: HTMLDivElement;
|
|
45
|
+
content: HTMLDivElement;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type HeapT = {
|
|
49
|
+
lastElement?: HTMLElement;
|
|
50
|
+
lastPageX: number;
|
|
51
|
+
lastPageY: number;
|
|
52
|
+
lastPlacement?: PLACEMENTS | undefined;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export interface TooltipThemeT {
|
|
56
|
+
appearance?: string;
|
|
57
|
+
arrow?: string;
|
|
58
|
+
content?: string;
|
|
59
|
+
container?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates tooltip components.
|
|
64
|
+
* @ignore
|
|
65
|
+
* @param {object} theme Themes to use for tooltip container, arrow,
|
|
66
|
+
* and content.
|
|
67
|
+
* @return {object} Object with DOM references to the container components:
|
|
68
|
+
* container, arrow, content.
|
|
69
|
+
*/
|
|
70
|
+
function createTooltipComponents(theme: TooltipThemeT): ComponentsT {
|
|
71
|
+
const arrow = document.createElement('div');
|
|
72
|
+
if (theme.arrow) arrow.setAttribute('class', theme.arrow);
|
|
73
|
+
|
|
74
|
+
const content = document.createElement('div');
|
|
75
|
+
if (theme.content) content.setAttribute('class', theme.content);
|
|
76
|
+
|
|
77
|
+
const container = document.createElement('div');
|
|
78
|
+
if (theme.container) container.setAttribute('class', theme.container);
|
|
79
|
+
|
|
80
|
+
container.appendChild(arrow);
|
|
81
|
+
container.appendChild(content);
|
|
82
|
+
document.body.appendChild(container);
|
|
83
|
+
|
|
84
|
+
return { container, arrow, content };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
type TooltipRectsT = {
|
|
88
|
+
arrow: DOMRect;
|
|
89
|
+
container: DOMRect;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Generates bounding client rectangles for tooltip components.
|
|
94
|
+
* @ignore
|
|
95
|
+
* @param tooltip DOM references to the tooltip components.
|
|
96
|
+
* @param tooltip.arrow
|
|
97
|
+
* @param tooltip.container
|
|
98
|
+
* @return Object holding tooltip rectangles in
|
|
99
|
+
* two fields.
|
|
100
|
+
*/
|
|
101
|
+
function calcTooltipRects(tooltip: ComponentsT): TooltipRectsT {
|
|
102
|
+
return {
|
|
103
|
+
arrow: tooltip.arrow.getBoundingClientRect(),
|
|
104
|
+
container: tooltip.container.getBoundingClientRect(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Calculates the document viewport size.
|
|
110
|
+
* @ignore
|
|
111
|
+
* @return {{x, y, width, height}}
|
|
112
|
+
*/
|
|
113
|
+
function calcViewportRect() {
|
|
114
|
+
const { scrollX, scrollY } = window;
|
|
115
|
+
const { documentElement: { clientHeight, clientWidth } } = document;
|
|
116
|
+
return {
|
|
117
|
+
left: scrollX,
|
|
118
|
+
right: scrollX + clientWidth,
|
|
119
|
+
top: scrollY,
|
|
120
|
+
bottom: scrollY + clientHeight,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Calculates tooltip and arrow positions for the placement just above
|
|
126
|
+
* the cursor.
|
|
127
|
+
* @ignore
|
|
128
|
+
* @param {number} x Cursor page-x position.
|
|
129
|
+
* @param {number} y Cursor page-y position.
|
|
130
|
+
* @param {object} tooltipRects Bounding client rectangles of tooltip parts.
|
|
131
|
+
* @param {object} tooltipRects.arrow
|
|
132
|
+
* @param {object} tooltipRects.container
|
|
133
|
+
* @return {object} Contains the following fields:
|
|
134
|
+
* - {number} arrowX
|
|
135
|
+
* - {number} arrowY
|
|
136
|
+
* - {number} containerX
|
|
137
|
+
* - {number} containerY
|
|
138
|
+
* - {string} baseArrowStyle
|
|
139
|
+
*/
|
|
140
|
+
function calcPositionAboveXY(
|
|
141
|
+
x: number,
|
|
142
|
+
y: number,
|
|
143
|
+
tooltipRects: TooltipRectsT,
|
|
144
|
+
) {
|
|
145
|
+
const { arrow, container } = tooltipRects;
|
|
146
|
+
return {
|
|
147
|
+
arrowX: 0.5 * (container.width - arrow.width),
|
|
148
|
+
arrowY: container.height,
|
|
149
|
+
containerX: x - container.width / 2,
|
|
150
|
+
containerY: y - container.height - arrow.height / 1.5,
|
|
151
|
+
|
|
152
|
+
// TODO: Instead of already setting the base style here, we should
|
|
153
|
+
// introduce a set of constants for arrow directions, which will help
|
|
154
|
+
// to do checks dependant on the arrow direction.
|
|
155
|
+
baseArrowStyle: ARROW_STYLE_DOWN,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/*
|
|
160
|
+
const HIT = {
|
|
161
|
+
NONE: false,
|
|
162
|
+
LEFT: 'LEFT',
|
|
163
|
+
RIGHT: 'RIGHT',
|
|
164
|
+
TOP: 'TOP',
|
|
165
|
+
BOTTOM: 'BOTTOM',
|
|
166
|
+
};
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Checks whether
|
|
171
|
+
* @param {object} pos
|
|
172
|
+
* @param {object} tooltipRects
|
|
173
|
+
* @param {object} viewportRect
|
|
174
|
+
* @return {HIT}
|
|
175
|
+
*/
|
|
176
|
+
/*
|
|
177
|
+
function checkViewportFit(pos, tooltipRects, viewportRect) {
|
|
178
|
+
const { containerX, containerY } = pos;
|
|
179
|
+
if (containerX < viewportRect.left + 6) return HIT.LEFT;
|
|
180
|
+
if (containerX > viewportRect.right - 6) return HIT.RIGHT;
|
|
181
|
+
return HIT.NONE;
|
|
182
|
+
}
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Shifts tooltip horizontally to fit into the viewport, while keeping
|
|
187
|
+
* the arrow pointed to the XY point.
|
|
188
|
+
* @param {number} x
|
|
189
|
+
* @param {number} y
|
|
190
|
+
* @param {object} pos
|
|
191
|
+
* @param {number} pageXOffset
|
|
192
|
+
* @param {number} pageXWidth
|
|
193
|
+
*/
|
|
194
|
+
/*
|
|
195
|
+
function xPageFitCorrection(x, y, pos, pageXOffset, pageXWidth) {
|
|
196
|
+
if (pos.containerX < pageXOffset + 6) {
|
|
197
|
+
pos.containerX = pageXOffset + 6;
|
|
198
|
+
pos.arrowX = Math.max(6, pageX - containerX - arrowRect.width / 2);
|
|
199
|
+
} else {
|
|
200
|
+
const maxX = pageXOffset + docRect.width - containerRect.width - 6;
|
|
201
|
+
if (containerX > maxX) {
|
|
202
|
+
containerX = maxX;
|
|
203
|
+
arrowX = Math.min(
|
|
204
|
+
containerRect.width - 6,
|
|
205
|
+
pageX - containerX - arrowRect.width / 2,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Sets positions of tooltip components to point the tooltip to the specified
|
|
214
|
+
* page point.
|
|
215
|
+
* @ignore
|
|
216
|
+
* @param pageX
|
|
217
|
+
* @param pageY
|
|
218
|
+
* @param placement
|
|
219
|
+
* @param element DOM reference to the element wrapped by the tooltip.
|
|
220
|
+
* @param tooltip
|
|
221
|
+
* @param tooltip.arrow DOM reference to the tooltip arrow.
|
|
222
|
+
* @param tooltip.container DOM reference to the tooltip container.
|
|
223
|
+
*/
|
|
224
|
+
function setComponentPositions(
|
|
225
|
+
pageX: number,
|
|
226
|
+
pageY: number,
|
|
227
|
+
placement: PLACEMENTS | undefined,
|
|
228
|
+
element: HTMLElement | undefined,
|
|
229
|
+
tooltip: ComponentsT,
|
|
230
|
+
) {
|
|
231
|
+
const tooltipRects = calcTooltipRects(tooltip);
|
|
232
|
+
const viewportRect = calcViewportRect();
|
|
233
|
+
|
|
234
|
+
/* Default container coords: tooltip at the top. */
|
|
235
|
+
const pos = calcPositionAboveXY(pageX, pageY, tooltipRects);
|
|
236
|
+
|
|
237
|
+
if (pos.containerX < viewportRect.left + 6) {
|
|
238
|
+
pos.containerX = viewportRect.left + 6;
|
|
239
|
+
pos.arrowX = Math.max(
|
|
240
|
+
6,
|
|
241
|
+
pageX - pos.containerX - tooltipRects.arrow.width / 2,
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
const maxX = viewportRect.right - 6 - tooltipRects.container.width;
|
|
245
|
+
if (pos.containerX > maxX) {
|
|
246
|
+
pos.containerX = maxX;
|
|
247
|
+
pos.arrowX = Math.min(
|
|
248
|
+
tooltipRects.container.width - 6,
|
|
249
|
+
pageX - pos.containerX - tooltipRects.arrow.width / 2,
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* If tooltip has not enough space on top - make it bottom tooltip. */
|
|
255
|
+
if (pos.containerY < viewportRect.top + 6) {
|
|
256
|
+
pos.containerY += tooltipRects.container.height
|
|
257
|
+
+ 2 * tooltipRects.arrow.height;
|
|
258
|
+
pos.arrowY -= tooltipRects.container.height
|
|
259
|
+
+ tooltipRects.arrow.height;
|
|
260
|
+
pos.baseArrowStyle = ARROW_STYLE_UP;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const containerStyle = `left:${pos.containerX}px;top:${pos.containerY}px`;
|
|
264
|
+
tooltip.container.setAttribute('style', containerStyle);
|
|
265
|
+
|
|
266
|
+
const arrowStyle = `${pos.baseArrowStyle};left:${pos.arrowX}px;top:${pos.arrowY}px`;
|
|
267
|
+
tooltip.arrow.setAttribute('style', arrowStyle);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* The Tooltip component itself. */
|
|
271
|
+
const Tooltip = forwardRef<unknown, {
|
|
272
|
+
children?: ReactNode;
|
|
273
|
+
theme: any;
|
|
274
|
+
}>(({ children, theme }, ref) => {
|
|
275
|
+
// NOTE: The way it has to be implemented, for clean mounting and unmounting
|
|
276
|
+
// at the client side, the <Tooltip> is fully mounted into DOM in the next
|
|
277
|
+
// rendering cycles, and only then it can be correctly measured and positioned.
|
|
278
|
+
// Thus, when we create the <Tooltip> we have to record its target positioning
|
|
279
|
+
// details, and then apply them when it is created.
|
|
280
|
+
|
|
281
|
+
const { current: heap } = useRef<HeapT>({
|
|
282
|
+
lastElement: undefined,
|
|
283
|
+
lastPageX: 0,
|
|
284
|
+
lastPageY: 0,
|
|
285
|
+
lastPlacement: undefined,
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
const [components, setComponents] = useState<ComponentsT | null>(null);
|
|
289
|
+
|
|
290
|
+
const pointTo = (
|
|
291
|
+
pageX: number,
|
|
292
|
+
pageY: number,
|
|
293
|
+
placement: PLACEMENTS,
|
|
294
|
+
element: HTMLElement,
|
|
295
|
+
) => {
|
|
296
|
+
heap.lastElement = element;
|
|
297
|
+
heap.lastPageX = pageX;
|
|
298
|
+
heap.lastPageY = pageY;
|
|
299
|
+
heap.lastPlacement = placement;
|
|
300
|
+
|
|
301
|
+
if (components) {
|
|
302
|
+
setComponentPositions(pageX, pageY, placement, element, components);
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
useImperativeHandle(ref, () => ({ pointTo }));
|
|
306
|
+
|
|
307
|
+
/* Inits and destroys tooltip components. */
|
|
308
|
+
useEffect(() => {
|
|
309
|
+
const x = createTooltipComponents(theme);
|
|
310
|
+
setComponents(x);
|
|
311
|
+
return () => {
|
|
312
|
+
document.body.removeChild(x.container);
|
|
313
|
+
setComponents(null);
|
|
314
|
+
};
|
|
315
|
+
}, [theme]);
|
|
316
|
+
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
if (components) {
|
|
319
|
+
setComponentPositions(
|
|
320
|
+
heap.lastPageX,
|
|
321
|
+
heap.lastPageY,
|
|
322
|
+
heap.lastPlacement,
|
|
323
|
+
heap.lastElement,
|
|
324
|
+
components,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
}, [
|
|
328
|
+
components,
|
|
329
|
+
// BEWARE: Careful about these dependencies - they are updated when mouse
|
|
330
|
+
// is moved over the tool-tipped element, thus potentially may cause
|
|
331
|
+
// unnecessary firing of this effect on each mouse event. It does not
|
|
332
|
+
// happen now just because the mouse movements themselves are not causing
|
|
333
|
+
// the component re-rendering, thus dependencies of this effect are not
|
|
334
|
+
// really re-evaluated.
|
|
335
|
+
heap.lastPageX,
|
|
336
|
+
heap.lastPageY,
|
|
337
|
+
heap.lastPlacement,
|
|
338
|
+
heap.lastElement,
|
|
339
|
+
]);
|
|
340
|
+
|
|
341
|
+
return components ? createPortal(children, components.content) : null;
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
Tooltip.propTypes = {
|
|
345
|
+
children: PT.node,
|
|
346
|
+
theme: PT.shape({}).isRequired,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
Tooltip.defaultProps = {
|
|
350
|
+
children: null,
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
export default Tooltip;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
@keyframes appearance {
|
|
2
|
+
from { opacity: 0; }
|
|
3
|
+
to { opacity: 1; }
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
*,
|
|
7
|
+
.ad.hoc,
|
|
8
|
+
.context {
|
|
9
|
+
&.arrow {
|
|
10
|
+
border: 0.6em solid grey;
|
|
11
|
+
pointer-events: none;
|
|
12
|
+
position: absolute;
|
|
13
|
+
width: 0;
|
|
14
|
+
height: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
&.content { }
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
&.container {
|
|
22
|
+
background: grey;
|
|
23
|
+
border-radius: 0.3em;
|
|
24
|
+
color: white;
|
|
25
|
+
display: inline-block;
|
|
26
|
+
padding: 0 0.3em;
|
|
27
|
+
position: absolute;
|
|
28
|
+
top: 0;
|
|
29
|
+
left: 0;
|
|
30
|
+
animation: appearance 0.6s;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&.wrapper {
|
|
34
|
+
display: inline-block;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/* global window */
|
|
2
|
+
|
|
3
|
+
import PT from 'prop-types';
|
|
4
|
+
import {
|
|
5
|
+
type ReactNode,
|
|
6
|
+
useEffect,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
} from 'react';
|
|
10
|
+
|
|
11
|
+
import themed, { type Theme } from '@dr.pogodin/react-themes';
|
|
12
|
+
|
|
13
|
+
import Tooltip, { PLACEMENTS, type TooltipThemeT } from './Tooltip';
|
|
14
|
+
|
|
15
|
+
import defaultTheme from './default-theme.scss';
|
|
16
|
+
|
|
17
|
+
type PropsT = {
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
placement?: PLACEMENTS;
|
|
20
|
+
tip?: ReactNode;
|
|
21
|
+
theme: Theme & TooltipThemeT & {
|
|
22
|
+
wrapper?: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type TooltipRefT = {
|
|
27
|
+
pointTo: (
|
|
28
|
+
x: number,
|
|
29
|
+
y: number,
|
|
30
|
+
placement: PLACEMENTS,
|
|
31
|
+
wrapperRef: HTMLDivElement,
|
|
32
|
+
) => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type HeapT = {
|
|
36
|
+
lastCursorX: number;
|
|
37
|
+
lastCursorY: number;
|
|
38
|
+
triggeredByTouch: boolean;
|
|
39
|
+
timerId?: NodeJS.Timeout;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Implements a simple to use and themeable tooltip component, _e.g._
|
|
44
|
+
* ```js
|
|
45
|
+
* <WithTooltip tip="This is example tooltip.">
|
|
46
|
+
* <p>Hover to see the tooltip.</p>
|
|
47
|
+
* </WithTooltip>
|
|
48
|
+
* ```
|
|
49
|
+
* **Children:** Children are rendered in the place of `<WithTooltip>`,
|
|
50
|
+
* and when hovered the tooltip is shown. By default the wrapper itself is
|
|
51
|
+
* `<div>` block with `display: inline-block`.
|
|
52
|
+
* @param {object} props Component properties.
|
|
53
|
+
* @param {React.node} props.tip – Anything React is able to render,
|
|
54
|
+
* _e.g._ a tooltip text. This will be the tooltip content.
|
|
55
|
+
* @param {WithTooltipTheme} props.theme _Ad hoc_ theme.
|
|
56
|
+
*/
|
|
57
|
+
const Wrapper: React.FunctionComponent<PropsT> = ({
|
|
58
|
+
children,
|
|
59
|
+
placement,
|
|
60
|
+
tip,
|
|
61
|
+
theme,
|
|
62
|
+
}) => {
|
|
63
|
+
const { current: heap } = useRef<HeapT>({
|
|
64
|
+
lastCursorX: 0,
|
|
65
|
+
lastCursorY: 0,
|
|
66
|
+
triggeredByTouch: false,
|
|
67
|
+
timerId: undefined,
|
|
68
|
+
});
|
|
69
|
+
const tooltipRef = useRef<TooltipRefT>();
|
|
70
|
+
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
71
|
+
const [showTooltip, setShowTooltip] = useState(false);
|
|
72
|
+
|
|
73
|
+
const updatePortalPosition = (cursorX: number, cursorY: number) => {
|
|
74
|
+
if (!showTooltip) {
|
|
75
|
+
heap.lastCursorX = cursorX;
|
|
76
|
+
heap.lastCursorY = cursorY;
|
|
77
|
+
|
|
78
|
+
// If tooltip was triggered by a touch, we delay its opening by a bit,
|
|
79
|
+
// to ensure it was not a touch-click - in the case of touch click we
|
|
80
|
+
// want to do the click, rather than show the tooltip, and the delay
|
|
81
|
+
// gives click handler a chance to abort the tooltip openning.
|
|
82
|
+
if (heap.triggeredByTouch) {
|
|
83
|
+
if (!heap.timerId) {
|
|
84
|
+
heap.timerId = setTimeout(() => {
|
|
85
|
+
heap.triggeredByTouch = false;
|
|
86
|
+
heap.timerId = undefined;
|
|
87
|
+
setShowTooltip(true);
|
|
88
|
+
}, 300);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Otherwise we can just open the tooltip right away.
|
|
92
|
+
} else setShowTooltip(true);
|
|
93
|
+
} else {
|
|
94
|
+
const wrapperRect = wrapperRef.current!.getBoundingClientRect();
|
|
95
|
+
if (
|
|
96
|
+
cursorX < wrapperRect.left
|
|
97
|
+
|| cursorX > wrapperRect.right
|
|
98
|
+
|| cursorY < wrapperRect.top
|
|
99
|
+
|| cursorY > wrapperRect.bottom
|
|
100
|
+
) {
|
|
101
|
+
setShowTooltip(false);
|
|
102
|
+
} else if (tooltipRef.current) {
|
|
103
|
+
tooltipRef.current.pointTo(
|
|
104
|
+
cursorX + window.scrollX,
|
|
105
|
+
cursorY + window.scrollY,
|
|
106
|
+
placement!,
|
|
107
|
+
wrapperRef.current!,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
if (showTooltip && tip !== null) {
|
|
115
|
+
// This is necessary to ensure that even when a single mouse event
|
|
116
|
+
// arrives to a tool-tipped component, the tooltip is correctly positioned
|
|
117
|
+
// once opened (because similar call above does not have effect until
|
|
118
|
+
// the tooltip is fully mounted, and that is delayed to future rendering
|
|
119
|
+
// cycle due to the implementation).
|
|
120
|
+
if (tooltipRef.current) {
|
|
121
|
+
tooltipRef.current.pointTo(
|
|
122
|
+
heap.lastCursorX + window.scrollX,
|
|
123
|
+
heap.lastCursorY + window.scrollY,
|
|
124
|
+
placement!,
|
|
125
|
+
wrapperRef.current!,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const listener = () => setShowTooltip(false);
|
|
130
|
+
window.addEventListener('scroll', listener);
|
|
131
|
+
return () => window.removeEventListener('scroll', listener);
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}, [
|
|
135
|
+
heap.lastCursorX,
|
|
136
|
+
heap.lastCursorY,
|
|
137
|
+
placement,
|
|
138
|
+
showTooltip,
|
|
139
|
+
tip,
|
|
140
|
+
]);
|
|
141
|
+
|
|
142
|
+
return (
|
|
143
|
+
<div
|
|
144
|
+
className={theme.wrapper}
|
|
145
|
+
onMouseLeave={() => setShowTooltip(false)}
|
|
146
|
+
onMouseMove={(e) => updatePortalPosition(e.clientX, e.clientY)}
|
|
147
|
+
onClick={() => {
|
|
148
|
+
if (heap.timerId) {
|
|
149
|
+
clearTimeout(heap.timerId);
|
|
150
|
+
heap.timerId = undefined;
|
|
151
|
+
heap.triggeredByTouch = false;
|
|
152
|
+
}
|
|
153
|
+
}}
|
|
154
|
+
onTouchStart={() => {
|
|
155
|
+
heap.triggeredByTouch = true;
|
|
156
|
+
}}
|
|
157
|
+
ref={wrapperRef}
|
|
158
|
+
role="presentation"
|
|
159
|
+
>
|
|
160
|
+
{
|
|
161
|
+
showTooltip && tip !== null ? (
|
|
162
|
+
<Tooltip ref={tooltipRef} theme={theme}>{tip}</Tooltip>
|
|
163
|
+
) : null
|
|
164
|
+
}
|
|
165
|
+
{children}
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const ThemedWrapper = themed(
|
|
171
|
+
Wrapper,
|
|
172
|
+
'WithTooltip',
|
|
173
|
+
[
|
|
174
|
+
'appearance',
|
|
175
|
+
'arrow',
|
|
176
|
+
'container',
|
|
177
|
+
'content',
|
|
178
|
+
'wrapper',
|
|
179
|
+
],
|
|
180
|
+
defaultTheme,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
type ExportT = typeof ThemedWrapper & {
|
|
184
|
+
PLACEMENTS: typeof PLACEMENTS;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const e: ExportT = ThemedWrapper as ExportT;
|
|
188
|
+
|
|
189
|
+
e.PLACEMENTS = PLACEMENTS;
|
|
190
|
+
|
|
191
|
+
Wrapper.propTypes = {
|
|
192
|
+
children: PT.node,
|
|
193
|
+
placement: PT.oneOf(Object.values(PLACEMENTS)),
|
|
194
|
+
theme: ThemedWrapper.themeType.isRequired,
|
|
195
|
+
tip: PT.node,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
Wrapper.defaultProps = {
|
|
199
|
+
children: null,
|
|
200
|
+
placement: PLACEMENTS.ABOVE_CURSOR,
|
|
201
|
+
tip: null,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export default e;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import PT from 'prop-types';
|
|
2
|
+
import qs from 'qs';
|
|
3
|
+
|
|
4
|
+
import themed, { type Theme } from '@dr.pogodin/react-themes';
|
|
5
|
+
|
|
6
|
+
import ScalableRect from 'components/ScalableRect';
|
|
7
|
+
import Throbber from 'components/Throbber';
|
|
8
|
+
|
|
9
|
+
import baseTheme from './base.scss';
|
|
10
|
+
import throbberTheme from './throbber.scss';
|
|
11
|
+
|
|
12
|
+
type ComponentThemeT = Theme & {
|
|
13
|
+
container?: string;
|
|
14
|
+
video?: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type PropsT = {
|
|
18
|
+
autoplay?: boolean;
|
|
19
|
+
src: string;
|
|
20
|
+
theme: ComponentThemeT,
|
|
21
|
+
title?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A component for embeding a YouTube video.
|
|
26
|
+
* @param [props] Component properties.
|
|
27
|
+
* @param [props.autoplay] If `true` the video will start to play
|
|
28
|
+
* automatically once loaded.
|
|
29
|
+
* @param [props.src] URL of the video to play. Can be in any of
|
|
30
|
+
* the following formats, and keeps any additional query parameters understood
|
|
31
|
+
* by the YouTube IFrame player:
|
|
32
|
+
* - `https://www.youtube.com/watch?v=NdF6Rmt6Ado`
|
|
33
|
+
* - `https://youtu.be/NdF6Rmt6Ado`
|
|
34
|
+
* - `https://www.youtube.com/embed/NdF6Rmt6Ado`
|
|
35
|
+
* @param [props.theme] _Ad hoc_ theme.
|
|
36
|
+
* @param [props.title] The `title` attribute to add to the player
|
|
37
|
+
* IFrame.
|
|
38
|
+
*/
|
|
39
|
+
const YouTubeVideo: React.FunctionComponent<PropsT> = ({
|
|
40
|
+
autoplay,
|
|
41
|
+
src,
|
|
42
|
+
theme,
|
|
43
|
+
title,
|
|
44
|
+
}) => {
|
|
45
|
+
const srcParts = src.split('?');
|
|
46
|
+
let url = srcParts[0];
|
|
47
|
+
const queryString = srcParts[1];
|
|
48
|
+
const query = queryString ? qs.parse(queryString) : {};
|
|
49
|
+
|
|
50
|
+
const videoId = query.v || url.match(/\/([a-zA-Z0-9-_]*)$/)?.[1];
|
|
51
|
+
url = `https://www.youtube.com/embed/${videoId}`;
|
|
52
|
+
|
|
53
|
+
delete query.v;
|
|
54
|
+
query.autoplay = autoplay ? '1' : '0';
|
|
55
|
+
url += `?${qs.stringify(query)}`;
|
|
56
|
+
|
|
57
|
+
// TODO: https://developers.google.com/youtube/player_parameters
|
|
58
|
+
// More query parameters can be exposed via the component props.
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ScalableRect className={theme.container} ratio="16:9">
|
|
62
|
+
<Throbber theme={throbberTheme} />
|
|
63
|
+
<iframe
|
|
64
|
+
allow="autoplay"
|
|
65
|
+
allowFullScreen
|
|
66
|
+
className={theme.video}
|
|
67
|
+
src={url}
|
|
68
|
+
title={title}
|
|
69
|
+
/>
|
|
70
|
+
</ScalableRect>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const ThemedYouTubeVideo = themed(
|
|
75
|
+
YouTubeVideo,
|
|
76
|
+
'YouTubeVideo',
|
|
77
|
+
[
|
|
78
|
+
'container',
|
|
79
|
+
'video',
|
|
80
|
+
],
|
|
81
|
+
baseTheme,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
YouTubeVideo.propTypes = {
|
|
85
|
+
autoplay: PT.bool,
|
|
86
|
+
src: PT.string.isRequired,
|
|
87
|
+
theme: ThemedYouTubeVideo.themeType.isRequired,
|
|
88
|
+
title: PT.string,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
YouTubeVideo.defaultProps = {
|
|
92
|
+
autoplay: false,
|
|
93
|
+
title: '',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export default ThemedYouTubeVideo;
|