@linktr.ee/messaging-react 3.34.0 → 3.35.0-rc-1786104411
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-D28R0tmP.js → Card-ByFbaqsR.js} +2 -2
- package/dist/{Card-D28R0tmP.js.map → Card-ByFbaqsR.js.map} +1 -1
- package/dist/{Card-CbxXZm7k.cjs → Card-C6IwpYii.cjs} +2 -2
- package/dist/{Card-CbxXZm7k.cjs.map → Card-C6IwpYii.cjs.map} +1 -1
- package/dist/{index-Czf2XyYL.js → index-BaeffiED.js} +1044 -857
- package/dist/index-BaeffiED.js.map +1 -0
- package/dist/index-DNWZLZ_A.cjs +2 -0
- package/dist/index-DNWZLZ_A.cjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +95 -0
- package/dist/index.js +21 -20
- package/package.json +3 -3
- package/src/components/LinkAttachment/components/_shared/CardCta.tsx +21 -2
- package/src/components/RichCard/RichCard.stories.tsx +364 -0
- package/src/components/RichCard/RichCard.test.tsx +116 -0
- package/src/components/RichCard/RichCardBody.tsx +123 -0
- package/src/components/RichCard/RichCardCard.tsx +93 -0
- package/src/components/RichCard/RichCardImages.tsx +108 -0
- package/src/components/RichCard/index.tsx +44 -0
- package/src/components/RichCard/types.ts +72 -0
- package/src/index.ts +8 -0
- package/dist/index-BrVCdobn.cjs +0 -2
- package/dist/index-BrVCdobn.cjs.map +0 -1
- package/dist/index-Czf2XyYL.js.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-DNWZLZ_A.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.RichCard=e.RichCard;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
|
@@ -18,6 +18,7 @@ import { MessagingUser } from '@linktr.ee/messaging-core';
|
|
|
18
18
|
import { OfficialCtaActionKind } from '@linktr.ee/messaging-taxonomy';
|
|
19
19
|
import { OfficialCtaActionPayload } from '@linktr.ee/messaging-taxonomy';
|
|
20
20
|
import { Provider } from 'react';
|
|
21
|
+
import { RichCardImagePresentation } from '@linktr.ee/messaging-taxonomy';
|
|
21
22
|
import { SendMessageAPIResponse } from 'stream-chat';
|
|
22
23
|
import { SetStateAction } from 'react';
|
|
23
24
|
import { StreamChat } from 'stream-chat';
|
|
@@ -1659,6 +1660,100 @@ export declare function resolveMediaFromMessage(message: LocalMessage): MediaMes
|
|
|
1659
1660
|
*/
|
|
1660
1661
|
export declare function resolveParticipantDisplayName(user?: ParticipantDisplayUser | null): string;
|
|
1661
1662
|
|
|
1663
|
+
/**
|
|
1664
|
+
* The generic rich message card (`linktree_rich_card`) — the shared render
|
|
1665
|
+
* primitive for rich message surfaces. Render `RichCard.Sent` for the sender's
|
|
1666
|
+
* own copy (dark chrome) and `RichCard.Received` in the recipient's thread
|
|
1667
|
+
* (light chrome). The official welcome card and broadcast link/collection cards
|
|
1668
|
+
* are all this one card at different knobs:
|
|
1669
|
+
*
|
|
1670
|
+
* - `hero` + `primary` action → the official welcome card (stacked button)
|
|
1671
|
+
* - `hero` + `secondary` action → a single-link broadcast (inline button)
|
|
1672
|
+
* - `fan` + `secondary` action → a collection broadcast
|
|
1673
|
+
*
|
|
1674
|
+
* A standalone renderer, not tied to Stream's `Attachment`: the dispatching
|
|
1675
|
+
* consumer narrows an attachment with the taxonomy's `isRichCard`, then maps
|
|
1676
|
+
* the taxonomy `RichCard` fields onto these props (resolving each action's
|
|
1677
|
+
* `kind` to an `href` / `onClick`).
|
|
1678
|
+
*/
|
|
1679
|
+
export declare const RichCard: {
|
|
1680
|
+
Sent: default_2.FC<RichCardBaseProps>;
|
|
1681
|
+
Received: default_2.FC<RichCardBaseProps>;
|
|
1682
|
+
};
|
|
1683
|
+
|
|
1684
|
+
/**
|
|
1685
|
+
* A single footer action button — the **presentational** half of a taxonomy
|
|
1686
|
+
* `RichCardAction`. The dispatching consumer resolves the action's `kind` to a
|
|
1687
|
+
* behaviour before handing it here: `open_external_url` → `href`; a `launch_*`
|
|
1688
|
+
* / `post_to_socials` kind → an `onClick` that opens its client surface. A kind
|
|
1689
|
+
* the consumer does not recognise is dropped (no button) rather than mapped, so
|
|
1690
|
+
* the card stands on its own image/title/subtitle — exactly the taxonomy's
|
|
1691
|
+
* unknown-kind rule.
|
|
1692
|
+
*/
|
|
1693
|
+
export declare interface RichCardActionButton {
|
|
1694
|
+
label: string;
|
|
1695
|
+
/** Renders the button as an `<a target="_blank">` (for `open_external_url`). */
|
|
1696
|
+
href?: string;
|
|
1697
|
+
/** Called on activation (in addition to `href` navigation when both are set). */
|
|
1698
|
+
onClick?: () => void;
|
|
1699
|
+
/**
|
|
1700
|
+
* Button emphasis. The **first** action's variant drives the footer layout:
|
|
1701
|
+
* `primary` → a stacked full-width button; `secondary` → an inline compact
|
|
1702
|
+
* row. Defaults to `primary`.
|
|
1703
|
+
*/
|
|
1704
|
+
variant?: 'primary' | 'secondary';
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
/**
|
|
1708
|
+
* Shared props for the `RichCard.*` variants (Sent, Received). The generic
|
|
1709
|
+
* rich message card: a `hero` or `fan` image region, a title + subtitle, and a
|
|
1710
|
+
* `variant`-driven footer of action buttons. The official welcome card and
|
|
1711
|
+
* broadcast link/collection cards are all this one card at different knobs.
|
|
1712
|
+
*/
|
|
1713
|
+
export declare interface RichCardBaseProps {
|
|
1714
|
+
title?: string;
|
|
1715
|
+
/** Subtitle / body copy — the base attachment's `text`. */
|
|
1716
|
+
subtitle?: string;
|
|
1717
|
+
/**
|
|
1718
|
+
* Max lines the subtitle wraps before clamping. Defaults to `3` — rich cards
|
|
1719
|
+
* carry prose, unlike the single-line link-preview default.
|
|
1720
|
+
*/
|
|
1721
|
+
subtitleLines?: number;
|
|
1722
|
+
/** SAFE public thumbnail URLs. `hero` shows the first; `fan` shows up to 3. */
|
|
1723
|
+
images?: string[];
|
|
1724
|
+
/**
|
|
1725
|
+
* Image layout. Defaults from {@link images} length (≤1 → `hero`, ≥2 →
|
|
1726
|
+
* `fan`) via the taxonomy's `resolveRichCardImagePresentation`.
|
|
1727
|
+
*/
|
|
1728
|
+
imagePresentation?: RichCardImagePresentation;
|
|
1729
|
+
/**
|
|
1730
|
+
* Footer actions. Empty / absent → no footer. Clients pass one today; the
|
|
1731
|
+
* shape is future-proof for several.
|
|
1732
|
+
*/
|
|
1733
|
+
actions?: RichCardActionButton[];
|
|
1734
|
+
/** Optional 16×16 brand badge rendered before the title. */
|
|
1735
|
+
appIcon?: default_2.ReactNode;
|
|
1736
|
+
/**
|
|
1737
|
+
* Dominant colour of the hero image (`#RRGGBB` or bare hex), painted as the
|
|
1738
|
+
* card + tail surface. Only applied for a single on-screen `hero` image (a
|
|
1739
|
+
* `fan` card keeps the plain fill) — the same gate LinkAttachment uses.
|
|
1740
|
+
*/
|
|
1741
|
+
accentColor?: string;
|
|
1742
|
+
/**
|
|
1743
|
+
* Position of this card inside its same-author run. `'single'` / `'end'`
|
|
1744
|
+
* paint the accent-coloured bubble tail; `'first'` / `'middle'` omit it.
|
|
1745
|
+
* Defaults to `'single'`.
|
|
1746
|
+
*/
|
|
1747
|
+
groupPosition?: MessageAttachmentGroupPosition;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
export { RichCardImagePresentation }
|
|
1751
|
+
|
|
1752
|
+
export declare type RichCardReceivedCardProps = RichCardBaseProps;
|
|
1753
|
+
|
|
1754
|
+
/** Props for `RichCard.Sent` / `RichCard.Received`. */
|
|
1755
|
+
export declare type RichCardSentCardProps = RichCardBaseProps;
|
|
1756
|
+
|
|
1662
1757
|
export declare interface SentBubbleProps extends MessageBubbleBaseProps {
|
|
1663
1758
|
}
|
|
1664
1759
|
|
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
|
|
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 h, k as L, l as d, m as p, R as C, n as v, p as A, q as k, s as F, t as b, u as P, v as f, w as x, x as y, o as z, y as B, z as S, B as q, D, E, G as R, H as V, I as w } from "./index-BaeffiED.js";
|
|
2
2
|
export {
|
|
3
3
|
e as ActionButton,
|
|
4
4
|
t as Avatar,
|
|
@@ -12,26 +12,27 @@ export {
|
|
|
12
12
|
M as LockedAttachment,
|
|
13
13
|
c as MediaMessage,
|
|
14
14
|
u as MessageAttachment,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
h as MessageBubble,
|
|
16
|
+
L as MessageVoteButtons,
|
|
17
|
+
d as MessagingProvider,
|
|
18
|
+
p as MessagingShell,
|
|
19
|
+
C as RichCard,
|
|
19
20
|
v as buildCompactMetaLabel,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
21
|
+
A as formatFileSize,
|
|
22
|
+
k as formatRelativeTime,
|
|
23
|
+
F as getFileExtensionLabel,
|
|
24
|
+
b as getMessageDisplayText,
|
|
25
|
+
P as isLinkAttachment,
|
|
26
|
+
f as isUuidLike,
|
|
27
|
+
x as messageAttachmentGroupPositionFromStream,
|
|
28
|
+
y as normalizeLanguageCode,
|
|
29
|
+
z as optimizeMessagingAttachmentUrl,
|
|
30
|
+
B as resolveConversationParticipant,
|
|
31
|
+
S as resolveLinkAttachment,
|
|
32
|
+
q as resolveMediaFromMessage,
|
|
33
|
+
D as resolveParticipantDisplayName,
|
|
34
|
+
E as useComposerLocked,
|
|
35
|
+
R as useCustomMessage,
|
|
35
36
|
V as useMessageVote,
|
|
36
37
|
w as useMessaging
|
|
37
38
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linktr.ee/messaging-react",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.35.0-rc-1786104411",
|
|
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
|
},
|
|
51
51
|
"dependencies": {
|
|
52
52
|
"@linktr.ee/component-library": "11.8.6",
|
|
53
|
-
"@linktr.ee/messaging-core": "
|
|
54
|
-
"@linktr.ee/messaging-taxonomy": "^0.
|
|
53
|
+
"@linktr.ee/messaging-core": "2.4.0-rc-1786104411",
|
|
54
|
+
"@linktr.ee/messaging-taxonomy": "^0.7.0",
|
|
55
55
|
"@phosphor-icons/react": "^2.1.10"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
@@ -9,6 +9,18 @@ import { normalizeExternalHref } from './normalizeExternalHref'
|
|
|
9
9
|
export interface CardCtaProps {
|
|
10
10
|
variant: LinkAttachmentVariant
|
|
11
11
|
cta: LinkAttachmentCta
|
|
12
|
+
/**
|
|
13
|
+
* Full-width pill (the default — LinkAttachment's single footer button).
|
|
14
|
+
* `false` sizes the button to its content, for an inline multi-action footer
|
|
15
|
+
* row (RichCard's `secondary` footer).
|
|
16
|
+
*/
|
|
17
|
+
fullWidth?: boolean
|
|
18
|
+
/**
|
|
19
|
+
* Omit the default `mt-2` top margin when the caller owns spacing — e.g. a
|
|
20
|
+
* multi-action footer that spaces its buttons with its own `gap`. Defaults to
|
|
21
|
+
* `false`, preserving LinkAttachment's button-below-description offset.
|
|
22
|
+
*/
|
|
23
|
+
standalone?: boolean
|
|
12
24
|
}
|
|
13
25
|
|
|
14
26
|
type CtaEmphasis = NonNullable<LinkAttachmentCta['variant']>
|
|
@@ -37,10 +49,17 @@ const BUTTON_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, Record<CtaEmphasis,
|
|
|
37
49
|
* Renders as `<a target="_blank">` when `cta.href` is set, otherwise as a
|
|
38
50
|
* plain `<button>`.
|
|
39
51
|
*/
|
|
40
|
-
const CardCta: React.FC<CardCtaProps> = ({
|
|
52
|
+
const CardCta: React.FC<CardCtaProps> = ({
|
|
53
|
+
variant,
|
|
54
|
+
cta,
|
|
55
|
+
fullWidth = true,
|
|
56
|
+
standalone = false,
|
|
57
|
+
}) => {
|
|
41
58
|
const emphasis: CtaEmphasis = cta.variant ?? 'primary'
|
|
42
59
|
const className = classNames(
|
|
43
|
-
'
|
|
60
|
+
'inline-flex h-10 items-center justify-center rounded-full px-4 text-sm font-medium leading-none transition-colors',
|
|
61
|
+
fullWidth && 'w-full',
|
|
62
|
+
!standalone && 'mt-2',
|
|
44
63
|
BUTTON_CLASS_BY_VARIANT[variant][emphasis]
|
|
45
64
|
)
|
|
46
65
|
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import type { Meta, StoryFn } from '@storybook/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
|
|
4
|
+
import type { RichCardActionButton, RichCardBaseProps } from './types'
|
|
5
|
+
|
|
6
|
+
import RichCard from '.'
|
|
7
|
+
|
|
8
|
+
const IMAGE = '/image-thumbnail.jpg'
|
|
9
|
+
const IMAGE_2 = '/video-thumbnail.jpg'
|
|
10
|
+
const IMAGE_3 = '/document-thumbnail.jpg'
|
|
11
|
+
|
|
12
|
+
const meta: Meta = {
|
|
13
|
+
title: 'RichCard',
|
|
14
|
+
parameters: {
|
|
15
|
+
layout: 'fullscreen',
|
|
16
|
+
// Matrix canvases are design-review surfaces, not regression targets — a
|
|
17
|
+
// single-pixel change in one cell would diff the whole grid. Default off;
|
|
18
|
+
// the stories that ARE regression targets opt back in per-story below.
|
|
19
|
+
chromatic: { disableSnapshot: true },
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
export default meta
|
|
23
|
+
|
|
24
|
+
/* ── Story scaffold (mirrors LinkAttachment.stories) ─────────────────── */
|
|
25
|
+
|
|
26
|
+
const Table = ({ children }: { children: React.ReactNode }) => (
|
|
27
|
+
<div className="min-h-screen w-full bg-[#F9F7F4] p-12">
|
|
28
|
+
<table className="border-separate border-spacing-4">{children}</table>
|
|
29
|
+
</div>
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const TableHead = ({ columns }: { columns: string[] }) => (
|
|
33
|
+
<thead>
|
|
34
|
+
<tr>
|
|
35
|
+
<th className="pb-2 text-left text-xs font-medium text-black/40" />
|
|
36
|
+
{columns.map((column) => (
|
|
37
|
+
<th
|
|
38
|
+
key={column}
|
|
39
|
+
className="pb-2 text-left text-xs font-medium text-black/40"
|
|
40
|
+
>
|
|
41
|
+
{column}
|
|
42
|
+
</th>
|
|
43
|
+
))}
|
|
44
|
+
</tr>
|
|
45
|
+
</thead>
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const RowLabel = ({ children }: { children: React.ReactNode }) => (
|
|
49
|
+
<td className="pr-4 pt-2 text-right align-top text-xs font-medium text-black/40">
|
|
50
|
+
{children}
|
|
51
|
+
</td>
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
const Cell = ({ children }: { children: React.ReactNode }) => (
|
|
55
|
+
<td className="align-top">{children}</td>
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
/* ── Shared fixtures ─────────────────────────────────────────────────── */
|
|
59
|
+
|
|
60
|
+
const primary = (label: string): RichCardActionButton => ({
|
|
61
|
+
label,
|
|
62
|
+
variant: 'primary',
|
|
63
|
+
onClick: () => alert(`Tapped ${label}`),
|
|
64
|
+
})
|
|
65
|
+
const secondaryLink = (label: string, url: string): RichCardActionButton => ({
|
|
66
|
+
label,
|
|
67
|
+
variant: 'secondary',
|
|
68
|
+
href: url,
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
// The three canonical surfaces the card converges (interactive-surfaces.md).
|
|
72
|
+
const OFFICIAL_WELCOME: RichCardBaseProps = {
|
|
73
|
+
title: 'Set up your welcome message',
|
|
74
|
+
subtitle: 'Automatically greet new followers the moment they message you.',
|
|
75
|
+
images: [IMAGE],
|
|
76
|
+
imagePresentation: 'hero',
|
|
77
|
+
actions: [primary('Set up welcome message')],
|
|
78
|
+
}
|
|
79
|
+
const SINGLE_LINK: RichCardBaseProps = {
|
|
80
|
+
title: 'Check out my new drop',
|
|
81
|
+
subtitle: 'Fresh merch, live now — grab it before it’s gone.',
|
|
82
|
+
images: [IMAGE],
|
|
83
|
+
imagePresentation: 'hero',
|
|
84
|
+
actions: [secondaryLink('Shop now', 'tr.ee/briedrop')],
|
|
85
|
+
}
|
|
86
|
+
const COLLECTION: RichCardBaseProps = {
|
|
87
|
+
title: 'My summer collection',
|
|
88
|
+
subtitle: 'Three new looks, styled and ready.',
|
|
89
|
+
images: [IMAGE, IMAGE_2, IMAGE_3],
|
|
90
|
+
imagePresentation: 'fan',
|
|
91
|
+
actions: [secondaryLink('View collection', 'tr.ee/briesummer')],
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const bothSides = (props: RichCardBaseProps) => (
|
|
95
|
+
<>
|
|
96
|
+
<Cell>
|
|
97
|
+
<RichCard.Received {...props} />
|
|
98
|
+
</Cell>
|
|
99
|
+
<Cell>
|
|
100
|
+
<RichCard.Sent {...props} />
|
|
101
|
+
</Cell>
|
|
102
|
+
</>
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
/* ── Instances — the headline: one card, three surfaces ──────────────── */
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The three real surfaces that converge on `linktree_rich_card`, each shown
|
|
109
|
+
* Received (light) and Sent (dark). They differ only by knobs: the official
|
|
110
|
+
* welcome card is `hero + primary`; a single-link broadcast is `hero +
|
|
111
|
+
* secondary`; a collection is `fan + secondary`.
|
|
112
|
+
*/
|
|
113
|
+
export const Instances: StoryFn = () => (
|
|
114
|
+
<Table>
|
|
115
|
+
<TableHead columns={['Received (light)', 'Sent (dark)']} />
|
|
116
|
+
<tbody>
|
|
117
|
+
<tr>
|
|
118
|
+
<RowLabel>Official welcome — hero · primary</RowLabel>
|
|
119
|
+
{bothSides(OFFICIAL_WELCOME)}
|
|
120
|
+
</tr>
|
|
121
|
+
<tr>
|
|
122
|
+
<RowLabel>Single link — hero · secondary</RowLabel>
|
|
123
|
+
{bothSides(SINGLE_LINK)}
|
|
124
|
+
</tr>
|
|
125
|
+
<tr>
|
|
126
|
+
<RowLabel>Collection — fan · secondary</RowLabel>
|
|
127
|
+
{bothSides(COLLECTION)}
|
|
128
|
+
</tr>
|
|
129
|
+
</tbody>
|
|
130
|
+
</Table>
|
|
131
|
+
)
|
|
132
|
+
Instances.parameters = { chromatic: { disableSnapshot: false } }
|
|
133
|
+
|
|
134
|
+
/* ── Image presentation — hero / fan / count default ─────────────────── */
|
|
135
|
+
|
|
136
|
+
const PRESENTATION_BASE: RichCardBaseProps = {
|
|
137
|
+
title: 'My summer collection',
|
|
138
|
+
subtitle: 'Three new looks, styled and ready.',
|
|
139
|
+
actions: [secondaryLink('View collection', 'tr.ee/briesummer')],
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The `imagePresentation` knob: `hero` (one prominent image), `fan` (2–3 fanned
|
|
144
|
+
* thumbnails), and the count-derived default when the knob is omitted (≤1 →
|
|
145
|
+
* hero, ≥2 → fan). Shown on both surfaces.
|
|
146
|
+
*/
|
|
147
|
+
export const ImagePresentation: StoryFn = () => (
|
|
148
|
+
<Table>
|
|
149
|
+
<TableHead
|
|
150
|
+
columns={[
|
|
151
|
+
'hero (1)',
|
|
152
|
+
'fan (2)',
|
|
153
|
+
'fan (3)',
|
|
154
|
+
'default — 1 img',
|
|
155
|
+
'default — 2 imgs',
|
|
156
|
+
]}
|
|
157
|
+
/>
|
|
158
|
+
<tbody>
|
|
159
|
+
{(['Received', 'Sent'] as const).map((side) => {
|
|
160
|
+
const Card = RichCard[side]
|
|
161
|
+
return (
|
|
162
|
+
<tr key={side}>
|
|
163
|
+
<RowLabel>{side}</RowLabel>
|
|
164
|
+
<Cell>
|
|
165
|
+
<Card {...PRESENTATION_BASE} images={[IMAGE]} imagePresentation="hero" />
|
|
166
|
+
</Cell>
|
|
167
|
+
<Cell>
|
|
168
|
+
<Card
|
|
169
|
+
{...PRESENTATION_BASE}
|
|
170
|
+
images={[IMAGE, IMAGE_2]}
|
|
171
|
+
imagePresentation="fan"
|
|
172
|
+
/>
|
|
173
|
+
</Cell>
|
|
174
|
+
<Cell>
|
|
175
|
+
<Card
|
|
176
|
+
{...PRESENTATION_BASE}
|
|
177
|
+
images={[IMAGE, IMAGE_2, IMAGE_3]}
|
|
178
|
+
imagePresentation="fan"
|
|
179
|
+
/>
|
|
180
|
+
</Cell>
|
|
181
|
+
<Cell>
|
|
182
|
+
<Card {...PRESENTATION_BASE} images={[IMAGE]} />
|
|
183
|
+
</Cell>
|
|
184
|
+
<Cell>
|
|
185
|
+
<Card {...PRESENTATION_BASE} images={[IMAGE, IMAGE_2]} />
|
|
186
|
+
</Cell>
|
|
187
|
+
</tr>
|
|
188
|
+
)
|
|
189
|
+
})}
|
|
190
|
+
</tbody>
|
|
191
|
+
</Table>
|
|
192
|
+
)
|
|
193
|
+
ImagePresentation.parameters = { chromatic: { disableSnapshot: false } }
|
|
194
|
+
|
|
195
|
+
/* ── Footer layout — variant drives stacked vs inline ────────────────── */
|
|
196
|
+
|
|
197
|
+
const FOOTER_BASE: RichCardBaseProps = {
|
|
198
|
+
title: 'A note from Brie',
|
|
199
|
+
subtitle: 'Pick how you’d like to keep in touch.',
|
|
200
|
+
images: [IMAGE],
|
|
201
|
+
imagePresentation: 'hero',
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* The footer layout is the visual face of the **first** action's `variant`:
|
|
206
|
+
* `primary` → a stacked full-width button; `secondary` → an inline compact row.
|
|
207
|
+
* Shown single-action (today's case) and two-action (future-proof), on both
|
|
208
|
+
* surfaces.
|
|
209
|
+
*/
|
|
210
|
+
export const FooterLayout: StoryFn = () => (
|
|
211
|
+
<Table>
|
|
212
|
+
<TableHead
|
|
213
|
+
columns={[
|
|
214
|
+
'primary — single',
|
|
215
|
+
'secondary — single',
|
|
216
|
+
'primary — two',
|
|
217
|
+
'secondary — two',
|
|
218
|
+
]}
|
|
219
|
+
/>
|
|
220
|
+
<tbody>
|
|
221
|
+
{(['Received', 'Sent'] as const).map((side) => {
|
|
222
|
+
const Card = RichCard[side]
|
|
223
|
+
return (
|
|
224
|
+
<tr key={side}>
|
|
225
|
+
<RowLabel>{side}</RowLabel>
|
|
226
|
+
<Cell>
|
|
227
|
+
<Card {...FOOTER_BASE} actions={[primary('Set up welcome message')]} />
|
|
228
|
+
</Cell>
|
|
229
|
+
<Cell>
|
|
230
|
+
<Card
|
|
231
|
+
{...FOOTER_BASE}
|
|
232
|
+
actions={[secondaryLink('Shop now', 'tr.ee/x')]}
|
|
233
|
+
/>
|
|
234
|
+
</Cell>
|
|
235
|
+
<Cell>
|
|
236
|
+
<Card
|
|
237
|
+
{...FOOTER_BASE}
|
|
238
|
+
actions={[primary('Set up now'), primary('Maybe later')]}
|
|
239
|
+
/>
|
|
240
|
+
</Cell>
|
|
241
|
+
<Cell>
|
|
242
|
+
<Card
|
|
243
|
+
{...FOOTER_BASE}
|
|
244
|
+
actions={[
|
|
245
|
+
secondaryLink('Shop', 'tr.ee/x'),
|
|
246
|
+
secondaryLink('Share', 'tr.ee/y'),
|
|
247
|
+
]}
|
|
248
|
+
/>
|
|
249
|
+
</Cell>
|
|
250
|
+
</tr>
|
|
251
|
+
)
|
|
252
|
+
})}
|
|
253
|
+
</tbody>
|
|
254
|
+
</Table>
|
|
255
|
+
)
|
|
256
|
+
FooterLayout.parameters = { chromatic: { disableSnapshot: false } }
|
|
257
|
+
|
|
258
|
+
/* ── Accent chin — hero-only derivation + gate ───────────────────────── */
|
|
259
|
+
|
|
260
|
+
const ACCENTS: Array<{ label: string; accentColor?: string }> = [
|
|
261
|
+
{ label: '#7008E7', accentColor: '#7008E7' },
|
|
262
|
+
{ label: '#738883', accentColor: '#738883' },
|
|
263
|
+
{ label: 'none', accentColor: undefined },
|
|
264
|
+
{ label: 'malformed', accentColor: 'rebeccapurple' },
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Accent derivation reuses LinkAttachment's `useChinPalette`: a validated hex
|
|
269
|
+
* paints the card + tail surface, hue kept and lightness swapped to a WCAG-safe
|
|
270
|
+
* stop; `none` / malformed keep the plain fill. It is gated to a single on-
|
|
271
|
+
* screen `hero` image — the bottom row shows a `fan` card ignoring the accent.
|
|
272
|
+
*/
|
|
273
|
+
export const AccentChin: StoryFn = () => (
|
|
274
|
+
<Table>
|
|
275
|
+
<TableHead columns={ACCENTS.map(({ label }) => label)} />
|
|
276
|
+
<tbody>
|
|
277
|
+
<tr>
|
|
278
|
+
<RowLabel>Sent (last in run)</RowLabel>
|
|
279
|
+
{ACCENTS.map(({ label, accentColor }) => (
|
|
280
|
+
<Cell key={label}>
|
|
281
|
+
<RichCard.Sent {...SINGLE_LINK} accentColor={accentColor} />
|
|
282
|
+
</Cell>
|
|
283
|
+
))}
|
|
284
|
+
</tr>
|
|
285
|
+
<tr>
|
|
286
|
+
<RowLabel>Received</RowLabel>
|
|
287
|
+
{ACCENTS.map(({ label, accentColor }) => (
|
|
288
|
+
<Cell key={label}>
|
|
289
|
+
<RichCard.Received {...SINGLE_LINK} accentColor={accentColor} />
|
|
290
|
+
</Cell>
|
|
291
|
+
))}
|
|
292
|
+
</tr>
|
|
293
|
+
<tr>
|
|
294
|
+
<RowLabel>fan — accent ignored</RowLabel>
|
|
295
|
+
{ACCENTS.map(({ label, accentColor }) => (
|
|
296
|
+
<Cell key={label}>
|
|
297
|
+
<RichCard.Sent {...COLLECTION} accentColor={accentColor} />
|
|
298
|
+
</Cell>
|
|
299
|
+
))}
|
|
300
|
+
</tr>
|
|
301
|
+
</tbody>
|
|
302
|
+
</Table>
|
|
303
|
+
)
|
|
304
|
+
AccentChin.parameters = { chromatic: { disableSnapshot: false } }
|
|
305
|
+
|
|
306
|
+
/* ── Content edges — clamps, missing pieces, tailless ────────────────── */
|
|
307
|
+
|
|
308
|
+
const LONG_TITLE =
|
|
309
|
+
'Set up your welcome message so every new follower gets greeted the moment they say hello'
|
|
310
|
+
const LONG_SUBTITLE =
|
|
311
|
+
'Thanks for reaching out! This is the kind of longer prose body an official message carries — it runs well past a single line and should wrap up to the clamp instead of clipping like a link preview, then trail off with an ellipsis once it reaches the limit.'
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Edge cases: a title past two lines (clamps at 2), a long prose subtitle
|
|
315
|
+
* (clamps at the 3-line default), an image-less card (chin only, content-sized),
|
|
316
|
+
* a title-only card, an action-only card, and a mid-run card (no bubble tail).
|
|
317
|
+
*/
|
|
318
|
+
export const ContentEdges: StoryFn = () => (
|
|
319
|
+
<Table>
|
|
320
|
+
<TableHead
|
|
321
|
+
columns={[
|
|
322
|
+
'long title',
|
|
323
|
+
'long subtitle',
|
|
324
|
+
'no image',
|
|
325
|
+
'title only',
|
|
326
|
+
'mid-run (no tail)',
|
|
327
|
+
]}
|
|
328
|
+
/>
|
|
329
|
+
<tbody>
|
|
330
|
+
{(['Received', 'Sent'] as const).map((side) => {
|
|
331
|
+
const Card = RichCard[side]
|
|
332
|
+
return (
|
|
333
|
+
<tr key={side}>
|
|
334
|
+
<RowLabel>{side}</RowLabel>
|
|
335
|
+
<Cell>
|
|
336
|
+
<Card
|
|
337
|
+
{...OFFICIAL_WELCOME}
|
|
338
|
+
title={LONG_TITLE}
|
|
339
|
+
subtitle="Short subtitle."
|
|
340
|
+
/>
|
|
341
|
+
</Cell>
|
|
342
|
+
<Cell>
|
|
343
|
+
<Card {...OFFICIAL_WELCOME} subtitle={LONG_SUBTITLE} />
|
|
344
|
+
</Cell>
|
|
345
|
+
<Cell>
|
|
346
|
+
<Card
|
|
347
|
+
title="No image, just copy"
|
|
348
|
+
subtitle="An image-less rich card sizes to its content instead of the 250px box."
|
|
349
|
+
actions={[primary('Got it')]}
|
|
350
|
+
/>
|
|
351
|
+
</Cell>
|
|
352
|
+
<Cell>
|
|
353
|
+
<Card {...OFFICIAL_WELCOME} subtitle={undefined} actions={undefined} />
|
|
354
|
+
</Cell>
|
|
355
|
+
<Cell>
|
|
356
|
+
<Card {...OFFICIAL_WELCOME} groupPosition="middle" />
|
|
357
|
+
</Cell>
|
|
358
|
+
</tr>
|
|
359
|
+
)
|
|
360
|
+
})}
|
|
361
|
+
</tbody>
|
|
362
|
+
</Table>
|
|
363
|
+
)
|
|
364
|
+
ContentEdges.parameters = { chromatic: { disableSnapshot: false } }
|