@dr.pogodin/react-utils 1.47.0-alpha.7 → 1.47.0-alpha.9
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/build/development/client/index.js +1 -1
- package/build/development/client/index.js.map +1 -1
- package/build/development/index.js +1 -11
- package/build/development/index.js.map +1 -1
- package/build/development/server/index.js +3 -6
- package/build/development/server/index.js.map +1 -1
- package/build/development/server/utils/index.js +1 -1
- package/build/development/server/utils/index.js.map +1 -1
- package/build/development/shared/components/Button/index.js +1 -1
- package/build/development/shared/components/Button/index.js.map +1 -1
- package/build/development/shared/components/Checkbox/index.js +1 -1
- package/build/development/shared/components/Checkbox/index.js.map +1 -1
- package/build/development/shared/components/Input/index.js +1 -1
- package/build/development/shared/components/Input/index.js.map +1 -1
- package/build/development/shared/components/Modal/index.js +1 -1
- package/build/development/shared/components/Modal/index.js.map +1 -1
- package/build/development/shared/components/PageLayout/index.js +1 -1
- package/build/development/shared/components/PageLayout/index.js.map +1 -1
- package/build/development/shared/components/TextArea/index.js +1 -1
- package/build/development/shared/components/TextArea/index.js.map +1 -1
- package/build/development/shared/components/Throbber/index.js +1 -1
- package/build/development/shared/components/Throbber/index.js.map +1 -1
- package/build/development/shared/components/WithTooltip/index.js +1 -1
- package/build/development/shared/components/WithTooltip/index.js.map +1 -1
- package/build/development/shared/components/YouTubeVideo/index.js +1 -1
- package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/index.js +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/NativeDropdown/index.js +1 -1
- package/build/development/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/Switch/index.js +1 -1
- package/build/development/shared/components/selectors/Switch/index.js.map +1 -1
- package/build/production/client/index.js +1 -1
- package/build/production/client/index.js.map +1 -1
- package/build/production/index.js +2 -6
- package/build/production/index.js.map +1 -1
- package/build/production/server/index.js +3 -3
- package/build/production/server/index.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 +1 -1
- package/build/production/shared/components/Button/index.js.map +1 -1
- package/build/production/shared/components/Checkbox/index.js +1 -1
- package/build/production/shared/components/Checkbox/index.js.map +1 -1
- package/build/production/shared/components/Input/index.js +1 -1
- package/build/production/shared/components/Input/index.js.map +1 -1
- package/build/production/shared/components/Modal/index.js +1 -1
- package/build/production/shared/components/Modal/index.js.map +1 -1
- package/build/production/shared/components/PageLayout/index.js +1 -1
- package/build/production/shared/components/PageLayout/index.js.map +1 -1
- package/build/production/shared/components/TextArea/index.js +1 -1
- package/build/production/shared/components/TextArea/index.js.map +1 -1
- package/build/production/shared/components/Throbber/index.js +1 -1
- package/build/production/shared/components/Throbber/index.js.map +1 -1
- package/build/production/shared/components/WithTooltip/index.js +1 -1
- package/build/production/shared/components/WithTooltip/index.js.map +1 -1
- package/build/production/shared/components/YouTubeVideo/index.js +1 -1
- package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/index.js +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/production/shared/components/selectors/NativeDropdown/index.js +1 -1
- package/build/production/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/production/shared/components/selectors/Switch/index.js +1 -1
- package/build/production/shared/components/selectors/Switch/index.js.map +1 -1
- package/build/types-code/client/index.d.ts +1 -1
- package/build/types-code/index.d.ts +0 -6
- package/build/types-code/server/index.d.ts +4 -9
- package/build/types-code/server/utils/index.d.ts +1 -1
- package/build/web/client/index.js +1 -1
- package/build/web/client/index.js.map +1 -1
- package/build/web/index.js +0 -12
- package/build/web/index.js.map +1 -1
- package/build/web/server/index.js +3 -6
- package/build/web/server/index.js.map +1 -1
- package/build/web/server/utils/index.js +1 -1
- package/build/web/server/utils/index.js.map +1 -1
- package/build/web/shared/components/Button/index.js +1 -1
- package/build/web/shared/components/Button/index.js.map +1 -1
- package/build/web/shared/components/Checkbox/index.js +1 -1
- package/build/web/shared/components/Checkbox/index.js.map +1 -1
- package/build/web/shared/components/Input/index.js +1 -1
- package/build/web/shared/components/Input/index.js.map +1 -1
- package/build/web/shared/components/Modal/index.js +1 -1
- package/build/web/shared/components/Modal/index.js.map +1 -1
- package/build/web/shared/components/PageLayout/index.js +1 -1
- package/build/web/shared/components/PageLayout/index.js.map +1 -1
- package/build/web/shared/components/TextArea/index.js +1 -1
- package/build/web/shared/components/TextArea/index.js.map +1 -1
- package/build/web/shared/components/Throbber/index.js +1 -1
- package/build/web/shared/components/Throbber/index.js.map +1 -1
- package/build/web/shared/components/WithTooltip/index.js +1 -1
- package/build/web/shared/components/WithTooltip/index.js.map +1 -1
- package/build/web/shared/components/YouTubeVideo/index.js +1 -1
- package/build/web/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/web/shared/components/selectors/CustomDropdown/index.js +1 -1
- package/build/web/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/web/shared/components/selectors/NativeDropdown/index.js +1 -1
- package/build/web/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/web/shared/components/selectors/Switch/index.js +1 -1
- package/build/web/shared/components/selectors/Switch/index.js.map +1 -1
- package/package.json +17 -6
- package/src/client/getInj.ts +0 -71
- package/src/client/index.tsx +0 -45
- package/src/client/init.ts +0 -52
- package/src/index.ts +0 -75
- package/src/server/Cache.ts +0 -61
- package/src/server/index.ts +0 -243
- package/src/server/renderer.tsx +0 -635
- package/src/server/server.ts +0 -330
- package/src/server/utils/errors.ts +0 -122
- package/src/server/utils/index.ts +0 -3
- package/src/shared/components/Button/index.tsx +0 -130
- package/src/shared/components/Button/style.scss +0 -54
- package/src/shared/components/Checkbox/index.tsx +0 -53
- package/src/shared/components/Checkbox/theme.scss +0 -63
- package/src/shared/components/GenericLink/index.tsx +0 -141
- package/src/shared/components/GenericLink/style.scss +0 -3
- package/src/shared/components/Input/index.tsx +0 -96
- package/src/shared/components/Input/theme.scss +0 -52
- package/src/shared/components/Link.tsx +0 -25
- package/src/shared/components/Modal/base-theme.scss +0 -38
- package/src/shared/components/Modal/index.tsx +0 -177
- package/src/shared/components/Modal/styles.scss +0 -3
- package/src/shared/components/NavLink.tsx +0 -18
- package/src/shared/components/PageLayout/base-theme.scss +0 -30
- package/src/shared/components/PageLayout/index.tsx +0 -51
- package/src/shared/components/TextArea/index.tsx +0 -139
- package/src/shared/components/TextArea/style.scss +0 -68
- package/src/shared/components/Throbber/index.tsx +0 -28
- package/src/shared/components/Throbber/theme.scss +0 -26
- package/src/shared/components/WithTooltip/Tooltip.tsx +0 -274
- package/src/shared/components/WithTooltip/default-theme.scss +0 -36
- package/src/shared/components/WithTooltip/index.tsx +0 -186
- package/src/shared/components/YouTubeVideo/base.scss +0 -15
- package/src/shared/components/YouTubeVideo/index.tsx +0 -73
- package/src/shared/components/YouTubeVideo/throbber.scss +0 -11
- package/src/shared/components/index.ts +0 -20
- package/src/shared/components/selectors/CustomDropdown/Options/index.tsx +0 -124
- package/src/shared/components/selectors/CustomDropdown/Options/style.scss +0 -6
- package/src/shared/components/selectors/CustomDropdown/index.tsx +0 -146
- package/src/shared/components/selectors/CustomDropdown/theme.scss +0 -118
- package/src/shared/components/selectors/NativeDropdown/index.tsx +0 -90
- package/src/shared/components/selectors/NativeDropdown/theme.scss +0 -81
- package/src/shared/components/selectors/Switch/index.tsx +0 -74
- package/src/shared/components/selectors/Switch/theme.scss +0 -39
- package/src/shared/components/selectors/common.ts +0 -57
- package/src/shared/components/selectors/index.ts +0 -8
- package/src/shared/utils/config.ts +0 -59
- package/src/shared/utils/globalState.ts +0 -44
- package/src/shared/utils/index.ts +0 -45
- package/src/shared/utils/isomorphy/buildInfo.ts +0 -50
- package/src/shared/utils/isomorphy/environment-check.ts +0 -18
- package/src/shared/utils/isomorphy/index.ts +0 -36
- package/src/shared/utils/jest/E2eSsrEnv.ts +0 -304
- package/src/shared/utils/jest/global.ts +0 -17
- package/src/shared/utils/jest/index.ts +0 -215
- package/src/shared/utils/splitComponent.tsx +0 -284
- package/src/shared/utils/time.ts +0 -101
- package/src/shared/utils/webpack.ts +0 -80
- package/src/styles/_global/reset.css +0 -52
- package/src/styles/_mixins/fonts.scss +0 -40
- package/src/styles/_mixins/media.scss +0 -150
- package/src/styles/_mixins/typography.scss +0 -83
- package/src/styles/global.scss +0 -11
- package/src/styles/mixins.scss +0 -6
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/* global window */
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
type FunctionComponent,
|
|
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, {
|
|
14
|
-
type ThemeKeysT as TooltipThemeKeysT,
|
|
15
|
-
PLACEMENTS,
|
|
16
|
-
} from './Tooltip';
|
|
17
|
-
|
|
18
|
-
import defaultTheme from './default-theme.scss';
|
|
19
|
-
|
|
20
|
-
type PropsT = {
|
|
21
|
-
children?: ReactNode;
|
|
22
|
-
placement?: PLACEMENTS;
|
|
23
|
-
tip?: ReactNode;
|
|
24
|
-
theme: Theme<'wrapper' | TooltipThemeKeysT>;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type TooltipRefT = {
|
|
28
|
-
pointTo: (
|
|
29
|
-
x: number,
|
|
30
|
-
y: number,
|
|
31
|
-
placement: PLACEMENTS,
|
|
32
|
-
wrapperRef: HTMLDivElement,
|
|
33
|
-
) => void;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type HeapT = {
|
|
37
|
-
lastCursorX: number;
|
|
38
|
-
lastCursorY: number;
|
|
39
|
-
triggeredByTouch: boolean;
|
|
40
|
-
timerId?: NodeJS.Timeout;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Implements a simple to use and themeable tooltip component, _e.g._
|
|
45
|
-
* ```js
|
|
46
|
-
* <WithTooltip tip="This is example tooltip.">
|
|
47
|
-
* <p>Hover to see the tooltip.</p>
|
|
48
|
-
* </WithTooltip>
|
|
49
|
-
* ```
|
|
50
|
-
* **Children:** Children are rendered in the place of `<WithTooltip>`,
|
|
51
|
-
* and when hovered the tooltip is shown. By default the wrapper itself is
|
|
52
|
-
* `<div>` block with `display: inline-block`.
|
|
53
|
-
* @param 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: FunctionComponent<PropsT> = ({
|
|
58
|
-
children,
|
|
59
|
-
placement = PLACEMENTS.ABOVE_CURSOR,
|
|
60
|
-
tip,
|
|
61
|
-
theme,
|
|
62
|
-
}) => {
|
|
63
|
-
const { current: heap } = useRef<HeapT>({
|
|
64
|
-
lastCursorX: 0,
|
|
65
|
-
lastCursorY: 0,
|
|
66
|
-
timerId: undefined,
|
|
67
|
-
triggeredByTouch: false,
|
|
68
|
-
});
|
|
69
|
-
const tooltipRef = useRef<TooltipRefT>(null);
|
|
70
|
-
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
71
|
-
const [showTooltip, setShowTooltip] = useState(false);
|
|
72
|
-
|
|
73
|
-
const updatePortalPosition = (cursorX: number, cursorY: number) => {
|
|
74
|
-
if (showTooltip) {
|
|
75
|
-
const wrapperRect = wrapperRef.current!.getBoundingClientRect();
|
|
76
|
-
if (
|
|
77
|
-
cursorX < wrapperRect.left
|
|
78
|
-
|| cursorX > wrapperRect.right
|
|
79
|
-
|| cursorY < wrapperRect.top
|
|
80
|
-
|| cursorY > wrapperRect.bottom
|
|
81
|
-
) {
|
|
82
|
-
setShowTooltip(false);
|
|
83
|
-
} else if (tooltipRef.current) {
|
|
84
|
-
tooltipRef.current.pointTo(
|
|
85
|
-
cursorX + window.scrollX,
|
|
86
|
-
cursorY + window.scrollY,
|
|
87
|
-
placement,
|
|
88
|
-
wrapperRef.current!,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
heap.lastCursorX = cursorX;
|
|
93
|
-
heap.lastCursorY = cursorY;
|
|
94
|
-
|
|
95
|
-
// If tooltip was triggered by a touch, we delay its opening by a bit,
|
|
96
|
-
// to ensure it was not a touch-click - in the case of touch click we
|
|
97
|
-
// want to do the click, rather than show the tooltip, and the delay
|
|
98
|
-
// gives click handler a chance to abort the tooltip openning.
|
|
99
|
-
if (heap.triggeredByTouch) {
|
|
100
|
-
heap.timerId ??= setTimeout(() => {
|
|
101
|
-
heap.triggeredByTouch = false;
|
|
102
|
-
heap.timerId = undefined;
|
|
103
|
-
setShowTooltip(true);
|
|
104
|
-
}, 300);
|
|
105
|
-
|
|
106
|
-
// Otherwise we can just open the tooltip right away.
|
|
107
|
-
} else setShowTooltip(true);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (showTooltip && tip !== null) {
|
|
113
|
-
// This is necessary to ensure that even when a single mouse event
|
|
114
|
-
// arrives to a tool-tipped component, the tooltip is correctly positioned
|
|
115
|
-
// once opened (because similar call above does not have effect until
|
|
116
|
-
// the tooltip is fully mounted, and that is delayed to future rendering
|
|
117
|
-
// cycle due to the implementation).
|
|
118
|
-
if (tooltipRef.current) {
|
|
119
|
-
tooltipRef.current.pointTo(
|
|
120
|
-
heap.lastCursorX + window.scrollX,
|
|
121
|
-
heap.lastCursorY + window.scrollY,
|
|
122
|
-
placement,
|
|
123
|
-
wrapperRef.current!,
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const listener = () => {
|
|
128
|
-
setShowTooltip(false);
|
|
129
|
-
};
|
|
130
|
-
window.addEventListener('scroll', listener);
|
|
131
|
-
return () => {
|
|
132
|
-
window.removeEventListener('scroll', listener);
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
return undefined;
|
|
136
|
-
}, [
|
|
137
|
-
heap.lastCursorX,
|
|
138
|
-
heap.lastCursorY,
|
|
139
|
-
placement,
|
|
140
|
-
showTooltip,
|
|
141
|
-
tip,
|
|
142
|
-
]);
|
|
143
|
-
|
|
144
|
-
return (
|
|
145
|
-
<div
|
|
146
|
-
className={theme.wrapper}
|
|
147
|
-
onClick={() => {
|
|
148
|
-
if (heap.timerId) {
|
|
149
|
-
clearTimeout(heap.timerId);
|
|
150
|
-
heap.timerId = undefined;
|
|
151
|
-
heap.triggeredByTouch = false;
|
|
152
|
-
}
|
|
153
|
-
}}
|
|
154
|
-
onMouseLeave={() => {
|
|
155
|
-
setShowTooltip(false);
|
|
156
|
-
}}
|
|
157
|
-
onMouseMove={(e) => {
|
|
158
|
-
updatePortalPosition(e.clientX, e.clientY);
|
|
159
|
-
}}
|
|
160
|
-
onTouchStart={() => {
|
|
161
|
-
heap.triggeredByTouch = true;
|
|
162
|
-
}}
|
|
163
|
-
ref={wrapperRef}
|
|
164
|
-
role="presentation"
|
|
165
|
-
>
|
|
166
|
-
{
|
|
167
|
-
showTooltip && tip !== null
|
|
168
|
-
? <Tooltip ref={tooltipRef} theme={theme}>{tip}</Tooltip>
|
|
169
|
-
: null
|
|
170
|
-
}
|
|
171
|
-
{children}
|
|
172
|
-
</div>
|
|
173
|
-
);
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const ThemedWrapper = themed(Wrapper, 'WithTooltip', defaultTheme);
|
|
177
|
-
|
|
178
|
-
type ExportT = typeof ThemedWrapper & {
|
|
179
|
-
PLACEMENTS: typeof PLACEMENTS;
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
const e: ExportT = ThemedWrapper as ExportT;
|
|
183
|
-
|
|
184
|
-
e.PLACEMENTS = PLACEMENTS;
|
|
185
|
-
|
|
186
|
-
export default e;
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import qs from 'qs';
|
|
2
|
-
|
|
3
|
-
import themed, { type Theme } from '@dr.pogodin/react-themes';
|
|
4
|
-
|
|
5
|
-
import Throbber from 'components/Throbber';
|
|
6
|
-
|
|
7
|
-
import baseTheme from './base.scss';
|
|
8
|
-
import throbberTheme from './throbber.scss';
|
|
9
|
-
|
|
10
|
-
type ComponentThemeT = Theme<'container' | 'video'>;
|
|
11
|
-
|
|
12
|
-
type PropsT = {
|
|
13
|
-
autoplay?: boolean;
|
|
14
|
-
src: string;
|
|
15
|
-
theme: ComponentThemeT;
|
|
16
|
-
title?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A component for embeding a YouTube video.
|
|
21
|
-
* @param [props] Component properties.
|
|
22
|
-
* @param [props.autoplay] If `true` the video will start to play
|
|
23
|
-
* automatically once loaded.
|
|
24
|
-
* @param [props.src] URL of the video to play. Can be in any of
|
|
25
|
-
* the following formats, and keeps any additional query parameters understood
|
|
26
|
-
* by the YouTube IFrame player:
|
|
27
|
-
* - `https://www.youtube.com/watch?v=NdF6Rmt6Ado`
|
|
28
|
-
* - `https://youtu.be/NdF6Rmt6Ado`
|
|
29
|
-
* - `https://www.youtube.com/embed/NdF6Rmt6Ado`
|
|
30
|
-
* @param [props.theme] _Ad hoc_ theme.
|
|
31
|
-
* @param [props.title] The `title` attribute to add to the player
|
|
32
|
-
* IFrame.
|
|
33
|
-
*/
|
|
34
|
-
const YouTubeVideo: React.FunctionComponent<PropsT> = ({
|
|
35
|
-
autoplay,
|
|
36
|
-
src,
|
|
37
|
-
theme,
|
|
38
|
-
title,
|
|
39
|
-
}) => {
|
|
40
|
-
const srcParts = src.split('?');
|
|
41
|
-
let [url] = srcParts;
|
|
42
|
-
const [, queryString] = srcParts;
|
|
43
|
-
const query = queryString ? qs.parse(queryString) : {};
|
|
44
|
-
|
|
45
|
-
const videoId = query.v ?? url?.match(/\/([a-zA-Z0-9-_]*)$/)?.[1];
|
|
46
|
-
url = `https://www.youtube.com/embed/${videoId as string}`;
|
|
47
|
-
|
|
48
|
-
delete query.v;
|
|
49
|
-
query.autoplay = autoplay ? '1' : '0';
|
|
50
|
-
url += `?${qs.stringify(query)}`;
|
|
51
|
-
|
|
52
|
-
// TODO: https://developers.google.com/youtube/player_parameters
|
|
53
|
-
// More query parameters can be exposed via the component props.
|
|
54
|
-
|
|
55
|
-
return (
|
|
56
|
-
<div className={theme.container}>
|
|
57
|
-
<Throbber theme={throbberTheme} />
|
|
58
|
-
{/* TODO: I guess, we better add the sanbox, but right now I don't have
|
|
59
|
-
time to carefully explore which restrictions should be lifted to allow
|
|
60
|
-
embed YouTube player to work... sometime later we'll take care of it */
|
|
61
|
-
}
|
|
62
|
-
<iframe // eslint-disable-line react/iframe-missing-sandbox
|
|
63
|
-
allow="autoplay"
|
|
64
|
-
allowFullScreen
|
|
65
|
-
className={theme.video}
|
|
66
|
-
src={url}
|
|
67
|
-
title={title}
|
|
68
|
-
/>
|
|
69
|
-
</div>
|
|
70
|
-
);
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
export default themed(YouTubeVideo, 'YouTubeVideo', baseTheme);
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Just an aggregation of all exported components into a single module.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export { MetaTags } from '@dr.pogodin/react-helmet';
|
|
6
|
-
|
|
7
|
-
export * from 'components/selectors';
|
|
8
|
-
|
|
9
|
-
export { default as Button, BaseButton } from 'components/Button';
|
|
10
|
-
export { default as Checkbox } from 'components/Checkbox';
|
|
11
|
-
export { default as Input } from 'components/Input';
|
|
12
|
-
export { default as Link } from 'components/Link';
|
|
13
|
-
export { default as PageLayout } from 'components/PageLayout';
|
|
14
|
-
export { default as Modal, BaseModal } from 'components/Modal';
|
|
15
|
-
export { default as NavLink } from 'components/NavLink';
|
|
16
|
-
export { default as Throbber } from 'components/Throbber';
|
|
17
|
-
export { default as WithTooltip } from 'components/WithTooltip';
|
|
18
|
-
export { default as YouTubeVideo } from 'components/YouTubeVideo';
|
|
19
|
-
|
|
20
|
-
export { default as TextArea } from './TextArea';
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type FunctionComponent,
|
|
3
|
-
type ReactNode,
|
|
4
|
-
type RefObject,
|
|
5
|
-
useImperativeHandle,
|
|
6
|
-
useRef,
|
|
7
|
-
} from 'react';
|
|
8
|
-
|
|
9
|
-
import { BaseModal } from 'components/Modal';
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
type OptionT,
|
|
13
|
-
type OptionsT,
|
|
14
|
-
type ValueT,
|
|
15
|
-
optionValueName,
|
|
16
|
-
} from '../../common';
|
|
17
|
-
|
|
18
|
-
import S from './style.scss';
|
|
19
|
-
|
|
20
|
-
export type ContainerPosT = {
|
|
21
|
-
left: number;
|
|
22
|
-
top: number;
|
|
23
|
-
width: number;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export function areEqual(a?: ContainerPosT, b?: ContainerPosT): boolean {
|
|
27
|
-
return a?.left === b?.left && a?.top === b?.top && a?.width === b?.width;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export type RefT = {
|
|
31
|
-
measure: () => DOMRect | undefined;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
type PropsT = {
|
|
35
|
-
containerClass: string;
|
|
36
|
-
containerStyle?: ContainerPosT;
|
|
37
|
-
filter?: (item: OptionT<ReactNode> | ValueT) => boolean;
|
|
38
|
-
optionClass: string;
|
|
39
|
-
options: Readonly<OptionsT<ReactNode>>;
|
|
40
|
-
onCancel: () => void;
|
|
41
|
-
onChange: (value: ValueT) => void;
|
|
42
|
-
ref?: RefObject<RefT | null>;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const Options: FunctionComponent<PropsT> = ({
|
|
46
|
-
containerClass,
|
|
47
|
-
containerStyle,
|
|
48
|
-
filter,
|
|
49
|
-
onCancel,
|
|
50
|
-
onChange,
|
|
51
|
-
optionClass,
|
|
52
|
-
options,
|
|
53
|
-
ref,
|
|
54
|
-
}) => {
|
|
55
|
-
const opsRef = useRef<HTMLDivElement>(null);
|
|
56
|
-
|
|
57
|
-
useImperativeHandle(ref, () => ({
|
|
58
|
-
measure: () => {
|
|
59
|
-
const e = opsRef.current?.parentElement;
|
|
60
|
-
if (!e) return undefined;
|
|
61
|
-
|
|
62
|
-
const rect = opsRef.current!.getBoundingClientRect();
|
|
63
|
-
const style = window.getComputedStyle(e);
|
|
64
|
-
const mBottom = parseFloat(style.marginBottom);
|
|
65
|
-
const mTop = parseFloat(style.marginTop);
|
|
66
|
-
|
|
67
|
-
rect.height += mBottom + mTop;
|
|
68
|
-
|
|
69
|
-
return rect;
|
|
70
|
-
},
|
|
71
|
-
}), []);
|
|
72
|
-
|
|
73
|
-
const optionNodes: ReactNode[] = [];
|
|
74
|
-
for (const option of options) {
|
|
75
|
-
if (!filter || filter(option)) {
|
|
76
|
-
const [iValue, iName] = optionValueName(option);
|
|
77
|
-
optionNodes.push(
|
|
78
|
-
<div
|
|
79
|
-
className={optionClass}
|
|
80
|
-
key={iValue}
|
|
81
|
-
onClick={(e) => {
|
|
82
|
-
onChange(iValue);
|
|
83
|
-
e.stopPropagation();
|
|
84
|
-
}}
|
|
85
|
-
onKeyDown={(e) => {
|
|
86
|
-
if (e.key === 'Enter') {
|
|
87
|
-
onChange(iValue);
|
|
88
|
-
e.stopPropagation();
|
|
89
|
-
}
|
|
90
|
-
}}
|
|
91
|
-
role="button"
|
|
92
|
-
tabIndex={0}
|
|
93
|
-
>
|
|
94
|
-
{iName}
|
|
95
|
-
</div>,
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return (
|
|
101
|
-
<BaseModal
|
|
102
|
-
// Closes the dropdown (cancels the selection) on any page scrolling attempt.
|
|
103
|
-
// This is the same native <select> elements do on scrolling, and at least for
|
|
104
|
-
// now we have no reason to deal with complications needed to support open
|
|
105
|
-
// dropdowns during the scrolling (that would need to re-position it in
|
|
106
|
-
// response to the position changes of the root dropdown element).
|
|
107
|
-
cancelOnScrolling
|
|
108
|
-
dontDisableScrolling
|
|
109
|
-
onCancel={onCancel}
|
|
110
|
-
style={containerStyle}
|
|
111
|
-
theme={{
|
|
112
|
-
ad: '',
|
|
113
|
-
container: containerClass,
|
|
114
|
-
context: '',
|
|
115
|
-
hoc: '',
|
|
116
|
-
overlay: S.overlay,
|
|
117
|
-
}}
|
|
118
|
-
>
|
|
119
|
-
<div ref={opsRef}>{optionNodes}</div>
|
|
120
|
-
</BaseModal>
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
export default Options;
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { useEffect, useRef, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import themed from '@dr.pogodin/react-themes';
|
|
4
|
-
|
|
5
|
-
import { type PropsT, type ValueT, optionValueName } from '../common';
|
|
6
|
-
|
|
7
|
-
import Options, { type ContainerPosT, type RefT, areEqual } from './Options';
|
|
8
|
-
|
|
9
|
-
import defaultTheme from './theme.scss';
|
|
10
|
-
|
|
11
|
-
const BaseCustomDropdown: React.FunctionComponent<
|
|
12
|
-
PropsT<React.ReactNode, (value: ValueT) => void>
|
|
13
|
-
> = ({
|
|
14
|
-
filter,
|
|
15
|
-
label,
|
|
16
|
-
onChange,
|
|
17
|
-
options,
|
|
18
|
-
theme,
|
|
19
|
-
value,
|
|
20
|
-
}) => {
|
|
21
|
-
const [active, setActive] = useState(false);
|
|
22
|
-
|
|
23
|
-
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
24
|
-
const opsRef = useRef<RefT>(null);
|
|
25
|
-
|
|
26
|
-
const [opsPos, setOpsPos] = useState<ContainerPosT>();
|
|
27
|
-
const [upward, setUpward] = useState(false);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (!active) return undefined;
|
|
31
|
-
|
|
32
|
-
let id: number;
|
|
33
|
-
const cb = () => {
|
|
34
|
-
const anchor = dropdownRef.current?.getBoundingClientRect();
|
|
35
|
-
const opsRect = opsRef.current?.measure();
|
|
36
|
-
if (anchor && opsRect) {
|
|
37
|
-
const fitsDown = anchor.bottom + opsRect.height
|
|
38
|
-
< (window.visualViewport?.height ?? 0);
|
|
39
|
-
const fitsUp = anchor.top - opsRect.height > 0;
|
|
40
|
-
|
|
41
|
-
const up = !fitsDown && fitsUp;
|
|
42
|
-
setUpward(up);
|
|
43
|
-
|
|
44
|
-
const pos = up ? {
|
|
45
|
-
left: anchor.left,
|
|
46
|
-
top: anchor.top - opsRect.height - 1,
|
|
47
|
-
width: anchor.width,
|
|
48
|
-
} : {
|
|
49
|
-
left: anchor.left,
|
|
50
|
-
top: anchor.bottom,
|
|
51
|
-
width: anchor.width,
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
setOpsPos((now) => (areEqual(now, pos) ? now : pos));
|
|
55
|
-
}
|
|
56
|
-
id = requestAnimationFrame(cb);
|
|
57
|
-
};
|
|
58
|
-
requestAnimationFrame(cb);
|
|
59
|
-
|
|
60
|
-
return () => {
|
|
61
|
-
cancelAnimationFrame(id);
|
|
62
|
-
};
|
|
63
|
-
}, [active]);
|
|
64
|
-
|
|
65
|
-
const openList = (
|
|
66
|
-
e: React.KeyboardEvent<HTMLDivElement> | React.MouseEvent<HTMLDivElement>,
|
|
67
|
-
) => {
|
|
68
|
-
const view = window.visualViewport;
|
|
69
|
-
const rect = dropdownRef.current!.getBoundingClientRect();
|
|
70
|
-
setActive(true);
|
|
71
|
-
|
|
72
|
-
// NOTE: This first opens the dropdown off-screen, where it is measured
|
|
73
|
-
// by an effect declared above, and then positioned below, or above
|
|
74
|
-
// the original dropdown element, depending where it fits best
|
|
75
|
-
// (if we first open it downward, it would flick if we immediately
|
|
76
|
-
// move it above, at least with the current position update via local
|
|
77
|
-
// react state, and not imperatively).
|
|
78
|
-
setOpsPos({
|
|
79
|
-
left: view?.width ?? 0,
|
|
80
|
-
top: view?.height ?? 0,
|
|
81
|
-
width: rect.width,
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
e.stopPropagation();
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
let selected: React.ReactNode = <>‌</>;
|
|
88
|
-
for (const option of options) {
|
|
89
|
-
if (!filter || filter(option)) {
|
|
90
|
-
const [iValue, iName] = optionValueName(option);
|
|
91
|
-
if (iValue === value) {
|
|
92
|
-
selected = iName;
|
|
93
|
-
break;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
let containerClassName = theme.container;
|
|
99
|
-
if (active) containerClassName += ` ${theme.active}`;
|
|
100
|
-
|
|
101
|
-
let opsContainerClass = theme.select ?? '';
|
|
102
|
-
if (upward) {
|
|
103
|
-
containerClassName += ` ${theme.upward}`;
|
|
104
|
-
opsContainerClass += ` ${theme.upward}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
return (
|
|
108
|
-
<div className={containerClassName}>
|
|
109
|
-
{label === undefined ? null
|
|
110
|
-
: <div className={theme.label}>{label}</div>}
|
|
111
|
-
<div
|
|
112
|
-
className={theme.dropdown}
|
|
113
|
-
onClick={openList}
|
|
114
|
-
onKeyDown={(e) => {
|
|
115
|
-
if (e.key === 'Enter') openList(e);
|
|
116
|
-
}}
|
|
117
|
-
ref={dropdownRef}
|
|
118
|
-
role="listbox"
|
|
119
|
-
tabIndex={0}
|
|
120
|
-
>
|
|
121
|
-
{selected}
|
|
122
|
-
<div className={theme.arrow} />
|
|
123
|
-
</div>
|
|
124
|
-
{
|
|
125
|
-
active ? (
|
|
126
|
-
<Options
|
|
127
|
-
containerClass={opsContainerClass}
|
|
128
|
-
containerStyle={opsPos}
|
|
129
|
-
onCancel={() => {
|
|
130
|
-
setActive(false);
|
|
131
|
-
}}
|
|
132
|
-
onChange={(newValue) => {
|
|
133
|
-
setActive(false);
|
|
134
|
-
if (onChange) onChange(newValue);
|
|
135
|
-
}}
|
|
136
|
-
optionClass={theme.option ?? ''}
|
|
137
|
-
options={options}
|
|
138
|
-
ref={opsRef}
|
|
139
|
-
/>
|
|
140
|
-
) : null
|
|
141
|
-
}
|
|
142
|
-
</div>
|
|
143
|
-
);
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export default themed(BaseCustomDropdown, 'CustomDropdown', defaultTheme);
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
$border: 1px solid gray;
|
|
2
|
-
|
|
3
|
-
*,
|
|
4
|
-
.context,
|
|
5
|
-
.ad.hoc {
|
|
6
|
-
// The outermost dropdown container, holding together the label (if any),
|
|
7
|
-
// and the select element with arrow. Note, that the dropdown option list,
|
|
8
|
-
// when opened, exists completely outside the dropdown DOM hierarchy, and
|
|
9
|
-
// is aligned into the correct position by JS.
|
|
10
|
-
&.container {
|
|
11
|
-
align-items: center;
|
|
12
|
-
display: inline-flex;
|
|
13
|
-
margin: 0.1em;
|
|
14
|
-
position: relative;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Styling of default label next to the dropdown (has no effect on custom
|
|
18
|
-
// non-string label node, if provided).
|
|
19
|
-
&.label {
|
|
20
|
-
margin: 0 0.6em 0 1.2em;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
&.dropdown {
|
|
24
|
-
border: $border;
|
|
25
|
-
border-radius: 0.3em;
|
|
26
|
-
cursor: pointer;
|
|
27
|
-
min-width: 200px;
|
|
28
|
-
outline: none;
|
|
29
|
-
padding: 0.3em 3.0em 0.3em 0.6em;
|
|
30
|
-
position: relative;
|
|
31
|
-
user-select: none;
|
|
32
|
-
|
|
33
|
-
&:focus {
|
|
34
|
-
border-color: blue;
|
|
35
|
-
box-shadow: 0 0 3px 1px lightblue;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
&.option {
|
|
40
|
-
cursor: pointer;
|
|
41
|
-
outline: none ;
|
|
42
|
-
padding: 0 0.6em;
|
|
43
|
-
|
|
44
|
-
&:focus {
|
|
45
|
-
background: royalblue;
|
|
46
|
-
color: white;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
&:hover {
|
|
50
|
-
background: royalblue;
|
|
51
|
-
color: white;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
&.select {
|
|
56
|
-
background: white;
|
|
57
|
-
border: $border;
|
|
58
|
-
border-radius: 0 0 0.3em 0.3em;
|
|
59
|
-
border-top: none;
|
|
60
|
-
box-shadow: 0 6px 12px 3px lightgray;
|
|
61
|
-
position: fixed;
|
|
62
|
-
z-index: 1001;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
&.arrow {
|
|
66
|
-
background-image: linear-gradient(to top, lightgray, white 50%, white);
|
|
67
|
-
border-left: $border;
|
|
68
|
-
border-radius: 0 0.3em 0.3em 0;
|
|
69
|
-
bottom: 0;
|
|
70
|
-
padding: 0.3em 0.6em;
|
|
71
|
-
position: absolute;
|
|
72
|
-
right: 0;
|
|
73
|
-
top: 0;
|
|
74
|
-
|
|
75
|
-
&::after {
|
|
76
|
-
content: '▼';
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
&.active {
|
|
81
|
-
.arrow {
|
|
82
|
-
border-radius: 0 0.3em 0 0;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
.dropdown {
|
|
86
|
-
border-color: blue;
|
|
87
|
-
border-radius: 0.3em 0.3em 0 0;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
&.upward {
|
|
92
|
-
&.active {
|
|
93
|
-
// NOTE: Here StyleLint complains about order & specifity of selectors in
|
|
94
|
-
// the compiled CSS, but it should have no effect on the actual styling.
|
|
95
|
-
// stylelint-disable no-descending-specificity
|
|
96
|
-
.arrow {
|
|
97
|
-
border-radius: 0 0 0.3em;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.dropdown {
|
|
101
|
-
border-radius: 0 0 0.3em 0.3em;
|
|
102
|
-
}
|
|
103
|
-
// stylelint-enable no-descending-specificity
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
&.select {
|
|
107
|
-
border-bottom: none;
|
|
108
|
-
border-top: $border;
|
|
109
|
-
border-radius: 0.3em 0.3em 0 0;
|
|
110
|
-
|
|
111
|
-
// NOTE: Here a normal (downward) shadow would weirdly cast over
|
|
112
|
-
// the dropdown element, and other ways to cast the shadow result in
|
|
113
|
-
// "upward" shadow, which is also weird. Thus, better no shadow at all
|
|
114
|
-
// for the upward-opened dropdown.
|
|
115
|
-
box-shadow: none;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|