@flamingo-stack/openframe-frontend-core 0.0.665 → 0.0.666-snapshot.20260925092147
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-MNUR6PAJ.cjs → chunk-23XIOIGS.cjs} +392 -370
- package/dist/chunk-23XIOIGS.cjs.map +1 -0
- package/dist/{chunk-ZMZUF6FX.cjs → chunk-2FUSTKIV.cjs} +3 -3
- package/dist/{chunk-ZMZUF6FX.cjs.map → chunk-2FUSTKIV.cjs.map} +1 -1
- package/dist/{chunk-EMJAT6IK.cjs → chunk-3A6MM3CK.cjs} +15 -15
- package/dist/{chunk-EMJAT6IK.cjs.map → chunk-3A6MM3CK.cjs.map} +1 -1
- package/dist/{chunk-CQJXOBWK.js → chunk-4FHL6C5E.js} +3 -3
- package/dist/{chunk-Z2MJPTIB.js → chunk-57BIWOGD.js} +3 -3
- package/dist/{chunk-YRMKUDOE.js → chunk-5ZHLPC32.js} +5 -5
- package/dist/{chunk-JHH7NETU.cjs → chunk-6M5NRLFI.cjs} +5 -5
- package/dist/{chunk-JHH7NETU.cjs.map → chunk-6M5NRLFI.cjs.map} +1 -1
- package/dist/{chunk-GDTFJ5QN.js → chunk-7ZIOUUWU.js} +2 -2
- package/dist/{chunk-RT5JNTCN.js → chunk-AAZLZBR5.js} +3 -3
- package/dist/{chunk-XCACXPCM.cjs → chunk-AUHHSY5H.cjs} +22 -22
- package/dist/{chunk-XCACXPCM.cjs.map → chunk-AUHHSY5H.cjs.map} +1 -1
- package/dist/{chunk-FGQSSFP4.cjs → chunk-B76ZH6HA.cjs} +10 -10
- package/dist/{chunk-FGQSSFP4.cjs.map → chunk-B76ZH6HA.cjs.map} +1 -1
- package/dist/{chunk-OLCI3UA5.js → chunk-BO2ZXX4Z.js} +6 -6
- package/dist/{chunk-77WMNQTE.js → chunk-CEPI3A3T.js} +3 -3
- package/dist/{chunk-X32X52VN.cjs → chunk-CIB3QB2T.cjs} +71 -71
- package/dist/{chunk-X32X52VN.cjs.map → chunk-CIB3QB2T.cjs.map} +1 -1
- package/dist/{chunk-LTW2GWJ2.cjs → chunk-CYBRUQ2Z.cjs} +22 -22
- package/dist/{chunk-LTW2GWJ2.cjs.map → chunk-CYBRUQ2Z.cjs.map} +1 -1
- package/dist/{chunk-MBLACUCE.js → chunk-DG763XMY.js} +5 -5
- package/dist/{chunk-LXL3PZE2.cjs → chunk-DKBXW3EI.cjs} +10 -10
- package/dist/{chunk-LXL3PZE2.cjs.map → chunk-DKBXW3EI.cjs.map} +1 -1
- package/dist/{chunk-BPKEDLGR.js → chunk-DYFQCHUY.js} +5 -5
- package/dist/{chunk-QR3NUFA5.js → chunk-GKAZC2JI.js} +2 -2
- package/dist/{chunk-OMTLCMQB.js → chunk-IWCYTHNG.js} +31 -9
- package/dist/chunk-IWCYTHNG.js.map +1 -0
- package/dist/{chunk-6FD7ZEBY.cjs → chunk-JMPZUL2X.cjs} +197 -170
- package/dist/chunk-JMPZUL2X.cjs.map +1 -0
- package/dist/{chunk-RM4S53GB.js → chunk-KS2PRUHN.js} +2 -2
- package/dist/{chunk-O3GB7JFL.js → chunk-M6PDHXI5.js} +3 -3
- package/dist/{chunk-LCYYTWRF.cjs → chunk-QHJ4GEDA.cjs} +4 -4
- package/dist/{chunk-LCYYTWRF.cjs.map → chunk-QHJ4GEDA.cjs.map} +1 -1
- package/dist/{chunk-3SS37SNM.cjs → chunk-SFMFRUY2.cjs} +10 -10
- package/dist/{chunk-3SS37SNM.cjs.map → chunk-SFMFRUY2.cjs.map} +1 -1
- package/dist/{chunk-JHAS6Y7P.js → chunk-TN33GNFP.js} +4 -4
- package/dist/{chunk-DNFC6RYF.js → chunk-TSEYXPXZ.js} +55 -28
- package/dist/{chunk-DNFC6RYF.js.map → chunk-TSEYXPXZ.js.map} +1 -1
- package/dist/{chunk-ZGPST26T.cjs → chunk-VUQWJUW7.cjs} +45 -45
- package/dist/{chunk-ZGPST26T.cjs.map → chunk-VUQWJUW7.cjs.map} +1 -1
- package/dist/{chunk-L4ZNA3NX.cjs → chunk-WD73QMY3.cjs} +3 -3
- package/dist/{chunk-L4ZNA3NX.cjs.map → chunk-WD73QMY3.cjs.map} +1 -1
- package/dist/{chunk-KBUYG5N6.cjs → chunk-YW4LA55U.cjs} +33 -33
- package/dist/{chunk-KBUYG5N6.cjs.map → chunk-YW4LA55U.cjs.map} +1 -1
- package/dist/{chunk-6W5N23LC.cjs → chunk-Z5FOWR7O.cjs} +26 -26
- package/dist/{chunk-6W5N23LC.cjs.map → chunk-Z5FOWR7O.cjs.map} +1 -1
- package/dist/{chunk-BKCPXDOE.js → chunk-ZRD3YAPM.js} +2 -2
- package/dist/components/case-studies/index.cjs +14 -14
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/chat-composer.d.ts +10 -1
- package/dist/components/chat/chat-composer.d.ts.map +1 -1
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +10 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/types/component.types.d.ts +7 -0
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/board/ticket-card.d.ts +13 -3
- package/dist/components/features/board/ticket-card.d.ts.map +1 -1
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +39 -39
- package/dist/components/index.js +13 -13
- package/dist/components/meeting-scheduler/index.cjs +24 -24
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/index.cjs +7 -7
- package/dist/index.js +6 -6
- package/package.json +1 -1
- package/src/__tests__/ticket-card-meta.test.tsx +39 -1
- package/src/components/chat/chat-composer.tsx +21 -5
- package/src/components/chat/chat-input.tsx +15 -3
- package/src/components/chat/embeddable-chat.tsx +16 -2
- package/src/components/chat/types/component.types.ts +7 -0
- package/src/components/features/board/.ticket-card.md +2 -2
- package/src/components/features/board/ticket-card.tsx +49 -10
- package/src/stories/EmbeddableChat.stories.tsx +21 -0
- package/dist/chunk-6FD7ZEBY.cjs.map +0 -1
- package/dist/chunk-MNUR6PAJ.cjs.map +0 -1
- package/dist/chunk-OMTLCMQB.js.map +0 -1
- /package/dist/{chunk-CQJXOBWK.js.map → chunk-4FHL6C5E.js.map} +0 -0
- /package/dist/{chunk-Z2MJPTIB.js.map → chunk-57BIWOGD.js.map} +0 -0
- /package/dist/{chunk-YRMKUDOE.js.map → chunk-5ZHLPC32.js.map} +0 -0
- /package/dist/{chunk-GDTFJ5QN.js.map → chunk-7ZIOUUWU.js.map} +0 -0
- /package/dist/{chunk-RT5JNTCN.js.map → chunk-AAZLZBR5.js.map} +0 -0
- /package/dist/{chunk-OLCI3UA5.js.map → chunk-BO2ZXX4Z.js.map} +0 -0
- /package/dist/{chunk-77WMNQTE.js.map → chunk-CEPI3A3T.js.map} +0 -0
- /package/dist/{chunk-MBLACUCE.js.map → chunk-DG763XMY.js.map} +0 -0
- /package/dist/{chunk-BPKEDLGR.js.map → chunk-DYFQCHUY.js.map} +0 -0
- /package/dist/{chunk-QR3NUFA5.js.map → chunk-GKAZC2JI.js.map} +0 -0
- /package/dist/{chunk-RM4S53GB.js.map → chunk-KS2PRUHN.js.map} +0 -0
- /package/dist/{chunk-O3GB7JFL.js.map → chunk-M6PDHXI5.js.map} +0 -0
- /package/dist/{chunk-JHAS6Y7P.js.map → chunk-TN33GNFP.js.map} +0 -0
- /package/dist/{chunk-BKCPXDOE.js.map → chunk-ZRD3YAPM.js.map} +0 -0
|
@@ -12,10 +12,20 @@ import { ModelDisplay } from './model-display';
|
|
|
12
12
|
import type { ChatInputRef, ModelDisplayProps } from './types/component.types';
|
|
13
13
|
import type { ChatContextItem, ChatContextPickerConfig } from './types/context-item.types';
|
|
14
14
|
|
|
15
|
+
/** Why the composer takes no input, in the user's words — shown where the prompt would be. */
|
|
16
|
+
export interface ChatComposerLock {
|
|
17
|
+
placeholder: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
export interface ChatComposerProps {
|
|
16
21
|
/** Read-only archived chat → render the unarchive placeholder instead of the
|
|
17
22
|
* input (Figma node 7361:426949). */
|
|
18
23
|
archived?: boolean;
|
|
24
|
+
/** The host cannot take a message right now — e.g. the AI balance is spent
|
|
25
|
+
* and the agents are paused (Figma 954:28455). The composer keeps its place
|
|
26
|
+
* but takes no input: the editor is disabled with the lock's copy in the
|
|
27
|
+
* prompt's place, and the `+` menu, the chips and Send are inert. */
|
|
28
|
+
lock?: ChatComposerLock | null;
|
|
19
29
|
inputRef: Ref<ChatInputRef>;
|
|
20
30
|
onSend: (text: string) => void;
|
|
21
31
|
onStop: () => void;
|
|
@@ -80,6 +90,7 @@ export interface ChatComposerProps {
|
|
|
80
90
|
*/
|
|
81
91
|
export function ChatComposer({
|
|
82
92
|
archived = false,
|
|
93
|
+
lock = null,
|
|
83
94
|
inputRef,
|
|
84
95
|
onSend,
|
|
85
96
|
onStop,
|
|
@@ -107,6 +118,7 @@ export function ChatComposer({
|
|
|
107
118
|
previewText,
|
|
108
119
|
}: ChatComposerProps) {
|
|
109
120
|
const contextEnabled = !!contextPicker && !archived;
|
|
121
|
+
const locked = lock != null;
|
|
110
122
|
const selected = selectedContextItems ?? [];
|
|
111
123
|
const memory = contextMemoryItems ?? [];
|
|
112
124
|
|
|
@@ -131,11 +143,15 @@ export function ChatComposer({
|
|
|
131
143
|
onStop={onStop}
|
|
132
144
|
sending={sending}
|
|
133
145
|
placeholder={placeholder}
|
|
146
|
+
// Locked: the editor is disabled and the lock's copy stands where the
|
|
147
|
+
// prompt would; there is nothing to focus, so autofocus stands down too.
|
|
148
|
+
disabled={locked}
|
|
149
|
+
disabledPlaceholder={lock?.placeholder}
|
|
134
150
|
previewText={previewText}
|
|
135
151
|
fullWidth
|
|
136
152
|
className="px-0"
|
|
137
153
|
reserveAvatarOffset={false}
|
|
138
|
-
autoFocus={autoFocus}
|
|
154
|
+
autoFocus={locked ? false : autoFocus}
|
|
139
155
|
slashCommands={slashCommands}
|
|
140
156
|
onMentionQueryChange={contextEnabled ? onMentionQueryChange : undefined}
|
|
141
157
|
onValueChange={contextEnabled ? onValueChange : undefined}
|
|
@@ -148,7 +164,7 @@ export function ChatComposer({
|
|
|
148
164
|
<ChatComposerPlusMenu
|
|
149
165
|
onToggle={() => (contextPickerOpen ? onCloseContextPicker?.() : onOpenContextPicker?.())}
|
|
150
166
|
open={contextPickerOpen}
|
|
151
|
-
disabled={attachmentsDisabled}
|
|
167
|
+
disabled={attachmentsDisabled || locked}
|
|
152
168
|
dropdown={
|
|
153
169
|
contextPicker ? (
|
|
154
170
|
<ChatContextPicker
|
|
@@ -202,7 +218,7 @@ export function ChatComposer({
|
|
|
202
218
|
items={memory}
|
|
203
219
|
onRemove={onRemoveContextMemoryItem}
|
|
204
220
|
resolveIcon={resolveContextIcon}
|
|
205
|
-
disabled={sending}
|
|
221
|
+
disabled={sending || locked}
|
|
206
222
|
className="rounded-t-md"
|
|
207
223
|
/>
|
|
208
224
|
{selected.length > 0 && (
|
|
@@ -210,7 +226,7 @@ export function ChatComposer({
|
|
|
210
226
|
items={selected}
|
|
211
227
|
onRemove={onRemoveContextItem}
|
|
212
228
|
resolveIcon={resolveContextIcon}
|
|
213
|
-
disabled={sending}
|
|
229
|
+
disabled={sending || locked}
|
|
214
230
|
className={cn(
|
|
215
231
|
'border-b border-ods-border bg-ods-bg p-2',
|
|
216
232
|
// Only the TOP strip rounds into the card corner.
|
|
@@ -236,7 +252,7 @@ export function ChatComposer({
|
|
|
236
252
|
attachmentsEnabled
|
|
237
253
|
attachmentsCount={attachmentsCount}
|
|
238
254
|
onAddFiles={onAddFiles}
|
|
239
|
-
disabled={attachmentsDisabled}
|
|
255
|
+
disabled={attachmentsDisabled || locked}
|
|
240
256
|
/>
|
|
241
257
|
)}
|
|
242
258
|
<div className="min-w-0 flex-1">
|
|
@@ -167,6 +167,7 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>((allProps, ref) => {
|
|
|
167
167
|
placeholder = 'Enter your Request...',
|
|
168
168
|
reserveAvatarOffset: _reserveAvatarOffset,
|
|
169
169
|
disabled = false,
|
|
170
|
+
disabledPlaceholder = 'Connection lost. Waiting to reconnect...',
|
|
170
171
|
autoFocus = false,
|
|
171
172
|
fullWidth = false,
|
|
172
173
|
allowEmptySend = false,
|
|
@@ -607,8 +608,18 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>((allProps, ref) => {
|
|
|
607
608
|
the draft is EMPTY, so the editor is always on its plain line
|
|
608
609
|
box here. */}
|
|
609
610
|
{isEmpty && !showPreview && (
|
|
610
|
-
<span
|
|
611
|
-
{
|
|
611
|
+
<span
|
|
612
|
+
className={cn(
|
|
613
|
+
// One line, clipped with an ellipsis (Figma 954:28455): the
|
|
614
|
+
// disabled copy is a sentence, and the panel's narrowest
|
|
615
|
+
// width does not fit it — wrapped, it ran under the row.
|
|
616
|
+
'pointer-events-none absolute left-0 top-0 w-full select-none truncate text-h4',
|
|
617
|
+
// Disabled reads as disabled (Figma 954:28455): the copy says
|
|
618
|
+
// why, and the colour says it is not an invitation to type.
|
|
619
|
+
disabled ? 'text-ods-text-disabled' : 'text-ods-text-secondary',
|
|
620
|
+
)}
|
|
621
|
+
>
|
|
622
|
+
{disabled ? disabledPlaceholder : placeholder}
|
|
612
623
|
</span>
|
|
613
624
|
)}
|
|
614
625
|
{/* Ghost preview of a hovered quick-action's prompt. Overlaid like
|
|
@@ -642,7 +653,8 @@ const ChatInput = forwardRef<ChatInputRef, ChatInputProps>((allProps, ref) => {
|
|
|
642
653
|
data-editor
|
|
643
654
|
role="textbox"
|
|
644
655
|
aria-multiline="true"
|
|
645
|
-
|
|
656
|
+
// Named by what it shows: locked, the lock's copy is the name.
|
|
657
|
+
aria-label={disabled ? disabledPlaceholder : typeof placeholder === 'string' ? placeholder : 'Message'}
|
|
646
658
|
// `sending` blocks SENDING, never typing. Taking
|
|
647
659
|
// `contentEditable` away under a focused editor blurs it, and
|
|
648
660
|
// iOS dismisses the keyboard on blur; restoring it re-establishes
|
|
@@ -62,7 +62,7 @@ import { CollisionBoundaryContext, PortalContainerContext } from '../ui/portal-c
|
|
|
62
62
|
import { SquareAvatar } from '../ui/square-avatar';
|
|
63
63
|
import { ChatArchivePage } from './chat-archive-page';
|
|
64
64
|
import { ChatAttachmentChipStrip } from './chat-attachment-bar';
|
|
65
|
-
import { ChatComposer } from './chat-composer';
|
|
65
|
+
import { ChatComposer, type ChatComposerLock } from './chat-composer';
|
|
66
66
|
import { chatDialogMenuItems } from './chat-dialog-menu-items';
|
|
67
67
|
import { ChatHeaderIconButton } from './chat-header-icon-button';
|
|
68
68
|
import { ChatHeaderSearchField } from './chat-header-search-field';
|
|
@@ -185,6 +185,15 @@ export interface EmbeddableChatProps {
|
|
|
185
185
|
* cursor. Scrolling is intentionally disabled too. Defaults to `false`.
|
|
186
186
|
*/
|
|
187
187
|
previewMode?: boolean;
|
|
188
|
+
/**
|
|
189
|
+
* The host cannot serve a message right now — e.g. the tenant's AI balance
|
|
190
|
+
* is spent and the agents are paused (Figma 954:28455). The composer stays
|
|
191
|
+
* in place but takes no input: the editor is disabled with `placeholder`
|
|
192
|
+
* where the prompt would be, and the `+` menu and Send are inert. Sends that
|
|
193
|
+
* bypass the composer (quick-action chips, imperative prompts) are the
|
|
194
|
+
* host's to hold back — it owns their callbacks. `null`/unset = live.
|
|
195
|
+
*/
|
|
196
|
+
composerLock?: ChatComposerLock | null;
|
|
188
197
|
/** Optional builders for chat-card types whose props live in hub-land
|
|
189
198
|
* (programs + product_release). Forwarded straight to
|
|
190
199
|
* `renderChatInlineEntityCard`. */
|
|
@@ -943,6 +952,7 @@ function EmbeddableChatInner({
|
|
|
943
952
|
defaultOpen,
|
|
944
953
|
showInternalTrigger = true,
|
|
945
954
|
previewMode = false,
|
|
955
|
+
composerLock = null,
|
|
946
956
|
extras,
|
|
947
957
|
tableIdForDocumentType,
|
|
948
958
|
modes,
|
|
@@ -1571,6 +1581,9 @@ function EmbeddableChatInner({
|
|
|
1571
1581
|
|
|
1572
1582
|
const handleSend = useCallback(
|
|
1573
1583
|
(text: string) => {
|
|
1584
|
+
// Locked: the disabled editor cannot submit, but a queued Enter or a
|
|
1585
|
+
// stale ref call still lands here — refused at the seam, not by luck.
|
|
1586
|
+
if (composerLock) return;
|
|
1574
1587
|
// Append chat-attachment markdown lines to the user's bubble.
|
|
1575
1588
|
let augmentedText = text;
|
|
1576
1589
|
if (readyAttachments.length > 0) {
|
|
@@ -1598,7 +1611,7 @@ function EmbeddableChatInner({
|
|
|
1598
1611
|
setContextItems([]);
|
|
1599
1612
|
}
|
|
1600
1613
|
},
|
|
1601
|
-
[sendMessage, readyAttachments, viewUrlPrefix, clearAttachments, contextItems],
|
|
1614
|
+
[composerLock, sendMessage, readyAttachments, viewUrlPrefix, clearAttachments, contextItems],
|
|
1602
1615
|
);
|
|
1603
1616
|
|
|
1604
1617
|
/**
|
|
@@ -2649,6 +2662,7 @@ function EmbeddableChatInner({
|
|
|
2649
2662
|
|
|
2650
2663
|
<ChatComposer
|
|
2651
2664
|
archived={isViewingArchived}
|
|
2665
|
+
lock={composerLock}
|
|
2652
2666
|
inputRef={chatInputRef}
|
|
2653
2667
|
onSend={handleSend}
|
|
2654
2668
|
onStop={stopMessage}
|
|
@@ -453,6 +453,13 @@ export interface ChatInputProps extends Omit<TextareaHTMLAttributes<HTMLTextArea
|
|
|
453
453
|
*/
|
|
454
454
|
reserveAvatarOffset?: boolean;
|
|
455
455
|
disabled?: boolean;
|
|
456
|
+
/**
|
|
457
|
+
* Placeholder shown while `disabled`, in the prompt's place — what the user
|
|
458
|
+
* cannot do right now. Defaults to the connection-lost copy, which is what
|
|
459
|
+
* `disabled` has meant so far (the ticket reply composer); a host that
|
|
460
|
+
* disables the input for another reason says so here.
|
|
461
|
+
*/
|
|
462
|
+
disabledPlaceholder?: string;
|
|
456
463
|
maxRows?: number;
|
|
457
464
|
showSendButton?: boolean;
|
|
458
465
|
sendButtonLabel?: string;
|
|
@@ -5,10 +5,10 @@ Draggable and static board ticket card components for a kanban-style board view,
|
|
|
5
5
|
|
|
6
6
|
| Export | Type | Description |
|
|
7
7
|
|---|---|---|
|
|
8
|
-
| `TicketCardBody` | Component | Pure presentational inner content — title, device/org row, priority flag, assignees, tags, the `<ticketNumber> • <relative time>` line (either half alone when the other is missing), new message badge, and approval row |
|
|
8
|
+
| `TicketCardBody` | Component | Pure presentational inner content — title, device/org row, priority flag, assignees, tags, the `<ticketNumber> • <relative time>` line (either half alone when the other is missing; with `href` it is a link to the ticket, since it takes pointer events for its tooltip and would otherwise swallow the card's click), new message badge, and approval row |
|
|
9
9
|
| `TicketCardView` | Component | Static, non-draggable card using the same shell and body as the board card; suitable for embeds, previews, and marketing |
|
|
10
10
|
| `TicketCard` | Component | Full board card: registers itself as both draggable and drop target, fades in place while carried, draws the insertion line for the shared aim, and supports an optional `href` link plus drag/drop-disabled state |
|
|
11
|
-
| `TicketCardBodyProps` | Interface | Props for the shared body: `ticket`, `columnColor`, `renderAssignSlot`, `onApprove`, `onReject` |
|
|
11
|
+
| `TicketCardBodyProps` | Interface | Props for the shared body: `ticket`, `columnColor`, `renderAssignSlot`, `onApprove`, `onReject`, and optional `href` / `onLinkClick` for the number/time line |
|
|
12
12
|
| `TicketCardProps` | Interface | Extends body props with `columnId`, `href`, `dragDisabled`, `dropDisabled`, `allowedFromColumns`, and board-aware approve/reject signatures |
|
|
13
13
|
|
|
14
14
|
**Nothing about the lane moves during a drag.** The card being carried stays exactly where it is,
|
|
@@ -7,6 +7,7 @@ import { preserveOffsetOnSource } from '@atlaskit/pragmatic-drag-and-drop/elemen
|
|
|
7
7
|
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
|
|
8
8
|
import {
|
|
9
9
|
memo,
|
|
10
|
+
useCallback,
|
|
10
11
|
useEffect,
|
|
11
12
|
useMemo,
|
|
12
13
|
useRef,
|
|
@@ -82,11 +83,27 @@ export interface TicketCardBodyProps {
|
|
|
82
83
|
* `TicketCard` adapts its own `(ticketId, requestId)` signature onto these). */
|
|
83
84
|
onApprove?: (requestId?: string) => void | Promise<void>;
|
|
84
85
|
onReject?: (requestId?: string) => void | Promise<void>;
|
|
86
|
+
/** Where the card opens. The body sits above the card's own link with pointer
|
|
87
|
+
* events off, but the number/time line has to take them back for its tooltip -
|
|
88
|
+
* so with an `href` that line is a link of its own, and a click on it opens the
|
|
89
|
+
* ticket like a click anywhere else on the card. That link is out of the tab
|
|
90
|
+
* order, so pass `href` only from a card that already renders its own focusable
|
|
91
|
+
* link to the same place, as `TicketCard` does. */
|
|
92
|
+
href?: string;
|
|
93
|
+
onLinkClick?: (e: MouseEvent) => void;
|
|
85
94
|
}
|
|
86
95
|
|
|
87
96
|
/** The card's inner content: title + device/org, priority + assignees, tags,
|
|
88
97
|
* timestamp, "New Message", and the approval row. Pure — driven only by props. */
|
|
89
|
-
export function TicketCardBody({
|
|
98
|
+
export function TicketCardBody({
|
|
99
|
+
ticket,
|
|
100
|
+
columnColor,
|
|
101
|
+
renderAssignSlot,
|
|
102
|
+
onApprove,
|
|
103
|
+
onReject,
|
|
104
|
+
href,
|
|
105
|
+
onLinkClick,
|
|
106
|
+
}: TicketCardBodyProps) {
|
|
90
107
|
const showNewMessage = !!ticket.hasNewMessage && !!columnColor;
|
|
91
108
|
const newMessageTextColor = columnColor ? getReadableTextColor(columnColor) : undefined;
|
|
92
109
|
|
|
@@ -143,6 +160,22 @@ export function TicketCardBody({ ticket, columnColor, renderAssignSlot, onApprov
|
|
|
143
160
|
// may be empty - the hub hero card and tickets without one send '' - so either
|
|
144
161
|
// half stands on its own and the line disappears with both missing.
|
|
145
162
|
const metaLabel = [ticket.ticketNumber || null, timestampLabel].filter(Boolean).join(' \u2022 ');
|
|
163
|
+
const metaClass = 'truncate text-ods-text-secondary text-h6';
|
|
164
|
+
// Out of the tab order: the card's own link is the one stop for the ticket.
|
|
165
|
+
const meta = href ? (
|
|
166
|
+
<Link
|
|
167
|
+
href={href}
|
|
168
|
+
draggable={false}
|
|
169
|
+
prefetch={false}
|
|
170
|
+
tabIndex={-1}
|
|
171
|
+
onClick={onLinkClick}
|
|
172
|
+
className={cn('pointer-events-auto block', metaClass)}
|
|
173
|
+
>
|
|
174
|
+
{metaLabel}
|
|
175
|
+
</Link>
|
|
176
|
+
) : (
|
|
177
|
+
<p className={cn(tooltipLabel && 'pointer-events-auto', metaClass)}>{metaLabel}</p>
|
|
178
|
+
);
|
|
146
179
|
|
|
147
180
|
return (
|
|
148
181
|
<>
|
|
@@ -165,14 +198,12 @@ export function TicketCardBody({ ticket, columnColor, renderAssignSlot, onApprov
|
|
|
165
198
|
(tooltipLabel ? (
|
|
166
199
|
<TooltipProvider>
|
|
167
200
|
<Tooltip>
|
|
168
|
-
<TooltipTrigger asChild>
|
|
169
|
-
<p className="pointer-events-auto truncate text-ods-text-secondary text-h6">{metaLabel}</p>
|
|
170
|
-
</TooltipTrigger>
|
|
201
|
+
<TooltipTrigger asChild>{meta}</TooltipTrigger>
|
|
171
202
|
<TooltipContent>{tooltipLabel}</TooltipContent>
|
|
172
203
|
</Tooltip>
|
|
173
204
|
</TooltipProvider>
|
|
174
205
|
) : (
|
|
175
|
-
|
|
206
|
+
meta
|
|
176
207
|
))}
|
|
177
208
|
{ticket.escalatedByUser && (
|
|
178
209
|
<div className="flex items-center gap-[var(--spacing-system-xxs)] text-ods-open-yellow text-h6">
|
|
@@ -215,7 +246,9 @@ export function TicketCardBody({ ticket, columnColor, renderAssignSlot, onApprov
|
|
|
215
246
|
// drag-and-drop / board context (for embeds, marketing heroes, previews).
|
|
216
247
|
// =============================================================================
|
|
217
248
|
|
|
218
|
-
|
|
249
|
+
/** No `href`: the static card has no card-wide link for keyboard users, so the
|
|
250
|
+
* number/time line must not become a pointer-only one. */
|
|
251
|
+
export interface TicketCardViewProps extends Omit<TicketCardBodyProps, 'href' | 'onLinkClick'> {
|
|
219
252
|
className?: string;
|
|
220
253
|
/** Merged over the shell's own — the board uses it to fade the copy it leaves
|
|
221
254
|
* behind and to place the one that follows the pointer. */
|
|
@@ -412,9 +445,13 @@ export const TicketCard = memo(function TicketCardImpl({
|
|
|
412
445
|
const style: CSSProperties = {};
|
|
413
446
|
if (showNewMessage) style.borderColor = columnColor;
|
|
414
447
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
448
|
+
// Stable between drags: it is handed into the memoized body below.
|
|
449
|
+
const handleClick = useCallback(
|
|
450
|
+
(e: MouseEvent) => {
|
|
451
|
+
if (isDragging) e.preventDefault();
|
|
452
|
+
},
|
|
453
|
+
[isDragging],
|
|
454
|
+
);
|
|
418
455
|
|
|
419
456
|
// Held as one memoized element, not re-created per render. A card being
|
|
420
457
|
// dragged over re-renders on every edge flip — it owns the hover state that
|
|
@@ -430,9 +467,11 @@ export const TicketCard = memo(function TicketCardImpl({
|
|
|
430
467
|
renderAssignSlot={renderAssignSlot}
|
|
431
468
|
onApprove={onApprove ? requestId => onApprove(ticket.id, requestId) : undefined}
|
|
432
469
|
onReject={onReject ? requestId => onReject(ticket.id, requestId) : undefined}
|
|
470
|
+
href={href}
|
|
471
|
+
onLinkClick={handleClick}
|
|
433
472
|
/>
|
|
434
473
|
),
|
|
435
|
-
[ticket, columnColor, renderAssignSlot, onApprove, onReject],
|
|
474
|
+
[ticket, columnColor, renderAssignSlot, onApprove, onReject, href, handleClick],
|
|
436
475
|
);
|
|
437
476
|
|
|
438
477
|
// No transition on the margins below, and that is not a style choice: room is
|
|
@@ -535,6 +535,27 @@ export const MingoOnly: Story = {
|
|
|
535
535
|
args: {},
|
|
536
536
|
};
|
|
537
537
|
|
|
538
|
+
/**
|
|
539
|
+
* The host cannot serve a message — the tenant's AI balance is spent and the
|
|
540
|
+
* agents are paused (Figma 954:28455). The panel reads as usual; the composer
|
|
541
|
+
* keeps its place, says why it takes nothing, and its `+` and Send are inert.
|
|
542
|
+
*/
|
|
543
|
+
export const ComposerLocked: Story = {
|
|
544
|
+
render: args => (
|
|
545
|
+
<EmbeddableChat
|
|
546
|
+
{...args}
|
|
547
|
+
modes={{
|
|
548
|
+
mingo: createMockMingoConfig(),
|
|
549
|
+
}}
|
|
550
|
+
defaultActiveMode="mingo"
|
|
551
|
+
defaultOpen
|
|
552
|
+
showInternalTrigger={false}
|
|
553
|
+
composerLock={{ placeholder: 'Mingo chat unavailable. Top up your balance.' }}
|
|
554
|
+
/>
|
|
555
|
+
),
|
|
556
|
+
args: {},
|
|
557
|
+
};
|
|
558
|
+
|
|
538
559
|
// =============================================================================
|
|
539
560
|
// 3. Both modes (toggle visible — openframe-frontend target)
|
|
540
561
|
// =============================================================================
|