@faybl/react-utils 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -94,6 +94,16 @@ dark app has to declare it, with MUI through `<CssBaseline enableColorScheme />`
94
94
  page the user left, which asks the user to sign in again. `signOutUrl` on the
95
95
  provider sends it somewhere else instead.
96
96
 
97
+ `dialogComponent` replaces the dialog with one of the app's own, which gets
98
+ `open`, `onSignIn` and `labels`. An MUI app passes the one from
99
+ `@faybl/react-utils/mui`, which takes the app's theme:
100
+
101
+ ```jsx
102
+ import { MuiSessionExpiredDialog } from '@faybl/react-utils/mui';
103
+
104
+ <SessionProvider client={client} dialogComponent={MuiSessionExpiredDialog}>
105
+ ```
106
+
97
107
  An app that handles the expiry itself can still use the dialog:
98
108
 
99
109
  ```jsx
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@faybl/react-utils",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "React hooks for Faybl web apps",
5
5
  "license": "TBD",
6
6
  "author": "Arcturitech",
7
7
  "type": "module",
8
8
  "exports": {
9
- ".": "./src/index.js"
9
+ ".": "./src/index.js",
10
+ "./mui": "./src/mui/index.js"
10
11
  },
11
12
  "files": [
12
13
  "src"
@@ -22,15 +23,19 @@
22
23
  },
23
24
  "peerDependencies": {
24
25
  "@faybl/client": "^4.1.0",
26
+ "@mui/material": "^9.0.0",
25
27
  "react": "^19.2.0"
26
28
  },
27
29
  "peerDependenciesMeta": {
28
30
  "@faybl/client": {
29
31
  "optional": true
32
+ },
33
+ "@mui/material": {
34
+ "optional": true
30
35
  }
31
36
  },
32
37
  "dependencies": {
33
- "@faybl/utils": "^2.0.1"
38
+ "@faybl/utils": "^2.1.0"
34
39
  },
35
40
  "engines": {
36
41
  "node": ">=22"
@@ -1,6 +1,6 @@
1
1
  import { createElement, useEffect, useId, useRef } from 'react';
2
2
 
3
- const DEFAULT_LABELS = { title: 'You have been signed out', action: 'Sign in again' };
3
+ import { DEFAULT_SESSION_DIALOG_LABELS } from './sessionDialogLabels.js';
4
4
 
5
5
  // Its colours are the browser's for the page's CSS color-scheme, which a dark
6
6
  // app sets on the root element (MUI: CssBaseline enableColorScheme).
@@ -40,7 +40,7 @@ const useModal = (open) => {
40
40
  export const SessionExpiredDialog = ({ open, onSignIn, labels }) => {
41
41
  const ref = useModal(open);
42
42
  const titleId = useId();
43
- const { title, action } = { ...DEFAULT_LABELS, ...labels };
43
+ const { title, action } = { ...DEFAULT_SESSION_DIALOG_LABELS, ...labels };
44
44
  return createElement(
45
45
  'dialog',
46
46
  { ref, 'aria-labelledby': titleId, onCancel: keepOpen, style: dialogStyle },
@@ -61,6 +61,7 @@ const useAutomaticReload = (reaction) =>
61
61
  * @param {number} [params.reloadCooldownMs] Default 30 000
62
62
  * @param {string} [params.signOutUrl] Where signOut goes; through the login proxy by default
63
63
  * @param {{ title?: string, action?: string }} [params.dialogLabels]
64
+ * @param {import('react').ComponentType} [params.dialogComponent] Drawn for `dialog`; gets `open`, `onSignIn` and `labels`
64
65
  * @param {import('react').ReactNode} params.children
65
66
  */
66
67
  export const SessionProvider = ({
@@ -70,6 +71,7 @@ export const SessionProvider = ({
70
71
  reloadCooldownMs = DEFAULT_RELOAD_COOLDOWN_MS,
71
72
  signOutUrl,
72
73
  dialogLabels,
74
+ dialogComponent = SessionExpiredDialog,
73
75
  children,
74
76
  }) => {
75
77
  const reactions = useMemo(
@@ -108,7 +110,7 @@ export const SessionProvider = ({
108
110
  SessionContext,
109
111
  { value },
110
112
  children,
111
- createElement(SessionExpiredDialog, {
113
+ createElement(dialogComponent, {
112
114
  open: reaction === REACTION.DIALOG,
113
115
  onSignIn: reloadPage,
114
116
  labels: dialogLabels,
@@ -0,0 +1,28 @@
1
+ import { Button, Dialog, DialogActions, DialogTitle } from '@mui/material';
2
+ import { createElement, useId } from 'react';
3
+
4
+ import { DEFAULT_SESSION_DIALOG_LABELS } from '../sessionDialogLabels.js';
5
+
6
+ /**
7
+ * SessionExpiredDialog drawn with MUI, so it takes the app's theme. Pass it to
8
+ * SessionProvider as `dialogComponent`. It has no onClose: neither Escape nor
9
+ * the backdrop closes it, since the only way on is to sign in again.
10
+ * @param {object} params
11
+ * @param {boolean} params.open
12
+ * @param {() => void} params.onSignIn
13
+ * @param {{ title?: string, action?: string }} [params.labels]
14
+ */
15
+ export const MuiSessionExpiredDialog = ({ open, onSignIn, labels }) => {
16
+ const titleId = useId();
17
+ const { title, action } = { ...DEFAULT_SESSION_DIALOG_LABELS, ...labels };
18
+ return createElement(
19
+ Dialog,
20
+ { open, 'aria-labelledby': titleId },
21
+ createElement(DialogTitle, { id: titleId }, title),
22
+ createElement(
23
+ DialogActions,
24
+ undefined,
25
+ createElement(Button, { variant: 'contained', onClick: onSignIn, autoFocus: true }, action),
26
+ ),
27
+ );
28
+ };
@@ -0,0 +1 @@
1
+ export { MuiSessionExpiredDialog } from './MuiSessionExpiredDialog.js';
@@ -0,0 +1,4 @@
1
+ export const DEFAULT_SESSION_DIALOG_LABELS = {
2
+ title: 'You have been signed out',
3
+ action: 'Sign in again',
4
+ };