@flamingo-stack/openframe-frontend-core 0.0.210-snapshot.20260528032637 → 0.0.210
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-P5EE2VJX.cjs → chunk-6RZYJICV.cjs} +1 -1
- package/dist/chunk-6RZYJICV.cjs.map +1 -0
- package/dist/{chunk-ZG2YY5E7.js → chunk-7L4DWM7P.js} +1 -1
- package/dist/chunk-7L4DWM7P.js.map +1 -0
- package/dist/{chunk-QKFBZLIR.js → chunk-ATEUJQKU.js} +2 -2
- package/dist/{chunk-VTUIMMHO.cjs → chunk-MDTIOPVS.cjs} +24 -24
- package/dist/{chunk-VTUIMMHO.cjs.map → chunk-MDTIOPVS.cjs.map} +1 -1
- package/dist/{chunk-3E5ANY55.js → chunk-R5RNRH62.js} +5 -14
- package/dist/chunk-R5RNRH62.js.map +1 -0
- package/dist/{chunk-WI76ZUBE.cjs → chunk-TWKPYZNQ.cjs} +17 -26
- package/dist/chunk-TWKPYZNQ.cjs.map +1 -0
- package/dist/{chunk-ZFBLC5GV.cjs → chunk-VBFOCTMD.cjs} +17 -17
- package/dist/{chunk-ZFBLC5GV.cjs.map → chunk-VBFOCTMD.cjs.map} +1 -1
- package/dist/{chunk-5BNWGK6D.js → chunk-WJBPLMBX.js} +2 -2
- package/dist/components/chat/hooks/use-chat-identity.d.ts +3 -3
- package/dist/components/chat/hooks/use-chat-identity.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +3 -3
- package/dist/components/chat/index.js +2 -2
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/features/index.cjs +3 -3
- package/dist/components/features/index.js +2 -2
- package/dist/components/footer-waitlist-button.d.ts +2 -21
- package/dist/components/footer-waitlist-button.d.ts.map +1 -1
- package/dist/components/index.cjs +91 -87
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +16 -12
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/navigation/sticky-section-nav.d.ts.map +1 -1
- package/dist/components/tickets/help-center-card.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +98 -139
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +54 -95
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/tickets/ticket-row.d.ts.map +1 -1
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/contexts/chat-runtime-context.d.ts +3 -6
- package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
- package/dist/contexts/index.cjs +2 -2
- package/dist/contexts/index.js +1 -1
- package/dist/index.cjs +3 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -4
- package/dist/utils/index.cjs +0 -10
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -10
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/hooks/use-chat-identity.ts +7 -8
- package/src/components/footer-waitlist-button.tsx +16 -33
- package/src/components/navigation/sticky-section-nav.tsx +4 -6
- package/src/components/tickets/help-center-card.tsx +1 -55
- package/src/components/tickets/help-center-list.tsx +1 -9
- package/src/components/tickets/ticket-detail-drawer.tsx +4 -19
- package/src/components/tickets/ticket-row.tsx +19 -30
- package/src/contexts/chat-runtime-context.tsx +3 -6
- package/src/stories/EmbeddableChat.stories.tsx +1 -1
- package/src/utils/index.ts +0 -12
- package/dist/chunk-3E5ANY55.js.map +0 -1
- package/dist/chunk-P5EE2VJX.cjs.map +0 -1
- package/dist/chunk-WI76ZUBE.cjs.map +0 -1
- package/dist/chunk-ZG2YY5E7.js.map +0 -1
- package/dist/utils/scroll-into-view.d.ts +0 -63
- package/dist/utils/scroll-into-view.d.ts.map +0 -1
- package/src/utils/scroll-into-view.ts +0 -74
- /package/dist/{chunk-QKFBZLIR.js.map → chunk-ATEUJQKU.js.map} +0 -0
- /package/dist/{chunk-5BNWGK6D.js.map → chunk-WJBPLMBX.js.map} +0 -0
package/package.json
CHANGED
|
@@ -8,9 +8,8 @@
|
|
|
8
8
|
* (attachment button, drawer composer, /tickets gate) on chat-side
|
|
9
9
|
* identity tiers WITHOUT sending a chat message first.
|
|
10
10
|
*
|
|
11
|
-
* Server-side parity: the
|
|
12
|
-
*
|
|
13
|
-
* runs the same 3-tier `requireChatAuth` chain the chat itself uses.
|
|
11
|
+
* Server-side parity: the route at `/api/chat/identity` runs the same
|
|
12
|
+
* 3-tier `requireChatAuth` chain the chat itself uses.
|
|
14
13
|
* `attachmentsEnabled` is computed server-side as
|
|
15
14
|
* `authTier !== 'anon' AND isSelfScopedSource(source)` — single
|
|
16
15
|
* source of truth, consumers don't combine the fields themselves.
|
|
@@ -28,7 +27,7 @@
|
|
|
28
27
|
* 3. The fetch is cheap and short — no perf justification for a
|
|
29
28
|
* cache layer
|
|
30
29
|
*
|
|
31
|
-
* Endpoint URL: read from `useRequiredChatRuntime().endpoints.
|
|
30
|
+
* Endpoint URL: read from `useRequiredChatRuntime().endpoints.chatIdentityUrl`
|
|
32
31
|
* so embedded apps with their own reverse-proxy topology can override.
|
|
33
32
|
*/
|
|
34
33
|
|
|
@@ -38,9 +37,9 @@ import { chatAuthedFetch } from '../utils/chat-authed-fetch'
|
|
|
38
37
|
import { getChatProxyAuth } from '../utils/chat-proxy-auth-storage'
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
|
-
* Wire-shape for the identity route response. Mirrors
|
|
42
|
-
* `ChatIdentityResponse` (in `app/api/
|
|
43
|
-
* kept in sync there. Lib-side declaration so the chat panel can
|
|
40
|
+
* Wire-shape for the `/api/chat/identity` route response. Mirrors
|
|
41
|
+
* the hub's `ChatIdentityResponse` (in `app/api/chat/identity/route.ts`)
|
|
42
|
+
* — kept in sync there. Lib-side declaration so the chat panel can
|
|
44
43
|
* compile without depending on hub-internal types.
|
|
45
44
|
*/
|
|
46
45
|
export interface ChatIdentityResponse {
|
|
@@ -88,7 +87,7 @@ const ANON_DEFAULTS: ChatIdentityResponse = {
|
|
|
88
87
|
|
|
89
88
|
export function useChatIdentity(): ChatIdentitySurface {
|
|
90
89
|
const runtime = useRequiredChatRuntime()
|
|
91
|
-
const url = runtime.endpoints.
|
|
90
|
+
const url = runtime.endpoints.chatIdentityUrl
|
|
92
91
|
// `getChatProxyAuth()` reads localStorage every render. If the user
|
|
93
92
|
// pastes bearer creds mid-session (via the `/debug` creds bar),
|
|
94
93
|
// their email arrives here and the effect's dep changes → refetch.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useRouter } from '../embed-shims/next-navigation';
|
|
4
|
-
import { useChatRuntime } from '../contexts/chat-runtime-context';
|
|
3
|
+
import { usePathname, useRouter } from '../embed-shims/next-navigation';
|
|
5
4
|
import { useCallback } from 'react';
|
|
6
5
|
import { OpenFrameLogo } from './icons';
|
|
7
6
|
import { Button } from './ui/button';
|
|
@@ -12,43 +11,27 @@ export interface FooterWaitlistButtonProps {
|
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Small wrapper around JoinWaitlistButton for use inside the footer.
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* (`runtime.navigation.navigate`) when a `ChatRuntimeContext` is
|
|
18
|
-
* mounted — that's the same path EVERY other in-app navigation
|
|
19
|
-
* surface uses (source chips, inline cards, search-autocomplete,
|
|
20
|
-
* action cards). One rule, one decision tree across the whole app.
|
|
21
|
-
* The hub's `HubRuntimeProvider` wires `navigate` to its `useUnifiedNav`
|
|
22
|
-
* helper, so this button picks up cross-platform new-tab decisions,
|
|
23
|
-
* same-URL re-scroll handling, embed-mode short-circuiting, and any
|
|
24
|
-
* future host-side nav rules for free.
|
|
25
|
-
*
|
|
26
|
-
* Falls back to the embed-shim's `router.push` when no runtime is
|
|
27
|
-
* mounted (third-party embedders who haven't set up
|
|
28
|
-
* `ChatRuntimeContext` — the lib stays usable without forcing them
|
|
29
|
-
* to wire the full chat-runtime).
|
|
30
|
-
*
|
|
31
|
-
* Target URL: `/waitlist#top`. `#top` is the canonical "scroll to
|
|
32
|
-
* page top" anchor — the destination page has an explicit
|
|
33
|
-
* `<div id="top">` at the top of `<main>` so native browser anchor
|
|
34
|
-
* scroll works in every browser regardless of the HTML5 magic-anchor
|
|
35
|
-
* behavior.
|
|
14
|
+
* Provides a default click handler that scrolls/focuses the wait-list form
|
|
15
|
+
* if the user is already on /waitlist; otherwise navigates to it.
|
|
36
16
|
*/
|
|
37
17
|
export function FooterWaitlistButton({ className }: FooterWaitlistButtonProps) {
|
|
38
18
|
const router = useRouter();
|
|
39
|
-
const
|
|
19
|
+
const pathname = usePathname();
|
|
40
20
|
|
|
41
21
|
const handleClick = useCallback(() => {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
22
|
+
if (pathname?.startsWith('/waitlist')) {
|
|
23
|
+
const anchor = document.getElementById('waitlist-form');
|
|
24
|
+
if (anchor) {
|
|
25
|
+
anchor.scrollIntoView({ behavior: 'smooth', block: 'center' } as any);
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
const input = anchor.querySelector('input[type="email"]') as HTMLInputElement | null;
|
|
28
|
+
input?.focus();
|
|
29
|
+
}, 400);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
49
32
|
}
|
|
50
|
-
router.push(
|
|
51
|
-
}, [
|
|
33
|
+
router.push('/waitlist#waitlist-form');
|
|
34
|
+
}, [pathname, router]);
|
|
52
35
|
|
|
53
36
|
return (
|
|
54
37
|
<Button
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useEffect, useState, useCallback, useRef } from 'react'
|
|
4
4
|
import { cn } from '../../utils'
|
|
5
|
-
import { scrollElementIntoView } from '../../utils/scroll-into-view'
|
|
6
5
|
|
|
7
6
|
export interface StickyNavSection {
|
|
8
7
|
id: string
|
|
@@ -98,10 +97,7 @@ export function useSectionNavigation(
|
|
|
98
97
|
const isScrollingFromClick = useRef(false)
|
|
99
98
|
const { offset = 100 } = options || {}
|
|
100
99
|
|
|
101
|
-
// Handle click - scroll to the element
|
|
102
|
-
// The `offset` prop maps to `headerOffset` (sticky chrome above the
|
|
103
|
-
// section nav); same smooth-scroll mechanics every other anchor
|
|
104
|
-
// surface in the app uses.
|
|
100
|
+
// Handle click - just scroll to the element
|
|
105
101
|
const handleSectionClick = useCallback((sectionId: string) => {
|
|
106
102
|
const targetElement = document.getElementById(sectionId)
|
|
107
103
|
if (!targetElement) return
|
|
@@ -110,7 +106,9 @@ export function useSectionNavigation(
|
|
|
110
106
|
isScrollingFromClick.current = true
|
|
111
107
|
setActiveSection(sectionId)
|
|
112
108
|
|
|
113
|
-
|
|
109
|
+
// Scroll to element
|
|
110
|
+
const top = targetElement.offsetTop - offset
|
|
111
|
+
window.scrollTo({ top, behavior: 'smooth' })
|
|
114
112
|
|
|
115
113
|
// Allow scroll spy again after scroll completes
|
|
116
114
|
setTimeout(() => {
|
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
* is a SIBLING of the toggle button, not nested inside it.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import { useCallback, useRef } from 'react'
|
|
19
18
|
import { StatusBadge, type StatusBadgeProps } from '../ui'
|
|
20
19
|
import { formatRelativeTime } from '../../utils/date-utils'
|
|
21
|
-
import { scrollElementIntoView } from '../../utils/scroll-into-view'
|
|
22
20
|
import { getStatusColorScheme } from '../chat/utils/agent-status-message'
|
|
23
21
|
import { DevCardRowContent } from '../shared/dev-section/dev-card-row'
|
|
24
22
|
import {
|
|
@@ -28,23 +26,6 @@ import {
|
|
|
28
26
|
import type { AnyTicket } from './types'
|
|
29
27
|
import { isOptimistic } from './types'
|
|
30
28
|
|
|
31
|
-
/** Sticky page-chrome offset, applied two ways from this ONE constant:
|
|
32
|
-
*
|
|
33
|
-
* 1. As `scrollMarginTop` inline style on the wrapper — so any
|
|
34
|
-
* anchor-driven or `scrollIntoView()`-driven scroll (browser
|
|
35
|
-
* `#hash` navigation, Tab-focus into the card) lands BELOW the
|
|
36
|
-
* sticky header.
|
|
37
|
-
* 2. As `headerOffset` passed to `scrollElementIntoView(...)` — for
|
|
38
|
-
* the click-to-expand `window.scrollTo` path, which pre-computes
|
|
39
|
-
* its target pixel and ignores CSS `scroll-margin-top`.
|
|
40
|
-
*
|
|
41
|
-
* Single source of truth: change 96 here and BOTH paths follow. The
|
|
42
|
-
* previous code combined a `scroll-mt-24` (=96px) Tailwind class
|
|
43
|
-
* with this constant — two declarations, one comment binding them,
|
|
44
|
-
* drift hazard. Now there's nothing to keep in sync.
|
|
45
|
-
*/
|
|
46
|
-
const STICKY_HEADER_OFFSET_PX = 96
|
|
47
|
-
|
|
48
29
|
export interface HelpCenterCardProps {
|
|
49
30
|
ticket: AnyTicket
|
|
50
31
|
expanded: boolean
|
|
@@ -91,39 +72,6 @@ export function HelpCenterCard({
|
|
|
91
72
|
const isExpandable = !optimistic
|
|
92
73
|
const isExpanded = expanded && isExpandable
|
|
93
74
|
|
|
94
|
-
// Scroll-on-click — delegates to the canonical `scrollElementIntoView`
|
|
95
|
-
// helper with a cross-row layout-shift `adjustTargetY` callback. The
|
|
96
|
-
// helper owns the smooth-scroll mechanics + sticky-chrome offset; we
|
|
97
|
-
// pass the consumer-specific knowledge ("a sibling drawer above me
|
|
98
|
-
// is about to collapse — subtract its height from the target Y").
|
|
99
|
-
//
|
|
100
|
-
// Cross-row gotcha: if ANOTHER row above this one is currently
|
|
101
|
-
// expanded, its drawer collapses simultaneously with our toggle.
|
|
102
|
-
// The collapse shrinks the page above our row → our final Y is
|
|
103
|
-
// HIGHER than the current `rect.top`. By pre-subtracting the
|
|
104
|
-
// collapsing drawer's height we land at the post-shift position
|
|
105
|
-
// cleanly, without scrollIntoView's mid-animation drift.
|
|
106
|
-
const rowRef = useRef<HTMLDivElement | null>(null)
|
|
107
|
-
const handleClick = useCallback(() => {
|
|
108
|
-
onToggle(ticket.id)
|
|
109
|
-
scrollElementIntoView(rowRef.current, {
|
|
110
|
-
headerOffset: STICKY_HEADER_OFFSET_PX,
|
|
111
|
-
adjustTargetY: (raw) => {
|
|
112
|
-
if (!rowRef.current) return raw
|
|
113
|
-
const expandedDrawer = document.querySelector(
|
|
114
|
-
'div[id^="help-center-drawer-"]',
|
|
115
|
-
)
|
|
116
|
-
if (!(expandedDrawer instanceof HTMLElement)) return raw
|
|
117
|
-
const drawerRect = expandedDrawer.getBoundingClientRect()
|
|
118
|
-
const myRect = rowRef.current.getBoundingClientRect()
|
|
119
|
-
// Only adjust when the drawer is ABOVE us. Drawers below us
|
|
120
|
-
// don't shift our position when they collapse.
|
|
121
|
-
if (drawerRect.bottom > myRect.top) return raw
|
|
122
|
-
return raw - drawerRect.height
|
|
123
|
-
},
|
|
124
|
-
})
|
|
125
|
-
}, [onToggle, ticket.id])
|
|
126
|
-
|
|
127
75
|
const rightBadges = (
|
|
128
76
|
<>
|
|
129
77
|
<StatusBadge
|
|
@@ -145,14 +93,12 @@ export function HelpCenterCard({
|
|
|
145
93
|
|
|
146
94
|
return (
|
|
147
95
|
<div
|
|
148
|
-
ref={rowRef}
|
|
149
|
-
style={{ scrollMarginTop: STICKY_HEADER_OFFSET_PX }}
|
|
150
96
|
className={`border-b border-ods-border last:border-b-0 ${optimistic ? 'opacity-60' : ''}`}
|
|
151
97
|
aria-busy={optimistic || undefined}
|
|
152
98
|
>
|
|
153
99
|
<button
|
|
154
100
|
type="button"
|
|
155
|
-
onClick={isExpandable ?
|
|
101
|
+
onClick={isExpandable ? () => onToggle(ticket.id) : undefined}
|
|
156
102
|
disabled={!isExpandable}
|
|
157
103
|
aria-expanded={isExpandable ? isExpanded : undefined}
|
|
158
104
|
aria-controls={isExpanded ? `help-center-drawer-${ticket.id}` : undefined}
|
|
@@ -264,15 +264,7 @@ function HelpCenterListAuthed({
|
|
|
264
264
|
/>
|
|
265
265
|
)
|
|
266
266
|
) : (
|
|
267
|
-
|
|
268
|
-
// clip the rounded corners, but `hidden` makes the element
|
|
269
|
-
// a "scroll container" per CSSOM spec, which causes
|
|
270
|
-
// `scrollIntoView` calls inside (`<HelpCenterCard>` click
|
|
271
|
-
// handlers) to try scrolling THIS div (can't, overflow
|
|
272
|
-
// hidden) instead of bubbling up to the window. `clip`
|
|
273
|
-
// keeps the visual clip but NOT the scroll-container
|
|
274
|
-
// status, so click-to-scroll actually moves the page.
|
|
275
|
-
<div className="bg-ods-card border border-ods-border rounded-[6px] overflow-clip w-full">
|
|
267
|
+
<div className="bg-ods-card border border-ods-border rounded-[6px] overflow-hidden w-full">
|
|
276
268
|
{merged.map((ticket) => (
|
|
277
269
|
<HelpCenterCard
|
|
278
270
|
key={ticket.id}
|
|
@@ -212,16 +212,9 @@ function TicketTimelinePanel({ ticket }: { ticket: AnyTicket }) {
|
|
|
212
212
|
return (
|
|
213
213
|
<div className="bg-ods-card border border-ods-border rounded-[6px] overflow-hidden w-full">
|
|
214
214
|
{/* Customer-authored description + any legacy `---`-joined
|
|
215
|
-
comments.
|
|
216
|
-
"
|
|
217
|
-
|
|
218
|
-
`hubspot-conversations-utils.ts`) drops the customer's first
|
|
219
|
-
message from engagements when it was part of the HubSpot
|
|
220
|
-
Custom Channel bot intake — bodyTurns IS the canonical
|
|
221
|
-
original for those tickets. For tickets created without bot
|
|
222
|
-
intake (admin-created, email channel) bodyTurns shows the
|
|
223
|
-
manually-entered description and engagements show subsequent
|
|
224
|
-
replies — same flow, no duplication. */}
|
|
215
|
+
comments. Original message gets a special role label; legacy
|
|
216
|
+
updates get "Update N" or "Resolution". Timestamp matches the
|
|
217
|
+
ticket's creation time when available. */}
|
|
225
218
|
{bodyTurns.map((turn, i) => {
|
|
226
219
|
const isResolution = turn.startsWith('[Resolution]')
|
|
227
220
|
const role =
|
|
@@ -322,19 +315,11 @@ function TicketTimelinePanel({ ticket }: { ticket: AnyTicket }) {
|
|
|
322
315
|
avatarSrc = undefined
|
|
323
316
|
}
|
|
324
317
|
|
|
325
|
-
// Role label: every engagement is a customer-visible
|
|
326
|
-
// Conversations message (customer ↔ agent on the Custom
|
|
327
|
-
// Channel). There are no internal Notes on this surface
|
|
328
|
-
// anymore — the read path explicitly filters them. So
|
|
329
|
-
// "Reply" for BOTH sides. The previous "Note" label for
|
|
330
|
-
// support bubbles was a legacy artifact from when Notes
|
|
331
|
-
// were rendered and made customers think their support
|
|
332
|
-
// engineer was leaving internal comments on their ticket.
|
|
333
318
|
return (
|
|
334
319
|
<ConversationCardRow
|
|
335
320
|
key={eng.id}
|
|
336
321
|
author={author}
|
|
337
|
-
role=
|
|
322
|
+
role={isCustomer ? 'Reply' : 'Note'}
|
|
338
323
|
avatarSrc={avatarSrc}
|
|
339
324
|
timestamp={eng.createdAt}
|
|
340
325
|
body={stripAttachmentsPreamble(eng.body ?? '')}
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* rendered only when this row is the expanded one.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { useEffect, useRef } from 'react'
|
|
22
22
|
import {
|
|
23
23
|
Collapsible,
|
|
24
24
|
CollapsibleContent,
|
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
type ChatTicketItemData,
|
|
29
29
|
} from '../chat/entity-cards/chat-ticket-item'
|
|
30
30
|
import { formatRelativeTime } from '../../utils/date-utils'
|
|
31
|
-
import { scrollElementIntoView } from '../../utils/scroll-into-view'
|
|
32
31
|
import {
|
|
33
32
|
TicketDetailDrawer,
|
|
34
33
|
type TicketDetailDrawerProps,
|
|
@@ -65,35 +64,25 @@ export function TicketRow({
|
|
|
65
64
|
// arrived yet, so action targets would be undefined.
|
|
66
65
|
const optimistic = isOptimistic(ticket)
|
|
67
66
|
|
|
68
|
-
// Scroll the
|
|
69
|
-
//
|
|
70
|
-
//
|
|
67
|
+
// Scroll the row's summary tile to the top of the viewport when the
|
|
68
|
+
// user expands it. Without this, expanding a row near the bottom of
|
|
69
|
+
// the viewport leaves most of the drawer (timeline + composer)
|
|
70
|
+
// hidden — the user has to scroll manually to see what they just
|
|
71
|
+
// opened.
|
|
71
72
|
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// Same pattern as `<HelpCenterCard>` — the only diff is the drawer
|
|
77
|
-
// id prefix (`ticket-drawer-` vs `help-center-drawer-`).
|
|
73
|
+
// Uses smooth-scroll + `block: 'start'` so the tile lands at the top
|
|
74
|
+
// edge. Two RAFs let the Collapsible's height animation start before
|
|
75
|
+
// we measure the row position, otherwise the scroll lands at the
|
|
76
|
+
// pre-expansion position.
|
|
78
77
|
const rowRef = useRef<HTMLDivElement | null>(null)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
'div[id^="ticket-drawer-"]',
|
|
86
|
-
)
|
|
87
|
-
if (!(expandedDrawer instanceof HTMLElement)) return raw
|
|
88
|
-
const drawerRect = expandedDrawer.getBoundingClientRect()
|
|
89
|
-
const myRect = rowRef.current.getBoundingClientRect()
|
|
90
|
-
// Only adjust when the drawer is ABOVE us. Drawers below
|
|
91
|
-
// don't shift our position when they collapse.
|
|
92
|
-
if (drawerRect.bottom > myRect.top) return raw
|
|
93
|
-
return raw - drawerRect.height
|
|
94
|
-
},
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
if (!expanded || optimistic) return
|
|
80
|
+
requestAnimationFrame(() => {
|
|
81
|
+
requestAnimationFrame(() => {
|
|
82
|
+
rowRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' })
|
|
83
|
+
})
|
|
95
84
|
})
|
|
96
|
-
}, [
|
|
85
|
+
}, [expanded, optimistic])
|
|
97
86
|
|
|
98
87
|
const tileData: ChatTicketItemData = {
|
|
99
88
|
id: ticket.id,
|
|
@@ -123,14 +112,14 @@ export function TicketRow({
|
|
|
123
112
|
}
|
|
124
113
|
|
|
125
114
|
return (
|
|
126
|
-
<div ref={rowRef} className="scroll-mt-
|
|
115
|
+
<div ref={rowRef} className="scroll-mt-4">
|
|
127
116
|
<Collapsible
|
|
128
117
|
open={expanded && !optimistic}
|
|
129
118
|
className="border-b border-ods-border last:border-b-0"
|
|
130
119
|
>
|
|
131
120
|
<ChatTicketItem
|
|
132
121
|
ticket={tileData}
|
|
133
|
-
onClick={optimistic ? undefined :
|
|
122
|
+
onClick={optimistic ? undefined : onToggle}
|
|
134
123
|
aria-expanded={expanded && !optimistic}
|
|
135
124
|
aria-controls={`ticket-drawer-${ticket.id}`}
|
|
136
125
|
/>
|
|
@@ -62,15 +62,12 @@ export interface ChatRuntime {
|
|
|
62
62
|
* relative `/api/storage/view/chat-attachments/` is sufficient
|
|
63
63
|
* (same-origin); embedders supply an absolute hub URL so the
|
|
64
64
|
* browser can fetch cross-origin.
|
|
65
|
-
* - `
|
|
65
|
+
* - `chatIdentityUrl` — GET endpoint the `useChatIdentity` hook
|
|
66
66
|
* hits to learn the `{authTier, source, attachmentsEnabled}`
|
|
67
|
-
* capability bag for the current session.
|
|
68
|
-
* (tickets / contact form / any embedded surface that needs
|
|
69
|
-
* to identify the proxied customer), so the name has no
|
|
70
|
-
* "chat" prefix even though the consuming hook still does. */
|
|
67
|
+
* capability bag for the current session. */
|
|
71
68
|
attachmentUploadUrl: string
|
|
72
69
|
attachmentViewUrlPrefix: string
|
|
73
|
-
|
|
70
|
+
chatIdentityUrl: string
|
|
74
71
|
/** Optional URL prefix for the image proxy (`<prefix>?url=<external>`).
|
|
75
72
|
* When unset, lib's `getProxiedImageUrl` returns the original URL
|
|
76
73
|
* unchanged. Hub default: '/api/image-proxy'. Embedders that don't
|
|
@@ -35,7 +35,7 @@ function createMockRuntime(): ChatRuntime {
|
|
|
35
35
|
buildListUrl: () => null,
|
|
36
36
|
attachmentUploadUrl: '/__story__/upload',
|
|
37
37
|
attachmentViewUrlPrefix: '/__story__/view/',
|
|
38
|
-
|
|
38
|
+
chatIdentityUrl: '/__story__/identity',
|
|
39
39
|
},
|
|
40
40
|
navigation: {
|
|
41
41
|
mode: 'embed',
|
package/src/utils/index.ts
CHANGED
|
@@ -200,15 +200,3 @@ export { fetchPriorityProp, type FetchPriorityValue } from './fetch-priority'
|
|
|
200
200
|
// server-safe (no JSX, no contexts/* imports); imported by route-page
|
|
201
201
|
// `metadata` exports + the shared `<DevSectionView>` chrome.
|
|
202
202
|
export * from './dev-sections'
|
|
203
|
-
|
|
204
|
-
// Canonical "smooth scroll element into view with sticky-chrome
|
|
205
|
-
// offset" helper. Single source of truth across the lib + hub for
|
|
206
|
-
// pre-computed-target scrolling — see `scroll-into-view.ts` for the
|
|
207
|
-
// rationale (TL;DR: window.scrollTo({top, behavior:'smooth'}) with a
|
|
208
|
-
// pre-computed pixel value avoids the mid-animation jitter that
|
|
209
|
-
// `element.scrollIntoView()` produces when layout shifts during the
|
|
210
|
-
// scroll).
|
|
211
|
-
export {
|
|
212
|
-
type ScrollElementIntoViewOptions,
|
|
213
|
-
scrollElementIntoView,
|
|
214
|
-
} from './scroll-into-view'
|