@flamingo-stack/openframe-frontend-core 0.0.383-snapshot.20260708154433 → 0.0.383
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-FBUTS5UQ.js → chunk-6SR7HXGW.js} +4 -4
- package/dist/{chunk-FV2FQQYF.cjs → chunk-7TU6MLIQ.cjs} +12 -12
- package/dist/{chunk-FV2FQQYF.cjs.map → chunk-7TU6MLIQ.cjs.map} +1 -1
- package/dist/{chunk-Q6EGRFVE.cjs → chunk-AHCBZ6TC.cjs} +62 -62
- package/dist/{chunk-Q6EGRFVE.cjs.map → chunk-AHCBZ6TC.cjs.map} +1 -1
- package/dist/{chunk-UOLDL3U3.js → chunk-AJJF43ZL.js} +2 -2
- package/dist/{chunk-FQR2CNKB.js → chunk-B45U4FY5.js} +2 -2
- package/dist/{chunk-Q2OBKYTK.cjs → chunk-BALSUWWU.cjs} +37 -37
- package/dist/{chunk-Q2OBKYTK.cjs.map → chunk-BALSUWWU.cjs.map} +1 -1
- package/dist/{chunk-VZ4NJBQT.js → chunk-CNDLMBFN.js} +283 -345
- package/dist/chunk-CNDLMBFN.js.map +1 -0
- package/dist/{chunk-MVLWQ6OZ.js → chunk-EBOK7SG7.js} +2 -2
- package/dist/{chunk-KJ3O7A65.js → chunk-EGFLSQEJ.js} +4 -4
- package/dist/{chunk-MMYAOMD4.cjs → chunk-FL6YJLPC.cjs} +7 -7
- package/dist/{chunk-MMYAOMD4.cjs.map → chunk-FL6YJLPC.cjs.map} +1 -1
- package/dist/{chunk-XGE53EOX.js → chunk-KBG3PLKS.js} +2 -2
- package/dist/{chunk-KJTGCH3I.cjs → chunk-KFTCNBSJ.cjs} +5 -5
- package/dist/{chunk-KJTGCH3I.cjs.map → chunk-KFTCNBSJ.cjs.map} +1 -1
- package/dist/{chunk-FSN3GIUJ.cjs → chunk-KGSOMSHT.cjs} +25 -25
- package/dist/{chunk-FSN3GIUJ.cjs.map → chunk-KGSOMSHT.cjs.map} +1 -1
- package/dist/{chunk-K7YBWMJ6.js → chunk-KQQ6U4UW.js} +4 -4
- package/dist/{chunk-JEADRVBL.js → chunk-LI7OAUSA.js} +4 -4
- package/dist/{chunk-LMIN2CWQ.js → chunk-MY7TV47R.js} +2 -2
- package/dist/{chunk-KI5QS5C2.cjs → chunk-S2V4EPKL.cjs} +15 -15
- package/dist/{chunk-KI5QS5C2.cjs.map → chunk-S2V4EPKL.cjs.map} +1 -1
- package/dist/{chunk-EEUUTWFQ.js → chunk-SGMZQTP6.js} +2 -2
- package/dist/{chunk-NGWYDTZZ.cjs → chunk-VQCV3GGI.cjs} +120 -182
- package/dist/chunk-VQCV3GGI.cjs.map +1 -0
- package/dist/{chunk-AZOME5YC.cjs → chunk-W456FUZG.cjs} +57 -57
- package/dist/{chunk-AZOME5YC.cjs.map → chunk-W456FUZG.cjs.map} +1 -1
- package/dist/{chunk-H7PRWFAQ.cjs → chunk-WVJFSCXR.cjs} +26 -26
- package/dist/{chunk-H7PRWFAQ.cjs.map → chunk-WVJFSCXR.cjs.map} +1 -1
- package/dist/{chunk-FG4J67VX.cjs → chunk-YW6K7IYF.cjs} +31 -31
- package/dist/{chunk-FG4J67VX.cjs.map → chunk-YW6K7IYF.cjs.map} +1 -1
- package/dist/{chunk-U22GJXOD.js → chunk-ZD6GZX7E.js} +2 -2
- package/dist/{chunk-WRAECAST.cjs → chunk-ZFIDR55N.cjs} +9 -9
- package/dist/{chunk-WRAECAST.cjs.map → chunk-ZFIDR55N.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/approval-batch-message.d.ts +0 -20
- package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -4
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +1 -3
- 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/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/features/notifications/approval-request-notification-tile.d.ts.map +1 -1
- package/dist/components/features/notifications/notification-tile.d.ts +2 -8
- package/dist/components/features/notifications/notification-tile.d.ts.map +1 -1
- package/dist/components/features/notifications/types.d.ts +1 -11
- package/dist/components/features/notifications/types.d.ts.map +1 -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 +120 -122
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +9 -11
- 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 +2 -4
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +1 -3
- package/dist/index.cjs +2 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -3
- package/package.json +1 -1
- package/src/components/chat/approval-batch-message.tsx +15 -28
- package/src/components/features/notifications/approval-request-notification-tile.tsx +46 -70
- package/src/components/features/notifications/notification-tile.tsx +41 -125
- package/src/components/features/notifications/types.ts +1 -11
- package/src/stories/NotificationDrawer.stories.tsx +31 -220
- package/dist/chunk-NGWYDTZZ.cjs.map +0 -1
- package/dist/chunk-VZ4NJBQT.js.map +0 -1
- /package/dist/{chunk-FBUTS5UQ.js.map → chunk-6SR7HXGW.js.map} +0 -0
- /package/dist/{chunk-UOLDL3U3.js.map → chunk-AJJF43ZL.js.map} +0 -0
- /package/dist/{chunk-FQR2CNKB.js.map → chunk-B45U4FY5.js.map} +0 -0
- /package/dist/{chunk-MVLWQ6OZ.js.map → chunk-EBOK7SG7.js.map} +0 -0
- /package/dist/{chunk-KJ3O7A65.js.map → chunk-EGFLSQEJ.js.map} +0 -0
- /package/dist/{chunk-XGE53EOX.js.map → chunk-KBG3PLKS.js.map} +0 -0
- /package/dist/{chunk-K7YBWMJ6.js.map → chunk-KQQ6U4UW.js.map} +0 -0
- /package/dist/{chunk-JEADRVBL.js.map → chunk-LI7OAUSA.js.map} +0 -0
- /package/dist/{chunk-LMIN2CWQ.js.map → chunk-MY7TV47R.js.map} +0 -0
- /package/dist/{chunk-EEUUTWFQ.js.map → chunk-SGMZQTP6.js.map} +0 -0
- /package/dist/{chunk-U22GJXOD.js.map → chunk-ZD6GZX7E.js.map} +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useMemo, useState } from 'react'
|
|
4
|
-
import {
|
|
5
|
-
import { ApprovalBatchMessage
|
|
4
|
+
import { cn } from '../../../utils/cn'
|
|
5
|
+
import { ApprovalBatchMessage } 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,7 +35,6 @@ 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)
|
|
39
38
|
// Optimistic status for this user's own click; backend resolution (below) wins once it arrives.
|
|
40
39
|
const [localStatus, setLocalStatus] = useState<ChatApprovalStatus | null>(null)
|
|
41
40
|
|
|
@@ -49,89 +48,66 @@ export function ApprovalRequestNotificationTile({
|
|
|
49
48
|
|
|
50
49
|
if (!approval || !batchData) return null
|
|
51
50
|
|
|
52
|
-
const
|
|
53
|
-
|
|
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`
|
|
51
|
+
const commandWord = batchData.toolCalls.length > 1 ? 'Commands' : 'Command'
|
|
52
|
+
const toggleLabel = `${expanded ? 'Hide' : 'Show'} ${commandWord}`
|
|
59
53
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
action: (approvalRequestId: string) => void | Promise<void>,
|
|
63
|
-
) => {
|
|
64
|
-
setProcessing(true)
|
|
65
|
-
setLocalStatus(nextStatus)
|
|
54
|
+
const handleApprove = async () => {
|
|
55
|
+
setLocalStatus('approved')
|
|
66
56
|
try {
|
|
67
|
-
await
|
|
57
|
+
await onApprove(batchData.approvalRequestId)
|
|
58
|
+
onComplete(notification.id)
|
|
68
59
|
} catch {
|
|
69
60
|
setLocalStatus(null) // roll back the optimistic flip; the request failed
|
|
70
|
-
return
|
|
71
|
-
} finally {
|
|
72
|
-
setProcessing(false)
|
|
73
61
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
+
}
|
|
77
71
|
}
|
|
78
72
|
|
|
79
73
|
return (
|
|
80
74
|
<NotificationTile
|
|
81
|
-
notification={
|
|
75
|
+
notification={notification}
|
|
82
76
|
liveDurationMs={liveDurationMs}
|
|
83
77
|
onComplete={onComplete}
|
|
84
78
|
onSettle={onSettle}
|
|
85
79
|
className={className}
|
|
86
|
-
paused={pinned
|
|
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
|
-
}
|
|
80
|
+
paused={pinned}
|
|
121
81
|
>
|
|
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
|
+
|
|
122
99
|
<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. */}
|
|
125
100
|
<div ref={innerRef}>
|
|
126
|
-
<
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
/>
|
|
135
111
|
</div>
|
|
136
112
|
</div>
|
|
137
113
|
</NotificationTile>
|
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { useMemo, useEffect,
|
|
3
|
+
import { useMemo, useEffect, 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'
|
|
6
7
|
import { dotColorByVariant, progressColorByVariant } from '../../ui/toaster'
|
|
7
8
|
import { cn } from '../../../utils/cn'
|
|
8
|
-
import {
|
|
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'
|
|
9
|
+
import type { Notification } from './types'
|
|
30
10
|
|
|
31
11
|
export interface NotificationTileProps {
|
|
32
12
|
notification: Notification
|
|
@@ -34,13 +14,7 @@ export interface NotificationTileProps {
|
|
|
34
14
|
onComplete: (id: string) => void
|
|
35
15
|
onSettle?: (id: string) => void
|
|
36
16
|
className?: string
|
|
37
|
-
/**
|
|
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
|
-
*/
|
|
17
|
+
/** Extra content rendered below the header inside the same card (e.g. an approval command section). */
|
|
44
18
|
children?: ReactNode
|
|
45
19
|
/** Pin the tile: cancel the live auto-dismiss countdown (timer + progress bar) without settling it. */
|
|
46
20
|
paused?: boolean
|
|
@@ -52,36 +26,10 @@ export function NotificationTile({
|
|
|
52
26
|
onComplete,
|
|
53
27
|
onSettle,
|
|
54
28
|
className,
|
|
55
|
-
actions,
|
|
56
29
|
children,
|
|
57
30
|
paused = false,
|
|
58
31
|
}: NotificationTileProps) {
|
|
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()
|
|
32
|
+
const { id, variant = 'default', title, description, createdAt, read, settled } = notification
|
|
85
33
|
|
|
86
34
|
const initialElapsed = useMemo(() => Date.now() - createdAt, [createdAt])
|
|
87
35
|
const isLive = !read && !settled && initialElapsed < liveDurationMs
|
|
@@ -105,89 +53,43 @@ export function NotificationTile({
|
|
|
105
53
|
>
|
|
106
54
|
<div
|
|
107
55
|
className={cn(
|
|
108
|
-
'relative flex
|
|
109
|
-
|
|
56
|
+
'relative flex items-center gap-[var(--spacing-system-xs)] p-[var(--spacing-system-s)]',
|
|
57
|
+
children ? 'border-b border-ods-border' : null,
|
|
110
58
|
)}
|
|
111
59
|
>
|
|
112
|
-
|
|
113
|
-
|
|
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>
|
|
60
|
+
<div className="flex size-6 shrink-0 items-center justify-center">
|
|
61
|
+
<span className={cn('size-1.5 rounded-full', dotColorByVariant[variant])} />
|
|
173
62
|
</div>
|
|
174
63
|
|
|
175
|
-
<div className="flex min-w-0 flex-col">
|
|
64
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
176
65
|
{title ? <p className="truncate text-h4 text-ods-text-primary" title={typeof title === 'string' ? title : undefined}>{title}</p> : null}
|
|
177
66
|
{description ? (
|
|
178
|
-
<p className="text-h6 line-clamp-
|
|
67
|
+
<p className="text-h6 line-clamp-2 text-ods-text-secondary" title={typeof description === 'string' ? description : undefined}>{description}</p>
|
|
179
68
|
) : null}
|
|
180
69
|
</div>
|
|
181
70
|
|
|
182
|
-
{
|
|
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>
|
|
183
85
|
|
|
184
|
-
{/* Progress sits at the
|
|
86
|
+
{/* Progress sits at the header's bottom edge (above any children). */}
|
|
185
87
|
{counting ? (
|
|
186
88
|
<div
|
|
187
89
|
aria-hidden
|
|
188
90
|
className={cn(
|
|
189
91
|
'absolute inset-x-0 -bottom-px h-1 origin-left',
|
|
190
|
-
progressColorByVariant[
|
|
92
|
+
progressColorByVariant[variant],
|
|
191
93
|
)}
|
|
192
94
|
style={{
|
|
193
95
|
animation: `toast-progress ${liveDurationMs}ms linear forwards`,
|
|
@@ -198,6 +100,20 @@ export function NotificationTile({
|
|
|
198
100
|
</div>
|
|
199
101
|
|
|
200
102
|
{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>
|
|
201
117
|
</output>
|
|
202
118
|
)
|
|
203
119
|
}
|
|
@@ -3,21 +3,11 @@ 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' | '
|
|
6
|
+
export type NotificationSeverity = 'INFO' | '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
|
|
21
11
|
title: ReactNode
|
|
22
12
|
description?: ReactNode
|
|
23
13
|
createdAt: number
|