@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.
- package/dist/{chunk-43KGFHI3.js → chunk-2ELINEWQ.js} +2 -2
- package/dist/{chunk-OZTPC6VW.cjs → chunk-374KFDET.cjs} +31 -31
- package/dist/{chunk-OZTPC6VW.cjs.map → chunk-374KFDET.cjs.map} +1 -1
- package/dist/{chunk-55EIHSOQ.js → chunk-3AWPRVFM.js} +2 -2
- package/dist/{chunk-SLCANQN3.js → chunk-3OKVH6FW.js} +4 -4
- package/dist/{chunk-UXXCNZXC.js → chunk-3XWBE3SM.js} +4 -4
- package/dist/{chunk-7JUUQAAH.js → chunk-67LZDLIO.js} +2 -2
- package/dist/{chunk-YP3DO4BM.js → chunk-A6JKKKIF.js} +4 -4
- package/dist/{chunk-RR77Z27A.js → chunk-AY5TBDWZ.js} +2 -2
- package/dist/{chunk-LZYKGEN3.cjs → chunk-CJOFXDTK.cjs} +26 -26
- package/dist/{chunk-LZYKGEN3.cjs.map → chunk-CJOFXDTK.cjs.map} +1 -1
- package/dist/{chunk-I7YFFSDS.cjs → chunk-CTY37UUO.cjs} +59 -59
- package/dist/{chunk-I7YFFSDS.cjs.map → chunk-CTY37UUO.cjs.map} +1 -1
- package/dist/{chunk-NXCFIPEY.cjs → chunk-D4K53CES.cjs} +26 -26
- package/dist/{chunk-NXCFIPEY.cjs.map → chunk-D4K53CES.cjs.map} +1 -1
- package/dist/{chunk-FFDGTMZX.cjs → chunk-FL4IOCYH.cjs} +5 -5
- package/dist/{chunk-FFDGTMZX.cjs.map → chunk-FL4IOCYH.cjs.map} +1 -1
- package/dist/{chunk-7ELFQWYF.cjs → chunk-FYMD7CCA.cjs} +7 -7
- package/dist/{chunk-7ELFQWYF.cjs.map → chunk-FYMD7CCA.cjs.map} +1 -1
- package/dist/{chunk-CVYVCBMM.js → chunk-IMOJWQDR.js} +4 -4
- package/dist/{chunk-N2CTZ3Q2.cjs → chunk-KDVGC6EG.cjs} +37 -37
- package/dist/{chunk-N2CTZ3Q2.cjs.map → chunk-KDVGC6EG.cjs.map} +1 -1
- package/dist/{chunk-DA2VSDAX.js → chunk-NZBQGODM.js} +2 -2
- package/dist/{chunk-SJTNVETH.cjs → chunk-PP5KNHMK.cjs} +408 -233
- package/dist/chunk-PP5KNHMK.cjs.map +1 -0
- package/dist/{chunk-MJH4VS4L.cjs → chunk-Q7SYPXUX.cjs} +11 -11
- package/dist/{chunk-MJH4VS4L.cjs.map → chunk-Q7SYPXUX.cjs.map} +1 -1
- package/dist/{chunk-DQ5NRLCG.js → chunk-S7UXICHB.js} +2 -2
- package/dist/{chunk-KMXHSL6M.js → chunk-TDITU4KW.js} +2 -2
- package/dist/{chunk-OBDDE4WV.js → chunk-TYANQCZA.js} +3034 -2859
- package/dist/chunk-TYANQCZA.js.map +1 -0
- package/dist/{chunk-F4WTCWOY.cjs → chunk-WQN4B6CM.cjs} +15 -15
- package/dist/{chunk-F4WTCWOY.cjs.map → chunk-WQN4B6CM.cjs.map} +1 -1
- package/dist/{chunk-533XR4JK.cjs → chunk-WQR7H56P.cjs} +62 -62
- package/dist/{chunk-533XR4JK.cjs.map → chunk-WQR7H56P.cjs.map} +1 -1
- package/dist/{chunk-MO4QIPT7.cjs → chunk-ZUP2ZKS6.cjs} +9 -9
- package/dist/{chunk-MO4QIPT7.cjs.map → chunk-ZUP2ZKS6.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +8 -8
- package/dist/components/case-studies/index.js +2 -2
- package/dist/components/chat/chat-message-skeleton.d.ts +17 -2
- package/dist/components/chat/chat-message-skeleton.d.ts.map +1 -1
- package/dist/components/chat/chat-panel-header.d.ts +6 -1
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
- package/dist/components/chat/chat-quick-action-row.d.ts +12 -2
- package/dist/components/chat/chat-quick-action-row.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +1 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +31 -2
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-scripted-stream.d.ts +3 -0
- package/dist/components/chat/hooks/use-scripted-stream.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +14 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +13 -1
- package/dist/components/chat/quick-action-chip.d.ts +5 -1
- package/dist/components/chat/quick-action-chip.d.ts.map +1 -1
- package/dist/components/chat/utils/index.d.ts +1 -0
- package/dist/components/chat/utils/index.d.ts.map +1 -1
- package/dist/components/chat/utils/scripted-stream.d.ts +38 -0
- package/dist/components/chat/utils/scripted-stream.d.ts.map +1 -0
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/docs/index.cjs +5 -5
- package/dist/components/docs/index.js +4 -4
- package/dist/components/embeds/index.cjs +3 -3
- package/dist/components/embeds/index.js +2 -2
- package/dist/components/faq/index.cjs +3 -3
- package/dist/components/faq/index.js +2 -2
- package/dist/components/features/board/index.d.ts +2 -2
- package/dist/components/features/board/index.d.ts.map +1 -1
- package/dist/components/features/board/ticket-card.d.ts +22 -2
- package/dist/components/features/board/ticket-card.d.ts.map +1 -1
- package/dist/components/features/index.cjs +6 -2
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +5 -1
- package/dist/components/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +146 -128
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +27 -9
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +2 -2
- package/dist/components/navigation/index.js +1 -1
- package/dist/components/onboarding-guides/index.cjs +5 -5
- package/dist/components/onboarding-guides/index.js +4 -4
- package/dist/components/related-content/index.cjs +3 -3
- package/dist/components/related-content/index.js +2 -2
- package/dist/components/tickets/index.cjs +5 -5
- package/dist/components/tickets/index.js +4 -4
- package/dist/components/ui/index.cjs +16 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +1 -0
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +15 -1
- package/dist/components/ui/info-card-row.d.ts +13 -0
- package/dist/components/ui/info-card-row.d.ts.map +1 -0
- package/dist/components/ui/tag.d.ts +1 -1
- package/dist/components/ui/tag.d.ts.map +1 -1
- package/dist/index.cjs +20 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +19 -1
- package/package.json +1 -1
- package/src/components/chat/chat-message-skeleton.tsx +40 -9
- package/src/components/chat/chat-panel-header.tsx +28 -16
- package/src/components/chat/chat-quick-action-row.tsx +33 -0
- package/src/components/chat/embeddable-chat.tsx +11 -1
- package/src/components/chat/hooks/index.ts +1 -0
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +30 -1
- package/src/components/chat/hooks/use-scripted-stream.ts +69 -0
- package/src/components/chat/quick-action-chip.tsx +6 -1
- package/src/components/chat/utils/index.ts +2 -0
- package/src/components/chat/utils/scripted-stream.ts +111 -0
- package/src/components/features/board/index.ts +2 -2
- package/src/components/features/board/ticket-card.tsx +108 -64
- package/src/components/ui/index.ts +1 -0
- package/src/components/ui/info-card-row.tsx +70 -0
- package/src/components/ui/tag.tsx +7 -0
- package/src/stories/InfoCardRow.stories.tsx +68 -0
- package/dist/chunk-OBDDE4WV.js.map +0 -1
- package/dist/chunk-SJTNVETH.cjs.map +0 -1
- /package/dist/{chunk-43KGFHI3.js.map → chunk-2ELINEWQ.js.map} +0 -0
- /package/dist/{chunk-55EIHSOQ.js.map → chunk-3AWPRVFM.js.map} +0 -0
- /package/dist/{chunk-SLCANQN3.js.map → chunk-3OKVH6FW.js.map} +0 -0
- /package/dist/{chunk-UXXCNZXC.js.map → chunk-3XWBE3SM.js.map} +0 -0
- /package/dist/{chunk-7JUUQAAH.js.map → chunk-67LZDLIO.js.map} +0 -0
- /package/dist/{chunk-YP3DO4BM.js.map → chunk-A6JKKKIF.js.map} +0 -0
- /package/dist/{chunk-RR77Z27A.js.map → chunk-AY5TBDWZ.js.map} +0 -0
- /package/dist/{chunk-CVYVCBMM.js.map → chunk-IMOJWQDR.js.map} +0 -0
- /package/dist/{chunk-DA2VSDAX.js.map → chunk-NZBQGODM.js.map} +0 -0
- /package/dist/{chunk-DQ5NRLCG.js.map → chunk-S7UXICHB.js.map} +0 -0
- /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
|
|
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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|