@flamingo-stack/openframe-frontend-core 0.0.461 → 0.0.462-snapshot.20260721190919
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-KUVFE5WC.js → chunk-2ZDVWIAX.js} +3735 -3603
- package/dist/chunk-2ZDVWIAX.js.map +1 -0
- package/dist/{chunk-7NUTHK3A.js → chunk-4RQZ5X6Y.js} +4 -4
- package/dist/{chunk-FQKLIGV3.cjs → chunk-52NND23E.cjs} +62 -62
- package/dist/{chunk-FQKLIGV3.cjs.map → chunk-52NND23E.cjs.map} +1 -1
- package/dist/{chunk-5D3UPZIM.cjs → chunk-5ME74JNR.cjs} +59 -59
- package/dist/{chunk-5D3UPZIM.cjs.map → chunk-5ME74JNR.cjs.map} +1 -1
- package/dist/{chunk-S2KZZVJF.js → chunk-CIWVOQKW.js} +2 -2
- package/dist/{chunk-6IA5TD7O.cjs → chunk-E6OKPI77.cjs} +15 -15
- package/dist/{chunk-6IA5TD7O.cjs.map → chunk-E6OKPI77.cjs.map} +1 -1
- package/dist/{chunk-HWJWRQGV.cjs → chunk-G2GSHNSJ.cjs} +5 -5
- package/dist/{chunk-HWJWRQGV.cjs.map → chunk-G2GSHNSJ.cjs.map} +1 -1
- package/dist/{chunk-RTLYVFFQ.js → chunk-GHSSMGZL.js} +2 -2
- package/dist/{chunk-ZYHO7WAW.js → chunk-IDMH7CZO.js} +2 -2
- package/dist/{chunk-TFVBIOSB.cjs → chunk-IRVMOG4M.cjs} +956 -824
- package/dist/chunk-IRVMOG4M.cjs.map +1 -0
- package/dist/{chunk-BFEMQ2XC.cjs → chunk-KFND7IRU.cjs} +26 -26
- package/dist/{chunk-BFEMQ2XC.cjs.map → chunk-KFND7IRU.cjs.map} +1 -1
- package/dist/{chunk-2T5MXXTK.cjs → chunk-ME6PX3N3.cjs} +31 -31
- package/dist/{chunk-2T5MXXTK.cjs.map → chunk-ME6PX3N3.cjs.map} +1 -1
- package/dist/{chunk-HHKLSJ5Y.js → chunk-NXCDJV2N.js} +2 -2
- package/dist/{chunk-EXSLPPEY.js → chunk-OURYLIMA.js} +4 -4
- package/dist/{chunk-NFILBHXY.cjs → chunk-Q5QILCAZ.cjs} +9 -9
- package/dist/{chunk-NFILBHXY.cjs.map → chunk-Q5QILCAZ.cjs.map} +1 -1
- package/dist/{chunk-4TKGFC7I.js → chunk-QMJALAKE.js} +2 -2
- package/dist/{chunk-CDARBFPL.js → chunk-RQG32QZS.js} +2 -2
- package/dist/{chunk-QLLXI554.cjs → chunk-VHR5BOCQ.cjs} +26 -26
- package/dist/{chunk-QLLXI554.cjs.map → chunk-VHR5BOCQ.cjs.map} +1 -1
- package/dist/{chunk-WHXYDEAM.cjs → chunk-VUDXSYY7.cjs} +37 -37
- package/dist/{chunk-WHXYDEAM.cjs.map → chunk-VUDXSYY7.cjs.map} +1 -1
- package/dist/{chunk-3H5XD5UD.cjs → chunk-WDN6RDXY.cjs} +7 -7
- package/dist/{chunk-3H5XD5UD.cjs.map → chunk-WDN6RDXY.cjs.map} +1 -1
- package/dist/{chunk-FYIX7RTZ.js → chunk-XEQCEYTI.js} +4 -4
- package/dist/{chunk-UNGRKZ65.cjs → chunk-YMRAEKRR.cjs} +11 -11
- package/dist/{chunk-UNGRKZ65.cjs.map → chunk-YMRAEKRR.cjs.map} +1 -1
- package/dist/{chunk-PLJSN3SR.js → chunk-YQI5NNA2.js} +4 -4
- package/dist/{chunk-IQU6ZIZR.js → chunk-YROSJGPY.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/chat-panel-header.d.ts +9 -1
- package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
- package/dist/components/chat/embeddable-chat.d.ts +8 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-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/mingo-chat-history.d.ts.map +1 -1
- package/dist/components/chat/mingo-history-rail.d.ts +11 -1
- package/dist/components/chat/mingo-history-rail.d.ts.map +1 -1
- package/dist/components/chat/quick-action-wall.d.ts.map +1 -1
- package/dist/components/chat/types/component.types.d.ts +8 -0
- package/dist/components/chat/types/component.types.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +7 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -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/help-center-pages/index.cjs +20 -20
- package/dist/components/help-center-pages/index.js +11 -11
- package/dist/components/index.cjs +129 -129
- package/dist/components/index.js +9 -9
- 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 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/components/ui/tab-selector.d.ts.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/components/chat/chat-panel-header.tsx +21 -1
- package/src/components/chat/embeddable-chat.tsx +51 -4
- package/src/components/chat/hooks/use-unified-chat.ts +9 -0
- package/src/components/chat/mingo-chat-history.tsx +85 -37
- package/src/components/chat/mingo-history-rail.tsx +31 -0
- package/src/components/chat/quick-action-wall.tsx +47 -27
- package/src/components/chat/types/component.types.ts +8 -0
- package/src/components/chat/types/unified-chat-state.types.ts +9 -0
- package/src/components/ui/tab-selector.tsx +7 -4
- package/src/stories/QuickActionWall.stories.tsx +23 -0
- package/dist/chunk-KUVFE5WC.js.map +0 -1
- package/dist/chunk-TFVBIOSB.cjs.map +0 -1
- /package/dist/{chunk-7NUTHK3A.js.map → chunk-4RQZ5X6Y.js.map} +0 -0
- /package/dist/{chunk-S2KZZVJF.js.map → chunk-CIWVOQKW.js.map} +0 -0
- /package/dist/{chunk-RTLYVFFQ.js.map → chunk-GHSSMGZL.js.map} +0 -0
- /package/dist/{chunk-ZYHO7WAW.js.map → chunk-IDMH7CZO.js.map} +0 -0
- /package/dist/{chunk-HHKLSJ5Y.js.map → chunk-NXCDJV2N.js.map} +0 -0
- /package/dist/{chunk-EXSLPPEY.js.map → chunk-OURYLIMA.js.map} +0 -0
- /package/dist/{chunk-4TKGFC7I.js.map → chunk-QMJALAKE.js.map} +0 -0
- /package/dist/{chunk-CDARBFPL.js.map → chunk-RQG32QZS.js.map} +0 -0
- /package/dist/{chunk-FYIX7RTZ.js.map → chunk-XEQCEYTI.js.map} +0 -0
- /package/dist/{chunk-PLJSN3SR.js.map → chunk-YQI5NNA2.js.map} +0 -0
- /package/dist/{chunk-IQU6ZIZR.js.map → chunk-YROSJGPY.js.map} +0 -0
|
@@ -5,6 +5,7 @@ import { cn } from '../../utils/cn'
|
|
|
5
5
|
import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu'
|
|
6
6
|
import { Button } from '../ui/button'
|
|
7
7
|
import { ScrollFadeOverlay, useScrollFade } from '../ui/scroll-fade'
|
|
8
|
+
import { SquareAvatar } from '../ui/square-avatar'
|
|
8
9
|
import { Ellipsis01Icon, SearchXmarkIcon } from '../icons-v2-generated'
|
|
9
10
|
import { ChatListEmptyState } from './chat-list-empty-state'
|
|
10
11
|
import type { DialogItem } from './types/component.types'
|
|
@@ -107,6 +108,8 @@ function MingoChatHistoryRow({
|
|
|
107
108
|
const title = dialog.title || 'Untitled Chat'
|
|
108
109
|
const unread = dialog.unreadMessagesCount ?? 0
|
|
109
110
|
const hasMenu = !!onRequestRename || !!onRequestArchive
|
|
111
|
+
const owner = dialog.owner
|
|
112
|
+
const hasAvatar = !!(owner?.name || owner?.avatarUrl)
|
|
110
113
|
// Keep the `⋯` visible while its menu is open — once Radix moves focus into
|
|
111
114
|
// the portalled content the row loses hover/focus-within.
|
|
112
115
|
const [menuOpen, setMenuOpen] = React.useState(false)
|
|
@@ -173,46 +176,82 @@ function MingoChatHistoryRow({
|
|
|
173
176
|
{title}
|
|
174
177
|
</span>
|
|
175
178
|
</div>
|
|
176
|
-
{hasMenu ? (
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
179
|
+
{hasMenu || hasAvatar ? (
|
|
180
|
+
// Trailing 48px cell (Figma 113:63224): the owner avatar sits centred
|
|
181
|
+
// by default; hovering the row (or opening the menu) swaps it for the
|
|
182
|
+
// `⋯` actions button IN PLACE, so the title never reflows. Without an
|
|
183
|
+
// avatar the cell exists only for the hover menu, so it collapses on
|
|
184
|
+
// mobile exactly like the old hover-only gutter (no hover on touch).
|
|
181
185
|
<span
|
|
182
|
-
onClick={(e) => e.stopPropagation()}
|
|
183
186
|
className={cn(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// row's `items-center` the wrapper would otherwise collapse to its
|
|
187
|
-
// content and `h-full` on the button would no-op).
|
|
188
|
-
'self-stretch shrink-0 transition-opacity max-md:hidden',
|
|
189
|
-
menuOpen
|
|
190
|
-
? 'opacity-100'
|
|
191
|
-
: 'opacity-0 group-hover/row:opacity-100 focus-within:opacity-100',
|
|
187
|
+
'relative w-12 self-stretch shrink-0',
|
|
188
|
+
!hasAvatar && 'max-md:hidden',
|
|
192
189
|
)}
|
|
193
190
|
>
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
191
|
+
{hasMenu ? (
|
|
192
|
+
// Revealed on row hover/its own focus; kept visible while the menu
|
|
193
|
+
// is open. Hidden on mobile — it's a hover affordance. `peer/menu`
|
|
194
|
+
// so the avatar layer (a later sibling) can fade when the trigger
|
|
195
|
+
// holds keyboard focus.
|
|
196
|
+
<span
|
|
197
|
+
onClick={(e) => e.stopPropagation()}
|
|
198
|
+
className={cn(
|
|
199
|
+
'peer/menu absolute inset-0 transition-opacity max-md:hidden',
|
|
200
|
+
menuOpen
|
|
201
|
+
? 'opacity-100'
|
|
202
|
+
: 'opacity-0 group-hover/row:opacity-100 focus-within:opacity-100',
|
|
203
|
+
)}
|
|
204
|
+
>
|
|
205
|
+
<ActionsMenuDropdown
|
|
206
|
+
triggerAriaLabel="Chat actions"
|
|
207
|
+
groups={[{ items: menuItems }]}
|
|
208
|
+
open={menuOpen}
|
|
209
|
+
onOpenChange={setMenuOpen}
|
|
210
|
+
// Don't return focus (and its ring) to the `⋯` trigger on close.
|
|
211
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
212
|
+
customTrigger={
|
|
213
|
+
<Button
|
|
214
|
+
variant="transparent"
|
|
215
|
+
size="icon"
|
|
216
|
+
aria-label="Chat actions"
|
|
217
|
+
// Square, grey icon → white on hover. `h-full`/`w-full` fill
|
|
218
|
+
// the 48px cell exactly (no breakpoint poke from
|
|
219
|
+
// `size="icon"`'s responsive `md:h-12`); `p-0` drops the
|
|
220
|
+
// icon-size padding. tailwind-merge so these win over
|
|
221
|
+
// defaults.
|
|
222
|
+
className="h-full md:h-full w-full rounded-none p-0 text-ods-text-secondary hover:text-ods-text-primary"
|
|
223
|
+
>
|
|
224
|
+
<Ellipsis01Icon />
|
|
225
|
+
</Button>
|
|
226
|
+
}
|
|
227
|
+
/>
|
|
228
|
+
</span>
|
|
229
|
+
) : null}
|
|
230
|
+
{hasAvatar ? (
|
|
231
|
+
// Avatar layer — clicks fall through to row selection (it covers
|
|
232
|
+
// the cell whenever the menu trigger is faded out). On desktop it
|
|
233
|
+
// fades for the menu swap; on mobile it's always shown.
|
|
234
|
+
<span
|
|
235
|
+
onClick={() => onSelect?.(dialog.id)}
|
|
236
|
+
title={owner?.name || undefined}
|
|
237
|
+
className={cn(
|
|
238
|
+
'absolute inset-0 flex cursor-pointer items-center justify-center transition-opacity',
|
|
239
|
+
hasMenu &&
|
|
240
|
+
(menuOpen
|
|
241
|
+
? 'opacity-0 pointer-events-none'
|
|
242
|
+
: 'md:group-hover/row:opacity-0 md:group-hover/row:pointer-events-none md:peer-[:focus-within]/menu:opacity-0 md:peer-[:focus-within]/menu:pointer-events-none'),
|
|
243
|
+
)}
|
|
244
|
+
>
|
|
245
|
+
<SquareAvatar
|
|
246
|
+
variant="round"
|
|
247
|
+
sizePx={24}
|
|
248
|
+
src={owner?.avatarUrl || undefined}
|
|
249
|
+
alt={owner?.name || undefined}
|
|
250
|
+
fallback={owner?.name || undefined}
|
|
251
|
+
initialsClassName="text-[10px] text-ods-text-secondary"
|
|
252
|
+
/>
|
|
253
|
+
</span>
|
|
254
|
+
) : null}
|
|
216
255
|
</span>
|
|
217
256
|
) : null}
|
|
218
257
|
</div>
|
|
@@ -316,6 +355,7 @@ export function MingoChatHistory({
|
|
|
316
355
|
onLoadMoreRef.current = onLoadMore
|
|
317
356
|
const isLoadingMoreRef = React.useRef(isLoadingMore)
|
|
318
357
|
isLoadingMoreRef.current = isLoadingMore
|
|
358
|
+
const dialogCount = dialogs.length
|
|
319
359
|
React.useEffect(() => {
|
|
320
360
|
const root = scrollRef.current
|
|
321
361
|
const sentinel = sentinelRef.current
|
|
@@ -331,7 +371,15 @@ export function MingoChatHistory({
|
|
|
331
371
|
)
|
|
332
372
|
io.observe(sentinel)
|
|
333
373
|
return () => io.disconnect()
|
|
334
|
-
|
|
374
|
+
// `dialogCount`/`isLoadingMore` deps re-arm the observer after every page
|
|
375
|
+
// lands: `observe()` re-reports the CURRENT intersection, so a sentinel
|
|
376
|
+
// that stays in view (short host-filtered list — e.g. the "My Chats"
|
|
377
|
+
// scope keeps a handful of rows out of a 20-row page) keeps chaining
|
|
378
|
+
// loads until the viewport fills or `hasMore` flips. A continuously
|
|
379
|
+
// visible sentinel never re-crosses the threshold, so the previous
|
|
380
|
+
// [hasMore]-only observer fired exactly once per mount and the list
|
|
381
|
+
// grew by a single page per drawer open.
|
|
382
|
+
}, [hasMore, isLoadingMore, dialogCount])
|
|
335
383
|
|
|
336
384
|
return (
|
|
337
385
|
<div className={cn('relative flex flex-1 min-h-0 flex-col gap-[var(--spacing-system-m)]', className)}>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import * as React from 'react'
|
|
4
4
|
import { cn } from '../../utils/cn'
|
|
5
5
|
import { Button } from '../ui/button'
|
|
6
|
+
import { TabSelector } from '../ui/tab-selector'
|
|
6
7
|
import { ChatsIcon } from '../icons-v2-generated/communication/chats-icon'
|
|
7
8
|
import { PlusCircleIcon } from '../icons-v2-generated/signs-and-symbols/plus-circle-icon'
|
|
8
9
|
import { AlertCircleIcon, Refresh01RightIcon } from '../icons-v2-generated'
|
|
@@ -14,6 +15,10 @@ import type { DialogItem } from './types/component.types'
|
|
|
14
15
|
// Types
|
|
15
16
|
// =============================================================================
|
|
16
17
|
|
|
18
|
+
/** Ownership scope of the rail's dialog list — the current user's chats only,
|
|
19
|
+
* or every admin's chats in the tenant. */
|
|
20
|
+
export type MingoHistoryScope = 'my' | 'all'
|
|
21
|
+
|
|
17
22
|
export interface MingoHistoryRailProps {
|
|
18
23
|
/** Dialogs to list, assumed already sorted newest-first by the host. */
|
|
19
24
|
dialogs: ReadonlyArray<DialogItem>
|
|
@@ -32,6 +37,13 @@ export interface MingoHistoryRailProps {
|
|
|
32
37
|
onRequestRename?: (dialog: DialogItem) => void
|
|
33
38
|
/** Request archive — enables the row "Archive chat" action. */
|
|
34
39
|
onRequestArchive?: (dialog: DialogItem) => void
|
|
40
|
+
/** Ownership scope shown as a two-state "My Chats / All Chats" selector
|
|
41
|
+
* between "Start New Chat" and the list. Rendered only when BOTH `scope`
|
|
42
|
+
* and `onScopeChange` are provided; the host owns the state and refilters
|
|
43
|
+
* the `dialogs` it passes in. */
|
|
44
|
+
scope?: MingoHistoryScope
|
|
45
|
+
/** Scope selector change handler — see `scope`. */
|
|
46
|
+
onScopeChange?: (scope: MingoHistoryScope) => void
|
|
35
47
|
/** Current server-side search term. Drives the list's "No chats found"
|
|
36
48
|
* empty state; the search INPUT itself lives in the panel header now, not
|
|
37
49
|
* in the rail body. */
|
|
@@ -77,6 +89,8 @@ export function MingoHistoryRail({
|
|
|
77
89
|
newChatAlways = false,
|
|
78
90
|
onRequestRename,
|
|
79
91
|
onRequestArchive,
|
|
92
|
+
scope,
|
|
93
|
+
onScopeChange,
|
|
80
94
|
searchQuery,
|
|
81
95
|
hasMore = false,
|
|
82
96
|
isLoadingMore = false,
|
|
@@ -114,6 +128,23 @@ export function MingoHistoryRail({
|
|
|
114
128
|
</Button>
|
|
115
129
|
)}
|
|
116
130
|
|
|
131
|
+
{/* Two-state ownership scope — "My Chats" / "All Chats". Stays visible in
|
|
132
|
+
the empty state too: an empty "My Chats" list must still offer the
|
|
133
|
+
switch to "All Chats" (host filters, so the empty state can be
|
|
134
|
+
scope-induced). Hidden while the load failed — retry is the only
|
|
135
|
+
useful action there. */}
|
|
136
|
+
{scope && onScopeChange && !loadError ? (
|
|
137
|
+
<TabSelector
|
|
138
|
+
className="shrink-0"
|
|
139
|
+
value={scope}
|
|
140
|
+
onValueChange={(value) => onScopeChange(value as MingoHistoryScope)}
|
|
141
|
+
items={[
|
|
142
|
+
{ id: 'my', label: 'My Chats' },
|
|
143
|
+
{ id: 'all', label: 'All Chats' },
|
|
144
|
+
]}
|
|
145
|
+
/>
|
|
146
|
+
) : null}
|
|
147
|
+
|
|
117
148
|
{loadError ? (
|
|
118
149
|
<div className="flex flex-1 min-h-0 flex-col items-center justify-center gap-[var(--spacing-system-m)] text-center">
|
|
119
150
|
<AlertCircleIcon className="h-8 w-8 text-ods-text-secondary shrink-0" />
|
|
@@ -38,24 +38,27 @@ export function interleave<T>(a: ReadonlyArray<T>, b: ReadonlyArray<T>): T[] {
|
|
|
38
38
|
return out
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
// Brick-mode padding fallback: ~14 columns of chips per row
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
41
|
+
// Brick-mode padding fallback: ~14 columns of chips per row — one row copy
|
|
42
|
+
// overflows any sensible container, so the loop always engages. Used as the
|
|
43
|
+
// row-count reference, and as the pad target until the container and chip
|
|
44
|
+
// widths have been measured (then the per-row target adapts — see chipWidth).
|
|
45
45
|
const DEFAULT_MIN_CHIPS_PER_ROW = 14
|
|
46
46
|
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
// copy overflows comfortably; the `+ADAPTIVE_PAD_MARGIN` covers the worst case.
|
|
53
|
-
const MIN_CHIP_PX = 72
|
|
47
|
+
// The adaptive pad target is derived from the MEASURED narrowest chip, not a
|
|
48
|
+
// guessed width: repeats = ceil(containerWidth / measuredChipWidth) + margin.
|
|
49
|
+
// Measuring (rather than assuming ~72px) is what makes it correct for a tiny
|
|
50
|
+
// one-word chip AND a wide multi-word one — a guess that overshoots the real
|
|
51
|
+
// chip under-pads and the row silently stops scrolling.
|
|
54
52
|
const ADAPTIVE_PAD_MARGIN = 2
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
53
|
+
// Divide-by-near-zero guard ONLY: floors a glitched sub-pixel measurement, but
|
|
54
|
+
// stays BELOW the narrowest real chip (padding alone is wider), so a genuinely
|
|
55
|
+
// tiny one-glyph chip keeps its true measured width and still gets enough
|
|
56
|
+
// repeats to overflow. MAX_ADAPTIVE_PAD bounds the count if the measurement is
|
|
57
|
+
// ever degenerate.
|
|
58
|
+
const MIN_MEASURED_CHIP_PX = 12
|
|
59
|
+
// Sanity bounds on the resulting count.
|
|
58
60
|
const MIN_ADAPTIVE_PAD = 4
|
|
61
|
+
const MAX_ADAPTIVE_PAD = 60
|
|
59
62
|
|
|
60
63
|
// Chat-agent walls (fae/mingo) cap the brick stack at 2 rows so the quick
|
|
61
64
|
// actions never crowd the composer; every other surface keeps the caller's
|
|
@@ -187,22 +190,39 @@ export function QuickActionWall({
|
|
|
187
190
|
contentClassName,
|
|
188
191
|
sync,
|
|
189
192
|
}: QuickActionWallProps) {
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
193
|
+
// Adapt the per-row pad target to the actual geometry (brick mode only): a
|
|
194
|
+
// narrow chat composer needs only a handful of repeats to overflow, a wide
|
|
195
|
+
// wall needs many, and a tiny chip needs more repeats than a wide one. We
|
|
196
|
+
// measure BOTH the container width and the narrowest rendered chip, then pad
|
|
197
|
+
// to ceil(width / chip) + margin. Pre-measure / SSR falls back to the fixed
|
|
198
|
+
// default, which overflows any width.
|
|
194
199
|
const brickRef = React.useRef<HTMLDivElement>(null)
|
|
195
200
|
const [brickWidth, setBrickWidth] = React.useState(0)
|
|
201
|
+
const [chipWidth, setChipWidth] = React.useState(0)
|
|
196
202
|
React.useEffect(() => {
|
|
197
203
|
const el = brickRef.current
|
|
198
|
-
if (!el
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
if (!el) return
|
|
205
|
+
// Read both widths straight off the laid-out DOM (offsetWidth is synchronous
|
|
206
|
+
// and reliable — a ResizeObserver initial callback can be throttled or never
|
|
207
|
+
// fire in a backgrounded/offscreen tab, which would strand the target on its
|
|
208
|
+
// fallback). Chips render as buttons on interactive walls — the only case
|
|
209
|
+
// that pads short sets; decorative walls keep the fallback. Converges in one
|
|
210
|
+
// pass: the widths don't depend on how many repeats we then draw.
|
|
211
|
+
const measure = () => {
|
|
212
|
+
const w = el.offsetWidth
|
|
213
|
+
if (w > 0) setBrickWidth(prev => (prev === w ? prev : w))
|
|
214
|
+
const chips = [...el.querySelectorAll('button')].map(b => (b as HTMLElement).offsetWidth).filter(x => x > 0)
|
|
215
|
+
if (chips.length > 0) {
|
|
216
|
+
const min = Math.max(Math.min(...chips), MIN_MEASURED_CHIP_PX)
|
|
217
|
+
setChipWidth(prev => (prev === min ? prev : min))
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
measure()
|
|
221
|
+
if (typeof ResizeObserver === 'undefined') return
|
|
222
|
+
const ro = new ResizeObserver(measure)
|
|
203
223
|
ro.observe(el)
|
|
204
224
|
return () => ro.disconnect()
|
|
205
|
-
}, [])
|
|
225
|
+
}, [chips])
|
|
206
226
|
|
|
207
227
|
// Repeat-pad the track (stable suffixed keys; repeats keep the full chip —
|
|
208
228
|
// identical selected/interactive state, so every visible instance behaves
|
|
@@ -266,10 +286,10 @@ export function QuickActionWall({
|
|
|
266
286
|
// duplicates); before the first measure (or SSR) fall back to the fixed
|
|
267
287
|
// default, which overflows any width.
|
|
268
288
|
const adaptivePerRow =
|
|
269
|
-
brickWidth > 0
|
|
289
|
+
brickWidth > 0 && chipWidth > 0
|
|
270
290
|
? Math.min(
|
|
271
|
-
Math.max(Math.ceil(brickWidth /
|
|
272
|
-
|
|
291
|
+
Math.max(Math.ceil(brickWidth / chipWidth) + ADAPTIVE_PAD_MARGIN, MIN_ADAPTIVE_PAD),
|
|
292
|
+
MAX_ADAPTIVE_PAD,
|
|
273
293
|
)
|
|
274
294
|
: DEFAULT_MIN_CHIPS_PER_ROW
|
|
275
295
|
const perRowTarget = minChips ? Math.ceil(minChips / stackRows) : adaptivePerRow
|
|
@@ -608,6 +608,14 @@ export interface DialogItem {
|
|
|
608
608
|
timestamp?: Date | string
|
|
609
609
|
isActive?: boolean
|
|
610
610
|
unreadMessagesCount?: number
|
|
611
|
+
/** Dialog owner (creator) — drives the trailing avatar in the chat-history
|
|
612
|
+
* rows (Figma 113:63224). Omit to render the row without an avatar. */
|
|
613
|
+
owner?: {
|
|
614
|
+
/** Full display name — initials fallback + `title` tooltip. */
|
|
615
|
+
name?: string | null
|
|
616
|
+
/** Absolute avatar URL; falls back to initials when absent or failing. */
|
|
617
|
+
avatarUrl?: string | null
|
|
618
|
+
}
|
|
611
619
|
}
|
|
612
620
|
|
|
613
621
|
// ========== Chat Sidebar Props ==========
|
|
@@ -369,6 +369,15 @@ export interface UnifiedChatState {
|
|
|
369
369
|
/** Fetch the next page of dialogs. No-op when `hasMoreDialogs` is false. */
|
|
370
370
|
loadMoreDialogs: () => Promise<void>
|
|
371
371
|
|
|
372
|
+
/** Ownership scope of `dialogs` — `'my'` (current user's chats) or `'all'`
|
|
373
|
+
* (every admin's). Optional: adapters without ownership data omit it and
|
|
374
|
+
* the "My Chats / All Chats" selector stays hidden. The HOST owns the
|
|
375
|
+
* filtering — `dialogs` must already reflect this scope. */
|
|
376
|
+
dialogScope?: 'my' | 'all'
|
|
377
|
+
|
|
378
|
+
/** Switch the ownership scope — see `dialogScope`. */
|
|
379
|
+
setDialogScope?: (scope: 'my' | 'all') => void
|
|
380
|
+
|
|
372
381
|
/** Whether more historical messages remain in the active dialog. */
|
|
373
382
|
hasMoreMessages: boolean
|
|
374
383
|
|
|
@@ -67,15 +67,18 @@ export function TabSelector({
|
|
|
67
67
|
onClick={() => onValueChange(item.id)}
|
|
68
68
|
aria-label={!item.label ? item.ariaLabel : undefined}
|
|
69
69
|
className={cn(
|
|
70
|
-
|
|
70
|
+
// Bold on BOTH states — a medium↔bold flip between the active
|
|
71
|
+
// and inactive tabs read as inconsistent weight (and nudged
|
|
72
|
+
// label widths on every switch).
|
|
73
|
+
'flex flex-1 items-center justify-center gap-2 rounded-xs p-2 text-h4 font-bold transition-colors duration-200 whitespace-nowrap',
|
|
71
74
|
isDisabled
|
|
72
75
|
? isActive
|
|
73
|
-
? 'cursor-not-allowed bg-ods-bg-surface text-ods-text-secondary
|
|
76
|
+
? 'cursor-not-allowed bg-ods-bg-surface text-ods-text-secondary'
|
|
74
77
|
: 'cursor-not-allowed bg-transparent text-ods-text-secondary'
|
|
75
78
|
: isActive
|
|
76
79
|
? variant === 'primary'
|
|
77
|
-
? 'bg-ods-accent text-ods-text-on-accent cursor-default
|
|
78
|
-
: 'bg-ods-bg-surface text-ods-text-primary cursor-default
|
|
80
|
+
? 'bg-ods-accent text-ods-text-on-accent cursor-default'
|
|
81
|
+
: 'bg-ods-bg-surface text-ods-text-primary cursor-default'
|
|
79
82
|
: 'bg-transparent text-ods-text-primary hover:bg-ods-bg-hover cursor-pointer',
|
|
80
83
|
)}
|
|
81
84
|
>
|
|
@@ -200,6 +200,29 @@ export const BrickNarrowComposer: Story = {
|
|
|
200
200
|
),
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
/** Brick, single short chip: one tiny one-word action ("ONE") in a ~412px
|
|
204
|
+
* composer. The pad target is measured from the ACTUAL chip width, so a narrow
|
|
205
|
+
* chip is repeated enough times to overflow and scroll — a width guess that
|
|
206
|
+
* overshot the real chip would under-pad and the row would sit static. */
|
|
207
|
+
export const BrickSingleShortChip: Story = {
|
|
208
|
+
render: () => (
|
|
209
|
+
<div className="w-[412px] max-w-full rounded-md border border-ods-border bg-ods-card p-[var(--spacing-system-mf)]">
|
|
210
|
+
<QuickActionWall
|
|
211
|
+
chips={[{ id: 'one', label: 'ONE', theme: FAE_THEME, onSelect: () => console.log('one') }]}
|
|
212
|
+
agentSlug="mingo"
|
|
213
|
+
rows={4}
|
|
214
|
+
pauseOnHover
|
|
215
|
+
dragScroll
|
|
216
|
+
fade={['left', 'right']}
|
|
217
|
+
fadeSize={{ left: 32 }}
|
|
218
|
+
fadeColor="var(--color-bg-card)"
|
|
219
|
+
copyGap="var(--spacing-system-xxs)"
|
|
220
|
+
className="max-h-44"
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
),
|
|
224
|
+
}
|
|
225
|
+
|
|
203
226
|
/** Brick, non-agent (no `agentSlug`): keeps exactly `rows` rows (marketing /
|
|
204
227
|
* onboarding walls are sized for their design), still getting the even split
|
|
205
228
|
* so a row is never filled with only one repeated chip. */
|