@stack-spot/portal-layout 0.0.65 → 1.0.0-dev.1768482785050
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 +779 -0
- package/dist/Layout.d.ts +60 -8
- package/dist/Layout.d.ts.map +1 -1
- package/dist/Layout.js +59 -24
- package/dist/Layout.js.map +1 -1
- package/dist/LayoutOverlayManager.d.ts +274 -19
- package/dist/LayoutOverlayManager.d.ts.map +1 -1
- package/dist/LayoutOverlayManager.js +373 -82
- package/dist/LayoutOverlayManager.js.map +1 -1
- package/dist/WelcomeTour.d.ts +2 -0
- package/dist/WelcomeTour.d.ts.map +1 -0
- package/dist/WelcomeTour.js +8 -0
- package/dist/WelcomeTour.js.map +1 -0
- package/dist/components/Backdrop.d.ts +75 -0
- package/dist/components/Backdrop.d.ts.map +1 -0
- package/dist/components/Backdrop.js +69 -0
- package/dist/components/Backdrop.js.map +1 -0
- package/dist/components/Contact/show-contact-modal.d.ts +5 -0
- package/dist/components/Contact/show-contact-modal.d.ts.map +1 -0
- package/dist/components/Contact/show-contact-modal.js +37 -0
- package/dist/components/Contact/show-contact-modal.js.map +1 -0
- package/dist/components/ContactModal.d.ts +1 -0
- package/dist/components/ContactModal.d.ts.map +1 -0
- package/dist/components/ContactModal.js +2 -0
- package/dist/components/ContactModal.js.map +1 -0
- package/dist/components/Dialog.d.ts +54 -7
- package/dist/components/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog.js +8 -2
- package/dist/components/Dialog.js.map +1 -1
- package/dist/components/Header.d.ts +38 -2
- package/dist/components/Header.d.ts.map +1 -1
- package/dist/components/Header.js +9 -4
- package/dist/components/Header.js.map +1 -1
- package/dist/components/NotificationCenter/NotificationPanel.d.ts +3 -0
- package/dist/components/NotificationCenter/NotificationPanel.d.ts.map +1 -0
- package/dist/components/NotificationCenter/NotificationPanel.js +19 -0
- package/dist/components/NotificationCenter/NotificationPanel.js.map +1 -0
- package/dist/components/NotificationCenter/NotificationPanelHeader.d.ts +3 -0
- package/dist/components/NotificationCenter/NotificationPanelHeader.d.ts.map +1 -0
- package/dist/components/NotificationCenter/NotificationPanelHeader.js +16 -0
- package/dist/components/NotificationCenter/NotificationPanelHeader.js.map +1 -0
- package/dist/components/NotificationCenter/NotificationsPanelFooter.d.ts +4 -0
- package/dist/components/NotificationCenter/NotificationsPanelFooter.d.ts.map +1 -0
- package/dist/components/NotificationCenter/NotificationsPanelFooter.js +12 -0
- package/dist/components/NotificationCenter/NotificationsPanelFooter.js.map +1 -0
- package/dist/components/NotificationCenter/dictionary.d.ts +2 -0
- package/dist/components/NotificationCenter/dictionary.d.ts.map +1 -0
- package/dist/components/NotificationCenter/dictionary.js +43 -0
- package/dist/components/NotificationCenter/dictionary.js.map +1 -0
- package/dist/components/NotificationCenter/index.d.ts +2 -0
- package/dist/components/NotificationCenter/index.d.ts.map +1 -0
- package/dist/components/NotificationCenter/index.js +34 -0
- package/dist/components/NotificationCenter/index.js.map +1 -0
- package/dist/components/NotificationCenter/styled.d.ts +3 -0
- package/dist/components/NotificationCenter/styled.d.ts.map +1 -0
- package/dist/components/NotificationCenter/styled.js +74 -0
- package/dist/components/NotificationCenter/styled.js.map +1 -0
- package/dist/components/NotificationCenter/types.d.ts +21 -0
- package/dist/components/NotificationCenter/types.d.ts.map +1 -0
- package/dist/components/NotificationCenter/types.js.map +1 -0
- package/dist/components/NotificationCenter/utils.d.ts +5 -0
- package/dist/components/NotificationCenter/utils.d.ts.map +1 -0
- package/dist/components/NotificationCenter/utils.js +18 -0
- package/dist/components/NotificationCenter/utils.js.map +1 -0
- package/dist/components/OverlayContent.d.ts +27 -1
- package/dist/components/OverlayContent.d.ts.map +1 -1
- package/dist/components/OverlayContent.js +8 -4
- package/dist/components/OverlayContent.js.map +1 -1
- package/dist/components/PortalSwitcher.d.ts +19 -1
- package/dist/components/PortalSwitcher.d.ts.map +1 -1
- package/dist/components/PortalSwitcher.js +16 -31
- package/dist/components/PortalSwitcher.js.map +1 -1
- package/dist/components/PrivacyPolicyMessage/hooks.d.ts +10 -0
- package/dist/components/PrivacyPolicyMessage/hooks.d.ts.map +1 -0
- package/dist/components/PrivacyPolicyMessage/hooks.js +33 -0
- package/dist/components/PrivacyPolicyMessage/hooks.js.map +1 -0
- package/dist/components/PrivacyPolicyMessage/index.d.ts +7 -0
- package/dist/components/PrivacyPolicyMessage/index.d.ts.map +1 -0
- package/dist/components/PrivacyPolicyMessage/index.js +5 -0
- package/dist/components/PrivacyPolicyMessage/index.js.map +1 -0
- package/dist/components/Rate/FeedbackModal.d.ts +8 -0
- package/dist/components/Rate/FeedbackModal.d.ts.map +1 -0
- package/dist/components/Rate/FeedbackModal.js +52 -0
- package/dist/components/Rate/FeedbackModal.js.map +1 -0
- package/dist/components/Rate/hook.d.ts +3 -0
- package/dist/components/Rate/hook.d.ts.map +1 -0
- package/dist/components/Rate/hook.js +48 -0
- package/dist/components/Rate/hook.js.map +1 -0
- package/dist/components/Rate/index.d.ts +10 -0
- package/dist/components/Rate/index.d.ts.map +1 -0
- package/dist/components/Rate/index.js +16 -0
- package/dist/components/Rate/index.js.map +1 -0
- package/dist/components/Rate/on-nps-submit.d.ts +7 -0
- package/dist/components/Rate/on-nps-submit.d.ts.map +1 -0
- package/dist/components/Rate/on-nps-submit.js +8 -0
- package/dist/components/Rate/on-nps-submit.js.map +1 -0
- package/dist/components/Rate/show-rate-us-modals.d.ts +17 -0
- package/dist/components/Rate/show-rate-us-modals.d.ts.map +1 -0
- package/dist/components/Rate/show-rate-us-modals.js +14 -0
- package/dist/components/Rate/show-rate-us-modals.js.map +1 -0
- package/dist/components/Rate/utils.d.ts +2 -0
- package/dist/components/Rate/utils.d.ts.map +1 -0
- package/dist/components/Rate/utils.js +10 -0
- package/dist/components/Rate/utils.js.map +1 -0
- package/dist/components/Toaster.d.ts +35 -0
- package/dist/components/Toaster.d.ts.map +1 -1
- package/dist/components/Toaster.js +32 -4
- package/dist/components/Toaster.js.map +1 -1
- package/dist/components/TypeForm/hook.d.ts +2 -0
- package/dist/components/TypeForm/hook.d.ts.map +1 -0
- package/dist/components/TypeForm/hook.js +11 -0
- package/dist/components/TypeForm/hook.js.map +1 -0
- package/dist/components/TypeForm/index.d.ts +6 -0
- package/dist/components/TypeForm/index.d.ts.map +1 -0
- package/dist/components/TypeForm/index.js +23 -0
- package/dist/components/TypeForm/index.js.map +1 -0
- package/dist/components/TypeForm/show-typeform-modal.d.ts +5 -0
- package/dist/components/TypeForm/show-typeform-modal.d.ts.map +1 -0
- package/dist/components/TypeForm/show-typeform-modal.js +11 -0
- package/dist/components/TypeForm/show-typeform-modal.js.map +1 -0
- package/dist/components/TypeForm/utils.d.ts +2 -0
- package/dist/components/TypeForm/utils.d.ts.map +1 -0
- package/dist/components/TypeForm/utils.js +9 -0
- package/dist/components/TypeForm/utils.js.map +1 -0
- package/dist/components/UserMenu.d.ts +19 -2
- package/dist/components/UserMenu.d.ts.map +1 -1
- package/dist/components/UserMenu.js +12 -6
- package/dist/components/UserMenu.js.map +1 -1
- package/dist/components/error/ErrorBoundary.d.ts +25 -4
- package/dist/components/error/ErrorBoundary.d.ts.map +1 -1
- package/dist/components/error/ErrorBoundary.js +10 -1
- package/dist/components/error/ErrorBoundary.js.map +1 -1
- package/dist/components/error/ErrorManager.d.ts +22 -6
- package/dist/components/error/ErrorManager.d.ts.map +1 -1
- package/dist/components/error/ErrorManager.js +21 -1
- package/dist/components/error/ErrorManager.js.map +1 -1
- package/dist/components/error/SilentErrorBoundary.d.ts +26 -5
- package/dist/components/error/SilentErrorBoundary.d.ts.map +1 -1
- package/dist/components/error/SilentErrorBoundary.js +10 -0
- package/dist/components/error/SilentErrorBoundary.js.map +1 -1
- package/dist/components/menu/MenuContent.d.ts +20 -4
- package/dist/components/menu/MenuContent.d.ts.map +1 -1
- package/dist/components/menu/MenuContent.js +89 -88
- package/dist/components/menu/MenuContent.js.map +1 -1
- package/dist/components/menu/MenuSectionGroup.d.ts +2 -0
- package/dist/components/menu/MenuSectionGroup.d.ts.map +1 -0
- package/dist/components/menu/MenuSectionGroup.js +121 -0
- package/dist/components/menu/MenuSectionGroup.js.map +1 -0
- package/dist/components/menu/MenuSections.d.ts +17 -0
- package/dist/components/menu/MenuSections.d.ts.map +1 -1
- package/dist/components/menu/MenuSections.js +159 -39
- package/dist/components/menu/MenuSections.js.map +1 -1
- package/dist/components/menu/PageSelector.d.ts +5 -0
- package/dist/components/menu/PageSelector.d.ts.map +1 -1
- package/dist/components/menu/PageSelector.js +9 -4
- package/dist/components/menu/PageSelector.js.map +1 -1
- package/dist/components/menu/types.d.ts +219 -8
- package/dist/components/menu/types.d.ts.map +1 -1
- package/dist/components/tour/StepContainer.d.ts +37 -0
- package/dist/components/tour/StepContainer.d.ts.map +1 -0
- package/dist/components/tour/StepContainer.js +51 -0
- package/dist/components/tour/StepContainer.js.map +1 -0
- package/dist/components/tour/StepNavigation.d.ts +29 -0
- package/dist/components/tour/StepNavigation.d.ts.map +1 -0
- package/dist/components/tour/StepNavigation.js +37 -0
- package/dist/components/tour/StepNavigation.js.map +1 -0
- package/dist/components/tour/StepTitle.d.ts +17 -0
- package/dist/components/tour/StepTitle.d.ts.map +1 -0
- package/dist/components/tour/StepTitle.js +10 -0
- package/dist/components/tour/StepTitle.js.map +1 -0
- package/dist/components/tour/hook.d.ts +3 -0
- package/dist/components/tour/hook.d.ts.map +1 -0
- package/dist/components/tour/hook.js +10 -0
- package/dist/components/tour/hook.js.map +1 -0
- package/dist/components/tour/index.d.ts +5 -0
- package/dist/components/tour/index.d.ts.map +1 -0
- package/dist/components/tour/index.js +5 -0
- package/dist/components/tour/index.js.map +1 -0
- package/dist/components/tour/manager.d.ts +34 -0
- package/dist/components/tour/manager.d.ts.map +1 -0
- package/dist/components/tour/manager.js +104 -0
- package/dist/components/tour/manager.js.map +1 -0
- package/dist/components/tour/utils.d.ts +67 -0
- package/dist/components/tour/utils.d.ts.map +1 -0
- package/dist/components/tour/utils.js +60 -0
- package/dist/components/tour/utils.js.map +1 -0
- package/dist/components/user-menu-manager.d.ts +13 -0
- package/dist/components/user-menu-manager.d.ts.map +1 -0
- package/dist/components/user-menu-manager.js +36 -0
- package/dist/components/user-menu-manager.js.map +1 -0
- package/dist/dictionary.d.ts +6 -1
- package/dist/dictionary.d.ts.map +1 -1
- package/dist/dictionary.js +7 -2
- package/dist/dictionary.js.map +1 -1
- package/dist/elements.d.ts +7 -0
- package/dist/elements.d.ts.map +1 -1
- package/dist/elements.js +7 -0
- package/dist/elements.js.map +1 -1
- package/dist/index.d.ts +15 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -8
- package/dist/index.js.map +1 -1
- package/dist/layout.css +190 -35
- package/dist/svg/StarFillWithGradient.d.ts +6 -0
- package/dist/svg/StarFillWithGradient.d.ts.map +1 -0
- package/dist/svg/StarFillWithGradient.js +4 -0
- package/dist/svg/StarFillWithGradient.js.map +1 -0
- package/dist/toaster.d.ts +55 -9
- package/dist/toaster.d.ts.map +1 -1
- package/dist/toaster.js +34 -6
- package/dist/toaster.js.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +6 -69
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +9 -130
- package/dist/utils.js.map +1 -1
- package/package.dev.json +3 -0
- package/package.json +24 -13
- package/package.stg.json +3 -0
- package/readme.md +147 -0
- package/src/Layout.tsx +166 -58
- package/src/LayoutOverlayManager.tsx +499 -85
- package/src/WelcomeTour.tsx +8 -0
- package/src/components/Backdrop.tsx +116 -0
- package/src/components/Contact/show-contact-modal.tsx +71 -0
- package/src/components/Dialog.tsx +58 -9
- package/src/components/Header.tsx +53 -5
- package/src/components/NotificationCenter/NotificationPanel.tsx +40 -0
- package/src/components/NotificationCenter/NotificationPanelHeader.tsx +53 -0
- package/src/components/NotificationCenter/NotificationsPanelFooter.tsx +25 -0
- package/src/components/NotificationCenter/dictionary.ts +44 -0
- package/src/components/NotificationCenter/index.tsx +58 -0
- package/src/components/NotificationCenter/styled.ts +75 -0
- package/src/components/NotificationCenter/types.ts +24 -0
- package/src/components/NotificationCenter/utils.ts +20 -0
- package/src/components/OverlayContent.tsx +40 -5
- package/src/components/PortalSwitcher.tsx +33 -39
- package/src/components/PrivacyPolicyMessage/hooks.tsx +49 -0
- package/src/components/PrivacyPolicyMessage/index.tsx +21 -0
- package/src/components/Rate/FeedbackModal.tsx +86 -0
- package/src/components/Rate/hook.tsx +61 -0
- package/src/components/Rate/index.tsx +36 -0
- package/src/components/Rate/on-nps-submit.ts +18 -0
- package/src/components/Rate/show-rate-us-modals.tsx +29 -0
- package/src/components/Rate/utils.ts +11 -0
- package/src/components/Toaster.tsx +82 -3
- package/src/components/TypeForm/hook.tsx +13 -0
- package/src/components/TypeForm/index.tsx +50 -0
- package/src/components/TypeForm/show-typeform-modal.tsx +10 -0
- package/src/components/TypeForm/utils.ts +8 -0
- package/src/components/UserMenu.tsx +32 -8
- package/src/components/error/ErrorBoundary.tsx +11 -2
- package/src/components/error/ErrorManager.ts +22 -6
- package/src/components/error/SilentErrorBoundary.tsx +12 -2
- package/src/components/menu/MenuContent.tsx +102 -110
- package/src/components/menu/MenuSectionGroup.tsx +121 -0
- package/src/components/menu/MenuSections.tsx +342 -93
- package/src/components/menu/PageSelector.tsx +16 -4
- package/src/components/menu/types.ts +221 -9
- package/src/components/tour/StepContainer.tsx +92 -0
- package/src/components/tour/StepNavigation.tsx +72 -0
- package/src/components/tour/StepTitle.tsx +28 -0
- package/src/components/tour/hook.ts +12 -0
- package/src/components/tour/index.ts +6 -0
- package/src/components/tour/manager.tsx +119 -0
- package/src/components/tour/utils.tsx +119 -0
- package/src/components/user-menu-manager.ts +31 -0
- package/src/dictionary.ts +7 -2
- package/src/elements.ts +7 -0
- package/src/index.ts +15 -8
- package/src/layout.css +190 -35
- package/src/svg/StarFillWithGradient.tsx +14 -0
- package/src/toaster.tsx +90 -13
- package/src/types.ts +4 -0
- package/src/utils.ts +9 -142
- package/dist/components/BottomNotification.d.ts +0 -1
- package/dist/components/BottomNotification.d.ts.map +0 -1
- package/dist/components/BottomNotification.js +0 -2
- package/dist/components/BottomNotification.js.map +0 -1
- package/dist/components/BottomPanel.d.ts +0 -1
- package/dist/components/BottomPanel.d.ts.map +0 -1
- package/dist/components/BottomPanel.js +0 -2
- package/dist/components/BottomPanel.js.map +0 -1
- package/dist/components/SelectionList.d.ts +0 -36
- package/dist/components/SelectionList.d.ts.map +0 -1
- package/dist/components/SelectionList.js +0 -140
- package/dist/components/SelectionList.js.map +0 -1
- package/dist/components/error/ErrorFeedback.d.ts +0 -3
- package/dist/components/error/ErrorFeedback.d.ts.map +0 -1
- package/dist/components/error/ErrorFeedback.js +0 -66
- package/dist/components/error/ErrorFeedback.js.map +0 -1
- package/dist/components/menu/use-check-text-overflow.d.ts +0 -6
- package/dist/components/menu/use-check-text-overflow.d.ts.map +0 -1
- package/dist/components/menu/use-check-text-overflow.js +0 -20
- package/dist/components/menu/use-check-text-overflow.js.map +0 -1
- package/dist/components/menu/use-keyboard-controls.d.ts +0 -23
- package/dist/components/menu/use-keyboard-controls.d.ts.map +0 -1
- package/dist/components/menu/use-keyboard-controls.js +0 -49
- package/dist/components/menu/use-keyboard-controls.js.map +0 -1
- package/dist/components/tour/PortalSwitcherStep.d.ts +0 -3
- package/dist/components/tour/PortalSwitcherStep.d.ts.map +0 -1
- package/dist/components/tour/PortalSwitcherStep.js +0 -29
- package/dist/components/tour/PortalSwitcherStep.js.map +0 -1
- package/dist/components/types.d.ts +0 -15
- package/dist/components/types.d.ts.map +0 -1
- package/dist/components/types.js.map +0 -1
- package/dist/layout-context.d.ts +0 -10
- package/dist/layout-context.d.ts.map +0 -1
- package/dist/layout-context.js +0 -11
- package/dist/layout-context.js.map +0 -1
- package/dist/svg/AI.d.ts +0 -6
- package/dist/svg/AI.d.ts.map +0 -1
- package/dist/svg/AI.js +0 -9
- package/dist/svg/AI.js.map +0 -1
- package/dist/svg/EDP.d.ts +0 -6
- package/dist/svg/EDP.d.ts.map +0 -1
- package/dist/svg/EDP.js +0 -5
- package/dist/svg/EDP.js.map +0 -1
- package/dist/svg/Forbidden.d.ts +0 -6
- package/dist/svg/Forbidden.d.ts.map +0 -1
- package/dist/svg/Forbidden.js +0 -4
- package/dist/svg/Forbidden.js.map +0 -1
- package/dist/svg/HUB.d.ts +0 -6
- package/dist/svg/HUB.d.ts.map +0 -1
- package/dist/svg/HUB.js +0 -5
- package/dist/svg/HUB.js.map +0 -1
- package/dist/svg/Logo.d.ts +0 -2
- package/dist/svg/Logo.d.ts.map +0 -1
- package/dist/svg/Logo.js +0 -4
- package/dist/svg/Logo.js.map +0 -1
- package/dist/svg/NotFound.d.ts +0 -6
- package/dist/svg/NotFound.d.ts.map +0 -1
- package/dist/svg/NotFound.js +0 -4
- package/dist/svg/NotFound.js.map +0 -1
- package/dist/svg/ServerError.d.ts +0 -6
- package/dist/svg/ServerError.d.ts.map +0 -1
- package/dist/svg/ServerError.js +0 -4
- package/dist/svg/ServerError.js.map +0 -1
- package/dist/svg/Unauthenticated.d.ts +0 -6
- package/dist/svg/Unauthenticated.d.ts.map +0 -1
- package/dist/svg/Unauthenticated.js +0 -4
- package/dist/svg/Unauthenticated.js.map +0 -1
- package/src/components/BottomPanel.tsx +0 -0
- package/src/components/SelectionList.tsx +0 -272
- package/src/components/error/ErrorFeedback.tsx +0 -114
- package/src/components/menu/use-check-text-overflow.tsx +0 -26
- package/src/components/menu/use-keyboard-controls.tsx +0 -70
- package/src/components/tour/PortalSwitcherStep.tsx +0 -36
- package/src/components/types.ts +0 -15
- package/src/layout-context.tsx +0 -22
- package/src/svg/AI.tsx +0 -37
- package/src/svg/EDP.tsx +0 -35
- package/src/svg/Forbidden.tsx +0 -22
- package/src/svg/HUB.tsx +0 -35
- package/src/svg/Logo.tsx +0 -35
- package/src/svg/NotFound.tsx +0 -16
- package/src/svg/ServerError.tsx +0 -33
- package/src/svg/Unauthenticated.tsx +0 -16
- /package/dist/components/{types.js → NotificationCenter/types.js} +0 -0
- /package/src/components/{BottomNotification.tsx → ContactModal.tsx} +0 -0
|
@@ -1,38 +1,104 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
2
|
|
|
3
3
|
import { Button } from '@citric/core'
|
|
4
|
+
import { ModalContent } from '@citric/ui'
|
|
5
|
+
import { focusAccessibleElement, focusFirstChild } from '@stack-spot/portal-components'
|
|
6
|
+
import { last } from 'lodash'
|
|
4
7
|
import { ReactElement, useLayoutEffect, useState } from 'react'
|
|
5
8
|
import { Dialog, DialogOptions } from './components/Dialog'
|
|
6
9
|
import { CLOSE_OVERLAY_ID, OverlayContent, OverlayContentProps } from './components/OverlayContent'
|
|
7
10
|
import { getDictionary } from './dictionary'
|
|
8
11
|
import { LayoutElements, elementIds, getLayoutElements } from './elements'
|
|
9
12
|
import { ElementNotFound, LayoutError } from './errors'
|
|
10
|
-
import { showToaster as showReactToaster } from './toaster'
|
|
11
|
-
import {
|
|
13
|
+
import { CustomToasterOptions, DefaultToasterOptions, closeReactToaster, showToaster as showReactToaster } from './toaster'
|
|
14
|
+
import { CustomModalSize, ModalSize, RightPanelSize } from './types'
|
|
15
|
+
import { valueOfLayoutVar } from './utils'
|
|
12
16
|
|
|
13
17
|
interface AlertOptions extends Omit<DialogOptions, 'cancel'> {
|
|
18
|
+
/**
|
|
19
|
+
* Whether or not to show an "ok" button. If false, the dialog can still be closed through the close button, by clicking outside it or by
|
|
20
|
+
* pressing ESC.
|
|
21
|
+
*/
|
|
14
22
|
showButton?: boolean,
|
|
15
23
|
}
|
|
16
24
|
|
|
25
|
+
interface ModalContent {
|
|
26
|
+
id?: string,
|
|
27
|
+
element: React.ReactElement,
|
|
28
|
+
size: CustomModalSize | RightPanelSize,
|
|
29
|
+
onClose?: () => void,
|
|
30
|
+
stack: boolean,
|
|
31
|
+
}
|
|
32
|
+
|
|
17
33
|
type BottomDialogOptions = Omit<DialogOptions, 'title'>
|
|
18
|
-
type
|
|
19
|
-
type ModalSize = 'fit-content' | OverlaySize
|
|
20
|
-
type SetContentFn = ((content: ReactElement | undefined) => void) | undefined
|
|
34
|
+
type SetContentFn = (content: ModalContent[]) => void
|
|
21
35
|
|
|
22
36
|
interface OverlayContentSetter {
|
|
23
37
|
modal?: SetContentFn,
|
|
24
38
|
rightPanel?: SetContentFn,
|
|
25
|
-
bottomDialog?:
|
|
39
|
+
bottomDialog?: ((content: React.ReactElement | undefined) => void),
|
|
26
40
|
}
|
|
27
41
|
|
|
28
42
|
interface CustomModalOptions {
|
|
29
|
-
|
|
43
|
+
/**
|
|
44
|
+
* An optional, unique identifier for this modal.
|
|
45
|
+
*/
|
|
46
|
+
id?: string,
|
|
47
|
+
/**
|
|
48
|
+
* The size of the modal.
|
|
49
|
+
*/
|
|
50
|
+
size?: CustomModalSize,
|
|
51
|
+
/**
|
|
52
|
+
* A function to call when the modal closes.
|
|
53
|
+
*/
|
|
30
54
|
onClose?: () => void,
|
|
55
|
+
/**
|
|
56
|
+
* Property that defines whether the modal should ignore the initial focus on the close button.
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
ignoreFirstFocusOnCloseButton?: boolean,
|
|
60
|
+
/**
|
|
61
|
+
* If true, instead of replacing the previously opened modal (if any), it will open on top of it (stacked).
|
|
62
|
+
*
|
|
63
|
+
* When a modal is stacked on top of another:
|
|
64
|
+
* - Closing the modal, closes all opened modals.
|
|
65
|
+
* - Popping the modal, closes only the modal at the top of the stack.
|
|
66
|
+
* - Only the modal at the top of the stack can be interacted with.
|
|
67
|
+
*
|
|
68
|
+
* @default false
|
|
69
|
+
*/
|
|
70
|
+
stack?: boolean,
|
|
31
71
|
}
|
|
32
72
|
|
|
33
73
|
interface CustomRightPanelOptions {
|
|
34
|
-
|
|
74
|
+
/**
|
|
75
|
+
* An optional, unique identifier for this right panel.
|
|
76
|
+
*/
|
|
77
|
+
id?: string,
|
|
78
|
+
/**
|
|
79
|
+
* The size of the right panel.
|
|
80
|
+
*/
|
|
81
|
+
size?: RightPanelSize,
|
|
82
|
+
/**
|
|
83
|
+
* A function to call when the right panel closes.
|
|
84
|
+
*/
|
|
35
85
|
onClose?: () => void,
|
|
86
|
+
/**
|
|
87
|
+
* Property that defines whether the modal should ignore the initial focus on the close button.
|
|
88
|
+
* @default true
|
|
89
|
+
*/
|
|
90
|
+
ignoreFirstFocusOnCloseButton?: boolean,
|
|
91
|
+
/**
|
|
92
|
+
* If true, instead of replacing the previously opened right panel (if any), it will open on top of it (stacked).
|
|
93
|
+
*
|
|
94
|
+
* When a right panel is stacked on top of another:
|
|
95
|
+
* - Closing the panel, closes all opened panels.
|
|
96
|
+
* - Popping the panel, closes only the panel at the top of the stack.
|
|
97
|
+
* - Only the panel at the top of the stack can be interacted with.
|
|
98
|
+
*
|
|
99
|
+
* @default false
|
|
100
|
+
*/
|
|
101
|
+
stack?: boolean,
|
|
36
102
|
}
|
|
37
103
|
|
|
38
104
|
function multipleCallsWarning(type: 'modal' | 'rightPanel', timeMS: number) {
|
|
@@ -48,46 +114,90 @@ class LayoutOverlayManager {
|
|
|
48
114
|
static readonly instance?: LayoutOverlayManager
|
|
49
115
|
private setContent: OverlayContentSetter = {}
|
|
50
116
|
private elements?: LayoutElements
|
|
51
|
-
private onModalClose?: () => void
|
|
52
117
|
/**
|
|
53
118
|
* Last element with focus before an overlay is shown.
|
|
54
119
|
*/
|
|
55
120
|
private lastActiveElement: Element | null = null
|
|
121
|
+
private modals: ModalContent[] = []
|
|
122
|
+
private panels: ModalContent[] = []
|
|
123
|
+
|
|
124
|
+
private closeCustomBackdrops(elements: NodeListOf<Element>) {
|
|
125
|
+
// this is the easiest way to close each custom backdrop by calling their respective "onClose" callbacks. This is a hidden button
|
|
126
|
+
// that exists in every <Backdrop> component.
|
|
127
|
+
elements.forEach(element => (element.querySelector('[data-custom-backdrop-close]') as HTMLElement)?.click?.())
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private onClickBackdrop(event: MouseEvent) {
|
|
131
|
+
if (this.isModalOpen()) !this.elements?.modal?.contains?.(event.target as Node) && this.closeModal()
|
|
132
|
+
else if (this.isRightPanelOpen()) !this.elements?.rightPanel?.contains?.(event.target as Node) && this.closeRightPanel()
|
|
133
|
+
else {
|
|
134
|
+
const customBackdrops = this.getAllVisibleCustomBackdrops()
|
|
135
|
+
if (customBackdrops.length) {
|
|
136
|
+
let isClickInside = false
|
|
137
|
+
customBackdrops.forEach((element) => {
|
|
138
|
+
if (element.contains(event.target as Node)) isClickInside = true
|
|
139
|
+
})
|
|
140
|
+
if (!isClickInside) this.closeCustomBackdrops(customBackdrops)
|
|
141
|
+
} else {
|
|
142
|
+
this.setMainContentInteractivity(true)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private onPressKeyInBackdrop(event: KeyboardEvent) {
|
|
148
|
+
if (event.key !== 'Escape') return
|
|
149
|
+
if (this.isModalOpen()) this.closeModal()
|
|
150
|
+
if (this.isRightPanelOpen()) this.closeRightPanel()
|
|
151
|
+
else {
|
|
152
|
+
const customBackdrops = this.getAllVisibleCustomBackdrops()
|
|
153
|
+
if (customBackdrops.length) this.closeCustomBackdrops(customBackdrops)
|
|
154
|
+
else this.setMainContentInteractivity(true)
|
|
155
|
+
}
|
|
156
|
+
event.preventDefault()
|
|
157
|
+
}
|
|
56
158
|
|
|
57
159
|
private setupElements() {
|
|
58
160
|
this.elements = getLayoutElements()
|
|
59
|
-
this.elements.backdrop?.addEventListener('mousedown', (event) =>
|
|
60
|
-
|
|
61
|
-
else if (this.isRightPanelOpen()) !this.elements?.rightPanel?.contains?.(event.target as Node) && this.closeRightPanel()
|
|
62
|
-
else this.setMainContentInteractivity(true)
|
|
63
|
-
})
|
|
64
|
-
this.elements.backdrop?.addEventListener('keydown', (event) => {
|
|
65
|
-
if (event.key !== 'Escape') return
|
|
66
|
-
if (this.isModalOpen()) this.closeModal()
|
|
67
|
-
if (this.isRightPanelOpen()) this.closeRightPanel()
|
|
68
|
-
else this.setMainContentInteractivity(true)
|
|
69
|
-
event.preventDefault()
|
|
70
|
-
})
|
|
161
|
+
this.elements.backdrop?.addEventListener('mousedown', (event) => this.onClickBackdrop(event))
|
|
162
|
+
this.elements.backdrop?.addEventListener('keydown', (event) => this.onPressKeyInBackdrop(event))
|
|
71
163
|
this.setInteractivity(this.elements?.modal, false)
|
|
72
164
|
this.setInteractivity(this.elements?.rightPanel, false)
|
|
73
165
|
this.setInteractivity(this.elements?.bottomDialog, false)
|
|
74
166
|
}
|
|
75
167
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
168
|
+
private getAllVisibleCustomBackdrops() {
|
|
169
|
+
return document.querySelectorAll('[data-custom-backdrop-visibility=true]')
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Setup the overlay layout elements.
|
|
174
|
+
* @returns the content for the modal, rightPanel and bottomDialog.
|
|
175
|
+
* @internal
|
|
176
|
+
*/
|
|
177
|
+
useOverlays() {
|
|
79
178
|
useLayoutEffect(() => {
|
|
80
179
|
if (!this.elements) this.setupElements()
|
|
81
180
|
}, [])
|
|
82
|
-
const [modal, setModal] = useState<
|
|
83
|
-
const [rightPanel, setRightPanel] = useState<
|
|
181
|
+
const [modal, setModal] = useState<ModalContent[]>([])
|
|
182
|
+
const [rightPanel, setRightPanel] = useState<ModalContent[]>([])
|
|
84
183
|
const [bottomDialog, setBottomDialog] = useState<ReactElement | undefined>()
|
|
85
|
-
this.setContent.modal =
|
|
86
|
-
|
|
184
|
+
this.setContent.modal = (content) => {
|
|
185
|
+
this.modals = content
|
|
186
|
+
setModal(content)
|
|
187
|
+
}
|
|
188
|
+
this.setContent.rightPanel = (content) => {
|
|
189
|
+
this.panels = content
|
|
190
|
+
setRightPanel(content)
|
|
191
|
+
}
|
|
87
192
|
this.setContent.bottomDialog = setBottomDialog
|
|
88
193
|
return { modal, rightPanel, bottomDialog }
|
|
89
194
|
}
|
|
90
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Enables or disables the interactivity of an element.
|
|
198
|
+
* @param element the element to have its interactivity changed.
|
|
199
|
+
* @param interactive false to disable interactivity, true to enable.
|
|
200
|
+
*/
|
|
91
201
|
private setInteractivity(element: HTMLElement | null | undefined, interactive: boolean) {
|
|
92
202
|
if (interactive) {
|
|
93
203
|
element?.removeAttribute('inert')
|
|
@@ -105,114 +215,273 @@ class LayoutOverlayManager {
|
|
|
105
215
|
this.elements?.backdrop?.setAttribute('class', interactive ? '' : 'visible')
|
|
106
216
|
}
|
|
107
217
|
|
|
108
|
-
private showOverlay(element: HTMLElement | null | undefined,
|
|
218
|
+
private showOverlay(element: HTMLElement | null | undefined,
|
|
219
|
+
extraClasses: string[] = [],
|
|
220
|
+
blockMainContent = true,
|
|
221
|
+
manageClasses = true,
|
|
222
|
+
ignoreFirstFocusOnCloseButton = true,
|
|
223
|
+
) {
|
|
109
224
|
this.lastActiveElement = document.activeElement
|
|
110
|
-
element?.classList.add('visible', ...extraClasses)
|
|
225
|
+
if (manageClasses) element?.classList.add('visible', ...extraClasses)
|
|
111
226
|
this.setInteractivity(element, true)
|
|
112
227
|
if (blockMainContent) this.setMainContentInteractivity(false)
|
|
113
228
|
setTimeout(() => focusFirstChild(
|
|
114
229
|
element,
|
|
115
|
-
{
|
|
230
|
+
{
|
|
231
|
+
priority: [['input', 'textarea', 'select', 'other', 'button']] as const,
|
|
232
|
+
...(ignoreFirstFocusOnCloseButton ? { ignore: `#${CLOSE_OVERLAY_ID}` } : {}),
|
|
233
|
+
},
|
|
116
234
|
), 50)
|
|
117
235
|
}
|
|
118
236
|
|
|
119
|
-
private hideOverlay(element: HTMLElement | null | undefined) {
|
|
120
|
-
element?.setAttribute('class', '')
|
|
237
|
+
private hideOverlay(element: HTMLElement | null | undefined, manageClasses = true) {
|
|
238
|
+
if (manageClasses) element?.setAttribute('class', '')
|
|
121
239
|
this.setInteractivity(element, false)
|
|
122
240
|
this.setMainContentInteractivity(true)
|
|
123
241
|
}
|
|
124
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Shows the backdrop. The element passed as parameter must be a child of backdrop. Some accessibility features will be attached to
|
|
245
|
+
* the element.
|
|
246
|
+
*
|
|
247
|
+
* Consider using the component <Backdrop> from this library instead of calling this function directly.
|
|
248
|
+
* @param element the element to show inside the backdrop. It must already be a child of the backdrop.
|
|
249
|
+
*/
|
|
250
|
+
showBackdrop(element?: HTMLElement | null) {
|
|
251
|
+
this.showOverlay(element, [], true, false)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Closes the backdrop. The element passed as parameter must be a child of backdrop. Some accessibility features will be run on the
|
|
256
|
+
* element.
|
|
257
|
+
*
|
|
258
|
+
* Consider using the component <Backdrop> from this library instead of calling this function directly.
|
|
259
|
+
* @param element the element showing inside the backdrop. It must be a child of the backdrop.
|
|
260
|
+
*/
|
|
261
|
+
closeBackdrop(element?: HTMLElement | null) {
|
|
262
|
+
this.hideOverlay(element, false)
|
|
263
|
+
const lastActiveElement = this.lastActiveElement as HTMLElement | null
|
|
264
|
+
lastActiveElement?.focus?.()
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* @returns true if the modal is currently opened. False otherwise.
|
|
269
|
+
*/
|
|
125
270
|
isModalOpen() {
|
|
126
271
|
return this.elements?.modal?.classList.contains('visible') ?? false
|
|
127
272
|
}
|
|
128
273
|
|
|
274
|
+
/**
|
|
275
|
+
* @returns the number of modals currently tracked.
|
|
276
|
+
*/
|
|
277
|
+
getNumberOfOpenModals() {
|
|
278
|
+
return this.modals.length
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @returns the number of right panels currently tracked.
|
|
283
|
+
*/
|
|
284
|
+
getNumberOfOpenRightPanels() {
|
|
285
|
+
return this.panels.length
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* @returns true if the right panel is currently opened. False otherwise.
|
|
290
|
+
*/
|
|
129
291
|
isRightPanelOpen() {
|
|
130
292
|
return this.elements?.rightPanel?.classList.contains('visible') ?? false
|
|
131
293
|
}
|
|
132
294
|
|
|
295
|
+
/**
|
|
296
|
+
* @returns true if the bottom dialog is currently opened. False otherwise.
|
|
297
|
+
*/
|
|
133
298
|
isBottomDialogOpen() {
|
|
134
299
|
return this.elements?.bottomDialog?.classList.contains('visible') ?? false
|
|
135
300
|
}
|
|
136
301
|
|
|
137
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Opens a modal with custom content.
|
|
304
|
+
*
|
|
305
|
+
* Attention: the modal state must be declared within the modal. If the state is declared outside the modal, its content won't be updated
|
|
306
|
+
* accordingly. To force an update of an outside state, you need to call `showCustomModal` again with the new state value.
|
|
307
|
+
*
|
|
308
|
+
* @param content a react element with the modal content.
|
|
309
|
+
* @param options the modal options {@link CustomModalOptions}.
|
|
310
|
+
*/
|
|
311
|
+
showCustomModal(content: React.ReactElement,
|
|
312
|
+
{ size = 'medium', onClose, ignoreFirstFocusOnCloseButton = true, stack = false, id }: CustomModalOptions = {},
|
|
313
|
+
) {
|
|
138
314
|
if (!this.elements?.modal) throw new ElementNotFound('modal', elementIds.modal)
|
|
139
315
|
if (!this.setContent.modal) throw new LayoutError('unable to show modal, because it has not been setup yet.')
|
|
140
|
-
|
|
141
|
-
this.
|
|
142
|
-
this.
|
|
316
|
+
const modal = { element: content, onClose, size, stack, id }
|
|
317
|
+
const currentModalSize = last(this.modals)?.size
|
|
318
|
+
this.setContent.modal(stack ? [...this.modals, modal] : [modal])
|
|
319
|
+
// we should remove the previous size, if any, before showing the modal
|
|
320
|
+
if (currentModalSize) this.elements.modal.classList.remove(currentModalSize)
|
|
321
|
+
this.showOverlay(this.elements.modal, [size], true, true, ignoreFirstFocusOnCloseButton)
|
|
143
322
|
}
|
|
144
323
|
|
|
145
|
-
|
|
146
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Opens a modal.
|
|
326
|
+
*
|
|
327
|
+
* Attention: the modal state must be declared within the modal. If the state is declared outside the modal, its content won't be updated
|
|
328
|
+
* accordingly. To force an update of an outside state, you need to call `showModal` again with the new state value.
|
|
329
|
+
*
|
|
330
|
+
* @param options the modal options: {@link OverlayContentProps} & { size: {@link ModalSize} }.
|
|
331
|
+
*/
|
|
332
|
+
showModal({
|
|
333
|
+
size, ignoreFirstFocusOnCloseButton, stack, onGoBack, id, ...props
|
|
334
|
+
}: OverlayContentProps & { size?: ModalSize } & Pick<CustomModalOptions, 'ignoreFirstFocusOnCloseButton' | 'stack' | 'id'>) {
|
|
335
|
+
const handleBack = onGoBack ?? ((stack && this.modals.length >= 1) ? () => this.popModal() : undefined)
|
|
336
|
+
this.showCustomModal(
|
|
337
|
+
<OverlayContent {...props} onGoBack={handleBack} onClose={() => this.closeModal()} type="modal" />,
|
|
338
|
+
{ size, onClose: props.onClose, ignoreFirstFocusOnCloseButton, stack, id },
|
|
339
|
+
)
|
|
147
340
|
}
|
|
148
341
|
|
|
149
|
-
private showDialog(options
|
|
342
|
+
private showDialog({ ignoreFirstFocusOnCloseButton = false, options, size = 'small' }:
|
|
343
|
+
{ ignoreFirstFocusOnCloseButton?: boolean, size?: CustomModalSize, options: DialogOptions }): Promise<boolean> {
|
|
150
344
|
let dialogResult = false
|
|
151
345
|
return new Promise((resolve, reject) => {
|
|
152
346
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
347
|
+
if (options.type === 'panel') {
|
|
348
|
+
this.showCustomRightPanel(
|
|
349
|
+
<Dialog
|
|
350
|
+
{...options}
|
|
351
|
+
onCancel={() => this.closeModal()}
|
|
352
|
+
onConfirm={() => {
|
|
353
|
+
dialogResult = true
|
|
354
|
+
this.closeModal()
|
|
355
|
+
}}
|
|
356
|
+
/>,
|
|
357
|
+
{ size: size as RightPanelSize, onClose: () => resolve(dialogResult) },
|
|
358
|
+
)
|
|
359
|
+
} else {
|
|
360
|
+
this.showCustomModal(
|
|
361
|
+
<Dialog
|
|
362
|
+
{...options}
|
|
363
|
+
onCancel={() => this.closeModal()}
|
|
364
|
+
onConfirm={() => {
|
|
365
|
+
dialogResult = true
|
|
366
|
+
this.closeModal()
|
|
367
|
+
}}
|
|
368
|
+
/>,
|
|
369
|
+
{ size, onClose: () => resolve(dialogResult), ignoreFirstFocusOnCloseButton },
|
|
370
|
+
)
|
|
371
|
+
}
|
|
164
372
|
} catch (error) {
|
|
165
373
|
reject(error)
|
|
166
374
|
}
|
|
167
375
|
})
|
|
168
376
|
}
|
|
169
377
|
|
|
170
|
-
|
|
378
|
+
/**
|
|
379
|
+
* Shows a confirmation dialog and returns a promise that resolves as soon as the dialog is closed. The result of the promise is true if
|
|
380
|
+
* the user confirms and false otherwise.
|
|
381
|
+
*
|
|
382
|
+
* If you need the user to type something to confirm the action, use the property `validate` in the options parameter.
|
|
383
|
+
* @param options the dialog options and its size: {@link DialogOptions}.
|
|
384
|
+
* @returns a promise that resolves with the user's answer.
|
|
385
|
+
*/
|
|
386
|
+
confirm({ confirm, cancel, size, ...options }: DialogOptions & { size?: CustomModalSize }): Promise<boolean> {
|
|
171
387
|
const t = getDictionary()
|
|
172
|
-
return this.showDialog({
|
|
388
|
+
return this.showDialog({
|
|
389
|
+
ignoreFirstFocusOnCloseButton: !!options.validation,
|
|
390
|
+
size,
|
|
391
|
+
options: {
|
|
392
|
+
...options,
|
|
393
|
+
confirm: confirm || t.confirm,
|
|
394
|
+
cancel: cancel || t.cancel,
|
|
395
|
+
},
|
|
396
|
+
})
|
|
173
397
|
}
|
|
174
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Shows an alert dialog and returns a promise that resolves as soon as the dialog is closed.
|
|
401
|
+
*
|
|
402
|
+
* @param options the dialog options: {@link AlertOptions}.
|
|
403
|
+
* @returns a promise that resolves to undefined as soon as the dialog is closed.
|
|
404
|
+
*/
|
|
175
405
|
async alert({ confirm, showButton = true, ...options }: AlertOptions): Promise<void> {
|
|
176
406
|
const t = getDictionary()
|
|
177
|
-
await this.showDialog({
|
|
407
|
+
await this.showDialog({
|
|
408
|
+
ignoreFirstFocusOnCloseButton: !!options.validation,
|
|
409
|
+
options: {
|
|
410
|
+
...options,
|
|
411
|
+
confirm: showButton ? (confirm || t.confirm) : undefined,
|
|
412
|
+
},
|
|
413
|
+
})
|
|
178
414
|
}
|
|
179
415
|
|
|
180
|
-
|
|
416
|
+
/**
|
|
417
|
+
* Shows a message at the bottom of the window and asks the user to confirm or decline it. The return value is a promise that resolves as
|
|
418
|
+
* soon as the user presses one of the buttons. The result of the promise is true if the user confirms and false otherwise.
|
|
419
|
+
*
|
|
420
|
+
* Differently than `confirm` and `alert`, this message can only be closed if the user clicks one of the buttons or `closeBottomDialog`
|
|
421
|
+
* is called.
|
|
422
|
+
*
|
|
423
|
+
* @param options the dialog options: {@link BottomDialogOptions}.
|
|
424
|
+
* @returns a promise that resolves with the user's answer.
|
|
425
|
+
*/
|
|
426
|
+
showBottomDialog({ children, cancel, confirm }: BottomDialogOptions): Promise<boolean> {
|
|
181
427
|
if (!this.elements?.bottomDialog) throw new ElementNotFound('bottom dialog', elementIds.bottomDialog)
|
|
182
428
|
if (!this.setContent.bottomDialog) throw new LayoutError('unable to show bottom dialog, because it has not been setup yet.')
|
|
183
429
|
return new Promise((resolve) => {
|
|
184
430
|
this.setContent.bottomDialog?.(
|
|
185
431
|
<>
|
|
186
|
-
{
|
|
432
|
+
{children}
|
|
187
433
|
<div className="btn-group">
|
|
188
434
|
{cancel && <Button onClick={() => resolve(false)} colorScheme="light" appearance="outlined">{cancel}</Button>}
|
|
189
435
|
{confirm && <Button onClick={() => resolve(true)} colorScheme="light">{confirm}</Button>}
|
|
190
436
|
</div>
|
|
191
437
|
</>,
|
|
192
438
|
)
|
|
193
|
-
this.showOverlay(this.elements?.bottomDialog, undefined, false)
|
|
439
|
+
this.showOverlay(this.elements?.bottomDialog, undefined, false, true, true)
|
|
194
440
|
})
|
|
195
441
|
}
|
|
196
442
|
|
|
197
|
-
|
|
443
|
+
/**
|
|
444
|
+
* Opens a right panel with custom content.
|
|
445
|
+
*
|
|
446
|
+
* Attention: the right panel state must be declared within the right panel. If the state is declared outside the right panel, its content
|
|
447
|
+
* won't be updated accordingly. To force an update of an outside state, you need to call `showCustomRightPanel` again with the new state
|
|
448
|
+
* value.
|
|
449
|
+
*
|
|
450
|
+
* @param content a react element with the modal content.
|
|
451
|
+
* @param options the modal options {@link CustomModalOptions}.
|
|
452
|
+
*/
|
|
453
|
+
showCustomRightPanel(content: ReactElement, { size = 'medium', onClose, ignoreFirstFocusOnCloseButton = true, stack = false, id }:
|
|
454
|
+
CustomRightPanelOptions = {}) {
|
|
198
455
|
if (!this.elements?.rightPanel) throw new ElementNotFound('right panel overlay', elementIds.rightPanel)
|
|
199
456
|
if (!this.setContent.rightPanel) throw new LayoutError('unable to show right panel overlay, because it has not been setup yet.')
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
this.
|
|
457
|
+
const panel = { element: content, onClose, size, stack, id }
|
|
458
|
+
const currentPanelSize = last(this.modals)?.size
|
|
459
|
+
this.setContent.rightPanel(stack ? [...this.panels, panel] : [panel])
|
|
460
|
+
// we should remove the previous size, if any, before showing the panel
|
|
461
|
+
if (currentPanelSize) this.elements.rightPanel.classList.remove(currentPanelSize)
|
|
203
462
|
setTimeout(() => {
|
|
204
|
-
this.showOverlay(this.elements?.rightPanel)
|
|
463
|
+
this.showOverlay(this.elements?.rightPanel, [size], true, true, ignoreFirstFocusOnCloseButton)
|
|
205
464
|
})
|
|
206
465
|
}
|
|
207
466
|
|
|
208
|
-
|
|
467
|
+
/**
|
|
468
|
+
* Opens a right panel.
|
|
469
|
+
*
|
|
470
|
+
* Attention: the right panel state must be declared within the right panel. If the state is declared outside the right panel, its content
|
|
471
|
+
* won't be updated accordingly. To force an update of an outside state, you need to call `showRightPanel` again with the new state value.
|
|
472
|
+
*
|
|
473
|
+
* @param options the modal options: {@link OverlayContentProps} & { size: {@link ModalSize} }.
|
|
474
|
+
*/
|
|
475
|
+
showRightPanel({ size, ignoreFirstFocusOnCloseButton, stack, onGoBack, id, ...props }:
|
|
476
|
+
OverlayContentProps & Pick<CustomRightPanelOptions, 'size' | 'ignoreFirstFocusOnCloseButton' | 'stack' | 'id'>) {
|
|
477
|
+
const handleBack = onGoBack ?? ((stack && this.panels.length >= 1) ? () => this.popRightPanel() : undefined)
|
|
209
478
|
this.showCustomRightPanel(
|
|
210
|
-
<OverlayContent {...props} onClose={() => this.closeRightPanel()} type="panel" />,
|
|
211
|
-
{ size, onClose: props.onClose },
|
|
479
|
+
<OverlayContent {...props} onGoBack={handleBack} onClose={() => this.closeRightPanel()} type="panel" />,
|
|
480
|
+
{ size, onClose: props.onClose, ignoreFirstFocusOnCloseButton, stack, id },
|
|
212
481
|
)
|
|
213
482
|
}
|
|
214
483
|
|
|
215
|
-
|
|
484
|
+
/*
|
|
216
485
|
* Focus the element that had focus before the last overlay was opened. If the element is not visible anymore, another one that makes
|
|
217
486
|
* sense (accessibility-wise) is focused.
|
|
218
487
|
*/
|
|
@@ -221,69 +490,214 @@ class LayoutOverlayManager {
|
|
|
221
490
|
this.lastActiveElement = null
|
|
222
491
|
}
|
|
223
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Closes all opened modals.
|
|
495
|
+
* @param runCloseListener whether or not to run the function `onClose` passed to `showModal` or `showCustomModal`. Defaults to true.
|
|
496
|
+
*/
|
|
224
497
|
closeModal(runCloseListener = true) {
|
|
225
498
|
this.elements?.modal?.classList.remove('visible')
|
|
226
|
-
this.
|
|
227
|
-
if (
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
499
|
+
const shouldHideBackdrop = this.panels.length === 0
|
|
500
|
+
if (shouldHideBackdrop) this.elements?.backdrop?.setAttribute('class', '')
|
|
501
|
+
if (runCloseListener) {
|
|
502
|
+
this.modals.forEach((modal) => {
|
|
503
|
+
const onClose = modal.onClose
|
|
504
|
+
// setting it to undefined before running it prevents nested calls to closeModal from generating infinite loops.
|
|
505
|
+
modal.onClose = undefined
|
|
506
|
+
onClose?.()
|
|
507
|
+
})
|
|
232
508
|
}
|
|
233
509
|
const animationMS = parseFloat(valueOfLayoutVar('--modal-animation-duration')) * 1000
|
|
234
510
|
setTimeout(
|
|
235
511
|
() => {
|
|
236
|
-
if (this.elements?.backdrop?.classList.contains('visible')) {
|
|
512
|
+
if (shouldHideBackdrop && this.elements?.backdrop?.classList.contains('visible')) {
|
|
237
513
|
// eslint-disable-next-line no-console
|
|
238
514
|
console.warn(multipleCallsWarning('modal', animationMS))
|
|
239
515
|
this.elements?.modal?.classList.remove('visible')
|
|
240
516
|
}
|
|
241
|
-
if (this.setContent.modal) this.setContent.modal(
|
|
242
|
-
this.hideOverlay(this.elements?.modal)
|
|
517
|
+
if (this.setContent.modal) this.setContent.modal([])
|
|
518
|
+
if (shouldHideBackdrop) this.hideOverlay(this.elements?.modal)
|
|
243
519
|
this.focusLastActiveElement()
|
|
244
520
|
},
|
|
245
521
|
animationMS,
|
|
246
522
|
)
|
|
247
523
|
}
|
|
248
524
|
|
|
525
|
+
/**
|
|
526
|
+
* Closes the top-most modal in the stack. Will behave like `closeModal` if only a single modal exists in the stack.
|
|
527
|
+
* @param amount number of modals to pop. Defaults to 1.
|
|
528
|
+
* @param runCloseListener whether or not to run the function `onClose` passed to `showModal` or `showCustomModal`. Defaults to true.
|
|
529
|
+
*/
|
|
530
|
+
popModal(amount = 1, runCloseListener = true) {
|
|
531
|
+
if (amount <= 0) return
|
|
532
|
+
if (this.modals.length <= amount) return this.closeModal(runCloseListener)
|
|
533
|
+
for (let i = 0; i < amount; i++) {
|
|
534
|
+
const modalToClose = this.modals.pop()! // "!": because of the second "if", the array can't have less than "amount + 1" elements
|
|
535
|
+
if (runCloseListener) modalToClose.onClose?.()
|
|
536
|
+
this.elements?.modal?.classList.remove(modalToClose.size)
|
|
537
|
+
}
|
|
538
|
+
const topModal = last(this.modals)! // "!": because of the second "if", the array can't have less than "amount + 1" elements
|
|
539
|
+
this.elements?.modal?.classList.add(topModal.size)
|
|
540
|
+
this.setContent.modal?.([...this.modals])
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Close all modals in the stack of modal until the modal with the given id is found. If no modal with the given id is found, nothing
|
|
545
|
+
* happens.
|
|
546
|
+
* @param id the id of the modal to pop to.
|
|
547
|
+
* @param inclusive when true, the modal with the given id is also popped. Defaults to false.
|
|
548
|
+
*/
|
|
549
|
+
popModalTo(id: string, inclusive = false) {
|
|
550
|
+
const index = this.modals.findIndex(m => m.id === id)
|
|
551
|
+
if (index >= 0) this.popModal(this.modals.length - index - (inclusive ? 0 : 1))
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Closes all opened right panels.
|
|
556
|
+
* @param runCloseListener whether or not to run the function `onClose` passed to `showRightPanel` or `showCustomRightPanel`. Defaults to
|
|
557
|
+
* true.
|
|
558
|
+
*/
|
|
249
559
|
closeRightPanel(runCloseListener = true) {
|
|
250
560
|
this.elements?.rightPanel?.classList.remove('visible')
|
|
251
|
-
this.
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
561
|
+
const shouldHideBackdrop = this.modals.length === 0
|
|
562
|
+
if (shouldHideBackdrop) this.elements?.backdrop?.setAttribute('class', '')
|
|
563
|
+
if (runCloseListener) {
|
|
564
|
+
this.panels.forEach((panel) => {
|
|
565
|
+
const onClose = panel.onClose
|
|
566
|
+
// setting it to undefined before running it prevents nested calls to closeRightPanel from generating infinite loops.
|
|
567
|
+
panel.onClose = undefined
|
|
568
|
+
onClose?.()
|
|
569
|
+
})
|
|
257
570
|
}
|
|
258
571
|
const animationMS = parseFloat(valueOfLayoutVar('--right-panel-animation-duration')) * 1000
|
|
259
572
|
setTimeout(
|
|
260
573
|
() => {
|
|
261
|
-
if (this.elements?.backdrop?.classList.contains('visible')) {
|
|
574
|
+
if (shouldHideBackdrop && this.elements?.backdrop?.classList.contains('visible')) {
|
|
262
575
|
// eslint-disable-next-line no-console
|
|
263
576
|
console.warn(multipleCallsWarning('rightPanel', animationMS))
|
|
264
577
|
this.elements?.rightPanel?.classList.remove('visible')
|
|
265
578
|
}
|
|
266
|
-
if (this.setContent.rightPanel) this.setContent.rightPanel(
|
|
267
|
-
this.hideOverlay(this.elements?.rightPanel)
|
|
579
|
+
if (this.setContent.rightPanel) this.setContent.rightPanel([])
|
|
580
|
+
if (shouldHideBackdrop) this.hideOverlay(this.elements?.rightPanel)
|
|
268
581
|
this.focusLastActiveElement()
|
|
269
582
|
},
|
|
270
583
|
animationMS,
|
|
271
584
|
)
|
|
272
585
|
}
|
|
273
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Closes the top-most modal in the stack. Will behave like `closeModal` if only a single modal exists in the stack.
|
|
589
|
+
* @param runCloseListener whether or not to run the function `onClose` passed to `showModal` or `showCustomModal`. Defaults to true.
|
|
590
|
+
*/
|
|
591
|
+
popRightPanel(amount = 1, runCloseListener = true) {
|
|
592
|
+
if (amount <= 0) return
|
|
593
|
+
if (this.panels.length <= amount) return this.closeRightPanel(runCloseListener)
|
|
594
|
+
for (let i = 0; i < amount; i++) {
|
|
595
|
+
const panelToClose = this.panels.pop()! // "!": because of the second "if", the array can't have less than "amount + 1" elements
|
|
596
|
+
if (runCloseListener) panelToClose.onClose?.()
|
|
597
|
+
this.elements?.rightPanel?.classList.remove(panelToClose.size)
|
|
598
|
+
}
|
|
599
|
+
const topPanel = last(this.panels)! // "!": because of the second "if", the array can't have less than "amount + 1" elements
|
|
600
|
+
this.elements?.rightPanel?.classList.add(topPanel.size)
|
|
601
|
+
this.setContent.rightPanel?.([...this.panels])
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Close all right panels in the stack of panels until the panel with the given id is found. If no panel with the given id is found,
|
|
606
|
+
* nothing happens.
|
|
607
|
+
* @param id the id of the right panel to pop to.
|
|
608
|
+
* @param inclusive when true, the right panel with the given id is also popped. Defaults to false.
|
|
609
|
+
*/
|
|
610
|
+
popRightPanelTo(id: string, inclusive = false) {
|
|
611
|
+
const index = this.panels.findIndex(m => m.id === id)
|
|
612
|
+
if (index >= 0) this.popRightPanel(this.panels.length - index - (inclusive ? 0 : 1))
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Closes the bottom dialog if it's open.
|
|
617
|
+
*/
|
|
274
618
|
closeBottomDialog() {
|
|
275
619
|
this.hideOverlay(this.elements?.bottomDialog)
|
|
276
620
|
}
|
|
277
621
|
|
|
622
|
+
/**
|
|
623
|
+
* Verifies if the HTML element passed as parameter is inside the modal.
|
|
624
|
+
* @param element the HTML element to check.
|
|
625
|
+
* @returns true if `element` is inside the modal; false otherwise.
|
|
626
|
+
*/
|
|
278
627
|
isInsideModal(element: HTMLElement) {
|
|
279
628
|
return !!this.elements?.modal?.contains(element)
|
|
280
629
|
}
|
|
281
630
|
|
|
631
|
+
/**
|
|
632
|
+
* Verifies if the HTML element passed as parameter is inside the right panel.
|
|
633
|
+
* @param element the HTML element to check.
|
|
634
|
+
* @returns true if `element` is inside the right panel; false otherwise.
|
|
635
|
+
*/
|
|
282
636
|
isInsideRightPanel(element: HTMLElement) {
|
|
283
637
|
return !!this.elements?.rightPanel?.contains(element)
|
|
284
638
|
}
|
|
285
639
|
|
|
286
|
-
|
|
640
|
+
/**
|
|
641
|
+
* Shows a new toaster on the top right corner of the layout.
|
|
642
|
+
* @example
|
|
643
|
+
* ```
|
|
644
|
+
* overlay.showToaster({ title: 'Welcome', message: 'Hello World' })
|
|
645
|
+
* overlay.showToaster({
|
|
646
|
+
* title: 'Welcome',
|
|
647
|
+
* message: 'Hello World',
|
|
648
|
+
* actions: [
|
|
649
|
+
* { label: 'Got it!' },
|
|
650
|
+
* {
|
|
651
|
+
* label: 'Tell me more',
|
|
652
|
+
* closeOnClick: false,
|
|
653
|
+
* onClick: (event) => {
|
|
654
|
+
* // do something...
|
|
655
|
+
* },
|
|
656
|
+
* },
|
|
657
|
+
* ]
|
|
658
|
+
* })
|
|
659
|
+
* ```
|
|
660
|
+
* @param options the options for the toaster: {@link DefaultToasterOptions}.
|
|
661
|
+
* @returns the toaster's id.
|
|
662
|
+
*/
|
|
663
|
+
showToaster(defaultToasterConfig: DefaultToasterOptions): number | string
|
|
664
|
+
/**
|
|
665
|
+
* Shows a fully customized toaster on the top right corner of the layout.
|
|
666
|
+
* @example
|
|
667
|
+
* ```
|
|
668
|
+
* overlay.showToaster({
|
|
669
|
+
* custom: true,
|
|
670
|
+
* message: <MyCustomToasterContent />,
|
|
671
|
+
* closeButton: <MyCustomCloseButton />,
|
|
672
|
+
* })
|
|
673
|
+
* ```
|
|
674
|
+
* @param options the options for the toaster: {@link CustomToasterOptions}.
|
|
675
|
+
* @returns the toaster's id.
|
|
676
|
+
*/
|
|
677
|
+
showToaster(customToasterConfig: CustomToasterOptions): number | string
|
|
678
|
+
/**
|
|
679
|
+
* Shows the message passed as parameter in a new toaster on the top right corner of the layout.
|
|
680
|
+
* @example
|
|
681
|
+
* ```
|
|
682
|
+
* overlay.showToaster('Hello World!')
|
|
683
|
+
* overlay.showToaster(<p>Hello World</p>)
|
|
684
|
+
* ```
|
|
685
|
+
* @param message the message to show, can be either a string or React Element.
|
|
686
|
+
* @returns the toaster's id.
|
|
687
|
+
*/
|
|
688
|
+
showToaster(message: React.ReactNode): number | string
|
|
689
|
+
showToaster(options: any): number | string {
|
|
690
|
+
return showReactToaster(options)
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Closes the toaster with the specified id.
|
|
695
|
+
* @param id the id of the toaster to close.
|
|
696
|
+
*/
|
|
697
|
+
closeToaster = closeReactToaster
|
|
287
698
|
}
|
|
288
699
|
|
|
700
|
+
/**
|
|
701
|
+
* Manages overlay components of the layout like: modal, rightPanel, bottomDialog and toaster.
|
|
702
|
+
*/
|
|
289
703
|
export const overlay = new LayoutOverlayManager()
|