@stack-spot/portal-layout 0.0.48 → 0.0.50

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 (95) hide show
  1. package/dist/Layout.d.ts +2 -2
  2. package/dist/Layout.js +1 -1
  3. package/dist/LayoutOverlayManager.js +6 -6
  4. package/dist/LayoutOverlayManager.js.map +1 -1
  5. package/dist/components/Dialog.d.ts +1 -1
  6. package/dist/components/Dialog.js +1 -1
  7. package/dist/components/Header.d.ts +1 -1
  8. package/dist/components/Header.js +1 -1
  9. package/dist/components/OverlayContent.d.ts +1 -1
  10. package/dist/components/OverlayContent.js +20 -20
  11. package/dist/components/PortalSwitcher.d.ts +1 -1
  12. package/dist/components/PortalSwitcher.js +54 -54
  13. package/dist/components/SelectionList.d.ts +1 -1
  14. package/dist/components/SelectionList.js +54 -54
  15. package/dist/components/SelectionList.js.map +1 -1
  16. package/dist/components/Toaster.d.ts +1 -1
  17. package/dist/components/Toaster.js +1 -1
  18. package/dist/components/UserMenu.d.ts +1 -1
  19. package/dist/components/UserMenu.js +42 -42
  20. package/dist/components/UserMenu.js.map +1 -1
  21. package/dist/components/error/ErrorBoundary.d.ts +1 -1
  22. package/dist/components/error/ErrorBoundary.js +1 -1
  23. package/dist/components/error/ErrorFeedback.d.ts +1 -1
  24. package/dist/components/error/ErrorFeedback.js +1 -1
  25. package/dist/components/error/SilentErrorBoundary.d.ts +1 -1
  26. package/dist/components/error/SilentErrorBoundary.js +1 -1
  27. package/dist/components/menu/MenuContent.d.ts +12 -10
  28. package/dist/components/menu/MenuContent.d.ts.map +1 -1
  29. package/dist/components/menu/MenuContent.js +147 -147
  30. package/dist/components/menu/MenuContent.js.map +1 -1
  31. package/dist/components/menu/MenuSections.d.ts +1 -1
  32. package/dist/components/menu/MenuSections.js +1 -1
  33. package/dist/components/menu/MenuSections.js.map +1 -1
  34. package/dist/components/menu/PageSelector.d.ts +1 -1
  35. package/dist/components/menu/PageSelector.js +65 -65
  36. package/dist/components/menu/PageSelector.js.map +1 -1
  37. package/dist/components/menu/use-check-text-overflow.js.map +1 -1
  38. package/dist/layout.css +465 -465
  39. package/dist/svg/AI.d.ts +1 -1
  40. package/dist/svg/AI.js +1 -1
  41. package/dist/svg/EDP.d.ts +1 -1
  42. package/dist/svg/EDP.js +1 -1
  43. package/dist/svg/Forbidden.d.ts +1 -1
  44. package/dist/svg/Forbidden.js +1 -1
  45. package/dist/svg/HUB.d.ts +1 -1
  46. package/dist/svg/HUB.js +1 -1
  47. package/dist/svg/Logo.d.ts +1 -1
  48. package/dist/svg/Logo.js +1 -1
  49. package/dist/svg/NotFound.d.ts +1 -1
  50. package/dist/svg/NotFound.js +1 -1
  51. package/dist/svg/ServerError.d.ts +1 -1
  52. package/dist/svg/ServerError.js +1 -1
  53. package/dist/svg/Unauthenticated.d.ts +1 -1
  54. package/dist/svg/Unauthenticated.js +1 -1
  55. package/dist/toaster.js +2 -2
  56. package/dist/toaster.js.map +1 -1
  57. package/dist/utils.js.map +1 -1
  58. package/package.json +2 -2
  59. package/src/Layout.tsx +103 -103
  60. package/src/LayoutOverlayManager.tsx +273 -273
  61. package/src/components/Dialog.tsx +93 -93
  62. package/src/components/Header.tsx +29 -29
  63. package/src/components/OverlayContent.tsx +58 -58
  64. package/src/components/PortalSwitcher.tsx +147 -147
  65. package/src/components/SelectionList.tsx +268 -268
  66. package/src/components/Toaster.tsx +16 -16
  67. package/src/components/UserMenu.tsx +111 -111
  68. package/src/components/error/ErrorBoundary.tsx +38 -38
  69. package/src/components/error/ErrorFeedback.tsx +114 -114
  70. package/src/components/error/ErrorManager.ts +31 -31
  71. package/src/components/error/SilentErrorBoundary.tsx +54 -54
  72. package/src/components/menu/MenuContent.tsx +293 -293
  73. package/src/components/menu/MenuSections.tsx +268 -268
  74. package/src/components/menu/PageSelector.tsx +152 -152
  75. package/src/components/menu/constants.ts +2 -2
  76. package/src/components/menu/types.ts +112 -112
  77. package/src/components/menu/use-check-text-overflow.tsx +26 -26
  78. package/src/components/menu/use-keyboard-controls.tsx +70 -70
  79. package/src/components/types.ts +15 -15
  80. package/src/dictionary.ts +25 -25
  81. package/src/elements.ts +24 -24
  82. package/src/errors.ts +11 -11
  83. package/src/index.ts +17 -17
  84. package/src/layout.css +465 -465
  85. package/src/svg/AI.tsx +37 -37
  86. package/src/svg/EDP.tsx +35 -35
  87. package/src/svg/Forbidden.tsx +22 -22
  88. package/src/svg/HUB.tsx +35 -35
  89. package/src/svg/Logo.tsx +35 -35
  90. package/src/svg/NotFound.tsx +16 -16
  91. package/src/svg/ServerError.tsx +33 -33
  92. package/src/svg/Unauthenticated.tsx +16 -16
  93. package/src/toaster.tsx +76 -76
  94. package/src/utils.ts +114 -114
  95. package/tsconfig.json +8 -8
@@ -1,273 +1,273 @@
1
- /* eslint-disable react-hooks/rules-of-hooks */
2
-
3
- import { Button } from '@citric/core'
4
- import { ReactElement, useLayoutEffect, useState } from 'react'
5
- import { Dialog, DialogOptions } from './components/Dialog'
6
- import { CLOSE_OVERLAY_ID, OverlayContent, OverlayContentProps } from './components/OverlayContent'
7
- import { getDictionary } from './dictionary'
8
- import { LayoutElements, elementIds, getLayoutElements } from './elements'
9
- import { ElementNotFound, LayoutError } from './errors'
10
- import { showToaster as showReactToaster } from './toaster'
11
- import { focusFirstChild, valueOfLayoutVar } from './utils'
12
-
13
- interface AlertOptions extends Omit<DialogOptions, 'cancel'> {
14
- showButton?: boolean,
15
- }
16
-
17
- type BottomDialogOptions = Omit<DialogOptions, 'title'>
18
- type OverlaySize = 'small' | 'medium' | 'large'
19
- type ModalSize = 'fit-content' | OverlaySize
20
- type SetContentFn = ((content: ReactElement | undefined) => void) | undefined
21
-
22
- interface OverlayContentSetter {
23
- modal?: SetContentFn,
24
- rightPanel?: SetContentFn,
25
- bottomDialog?: SetContentFn,
26
- }
27
-
28
- interface CustomModalOptions {
29
- size?: ModalSize,
30
- onClose?: () => void,
31
- }
32
-
33
- interface CustomRightPanelOptions {
34
- size?: OverlaySize,
35
- onClose?: () => void,
36
- }
37
-
38
- function multipleCallsWarning(type: 'modal' | 'rightPanel', timeMS: number) {
39
- return `
40
- Attempted to show a modal or rightPanel while a ${type} was still being closed. Closing a ${type} takes only ${timeMS}ms, so this action
41
- is unlikely to have been triggered by the user and may point to errors in your code. Please check.\nTip: showModal and showRightPanel
42
- are sideEffects and should never be called during the render of a component. Try to use "useEffect" or link it to an event, like
43
- "onClick".
44
- `.replace(/\s*\n\s+/g, ' ')
45
- }
46
-
47
- class LayoutOverlayManager {
48
- static readonly instance?: LayoutOverlayManager
49
- private setContent: OverlayContentSetter = {}
50
- private elements?: LayoutElements
51
- private onModalClose?: () => void
52
-
53
- private setupElements() {
54
- this.elements = getLayoutElements()
55
- this.elements.backdrop?.addEventListener('mousedown', (event) => {
56
- if (this.isModalOpen()) !this.elements?.modal?.contains?.(event.target as Node) && this.closeModal()
57
- else if (this.isRightPanelOpen()) !this.elements?.rightPanel?.contains?.(event.target as Node) && this.closeRightPanel()
58
- else this.setMainContentInteractivity(true)
59
- })
60
- this.elements.backdrop?.addEventListener('keydown', (event) => {
61
- if (event.key !== 'Escape') return
62
- if (this.isModalOpen()) this.closeModal()
63
- if (this.isRightPanelOpen()) this.closeRightPanel()
64
- else this.setMainContentInteractivity(true)
65
- event.preventDefault()
66
- })
67
- this.setInteractivity(this.elements?.modal, false)
68
- this.setInteractivity(this.elements?.rightPanel, false)
69
- this.setInteractivity(this.elements?.bottomDialog, false)
70
- }
71
-
72
- // this should actually be like Kotlin's "internal", i.e. private to any user of the lib, but public to the lib itself.
73
- // @ts-ignore
74
- private useOverlays() {
75
- useLayoutEffect(() => {
76
- if (!this.elements) this.setupElements()
77
- }, [])
78
- const [modal, setModal] = useState<ReactElement | undefined>()
79
- const [rightPanel, setRightPanel] = useState<ReactElement | undefined>()
80
- const [bottomDialog, setBottomDialog] = useState<ReactElement | undefined>()
81
- this.setContent.modal = setModal
82
- this.setContent.rightPanel = setRightPanel
83
- this.setContent.bottomDialog = setBottomDialog
84
- return { modal, rightPanel, bottomDialog }
85
- }
86
-
87
- private setInteractivity(element: HTMLElement | null | undefined, interactive: boolean) {
88
- if (interactive) {
89
- element?.removeAttribute('inert')
90
- element?.removeAttribute('aria-hidden')
91
- } else {
92
- element?.setAttribute('aria-hidden', '')
93
- element?.setAttribute('inert', '')
94
- }
95
- }
96
-
97
- private setMainContentInteractivity(interactive: boolean) {
98
- this.setInteractivity(this.elements?.page, interactive)
99
- this.setInteractivity(this.elements?.header, interactive)
100
- this.setInteractivity(this.elements?.menu, interactive)
101
- this.elements?.backdrop?.setAttribute('class', interactive ? '' : 'visible')
102
- }
103
-
104
- private showOverlay(element: HTMLElement | null | undefined, extraClasses: string[] = [], blockMainContent = true) {
105
- element?.classList.add('visible', ...extraClasses)
106
- this.setInteractivity(element, true)
107
- if (blockMainContent) this.setMainContentInteractivity(false)
108
- setTimeout(() => focusFirstChild(
109
- element,
110
- { priority: [['input', 'textarea', 'select', 'other', 'button']], ignore: `#${CLOSE_OVERLAY_ID}` },
111
- ), 50)
112
- }
113
-
114
- private hideOverlay(element: HTMLElement | null | undefined) {
115
- element?.setAttribute('class', '')
116
- this.setInteractivity(element, false)
117
- this.setMainContentInteractivity(true)
118
- }
119
-
120
- isModalOpen() {
121
- return this.elements?.modal?.classList.contains('visible') ?? false
122
- }
123
-
124
- isRightPanelOpen() {
125
- return this.elements?.rightPanel?.classList.contains('visible') ?? false
126
- }
127
-
128
- isBottomDialogOpen() {
129
- return this.elements?.bottomDialog?.classList.contains('visible') ?? false
130
- }
131
-
132
- showCustomModal(content: React.ReactElement, { size = 'medium', onClose }: CustomModalOptions = {}) {
133
- if (!this.elements?.modal) throw new ElementNotFound('modal', elementIds.modal)
134
- if (!this.setContent.modal) throw new LayoutError('unable to show modal, because it has not been setup yet.')
135
- this.onModalClose = onClose
136
- this.setContent.modal(content)
137
- this.showOverlay(this.elements.modal, [size])
138
- }
139
-
140
- showModal({ size, ...props }: OverlayContentProps & { size?: ModalSize }) {
141
- this.showCustomModal(<OverlayContent {...props} onClose={() => this.closeModal()} type="modal" />, { size, onClose: props.onClose })
142
- }
143
-
144
- private showDialog(options: DialogOptions): Promise<boolean> {
145
- let dialogResult = false
146
- return new Promise((resolve, reject) => {
147
- try {
148
- this.showCustomModal(
149
- <Dialog
150
- {...options}
151
- onCancel={() => this.closeModal()}
152
- onConfirm={() => {
153
- dialogResult = true
154
- this.closeModal()
155
- }}
156
- />,
157
- { size: 'small', onClose: () => resolve(dialogResult) },
158
- )
159
- } catch (error) {
160
- reject(error)
161
- }
162
- })
163
- }
164
-
165
- confirm({ confirm, cancel, ...options }: DialogOptions): Promise<boolean> {
166
- const t = getDictionary()
167
- return this.showDialog({ ...options, confirm: confirm || t.confirm, cancel: cancel || t.cancel })
168
- }
169
-
170
- async alert({ confirm, showButton = true, ...options }: AlertOptions): Promise<void> {
171
- const t = getDictionary()
172
- await this.showDialog({ ...options, confirm: showButton ? (confirm || t.confirm) : undefined })
173
- }
174
-
175
- showBottomDialog({ message, cancel, confirm }: BottomDialogOptions): Promise<boolean> {
176
- if (!this.elements?.bottomDialog) throw new ElementNotFound('bottom dialog', elementIds.bottomDialog)
177
- if (!this.setContent.bottomDialog) throw new LayoutError('unable to show bottom dialog, because it has not been setup yet.')
178
- return new Promise((resolve) => {
179
- this.setContent.bottomDialog?.(
180
- <>
181
- {message}
182
- <div className="btn-group">
183
- {cancel && <Button onClick={() => resolve(false)} colorScheme="light" appearance="outlined">{cancel}</Button>}
184
- {confirm && <Button onClick={() => resolve(true)} colorScheme="light">{confirm}</Button>}
185
- </div>
186
- </>,
187
- )
188
- this.showOverlay(this.elements?.bottomDialog, undefined, false)
189
- })
190
- }
191
-
192
- showCustomRightPanel(content: ReactElement, { size = 'medium', onClose }: CustomRightPanelOptions = {}) {
193
- if (!this.elements?.rightPanel) throw new ElementNotFound('right panel overlay', elementIds.rightPanel)
194
- if (!this.setContent.rightPanel) throw new LayoutError('unable to show right panel overlay, because it has not been setup yet.')
195
- this.onModalClose = onClose
196
- this.setContent.rightPanel(content)
197
- this.elements?.rightPanel.classList.add(size)
198
- setTimeout(() => {
199
- this.showOverlay(this.elements?.rightPanel)
200
- })
201
- }
202
-
203
- showRightPanel({ size, ...props }: OverlayContentProps & { size?: OverlaySize }) {
204
- this.showCustomRightPanel(
205
- <OverlayContent {...props} onClose={() => this.closeRightPanel()} type="panel" />,
206
- { size, onClose: props.onClose },
207
- )
208
- }
209
-
210
- closeModal(runCloseListener = true) {
211
- this.elements?.modal?.classList.remove('visible')
212
- this.elements?.backdrop?.setAttribute('class', '')
213
- if (runCloseListener && this.onModalClose) {
214
- const onClose = this.onModalClose
215
- // setting it to undefined before running it prevents nested calls to closeModal from generating infinite loops.
216
- this.onModalClose = undefined
217
- onClose()
218
- }
219
- const animationMS = parseFloat(valueOfLayoutVar('--modal-animation-duration')) * 1000
220
- setTimeout(
221
- () => {
222
- if (this.elements?.backdrop?.classList.contains('visible')) {
223
- // eslint-disable-next-line no-console
224
- console.warn(multipleCallsWarning('modal', animationMS))
225
- this.elements?.modal?.classList.remove('visible')
226
- }
227
- if (this.setContent.modal) this.setContent.modal(undefined)
228
- this.hideOverlay(this.elements?.modal)
229
- },
230
- animationMS,
231
- )
232
- }
233
-
234
- closeRightPanel(runCloseListener = true) {
235
- this.elements?.rightPanel?.classList.remove('visible')
236
- this.elements?.backdrop?.setAttribute('class', '')
237
- if (runCloseListener && this.onModalClose) {
238
- const onClose = this.onModalClose
239
- // setting it to undefined before running it prevents nested calls to closeRightPanel from generating infinite loops.
240
- this.onModalClose = undefined
241
- onClose()
242
- }
243
- const animationMS = parseFloat(valueOfLayoutVar('--right-panel-animation-duration')) * 1000
244
- setTimeout(
245
- () => {
246
- if (this.elements?.backdrop?.classList.contains('visible')) {
247
- // eslint-disable-next-line no-console
248
- console.warn(multipleCallsWarning('rightPanel', animationMS))
249
- this.elements?.rightPanel?.classList.remove('visible')
250
- }
251
- if (this.setContent.rightPanel) this.setContent.rightPanel(undefined)
252
- this.hideOverlay(this.elements?.rightPanel)
253
- },
254
- animationMS,
255
- )
256
- }
257
-
258
- closeBottomDialog() {
259
- this.hideOverlay(this.elements?.bottomDialog)
260
- }
261
-
262
- isInsideModal(element: HTMLElement) {
263
- return !!this.elements?.modal?.contains(element)
264
- }
265
-
266
- isInsideRightPanel(element: HTMLElement) {
267
- return !!this.elements?.rightPanel?.contains(element)
268
- }
269
-
270
- showToaster = showReactToaster
271
- }
272
-
273
- export const overlay = new LayoutOverlayManager()
1
+ /* eslint-disable react-hooks/rules-of-hooks */
2
+
3
+ import { Button } from '@citric/core'
4
+ import { ReactElement, useLayoutEffect, useState } from 'react'
5
+ import { Dialog, DialogOptions } from './components/Dialog'
6
+ import { CLOSE_OVERLAY_ID, OverlayContent, OverlayContentProps } from './components/OverlayContent'
7
+ import { getDictionary } from './dictionary'
8
+ import { LayoutElements, elementIds, getLayoutElements } from './elements'
9
+ import { ElementNotFound, LayoutError } from './errors'
10
+ import { showToaster as showReactToaster } from './toaster'
11
+ import { focusFirstChild, valueOfLayoutVar } from './utils'
12
+
13
+ interface AlertOptions extends Omit<DialogOptions, 'cancel'> {
14
+ showButton?: boolean,
15
+ }
16
+
17
+ type BottomDialogOptions = Omit<DialogOptions, 'title'>
18
+ type OverlaySize = 'small' | 'medium' | 'large'
19
+ type ModalSize = 'fit-content' | OverlaySize
20
+ type SetContentFn = ((content: ReactElement | undefined) => void) | undefined
21
+
22
+ interface OverlayContentSetter {
23
+ modal?: SetContentFn,
24
+ rightPanel?: SetContentFn,
25
+ bottomDialog?: SetContentFn,
26
+ }
27
+
28
+ interface CustomModalOptions {
29
+ size?: ModalSize,
30
+ onClose?: () => void,
31
+ }
32
+
33
+ interface CustomRightPanelOptions {
34
+ size?: OverlaySize,
35
+ onClose?: () => void,
36
+ }
37
+
38
+ function multipleCallsWarning(type: 'modal' | 'rightPanel', timeMS: number) {
39
+ return `
40
+ Attempted to show a modal or rightPanel while a ${type} was still being closed. Closing a ${type} takes only ${timeMS}ms, so this action
41
+ is unlikely to have been triggered by the user and may point to errors in your code. Please check.\nTip: showModal and showRightPanel
42
+ are sideEffects and should never be called during the render of a component. Try to use "useEffect" or link it to an event, like
43
+ "onClick".
44
+ `.replace(/\s*\n\s+/g, ' ')
45
+ }
46
+
47
+ class LayoutOverlayManager {
48
+ static readonly instance?: LayoutOverlayManager
49
+ private setContent: OverlayContentSetter = {}
50
+ private elements?: LayoutElements
51
+ private onModalClose?: () => void
52
+
53
+ private setupElements() {
54
+ this.elements = getLayoutElements()
55
+ this.elements.backdrop?.addEventListener('mousedown', (event) => {
56
+ if (this.isModalOpen()) !this.elements?.modal?.contains?.(event.target as Node) && this.closeModal()
57
+ else if (this.isRightPanelOpen()) !this.elements?.rightPanel?.contains?.(event.target as Node) && this.closeRightPanel()
58
+ else this.setMainContentInteractivity(true)
59
+ })
60
+ this.elements.backdrop?.addEventListener('keydown', (event) => {
61
+ if (event.key !== 'Escape') return
62
+ if (this.isModalOpen()) this.closeModal()
63
+ if (this.isRightPanelOpen()) this.closeRightPanel()
64
+ else this.setMainContentInteractivity(true)
65
+ event.preventDefault()
66
+ })
67
+ this.setInteractivity(this.elements?.modal, false)
68
+ this.setInteractivity(this.elements?.rightPanel, false)
69
+ this.setInteractivity(this.elements?.bottomDialog, false)
70
+ }
71
+
72
+ // this should actually be like Kotlin's "internal", i.e. private to any user of the lib, but public to the lib itself.
73
+ // @ts-ignore
74
+ private useOverlays() {
75
+ useLayoutEffect(() => {
76
+ if (!this.elements) this.setupElements()
77
+ }, [])
78
+ const [modal, setModal] = useState<ReactElement | undefined>()
79
+ const [rightPanel, setRightPanel] = useState<ReactElement | undefined>()
80
+ const [bottomDialog, setBottomDialog] = useState<ReactElement | undefined>()
81
+ this.setContent.modal = setModal
82
+ this.setContent.rightPanel = setRightPanel
83
+ this.setContent.bottomDialog = setBottomDialog
84
+ return { modal, rightPanel, bottomDialog }
85
+ }
86
+
87
+ private setInteractivity(element: HTMLElement | null | undefined, interactive: boolean) {
88
+ if (interactive) {
89
+ element?.removeAttribute('inert')
90
+ element?.removeAttribute('aria-hidden')
91
+ } else {
92
+ element?.setAttribute('aria-hidden', '')
93
+ element?.setAttribute('inert', '')
94
+ }
95
+ }
96
+
97
+ private setMainContentInteractivity(interactive: boolean) {
98
+ this.setInteractivity(this.elements?.page, interactive)
99
+ this.setInteractivity(this.elements?.header, interactive)
100
+ this.setInteractivity(this.elements?.menu, interactive)
101
+ this.elements?.backdrop?.setAttribute('class', interactive ? '' : 'visible')
102
+ }
103
+
104
+ private showOverlay(element: HTMLElement | null | undefined, extraClasses: string[] = [], blockMainContent = true) {
105
+ element?.classList.add('visible', ...extraClasses)
106
+ this.setInteractivity(element, true)
107
+ if (blockMainContent) this.setMainContentInteractivity(false)
108
+ setTimeout(() => focusFirstChild(
109
+ element,
110
+ { priority: [['input', 'textarea', 'select', 'other', 'button']], ignore: `#${CLOSE_OVERLAY_ID}` },
111
+ ), 50)
112
+ }
113
+
114
+ private hideOverlay(element: HTMLElement | null | undefined) {
115
+ element?.setAttribute('class', '')
116
+ this.setInteractivity(element, false)
117
+ this.setMainContentInteractivity(true)
118
+ }
119
+
120
+ isModalOpen() {
121
+ return this.elements?.modal?.classList.contains('visible') ?? false
122
+ }
123
+
124
+ isRightPanelOpen() {
125
+ return this.elements?.rightPanel?.classList.contains('visible') ?? false
126
+ }
127
+
128
+ isBottomDialogOpen() {
129
+ return this.elements?.bottomDialog?.classList.contains('visible') ?? false
130
+ }
131
+
132
+ showCustomModal(content: React.ReactElement, { size = 'medium', onClose }: CustomModalOptions = {}) {
133
+ if (!this.elements?.modal) throw new ElementNotFound('modal', elementIds.modal)
134
+ if (!this.setContent.modal) throw new LayoutError('unable to show modal, because it has not been setup yet.')
135
+ this.onModalClose = onClose
136
+ this.setContent.modal(content)
137
+ this.showOverlay(this.elements.modal, [size])
138
+ }
139
+
140
+ showModal({ size, ...props }: OverlayContentProps & { size?: ModalSize }) {
141
+ this.showCustomModal(<OverlayContent {...props} onClose={() => this.closeModal()} type="modal" />, { size, onClose: props.onClose })
142
+ }
143
+
144
+ private showDialog(options: DialogOptions): Promise<boolean> {
145
+ let dialogResult = false
146
+ return new Promise((resolve, reject) => {
147
+ try {
148
+ this.showCustomModal(
149
+ <Dialog
150
+ {...options}
151
+ onCancel={() => this.closeModal()}
152
+ onConfirm={() => {
153
+ dialogResult = true
154
+ this.closeModal()
155
+ }}
156
+ />,
157
+ { size: 'small', onClose: () => resolve(dialogResult) },
158
+ )
159
+ } catch (error) {
160
+ reject(error)
161
+ }
162
+ })
163
+ }
164
+
165
+ confirm({ confirm, cancel, ...options }: DialogOptions): Promise<boolean> {
166
+ const t = getDictionary()
167
+ return this.showDialog({ ...options, confirm: confirm || t.confirm, cancel: cancel || t.cancel })
168
+ }
169
+
170
+ async alert({ confirm, showButton = true, ...options }: AlertOptions): Promise<void> {
171
+ const t = getDictionary()
172
+ await this.showDialog({ ...options, confirm: showButton ? (confirm || t.confirm) : undefined })
173
+ }
174
+
175
+ showBottomDialog({ message, cancel, confirm }: BottomDialogOptions): Promise<boolean> {
176
+ if (!this.elements?.bottomDialog) throw new ElementNotFound('bottom dialog', elementIds.bottomDialog)
177
+ if (!this.setContent.bottomDialog) throw new LayoutError('unable to show bottom dialog, because it has not been setup yet.')
178
+ return new Promise((resolve) => {
179
+ this.setContent.bottomDialog?.(
180
+ <>
181
+ {message}
182
+ <div className="btn-group">
183
+ {cancel && <Button onClick={() => resolve(false)} colorScheme="light" appearance="outlined">{cancel}</Button>}
184
+ {confirm && <Button onClick={() => resolve(true)} colorScheme="light">{confirm}</Button>}
185
+ </div>
186
+ </>,
187
+ )
188
+ this.showOverlay(this.elements?.bottomDialog, undefined, false)
189
+ })
190
+ }
191
+
192
+ showCustomRightPanel(content: ReactElement, { size = 'medium', onClose }: CustomRightPanelOptions = {}) {
193
+ if (!this.elements?.rightPanel) throw new ElementNotFound('right panel overlay', elementIds.rightPanel)
194
+ if (!this.setContent.rightPanel) throw new LayoutError('unable to show right panel overlay, because it has not been setup yet.')
195
+ this.onModalClose = onClose
196
+ this.setContent.rightPanel(content)
197
+ this.elements?.rightPanel.classList.add(size)
198
+ setTimeout(() => {
199
+ this.showOverlay(this.elements?.rightPanel)
200
+ })
201
+ }
202
+
203
+ showRightPanel({ size, ...props }: OverlayContentProps & { size?: OverlaySize }) {
204
+ this.showCustomRightPanel(
205
+ <OverlayContent {...props} onClose={() => this.closeRightPanel()} type="panel" />,
206
+ { size, onClose: props.onClose },
207
+ )
208
+ }
209
+
210
+ closeModal(runCloseListener = true) {
211
+ this.elements?.modal?.classList.remove('visible')
212
+ this.elements?.backdrop?.setAttribute('class', '')
213
+ if (runCloseListener && this.onModalClose) {
214
+ const onClose = this.onModalClose
215
+ // setting it to undefined before running it prevents nested calls to closeModal from generating infinite loops.
216
+ this.onModalClose = undefined
217
+ onClose()
218
+ }
219
+ const animationMS = parseFloat(valueOfLayoutVar('--modal-animation-duration')) * 1000
220
+ setTimeout(
221
+ () => {
222
+ if (this.elements?.backdrop?.classList.contains('visible')) {
223
+ // eslint-disable-next-line no-console
224
+ console.warn(multipleCallsWarning('modal', animationMS))
225
+ this.elements?.modal?.classList.remove('visible')
226
+ }
227
+ if (this.setContent.modal) this.setContent.modal(undefined)
228
+ this.hideOverlay(this.elements?.modal)
229
+ },
230
+ animationMS,
231
+ )
232
+ }
233
+
234
+ closeRightPanel(runCloseListener = true) {
235
+ this.elements?.rightPanel?.classList.remove('visible')
236
+ this.elements?.backdrop?.setAttribute('class', '')
237
+ if (runCloseListener && this.onModalClose) {
238
+ const onClose = this.onModalClose
239
+ // setting it to undefined before running it prevents nested calls to closeRightPanel from generating infinite loops.
240
+ this.onModalClose = undefined
241
+ onClose()
242
+ }
243
+ const animationMS = parseFloat(valueOfLayoutVar('--right-panel-animation-duration')) * 1000
244
+ setTimeout(
245
+ () => {
246
+ if (this.elements?.backdrop?.classList.contains('visible')) {
247
+ // eslint-disable-next-line no-console
248
+ console.warn(multipleCallsWarning('rightPanel', animationMS))
249
+ this.elements?.rightPanel?.classList.remove('visible')
250
+ }
251
+ if (this.setContent.rightPanel) this.setContent.rightPanel(undefined)
252
+ this.hideOverlay(this.elements?.rightPanel)
253
+ },
254
+ animationMS,
255
+ )
256
+ }
257
+
258
+ closeBottomDialog() {
259
+ this.hideOverlay(this.elements?.bottomDialog)
260
+ }
261
+
262
+ isInsideModal(element: HTMLElement) {
263
+ return !!this.elements?.modal?.contains(element)
264
+ }
265
+
266
+ isInsideRightPanel(element: HTMLElement) {
267
+ return !!this.elements?.rightPanel?.contains(element)
268
+ }
269
+
270
+ showToaster = showReactToaster
271
+ }
272
+
273
+ export const overlay = new LayoutOverlayManager()