import type { Meta, StoryObj } from '@storybook/nextjs-vite' import type { ReactNode } from 'react' import { MingoInfoCard } from '../components/chat/mingo-info-card' import { ChatColumnDecorator, makeAnchorProps } from './__fixtures__/chat-card-decorator' import { MingoIcon } from '../components/icons' import { EyeIcon } from '../components/icons-v2-generated/interface/eye-icon' import { ArrowRightUpIcon } from '../components/icons-v2-generated/arrows/arrow-right-up-icon' import { TagIcon } from '../components/icons-v2-generated/shopping/tag-icon' import { CodingCommitIcon } from '../components/icons-v2-generated/coding/coding-commit-icon' import { CodingPullRequestIcon } from '../components/icons-v2-generated/coding/coding-pull-request-icon' import { CodeIcon } from '../components/icons-v2-generated/coding/code-icon' import { SlackLogoGreyIcon } from '../components/icons-v2-generated/brand-logos/slack-logo-grey-icon' import { FileContentIcon } from '../components/icons-v2-generated/documents/file-content-icon' import { ChartBar01VerIcon } from '../components/icons-v2-generated/charts/chart-bar-01-ver-icon' import { ChartPieIcon } from '../components/icons-v2-generated/charts/chart-pie-icon' import { MoneyBillDollarIcon } from '../components/icons-v2-generated/finance/money-bill-dollar-icon' import { BankIcon } from '../components/icons-v2-generated/finance/bank-icon' import { CoinsExchangeCurrencyIcon } from '../components/icons-v2-generated/finance/coins-exchange-currency-icon' import { NewspaperIcon } from '../components/icons-v2-generated/documents/newspaper-icon' import { TrophyIcon } from '../components/icons-v2-generated/sport/trophy-icon' import { MicrophoneIcon } from '../components/icons-v2-generated/household/microphone-icon' import { Rocket02Icon } from '../components/icons-v2-generated/vehicles-and-delivery/rocket-02-icon' import { TruckFastIcon } from '../components/icons-v2-generated/vehicles-and-delivery/truck-fast-icon' import { PresentationBarIcon } from '../components/icons-v2-generated/charts/presentation-bar-icon' import { PresentationLineIcon } from '../components/icons-v2-generated/charts/presentation-line-icon' import { CalendarIcon } from '../components/icons-v2-generated/date-and-time/calendar-icon' import { CompassIcon } from '../components/icons-v2-generated/map-and-travel/compass-icon' import { MapIcon } from '../components/icons-v2-generated/map-and-travel/map-icon' import { CheckSquareIcon } from '../components/icons-v2-generated/signs-and-symbols/check-square-icon' import { Megaphone01Icon } from '../components/icons-v2-generated/shopping/megaphone-01-icon' /** * Chat entity-cards re-skinned with `MingoInfoCard`. * * As each `[card://type:id]` registry entry in `entity-cards/dispatch.tsx` is * migrated to render a `MingoInfoCard`, its variants are mirrored here so the * compact in-chat appearance can be reviewed in isolation — same column width * and background the dispatch renders into, without booting the chat runtime. * * Each `render` reproduces the registry's field mapping for that card type * (icon, status pill, description, "⋯" menu). Anchor clicks are neutralized via * `makeAnchorProps` so the Storybook iframe doesn't navigate away. */ const meta: Meta = { title: 'Chat/ChatMingoInfoCards', component: MingoInfoCard, parameters: { layout: 'fullscreen', docs: { description: { component: 'In-chat entity cards rendered through `MingoInfoCard`. One section per migrated `CHAT_CARD_REGISTRY` type — mirrors the icon / status / description / menu mapping from `entity-cards/dispatch.tsx`.', }, }, }, decorators: [ (Story) => ( ), ], } export default meta type Story = StoryObj /** Shared "⋯" overflow menu (Figma `7740:55075`): "Ask Mingo" (Mingo logo) * followed by "Open Details" (eye) with a trailing ↗ open-in-new-tab side * button. */ const openMenu = ( href: string, openDetails?: { label: string; icon: ReactNode }, ) => [ { items: [ { id: 'discuss', label: 'Ask Mingo', icon: ( ), onClick: () => { // eslint-disable-next-line no-console console.log('[story] ask mingo', href) }, }, { id: 'open-details', label: openDetails?.label ?? 'Open Details', icon: openDetails?.icon ?? , onClick: () => { // eslint-disable-next-line no-console console.log('[story] open details', href) }, iconAction: { icon: , 'aria-label': 'Open in new tab', onClick: () => { // eslint-disable-next-line no-console console.log('[story] open in new tab', href) }, }, }, ], }, ] // ============================================================================= // HubSpot ticket — icon: HubSpot grey logo · status: ticket state · desc: preview // ============================================================================= export const HubspotTicket: Story = { parameters: { docs: { description: { story: 'Registry types `hubspot_ticket` / `_anon` / `_self`. Status pill maps the ticket state (Closed → green, Open → amber, other → grey); description is the ticket preview.', }, }, }, render: () => { const HREF = 'https://app.hubspot.com/contacts/0/ticket/123' return (
} status={{ label: 'Open', variant: 'warning' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF, { label: 'Open Ticket Details', icon: , })} menuAriaLabel="Ticket actions" /> } status={{ label: 'Closed', variant: 'success' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF, { label: 'Open Ticket Details', icon: , })} menuAriaLabel="Ticket actions" /> } status={{ label: 'Waiting on us', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF, { label: 'Open Ticket Details', icon: , })} menuAriaLabel="Ticket actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF, { label: 'Open Ticket Details', icon: , })} menuAriaLabel="Ticket actions" />
) }, } // ============================================================================= // GitHub activity — icon per kind · status: review state or activity type · // description: " · " // ============================================================================= export const GithubActivity: Story = { parameters: { docs: { description: { story: 'Registry types `github_commit` / `github_pull_request` / `github_pr_review` (+ `_public`). Icon differs per kind (commit / pull-request / code); the pill shows the PR review state (Approved → green, Changes → red, Comment → grey) or, for commits/PRs, the neutral activity type.', }, }, }, render: () => { const HREF = 'https://github.com/flamingo/openframe/commit/a1b2c3d' return (
} status={{ label: 'Commit', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Activity actions" /> } status={{ label: 'PR', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Activity actions" /> } status={{ label: 'Approved', variant: 'success' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Activity actions" /> } status={{ label: 'Changes', variant: 'error' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Activity actions" /> } status={{ label: 'Comment', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Activity actions" />
) }, } // ============================================================================= // Slack message — icon: Slack grey logo · no pill · description: message text // ============================================================================= export const SlackMessage: Story = { parameters: { docs: { description: { story: 'Registry type `slack_message`. No status pill (Slack has no status); the channel is appended to the author title ("Author · #channel"), and the description is the message preview text.', }, }, }, render: () => { const HREF = 'https://flamingo.slack.com/archives/C12345/p1700000000' return (
} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Message actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Message actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Message actions" />
) }, } // ============================================================================= // Data-room doc / markdown — icon: FileContent · status: source badge · // description: preview // ============================================================================= export const DataRoomDoc: Story = { parameters: { docs: { description: { story: 'Registry types `data_room_doc` / `markdown`. Status pill is the provenance label (the source-repo label, or a neutral "Document" when unknown); the description is the content preview.', }, }, }, render: () => { const HREF = 'https://hub.flamingo.cx/docs/openframe/getting-started' return (
} status={{ label: 'OpenFrame Docs', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" /> } status={{ label: 'Data room', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" /> } status={{ label: 'Document', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" />
) }, } // ============================================================================= // Financial — distinct icon per type · no pill · description: subtitle // ============================================================================= export const Financial: Story = { parameters: { docs: { description: { story: 'Registry types `financial_kpi` / `cap_table` / `profit_loss` / `balance_sheet` / `cash_flow`. No status pill — the per-type icon carries the type signal (chart / pie / money / bank / exchange); the description is the subtitle.', }, }, }, render: () => { const HREF = 'https://hub.flamingo.cx/data-room/financials' return (
} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Document actions" />
) }, } // ============================================================================= // Blog post — media: cover image (newspaper-icon fallback) · pill: category · // description: summary // ============================================================================= export const BlogPost: Story = { parameters: { docs: { description: { story: 'Registry type `blog_post` (fetch mode). Media is the cover `featured_image` (OG placeholder, then a newspaper glyph as fallbacks); the pill is the first category; the description is the post summary.', }, }, }, render: () => { const HREF = 'https://openmsp.com/blog/rmm-buyers-guide' return (
} status={{ label: 'Guides', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Article actions" /> } status={{ label: 'News', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Article actions" /> } status={{ label: 'Video', variant: 'primary' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Article actions" />
) }, } // ============================================================================= // Case study — trophy fallback icon · "Case study" pill · cover image // ============================================================================= export const CaseStudy: Story = { render: () => { const HREF = 'https://openmsp.com/case-studies/acme-it' return (
} status={{ label: 'Case study', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Case study actions" /> } status={{ label: 'Case study', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Case study actions" />
) }, } // ============================================================================= // Customer interview — microphone fallback · "Interview" pill // ============================================================================= export const CustomerInterview: Story = { render: () => { const HREF = 'https://openmsp.com/interviews/jamie-fox' return (
} status={{ label: 'Interview', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Interview actions" />
) }, } // ============================================================================= // Product release — rocket fallback · version pill · description: summary/date // ============================================================================= export const ProductRelease: Story = { render: () => { const HREF = 'https://openframe.org/releases/2-14-0' return (
} status={{ label: 'v2.14', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Release actions" /> } status={{ label: 'v2.13.2', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Release actions" />
) }, } // ============================================================================= // Programs — podcast / webinar / event, distinct icon + type pill // ============================================================================= export const Programs: Story = { render: () => { const HREF = 'https://openmsp.com/podcasts/ep-42' return (
} status={{ label: 'Podcast', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Podcast actions" /> } status={{ label: 'Webinar', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Webinar actions" /> } status={{ label: 'Event', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Event actions" />
) }, } // ============================================================================= // Investor update — presentation fallback · "Investor update" pill // ============================================================================= export const InvestorUpdate: Story = { render: () => { const HREF = 'https://hub.flamingo.cx/investor-updates/2026-q1' return (
} status={{ label: 'Investor update', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Update actions" /> } status={{ label: 'Investor update', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Update actions" />
) }, } // ============================================================================= // Onboarding guide — compass fallback · "Guide" pill // ============================================================================= export const OnboardingGuide: Story = { render: () => { const HREF = 'https://hub.flamingo.cx/guides/first-device' return (
} status={{ label: 'Guide', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Guide actions" />
) }, } // ============================================================================= // Roadmap / delivery / task — per-type icon · coloured status pill · no image // ============================================================================= export const Roadmap: Story = { parameters: { docs: { description: { story: 'Registry types `roadmap_item` / `delivery_item` / `internal_task`. Icon priority mirrors RoadmapCard: server integration logo (`item.icon`, shown contained) → task-type glyph → per-type default (map / truck / check-square). The status pill is coloured via the shared `getStatusColorScheme` mapping (done → green, in-progress → amber, blocked → red, other → grey).', }, }, }, render: () => { const HREF = 'https://hub.flamingo.cx/roadmap/RM-128' return (
} status={{ label: 'In Progress', variant: 'warning' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Roadmap actions" /> } status={{ label: 'Planned', variant: 'grey' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Roadmap actions" /> } status={{ label: 'Done', variant: 'success' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Roadmap actions" /> } status={{ label: 'Blocked', variant: 'error' }} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Roadmap actions" />
) }, } // ============================================================================= // Marketing campaign — megaphone icon · coloured status pill // ============================================================================= export const MarketingCampaign: Story = { render: () => { const HREF = 'https://hub.flamingo.cx/admin/campaigns/c-91' return (
} anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Campaign actions" /> } anchorProps={makeAnchorProps(HREF)} menuGroups={openMenu(HREF)} menuAriaLabel="Campaign actions" />
) }, }