@flamingo-stack/openframe-frontend-core 0.0.383 → 0.0.384-snapshot.20260708165506
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-FL6YJLPC.cjs → chunk-4PXS3XNY.cjs} +7 -7
- package/dist/{chunk-FL6YJLPC.cjs.map → chunk-4PXS3XNY.cjs.map} +1 -1
- package/dist/{chunk-KGSOMSHT.cjs → chunk-4RGWQE26.cjs} +25 -25
- package/dist/{chunk-KGSOMSHT.cjs.map → chunk-4RGWQE26.cjs.map} +1 -1
- package/dist/{chunk-ZFIDR55N.cjs → chunk-5SMNVXIF.cjs} +9 -9
- package/dist/{chunk-ZFIDR55N.cjs.map → chunk-5SMNVXIF.cjs.map} +1 -1
- package/dist/{chunk-KQQ6U4UW.js → chunk-AIMUYSOW.js} +4 -4
- package/dist/{chunk-LI7OAUSA.js → chunk-BFLPXPWF.js} +4 -4
- package/dist/{chunk-AJJF43ZL.js → chunk-CES6HK7Y.js} +2 -2
- package/dist/{chunk-WVJFSCXR.cjs → chunk-FLYWXXYF.cjs} +26 -26
- package/dist/{chunk-WVJFSCXR.cjs.map → chunk-FLYWXXYF.cjs.map} +1 -1
- package/dist/{chunk-VQCV3GGI.cjs → chunk-FX75P47M.cjs} +186 -121
- package/dist/chunk-FX75P47M.cjs.map +1 -0
- package/dist/{chunk-CNDLMBFN.js → chunk-IZIVDLN5.js} +349 -284
- package/dist/chunk-IZIVDLN5.js.map +1 -0
- package/dist/{chunk-SGMZQTP6.js → chunk-KOYLFI7L.js} +2 -2
- package/dist/{chunk-YW6K7IYF.cjs → chunk-KUUKMK6R.cjs} +31 -31
- package/dist/{chunk-YW6K7IYF.cjs.map → chunk-KUUKMK6R.cjs.map} +1 -1
- package/dist/{chunk-AHCBZ6TC.cjs → chunk-L3I7HAZ2.cjs} +62 -62
- package/dist/{chunk-AHCBZ6TC.cjs.map → chunk-L3I7HAZ2.cjs.map} +1 -1
- package/dist/{chunk-MY7TV47R.js → chunk-LB5NHXL2.js} +2 -2
- package/dist/{chunk-6SR7HXGW.js → chunk-MKTUZSHY.js} +4 -4
- package/dist/{chunk-KBG3PLKS.js → chunk-OHVOXFGF.js} +2 -2
- package/dist/{chunk-EGFLSQEJ.js → chunk-QS4HXEQH.js} +4 -4
- package/dist/{chunk-KFTCNBSJ.cjs → chunk-RPBA7EVD.cjs} +5 -5
- package/dist/{chunk-KFTCNBSJ.cjs.map → chunk-RPBA7EVD.cjs.map} +1 -1
- package/dist/{chunk-7TU6MLIQ.cjs → chunk-U6ZRFH6F.cjs} +12 -12
- package/dist/{chunk-7TU6MLIQ.cjs.map → chunk-U6ZRFH6F.cjs.map} +1 -1
- package/dist/{chunk-ZD6GZX7E.js → chunk-UT4OHF5S.js} +2 -2
- package/dist/{chunk-S2V4EPKL.cjs → chunk-WT37HVVE.cjs} +15 -15
- package/dist/{chunk-S2V4EPKL.cjs.map → chunk-WT37HVVE.cjs.map} +1 -1
- package/dist/{chunk-BALSUWWU.cjs → chunk-XS6VOGM6.cjs} +37 -37
- package/dist/{chunk-BALSUWWU.cjs.map → chunk-XS6VOGM6.cjs.map} +1 -1
- package/dist/{chunk-W456FUZG.cjs → chunk-ZHHJEUXL.cjs} +57 -57
- package/dist/{chunk-W456FUZG.cjs.map → chunk-ZHHJEUXL.cjs.map} +1 -1
- package/dist/{chunk-B45U4FY5.js → chunk-ZI2ACZJG.js} +2 -2
- package/dist/{chunk-EBOK7SG7.js → chunk-ZLD3BKEI.js} +2 -2
- 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 +20 -0
- package/dist/components/chat/approval-batch-message.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +4 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +3 -1
- 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 +8 -2
- package/dist/components/features/notifications/notification-tile.d.ts.map +1 -1
- package/dist/components/features/notifications/types.d.ts +11 -1
- 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 +122 -120
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +11 -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/data-table/data-table-body.d.ts +6 -1
- package/dist/components/ui/data-table/data-table-body.d.ts.map +1 -1
- package/dist/components/ui/data-table/data-table-row.d.ts +7 -1
- package/dist/components/ui/data-table/data-table-row.d.ts.map +1 -1
- package/dist/components/ui/data-table/index.d.ts +1 -1
- package/dist/components/ui/index.cjs +4 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +3 -1
- package/dist/index.cjs +4 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -1
- package/package.json +1 -1
- package/src/components/chat/approval-batch-message.tsx +28 -15
- package/src/components/features/notifications/approval-request-notification-tile.tsx +70 -46
- package/src/components/features/notifications/notification-tile.tsx +125 -41
- package/src/components/features/notifications/types.ts +11 -1
- package/src/components/ui/data-table/data-table-body.tsx +7 -0
- package/src/components/ui/data-table/data-table-row.tsx +12 -1
- package/src/stories/NotificationDrawer.stories.tsx +220 -31
- package/dist/chunk-CNDLMBFN.js.map +0 -1
- package/dist/chunk-VQCV3GGI.cjs.map +0 -1
- /package/dist/{chunk-KQQ6U4UW.js.map → chunk-AIMUYSOW.js.map} +0 -0
- /package/dist/{chunk-LI7OAUSA.js.map → chunk-BFLPXPWF.js.map} +0 -0
- /package/dist/{chunk-AJJF43ZL.js.map → chunk-CES6HK7Y.js.map} +0 -0
- /package/dist/{chunk-SGMZQTP6.js.map → chunk-KOYLFI7L.js.map} +0 -0
- /package/dist/{chunk-MY7TV47R.js.map → chunk-LB5NHXL2.js.map} +0 -0
- /package/dist/{chunk-6SR7HXGW.js.map → chunk-MKTUZSHY.js.map} +0 -0
- /package/dist/{chunk-KBG3PLKS.js.map → chunk-OHVOXFGF.js.map} +0 -0
- /package/dist/{chunk-EGFLSQEJ.js.map → chunk-QS4HXEQH.js.map} +0 -0
- /package/dist/{chunk-ZD6GZX7E.js.map → chunk-UT4OHF5S.js.map} +0 -0
- /package/dist/{chunk-B45U4FY5.js.map → chunk-ZI2ACZJG.js.map} +0 -0
- /package/dist/{chunk-EBOK7SG7.js.map → chunk-ZLD3BKEI.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 } 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
|
|
52
|
-
|
|
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
|
|
55
|
-
|
|
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
|
|
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
|
-
|
|
64
|
-
|
|
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={
|
|
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
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
|
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
|
-
/**
|
|
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 {
|
|
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
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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-
|
|
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-
|
|
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
|
-
{
|
|
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
|
|
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[
|
|
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
|
|
@@ -29,6 +29,11 @@ export interface DataTableBodyProps<T = any> {
|
|
|
29
29
|
rowClassName?: string | ((item: T, index: number) => string)
|
|
30
30
|
/** Dense row height. */
|
|
31
31
|
compact?: boolean
|
|
32
|
+
/**
|
|
33
|
+
* Treat the design row height as a minimum so multi-line cell content grows
|
|
34
|
+
* the row instead of clipping. Default keeps the fixed height.
|
|
35
|
+
*/
|
|
36
|
+
autoHeight?: boolean
|
|
32
37
|
/**
|
|
33
38
|
* Click anywhere on a row (except elements with `data-no-row-click`). Prefer
|
|
34
39
|
* `useCallback` to avoid breaking `React.memo` on rows.
|
|
@@ -66,6 +71,7 @@ export function DataTableBody<T = any>({
|
|
|
66
71
|
className,
|
|
67
72
|
rowClassName,
|
|
68
73
|
compact,
|
|
74
|
+
autoHeight,
|
|
69
75
|
onRowClick,
|
|
70
76
|
rowHref,
|
|
71
77
|
minRows,
|
|
@@ -112,6 +118,7 @@ export function DataTableBody<T = any>({
|
|
|
112
118
|
onClick={onRowClick}
|
|
113
119
|
href={href}
|
|
114
120
|
compact={compact}
|
|
121
|
+
autoHeight={autoHeight}
|
|
115
122
|
className={cls}
|
|
116
123
|
subRow={renderSubRow?.(item)}
|
|
117
124
|
/>
|
|
@@ -13,6 +13,12 @@ export interface DataTableRowProps<T> {
|
|
|
13
13
|
href?: string | null
|
|
14
14
|
/** Dense row height. */
|
|
15
15
|
compact?: boolean
|
|
16
|
+
/**
|
|
17
|
+
* Treat the design row height as a minimum: a cell rendering multi-line
|
|
18
|
+
* content (e.g. a line-clamped description) grows the row instead of being
|
|
19
|
+
* clipped. Default keeps the fixed height.
|
|
20
|
+
*/
|
|
21
|
+
autoHeight?: boolean
|
|
16
22
|
className?: string
|
|
17
23
|
/** Expandable content rendered below the cells, inside the same card. */
|
|
18
24
|
subRow?: ReactNode
|
|
@@ -60,6 +66,7 @@ function DataTableRowImpl<T>({
|
|
|
60
66
|
onClick,
|
|
61
67
|
href,
|
|
62
68
|
compact,
|
|
69
|
+
autoHeight,
|
|
63
70
|
className,
|
|
64
71
|
subRow,
|
|
65
72
|
}: DataTableRowProps<T>) {
|
|
@@ -100,7 +107,11 @@ function DataTableRowImpl<T>({
|
|
|
100
107
|
<div
|
|
101
108
|
className={cn(
|
|
102
109
|
'flex items-center gap-[var(--spacing-system-mf)] px-[var(--spacing-system-mf)]',
|
|
103
|
-
compact
|
|
110
|
+
compact
|
|
111
|
+
? 'py-[var(--spacing-system-xsf)]'
|
|
112
|
+
: autoHeight
|
|
113
|
+
? 'py-[var(--spacing-system-sf)] min-h-[68px] md:min-h-[80px]'
|
|
114
|
+
: `py-0 ${ROW_HEIGHT_DESKTOP}`,
|
|
104
115
|
hasSubRow && 'border-b border-ods-border',
|
|
105
116
|
)}
|
|
106
117
|
>
|