@flamingo-stack/openframe-frontend-core 0.0.428 → 0.0.429-snapshot.20260715222543

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 (131) hide show
  1. package/dist/{chunk-43KGFHI3.js → chunk-2ELINEWQ.js} +2 -2
  2. package/dist/{chunk-OZTPC6VW.cjs → chunk-374KFDET.cjs} +31 -31
  3. package/dist/{chunk-OZTPC6VW.cjs.map → chunk-374KFDET.cjs.map} +1 -1
  4. package/dist/{chunk-55EIHSOQ.js → chunk-3AWPRVFM.js} +2 -2
  5. package/dist/{chunk-SLCANQN3.js → chunk-3OKVH6FW.js} +4 -4
  6. package/dist/{chunk-UXXCNZXC.js → chunk-3XWBE3SM.js} +4 -4
  7. package/dist/{chunk-7JUUQAAH.js → chunk-67LZDLIO.js} +2 -2
  8. package/dist/{chunk-YP3DO4BM.js → chunk-A6JKKKIF.js} +4 -4
  9. package/dist/{chunk-RR77Z27A.js → chunk-AY5TBDWZ.js} +2 -2
  10. package/dist/{chunk-LZYKGEN3.cjs → chunk-CJOFXDTK.cjs} +26 -26
  11. package/dist/{chunk-LZYKGEN3.cjs.map → chunk-CJOFXDTK.cjs.map} +1 -1
  12. package/dist/{chunk-I7YFFSDS.cjs → chunk-CTY37UUO.cjs} +59 -59
  13. package/dist/{chunk-I7YFFSDS.cjs.map → chunk-CTY37UUO.cjs.map} +1 -1
  14. package/dist/{chunk-NXCFIPEY.cjs → chunk-D4K53CES.cjs} +26 -26
  15. package/dist/{chunk-NXCFIPEY.cjs.map → chunk-D4K53CES.cjs.map} +1 -1
  16. package/dist/{chunk-FFDGTMZX.cjs → chunk-FL4IOCYH.cjs} +5 -5
  17. package/dist/{chunk-FFDGTMZX.cjs.map → chunk-FL4IOCYH.cjs.map} +1 -1
  18. package/dist/{chunk-7ELFQWYF.cjs → chunk-FYMD7CCA.cjs} +7 -7
  19. package/dist/{chunk-7ELFQWYF.cjs.map → chunk-FYMD7CCA.cjs.map} +1 -1
  20. package/dist/{chunk-CVYVCBMM.js → chunk-IMOJWQDR.js} +4 -4
  21. package/dist/{chunk-N2CTZ3Q2.cjs → chunk-KDVGC6EG.cjs} +37 -37
  22. package/dist/{chunk-N2CTZ3Q2.cjs.map → chunk-KDVGC6EG.cjs.map} +1 -1
  23. package/dist/{chunk-DA2VSDAX.js → chunk-NZBQGODM.js} +2 -2
  24. package/dist/{chunk-SJTNVETH.cjs → chunk-PP5KNHMK.cjs} +408 -233
  25. package/dist/chunk-PP5KNHMK.cjs.map +1 -0
  26. package/dist/{chunk-MJH4VS4L.cjs → chunk-Q7SYPXUX.cjs} +11 -11
  27. package/dist/{chunk-MJH4VS4L.cjs.map → chunk-Q7SYPXUX.cjs.map} +1 -1
  28. package/dist/{chunk-DQ5NRLCG.js → chunk-S7UXICHB.js} +2 -2
  29. package/dist/{chunk-KMXHSL6M.js → chunk-TDITU4KW.js} +2 -2
  30. package/dist/{chunk-OBDDE4WV.js → chunk-TYANQCZA.js} +3034 -2859
  31. package/dist/chunk-TYANQCZA.js.map +1 -0
  32. package/dist/{chunk-F4WTCWOY.cjs → chunk-WQN4B6CM.cjs} +15 -15
  33. package/dist/{chunk-F4WTCWOY.cjs.map → chunk-WQN4B6CM.cjs.map} +1 -1
  34. package/dist/{chunk-533XR4JK.cjs → chunk-WQR7H56P.cjs} +62 -62
  35. package/dist/{chunk-533XR4JK.cjs.map → chunk-WQR7H56P.cjs.map} +1 -1
  36. package/dist/{chunk-MO4QIPT7.cjs → chunk-ZUP2ZKS6.cjs} +9 -9
  37. package/dist/{chunk-MO4QIPT7.cjs.map → chunk-ZUP2ZKS6.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/index.d.ts +1 -0
  47. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  48. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +31 -2
  49. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  50. package/dist/components/chat/hooks/use-scripted-stream.d.ts +3 -0
  51. package/dist/components/chat/hooks/use-scripted-stream.d.ts.map +1 -0
  52. package/dist/components/chat/index.cjs +14 -2
  53. package/dist/components/chat/index.cjs.map +1 -1
  54. package/dist/components/chat/index.js +13 -1
  55. package/dist/components/chat/quick-action-chip.d.ts +5 -1
  56. package/dist/components/chat/quick-action-chip.d.ts.map +1 -1
  57. package/dist/components/chat/utils/index.d.ts +1 -0
  58. package/dist/components/chat/utils/index.d.ts.map +1 -1
  59. package/dist/components/chat/utils/scripted-stream.d.ts +38 -0
  60. package/dist/components/chat/utils/scripted-stream.d.ts.map +1 -0
  61. package/dist/components/contact/index.cjs +3 -3
  62. package/dist/components/contact/index.js +2 -2
  63. package/dist/components/docs/index.cjs +5 -5
  64. package/dist/components/docs/index.js +4 -4
  65. package/dist/components/embeds/index.cjs +3 -3
  66. package/dist/components/embeds/index.js +2 -2
  67. package/dist/components/faq/index.cjs +3 -3
  68. package/dist/components/faq/index.js +2 -2
  69. package/dist/components/features/board/index.d.ts +2 -2
  70. package/dist/components/features/board/index.d.ts.map +1 -1
  71. package/dist/components/features/board/ticket-card.d.ts +22 -2
  72. package/dist/components/features/board/ticket-card.d.ts.map +1 -1
  73. package/dist/components/features/index.cjs +6 -2
  74. package/dist/components/features/index.cjs.map +1 -1
  75. package/dist/components/features/index.js +5 -1
  76. package/dist/components/help-center-pages/index.cjs +20 -20
  77. package/dist/components/help-center-pages/index.js +11 -11
  78. package/dist/components/index.cjs +146 -128
  79. package/dist/components/index.cjs.map +1 -1
  80. package/dist/components/index.js +27 -9
  81. package/dist/components/index.js.map +1 -1
  82. package/dist/components/navigation/index.cjs +2 -2
  83. package/dist/components/navigation/index.js +1 -1
  84. package/dist/components/onboarding-guides/index.cjs +5 -5
  85. package/dist/components/onboarding-guides/index.js +4 -4
  86. package/dist/components/related-content/index.cjs +3 -3
  87. package/dist/components/related-content/index.js +2 -2
  88. package/dist/components/tickets/index.cjs +5 -5
  89. package/dist/components/tickets/index.js +4 -4
  90. package/dist/components/ui/index.cjs +16 -2
  91. package/dist/components/ui/index.cjs.map +1 -1
  92. package/dist/components/ui/index.d.ts +1 -0
  93. package/dist/components/ui/index.d.ts.map +1 -1
  94. package/dist/components/ui/index.js +15 -1
  95. package/dist/components/ui/info-card-row.d.ts +13 -0
  96. package/dist/components/ui/info-card-row.d.ts.map +1 -0
  97. package/dist/components/ui/tag.d.ts +1 -1
  98. package/dist/components/ui/tag.d.ts.map +1 -1
  99. package/dist/index.cjs +20 -2
  100. package/dist/index.cjs.map +1 -1
  101. package/dist/index.js +19 -1
  102. package/package.json +1 -1
  103. package/src/components/chat/chat-message-skeleton.tsx +40 -9
  104. package/src/components/chat/chat-panel-header.tsx +28 -16
  105. package/src/components/chat/chat-quick-action-row.tsx +33 -0
  106. package/src/components/chat/embeddable-chat.tsx +11 -1
  107. package/src/components/chat/hooks/index.ts +1 -0
  108. package/src/components/chat/hooks/use-nats-chat-adapter.ts +30 -1
  109. package/src/components/chat/hooks/use-scripted-stream.ts +69 -0
  110. package/src/components/chat/quick-action-chip.tsx +6 -1
  111. package/src/components/chat/utils/index.ts +2 -0
  112. package/src/components/chat/utils/scripted-stream.ts +111 -0
  113. package/src/components/features/board/index.ts +2 -2
  114. package/src/components/features/board/ticket-card.tsx +108 -64
  115. package/src/components/ui/index.ts +1 -0
  116. package/src/components/ui/info-card-row.tsx +70 -0
  117. package/src/components/ui/tag.tsx +7 -0
  118. package/src/stories/InfoCardRow.stories.tsx +68 -0
  119. package/dist/chunk-OBDDE4WV.js.map +0 -1
  120. package/dist/chunk-SJTNVETH.cjs.map +0 -1
  121. /package/dist/{chunk-43KGFHI3.js.map → chunk-2ELINEWQ.js.map} +0 -0
  122. /package/dist/{chunk-55EIHSOQ.js.map → chunk-3AWPRVFM.js.map} +0 -0
  123. /package/dist/{chunk-SLCANQN3.js.map → chunk-3OKVH6FW.js.map} +0 -0
  124. /package/dist/{chunk-UXXCNZXC.js.map → chunk-3XWBE3SM.js.map} +0 -0
  125. /package/dist/{chunk-7JUUQAAH.js.map → chunk-67LZDLIO.js.map} +0 -0
  126. /package/dist/{chunk-YP3DO4BM.js.map → chunk-A6JKKKIF.js.map} +0 -0
  127. /package/dist/{chunk-RR77Z27A.js.map → chunk-AY5TBDWZ.js.map} +0 -0
  128. /package/dist/{chunk-CVYVCBMM.js.map → chunk-IMOJWQDR.js.map} +0 -0
  129. /package/dist/{chunk-DA2VSDAX.js.map → chunk-NZBQGODM.js.map} +0 -0
  130. /package/dist/{chunk-DQ5NRLCG.js.map → chunk-S7UXICHB.js.map} +0 -0
  131. /package/dist/{chunk-KMXHSL6M.js.map → chunk-TDITU4KW.js.map} +0 -0
@@ -4,7 +4,7 @@ import { useSortable } from '@dnd-kit/sortable'
4
4
  import { CSS } from '@dnd-kit/utilities'
5
5
  import Link from '../../../embed-shims/next-link'
6
6
  import * as React from 'react'
7
- import { LaptopIcon, Flag02Icon, MessagesIcon, CheckCircleIcon } from '../../icons-v2-generated'
7
+ import { LaptopIcon, Flag02Icon, MessagesIcon } from '../../icons-v2-generated'
8
8
  import { SquareAvatar } from '../../ui/square-avatar'
9
9
  import { Tag } from '../../ui/tag'
10
10
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../../ui/tooltip'
@@ -24,64 +24,36 @@ const PRIORITY_COLOR_CLASS: Record<BoardPriority, string> = {
24
24
  const MAX_VISIBLE_TAGS = 2
25
25
  const MAX_VISIBLE_ASSIGNEES = 3
26
26
 
27
- export interface TicketCardProps {
27
+ /** Shared card shell (border / padding / bg). Same footprint for the draggable
28
+ * board card and the static {@link TicketCardView}. */
29
+ const TICKET_CARD_SHELL =
30
+ 'relative flex flex-col gap-[var(--spacing-system-sf)] rounded-md border border-ods-border bg-ods-bg p-[var(--spacing-system-sf)] select-none text-left'
31
+
32
+ // =============================================================================
33
+ // Presentational body — the ONE ticket-card design, no drag/link/board context.
34
+ // Reused by the draggable `TicketCard` AND the static `TicketCardView`.
35
+ // =============================================================================
36
+
37
+ export interface TicketCardBodyProps {
28
38
  ticket: BoardTicket
29
- columnId: string
30
39
  columnColor?: string
31
- href?: string
32
- isOverlay?: boolean
33
- dragDisabled?: boolean
34
- approved?: boolean
35
40
  renderAssignSlot?: (ticket: BoardTicket) => React.ReactNode
36
- onApprove?: (ticketId: string, requestId?: string) => void | Promise<void>
37
- onReject?: (ticketId: string, requestId?: string) => void | Promise<void>
41
+ /** Approval callbacks receive the request id directly (the draggable
42
+ * `TicketCard` adapts its own `(ticketId, requestId)` signature onto these). */
43
+ onApprove?: (requestId?: string) => void | Promise<void>
44
+ onReject?: (requestId?: string) => void | Promise<void>
38
45
  }
39
46
 
40
- export function TicketCard({
41
- ticket,
42
- columnId,
43
- columnColor,
44
- href,
45
- isOverlay = false,
46
- dragDisabled,
47
- approved = false,
48
- renderAssignSlot,
49
- onApprove,
50
- onReject,
51
- }: TicketCardProps) {
52
- const sortableData = React.useMemo(
53
- () => ({ columnId, type: 'ticket' as const }),
54
- [columnId],
55
- )
56
- const sortable = useSortable({
57
- id: ticket.id,
58
- data: sortableData,
59
- disabled: dragDisabled,
60
- })
61
-
47
+ /** The card's inner content: title + device/org, priority + assignees, tags,
48
+ * timestamp, "New Message", and the approval row. Pure — driven only by props. */
49
+ export function TicketCardBody({ ticket, columnColor, renderAssignSlot, onApprove, onReject }: TicketCardBodyProps) {
62
50
  const showNewMessage = !!ticket.hasNewMessage && !!columnColor
63
51
  const newMessageTextColor = columnColor ? getReadableTextColor(columnColor) : undefined
64
52
 
65
- const style: React.CSSProperties = isOverlay
66
- ? {}
67
- : {
68
- transform: CSS.Transform.toString(sortable.transform),
69
- transition: sortable.transition,
70
- }
71
- if (showNewMessage) style.borderColor = columnColor
72
-
73
53
  const showDeviceRow = !!(ticket.deviceHostnames?.length || ticket.organizationName)
74
- const deviceText = [
75
- ticket.deviceHostnames?.join(', '),
76
- ticket.organizationName,
77
- ].filter(Boolean).join(', ')
78
-
79
- const handleClick = (e: React.MouseEvent) => {
80
- if (sortable.isDragging) e.preventDefault()
81
- }
82
-
83
- const hasRightSection = !!(approved || ticket.priority || ticket.assignees?.length || renderAssignSlot)
54
+ const deviceText = [ticket.deviceHostnames?.join(', '), ticket.organizationName].filter(Boolean).join(', ')
84
55
 
56
+ const hasRightSection = !!(ticket.priority || ticket.assignees?.length || renderAssignSlot)
85
57
  const rightSection = hasRightSection ? (
86
58
  <div className="pointer-events-auto flex shrink-0 items-center gap-[var(--spacing-system-xsf)]">
87
59
  {ticket.priority && (
@@ -90,14 +62,6 @@ export function TicketCard({
90
62
  aria-label={`Priority: ${ticket.priority}`}
91
63
  />
92
64
  )}
93
- {approved && (
94
- <CheckCircleIcon
95
- size={20}
96
- color="var(--ods-attention-green-success)"
97
- className="shrink-0"
98
- aria-label="Approved"
99
- />
100
- )}
101
65
  {renderAssignSlot ? (
102
66
  renderAssignSlot(ticket)
103
67
  ) : ticket.assignees?.length ? (
@@ -125,7 +89,7 @@ export function TicketCard({
125
89
  const timestampLabel = ticket.createdAt ? formatTicketRelativeTime(ticket.createdAt) : null
126
90
  const tooltipLabel = ticket.createdAt ? formatTicketFullTimestamp(ticket.createdAt) : null
127
91
 
128
- const body = (
92
+ return (
129
93
  <>
130
94
  <div className="flex items-start gap-[var(--spacing-system-sf)]">
131
95
  <div className="flex min-w-0 flex-1 flex-col gap-[var(--spacing-system-zero)]" title={ticket.title}>
@@ -159,17 +123,97 @@ export function TicketCard({
159
123
  />
160
124
  )}
161
125
  {ticket.pendingApproval && (
162
- <BoardTicketApproval
163
- pendingApproval={ticket.pendingApproval}
164
- onApprove={onApprove ? requestId => onApprove(ticket.id, requestId) : undefined}
165
- onReject={onReject ? requestId => onReject(ticket.id, requestId) : undefined}
166
- />
126
+ <BoardTicketApproval pendingApproval={ticket.pendingApproval} onApprove={onApprove} onReject={onReject} />
167
127
  )}
168
128
  </>
169
129
  )
130
+ }
131
+
132
+ // =============================================================================
133
+ // Static card — the real board card design, rendered from props with NO
134
+ // drag-and-drop / board context (for embeds, marketing heroes, previews).
135
+ // =============================================================================
136
+
137
+ export interface TicketCardViewProps extends TicketCardBodyProps {
138
+ className?: string
139
+ }
140
+
141
+ /**
142
+ * The exact board `TicketCard` visual, standalone — same shell + `TicketCardBody`
143
+ * as the draggable card, but without the `useSortable` dnd-kit dependency, so it
144
+ * renders anywhere from static props. Use this outside a `<Board>`.
145
+ */
146
+ export function TicketCardView({ className, ...bodyProps }: TicketCardViewProps) {
147
+ const { ticket, columnColor } = bodyProps
148
+ const showNewMessage = !!ticket.hasNewMessage && !!columnColor
149
+ return (
150
+ <div
151
+ className={cn(TICKET_CARD_SHELL, className)}
152
+ style={showNewMessage ? { borderColor: columnColor } : undefined}
153
+ >
154
+ <div className="relative z-10 flex flex-col gap-[var(--spacing-system-sf)]">
155
+ <TicketCardBody {...bodyProps} />
156
+ </div>
157
+ </div>
158
+ )
159
+ }
160
+
161
+ // =============================================================================
162
+ // Draggable board card
163
+ // =============================================================================
164
+
165
+ export interface TicketCardProps {
166
+ ticket: BoardTicket
167
+ columnId: string
168
+ columnColor?: string
169
+ href?: string
170
+ isOverlay?: boolean
171
+ dragDisabled?: boolean
172
+ renderAssignSlot?: (ticket: BoardTicket) => React.ReactNode
173
+ onApprove?: (ticketId: string, requestId?: string) => void | Promise<void>
174
+ onReject?: (ticketId: string, requestId?: string) => void | Promise<void>
175
+ }
176
+
177
+ export function TicketCard({
178
+ ticket,
179
+ columnId,
180
+ columnColor,
181
+ href,
182
+ isOverlay = false,
183
+ dragDisabled,
184
+ renderAssignSlot,
185
+ onApprove,
186
+ onReject,
187
+ }: TicketCardProps) {
188
+ const sortableData = React.useMemo(() => ({ columnId, type: 'ticket' as const }), [columnId])
189
+ const sortable = useSortable({ id: ticket.id, data: sortableData, disabled: dragDisabled })
190
+
191
+ const showNewMessage = !!ticket.hasNewMessage && !!columnColor
192
+
193
+ const style: React.CSSProperties = isOverlay
194
+ ? {}
195
+ : {
196
+ transform: CSS.Transform.toString(sortable.transform),
197
+ transition: sortable.transition,
198
+ }
199
+ if (showNewMessage) style.borderColor = columnColor
200
+
201
+ const handleClick = (e: React.MouseEvent) => {
202
+ if (sortable.isDragging) e.preventDefault()
203
+ }
204
+
205
+ const body = (
206
+ <TicketCardBody
207
+ ticket={ticket}
208
+ columnColor={columnColor}
209
+ renderAssignSlot={renderAssignSlot}
210
+ onApprove={onApprove ? requestId => onApprove(ticket.id, requestId) : undefined}
211
+ onReject={onReject ? requestId => onReject(ticket.id, requestId) : undefined}
212
+ />
213
+ )
170
214
 
171
215
  const cardClasses = cn(
172
- 'relative flex flex-col gap-[var(--spacing-system-sf)] rounded-md border border-ods-border bg-ods-bg p-[var(--spacing-system-sf)] select-none text-left',
216
+ TICKET_CARD_SHELL,
173
217
  !dragDisabled && 'cursor-pointer',
174
218
  sortable.isDragging && !isOverlay && 'opacity-40',
175
219
  isOverlay && 'rotate-1 shadow-card-hover',
@@ -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
+ }