@flamingo-stack/openframe-frontend-core 0.0.222 → 0.0.223

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.
Files changed (183) hide show
  1. package/dist/{chunk-AIKPR6BE.js → chunk-4VHWSAQR.js} +6069 -5235
  2. package/dist/chunk-4VHWSAQR.js.map +1 -0
  3. package/dist/{chunk-GMNE7P7V.cjs → chunk-4WBVMIKQ.cjs} +26 -26
  4. package/dist/{chunk-GMNE7P7V.cjs.map → chunk-4WBVMIKQ.cjs.map} +1 -1
  5. package/dist/{chunk-QYRV6MKX.js → chunk-53EA4QPZ.js} +13 -7
  6. package/dist/chunk-53EA4QPZ.js.map +1 -0
  7. package/dist/{chunk-4JOWZLEL.cjs → chunk-7TKYFZ3L.cjs} +7 -7
  8. package/dist/{chunk-4JOWZLEL.cjs.map → chunk-7TKYFZ3L.cjs.map} +1 -1
  9. package/dist/{chunk-IZA5WF44.cjs → chunk-ABBU7QOM.cjs} +12 -12
  10. package/dist/{chunk-IZA5WF44.cjs.map → chunk-ABBU7QOM.cjs.map} +1 -1
  11. package/dist/{chunk-KM4KK2A6.cjs → chunk-ENDUMC7Z.cjs} +2558 -1724
  12. package/dist/chunk-ENDUMC7Z.cjs.map +1 -0
  13. package/dist/{chunk-CCSEXVI4.js → chunk-GYYGA24C.js} +2 -2
  14. package/dist/{chunk-RMOHKAHA.js → chunk-ISWWP2BH.js} +3 -3
  15. package/dist/{chunk-QNYH3WUU.cjs → chunk-JUWPBMQM.cjs} +13 -7
  16. package/dist/chunk-JUWPBMQM.cjs.map +1 -0
  17. package/dist/{chunk-A2SFHYVF.cjs → chunk-KXLTWANQ.cjs} +10 -10
  18. package/dist/{chunk-A2SFHYVF.cjs.map → chunk-KXLTWANQ.cjs.map} +1 -1
  19. package/dist/{chunk-ND24XSVT.js → chunk-TA6Z2AKD.js} +3 -3
  20. package/dist/chunk-TA6Z2AKD.js.map +1 -0
  21. package/dist/{chunk-HRIAZQ5D.js → chunk-WOKB6RJ7.js} +3 -3
  22. package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
  23. package/dist/components/chat/chat-composer.d.ts.map +1 -1
  24. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  25. package/dist/components/chat/chat-message-skeleton.d.ts +19 -4
  26. package/dist/components/chat/chat-message-skeleton.d.ts.map +1 -1
  27. package/dist/components/chat/chat-panel-header-mobile.d.ts +24 -0
  28. package/dist/components/chat/chat-panel-header-mobile.d.ts.map +1 -0
  29. package/dist/components/chat/chat-panel-header.d.ts +6 -0
  30. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  31. package/dist/components/chat/chat-quick-action-row.d.ts +31 -0
  32. package/dist/components/chat/chat-quick-action-row.d.ts.map +1 -0
  33. package/dist/components/chat/embeddable-chat.d.ts +27 -0
  34. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  35. package/dist/components/chat/entity-cards/dispatch.d.ts +14 -0
  36. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  37. package/dist/components/chat/entity-cards/github-activity-card.d.ts +10 -1
  38. package/dist/components/chat/entity-cards/github-activity-card.d.ts.map +1 -1
  39. package/dist/components/chat/guide-welcome.d.ts.map +1 -1
  40. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +9 -0
  41. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  42. package/dist/components/chat/hooks/use-slash-commands.d.ts +26 -0
  43. package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
  44. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +12 -1
  45. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  46. package/dist/components/chat/hooks/use-unified-chat.d.ts +12 -0
  47. package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
  48. package/dist/components/chat/index.cjs +9 -3
  49. package/dist/components/chat/index.cjs.map +1 -1
  50. package/dist/components/chat/index.js +8 -2
  51. package/dist/components/chat/mingo-chat-history.d.ts +10 -0
  52. package/dist/components/chat/mingo-chat-history.d.ts.map +1 -1
  53. package/dist/components/chat/mingo-info-card.d.ts +63 -0
  54. package/dist/components/chat/mingo-info-card.d.ts.map +1 -0
  55. package/dist/components/chat/mingo-welcome.d.ts +14 -1
  56. package/dist/components/chat/mingo-welcome.d.ts.map +1 -1
  57. package/dist/components/chat/types/chat.types.d.ts +6 -0
  58. package/dist/components/chat/types/chat.types.d.ts.map +1 -1
  59. package/dist/components/chat/types/unified-chat-state.types.d.ts +30 -0
  60. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  61. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  62. package/dist/components/contact/index.cjs +4 -4
  63. package/dist/components/contact/index.js +3 -3
  64. package/dist/components/features/index.cjs +3 -3
  65. package/dist/components/features/index.js +2 -2
  66. package/dist/components/index.cjs +110 -104
  67. package/dist/components/index.cjs.map +1 -1
  68. package/dist/components/index.js +36 -30
  69. package/dist/components/index.js.map +1 -1
  70. package/dist/components/navigation/app-layout-drawer.d.ts +7 -0
  71. package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -1
  72. package/dist/components/navigation/index.cjs +3 -3
  73. package/dist/components/navigation/index.js +2 -2
  74. package/dist/components/onboarding-guides/index.cjs +26 -26
  75. package/dist/components/onboarding-guides/index.js +4 -4
  76. package/dist/components/tickets/index.cjs +67 -67
  77. package/dist/components/tickets/index.js +5 -5
  78. package/dist/components/ui/actions-menu.d.ts +9 -0
  79. package/dist/components/ui/actions-menu.d.ts.map +1 -1
  80. package/dist/components/ui/dropdown-button.d.ts +2 -2
  81. package/dist/components/ui/dropdown-button.d.ts.map +1 -1
  82. package/dist/components/ui/index.cjs +9 -3
  83. package/dist/components/ui/index.cjs.map +1 -1
  84. package/dist/components/ui/index.js +8 -2
  85. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  86. package/dist/components/ui/more-actions-menu.d.ts +13 -0
  87. package/dist/components/ui/more-actions-menu.d.ts.map +1 -1
  88. package/dist/components/ui/square-avatar.d.ts +4 -0
  89. package/dist/components/ui/square-avatar.d.ts.map +1 -1
  90. package/dist/components/ui/textarea.d.ts.map +1 -1
  91. package/dist/hooks/index.cjs +2 -2
  92. package/dist/hooks/index.js +1 -1
  93. package/dist/hooks/ui/use-auto-limit-tags.d.ts +7 -1
  94. package/dist/hooks/ui/use-auto-limit-tags.d.ts.map +1 -1
  95. package/dist/index.cjs +9 -3
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.js +8 -2
  98. package/package.json +1 -1
  99. package/src/components/chat/chat-archive-page.tsx +32 -7
  100. package/src/components/chat/chat-composer.tsx +6 -2
  101. package/src/components/chat/chat-message-enhanced.tsx +24 -7
  102. package/src/components/chat/chat-message-list.tsx +1 -1
  103. package/src/components/chat/chat-message-skeleton.tsx +60 -60
  104. package/src/components/chat/chat-panel-header-mobile.tsx +94 -0
  105. package/src/components/chat/chat-panel-header.tsx +78 -50
  106. package/src/components/chat/chat-quick-action-row.tsx +146 -0
  107. package/src/components/chat/embeddable-chat.tsx +140 -45
  108. package/src/components/chat/entity-cards/dispatch.tsx +770 -232
  109. package/src/components/chat/entity-cards/github-activity-card.tsx +9 -3
  110. package/src/components/chat/guide-welcome.tsx +11 -8
  111. package/src/components/chat/hooks/use-nats-chat-adapter.ts +80 -7
  112. package/src/components/chat/hooks/use-slash-commands.ts +39 -0
  113. package/src/components/chat/hooks/use-sse-chat-adapter.ts +49 -45
  114. package/src/components/chat/hooks/use-unified-chat.ts +31 -8
  115. package/src/components/chat/mingo-chat-history.tsx +118 -36
  116. package/src/components/chat/mingo-info-card.tsx +214 -0
  117. package/src/components/chat/mingo-welcome.tsx +89 -36
  118. package/src/components/chat/types/chat.types.ts +4 -0
  119. package/src/components/chat/types/unified-chat-state.types.ts +35 -0
  120. package/src/components/chat/utils/process-historical-messages.ts +8 -0
  121. package/src/components/content-loading-container.tsx +1 -1
  122. package/src/components/empty-state.tsx +3 -3
  123. package/src/components/faq-accordion.tsx +2 -2
  124. package/src/components/features/changelog-manager.tsx +2 -2
  125. package/src/components/features/highlight-config-section.tsx +2 -2
  126. package/src/components/features/og-editor-preview.tsx +3 -3
  127. package/src/components/features/release-media-manager.tsx +2 -2
  128. package/src/components/loading/card-skeleton.tsx +3 -3
  129. package/src/components/loading/device-card-skeleton.tsx +1 -1
  130. package/src/components/loading/margin-report-skeleton.tsx +2 -2
  131. package/src/components/loading/organization-card-skeleton.tsx +1 -1
  132. package/src/components/loading/organization-icon-skeleton.tsx +1 -1
  133. package/src/components/loading/page-layout-skeleton.tsx +9 -9
  134. package/src/components/loading/users-grid-skeleton.tsx +2 -2
  135. package/src/components/logs-list.tsx +1 -1
  136. package/src/components/media-carousel.tsx +1 -1
  137. package/src/components/navigation/app-layout-drawer.tsx +70 -6
  138. package/src/components/open-source-features.tsx +2 -2
  139. package/src/components/ui/actions-menu.tsx +36 -13
  140. package/src/components/ui/allowed-domains-input.tsx +3 -3
  141. package/src/components/ui/cursor-pagination.tsx +1 -1
  142. package/src/components/ui/dropdown-button.tsx +6 -6
  143. package/src/components/ui/info-row.tsx +2 -2
  144. package/src/components/ui/modal-v2.tsx +8 -1
  145. package/src/components/ui/more-actions-menu.tsx +13 -0
  146. package/src/components/ui/square-avatar.tsx +6 -1
  147. package/src/components/ui/tags-input.tsx +1 -1
  148. package/src/components/ui/textarea.tsx +18 -4
  149. package/src/components/vendor-icon.tsx +1 -1
  150. package/src/components/vendor-tag.tsx +1 -1
  151. package/src/components/why-it-matters.tsx +1 -1
  152. package/src/hooks/ui/use-auto-limit-tags.ts +22 -8
  153. package/src/stories/ActionsMenu.stories.tsx +256 -0
  154. package/src/stories/AppLayoutSidebar.stories.tsx +6 -0
  155. package/src/stories/Board.stories.tsx +1 -1
  156. package/src/stories/ChatMingoInfoCards.stories.tsx +748 -0
  157. package/src/stories/ChatTicketList.stories.tsx +1 -1
  158. package/src/stories/DataTable.stories.tsx +17 -13
  159. package/src/stories/EmbeddableChat.stories.tsx +233 -1
  160. package/src/stories/MingoInfoCard.stories.tsx +193 -0
  161. package/src/stories/MingoOnboardingCard.stories.tsx +1 -1
  162. package/src/stories/Modal.stories.tsx +1 -0
  163. package/src/stories/NotificationDrawer.stories.tsx +14 -3
  164. package/src/stories/PageActions.stories.tsx +4 -4
  165. package/src/stories/PageContainer.stories.tsx +5 -5
  166. package/src/stories/PageLayout.stories.tsx +1 -1
  167. package/src/stories/TabSelector.stories.tsx +2 -2
  168. package/src/stories/Tag.stories.tsx +10 -5
  169. package/src/stories/TicketInfoSection.stories.tsx +23 -11
  170. package/src/stories/Toaster.stories.tsx +5 -5
  171. package/src/stories/__fixtures__/chat-card-decorator.tsx +44 -0
  172. package/dist/chunk-AIKPR6BE.js.map +0 -1
  173. package/dist/chunk-KM4KK2A6.cjs.map +0 -1
  174. package/dist/chunk-ND24XSVT.js.map +0 -1
  175. package/dist/chunk-QNYH3WUU.cjs.map +0 -1
  176. package/dist/chunk-QYRV6MKX.js.map +0 -1
  177. package/src/stories/.TicketDescriptionViewer.stories.md +0 -39
  178. package/src/stories/.TicketKnowledgeBaseList.stories.md +0 -36
  179. package/src/stories/TicketDescriptionViewer.stories.tsx +0 -70
  180. package/src/stories/TicketKnowledgeBaseList.stories.tsx +0 -51
  181. /package/dist/{chunk-CCSEXVI4.js.map → chunk-GYYGA24C.js.map} +0 -0
  182. /package/dist/{chunk-RMOHKAHA.js.map → chunk-ISWWP2BH.js.map} +0 -0
  183. /package/dist/{chunk-HRIAZQ5D.js.map → chunk-WOKB6RJ7.js.map} +0 -0
@@ -6,54 +6,40 @@ interface ChatMessageSkeletonProps {
6
6
  className?: string
7
7
  showAvatar?: boolean
8
8
  isUser?: boolean
9
- assistantType?: 'mingo' | 'fae'
10
9
  }
11
10
 
12
- export function ChatMessageSkeleton({
13
- className,
14
- showAvatar = true,
15
- isUser = false,
16
- assistantType = 'fae'
11
+ /**
12
+ * One message-row skeleton. Mirrors `ChatMessageEnhanced`'s real layout:
13
+ * full panel width, no card background, and an INLINE avatar in the name row
14
+ * (the 2025 chat pattern — Claude.ai / ChatGPT / Gemini), NOT the legacy
15
+ * `absolute -left-16` hanging avatar. Body lines use percentage widths so they
16
+ * reflow cleanly as the panel resizes.
17
+ */
18
+ export function ChatMessageSkeleton({
19
+ className,
20
+ showAvatar = true,
21
+ isUser = false,
17
22
  }: ChatMessageSkeletonProps) {
18
- const isMingo = assistantType === 'mingo'
19
-
20
23
  return (
21
- <div
22
- className={cn(
23
- "relative",
24
- !isUser && "bg-ods-card/50 rounded-lg px-4 -mx-4",
25
- className
26
- )}
27
- >
28
- {/* Avatar Skeleton - absolutely positioned outside content area to match
29
- the real ChatMessageEnhanced hanging-avatar layout. */}
30
- {showAvatar && !isUser && (
31
- <div className={cn(
32
- "absolute -left-16 top-[var(--spacing-system-s)] w-12 h-12 rounded-full animate-pulse",
33
- isMingo
34
- ? "bg-gradient-to-br from-cyan-400/30 to-cyan-600/30"
35
- : "bg-gradient-to-br from-pink-400/30 to-pink-600/30"
36
- )} />
37
- )}
38
-
39
- {/* Message Content Skeleton */}
40
- <div className="flex flex-col gap-1 min-w-0">
41
- {/* Name and Timestamp Row Skeleton */}
42
- <div className="flex items-center justify-between pr-2">
43
- <div className="h-5 w-16 bg-ods-border rounded animate-pulse" />
44
- <div className="h-4 w-12 bg-ods-border rounded animate-pulse" />
24
+ <div className={cn("relative py-[var(--spacing-system-s)]", className)}>
25
+ <div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
26
+ {/* Name row — inline avatar (assistant only) + name + timestamp,
27
+ matching ChatMessageEnhanced's inline-avatar header. Neutral
28
+ `bg-ods-border` so the placeholders are actually visible (a tinted
29
+ low-alpha fill reads as blank on the dark surface). */}
30
+ <div className="flex items-center gap-[var(--spacing-system-xs)]">
31
+ {showAvatar && !isUser && (
32
+ <div className="h-8 w-8 flex-shrink-0 rounded-full bg-ods-border animate-pulse" />
33
+ )}
34
+ <div className="h-5 w-32 rounded bg-ods-border animate-pulse" />
35
+ <div className="ml-auto h-4 w-14 rounded bg-ods-border animate-pulse" />
45
36
  </div>
46
-
47
- {/* Message Content Skeleton */}
48
- <div className="flex flex-col gap-2">
49
- <div className="space-y-2">
50
- {/* First line - full width */}
51
- <div className="h-4 w-full bg-ods-border rounded animate-pulse" />
52
- {/* Second line - 80% width */}
53
- <div className="h-4 w-4/5 bg-ods-border rounded animate-pulse" />
54
- {/* Third line - 60% width (optional for variation) */}
55
- <div className="h-4 w-3/5 bg-ods-border rounded animate-pulse" />
56
- </div>
37
+
38
+ {/* Body lines percentage widths so they reflow on panel resize. */}
39
+ <div className="flex flex-col gap-2 pt-[var(--spacing-system-xxs)]">
40
+ <div className="h-4 w-full rounded bg-ods-border animate-pulse" />
41
+ <div className="h-4 w-11/12 rounded bg-ods-border animate-pulse" />
42
+ <div className="h-4 w-3/5 rounded bg-ods-border animate-pulse" />
57
43
  </div>
58
44
  </div>
59
45
  </div>
@@ -64,7 +50,16 @@ interface ChatMessageListSkeletonProps {
64
50
  className?: string
65
51
  messageCount?: number
66
52
  showAvatars?: boolean
67
- assistantType?: 'mingo' | 'fae'
53
+ /**
54
+ * Match `ChatMessageList`'s `fullWidth`: drop the centered
55
+ * `max-w-ods-content-narrow` column so the skeleton fills the panel and
56
+ * reflows on resize (instead of staying a fixed narrow column with large
57
+ * side gutters). Same semantics as the real list.
58
+ */
59
+ fullWidth?: boolean
60
+ /** Extra classes on the content column (e.g. host-provided padding). The
61
+ * host wrapper is expected to own the panel padding; this stays empty by
62
+ * default so the skeleton sits flush. */
68
63
  contentClassName?: string
69
64
  }
70
65
 
@@ -72,13 +67,12 @@ export function ChatMessageListSkeleton({
72
67
  className,
73
68
  messageCount = 6,
74
69
  showAvatars = true,
75
- assistantType = 'fae',
76
- contentClassName
70
+ fullWidth = false,
71
+ contentClassName,
77
72
  }: ChatMessageListSkeletonProps) {
78
73
  const messages = Array.from({ length: messageCount }, (_, index) => ({
79
74
  id: index,
80
75
  isUser: index % 3 === 0,
81
- assistantType: assistantType
82
76
  }))
83
77
 
84
78
  return (
@@ -88,23 +82,29 @@ export function ChatMessageListSkeleton({
88
82
  "flex h-full w-full flex-col overflow-y-auto overflow-x-hidden flex-1",
89
83
  "[scroll-behavior:smooth]",
90
84
  "scrollbar-thin scrollbar-track-transparent scrollbar-thumb-ods-border/30 hover:scrollbar-thumb-ods-text-secondary/30",
91
- className
85
+ className,
92
86
  )}
93
87
  >
94
- <div className={cn("mx-auto flex w-full max-w-ods-content-narrow flex-col pb-[var(--spacing-system-xs)] min-w-0", contentClassName ?? "px-[var(--spacing-system-m)]")} style={{ minHeight: '100%' }}>
88
+ <div
89
+ className={cn(
90
+ fullWidth
91
+ ? "flex w-full flex-col min-w-0"
92
+ : "mx-auto flex w-full max-w-ods-content-narrow flex-col min-w-0",
93
+ contentClassName,
94
+ )}
95
+ style={{ minHeight: '100%' }}
96
+ >
97
+ {/* Bottom-anchor the rows like the real list. */}
95
98
  <div className="flex-1" />
96
- <div className="space-y-6">
97
- {messages.map((message) => (
98
- <ChatMessageSkeleton
99
- key={message.id}
100
- showAvatar={showAvatars}
101
- isUser={message.isUser}
102
- assistantType={message.assistantType}
103
- />
104
- ))}
105
- </div>
99
+ {messages.map((message) => (
100
+ <ChatMessageSkeleton
101
+ key={message.id}
102
+ showAvatar={showAvatars}
103
+ isUser={message.isUser}
104
+ />
105
+ ))}
106
106
  </div>
107
107
  </div>
108
108
  </div>
109
109
  )
110
- }
110
+ }
@@ -0,0 +1,94 @@
1
+ 'use client'
2
+
3
+ import { Chevron02LeftIcon, ClockHistoryIcon } from '../icons-v2-generated'
4
+ import { Button } from '../ui/button'
5
+ import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu'
6
+ import { cn } from '../../utils/cn'
7
+ import type { ChatPanelHeaderProps } from './chat-panel-header'
8
+
9
+ export interface ChatPanelHeaderMobileProps extends ChatPanelHeaderProps {
10
+ className?: string
11
+ }
12
+
13
+ /**
14
+ * Mobile chat panel header — Figma node `7363:85532`. A distinct layout from
15
+ * the desktop bar (which packs full-height divider cells into a fixed `h-14`):
16
+ *
17
+ * - Surface: `ods-card` with a bottom border; generous `l` side padding,
18
+ * `l` bottom padding, and `xl` top padding on the title block (so the row
19
+ * bottom-aligns under a tall header).
20
+ * - Title: `text-h2` (vs the desktop `h3`/`h4`), truncated.
21
+ * - Actions: 44px outlined icon buttons (`Button variant="outline"
22
+ * size="icon"`) — a back chevron (when applicable) and a single trailing
23
+ * `⋯` menu carrying the view-dependent actions. There is no Close action
24
+ * (the drawer is dismissed via its backdrop / the back chevron); when no
25
+ * action applies the `⋯` trigger is hidden entirely.
26
+ *
27
+ * Same prop contract as `ChatPanelHeader` (`onClose` is accepted for parity but
28
+ * unused here); the orchestrator renders this on mobile and the desktop bar on
29
+ * `md+`.
30
+ */
31
+ export function ChatPanelHeaderMobile({
32
+ showBack = false,
33
+ title,
34
+ backAriaLabel = 'Back',
35
+ isArchivedView = false,
36
+ onBack,
37
+ onRestore,
38
+ onRename,
39
+ onArchive,
40
+ onOpenArchive,
41
+ className,
42
+ }: ChatPanelHeaderMobileProps) {
43
+ // The right-hand ⋯ menu carries the view-dependent actions. Close is not
44
+ // listed (the panel is dismissed elsewhere); the trigger is hidden entirely
45
+ // when no action applies (e.g. the archive list view).
46
+ const menuItems = [
47
+ !showBack && onOpenArchive && { id: 'open-archive', label: 'Chat archive', icon: <ClockHistoryIcon className="w-full h-full" />, onClick: onOpenArchive },
48
+ isArchivedView && onRestore && { id: 'unarchive', label: 'Unarchive chat', onClick: onRestore },
49
+ !isArchivedView && onRename && { id: 'rename', label: 'Rename chat', onClick: onRename },
50
+ !isArchivedView && onArchive && { id: 'archive', label: 'Archive chat', onClick: onArchive },
51
+ ].filter(Boolean) as ActionsMenuItem[]
52
+
53
+ return (
54
+ <div
55
+ className={cn(
56
+ 'flex-shrink-0 flex flex-col border-b border-ods-border bg-ods-card',
57
+ 'px-[var(--spacing-system-l)] pb-[var(--spacing-system-l)]',
58
+ className,
59
+ )}
60
+ >
61
+ <div className="flex w-full items-end gap-[var(--spacing-system-sf)]">
62
+ {showBack && (
63
+ <Button
64
+ variant="outline"
65
+ size="icon"
66
+ aria-label={backAriaLabel}
67
+ onClick={onBack}
68
+ className="shrink-0"
69
+ >
70
+ <Chevron02LeftIcon />
71
+ </Button>
72
+ )}
73
+
74
+ {/* Title block — `xl` top padding pushes the title down so the row
75
+ bottom-aligns the title, back chevron, and ⋯ menu. */}
76
+ <div className="flex flex-1 min-w-0 items-end gap-[var(--spacing-system-m)] pt-[var(--spacing-system-xl)]">
77
+ <div className="flex flex-1 min-w-0 flex-col gap-[var(--spacing-system-xs)]">
78
+ <p className="min-w-0 truncate text-h2 text-ods-text-primary" title={title}>
79
+ {title}
80
+ </p>
81
+ </div>
82
+
83
+ {menuItems.length > 0 && (
84
+ <ActionsMenuDropdown
85
+ groups={[{ items: menuItems }]}
86
+ triggerAriaLabel="Chat actions"
87
+ triggerClassName="shrink-0 bg-ods-card border-ods-border hover:bg-ods-bg-hover flex items-center justify-center focus-visible:ring-0"
88
+ />
89
+ )}
90
+ </div>
91
+ </div>
92
+ </div>
93
+ )
94
+ }
@@ -9,7 +9,16 @@ import {
9
9
  } from '../icons-v2-generated'
10
10
  import { XmarkIcon } from '../icons-v2-generated/signs-and-symbols/xmark-icon'
11
11
  import { ChatHeaderIconButton } from './chat-header-icon-button'
12
- import { MoreActionsMenu } from '../ui/more-actions-menu'
12
+ import { ActionsMenuDropdown, type ActionsMenuItem } from '../ui/actions-menu'
13
+ import { ChatPanelHeaderMobile } from './chat-panel-header-mobile'
14
+
15
+ /**
16
+ * Compact rounded header button used for the desktop back chevron. Borderless
17
+ * square with a hover background. (Mobile uses its own header component —
18
+ * `ChatPanelHeaderMobile`.)
19
+ */
20
+ export const COMPACT_HEADER_BUTTON =
21
+ 'inline-flex shrink-0 items-center justify-center size-8 rounded-md text-ods-text-secondary transition-colors hover:bg-ods-bg-hover hover:text-ods-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent'
13
22
 
14
23
  export interface ChatPanelHeaderProps {
15
24
  /** Show the back-chevron + bold (h3) title. When false, the static list
@@ -53,62 +62,81 @@ export function ChatPanelHeader({
53
62
  onArchive,
54
63
  onOpenArchive,
55
64
  }: ChatPanelHeaderProps) {
65
+ // Desktop ⋯ menu (active, non-archived conversation only) — rename / archive.
56
66
  const menuItems = [
57
- onRename && { label: 'Rename chat', onClick: onRename },
58
- onArchive && { label: 'Archive chat', onClick: onArchive },
59
- ].filter(Boolean) as { label: string; onClick: () => void }[]
67
+ onRename && { id: 'rename', label: 'Rename chat', onClick: onRename },
68
+ onArchive && { id: 'archive', label: 'Archive chat', onClick: onArchive },
69
+ ].filter(Boolean) as ActionsMenuItem[]
60
70
 
61
71
  return (
62
- <div className="flex-shrink-0 flex h-14 w-full overflow-hidden border-b border-ods-border bg-ods-card">
63
- <div className="flex flex-1 min-w-0 items-center gap-2 px-4 py-3">
64
- {showBack ? (
65
- <>
66
- <button
67
- type="button"
68
- onClick={onBack}
69
- aria-label={backAriaLabel}
70
- className="inline-flex shrink-0 items-center justify-center size-8 -ml-1 rounded-md text-ods-text-secondary transition-colors hover:bg-ods-bg-hover hover:text-ods-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
71
- >
72
- <Chevron01LeftIcon size={20} />
73
- </button>
74
- <span className="truncate text-h3 text-ods-text-primary">{title}</span>
75
- </>
76
- ) : (
77
- <p className="truncate text-h4 text-ods-text-primary">{title}</p>
72
+ <>
73
+ {/* Mobile (<md): a distinct layout (Figma node 7363:85532). */}
74
+ <ChatPanelHeaderMobile
75
+ className="flex md:hidden"
76
+ showBack={showBack}
77
+ title={title}
78
+ backAriaLabel={backAriaLabel}
79
+ isArchivedView={isArchivedView}
80
+ onBack={onBack}
81
+ onClose={onClose}
82
+ onRestore={onRestore}
83
+ onRename={onRename}
84
+ onArchive={onArchive}
85
+ onOpenArchive={onOpenArchive}
86
+ />
87
+
88
+ {/* Desktop (md+): fixed-height bar with full-height divider action cells. */}
89
+ <div className="hidden md:flex flex-shrink-0 h-14 w-full overflow-hidden border-b border-ods-border bg-ods-card">
90
+ <div className="flex flex-1 min-w-0 items-center gap-2 px-4 py-3">
91
+ {showBack ? (
92
+ <>
93
+ <button
94
+ type="button"
95
+ onClick={onBack}
96
+ aria-label={backAriaLabel}
97
+ className={`${COMPACT_HEADER_BUTTON} -ml-1`}
98
+ >
99
+ <Chevron01LeftIcon size={20} />
100
+ </button>
101
+ <span className="truncate text-h3 text-ods-text-primary">{title}</span>
102
+ </>
103
+ ) : (
104
+ <p className="truncate text-h3 text-ods-text-primary">{title}</p>
105
+ )}
106
+ </div>
107
+
108
+ {/* Restore (refresh) — archived chat header (Figma node 7361:425441). */}
109
+ {isArchivedView && onRestore && (
110
+ <ChatHeaderIconButton onClick={onRestore} aria-label="Unarchive chat">
111
+ <Refresh01LeftIcon size={24} />
112
+ </ChatHeaderIconButton>
78
113
  )}
79
- </div>
80
114
 
81
- {/* Restore (refresh) archived chat header (Figma node 7361:425441). */}
82
- {isArchivedView && onRestore && (
83
- <ChatHeaderIconButton onClick={onRestore} aria-label="Unarchive chat">
84
- <Refresh01LeftIcon size={24} />
85
- </ChatHeaderIconButton>
86
- )}
115
+ {/* Rename / Archive menu active (non-archived) conversation only. */}
116
+ {showBack && !isArchivedView && menuItems.length > 0 && (
117
+ <ActionsMenuDropdown
118
+ triggerAriaLabel="Chat actions"
119
+ onCloseAutoFocus={(e) => e.preventDefault()}
120
+ groups={[{ items: menuItems }]}
121
+ customTrigger={
122
+ <ChatHeaderIconButton aria-label="Chat actions">
123
+ <Ellipsis01Icon size={24} />
124
+ </ChatHeaderIconButton>
125
+ }
126
+ />
127
+ )}
87
128
 
88
- {/* Rename / Archive menu active (non-archived) conversation only. */}
89
- {showBack && !isArchivedView && menuItems.length > 0 && (
90
- <MoreActionsMenu
91
- ariaLabel="Chat actions"
92
- onCloseAutoFocus={(e) => e.preventDefault()}
93
- items={menuItems}
94
- trigger={
95
- <ChatHeaderIconButton aria-label="Chat actions">
96
- <Ellipsis01Icon size={24} />
97
- </ChatHeaderIconButton>
98
- }
99
- />
100
- )}
129
+ {/* Chat Archive entry (Figma node 7532:225034) list view only. */}
130
+ {!showBack && onOpenArchive && (
131
+ <ChatHeaderIconButton onClick={onOpenArchive} aria-label="Chat archive">
132
+ <ClockHistoryIcon size={24} />
133
+ </ChatHeaderIconButton>
134
+ )}
101
135
 
102
- {/* Chat Archive entry (Figma node 7532:225034) — list view only. */}
103
- {!showBack && onOpenArchive && (
104
- <ChatHeaderIconButton onClick={onOpenArchive} aria-label="Chat archive">
105
- <ClockHistoryIcon size={24} />
136
+ <ChatHeaderIconButton onClick={onClose} aria-label="Close">
137
+ <XmarkIcon size={24} />
106
138
  </ChatHeaderIconButton>
107
- )}
108
-
109
- <ChatHeaderIconButton onClick={onClose} aria-label="Close">
110
- <XmarkIcon size={24} />
111
- </ChatHeaderIconButton>
112
- </div>
139
+ </div>
140
+ </>
113
141
  )
114
142
  }
@@ -0,0 +1,146 @@
1
+ 'use client'
2
+
3
+ import * as React from 'react'
4
+ import { cn } from '../../utils/cn'
5
+ import { Tag } from '../ui/tag'
6
+ import { ActionsMenuDropdown } from '../ui/actions-menu'
7
+ import { Ellipsis01Icon } from '../icons-v2-generated'
8
+ import { useAutoLimitTags } from '../../hooks/ui/use-auto-limit-tags'
9
+
10
+ // =============================================================================
11
+ // Types
12
+ // =============================================================================
13
+
14
+ /** A single collapsible quick-action chip. */
15
+ export interface QuickActionChip {
16
+ /** Stable React key + menu-item id. */
17
+ id: string
18
+ label: string
19
+ icon?: React.ReactNode
20
+ /** `'primary'` = accent (yellow) chip, `'outline'` = bordered chip (default). */
21
+ variant?: 'primary' | 'outline'
22
+ onSelect?: () => void
23
+ }
24
+
25
+ export interface ChatQuickActionRowProps {
26
+ /** Always-visible leading chip(s) (e.g. Mingo's "Start Guide Chat"). Never
27
+ * collapses into the overflow menu — rendered before the measured zone. */
28
+ leading?: React.ReactNode
29
+ /** Collapsible chips — as many as fit on one line render inline; the rest
30
+ * collapse under a trailing "⋯" overflow menu (small icon button). */
31
+ chips: ReadonlyArray<QuickActionChip>
32
+ className?: string
33
+ }
34
+
35
+ // =============================================================================
36
+ // Chip button
37
+ // =============================================================================
38
+
39
+ function ChipButton({ chip }: { chip: QuickActionChip }) {
40
+ return (
41
+ <button
42
+ type="button"
43
+ onClick={chip.onSelect}
44
+ className="shrink-0 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
45
+ >
46
+ <Tag variant={chip.variant ?? 'outline'} icon={chip.icon} label={chip.label} />
47
+ </button>
48
+ )
49
+ }
50
+
51
+ // =============================================================================
52
+ // Component
53
+ // =============================================================================
54
+
55
+ /**
56
+ * A single-line quick-action chip row that collapses overflow into a "⋯" menu.
57
+ *
58
+ * Same dynamic, width-measured overflow used by the multi-select Autocomplete
59
+ * (`useAutoLimitTags`): off-screen copies of every chip are measured, as many
60
+ * as fit render inline, and the remainder move into an `ActionsMenuDropdown`
61
+ * behind a small "⋯" icon button. An optional `leading` slot stays pinned and
62
+ * never collapses, so the row's primary action is always reachable.
63
+ */
64
+ export function ChatQuickActionRow({
65
+ leading,
66
+ chips,
67
+ className,
68
+ }: ChatQuickActionRowProps) {
69
+ const auto = useAutoLimitTags({
70
+ count: chips.length,
71
+ reserveInputWidth: false,
72
+ })
73
+
74
+ const visibleChips = chips.slice(0, auto.visibleCount)
75
+ const hiddenChips = chips.slice(auto.visibleCount)
76
+
77
+ if (!leading && chips.length === 0) return null
78
+
79
+ return (
80
+ <div
81
+ className={cn('relative flex shrink-0 items-center gap-1', className)}
82
+ >
83
+ {leading}
84
+
85
+ {chips.length > 0 && (
86
+ // Measured zone: `flex-1 min-w-0 overflow-hidden` so it claims the width
87
+ // left by the pinned `leading` slot and clips anything past the fit.
88
+ <div
89
+ ref={auto.middleRef}
90
+ className="flex flex-1 min-w-0 items-center gap-1 overflow-hidden"
91
+ >
92
+ {visibleChips.map((chip) => (
93
+ <ChipButton key={chip.id} chip={chip} />
94
+ ))}
95
+
96
+ {hiddenChips.length > 0 && (
97
+ <ActionsMenuDropdown
98
+ triggerAriaLabel="More quick actions"
99
+ // Don't return focus (and its ring) to the "⋯" trigger on close.
100
+ onCloseAutoFocus={(e) => e.preventDefault()}
101
+ groups={[
102
+ {
103
+ items: hiddenChips.map((chip) => ({
104
+ id: chip.id,
105
+ label: chip.label,
106
+ icon: chip.icon,
107
+ onClick: chip.onSelect,
108
+ })),
109
+ },
110
+ ]}
111
+ customTrigger={
112
+ <button
113
+ ref={auto.badgeRef}
114
+ type="button"
115
+ aria-label="More quick actions"
116
+ className="shrink-0 rounded-md focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
117
+ >
118
+ <Tag variant="outline" label={<Ellipsis01Icon size={16} />} />
119
+ </button>
120
+ }
121
+ />
122
+ )}
123
+ </div>
124
+ )}
125
+
126
+ {/* Off-screen measurement copies of every chip — the hook reads their
127
+ widths to decide how many fit. Mirror the visible chip markup (same
128
+ variant/icon/label and the same `gap-1`) so the measured widths match
129
+ what actually renders. */}
130
+ <div
131
+ ref={auto.measureRef}
132
+ aria-hidden
133
+ className="pointer-events-none invisible absolute left-0 top-0 -z-10 flex gap-1"
134
+ >
135
+ {chips.map((chip) => (
136
+ <Tag
137
+ key={`measure-${chip.id}`}
138
+ variant={chip.variant ?? 'outline'}
139
+ icon={chip.icon}
140
+ label={chip.label}
141
+ />
142
+ ))}
143
+ </div>
144
+ </div>
145
+ )
146
+ }