@flamingo-stack/openframe-frontend-core 0.0.382 → 0.0.383-snapshot.20260708154433

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 (101) hide show
  1. package/dist/{chunk-JQT7PD6Z.cjs → chunk-AZOME5YC.cjs} +57 -57
  2. package/dist/{chunk-JQT7PD6Z.cjs.map → chunk-AZOME5YC.cjs.map} +1 -1
  3. package/dist/{chunk-PI3XFKEW.js → chunk-EEUUTWFQ.js} +2 -2
  4. package/dist/{chunk-HDWY2SXF.js → chunk-FBUTS5UQ.js} +4 -4
  5. package/dist/{chunk-QQAHEWSR.cjs → chunk-FG4J67VX.cjs} +31 -31
  6. package/dist/{chunk-QQAHEWSR.cjs.map → chunk-FG4J67VX.cjs.map} +1 -1
  7. package/dist/{chunk-K5P6XKIT.js → chunk-FQR2CNKB.js} +2 -2
  8. package/dist/{chunk-7IQK3Y3G.cjs → chunk-FSN3GIUJ.cjs} +25 -25
  9. package/dist/{chunk-7IQK3Y3G.cjs.map → chunk-FSN3GIUJ.cjs.map} +1 -1
  10. package/dist/{chunk-EE26RXMG.cjs → chunk-FV2FQQYF.cjs} +12 -12
  11. package/dist/{chunk-EE26RXMG.cjs.map → chunk-FV2FQQYF.cjs.map} +1 -1
  12. package/dist/{chunk-32YONDMH.cjs → chunk-H7PRWFAQ.cjs} +26 -26
  13. package/dist/{chunk-32YONDMH.cjs.map → chunk-H7PRWFAQ.cjs.map} +1 -1
  14. package/dist/{chunk-GZ5N76H2.js → chunk-JEADRVBL.js} +4 -4
  15. package/dist/{chunk-LRA7FGQC.js → chunk-K7YBWMJ6.js} +4 -4
  16. package/dist/{chunk-AS6FET7G.cjs → chunk-KI5QS5C2.cjs} +15 -15
  17. package/dist/{chunk-AS6FET7G.cjs.map → chunk-KI5QS5C2.cjs.map} +1 -1
  18. package/dist/{chunk-23PZFAET.js → chunk-KJ3O7A65.js} +4 -4
  19. package/dist/{chunk-IENK6OY5.cjs → chunk-KJTGCH3I.cjs} +5 -5
  20. package/dist/{chunk-IENK6OY5.cjs.map → chunk-KJTGCH3I.cjs.map} +1 -1
  21. package/dist/{chunk-DQJCWNRT.js → chunk-LMIN2CWQ.js} +2 -2
  22. package/dist/{chunk-QI3PW4F7.cjs → chunk-MMYAOMD4.cjs} +7 -7
  23. package/dist/{chunk-QI3PW4F7.cjs.map → chunk-MMYAOMD4.cjs.map} +1 -1
  24. package/dist/{chunk-4A4DA7OX.js → chunk-MVLWQ6OZ.js} +2 -2
  25. package/dist/{chunk-SZSBHBXE.cjs → chunk-NGWYDTZZ.cjs} +666 -598
  26. package/dist/chunk-NGWYDTZZ.cjs.map +1 -0
  27. package/dist/{chunk-XHCRZDM6.cjs → chunk-Q2OBKYTK.cjs} +37 -37
  28. package/dist/{chunk-XHCRZDM6.cjs.map → chunk-Q2OBKYTK.cjs.map} +1 -1
  29. package/dist/{chunk-L3DRZ2WE.cjs → chunk-Q6EGRFVE.cjs} +62 -62
  30. package/dist/{chunk-L3DRZ2WE.cjs.map → chunk-Q6EGRFVE.cjs.map} +1 -1
  31. package/dist/{chunk-3GGIVFSI.js → chunk-U22GJXOD.js} +2 -2
  32. package/dist/{chunk-LIK575RM.js → chunk-UOLDL3U3.js} +2 -2
  33. package/dist/{chunk-GZIHB6TH.js → chunk-VZ4NJBQT.js} +364 -296
  34. package/dist/chunk-VZ4NJBQT.js.map +1 -0
  35. package/dist/{chunk-Z56MCQ6X.cjs → chunk-WRAECAST.cjs} +9 -9
  36. package/dist/{chunk-Z56MCQ6X.cjs.map → chunk-WRAECAST.cjs.map} +1 -1
  37. package/dist/{chunk-MGDDM2UI.js → chunk-XGE53EOX.js} +2 -2
  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/approval-batch-message.d.ts +20 -0
  41. package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
  42. package/dist/components/chat/index.cjs +4 -2
  43. package/dist/components/chat/index.cjs.map +1 -1
  44. package/dist/components/chat/index.js +3 -1
  45. package/dist/components/contact/index.cjs +3 -3
  46. package/dist/components/contact/index.js +2 -2
  47. package/dist/components/docs/index.cjs +5 -5
  48. package/dist/components/docs/index.js +4 -4
  49. package/dist/components/embeds/index.cjs +3 -3
  50. package/dist/components/embeds/index.js +2 -2
  51. package/dist/components/faq/index.cjs +3 -3
  52. package/dist/components/faq/index.js +2 -2
  53. package/dist/components/features/auth/create-organization-form.d.ts +8 -1
  54. package/dist/components/features/auth/create-organization-form.d.ts.map +1 -1
  55. package/dist/components/features/index.cjs +2 -2
  56. package/dist/components/features/index.js +1 -1
  57. package/dist/components/features/notifications/approval-request-notification-tile.d.ts.map +1 -1
  58. package/dist/components/features/notifications/notification-tile.d.ts +8 -2
  59. package/dist/components/features/notifications/notification-tile.d.ts.map +1 -1
  60. package/dist/components/features/notifications/types.d.ts +11 -1
  61. package/dist/components/features/notifications/types.d.ts.map +1 -1
  62. package/dist/components/help-center-pages/index.cjs +20 -20
  63. package/dist/components/help-center-pages/index.js +11 -11
  64. package/dist/components/index.cjs +122 -120
  65. package/dist/components/index.cjs.map +1 -1
  66. package/dist/components/index.js +11 -9
  67. package/dist/components/index.js.map +1 -1
  68. package/dist/components/navigation/index.cjs +2 -2
  69. package/dist/components/navigation/index.js +1 -1
  70. package/dist/components/onboarding-guides/index.cjs +5 -5
  71. package/dist/components/onboarding-guides/index.js +4 -4
  72. package/dist/components/related-content/index.cjs +3 -3
  73. package/dist/components/related-content/index.js +2 -2
  74. package/dist/components/tickets/index.cjs +5 -5
  75. package/dist/components/tickets/index.js +4 -4
  76. package/dist/components/ui/index.cjs +4 -2
  77. package/dist/components/ui/index.cjs.map +1 -1
  78. package/dist/components/ui/index.js +3 -1
  79. package/dist/index.cjs +4 -2
  80. package/dist/index.cjs.map +1 -1
  81. package/dist/index.js +3 -1
  82. package/package.json +1 -1
  83. package/src/components/chat/approval-batch-message.tsx +28 -15
  84. package/src/components/features/auth/create-organization-form.tsx +21 -10
  85. package/src/components/features/notifications/approval-request-notification-tile.tsx +70 -46
  86. package/src/components/features/notifications/notification-tile.tsx +125 -41
  87. package/src/components/features/notifications/types.ts +11 -1
  88. package/src/stories/NotificationDrawer.stories.tsx +220 -31
  89. package/dist/chunk-GZIHB6TH.js.map +0 -1
  90. package/dist/chunk-SZSBHBXE.cjs.map +0 -1
  91. /package/dist/{chunk-PI3XFKEW.js.map → chunk-EEUUTWFQ.js.map} +0 -0
  92. /package/dist/{chunk-HDWY2SXF.js.map → chunk-FBUTS5UQ.js.map} +0 -0
  93. /package/dist/{chunk-K5P6XKIT.js.map → chunk-FQR2CNKB.js.map} +0 -0
  94. /package/dist/{chunk-GZ5N76H2.js.map → chunk-JEADRVBL.js.map} +0 -0
  95. /package/dist/{chunk-LRA7FGQC.js.map → chunk-K7YBWMJ6.js.map} +0 -0
  96. /package/dist/{chunk-23PZFAET.js.map → chunk-KJ3O7A65.js.map} +0 -0
  97. /package/dist/{chunk-DQJCWNRT.js.map → chunk-LMIN2CWQ.js.map} +0 -0
  98. /package/dist/{chunk-4A4DA7OX.js.map → chunk-MVLWQ6OZ.js.map} +0 -0
  99. /package/dist/{chunk-3GGIVFSI.js.map → chunk-U22GJXOD.js.map} +0 -0
  100. /package/dist/{chunk-LIK575RM.js.map → chunk-UOLDL3U3.js.map} +0 -0
  101. /package/dist/{chunk-MGDDM2UI.js.map → chunk-XGE53EOX.js.map} +0 -0
@@ -1,8 +1,8 @@
1
1
  'use client'
2
2
 
3
3
  import { useMemo, useState } from 'react'
4
- import { cn } from '../../../utils/cn'
5
- import { ApprovalBatchMessage } from '../../chat/approval-batch-message'
4
+ import { Button } from '../../ui/button'
5
+ import { ApprovalBatchMessage, ApprovalStatusTag } from '../../chat/approval-batch-message'
6
6
  import { ExpandChevron } from '../../chat/expand-chevron'
7
7
  import { useCollapsible } from '../../chat/hooks/use-collapsible'
8
8
  import type { ChatApprovalStatus } from '../../chat/types'
@@ -35,6 +35,7 @@ export function ApprovalRequestNotificationTile({
35
35
  // Toggling the command section pins the tile so a live pop-up doesn't
36
36
  // auto-dismiss out from under the user mid-read.
37
37
  const [pinned, setPinned] = useState(defaultExpanded)
38
+ const [processing, setProcessing] = useState(false)
38
39
  // Optimistic status for this user's own click; backend resolution (below) wins once it arrives.
39
40
  const [localStatus, setLocalStatus] = useState<ChatApprovalStatus | null>(null)
40
41
 
@@ -48,66 +49,89 @@ export function ApprovalRequestNotificationTile({
48
49
 
49
50
  if (!approval || !batchData) return null
50
51
 
51
- const commandWord = batchData.toolCalls.length > 1 ? 'Commands' : 'Command'
52
- const toggleLabel = `${expanded ? 'Hide' : 'Show'} ${commandWord}`
52
+ const tileNotification: Notification = {
53
+ ...notification,
54
+ type: notification.type ?? 'Approval Required',
55
+ }
56
+
57
+ const commandWord = batchData.toolCalls.length > 1 ? 'commands' : 'command'
58
+ const toggleLabel = `${expanded ? 'Hide' : 'Show'} ${commandWord} section`
53
59
 
54
- const handleApprove = async () => {
55
- setLocalStatus('approved')
60
+ const resolve = async (
61
+ nextStatus: 'approved' | 'rejected',
62
+ action: (approvalRequestId: string) => void | Promise<void>,
63
+ ) => {
64
+ setProcessing(true)
65
+ setLocalStatus(nextStatus)
56
66
  try {
57
- await onApprove(batchData.approvalRequestId)
58
- onComplete(notification.id)
67
+ await action(batchData.approvalRequestId)
59
68
  } catch {
60
69
  setLocalStatus(null) // roll back the optimistic flip; the request failed
70
+ return
71
+ } finally {
72
+ setProcessing(false)
61
73
  }
62
- }
63
- const handleReject = async () => {
64
- setLocalStatus('rejected')
65
- try {
66
- await onReject(batchData.approvalRequestId)
67
- onComplete(notification.id)
68
- } catch {
69
- setLocalStatus(null)
70
- }
74
+ // Outside the try: a throwing consumer callback must not roll back a request
75
+ // that already succeeded server-side.
76
+ onComplete(notification.id)
71
77
  }
72
78
 
73
79
  return (
74
80
  <NotificationTile
75
- notification={notification}
81
+ notification={tileNotification}
76
82
  liveDurationMs={liveDurationMs}
77
83
  onComplete={onComplete}
78
84
  onSettle={onSettle}
79
85
  className={className}
80
- paused={pinned}
86
+ paused={pinned || processing}
87
+ actions={
88
+ <div className="flex w-full items-center gap-[var(--spacing-system-xs)]">
89
+ {status === 'pending' ? (
90
+ <>
91
+ <Button variant="accent" size="small" onClick={() => resolve('approved', onApprove)} disabled={processing}>
92
+ Approve
93
+ </Button>
94
+ <Button variant="outline" size="small" onClick={() => resolve('rejected', onReject)} disabled={processing}>
95
+ Reject
96
+ </Button>
97
+ </>
98
+ ) : (
99
+ <span className="flex min-w-0 items-center gap-[var(--spacing-system-xsf)]">
100
+ <ApprovalStatusTag status={status} />
101
+ {approval.resolvedByName ? (
102
+ <span className="truncate text-h6 text-ods-text-secondary">by {approval.resolvedByName}</span>
103
+ ) : null}
104
+ </span>
105
+ )}
106
+ <Button
107
+ variant="outline"
108
+ size="small"
109
+ onClick={() => {
110
+ setExpanded(prev => !prev)
111
+ setPinned(true)
112
+ }}
113
+ aria-expanded={expanded}
114
+ aria-label={toggleLabel}
115
+ className="ml-auto w-6 shrink-0 px-0 md:w-8"
116
+ >
117
+ <ExpandChevron expanded={expanded} />
118
+ </Button>
119
+ </div>
120
+ }
81
121
  >
82
- {/* Persistent collapse/expand control for the command section. */}
83
- <button
84
- type="button"
85
- onClick={() => {
86
- setExpanded(prev => !prev)
87
- setPinned(true)
88
- }}
89
- aria-expanded={expanded}
90
- className={cn(
91
- 'flex w-full items-center gap-[var(--spacing-system-xs)] bg-ods-card px-[var(--spacing-system-s)] py-[var(--spacing-system-xs)] text-left',
92
- expanded && 'border-b border-ods-border',
93
- )}
94
- >
95
- <span className="min-w-0 flex-1 text-h6 text-ods-text-primary">{toggleLabel}</span>
96
- <ExpandChevron expanded={expanded} />
97
- </button>
98
-
99
122
  <div style={containerStyle}>
123
+ {/* Divider lives inside the measured element — useCollapsible caps the
124
+ container at the inner scrollHeight, which excludes the inner's own borders. */}
100
125
  <div ref={innerRef}>
101
- <ApprovalBatchMessage
102
- data={batchData}
103
- status={status}
104
- resolvedByName={approval.resolvedByName}
105
- showExecutionStatus={false}
106
- onApprove={handleApprove}
107
- onReject={handleReject}
108
- maxBodyHeight="50vh"
109
- className="mb-0 rounded-none border-0"
110
- />
126
+ <div className="border-t border-ods-border">
127
+ <ApprovalBatchMessage
128
+ data={batchData}
129
+ showExecutionStatus={false}
130
+ showFooterActions={false}
131
+ maxBodyHeight="50vh"
132
+ className="mb-0 rounded-none border-0"
133
+ />
134
+ </div>
111
135
  </div>
112
136
  </div>
113
137
  </NotificationTile>
@@ -1,12 +1,32 @@
1
1
  'use client'
2
2
 
3
- import { useMemo, useEffect, type ReactNode } from 'react'
3
+ import { useMemo, useEffect, useState, type ReactNode } from 'react'
4
4
  import { CheckCircleIcon } from '../../icons-v2-generated/signs-and-symbols/check-circle-icon'
5
5
  import { XmarkIcon } from '../../icons-v2-generated/signs-and-symbols/xmark-icon'
6
- import { Button } from '../../ui/button/button'
7
6
  import { dotColorByVariant, progressColorByVariant } from '../../ui/toaster'
8
7
  import { cn } from '../../../utils/cn'
9
- import type { Notification } from './types'
8
+ import { formatTicketRelativeTime } from '../../../utils/date-utils'
9
+ import type { Notification, NotificationSeverity, NotificationVariant } from './types'
10
+
11
+ /** Backend severity → tile color variant; overrides `notification.variant` when present. */
12
+ const variantBySeverity: Record<NotificationSeverity, NotificationVariant> = {
13
+ INFO: 'info',
14
+ SUCCESS: 'success',
15
+ WARNING: 'warning',
16
+ DANGER: 'error',
17
+ }
18
+
19
+ /** Severity color applied to the header type label and icon slot. */
20
+ const typeColorByVariant: Record<NotificationVariant, string> = {
21
+ default: 'text-ods-text-secondary',
22
+ info: 'text-ods-text-secondary',
23
+ success: 'text-ods-success',
24
+ warning: 'text-ods-warning',
25
+ error: 'text-ods-error',
26
+ }
27
+
28
+ const headerControlClass =
29
+ 'absolute inset-0 flex items-center justify-center text-ods-text-secondary transition-opacity duration-200 hover:text-ods-text-primary'
10
30
 
11
31
  export interface NotificationTileProps {
12
32
  notification: Notification
@@ -14,7 +34,13 @@ export interface NotificationTileProps {
14
34
  onComplete: (id: string) => void
15
35
  onSettle?: (id: string) => void
16
36
  className?: string
17
- /** Extra content rendered below the header inside the same card (e.g. an approval command section). */
37
+ /** Action row rendered below the body inside the padded section (e.g. approval buttons). */
38
+ actions?: ReactNode
39
+ /**
40
+ * Extra content rendered below the padded section (e.g. a collapsible approval command
41
+ * section). Bring your own top divider — the tile no longer draws one, so a zero-height
42
+ * collapsed child doesn't leave a stray border above the card's bottom edge.
43
+ */
18
44
  children?: ReactNode
19
45
  /** Pin the tile: cancel the live auto-dismiss countdown (timer + progress bar) without settling it. */
20
46
  paused?: boolean
@@ -26,10 +52,36 @@ export function NotificationTile({
26
52
  onComplete,
27
53
  onSettle,
28
54
  className,
55
+ actions,
29
56
  children,
30
57
  paused = false,
31
58
  }: NotificationTileProps) {
32
- const { id, variant = 'default', title, description, createdAt, read, settled } = notification
59
+ const {
60
+ id,
61
+ variant = 'default',
62
+ severity,
63
+ type,
64
+ icon,
65
+ imageUrl,
66
+ title,
67
+ description,
68
+ createdAt,
69
+ read,
70
+ settled,
71
+ } = notification
72
+
73
+ // Unknown severity strings (backend data can outrun the union) degrade to `variant`.
74
+ const accentVariant = (severity ? variantBySeverity[severity] : undefined) ?? variant
75
+
76
+ // A blocked/broken image (ad-blockers routinely kill external avatars) falls
77
+ // through to the icon/category/dot chain instead of a broken-image glyph.
78
+ const [failedImageUrl, setFailedImageUrl] = useState<string | null>(null)
79
+ const image = imageUrl && imageUrl !== failedImageUrl ? imageUrl : undefined
80
+
81
+ // Gate on the Date, not the number: NaN, Infinity AND finite-but-out-of-range
82
+ // epochs (e.g. nanoseconds) all make toISOString() throw.
83
+ const createdAtDate = new Date(createdAt)
84
+ const createdAtIso = Number.isNaN(createdAtDate.getTime()) ? null : createdAtDate.toISOString()
33
85
 
34
86
  const initialElapsed = useMemo(() => Date.now() - createdAt, [createdAt])
35
87
  const isLive = !read && !settled && initialElapsed < liveDurationMs
@@ -53,43 +105,89 @@ export function NotificationTile({
53
105
  >
54
106
  <div
55
107
  className={cn(
56
- 'relative flex items-center gap-[var(--spacing-system-xs)] p-[var(--spacing-system-s)]',
57
- children ? 'border-b border-ods-border' : null,
108
+ 'relative flex flex-col gap-[var(--spacing-system-xs)] p-[var(--spacing-system-s)]',
109
+ isLive && 'pb-[var(--spacing-system-m)]',
58
110
  )}
59
111
  >
60
- <div className="flex size-6 shrink-0 items-center justify-center">
61
- <span className={cn('size-1.5 rounded-full', dotColorByVariant[variant])} />
112
+ {/* Header: icon/image + type label (both severity-colored) + time + dismiss/complete control. */}
113
+ <div className="flex items-center gap-[var(--spacing-system-xs)]">
114
+ <span
115
+ className={cn(
116
+ 'flex size-4 shrink-0 items-center justify-center',
117
+ typeColorByVariant[accentVariant],
118
+ )}
119
+ >
120
+ {image ? (
121
+ <img
122
+ src={image}
123
+ alt=""
124
+ onError={() => setFailedImageUrl(image)}
125
+ className="size-4 rounded-[2px] object-cover"
126
+ />
127
+ ) : (
128
+ icon ?? <span className={cn('size-1.5 rounded-full', dotColorByVariant[accentVariant])} />
129
+ )}
130
+ </span>
131
+
132
+ {type ? (
133
+ <p
134
+ className={cn('min-w-0 flex-1 truncate text-h5', typeColorByVariant[accentVariant])}
135
+ title={type}
136
+ >
137
+ {type}
138
+ </p>
139
+ ) : (
140
+ <span className="min-w-0 flex-1" />
141
+ )}
142
+
143
+ {!isLive && createdAtIso ? (
144
+ <time
145
+ dateTime={createdAtIso}
146
+ className="shrink-0 whitespace-nowrap text-h6 text-ods-text-secondary"
147
+ >
148
+ {formatTicketRelativeTime(createdAtIso)}
149
+ </time>
150
+ ) : null}
151
+
152
+ {/* Live X and settled check swap in the same 16px slot; the inactive
153
+ one is removed from the a11y tree and disabled, not just faded. */}
154
+ <span className="relative size-4 shrink-0">
155
+ {[
156
+ { active: isLive, label: 'Dismiss notification', icon: <XmarkIcon size={16} />, onClick: () => onSettle?.(id) },
157
+ { active: !isLive, label: 'Mark notification complete', icon: <CheckCircleIcon size={16} />, onClick: () => onComplete(id) },
158
+ ].map(({ active, label, icon, onClick }) => (
159
+ <button
160
+ key={label}
161
+ type="button"
162
+ onClick={onClick}
163
+ aria-label={label}
164
+ aria-hidden={!active}
165
+ disabled={!active}
166
+ tabIndex={active ? 0 : -1}
167
+ className={cn(headerControlClass, !active && 'pointer-events-none opacity-0')}
168
+ >
169
+ {icon}
170
+ </button>
171
+ ))}
172
+ </span>
62
173
  </div>
63
174
 
64
- <div className="flex min-w-0 flex-1 flex-col">
175
+ <div className="flex min-w-0 flex-col">
65
176
  {title ? <p className="truncate text-h4 text-ods-text-primary" title={typeof title === 'string' ? title : undefined}>{title}</p> : null}
66
177
  {description ? (
67
- <p className="text-h6 line-clamp-2 text-ods-text-secondary" title={typeof description === 'string' ? description : undefined}>{description}</p>
178
+ <p className="text-h6 line-clamp-3 text-ods-text-secondary" title={typeof description === 'string' ? description : undefined}>{description}</p>
68
179
  ) : null}
69
180
  </div>
70
181
 
71
- {/* Settled action. */}
72
- <Button
73
- variant="outline"
74
- size="small"
75
- onClick={() => onComplete(id)}
76
- aria-label="Mark notification complete"
77
- tabIndex={isLive ? -1 : 0}
78
- className={cn(
79
- 'shrink-0 hover:text-ods-text-primary',
80
- isLive && 'invisible',
81
- )}
82
- >
83
- <CheckCircleIcon />
84
- </Button>
182
+ {actions}
85
183
 
86
- {/* Progress sits at the header's bottom edge (above any children). */}
184
+ {/* Progress sits at the section's bottom edge (above any children). */}
87
185
  {counting ? (
88
186
  <div
89
187
  aria-hidden
90
188
  className={cn(
91
189
  'absolute inset-x-0 -bottom-px h-1 origin-left',
92
- progressColorByVariant[variant],
190
+ progressColorByVariant[accentVariant],
93
191
  )}
94
192
  style={{
95
193
  animation: `toast-progress ${liveDurationMs}ms linear forwards`,
@@ -100,20 +198,6 @@ export function NotificationTile({
100
198
  </div>
101
199
 
102
200
  {children}
103
-
104
- {/* Live-only bare X overlay */}
105
- <button
106
- type="button"
107
- onClick={() => onSettle?.(id)}
108
- aria-label="Dismiss notification"
109
- tabIndex={isLive ? 0 : -1}
110
- className={cn(
111
- 'absolute right-[var(--spacing-system-xsf)] top-[var(--spacing-system-xsf)] flex size-4 items-center justify-center text-ods-text-secondary transition-opacity duration-200 hover:text-ods-text-primary',
112
- !isLive && 'pointer-events-none opacity-0',
113
- )}
114
- >
115
- <XmarkIcon size={16} />
116
- </button>
117
201
  </output>
118
202
  )
119
203
  }
@@ -3,11 +3,21 @@ import { APPROVAL_STATUS, type ApprovalBatchData, type ChatApprovalStatus } from
3
3
 
4
4
  export type NotificationVariant = 'default' | 'success' | 'warning' | 'error' | 'info'
5
5
 
6
- export type NotificationSeverity = 'INFO' | 'WARNING' | 'DANGER'
6
+ export type NotificationSeverity = 'INFO' | 'SUCCESS' | 'WARNING' | 'DANGER'
7
7
 
8
8
  export interface Notification {
9
9
  id: string
10
10
  variant?: NotificationVariant
11
+ /** Kind label rendered uppercase in the tile header (e.g. "New Customer Created"). */
12
+ type?: string
13
+ /** Custom icon for the tile header slot; inherits the severity color via `currentColor`. */
14
+ icon?: ReactNode
15
+ /**
16
+ * Image URL for the tile header slot; takes precedence over `icon`. Proxy external
17
+ * URLs on the consumer side (ad-blockers routinely block direct third-party avatars);
18
+ * on load failure the tile falls back to `icon`/dot.
19
+ */
20
+ imageUrl?: string
11
21
  title: ReactNode
12
22
  description?: ReactNode
13
23
  createdAt: number