@flamingo-stack/openframe-frontend-core 0.0.428 → 0.0.429

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 (112) hide show
  1. package/dist/{chunk-N2CTZ3Q2.cjs → chunk-2LJA3ABH.cjs} +37 -37
  2. package/dist/{chunk-N2CTZ3Q2.cjs.map → chunk-2LJA3ABH.cjs.map} +1 -1
  3. package/dist/{chunk-UXXCNZXC.js → chunk-2WJ25MTA.js} +4 -4
  4. package/dist/{chunk-55EIHSOQ.js → chunk-4VODJPTB.js} +2 -2
  5. package/dist/{chunk-OBDDE4WV.js → chunk-62L7S7HQ.js} +2769 -2697
  6. package/dist/chunk-62L7S7HQ.js.map +1 -0
  7. package/dist/{chunk-7ELFQWYF.cjs → chunk-7MJTM6QT.cjs} +7 -7
  8. package/dist/{chunk-7ELFQWYF.cjs.map → chunk-7MJTM6QT.cjs.map} +1 -1
  9. package/dist/{chunk-YP3DO4BM.js → chunk-7PA4B6SD.js} +4 -4
  10. package/dist/{chunk-KMXHSL6M.js → chunk-B3FBMRHQ.js} +2 -2
  11. package/dist/{chunk-I7YFFSDS.cjs → chunk-B3Y47C2U.cjs} +59 -59
  12. package/dist/{chunk-I7YFFSDS.cjs.map → chunk-B3Y47C2U.cjs.map} +1 -1
  13. package/dist/{chunk-F4WTCWOY.cjs → chunk-BN3V465H.cjs} +15 -15
  14. package/dist/{chunk-F4WTCWOY.cjs.map → chunk-BN3V465H.cjs.map} +1 -1
  15. package/dist/{chunk-533XR4JK.cjs → chunk-CCL6JEMS.cjs} +62 -62
  16. package/dist/{chunk-533XR4JK.cjs.map → chunk-CCL6JEMS.cjs.map} +1 -1
  17. package/dist/{chunk-LZYKGEN3.cjs → chunk-CRGH6TV6.cjs} +26 -26
  18. package/dist/{chunk-LZYKGEN3.cjs.map → chunk-CRGH6TV6.cjs.map} +1 -1
  19. package/dist/{chunk-MJH4VS4L.cjs → chunk-E4PKZNR7.cjs} +11 -11
  20. package/dist/{chunk-MJH4VS4L.cjs.map → chunk-E4PKZNR7.cjs.map} +1 -1
  21. package/dist/{chunk-MO4QIPT7.cjs → chunk-GGJUXN6Z.cjs} +9 -9
  22. package/dist/{chunk-MO4QIPT7.cjs.map → chunk-GGJUXN6Z.cjs.map} +1 -1
  23. package/dist/{chunk-43KGFHI3.js → chunk-GMR5AA3C.js} +2 -2
  24. package/dist/{chunk-RR77Z27A.js → chunk-KHKB5RY2.js} +2 -2
  25. package/dist/{chunk-DQ5NRLCG.js → chunk-MAYEJ3NY.js} +2 -2
  26. package/dist/{chunk-DA2VSDAX.js → chunk-NYISC4Z3.js} +2 -2
  27. package/dist/{chunk-NXCFIPEY.cjs → chunk-OLGFIDZY.cjs} +26 -26
  28. package/dist/{chunk-NXCFIPEY.cjs.map → chunk-OLGFIDZY.cjs.map} +1 -1
  29. package/dist/{chunk-SLCANQN3.js → chunk-RTOVYU4D.js} +4 -4
  30. package/dist/{chunk-7JUUQAAH.js → chunk-TJ4HJ3KX.js} +2 -2
  31. package/dist/{chunk-FFDGTMZX.cjs → chunk-WXKFFVGC.cjs} +5 -5
  32. package/dist/{chunk-FFDGTMZX.cjs.map → chunk-WXKFFVGC.cjs.map} +1 -1
  33. package/dist/{chunk-SJTNVETH.cjs → chunk-XDBD6BJN.cjs} +92 -20
  34. package/dist/chunk-XDBD6BJN.cjs.map +1 -0
  35. package/dist/{chunk-CVYVCBMM.js → chunk-Y2TW664W.js} +4 -4
  36. package/dist/{chunk-OZTPC6VW.cjs → chunk-YGITDP6O.cjs} +31 -31
  37. package/dist/{chunk-OZTPC6VW.cjs.map → chunk-YGITDP6O.cjs.map} +1 -1
  38. package/dist/components/case-studies/index.cjs +8 -8
  39. package/dist/components/case-studies/index.js +2 -2
  40. package/dist/components/chat/chat-message-skeleton.d.ts +17 -2
  41. package/dist/components/chat/chat-message-skeleton.d.ts.map +1 -1
  42. package/dist/components/chat/chat-panel-header.d.ts +6 -1
  43. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  44. package/dist/components/chat/chat-quick-action-row.d.ts +12 -2
  45. package/dist/components/chat/chat-quick-action-row.d.ts.map +1 -1
  46. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +22 -2
  47. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  48. package/dist/components/chat/index.cjs +4 -2
  49. package/dist/components/chat/index.cjs.map +1 -1
  50. package/dist/components/chat/index.js +3 -1
  51. package/dist/components/chat/quick-action-chip.d.ts +5 -1
  52. package/dist/components/chat/quick-action-chip.d.ts.map +1 -1
  53. package/dist/components/contact/index.cjs +3 -3
  54. package/dist/components/contact/index.js +2 -2
  55. package/dist/components/docs/index.cjs +5 -5
  56. package/dist/components/docs/index.js +4 -4
  57. package/dist/components/embeds/index.cjs +3 -3
  58. package/dist/components/embeds/index.js +2 -2
  59. package/dist/components/faq/index.cjs +3 -3
  60. package/dist/components/faq/index.js +2 -2
  61. package/dist/components/features/index.cjs +2 -2
  62. package/dist/components/features/index.js +1 -1
  63. package/dist/components/help-center-pages/index.cjs +20 -20
  64. package/dist/components/help-center-pages/index.js +11 -11
  65. package/dist/components/index.cjs +132 -128
  66. package/dist/components/index.cjs.map +1 -1
  67. package/dist/components/index.js +13 -9
  68. package/dist/components/index.js.map +1 -1
  69. package/dist/components/navigation/index.cjs +2 -2
  70. package/dist/components/navigation/index.js +1 -1
  71. package/dist/components/onboarding-guides/index.cjs +5 -5
  72. package/dist/components/onboarding-guides/index.js +4 -4
  73. package/dist/components/related-content/index.cjs +3 -3
  74. package/dist/components/related-content/index.js +2 -2
  75. package/dist/components/tickets/index.cjs +5 -5
  76. package/dist/components/tickets/index.js +4 -4
  77. package/dist/components/ui/index.cjs +6 -2
  78. package/dist/components/ui/index.cjs.map +1 -1
  79. package/dist/components/ui/index.d.ts +1 -0
  80. package/dist/components/ui/index.d.ts.map +1 -1
  81. package/dist/components/ui/index.js +5 -1
  82. package/dist/components/ui/info-card-row.d.ts +13 -0
  83. package/dist/components/ui/info-card-row.d.ts.map +1 -0
  84. package/dist/components/ui/tag.d.ts +1 -1
  85. package/dist/components/ui/tag.d.ts.map +1 -1
  86. package/dist/index.cjs +6 -2
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.js +5 -1
  89. package/package.json +1 -1
  90. package/src/components/chat/chat-message-skeleton.tsx +40 -9
  91. package/src/components/chat/chat-panel-header.tsx +28 -16
  92. package/src/components/chat/chat-quick-action-row.tsx +33 -0
  93. package/src/components/chat/embeddable-chat.tsx +11 -1
  94. package/src/components/chat/hooks/use-nats-chat-adapter.ts +7 -1
  95. package/src/components/chat/quick-action-chip.tsx +6 -1
  96. package/src/components/ui/index.ts +1 -0
  97. package/src/components/ui/info-card-row.tsx +70 -0
  98. package/src/components/ui/tag.tsx +7 -0
  99. package/src/stories/InfoCardRow.stories.tsx +68 -0
  100. package/dist/chunk-OBDDE4WV.js.map +0 -1
  101. package/dist/chunk-SJTNVETH.cjs.map +0 -1
  102. /package/dist/{chunk-UXXCNZXC.js.map → chunk-2WJ25MTA.js.map} +0 -0
  103. /package/dist/{chunk-55EIHSOQ.js.map → chunk-4VODJPTB.js.map} +0 -0
  104. /package/dist/{chunk-YP3DO4BM.js.map → chunk-7PA4B6SD.js.map} +0 -0
  105. /package/dist/{chunk-KMXHSL6M.js.map → chunk-B3FBMRHQ.js.map} +0 -0
  106. /package/dist/{chunk-43KGFHI3.js.map → chunk-GMR5AA3C.js.map} +0 -0
  107. /package/dist/{chunk-RR77Z27A.js.map → chunk-KHKB5RY2.js.map} +0 -0
  108. /package/dist/{chunk-DQ5NRLCG.js.map → chunk-MAYEJ3NY.js.map} +0 -0
  109. /package/dist/{chunk-DA2VSDAX.js.map → chunk-NYISC4Z3.js.map} +0 -0
  110. /package/dist/{chunk-SLCANQN3.js.map → chunk-RTOVYU4D.js.map} +0 -0
  111. /package/dist/{chunk-7JUUQAAH.js.map → chunk-TJ4HJ3KX.js.map} +0 -0
  112. /package/dist/{chunk-CVYVCBMM.js.map → chunk-Y2TW664W.js.map} +0 -0
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import * as React from 'react'
4
+ import { cn } from '../../utils/cn'
4
5
  import { Chevron01LeftIcon } from '../icons-v2-generated/arrows/chevron-01-left-icon'
5
6
  import {
6
7
  Ellipsis01Icon,
@@ -42,6 +43,11 @@ export interface ChatPanelHeaderProps {
42
43
  onArchive?: () => void
43
44
  /** Open the Chat Archive page — renders the clock button (list view only). */
44
45
  onOpenArchive?: () => void
46
+ /** Force the COMPACT (`h-14` desktop-style) bar at every width, skipping the
47
+ * full-screen mobile header. For EMBEDDED surfaces (e.g. a small preview
48
+ * panel) where the phone-sized full-bleed header (large `text-h2` title) is
49
+ * wrong regardless of viewport. Default false = viewport-responsive. */
50
+ compact?: boolean
45
51
  }
46
52
 
47
53
  /**
@@ -61,6 +67,7 @@ export function ChatPanelHeader({
61
67
  onRename,
62
68
  onArchive,
63
69
  onOpenArchive,
70
+ compact = false,
64
71
  }: ChatPanelHeaderProps) {
65
72
  // Desktop ⋯ menu (active, non-archived conversation only) — rename / archive.
66
73
  const menuItems = [
@@ -70,23 +77,28 @@ export function ChatPanelHeader({
70
77
 
71
78
  return (
72
79
  <>
73
- {/* Mobile (<md): a distinct layout (Figma node 7363:85532). */}
74
- <ChatPanelHeaderMobile
75
- className="flex md:hidden"
76
- showBack={showBack}
77
- title={title}
78
- backAriaLabel={backAriaLabel}
79
- isArchivedView={isArchivedView}
80
- onBack={onBack}
81
- onClose={onClose}
82
- onRestore={onRestore}
83
- onRename={onRename}
84
- onArchive={onArchive}
85
- onOpenArchive={onOpenArchive}
86
- />
80
+ {/* Mobile (<md): a distinct full-screen layout (Figma node 7363:85532).
81
+ Skipped in `compact` mode — an embedded preview keeps the small bar
82
+ at every width. */}
83
+ {!compact && (
84
+ <ChatPanelHeaderMobile
85
+ className="flex md:hidden"
86
+ showBack={showBack}
87
+ title={title}
88
+ backAriaLabel={backAriaLabel}
89
+ isArchivedView={isArchivedView}
90
+ onBack={onBack}
91
+ onClose={onClose}
92
+ onRestore={onRestore}
93
+ onRename={onRename}
94
+ onArchive={onArchive}
95
+ onOpenArchive={onOpenArchive}
96
+ />
97
+ )}
87
98
 
88
- {/* Desktop (md+): fixed-height bar with full-height divider action cells. */}
89
- <div className="hidden md:flex flex-shrink-0 h-14 w-full overflow-hidden border-b border-ods-border bg-ods-card">
99
+ {/* Compact bar: `md+` normally, or ALL widths when `compact`. Fixed-height
100
+ bar with full-height divider action cells. */}
101
+ <div className={cn('flex-shrink-0 h-14 w-full overflow-hidden border-b border-ods-border bg-ods-card', compact ? 'flex' : 'hidden md:flex')}>
90
102
  <div className="flex flex-1 min-w-0 items-center gap-2 px-4 py-3">
91
103
  {showBack ? (
92
104
  <>
@@ -9,8 +9,10 @@ import { Ellipsis01Icon } from '../icons-v2-generated'
9
9
  import { useAutoLimitTags } from '../../hooks/ui/use-auto-limit-tags'
10
10
  import {
11
11
  QuickActionChipButton,
12
+ QuickActionChipSkeleton,
12
13
  renderQuickActionIcon,
13
14
  type QuickActionIconSpec,
15
+ type QuickActionAccent,
14
16
  } from './quick-action-chip'
15
17
 
16
18
  // =============================================================================
@@ -27,6 +29,11 @@ export interface QuickActionChip {
27
29
  icon?: React.ReactNode | QuickActionIconSpec
28
30
  /** `'primary'` = accent (yellow) chip, `'outline'` = bordered chip (default). */
29
31
  variant?: 'primary' | 'outline'
32
+ /** Active single-select state — renders the accented `selected` skin
33
+ * (overrides `variant`). */
34
+ selected?: boolean
35
+ /** Accent for the `selected` skin (`'cyan'` = cyan twin, else pink). */
36
+ selectedAccent?: QuickActionAccent
30
37
  onSelect?: () => void
31
38
  /** Pointer/keyboard focus enters the chip — e.g. preview the full prompt in
32
39
  * the composer. */
@@ -47,9 +54,18 @@ export interface ChatQuickActionRowProps {
47
54
  * for roomy contexts (e.g. the Guide empty state) where overflow-hiding chips
48
55
  * would defeat per-chip hover/preview. */
49
56
  wrap?: boolean
57
+ /** Render `skeletonCount` loading chips (the real {@link QuickActionChipSkeleton},
58
+ * so geometry is 1:1 with the loaded chips) instead of `chips`. Wrap-aware. */
59
+ loading?: boolean
60
+ /** How many skeleton chips to draw when `loading`. Default 8. */
61
+ skeletonCount?: number
50
62
  className?: string
51
63
  }
52
64
 
65
+ // Deterministic label widths (in `ch`) so a loading wall reads like a populated
66
+ // one — a varied spread, not a uniform block.
67
+ const SKELETON_LABEL_CHS = [16, 9, 13, 7, 18, 8, 20, 11, 15, 10, 19, 12, 17, 8, 21, 14]
68
+
53
69
  // =============================================================================
54
70
  // Chip button
55
71
  // =============================================================================
@@ -62,6 +78,8 @@ function ChipButton({ chip }: { chip: QuickActionChip }) {
62
78
  label={chip.label}
63
79
  icon={chip.icon}
64
80
  variant={chip.variant ?? 'outline'}
81
+ selected={chip.selected}
82
+ selectedAccent={chip.selectedAccent}
65
83
  onSelect={chip.onSelect}
66
84
  onHoverStart={chip.onHoverStart}
67
85
  onHoverEnd={chip.onHoverEnd}
@@ -86,6 +104,8 @@ export function ChatQuickActionRow({
86
104
  leading,
87
105
  chips,
88
106
  wrap = false,
107
+ loading = false,
108
+ skeletonCount = 8,
89
109
  className,
90
110
  }: ChatQuickActionRowProps) {
91
111
  const auto = useAutoLimitTags({
@@ -96,6 +116,19 @@ export function ChatQuickActionRow({
96
116
  const visibleChips = chips.slice(0, auto.visibleCount)
97
117
  const hiddenChips = chips.slice(auto.visibleCount)
98
118
 
119
+ // Loading: draw skeleton chips (real `QuickActionChipSkeleton`, 1:1 geometry)
120
+ // in the same wrap/inline layout — one shared skeleton for every consumer, no
121
+ // hand-rolled bars.
122
+ if (loading) {
123
+ return (
124
+ <div className={cn('flex shrink-0 items-center gap-1', wrap && 'flex-wrap', className)}>
125
+ {Array.from({ length: skeletonCount }, (_, i) => (
126
+ <QuickActionChipSkeleton key={i} labelCh={SKELETON_LABEL_CHS[i % SKELETON_LABEL_CHS.length]} />
127
+ ))}
128
+ </div>
129
+ )
130
+ }
131
+
99
132
  if (!leading && chips.length === 0) return null
100
133
 
101
134
  // Wrap mode: no width-measurement / "⋯" overflow — every chip renders inline
@@ -1557,7 +1557,13 @@ function EmbeddableChatInner({
1557
1557
  // history has loaded (`hasMessages`). Driving the surface + content branch
1558
1558
  // off this makes the normal-chat open animate identically to the archived
1559
1559
  // one instead of lagging behind the message fetch.
1560
- const hasConversation = hasMessages || isOpeningDialog || isViewingArchived
1560
+ // In `previewMode`, the host drives the whole state (`mingoStateOverride`) and
1561
+ // there is no dialog manager to set `isOpeningDialog`. So an embedded preview
1562
+ // that wants a message-list skeleton (real header + composer, skeleton bubbles)
1563
+ // signals it via `isMessagesLoading` — treat that as an open conversation so
1564
+ // the content branch shows the skeleton instead of the new-user welcome.
1565
+ const hasConversation =
1566
+ hasMessages || isOpeningDialog || isViewingArchived || (previewMode && isMessagesLoading)
1561
1567
  // Opening a dialog whose history hasn't arrived yet — show a message-list
1562
1568
  // skeleton instead of an empty thread so the open reads as "loading" rather
1563
1569
  // than a blank flash before the bubbles stream in.
@@ -1680,6 +1686,10 @@ function EmbeddableChatInner({
1680
1686
  className="flex flex-1 min-h-0 flex-col animate-in fade-in-0 duration-200"
1681
1687
  >
1682
1688
  <ChatPanelHeader
1689
+ // Embedded previews (the hero demo tabs) keep the small bar at
1690
+ // every width — the phone-sized full-screen header (large title)
1691
+ // is wrong in a small embedded panel.
1692
+ compact={previewMode}
1683
1693
  // Guide-mode empty state shows a back-chevron + "Mingo Guide"
1684
1694
  // (back returns to the default Mingo welcome); an open
1685
1695
  // conversation shows back + the dialog title; the Mingo list
@@ -374,8 +374,14 @@ function updateTrailingAssistant(
374
374
  * here because the unified contract surfaces errors as banners, not
375
375
  * inline messages. (Hosts that need inline error bubbles can extend
376
376
  * the contract later.)
377
+ *
378
+ * Exported for host reuse (e.g. scripted marketing demos that rehydrate a
379
+ * stored `HistoricalMessage[]` via `processHistoricalMessagesWithErrors` and
380
+ * feed a `previewMode` EmbeddableChat). This mapper carries the segment/content
381
+ * shape only; author identity (name/avatar/authorType/timestamp) that a host
382
+ * needs for the demo is re-attached by the host from the same processed rows.
377
383
  */
378
- function mapProcessedToUnified(
384
+ export function mapProcessedToUnified(
379
385
  processed: Array<{
380
386
  id: string
381
387
  role: 'user' | 'assistant' | 'error'
@@ -145,6 +145,10 @@ export interface QuickActionChipButtonProps {
145
145
  * Tag's `selected` variant (pink border + pink-secondary fill), overriding
146
146
  * `variant`. Used by chip groups acting as tabs (OpenFrame categories). */
147
147
  selected?: boolean
148
+ /** Accent for the `selected` skin: `'cyan'` uses the cyan twin, anything else
149
+ * (default) uses pink. Lets an agent's chip group match its own theme accent
150
+ * (fae pink / mingo cyan) instead of the fixed pink. */
151
+ selectedAccent?: QuickActionAccent
148
152
  /** Chip scale, forwarded to `Tag` — `'large'` is the Figma "Feature Item"
149
153
  * 48px chip (h3 bold label, 24px icon box). Default `'default'` (32px). */
150
154
  size?: 'default' | 'large'
@@ -226,6 +230,7 @@ export function QuickActionChipButton({
226
230
  lozenge,
227
231
  variant = 'outline',
228
232
  selected = false,
233
+ selectedAccent,
229
234
  size = 'default',
230
235
  onSelect,
231
236
  onHoverStart,
@@ -235,7 +240,7 @@ export function QuickActionChipButton({
235
240
  }: QuickActionChipButtonProps) {
236
241
  const resolvedIcon = renderQuickActionIcon(icon)
237
242
  const resolvedLabel = composeChipLabel(label, lozenge)
238
- const tagVariant = selected ? 'selected' : variant
243
+ const tagVariant = selected ? (selectedAccent === 'cyan' ? 'selectedCyan' : 'selected') : variant
239
244
  if (!interactive) {
240
245
  return <Tag variant={tagVariant} size={size} icon={resolvedIcon} label={resolvedLabel} className={className} />
241
246
  }
@@ -83,6 +83,7 @@ export * from './chevron-button'
83
83
  export * from './circular-progress'
84
84
  export { CheckIcon, CheckCircleIcon as LucideCheckCircleIcon, XIcon as LucideXIcon, MinusIcon, XCircleIcon } from './custom-icons'
85
85
  export * from './dashboard-info-card'
86
+ export * from './info-card-row'
86
87
  export * from './device-card'
87
88
  export * from './device-card-compact'
88
89
  export * from './entity-image'
@@ -0,0 +1,70 @@
1
+ import type * as React from 'react'
2
+ import { cn } from '../../utils/cn'
3
+
4
+ export interface InfoCardRowSection {
5
+ title: string
6
+ caption: string
7
+ icon?: React.ReactNode
8
+ }
9
+
10
+ export interface InfoCardRowProps {
11
+ lead: InfoCardRowSection
12
+ stats: [InfoCardRowSection, InfoCardRowSection]
13
+ className?: string
14
+ }
15
+
16
+ const CELL =
17
+ 'flex items-center gap-[var(--spacing-system-s)] min-w-0 p-[var(--spacing-system-xsf)] md:p-[var(--spacing-system-m)] min-h-[60px] md:min-h-[76px]'
18
+
19
+ function StatCell({ section, className }: { section: InfoCardRowSection; className?: string }) {
20
+ return (
21
+ <div className={cn(CELL, 'flex-1', className)}>
22
+ <div className="flex min-w-0 flex-1 flex-col">
23
+ <p className="truncate text-h5 uppercase text-ods-text-secondary">{section.title}</p>
24
+ <div className="flex min-w-0 items-center gap-[var(--spacing-system-s)]">
25
+ <p className="min-w-0 flex-1 truncate text-h3 text-ods-text-primary">
26
+ {section.caption}
27
+ </p>
28
+ {section.icon && (
29
+ <div className="flex size-6 shrink-0 items-center justify-center md:hidden">
30
+ {section.icon}
31
+ </div>
32
+ )}
33
+ </div>
34
+ </div>
35
+ {section.icon && (
36
+ <div className="hidden size-6 shrink-0 items-center justify-center md:flex">
37
+ {section.icon}
38
+ </div>
39
+ )}
40
+ </div>
41
+ )
42
+ }
43
+
44
+ export function InfoCardRow({ lead, stats, className }: InfoCardRowProps) {
45
+ return (
46
+ <div
47
+ className={cn(
48
+ 'flex shrink-0 flex-col overflow-hidden rounded-md border border-ods-border bg-ods-card md:flex-row',
49
+ className,
50
+ )}
51
+ >
52
+ <div className={cn(CELL, 'border-b border-ods-border md:flex-1 md:border-b-0 md:border-r')}>
53
+ {lead.icon && (
54
+ <div className="flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-sm border border-ods-border [&>*]:size-full [&_img]:object-cover">
55
+ {lead.icon}
56
+ </div>
57
+ )}
58
+ <div className="flex min-w-0 flex-1 flex-col justify-center">
59
+ <p className="truncate text-h3 text-ods-text-primary">{lead.title}</p>
60
+ <p className="truncate text-h6 text-ods-text-secondary">{lead.caption}</p>
61
+ </div>
62
+ </div>
63
+
64
+ <div className="flex md:flex-1">
65
+ <StatCell section={stats[0]} className="border-r border-ods-border" />
66
+ <StatCell section={stats[1]} />
67
+ </div>
68
+ </div>
69
+ )
70
+ }
@@ -58,6 +58,13 @@ const tagVariants = cva(
58
58
  "hover:bg-[var(--ods-flamingo-pink-secondary-hover)] hover:border-[var(--ods-flamingo-pink-base)]",
59
59
  "active:bg-[var(--ods-flamingo-pink-secondary-action)]",
60
60
  ],
61
+ // Cyan twin of `selected` (Mingo's accent) — same active-chip skin in the
62
+ // cyan theme so agent chip groups can match their own accent.
63
+ selectedCyan: [
64
+ "bg-[var(--ods-flamingo-cyan-secondary)] text-[var(--ods-system-greys-white)] border border-[var(--ods-flamingo-cyan-base)]",
65
+ "hover:bg-[var(--ods-flamingo-cyan-secondary-hover)] hover:border-[var(--ods-flamingo-cyan-base)]",
66
+ "active:bg-[var(--ods-flamingo-cyan-secondary-action)]",
67
+ ],
61
68
  // Matches the EntityTagBadges / StatusBadge tag skin (ods-card + ods-border,
62
69
  // mono uppercase) so the tag-editor chips render identically to the public
63
70
  // tag badges. Used for FilterChipData variant 'tag' (see search-input).
@@ -0,0 +1,68 @@
1
+ import type { Meta, StoryObj } from '@storybook/nextjs-vite'
2
+ import { InfoCardRow } from '../components/ui/info-card-row'
3
+ import { CheckCircleIcon } from '../components/icons-v2-generated/signs-and-symbols/check-circle-icon'
4
+ import { TagIcon } from '../components/icons-v2-generated/shopping/tag-icon'
5
+
6
+ // Stand-in company logo — the lead `icon` fills the 40px bordered avatar box.
7
+ // In real usage this is typically an `<img>` logo (rendered with object-cover).
8
+ const companyLogo = (
9
+ <div className="size-full" style={{ background: 'linear-gradient(135deg, #5EFAF0, #F357BB)' }} />
10
+ )
11
+
12
+ const meta = {
13
+ title: 'UI/InfoCardRow',
14
+ component: InfoCardRow,
15
+ parameters: {
16
+ layout: 'padded',
17
+ docs: {
18
+ description: {
19
+ component:
20
+ 'A responsive summary row: a 50%-width lead section (icon + title/caption) and two 25%-width stat sections (title + caption + right icon). On mobile the two stat sections wrap into their own row beneath the lead (76px rows on desktop/tablet, 60px on mobile), and each stat icon drops onto the caption line. Neighbouring sections share inner borders; only the outer edges are rounded. Each section takes a required `title` and `caption`, and an optional `icon`.',
21
+ },
22
+ },
23
+ },
24
+ tags: ['autodocs'],
25
+ argTypes: {
26
+ lead: { control: false, description: 'Lead section (50% width): { title, caption, icon? }' },
27
+ stats: {
28
+ control: false,
29
+ description: 'Two stat sections (25% width each): { title, caption, icon? }',
30
+ },
31
+ },
32
+ decorators: [
33
+ (Story) => (
34
+ <div className="w-full max-w-[1024px]">
35
+ <Story />
36
+ </div>
37
+ ),
38
+ ],
39
+ } satisfies Meta<typeof InfoCardRow>
40
+
41
+ export default meta
42
+ type Story = StoryObj<typeof meta>
43
+
44
+ /** Full row — lead logo + name/devices, and two stats with icons. Matches Figma. */
45
+ export const Default: Story = {
46
+ args: {
47
+ lead: { title: 'Precision Accounting Partners', caption: '847 devices', icon: companyLogo },
48
+ stats: [
49
+ {
50
+ title: 'Resolved this week',
51
+ caption: '312',
52
+ icon: <CheckCircleIcon className="text-ods-success" />,
53
+ },
54
+ { title: 'Open tickets', caption: '2', icon: <TagIcon className="text-ods-text-secondary" /> },
55
+ ],
56
+ },
57
+ }
58
+
59
+ /** Icons omitted — text-only sections. */
60
+ export const WithoutIcons: Story = {
61
+ args: {
62
+ lead: { title: 'Precision Accounting Partners', caption: '847 devices' },
63
+ stats: [
64
+ { title: 'Resolved this week', caption: '312' },
65
+ { title: 'Open tickets', caption: '2' },
66
+ ],
67
+ },
68
+ }