@pushwoosh/dumb-components 1.1.232 → 1.1.234

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/NativeRichMediaPreview/NativeRichMediaPreview.d.ts +3 -0
  2. package/NativeRichMediaPreview/NativeRichMediaPreview.js +50 -8
  3. package/NativeRichMediaPreview/__tests__/NativeRichMediaPreview.test.d.ts +1 -0
  4. package/NativeRichMediaPreview/__tests__/NativeRichMediaPreview.test.js +81 -0
  5. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/NativeRichMediaThemeChip.d.ts +3 -0
  6. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/NativeRichMediaThemeChip.js +25 -0
  7. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/index.d.ts +1 -0
  8. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/index.js +1 -0
  9. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/styles.d.ts +8 -0
  10. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/styles.js +7 -0
  11. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/types.d.ts +8 -0
  12. package/NativeRichMediaPreview/components/NativeRichMediaThemeChip/types.js +1 -0
  13. package/NativeRichMediaPreview/icons/MoonIcon.svg +3 -0
  14. package/NativeRichMediaPreview/icons/SunIcon.svg +4 -0
  15. package/NativeRichMediaPreview/icons/index.d.ts +2 -0
  16. package/NativeRichMediaPreview/icons/index.js +3 -1
  17. package/NativeRichMediaPreview/index.d.ts +2 -1
  18. package/NativeRichMediaPreview/index.js +2 -1
  19. package/NativeRichMediaPreview/mergeNativeDarkOverlay.d.ts +3 -0
  20. package/NativeRichMediaPreview/mergeNativeDarkOverlay.js +89 -0
  21. package/NativeRichMediaPreview/polyglot/messages_ar.js +2 -1
  22. package/NativeRichMediaPreview/polyglot/messages_bn.js +2 -1
  23. package/NativeRichMediaPreview/polyglot/messages_de.js +2 -1
  24. package/NativeRichMediaPreview/polyglot/messages_en.js +2 -1
  25. package/NativeRichMediaPreview/polyglot/messages_es.js +2 -1
  26. package/NativeRichMediaPreview/polyglot/messages_fr.js +2 -1
  27. package/NativeRichMediaPreview/polyglot/messages_he.js +2 -1
  28. package/NativeRichMediaPreview/polyglot/messages_hi.js +2 -1
  29. package/NativeRichMediaPreview/polyglot/messages_ja.js +2 -1
  30. package/NativeRichMediaPreview/polyglot/messages_ko.js +2 -1
  31. package/NativeRichMediaPreview/polyglot/messages_pt.js +2 -1
  32. package/NativeRichMediaPreview/polyglot/messages_ru.js +2 -1
  33. package/NativeRichMediaPreview/polyglot/messages_th.js +2 -1
  34. package/NativeRichMediaPreview/polyglot/messages_zh.js +2 -1
  35. package/NativeRichMediaPreview/polyglot/types.d.ts +1 -0
  36. package/NativeRichMediaPreview/types.d.ts +32 -0
  37. package/index.d.ts +1 -1
  38. package/package.json +1 -1
@@ -13,6 +13,9 @@ import type { NativeRichMediaPreviewProps } from './types';
13
13
  * the box — the way to cram a phone-sized layout into a small preview box
14
14
  * while keeping its proportions. Shows an invalid-config placeholder when the
15
15
  * config cannot be parsed or misses required contract fields.
16
+ * With `withThemeToggle` the preview carries a chip that flips it between the light
17
+ * values and the dark overlay merged over them; pass `theme` with `onThemeChange`
18
+ * to drive that from outside, when a surrounding editor has its own theme switch.
16
19
  *
17
20
  * @example
18
21
  * <NativeRichMediaPreview
@@ -1,12 +1,14 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useMemo } from 'react';
2
+ import { useMemo, useState } from 'react';
3
3
  import { Color } from '@pushwoosh/kit-constants';
4
4
  import { Spinner } from '../Spinner';
5
5
  import { NativeRichMediaCloseChip } from './components/NativeRichMediaCloseChip';
6
6
  import { NativeRichMediaCoverImg } from './components/NativeRichMediaCoverImg';
7
7
  import { NativeRichMediaDim } from './components/NativeRichMediaDim';
8
8
  import { NativeRichMediaMuteChip } from './components/NativeRichMediaMuteChip';
9
+ import { NativeRichMediaThemeChip } from './components/NativeRichMediaThemeChip';
9
10
  import { EDGE_INSET, clampIndex } from './helpers';
11
+ import { mergeNativeDarkOverlay } from './mergeNativeDarkOverlay';
10
12
  import { parseNativeRichMediaConfig } from './parseNativeRichMediaConfig';
11
13
  import { resolveLocalization } from './resolveLocalization';
12
14
  import { BackdropLayer, ContentFlow, PreviewHost, ScaleViewport, ScrollLayer, SpinnerLayer } from './styles';
@@ -150,6 +152,17 @@ function renderConfig(config, activeIndex) {
150
152
  return _jsx(NativeRichMediaInvalidView, {});
151
153
  }
152
154
  }
155
+ function parseDarkVariant(content, withThemeToggle) {
156
+ if (!withThemeToggle) {
157
+ return null;
158
+ }
159
+ const merged = mergeNativeDarkOverlay(content);
160
+ // The merge hands back `content` itself, untouched, whenever there is nothing to apply.
161
+ if (merged === content) {
162
+ return null;
163
+ }
164
+ return parseNativeRichMediaConfig(merged);
165
+ }
153
166
  /**
154
167
  * Preview of a native rich media (modal, banner, stories, spinwheel, …)
155
168
  * rendered from its JSON config; lays out with plain CSS in the real pixels
@@ -163,6 +176,9 @@ function renderConfig(config, activeIndex) {
163
176
  * the box — the way to cram a phone-sized layout into a small preview box
164
177
  * while keeping its proportions. Shows an invalid-config placeholder when the
165
178
  * config cannot be parsed or misses required contract fields.
179
+ * With `withThemeToggle` the preview carries a chip that flips it between the light
180
+ * values and the dark overlay merged over them; pass `theme` with `onThemeChange`
181
+ * to drive that from outside, when a surrounding editor has its own theme switch.
166
182
  *
167
183
  * @example
168
184
  * <NativeRichMediaPreview
@@ -190,15 +206,37 @@ export function NativeRichMediaPreview(props) {
190
206
  scale = 1,
191
207
  activeIndex = 0,
192
208
  isLoading = false,
193
- withBackground = true
209
+ withBackground = true,
210
+ withThemeToggle = false,
211
+ theme: controlledTheme,
212
+ onThemeChange
194
213
  } = props;
195
- const config = useMemo(() => {
196
- const parsed = parseNativeRichMediaConfig(content);
197
- if (!parsed) {
198
- return null;
214
+ const [ownTheme, setOwnTheme] = useState('light');
215
+ const isControlled = controlledTheme !== undefined;
216
+ const theme = controlledTheme ?? ownTheme;
217
+ const toggleTheme = () => {
218
+ const next = theme === 'dark' ? 'light' : 'dark';
219
+ if (!isControlled) {
220
+ setOwnTheme(next);
199
221
  }
200
- return parseNativeRichMediaConfig(resolveLocalization(parsed, localization, language));
201
- }, [content, localization, language]);
222
+ onThemeChange?.(next);
223
+ };
224
+ const {
225
+ config,
226
+ isDarkRendered,
227
+ canToggleDark
228
+ } = useMemo(() => {
229
+ const lightParsed = parseNativeRichMediaConfig(content);
230
+ const darkParsed = parseDarkVariant(content, withThemeToggle);
231
+ const themed = theme === 'dark' && darkParsed ? darkParsed : lightParsed;
232
+ const resolved = themed ? parseNativeRichMediaConfig(resolveLocalization(themed, localization, language)) : null;
233
+ return {
234
+ config: resolved,
235
+ isDarkRendered: Boolean(resolved) && theme === 'dark' && darkParsed !== null,
236
+ canToggleDark: darkParsed !== null
237
+ };
238
+ }, [content, localization, language, theme, withThemeToggle]);
239
+ const renderedTheme = isDarkRendered ? 'dark' : 'light';
202
240
  const shouldDim = !isLoading && dimsScreen(config);
203
241
  return _jsxs(PreviewHost, {
204
242
  ...LIGHT_ISLAND_PROPS,
@@ -215,6 +253,10 @@ export function NativeRichMediaPreview(props) {
215
253
  children: renderConfig(config, activeIndex)
216
254
  })
217
255
  }, config.displayType), !isLoading && config && renderChrome(config)]
256
+ }), !isLoading && config && withThemeToggle && _jsx(NativeRichMediaThemeChip, {
257
+ theme: isControlled ? theme : renderedTheme,
258
+ disabled: !isControlled && !canToggleDark,
259
+ onToggle: toggleTheme
218
260
  }), isLoading && _jsx(SpinnerLayer, {
219
261
  children: _jsx(Spinner, {
220
262
  size: "medium",
@@ -0,0 +1,81 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import { NativeRichMediaPreview } from '../NativeRichMediaPreview';
4
+ const lightContent = {
5
+ displayType: 'modal',
6
+ modal: {
7
+ showClose: true,
8
+ dimBackground: true,
9
+ background: '#FFFFFFFF',
10
+ title: {
11
+ text: 'Hi',
12
+ color: '#4B5057FF'
13
+ },
14
+ buttons: []
15
+ }
16
+ };
17
+ const darkableContent = {
18
+ ...lightContent,
19
+ modal: {
20
+ ...lightContent.modal,
21
+ dark: {
22
+ background: '#14161AFF',
23
+ title: {
24
+ color: '#F2F4F7FF'
25
+ }
26
+ }
27
+ }
28
+ };
29
+ const themeChip = () => screen.getByLabelText('Switch light and dark');
30
+ const renderPreview = (props = {}) => render(_jsx(NativeRichMediaPreview, {
31
+ content: darkableContent,
32
+ width: 304,
33
+ height: 659,
34
+ withThemeToggle: true,
35
+ ...props
36
+ }));
37
+ describe('NativeRichMediaPreview theme toggle', () => {
38
+ it('keeps its own theme when uncontrolled', () => {
39
+ renderPreview();
40
+ expect(themeChip()).toHaveAttribute('aria-pressed', 'false');
41
+ fireEvent.click(themeChip());
42
+ expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
43
+ });
44
+ it('follows the theme prop instead of a click when controlled', () => {
45
+ const onThemeChange = jest.fn();
46
+ const {
47
+ rerender
48
+ } = renderPreview({
49
+ theme: 'dark',
50
+ onThemeChange
51
+ });
52
+ expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
53
+ fireEvent.click(themeChip());
54
+ expect(onThemeChange).toHaveBeenCalledWith('light');
55
+ expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
56
+ rerender(_jsx(NativeRichMediaPreview, {
57
+ content: darkableContent,
58
+ width: 304,
59
+ height: 659,
60
+ withThemeToggle: true,
61
+ theme: "light",
62
+ onThemeChange: onThemeChange
63
+ }));
64
+ expect(themeChip()).toHaveAttribute('aria-pressed', 'false');
65
+ });
66
+ it('reports the controlled theme even when the content has no dark overlay', () => {
67
+ renderPreview({
68
+ content: lightContent,
69
+ theme: 'dark',
70
+ onThemeChange: jest.fn()
71
+ });
72
+ expect(themeChip()).toHaveAttribute('aria-pressed', 'true');
73
+ expect(themeChip()).not.toBeDisabled();
74
+ });
75
+ it('disables the chip on content without a dark overlay when uncontrolled', () => {
76
+ renderPreview({
77
+ content: lightContent
78
+ });
79
+ expect(themeChip()).toBeDisabled();
80
+ });
81
+ });
@@ -0,0 +1,3 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { NativeRichMediaThemeChipProps } from './types';
3
+ export declare function NativeRichMediaThemeChip(props: NativeRichMediaThemeChipProps): ReactElement;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ThemeToggleChip } from './styles';
3
+ import { MoonIcon, SunIcon } from '../../icons';
4
+ import { useLang } from '../../polyglot';
5
+ export function NativeRichMediaThemeChip(props) {
6
+ const {
7
+ theme,
8
+ disabled,
9
+ onToggle
10
+ } = props;
11
+ const lang = useLang();
12
+ return _jsx(ThemeToggleChip, {
13
+ as: "button",
14
+ type: "button",
15
+ "$bottom": 8,
16
+ "$right": 8,
17
+ disabled: disabled,
18
+ "aria-label": lang({
19
+ message: 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME'
20
+ }),
21
+ "aria-pressed": theme === 'dark',
22
+ onClick: onToggle,
23
+ children: theme === 'dark' ? _jsx(MoonIcon, {}) : _jsx(SunIcon, {})
24
+ });
25
+ }
@@ -0,0 +1 @@
1
+ export { NativeRichMediaThemeChip } from './NativeRichMediaThemeChip';
@@ -0,0 +1 @@
1
+ export { NativeRichMediaThemeChip } from './NativeRichMediaThemeChip';
@@ -0,0 +1,8 @@
1
+ /** `Chip` is a `div`; rendered `as="button"` it needs the UA button chrome stripped or it fights `border-radius: 50%`. */
2
+ export declare const ThemeToggleChip: import("styled-components").StyledComponent<"div", any, {
3
+ $small?: boolean;
4
+ $top?: number;
5
+ $right?: number;
6
+ $bottom?: number;
7
+ $left?: number;
8
+ }, never>;
@@ -0,0 +1,7 @@
1
+ import styled from 'styled-components';
2
+ import { Chip } from '../NativeRichMediaCloseChip';
3
+ /** `Chip` is a `div`; rendered `as="button"` it needs the UA button chrome stripped or it fights `border-radius: 50%`. */
4
+ export const ThemeToggleChip = styled(Chip).withConfig({
5
+ displayName: "ThemeToggleChip",
6
+ componentId: "sc-e7nbqw-0"
7
+ })(["border:none;padding:0;appearance:none;font:inherit;&:disabled{cursor:default;opacity:0.4;}"]);
@@ -0,0 +1,8 @@
1
+ export type NativeRichMediaThemeChipProps = {
2
+ /** Theme actually rendered right now — may lag a click while an overlay applies, or fall back to light. */
3
+ theme: 'light' | 'dark';
4
+ /** Disable when the current content has no dark overlay to switch to. */
5
+ disabled: boolean;
6
+ /** Flip to the other theme. */
7
+ onToggle: () => void;
8
+ };
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none">
2
+ <path d="M20.5 14.9A8.6 8.6 0 019.1 3.5a8.6 8.6 0 1011.4 11.4z" fill="#fff"/>
3
+ </svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none">
2
+ <circle cx="12" cy="12" r="4.5" fill="#fff"/>
3
+ <path d="M12 2v2.5M12 19.5V22M4.22 4.22l1.77 1.77M18.01 18.01l1.77 1.77M2 12h2.5M19.5 12H22M4.22 19.78l1.77-1.77M18.01 5.99l1.77-1.77" stroke="#fff" stroke-width="2" stroke-linecap="round"/>
4
+ </svg>
@@ -1,5 +1,7 @@
1
1
  export { default as CopyIcon } from './CopyIcon.svg';
2
2
  export { default as ExpandIcon } from './ExpandIcon.svg';
3
+ export { default as MoonIcon } from './MoonIcon.svg';
3
4
  export { default as MutedIcon } from './MutedIcon.svg';
4
5
  export { default as ScribbleIcon } from './ScribbleIcon.svg';
5
6
  export { default as SoundIcon } from './SoundIcon.svg';
7
+ export { default as SunIcon } from './SunIcon.svg';
@@ -1,5 +1,7 @@
1
1
  export { default as CopyIcon } from './CopyIcon.svg';
2
2
  export { default as ExpandIcon } from './ExpandIcon.svg';
3
+ export { default as MoonIcon } from './MoonIcon.svg';
3
4
  export { default as MutedIcon } from './MutedIcon.svg';
4
5
  export { default as ScribbleIcon } from './ScribbleIcon.svg';
5
- export { default as SoundIcon } from './SoundIcon.svg';
6
+ export { default as SoundIcon } from './SoundIcon.svg';
7
+ export { default as SunIcon } from './SunIcon.svg';
@@ -1,5 +1,6 @@
1
- export type { NativeRichMediaConfig, NativeRichMediaDisplayType, NativeRichMediaText, NativeRichMediaBorder, NativeRichMediaCover, NativeRichMediaAction, NativeRichMediaButton, NativeRichMediaReward, NativeRichMediaModalBlock, NativeRichMediaSheetBlock, NativeRichMediaCarouselBlock, NativeRichMediaStoriesBlock, NativeRichMediaBannerBlock, NativeRichMediaFullscreenBlock, NativeRichMediaVideoBlock, NativeRichMediaPipBlock, NativeRichMediaScratchcardBlock, NativeRichMediaSpinwheelBlock, NativeRichMediaPreviewProps, NativeRichMediaLocalization, } from './types';
1
+ export type { NativeRichMediaConfig, NativeRichMediaDisplayType, NativeRichMediaDarkOverlay, NativeRichMediaText, NativeRichMediaBorder, NativeRichMediaCover, NativeRichMediaAction, NativeRichMediaButton, NativeRichMediaReward, NativeRichMediaModalBlock, NativeRichMediaSheetBlock, NativeRichMediaCarouselBlock, NativeRichMediaStoriesBlock, NativeRichMediaBannerBlock, NativeRichMediaFullscreenBlock, NativeRichMediaVideoBlock, NativeRichMediaPipBlock, NativeRichMediaScratchcardBlock, NativeRichMediaSpinwheelBlock, NativeRichMediaPreviewProps, NativeRichMediaLocalization, NativeRichMediaTheme, } from './types';
2
2
  export { NATIVE_RICH_MEDIA_DISPLAY_TYPES, EDITABLE_NATIVE_RICH_MEDIA_DISPLAY_TYPES } from './types';
3
3
  export { parseNativeRichMediaConfig } from './parseNativeRichMediaConfig';
4
4
  export { parseLocalizationParams } from './parseLocalizationParams';
5
5
  export { NativeRichMediaPreview } from './NativeRichMediaPreview';
6
+ export { mergeNativeDarkOverlay } from './mergeNativeDarkOverlay';
@@ -1,4 +1,5 @@
1
1
  export { NATIVE_RICH_MEDIA_DISPLAY_TYPES, EDITABLE_NATIVE_RICH_MEDIA_DISPLAY_TYPES } from './types';
2
2
  export { parseNativeRichMediaConfig } from './parseNativeRichMediaConfig';
3
3
  export { parseLocalizationParams } from './parseLocalizationParams';
4
- export { NativeRichMediaPreview } from './NativeRichMediaPreview';
4
+ export { NativeRichMediaPreview } from './NativeRichMediaPreview';
5
+ export { mergeNativeDarkOverlay } from './mergeNativeDarkOverlay';
@@ -0,0 +1,3 @@
1
+ import type { NativeRichMediaConfig } from './types';
2
+ /** Applies a config's `dark` overlay onto its display block ahead of strict parsing, since the light block may only become valid once the overlay lands; any break returns the input untouched. */
3
+ export declare function mergeNativeDarkOverlay(content: string | NativeRichMediaConfig): string | NativeRichMediaConfig;
@@ -0,0 +1,89 @@
1
+ import { isObject } from './helpers';
2
+ /** Leaves a theme may replace: everything else is content, not appearance. */
3
+ const VISUAL_LEAF_KEYS = new Set(['background', 'color', 'image', 'poster', 'fallback']);
4
+ /** The only arrays that merge; both sides must line up element for element. */
5
+ const POSITIONAL_ARRAY_KEYS = new Set(['buttons', 'items']);
6
+ function overlayOnto(light, dark) {
7
+ const merged = {
8
+ ...light
9
+ };
10
+ for (const key of Object.keys(dark)) {
11
+ const darkValue = dark[key];
12
+ const lightValue = light[key];
13
+ if (darkValue === null || darkValue === undefined) {
14
+ continue;
15
+ }
16
+ if (POSITIONAL_ARRAY_KEYS.has(key)) {
17
+ if (!Array.isArray(darkValue) || !Array.isArray(lightValue)) {
18
+ return null;
19
+ }
20
+ if (darkValue.length !== lightValue.length) {
21
+ return null;
22
+ }
23
+ const items = [];
24
+ for (let i = 0; i < darkValue.length; i += 1) {
25
+ const darkItem = darkValue[i];
26
+ const lightItem = lightValue[i];
27
+ if (!isObject(darkItem) || !isObject(lightItem)) {
28
+ return null;
29
+ }
30
+ const mergedItem = overlayOnto(lightItem, darkItem);
31
+ if (!mergedItem) {
32
+ return null;
33
+ }
34
+ items.push(mergedItem);
35
+ }
36
+ merged[key] = items;
37
+ continue;
38
+ }
39
+ if (isObject(lightValue)) {
40
+ if (isObject(darkValue)) {
41
+ const mergedChild = overlayOnto(lightValue, darkValue);
42
+ if (!mergedChild) {
43
+ return null;
44
+ }
45
+ merged[key] = mergedChild;
46
+ }
47
+ continue;
48
+ }
49
+ if (VISUAL_LEAF_KEYS.has(key)) {
50
+ merged[key] = Array.isArray(darkValue) ? [...darkValue] : darkValue;
51
+ }
52
+ }
53
+ return merged;
54
+ }
55
+ /** Applies a config's `dark` overlay onto its display block ahead of strict parsing, since the light block may only become valid once the overlay lands; any break returns the input untouched. */
56
+ export function mergeNativeDarkOverlay(content) {
57
+ let raw = content;
58
+ if (typeof content === 'string') {
59
+ try {
60
+ raw = JSON.parse(content);
61
+ } catch {
62
+ return content;
63
+ }
64
+ }
65
+ if (!isObject(raw)) {
66
+ return content;
67
+ }
68
+ const displayType = raw.displayType;
69
+ if (typeof displayType !== 'string') {
70
+ return content;
71
+ }
72
+ const block = raw[displayType];
73
+ if (!isObject(block)) {
74
+ return content;
75
+ }
76
+ const overlay = block.dark;
77
+ if (!isObject(overlay)) {
78
+ return content;
79
+ }
80
+ const merged = overlayOnto(block, overlay);
81
+ if (!merged) {
82
+ return content;
83
+ }
84
+ delete merged.dark;
85
+ return {
86
+ ...raw,
87
+ [displayType]: merged
88
+ };
89
+ }
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'تكوين rich media غير صالح',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'اكشط هنا'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'اكشط هنا',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'تبديل الوضع الفاتح والداكن'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'অবৈধ rich media কনফিগারেশন',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'এখানে ঘষুন'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'এখানে ঘষুন',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'হালকা এবং গাঢ় থিমের মধ্যে পরিবর্তন করুন'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'Ungültige Rich-Media-Konfiguration',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Hier freirubbeln'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Hier freirubbeln',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'Zwischen hellem und dunklem Design wechseln'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'Invalid rich media config',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Scratch here'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Scratch here',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'Switch light and dark'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'Configuración de rich media no válida',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Rasca aquí'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Rasca aquí',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'Cambiar entre claro y oscuro'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'Configuration rich media non valide',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Grattez ici'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Grattez ici',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'Basculer entre clair et sombre'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'תצורת rich media לא חוקית',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'גרדו כאן'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'גרדו כאן',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'החלף בין ערכת נושא בהירה לכהה'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'अमान्य rich media कॉन्फ़िगरेशन',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'यहाँ खरोंचें'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'यहाँ खरोंचें',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'लाइट और डार्क थीम के बीच स्विच करें'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'リッチメディア設定が無効です',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'ここを削ってください'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'ここを削ってください',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'ライトとダークを切り替える'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': '리치 미디어 구성이 잘못되었습니다',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': '여기를 긁어보세요'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': '여기를 긁어보세요',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': '라이트와 다크 테마 전환'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'Configuração de rich media inválida',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Raspe aqui'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Raspe aqui',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'Alternar entre claro e escuro'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'Некорректная конфигурация rich media',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Сотрите здесь'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'Сотрите здесь',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'Переключить светлую и тёмную тему'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': 'การกำหนดค่า rich media ไม่ถูกต้อง',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'ขูดที่นี่'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': 'ขูดที่นี่',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': 'สลับธีมสว่างและมืด'
4
5
  };
@@ -1,4 +1,5 @@
1
1
  export const messages = {
2
2
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': '富媒体配置无效',
3
- 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': '刮开此处'
3
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': '刮开此处',
4
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': '切换浅色和深色主题'
4
5
  };
@@ -2,4 +2,5 @@ import type { NoParams } from '../../polyglot';
2
2
  export type NativeRichMediaPreviewMessages = {
3
3
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.INVALID_CONFIG': NoParams;
4
4
  'DUMB.NATIVE_RICH_MEDIA_PREVIEW.SCRATCH_HERE': NoParams;
5
+ 'DUMB.NATIVE_RICH_MEDIA_PREVIEW.TOGGLE_THEME': NoParams;
5
6
  };
@@ -50,6 +50,10 @@ export type NativeRichMediaReward = {
50
50
  /** Confirmation button, including its own tap action. */
51
51
  button?: NativeRichMediaButton;
52
52
  };
53
+ /** Sparse dark overlay over a display block: visual leaves replace, objects merge recursively, `buttons`/`items` positionally; typed loosely because the shape is checked at merge time. */
54
+ export type NativeRichMediaDarkOverlay = {
55
+ [key: string]: string | string[] | NativeRichMediaDarkOverlay | NativeRichMediaDarkOverlay[] | undefined;
56
+ };
53
57
  /** Content of a `banner` rich media. */
54
58
  export type NativeRichMediaBannerBlock = {
55
59
  /** Show a close (✕) chip. */
@@ -68,6 +72,8 @@ export type NativeRichMediaBannerBlock = {
68
72
  action: NativeRichMediaAction;
69
73
  /** Auto-dismiss timeout in seconds; omit to keep the banner until closed. */
70
74
  autoDismiss?: number;
75
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
76
+ dark?: NativeRichMediaDarkOverlay;
71
77
  };
72
78
  /** Content of a `carousel` rich media. */
73
79
  export type NativeRichMediaCarouselBlock = {
@@ -84,6 +90,8 @@ export type NativeRichMediaCarouselBlock = {
84
90
  /** Tap action of the card. */
85
91
  action?: NativeRichMediaAction;
86
92
  }>;
93
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
94
+ dark?: NativeRichMediaDarkOverlay;
87
95
  };
88
96
  /** Content of a `modal` rich media. */
89
97
  export type NativeRichMediaModalBlock = {
@@ -101,6 +109,8 @@ export type NativeRichMediaModalBlock = {
101
109
  message?: NativeRichMediaText;
102
110
  /** Action buttons under the message. */
103
111
  buttons: Array<NativeRichMediaButton>;
112
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
113
+ dark?: NativeRichMediaDarkOverlay;
104
114
  };
105
115
  /** Content of a `sheet` rich media. */
106
116
  export type NativeRichMediaSheetBlock = {
@@ -118,6 +128,8 @@ export type NativeRichMediaSheetBlock = {
118
128
  message?: NativeRichMediaText;
119
129
  /** Action buttons under the message. */
120
130
  buttons: Array<NativeRichMediaButton>;
131
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
132
+ dark?: NativeRichMediaDarkOverlay;
121
133
  };
122
134
  /** Content of a `stories` rich media. */
123
135
  export type NativeRichMediaStoriesBlock = {
@@ -138,6 +150,8 @@ export type NativeRichMediaStoriesBlock = {
138
150
  /** Slide duration in seconds (SDK: default 5, max 30). */
139
151
  duration: number;
140
152
  }>;
153
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
154
+ dark?: NativeRichMediaDarkOverlay;
141
155
  };
142
156
  /** Content of a `fullscreen` rich media. */
143
157
  export type NativeRichMediaFullscreenBlock = {
@@ -151,6 +165,8 @@ export type NativeRichMediaFullscreenBlock = {
151
165
  message?: NativeRichMediaText;
152
166
  /** Action buttons at the bottom. */
153
167
  buttons: Array<NativeRichMediaButton>;
168
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
169
+ dark?: NativeRichMediaDarkOverlay;
154
170
  };
155
171
  /** Content of a `video` rich media. */
156
172
  export type NativeRichMediaVideoBlock = {
@@ -172,6 +188,8 @@ export type NativeRichMediaVideoBlock = {
172
188
  fallback?: string;
173
189
  /** Action buttons at the bottom. */
174
190
  buttons: Array<NativeRichMediaButton>;
191
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
192
+ dark?: NativeRichMediaDarkOverlay;
175
193
  };
176
194
  /** Content of a `pip` (picture-in-picture) rich media. */
177
195
  export type NativeRichMediaPipBlock = {
@@ -197,6 +215,8 @@ export type NativeRichMediaPipBlock = {
197
215
  borderRadius?: number;
198
216
  /** Tap action of the pip window itself. */
199
217
  action?: NativeRichMediaAction;
218
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
219
+ dark?: NativeRichMediaDarkOverlay;
200
220
  };
201
221
  /** Content of a `scratchcard` rich media. */
202
222
  export type NativeRichMediaScratchcardBlock = {
@@ -216,6 +236,8 @@ export type NativeRichMediaScratchcardBlock = {
216
236
  message?: NativeRichMediaText;
217
237
  /** Reward revealed under the cover; required by the SDK, may be absent in a draft. */
218
238
  reward?: NativeRichMediaReward;
239
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
240
+ dark?: NativeRichMediaDarkOverlay;
219
241
  };
220
242
  /** Content of a `spinwheel` rich media. */
221
243
  export type NativeRichMediaSpinwheelBlock = {
@@ -246,6 +268,8 @@ export type NativeRichMediaSpinwheelBlock = {
246
268
  /** Reward granted when the wheel stops on this sector. */
247
269
  reward?: NativeRichMediaReward;
248
270
  }>;
271
+ /** Dark-theme overlay merged over this block when the dark variant is shown. */
272
+ dark?: NativeRichMediaDarkOverlay;
249
273
  };
250
274
  /** Native rich media config: a display type and the matching content block. */
251
275
  export type NativeRichMediaConfig = {
@@ -295,6 +319,8 @@ export declare const EDITABLE_NATIVE_RICH_MEDIA_DISPLAY_TYPES: NativeRichMediaDi
295
319
  * fallback. Text lives here rather than inline in `content`.
296
320
  */
297
321
  export type NativeRichMediaLocalization = Record<string, Record<string, string>>;
322
+ /** Colour scheme the preview renders: the light values, or them merged with the dark overlay. */
323
+ export type NativeRichMediaTheme = 'light' | 'dark';
298
324
  /** Props of {@link NativeRichMediaPreview}. */
299
325
  export type NativeRichMediaPreviewProps = {
300
326
  /** Rich media config, as an object or a JSON string. */
@@ -315,4 +341,10 @@ export type NativeRichMediaPreviewProps = {
315
341
  isLoading?: boolean;
316
342
  /** Render the container chrome — white backdrop and border (default `true`). Set `false` for a transparent preview over the consumer's own background. */
317
343
  withBackground?: boolean;
344
+ /** Show a chip that switches the preview between the light and dark variants (default `false`). Uncontrolled, the preview opens light. */
345
+ withThemeToggle?: boolean;
346
+ /** Theme to render, making the chip controlled: the preview follows this value, the chip mirrors it (rather than the rendered result, since content without a dark overlay renders identically in both themes) and only reports clicks through `onThemeChange`. Omit to let the preview keep its own state. */
347
+ theme?: NativeRichMediaTheme;
348
+ /** Called with the other theme when the chip is clicked; the only way to react to the chip on a controlled preview. */
349
+ onThemeChange?: (theme: NativeRichMediaTheme) => void;
318
350
  };
package/index.d.ts CHANGED
@@ -50,7 +50,7 @@ export { Chip, type ChipProps, type ChipVariant } from './Chip';
50
50
  export { RichmediaPreview, useRichmediaHtml } from './RichmediaPreview';
51
51
  export { SandboxedHtmlFrame, type SandboxedHtmlFrameProps, type IsolatedSandboxedHtmlFrameProps, type InspectableSandboxedHtmlFrameProps, } from './SandboxedHtmlFrame';
52
52
  export { NativeRichMediaPreview, parseNativeRichMediaConfig, parseLocalizationParams, NATIVE_RICH_MEDIA_DISPLAY_TYPES, EDITABLE_NATIVE_RICH_MEDIA_DISPLAY_TYPES, } from './NativeRichMediaPreview';
53
- export type { NativeRichMediaConfig, NativeRichMediaDisplayType, NativeRichMediaText, NativeRichMediaBorder, NativeRichMediaCover, NativeRichMediaAction, NativeRichMediaButton, NativeRichMediaReward, NativeRichMediaModalBlock, NativeRichMediaSheetBlock, NativeRichMediaCarouselBlock, NativeRichMediaStoriesBlock, NativeRichMediaBannerBlock, NativeRichMediaFullscreenBlock, NativeRichMediaVideoBlock, NativeRichMediaPipBlock, NativeRichMediaScratchcardBlock, NativeRichMediaSpinwheelBlock, NativeRichMediaPreviewProps, NativeRichMediaLocalization, } from './NativeRichMediaPreview';
53
+ export type { NativeRichMediaConfig, NativeRichMediaDisplayType, NativeRichMediaText, NativeRichMediaBorder, NativeRichMediaCover, NativeRichMediaAction, NativeRichMediaButton, NativeRichMediaReward, NativeRichMediaDarkOverlay, NativeRichMediaModalBlock, NativeRichMediaSheetBlock, NativeRichMediaCarouselBlock, NativeRichMediaStoriesBlock, NativeRichMediaBannerBlock, NativeRichMediaFullscreenBlock, NativeRichMediaVideoBlock, NativeRichMediaPipBlock, NativeRichMediaScratchcardBlock, NativeRichMediaSpinwheelBlock, NativeRichMediaPreviewProps, NativeRichMediaLocalization, NativeRichMediaTheme, } from './NativeRichMediaPreview';
54
54
  export { RichMessageEditor, RichMessageViewer, type DynamicContent, } from './RichMessageEditor';
55
55
  export { FrequencyCappingEdit, FrequencyCappingReadonly, FrequencyCappingInApps, CappingCountingMode, CountingMode, type CountingModeProps, type LocalCapping, type CappingInAppBase, } from './FrequencyCapping';
56
56
  export { SendRateEdit, SendRateReadonly, type SendRateBase, type SendRateEditProps, type SendRateReadonlyProps, } from './SendRate';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/dumb-components",
3
- "version": "1.1.232",
3
+ "version": "1.1.234",
4
4
  "description": "React components to build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",