@pushwoosh/dumb-components 2.7.2 → 2.8.1

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 (65) hide show
  1. package/PushStoriesPreview/PushStoriesPreview.d.ts +5 -0
  2. package/PushStoriesPreview/PushStoriesPreview.js +71 -0
  3. package/PushStoriesPreview/__tests__/PushStoriesPreview.test.d.ts +1 -0
  4. package/PushStoriesPreview/__tests__/PushStoriesPreview.test.js +234 -0
  5. package/PushStoriesPreview/components/PushStoriesPage/PushStoriesPage.d.ts +4 -0
  6. package/PushStoriesPreview/components/PushStoriesPage/PushStoriesPage.js +38 -0
  7. package/PushStoriesPreview/components/PushStoriesPage/index.d.ts +1 -0
  8. package/PushStoriesPreview/components/PushStoriesPage/index.js +1 -0
  9. package/PushStoriesPreview/components/PushStoriesPage/styles.d.ts +7 -0
  10. package/PushStoriesPreview/components/PushStoriesPage/styles.js +32 -0
  11. package/PushStoriesPreview/components/PushStoriesPage/types.d.ts +6 -0
  12. package/PushStoriesPreview/components/PushStoriesPage/types.js +1 -0
  13. package/PushStoriesPreview/components/PushStoriesProgress/PushStoriesProgress.d.ts +4 -0
  14. package/PushStoriesPreview/components/PushStoriesProgress/PushStoriesProgress.js +24 -0
  15. package/PushStoriesPreview/components/PushStoriesProgress/index.d.ts +1 -0
  16. package/PushStoriesPreview/components/PushStoriesProgress/index.js +1 -0
  17. package/PushStoriesPreview/components/PushStoriesProgress/styles.d.ts +5 -0
  18. package/PushStoriesPreview/components/PushStoriesProgress/styles.js +19 -0
  19. package/PushStoriesPreview/components/PushStoriesProgress/types.d.ts +15 -0
  20. package/PushStoriesPreview/components/PushStoriesProgress/types.js +1 -0
  21. package/PushStoriesPreview/constants.d.ts +8 -0
  22. package/PushStoriesPreview/constants.js +9 -0
  23. package/PushStoriesPreview/helpers.d.ts +9 -0
  24. package/PushStoriesPreview/helpers.js +23 -0
  25. package/PushStoriesPreview/index.d.ts +4 -0
  26. package/PushStoriesPreview/index.js +3 -0
  27. package/PushStoriesPreview/polyglot/index.d.ts +2 -0
  28. package/PushStoriesPreview/polyglot/index.js +34 -0
  29. package/PushStoriesPreview/polyglot/messages_ar.d.ts +3 -0
  30. package/PushStoriesPreview/polyglot/messages_ar.js +4 -0
  31. package/PushStoriesPreview/polyglot/messages_bn.d.ts +3 -0
  32. package/PushStoriesPreview/polyglot/messages_bn.js +4 -0
  33. package/PushStoriesPreview/polyglot/messages_de.d.ts +3 -0
  34. package/PushStoriesPreview/polyglot/messages_de.js +4 -0
  35. package/PushStoriesPreview/polyglot/messages_en.d.ts +3 -0
  36. package/PushStoriesPreview/polyglot/messages_en.js +4 -0
  37. package/PushStoriesPreview/polyglot/messages_es.d.ts +3 -0
  38. package/PushStoriesPreview/polyglot/messages_es.js +4 -0
  39. package/PushStoriesPreview/polyglot/messages_fr.d.ts +3 -0
  40. package/PushStoriesPreview/polyglot/messages_fr.js +4 -0
  41. package/PushStoriesPreview/polyglot/messages_he.d.ts +3 -0
  42. package/PushStoriesPreview/polyglot/messages_he.js +4 -0
  43. package/PushStoriesPreview/polyglot/messages_hi.d.ts +3 -0
  44. package/PushStoriesPreview/polyglot/messages_hi.js +4 -0
  45. package/PushStoriesPreview/polyglot/messages_ja.d.ts +3 -0
  46. package/PushStoriesPreview/polyglot/messages_ja.js +4 -0
  47. package/PushStoriesPreview/polyglot/messages_ko.d.ts +3 -0
  48. package/PushStoriesPreview/polyglot/messages_ko.js +4 -0
  49. package/PushStoriesPreview/polyglot/messages_pt.d.ts +3 -0
  50. package/PushStoriesPreview/polyglot/messages_pt.js +4 -0
  51. package/PushStoriesPreview/polyglot/messages_ru.d.ts +3 -0
  52. package/PushStoriesPreview/polyglot/messages_ru.js +4 -0
  53. package/PushStoriesPreview/polyglot/messages_th.d.ts +3 -0
  54. package/PushStoriesPreview/polyglot/messages_th.js +4 -0
  55. package/PushStoriesPreview/polyglot/messages_zh.d.ts +3 -0
  56. package/PushStoriesPreview/polyglot/messages_zh.js +4 -0
  57. package/PushStoriesPreview/polyglot/types.d.ts +5 -0
  58. package/PushStoriesPreview/polyglot/types.js +1 -0
  59. package/PushStoriesPreview/styles.d.ts +6 -0
  60. package/PushStoriesPreview/styles.js +23 -0
  61. package/PushStoriesPreview/types.d.ts +32 -0
  62. package/PushStoriesPreview/types.js +1 -0
  63. package/index.d.ts +1 -0
  64. package/index.js +1 -0
  65. package/package.json +1 -1
@@ -0,0 +1,5 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { PushStoriesPreviewProps } from './types';
3
+ /** Expanded iOS push story as `PushwooshStoriesViewController` draws it: 2:3 page, progress, scrim, button.
4
+ * Fills its container's width, so it fits a form column as well as the phone of a push preview. */
5
+ export declare function PushStoriesPreview(props: PushStoriesPreviewProps): ReactElement;
@@ -0,0 +1,71 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { ImageIcon } from '@pushwoosh/kit-icons';
4
+ import { PushStoriesPage } from './components/PushStoriesPage';
5
+ import { PushStoriesProgress } from './components/PushStoriesProgress';
6
+ import { clampStoryIndex, normalizeStoryDuration } from './helpers';
7
+ import { Lang, useLang } from './polyglot';
8
+ import { EmptyState, StoriesCanvas, StoriesFrame, TapZone } from './styles';
9
+ /** Expanded iOS push story as `PushwooshStoriesViewController` draws it: 2:3 page, progress, scrim, button.
10
+ * Fills its container's width, so it fits a form column as well as the phone of a push preview. */
11
+ export function PushStoriesPreview(props) {
12
+ const {
13
+ pages,
14
+ activeIndex,
15
+ defaultActiveIndex = 0,
16
+ onActiveIndexChange,
17
+ autoplay = false,
18
+ interactive = true,
19
+ width
20
+ } = props;
21
+ const lang = useLang();
22
+ const [ownIndex, setOwnIndex] = useState(defaultActiveIndex);
23
+ const [cycle, setCycle] = useState(0);
24
+ if (pages.length === 0) {
25
+ return _jsx(StoriesFrame, {
26
+ "$width": width,
27
+ children: _jsx(StoriesCanvas, {
28
+ children: _jsxs(EmptyState, {
29
+ children: [_jsx(ImageIcon, {
30
+ size: "medium",
31
+ view: "lined"
32
+ }), _jsx(Lang, {
33
+ message: "DUMB.PUSH_STORIES_PREVIEW.EMPTY"
34
+ })]
35
+ })
36
+ })
37
+ });
38
+ }
39
+ const index = clampStoryIndex(activeIndex ?? ownIndex, pages.length);
40
+ const goNext = () => {
41
+ const next = (index + 1) % pages.length;
42
+ setCycle(value => value + 1);
43
+ if (activeIndex === undefined) {
44
+ setOwnIndex(next);
45
+ }
46
+ if (next !== index) {
47
+ onActiveIndexChange?.(next);
48
+ }
49
+ };
50
+ return _jsx(StoriesFrame, {
51
+ "$width": width,
52
+ children: _jsxs(StoriesCanvas, {
53
+ children: [_jsx(PushStoriesPage, {
54
+ page: pages[index]
55
+ }), _jsx(PushStoriesProgress, {
56
+ count: pages.length,
57
+ activeIndex: index,
58
+ playing: autoplay,
59
+ duration: normalizeStoryDuration(pages[index].duration),
60
+ cycle: cycle,
61
+ onComplete: goNext
62
+ }), interactive && _jsx(TapZone, {
63
+ "aria-label": lang({
64
+ message: 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE'
65
+ }),
66
+ type: "button",
67
+ onClick: goNext
68
+ })]
69
+ })
70
+ });
71
+ }
@@ -0,0 +1,234 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { fireEvent, render, screen } from '@testing-library/react';
3
+ import { hasStoryButton, normalizeStoryDuration } from '../helpers';
4
+ import { PushStoriesPreview } from '../PushStoriesPreview';
5
+ const pages = [{
6
+ image: 'https://example.com/1.jpg',
7
+ title: 'Welcome',
8
+ subtitle: 'Swipe to explore',
9
+ buttonTitle: 'Get started',
10
+ link: 'app://one'
11
+ }, {
12
+ image: 'https://example.com/2.jpg',
13
+ title: 'Stay in the loop',
14
+ duration: 4
15
+ }, {
16
+ image: 'https://example.com/3.jpg',
17
+ title: 'You are all set',
18
+ buttonTitle: 'Open app',
19
+ link: 'app://three'
20
+ }];
21
+ const next = () => {
22
+ fireEvent.click(screen.getByLabelText('Next page'));
23
+ };
24
+ const progressFill = index => screen.getByTestId('push-stories-progress').children[index].firstElementChild;
25
+ describe('normalizeStoryDuration', () => {
26
+ it('plays a missing, zero, negative or non-finite duration for 5 seconds', () => {
27
+ expect(normalizeStoryDuration(undefined)).toBe(5);
28
+ expect(normalizeStoryDuration(0)).toBe(5);
29
+ expect(normalizeStoryDuration(-3)).toBe(5);
30
+ expect(normalizeStoryDuration(Number.NaN)).toBe(5);
31
+ expect(normalizeStoryDuration(Number.POSITIVE_INFINITY)).toBe(5);
32
+ });
33
+ it('keeps a valid duration and caps it at 30 seconds', () => {
34
+ expect(normalizeStoryDuration(1.5)).toBe(1.5);
35
+ expect(normalizeStoryDuration(45)).toBe(30);
36
+ });
37
+ });
38
+ describe('hasStoryButton', () => {
39
+ it('needs both a title and a link with a scheme, as the SDK does', () => {
40
+ expect(hasStoryButton({
41
+ buttonTitle: 'Go',
42
+ link: 'app://x'
43
+ })).toBe(true);
44
+ expect(hasStoryButton({
45
+ buttonTitle: 'Go',
46
+ link: 'https://example.com'
47
+ })).toBe(true);
48
+ expect(hasStoryButton({
49
+ buttonTitle: 'Go',
50
+ link: 'example.com/promo'
51
+ })).toBe(false);
52
+ expect(hasStoryButton({
53
+ buttonTitle: 'Go'
54
+ })).toBe(false);
55
+ expect(hasStoryButton({
56
+ buttonTitle: ' ',
57
+ link: 'app://x'
58
+ })).toBe(false);
59
+ expect(hasStoryButton({
60
+ link: 'app://x'
61
+ })).toBe(false);
62
+ });
63
+ });
64
+ describe('PushStoriesPreview', () => {
65
+ it('draws the empty state without pages', () => {
66
+ render(_jsx(PushStoriesPreview, {
67
+ pages: []
68
+ }));
69
+ expect(screen.getByText('Add a page to see the story preview')).toBeInTheDocument();
70
+ expect(screen.queryByTestId('push-stories-progress')).not.toBeInTheDocument();
71
+ });
72
+ it('draws one progress segment per page and the first page text', () => {
73
+ render(_jsx(PushStoriesPreview, {
74
+ pages: pages
75
+ }));
76
+ expect(screen.getByTestId('push-stories-progress').children).toHaveLength(3);
77
+ expect(screen.getByText('Welcome')).toBeInTheDocument();
78
+ expect(screen.getByText('Swipe to explore')).toBeInTheDocument();
79
+ expect(screen.getByText('Get started')).toBeInTheDocument();
80
+ });
81
+ it('keeps one segment per page and one image on screen through clicks and page list changes', () => {
82
+ const {
83
+ container,
84
+ rerender
85
+ } = render(_jsx(PushStoriesPreview, {
86
+ pages: pages
87
+ }));
88
+ const expectShape = count => {
89
+ expect(screen.getByTestId('push-stories-progress').children).toHaveLength(count);
90
+ expect(container.querySelectorAll('img')).toHaveLength(1);
91
+ };
92
+ for (let click = 0; click < 7; click += 1) {
93
+ expectShape(3);
94
+ next();
95
+ }
96
+ rerender(_jsx(PushStoriesPreview, {
97
+ pages: [...pages, {
98
+ image: 'https://example.com/4.jpg',
99
+ title: 'Fourth'
100
+ }]
101
+ }));
102
+ expectShape(4);
103
+ rerender(_jsx(PushStoriesPreview, {
104
+ pages: pages.slice(0, 2)
105
+ }));
106
+ expectShape(2);
107
+ });
108
+ it('hides the button of a page without a link', () => {
109
+ render(_jsx(PushStoriesPreview, {
110
+ pages: [{
111
+ ...pages[0],
112
+ link: ''
113
+ }]
114
+ }));
115
+ expect(screen.queryByText('Get started')).not.toBeInTheDocument();
116
+ });
117
+ it('shows the next page on every click and goes round after the last one', () => {
118
+ render(_jsx(PushStoriesPreview, {
119
+ pages: pages
120
+ }));
121
+ next();
122
+ expect(screen.getByText('Stay in the loop')).toBeInTheDocument();
123
+ expect(progressFill(0)).not.toBeNull();
124
+ next();
125
+ expect(screen.getByText('You are all set')).toBeInTheDocument();
126
+ next();
127
+ expect(screen.getByText('Welcome')).toBeInTheDocument();
128
+ expect(progressFill(1)).toBeNull();
129
+ next();
130
+ expect(screen.getByText('Stay in the loop')).toBeInTheDocument();
131
+ });
132
+ it('has a single click target over the whole story', () => {
133
+ render(_jsx(PushStoriesPreview, {
134
+ pages: pages
135
+ }));
136
+ expect(screen.getAllByRole('button')).toHaveLength(1);
137
+ });
138
+ it('follows the activeIndex prop and reports clicks instead of moving itself', () => {
139
+ const onActiveIndexChange = jest.fn();
140
+ const {
141
+ rerender
142
+ } = render(_jsx(PushStoriesPreview, {
143
+ pages: pages,
144
+ activeIndex: 1,
145
+ onActiveIndexChange: onActiveIndexChange
146
+ }));
147
+ expect(screen.getByText('Stay in the loop')).toBeInTheDocument();
148
+ next();
149
+ expect(onActiveIndexChange).toHaveBeenCalledWith(2);
150
+ expect(screen.getByText('Stay in the loop')).toBeInTheDocument();
151
+ rerender(_jsx(PushStoriesPreview, {
152
+ pages: pages,
153
+ activeIndex: 2,
154
+ onActiveIndexChange: onActiveIndexChange
155
+ }));
156
+ expect(screen.getByText('You are all set')).toBeInTheDocument();
157
+ });
158
+ it('clamps an activeIndex past the last page', () => {
159
+ render(_jsx(PushStoriesPreview, {
160
+ pages: pages,
161
+ activeIndex: 9
162
+ }));
163
+ expect(screen.getByText('You are all set')).toBeInTheDocument();
164
+ });
165
+ it('advances when the autoplay segment finishes and goes round after the last page', () => {
166
+ render(_jsx(PushStoriesPreview, {
167
+ pages: pages.slice(0, 2),
168
+ autoplay: true
169
+ }));
170
+ fireEvent.animationEnd(progressFill(0));
171
+ expect(screen.getByText('Stay in the loop')).toBeInTheDocument();
172
+ fireEvent.animationEnd(progressFill(1));
173
+ expect(screen.getByText('Welcome')).toBeInTheDocument();
174
+ expect(progressFill(1)).toBeNull();
175
+ });
176
+ it('fills segments up to and including the current page without autoplay', () => {
177
+ const {
178
+ container
179
+ } = render(_jsx(PushStoriesPreview, {
180
+ pages: pages
181
+ }));
182
+ const filled = () => [...screen.getByTestId('push-stories-progress').children].filter(track => track.firstElementChild).length;
183
+ expect(filled()).toBe(1);
184
+ next();
185
+ expect(filled()).toBe(2);
186
+ next();
187
+ expect(filled()).toBe(3);
188
+ next();
189
+ expect(filled()).toBe(1);
190
+ expect(container.querySelectorAll('img')).toHaveLength(1);
191
+ });
192
+ it('starts the next segment from zero when a click interrupts autoplay', () => {
193
+ render(_jsx(PushStoriesPreview, {
194
+ pages: pages,
195
+ autoplay: true
196
+ }));
197
+ const interrupted = progressFill(0);
198
+ next();
199
+ expect(screen.getByText('Stay in the loop')).toBeInTheDocument();
200
+ expect(progressFill(0)).not.toBe(interrupted);
201
+ expect(progressFill(1)).not.toBeNull();
202
+ });
203
+ it('replays a single page instead of moving anywhere', () => {
204
+ render(_jsx(PushStoriesPreview, {
205
+ pages: pages.slice(0, 1),
206
+ autoplay: true
207
+ }));
208
+ const run = progressFill(0);
209
+ next();
210
+ expect(screen.getByTestId('push-stories-progress').children).toHaveLength(1);
211
+ expect(screen.getByText('Welcome')).toBeInTheDocument();
212
+ expect(progressFill(0)).not.toBe(run);
213
+ });
214
+ it('draws a placeholder for a missing or broken image', () => {
215
+ const {
216
+ container
217
+ } = render(_jsx(PushStoriesPreview, {
218
+ pages: [{
219
+ title: 'No image'
220
+ }, pages[1]]
221
+ }));
222
+ expect(screen.getByTestId('push-stories-image-placeholder')).toBeInTheDocument();
223
+ next();
224
+ fireEvent.error(container.querySelector('img'));
225
+ expect(screen.getByTestId('push-stories-image-placeholder')).toBeInTheDocument();
226
+ });
227
+ it('drops the click target when not interactive', () => {
228
+ render(_jsx(PushStoriesPreview, {
229
+ pages: pages,
230
+ interactive: false
231
+ }));
232
+ expect(screen.queryByLabelText('Next page')).not.toBeInTheDocument();
233
+ });
234
+ });
@@ -0,0 +1,4 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { PushStoriesPageProps } from './types';
3
+ /** One story page: the image, the scrim and the title, subtitle and button laid over it. */
4
+ export declare function PushStoriesPage(props: PushStoriesPageProps): ReactElement;
@@ -0,0 +1,38 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { ImageIcon } from '@pushwoosh/kit-icons';
4
+ import { BottomArea, ImagePlaceholder, PageButton, PageButtonLabel, PageImage, PageSubtitle, PageTitle } from './styles';
5
+ import { hasStoryButton } from '../../helpers';
6
+ /** One story page: the image, the scrim and the title, subtitle and button laid over it. */
7
+ export function PushStoriesPage(props) {
8
+ const {
9
+ page
10
+ } = props;
11
+ const [failedImage, setFailedImage] = useState();
12
+ const image = page.image?.trim();
13
+ const title = page.title?.trim();
14
+ const subtitle = page.subtitle?.trim();
15
+ return _jsxs(_Fragment, {
16
+ children: [image && failedImage !== image ? _jsx(PageImage, {
17
+ alt: "",
18
+ src: image,
19
+ onError: () => setFailedImage(image)
20
+ }) : _jsx(ImagePlaceholder, {
21
+ "data-testid": "push-stories-image-placeholder",
22
+ children: _jsx(ImageIcon, {
23
+ size: "medium",
24
+ view: "lined"
25
+ })
26
+ }), _jsxs(BottomArea, {
27
+ children: [title && _jsx(PageTitle, {
28
+ children: title
29
+ }), subtitle && _jsx(PageSubtitle, {
30
+ children: subtitle
31
+ }), hasStoryButton(page) && _jsx(PageButton, {
32
+ children: _jsx(PageButtonLabel, {
33
+ children: page.buttonTitle?.trim()
34
+ })
35
+ })]
36
+ })]
37
+ });
38
+ }
@@ -0,0 +1 @@
1
+ export { PushStoriesPage } from './PushStoriesPage';
@@ -0,0 +1 @@
1
+ export { PushStoriesPage } from './PushStoriesPage';
@@ -0,0 +1,7 @@
1
+ export declare const PageImage: import("styled-components").StyledComponent<"img", any, {}, never>;
2
+ export declare const ImagePlaceholder: import("styled-components").StyledComponent<"div", any, {}, never>;
3
+ export declare const BottomArea: import("styled-components").StyledComponent<"div", any, {}, never>;
4
+ export declare const PageTitle: import("styled-components").StyledComponent<"div", any, {}, never>;
5
+ export declare const PageSubtitle: import("styled-components").StyledComponent<"div", any, {}, never>;
6
+ export declare const PageButton: import("styled-components").StyledComponent<"div", any, {}, never>;
7
+ export declare const PageButtonLabel: import("styled-components").StyledComponent<"span", any, {}, never>;
@@ -0,0 +1,32 @@
1
+ import styled from 'styled-components';
2
+ import { Color } from '@pushwoosh/kit-constants';
3
+ import { PLACEHOLDER_BACKGROUND } from '../../constants';
4
+ import { placeholderOutline, scaled } from '../../helpers';
5
+ export const PageImage = styled.img.withConfig({
6
+ displayName: "PageImage",
7
+ componentId: "sc-1q2ujdq-0"
8
+ })(["position:absolute;inset:0;width:100%;height:100%;object-fit:cover;"]);
9
+ export const ImagePlaceholder = styled.div.withConfig({
10
+ displayName: "ImagePlaceholder",
11
+ componentId: "sc-1q2ujdq-1"
12
+ })(["position:absolute;inset:0;display:flex;align-items:center;justify-content:center;", " background:", ";color:", ";> div{transform:scale(1.5);}"], placeholderOutline, PLACEHOLDER_BACKGROUND, Color.PHANTOM);
13
+ export const BottomArea = styled.div.withConfig({
14
+ displayName: "BottomArea",
15
+ componentId: "sc-1q2ujdq-2"
16
+ })(["position:absolute;left:0;right:0;bottom:0;z-index:2;display:flex;flex-direction:column;padding:", " ", " ", ";background:linear-gradient(to bottom,rgba(0,0,0,0) 0%,rgba(0,0,0,0.75) 100%);pointer-events:none;", " ~ &:empty{background:none;}"], scaled(44), scaled(24), scaled(24), ImagePlaceholder);
17
+ export const PageTitle = styled.div.withConfig({
18
+ displayName: "PageTitle",
19
+ componentId: "sc-1q2ujdq-3"
20
+ })(["display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;color:#FFF;font-size:", ";font-weight:700;line-height:1.2;overflow-wrap:anywhere;"], scaled(21));
21
+ export const PageSubtitle = styled.div.withConfig({
22
+ displayName: "PageSubtitle",
23
+ componentId: "sc-1q2ujdq-4"
24
+ })(["display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;color:rgba(255,255,255,0.85);font-size:", ";font-weight:400;line-height:1.3;overflow-wrap:anywhere;* + &{margin-top:", ";}"], scaled(14), scaled(6));
25
+ export const PageButton = styled.div.withConfig({
26
+ displayName: "PageButton",
27
+ componentId: "sc-1q2ujdq-5"
28
+ })(["display:flex;align-items:center;justify-content:center;height:", ";padding:0 ", ";border-radius:", ";background:#FFF;box-shadow:0 ", " ", " rgba(0,0,0,0.28);color:#0F0F0F;font-size:", ";font-weight:600;* + &{margin-top:", ";}"], scaled(52), scaled(28), scaled(26), scaled(6), scaled(28), scaled(17), scaled(16));
29
+ export const PageButtonLabel = styled.span.withConfig({
30
+ displayName: "PageButtonLabel",
31
+ componentId: "sc-1q2ujdq-6"
32
+ })(["overflow:hidden;white-space:nowrap;text-overflow:ellipsis;"]);
@@ -0,0 +1,6 @@
1
+ import type { PushStoriesPreviewPage } from '../../types';
2
+ /** Props of {@link PushStoriesPage}. */
3
+ export interface PushStoriesPageProps {
4
+ /** Page to draw. */
5
+ page: PushStoriesPreviewPage;
6
+ }
@@ -0,0 +1,4 @@
1
+ import { type ReactElement } from 'react';
2
+ import type { PushStoriesProgressProps } from './types';
3
+ /** Segmented bar over the top of the story, one segment per page. */
4
+ export declare function PushStoriesProgress(props: PushStoriesProgressProps): ReactElement;
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ProgressFill, ProgressRow, ProgressTrack } from './styles';
3
+ /** Segmented bar over the top of the story, one segment per page. */
4
+ export function PushStoriesProgress(props) {
5
+ const {
6
+ count,
7
+ activeIndex,
8
+ playing,
9
+ duration,
10
+ cycle,
11
+ onComplete
12
+ } = props;
13
+ return _jsx(ProgressRow, {
14
+ "data-testid": "push-stories-progress",
15
+ children: Array.from({
16
+ length: count
17
+ }, (_, index) => _jsxs(ProgressTrack, {
18
+ children: [(index < activeIndex || index === activeIndex && !playing) && _jsx(ProgressFill, {}), index === activeIndex && playing && _jsx(ProgressFill, {
19
+ "$duration": duration,
20
+ onAnimationEnd: onComplete
21
+ }, cycle)]
22
+ }, index))
23
+ });
24
+ }
@@ -0,0 +1 @@
1
+ export { PushStoriesProgress } from './PushStoriesProgress';
@@ -0,0 +1 @@
1
+ export { PushStoriesProgress } from './PushStoriesProgress';
@@ -0,0 +1,5 @@
1
+ export declare const ProgressRow: import("styled-components").StyledComponent<"div", any, {}, never>;
2
+ export declare const ProgressTrack: import("styled-components").StyledComponent<"div", any, {}, never>;
3
+ export declare const ProgressFill: import("styled-components").StyledComponent<"div", any, {
4
+ $duration?: number;
5
+ }, never>;
@@ -0,0 +1,19 @@
1
+ import styled, { css, keyframes } from 'styled-components';
2
+ import { Color } from '@pushwoosh/kit-constants';
3
+ import { scaled } from '../../helpers';
4
+ import { ImagePlaceholder } from '../PushStoriesPage/styles';
5
+ const grow = keyframes(["from{width:0;}to{width:100%;}"]);
6
+ export const ProgressRow = styled.div.withConfig({
7
+ displayName: "ProgressRow",
8
+ componentId: "sc-1p5nb36-0"
9
+ })(["position:absolute;top:", ";left:", ";right:", ";z-index:2;display:flex;gap:", ";pointer-events:none;"], scaled(23), scaled(12), scaled(12), scaled(4));
10
+ export const ProgressTrack = styled.div.withConfig({
11
+ displayName: "ProgressTrack",
12
+ componentId: "sc-1p5nb36-1"
13
+ })(["flex:1;height:max(2px,", ");border-radius:", ";background:rgba(255,255,255,0.3);overflow:hidden;", " ~ ", " &{background:", ";}"], scaled(3), scaled(1.5), ImagePlaceholder, ProgressRow, Color.FORM);
14
+ export const ProgressFill = styled.div.withConfig({
15
+ displayName: "ProgressFill",
16
+ componentId: "sc-1p5nb36-2"
17
+ })(["width:100%;height:100%;background:#FFF;", " ~ ", " &{background:", ";}", ""], ImagePlaceholder, ProgressRow, Color.LOCKED, ({
18
+ $duration
19
+ }) => $duration !== undefined && css(["animation:", " ", "s linear both;"], grow, $duration));
@@ -0,0 +1,15 @@
1
+ /** Props of {@link PushStoriesProgress}. */
2
+ export interface PushStoriesProgressProps {
3
+ /** Number of pages, one segment each. */
4
+ count: number;
5
+ /** Segment of the page on screen. */
6
+ activeIndex: number;
7
+ /** Fill the active segment over `duration`; otherwise it is drawn full, so page N shows N full segments. */
8
+ playing: boolean;
9
+ /** Seconds the active segment takes to fill. */
10
+ duration: number;
11
+ /** Changes whenever playback of the active page restarts, even on the same index. */
12
+ cycle: number;
13
+ /** Called when the active segment finishes filling. */
14
+ onComplete: () => void;
15
+ }
@@ -0,0 +1,8 @@
1
+ /** Height ÷ width of the stories area, the iOS SDK's default `storyAspectRatio`. */
2
+ export declare const PUSH_STORIES_ASPECT_RATIO = 1.5;
3
+ /** Seconds a page stays on screen when its duration is missing or invalid. */
4
+ export declare const PUSH_STORIES_DEFAULT_DURATION = 5;
5
+ /** Longest page duration in seconds the iOS SDK honours. */
6
+ export declare const PUSH_STORIES_MAX_DURATION = 30;
7
+ export declare const REFERENCE_WIDTH = 375;
8
+ export declare const PLACEHOLDER_BACKGROUND = "linear-gradient(160deg, var(--pw-color-divider, #EBF0F5) 0%, var(--pw-color-frozen, #F7F9FA) 100%)";
@@ -0,0 +1,9 @@
1
+ import { Color } from '@pushwoosh/kit-constants';
2
+ /** Height ÷ width of the stories area, the iOS SDK's default `storyAspectRatio`. */
3
+ export const PUSH_STORIES_ASPECT_RATIO = 1.5;
4
+ /** Seconds a page stays on screen when its duration is missing or invalid. */
5
+ export const PUSH_STORIES_DEFAULT_DURATION = 5;
6
+ /** Longest page duration in seconds the iOS SDK honours. */
7
+ export const PUSH_STORIES_MAX_DURATION = 30;
8
+ export const REFERENCE_WIDTH = 375;
9
+ export const PLACEHOLDER_BACKGROUND = `linear-gradient(160deg, ${Color.DIVIDER} 0%, ${Color.FROZEN} 100%)`;
@@ -0,0 +1,9 @@
1
+ import type { PushStoriesPreviewPage } from './types';
2
+ /** Page duration in seconds as the iOS SDK plays it. */
3
+ export declare function normalizeStoryDuration(duration?: number): number;
4
+ /** Whether the iOS SDK draws the page's button: it needs a title and a link with a scheme. */
5
+ export declare function hasStoryButton(page: PushStoriesPreviewPage): boolean;
6
+ export declare function clampStoryIndex(index: number, length: number): number;
7
+ export declare function scaled(points: number): string;
8
+ /** Hairline outline that keeps a light placeholder visible on a white card. */
9
+ export declare const placeholderOutline: import("styled-components").FlattenSimpleInterpolation;
@@ -0,0 +1,23 @@
1
+ import { css } from 'styled-components';
2
+ import { Color } from '@pushwoosh/kit-constants';
3
+ import { PUSH_STORIES_DEFAULT_DURATION, PUSH_STORIES_MAX_DURATION, REFERENCE_WIDTH } from './constants';
4
+ const URL_SCHEME = /^[a-z][a-z\d+.-]*:/i;
5
+ /** Page duration in seconds as the iOS SDK plays it. */
6
+ export function normalizeStoryDuration(duration) {
7
+ if (typeof duration !== 'number' || !Number.isFinite(duration) || duration <= 0) {
8
+ return PUSH_STORIES_DEFAULT_DURATION;
9
+ }
10
+ return Math.min(duration, PUSH_STORIES_MAX_DURATION);
11
+ }
12
+ /** Whether the iOS SDK draws the page's button: it needs a title and a link with a scheme. */
13
+ export function hasStoryButton(page) {
14
+ return !!page.buttonTitle?.trim() && URL_SCHEME.test(page.link?.trim() || '');
15
+ }
16
+ export function clampStoryIndex(index, length) {
17
+ return Math.max(0, Math.min(index, length - 1));
18
+ }
19
+ export function scaled(points) {
20
+ return `calc(${points} * 100cqw / ${REFERENCE_WIDTH})`;
21
+ }
22
+ /** Hairline outline that keeps a light placeholder visible on a white card. */
23
+ export const placeholderOutline = css(["border-radius:inherit;box-shadow:inset 0 0 0 1px ", ";"], Color.DIVIDER);
@@ -0,0 +1,4 @@
1
+ export { PushStoriesPreview } from './PushStoriesPreview';
2
+ export { hasStoryButton, normalizeStoryDuration } from './helpers';
3
+ export { PUSH_STORIES_ASPECT_RATIO, PUSH_STORIES_DEFAULT_DURATION, PUSH_STORIES_MAX_DURATION, } from './constants';
4
+ export type { PushStoriesPreviewPage, PushStoriesPreviewProps } from './types';
@@ -0,0 +1,3 @@
1
+ export { PushStoriesPreview } from './PushStoriesPreview';
2
+ export { hasStoryButton, normalizeStoryDuration } from './helpers';
3
+ export { PUSH_STORIES_ASPECT_RATIO, PUSH_STORIES_DEFAULT_DURATION, PUSH_STORIES_MAX_DURATION } from './constants';
@@ -0,0 +1,2 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ export declare const Lang: import("react").FC<import("../../polyglot").LangProps<PushStoriesPreviewMessages>>, useLang: () => (props: import("../../polyglot").LangProps<PushStoriesPreviewMessages>) => string;
@@ -0,0 +1,34 @@
1
+ import { messages as ar } from './messages_ar';
2
+ import { messages as bn } from './messages_bn';
3
+ import { messages as de } from './messages_de';
4
+ import { messages as en } from './messages_en';
5
+ import { messages as es } from './messages_es';
6
+ import { messages as fr } from './messages_fr';
7
+ import { messages as he } from './messages_he';
8
+ import { messages as hi } from './messages_hi';
9
+ import { messages as ja } from './messages_ja';
10
+ import { messages as ko } from './messages_ko';
11
+ import { messages as pt } from './messages_pt';
12
+ import { messages as ru } from './messages_ru';
13
+ import { messages as th } from './messages_th';
14
+ import { messages as zh } from './messages_zh';
15
+ import { createLang } from '../../polyglot';
16
+ export const {
17
+ Lang,
18
+ useLang
19
+ } = createLang({
20
+ ar,
21
+ bn,
22
+ de,
23
+ en,
24
+ es,
25
+ fr,
26
+ he,
27
+ hi,
28
+ ja,
29
+ ko,
30
+ pt,
31
+ ru,
32
+ th,
33
+ zh
34
+ });
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'أضف صفحة لعرض معاينة القصة',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'الصفحة التالية'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'স্টোরির প্রিভিউ দেখতে একটি পেজ যোগ করুন',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'পরের পেজ'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'Fügen Sie eine Seite hinzu, um die Story-Vorschau zu sehen',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'Nächste Seite'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'Add a page to see the story preview',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'Next page'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'Añade una página para ver la vista previa de la historia',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'Página siguiente'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'Ajoutez une page pour voir l’aperçu de la story',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'Page suivante'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'הוסיפו עמוד כדי לראות תצוגה מקדימה של הסטורי',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'העמוד הבא'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'स्टोरी का प्रीव्यू देखने के लिए एक पेज जोड़ें',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'अगला पेज'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'ページを追加するとストーリーのプレビューが表示されます',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': '次のページ'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': '페이지를 추가하면 스토리 미리보기가 표시됩니다',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': '다음 페이지'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'Adicione uma página para ver a pré-visualização da história',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'Próxima página'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'Добавьте страницу, чтобы увидеть превью сториз',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'Следующая страница'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': 'เพิ่มหน้าเพื่อดูตัวอย่างสตอรี่',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': 'หน้าถัดไป'
4
+ };
@@ -0,0 +1,3 @@
1
+ import { type PushStoriesPreviewMessages } from './types';
2
+ import type { LangMessages } from '../../polyglot';
3
+ export declare const messages: LangMessages<PushStoriesPreviewMessages>;
@@ -0,0 +1,4 @@
1
+ export const messages = {
2
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': '添加页面以查看快拍预览',
3
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': '下一页'
4
+ };
@@ -0,0 +1,5 @@
1
+ import type { NoParams } from '../../polyglot';
2
+ export type PushStoriesPreviewMessages = {
3
+ 'DUMB.PUSH_STORIES_PREVIEW.EMPTY': NoParams;
4
+ 'DUMB.PUSH_STORIES_PREVIEW.NEXT_PAGE': NoParams;
5
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ export declare const StoriesFrame: import("styled-components").StyledComponent<"div", any, {
2
+ $width?: string | number;
3
+ }, never>;
4
+ export declare const StoriesCanvas: import("styled-components").StyledComponent<"div", any, {}, never>;
5
+ export declare const TapZone: import("styled-components").StyledComponent<"button", any, {}, never>;
6
+ export declare const EmptyState: import("styled-components").StyledComponent<"div", any, {}, never>;
@@ -0,0 +1,23 @@
1
+ import styled from 'styled-components';
2
+ import { Color, FontFamily } from '@pushwoosh/kit-constants';
3
+ import { toCssValue } from '@pushwoosh/kit-helpers';
4
+ import { PLACEHOLDER_BACKGROUND, PUSH_STORIES_ASPECT_RATIO } from './constants';
5
+ import { placeholderOutline, scaled } from './helpers';
6
+ export const StoriesFrame = styled.div.withConfig({
7
+ displayName: "StoriesFrame",
8
+ componentId: "sc-udrlhm-0"
9
+ })(["position:relative;container-type:inline-size;width:", ";aspect-ratio:1 / ", ";font-family:", ";user-select:none;"], ({
10
+ $width
11
+ }) => $width ? toCssValue($width) : '100%', PUSH_STORIES_ASPECT_RATIO, FontFamily.MAIN);
12
+ export const StoriesCanvas = styled.div.withConfig({
13
+ displayName: "StoriesCanvas",
14
+ componentId: "sc-udrlhm-1"
15
+ })(["position:absolute;inset:0;overflow:hidden;border-radius:", ";background:#000;isolation:isolate;"], scaled(24));
16
+ export const TapZone = styled.button.withConfig({
17
+ displayName: "TapZone",
18
+ componentId: "sc-udrlhm-2"
19
+ })(["position:absolute;inset:0;z-index:1;padding:0;border:none;background:transparent;cursor:pointer;&:focus-visible{outline:none;box-shadow:inset 0 0 0 2px rgba(255,255,255,0.6);}"]);
20
+ export const EmptyState = styled.div.withConfig({
21
+ displayName: "EmptyState",
22
+ componentId: "sc-udrlhm-3"
23
+ })(["position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:", ";padding:0 ", ";", " background:", ";color:", ";font-size:", ";line-height:1.35;text-align:center;> div{margin:", " 0;transform:scale(1.5);}"], scaled(12), scaled(32), placeholderOutline, PLACEHOLDER_BACKGROUND, Color.LOCKED, scaled(15), scaled(6));
@@ -0,0 +1,32 @@
1
+ /** One full-screen page of an iOS push story, in the shape the push preset form edits. */
2
+ export interface PushStoriesPreviewPage {
3
+ /** Full-bleed image URL; an empty or broken one is drawn as a placeholder. */
4
+ image?: string;
5
+ /** Seconds before the page auto-advances; clamped to 30, non-positive falls back to 5. */
6
+ duration?: number;
7
+ /** Title over the bottom of the page, two lines at most. */
8
+ title?: string;
9
+ /** Subtitle under the title, two lines at most. */
10
+ subtitle?: string;
11
+ /** Call-to-action text; the button shows only together with a `link` that has a scheme. */
12
+ buttonTitle?: string;
13
+ /** Deep link the button opens on the device. */
14
+ link?: string;
15
+ }
16
+ /** Props of {@link PushStoriesPreview}. */
17
+ export interface PushStoriesPreviewProps {
18
+ /** Pages in playback order; an empty list draws the empty state. */
19
+ pages: PushStoriesPreviewPage[];
20
+ /** Page to show; pass it to control the preview, e.g. to follow the page being edited. */
21
+ activeIndex?: number;
22
+ /** Page shown first when `activeIndex` is not controlled (default 0). */
23
+ defaultActiveIndex?: number;
24
+ /** Called when a click or autoplay moves to another page. */
25
+ onActiveIndexChange?: (index: number) => void;
26
+ /** Advance pages by their `duration`, animating the progress bar, round and round (default false). */
27
+ autoplay?: boolean;
28
+ /** A click anywhere on the story shows the next page, after the last one the first (default true). */
29
+ interactive?: boolean;
30
+ /** CSS width; the height follows the device's 2:3 stories ratio (default 100%). */
31
+ width?: string | number;
32
+ }
@@ -0,0 +1 @@
1
+ export {};
package/index.d.ts CHANGED
@@ -34,6 +34,7 @@ export { Link, type LinkPropsWithoutAs, type LinkPropsWithAsTag, type LinkPropsW
34
34
  export { StatisticCard } from './StatisticCard';
35
35
  export { EmailTemplatePreview, NoEmailTemplatePreview, replaceLocalization, replacePlaceholders, matchClickMapItems, decorateClickMap, CLICK_MAP_BADGE_ATTRIBUTE, type EmailClickMapItem, type EmailClickMapPlacement, type EmailClickMapProps, type PreviewIframeProps, } from './EmailTemplatePreview';
36
36
  export { PushPreview } from './PushPreview';
37
+ export { PushStoriesPreview, hasStoryButton, normalizeStoryDuration, PUSH_STORIES_ASPECT_RATIO, PUSH_STORIES_DEFAULT_DURATION, PUSH_STORIES_MAX_DURATION, type PushStoriesPreviewPage, type PushStoriesPreviewProps, } from './PushStoriesPreview';
37
38
  export { InboxPreview, resolveInboxLayout, MAX_CAROUSEL_SLIDES, type InboxLayoutType, type InboxPreviewPlatform, type InboxPreviewContent, type InboxPreviewProps, type InboxPreviewSlide, } from './InboxPreview';
38
39
  export { WhatsAppPreview } from './WhatsAppPreview';
39
40
  export type { WhatsAppAttachment, WhatsAppAttachmentKind, WhatsAppMessage, WhatsAppPreviewProps, } from './WhatsAppPreview';
package/index.js CHANGED
@@ -34,6 +34,7 @@ export { Link } from './Link';
34
34
  export { StatisticCard } from './StatisticCard';
35
35
  export { EmailTemplatePreview, NoEmailTemplatePreview, replaceLocalization, replacePlaceholders, matchClickMapItems, decorateClickMap, CLICK_MAP_BADGE_ATTRIBUTE } from './EmailTemplatePreview';
36
36
  export { PushPreview } from './PushPreview';
37
+ export { PushStoriesPreview, hasStoryButton, normalizeStoryDuration, PUSH_STORIES_ASPECT_RATIO, PUSH_STORIES_DEFAULT_DURATION, PUSH_STORIES_MAX_DURATION } from './PushStoriesPreview';
37
38
  export { InboxPreview, resolveInboxLayout, MAX_CAROUSEL_SLIDES } from './InboxPreview';
38
39
  export { WhatsAppPreview } from './WhatsAppPreview';
39
40
  export { Toast, ToastProvider, useToast } from './Toast';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushwoosh/dumb-components",
3
- "version": "2.7.2",
3
+ "version": "2.8.1",
4
4
  "description": "React components to build Pushwoosh products",
5
5
  "main": "index.js",
6
6
  "module": "index.js",