@reykjavik/webtools 0.3.5 → 0.3.7
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/CHANGELOG.md +17 -1
- package/README.md +355 -9
- package/alertsStore/index.d.ts +201 -0
- package/alertsStore/index.js +317 -0
- package/alertsStore/react.d.ts +36 -0
- package/alertsStore/react.js +96 -0
- package/async.d.ts +51 -0
- package/async.js +59 -1
- package/errorhandling.d.ts +1 -1
- package/esm/alertsStore/index.d.ts +201 -0
- package/esm/alertsStore/index.js +280 -0
- package/esm/alertsStore/react.d.ts +36 -0
- package/esm/alertsStore/react.js +58 -0
- package/esm/async.d.ts +51 -0
- package/esm/async.js +57 -0
- package/esm/errorhandling.d.ts +1 -1
- package/esm/index.d.ts +2 -0
- package/index.d.ts +2 -0
- package/package.json +11 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
- ... <!-- Add new lines here. -->
|
|
6
6
|
|
|
7
|
+
## 0.3.7
|
|
8
|
+
|
|
9
|
+
_2026-03-16_
|
|
10
|
+
|
|
11
|
+
- feat: Add `@reykjavik/webtools/alertsStore` for toasts and other global UI
|
|
12
|
+
feedback messages along with `@reykjavik/webtools/alertsStore/react` helpers
|
|
13
|
+
|
|
14
|
+
## 0.3.6
|
|
15
|
+
|
|
16
|
+
_2026-02-24_
|
|
17
|
+
|
|
18
|
+
- `@reykjavik/webtools/async`:
|
|
19
|
+
- feat: Add `cachifyAsync` helper for simple (yet robust) caching of async
|
|
20
|
+
functions
|
|
21
|
+
|
|
7
22
|
## 0.3.5
|
|
8
23
|
|
|
9
24
|
_2026-02-18_
|
|
10
25
|
|
|
11
|
-
-
|
|
26
|
+
- `@reykjavik/webtools/errorhandling`:
|
|
27
|
+
- fix: `Result.ErrorOf<T>` not handling functions with parameters correctly
|
|
12
28
|
|
|
13
29
|
## 0.3.4
|
|
14
30
|
|
package/README.md
CHANGED
|
@@ -30,6 +30,7 @@ bun add @reykjavik/webtools
|
|
|
30
30
|
- [`promiseAllObject`](#promiseallobject)
|
|
31
31
|
- [`maxWait`](#maxwait)
|
|
32
32
|
- [`debounce`](#debounce)
|
|
33
|
+
- [`cachifyAsync`](#cachifyasync)
|
|
33
34
|
- [`throttle`](#throttle)
|
|
34
35
|
- [`@reykjavik/webtools/hoooks`](#reykjavikwebtoolshoooks)
|
|
35
36
|
- [`useDebounced`](#usedebounced)
|
|
@@ -48,6 +49,13 @@ bun add @reykjavik/webtools
|
|
|
48
49
|
- [`Result.throw`](#resultthrow)
|
|
49
50
|
- [Type `Result.PayloadOf`](#type-resultpayloadof)
|
|
50
51
|
- [Type `Result.ErrorOf`](#type-resulterrorof)
|
|
52
|
+
- [`@reykjavik/webtools/alertsStore`](#reykjavikwebtoolsalertsstore)
|
|
53
|
+
- [`createAlerterStore`](#createalerterstore)
|
|
54
|
+
- [type `AlerterConfig`](#type-alerterconfig)
|
|
55
|
+
- [`@reykjavik/webtools/alertsStore/react`](#reykjavikwebtoolsalertsstorereact)
|
|
56
|
+
- [`makeReactSubscription`](#makereactsubscription)
|
|
57
|
+
- [`renderAlertMessage`](#renderalertmessage)
|
|
58
|
+
- [`renderAlertMessage.withLinkRenderer`](#renderalertmessagewithlinkrenderer)
|
|
51
59
|
- [`@reykjavik/webtools/SiteImprove`](#reykjavikwebtoolssiteimprove)
|
|
52
60
|
- [`SiteImprove` component](#siteimprove-component)
|
|
53
61
|
- [`pingSiteImprove` helper](#pingsiteimprove-helper)
|
|
@@ -307,20 +315,21 @@ detection test.)
|
|
|
307
315
|
|
|
308
316
|
**`Intl.NumberFormat` and `toLocaleString`:**
|
|
309
317
|
|
|
310
|
-
- The `style: "unit"` option is not supported and prints units in Danish. (
|
|
311
|
-
many units and unit-variants…)
|
|
318
|
+
- The `style: "unit"` option is not supported and prints units in Danish. (So,
|
|
319
|
+
so (!!) many units and unit-variants… Impractical to handle size-wise.)
|
|
312
320
|
- The `currencyDisplay: "name"` option is not supported and prints the
|
|
313
|
-
currency's full name in Danish.
|
|
321
|
+
currency's full name in Danish. (Impractical to handle size-wise.)
|
|
314
322
|
|
|
315
323
|
**`Intl.DateTimeFormat` and `toLocaleDateString`:**
|
|
316
324
|
|
|
317
325
|
- The `month: 'narrow'` and `weekday: 'narrow'` options are not supported, and
|
|
318
|
-
print the corresponding Danish initials.
|
|
326
|
+
print the corresponding Danish initials. (Near impossible to patch because
|
|
327
|
+
the Danish initials are ambigious)
|
|
319
328
|
- For `timeZoneName` the values `"long"`, `"shortGeneric"` and `"longGeneric"`
|
|
320
|
-
will appear in Danish.
|
|
329
|
+
will appear in Danish. (Impractical to handle size-wise.)
|
|
321
330
|
- The `timeStyle: 'full'` option prints the timezone names in Danish
|
|
322
331
|
- The `dayPeriod` option has a couple of slight mismatches, at 5 am and 12
|
|
323
|
-
noon.
|
|
332
|
+
noon. (Completely harmless.)
|
|
324
333
|
|
|
325
334
|
We eagerly accept bugfixes, additions, etc. to this module!
|
|
326
335
|
|
|
@@ -439,6 +448,67 @@ sayHello.cancel(true); // `finish` parmeter is true
|
|
|
439
448
|
|
|
440
449
|
---
|
|
441
450
|
|
|
451
|
+
### `cachifyAsync`
|
|
452
|
+
|
|
453
|
+
**Syntax:**
|
|
454
|
+
`cachifyAsync<R, F extends (...args: any[]) => Promise<Result.TupleObj<R>>>(opts: { fn: F; ttl: TTL; throttle?: TTL; customTtl?: (args: Parameters<F>, result: Result.TupleObj<R>) => TTL | undefined; getKey?: (...args: Parameters<F>) => string; returnStale?: boolean }): F`
|
|
455
|
+
|
|
456
|
+
Wraps an async function with a simple, robust caching layer. Returns a
|
|
457
|
+
function with the same signature as `fn`, but with caching applied.
|
|
458
|
+
|
|
459
|
+
The caching strategy is simple. If `fn` resolves to an error result, the error
|
|
460
|
+
is cached for a short time (default: `30s`) to avoid hammering the underlying
|
|
461
|
+
function, and a stale (last successful) result is returned if available. The
|
|
462
|
+
error result is only while waiting for the issue to be resolved. Return stale
|
|
463
|
+
(last successful) result while throttling.
|
|
464
|
+
|
|
465
|
+
- No max size or eviction strategy—intended for caching a small, clearly
|
|
466
|
+
bounded number of different cache "keys" (e.g. per language).
|
|
467
|
+
|
|
468
|
+
**Options:**
|
|
469
|
+
|
|
470
|
+
- `fn: <T>(...args: ay[]) => Promise<Result.TupleObj<T>>` — The async function
|
|
471
|
+
to cache.
|
|
472
|
+
- `ttl: TTL` — How long to cache successful results. Number values are treated
|
|
473
|
+
as seconds. (See (`TTL` type)[#type-ttl]).
|
|
474
|
+
- `throttle? TTL` — The minimum time between retries for error results.
|
|
475
|
+
Numbers are treated as seconds.
|
|
476
|
+
- `customTtl?: (args: Parameters<typeof fn>, result: Result.TupleObj<T>) => TTL | undefined;`
|
|
477
|
+
— set a custom TTL on success and/or error results. Return `undefined` to
|
|
478
|
+
use the default `ttl`/`throttle` values.
|
|
479
|
+
- `getKey?: (...args: Parameters<typeof fn>) => string` — Creates a custom
|
|
480
|
+
cache key for the current result set. Default: `JSON.stringify(args)`.
|
|
481
|
+
- `returnStale?: boolean` — Whether to return stale (last successful) result
|
|
482
|
+
when `fn` resolves to an error result. Defaults to `true`.
|
|
483
|
+
|
|
484
|
+
**Example:**
|
|
485
|
+
|
|
486
|
+
```ts
|
|
487
|
+
import { cachifyAsync } from '@reykjavik/webtools/async';
|
|
488
|
+
import { Result } from '@reykjavik/webtools/errorhandling';
|
|
489
|
+
|
|
490
|
+
const fetchUser = async (id: string) =>
|
|
491
|
+
Result.ify(fetch(`/api/user/${id}`).then((r) => r.json()));
|
|
492
|
+
|
|
493
|
+
const cachedFetchUser = cachifyAsync({
|
|
494
|
+
fn: fetchUser,
|
|
495
|
+
ttl: '10m',
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
// ---------------------****---------------------------------------
|
|
499
|
+
// Usage:
|
|
500
|
+
|
|
501
|
+
const result = await cachedFetchUser('123');
|
|
502
|
+
|
|
503
|
+
if (result.error) {
|
|
504
|
+
// handle error
|
|
505
|
+
} else {
|
|
506
|
+
// use result.result
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
442
512
|
### `throttle`
|
|
443
513
|
|
|
444
514
|
**Syntax:**
|
|
@@ -621,7 +691,7 @@ handling `ResultTupleObj` instances:
|
|
|
621
691
|
|
|
622
692
|
- `Result.Success`
|
|
623
693
|
- `Result.Fail`
|
|
624
|
-
- `Result.catch`
|
|
694
|
+
- `Result.catch` / `Result.ify`
|
|
625
695
|
- `Result.map`
|
|
626
696
|
- `Result.throw`
|
|
627
697
|
|
|
@@ -855,7 +925,9 @@ import { Result } from '@reykjavik/webtools/errorhandling';
|
|
|
855
925
|
type ResTpl = Result.Tuple<string, Error>;
|
|
856
926
|
type ResTplPromise = Promise<Result.Tuple<number, Error>>;
|
|
857
927
|
type ResTplFn = (arg: unknown) => Result.Tuple<boolean, Error>;
|
|
858
|
-
type ResTplPromiseFn = (
|
|
928
|
+
type ResTplPromiseFn = (
|
|
929
|
+
arg: unknown
|
|
930
|
+
) => Promise<Result.TupleObj<Date, Error>>;
|
|
859
931
|
|
|
860
932
|
type Payload1 = Result.PayloadOf<ResTpl>; // string
|
|
861
933
|
type Payload2 = Result.PayloadOf<ResTplPromise>; // number
|
|
@@ -863,6 +935,9 @@ type Payload3 = Result.PayloadOf<ResTplFn>; // boolean
|
|
|
863
935
|
type Payload4 = Result.PayloadOf<ResTplPromiseFn>; // Date
|
|
864
936
|
```
|
|
865
937
|
|
|
938
|
+
NOTE: This type also works for [`ResultTupleObj`](#type-resulttupleobj) as
|
|
939
|
+
it's a subtype of `ResultTuple`.
|
|
940
|
+
|
|
866
941
|
---
|
|
867
942
|
|
|
868
943
|
### Type `Result.ErrorOf`
|
|
@@ -881,7 +956,7 @@ type ResTplPromise = Promise<Result.Tuple<number, RangeError>>;
|
|
|
881
956
|
type ResTplFn = (arg: unknown) => Result.Tuple<boolean, RangeError>;
|
|
882
957
|
type ResTplPromiseFn = (
|
|
883
958
|
arg: unknown
|
|
884
|
-
) => Promise<Result.
|
|
959
|
+
) => Promise<Result.TupleÞObj<Date, RangeError>>;
|
|
885
960
|
|
|
886
961
|
type Error1 = Result.ErrorOf<ResTpl>; // RangeError
|
|
887
962
|
type Error2 = Result.ErrorOf<ResTplPromise>; // RangeError
|
|
@@ -889,6 +964,277 @@ type Error3 = Result.ErrorOf<ResTplFn>; // RangeError
|
|
|
889
964
|
type Error4 = Result.ErrorOf<ResTplPromiseFn>; // RangeError
|
|
890
965
|
```
|
|
891
966
|
|
|
967
|
+
NOTE: This type also works for [`ResultTupleObj`](#type-resulttupleobj) as
|
|
968
|
+
it's a subtype of `ResultTuple`.
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## `@reykjavik/webtools/alertsStore`
|
|
973
|
+
|
|
974
|
+
A small JS alerts store for toasts and other global UI feedback messages.
|
|
975
|
+
|
|
976
|
+
Persists alerts to `sessionStorage` to survive browser reloads, and provides a
|
|
977
|
+
simple pub/sub API for components to subscribe to alert changes.
|
|
978
|
+
|
|
979
|
+
### `createAlerterStore`
|
|
980
|
+
|
|
981
|
+
**Syntax:**
|
|
982
|
+
`createAlerterStore(cfg?: AlerterConfig): { alerter: Record<Level, (payload: AlertPayload>) => void, subscribe: (callback: (alerts: Array<AlertInfo>, meta: { type: EventType, ids: Array<string> }) => void) => unsubscribe() => void; }`
|
|
983
|
+
|
|
984
|
+
Factory function that instantiates a new alerter store and returns a strongly
|
|
985
|
+
typed object with the following properties:
|
|
986
|
+
|
|
987
|
+
- `alerter`: A singleton object with methods for dispatching new alerts of
|
|
988
|
+
different levels. Pass a payload object to the method of the level you want
|
|
989
|
+
to dispatch, and the alert will be added to the store.
|
|
990
|
+
- `subscribe`: A function for subscribing to alert changes. It accepts a
|
|
991
|
+
callback that gets called with the current list of alerts and some metadata
|
|
992
|
+
whenever an alert is added or cleared.
|
|
993
|
+
The callback is called immediately upon subscription if there are already
|
|
994
|
+
active alerts.
|
|
995
|
+
It returns an unsubscribe function to stop receiving updates.
|
|
996
|
+
|
|
997
|
+
Simple useage with default settings:
|
|
998
|
+
|
|
999
|
+
```ts
|
|
1000
|
+
// ---------------------------------------------------------------------------
|
|
1001
|
+
// alerterStore.ts
|
|
1002
|
+
// ---------------------------------------------------------------------------
|
|
1003
|
+
|
|
1004
|
+
import { createAlerterStore } from '@reykjavik/webtools/alertsStore';
|
|
1005
|
+
import type { InferSubscriberAlerts, InferAlerterPayload } from '@reykjavik/webtools/alertsStore';
|
|
1006
|
+
|
|
1007
|
+
const { alerter, subscribe } = createAlerterStore();
|
|
1008
|
+
|
|
1009
|
+
export { alerter, subscribe };
|
|
1010
|
+
export type AlertPayload = InferAlerterPayload(typeof alerter);
|
|
1011
|
+
export type AlertInfo = InferSubscriberAlerts<typeof subscribe>;
|
|
1012
|
+
|
|
1013
|
+
// ---------------------------------------------------------------------------
|
|
1014
|
+
// appRoot.ts
|
|
1015
|
+
// ---------------------------------------------------------------------------
|
|
1016
|
+
|
|
1017
|
+
import { subscribe } from '../alerterStore';
|
|
1018
|
+
|
|
1019
|
+
const unsubscribe = subscribe((alerts, meta) => {
|
|
1020
|
+
console.log('Current alerts:', alerts);
|
|
1021
|
+
console.log('Change type:', meta.type);
|
|
1022
|
+
console.log('Affected alert IDs:', meta.ids);
|
|
1023
|
+
});
|
|
1024
|
+
|
|
1025
|
+
// Stop receiving updates after 1 hour
|
|
1026
|
+
setTimeout(unsubscribe, 3_600_000);
|
|
1027
|
+
|
|
1028
|
+
// ---------------------------------------------------------------------------
|
|
1029
|
+
// someOtherModule.ts
|
|
1030
|
+
// ---------------------------------------------------------------------------
|
|
1031
|
+
|
|
1032
|
+
import { alerter } from '../alerterStore';
|
|
1033
|
+
alerter.success({
|
|
1034
|
+
message: 'All is good',
|
|
1035
|
+
// type: 'something',
|
|
1036
|
+
// flags: ['pristine'],
|
|
1037
|
+
duration: 'MEDIUM',
|
|
1038
|
+
delay: 500, // Optional delay
|
|
1039
|
+
});
|
|
1040
|
+
// after 500ms the above alert is added to the store, and all subscribers
|
|
1041
|
+
// are notified. The subscriber in `appRoot.ts` will log the following;
|
|
1042
|
+
/*
|
|
1043
|
+
Current alerts: [
|
|
1044
|
+
{
|
|
1045
|
+
id: '_234566-27_', // autugenerated
|
|
1046
|
+
level: 'success',
|
|
1047
|
+
message: 'All is good',
|
|
1048
|
+
duration: 5000, // ms
|
|
1049
|
+
dismiss: <Function>,
|
|
1050
|
+
setFalgs: <Function>,
|
|
1051
|
+
}
|
|
1052
|
+
]
|
|
1053
|
+
Change type: 'add'
|
|
1054
|
+
Affected alert IDs: ['_234566-27_']
|
|
1055
|
+
*/
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
Note how the `AlertPayload` and `AlertInfo` types are inferred from the
|
|
1059
|
+
generated `alerter` and the `subscribe` functions, respectively, using the
|
|
1060
|
+
provided `InferAlerterPayload` and `InferSubscriberAlerts` utility types.
|
|
1061
|
+
|
|
1062
|
+
#### type `AlerterConfig`
|
|
1063
|
+
|
|
1064
|
+
The `createAlerter` function accepts an optional configuration object that
|
|
1065
|
+
allows the customization of all of the accepted alert values and durations.
|
|
1066
|
+
|
|
1067
|
+
The configuration values affect the type signatures of the generated `alerter`
|
|
1068
|
+
and the `subscribe` functions. (See `InferAlerterPayload` and
|
|
1069
|
+
`InferSubscriberAlerts` below)
|
|
1070
|
+
|
|
1071
|
+
The configuration options are as follows:
|
|
1072
|
+
|
|
1073
|
+
- **`key?: string`**
|
|
1074
|
+
Identifier for the alerts store, used to create the key to persist alerts in
|
|
1075
|
+
`sessionStorage` (or other provided storage).
|
|
1076
|
+
Required if you want to have multiple independent alert stores in the same
|
|
1077
|
+
application.
|
|
1078
|
+
Default: `'app-alerts'`.
|
|
1079
|
+
|
|
1080
|
+
- **`levels?: Array<string>`**
|
|
1081
|
+
The accepted alert levels. The returned `alerter` object has a named
|
|
1082
|
+
dispatcher method for each level.
|
|
1083
|
+
Default: `['success', 'info', 'warning', 'error']`.
|
|
1084
|
+
|
|
1085
|
+
- **`types?: Array<string>`**
|
|
1086
|
+
The allowed alert "types", which can be used to, for example, dispatch both
|
|
1087
|
+
"toasts" vs. "static alert banners" via the same store.
|
|
1088
|
+
This can also be used for more basic styling or categorization purposes.
|
|
1089
|
+
Default: no restrictions, any string value is allowed.
|
|
1090
|
+
|
|
1091
|
+
- **`flags?: Array<string>`**
|
|
1092
|
+
The allowed alert "flags", which can be changed during the lifetime of an
|
|
1093
|
+
alert using the `setFlags` function on the `AlertInfo` object.
|
|
1094
|
+
This can be used for styling or any other purpose you like.
|
|
1095
|
+
Default: no restriction, any string value is allowed.
|
|
1096
|
+
|
|
1097
|
+
- **`durations?: Record<string, number>`**
|
|
1098
|
+
The allowed alert "duration" names and their lengths in milliseconds.
|
|
1099
|
+
Default:
|
|
1100
|
+
`{ BLINK: 2_000, SHORT: 4_000, MEDIUM: 8_000, LONG: 16_000, XLONG: 32_000, INDEFINITE: 0 }`.
|
|
1101
|
+
|
|
1102
|
+
- **`defaultDuration?: string`**
|
|
1103
|
+
The duration to use for alerts if no duration is specified when
|
|
1104
|
+
dispatching.
|
|
1105
|
+
Default: `SHORT` if using the default durations, otherwise the default is
|
|
1106
|
+
`0` (indefinite)
|
|
1107
|
+
|
|
1108
|
+
- **`storage?: Pick<Storage, 'getItem' | 'setItem'>`**
|
|
1109
|
+
The storage object to use instead of `sessionStorage` (the default) for
|
|
1110
|
+
persisting alerts across page reloads, etc.
|
|
1111
|
+
|
|
1112
|
+
### `@reykjavik/webtools/alertsStore/react`
|
|
1113
|
+
|
|
1114
|
+
#### `makeReactSubscription`
|
|
1115
|
+
|
|
1116
|
+
**Syntax:**
|
|
1117
|
+
`makeReactSubscription(): { useAlerter: () => Array<AlertInfo>, AlertsContainer: (props: { children: (alerts: Array<alertInfo>) => ReactNode }) => ReactNode }`
|
|
1118
|
+
|
|
1119
|
+
Factory function that creates a React subscription hook and a container
|
|
1120
|
+
component linked to a specific alerter store subscibe function.
|
|
1121
|
+
|
|
1122
|
+
The returned `useAlerter` hook can be used in any React component to get the
|
|
1123
|
+
current list of alerts from the store
|
|
1124
|
+
|
|
1125
|
+
Meanwhile the `AlertsContainer` is a sugar component that calls `useAlerter()`
|
|
1126
|
+
internally and provides the current alerts list to its child as a render prop.
|
|
1127
|
+
|
|
1128
|
+
The returned list and its items and their properties are all immutable/stable
|
|
1129
|
+
so you can safely use them as dependencies in React hooks, etc.
|
|
1130
|
+
|
|
1131
|
+
```ts
|
|
1132
|
+
// ---------------------------------------------------------------------------
|
|
1133
|
+
// alerterStore.ts
|
|
1134
|
+
// ---------------------------------------------------------------------------
|
|
1135
|
+
|
|
1136
|
+
import { createAlerterStore } from '@reykjavik/webtools/alertsStore';
|
|
1137
|
+
import { makeReactSubscription } from '@reykjavik/webtools/alertsStore/react';
|
|
1138
|
+
|
|
1139
|
+
const { alerter, subscribe } = createAlerterStore();
|
|
1140
|
+
|
|
1141
|
+
export { alerter };
|
|
1142
|
+
export const { useAlerter, AlertsContainer } =
|
|
1143
|
+
makeReactSubscription(subscribe);
|
|
1144
|
+
|
|
1145
|
+
// ---------------------------------------------------------------------------
|
|
1146
|
+
// app.tsx
|
|
1147
|
+
// ---------------------------------------------------------------------------
|
|
1148
|
+
|
|
1149
|
+
import { AlertsContainer } from '../alerterStore';
|
|
1150
|
+
import { Toast } from '../components/Toast';
|
|
1151
|
+
|
|
1152
|
+
// In your App JSX
|
|
1153
|
+
<AlertsContainer>
|
|
1154
|
+
{(alerts) => (
|
|
1155
|
+
<div class="toastcontainer">
|
|
1156
|
+
{alerts.map((alert) => (
|
|
1157
|
+
<Toast key={alert.id} {...alert} />
|
|
1158
|
+
))}
|
|
1159
|
+
</div>
|
|
1160
|
+
)}
|
|
1161
|
+
</AlertsContainer>;
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
#### `renderAlertMessage`
|
|
1165
|
+
|
|
1166
|
+
**Syntax:**
|
|
1167
|
+
`renderAlertMessage(message: AlertInfo['message'], linkComponent?: renderAlertMessage.LinkRenderer): ReactNode`
|
|
1168
|
+
|
|
1169
|
+
Helper to render an alerter alert message, which can be a simple string or a
|
|
1170
|
+
more complex array of strings and objects representing links and rich (bold)
|
|
1171
|
+
text formatting.
|
|
1172
|
+
|
|
1173
|
+
It renders link objects as simple `<a href="" />` elements, by default, but
|
|
1174
|
+
you can optionally provide a custom `linkComponent` as a second parameter.
|
|
1175
|
+
|
|
1176
|
+
Third
|
|
1177
|
+
|
|
1178
|
+
```ts
|
|
1179
|
+
import { renderAlertMessage } from '@reykjavik/webtools/alertsStore/react';
|
|
1180
|
+
import Link from 'next/link';
|
|
1181
|
+
|
|
1182
|
+
import { AlertInfo } from '../alertsStore';
|
|
1183
|
+
|
|
1184
|
+
export const Toast = (props: AlertInfo) => {
|
|
1185
|
+
const dismissOnLinkClick = (e: React.MouseEvent) => {
|
|
1186
|
+
if ((e.target as HTMLElement).closest('a')) {
|
|
1187
|
+
props.dismiss(); // Dismiss the alert when a link is clicked
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
return (
|
|
1191
|
+
<div class="toast" onClick={dismissOnLinkClick}>
|
|
1192
|
+
{renderAlertMessage(props.message, Link)}
|
|
1193
|
+
</div>
|
|
1194
|
+
);
|
|
1195
|
+
};
|
|
1196
|
+
```
|
|
1197
|
+
|
|
1198
|
+
To build your own custom `LinkComponent`, you can use the
|
|
1199
|
+
`renderAlertMessage.LinkRenderer` type for the function signature.
|
|
1200
|
+
|
|
1201
|
+
```ts
|
|
1202
|
+
import { renderAlertMessage } from '@reykjavik/webtools/alertsStore/react';
|
|
1203
|
+
import { Link } from 'react-router';
|
|
1204
|
+
|
|
1205
|
+
const MyWrappedLink: renderAlertMessage.LinkRenderer = (props) => {
|
|
1206
|
+
const { href, ...linkProps } = props;
|
|
1207
|
+
return <Link to={href} {...linkProps} />;
|
|
1208
|
+
};
|
|
1209
|
+
|
|
1210
|
+
// Then elsewhere in your Alert/Toast component
|
|
1211
|
+
<div class="toast__message">
|
|
1212
|
+
{renderAlertMessage(props.message, MyWrappedLink)};
|
|
1213
|
+
</div>;
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
Alternatively, if you want to avoid passing the `LinkComponent` every time you
|
|
1217
|
+
call `renderAlertMessage`, you can use the
|
|
1218
|
+
`renderAlertMessage.withLinkRenderer` helper
|
|
1219
|
+
|
|
1220
|
+
#### `renderAlertMessage.withLinkRenderer`
|
|
1221
|
+
|
|
1222
|
+
**Syntax:**
|
|
1223
|
+
`renderAlertMessage.withLinkRenderer(LinkComponent: renderAlertMessage.LinkRenderer): (message: AlertInfo['message']):ReactNode`
|
|
1224
|
+
|
|
1225
|
+
It returns a curried version of [`renderAlertMessage`](#renderAlertMessage)
|
|
1226
|
+
that uses the passed `LinkComponent` for rendering links in alert messages.
|
|
1227
|
+
|
|
1228
|
+
```ts
|
|
1229
|
+
const curriedRenderAlertMessage =
|
|
1230
|
+
renderAlertMessage.withLinkRenderer(MyWrappedLink);
|
|
1231
|
+
|
|
1232
|
+
// Then elsewhere in your Alert/Toast component
|
|
1233
|
+
<div class="toast__message">
|
|
1234
|
+
{renderAlertMessage(props.message, MyWrappedLink)};
|
|
1235
|
+
</div>;
|
|
1236
|
+
```
|
|
1237
|
+
|
|
892
1238
|
---
|
|
893
1239
|
|
|
894
1240
|
## `@reykjavik/webtools/SiteImprove`
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
declare const messageSchema: v.UnionSchema<[v.StringSchema<undefined>, v.ArraySchema<v.UnionSchema<[v.StringSchema<undefined>, v.ObjectSchema<{
|
|
3
|
+
readonly tag: v.LiteralSchema<"a", undefined>;
|
|
4
|
+
readonly text: v.StringSchema<undefined>;
|
|
5
|
+
readonly href: v.StringSchema<undefined>;
|
|
6
|
+
readonly target: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
7
|
+
readonly hrefLang: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
8
|
+
readonly lang: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
9
|
+
}, undefined>, v.ObjectSchema<{
|
|
10
|
+
readonly tag: v.LiteralSchema<"strong", undefined>;
|
|
11
|
+
readonly text: v.StringSchema<undefined>;
|
|
12
|
+
}, undefined>], undefined>, undefined>], undefined>;
|
|
13
|
+
export type AlertMessage = v.InferOutput<typeof messageSchema>;
|
|
14
|
+
type _AlertNotification<Level, Type, Flag> = {
|
|
15
|
+
level: Level;
|
|
16
|
+
message: AlertMessage;
|
|
17
|
+
type?: Type;
|
|
18
|
+
flags?: Array<Flag>;
|
|
19
|
+
duration?: number;
|
|
20
|
+
id: string;
|
|
21
|
+
};
|
|
22
|
+
declare const defaultAlertLevels: readonly ["info", "warning", "success", "error"];
|
|
23
|
+
declare const defaultDurations: {
|
|
24
|
+
BLINK: number;
|
|
25
|
+
SHORT: number;
|
|
26
|
+
MEDIUM: number;
|
|
27
|
+
LONG: number;
|
|
28
|
+
XLONG: number;
|
|
29
|
+
INDEFINITE: number;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A configuration object for the `createAlerter` factory function, that allows
|
|
33
|
+
* the customization of all of the accepted alert values and durations.
|
|
34
|
+
*
|
|
35
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.3/README.md#type-alerterconfig
|
|
36
|
+
*/
|
|
37
|
+
export type AlerterConfig<Level extends string = (typeof defaultAlertLevels)[number], Type extends string = string, Flag extends string = string, Duration extends string = keyof typeof defaultDurations, Durations extends Record<Duration, number> = Record<Duration, number>> = {
|
|
38
|
+
/**
|
|
39
|
+
* Identifier for the alerts store, used to create the key to persist alerts
|
|
40
|
+
* in `sessionStorage` (or other provided storage).
|
|
41
|
+
*
|
|
42
|
+
* Only required if you need to create multiple independent alert stores in
|
|
43
|
+
* the same app.
|
|
44
|
+
*
|
|
45
|
+
* Default: `"app~alerts"`
|
|
46
|
+
*/
|
|
47
|
+
key?: string;
|
|
48
|
+
/**
|
|
49
|
+
* The allowed alert "levels". The returned `alerter` object will have a
|
|
50
|
+
* named dispatcher method for each level.
|
|
51
|
+
*
|
|
52
|
+
* Default: `['info', 'warning', 'success', 'error']`
|
|
53
|
+
*/
|
|
54
|
+
levels?: Array<Level>;
|
|
55
|
+
/**
|
|
56
|
+
* The allowed alert "types", which can be used to, for example, to dispatch
|
|
57
|
+
* both "toasts" vs. "static alert banners" via the same store.
|
|
58
|
+
*
|
|
59
|
+
* This can also be used for more basic styling or categorization purposes.
|
|
60
|
+
*
|
|
61
|
+
* Default: no restrictions, any string value is allowed.
|
|
62
|
+
*/
|
|
63
|
+
types?: Array<Type>;
|
|
64
|
+
/**
|
|
65
|
+
* The allowed alert "flags", which can be changed during the lifetime of
|
|
66
|
+
* an alert using the `setFlags` function on the `AlertInfo` object.
|
|
67
|
+
*
|
|
68
|
+
* This can be used for styling or any other purpose you like.
|
|
69
|
+
*
|
|
70
|
+
* Default: no restriction, any string value is allowed.
|
|
71
|
+
*/
|
|
72
|
+
flags?: Array<Flag>;
|
|
73
|
+
/**
|
|
74
|
+
* Optionally controls the allowed alert "duration" names and their lengths
|
|
75
|
+
* in milliseconds.
|
|
76
|
+
*
|
|
77
|
+
* Default:
|
|
78
|
+
* ```ts
|
|
79
|
+
* {
|
|
80
|
+
BLINK: 2_000,
|
|
81
|
+
SHORT: 4_000,
|
|
82
|
+
MEDIUM: 8_000,
|
|
83
|
+
LONG: 16_000,
|
|
84
|
+
XLONG: 32_000,
|
|
85
|
+
INDEFINITE: 0,
|
|
86
|
+
}
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
durations?: Durations;
|
|
90
|
+
/**
|
|
91
|
+
* Default duration to use for alerts if no duration is specified when
|
|
92
|
+
* dispatching.
|
|
93
|
+
*
|
|
94
|
+
* Default: `SHORT` if using the default durations, otherwise the default
|
|
95
|
+
* is `0` (indefinite)
|
|
96
|
+
*/
|
|
97
|
+
defaultDuration?: Durations extends Record<infer D, number> ? D : never;
|
|
98
|
+
/**
|
|
99
|
+
* Optional custom storage object to use instead of `sessionStorage` (the
|
|
100
|
+
* default) for persisting alerts across page reloads, etc.
|
|
101
|
+
*/
|
|
102
|
+
storage?: {
|
|
103
|
+
getItem: (key: string) => string | undefined | null;
|
|
104
|
+
setItem: (key: string, value: string) => void;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Factory function that creates an alerter store singleton with optional
|
|
109
|
+
* configuration for the genarated alerts.
|
|
110
|
+
*
|
|
111
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.3/README.md#createalerterstore
|
|
112
|
+
*/
|
|
113
|
+
export declare const createAlerterStore: <Level extends string = (typeof defaultAlertLevels)[number], Type extends string = string, Flag extends string = string, Duration extends string = keyof typeof defaultDurations>(cfg?: AlerterConfig<Level, Type, Flag, Duration>) => {
|
|
114
|
+
/**
|
|
115
|
+
* Singleton object with methods for showing alerts of different levels.
|
|
116
|
+
* Pass a payload object to the method of the level you want to dispatch,
|
|
117
|
+
* and the alert will be added to the store.
|
|
118
|
+
*
|
|
119
|
+
* Use `subscribeToAlerts` elsewhere in the app to subscribe to alert
|
|
120
|
+
* notifications and display them.
|
|
121
|
+
*
|
|
122
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.3/README.md#createalerterstore
|
|
123
|
+
*/
|
|
124
|
+
alerter: Record<Level, (payload: {
|
|
125
|
+
/**
|
|
126
|
+
* A simple string containing the alert message.
|
|
127
|
+
*
|
|
128
|
+
* For sightly more complex alert messages pass an array of strings
|
|
129
|
+
* (representing text nodes), objects with `tag: 'a'` and hyperlink-related
|
|
130
|
+
* props, or `tag: 'strong'` objects for minimal rich formatting.
|
|
131
|
+
*
|
|
132
|
+
* (Assume that such array itmes will be rendered with whitespace between
|
|
133
|
+
* them.)
|
|
134
|
+
*/
|
|
135
|
+
message: AlertMessage;
|
|
136
|
+
/**
|
|
137
|
+
* Allows distinguishing between different "types" of alerts, for example,
|
|
138
|
+
* to dispatch both "toasts" vs. "static alert banners" via the same store.
|
|
139
|
+
*
|
|
140
|
+
* May also be used for more basic styling or categorization purposes.
|
|
141
|
+
*/
|
|
142
|
+
type?: Type;
|
|
143
|
+
/**
|
|
144
|
+
* Flag values can be changed during the lifetime of
|
|
145
|
+
* an alert using the `setFlags` function on each `AlertInfo` object.
|
|
146
|
+
*
|
|
147
|
+
* Flags may be used for styling or any other purpose you like.
|
|
148
|
+
*/
|
|
149
|
+
flags?: Array<Flag>;
|
|
150
|
+
/**
|
|
151
|
+
* Hint for how long the notification should remain displayed before
|
|
152
|
+
* auto-dismissing.
|
|
153
|
+
*
|
|
154
|
+
* **NOTE:** The alerter store does not implement auto-dismissing. However,
|
|
155
|
+
* this value can be used by UI component that actually render the alert,
|
|
156
|
+
* by calling each `AlertInfo`'s `dismiss` method.
|
|
157
|
+
*/
|
|
158
|
+
duration?: Duration;
|
|
159
|
+
delay?: number;
|
|
160
|
+
}) => void>;
|
|
161
|
+
/**
|
|
162
|
+
* Subscribes to alert events. The provided callback will be called whenever a
|
|
163
|
+
* alert is added or cleared.
|
|
164
|
+
*
|
|
165
|
+
* The callback is called immediately upon subscription if there are already
|
|
166
|
+
* active alerts.
|
|
167
|
+
*
|
|
168
|
+
* Returns an unsubscribe function that can be called to stop receiving alert
|
|
169
|
+
* events.
|
|
170
|
+
*
|
|
171
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.3/README.md#createalerterstore
|
|
172
|
+
*/
|
|
173
|
+
subscribe: (callback: (notifications: Array<_AlertNotification<Level, Type, Flag> & {
|
|
174
|
+
/** Dispatcher function that dismisses/hides/removes the callback */
|
|
175
|
+
dismiss: () => void;
|
|
176
|
+
/**
|
|
177
|
+
* Dispatcher function that can be used to set a simple "flag" on the alert,
|
|
178
|
+
* which can be used for styling or other purposes.
|
|
179
|
+
*/
|
|
180
|
+
setFlags: (value: Flag | Array<Flag> | ((flags: Array<Flag> | undefined) => Array<Flag> | undefined)) => void;
|
|
181
|
+
}>, meta: {
|
|
182
|
+
type: "clear" | "add" | "change";
|
|
183
|
+
/** IDs of the alerts that were added or cleared in this event. */
|
|
184
|
+
ids: Array<string>;
|
|
185
|
+
}) => void) => () => void;
|
|
186
|
+
};
|
|
187
|
+
/**
|
|
188
|
+
* Utility type for inferring the payload shape of the dispatching methods of a
|
|
189
|
+
* specific `alerter` singleton.
|
|
190
|
+
*
|
|
191
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.3/README.md#createalerterstore
|
|
192
|
+
*/
|
|
193
|
+
export type InferAlerterPayload<F extends Record<string, (...args: Array<any>) => void>> = F extends Record<string, (payload: infer P) => void> ? P : never;
|
|
194
|
+
/**
|
|
195
|
+
* Utility type for inferring the alert info object shape received by the
|
|
196
|
+
* callbacks of a specific alerter `subscribe` function.
|
|
197
|
+
*
|
|
198
|
+
* @see https://github.com/reykjavikcity/webtools/blob/v0.3/README.md#createalerterstore
|
|
199
|
+
*/
|
|
200
|
+
export type InferSubscriberAlerts<F extends (callback: (alerts: Array<unknown>, meta: unknown) => void) => () => void> = F extends (callback: (alerts: Array<infer A>) => void) => () => void ? A : never;
|
|
201
|
+
export {};
|