@linktr.ee/messaging-react 3.31.1-rc-1785925535 → 3.31.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/{Card-Ch6N2pkc.js → Card-CmlaSw7i.js} +2 -2
- package/dist/{Card-Ch6N2pkc.js.map → Card-CmlaSw7i.js.map} +1 -1
- package/dist/{Card-CF1TZXD5.cjs → Card-EWN07XIl.cjs} +2 -2
- package/dist/{Card-CF1TZXD5.cjs.map → Card-EWN07XIl.cjs.map} +1 -1
- package/dist/assets/index.css +1 -1
- package/dist/index-CN3errpB.js +7828 -0
- package/dist/index-CN3errpB.js.map +1 -0
- package/dist/index-CcitzVoW.cjs +5 -0
- package/dist/index-CcitzVoW.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +10 -30
- package/dist/index.js +1 -1
- package/dist/testing.d.ts +5 -24
- package/package.json +6 -4
- package/src/components/LinkAttachment/LinkAttachment.stories.tsx +27 -44
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +39 -47
- package/src/components/LinkAttachment/components/Composer/Card.tsx +1 -1
- package/src/components/LinkAttachment/components/Received/Card.tsx +2 -2
- package/src/components/LinkAttachment/components/Sent/Card.tsx +2 -2
- package/src/components/LinkAttachment/components/_shared/BubbleTail.tsx +6 -16
- package/src/components/LinkAttachment/components/_shared/CardBody.tsx +5 -4
- package/src/components/LinkAttachment/components/_shared/CardShell.tsx +9 -22
- package/src/components/LinkAttachment/components/_shared/accentSurface.test.ts +135 -0
- package/src/components/LinkAttachment/components/_shared/accentSurface.ts +95 -0
- package/src/components/LinkAttachment/components/_shared/chinContrast.test.ts +95 -0
- package/src/components/LinkAttachment/components/_shared/chinContrast.ts +79 -0
- package/src/components/LinkAttachment/components/_shared/hex.ts +7 -7
- package/src/components/LinkAttachment/components/_shared/useChinPalette.ts +27 -36
- package/src/components/LinkAttachment/types.ts +5 -6
- package/src/stream-custom-data.ts +7 -24
- package/src/styles.css +0 -39
- package/dist/index-BKkCNnea.js +0 -5677
- package/dist/index-BKkCNnea.js.map +0 -1
- package/dist/index-Ct_Jgy8P.cjs +0 -2
- package/dist/index-Ct_Jgy8P.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-CcitzVoW.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageBubble=e.MessageBubble;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.optimizeMessagingAttachmentUrl=e.optimizeMessagingAttachmentUrl;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.d.ts
CHANGED
|
@@ -696,17 +696,16 @@ export declare interface LinkAttachmentBaseProps {
|
|
|
696
696
|
/**
|
|
697
697
|
* Dominant colour extracted from `thumbnailUrl`, as a hex string
|
|
698
698
|
* (`#RRGGBB` or `#RGB`). Paints the card and — when the card is the last
|
|
699
|
-
* bubble in a same-author run — its tail.
|
|
700
|
-
*
|
|
701
|
-
* stops clear WCAG AA at every hue by construction, so callers never pass a
|
|
699
|
+
* bubble in a same-author run — its tail. The card derives its own chin
|
|
700
|
+
* text colour from this value for WCAG AA contrast; callers never pass a
|
|
702
701
|
* text colour.
|
|
703
702
|
*
|
|
704
703
|
* Only painted while the hero image it was extracted from is actually on
|
|
705
704
|
* screen. Omitted, unparseable, `layout: 'classic'`, no `thumbnailUrl`,
|
|
706
705
|
* `status` other than `'ready'`, a playable-audio `mimeType` + `sourceUrl`
|
|
707
|
-
* (whose hero is the native player rather than an image), or a hero that
|
|
708
|
-
* to load
|
|
709
|
-
*
|
|
706
|
+
* (whose hero is the native player rather than an image), or a hero that
|
|
707
|
+
* fails to load, or the colour cannot clear AA against this side's ink =>
|
|
708
|
+
* the card falls back to `BUBBLE_BG_BY_VARIANT[variant]`, the plain bubble fill.
|
|
710
709
|
*/
|
|
711
710
|
accentColor?: string;
|
|
712
711
|
/**
|
|
@@ -1787,32 +1786,13 @@ declare module 'stream-chat' {
|
|
|
1787
1786
|
*/
|
|
1788
1787
|
accent_color?: string;
|
|
1789
1788
|
}
|
|
1790
|
-
interface CustomChannelData {
|
|
1789
|
+
interface CustomChannelData extends LinktreeChannelData {
|
|
1791
1790
|
/**
|
|
1792
|
-
* Whether
|
|
1793
|
-
*
|
|
1794
|
-
|
|
1795
|
-
has_visitor_message?: boolean;
|
|
1796
|
-
/**
|
|
1797
|
-
* Priority score assigned by backend classification for spotlighting.
|
|
1798
|
-
*/
|
|
1799
|
-
priority_score?: number;
|
|
1800
|
-
/**
|
|
1801
|
-
* Indicates a paid message is pending creator reply.
|
|
1802
|
-
*/
|
|
1803
|
-
pending_earn_message_reply?: boolean;
|
|
1804
|
-
/**
|
|
1805
|
-
* Indicates DM agent has deferred and creator follow-up is needed.
|
|
1806
|
-
*/
|
|
1807
|
-
agent_deferred?: boolean;
|
|
1808
|
-
/**
|
|
1809
|
-
* Whether DM agent auto-replies are paused for this channel.
|
|
1791
|
+
* Whether DM agent auto-replies are paused for this channel. Writer-only
|
|
1792
|
+
* field not read by other clients, so it stays local rather than in the
|
|
1793
|
+
* shared {@link LinktreeChannelData} contract.
|
|
1810
1794
|
*/
|
|
1811
1795
|
chatbot_paused?: boolean;
|
|
1812
|
-
/**
|
|
1813
|
-
* Customer classification tag for this conversation (e.g. CUSTOMER_PAID).
|
|
1814
|
-
*/
|
|
1815
|
-
customer_tag?: string;
|
|
1816
1796
|
}
|
|
1817
1797
|
interface CustomMemberData {
|
|
1818
1798
|
/**
|
|
@@ -1820,7 +1800,7 @@ declare module 'stream-chat' {
|
|
|
1820
1800
|
*/
|
|
1821
1801
|
is_follower?: boolean;
|
|
1822
1802
|
}
|
|
1823
|
-
interface CustomMessageData {
|
|
1803
|
+
interface CustomMessageData extends LinktreeMessageData {
|
|
1824
1804
|
/**
|
|
1825
1805
|
* Message metadata from backend.
|
|
1826
1806
|
* Contains type and payment information.
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as p, m as d, n as v, p as C, q as A, s as k, t as F, u as b, v as P, w as f, x, o as y, y as z, z as B, B as S, D as q, E as D, G as E, H as V, I as w } from "./index-
|
|
1
|
+
import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as p, m as d, n as v, p as C, q as A, s as k, t as F, u as b, v as P, w as f, x, o as y, y as z, z as B, B as S, D as q, E as D, G as E, H as V, I as w } from "./index-CN3errpB.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
package/dist/testing.d.ts
CHANGED
|
@@ -124,32 +124,13 @@ declare module 'stream-chat' {
|
|
|
124
124
|
*/
|
|
125
125
|
accent_color?: string;
|
|
126
126
|
}
|
|
127
|
-
interface CustomChannelData {
|
|
127
|
+
interface CustomChannelData extends LinktreeChannelData {
|
|
128
128
|
/**
|
|
129
|
-
* Whether
|
|
130
|
-
*
|
|
131
|
-
|
|
132
|
-
has_visitor_message?: boolean;
|
|
133
|
-
/**
|
|
134
|
-
* Priority score assigned by backend classification for spotlighting.
|
|
135
|
-
*/
|
|
136
|
-
priority_score?: number;
|
|
137
|
-
/**
|
|
138
|
-
* Indicates a paid message is pending creator reply.
|
|
139
|
-
*/
|
|
140
|
-
pending_earn_message_reply?: boolean;
|
|
141
|
-
/**
|
|
142
|
-
* Indicates DM agent has deferred and creator follow-up is needed.
|
|
143
|
-
*/
|
|
144
|
-
agent_deferred?: boolean;
|
|
145
|
-
/**
|
|
146
|
-
* Whether DM agent auto-replies are paused for this channel.
|
|
129
|
+
* Whether DM agent auto-replies are paused for this channel. Writer-only
|
|
130
|
+
* field not read by other clients, so it stays local rather than in the
|
|
131
|
+
* shared {@link LinktreeChannelData} contract.
|
|
147
132
|
*/
|
|
148
133
|
chatbot_paused?: boolean;
|
|
149
|
-
/**
|
|
150
|
-
* Customer classification tag for this conversation (e.g. CUSTOMER_PAID).
|
|
151
|
-
*/
|
|
152
|
-
customer_tag?: string;
|
|
153
134
|
}
|
|
154
135
|
interface CustomMemberData {
|
|
155
136
|
/**
|
|
@@ -157,7 +138,7 @@ declare module 'stream-chat' {
|
|
|
157
138
|
*/
|
|
158
139
|
is_follower?: boolean;
|
|
159
140
|
}
|
|
160
|
-
interface CustomMessageData {
|
|
141
|
+
interface CustomMessageData extends LinktreeMessageData {
|
|
161
142
|
/**
|
|
162
143
|
* Message metadata from backend.
|
|
163
144
|
* Contains type and payment information.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "3.31.1
|
|
3
|
+
"version": "3.31.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,9 +50,10 @@
|
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@linktr.ee/component-library": "11.8.6",
|
|
53
|
-
"@linktr.ee/messaging-core": "2.
|
|
54
|
-
"@linktr.ee/messaging-taxonomy": "^0.5.
|
|
55
|
-
"@phosphor-icons/react": "^2.1.10"
|
|
53
|
+
"@linktr.ee/messaging-core": "^2.2.0",
|
|
54
|
+
"@linktr.ee/messaging-taxonomy": "^0.5.1",
|
|
55
|
+
"@phosphor-icons/react": "^2.1.10",
|
|
56
|
+
"culori": "^4.0.2"
|
|
56
57
|
},
|
|
57
58
|
"devDependencies": {
|
|
58
59
|
"@storybook/addon-essentials": "^8.5.0",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"@testing-library/jest-dom": "^6.1.5",
|
|
67
68
|
"@testing-library/react": "^14.1.2",
|
|
68
69
|
"@testing-library/user-event": "^14.5.1",
|
|
70
|
+
"@types/culori": "^4",
|
|
69
71
|
"@types/react": "^18.3.24",
|
|
70
72
|
"@types/react-dom": "^18.3.7",
|
|
71
73
|
"@vitejs/plugin-react": "^4.2.1",
|
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
import type { Meta, StoryFn } from '@storybook/react'
|
|
8
8
|
import React from 'react'
|
|
9
9
|
|
|
10
|
+
import { accentSurface } from './components/_shared/accentSurface'
|
|
10
11
|
import type { LinkAttachmentVariant } from './components/_shared/CardShell'
|
|
11
|
-
import {
|
|
12
|
+
import { BUBBLE_BG_BY_VARIANT, inkFor } from './components/_shared/chinContrast'
|
|
12
13
|
|
|
13
14
|
import LinkAttachment from '.'
|
|
14
15
|
|
|
@@ -387,9 +388,9 @@ const AUDIO_PREVIEW = {
|
|
|
387
388
|
* player. The artwork is still handed over as `thumbnailUrl` and there is
|
|
388
389
|
* still no `<img>`, so `onImageError` can never clear the accent — the gate
|
|
389
390
|
* has to be up front. These three cells are the regression that prompted the
|
|
390
|
-
* rule:
|
|
391
|
-
* purple card would ship around a native player with the
|
|
392
|
-
* sight.
|
|
391
|
+
* rule: `accentStyle` is an inline `style` and outranks the `bg-[#F2F3F4]`
|
|
392
|
+
* audio fill, so a purple card would ship around a native player with the
|
|
393
|
+
* artwork nowhere in sight.
|
|
393
394
|
* - **No `thumbnailUrl`** leaves the type-icon placeholder, which is not an
|
|
394
395
|
* image the accent could have come from. Stream reaches this whenever a link
|
|
395
396
|
* preview carries a playable clip but no `image_url`.
|
|
@@ -461,8 +462,8 @@ AccentWithoutVisibleHero.parameters = { chromatic: { disableSnapshot: false } }
|
|
|
461
462
|
|
|
462
463
|
/**
|
|
463
464
|
* Max-chroma sRGB colour for a hue — three phase-shifted cosines, so stepping
|
|
464
|
-
* the hue walks the wheel at the most saturated triple each hue has.
|
|
465
|
-
* the
|
|
465
|
+
* the hue walks the wheel at the most saturated triple each hue has. Mirrors
|
|
466
|
+
* the generator `accentSurface.test.ts` sweeps.
|
|
466
467
|
*/
|
|
467
468
|
const vividHex = (hue: number): string =>
|
|
468
469
|
`#${[0, 120, 240]
|
|
@@ -478,7 +479,7 @@ const HUE_SWEEP = Array.from({ length: 24 }, (_, i) => ({
|
|
|
478
479
|
accentColor: vividHex(i * 15),
|
|
479
480
|
}))
|
|
480
481
|
|
|
481
|
-
// Neutrals and near-limits where the
|
|
482
|
+
// Neutrals and near-limits where the AA gate and the ink flip actually decide
|
|
482
483
|
// something, plus the two inputs that must never derive a surface.
|
|
483
484
|
const EDGE_SAMPLES: Array<{ label: string; accentColor?: string }> = [
|
|
484
485
|
{ label: 'mock #738883', accentColor: '#738883' },
|
|
@@ -491,25 +492,13 @@ const EDGE_SAMPLES: Array<{ label: string; accentColor?: string }> = [
|
|
|
491
492
|
{ label: 'malformed', accentColor: 'rebeccapurple' },
|
|
492
493
|
]
|
|
493
494
|
|
|
494
|
-
// Ink is fixed per side — white on the dark stop, near-black on the pale stop.
|
|
495
|
-
const INK_BY_VARIANT: Record<LinkAttachmentVariant, string> = {
|
|
496
|
-
dark: '#FFFFFF',
|
|
497
|
-
light: '#040403',
|
|
498
|
-
}
|
|
499
|
-
// The plain bubble fill each side falls back to, as a self-contained arbitrary
|
|
500
|
-
// value (mirrors `CardShell`). The accent rule in `styles.css` paints over it.
|
|
501
|
-
const FALLBACK_BG_CLASS: Record<LinkAttachmentVariant, string> = {
|
|
502
|
-
dark: 'bg-[#1e2330]',
|
|
503
|
-
light: 'bg-[#f1f0ee]',
|
|
504
|
-
}
|
|
505
|
-
|
|
506
495
|
/**
|
|
507
|
-
* One derivation cell
|
|
508
|
-
*
|
|
509
|
-
*
|
|
510
|
-
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
496
|
+
* One derivation cell: the surface the card would paint for this accent and
|
|
497
|
+
* variant, drawn in the ink `inkFor` picks against it. `accentSurface` and
|
|
498
|
+
* `inkFor` are the only outputs `culori` feeds into the card, so this paints
|
|
499
|
+
* exactly what a colour-library swap could move — with the resolved hex printed
|
|
500
|
+
* so a shift also reads as a text diff. A refused accent shows the variant's
|
|
501
|
+
* fallback fill, tagged.
|
|
513
502
|
*/
|
|
514
503
|
const DerivedSwatch = ({
|
|
515
504
|
accentColor,
|
|
@@ -518,23 +507,17 @@ const DerivedSwatch = ({
|
|
|
518
507
|
accentColor?: string
|
|
519
508
|
variant: LinkAttachmentVariant
|
|
520
509
|
}) => {
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
? toCssHex(accentColor)
|
|
524
|
-
: undefined
|
|
510
|
+
const surface = accentSurface(accentColor, variant)
|
|
511
|
+
const painted = surface ?? BUBBLE_BG_BY_VARIANT[variant]
|
|
525
512
|
return (
|
|
526
513
|
<div
|
|
527
|
-
className=
|
|
528
|
-
|
|
529
|
-
style={
|
|
530
|
-
{
|
|
531
|
-
color: INK_BY_VARIANT[variant],
|
|
532
|
-
...(accentHex ? { '--accent': accentHex } : {}),
|
|
533
|
-
} as React.CSSProperties
|
|
534
|
-
}
|
|
514
|
+
className="flex h-12 flex-col justify-between rounded-md px-2 py-1.5 text-[11px] font-medium"
|
|
515
|
+
style={{ backgroundColor: painted, color: inkFor(painted) }}
|
|
535
516
|
>
|
|
536
517
|
<span>Yoga on Ice</span>
|
|
537
|
-
<span className="font-mono opacity-80">
|
|
518
|
+
<span className="font-mono opacity-80">
|
|
519
|
+
{surface ?? `fallback ${painted}`}
|
|
520
|
+
</span>
|
|
538
521
|
</div>
|
|
539
522
|
)
|
|
540
523
|
}
|
|
@@ -544,12 +527,12 @@ const GridHead = ({ children }: { children: React.ReactNode }) => (
|
|
|
544
527
|
)
|
|
545
528
|
|
|
546
529
|
/**
|
|
547
|
-
* Isolates the accent
|
|
548
|
-
* neutral/edge input, the `dark` (Sent) and `light` (Received)
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
*
|
|
530
|
+
* Isolates the colour maths behind the accent chin: for every hue at maximum
|
|
531
|
+
* chroma and every neutral/edge input, the `dark` (Sent) and `light` (Received)
|
|
532
|
+
* surfaces `accentSurface` derives, each in the ink `inkFor` returns for it.
|
|
533
|
+
* One snapshot, no hero pixels — a dense visual companion to the
|
|
534
|
+
* `accentSurface` hue-sweep unit test, so any drift in the derivation shows up
|
|
535
|
+
* as both a colour change and a hex text diff across adjacent rows.
|
|
553
536
|
*/
|
|
554
537
|
export const AccentDerivation: StoryFn = () => (
|
|
555
538
|
<div className="min-h-screen w-full bg-[#F9F7F4] p-8">
|
|
@@ -119,33 +119,28 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
119
119
|
thumbnailUrl: 'https://cdn.example.com/yoga.jpg',
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// no visible hero), leaving the surface derivation to the browser. Pixel
|
|
126
|
-
// parity is covered by the `AccentDerivation` Chromatic story (MES-1412).
|
|
127
|
-
const accentOf = (element: HTMLElement) =>
|
|
128
|
-
element.style.getPropertyValue('--accent')
|
|
129
|
-
|
|
130
|
-
it('publishes the accent for the sender and inks the chin white', async () => {
|
|
122
|
+
const surfaceOf = (element: HTMLElement) => element.style.backgroundColor
|
|
123
|
+
|
|
124
|
+
it('derives a dark surface for the sender and inks it white', async () => {
|
|
131
125
|
renderWithProviders(
|
|
132
126
|
<LinkAttachment.Sent {...CARD_PROPS} accentColor={ACCENT} />
|
|
133
127
|
)
|
|
134
128
|
const root = await screen.findByRole('link')
|
|
135
|
-
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
expect(root.
|
|
129
|
+
// Not the raw accent: the hue is kept, the lightness is replaced with the
|
|
130
|
+
// stop this side's ink can clear. Painting the extracted colour verbatim is
|
|
131
|
+
// what MES-1388 was reported for.
|
|
132
|
+
expect(surfaceOf(root)).not.toBe('')
|
|
133
|
+
expect(root).not.toHaveStyle({ backgroundColor: ACCENT })
|
|
139
134
|
expect(screen.getByText(CARD_PROPS.title)).toHaveStyle({ color: '#FFFFFF' })
|
|
140
135
|
})
|
|
141
136
|
|
|
142
|
-
it('
|
|
137
|
+
it('derives a pale surface for the receiver and inks it near-black', async () => {
|
|
143
138
|
renderWithProviders(
|
|
144
139
|
<LinkAttachment.Received {...CARD_PROPS} accentColor={ACCENT} />
|
|
145
140
|
)
|
|
146
141
|
const root = await screen.findByRole('link')
|
|
147
|
-
expect(
|
|
148
|
-
expect(root.
|
|
142
|
+
expect(surfaceOf(root)).not.toBe('')
|
|
143
|
+
expect(root).not.toHaveStyle({ backgroundColor: ACCENT })
|
|
149
144
|
expect(screen.getByText(CARD_PROPS.title)).toHaveStyle({ color: '#040403' })
|
|
150
145
|
})
|
|
151
146
|
|
|
@@ -156,13 +151,13 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
156
151
|
const sent = renderWithProviders(
|
|
157
152
|
<LinkAttachment.Sent {...CARD_PROPS} accentColor={accentColor} />
|
|
158
153
|
)
|
|
159
|
-
expect(
|
|
154
|
+
expect(surfaceOf(await screen.findByRole('link'))).not.toBe('')
|
|
160
155
|
sent.unmount()
|
|
161
156
|
|
|
162
157
|
const received = renderWithProviders(
|
|
163
158
|
<LinkAttachment.Received {...CARD_PROPS} accentColor={accentColor} />
|
|
164
159
|
)
|
|
165
|
-
expect(
|
|
160
|
+
expect(surfaceOf(await screen.findByRole('link'))).not.toBe('')
|
|
166
161
|
received.unmount()
|
|
167
162
|
}
|
|
168
163
|
})
|
|
@@ -170,7 +165,7 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
170
165
|
it('falls back to a self-contained fill, not a consumer theme token', async () => {
|
|
171
166
|
renderWithProviders(<LinkAttachment.Sent {...CARD_PROPS} />)
|
|
172
167
|
const root = await screen.findByRole('link')
|
|
173
|
-
expect(
|
|
168
|
+
expect(surfaceOf(root)).toBe('')
|
|
174
169
|
|
|
175
170
|
// The fill has to survive in consumers that do not share our Tailwind
|
|
176
171
|
// theme. This package ships no utilities, so consumers generate our classes
|
|
@@ -191,8 +186,8 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
191
186
|
<LinkAttachment.Sent {...CARD_PROPS} accentColor="rebeccapurple" />
|
|
192
187
|
)
|
|
193
188
|
const root = await screen.findByRole('link')
|
|
194
|
-
expect(
|
|
195
|
-
// Ink still comes from the
|
|
189
|
+
expect(root.style.backgroundColor).toBe('')
|
|
190
|
+
// Ink still comes from the surface — here the sender's fallback fill.
|
|
196
191
|
expect(screen.getByText(CARD_PROPS.title)).toHaveStyle({ color: '#FFFFFF' })
|
|
197
192
|
})
|
|
198
193
|
|
|
@@ -201,12 +196,12 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
201
196
|
<LinkAttachment.Sent {...CARD_PROPS} accentColor={ACCENT} />
|
|
202
197
|
)
|
|
203
198
|
const root = await screen.findByRole('link')
|
|
204
|
-
expect(
|
|
199
|
+
expect(root.style.backgroundColor).not.toBe('')
|
|
205
200
|
|
|
206
201
|
// A surface derived from an image the viewer cannot see is a lie, so the
|
|
207
202
|
// card falls all the way back rather than keeping a colour with no source.
|
|
208
203
|
fireEvent.error(screen.getByRole('img'))
|
|
209
|
-
expect(
|
|
204
|
+
expect(root.style.backgroundColor).toBe('')
|
|
210
205
|
expect(screen.getByText(CARD_PROPS.title)).toHaveStyle({ color: '#FFFFFF' })
|
|
211
206
|
})
|
|
212
207
|
|
|
@@ -227,12 +222,12 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
227
222
|
const shell = container.querySelector(
|
|
228
223
|
'[class*="select-none"]'
|
|
229
224
|
) as HTMLElement
|
|
230
|
-
expect(shell.style.
|
|
225
|
+
expect(shell.style.backgroundColor).not.toBe('')
|
|
231
226
|
expect(container.querySelector('video')).toHaveAttribute('poster')
|
|
232
227
|
|
|
233
228
|
fireEvent.error(container.querySelector('img') as HTMLImageElement)
|
|
234
229
|
|
|
235
|
-
expect(shell.style.
|
|
230
|
+
expect(shell.style.backgroundColor).toBe('')
|
|
236
231
|
expect(screen.getByText(CARD_PROPS.title)).toHaveStyle({ color: '#FFFFFF' })
|
|
237
232
|
// The failed poster is dropped with the accent, so the player falls back to
|
|
238
233
|
// its own first frame instead of retrying a 404.
|
|
@@ -257,7 +252,7 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
257
252
|
<LinkAttachment.Sent {...CARD_PROPS} {...AUDIO} accentColor={ACCENT} />
|
|
258
253
|
)
|
|
259
254
|
const sent = container.firstElementChild as HTMLElement
|
|
260
|
-
expect(sent.style.
|
|
255
|
+
expect(sent.style.backgroundColor).toBe('')
|
|
261
256
|
expect(sent.className).toContain(AUDIO_BG_CLASS)
|
|
262
257
|
expect(screen.getByText(CARD_PROPS.title)).toHaveStyle({ color: '#040403' })
|
|
263
258
|
expect(screen.queryByRole('img')).toBeNull()
|
|
@@ -271,7 +266,7 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
271
266
|
/>
|
|
272
267
|
)
|
|
273
268
|
const received = await screen.findByTestId('link-attachment')
|
|
274
|
-
expect(received.style.
|
|
269
|
+
expect(received.style.backgroundColor).toBe('')
|
|
275
270
|
expect(received.className).toContain(AUDIO_BG_CLASS)
|
|
276
271
|
})
|
|
277
272
|
|
|
@@ -286,27 +281,29 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
286
281
|
/>
|
|
287
282
|
)
|
|
288
283
|
const root = await screen.findByRole('link')
|
|
289
|
-
expect(
|
|
284
|
+
expect(root.style.backgroundColor).toBe('')
|
|
290
285
|
// Variant fill, asserted by shape not literal — see the self-contained-fill
|
|
291
286
|
// test above for why this must stay an arbitrary value (MES-1388).
|
|
292
287
|
expect(root.className).toMatch(/bg-\[/)
|
|
293
288
|
expect(screen.getByText(CARD_PROPS.title)).toHaveStyle({ color: '#FFFFFF' })
|
|
294
289
|
})
|
|
295
290
|
|
|
296
|
-
it('normalizes a hashless accent so the
|
|
291
|
+
it('normalizes a hashless accent so the background actually paints', async () => {
|
|
297
292
|
// The extractor returns a bare hex often enough that dropping it would lose
|
|
298
|
-
// the colour silently — `
|
|
299
|
-
// the published `--accent` must carry the `#`.
|
|
293
|
+
// the colour silently — `background-color: 7008E7` is invalid CSS (MES-1349).
|
|
300
294
|
const hashless = renderWithProviders(
|
|
301
295
|
<LinkAttachment.Sent {...CARD_PROPS} accentColor="7008E7" />
|
|
302
296
|
)
|
|
303
|
-
|
|
297
|
+
const derived = (await screen.findByRole('link')).style.backgroundColor
|
|
298
|
+
expect(derived).not.toBe('')
|
|
304
299
|
hashless.unmount()
|
|
305
300
|
|
|
306
301
|
renderWithProviders(
|
|
307
302
|
<LinkAttachment.Sent {...CARD_PROPS} accentColor={ACCENT} />
|
|
308
303
|
)
|
|
309
|
-
expect(
|
|
304
|
+
expect((await screen.findByRole('link')).style.backgroundColor).toBe(
|
|
305
|
+
derived
|
|
306
|
+
)
|
|
310
307
|
})
|
|
311
308
|
|
|
312
309
|
it('lets a loading status outrank the audio hero on all three cards', () => {
|
|
@@ -352,15 +349,13 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
352
349
|
groupPosition="end"
|
|
353
350
|
/>
|
|
354
351
|
)
|
|
355
|
-
// The tail paints outside the shell's own box, so it
|
|
356
|
-
//
|
|
357
|
-
//
|
|
352
|
+
// The tail paints outside the shell's own box, so it takes the surface as an
|
|
353
|
+
// inline colour — it has to be the *derived* one or the tail and the card
|
|
354
|
+
// would disagree.
|
|
358
355
|
const card = await screen.findByRole('link')
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
)
|
|
363
|
-
expect(tail.style.getPropertyValue('--accent')).toBe(ACCENT)
|
|
356
|
+
expect(screen.getByTestId('link-card-tail')).toHaveStyle({
|
|
357
|
+
backgroundColor: card.style.backgroundColor,
|
|
358
|
+
})
|
|
364
359
|
unmount()
|
|
365
360
|
|
|
366
361
|
// Mid-run the following bubble owns the tail.
|
|
@@ -375,13 +370,10 @@ describe('LinkAttachment accent chin (MES-1349, MES-1388)', () => {
|
|
|
375
370
|
})
|
|
376
371
|
|
|
377
372
|
it('falls back to the variant fill for the tail when there is no accent', async () => {
|
|
378
|
-
// No accent published, so no `--accent`: the tail's `[data-variant='dark']`
|
|
379
|
-
// rule in styles.css paints the sender's plain `#1e2330` bubble fill (the
|
|
380
|
-
// computed value isn't observable in jsdom — see the note on `accentOf`).
|
|
381
373
|
renderWithProviders(<LinkAttachment.Sent {...CARD_PROPS} />)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
374
|
+
expect(await screen.findByTestId('link-card-tail')).toHaveStyle({
|
|
375
|
+
backgroundColor: '#1e2330',
|
|
376
|
+
})
|
|
385
377
|
})
|
|
386
378
|
|
|
387
379
|
it('exposes the loading skeleton as a labelled busy region', async () => {
|
|
@@ -130,7 +130,7 @@ const ComposerCard: React.FC<ComposerCardProps> = ({
|
|
|
130
130
|
<CardShell
|
|
131
131
|
variant="dark"
|
|
132
132
|
fixedHeight={!isClassic}
|
|
133
|
-
|
|
133
|
+
accentStyle={chin.accentStyle}
|
|
134
134
|
busy={chin.isLoading}
|
|
135
135
|
topRight={isClassic ? dismissButton : undefined}
|
|
136
136
|
>
|
|
@@ -108,7 +108,7 @@ const ReceivedCard: React.FC<ReceivedCardProps> = ({
|
|
|
108
108
|
|
|
109
109
|
return (
|
|
110
110
|
<BubbleTail
|
|
111
|
-
|
|
111
|
+
color={chin.tailColor}
|
|
112
112
|
side="receiver"
|
|
113
113
|
// Only the last card in a same-author run carries the tail; the audio
|
|
114
114
|
// player card is not a bubble, so it never does.
|
|
@@ -123,7 +123,7 @@ const ReceivedCard: React.FC<ReceivedCardProps> = ({
|
|
|
123
123
|
onClick={shellOnClick}
|
|
124
124
|
ariaLabel={title ?? 'Open attachment preview'}
|
|
125
125
|
fixedHeight={layout === 'featured' && audioBg == null}
|
|
126
|
-
|
|
126
|
+
accentStyle={chin.accentStyle}
|
|
127
127
|
busy={chin.isLoading}
|
|
128
128
|
bgClassName={audioBg}
|
|
129
129
|
data-testid="link-attachment"
|
|
@@ -92,7 +92,7 @@ const SentCard: React.FC<SentCardProps> = ({
|
|
|
92
92
|
|
|
93
93
|
return (
|
|
94
94
|
<BubbleTail
|
|
95
|
-
|
|
95
|
+
color={chin.tailColor}
|
|
96
96
|
side="sender"
|
|
97
97
|
// Only the last card in a same-author run carries the tail; the audio
|
|
98
98
|
// player card is not a bubble, so it never does.
|
|
@@ -105,7 +105,7 @@ const SentCard: React.FC<SentCardProps> = ({
|
|
|
105
105
|
href={shellHref}
|
|
106
106
|
onClick={shellOnClick}
|
|
107
107
|
fixedHeight={layout === 'featured' && !isAudio}
|
|
108
|
-
|
|
108
|
+
accentStyle={chin.accentStyle}
|
|
109
109
|
busy={chin.isLoading}
|
|
110
110
|
bgClassName={isAudio ? AUDIO_BG_CLASS : undefined}
|
|
111
111
|
>
|
|
@@ -3,13 +3,11 @@ import React from 'react'
|
|
|
3
3
|
|
|
4
4
|
export interface BubbleTailProps {
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* shell's box, so it can't inherit the shell's computed background — it runs
|
|
10
|
-
* the derivation itself off its own `--accent` (MES-1412).
|
|
6
|
+
* Hex the tail paints in — the card's accent colour, or its variant
|
|
7
|
+
* fallback fill. Inline rather than a class because the accent is a
|
|
8
|
+
* runtime value extracted from the attachment image.
|
|
11
9
|
*/
|
|
12
|
-
|
|
10
|
+
color: string
|
|
13
11
|
/** Sender tails hang off the right edge, receiver tails mirror to the left. */
|
|
14
12
|
side: 'sender' | 'receiver'
|
|
15
13
|
/**
|
|
@@ -32,20 +30,13 @@ export interface BubbleTailProps {
|
|
|
32
30
|
* tinted from the text-bubble surface would clash with a coloured card.
|
|
33
31
|
*/
|
|
34
32
|
const BubbleTail: React.FC<BubbleTailProps> = ({
|
|
35
|
-
|
|
33
|
+
color,
|
|
36
34
|
side,
|
|
37
35
|
visible,
|
|
38
36
|
children,
|
|
39
37
|
}) => {
|
|
40
38
|
if (!visible) return <>{children}</>
|
|
41
39
|
|
|
42
|
-
// Sender tails sit on the dark stop, receiver tails on the pale one — the
|
|
43
|
-
// same mapping the cards use (`SentCard` is `dark`, `ReceivedCard` `light`).
|
|
44
|
-
const variant = side === 'sender' ? 'dark' : 'light'
|
|
45
|
-
const accentStyle = accentHex
|
|
46
|
-
? ({ '--accent': accentHex } as React.CSSProperties)
|
|
47
|
-
: undefined
|
|
48
|
-
|
|
49
40
|
return (
|
|
50
41
|
// `isolate` keeps the tail's negative z-index from escaping behind the
|
|
51
42
|
// thread background; `w-fit` stops the wrapper from stretching in the
|
|
@@ -55,8 +46,7 @@ const BubbleTail: React.FC<BubbleTailProps> = ({
|
|
|
55
46
|
<span
|
|
56
47
|
aria-hidden
|
|
57
48
|
data-testid="link-card-tail"
|
|
58
|
-
|
|
59
|
-
style={accentStyle}
|
|
49
|
+
style={{ backgroundColor: color }}
|
|
60
50
|
className={classNames(
|
|
61
51
|
'messaging-link-card-tail',
|
|
62
52
|
side === 'receiver' && 'messaging-link-card-tail--received'
|
|
@@ -15,10 +15,11 @@ export interface CardBodyProps {
|
|
|
15
15
|
/** Footer URL shown below the description. Ignored when `cta` is set. */
|
|
16
16
|
url?: string
|
|
17
17
|
/**
|
|
18
|
-
* Ink for both chin lines, from `
|
|
19
|
-
*
|
|
20
|
-
* variant's own `text-*` classes
|
|
21
|
-
* card whose surface is not the
|
|
18
|
+
* Ink for both chin lines, from `chinContrast.inkFor` — measured against the
|
|
19
|
+
* surface this card actually paints, which may be a derived accent, the audio
|
|
20
|
+
* strip, or the variant fallback. Replaces the variant's own `text-*` classes
|
|
21
|
+
* (including their opacity) on both lines, so a card whose surface is not the
|
|
22
|
+
* variant default still reads (MES-1388).
|
|
22
23
|
*/
|
|
23
24
|
chinTextColor?: string
|
|
24
25
|
/**
|