@linktr.ee/messaging-react 4.4.0 โ 4.4.1-rc-1787614546
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 +859 -853
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/MessageBubble/MessageBubble.stories.tsx +9 -5
- package/src/components/MessageBubble/MessageBubble.test.tsx +18 -7
- package/src/components/MessageBubble/components/_shared/BubbleShell.tsx +13 -8
- package/src/components/MessageBubble/components/_shared/bubbleTail.tsx +2 -2
- package/src/components/RichCard/RichCard.stories.tsx +13 -1
- package/src/components/RichCard/RichCard.test.tsx +20 -0
- package/src/components/RichCard/RichCardBody.tsx +23 -14
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "4.4.
|
|
3
|
+
"version": "4.4.1-rc-1787614546",
|
|
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": "
|
|
54
|
-
"@linktr.ee/messaging-taxonomy": "
|
|
53
|
+
"@linktr.ee/messaging-core": "2.6.0-rc-1787614546",
|
|
54
|
+
"@linktr.ee/messaging-taxonomy": "0.11.0-rc-1787614546",
|
|
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="min-h-screen w-full space-y-6 bg-[#F9F7F4] p-12">
|
|
13
|
+
<div className="str-chat min-h-screen w-full space-y-6 bg-[#F9F7F4] p-12">
|
|
14
14
|
{children}
|
|
15
15
|
</div>
|
|
16
16
|
)
|
|
@@ -26,10 +26,14 @@ 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
|
-
|
|
29
|
+
<div className="str-chat__li str-chat__li--single">
|
|
30
|
+
<div
|
|
31
|
+
className={`str-chat__message ${
|
|
32
|
+
align === 'end' ? 'str-chat__message--me' : 'str-chat__message--other'
|
|
33
|
+
}`}
|
|
34
|
+
>
|
|
35
|
+
<div className="str-chat__message-inner">{children}</div>
|
|
36
|
+
</div>
|
|
33
37
|
</div>
|
|
34
38
|
</div>
|
|
35
39
|
)
|
|
@@ -22,12 +22,15 @@ describe('MessageBubble', () => {
|
|
|
22
22
|
const { rerender } = renderWithProviders(
|
|
23
23
|
<MessageBubble.Sent text="tailed" />
|
|
24
24
|
)
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const bubble = screen
|
|
26
|
+
.getByText('tailed')
|
|
27
|
+
.closest('.str-chat__message-bubble') as HTMLElement
|
|
27
28
|
expect(bubble.querySelector('span[aria-hidden="true"]')).not.toBeNull()
|
|
28
29
|
|
|
29
30
|
rerender(<MessageBubble.Sent text="tailed" tail={false} />)
|
|
30
|
-
const bubbleNoTail = screen
|
|
31
|
+
const bubbleNoTail = screen
|
|
32
|
+
.getByText('tailed')
|
|
33
|
+
.closest('.str-chat__message-bubble') as HTMLElement
|
|
31
34
|
expect(bubbleNoTail.querySelector('span[aria-hidden="true"]')).toBeNull()
|
|
32
35
|
})
|
|
33
36
|
|
|
@@ -47,13 +50,17 @@ describe('MessageBubble', () => {
|
|
|
47
50
|
|
|
48
51
|
it('applies the sent and received surface treatments from theme vars', () => {
|
|
49
52
|
const { rerender } = renderWithProviders(<MessageBubble.Sent text="mine" />)
|
|
50
|
-
const sent = screen
|
|
53
|
+
const sent = screen
|
|
54
|
+
.getByText('mine')
|
|
55
|
+
.closest('.str-chat__message-bubble') as HTMLElement
|
|
51
56
|
expect(sent.style.backgroundColor).toBe(
|
|
52
57
|
'var(--str-chat__own-message-bubble-background-color, #1e2330)'
|
|
53
58
|
)
|
|
54
59
|
|
|
55
60
|
rerender(<MessageBubble.Received text="theirs" />)
|
|
56
|
-
const received = screen
|
|
61
|
+
const received = screen
|
|
62
|
+
.getByText('theirs')
|
|
63
|
+
.closest('.str-chat__message-bubble') as HTMLElement
|
|
57
64
|
expect(received.style.backgroundColor).toBe(
|
|
58
65
|
'var(--str-chat__message-bubble-background-color, #f1f0ee)'
|
|
59
66
|
)
|
|
@@ -61,7 +68,9 @@ describe('MessageBubble', () => {
|
|
|
61
68
|
|
|
62
69
|
it('masks message content from session replay by default', () => {
|
|
63
70
|
renderWithProviders(<MessageBubble.Received header="Brie" text="secret" />)
|
|
64
|
-
const bubble = screen
|
|
71
|
+
const bubble = screen
|
|
72
|
+
.getByText('secret')
|
|
73
|
+
.closest('.str-chat__message-bubble') as HTMLElement
|
|
65
74
|
expect(bubble.getAttribute('data-dd-privacy')).toBe('mask')
|
|
66
75
|
})
|
|
67
76
|
|
|
@@ -69,7 +78,9 @@ describe('MessageBubble', () => {
|
|
|
69
78
|
renderWithProviders(
|
|
70
79
|
<MessageBubble.Sent text="wide" className="w-fit max-w-[520px]" />
|
|
71
80
|
)
|
|
72
|
-
const bubble = screen
|
|
81
|
+
const bubble = screen
|
|
82
|
+
.getByText('wide')
|
|
83
|
+
.closest('.str-chat__message-bubble') as HTMLElement
|
|
73
84
|
expect(bubble.classList.contains('w-fit')).toBe(true)
|
|
74
85
|
expect(bubble.className).toContain('max-w-[520px]')
|
|
75
86
|
})
|
|
@@ -67,7 +67,8 @@ const BubbleShell: React.FC<BubbleShellProps> = ({
|
|
|
67
67
|
<div
|
|
68
68
|
data-dd-privacy="mask"
|
|
69
69
|
className={classNames(
|
|
70
|
-
'
|
|
70
|
+
'str-chat__message-bubble',
|
|
71
|
+
'relative',
|
|
71
72
|
'text-[0.875rem] leading-[1.3125rem] tracking-[0.14px]',
|
|
72
73
|
className
|
|
73
74
|
)}
|
|
@@ -77,13 +78,17 @@ const BubbleShell: React.FC<BubbleShellProps> = ({
|
|
|
77
78
|
isolation: 'isolate',
|
|
78
79
|
}}
|
|
79
80
|
>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
<div className="str-chat__message-text">
|
|
82
|
+
{header != null && (
|
|
83
|
+
<div className="mb-1 font-medium" data-testid="message-bubble-header">
|
|
84
|
+
{header}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
<p className="m-0 whitespace-pre-wrap break-words hyphens-manual">
|
|
88
|
+
{text}
|
|
89
|
+
</p>
|
|
90
|
+
{tail && <BubbleTail variant={variant} />}
|
|
91
|
+
</div>
|
|
87
92
|
</div>
|
|
88
93
|
)
|
|
89
94
|
|
|
@@ -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.3125rem)' : undefined,
|
|
40
40
|
insetInlineStart:
|
|
41
|
-
variant === 'received' ? 'calc(-1 * 0.
|
|
41
|
+
variant === 'received' ? 'calc(-1 * 0.3125rem)' : undefined,
|
|
42
42
|
transform: variant === 'received' ? 'scaleX(-1)' : undefined,
|
|
43
43
|
transformOrigin: 'center',
|
|
44
44
|
backgroundColor: BG_VAR_BY_VARIANT[variant],
|
|
@@ -95,6 +95,13 @@ 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
|
+
}
|
|
98
105
|
|
|
99
106
|
const bothSides = (props: RichCardBaseProps) => (
|
|
100
107
|
<>
|
|
@@ -113,7 +120,8 @@ const bothSides = (props: RichCardBaseProps) => (
|
|
|
113
120
|
* The three real surfaces that converge on `linktree_rich_card`, each shown
|
|
114
121
|
* Received (light) and Sent (dark). They differ only by knobs: the official
|
|
115
122
|
* welcome card is `hero + primary`; a single-link broadcast is `hero +
|
|
116
|
-
* secondary`; a collection is `fan + secondary`.
|
|
123
|
+
* secondary`; a collection is `fan + secondary`. A fourth row shows a
|
|
124
|
+
* newline-separated subtitle (the onboarding result-card shape).
|
|
117
125
|
*/
|
|
118
126
|
export const Instances: StoryFn = () => (
|
|
119
127
|
<Table>
|
|
@@ -131,6 +139,10 @@ export const Instances: StoryFn = () => (
|
|
|
131
139
|
<RowLabel>Collection โ fan ยท secondary</RowLabel>
|
|
132
140
|
{bothSides(COLLECTION)}
|
|
133
141
|
</tr>
|
|
142
|
+
<tr>
|
|
143
|
+
<RowLabel>Subtitle with newlines</RowLabel>
|
|
144
|
+
{bothSides(NEWLINE_SUBTITLE)}
|
|
145
|
+
</tr>
|
|
134
146
|
</tbody>
|
|
135
147
|
</Table>
|
|
136
148
|
)
|
|
@@ -41,6 +41,26 @@ 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
|
+
)
|
|
44
64
|
})
|
|
45
65
|
|
|
46
66
|
it('renders a single hero image for the hero presentation', async () => {
|
|
@@ -18,17 +18,27 @@ export interface RichCardBodyProps {
|
|
|
18
18
|
actions?: RichCardActionButton[]
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
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.
|
|
21
|
+
// body/small from Figma. Title is medium; subtitle is regular. No
|
|
22
|
+
// `text-wrap:balance` โ it evened two-line titles by breaking mid-phrase.
|
|
28
23
|
const TITLE_CLASS =
|
|
29
|
-
'line-clamp-2 text-[14px] font-medium leading-5 tracking-[0.
|
|
30
|
-
const SUBTITLE_CLASS =
|
|
31
|
-
|
|
24
|
+
'line-clamp-2 text-[14px] font-medium leading-5 tracking-[0.02em]'
|
|
25
|
+
const SUBTITLE_CLASS = 'text-[14px] leading-5 tracking-[0.02em]'
|
|
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
|
+
))
|
|
32
42
|
|
|
33
43
|
/**
|
|
34
44
|
* The action footer. `layout` (chosen by the first action's `variant` in
|
|
@@ -56,7 +66,7 @@ const RichCardFooter: React.FC<{
|
|
|
56
66
|
// card's width; the button truncates within it and the copy yields
|
|
57
67
|
// first. `stacked` keeps the full-width buttons-below layout.
|
|
58
68
|
stacked
|
|
59
|
-
? 'mt-
|
|
69
|
+
? 'mt-4 flex-col'
|
|
60
70
|
: 'min-w-0 max-w-[60%] flex-row flex-wrap justify-end'
|
|
61
71
|
)}
|
|
62
72
|
>
|
|
@@ -111,7 +121,7 @@ const RichCardBody: React.FC<RichCardBodyProps> = ({
|
|
|
111
121
|
hasActions && (actions?.[0]?.variant ?? 'primary') === 'secondary'
|
|
112
122
|
|
|
113
123
|
const textBlock = (
|
|
114
|
-
<div className="flex min-w-0 flex-1 flex-col gap-
|
|
124
|
+
<div className="flex min-w-0 flex-1 flex-col gap-4">
|
|
115
125
|
{hasTitle && (
|
|
116
126
|
<div className="flex min-w-0 items-center gap-2">
|
|
117
127
|
{appIcon ? <span className="shrink-0">{appIcon}</span> : null}
|
|
@@ -128,12 +138,11 @@ const RichCardBody: React.FC<RichCardBodyProps> = ({
|
|
|
128
138
|
<p
|
|
129
139
|
className={classNames(
|
|
130
140
|
SUBTITLE_CLASS,
|
|
131
|
-
subtitleLines > 1 && 'whitespace-pre-line',
|
|
132
141
|
subtitleLines <= 1 && 'truncate'
|
|
133
142
|
)}
|
|
134
143
|
style={subtitleStyle}
|
|
135
144
|
>
|
|
136
|
-
{subtitle}
|
|
145
|
+
{subtitleLines > 1 ? withLineBreaks(subtitle ?? '') : subtitle}
|
|
137
146
|
</p>
|
|
138
147
|
)}
|
|
139
148
|
</div>
|