@pushwoosh/dumb-components 1.1.211 → 1.1.213
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/RadioCard/RadioCard.js +19 -4
- package/RadioCard/styled.d.ts +1 -0
- package/RadioCard/styled.js +11 -3
- package/RadioCard/types.d.ts +4 -0
- package/WhatsAppPreview/WhatsAppPreview.js +5 -3
- package/WhatsAppPreview/__tests__/attachment.test.d.ts +1 -0
- package/WhatsAppPreview/__tests__/attachment.test.js +97 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/WhatsAppAttachment.d.ts +4 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/WhatsAppAttachment.js +84 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/index.d.ts +1 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/index.js +1 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/styles.d.ts +132 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/styles.js +77 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/types.d.ts +11 -0
- package/WhatsAppPreview/components/WhatsAppAttachment/types.js +1 -0
- package/WhatsAppPreview/components/WhatsAppMessageBubble/WhatsAppMessageBubble.d.ts +1 -1
- package/WhatsAppPreview/components/WhatsAppMessageBubble/WhatsAppMessageBubble.js +18 -6
- package/WhatsAppPreview/components/WhatsAppMessageBubble/styles.d.ts +2 -2
- package/WhatsAppPreview/components/WhatsAppMessageBubble/styles.js +14 -5
- package/WhatsAppPreview/constants.d.ts +14 -0
- package/WhatsAppPreview/constants.js +17 -1
- package/WhatsAppPreview/helpers.d.ts +13 -0
- package/WhatsAppPreview/helpers.js +54 -0
- package/WhatsAppPreview/icons/WhatsAppDocumentIcon.svg +1 -0
- package/WhatsAppPreview/icons/WhatsAppPlayIcon.svg +1 -0
- package/WhatsAppPreview/icons/WhatsAppVideoIcon.svg +1 -0
- package/WhatsAppPreview/icons/index.d.ts +3 -0
- package/WhatsAppPreview/icons/index.js +3 -0
- package/WhatsAppPreview/index.d.ts +1 -1
- package/WhatsAppPreview/types.d.ts +19 -0
- package/index.d.ts +1 -1
- package/package.json +1 -1
package/RadioCard/RadioCard.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Horizontal, Vertical } from '@pushwoosh/kit-helpers';
|
|
3
3
|
import { H4, Paragraph } from '@pushwoosh/kit-typography';
|
|
4
4
|
import { Radio } from '../Radio';
|
|
5
|
+
import { TooltipTrigger } from '../Tooltip';
|
|
5
6
|
import { RootBox } from './styled';
|
|
6
7
|
/**
|
|
7
8
|
* Selectable card with a title, optional description and a radio indicator — a richer
|
|
@@ -14,23 +15,37 @@ export function RadioCard(props) {
|
|
|
14
15
|
const {
|
|
15
16
|
title,
|
|
16
17
|
description,
|
|
18
|
+
titleTooltip,
|
|
17
19
|
width = '262px',
|
|
18
20
|
isActive,
|
|
21
|
+
isDisabled,
|
|
19
22
|
onClick,
|
|
20
23
|
children
|
|
21
24
|
} = props;
|
|
22
25
|
return _jsxs(RootBox, {
|
|
23
26
|
"$width": width,
|
|
24
27
|
isActive: isActive,
|
|
25
|
-
|
|
28
|
+
isDisabled: isDisabled,
|
|
29
|
+
onClick: isDisabled ? undefined : onClick,
|
|
26
30
|
children: [_jsxs(Vertical, {
|
|
31
|
+
"$gap": 0,
|
|
27
32
|
children: [_jsxs(Horizontal, {
|
|
28
33
|
"$alignItems": "center",
|
|
29
34
|
"$justifyContent": "space-between",
|
|
30
|
-
children: [
|
|
31
|
-
|
|
35
|
+
children: [_jsxs(Horizontal, {
|
|
36
|
+
"$alignItems": "center",
|
|
37
|
+
"$gap": 4,
|
|
38
|
+
children: [_jsx(H4, {
|
|
39
|
+
children: title
|
|
40
|
+
}), titleTooltip && _jsx(TooltipTrigger, {
|
|
41
|
+
content: titleTooltip,
|
|
42
|
+
position: "top-middle",
|
|
43
|
+
view: "question"
|
|
44
|
+
})]
|
|
32
45
|
}), _jsx(Radio, {
|
|
33
|
-
isChecked: isActive
|
|
46
|
+
isChecked: isActive,
|
|
47
|
+
isDisabled: isDisabled,
|
|
48
|
+
size: "compact"
|
|
34
49
|
})]
|
|
35
50
|
}), description && _jsx(Paragraph, {
|
|
36
51
|
children: description
|
package/RadioCard/styled.d.ts
CHANGED
|
@@ -18,5 +18,6 @@ export declare const RootBox: import("styled-components").StyledComponent<"div",
|
|
|
18
18
|
$gap: number;
|
|
19
19
|
} & {
|
|
20
20
|
isActive?: boolean;
|
|
21
|
+
isDisabled?: boolean;
|
|
21
22
|
$width: string | number;
|
|
22
23
|
}, "$gridAutoFlow" | "$bgColor" | "$borderRadius" | "$padding" | "$alignItems" | "$gap">;
|
package/RadioCard/styled.js
CHANGED
|
@@ -6,12 +6,20 @@ export const RootBox = styled(Vertical).attrs({
|
|
|
6
6
|
$alignItems: 'start',
|
|
7
7
|
$bgColor: Color.CLEAR,
|
|
8
8
|
$borderRadius: ShapeRadius.SECTION,
|
|
9
|
-
$gap:
|
|
9
|
+
$gap: 0
|
|
10
10
|
}).withConfig({
|
|
11
11
|
displayName: "RootBox",
|
|
12
12
|
componentId: "sc-tkztr1-0"
|
|
13
|
-
})(["width:", ";height:100%;box-sizing:border-box;outline:", ";cursor:pointer;&:hover{background:", ";}"], ({
|
|
13
|
+
})(["width:", ";height:100%;box-sizing:border-box;outline:", ";cursor:", ";opacity:", ";pointer-events:", ";&:hover{background:", ";}"], ({
|
|
14
14
|
$width
|
|
15
15
|
}) => toCssValue($width), ({
|
|
16
16
|
isActive
|
|
17
|
-
}) => isActive ? `2px solid ${Color.PRIMARY}` : `1px solid ${Color.DIVIDER}`,
|
|
17
|
+
}) => isActive ? `2px solid ${Color.PRIMARY}` : `1px solid ${Color.DIVIDER}`, ({
|
|
18
|
+
isDisabled
|
|
19
|
+
}) => isDisabled ? 'default' : 'pointer', ({
|
|
20
|
+
isDisabled
|
|
21
|
+
}) => isDisabled ? 0.5 : 1, ({
|
|
22
|
+
isDisabled
|
|
23
|
+
}) => isDisabled ? 'none' : 'auto', ({
|
|
24
|
+
isDisabled
|
|
25
|
+
}) => isDisabled ? Color.CLEAR : Color.ROW_HOVER);
|
package/RadioCard/types.d.ts
CHANGED
|
@@ -5,10 +5,14 @@ export interface RadioCardProps {
|
|
|
5
5
|
title: string;
|
|
6
6
|
/** Supporting text under the title. */
|
|
7
7
|
description?: string;
|
|
8
|
+
/** Help tooltip shown via a question-mark icon next to the title. */
|
|
9
|
+
titleTooltip?: ReactNode;
|
|
8
10
|
/** CSS width of the card (default `"262px"`). */
|
|
9
11
|
width?: string | number;
|
|
10
12
|
/** Whether this card is the selected one (drives the radio + active styling). */
|
|
11
13
|
isActive?: boolean;
|
|
14
|
+
/** Dim the card and ignore clicks. */
|
|
15
|
+
isDisabled?: boolean;
|
|
12
16
|
/** Extra content rendered below the description. */
|
|
13
17
|
children?: ReactNode;
|
|
14
18
|
/** Fires when the card is clicked. */
|
|
@@ -7,6 +7,7 @@ import { WhatsAppDateChip } from './components/WhatsAppDateChip';
|
|
|
7
7
|
import { WhatsAppMessageBubble } from './components/WhatsAppMessageBubble';
|
|
8
8
|
import { WhatsAppStatusBar } from './components/WhatsAppStatusBar';
|
|
9
9
|
import { WHATSAPP_COLOR, WHATSAPP_PREVIEW_HEIGHT, WHATSAPP_PREVIEW_WIDTH, WHATSAPP_STATUS_BAR_TIME } from './constants';
|
|
10
|
+
import { isMessageDrawable } from './helpers';
|
|
10
11
|
import { WhatsAppWallpaperPattern } from './icons';
|
|
11
12
|
import { Lang } from './polyglot';
|
|
12
13
|
import { DeviceBox, WallpaperImage, WallpaperLayer } from './styles';
|
|
@@ -26,6 +27,7 @@ export function WhatsAppPreview(props) {
|
|
|
26
27
|
height = WHATSAPP_PREVIEW_HEIGHT,
|
|
27
28
|
isLoading = false
|
|
28
29
|
} = props;
|
|
30
|
+
const drawnMessages = messages.filter(isMessageDrawable);
|
|
29
31
|
const separator = dateLabel === undefined ? _jsx(Lang, {
|
|
30
32
|
message: "DUMB.WHATSAPP_PREVIEW.TODAY"
|
|
31
33
|
}) : dateLabel;
|
|
@@ -63,7 +65,7 @@ export function WhatsAppPreview(props) {
|
|
|
63
65
|
color: Color.LOCKED,
|
|
64
66
|
size: "medium"
|
|
65
67
|
})
|
|
66
|
-
}), !isLoading &&
|
|
68
|
+
}), !isLoading && drawnMessages.length === 0 && _jsx(Horizontal, {
|
|
67
69
|
"$alignItems": "center",
|
|
68
70
|
"$inset": 0,
|
|
69
71
|
"$justifyContent": "center",
|
|
@@ -76,7 +78,7 @@ export function WhatsAppPreview(props) {
|
|
|
76
78
|
message: "DUMB.WHATSAPP_PREVIEW.EMPTY"
|
|
77
79
|
})
|
|
78
80
|
})
|
|
79
|
-
}), !isLoading &&
|
|
81
|
+
}), !isLoading && drawnMessages.length > 0 && _jsx(Vertical, {
|
|
80
82
|
"$inset": 0,
|
|
81
83
|
"$overflowY": "auto",
|
|
82
84
|
"$position": "absolute",
|
|
@@ -92,7 +94,7 @@ export function WhatsAppPreview(props) {
|
|
|
92
94
|
},
|
|
93
95
|
children: [separator && _jsx(WhatsAppDateChip, {
|
|
94
96
|
children: separator
|
|
95
|
-
}),
|
|
97
|
+
}), drawnMessages.map((message, index) => _jsx(WhatsAppMessageBubble, {
|
|
96
98
|
message: message
|
|
97
99
|
}, message.id ?? index))]
|
|
98
100
|
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { formatAttachmentDuration, formatAttachmentSize, getAttachmentExtension, isMessageDrawable, resolveAttachmentKind, toVideoPosterSource } from '../helpers';
|
|
2
|
+
const SOURCE = 'data:image/jpeg;base64,AAAA';
|
|
3
|
+
describe('resolveAttachmentKind', () => {
|
|
4
|
+
it('reads the MIME type first', () => {
|
|
5
|
+
expect(resolveAttachmentKind({
|
|
6
|
+
name: 'a.pdf',
|
|
7
|
+
source: SOURCE,
|
|
8
|
+
mimeType: 'image/png'
|
|
9
|
+
})).toBe('image');
|
|
10
|
+
expect(resolveAttachmentKind({
|
|
11
|
+
name: 'a',
|
|
12
|
+
source: SOURCE,
|
|
13
|
+
mimeType: 'video/mp4'
|
|
14
|
+
})).toBe('video');
|
|
15
|
+
});
|
|
16
|
+
it('falls back to the extension', () => {
|
|
17
|
+
expect(resolveAttachmentKind({
|
|
18
|
+
name: 'store.JPG',
|
|
19
|
+
source: SOURCE
|
|
20
|
+
})).toBe('image');
|
|
21
|
+
expect(resolveAttachmentKind({
|
|
22
|
+
name: 'tour.mp4',
|
|
23
|
+
source: SOURCE
|
|
24
|
+
})).toBe('video');
|
|
25
|
+
expect(resolveAttachmentKind({
|
|
26
|
+
name: 'terms.docx',
|
|
27
|
+
source: SOURCE
|
|
28
|
+
})).toBe('document');
|
|
29
|
+
});
|
|
30
|
+
it('treats an unknown file as a document', () => {
|
|
31
|
+
expect(resolveAttachmentKind({
|
|
32
|
+
name: 'notes',
|
|
33
|
+
source: SOURCE
|
|
34
|
+
})).toBe('document');
|
|
35
|
+
});
|
|
36
|
+
it('obeys an explicit kind', () => {
|
|
37
|
+
expect(resolveAttachmentKind({
|
|
38
|
+
name: 'a.png',
|
|
39
|
+
source: SOURCE,
|
|
40
|
+
kind: 'document',
|
|
41
|
+
mimeType: 'image/png'
|
|
42
|
+
})).toBe('document');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('attachment meta', () => {
|
|
46
|
+
it('reads the extension off the name', () => {
|
|
47
|
+
expect(getAttachmentExtension({
|
|
48
|
+
name: 'Price list.pdf',
|
|
49
|
+
source: SOURCE
|
|
50
|
+
})).toBe('pdf');
|
|
51
|
+
expect(getAttachmentExtension({
|
|
52
|
+
name: 'notes',
|
|
53
|
+
source: SOURCE
|
|
54
|
+
})).toBe('');
|
|
55
|
+
});
|
|
56
|
+
it('writes sizes the way WhatsApp does', () => {
|
|
57
|
+
expect(formatAttachmentSize(912)).toBe('912 B');
|
|
58
|
+
expect(formatAttachmentSize(69_632)).toBe('68 kB');
|
|
59
|
+
expect(formatAttachmentSize(1_468_006)).toBe('1.4 MB');
|
|
60
|
+
});
|
|
61
|
+
it('writes video length as m:ss', () => {
|
|
62
|
+
expect(formatAttachmentDuration(6.2)).toBe('0:06');
|
|
63
|
+
expect(formatAttachmentDuration(75)).toBe('1:15');
|
|
64
|
+
});
|
|
65
|
+
it('asks the video for a poster frame once', () => {
|
|
66
|
+
expect(toVideoPosterSource('blob:tour')).toBe('blob:tour#t=0.001');
|
|
67
|
+
expect(toVideoPosterSource('blob:tour#t=2')).toBe('blob:tour#t=2');
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe('isMessageDrawable', () => {
|
|
71
|
+
it('keeps a message that has a body or a file', () => {
|
|
72
|
+
expect(isMessageDrawable({
|
|
73
|
+
direction: 'incoming',
|
|
74
|
+
text: 'hi'
|
|
75
|
+
})).toBe(true);
|
|
76
|
+
expect(isMessageDrawable({
|
|
77
|
+
direction: 'incoming',
|
|
78
|
+
attachment: {
|
|
79
|
+
name: 'a.jpg',
|
|
80
|
+
source: SOURCE
|
|
81
|
+
}
|
|
82
|
+
})).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
it('drops a message with nothing to show', () => {
|
|
85
|
+
expect(isMessageDrawable({
|
|
86
|
+
direction: 'incoming',
|
|
87
|
+
time: '11:06'
|
|
88
|
+
})).toBe(false);
|
|
89
|
+
expect(isMessageDrawable({
|
|
90
|
+
direction: 'incoming',
|
|
91
|
+
attachment: {
|
|
92
|
+
name: 'a.jpg',
|
|
93
|
+
source: ''
|
|
94
|
+
}
|
|
95
|
+
})).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
import type { WhatsAppAttachmentProps } from './types';
|
|
3
|
+
/** Draws an attached file inside a chat bubble: image, video with its play badge, or a document card. */
|
|
4
|
+
export declare function WhatsAppAttachment(props: WhatsAppAttachmentProps): ReactElement;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useState } from 'react';
|
|
3
|
+
import { Vertical } from '@pushwoosh/kit-helpers';
|
|
4
|
+
import { Text } from '@pushwoosh/kit-typography';
|
|
5
|
+
import { DocumentBox, DocumentGlyph, MediaBox, MediaDuration, MediaMeta, MediaOverlay, PlayButton, PlayGlyph } from './styles';
|
|
6
|
+
import { WHATSAPP_COLOR } from '../../constants';
|
|
7
|
+
import { formatAttachmentDuration, formatAttachmentSize, getAttachmentExtension, resolveAttachmentKind, toVideoPosterSource } from '../../helpers';
|
|
8
|
+
import { WhatsAppDocumentIcon, WhatsAppPlayIcon, WhatsAppVideoIcon } from '../../icons';
|
|
9
|
+
const DOCUMENT_GLYPH_COLOR = {
|
|
10
|
+
pdf: WHATSAPP_COLOR.DOCUMENT_PDF,
|
|
11
|
+
doc: WHATSAPP_COLOR.DOCUMENT_TEXT,
|
|
12
|
+
docx: WHATSAPP_COLOR.DOCUMENT_TEXT
|
|
13
|
+
};
|
|
14
|
+
/** Draws an attached file inside a chat bubble: image, video with its play badge, or a document card. */
|
|
15
|
+
export function WhatsAppAttachment(props) {
|
|
16
|
+
const {
|
|
17
|
+
attachment,
|
|
18
|
+
meta,
|
|
19
|
+
hasCaption = false
|
|
20
|
+
} = props;
|
|
21
|
+
const {
|
|
22
|
+
name,
|
|
23
|
+
source,
|
|
24
|
+
size,
|
|
25
|
+
duration
|
|
26
|
+
} = attachment;
|
|
27
|
+
const [readDuration, setReadDuration] = useState(null);
|
|
28
|
+
const kind = resolveAttachmentKind(attachment);
|
|
29
|
+
const extension = getAttachmentExtension(attachment);
|
|
30
|
+
const handleLoadedMetadata = useCallback(event => {
|
|
31
|
+
const {
|
|
32
|
+
duration: length
|
|
33
|
+
} = event.currentTarget;
|
|
34
|
+
setReadDuration(Number.isFinite(length) ? length : null);
|
|
35
|
+
}, []);
|
|
36
|
+
if (kind === 'document') {
|
|
37
|
+
const meaning = [size === undefined ? '' : formatAttachmentSize(size), extension].filter(Boolean);
|
|
38
|
+
return _jsxs(DocumentBox, {
|
|
39
|
+
"$hasCaption": hasCaption,
|
|
40
|
+
children: [_jsx(DocumentGlyph, {
|
|
41
|
+
"$color": DOCUMENT_GLYPH_COLOR[extension] ?? WHATSAPP_COLOR.DOCUMENT_GENERIC,
|
|
42
|
+
children: _jsx(WhatsAppDocumentIcon, {})
|
|
43
|
+
}), _jsxs(Vertical, {
|
|
44
|
+
"$gap": 1,
|
|
45
|
+
"$minWidth": 0,
|
|
46
|
+
children: [_jsx(Text, {
|
|
47
|
+
"$color": WHATSAPP_COLOR.BUBBLE_TEXT,
|
|
48
|
+
"$ellipsis": true,
|
|
49
|
+
"$variant": "standard",
|
|
50
|
+
children: name
|
|
51
|
+
}), meaning.length > 0 && _jsx(Text, {
|
|
52
|
+
"$color": WHATSAPP_COLOR.BUBBLE_META,
|
|
53
|
+
"$variant": "footnote",
|
|
54
|
+
children: meaning.join(' · ')
|
|
55
|
+
})]
|
|
56
|
+
})]
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const length = duration ?? readDuration;
|
|
60
|
+
const isVideo = kind === 'video';
|
|
61
|
+
return _jsxs(MediaBox, {
|
|
62
|
+
"$hasCaption": hasCaption,
|
|
63
|
+
children: [isVideo ? _jsx("video", {
|
|
64
|
+
muted: true,
|
|
65
|
+
playsInline: true,
|
|
66
|
+
onLoadedMetadata: handleLoadedMetadata,
|
|
67
|
+
preload: "metadata",
|
|
68
|
+
src: toVideoPosterSource(source)
|
|
69
|
+
}) : _jsx("img", {
|
|
70
|
+
alt: name,
|
|
71
|
+
src: source
|
|
72
|
+
}), isVideo && _jsx(PlayButton, {
|
|
73
|
+
children: _jsx(PlayGlyph, {
|
|
74
|
+
children: _jsx(WhatsAppPlayIcon, {})
|
|
75
|
+
})
|
|
76
|
+
}), (isVideo || meta) && _jsxs(MediaOverlay, {
|
|
77
|
+
children: [isVideo ? _jsxs(MediaDuration, {
|
|
78
|
+
children: [_jsx(WhatsAppVideoIcon, {}), length === null ? null : formatAttachmentDuration(length)]
|
|
79
|
+
}) : _jsx("span", {}), _jsx(MediaMeta, {
|
|
80
|
+
children: meta
|
|
81
|
+
})]
|
|
82
|
+
})]
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WhatsAppAttachment } from './WhatsAppAttachment';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { WhatsAppAttachment } from './WhatsAppAttachment';
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
export declare const MediaBox: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
|
|
2
|
+
$alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
|
|
3
|
+
$alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
4
|
+
$gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
|
|
5
|
+
$justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
|
|
6
|
+
} & {
|
|
7
|
+
$gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
|
|
8
|
+
$justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
9
|
+
$placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
10
|
+
} & {
|
|
11
|
+
$gridAutoFlow: string;
|
|
12
|
+
} & {
|
|
13
|
+
$position: string;
|
|
14
|
+
$bgColor: "#0B141A";
|
|
15
|
+
} & {
|
|
16
|
+
$hasCaption: boolean;
|
|
17
|
+
}, "$gridAutoFlow" | "$bgColor" | "$position">;
|
|
18
|
+
export declare const PlayButton: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
|
|
19
|
+
$alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
|
|
20
|
+
$alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
21
|
+
$gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
|
|
22
|
+
$justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
|
|
23
|
+
} & {
|
|
24
|
+
$gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
|
|
25
|
+
$justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
26
|
+
$placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
27
|
+
} & {
|
|
28
|
+
$gridAutoFlow: string;
|
|
29
|
+
} & {
|
|
30
|
+
$alignItems: string;
|
|
31
|
+
$justifyContent: string;
|
|
32
|
+
$position: string;
|
|
33
|
+
$inset: number;
|
|
34
|
+
}, "$gridAutoFlow" | "$inset" | "$position" | "$alignItems" | "$justifyContent">;
|
|
35
|
+
export declare const PlayGlyph: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
|
|
36
|
+
$alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
|
|
37
|
+
$alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
38
|
+
$gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
|
|
39
|
+
$justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
|
|
40
|
+
} & {
|
|
41
|
+
$gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
|
|
42
|
+
$justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
43
|
+
$placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
44
|
+
} & {
|
|
45
|
+
$gridAutoFlow: string;
|
|
46
|
+
} & {
|
|
47
|
+
$alignItems: string;
|
|
48
|
+
$justifyContent: string;
|
|
49
|
+
$width: number;
|
|
50
|
+
$height: number;
|
|
51
|
+
$borderRadius: string;
|
|
52
|
+
$bgColor: "rgba(255, 255, 255, 0.85)";
|
|
53
|
+
}, "$gridAutoFlow" | "$bgColor" | "$borderRadius" | "$height" | "$width" | "$alignItems" | "$justifyContent">;
|
|
54
|
+
export declare const MediaOverlay: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
|
|
55
|
+
$alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
|
|
56
|
+
$alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
57
|
+
$gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
|
|
58
|
+
$justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
|
|
59
|
+
} & {
|
|
60
|
+
$gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
|
|
61
|
+
$justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
62
|
+
$placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
63
|
+
} & {
|
|
64
|
+
$sizes: import("@pushwoosh/kit-helpers/styled-helpers").StrNum;
|
|
65
|
+
} & {
|
|
66
|
+
$alignItems: string;
|
|
67
|
+
$gap: number;
|
|
68
|
+
$sizes: string;
|
|
69
|
+
$padding: {
|
|
70
|
+
top: number;
|
|
71
|
+
right: number;
|
|
72
|
+
bottom: number;
|
|
73
|
+
left: number;
|
|
74
|
+
};
|
|
75
|
+
}, "$padding" | "$alignItems" | "$gap" | "$sizes">;
|
|
76
|
+
export declare const MediaDuration: import("styled-components").StyledComponent<"span", any, {
|
|
77
|
+
$color?: string | undefined;
|
|
78
|
+
$uppercase?: boolean | undefined;
|
|
79
|
+
$ellipsis?: boolean | undefined;
|
|
80
|
+
$variant?: import("@pushwoosh/kit-typography").TypographyVariant | undefined;
|
|
81
|
+
} & {
|
|
82
|
+
$variant: "footnote";
|
|
83
|
+
$color: "#FFFFFF";
|
|
84
|
+
}, "$color" | "$variant">;
|
|
85
|
+
export declare const MediaMeta: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
|
|
86
|
+
$alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
|
|
87
|
+
$alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
88
|
+
$gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
|
|
89
|
+
$justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
|
|
90
|
+
} & {
|
|
91
|
+
$gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
|
|
92
|
+
$justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
93
|
+
$placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
94
|
+
} & {
|
|
95
|
+
$gridAutoFlow: string;
|
|
96
|
+
} & {
|
|
97
|
+
$justifyContent: string;
|
|
98
|
+
}, "$gridAutoFlow" | "$justifyContent">;
|
|
99
|
+
export declare const DocumentBox: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
|
|
100
|
+
$alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
|
|
101
|
+
$alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
102
|
+
$gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
|
|
103
|
+
$justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
|
|
104
|
+
} & {
|
|
105
|
+
$gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
|
|
106
|
+
$justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
107
|
+
$placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
108
|
+
} & {
|
|
109
|
+
$sizes: import("@pushwoosh/kit-helpers/styled-helpers").StrNum;
|
|
110
|
+
} & {
|
|
111
|
+
$alignItems: string;
|
|
112
|
+
$gap: number;
|
|
113
|
+
$sizes: string;
|
|
114
|
+
$padding: string;
|
|
115
|
+
$bgColor: "#F5F6F6";
|
|
116
|
+
} & {
|
|
117
|
+
$hasCaption: boolean;
|
|
118
|
+
}, "$bgColor" | "$padding" | "$alignItems" | "$gap" | "$sizes">;
|
|
119
|
+
export declare const DocumentGlyph: import("styled-components").StyledComponent<"div", any, import("@pushwoosh/kit-helpers/styled-helpers").CommonBoxProps & {
|
|
120
|
+
$alignContent?: import("@pushwoosh/kit-helpers/styled-helpers").AlignContentValue | undefined;
|
|
121
|
+
$alignItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
122
|
+
$gap?: import("@pushwoosh/kit-helpers/styled-helpers").StrNum | undefined;
|
|
123
|
+
$justifyContent?: import("@pushwoosh/kit-helpers/styled-helpers").JustifyContentValue | undefined;
|
|
124
|
+
} & {
|
|
125
|
+
$gridAutoFlow?: "row" | "column" | "dense" | "row dense" | "column dense" | undefined;
|
|
126
|
+
$justifyItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
127
|
+
$placeItems?: import("@pushwoosh/kit-helpers/styled-helpers").ItemAlignmentValue | undefined;
|
|
128
|
+
} & {
|
|
129
|
+
$gridAutoFlow: string;
|
|
130
|
+
} & {
|
|
131
|
+
$color: string;
|
|
132
|
+
}, "$gridAutoFlow">;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import styled, { css } from 'styled-components';
|
|
2
|
+
import { Columns, Horizontal, Vertical } from '@pushwoosh/kit-helpers';
|
|
3
|
+
import { Text } from '@pushwoosh/kit-typography';
|
|
4
|
+
import { WHATSAPP_COLOR, WHATSAPP_MEDIA_MAX_HEIGHT } from '../../constants';
|
|
5
|
+
const bubbleInset = css(["margin:-3px -5px ", ";border-radius:6px;overflow:hidden;"], ({
|
|
6
|
+
$hasCaption
|
|
7
|
+
}) => $hasCaption ? '5px' : '-4px');
|
|
8
|
+
export const MediaBox = styled(Vertical).attrs({
|
|
9
|
+
$position: 'relative',
|
|
10
|
+
$bgColor: WHATSAPP_COLOR.MEDIA_BACKDROP
|
|
11
|
+
}).withConfig({
|
|
12
|
+
displayName: "MediaBox",
|
|
13
|
+
componentId: "sc-1uezfps-0"
|
|
14
|
+
})(["", " img,video{display:block;width:100%;max-height:", "px;object-fit:cover;}"], bubbleInset, WHATSAPP_MEDIA_MAX_HEIGHT);
|
|
15
|
+
export const PlayButton = styled(Horizontal).attrs({
|
|
16
|
+
$alignItems: 'center',
|
|
17
|
+
$justifyContent: 'center',
|
|
18
|
+
$position: 'absolute',
|
|
19
|
+
$inset: 0
|
|
20
|
+
}).withConfig({
|
|
21
|
+
displayName: "PlayButton",
|
|
22
|
+
componentId: "sc-1uezfps-1"
|
|
23
|
+
})(["pointer-events:none;"]);
|
|
24
|
+
export const PlayGlyph = styled(Horizontal).attrs({
|
|
25
|
+
$alignItems: 'center',
|
|
26
|
+
$justifyContent: 'center',
|
|
27
|
+
$width: 44,
|
|
28
|
+
$height: 44,
|
|
29
|
+
$borderRadius: '50%',
|
|
30
|
+
$bgColor: WHATSAPP_COLOR.PLAY_BUTTON
|
|
31
|
+
}).withConfig({
|
|
32
|
+
displayName: "PlayGlyph",
|
|
33
|
+
componentId: "sc-1uezfps-2"
|
|
34
|
+
})(["color:", ";svg{display:block;margin-left:3px;}"], WHATSAPP_COLOR.PLAY_GLYPH);
|
|
35
|
+
export const MediaOverlay = styled(Columns).attrs({
|
|
36
|
+
$alignItems: 'end',
|
|
37
|
+
$gap: 8,
|
|
38
|
+
$sizes: 'auto minmax(0, 1fr)',
|
|
39
|
+
$padding: {
|
|
40
|
+
top: 24,
|
|
41
|
+
right: 8,
|
|
42
|
+
bottom: 5,
|
|
43
|
+
left: 8
|
|
44
|
+
}
|
|
45
|
+
}).withConfig({
|
|
46
|
+
displayName: "MediaOverlay",
|
|
47
|
+
componentId: "sc-1uezfps-3"
|
|
48
|
+
})(["position:absolute;right:0;bottom:0;left:0;background:linear-gradient(to top,", ",transparent);pointer-events:none;"], WHATSAPP_COLOR.MEDIA_SCRIM);
|
|
49
|
+
export const MediaDuration = styled(Text).attrs({
|
|
50
|
+
$variant: 'footnote',
|
|
51
|
+
$color: WHATSAPP_COLOR.MEDIA_META
|
|
52
|
+
}).withConfig({
|
|
53
|
+
displayName: "MediaDuration",
|
|
54
|
+
componentId: "sc-1uezfps-4"
|
|
55
|
+
})(["display:inline-flex;align-items:center;gap:4px;svg{display:block;}"]);
|
|
56
|
+
export const MediaMeta = styled(Horizontal).attrs({
|
|
57
|
+
$justifyContent: 'end'
|
|
58
|
+
}).withConfig({
|
|
59
|
+
displayName: "MediaMeta",
|
|
60
|
+
componentId: "sc-1uezfps-5"
|
|
61
|
+
})(["color:", ";svg{color:", ";}"], WHATSAPP_COLOR.MEDIA_META, WHATSAPP_COLOR.MEDIA_META);
|
|
62
|
+
export const DocumentBox = styled(Columns).attrs({
|
|
63
|
+
$alignItems: 'center',
|
|
64
|
+
$gap: 10,
|
|
65
|
+
$sizes: 'auto minmax(0, 1fr)',
|
|
66
|
+
$padding: '8px 10px',
|
|
67
|
+
$bgColor: WHATSAPP_COLOR.DOCUMENT_CARD
|
|
68
|
+
}).withConfig({
|
|
69
|
+
displayName: "DocumentBox",
|
|
70
|
+
componentId: "sc-1uezfps-6"
|
|
71
|
+
})(["", ""], bubbleInset);
|
|
72
|
+
export const DocumentGlyph = styled(Horizontal).withConfig({
|
|
73
|
+
displayName: "DocumentGlyph",
|
|
74
|
+
componentId: "sc-1uezfps-7"
|
|
75
|
+
})(["color:", ";svg{display:block;}"], ({
|
|
76
|
+
$color
|
|
77
|
+
}) => $color);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { WhatsAppAttachment } from '../../types';
|
|
3
|
+
/** Props of {@link WhatsAppAttachment}. */
|
|
4
|
+
export interface WhatsAppAttachmentProps {
|
|
5
|
+
/** File to draw. */
|
|
6
|
+
attachment: WhatsAppAttachment;
|
|
7
|
+
/** Time and ticks overlaid on the media; pass it only when the bubble has no caption below. */
|
|
8
|
+
meta?: ReactNode;
|
|
9
|
+
/** Keep the bottom inset of the bubble instead of hugging its padding. */
|
|
10
|
+
hasCaption?: boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type ReactElement } from 'react';
|
|
2
2
|
import type { WhatsAppMessageBubbleProps } from './types';
|
|
3
|
-
/** Single chat bubble: body, time and — for outgoing messages — the delivery ticks. */
|
|
3
|
+
/** Single chat bubble: attachment, body, time and — for outgoing messages — the delivery ticks. */
|
|
4
4
|
export declare function WhatsAppMessageBubble(props: WhatsAppMessageBubbleProps): ReactElement;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Columns } from '@pushwoosh/kit-helpers';
|
|
3
3
|
import { BubbleBox, BubbleMeta, BubbleText } from './styles';
|
|
4
|
+
import { resolveAttachmentKind } from '../../helpers';
|
|
4
5
|
import { WhatsAppTickIcon, WhatsAppTicksIcon } from '../../icons';
|
|
6
|
+
import { WhatsAppAttachment } from '../WhatsAppAttachment';
|
|
5
7
|
import { WhatsAppFormattedText } from '../WhatsAppFormattedText';
|
|
6
|
-
/** Single chat bubble: body, time and — for outgoing messages — the delivery ticks. */
|
|
8
|
+
/** Single chat bubble: attachment, body, time and — for outgoing messages — the delivery ticks. */
|
|
7
9
|
export function WhatsAppMessageBubble(props) {
|
|
8
10
|
const {
|
|
9
11
|
message
|
|
@@ -12,23 +14,33 @@ export function WhatsAppMessageBubble(props) {
|
|
|
12
14
|
direction,
|
|
13
15
|
text = '',
|
|
14
16
|
content,
|
|
17
|
+
attachment,
|
|
15
18
|
time,
|
|
16
19
|
status
|
|
17
20
|
} = message;
|
|
18
21
|
const showTicks = direction === 'outgoing' && Boolean(status);
|
|
22
|
+
const hasBody = Boolean(text || content);
|
|
23
|
+
const drawnAttachment = attachment?.source ? attachment : undefined;
|
|
24
|
+
const isMediaOnly = drawnAttachment !== undefined && !hasBody && resolveAttachmentKind(drawnAttachment) !== 'document';
|
|
25
|
+
const meta = time || showTicks ? _jsxs(BubbleMeta, {
|
|
26
|
+
"$overlaid": isMediaOnly,
|
|
27
|
+
"$read": showTicks && status === 'read',
|
|
28
|
+
children: [time, showTicks && status === 'sent' && _jsx(WhatsAppTickIcon, {}), showTicks && status !== 'sent' && _jsx(WhatsAppTicksIcon, {})]
|
|
29
|
+
}) : null;
|
|
19
30
|
return _jsx(Columns, {
|
|
20
31
|
"$justifyItems": direction === 'outgoing' ? 'end' : 'start',
|
|
21
32
|
"$sizes": "minmax(0, 1fr)",
|
|
22
33
|
children: _jsxs(BubbleBox, {
|
|
23
34
|
"$direction": direction,
|
|
24
|
-
children: [_jsx(
|
|
35
|
+
children: [drawnAttachment && _jsx(WhatsAppAttachment, {
|
|
36
|
+
attachment: drawnAttachment,
|
|
37
|
+
hasCaption: hasBody,
|
|
38
|
+
meta: isMediaOnly ? meta : undefined
|
|
39
|
+
}), hasBody && _jsx(BubbleText, {
|
|
25
40
|
children: content ?? _jsx(WhatsAppFormattedText, {
|
|
26
41
|
text: text
|
|
27
42
|
})
|
|
28
|
-
}),
|
|
29
|
-
"$read": showTicks && status === 'read',
|
|
30
|
-
children: [time, showTicks && status === 'sent' && _jsx(WhatsAppTickIcon, {}), showTicks && status !== 'sent' && _jsx(WhatsAppTicksIcon, {})]
|
|
31
|
-
})]
|
|
43
|
+
}), !isMediaOnly && meta]
|
|
32
44
|
})
|
|
33
45
|
});
|
|
34
46
|
}
|
|
@@ -17,7 +17,7 @@ export declare const BubbleMeta: import("styled-components").StyledComponent<"sp
|
|
|
17
17
|
$variant?: import("@pushwoosh/kit-typography").TypographyVariant | undefined;
|
|
18
18
|
} & {
|
|
19
19
|
$variant: "footnote";
|
|
20
|
-
$color: "#667781";
|
|
21
20
|
} & {
|
|
22
21
|
$read: boolean;
|
|
23
|
-
|
|
22
|
+
$overlaid?: boolean;
|
|
23
|
+
}, "$variant">;
|
|
@@ -15,11 +15,20 @@ export const BubbleText = styled(Text).attrs({
|
|
|
15
15
|
componentId: "sc-va3yjo-1"
|
|
16
16
|
})(["white-space:pre-wrap;"]);
|
|
17
17
|
export const BubbleMeta = styled(Text).attrs({
|
|
18
|
-
$variant: 'footnote'
|
|
19
|
-
$color: WHATSAPP_COLOR.BUBBLE_META
|
|
18
|
+
$variant: 'footnote'
|
|
20
19
|
}).withConfig({
|
|
21
20
|
displayName: "BubbleMeta",
|
|
22
21
|
componentId: "sc-va3yjo-2"
|
|
23
|
-
})(["display:inline-flex;align-items:center;gap:3px;
|
|
24
|
-
$
|
|
25
|
-
}) => $
|
|
22
|
+
})(["display:inline-flex;align-items:center;gap:3px;color:", ";", " svg{display:block;color:", ";}"], ({
|
|
23
|
+
$overlaid
|
|
24
|
+
}) => $overlaid ? 'inherit' : WHATSAPP_COLOR.BUBBLE_META, ({
|
|
25
|
+
$overlaid
|
|
26
|
+
}) => !$overlaid && css(["float:right;margin:6px 0 -2px 8px;"]), ({
|
|
27
|
+
$read,
|
|
28
|
+
$overlaid
|
|
29
|
+
}) => {
|
|
30
|
+
if ($read) {
|
|
31
|
+
return WHATSAPP_COLOR.READ_TICK;
|
|
32
|
+
}
|
|
33
|
+
return $overlaid ? 'inherit' : WHATSAPP_COLOR.BUBBLE_META;
|
|
34
|
+
});
|
|
@@ -13,6 +13,15 @@ export declare const WHATSAPP_COLOR: {
|
|
|
13
13
|
readonly AVATAR: "#CFD6DB";
|
|
14
14
|
readonly AVATAR_GLYPH: "#FFFFFF";
|
|
15
15
|
readonly INCOMING_BUBBLE: "#FFFFFF";
|
|
16
|
+
readonly MEDIA_BACKDROP: "#0B141A";
|
|
17
|
+
readonly MEDIA_META: "#FFFFFF";
|
|
18
|
+
readonly MEDIA_SCRIM: "rgba(11, 20, 26, 0.5)";
|
|
19
|
+
readonly PLAY_BUTTON: "rgba(255, 255, 255, 0.85)";
|
|
20
|
+
readonly PLAY_GLYPH: "#111B21";
|
|
21
|
+
readonly DOCUMENT_CARD: "#F5F6F6";
|
|
22
|
+
readonly DOCUMENT_PDF: "#E7554B";
|
|
23
|
+
readonly DOCUMENT_TEXT: "#2E7CD6";
|
|
24
|
+
readonly DOCUMENT_GENERIC: "#8696A0";
|
|
16
25
|
readonly OUTGOING_BUBBLE: "#D9FDD3";
|
|
17
26
|
readonly BUBBLE_TEXT: "#111B21";
|
|
18
27
|
readonly BUBBLE_META: "#667781";
|
|
@@ -24,3 +33,8 @@ export declare const WHATSAPP_COLOR: {
|
|
|
24
33
|
export declare const WHATSAPP_PREVIEW_WIDTH = 340;
|
|
25
34
|
export declare const WHATSAPP_PREVIEW_HEIGHT = 640;
|
|
26
35
|
export declare const WHATSAPP_STATUS_BAR_TIME = "12:02";
|
|
36
|
+
export declare const WHATSAPP_MEDIA_MAX_HEIGHT = 320;
|
|
37
|
+
export declare const WHATSAPP_ATTACHMENT_EXTENSIONS: {
|
|
38
|
+
readonly image: string[];
|
|
39
|
+
readonly video: string[];
|
|
40
|
+
};
|
|
@@ -13,6 +13,15 @@ export const WHATSAPP_COLOR = {
|
|
|
13
13
|
AVATAR: '#CFD6DB',
|
|
14
14
|
AVATAR_GLYPH: '#FFFFFF',
|
|
15
15
|
INCOMING_BUBBLE: '#FFFFFF',
|
|
16
|
+
MEDIA_BACKDROP: '#0B141A',
|
|
17
|
+
MEDIA_META: '#FFFFFF',
|
|
18
|
+
MEDIA_SCRIM: 'rgba(11, 20, 26, 0.5)',
|
|
19
|
+
PLAY_BUTTON: 'rgba(255, 255, 255, 0.85)',
|
|
20
|
+
PLAY_GLYPH: '#111B21',
|
|
21
|
+
DOCUMENT_CARD: '#F5F6F6',
|
|
22
|
+
DOCUMENT_PDF: '#E7554B',
|
|
23
|
+
DOCUMENT_TEXT: '#2E7CD6',
|
|
24
|
+
DOCUMENT_GENERIC: '#8696A0',
|
|
16
25
|
OUTGOING_BUBBLE: '#D9FDD3',
|
|
17
26
|
BUBBLE_TEXT: '#111B21',
|
|
18
27
|
BUBBLE_META: '#667781',
|
|
@@ -23,4 +32,11 @@ export const WHATSAPP_COLOR = {
|
|
|
23
32
|
};
|
|
24
33
|
export const WHATSAPP_PREVIEW_WIDTH = 340;
|
|
25
34
|
export const WHATSAPP_PREVIEW_HEIGHT = 640;
|
|
26
|
-
export const WHATSAPP_STATUS_BAR_TIME = '12:02';
|
|
35
|
+
export const WHATSAPP_STATUS_BAR_TIME = '12:02';
|
|
36
|
+
export const WHATSAPP_MEDIA_MAX_HEIGHT = 320;
|
|
37
|
+
const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
|
38
|
+
const VIDEO_EXTENSIONS = ['mp4', 'mov', 'webm', '3gp'];
|
|
39
|
+
export const WHATSAPP_ATTACHMENT_EXTENSIONS = {
|
|
40
|
+
image: IMAGE_EXTENSIONS,
|
|
41
|
+
video: VIDEO_EXTENSIONS
|
|
42
|
+
};
|
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
import type { WhatsAppTextNode } from './components/WhatsAppFormattedText/types';
|
|
2
|
+
import type { WhatsAppAttachment, WhatsAppAttachmentKind, WhatsAppMessage } from './types';
|
|
2
3
|
/** Splits WhatsApp markup (`*bold*`, `_italic_`, `~strike~`, backtick monospace) into a render tree. */
|
|
3
4
|
export declare function parseWhatsAppMarkup(text: string): WhatsAppTextNode[];
|
|
5
|
+
/** Extension of the attached file, lowercase and without the dot; empty when the name carries none. */
|
|
6
|
+
export declare function getAttachmentExtension(attachment: WhatsAppAttachment): string;
|
|
7
|
+
/** Picks how the attachment is drawn: explicit `kind` first, then the MIME type, then the extension. */
|
|
8
|
+
export declare function resolveAttachmentKind(attachment: WhatsAppAttachment): WhatsAppAttachmentKind;
|
|
9
|
+
/** Whether the message has anything to show; a bubble with neither body nor file content is not drawn at all. */
|
|
10
|
+
export declare function isMessageDrawable(message: WhatsAppMessage): boolean;
|
|
11
|
+
/** File size the way WhatsApp writes it on a document card: `912 B`, `68 kB`, `1.4 MB`. */
|
|
12
|
+
export declare function formatAttachmentSize(size: number): string;
|
|
13
|
+
/** Video length as the `m:ss` badge of a WhatsApp video bubble. */
|
|
14
|
+
export declare function formatAttachmentDuration(duration: number): string;
|
|
15
|
+
/** Video source with a time fragment: a frame is only decoded for the poster once a position is asked for. */
|
|
16
|
+
export declare function toVideoPosterSource(source: string): string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { WHATSAPP_ATTACHMENT_EXTENSIONS } from './constants';
|
|
1
2
|
const MARKERS = [{
|
|
2
3
|
style: 'mono',
|
|
3
4
|
marker: '```'
|
|
@@ -53,4 +54,57 @@ export function parseWhatsAppMarkup(text) {
|
|
|
53
54
|
}
|
|
54
55
|
flush();
|
|
55
56
|
return nodes;
|
|
57
|
+
}
|
|
58
|
+
/** Extension of the attached file, lowercase and without the dot; empty when the name carries none. */
|
|
59
|
+
export function getAttachmentExtension(attachment) {
|
|
60
|
+
const {
|
|
61
|
+
name
|
|
62
|
+
} = attachment;
|
|
63
|
+
const dot = name.lastIndexOf('.');
|
|
64
|
+
return dot > 0 ? name.slice(dot + 1).toLowerCase() : '';
|
|
65
|
+
}
|
|
66
|
+
/** Picks how the attachment is drawn: explicit `kind` first, then the MIME type, then the extension. */
|
|
67
|
+
export function resolveAttachmentKind(attachment) {
|
|
68
|
+
if (attachment.kind) {
|
|
69
|
+
return attachment.kind;
|
|
70
|
+
}
|
|
71
|
+
const mimeType = (attachment.mimeType || '').toLowerCase();
|
|
72
|
+
if (mimeType.startsWith('image/')) {
|
|
73
|
+
return 'image';
|
|
74
|
+
}
|
|
75
|
+
if (mimeType.startsWith('video/')) {
|
|
76
|
+
return 'video';
|
|
77
|
+
}
|
|
78
|
+
const extension = getAttachmentExtension(attachment);
|
|
79
|
+
if (WHATSAPP_ATTACHMENT_EXTENSIONS.image.includes(extension)) {
|
|
80
|
+
return 'image';
|
|
81
|
+
}
|
|
82
|
+
if (WHATSAPP_ATTACHMENT_EXTENSIONS.video.includes(extension)) {
|
|
83
|
+
return 'video';
|
|
84
|
+
}
|
|
85
|
+
return 'document';
|
|
86
|
+
}
|
|
87
|
+
/** Whether the message has anything to show; a bubble with neither body nor file content is not drawn at all. */
|
|
88
|
+
export function isMessageDrawable(message) {
|
|
89
|
+
return Boolean(message.text || message.content || message.attachment?.source);
|
|
90
|
+
}
|
|
91
|
+
/** File size the way WhatsApp writes it on a document card: `912 B`, `68 kB`, `1.4 MB`. */
|
|
92
|
+
export function formatAttachmentSize(size) {
|
|
93
|
+
if (size < 1024) {
|
|
94
|
+
return `${size} B`;
|
|
95
|
+
}
|
|
96
|
+
if (size < 1024 * 1024) {
|
|
97
|
+
return `${Math.round(size / 1024)} kB`;
|
|
98
|
+
}
|
|
99
|
+
return `${(size / 1024 / 1024).toFixed(1)} MB`;
|
|
100
|
+
}
|
|
101
|
+
/** Video length as the `m:ss` badge of a WhatsApp video bubble. */
|
|
102
|
+
export function formatAttachmentDuration(duration) {
|
|
103
|
+
const total = Math.round(duration);
|
|
104
|
+
const seconds = total % 60;
|
|
105
|
+
return `${Math.floor(total / 60)}:${String(seconds).padStart(2, '0')}`;
|
|
106
|
+
}
|
|
107
|
+
/** Video source with a time fragment: a frame is only decoded for the poster once a position is asked for. */
|
|
108
|
+
export function toVideoPosterSource(source) {
|
|
109
|
+
return source.includes('#') ? source : `${source}#t=0.001`;
|
|
56
110
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="32" fill="none"><path fill="#fff" stroke="currentColor" stroke-width="1.4" d="M.7 3.3A2.6 2.6 0 0 1 3.3.7h11.2l10.1 10v18a2.6 2.6 0 0 1-2.6 2.6H3.3A2.6 2.6 0 0 1 .7 28.7V3.3Z"/><path fill="currentColor" d="M14.5.7l10.1 10h-8.7a1.4 1.4 0 0 1-1.4-1.4V.7Z"/><path fill="currentColor" d="M5 17.4h10v1.6H5v-1.6Zm0 4.2h10v1.6H5v-1.6Zm0 4.2h6.6v1.6H5v-1.6Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="currentColor"><path d="M5.5 3.4a.6.6 0 0 1 .9-.5l8 5.6a.6.6 0 0 1 0 1l-8 5.6a.6.6 0 0 1-.9-.5V3.4Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="currentColor"><path d="M1.2 3.4A1.2 1.2 0 0 1 2.4 2.2h5.4a1.2 1.2 0 0 1 1.2 1.2v7.2a1.2 1.2 0 0 1-1.2 1.2H2.4a1.2 1.2 0 0 1-1.2-1.2V3.4Zm9 2.7 2.3-1.7a.5.5 0 0 1 .8.4v4.4a.5.5 0 0 1-.8.4l-2.3-1.7V6.1Z"/></svg>
|
|
@@ -2,9 +2,12 @@ export { default as WhatsAppAvatarIcon } from './WhatsAppAvatarIcon.svg';
|
|
|
2
2
|
export { default as WhatsAppBackIcon } from './WhatsAppBackIcon.svg';
|
|
3
3
|
export { default as WhatsAppBatteryIcon } from './WhatsAppBatteryIcon.svg';
|
|
4
4
|
export { default as WhatsAppCallIcon } from './WhatsAppCallIcon.svg';
|
|
5
|
+
export { default as WhatsAppDocumentIcon } from './WhatsAppDocumentIcon.svg';
|
|
5
6
|
export { default as WhatsAppMenuIcon } from './WhatsAppMenuIcon.svg';
|
|
7
|
+
export { default as WhatsAppPlayIcon } from './WhatsAppPlayIcon.svg';
|
|
6
8
|
export { default as WhatsAppSignalIcon } from './WhatsAppSignalIcon.svg';
|
|
7
9
|
export { default as WhatsAppTickIcon } from './WhatsAppTickIcon.svg';
|
|
8
10
|
export { default as WhatsAppTicksIcon } from './WhatsAppTicksIcon.svg';
|
|
11
|
+
export { default as WhatsAppVideoIcon } from './WhatsAppVideoIcon.svg';
|
|
9
12
|
export { default as WhatsAppWallpaperPattern } from './WhatsAppWallpaperPattern.svg';
|
|
10
13
|
export { default as WhatsAppWifiIcon } from './WhatsAppWifiIcon.svg';
|
|
@@ -2,9 +2,12 @@ export { default as WhatsAppAvatarIcon } from './WhatsAppAvatarIcon.svg';
|
|
|
2
2
|
export { default as WhatsAppBackIcon } from './WhatsAppBackIcon.svg';
|
|
3
3
|
export { default as WhatsAppBatteryIcon } from './WhatsAppBatteryIcon.svg';
|
|
4
4
|
export { default as WhatsAppCallIcon } from './WhatsAppCallIcon.svg';
|
|
5
|
+
export { default as WhatsAppDocumentIcon } from './WhatsAppDocumentIcon.svg';
|
|
5
6
|
export { default as WhatsAppMenuIcon } from './WhatsAppMenuIcon.svg';
|
|
7
|
+
export { default as WhatsAppPlayIcon } from './WhatsAppPlayIcon.svg';
|
|
6
8
|
export { default as WhatsAppSignalIcon } from './WhatsAppSignalIcon.svg';
|
|
7
9
|
export { default as WhatsAppTickIcon } from './WhatsAppTickIcon.svg';
|
|
8
10
|
export { default as WhatsAppTicksIcon } from './WhatsAppTicksIcon.svg';
|
|
11
|
+
export { default as WhatsAppVideoIcon } from './WhatsAppVideoIcon.svg';
|
|
9
12
|
export { default as WhatsAppWallpaperPattern } from './WhatsAppWallpaperPattern.svg';
|
|
10
13
|
export { default as WhatsAppWifiIcon } from './WhatsAppWifiIcon.svg';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { WhatsAppPreview } from './WhatsAppPreview';
|
|
2
|
-
export type { WhatsAppMessage, WhatsAppMessageDirection, WhatsAppMessageStatus, WhatsAppPreviewProps, } from './types';
|
|
2
|
+
export type { WhatsAppAttachment, WhatsAppAttachmentKind, WhatsAppMessage, WhatsAppMessageDirection, WhatsAppMessageStatus, WhatsAppPreviewProps, } from './types';
|
|
@@ -3,6 +3,23 @@ import type { ReactNode } from 'react';
|
|
|
3
3
|
export type WhatsAppMessageDirection = 'incoming' | 'outgoing';
|
|
4
4
|
/** Delivery state drawn as ticks next to an outgoing message's time. */
|
|
5
5
|
export type WhatsAppMessageStatus = 'sent' | 'delivered' | 'read';
|
|
6
|
+
/** How WhatsApp draws an attachment: media fills the bubble, a document becomes a card inside it. */
|
|
7
|
+
export type WhatsAppAttachmentKind = 'image' | 'video' | 'document';
|
|
8
|
+
/** File attached to a {@link WhatsAppMessage}. */
|
|
9
|
+
export interface WhatsAppAttachment {
|
|
10
|
+
/** File name; shown on the document card and used to detect the kind. */
|
|
11
|
+
name: string;
|
|
12
|
+
/** File content: whatever an `<img>`/`<video>` accepts — a `data:` payload, a `blob:` or an http(s) address. */
|
|
13
|
+
source: string;
|
|
14
|
+
/** MIME type; wins over the name extension when the kind is detected. */
|
|
15
|
+
mimeType?: string;
|
|
16
|
+
/** File size in bytes, shown in the document card meta line. */
|
|
17
|
+
size?: number;
|
|
18
|
+
/** Overrides the kind detected from `mimeType` and `name`. */
|
|
19
|
+
kind?: WhatsAppAttachmentKind;
|
|
20
|
+
/** Video length in seconds; read from the file itself when omitted. */
|
|
21
|
+
duration?: number;
|
|
22
|
+
}
|
|
6
23
|
/** One chat bubble of {@link WhatsAppPreview}. */
|
|
7
24
|
export interface WhatsAppMessage {
|
|
8
25
|
/** Stable key when the list is re-ordered or edited. */
|
|
@@ -13,6 +30,8 @@ export interface WhatsAppMessage {
|
|
|
13
30
|
text?: string;
|
|
14
31
|
/** Custom body rendered instead of `text` — e.g. an inline editor for the welcome message. */
|
|
15
32
|
content?: ReactNode;
|
|
33
|
+
/** File shown above the text, the way WhatsApp renders images, videos and documents. */
|
|
34
|
+
attachment?: WhatsAppAttachment;
|
|
16
35
|
/** Time stamp inside the bubble. */
|
|
17
36
|
time?: string;
|
|
18
37
|
/** Tick state; ignored for incoming messages. */
|
package/index.d.ts
CHANGED
|
@@ -35,7 +35,7 @@ export { StatisticCard } from './StatisticCard';
|
|
|
35
35
|
export { EmailTemplatePreview, NoEmailTemplatePreview, replaceLocalization, replacePlaceholders } from './EmailTemplatePreview';
|
|
36
36
|
export { PushPreview } from './PushPreview';
|
|
37
37
|
export { WhatsAppPreview } from './WhatsAppPreview';
|
|
38
|
-
export type { WhatsAppMessage, WhatsAppMessageDirection, WhatsAppMessageStatus, WhatsAppPreviewProps, } from './WhatsAppPreview';
|
|
38
|
+
export type { WhatsAppAttachment, WhatsAppAttachmentKind, WhatsAppMessage, WhatsAppMessageDirection, WhatsAppMessageStatus, WhatsAppPreviewProps, } from './WhatsAppPreview';
|
|
39
39
|
export { Toast, ToastProvider, useToast } from './Toast';
|
|
40
40
|
export { AutosizeInput, PageHeader, PageHeaderEditable } from './PageHeader';
|
|
41
41
|
export { ExportResult } from './ExportResult';
|