@dr.pogodin/react-utils 1.30.2 → 1.31.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.
Files changed (117) hide show
  1. package/bin/build.js +5 -0
  2. package/build/development/client/index.js +1 -1
  3. package/build/development/client/index.js.map +1 -1
  4. package/build/development/index.js +7 -0
  5. package/build/development/index.js.map +1 -1
  6. package/build/development/shared/components/Checkbox/index.js +2 -2
  7. package/build/development/shared/components/Checkbox/index.js.map +1 -1
  8. package/build/development/shared/components/Input/index.js +2 -2
  9. package/build/development/shared/components/Input/index.js.map +1 -1
  10. package/build/development/shared/components/Modal/index.js +25 -5
  11. package/build/development/shared/components/Modal/index.js.map +1 -1
  12. package/build/development/shared/components/TextArea/index.js +5 -0
  13. package/build/development/shared/components/TextArea/index.js.map +1 -1
  14. package/build/development/shared/components/YouTubeVideo/index.js +1 -3
  15. package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
  16. package/build/development/shared/components/index.js +27 -14
  17. package/build/development/shared/components/index.js.map +1 -1
  18. package/build/development/shared/components/selectors/CustomDropdown/Options/index.js +93 -0
  19. package/build/development/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -0
  20. package/build/development/shared/components/selectors/CustomDropdown/index.js +105 -0
  21. package/build/development/shared/components/selectors/CustomDropdown/index.js.map +1 -0
  22. package/build/development/shared/components/{Dropdown → selectors/NativeDropdown}/index.js +25 -34
  23. package/build/development/shared/components/selectors/NativeDropdown/index.js.map +1 -0
  24. package/build/development/shared/components/selectors/Switch/index.js +76 -0
  25. package/build/development/shared/components/selectors/Switch/index.js.map +1 -0
  26. package/build/development/shared/components/selectors/common.js +24 -0
  27. package/build/development/shared/components/selectors/common.js.map +1 -0
  28. package/build/development/shared/components/selectors/index.js +28 -0
  29. package/build/development/shared/components/selectors/index.js.map +1 -0
  30. package/build/development/style.css +387 -225
  31. package/build/development/web.bundle.js +109 -49
  32. package/build/production/client/index.js +1 -1
  33. package/build/production/client/index.js.map +1 -1
  34. package/build/production/index.js +1 -1
  35. package/build/production/index.js.map +1 -1
  36. package/build/production/shared/components/Checkbox/index.js +2 -2
  37. package/build/production/shared/components/Checkbox/index.js.map +1 -1
  38. package/build/production/shared/components/Input/index.js +1 -1
  39. package/build/production/shared/components/Input/index.js.map +1 -1
  40. package/build/production/shared/components/Modal/index.js +3 -2
  41. package/build/production/shared/components/Modal/index.js.map +1 -1
  42. package/build/production/shared/components/TextArea/index.js +3 -3
  43. package/build/production/shared/components/TextArea/index.js.map +1 -1
  44. package/build/production/shared/components/YouTubeVideo/index.js +2 -2
  45. package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
  46. package/build/production/shared/components/index.js +1 -1
  47. package/build/production/shared/components/index.js.map +1 -1
  48. package/build/production/shared/components/selectors/CustomDropdown/Options/index.js +7 -0
  49. package/build/production/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -0
  50. package/build/production/shared/components/selectors/CustomDropdown/index.js +4 -0
  51. package/build/production/shared/components/selectors/CustomDropdown/index.js.map +1 -0
  52. package/build/production/shared/components/selectors/NativeDropdown/index.js +25 -0
  53. package/build/production/shared/components/selectors/NativeDropdown/index.js.map +1 -0
  54. package/build/production/shared/components/selectors/Switch/index.js +2 -0
  55. package/build/production/shared/components/selectors/Switch/index.js.map +1 -0
  56. package/build/production/shared/components/selectors/common.js +3 -0
  57. package/build/production/shared/components/selectors/common.js.map +1 -0
  58. package/build/production/shared/components/selectors/index.js +2 -0
  59. package/build/production/shared/components/selectors/index.js.map +1 -0
  60. package/build/production/style.css +1 -1
  61. package/build/production/style.css.map +1 -1
  62. package/build/production/web.bundle.js +1 -1
  63. package/build/production/web.bundle.js.map +1 -1
  64. package/build/types-code/client/index.d.ts +1 -0
  65. package/build/types-code/index.d.ts +1 -1
  66. package/build/types-code/shared/components/Checkbox/index.d.ts +1 -1
  67. package/build/types-code/shared/components/Input/index.d.ts +1 -1
  68. package/build/types-code/shared/components/Modal/index.d.ts +2 -1
  69. package/build/types-code/shared/components/TextArea/index.d.ts +1 -0
  70. package/build/types-code/shared/components/index.d.ts +1 -2
  71. package/build/types-code/shared/components/selectors/CustomDropdown/Options/index.d.ts +17 -0
  72. package/build/types-code/shared/components/selectors/CustomDropdown/index.d.ts +4 -0
  73. package/build/types-code/shared/components/selectors/NativeDropdown/index.d.ts +3 -0
  74. package/build/types-code/shared/components/selectors/Switch/index.d.ts +13 -0
  75. package/build/types-code/shared/components/selectors/common.d.ts +27 -0
  76. package/build/types-code/shared/components/selectors/index.d.ts +3 -0
  77. package/build/types-scss/src/shared/components/Modal/styles.scss.d.ts +1 -0
  78. package/build/types-scss/src/shared/components/selectors/CustomDropdown/Options/style.scss.d.ts +1 -0
  79. package/build/types-scss/src/shared/components/selectors/CustomDropdown/theme.scss.d.ts +10 -0
  80. package/build/types-scss/src/shared/components/{Dropdown → selectors/NativeDropdown}/theme.scss.d.ts +1 -0
  81. package/build/types-scss/src/shared/components/selectors/Switch/theme.scss.d.ts +6 -0
  82. package/package.json +6 -6
  83. package/src/client/index.tsx +2 -1
  84. package/src/index.ts +1 -0
  85. package/src/shared/components/Button/style.scss +1 -0
  86. package/src/shared/components/Checkbox/index.tsx +3 -3
  87. package/src/shared/components/Input/index.tsx +3 -3
  88. package/src/shared/components/Modal/base-theme.scss +1 -1
  89. package/src/shared/components/Modal/index.tsx +24 -5
  90. package/src/shared/components/Modal/styles.scss +2 -4
  91. package/src/shared/components/TextArea/index.tsx +5 -0
  92. package/src/shared/components/TextArea/style.scss +8 -0
  93. package/src/shared/components/YouTubeVideo/base.scss +3 -1
  94. package/src/shared/components/YouTubeVideo/index.tsx +2 -3
  95. package/src/shared/components/index.ts +2 -2
  96. package/src/shared/components/selectors/CustomDropdown/Options/index.tsx +117 -0
  97. package/src/shared/components/selectors/CustomDropdown/Options/style.scss +6 -0
  98. package/src/shared/components/selectors/CustomDropdown/index.tsx +115 -0
  99. package/src/shared/components/selectors/CustomDropdown/theme.scss +90 -0
  100. package/src/shared/components/{Dropdown → selectors/NativeDropdown}/index.tsx +21 -50
  101. package/src/shared/components/{Dropdown → selectors/NativeDropdown}/theme.scss +5 -0
  102. package/src/shared/components/selectors/Switch/index.tsx +94 -0
  103. package/src/shared/components/selectors/Switch/theme.scss +39 -0
  104. package/src/shared/components/selectors/common.ts +54 -0
  105. package/src/shared/components/selectors/index.ts +3 -0
  106. package/build/development/shared/components/Dropdown/index.js.map +0 -1
  107. package/build/development/shared/components/ScalableRect/index.js +0 -80
  108. package/build/development/shared/components/ScalableRect/index.js.map +0 -1
  109. package/build/production/shared/components/Dropdown/index.js +0 -24
  110. package/build/production/shared/components/Dropdown/index.js.map +0 -1
  111. package/build/production/shared/components/ScalableRect/index.js +0 -21
  112. package/build/production/shared/components/ScalableRect/index.js.map +0 -1
  113. package/build/types-code/shared/components/Dropdown/index.d.ts +0 -17
  114. package/build/types-code/shared/components/ScalableRect/index.d.ts +0 -19
  115. package/build/types-scss/src/shared/components/ScalableRect/style.scss.d.ts +0 -2
  116. package/src/shared/components/ScalableRect/index.tsx +0 -84
  117. package/src/shared/components/ScalableRect/style.scss +0 -10
@@ -1,6 +1,7 @@
1
1
  import { type ComponentType } from 'react';
2
2
  type OptionsT = {
3
3
  dontHydrate?: boolean;
4
+ initialState?: any;
4
5
  };
5
6
  /**
6
7
  * Prepares and launches the app at client side.
@@ -4,7 +4,7 @@ declare const server: typeof ServerT | null;
4
4
  declare const client: any;
5
5
  export { default as api } from 'axios';
6
6
  export * as PT from 'prop-types';
7
- export { type AsyncCollectionLoaderT, type AsyncDataEnvelopeT, type AsyncDataLoaderT, type ForceT, type UseAsyncDataOptionsT, type UseAsyncDataResT, type UseGlobalStateResT, type ValueOrInitializerT, getGlobalState, GlobalStateProvider, useAsyncCollection, useAsyncData, useGlobalState, withGlobalStateType, } from '@dr.pogodin/react-global-state';
7
+ export { type AsyncCollectionLoaderT, type AsyncDataEnvelopeT, type AsyncDataLoaderT, type ForceT, type UseAsyncDataOptionsT, type UseAsyncDataResT, type UseGlobalStateResT, type ValueOrInitializerT, getGlobalState, GlobalStateProvider, newAsyncDataEnvelope, useAsyncCollection, useAsyncData, useGlobalState, withGlobalStateType, } from '@dr.pogodin/react-global-state';
8
8
  export * from './shared/components';
9
9
  export { type Theme, config, Barrier, Emitter, isomorphy, getSsrContext, JU, Semaphore, splitComponent, themed, ThemeProvider, time, webpack, withRetries, } from './shared/utils';
10
10
  export { client, server };
@@ -3,7 +3,7 @@ import { type Theme } from '@dr.pogodin/react-themes';
3
3
  declare const validThemeKeys: readonly ["checkbox", "container", "label"];
4
4
  type PropT = {
5
5
  checked?: boolean;
6
- label?: string;
6
+ label?: React.ReactNode;
7
7
  onChange?: React.ChangeEventHandler<HTMLInputElement>;
8
8
  theme: Theme<typeof validThemeKeys>;
9
9
  };
@@ -2,7 +2,7 @@
2
2
  import { type Theme } from '@dr.pogodin/react-themes';
3
3
  declare const validThemeKeys: readonly ["container", "input", "label"];
4
4
  declare const ThemedInput: import("@dr.pogodin/react-themes").ThemedComponent<React.InputHTMLAttributes<HTMLInputElement> & {
5
- label?: string | undefined;
5
+ label?: React.ReactNode;
6
6
  theme: Theme<typeof validThemeKeys>;
7
7
  } & React.RefAttributes<HTMLInputElement>>;
8
8
  export default ThemedInput;
@@ -1,9 +1,10 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import { type Theme } from '@dr.pogodin/react-themes';
3
- import './styles.scss';
4
3
  declare const validThemeKeys: readonly ["container", "overlay"];
5
4
  type PropsT = {
6
5
  children?: ReactNode;
6
+ containerStyle?: React.CSSProperties;
7
+ dontDisableScrolling?: boolean;
7
8
  onCancel?: () => void;
8
9
  theme: Theme<typeof validThemeKeys>;
9
10
  };
@@ -2,6 +2,7 @@
2
2
  import { type Theme } from '@dr.pogodin/react-themes';
3
3
  declare const validThemeKeys: readonly ["container", "hidden", "textarea"];
4
4
  type Props = {
5
+ disabled?: boolean;
5
6
  onChange?: React.ChangeEventHandler<HTMLTextAreaElement>;
6
7
  onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>;
7
8
  placeholder?: string;
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * Just an aggregation of all exported components into a single module.
3
3
  */
4
+ export * from './selectors';
4
5
  export { default as Button } from './Button';
5
6
  export { default as Checkbox } from './Checkbox';
6
- export { default as Dropdown } from './Dropdown';
7
7
  export { default as Input } from './Input';
8
8
  export { default as Link } from './Link';
9
9
  export { default as PageLayout } from './PageLayout';
10
10
  export { default as MetaTags } from './MetaTags';
11
11
  export { default as Modal, BaseModal } from './Modal';
12
12
  export { default as NavLink } from './NavLink';
13
- export { default as ScalableRect } from './ScalableRect';
14
13
  export { default as Throbber } from './Throbber';
15
14
  export { default as WithTooltip } from './WithTooltip';
16
15
  export { default as YouTubeVideo } from './YouTubeVideo';
@@ -0,0 +1,17 @@
1
+ /// <reference types="react" />
2
+ import { type OptionT, type OptionsT } from '../../common';
3
+ type PropsT = {
4
+ anchorRect: {
5
+ bottom: number;
6
+ left: number;
7
+ width: number;
8
+ };
9
+ containerClass: string;
10
+ filter?: (item: OptionT<React.ReactNode> | string) => boolean;
11
+ optionClass: string;
12
+ options: OptionsT<React.ReactNode>;
13
+ onCancel: () => void;
14
+ onChange: (value: string) => void;
15
+ };
16
+ declare const Options: React.FunctionComponent<PropsT>;
17
+ export default Options;
@@ -0,0 +1,4 @@
1
+ /// <reference types="react" />
2
+ import { type PropsT } from '../common';
3
+ declare const ThemedCustomDropdown: import("@dr.pogodin/react-themes").ThemedComponent<PropsT<React.ReactNode, (value: string) => void>>;
4
+ export default ThemedCustomDropdown;
@@ -0,0 +1,3 @@
1
+ import { type PropsT } from '../common';
2
+ declare const ThemedDropdown: import("@dr.pogodin/react-themes").ThemedComponent<PropsT<string>>;
3
+ export default ThemedDropdown;
@@ -0,0 +1,13 @@
1
+ /// <reference types="react" />
2
+ import { type Theme } from '@dr.pogodin/react-themes';
3
+ import { type OptionsT } from '../common';
4
+ declare const validThemeKeys: readonly ["container", "label", "option", "selected", "switch"];
5
+ type PropsT = {
6
+ label?: React.ReactNode;
7
+ onChange?: (value: string) => void;
8
+ options?: Readonly<OptionsT<React.ReactNode>>;
9
+ theme: Theme<typeof validThemeKeys>;
10
+ value?: string;
11
+ };
12
+ declare const ThemedSwitch: import("@dr.pogodin/react-themes").ThemedComponent<PropsT>;
13
+ export default ThemedSwitch;
@@ -0,0 +1,27 @@
1
+ /// <reference types="react" />
2
+ import PT from 'prop-types';
3
+ import type { Theme } from '@dr.pogodin/react-themes';
4
+ export declare const validThemeKeys: readonly ["active", "arrow", "container", "dropdown", "hiddenOption", "label", "option", "select"];
5
+ export type OptionT<NameT> = {
6
+ name?: NameT | null;
7
+ value: string;
8
+ };
9
+ export type OptionsT<NameT> = Array<OptionT<NameT> | string>;
10
+ export type PropsT<NameT, OnChangeT = React.ChangeEventHandler<HTMLSelectElement>> = {
11
+ filter?: (item: OptionT<NameT> | string) => boolean;
12
+ label?: React.ReactNode;
13
+ onChange?: OnChangeT;
14
+ options?: OptionsT<NameT>;
15
+ theme: Theme<typeof validThemeKeys>;
16
+ value?: string;
17
+ };
18
+ export declare const optionValidator: PT.Requireable<NonNullable<string | NonNullable<PT.InferProps<{
19
+ name: PT.Requireable<string>;
20
+ value: PT.Validator<string>;
21
+ }>>>>;
22
+ export declare const optionsValidator: PT.Requireable<NonNullable<NonNullable<string | NonNullable<PT.InferProps<{
23
+ name: PT.Requireable<string>;
24
+ value: PT.Validator<string>;
25
+ }>>>>[]>;
26
+ /** Returns option value and name as a tuple. */
27
+ export declare function optionValueName<NameT>(option: OptionT<NameT> | string): [string, NameT | string];
@@ -0,0 +1,3 @@
1
+ export { default as CustomDropdown } from './CustomDropdown';
2
+ export { default as Dropdown } from './NativeDropdown';
3
+ export { default as Switch } from './Switch';
@@ -0,0 +1 @@
1
+ export declare const scrollingDisabledByModal: string;
@@ -0,0 +1 @@
1
+ export declare const overlay: string;
@@ -0,0 +1,10 @@
1
+ export declare const active: string;
2
+ export declare const ad: string;
3
+ export declare const arrow: string;
4
+ export declare const container: string;
5
+ export declare const context: string;
6
+ export declare const dropdown: string;
7
+ export declare const hoc: string;
8
+ export declare const label: string;
9
+ export declare const option: string;
10
+ export declare const select: string;
@@ -1,3 +1,4 @@
1
+ export declare const active: string;
1
2
  export declare const ad: string;
2
3
  export declare const arrow: string;
3
4
  export declare const container: string;
@@ -0,0 +1,6 @@
1
+ export declare const ad: string;
2
+ export declare const container: string;
3
+ export declare const context: string;
4
+ export declare const hoc: string;
5
+ export declare const option: string;
6
+ export declare const selected: string;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.30.2",
2
+ "version": "1.31.0",
3
3
  "bin": {
4
4
  "react-utils-build": "bin/build.js",
5
5
  "react-utils-setup": "bin/setup.js"
@@ -69,12 +69,12 @@
69
69
  "@types/csurf": "^1.11.5",
70
70
  "@types/express": "^4.17.21",
71
71
  "@types/jest": "^29.5.12",
72
- "@types/lodash": "^4.14.202",
72
+ "@types/lodash": "^4.17.0",
73
73
  "@types/morgan": "^1.9.9",
74
74
  "@types/node-forge": "^1.3.11",
75
75
  "@types/pretty": "^2.0.3",
76
- "@types/react": "^18.2.64",
77
- "@types/react-dom": "^18.2.21",
76
+ "@types/react": "^18.2.66",
77
+ "@types/react-dom": "^18.2.22",
78
78
  "@types/react-helmet": "^6.1.11",
79
79
  "@types/react-test-renderer": "^18.0.7",
80
80
  "@types/request-ip": "^0.0.41",
@@ -114,7 +114,7 @@
114
114
  "react-test-renderer": "^18.2.0",
115
115
  "regenerator-runtime": "^0.14.1",
116
116
  "resolve-url-loader": "^5.0.0",
117
- "sass": "^1.71.1",
117
+ "sass": "^1.72.0",
118
118
  "sass-loader": "^14.1.1",
119
119
  "sitemap": "^7.1.1",
120
120
  "stylelint": "^16.2.1",
@@ -123,7 +123,7 @@
123
123
  "tsc-alias": "^1.8.8",
124
124
  "typed-scss-modules": "^8.0.0",
125
125
  "typescript": "^5.4.2",
126
- "typescript-eslint": "^7.1.1",
126
+ "typescript-eslint": "^7.2.0",
127
127
  "webpack": "^5.90.3",
128
128
  "webpack-dev-middleware": "^7.0.0",
129
129
  "webpack-hot-middleware": "^2.26.1",
@@ -12,6 +12,7 @@ import getInj from './getInj';
12
12
 
13
13
  type OptionsT = {
14
14
  dontHydrate?: boolean;
15
+ initialState?: any;
15
16
  };
16
17
 
17
18
  /**
@@ -26,7 +27,7 @@ export default function Launch(
26
27
  const container = document.getElementById('react-view');
27
28
  if (!container) throw Error('Failed to find container for React app');
28
29
  const scene = (
29
- <GlobalStateProvider initialState={getInj().ISTATE}>
30
+ <GlobalStateProvider initialState={getInj().ISTATE || options.initialState}>
30
31
  <BrowserRouter future={{ v7_relativeSplatPath: true }}>
31
32
  <Application />
32
33
  </BrowserRouter>
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export {
22
22
  type ValueOrInitializerT,
23
23
  getGlobalState,
24
24
  GlobalStateProvider,
25
+ newAsyncDataEnvelope,
25
26
  useAsyncCollection,
26
27
  useAsyncData,
27
28
  useGlobalState,
@@ -17,6 +17,7 @@
17
17
  padding: 0.3em 1.2em;
18
18
  text-align: center;
19
19
  text-decoration: none;
20
+ user-select: none;
20
21
 
21
22
  &:visited {
22
23
  color: inherit;
@@ -8,7 +8,7 @@ const validThemeKeys = ['checkbox', 'container', 'label'] as const;
8
8
 
9
9
  type PropT = {
10
10
  checked?: boolean;
11
- label?: string;
11
+ label?: React.ReactNode;
12
12
  onChange?: React.ChangeEventHandler<HTMLInputElement>;
13
13
  theme: Theme<typeof validThemeKeys>;
14
14
  };
@@ -20,7 +20,7 @@ const Checkbox: React.FunctionComponent<PropT> = ({
20
20
  theme,
21
21
  }) => (
22
22
  <div className={theme.container}>
23
- { label === undefined ? null : <p className={theme.label}>{label}</p> }
23
+ { label === undefined ? null : <div className={theme.label}>{label}</div> }
24
24
  <input
25
25
  checked={checked}
26
26
  className={theme.checkbox}
@@ -56,7 +56,7 @@ const ThemedCheckbox = themed(
56
56
  */
57
57
  Checkbox.propTypes = {
58
58
  checked: PT.bool,
59
- label: PT.string,
59
+ label: PT.node,
60
60
  onChange: PT.func,
61
61
  theme: ThemedCheckbox.themeType.isRequired,
62
62
  };
@@ -12,7 +12,7 @@ const validThemeKeys = [
12
12
  ] as const;
13
13
 
14
14
  type PropsT = React.InputHTMLAttributes<HTMLInputElement> & {
15
- label?: string;
15
+ label?: React.ReactNode;
16
16
  theme: Theme<typeof validThemeKeys>;
17
17
  };
18
18
 
@@ -34,7 +34,7 @@ const Input = forwardRef<HTMLInputElement, PropsT>((
34
34
  ref,
35
35
  ) => (
36
36
  <span className={theme.container}>
37
- { label === undefined ? null : <p className={theme.label}>{label}</p> }
37
+ { label === undefined ? null : <div className={theme.label}>{label}</div> }
38
38
  <input
39
39
  className={theme.input}
40
40
  ref={ref}
@@ -46,7 +46,7 @@ const Input = forwardRef<HTMLInputElement, PropsT>((
46
46
  const ThemedInput = themed(Input, 'Input', validThemeKeys, defaultTheme);
47
47
 
48
48
  Input.propTypes = {
49
- label: PT.string,
49
+ label: PT.node,
50
50
  theme: ThemedInput.themeType.isRequired,
51
51
  };
52
52
 
@@ -19,7 +19,7 @@
19
19
  &.container {
20
20
  background: #fff;
21
21
  box-shadow: 0 0 14px 1px rgba(38 38 40 / 15%);
22
- border-radius: 4;
22
+ border-radius: 0.3em;
23
23
  max-height: 95vh;
24
24
  max-width: $screen-md;
25
25
  overflow: hidden;
@@ -15,12 +15,14 @@ import PT from 'prop-types';
15
15
  import themed, { type Theme } from '@dr.pogodin/react-themes';
16
16
 
17
17
  import baseTheme from './base-theme.scss';
18
- import './styles.scss';
18
+ import S from './styles.scss';
19
19
 
20
20
  const validThemeKeys = ['container', 'overlay'] as const;
21
21
 
22
22
  type PropsT = {
23
23
  children?: ReactNode;
24
+ containerStyle?: React.CSSProperties;
25
+ dontDisableScrolling?: boolean;
24
26
  onCancel?: () => void;
25
27
  theme: Theme<typeof validThemeKeys>;
26
28
  };
@@ -38,6 +40,8 @@ type PropsT = {
38
40
  */
39
41
  const BaseModal: React.FunctionComponent<PropsT> = ({
40
42
  children,
43
+ containerStyle,
44
+ dontDisableScrolling,
41
45
  onCancel,
42
46
  theme,
43
47
  }) => {
@@ -47,15 +51,25 @@ const BaseModal: React.FunctionComponent<PropsT> = ({
47
51
 
48
52
  useEffect(() => {
49
53
  const p = document.createElement('div');
50
- document.body.classList.add('scrolling-disabled-by-modal');
51
54
  document.body.appendChild(p);
52
55
  setPortal(p);
53
56
  return () => {
54
- document.body.classList.remove('scrolling-disabled-by-modal');
55
57
  document.body.removeChild(p);
56
58
  };
57
59
  }, []);
58
60
 
61
+ // Disables window scrolling, if it is not opted-out.
62
+ useEffect(() => {
63
+ if (!dontDisableScrolling) {
64
+ document.body.classList.add(S.scrollingDisabledByModal);
65
+ }
66
+ return () => {
67
+ if (!dontDisableScrolling) {
68
+ document.body.classList.remove(S.scrollingDisabledByModal);
69
+ }
70
+ };
71
+ }, [dontDisableScrolling]);
72
+
59
73
  const focusLast = useMemo(() => (
60
74
  <div
61
75
  onFocus={() => {
@@ -98,6 +112,7 @@ const BaseModal: React.FunctionComponent<PropsT> = ({
98
112
  onWheel={(event) => event.stopPropagation()}
99
113
  ref={containerRef}
100
114
  role="dialog"
115
+ style={containerStyle}
101
116
  >
102
117
  {children}
103
118
  </div>
@@ -124,14 +139,18 @@ const ThemedModal = themed(
124
139
  );
125
140
 
126
141
  BaseModal.propTypes = {
127
- onCancel: PT.func,
128
142
  children: PT.node,
143
+ containerStyle: PT.shape({}),
144
+ dontDisableScrolling: PT.bool,
145
+ onCancel: PT.func,
129
146
  theme: ThemedModal.themeType.isRequired,
130
147
  };
131
148
 
132
149
  BaseModal.defaultProps = {
133
- onCancel: noop,
134
150
  children: null,
151
+ containerStyle: undefined,
152
+ dontDisableScrolling: false,
153
+ onCancel: noop,
135
154
  };
136
155
 
137
156
  export default ThemedModal;
@@ -1,5 +1,3 @@
1
- :global {
2
- body.scrolling-disabled-by-modal {
3
- overflow: hidden;
4
- }
1
+ .scrollingDisabledByModal {
2
+ overflow: hidden;
5
3
  }
@@ -12,6 +12,7 @@ const validThemeKeys = [
12
12
  ] as const;
13
13
 
14
14
  type Props = {
15
+ disabled?: boolean;
15
16
  onChange?: React.ChangeEventHandler<HTMLTextAreaElement>;
16
17
  onKeyDown?: React.KeyboardEventHandler<HTMLTextAreaElement>;
17
18
  placeholder?: string;
@@ -20,6 +21,7 @@ type Props = {
20
21
  };
21
22
 
22
23
  const TextArea: React.FunctionComponent<Props> = ({
24
+ disabled,
23
25
  onChange,
24
26
  onKeyDown,
25
27
  placeholder,
@@ -66,6 +68,7 @@ const TextArea: React.FunctionComponent<Props> = ({
66
68
  value={localValue}
67
69
  />
68
70
  <textarea
71
+ disabled={disabled}
69
72
  // When value is "undefined" the text area is not-managed, and we should
70
73
  // manage it internally for the measurement / resizing functionality
71
74
  // to work.
@@ -90,6 +93,7 @@ const ThemedTextArea = themed(
90
93
  );
91
94
 
92
95
  TextArea.propTypes = {
96
+ disabled: PT.bool,
93
97
  onChange: PT.func,
94
98
  onKeyDown: PT.func,
95
99
  placeholder: PT.string,
@@ -98,6 +102,7 @@ TextArea.propTypes = {
98
102
  };
99
103
 
100
104
  TextArea.defaultProps = {
105
+ disabled: false,
101
106
  onChange: undefined,
102
107
  onKeyDown: undefined,
103
108
  placeholder: '',
@@ -1,3 +1,4 @@
1
+ @use "sass:color";
1
2
  @import "styles/mixins";
2
3
 
3
4
  *,
@@ -28,6 +29,13 @@
28
29
  &::placeholder {
29
30
  color: gray;
30
31
  }
32
+
33
+ &:disabled {
34
+ border-color: color.adjust($color: gray, $alpha: -0.66);
35
+ cursor: not-allowed;
36
+ color: color.adjust($color: gray, $alpha: -0.66);
37
+ user-select: none;
38
+ }
31
39
  }
32
40
 
33
41
  &.hidden {
@@ -2,12 +2,14 @@
2
2
  .context,
3
3
  .ad.hoc {
4
4
  .container {
5
+ aspect-ratio: 16 / 9;
5
6
  background: whitesmoke;
7
+ position: relative;
6
8
  }
7
9
 
8
10
  .video {
9
11
  height: 100%;
10
- position: relative;
12
+ position: absolute;
11
13
  width: 100%;
12
14
  }
13
15
  }
@@ -3,7 +3,6 @@ import qs from 'qs';
3
3
 
4
4
  import themed, { type Theme } from '@dr.pogodin/react-themes';
5
5
 
6
- import ScalableRect from 'components/ScalableRect';
7
6
  import Throbber from 'components/Throbber';
8
7
 
9
8
  import baseTheme from './base.scss';
@@ -57,7 +56,7 @@ const YouTubeVideo: React.FunctionComponent<PropsT> = ({
57
56
  // More query parameters can be exposed via the component props.
58
57
 
59
58
  return (
60
- <ScalableRect className={theme.container} ratio="16:9">
59
+ <div className={theme.container}>
61
60
  <Throbber theme={throbberTheme} />
62
61
  <iframe
63
62
  allow="autoplay"
@@ -66,7 +65,7 @@ const YouTubeVideo: React.FunctionComponent<PropsT> = ({
66
65
  src={url}
67
66
  title={title}
68
67
  />
69
- </ScalableRect>
68
+ </div>
70
69
  );
71
70
  };
72
71
 
@@ -2,16 +2,16 @@
2
2
  * Just an aggregation of all exported components into a single module.
3
3
  */
4
4
 
5
+ export * from 'components/selectors';
6
+
5
7
  export { default as Button } from 'components/Button';
6
8
  export { default as Checkbox } from 'components/Checkbox';
7
- export { default as Dropdown } from 'components/Dropdown';
8
9
  export { default as Input } from 'components/Input';
9
10
  export { default as Link } from 'components/Link';
10
11
  export { default as PageLayout } from 'components/PageLayout';
11
12
  export { default as MetaTags } from 'components/MetaTags';
12
13
  export { default as Modal, BaseModal } from 'components/Modal';
13
14
  export { default as NavLink } from 'components/NavLink';
14
- export { default as ScalableRect } from 'components/ScalableRect';
15
15
  export { default as Throbber } from 'components/Throbber';
16
16
  export { default as WithTooltip } from 'components/WithTooltip';
17
17
  export { default as YouTubeVideo } from 'components/YouTubeVideo';
@@ -0,0 +1,117 @@
1
+ import PT from 'prop-types';
2
+ import { useEffect } from 'react';
3
+
4
+ import { BaseModal } from 'components/Modal';
5
+
6
+ import S from './style.scss';
7
+
8
+ import {
9
+ type OptionT,
10
+ type OptionsT,
11
+ optionsValidator,
12
+ optionValueName,
13
+ } from '../../common';
14
+
15
+ type PropsT = {
16
+ anchorRect: {
17
+ bottom: number;
18
+ left: number;
19
+ width: number;
20
+ };
21
+ containerClass: string;
22
+ filter?: (item: OptionT<React.ReactNode> | string) => boolean;
23
+ optionClass: string;
24
+ options: OptionsT<React.ReactNode>;
25
+ onCancel: () => void;
26
+ onChange: (value: string) => void;
27
+ };
28
+
29
+ const Options: React.FunctionComponent<PropsT> = ({
30
+ anchorRect,
31
+ containerClass,
32
+ filter,
33
+ onCancel,
34
+ onChange,
35
+ optionClass,
36
+ options,
37
+ }) => {
38
+ // Closes the dropdown (cancels the selection) on any page scrolling attempt.
39
+ // This is the same native <select> elements do on scrolling, and at least for
40
+ // now we have no reason to deal with complications needed to support open
41
+ // dropdowns during the scrolling (that would need to re-position it in
42
+ // response to the position changes of the root dropdown element).
43
+ useEffect(() => {
44
+ const listener = () => {
45
+ onCancel();
46
+ };
47
+ window.addEventListener('scroll', listener);
48
+ return () => {
49
+ window.removeEventListener('scroll', listener);
50
+ };
51
+ }, [onCancel]);
52
+
53
+ const optionNodes: React.ReactNode[] = [];
54
+ for (let i = 0; i < options.length; ++i) {
55
+ const option = options[i];
56
+ if (!filter || filter(option)) {
57
+ const [iValue, iName] = optionValueName(option);
58
+ optionNodes.push(
59
+ <div
60
+ className={optionClass}
61
+ onClick={() => onChange(iValue)}
62
+ onKeyDown={(e) => {
63
+ if (e.key === 'Enter') {
64
+ onChange(iValue);
65
+ }
66
+ }}
67
+ key={iValue}
68
+ role="button"
69
+ tabIndex={0}
70
+ >
71
+ {iName}
72
+ </div>,
73
+ );
74
+ }
75
+ }
76
+
77
+ return (
78
+ <BaseModal
79
+ containerStyle={{
80
+ left: anchorRect.left,
81
+ top: anchorRect.bottom,
82
+ width: anchorRect.width,
83
+ }}
84
+ dontDisableScrolling
85
+ onCancel={onCancel}
86
+ theme={{
87
+ ad: '',
88
+ hoc: '',
89
+ container: containerClass,
90
+ context: '',
91
+ overlay: S.overlay,
92
+ }}
93
+ >
94
+ {optionNodes}
95
+ </BaseModal>
96
+ );
97
+ };
98
+
99
+ Options.propTypes = {
100
+ anchorRect: PT.shape({
101
+ bottom: PT.number.isRequired,
102
+ left: PT.number.isRequired,
103
+ width: PT.number.isRequired,
104
+ }).isRequired,
105
+ containerClass: PT.string.isRequired,
106
+ filter: PT.func,
107
+ onCancel: PT.func.isRequired,
108
+ onChange: PT.func.isRequired,
109
+ optionClass: PT.string.isRequired,
110
+ options: optionsValidator.isRequired,
111
+ };
112
+
113
+ Options.defaultProps = {
114
+ filter: undefined,
115
+ };
116
+
117
+ export default Options;