import type { Meta, StoryFn } from '@storybook/react' import React from 'react' import type { RichCardActionButton, RichCardBaseProps } from './types' import RichCard from '.' const IMAGE = '/image-thumbnail.jpg' const IMAGE_2 = '/video-thumbnail.jpg' const IMAGE_3 = '/document-thumbnail.jpg' const meta: Meta = { title: 'RichCard', parameters: { layout: 'fullscreen', // Matrix canvases are design-review surfaces, not regression targets — a // single-pixel change in one cell would diff the whole grid. Default off; // the stories that ARE regression targets opt back in per-story below. chromatic: { disableSnapshot: true }, }, } export default meta /* ── Story scaffold (mirrors LinkAttachment.stories) ─────────────────── */ const Table = ({ children }: { children: React.ReactNode }) => (
{children}
) const TableHead = ({ columns }: { columns: string[] }) => ( {columns.map((column) => ( {column} ))} ) const RowLabel = ({ children }: { children: React.ReactNode }) => ( {children} ) const Cell = ({ children }: { children: React.ReactNode }) => ( {children} ) /* ── Shared fixtures ─────────────────────────────────────────────────── */ const primary = (label: string): RichCardActionButton => ({ label, variant: 'primary', onClick: () => alert(`Tapped ${label}`), }) const secondaryLink = (label: string, url: string): RichCardActionButton => ({ label, variant: 'secondary', href: url, }) const secondary = (label: string): RichCardActionButton => ({ label, variant: 'secondary', onClick: () => alert(`Tapped ${label}`), }) // The three canonical surfaces the card converges (interactive-surfaces.md). const OFFICIAL_WELCOME: RichCardBaseProps = { title: 'Set up your welcome message', subtitle: 'Automatically greet new followers the moment they message you.', images: [IMAGE], imagePresentation: 'hero', actions: [primary('Set up welcome message')], } const SINGLE_LINK: RichCardBaseProps = { title: 'Check out my new drop', subtitle: 'Fresh merch, live now — grab it before it’s gone.', images: [IMAGE], imagePresentation: 'hero', actions: [secondaryLink('Shop now', 'tr.ee/briedrop')], } const COLLECTION: RichCardBaseProps = { title: 'My summer collection', subtitle: 'Three new looks, styled and ready.', images: [IMAGE, IMAGE_2, IMAGE_3], imagePresentation: 'fan', actions: [secondaryLink('View collection', 'tr.ee/briesummer')], } const NEWLINE_SUBTITLE: RichCardBaseProps = { ...OFFICIAL_WELCOME, title: 'Start every conversation with a personal welcome message!', subtitle: '👋 Say hello as people arrive\n✨ Spark conversations\n💬 Add automated FAQs', subtitleLines: 6, } const bothSides = (props: RichCardBaseProps) => ( <> ) /* ── Instances — the headline: one card, three surfaces ──────────────── */ /** * The three real surfaces that converge on `linktree_rich_card`, each shown * Received (light) and Sent (dark). They differ only by knobs: the official * welcome card is `hero + primary`; a single-link broadcast is `hero + * secondary`; a collection is `fan + secondary`. A fourth row shows a * newline-separated subtitle (the onboarding result-card shape). */ export const Instances: StoryFn = () => ( Official welcome — hero · primary {bothSides(OFFICIAL_WELCOME)} Single link — hero · secondary {bothSides(SINGLE_LINK)} Collection — fan · secondary {bothSides(COLLECTION)} Subtitle with newlines {bothSides(NEWLINE_SUBTITLE)}
) Instances.parameters = { chromatic: { disableSnapshot: false } } /* ── Image presentation — hero / fan / count default ─────────────────── */ const PRESENTATION_BASE: RichCardBaseProps = { title: 'My summer collection', subtitle: 'Three new looks, styled and ready.', actions: [secondaryLink('View collection', 'tr.ee/briesummer')], } /** * The `imagePresentation` knob: `hero` (one prominent image), `fan` (2–3 fanned * thumbnails), and the count-derived default when the knob is omitted (≤1 → * hero, ≥2 → fan). Shown on both surfaces. */ export const ImagePresentation: StoryFn = () => ( {(['Received', 'Sent'] as const).map((side) => { const Card = RichCard[side] return ( {side} ) })}
) ImagePresentation.parameters = { chromatic: { disableSnapshot: false } } /* ── Footer layout — variant drives stacked vs inline ────────────────── */ const FOOTER_BASE: RichCardBaseProps = { title: 'A note from Brie', subtitle: 'Pick how you’d like to keep in touch.', images: [IMAGE], imagePresentation: 'hero', } /** * Footer emphasis + layout. A card has **one primary (main) action**; any * further action is `secondary`, so the hierarchy is carried by emphasis * (filled main vs subtle alternate) — not by two identical buttons. The first * action's variant also drives layout: `primary` → stacked full-width; * `secondary` → inline compact row. * * The last column is the anti-pattern (two primaries) — no hierarchy; author * `primary + secondary` instead. */ export const FooterLayout: StoryFn = () => ( {(['Received', 'Sent'] as const).map((side) => { const Card = RichCard[side] return ( {side} ) })}
) FooterLayout.parameters = { chromatic: { disableSnapshot: false } } /* ── Accent chin — hero-only derivation + gate ───────────────────────── */ const ACCENTS: Array<{ label: string; accentColor?: string }> = [ { label: '#7008E7', accentColor: '#7008E7' }, { label: '#738883', accentColor: '#738883' }, { label: 'none', accentColor: undefined }, { label: 'malformed', accentColor: 'rebeccapurple' }, ] /** * Accent derivation reuses LinkAttachment's `useChinPalette`: a validated hex * paints the card + tail surface, hue kept and lightness swapped to a WCAG-safe * stop; `none` / malformed keep the plain fill. It is gated to a single on- * screen `hero` image — the bottom row shows a `fan` card ignoring the accent. */ export const AccentChin: StoryFn = () => ( label)} /> Sent (last in run) {ACCENTS.map(({ label, accentColor }) => ( ))} Received {ACCENTS.map(({ label, accentColor }) => ( ))} fan — accent ignored {ACCENTS.map(({ label, accentColor }) => ( ))}
) AccentChin.parameters = { chromatic: { disableSnapshot: false } } /* ── Content edges — clamps, missing pieces, tailless ────────────────── */ const LONG_TITLE = 'Set up your welcome message so every new follower gets greeted the moment they say hello' const LONG_SUBTITLE = '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.' /** * Edge cases: a title past two lines (clamps at 2), a long prose subtitle * (clamps at the 3-line default), an image-less card (chin only, content-sized), * a title-only card, an action-only card, and a mid-run card (no bubble tail). */ export const ContentEdges: StoryFn = () => ( {(['Received', 'Sent'] as const).map((side) => { const Card = RichCard[side] return ( {side} ) })}
) ContentEdges.parameters = { chromatic: { disableSnapshot: false } }