@linktr.ee/messaging-react 3.31.1 → 3.31.2-rc-1785926578

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/{Card-EWN07XIl.cjs → Card-CF1TZXD5.cjs} +2 -2
  2. package/dist/{Card-EWN07XIl.cjs.map → Card-CF1TZXD5.cjs.map} +1 -1
  3. package/dist/{Card-CmlaSw7i.js → Card-CqYFVF6m.js} +2 -2
  4. package/dist/{Card-CmlaSw7i.js.map → Card-CqYFVF6m.js.map} +1 -1
  5. package/dist/assets/index.css +1 -1
  6. package/dist/index-Ct_Jgy8P.cjs +2 -0
  7. package/dist/index-Ct_Jgy8P.cjs.map +1 -0
  8. package/dist/index-ilHx7pIO.js +5672 -0
  9. package/dist/index-ilHx7pIO.js.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.ts +6 -5
  12. package/dist/index.js +1 -1
  13. package/package.json +3 -5
  14. package/src/components/LinkAttachment/LinkAttachment.stories.tsx +42 -27
  15. package/src/components/LinkAttachment/LinkAttachment.test.tsx +46 -39
  16. package/src/components/LinkAttachment/components/Composer/Card.tsx +1 -1
  17. package/src/components/LinkAttachment/components/Received/Card.tsx +2 -2
  18. package/src/components/LinkAttachment/components/Sent/Card.tsx +2 -2
  19. package/src/components/LinkAttachment/components/_shared/BubbleTail.tsx +16 -6
  20. package/src/components/LinkAttachment/components/_shared/CardBody.tsx +4 -5
  21. package/src/components/LinkAttachment/components/_shared/CardShell.tsx +27 -19
  22. package/src/components/LinkAttachment/components/_shared/hex.ts +10 -12
  23. package/src/components/LinkAttachment/components/_shared/useChinPalette.ts +45 -48
  24. package/src/components/LinkAttachment/types.ts +6 -5
  25. package/src/styles.css +41 -4
  26. package/dist/index-CN3errpB.js +0 -7828
  27. package/dist/index-CN3errpB.js.map +0 -1
  28. package/dist/index-CcitzVoW.cjs +0 -5
  29. package/dist/index-CcitzVoW.cjs.map +0 -1
  30. package/src/components/LinkAttachment/components/_shared/accentSurface.test.ts +0 -135
  31. package/src/components/LinkAttachment/components/_shared/accentSurface.ts +0 -95
  32. package/src/components/LinkAttachment/components/_shared/chinContrast.test.ts +0 -95
  33. package/src/components/LinkAttachment/components/_shared/chinContrast.ts +0 -79
@@ -1,25 +1,23 @@
1
1
  /**
2
- * The hex contract shared by `chinContrast` and `accentSurface`.
2
+ * The hex contract for the accent colour `useChinPalette` publishes.
3
3
  *
4
- * `culori` does the parsing, and it accepts every CSS colour — including names
5
- * like `rebeccapurple` and formats with alpha. Every colour reaching these two
6
- * modules comes from `getDominantColorFromImageUrl` via a Stream attachment
7
- * field, or from a constant in this directory, so both narrow the input to a hex
8
- * before handing it over rather than widening their contract by accident.
4
+ * The extracted `accent_color` (from `getDominantColorFromImageUrl` via a Stream
5
+ * attachment field) is always a hex, but CSS `oklch(from …)` would resolve any
6
+ * colour name including `rebeccapurple` so the input is narrowed to a hex
7
+ * before it is published as `--accent`, rather than widening that contract by
8
+ * accident.
9
9
  */
10
10
 
11
11
  /**
12
12
  * `#abc`, `#aabbcc`, or either without the leading `#`.
13
13
  *
14
- * The missing hash is deliberate: the extractor returns bare hexes often enough
15
- * that rejecting them would silently drop the colour. It is also why `toCssHex`
16
- * exists — `background-color: 738883` is invalid and the DOM drops it, which is
17
- * how a card once rendered unaccented while every calculation behind it had
18
- * succeeded (MES-1349).
14
+ * The optional hash is deliberate: the extractor returns bare hexes often
15
+ * enough that rejecting them would drop the colour. It is also why `toCssHex`
16
+ * exists — `738883` without the `#` is not a valid CSS colour.
19
17
  */
20
18
  export const HEX_PATTERN = /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i
21
19
 
22
- /** Normalizes a hex for CSS and for culori, adding the `#` the extractor omits. */
20
+ /** Normalizes a hex for CSS, adding the `#` the extractor omits. */
23
21
  export function toCssHex(hex: string): string {
24
22
  const trimmed = hex.trim()
25
23
  return trimmed.startsWith('#') ? trimmed : `#${trimmed}`
@@ -5,10 +5,19 @@ import React from 'react'
5
5
 
6
6
  import type { LinkAttachmentLayout, LinkAttachmentStatus } from '../../types'
7
7
 
8
- import { accentSurface } from './accentSurface'
9
8
  import type { LinkAttachmentVariant } from './CardShell'
10
- import { AUDIO_BG, isPlayableAudio } from './CardThumbnail'
11
- import { BUBBLE_BG_BY_VARIANT, inkFor } from './chinContrast'
9
+ import { isPlayableAudio } from './CardThumbnail'
10
+ import { HEX_PATTERN, toCssHex } from './hex'
11
+
12
+ /** White on the dark stop, near-black on the light stop and the audio strip. */
13
+ const INK_ON_DARK = '#FFFFFF'
14
+ /** The near-black used across the Figma frames. */
15
+ const INK_ON_LIGHT = '#040403'
16
+
17
+ const INK_BY_VARIANT: Record<LinkAttachmentVariant, string> = {
18
+ dark: INK_ON_DARK,
19
+ light: INK_ON_LIGHT,
20
+ }
12
21
 
13
22
  export interface ChinPaletteInput {
14
23
  variant: LinkAttachmentVariant
@@ -31,21 +40,17 @@ export interface ChinPalette {
31
40
  */
32
41
  thumbnailUrl?: string
33
42
  /**
34
- * Inline background for the card shell, or `undefined` to keep the variant's
35
- * own `bg-*` class.
36
- */
37
- accentStyle?: React.CSSProperties
38
- /**
39
- * Ink for both chin lines, chosen against the surface the card actually
40
- * paints — the derived accent, the audio strip, or the fallback fill. Always
41
- * defined for a parseable surface.
43
+ * Raw extracted colour to publish as the `--accent` custom property on the
44
+ * shell and the tail, or `undefined` when there is no paintable accent. The
45
+ * card keeps its hue and swaps the lightness in CSS (`styles.css`).
42
46
  */
43
- chinTextColor?: string
47
+ accentHex?: string
44
48
  /**
45
- * Colour the bubble tail paints in, always defined: the accent when one is
46
- * paintable, otherwise this side's plain bubble fill.
49
+ * Ink for both chin lines: white on the sender's dark stop, near-black on the
50
+ * receiver's pale stop and on the audio strip. Fixed per surface — the stops
51
+ * clear AA at every hue.
47
52
  */
48
- tailColor: string
53
+ chinTextColor: string
49
54
  /** True while the card renders as a skeleton. */
50
55
  isLoading: boolean
51
56
  /** Hand to `CardThumbnail` so a broken hero drops the accent with it. */
@@ -53,33 +58,25 @@ export interface ChinPalette {
53
58
  }
54
59
 
55
60
  /**
56
- * Resolves a link card's accent-derived colours and tracks hero-image failure.
61
+ * Resolves the accent a link card publishes and tracks hero-image failure.
57
62
  *
58
63
  * The accent is a claim about an image the viewer can see, so it only paints
59
- * when `CardThumbnail` actually mounts that image. Mirroring its branches
60
- * (`CardThumbnail.tsx:98-160`), the hero is *not* the accent's source when:
64
+ * when `CardThumbnail` actually mounts that image. Mirroring its branches, the
65
+ * hero is *not* the accent's source when:
61
66
  *
62
67
  * - `layout: 'classic'` — there is no hero region at all;
63
- * - `status` is not `'ready'` — the hero is the pulsing skeleton and there is
64
- * no image yet;
68
+ * - `status` is not `'ready'` — the hero is the pulsing skeleton, no image yet;
65
69
  * - `thumbnailUrl` is absent — the hero is the type-icon placeholder, or for a
66
70
  * playable video a bare `bg-black` box with no poster;
67
71
  * - the attachment is playable audio — the hero collapses to the native
68
- * `<audio>` player and the `<img>` never mounts *even though `thumbnailUrl`
69
- * is set*, so `onImageError` can never rescue this one. Left ungated, an
70
- * artwork-backed audio link preview shipped a coloured card with no visible
71
- * source image, and the accent beat `AUDIO_BG_CLASS` on Sent / Received
72
- * because `CardShell` applies it as an inline `style`, which outranks any
73
- * `bg-*` class. `ComposerCard` already dodged that by returning early before
74
- * it passes `accentStyle`; this is that same rule, stated once instead of
75
- * three times (MES-1349);
72
+ * `<audio>` player and the `<img>` never mounts even though `thumbnailUrl` is
73
+ * set, so `onImageError` can never rescue it and the gate has to be up front;
76
74
  * - the image 404s — an accent extracted from something the viewer cannot see.
77
75
  * `onImageError` covers both hero shapes: the `<img>` reports directly, and a
78
76
  * playable video's `poster` (which raises no event of its own) is verified by
79
77
  * the hidden probe image in `CardThumbnail`.
80
78
  *
81
- * Every case falls back to the variant's own fill, which already pairs with
82
- * the variant's own text colours.
79
+ * Every excluded case publishes no accent and keeps the variant's own fill.
83
80
  */
84
81
  export function useChinPalette({
85
82
  variant,
@@ -105,32 +102,32 @@ export function useChinPalette({
105
102
  // Truthiness rather than `!= null` so this tracks `CardThumbnail`'s own
106
103
  // `thumbnailUrl ?` branch, which renders the placeholder for `''` too.
107
104
  //
108
- // Playable audio stays excluded, as MES-1349 decided: its hero collapses to
109
- // the native `<audio>` player, so the artwork never appears and an accent
110
- // taken from it would be a claim about an image the viewer cannot see. The
111
- // `<img>` never mounts either, so `onImageError` could not withdraw the colour
112
- // if the artwork 404'd. Colouring audio cards from their artwork is a change
113
- // to the audio tickets (MES-1360 / MES-1361), not to this one.
105
+ // Playable audio stays excluded: its hero collapses to the native `<audio>`
106
+ // player, so the artwork never appears and an accent taken from it would claim
107
+ // an image the viewer cannot see. The `<img>` never mounts either, so
108
+ // `onImageError` could not withdraw the colour if the artwork 404'd.
114
109
  const isAudio = isPlayableAudio(mimeType, sourceUrl)
115
110
  const hasHero =
116
111
  layout === 'featured' && !!thumbnailUrl && !isAudio && !heroFailed
117
- const surface =
118
- hasHero && status === 'ready'
119
- ? accentSurface(accentColor, variant)
112
+ const canDerive = hasHero && status === 'ready'
113
+ // Only a validated hex is published: the extracted `accent_color` is always a
114
+ // hex, and CSS would resolve any colour name, so narrow the input here. Bare
115
+ // hexes are accepted because the extractor omits the `#` often enough that
116
+ // rejecting them would silently lose the colour.
117
+ const accentHex =
118
+ canDerive && accentColor != null && HEX_PATTERN.test(accentColor.trim())
119
+ ? toCssHex(accentColor)
120
120
  : undefined
121
121
 
122
- // Whatever the card ends up painted with, legible or not, the ink is chosen
123
- // against it. The accent is derived to suit this side's ink, but the audio
124
- // strip and the fallback fill are imposed and cannot be refused — a sent
125
- // audio card put white ink on `AUDIO_BG`'s near-white at 1.13:1 (MES-1388).
126
- const painted =
127
- surface ?? (isAudio ? AUDIO_BG : BUBBLE_BG_BY_VARIANT[variant])
122
+ // The accent surface and the sender's dark fallback both take white ink; the
123
+ // pale receiver fallback and the imposed audio strip both take near-black so
124
+ // the ink is fixed per surface.
125
+ const chinTextColor = isAudio ? INK_ON_LIGHT : INK_BY_VARIANT[variant]
128
126
 
129
127
  return {
130
128
  thumbnailUrl: heroFailed ? undefined : thumbnailUrl,
131
- accentStyle: surface ? { backgroundColor: surface } : undefined,
132
- chinTextColor: inkFor(painted),
133
- tailColor: painted,
129
+ accentHex,
130
+ chinTextColor,
134
131
  isLoading: status === 'loading',
135
132
  onImageError,
136
133
  }
@@ -77,16 +77,17 @@ export interface LinkAttachmentBaseProps {
77
77
  /**
78
78
  * Dominant colour extracted from `thumbnailUrl`, as a hex string
79
79
  * (`#RRGGBB` or `#RGB`). Paints the card and — when the card is the last
80
- * bubble in a same-author run — its tail. The card derives its own chin
81
- * text colour from this value for WCAG AA contrast; callers never pass a
80
+ * bubble in a same-author run — its tail. Its hue is kept while the lightness
81
+ * is swapped to a fixed per-side stop (`oklch(from …)` in `styles.css`); the
82
+ * stops clear WCAG AA at every hue by construction, so callers never pass a
82
83
  * text colour.
83
84
  *
84
85
  * Only painted while the hero image it was extracted from is actually on
85
86
  * screen. Omitted, unparseable, `layout: 'classic'`, no `thumbnailUrl`,
86
87
  * `status` other than `'ready'`, a playable-audio `mimeType` + `sourceUrl`
87
- * (whose hero is the native player rather than an image), or a hero that
88
- * fails to load, or the colour cannot clear AA against this side's ink =>
89
- * the card falls back to `BUBBLE_BG_BY_VARIANT[variant]`, the plain bubble fill.
88
+ * (whose hero is the native player rather than an image), or a hero that fails
89
+ * to load => the card keeps the plain bubble fill. Browsers without
90
+ * relative-colour-syntax support also keep that fill (progressive enhancement).
90
91
  */
91
92
  accentColor?: string
92
93
 
package/src/styles.css CHANGED
@@ -1101,10 +1101,28 @@
1101
1101
  }
1102
1102
  }
1103
1103
 
1104
- /* Bubble tail for `LinkAttachment` cards (MES-1349). Same 20x23 geometry and
1105
- mask path as the text-bubble tails above the difference is that the fill
1106
- arrives as an inline style, because it is a dominant colour extracted from
1107
- the attachment image at send time and Tailwind cannot express a runtime hex.
1104
+ /* Derived accent surface for `LinkAttachment` cards. `--accent` is the raw
1105
+ colour extracted from the attachment image; the card keeps its hue and swaps
1106
+ the lightness to this side's `--surface-l` stop. Only paints where the browser
1107
+ resolves relative colour syntax and an accent is set otherwise the card's
1108
+ `bg-[…]` fallback fill shows through. The stops clear 4.5:1 against each side's
1109
+ ink at every hue. */
1110
+ .messaging-link-card[data-variant='dark'] {
1111
+ --surface-l: 0.3;
1112
+ }
1113
+ .messaging-link-card[data-variant='light'] {
1114
+ --surface-l: 0.9;
1115
+ }
1116
+
1117
+ @supports (background: oklch(from red l c h)) {
1118
+ .messaging-link-card[style*='--accent'] {
1119
+ background: oklch(from var(--accent) var(--surface-l) c h);
1120
+ }
1121
+ }
1122
+
1123
+ /* Bubble tail for `LinkAttachment` cards. Same 20x23 geometry and mask path as
1124
+ the text-bubble tails above; the fill is the card's accent surface, derived
1125
+ below from its own `--accent`.
1108
1126
 
1109
1127
  Hand-written here rather than as a toolkit-only Tailwind utility on purpose:
1110
1128
  `styles.css` ships as a built stylesheet that every consumer imports, whereas
@@ -1135,6 +1153,25 @@
1135
1153
  transform-origin: center;
1136
1154
  }
1137
1155
 
1156
+ /* The tail paints outside the shell's box, so it can't inherit the shell's
1157
+ computed background — it runs the same derivation itself off its own
1158
+ `--accent`, and falls back to this side's plain bubble fill. Keep these fills
1159
+ in sync with the shell's `bg-[…]` classes and the `--str-chat` bubble vars. */
1160
+ .messaging-link-card-tail[data-variant='dark'] {
1161
+ --surface-l: 0.3;
1162
+ background-color: #1e2330;
1163
+ }
1164
+ .messaging-link-card-tail[data-variant='light'] {
1165
+ --surface-l: 0.9;
1166
+ background-color: #f1f0ee;
1167
+ }
1168
+
1169
+ @supports (background: oklch(from red l c h)) {
1170
+ .messaging-link-card-tail[style*='--accent'] {
1171
+ background-color: oklch(from var(--accent) var(--surface-l) c h);
1172
+ }
1173
+ }
1174
+
1138
1175
  /* Locked attachment wrapper: stack card + text bubble in the correct direction */
1139
1176
  .str-chat__li .str-chat__message--me .str-chat__message-bubble-wrapper {
1140
1177
  display: flex;