@dr.pogodin/react-utils 1.42.0 → 1.42.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/babel.config.js +3 -0
- package/bin/build.js +5 -8
- package/bin/setup.js +2 -1
- package/build/development/client/getInj.js +7 -2
- package/build/development/client/getInj.js.map +1 -1
- package/build/development/client/index.js +1 -2
- package/build/development/client/index.js.map +1 -1
- package/build/development/client/init.js +16 -13
- package/build/development/client/init.js.map +1 -1
- package/build/development/index.js +4 -1
- package/build/development/index.js.map +1 -1
- package/build/development/server/Cache.js +5 -9
- package/build/development/server/Cache.js.map +1 -1
- package/build/development/server/index.js +14 -11
- package/build/development/server/index.js.map +1 -1
- package/build/development/server/renderer.js +36 -40
- package/build/development/server/renderer.js.map +1 -1
- package/build/development/server/server.js +19 -12
- package/build/development/server/server.js.map +1 -1
- package/build/development/server/utils/errors.js.map +1 -1
- package/build/development/shared/components/Button/index.js +2 -3
- package/build/development/shared/components/Button/index.js.map +1 -1
- package/build/development/shared/components/Checkbox/index.js +3 -1
- package/build/development/shared/components/Checkbox/index.js.map +1 -1
- package/build/development/shared/components/GenericLink/index.js +13 -6
- package/build/development/shared/components/GenericLink/index.js.map +1 -1
- package/build/development/shared/components/Input/index.js +5 -1
- package/build/development/shared/components/Input/index.js.map +1 -1
- package/build/development/shared/components/Link.js +5 -6
- package/build/development/shared/components/Link.js.map +1 -1
- package/build/development/shared/components/MetaTags.js +31 -24
- package/build/development/shared/components/MetaTags.js.map +1 -1
- package/build/development/shared/components/Modal/index.js +13 -6
- package/build/development/shared/components/Modal/index.js.map +1 -1
- package/build/development/shared/components/NavLink.js +6 -6
- package/build/development/shared/components/NavLink.js.map +1 -1
- package/build/development/shared/components/TextArea/index.js +6 -3
- package/build/development/shared/components/TextArea/index.js.map +1 -1
- package/build/development/shared/components/WithTooltip/Tooltip.js +35 -39
- package/build/development/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/development/shared/components/WithTooltip/index.js +27 -21
- package/build/development/shared/components/WithTooltip/index.js.map +1 -1
- package/build/development/shared/components/YouTubeVideo/index.js +4 -3
- package/build/development/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/Options/index.js +4 -5
- package/build/development/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
- package/build/development/shared/components/selectors/CustomDropdown/index.js +7 -9
- package/build/development/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/NativeDropdown/index.js +3 -5
- package/build/development/shared/components/selectors/NativeDropdown/index.js.map +1 -1
- package/build/development/shared/components/selectors/Switch/index.js +21 -20
- package/build/development/shared/components/selectors/Switch/index.js.map +1 -1
- package/build/development/shared/utils/config.js +6 -3
- package/build/development/shared/utils/config.js.map +1 -1
- package/build/development/shared/utils/globalState.js +6 -0
- package/build/development/shared/utils/globalState.js.map +1 -1
- package/build/development/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/development/shared/utils/isomorphy/environment-check.js +6 -1
- package/build/development/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/development/shared/utils/isomorphy/index.js +1 -3
- package/build/development/shared/utils/isomorphy/index.js.map +1 -1
- package/build/development/shared/utils/jest/E2eSsrEnv.js +26 -17
- package/build/development/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/development/shared/utils/jest/global.js +0 -7
- package/build/development/shared/utils/jest/global.js.map +1 -1
- package/build/development/shared/utils/jest/index.js +15 -4
- package/build/development/shared/utils/jest/index.js.map +1 -1
- package/build/development/shared/utils/splitComponent.js +37 -19
- package/build/development/shared/utils/splitComponent.js.map +1 -1
- package/build/development/shared/utils/time.js +3 -3
- package/build/development/shared/utils/time.js.map +1 -1
- package/build/development/shared/utils/webpack.js +11 -11
- package/build/development/shared/utils/webpack.js.map +1 -1
- package/build/development/web.bundle.js +30 -30
- package/build/production/client/getInj.js +4 -2
- package/build/production/client/getInj.js.map +1 -1
- package/build/production/client/index.js +2 -2
- package/build/production/client/index.js.map +1 -1
- package/build/production/client/init.js +4 -2
- package/build/production/client/init.js.map +1 -1
- package/build/production/index.js +2 -1
- package/build/production/index.js.map +1 -1
- package/build/production/server/Cache.js +1 -2
- package/build/production/server/Cache.js.map +1 -1
- package/build/production/server/index.js +9 -5
- package/build/production/server/index.js.map +1 -1
- package/build/production/server/renderer.js +24 -21
- package/build/production/server/renderer.js.map +1 -1
- package/build/production/server/server.js +13 -8
- package/build/production/server/server.js.map +1 -1
- package/build/production/server/utils/errors.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/GenericLink/index.js +6 -4
- package/build/production/shared/components/GenericLink/index.js.map +1 -1
- package/build/production/shared/components/Input/index.js +3 -1
- package/build/production/shared/components/Input/index.js.map +1 -1
- package/build/production/shared/components/Link.js +3 -1
- package/build/production/shared/components/Link.js.map +1 -1
- package/build/production/shared/components/MetaTags.js +5 -2
- package/build/production/shared/components/MetaTags.js.map +1 -1
- package/build/production/shared/components/Modal/index.js +6 -2
- package/build/production/shared/components/Modal/index.js.map +1 -1
- package/build/production/shared/components/NavLink.js +4 -1
- package/build/production/shared/components/NavLink.js.map +1 -1
- package/build/production/shared/components/TextArea/index.js +4 -4
- package/build/production/shared/components/TextArea/index.js.map +1 -1
- package/build/production/shared/components/WithTooltip/Tooltip.js +37 -38
- package/build/production/shared/components/WithTooltip/Tooltip.js.map +1 -1
- package/build/production/shared/components/WithTooltip/index.js +4 -4
- package/build/production/shared/components/WithTooltip/index.js.map +1 -1
- package/build/production/shared/components/YouTubeVideo/index.js +3 -2
- package/build/production/shared/components/YouTubeVideo/index.js.map +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/Options/index.js +2 -2
- package/build/production/shared/components/selectors/CustomDropdown/Options/index.js.map +1 -1
- package/build/production/shared/components/selectors/CustomDropdown/index.js +2 -2
- package/build/production/shared/components/selectors/CustomDropdown/index.js.map +1 -1
- package/build/production/shared/components/selectors/NativeDropdown/index.js +2 -2
- 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/production/shared/utils/config.js +6 -4
- package/build/production/shared/utils/config.js.map +1 -1
- package/build/production/shared/utils/globalState.js +4 -1
- package/build/production/shared/utils/globalState.js.map +1 -1
- package/build/production/shared/utils/isomorphy/buildInfo.js.map +1 -1
- package/build/production/shared/utils/isomorphy/environment-check.js +5 -1
- package/build/production/shared/utils/isomorphy/environment-check.js.map +1 -1
- package/build/production/shared/utils/isomorphy/index.js +1 -3
- package/build/production/shared/utils/isomorphy/index.js.map +1 -1
- package/build/production/shared/utils/jest/E2eSsrEnv.js +15 -8
- package/build/production/shared/utils/jest/E2eSsrEnv.js.map +1 -1
- package/build/production/shared/utils/jest/global.js +1 -1
- package/build/production/shared/utils/jest/global.js.map +1 -1
- package/build/production/shared/utils/jest/index.js +13 -5
- package/build/production/shared/utils/jest/index.js.map +1 -1
- package/build/production/shared/utils/splitComponent.js +16 -8
- package/build/production/shared/utils/splitComponent.js.map +1 -1
- package/build/production/shared/utils/time.js +2 -2
- package/build/production/shared/utils/time.js.map +1 -1
- package/build/production/shared/utils/webpack.js +5 -2
- 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 +1 -1
- package/build/types-code/client/index.d.ts +2 -2
- package/build/types-code/client/init.d.ts +1 -1
- package/build/types-code/index.d.ts +6 -5
- package/build/types-code/server/Cache.d.ts +1 -2
- package/build/types-code/server/index.d.ts +4 -5
- package/build/types-code/server/renderer.d.ts +8 -10
- package/build/types-code/server/server.d.ts +3 -5
- package/build/types-code/server/utils/errors.d.ts +1 -1
- package/build/types-code/shared/components/Button/index.d.ts +2 -2
- package/build/types-code/shared/components/TextArea/index.d.ts +3 -3
- package/build/types-code/shared/components/WithTooltip/Tooltip.d.ts +7 -1
- package/build/types-code/shared/components/index.d.ts +12 -12
- package/build/types-code/shared/utils/config.d.ts +1 -1
- package/build/types-code/shared/utils/globalState.d.ts +3 -6
- package/build/types-code/shared/utils/isomorphy/index.d.ts +1 -3
- package/build/types-code/shared/utils/jest/E2eSsrEnv.d.ts +1 -1
- package/build/types-code/shared/utils/jest/global.d.ts +4 -4
- package/build/types-code/shared/utils/jest/index.d.ts +2 -2
- package/build/types-code/shared/utils/webpack.d.ts +1 -1
- package/config/babel/node-ssr.d.ts +3 -2
- package/config/babel/node-ssr.js +5 -7
- package/config/babel/webpack.d.ts +3 -11
- package/config/babel/webpack.js +15 -15
- package/config/eslint/default.mjs +32 -0
- package/config/jest/default.js +10 -6
- package/config/jest/resolver.js +2 -0
- package/config/jest/setup.js +2 -2
- package/config/stylelint/default.js +3 -0
- package/config/webpack/app-base.d.ts +0 -6
- package/config/webpack/app-base.js +64 -70
- package/config/webpack/app-development.d.ts +2 -2
- package/config/webpack/app-development.js +8 -12
- package/config/webpack/app-production.js +1 -0
- package/config/webpack/lib-base.js +20 -18
- package/config/webpack/lib-development.js +1 -0
- package/config/webpack/lib-production.js +1 -0
- package/config/workbox/default.js +2 -5
- package/dev-styles.js +1 -0
- package/eslint.config.mjs +13 -0
- package/node-entry.js +7 -2
- package/null.js +1 -0
- package/package.json +17 -25
- package/prod-styles.js +1 -0
- package/src/client/getInj.ts +8 -3
- package/src/client/index.tsx +4 -4
- package/src/client/init.ts +18 -15
- package/src/index.ts +8 -3
- package/src/server/Cache.ts +7 -15
- package/src/server/index.ts +30 -21
- package/src/server/renderer.tsx +76 -66
- package/src/server/server.ts +38 -20
- package/src/server/utils/errors.ts +6 -3
- package/src/shared/components/Button/index.tsx +4 -5
- package/src/shared/components/Checkbox/index.tsx +10 -7
- package/src/shared/components/GenericLink/index.tsx +14 -7
- package/src/shared/components/Input/index.tsx +4 -1
- package/src/shared/components/Link.tsx +9 -5
- package/src/shared/components/MetaTags.tsx +21 -15
- package/src/shared/components/Modal/index.tsx +12 -11
- package/src/shared/components/NavLink.tsx +10 -5
- package/src/shared/components/TextArea/index.tsx +17 -9
- package/src/shared/components/WithTooltip/{Tooltip.tsx → Tooltip.ts} +35 -39
- package/src/shared/components/WithTooltip/index.tsx +36 -30
- package/src/shared/components/YouTubeVideo/index.tsx +10 -6
- package/src/shared/components/selectors/CustomDropdown/Options/index.tsx +5 -6
- package/src/shared/components/selectors/CustomDropdown/index.tsx +10 -14
- package/src/shared/components/selectors/NativeDropdown/index.tsx +5 -7
- package/src/shared/components/selectors/Switch/index.tsx +19 -17
- package/src/shared/utils/config.ts +12 -5
- package/src/shared/utils/globalState.ts +8 -5
- package/src/shared/utils/isomorphy/buildInfo.ts +1 -1
- package/src/shared/utils/isomorphy/environment-check.ts +5 -1
- package/src/shared/utils/isomorphy/index.ts +4 -6
- package/src/shared/utils/jest/E2eSsrEnv.ts +64 -39
- package/src/shared/utils/jest/global.ts +6 -8
- package/src/shared/utils/jest/{index.tsx → index.ts} +25 -12
- package/src/shared/utils/splitComponent.tsx +44 -25
- package/src/shared/utils/time.ts +16 -9
- package/src/shared/utils/webpack.ts +19 -14
- package/webpack.config.ts +36 -10
- package/config/eslint/default.json +0 -30
- package/config/eslint/jest.json +0 -19
- package/config/eslint/typescript.js +0 -46
package/src/server/server.ts
CHANGED
|
@@ -18,6 +18,7 @@ import csrf from '@dr.pogodin/csurf';
|
|
|
18
18
|
import express, {
|
|
19
19
|
type Express,
|
|
20
20
|
type NextFunction,
|
|
21
|
+
type RequestHandler,
|
|
21
22
|
type Request,
|
|
22
23
|
type Response,
|
|
23
24
|
} from 'express';
|
|
@@ -27,7 +28,8 @@ import helmet, { type HelmetOptions } from 'helmet';
|
|
|
27
28
|
import loggerMiddleware from 'morgan';
|
|
28
29
|
import requestIp from 'request-ip';
|
|
29
30
|
import { v4 as uuid } from 'uuid';
|
|
30
|
-
|
|
31
|
+
|
|
32
|
+
import type { Compiler, Configuration } from 'webpack';
|
|
31
33
|
|
|
32
34
|
import rendererFactory, {
|
|
33
35
|
type LoggerI,
|
|
@@ -44,6 +46,7 @@ import {
|
|
|
44
46
|
export type CspOptionsT =
|
|
45
47
|
Exclude<HelmetOptions['contentSecurityPolicy'], boolean | undefined>;
|
|
46
48
|
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
|
47
50
|
interface RequestT extends Request {
|
|
48
51
|
cspNonce: string;
|
|
49
52
|
nonce: string;
|
|
@@ -98,7 +101,9 @@ delete defaultCspSettings.directives['upgrade-insecure-requests'];
|
|
|
98
101
|
* with the exception of `nonce-xxx` clause in `script-src` directive,
|
|
99
102
|
* which is added dynamically for each request.
|
|
100
103
|
*/
|
|
101
|
-
export function getDefaultCspSettings() {
|
|
104
|
+
export function getDefaultCspSettings(): {
|
|
105
|
+
directives: Record<string, string[]>;
|
|
106
|
+
} {
|
|
102
107
|
return cloneDeep(defaultCspSettings);
|
|
103
108
|
}
|
|
104
109
|
|
|
@@ -107,7 +112,9 @@ export type ServerT = Express & {
|
|
|
107
112
|
};
|
|
108
113
|
|
|
109
114
|
export type OptionsT = RendererOptionsT & {
|
|
110
|
-
beforeExpressJsError?:
|
|
115
|
+
beforeExpressJsError?:
|
|
116
|
+
(server: ServerT) => boolean | Promise<boolean>;
|
|
117
|
+
|
|
111
118
|
beforeExpressJsSetup?: (server: ServerT) => Promise<void> | void;
|
|
112
119
|
cspSettingsHook?: (
|
|
113
120
|
defaultOptions: CspOptionsT,
|
|
@@ -121,7 +128,7 @@ export type OptionsT = RendererOptionsT & {
|
|
|
121
128
|
export default async function factory(
|
|
122
129
|
webpackConfig: Configuration,
|
|
123
130
|
options: OptionsT,
|
|
124
|
-
) {
|
|
131
|
+
): Promise<ServerT> {
|
|
125
132
|
const rendererOps: RendererOptionsT = pick(options, [
|
|
126
133
|
'Application',
|
|
127
134
|
'beforeRender',
|
|
@@ -150,9 +157,10 @@ export default async function factory(
|
|
|
150
157
|
if (schema === 'http') {
|
|
151
158
|
let url = `https://${req.headers.host}`;
|
|
152
159
|
if (req.originalUrl !== '/') url += req.originalUrl;
|
|
153
|
-
|
|
160
|
+
res.redirect(url);
|
|
161
|
+
return;
|
|
154
162
|
}
|
|
155
|
-
|
|
163
|
+
next();
|
|
156
164
|
});
|
|
157
165
|
}
|
|
158
166
|
|
|
@@ -222,7 +230,7 @@ export default async function factory(
|
|
|
222
230
|
// Thus, this setup to serve it. Probably, need some more configuration
|
|
223
231
|
// for special cases, but this will do for now.
|
|
224
232
|
server.get('/__service-worker.js', express.static(
|
|
225
|
-
webpackConfig.output?.path
|
|
233
|
+
webpackConfig.output?.path ?? '',
|
|
226
234
|
{
|
|
227
235
|
setHeaders: (res) => res.set('Cache-Control', 'no-cache'),
|
|
228
236
|
},
|
|
@@ -231,23 +239,31 @@ export default async function factory(
|
|
|
231
239
|
/* Setup of Hot Module Reloading for development environment.
|
|
232
240
|
* These dependencies are not used, nor installed for production use,
|
|
233
241
|
* hence we should violate some import-related lint rules. */
|
|
234
|
-
/* eslint-disable global-require */
|
|
235
242
|
/* eslint-disable import/no-extraneous-dependencies */
|
|
236
|
-
/* eslint-disable import/no-unresolved */
|
|
237
243
|
if (options.devMode) {
|
|
238
244
|
// This is a workaround for SASS bug:
|
|
239
245
|
// https://github.com/dart-lang/sdk/issues/27979
|
|
240
246
|
// which manifests itself sometimes when webpack dev middleware is used
|
|
241
247
|
// (in dev mode), and app modules are imported in some unfortunate ways.
|
|
248
|
+
// TODO: Double-check, what is going on here.
|
|
249
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
242
250
|
if (!global.location) {
|
|
243
251
|
global.location = {
|
|
244
252
|
href: `${pathToFileURL(process.cwd()).href}${sep}`,
|
|
245
253
|
} as Location;
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
256
|
+
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
257
|
+
const webpack = require('webpack') as (ops: Configuration) => Compiler;
|
|
258
|
+
|
|
259
|
+
// TODO: Figure out the exact type for options, don't wanna waste time on it
|
|
260
|
+
// right now.
|
|
261
|
+
const webpackDevMiddleware = require('webpack-dev-middleware') as
|
|
262
|
+
(c: Compiler, ops: unknown) => RequestHandler;
|
|
263
|
+
|
|
264
|
+
const webpackHotMiddleware = require('webpack-hot-middleware') as
|
|
265
|
+
(c: Compiler) => RequestHandler;
|
|
266
|
+
|
|
251
267
|
const compiler = webpack(webpackConfig);
|
|
252
268
|
server.use(webpackDevMiddleware(compiler, {
|
|
253
269
|
publicPath,
|
|
@@ -255,9 +271,7 @@ export default async function factory(
|
|
|
255
271
|
}));
|
|
256
272
|
server.use(webpackHotMiddleware(compiler));
|
|
257
273
|
}
|
|
258
|
-
/* eslint-enable global-require */
|
|
259
274
|
/* eslint-enable import/no-extraneous-dependencies */
|
|
260
|
-
/* eslint-enable import/no-unresolved */
|
|
261
275
|
|
|
262
276
|
server.use(publicPath as string, express.static(webpackConfig.output!.path!));
|
|
263
277
|
|
|
@@ -287,20 +301,25 @@ export default async function factory(
|
|
|
287
301
|
// prevents to do it without some extra refactoring. Should be done sometime
|
|
288
302
|
// though.
|
|
289
303
|
server.use((
|
|
290
|
-
error:
|
|
304
|
+
error: Error & {
|
|
305
|
+
status?: number;
|
|
306
|
+
},
|
|
291
307
|
req: Request,
|
|
292
308
|
res: Response,
|
|
293
309
|
next: NextFunction,
|
|
294
310
|
) => {
|
|
295
311
|
// TODO: This is needed to correctly handled any errors thrown after
|
|
296
312
|
// sending initial response to the client.
|
|
297
|
-
if (res.headersSent)
|
|
313
|
+
if (res.headersSent) {
|
|
314
|
+
next(error);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
298
317
|
|
|
299
|
-
const status = error.status
|
|
300
|
-
const serverSide = status >= CODES.INTERNAL_SERVER_ERROR;
|
|
318
|
+
const status = error.status ?? CODES.INTERNAL_SERVER_ERROR;
|
|
319
|
+
const serverSide = status >= (CODES.INTERNAL_SERVER_ERROR as number);
|
|
301
320
|
|
|
302
321
|
// Log server-side errors always, client-side at debug level only.
|
|
303
|
-
options.logger!.log(serverSide ? 'error' : 'debug', error);
|
|
322
|
+
options.logger!.log(serverSide ? 'error' : 'debug', error.toString());
|
|
304
323
|
|
|
305
324
|
let message = error.message || getErrorForCode(status);
|
|
306
325
|
if (serverSide && process.env.NODE_ENV === 'production') {
|
|
@@ -308,7 +327,6 @@ export default async function factory(
|
|
|
308
327
|
}
|
|
309
328
|
|
|
310
329
|
res.status(status).send(message);
|
|
311
|
-
return undefined;
|
|
312
330
|
});
|
|
313
331
|
}
|
|
314
332
|
|
|
@@ -89,7 +89,10 @@ class ErrorWithStatus extends Error {
|
|
|
89
89
|
* Server Error).
|
|
90
90
|
* @return {Error}
|
|
91
91
|
*/
|
|
92
|
-
export function newError(
|
|
92
|
+
export function newError(
|
|
93
|
+
message: string,
|
|
94
|
+
statusCode = CODES.INTERNAL_SERVER_ERROR,
|
|
95
|
+
): ErrorWithStatus {
|
|
93
96
|
const error = new ErrorWithStatus(message);
|
|
94
97
|
error.status = statusCode;
|
|
95
98
|
return error;
|
|
@@ -119,11 +122,11 @@ export function fail(
|
|
|
119
122
|
* Request).
|
|
120
123
|
*/
|
|
121
124
|
export function assert(
|
|
122
|
-
value:
|
|
125
|
+
value: unknown,
|
|
123
126
|
schema: joi.AnySchema,
|
|
124
127
|
message = '',
|
|
125
128
|
statusCode = CODES.BAD_REQUEST,
|
|
126
|
-
) {
|
|
129
|
+
): void {
|
|
127
130
|
const { error } = schema.validate(value, { abortEarly: false });
|
|
128
131
|
if (error) {
|
|
129
132
|
fail(message.concat(message ? '\n' : '', error.message), statusCode);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// The <Button> component implements a standard button / button-like link.
|
|
2
2
|
|
|
3
|
-
import type { PointerEventHandler, ReactNode } from 'react';
|
|
3
|
+
import type { FunctionComponent, PointerEventHandler, ReactNode } from 'react';
|
|
4
4
|
|
|
5
5
|
import Link from 'components/Link';
|
|
6
6
|
|
|
@@ -24,8 +24,7 @@ type PropsT = {
|
|
|
24
24
|
to?: object | string;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
export const BaseButton: React.FunctionComponent<PropsT> = ({
|
|
27
|
+
export const BaseButton: FunctionComponent<PropsT> = ({
|
|
29
28
|
active,
|
|
30
29
|
children,
|
|
31
30
|
disabled,
|
|
@@ -75,9 +74,9 @@ export const BaseButton: React.FunctionComponent<PropsT> = ({
|
|
|
75
74
|
className={className}
|
|
76
75
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
77
76
|
onClick={onClick}
|
|
78
|
-
onKeyDown={onClick
|
|
77
|
+
onKeyDown={onClick ? (e) => {
|
|
79
78
|
if (e.key === 'Enter') onClick(e);
|
|
80
|
-
}
|
|
79
|
+
} : undefined}
|
|
81
80
|
onMouseDown={onMouseDown}
|
|
82
81
|
onPointerDown={onPointerDown}
|
|
83
82
|
role="button"
|
|
@@ -9,11 +9,11 @@ type PropT<ValueT> = {
|
|
|
9
9
|
onChange?: React.ChangeEventHandler<HTMLInputElement>;
|
|
10
10
|
testId?: string;
|
|
11
11
|
theme: Theme<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
| 'checkbox'
|
|
13
|
+
| 'container'
|
|
14
|
+
| 'disabled'
|
|
15
|
+
| 'indeterminate'
|
|
16
|
+
| 'label'
|
|
17
17
|
>;
|
|
18
18
|
};
|
|
19
19
|
|
|
@@ -33,14 +33,17 @@ const Checkbox = <ValueT extends boolean | 'indeterminate' = boolean>({
|
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
35
|
<div className={containerClassName}>
|
|
36
|
-
{ label === undefined
|
|
36
|
+
{ label === undefined
|
|
37
|
+
? null : <div className={theme.label}>{label}</div> }
|
|
37
38
|
<input
|
|
38
39
|
checked={checked === undefined ? undefined : checked === true}
|
|
39
40
|
className={checkboxClassName}
|
|
40
41
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
41
42
|
disabled={disabled}
|
|
42
43
|
onChange={onChange}
|
|
43
|
-
onClick={(e) =>
|
|
44
|
+
onClick={(e) => {
|
|
45
|
+
e.stopPropagation();
|
|
46
|
+
}}
|
|
44
47
|
type="checkbox"
|
|
45
48
|
/>
|
|
46
49
|
</div>
|
|
@@ -87,7 +87,8 @@ const GenericLink = ({
|
|
|
87
87
|
* - It should be opened in a new tab;
|
|
88
88
|
* - It is an absolte URL (starts with http:// or https://);
|
|
89
89
|
* - It is anchor link (starts with #). */
|
|
90
|
-
if (disabled || enforceA || openNewTab
|
|
90
|
+
if (disabled || enforceA || openNewTab
|
|
91
|
+
|| (to as string).match(/^(#|(https?|mailto):)/)) {
|
|
91
92
|
return (
|
|
92
93
|
<a
|
|
93
94
|
className={className}
|
|
@@ -96,8 +97,12 @@ const GenericLink = ({
|
|
|
96
97
|
// styled as a link.
|
|
97
98
|
// disabled={disabled}
|
|
98
99
|
href={to as string}
|
|
99
|
-
onClick={disabled ? (e) =>
|
|
100
|
-
|
|
100
|
+
onClick={disabled ? (e) => {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
} : onClick}
|
|
103
|
+
onMouseDown={disabled ? (e) => {
|
|
104
|
+
e.preventDefault();
|
|
105
|
+
} : onMouseDown}
|
|
101
106
|
rel="noopener noreferrer"
|
|
102
107
|
styleName="link"
|
|
103
108
|
target={openNewTab ? '_blank' : ''}
|
|
@@ -114,9 +119,6 @@ const GenericLink = ({
|
|
|
114
119
|
className={className}
|
|
115
120
|
discover="none"
|
|
116
121
|
// disabled
|
|
117
|
-
onMouseDown={onMouseDown}
|
|
118
|
-
replace={replace}
|
|
119
|
-
to={to!}
|
|
120
122
|
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
121
123
|
// Executes the user-provided event handler, if any.
|
|
122
124
|
if (onClick) onClick(e);
|
|
@@ -124,7 +126,12 @@ const GenericLink = ({
|
|
|
124
126
|
// By default, clicking the link scrolls the page to beginning.
|
|
125
127
|
if (!keepScrollPosition) window.scroll(0, 0);
|
|
126
128
|
}}
|
|
127
|
-
{
|
|
129
|
+
onMouseDown={onMouseDown}
|
|
130
|
+
replace={replace}
|
|
131
|
+
to={to}
|
|
132
|
+
// TODO: Refactor it later.
|
|
133
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
134
|
+
{...rest}
|
|
128
135
|
>
|
|
129
136
|
{children}
|
|
130
137
|
</L>
|
|
@@ -37,7 +37,10 @@ const Input: FunctionComponent<PropsT> = ({
|
|
|
37
37
|
className={theme.input}
|
|
38
38
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
39
39
|
ref={ref}
|
|
40
|
-
|
|
40
|
+
|
|
41
|
+
// TODO: Avoid the spreading later.
|
|
42
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
43
|
+
{...rest}
|
|
41
44
|
/>
|
|
42
45
|
</span>
|
|
43
46
|
);
|
|
@@ -12,10 +12,14 @@ import GenericLink, { type PropsT as GenericLinkPropsT } from './GenericLink';
|
|
|
12
12
|
|
|
13
13
|
type PropsT = Omit<GenericLinkPropsT, 'routerLinkType'> & LinkProps;
|
|
14
14
|
|
|
15
|
-
const Link: React.FunctionComponent<PropsT>
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
const Link: React.FunctionComponent<PropsT>
|
|
16
|
+
= (props) => (
|
|
17
|
+
<GenericLink
|
|
18
|
+
// TODO: Avoid the spreading later.
|
|
19
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
20
|
+
{...props}
|
|
21
|
+
routerLinkType={RrLink}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
20
24
|
|
|
21
25
|
export default Link;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// TODO: Move this component to React Helmet library.
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
type Context as ContextT,
|
|
3
5
|
type FunctionComponent,
|
|
@@ -46,8 +48,12 @@ const MetaTags: FunctionComponent<PropsT> & {
|
|
|
46
48
|
title,
|
|
47
49
|
url,
|
|
48
50
|
}) => {
|
|
51
|
+
// NOTE: I guess, in this very case, we should prefer title and description
|
|
52
|
+
// also to empty social title and decscription?
|
|
53
|
+
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
|
49
54
|
const socTitle = socialTitle || title;
|
|
50
55
|
const socDesc = socialDescription || description;
|
|
56
|
+
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */
|
|
51
57
|
|
|
52
58
|
const context = useMemo(() => ({
|
|
53
59
|
description,
|
|
@@ -74,8 +80,8 @@ const MetaTags: FunctionComponent<PropsT> & {
|
|
|
74
80
|
extra.push(
|
|
75
81
|
<meta
|
|
76
82
|
content={content}
|
|
77
|
-
name={name}
|
|
78
83
|
key={`extra-meta-tag-${i}`}
|
|
84
|
+
name={name}
|
|
79
85
|
/>,
|
|
80
86
|
);
|
|
81
87
|
}
|
|
@@ -88,28 +94,28 @@ const MetaTags: FunctionComponent<PropsT> & {
|
|
|
88
94
|
<title>
|
|
89
95
|
{title}
|
|
90
96
|
</title>
|
|
91
|
-
<meta name="description"
|
|
97
|
+
<meta content={description} name="description" />
|
|
92
98
|
|
|
93
99
|
{/* Twitter cards. */}
|
|
94
|
-
<meta name="twitter:card"
|
|
95
|
-
<meta name="twitter:title"
|
|
96
|
-
<meta name="twitter:description"
|
|
97
|
-
{ image ? <meta name="twitter:image"
|
|
100
|
+
<meta content="summary_large_image" name="twitter:card" />
|
|
101
|
+
<meta content={socTitle} name="twitter:title" />
|
|
102
|
+
<meta content={socDesc} name="twitter:description" />
|
|
103
|
+
{ image ? <meta content={image} name="twitter:image" /> : null }
|
|
98
104
|
{
|
|
99
|
-
siteName
|
|
100
|
-
<meta
|
|
101
|
-
|
|
105
|
+
siteName
|
|
106
|
+
? <meta content={`@${siteName}`} name="twitter:site" />
|
|
107
|
+
: null
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
{/* Open Graph data. */}
|
|
105
|
-
<meta name="og:title"
|
|
106
|
-
{ image ? <meta name="og:image"
|
|
107
|
-
{ image ? <meta name="og:image:alt"
|
|
108
|
-
<meta name="og:description"
|
|
111
|
+
<meta content={socTitle} name="og:title" />
|
|
112
|
+
{ image ? <meta content={image} name="og:image" /> : null }
|
|
113
|
+
{ image ? <meta content={socTitle} name="og:image:alt" /> : null }
|
|
114
|
+
<meta content={socDesc} name="og:description" />
|
|
109
115
|
{
|
|
110
|
-
siteName ?
|
|
116
|
+
siteName ? <meta content={siteName} name="og:sitename" /> : null
|
|
111
117
|
}
|
|
112
|
-
{ url ?
|
|
118
|
+
{ url ? <meta content={url} name="og:url" /> : null }
|
|
113
119
|
{extra}
|
|
114
120
|
</Helmet>
|
|
115
121
|
{
|
|
@@ -94,13 +94,14 @@ const BaseModal: FunctionComponent<PropsT> = ({
|
|
|
94
94
|
const focusLast = useMemo(() => (
|
|
95
95
|
<div
|
|
96
96
|
onFocus={() => {
|
|
97
|
-
const elems = containerRef.current
|
|
97
|
+
const elems = containerRef.current!.querySelectorAll('*');
|
|
98
98
|
for (let i = elems.length - 1; i >= 0; --i) {
|
|
99
|
-
elems[i]
|
|
99
|
+
(elems[i] as HTMLElement).focus();
|
|
100
100
|
if (document.activeElement === elems[i]) return;
|
|
101
101
|
}
|
|
102
102
|
overlayRef.current?.focus();
|
|
103
103
|
}}
|
|
104
|
+
// TODO: Have a look at this later.
|
|
104
105
|
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
105
106
|
tabIndex={0}
|
|
106
107
|
/>
|
|
@@ -149,30 +150,30 @@ const BaseModal: FunctionComponent<PropsT> = ({
|
|
|
149
150
|
// (because visually and logically the modal dialog does not belong
|
|
150
151
|
// to its parent container, where it technically belongs from
|
|
151
152
|
// the HTML mark-up perpective).
|
|
152
|
-
/* eslint-disable jsx-a11y/click-events-have-key-events,
|
|
153
|
-
jsx-a11y/no-noninteractive-element-interactions */
|
|
154
153
|
}
|
|
155
|
-
<div
|
|
154
|
+
<div // eslint-disable-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
|
|
156
155
|
aria-modal="true"
|
|
157
156
|
className={theme.container}
|
|
158
157
|
data-testid={process.env.NODE_ENV === 'production' ? undefined : testId}
|
|
159
|
-
onClick={(e) =>
|
|
160
|
-
|
|
158
|
+
onClick={(e) => {
|
|
159
|
+
e.stopPropagation();
|
|
160
|
+
}}
|
|
161
|
+
onWheel={(event) => {
|
|
162
|
+
event.stopPropagation();
|
|
163
|
+
}}
|
|
161
164
|
ref={containerRef}
|
|
162
165
|
role="dialog"
|
|
163
166
|
style={style ?? containerStyle}
|
|
164
167
|
>
|
|
165
168
|
{children}
|
|
166
169
|
</div>
|
|
167
|
-
{/* eslint-enable jsx-a11y/click-events-have-key-events,
|
|
168
|
-
jsx-a11y/no-noninteractive-element-interactions */}
|
|
169
170
|
<div
|
|
170
171
|
onFocus={() => {
|
|
171
172
|
overlayRef.current?.focus();
|
|
172
173
|
}}
|
|
173
|
-
|
|
174
|
+
// TODO: Have a look at this later.
|
|
175
|
+
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
|
|
174
176
|
tabIndex={0}
|
|
175
|
-
/* eslint-enable jsx-a11y/no-noninteractive-tabindex */
|
|
176
177
|
/>
|
|
177
178
|
{focusLast}
|
|
178
179
|
</>
|
|
@@ -4,10 +4,15 @@ import GenericLink, { type PropsT as GenericLinkPropsT } from './GenericLink';
|
|
|
4
4
|
|
|
5
5
|
type PropsT = Omit<GenericLinkPropsT, 'routerLinkType'> & NavLinkProps;
|
|
6
6
|
|
|
7
|
-
const NavLink: React.FunctionComponent<PropsT>
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
const NavLink: React.FunctionComponent<PropsT>
|
|
8
|
+
= (props) => (
|
|
9
|
+
<GenericLink
|
|
10
|
+
// TODO: I guess, we better re-write it to avoid the props spreading,
|
|
11
|
+
// but no need to spend time on it right now.
|
|
12
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
13
|
+
{...props}
|
|
14
|
+
routerLinkType={RrNavLink}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
12
17
|
|
|
13
18
|
export default NavLink;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ChangeEventHandler,
|
|
2
3
|
type FocusEventHandler,
|
|
4
|
+
type FunctionComponent,
|
|
5
|
+
type KeyboardEventHandler,
|
|
3
6
|
useEffect,
|
|
4
7
|
useRef,
|
|
5
8
|
useState,
|
|
@@ -17,14 +20,14 @@ type ThemeKeyT =
|
|
|
17
20
|
type Props = {
|
|
18
21
|
disabled?: boolean;
|
|
19
22
|
onBlur?: FocusEventHandler<HTMLTextAreaElement>;
|
|
20
|
-
onChange?:
|
|
21
|
-
onKeyDown?:
|
|
23
|
+
onChange?: ChangeEventHandler<HTMLTextAreaElement>;
|
|
24
|
+
onKeyDown?: KeyboardEventHandler<HTMLTextAreaElement>;
|
|
22
25
|
placeholder?: string;
|
|
23
26
|
theme: Theme<ThemeKeyT>;
|
|
24
27
|
value?: string;
|
|
25
28
|
};
|
|
26
29
|
|
|
27
|
-
const TextArea:
|
|
30
|
+
const TextArea: FunctionComponent<Props> = ({
|
|
28
31
|
disabled,
|
|
29
32
|
onBlur,
|
|
30
33
|
onChange,
|
|
@@ -36,7 +39,7 @@ const TextArea: React.FunctionComponent<Props> = ({
|
|
|
36
39
|
const hiddenAreaRef = useRef<HTMLTextAreaElement>(null);
|
|
37
40
|
const [height, setHeight] = useState<number | undefined>();
|
|
38
41
|
|
|
39
|
-
const [localValue, setLocalValue] = useState(value
|
|
42
|
+
const [localValue, setLocalValue] = useState(value ?? '');
|
|
40
43
|
if (value !== undefined && localValue !== value) setLocalValue(value);
|
|
41
44
|
|
|
42
45
|
// This resizes text area's height when its width is changed for any reason.
|
|
@@ -64,27 +67,32 @@ const TextArea: React.FunctionComponent<Props> = ({
|
|
|
64
67
|
return (
|
|
65
68
|
<div className={theme.container}>
|
|
66
69
|
<textarea
|
|
70
|
+
className={`${theme.textarea} ${theme.hidden}`}
|
|
71
|
+
|
|
67
72
|
// This text area is hidden underneath the primary one below,
|
|
68
73
|
// and it is used for text measurements, to implement auto-scaling
|
|
69
74
|
// of the primary textarea's height.
|
|
70
75
|
readOnly
|
|
71
76
|
ref={hiddenAreaRef}
|
|
72
|
-
className={`${theme.textarea} ${theme.hidden}`}
|
|
73
77
|
value={localValue}
|
|
74
78
|
/>
|
|
75
79
|
<textarea
|
|
80
|
+
className={theme.textarea}
|
|
76
81
|
disabled={disabled}
|
|
77
82
|
onBlur={onBlur}
|
|
83
|
+
|
|
78
84
|
// When value is "undefined" the text area is not-managed, and we should
|
|
79
85
|
// manage it internally for the measurement / resizing functionality
|
|
80
86
|
// to work.
|
|
81
|
-
onChange={
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
onChange={
|
|
88
|
+
value === undefined
|
|
89
|
+
? (e) => {
|
|
90
|
+
setLocalValue(e.target.value);
|
|
91
|
+
} : onChange
|
|
92
|
+
}
|
|
84
93
|
onKeyDown={onKeyDown}
|
|
85
94
|
placeholder={placeholder}
|
|
86
95
|
style={{ height }}
|
|
87
|
-
className={theme.textarea}
|
|
88
96
|
value={localValue}
|
|
89
97
|
/>
|
|
90
98
|
</div>
|