@linktr.ee/messaging-react 4.4.1-rc-1787614546 โ 4.4.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.
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +853 -859
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/MessageBubble/MessageBubble.stories.tsx +5 -9
- package/src/components/MessageBubble/MessageBubble.test.tsx +7 -18
- package/src/components/MessageBubble/components/_shared/BubbleShell.tsx +8 -13
- package/src/components/MessageBubble/components/_shared/bubbleTail.tsx +2 -2
- package/src/components/RichCard/RichCard.stories.tsx +1 -13
- package/src/components/RichCard/RichCard.test.tsx +0 -20
- package/src/components/RichCard/RichCardBody.tsx +14 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "4.4.1
|
|
3
|
+
"version": "4.4.1",
|
|
4
4
|
"description": "React messaging components built on messaging-core for web applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"chromatic:local": "yarn storybook:build && chromatic"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@linktr.ee/messaging-core": "2.6.0
|
|
54
|
-
"@linktr.ee/messaging-taxonomy": "0.11.0
|
|
53
|
+
"@linktr.ee/messaging-core": "^2.6.0",
|
|
54
|
+
"@linktr.ee/messaging-taxonomy": "^0.11.0",
|
|
55
55
|
"@phosphor-icons/react": "^2.1.10"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
@@ -10,7 +10,7 @@ const meta: Meta = {
|
|
|
10
10
|
export default meta
|
|
11
11
|
|
|
12
12
|
const Canvas = ({ children }: { children: React.ReactNode }) => (
|
|
13
|
-
<div className="
|
|
13
|
+
<div className="min-h-screen w-full space-y-6 bg-[#F9F7F4] p-12">
|
|
14
14
|
{children}
|
|
15
15
|
</div>
|
|
16
16
|
)
|
|
@@ -26,14 +26,10 @@ const Row = ({
|
|
|
26
26
|
}) => (
|
|
27
27
|
<div>
|
|
28
28
|
<p className="mb-2 text-xs font-medium text-black/40">{label}</p>
|
|
29
|
-
<div
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}`}
|
|
34
|
-
>
|
|
35
|
-
<div className="str-chat__message-inner">{children}</div>
|
|
36
|
-
</div>
|
|
29
|
+
<div
|
|
30
|
+
className={`flex ${align === 'end' ? 'justify-end' : 'justify-start'}`}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
37
33
|
</div>
|
|
38
34
|
</div>
|
|
39
35
|
)
|
|
@@ -22,15 +22,12 @@ describe('MessageBubble', () => {
|
|
|
22
22
|
const { rerender } = renderWithProviders(
|
|
23
23
|
<MessageBubble.Sent text="tailed" />
|
|
24
24
|
)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.closest('.str-chat__message-bubble') as HTMLElement
|
|
25
|
+
// The bubble box wraps the text; its last child is the tail <span>.
|
|
26
|
+
const bubble = screen.getByText('tailed').parentElement as HTMLElement
|
|
28
27
|
expect(bubble.querySelector('span[aria-hidden="true"]')).not.toBeNull()
|
|
29
28
|
|
|
30
29
|
rerender(<MessageBubble.Sent text="tailed" tail={false} />)
|
|
31
|
-
const bubbleNoTail = screen
|
|
32
|
-
.getByText('tailed')
|
|
33
|
-
.closest('.str-chat__message-bubble') as HTMLElement
|
|
30
|
+
const bubbleNoTail = screen.getByText('tailed').parentElement as HTMLElement
|
|
34
31
|
expect(bubbleNoTail.querySelector('span[aria-hidden="true"]')).toBeNull()
|
|
35
32
|
})
|
|
36
33
|
|
|
@@ -50,17 +47,13 @@ describe('MessageBubble', () => {
|
|
|
50
47
|
|
|
51
48
|
it('applies the sent and received surface treatments from theme vars', () => {
|
|
52
49
|
const { rerender } = renderWithProviders(<MessageBubble.Sent text="mine" />)
|
|
53
|
-
const sent = screen
|
|
54
|
-
.getByText('mine')
|
|
55
|
-
.closest('.str-chat__message-bubble') as HTMLElement
|
|
50
|
+
const sent = screen.getByText('mine').parentElement as HTMLElement
|
|
56
51
|
expect(sent.style.backgroundColor).toBe(
|
|
57
52
|
'var(--str-chat__own-message-bubble-background-color, #1e2330)'
|
|
58
53
|
)
|
|
59
54
|
|
|
60
55
|
rerender(<MessageBubble.Received text="theirs" />)
|
|
61
|
-
const received = screen
|
|
62
|
-
.getByText('theirs')
|
|
63
|
-
.closest('.str-chat__message-bubble') as HTMLElement
|
|
56
|
+
const received = screen.getByText('theirs').parentElement as HTMLElement
|
|
64
57
|
expect(received.style.backgroundColor).toBe(
|
|
65
58
|
'var(--str-chat__message-bubble-background-color, #f1f0ee)'
|
|
66
59
|
)
|
|
@@ -68,9 +61,7 @@ describe('MessageBubble', () => {
|
|
|
68
61
|
|
|
69
62
|
it('masks message content from session replay by default', () => {
|
|
70
63
|
renderWithProviders(<MessageBubble.Received header="Brie" text="secret" />)
|
|
71
|
-
const bubble = screen
|
|
72
|
-
.getByText('secret')
|
|
73
|
-
.closest('.str-chat__message-bubble') as HTMLElement
|
|
64
|
+
const bubble = screen.getByText('secret').parentElement as HTMLElement
|
|
74
65
|
expect(bubble.getAttribute('data-dd-privacy')).toBe('mask')
|
|
75
66
|
})
|
|
76
67
|
|
|
@@ -78,9 +69,7 @@ describe('MessageBubble', () => {
|
|
|
78
69
|
renderWithProviders(
|
|
79
70
|
<MessageBubble.Sent text="wide" className="w-fit max-w-[520px]" />
|
|
80
71
|
)
|
|
81
|
-
const bubble = screen
|
|
82
|
-
.getByText('wide')
|
|
83
|
-
.closest('.str-chat__message-bubble') as HTMLElement
|
|
72
|
+
const bubble = screen.getByText('wide').parentElement as HTMLElement
|
|
84
73
|
expect(bubble.classList.contains('w-fit')).toBe(true)
|
|
85
74
|
expect(bubble.className).toContain('max-w-[520px]')
|
|
86
75
|
})
|
|
@@ -67,8 +67,7 @@ const BubbleShell: React.FC<BubbleShellProps> = ({
|
|
|
67
67
|
<div
|
|
68
68
|
data-dd-privacy="mask"
|
|
69
69
|
className={classNames(
|
|
70
|
-
'
|
|
71
|
-
'relative',
|
|
70
|
+
'relative px-4 py-3',
|
|
72
71
|
'text-[0.875rem] leading-[1.3125rem] tracking-[0.14px]',
|
|
73
72
|
className
|
|
74
73
|
)}
|
|
@@ -78,17 +77,13 @@ const BubbleShell: React.FC<BubbleShellProps> = ({
|
|
|
78
77
|
isolation: 'isolate',
|
|
79
78
|
}}
|
|
80
79
|
>
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{text}
|
|
89
|
-
</p>
|
|
90
|
-
{tail && <BubbleTail variant={variant} />}
|
|
91
|
-
</div>
|
|
80
|
+
{header != null && (
|
|
81
|
+
<div className="mb-1 font-medium" data-testid="message-bubble-header">
|
|
82
|
+
{header}
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
<p className="m-0 whitespace-pre-wrap break-words hyphens-manual">{text}</p>
|
|
86
|
+
{tail && <BubbleTail variant={variant} />}
|
|
92
87
|
</div>
|
|
93
88
|
)
|
|
94
89
|
|
|
@@ -36,9 +36,9 @@ export const BubbleTail = ({ variant }: { variant: MessageBubbleVariant }) => (
|
|
|
36
36
|
aria-hidden="true"
|
|
37
37
|
className="pointer-events-none absolute bottom-0 z-[-1] h-[1.4375rem] w-5"
|
|
38
38
|
style={{
|
|
39
|
-
insetInlineEnd: variant === 'sent' ? 'calc(-1 * 0.
|
|
39
|
+
insetInlineEnd: variant === 'sent' ? 'calc(-1 * 0.25rem)' : undefined,
|
|
40
40
|
insetInlineStart:
|
|
41
|
-
variant === 'received' ? 'calc(-1 * 0.
|
|
41
|
+
variant === 'received' ? 'calc(-1 * 0.25rem)' : undefined,
|
|
42
42
|
transform: variant === 'received' ? 'scaleX(-1)' : undefined,
|
|
43
43
|
transformOrigin: 'center',
|
|
44
44
|
backgroundColor: BG_VAR_BY_VARIANT[variant],
|
|
@@ -95,13 +95,6 @@ const COLLECTION: RichCardBaseProps = {
|
|
|
95
95
|
imagePresentation: 'fan',
|
|
96
96
|
actions: [secondaryLink('View collection', 'tr.ee/briesummer')],
|
|
97
97
|
}
|
|
98
|
-
const NEWLINE_SUBTITLE: RichCardBaseProps = {
|
|
99
|
-
...OFFICIAL_WELCOME,
|
|
100
|
-
title: 'Start every conversation with a personal welcome message!',
|
|
101
|
-
subtitle:
|
|
102
|
-
'๐ Say hello as people arrive\nโจ Spark conversations\n๐ฌ Add automated FAQs',
|
|
103
|
-
subtitleLines: 6,
|
|
104
|
-
}
|
|
105
98
|
|
|
106
99
|
const bothSides = (props: RichCardBaseProps) => (
|
|
107
100
|
<>
|
|
@@ -120,8 +113,7 @@ const bothSides = (props: RichCardBaseProps) => (
|
|
|
120
113
|
* The three real surfaces that converge on `linktree_rich_card`, each shown
|
|
121
114
|
* Received (light) and Sent (dark). They differ only by knobs: the official
|
|
122
115
|
* welcome card is `hero + primary`; a single-link broadcast is `hero +
|
|
123
|
-
* secondary`; a collection is `fan + secondary`.
|
|
124
|
-
* newline-separated subtitle (the onboarding result-card shape).
|
|
116
|
+
* secondary`; a collection is `fan + secondary`.
|
|
125
117
|
*/
|
|
126
118
|
export const Instances: StoryFn = () => (
|
|
127
119
|
<Table>
|
|
@@ -139,10 +131,6 @@ export const Instances: StoryFn = () => (
|
|
|
139
131
|
<RowLabel>Collection โ fan ยท secondary</RowLabel>
|
|
140
132
|
{bothSides(COLLECTION)}
|
|
141
133
|
</tr>
|
|
142
|
-
<tr>
|
|
143
|
-
<RowLabel>Subtitle with newlines</RowLabel>
|
|
144
|
-
{bothSides(NEWLINE_SUBTITLE)}
|
|
145
|
-
</tr>
|
|
146
134
|
</tbody>
|
|
147
135
|
</Table>
|
|
148
136
|
)
|
|
@@ -41,26 +41,6 @@ describe('RichCard', () => {
|
|
|
41
41
|
expect(card).toHaveTextContent('๐ Say hello as people arrive')
|
|
42
42
|
expect(card).toHaveTextContent('โจ Spark conversations')
|
|
43
43
|
expect(card).toHaveTextContent('๐ฌ Add automated FAQs')
|
|
44
|
-
expect(card.querySelectorAll('br')).toHaveLength(2)
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('does not insert line breaks when the subtitle is clamped to one line', async () => {
|
|
48
|
-
renderWithProviders(
|
|
49
|
-
<RichCard.Received
|
|
50
|
-
title="Welcome"
|
|
51
|
-
subtitle={
|
|
52
|
-
'๐ Say hello as people arrive\nโจ Spark conversations\n๐ฌ Add automated FAQs'
|
|
53
|
-
}
|
|
54
|
-
subtitleLines={1}
|
|
55
|
-
images={[HERO]}
|
|
56
|
-
/>
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
const card = await screen.findByTestId('rich-card')
|
|
60
|
-
expect(card.querySelectorAll('br')).toHaveLength(0)
|
|
61
|
-
expect(screen.getByText(/Say hello as people arrive/)).toHaveClass(
|
|
62
|
-
'truncate'
|
|
63
|
-
)
|
|
64
44
|
})
|
|
65
45
|
|
|
66
46
|
it('renders a single hero image for the hero presentation', async () => {
|
|
@@ -18,27 +18,17 @@ export interface RichCardBodyProps {
|
|
|
18
18
|
actions?: RichCardActionButton[]
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
21
|
+
// Mirror LinkAttachment's `CardBody` type scales so the two cards read
|
|
22
|
+
// identically (14/20 medium title, 12/16 subtitle). Colour is always supplied
|
|
23
|
+
// via `chinTextColor`, so no per-variant text class is needed here.
|
|
24
|
+
// `[text-wrap:balance]` evens the title's line lengths so a two-line title
|
|
25
|
+
// doesn't drop a single word onto its own line; `[text-wrap:pretty]` does the
|
|
26
|
+
// lighter last-line-widow fix for the longer subtitle prose. Arbitrary-property
|
|
27
|
+
// form so the utilities generate regardless of the consumer's Tailwind version.
|
|
23
28
|
const TITLE_CLASS =
|
|
24
|
-
'line-clamp-2 text-[14px] font-medium leading-5 tracking-[0.
|
|
25
|
-
const SUBTITLE_CLASS =
|
|
26
|
-
|
|
27
|
-
/** `<br>` ignores vertical margin; the spacer is the 6px Figma gap. */
|
|
28
|
-
const LineBreak: React.FC = () => (
|
|
29
|
-
<>
|
|
30
|
-
<br />
|
|
31
|
-
<span aria-hidden className="block h-1.5" />
|
|
32
|
-
</>
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
const withLineBreaks = (text: string) =>
|
|
36
|
-
text.split(/\r?\n/).map((line, index, lines) => (
|
|
37
|
-
<React.Fragment key={index}>
|
|
38
|
-
{line}
|
|
39
|
-
{index < lines.length - 1 ? <LineBreak /> : null}
|
|
40
|
-
</React.Fragment>
|
|
41
|
-
))
|
|
29
|
+
'line-clamp-2 text-[14px] font-medium leading-5 tracking-[0.28px] [text-wrap:balance]'
|
|
30
|
+
const SUBTITLE_CLASS =
|
|
31
|
+
'text-[12px] leading-4 tracking-[0.24px] [text-wrap:pretty]'
|
|
42
32
|
|
|
43
33
|
/**
|
|
44
34
|
* The action footer. `layout` (chosen by the first action's `variant` in
|
|
@@ -66,7 +56,7 @@ const RichCardFooter: React.FC<{
|
|
|
66
56
|
// card's width; the button truncates within it and the copy yields
|
|
67
57
|
// first. `stacked` keeps the full-width buttons-below layout.
|
|
68
58
|
stacked
|
|
69
|
-
? 'mt-
|
|
59
|
+
? 'mt-3 flex-col'
|
|
70
60
|
: 'min-w-0 max-w-[60%] flex-row flex-wrap justify-end'
|
|
71
61
|
)}
|
|
72
62
|
>
|
|
@@ -121,7 +111,7 @@ const RichCardBody: React.FC<RichCardBodyProps> = ({
|
|
|
121
111
|
hasActions && (actions?.[0]?.variant ?? 'primary') === 'secondary'
|
|
122
112
|
|
|
123
113
|
const textBlock = (
|
|
124
|
-
<div className="flex min-w-0 flex-1 flex-col gap-
|
|
114
|
+
<div className="flex min-w-0 flex-1 flex-col gap-[2px]">
|
|
125
115
|
{hasTitle && (
|
|
126
116
|
<div className="flex min-w-0 items-center gap-2">
|
|
127
117
|
{appIcon ? <span className="shrink-0">{appIcon}</span> : null}
|
|
@@ -138,11 +128,12 @@ const RichCardBody: React.FC<RichCardBodyProps> = ({
|
|
|
138
128
|
<p
|
|
139
129
|
className={classNames(
|
|
140
130
|
SUBTITLE_CLASS,
|
|
131
|
+
subtitleLines > 1 && 'whitespace-pre-line',
|
|
141
132
|
subtitleLines <= 1 && 'truncate'
|
|
142
133
|
)}
|
|
143
134
|
style={subtitleStyle}
|
|
144
135
|
>
|
|
145
|
-
{
|
|
136
|
+
{subtitle}
|
|
146
137
|
</p>
|
|
147
138
|
)}
|
|
148
139
|
</div>
|