import {
ArticleIcon,
MusicNotesIcon,
PlayIcon,
QuestionIcon,
} from '@phosphor-icons/react'
import type { Meta, StoryFn } from '@storybook/react'
import React from 'react'
import type { LinkAttachmentVariant } from './components/_shared/CardShell'
import { HEX_PATTERN, toCssHex } from './components/_shared/hex'
import LinkAttachment from '.'
const IMAGE_THUMBNAIL = '/image-thumbnail.jpg'
const AUDIO_THUMBNAIL = '/audio-thumbnail.jpg'
const AUDIO_SOURCE = '/audio-source.mp3'
const VIDEO_SOURCE = '/video-source.mp4'
const meta: Meta = {
title: 'LinkAttachment',
parameters: {
layout: 'fullscreen',
// The single LinkApps story is a large matrix of every link-app card
// crossed with featured/classic layouts and locked/unlocked states.
// It's intended as a design-review canvas, not a regression target —
// a single pixel change in any cell would diff the whole thing. Skip
// it in Chromatic; the per-app/state behavior is exercised
// implicitly through the message-attachment stories that use these
// cards.
chromatic: { disableSnapshot: true },
},
}
export default meta
const Table = ({ children }: { children: React.ReactNode }) => (
)
/* ──────────────────────────────────────────────────────────────────
Accent chin (MES-1349)
────────────────────────────────────────────────────────────────── */
const ACCENTS: Array<{ label: string; accentColor?: string }> = [
// The Figma mock's own chin colour. Painted raw it measures 3.76:1 against
// the sender's white ink, which is why the mock cannot simply be reproduced;
// derived, its hue survives at a lightness both inks clear. The shipped sent
// card is therefore darker than the mock on purpose (MES-1388).
{ label: '#738883 (mock)', accentColor: '#738883' },
{ label: '#AA9F93', accentColor: '#AA9F93' },
{ label: '#FFF099', accentColor: '#FFF099' },
{ label: '#767676', accentColor: '#767676' },
{ label: '#7008E7', accentColor: '#7008E7' },
{ label: '#1e2330', accentColor: '#1e2330' },
{ label: 'none (fallback)', accentColor: undefined },
{ label: 'malformed', accentColor: 'rebeccapurple' },
]
const ACCENT_CARD = {
title: 'Yoga on Ice with Brie Nala — where ice meets inner calm.',
url: 'tr.ee/briewebsite',
thumbnailUrl: IMAGE_THUMBNAIL,
}
/**
* CTA emphasis — `cta.variant`. `primary` (default) is the filled button;
* `secondary` is the subtle/grey button used by the official-channel cards.
* Shown on both surfaces, and over an accent-derived chin: the translucent
* `secondary` fills (`bg-white/15`, `bg-black/[0.06]`) composite against the
* extracted colour, whereas the opaque `primary` fill is accent-independent —
* the bottom rows are the Chromatic guard for that legibility difference.
*/
export const CtaEmphasis: StoryFn = () => {
const cta = (label: string, variant?: 'primary' | 'secondary') => ({
label,
onClick: () => alert(`Tapped ${label}`),
...(variant ? { variant } : {}),
})
return (
Received (light)
Sent (dark)
Received + accent chin
Sent + accent chin
)
}
/**
* The dominant-colour card and its WCAG AA gate (MES-1349, corrected MES-1388).
*
* Each column is one extracted accent. Ink is *fixed per side* — `text-white`
* when sent, `text-black/90` when received — so contrast decides whether the
* accent may paint at all rather than which text colour to use. An accent under
* 4.5:1 against that side's ink is discarded for the plain message bubble fill
* (`#1e2330` sent, `#f1f0ee` received), so a card without a usable colour reads
* like the text bubbles around it.
*
* Each column is a synthetic base colour chosen to probe the derivation, not a
* colour taken from the hero — in production `accent_color` is extracted from
* the attachment image itself, so a photograph's own hue is what arrives here.
* Every column paints on both sides: the derivation moves lightness, never hue,
* so a base is never refused for contrast.
*
* The bottom rows cover the states where the accent is deliberately ignored:
* `classic` has no image to extract from, and `loading` has no image yet.
*/
export const AccentChin: StoryFn = () => (
label)} />
Sent (last in run)
{ACCENTS.map(({ label, accentColor }) => (
))}
Sent (mid-run, no tail)
{ACCENTS.map(({ label, accentColor }) => (
))}
Received
{ACCENTS.map(({ label, accentColor }) => (
1-line title (hero 188px)
{ACCENTS.map(({ label, accentColor }) => (
))}
Accent ignored
undefined}
/>
)
// Unlike the LinkApps canvas above, this matrix *is* the regression target for
// the accent geometry and the contrast flip — snapshot it.
AccentChin.parameters = { chromatic: { disableSnapshot: false } }
/** A link preview whose clip is playable audio, with cover artwork attached. */
const AUDIO_PREVIEW = {
mimeType: 'audio/mpeg',
sourceUrl: AUDIO_SOURCE,
thumbnailUrl: AUDIO_THUMBNAIL,
}
/**
* Accents that must **not** paint, because the hero image they claim to have
* been extracted from is not on screen (MES-1349).
*
* Every card here is handed `#7008E7` — a violent purple, picked so a
* regression is impossible to miss — and every card must render its own
* variant fill instead. `useChinPalette` gates the accent on `CardThumbnail`
* actually mounting the image, so:
*
* - **Playable audio** replaces the hero wholesale with the native `