@capillarytech/creatives-library 8.0.242-alpha.1 → 8.0.242-alpha.11
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/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/config/app.js +1 -1
- package/constants/unified.js +2 -2
- package/initialReducer.js +0 -2
- package/package.json +1 -1
- package/services/api.js +5 -10
- package/services/tests/api.test.js +0 -18
- package/translations/en.json +4 -3
- package/utils/common.js +6 -5
- package/utils/commonUtils.js +1 -14
- package/utils/imageUrlUpload.js +141 -0
- package/utils/tests/commonUtil.test.js +0 -224
- package/utils/transformTemplateConfig.js +10 -0
- package/v2Components/CapDeviceContent/index.js +56 -61
- package/v2Components/CapImageUpload/constants.js +2 -0
- package/v2Components/CapImageUpload/index.js +65 -16
- package/v2Components/CapImageUpload/index.scss +4 -1
- package/v2Components/CapImageUpload/messages.js +5 -1
- package/v2Components/CapImageUrlUpload/constants.js +26 -0
- package/v2Components/CapImageUrlUpload/index.js +365 -0
- package/v2Components/CapImageUrlUpload/index.scss +35 -0
- package/v2Components/CapImageUrlUpload/messages.js +47 -0
- package/v2Components/CapTagList/index.js +1 -6
- package/v2Components/CapTagListWithInput/index.js +1 -5
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +0 -5
- package/v2Components/ErrorInfoNote/index.js +72 -412
- package/v2Components/ErrorInfoNote/messages.js +0 -22
- package/v2Components/ErrorInfoNote/style.scss +2 -279
- package/v2Components/HtmlEditor/HTMLEditor.js +89 -210
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +133 -1132
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +12 -17
- package/v2Components/HtmlEditor/_htmlEditor.scss +23 -8
- package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +101 -13
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +139 -148
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +1 -2
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +0 -1
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +7 -4
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +45 -35
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +3 -1
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +6 -7
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +6 -3
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +11 -10
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +62 -87
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +31 -49
- package/v2Components/HtmlEditor/constants.js +20 -29
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +16 -373
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +146 -88
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +85 -95
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +101 -99
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +25 -23
- package/v2Components/HtmlEditor/utils/validationAdapter.js +41 -34
- package/v2Components/MobilePushPreviewV2/index.js +7 -32
- package/v2Components/TemplatePreview/_templatePreview.scss +24 -44
- package/v2Components/TemplatePreview/index.js +32 -47
- package/v2Components/TemplatePreview/messages.js +0 -4
- package/v2Components/TestAndPreviewSlidebox/index.js +25 -31
- package/v2Containers/App/constants.js +5 -0
- package/v2Containers/BeeEditor/index.js +80 -82
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +4 -3
- package/v2Containers/CreativesContainer/SlideBoxContent.js +118 -148
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -9
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
- package/v2Containers/CreativesContainer/constants.js +2 -1
- package/v2Containers/CreativesContainer/index.js +41 -173
- package/v2Containers/CreativesContainer/messages.js +4 -4
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +210 -0
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +354 -38
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +0 -36
- package/v2Containers/Email/actions.js +0 -7
- package/v2Containers/Email/constants.js +1 -5
- package/v2Containers/Email/index.js +0 -13
- package/v2Containers/Email/messages.js +0 -32
- package/v2Containers/Email/reducer.js +1 -12
- package/v2Containers/Email/sagas.js +6 -41
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -2
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +7 -193
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +74 -40
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +67 -2
- package/v2Containers/EmailWrapper/constants.js +0 -2
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +67 -436
- package/v2Containers/EmailWrapper/index.js +23 -99
- package/v2Containers/EmailWrapper/messages.js +1 -61
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +1 -26
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +77 -111
- package/v2Containers/InApp/actions.js +0 -7
- package/v2Containers/InApp/constants.js +4 -20
- package/v2Containers/InApp/index.js +357 -800
- package/v2Containers/InApp/index.scss +3 -4
- package/v2Containers/InApp/messages.js +3 -7
- package/v2Containers/InApp/reducer.js +3 -21
- package/v2Containers/InApp/sagas.js +9 -29
- package/v2Containers/InApp/selectors.js +5 -25
- package/v2Containers/InApp/tests/index.test.js +50 -154
- package/v2Containers/InApp/tests/reducer.test.js +0 -34
- package/v2Containers/InApp/tests/sagas.test.js +9 -61
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +12 -12
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +8 -8
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +100 -77
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +72 -63
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +184 -150
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +16 -12
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +32 -28
- package/v2Containers/TagList/index.js +1 -67
- package/v2Containers/Templates/ChannelTypeIllustration.js +13 -1
- package/v2Containers/Templates/_templates.scss +202 -56
- package/v2Containers/Templates/actions.js +2 -1
- package/v2Containers/Templates/constants.js +1 -0
- package/v2Containers/Templates/index.js +278 -128
- package/v2Containers/Templates/messages.js +24 -4
- package/v2Containers/Templates/reducer.js +2 -0
- package/v2Containers/Templates/tests/index.test.js +10 -0
- package/v2Containers/TemplatesV2/index.js +8 -1
- package/v2Containers/TemplatesV2/messages.js +4 -0
- package/v2Containers/WebPush/Create/components/BrandIconSection.js +108 -0
- package/v2Containers/WebPush/Create/components/ButtonForm.js +172 -0
- package/v2Containers/WebPush/Create/components/ButtonItem.js +101 -0
- package/v2Containers/WebPush/Create/components/ButtonList.js +145 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.js +164 -0
- package/v2Containers/WebPush/Create/components/ButtonsLinksSection.test.js +463 -0
- package/v2Containers/WebPush/Create/components/FormActions.js +54 -0
- package/v2Containers/WebPush/Create/components/FormActions.test.js +163 -0
- package/v2Containers/WebPush/Create/components/MediaSection.js +142 -0
- package/v2Containers/WebPush/Create/components/MediaSection.test.js +341 -0
- package/v2Containers/WebPush/Create/components/MessageSection.js +103 -0
- package/v2Containers/WebPush/Create/components/MessageSection.test.js +268 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.js +87 -0
- package/v2Containers/WebPush/Create/components/NotificationTitleSection.test.js +210 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.js +54 -0
- package/v2Containers/WebPush/Create/components/TemplateNameSection.test.js +143 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/ButtonsLinksSection.test.js.snap +86 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/FormActions.test.js.snap +16 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MediaSection.test.js.snap +41 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +54 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/NotificationTitleSection.test.js.snap +37 -0
- package/v2Containers/WebPush/Create/components/__snapshots__/TemplateNameSection.test.js.snap +21 -0
- package/v2Containers/WebPush/Create/components/_buttons.scss +246 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +554 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +607 -0
- package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +633 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +666 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +74 -0
- package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +78 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.js +138 -0
- package/v2Containers/WebPush/Create/hooks/useButtonManagement.test.js +406 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.js +30 -0
- package/v2Containers/WebPush/Create/hooks/useCharacterCount.test.js +151 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.js +104 -0
- package/v2Containers/WebPush/Create/hooks/useImageUpload.test.js +538 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.js +122 -0
- package/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +633 -0
- package/v2Containers/WebPush/Create/index.js +1056 -0
- package/v2Containers/WebPush/Create/index.scss +134 -0
- package/v2Containers/WebPush/Create/messages.js +203 -0
- package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +228 -0
- package/v2Containers/WebPush/Create/preview/NotificationContainer.js +294 -0
- package/v2Containers/WebPush/Create/preview/PreviewContent.js +90 -0
- package/v2Containers/WebPush/Create/preview/PreviewControls.js +305 -0
- package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +23 -0
- package/v2Containers/WebPush/Create/preview/WebPushPreview.js +150 -0
- package/v2Containers/WebPush/Create/preview/assets/Light.svg +53 -0
- package/v2Containers/WebPush/Create/preview/assets/Top.svg +5 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-down.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/android-arrow-up.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
- package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +106 -0
- package/v2Containers/WebPush/Create/preview/assets/iOS.svg +26 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-arrow-down-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/macos-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +18 -0
- package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +29 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-close-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/assets/windows-triple-dot-icon.svg +9 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +47 -0
- package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +141 -0
- package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +45 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +68 -0
- package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +61 -0
- package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +99 -0
- package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +733 -0
- package/v2Containers/WebPush/Create/preview/components/tests/WindowsChromeExpanded.test.js +571 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +81 -0
- package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/WindowsChromeExpanded.test.js.snap +81 -0
- package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +50 -0
- package/v2Containers/WebPush/Create/preview/constants.js +637 -0
- package/v2Containers/WebPush/Create/preview/notification-container.scss +79 -0
- package/v2Containers/WebPush/Create/preview/preview.scss +351 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +370 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +12 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +47 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +11 -0
- package/v2Containers/WebPush/Create/preview/styles/_base.scss +207 -0
- package/v2Containers/WebPush/Create/preview/styles/_ios.scss +153 -0
- package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +107 -0
- package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +101 -0
- package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +229 -0
- package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +909 -0
- package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +1081 -0
- package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +723 -0
- package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +943 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +131 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +112 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +144 -0
- package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +129 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.js +94 -0
- package/v2Containers/WebPush/Create/utils/payloadBuilder.test.js +390 -0
- package/v2Containers/WebPush/Create/utils/previewUtils.js +89 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.js +115 -0
- package/v2Containers/WebPush/Create/utils/urlValidation.test.js +449 -0
- package/v2Containers/WebPush/Create/utils/validation.js +75 -0
- package/v2Containers/WebPush/Create/utils/validation.test.js +283 -0
- package/v2Containers/WebPush/actions.js +60 -0
- package/v2Containers/WebPush/constants.js +128 -0
- package/v2Containers/WebPush/index.js +2 -0
- package/v2Containers/WebPush/reducer.js +104 -0
- package/v2Containers/WebPush/sagas.js +119 -0
- package/v2Containers/WebPush/selectors.js +65 -0
- package/v2Containers/WebPush/tests/reducer.test.js +863 -0
- package/v2Containers/WebPush/tests/sagas.test.js +566 -0
- package/v2Containers/WebPush/tests/selectors.test.js +843 -0
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +528 -431
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +0 -254
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +0 -362
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +0 -51
- package/v2Containers/BeePopupEditor/constants.js +0 -10
- package/v2Containers/BeePopupEditor/index.js +0 -193
- package/v2Containers/BeePopupEditor/tests/index.test.js +0 -627
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1045
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +0 -376
- package/v2Containers/InApp/__tests__/sagas.test.js +0 -363
- package/v2Containers/InApp/tests/selectors.test.js +0 -612
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +0 -162
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +0 -267
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +0 -9
- package/v2Containers/InAppWrapper/constants.js +0 -16
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +0 -473
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +0 -198
- package/v2Containers/InAppWrapper/index.js +0 -148
- package/v2Containers/InAppWrapper/messages.js +0 -49
- package/v2Containers/InappAdvance/index.js +0 -1099
- package/v2Containers/InappAdvance/index.scss +0 -10
- package/v2Containers/InappAdvance/tests/index.test.js +0 -448
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import chromeIcon from './assets/chrome-icon.png';
|
|
4
|
+
import AndroidMobileChromeHeader from './components/AndroidMobileChromeHeader';
|
|
5
|
+
import AndroidMobileExpanded from './components/AndroidMobileExpanded';
|
|
6
|
+
import NotificationExpandedContent from './components/NotificationExpandedContent';
|
|
7
|
+
import NotificationHeader from './components/NotificationHeader';
|
|
8
|
+
import WindowsChromeExpanded from './components/WindowsChromeExpanded';
|
|
9
|
+
import IOSHeader from './components/IOSHeader';
|
|
10
|
+
import {
|
|
11
|
+
OS_MACOS,
|
|
12
|
+
OS_IOS,
|
|
13
|
+
OS_IPADOS,
|
|
14
|
+
OS_WINDOWS,
|
|
15
|
+
OS_ANDROID_MOBILE,
|
|
16
|
+
OS_ANDROID_TABLET,
|
|
17
|
+
BROWSER_CHROME,
|
|
18
|
+
BROWSER_FIREFOX,
|
|
19
|
+
BROWSER_EDGE,
|
|
20
|
+
BROWSER_OPERA,
|
|
21
|
+
BRAND_ICON_SUPPORTED_BROWSERS,
|
|
22
|
+
BROWSER_DISPLAY_NAMES,
|
|
23
|
+
NOTIFICATION_STATE_COLLAPSED,
|
|
24
|
+
NOTIFICATION_STATE_EXPANDED,
|
|
25
|
+
} from './constants';
|
|
26
|
+
import {
|
|
27
|
+
getNotificationContainerClassName,
|
|
28
|
+
formatUrlForPreview,
|
|
29
|
+
transformButtonsForPreview,
|
|
30
|
+
} from '../utils/previewUtils';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* NotificationContainer Component
|
|
34
|
+
*
|
|
35
|
+
* Standalone notification container component that renders the actual notification UI.
|
|
36
|
+
* This component is decoupled from any wrapper containers and can be used
|
|
37
|
+
* in different contexts (main preview, device frame preview, etc.).
|
|
38
|
+
*
|
|
39
|
+
* The component renders the notification-container div with all its content:
|
|
40
|
+
* - Notification header (icon, title, URL, body)
|
|
41
|
+
* - Expanded content (media image, CTA buttons) when state is 'Expanded'
|
|
42
|
+
*
|
|
43
|
+
* @param {string} notificationTitle - Title of the notification
|
|
44
|
+
* @param {string} notificationBody - Body text of the notification
|
|
45
|
+
* @param {string} url - URL for the notification
|
|
46
|
+
* @param {string} selectedOS - Operating system name
|
|
47
|
+
* @param {string} selectedBrowser - Browser name
|
|
48
|
+
* @param {string} notificationState - 'Collapsed' or 'Expanded' (default: 'Collapsed')
|
|
49
|
+
* @param {string} className - Additional CSS classes to apply to the container
|
|
50
|
+
* @param {string} icon - Icon asset for the browser (defaults to Chrome)
|
|
51
|
+
* @param {boolean} supportsExpanded - Whether Expanded state is supported for this variant
|
|
52
|
+
* @param {boolean} enableMedia - Whether to render media section when expanded
|
|
53
|
+
* @param {boolean} enableCtas - Whether to render CTA buttons when expanded
|
|
54
|
+
* @param {string} imageSrc - Media/banner image source URL
|
|
55
|
+
* @param {string} brandIconSrc - Brand icon image source URL
|
|
56
|
+
*/
|
|
57
|
+
const NotificationContainer = ({
|
|
58
|
+
notificationTitle,
|
|
59
|
+
notificationBody,
|
|
60
|
+
url,
|
|
61
|
+
selectedOS,
|
|
62
|
+
selectedBrowser,
|
|
63
|
+
notificationState = NOTIFICATION_STATE_COLLAPSED,
|
|
64
|
+
className = '',
|
|
65
|
+
icon = chromeIcon,
|
|
66
|
+
supportsExpanded = true,
|
|
67
|
+
enableMedia = true,
|
|
68
|
+
enableCtas = true,
|
|
69
|
+
brandIcon = '',
|
|
70
|
+
enableBrandIconPreview = true,
|
|
71
|
+
imageSrc = '',
|
|
72
|
+
brandIconSrc = '',
|
|
73
|
+
buttons = [],
|
|
74
|
+
showSeparateIOSCTAs = false,
|
|
75
|
+
}) => {
|
|
76
|
+
// Force expanded UI for Windows when Collapsed state is selected
|
|
77
|
+
// Windows only supports expanded preview - when Windows + Collapsed is selected, render expanded UI
|
|
78
|
+
// Note: If Windows needs separate Collapsed/Expanded states later, remove the second condition
|
|
79
|
+
const isExpanded = (supportsExpanded && notificationState === NOTIFICATION_STATE_EXPANDED) ||
|
|
80
|
+
(selectedOS === OS_WINDOWS && notificationState === NOTIFICATION_STATE_COLLAPSED);
|
|
81
|
+
const notificationContainerClass = getNotificationContainerClassName(selectedOS, selectedBrowser);
|
|
82
|
+
const stateClass = isExpanded ? 'expanded' : 'collapsed';
|
|
83
|
+
const selectedBrowserLower = (selectedBrowser || '').toLowerCase();
|
|
84
|
+
|
|
85
|
+
// Platform detection object - provides clean access to all platform/browser checks
|
|
86
|
+
const platform = {
|
|
87
|
+
parts: notificationContainerClass.split('-'),
|
|
88
|
+
get os() { return this.parts[0]; },
|
|
89
|
+
get browser() { return this.parts.length === 3 ? this.parts[2] : this.parts[1]; },
|
|
90
|
+
get isMacOs() { return selectedOS === OS_MACOS; },
|
|
91
|
+
get isIOS() { return selectedOS === OS_IOS; },
|
|
92
|
+
get isIPadOS() { return selectedOS === OS_IPADOS; },
|
|
93
|
+
get isAndroidMobile() { return this.os === 'android' && this.parts[1] === 'mobile'; },
|
|
94
|
+
get isAndroidTablet() { return this.os === 'android' && this.parts[1] === 'tablet'; },
|
|
95
|
+
get isIOSBrowser() { return this.os === 'ios'; },
|
|
96
|
+
get isIPadOSBrowser() { return this.os === 'ipados'; },
|
|
97
|
+
get isWindows() { return this.os === 'windows'; },
|
|
98
|
+
get supportsBrandIcon() { return BRAND_ICON_SUPPORTED_BROWSERS.includes(selectedBrowserLower); },
|
|
99
|
+
get isMacOsBrandIconBrowser() { return this.isMacOs && this.supportsBrandIcon; },
|
|
100
|
+
get isWindowsExpanded() { return this.isWindows && isExpanded; },
|
|
101
|
+
get isMacOsExpandedBrand() { return this.isMacOs && isExpanded && this.supportsBrandIcon; },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const displayUrl = formatUrlForPreview(url) || url || '';
|
|
105
|
+
// Use real media image from form/props
|
|
106
|
+
const mediaImageUrl = imageSrc || '';
|
|
107
|
+
// Use real brand icon from form/props, fallback to prop if provided for backward compatibility
|
|
108
|
+
const actualBrandIcon = brandIconSrc || brandIcon || '';
|
|
109
|
+
const shouldShowBrandIconAndroidExpanded =
|
|
110
|
+
(platform.isAndroidMobile || platform.isAndroidTablet) && isExpanded && enableBrandIconPreview && Boolean(actualBrandIcon);
|
|
111
|
+
const ctaButtons = transformButtonsForPreview(buttons);
|
|
112
|
+
|
|
113
|
+
// Brand icon preview toggle (set enableBrandIconPreview to false to see no-brand state)
|
|
114
|
+
const shouldShowBrandIcon = (() => {
|
|
115
|
+
// iOS/iPadOS NEVER show brand icons - they use a simple notification style
|
|
116
|
+
if (platform.isIOSBrowser || platform.isIPadOSBrowser) return false;
|
|
117
|
+
|
|
118
|
+
if (!enableBrandIconPreview || !actualBrandIcon) return false;
|
|
119
|
+
if (platform.isAndroidMobile && !isExpanded) return true;
|
|
120
|
+
if (platform.isAndroidTablet && !isExpanded) return true;
|
|
121
|
+
// macOS: show brand icon for all browsers in collapsed state
|
|
122
|
+
if (platform.isMacOs && !isExpanded) return true;
|
|
123
|
+
// Windows browsers: show brand icon in collapsed state (same as macOS Chrome)
|
|
124
|
+
if (platform.isWindows && !isExpanded) return true;
|
|
125
|
+
return false;
|
|
126
|
+
})();
|
|
127
|
+
|
|
128
|
+
const shouldShowBrandIconInExpandedMac = (() => {
|
|
129
|
+
if (!platform.isMacOsExpandedBrand) return false;
|
|
130
|
+
if (!enableBrandIconPreview || !actualBrandIcon) return false;
|
|
131
|
+
return true;
|
|
132
|
+
})();
|
|
133
|
+
|
|
134
|
+
const shouldRenderExpandedMedia = (() => {
|
|
135
|
+
if (!enableMedia) return false;
|
|
136
|
+
if (platform.isMacOsExpandedBrand) {
|
|
137
|
+
// For macOS brand browsers, only render if brand icon is available.
|
|
138
|
+
return shouldShowBrandIconInExpandedMac;
|
|
139
|
+
}
|
|
140
|
+
return Boolean(mediaImageUrl);
|
|
141
|
+
})();
|
|
142
|
+
|
|
143
|
+
const containerClasses = [
|
|
144
|
+
'notification-container',
|
|
145
|
+
notificationContainerClass,
|
|
146
|
+
stateClass,
|
|
147
|
+
className,
|
|
148
|
+
platform.isWindowsExpanded ? 'windows-chrome-expanded' : '',
|
|
149
|
+
]
|
|
150
|
+
.filter(Boolean)
|
|
151
|
+
.join(' ')
|
|
152
|
+
.trim();
|
|
153
|
+
|
|
154
|
+
// Get browser display name for Windows expanded notifications
|
|
155
|
+
const getBrowserDisplayName = (browser) => {
|
|
156
|
+
return BROWSER_DISPLAY_NAMES[browser] || browser;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Check if we need to render separate CTA container for iOS + Collapsed
|
|
160
|
+
const shouldRenderSeparateIOSCTAs =
|
|
161
|
+
showSeparateIOSCTAs &&
|
|
162
|
+
platform.isIOSBrowser &&
|
|
163
|
+
!isExpanded &&
|
|
164
|
+
enableCtas &&
|
|
165
|
+
ctaButtons &&
|
|
166
|
+
ctaButtons.length > 0;
|
|
167
|
+
|
|
168
|
+
const notificationContent = (
|
|
169
|
+
<div className={containerClasses}>
|
|
170
|
+
{platform.isWindowsExpanded ? (
|
|
171
|
+
<WindowsChromeExpanded
|
|
172
|
+
icon={icon}
|
|
173
|
+
brandIcon={actualBrandIcon}
|
|
174
|
+
enableBrandIconPreview={enableBrandIconPreview}
|
|
175
|
+
notificationTitle={notificationTitle}
|
|
176
|
+
notificationBody={notificationBody}
|
|
177
|
+
displayUrl={displayUrl}
|
|
178
|
+
browserName={getBrowserDisplayName(selectedBrowser)}
|
|
179
|
+
mediaImageUrl={mediaImageUrl}
|
|
180
|
+
enableCtas={enableCtas}
|
|
181
|
+
ctaButtons={ctaButtons}
|
|
182
|
+
/>
|
|
183
|
+
) : (platform.isAndroidMobile || platform.isAndroidTablet) && isExpanded ? (
|
|
184
|
+
<AndroidMobileExpanded
|
|
185
|
+
icon={icon}
|
|
186
|
+
selectedBrowser={selectedBrowser}
|
|
187
|
+
notificationTitle={notificationTitle}
|
|
188
|
+
notificationBody={notificationBody}
|
|
189
|
+
displayUrl={displayUrl}
|
|
190
|
+
brandIcon={actualBrandIcon}
|
|
191
|
+
shouldShowBrandIcon={shouldShowBrandIcon}
|
|
192
|
+
shouldShowBrandIconExpanded={shouldShowBrandIconAndroidExpanded}
|
|
193
|
+
mediaImageUrl={mediaImageUrl}
|
|
194
|
+
enableCtas={enableCtas}
|
|
195
|
+
ctaButtons={ctaButtons}
|
|
196
|
+
/>
|
|
197
|
+
) : (
|
|
198
|
+
<>
|
|
199
|
+
{(platform.isAndroidMobile || platform.isAndroidTablet) && !isExpanded ? (
|
|
200
|
+
<AndroidMobileChromeHeader
|
|
201
|
+
icon={icon}
|
|
202
|
+
selectedBrowser={selectedBrowser}
|
|
203
|
+
notificationTitle={notificationTitle}
|
|
204
|
+
notificationBody={notificationBody}
|
|
205
|
+
brandIcon={actualBrandIcon}
|
|
206
|
+
shouldShowBrandIcon={shouldShowBrandIcon}
|
|
207
|
+
/>
|
|
208
|
+
) : (platform.isIOSBrowser || platform.isIPadOSBrowser) ? (
|
|
209
|
+
<IOSHeader
|
|
210
|
+
icon={icon}
|
|
211
|
+
selectedBrowser={selectedBrowser}
|
|
212
|
+
notificationTitle={notificationTitle}
|
|
213
|
+
notificationBody={notificationBody}
|
|
214
|
+
/>
|
|
215
|
+
) : (
|
|
216
|
+
<NotificationHeader
|
|
217
|
+
icon={icon}
|
|
218
|
+
selectedBrowser={selectedBrowser}
|
|
219
|
+
notificationTitle={notificationTitle}
|
|
220
|
+
displayUrl={displayUrl}
|
|
221
|
+
notificationBody={notificationBody}
|
|
222
|
+
shouldShowBrandIcon={shouldShowBrandIcon}
|
|
223
|
+
brandIcon={actualBrandIcon}
|
|
224
|
+
isExpanded={isExpanded}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
{isExpanded && (
|
|
229
|
+
<NotificationExpandedContent
|
|
230
|
+
shouldRenderExpandedMedia={shouldRenderExpandedMedia}
|
|
231
|
+
shouldShowBrandIconInExpandedMac={shouldShowBrandIconInExpandedMac}
|
|
232
|
+
brandIcon={actualBrandIcon}
|
|
233
|
+
mediaImageUrl={mediaImageUrl}
|
|
234
|
+
enableCtas={enableCtas}
|
|
235
|
+
ctaButtons={ctaButtons}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
</>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// For iOS + Collapsed with CTAs, render notification and separate CTA container
|
|
244
|
+
if (shouldRenderSeparateIOSCTAs) {
|
|
245
|
+
return (
|
|
246
|
+
<div className="ios-notification-wrapper">
|
|
247
|
+
{notificationContent}
|
|
248
|
+
<div className="ios-cta-container">
|
|
249
|
+
{ctaButtons.map((button) => (
|
|
250
|
+
<button
|
|
251
|
+
key={button.id}
|
|
252
|
+
type="button"
|
|
253
|
+
className="ios-cta-button"
|
|
254
|
+
>
|
|
255
|
+
{button.label}
|
|
256
|
+
</button>
|
|
257
|
+
))}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Default: return just the notification container
|
|
264
|
+
return notificationContent;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
NotificationContainer.propTypes = {
|
|
268
|
+
notificationTitle: PropTypes.string,
|
|
269
|
+
notificationBody: PropTypes.string,
|
|
270
|
+
url: PropTypes.string,
|
|
271
|
+
selectedOS: PropTypes.string.isRequired,
|
|
272
|
+
selectedBrowser: PropTypes.string.isRequired,
|
|
273
|
+
notificationState: PropTypes.oneOf([NOTIFICATION_STATE_COLLAPSED, NOTIFICATION_STATE_EXPANDED]),
|
|
274
|
+
className: PropTypes.string,
|
|
275
|
+
icon: PropTypes.string,
|
|
276
|
+
supportsExpanded: PropTypes.bool,
|
|
277
|
+
enableMedia: PropTypes.bool,
|
|
278
|
+
enableCtas: PropTypes.bool,
|
|
279
|
+
brandIcon: PropTypes.string,
|
|
280
|
+
enableBrandIconPreview: PropTypes.bool,
|
|
281
|
+
imageSrc: PropTypes.string,
|
|
282
|
+
brandIconSrc: PropTypes.string,
|
|
283
|
+
buttons: PropTypes.arrayOf(
|
|
284
|
+
PropTypes.shape({
|
|
285
|
+
text: PropTypes.string,
|
|
286
|
+
url: PropTypes.string,
|
|
287
|
+
type: PropTypes.string,
|
|
288
|
+
})
|
|
289
|
+
),
|
|
290
|
+
showSeparateIOSCTAs: PropTypes.bool,
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
export default NotificationContainer;
|
|
294
|
+
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import NotificationContainer from './NotificationContainer';
|
|
4
|
+
import { getResolvedVariant } from './config/notificationMappings';
|
|
5
|
+
import { NOTIFICATION_STATE_COLLAPSED, NOTIFICATION_STATE_EXPANDED } from './constants';
|
|
6
|
+
import './preview.scss';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* PreviewContent Component
|
|
10
|
+
*
|
|
11
|
+
* Renders the preview area for web push notification.
|
|
12
|
+
* Displays actual notification preview for macOS + Chrome combination.
|
|
13
|
+
* For other OS/Browser combinations, shows the selection text.
|
|
14
|
+
*
|
|
15
|
+
* This component wraps the NotificationContainer in a preview-specific
|
|
16
|
+
* container (notification-preview) for the main preview area.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} notificationState - NOTIFICATION_STATE_COLLAPSED or NOTIFICATION_STATE_EXPANDED (default: NOTIFICATION_STATE_COLLAPSED)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const PreviewContent = ({
|
|
22
|
+
notificationTitle,
|
|
23
|
+
notificationBody,
|
|
24
|
+
url,
|
|
25
|
+
selectedOS,
|
|
26
|
+
selectedBrowser,
|
|
27
|
+
notificationState = NOTIFICATION_STATE_COLLAPSED,
|
|
28
|
+
imageSrc,
|
|
29
|
+
brandIconSrc,
|
|
30
|
+
buttons = [],
|
|
31
|
+
}) => {
|
|
32
|
+
const resolvedVariant = getResolvedVariant(selectedOS, selectedBrowser, notificationState);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="preview-content">
|
|
36
|
+
<div className="preview-content-wrapper">
|
|
37
|
+
<div className={`notification-preview ${resolvedVariant ? '' : 'preview-unavailable'}`.trim()}>
|
|
38
|
+
{resolvedVariant && (
|
|
39
|
+
<NotificationContainer
|
|
40
|
+
notificationTitle={notificationTitle}
|
|
41
|
+
notificationBody={notificationBody}
|
|
42
|
+
url={url}
|
|
43
|
+
selectedOS={selectedOS}
|
|
44
|
+
selectedBrowser={selectedBrowser}
|
|
45
|
+
notificationState={resolvedVariant.stateKey}
|
|
46
|
+
icon={resolvedVariant.variant.icon}
|
|
47
|
+
supportsExpanded={resolvedVariant.stateConfig.supportsExpanded}
|
|
48
|
+
enableMedia={resolvedVariant.stateConfig.showMedia}
|
|
49
|
+
enableCtas={buttons.length > 0}
|
|
50
|
+
className={resolvedVariant.variant.notificationClassName}
|
|
51
|
+
imageSrc={imageSrc}
|
|
52
|
+
brandIconSrc={brandIconSrc}
|
|
53
|
+
buttons={buttons}
|
|
54
|
+
/>
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
PreviewContent.propTypes = {
|
|
63
|
+
notificationTitle: PropTypes.string,
|
|
64
|
+
notificationBody: PropTypes.string,
|
|
65
|
+
url: PropTypes.string,
|
|
66
|
+
selectedOS: PropTypes.string.isRequired,
|
|
67
|
+
selectedBrowser: PropTypes.string.isRequired,
|
|
68
|
+
notificationState: PropTypes.oneOf([NOTIFICATION_STATE_COLLAPSED, NOTIFICATION_STATE_EXPANDED]),
|
|
69
|
+
imageSrc: PropTypes.string,
|
|
70
|
+
brandIconSrc: PropTypes.string,
|
|
71
|
+
buttons: PropTypes.arrayOf(
|
|
72
|
+
PropTypes.shape({
|
|
73
|
+
text: PropTypes.string,
|
|
74
|
+
url: PropTypes.string,
|
|
75
|
+
type: PropTypes.string,
|
|
76
|
+
})
|
|
77
|
+
),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
PreviewContent.defaultProps = {
|
|
81
|
+
notificationTitle: '',
|
|
82
|
+
notificationBody: '',
|
|
83
|
+
url: '',
|
|
84
|
+
imageSrc: '',
|
|
85
|
+
brandIconSrc: '',
|
|
86
|
+
buttons: [],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default PreviewContent;
|
|
90
|
+
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
import { FormattedMessage } from 'react-intl';
|
|
4
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
5
|
+
import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
|
|
6
|
+
import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
|
|
7
|
+
import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
|
|
8
|
+
import { LAYOUT_MODE, SELECT_WIDTH, STATE_OPTIONS } from './constants';
|
|
9
|
+
import { getBrowserOptionsForOS, getSupportedStates } from './config/notificationMappings';
|
|
10
|
+
import messages from '../messages';
|
|
11
|
+
import './preview.scss';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* PreviewControls Component
|
|
15
|
+
*
|
|
16
|
+
* Renders dropdown controls for selecting Operating System, Browser, and State
|
|
17
|
+
* for the web push notification preview.
|
|
18
|
+
*
|
|
19
|
+
* Implements cascading dropdown reset behavior:
|
|
20
|
+
* - When OS changes, Browser and State reset to first available option
|
|
21
|
+
* - When Browser changes, State resets to first available option
|
|
22
|
+
*
|
|
23
|
+
* @param {string} layoutMode - 'inline', 'newRow', or 'compact' (default: 'newRow')
|
|
24
|
+
* @param {boolean} showStateDropdown - Whether to show the State dropdown (default: true)
|
|
25
|
+
* @param {boolean|function} stateDropdownDisabled - Controls State dropdown disabled state.
|
|
26
|
+
* - If boolean: directly sets disabled state
|
|
27
|
+
* - If function: receives (selectedOS, selectedBrowser, stateOptions) and returns boolean
|
|
28
|
+
* - Note: Can be extended to accept object { disabled: boolean, tooltip?: string } for tooltip support
|
|
29
|
+
*/
|
|
30
|
+
const PreviewControls = ({
|
|
31
|
+
selectedOS,
|
|
32
|
+
selectedBrowser,
|
|
33
|
+
selectedState,
|
|
34
|
+
osOptions,
|
|
35
|
+
browserOptions,
|
|
36
|
+
stateOptions,
|
|
37
|
+
onOSChange,
|
|
38
|
+
onBrowserChange,
|
|
39
|
+
onStateChange,
|
|
40
|
+
layoutMode = LAYOUT_MODE.NEW_ROW,
|
|
41
|
+
showStateDropdown = true,
|
|
42
|
+
stateDropdownDisabled = false,
|
|
43
|
+
}) => {
|
|
44
|
+
const selectWidth = layoutMode === LAYOUT_MODE.COMPACT ? SELECT_WIDTH.COMPACT : SELECT_WIDTH.FULL;
|
|
45
|
+
|
|
46
|
+
// Determine if State dropdown should be disabled
|
|
47
|
+
// Supports boolean or function for extensibility
|
|
48
|
+
// Note: Can be extended to support object { disabled: boolean, tooltip?: string } for tooltip messages
|
|
49
|
+
const isStateDropdownDisabled = useMemo(() => {
|
|
50
|
+
// Disable State dropdown for Windows (only Collapsed state is supported)
|
|
51
|
+
if (selectedOS === 'Windows') {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return typeof stateDropdownDisabled === 'function'
|
|
55
|
+
? stateDropdownDisabled(selectedOS, selectedBrowser, stateOptions)
|
|
56
|
+
: stateDropdownDisabled;
|
|
57
|
+
}, [stateDropdownDisabled, selectedOS, selectedBrowser, stateOptions]);
|
|
58
|
+
|
|
59
|
+
// Memoized State dropdown content to avoid recreating on every render
|
|
60
|
+
const stateDropdownContent = useMemo(() => (
|
|
61
|
+
<>
|
|
62
|
+
<CapLabel type="label1" className="preview-control-label" fontWeight="bold">
|
|
63
|
+
<FormattedMessage {...messages.state} />
|
|
64
|
+
</CapLabel>
|
|
65
|
+
<CapSelect.CapCustomSelect
|
|
66
|
+
width={selectWidth}
|
|
67
|
+
options={stateOptions}
|
|
68
|
+
value={selectedState}
|
|
69
|
+
onChange={onStateChange}
|
|
70
|
+
// Note: Add tooltip prop here when tooltip mechanism is implemented
|
|
71
|
+
// tooltip={tooltipMessage ? tooltipMessage : undefined}
|
|
72
|
+
/>
|
|
73
|
+
</>
|
|
74
|
+
), [selectWidth, stateOptions, selectedState, onStateChange]);
|
|
75
|
+
|
|
76
|
+
// Memoized State dropdown renderer with disabled wrapper
|
|
77
|
+
const renderStateDropdown = useMemo(() => {
|
|
78
|
+
if (isStateDropdownDisabled) {
|
|
79
|
+
return (
|
|
80
|
+
<div style={{ pointerEvents: 'none', opacity: 0.6 }}>
|
|
81
|
+
{stateDropdownContent}
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return stateDropdownContent;
|
|
86
|
+
}, [isStateDropdownDisabled, stateDropdownContent]);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Cascading dropdown reset handler for OS changes
|
|
90
|
+
* Resets Browser and State to first available option when OS changes
|
|
91
|
+
*
|
|
92
|
+
* This implements cascading dropdown behavior: when a parent dropdown (OS) changes,
|
|
93
|
+
* dependent dropdowns (Browser, State) reset to their first available option.
|
|
94
|
+
*/
|
|
95
|
+
const handleOSChangeWithCascade = useCallback((value) => {
|
|
96
|
+
onOSChange(value);
|
|
97
|
+
|
|
98
|
+
// Get browser options for the new OS to ensure we reset to a valid option
|
|
99
|
+
const newBrowserOptions = getBrowserOptionsForOS(value);
|
|
100
|
+
|
|
101
|
+
// Reset Browser to first available option for the new OS
|
|
102
|
+
const newBrowserValue = newBrowserOptions && newBrowserOptions.length > 0
|
|
103
|
+
? newBrowserOptions[0].value
|
|
104
|
+
: null;
|
|
105
|
+
|
|
106
|
+
if (newBrowserValue && onBrowserChange) {
|
|
107
|
+
onBrowserChange(newBrowserValue);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Compute fresh state options based on new OS and Browser combination
|
|
111
|
+
// Only reset state if stateOptions is provided and not empty
|
|
112
|
+
if (showStateDropdown && newBrowserValue && onStateChange && stateOptions && stateOptions.length > 0) {
|
|
113
|
+
// For Windows, always reset to "Collapsed" (only supported state)
|
|
114
|
+
if (value === 'Windows') {
|
|
115
|
+
onStateChange('Collapsed');
|
|
116
|
+
} else {
|
|
117
|
+
const newSupportedStates = getSupportedStates(value, newBrowserValue);
|
|
118
|
+
const newStateOptions = newSupportedStates.length > 0
|
|
119
|
+
? newSupportedStates.map((state) => ({ value: state, label: state }))
|
|
120
|
+
: STATE_OPTIONS;
|
|
121
|
+
|
|
122
|
+
if (newStateOptions.length > 0) {
|
|
123
|
+
onStateChange(newStateOptions[0].value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}, [onOSChange, onBrowserChange, onStateChange, stateOptions, showStateDropdown]);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Cascading dropdown reset handler for Browser changes
|
|
131
|
+
* Resets State to first available option when Browser changes
|
|
132
|
+
*/
|
|
133
|
+
const handleBrowserChangeWithCascade = useCallback((value) => {
|
|
134
|
+
onBrowserChange(value);
|
|
135
|
+
|
|
136
|
+
// Compute fresh state options based on current OS and new Browser combination
|
|
137
|
+
// Only reset state if stateOptions is provided and not empty
|
|
138
|
+
if (showStateDropdown && onStateChange && stateOptions && stateOptions.length > 0) {
|
|
139
|
+
const newSupportedStates = getSupportedStates(selectedOS, value);
|
|
140
|
+
const newStateOptions = newSupportedStates.length > 0
|
|
141
|
+
? newSupportedStates.map((state) => ({ value: state, label: state }))
|
|
142
|
+
: STATE_OPTIONS;
|
|
143
|
+
|
|
144
|
+
if (newStateOptions.length > 0) {
|
|
145
|
+
onStateChange(newStateOptions[0].value);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}, [onBrowserChange, onStateChange, selectedOS, stateOptions, showStateDropdown]);
|
|
149
|
+
|
|
150
|
+
const controls = [
|
|
151
|
+
{
|
|
152
|
+
key: 'os',
|
|
153
|
+
label: 'operatingSystem',
|
|
154
|
+
value: selectedOS,
|
|
155
|
+
options: osOptions,
|
|
156
|
+
onChange: handleOSChangeWithCascade,
|
|
157
|
+
className: 'preview-control-os',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
key: 'browser',
|
|
161
|
+
label: 'browser',
|
|
162
|
+
value: selectedBrowser,
|
|
163
|
+
options: browserOptions,
|
|
164
|
+
onChange: handleBrowserChangeWithCascade,
|
|
165
|
+
className: 'preview-control-browser',
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
if (showStateDropdown) {
|
|
170
|
+
controls.push({
|
|
171
|
+
key: 'state',
|
|
172
|
+
label: 'state',
|
|
173
|
+
value: selectedState,
|
|
174
|
+
options: stateOptions,
|
|
175
|
+
onChange: onStateChange,
|
|
176
|
+
className: 'preview-control-state',
|
|
177
|
+
disabled: isStateDropdownDisabled,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const renderControl = useCallback(({ key, label, value, options, onChange, className, disabled }) => {
|
|
182
|
+
// Special handling for state dropdown - use memoized renderer
|
|
183
|
+
if (key === 'state') {
|
|
184
|
+
return (
|
|
185
|
+
<div key={key} className={`preview-control ${className}`}>
|
|
186
|
+
{renderStateDropdown}
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const controlContent = (
|
|
192
|
+
<>
|
|
193
|
+
<CapLabel type="label1" className="preview-control-label" fontWeight="bold">
|
|
194
|
+
<FormattedMessage {...messages[label]} />
|
|
195
|
+
</CapLabel>
|
|
196
|
+
<CapSelect.CapCustomSelect
|
|
197
|
+
width={selectWidth}
|
|
198
|
+
options={options}
|
|
199
|
+
value={value}
|
|
200
|
+
onChange={onChange}
|
|
201
|
+
/>
|
|
202
|
+
</>
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
return (
|
|
206
|
+
<div key={key} className={`preview-control ${className}`}>
|
|
207
|
+
{disabled ? (
|
|
208
|
+
<div style={{ pointerEvents: 'none', opacity: 0.6 }}>
|
|
209
|
+
{controlContent}
|
|
210
|
+
</div>
|
|
211
|
+
) : (
|
|
212
|
+
controlContent
|
|
213
|
+
)}
|
|
214
|
+
</div>
|
|
215
|
+
);
|
|
216
|
+
}, [selectWidth, renderStateDropdown]);
|
|
217
|
+
|
|
218
|
+
if (layoutMode === LAYOUT_MODE.COMPACT) {
|
|
219
|
+
return (
|
|
220
|
+
<div className="preview-controls preview-controls-compact">
|
|
221
|
+
{controls.map(renderControl)}
|
|
222
|
+
</div>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<>
|
|
228
|
+
<CapRow className="preview-controls">
|
|
229
|
+
<CapColumn span={12} className="preview-control-os">
|
|
230
|
+
<CapLabel type="label1" className="preview-control-label" fontWeight="bold">
|
|
231
|
+
<FormattedMessage {...messages.operatingSystem} />
|
|
232
|
+
</CapLabel>
|
|
233
|
+
<CapSelect.CapCustomSelect
|
|
234
|
+
width={selectWidth}
|
|
235
|
+
options={osOptions}
|
|
236
|
+
value={selectedOS}
|
|
237
|
+
onChange={handleOSChangeWithCascade}
|
|
238
|
+
/>
|
|
239
|
+
</CapColumn>
|
|
240
|
+
<CapColumn span={12} className="preview-control-browser">
|
|
241
|
+
<CapLabel type="label1" className="preview-control-label" fontWeight="bold">
|
|
242
|
+
<FormattedMessage {...messages.browser} />
|
|
243
|
+
</CapLabel>
|
|
244
|
+
<CapSelect.CapCustomSelect
|
|
245
|
+
width={selectWidth}
|
|
246
|
+
options={browserOptions}
|
|
247
|
+
value={selectedBrowser}
|
|
248
|
+
onChange={handleBrowserChangeWithCascade}
|
|
249
|
+
/>
|
|
250
|
+
</CapColumn>
|
|
251
|
+
</CapRow>
|
|
252
|
+
{showStateDropdown && layoutMode === LAYOUT_MODE.NEW_ROW && (
|
|
253
|
+
<CapRow className="preview-controls-state">
|
|
254
|
+
<CapColumn span={12} className="preview-control-state">
|
|
255
|
+
{renderStateDropdown}
|
|
256
|
+
</CapColumn>
|
|
257
|
+
</CapRow>
|
|
258
|
+
)}
|
|
259
|
+
{showStateDropdown && layoutMode === LAYOUT_MODE.INLINE && (
|
|
260
|
+
<CapRow className="preview-controls">
|
|
261
|
+
<CapColumn span={12} className="preview-control-state">
|
|
262
|
+
{renderStateDropdown}
|
|
263
|
+
</CapColumn>
|
|
264
|
+
</CapRow>
|
|
265
|
+
)}
|
|
266
|
+
</>
|
|
267
|
+
);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
PreviewControls.propTypes = {
|
|
271
|
+
selectedOS: PropTypes.string.isRequired,
|
|
272
|
+
selectedBrowser: PropTypes.string.isRequired,
|
|
273
|
+
selectedState: PropTypes.string,
|
|
274
|
+
osOptions: PropTypes.arrayOf(
|
|
275
|
+
PropTypes.shape({
|
|
276
|
+
value: PropTypes.string.isRequired,
|
|
277
|
+
label: PropTypes.string.isRequired,
|
|
278
|
+
}),
|
|
279
|
+
).isRequired,
|
|
280
|
+
browserOptions: PropTypes.arrayOf(
|
|
281
|
+
PropTypes.shape({
|
|
282
|
+
value: PropTypes.string.isRequired,
|
|
283
|
+
label: PropTypes.string.isRequired,
|
|
284
|
+
}),
|
|
285
|
+
).isRequired,
|
|
286
|
+
stateOptions: PropTypes.arrayOf(
|
|
287
|
+
PropTypes.shape({
|
|
288
|
+
value: PropTypes.string.isRequired,
|
|
289
|
+
label: PropTypes.string.isRequired,
|
|
290
|
+
}),
|
|
291
|
+
),
|
|
292
|
+
onOSChange: PropTypes.func.isRequired,
|
|
293
|
+
onBrowserChange: PropTypes.func.isRequired,
|
|
294
|
+
onStateChange: PropTypes.func,
|
|
295
|
+
layoutMode: PropTypes.oneOf([LAYOUT_MODE.INLINE, LAYOUT_MODE.NEW_ROW, LAYOUT_MODE.COMPACT]),
|
|
296
|
+
showStateDropdown: PropTypes.bool,
|
|
297
|
+
stateDropdownDisabled: PropTypes.oneOfType([
|
|
298
|
+
PropTypes.bool,
|
|
299
|
+
PropTypes.func, // (selectedOS, selectedBrowser, stateOptions) => boolean
|
|
300
|
+
// Note: Can add PropTypes.shape({ disabled: PropTypes.bool, tooltip: PropTypes.string }) for tooltip support
|
|
301
|
+
]),
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
export default PreviewControls;
|
|
305
|
+
|