@flamingo-stack/openframe-frontend-core 0.0.560 → 0.0.561-1846.5000.1

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 (146) hide show
  1. package/dist/{chunk-F3F3PVJ3.js → chunk-44U25GD5.js} +2 -2
  2. package/dist/{chunk-DCB4EBA5.cjs → chunk-4LHEQMOV.cjs} +3 -3
  3. package/dist/chunk-4LHEQMOV.cjs.map +1 -0
  4. package/dist/{chunk-IGO6GNG7.js → chunk-5AEHCGQV.js} +6 -6
  5. package/dist/{chunk-QUYIRF6U.cjs → chunk-AGQ4VTUX.cjs} +29 -29
  6. package/dist/{chunk-QUYIRF6U.cjs.map → chunk-AGQ4VTUX.cjs.map} +1 -1
  7. package/dist/{chunk-GMDPQ5FQ.js → chunk-B6TM3QAL.js} +4 -4
  8. package/dist/{chunk-GZSUDIJB.cjs → chunk-CR2CMLTQ.cjs} +4 -4
  9. package/dist/{chunk-GZSUDIJB.cjs.map → chunk-CR2CMLTQ.cjs.map} +1 -1
  10. package/dist/{chunk-MAX76H7U.cjs → chunk-D3OY4CJU.cjs} +3 -3
  11. package/dist/{chunk-MAX76H7U.cjs.map → chunk-D3OY4CJU.cjs.map} +1 -1
  12. package/dist/{chunk-WXJZULAB.cjs → chunk-DAVVVBNL.cjs} +26 -26
  13. package/dist/{chunk-WXJZULAB.cjs.map → chunk-DAVVVBNL.cjs.map} +1 -1
  14. package/dist/{chunk-XRP6VHOS.cjs → chunk-DHRJFUBR.cjs} +11 -11
  15. package/dist/{chunk-XRP6VHOS.cjs.map → chunk-DHRJFUBR.cjs.map} +1 -1
  16. package/dist/{chunk-N66ANBF6.cjs → chunk-EK346HD7.cjs} +39 -39
  17. package/dist/{chunk-N66ANBF6.cjs.map → chunk-EK346HD7.cjs.map} +1 -1
  18. package/dist/{chunk-UR2JKFIO.cjs → chunk-FSWJCGRJ.cjs} +7 -7
  19. package/dist/{chunk-UR2JKFIO.cjs.map → chunk-FSWJCGRJ.cjs.map} +1 -1
  20. package/dist/{chunk-RDFMBOSI.js → chunk-GKGQ4DWE.js} +120 -48
  21. package/dist/chunk-GKGQ4DWE.js.map +1 -0
  22. package/dist/{chunk-FEI4KQYW.cjs → chunk-GWRQ5EEI.cjs} +953 -881
  23. package/dist/chunk-GWRQ5EEI.cjs.map +1 -0
  24. package/dist/{chunk-NCU45OV4.cjs → chunk-GYC3OTBI.cjs} +9 -9
  25. package/dist/{chunk-NCU45OV4.cjs.map → chunk-GYC3OTBI.cjs.map} +1 -1
  26. package/dist/{chunk-WIPZ65KU.js → chunk-JCQUWIPT.js} +3 -3
  27. package/dist/{chunk-2IL7SPBP.js → chunk-KRYLVEXU.js} +2 -2
  28. package/dist/{chunk-YNRK3N2D.js → chunk-KWNVQZ5Y.js} +4 -4
  29. package/dist/{chunk-QXDEXQDI.js → chunk-MLSDBNKT.js} +2 -2
  30. package/dist/{chunk-65ZXR4YJ.js → chunk-NGY4KET4.js} +2 -2
  31. package/dist/{chunk-QKGNSXF2.js → chunk-OCJG3X4U.js} +2 -2
  32. package/dist/{chunk-VAAQZZHT.js → chunk-ONDI64M3.js} +2 -2
  33. package/dist/{chunk-BKQWCJP5.js → chunk-QGQ26NEE.js} +2 -2
  34. package/dist/{chunk-RSZXB6J2.js → chunk-RDKJCZHY.js} +3 -3
  35. package/dist/chunk-RDKJCZHY.js.map +1 -0
  36. package/dist/{chunk-CSXEOFJ4.cjs → chunk-SUPBFRUK.cjs} +5 -5
  37. package/dist/{chunk-CSXEOFJ4.cjs.map → chunk-SUPBFRUK.cjs.map} +1 -1
  38. package/dist/{chunk-I7RGBCG6.cjs → chunk-UJ7MSY6F.cjs} +31 -31
  39. package/dist/{chunk-I7RGBCG6.cjs.map → chunk-UJ7MSY6F.cjs.map} +1 -1
  40. package/dist/{chunk-UW42K54L.cjs → chunk-UV53Z64O.cjs} +14 -14
  41. package/dist/{chunk-UW42K54L.cjs.map → chunk-UV53Z64O.cjs.map} +1 -1
  42. package/dist/{chunk-NFMYNXFH.cjs → chunk-WCM4BGHO.cjs} +97 -97
  43. package/dist/{chunk-NFMYNXFH.cjs.map → chunk-WCM4BGHO.cjs.map} +1 -1
  44. package/dist/{chunk-57G6CG2E.js → chunk-YUAKZNK5.js} +6 -6
  45. package/dist/{chunk-XQJPFAA7.cjs → chunk-ZENRHE55.cjs} +62 -62
  46. package/dist/{chunk-XQJPFAA7.cjs.map → chunk-ZENRHE55.cjs.map} +1 -1
  47. package/dist/{chunk-PHIRRCPY.js → chunk-ZJ7RI62F.js} +2 -2
  48. package/dist/components/calendar.cjs +81 -4
  49. package/dist/components/calendar.cjs.map +1 -1
  50. package/dist/components/calendar.js +81 -4
  51. package/dist/components/calendar.js.map +1 -1
  52. package/dist/components/case-studies/index.cjs +9 -9
  53. package/dist/components/case-studies/index.js +3 -3
  54. package/dist/components/chat/index.cjs +3 -3
  55. package/dist/components/chat/index.js +2 -2
  56. package/dist/components/contact/index.cjs +4 -4
  57. package/dist/components/contact/index.js +3 -3
  58. package/dist/components/docs/index.cjs +6 -6
  59. package/dist/components/docs/index.js +5 -5
  60. package/dist/components/embeds/index.cjs +4 -4
  61. package/dist/components/embeds/index.js +3 -3
  62. package/dist/components/faq/index.cjs +4 -4
  63. package/dist/components/faq/index.js +3 -3
  64. package/dist/components/features/index.cjs +5 -3
  65. package/dist/components/features/index.cjs.map +1 -1
  66. package/dist/components/features/index.d.ts +1 -1
  67. package/dist/components/features/index.d.ts.map +1 -1
  68. package/dist/components/features/index.js +4 -2
  69. package/dist/components/features/use-walkthrough-video.d.ts +1 -1
  70. package/dist/components/features/use-walkthrough-video.d.ts.map +1 -1
  71. package/dist/components/features/{floating-walkthrough-video.d.ts → walkthrough-video.d.ts} +61 -7
  72. package/dist/components/features/walkthrough-video.d.ts.map +1 -0
  73. package/dist/components/help-center-pages/index.cjs +23 -23
  74. package/dist/components/help-center-pages/index.js +14 -14
  75. package/dist/components/index.cjs +144 -140
  76. package/dist/components/index.cjs.map +1 -1
  77. package/dist/components/index.js +16 -12
  78. package/dist/components/index.js.map +1 -1
  79. package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
  80. package/dist/components/meeting-scheduler/context-panel.d.ts +24 -6
  81. package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
  82. package/dist/components/meeting-scheduler/index.cjs +529 -425
  83. package/dist/components/meeting-scheduler/index.cjs.map +1 -1
  84. package/dist/components/meeting-scheduler/index.d.ts +54 -1
  85. package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
  86. package/dist/components/meeting-scheduler/index.js +507 -403
  87. package/dist/components/meeting-scheduler/index.js.map +1 -1
  88. package/dist/components/meeting-scheduler/slot-picker.d.ts +35 -31
  89. package/dist/components/meeting-scheduler/slot-picker.d.ts.map +1 -1
  90. package/dist/components/navigation/index.cjs +3 -3
  91. package/dist/components/navigation/index.js +2 -2
  92. package/dist/components/onboarding-guides/index.cjs +6 -6
  93. package/dist/components/onboarding-guides/index.js +5 -5
  94. package/dist/components/related-content/index.cjs +4 -4
  95. package/dist/components/related-content/index.js +3 -3
  96. package/dist/components/tickets/index.cjs +7 -7
  97. package/dist/components/tickets/index.js +6 -6
  98. package/dist/components/ui/date-picker.d.ts +62 -1
  99. package/dist/components/ui/date-picker.d.ts.map +1 -1
  100. package/dist/components/ui/field-wrapper.d.ts +7 -0
  101. package/dist/components/ui/field-wrapper.d.ts.map +1 -1
  102. package/dist/components/ui/file-manager/index.cjs +36 -36
  103. package/dist/components/ui/file-manager/index.js +1 -1
  104. package/dist/components/ui/index.cjs +5 -3
  105. package/dist/components/ui/index.cjs.map +1 -1
  106. package/dist/components/ui/index.js +4 -2
  107. package/dist/hooks/use-meeting-booking.d.ts +31 -8
  108. package/dist/hooks/use-meeting-booking.d.ts.map +1 -1
  109. package/dist/index.cjs +7 -3
  110. package/dist/index.cjs.map +1 -1
  111. package/dist/index.js +6 -2
  112. package/package.json +1 -1
  113. package/src/components/features/{.floating-walkthrough-video.md → .walkthrough-video.md} +8 -5
  114. package/src/components/features/index.ts +1 -1
  115. package/src/components/features/use-walkthrough-video.ts +1 -1
  116. package/src/components/features/{floating-walkthrough-video.tsx → walkthrough-video.tsx} +108 -22
  117. package/src/components/meeting-scheduler/booking-form.tsx +60 -51
  118. package/src/components/meeting-scheduler/confirmation.tsx +1 -1
  119. package/src/components/meeting-scheduler/context-panel.tsx +166 -68
  120. package/src/components/meeting-scheduler/index.tsx +227 -81
  121. package/src/components/meeting-scheduler/slot-picker.tsx +275 -127
  122. package/src/components/ui/date-picker.tsx +200 -47
  123. package/src/components/ui/field-wrapper.tsx +9 -2
  124. package/src/hooks/use-meeting-booking.ts +97 -73
  125. package/dist/chunk-7J4DGFHZ.js +0 -88
  126. package/dist/chunk-7J4DGFHZ.js.map +0 -1
  127. package/dist/chunk-DCB4EBA5.cjs.map +0 -1
  128. package/dist/chunk-FEI4KQYW.cjs.map +0 -1
  129. package/dist/chunk-MKPJYEOR.cjs +0 -88
  130. package/dist/chunk-MKPJYEOR.cjs.map +0 -1
  131. package/dist/chunk-RDFMBOSI.js.map +0 -1
  132. package/dist/chunk-RSZXB6J2.js.map +0 -1
  133. package/dist/components/features/floating-walkthrough-video.d.ts.map +0 -1
  134. /package/dist/{chunk-F3F3PVJ3.js.map → chunk-44U25GD5.js.map} +0 -0
  135. /package/dist/{chunk-IGO6GNG7.js.map → chunk-5AEHCGQV.js.map} +0 -0
  136. /package/dist/{chunk-GMDPQ5FQ.js.map → chunk-B6TM3QAL.js.map} +0 -0
  137. /package/dist/{chunk-WIPZ65KU.js.map → chunk-JCQUWIPT.js.map} +0 -0
  138. /package/dist/{chunk-2IL7SPBP.js.map → chunk-KRYLVEXU.js.map} +0 -0
  139. /package/dist/{chunk-YNRK3N2D.js.map → chunk-KWNVQZ5Y.js.map} +0 -0
  140. /package/dist/{chunk-QXDEXQDI.js.map → chunk-MLSDBNKT.js.map} +0 -0
  141. /package/dist/{chunk-65ZXR4YJ.js.map → chunk-NGY4KET4.js.map} +0 -0
  142. /package/dist/{chunk-QKGNSXF2.js.map → chunk-OCJG3X4U.js.map} +0 -0
  143. /package/dist/{chunk-VAAQZZHT.js.map → chunk-ONDI64M3.js.map} +0 -0
  144. /package/dist/{chunk-BKQWCJP5.js.map → chunk-QGQ26NEE.js.map} +0 -0
  145. /package/dist/{chunk-57G6CG2E.js.map → chunk-YUAKZNK5.js.map} +0 -0
  146. /package/dist/{chunk-PHIRRCPY.js.map → chunk-ZJ7RI62F.js.map} +0 -0
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useMemo } from 'react'
4
4
  import { SquareAvatar, AvatarStack, Button, Skeleton, Autocomplete } from '../ui'
5
- import { ClockIcon } from '../icons-v2-generated'
5
+ import { BackButton } from '../layout/back-button'
6
6
  import { cn } from '../../utils/cn'
7
7
  import { formatDurationCompact } from '../../utils/format'
8
8
  import type { MeetingHost } from '../../schemas/meeting-booking-schema'
@@ -16,6 +16,13 @@ import type { MeetingHost } from '../../schemas/meeting-booking-schema'
16
16
  * SEARCHABLE timezone picker (all IANA zones with live GMT offsets;
17
17
  * rendering-only — the wire is always epoch-ms).
18
18
  *
19
+ * `onBack` (optional) puts the card's ONE back edge at the TOP of this panel —
20
+ * the one spot that stays put while the action side swaps between calendar,
21
+ * form and confirmation. What it means is the caller's call: the scheduler
22
+ * points it at the previous STEP from the form, and at the host's own exit
23
+ * from the calendar. One affordance, one place, so the visitor never has to
24
+ * work out which of two "Back"s goes where.
25
+ *
19
26
  * The picker renders only once the parent resolves the zone post-mount — SSR
20
27
  * output stays deterministic; a same-footprint skeleton holds the space.
21
28
  */
@@ -30,14 +37,67 @@ export interface SchedulerContextPanelProps {
30
37
  /** Resolved IANA zone (null until the client resolves it). */
31
38
  timezone: string | null
32
39
  onTimezoneChange?: (tz: string) => void
33
- /** True once a slot is chosen (details/confirmed steps) the duration and
34
- * timezone SELECTORS disappear entirely (the step summary carries both; a
35
- * disabled picker next to a form just reads as noise) and a static
36
- * duration line takes their place. Going Back restores them. */
40
+ /** Host-level exit (renders a `BackButton` at the top). Omitted no back
41
+ * affordance, which is what an embedder with its own page chrome wants. */
42
+ onBack?: () => void
43
+ /** Label for {@link onBack}. */
44
+ backLabel?: string
45
+ /** True once a slot is chosen (details/confirmed steps) — the duration
46
+ * CHIPS give way to a static duration line, since the step summary already
47
+ * states the length and a live selector beside a filled-in form invites a
48
+ * change that would throw the form away. Going Back restores them. */
37
49
  locked?: boolean
50
+ /** Whether the timezone picker renders. Default true: the zone governs every
51
+ * time on screen, the form's summary line included, so it stays reachable
52
+ * right up to the confirmation — where there is nothing left to re-read. */
53
+ showTimezone?: boolean
38
54
  className?: string
39
55
  }
40
56
 
57
+ /** Panel stack — one rhythm shared by the loaded panel and its skeleton
58
+ * (16px on phones, 24px from tablet up, via the responsive spacing token). */
59
+ const PANEL_STACK = 'flex flex-col gap-[var(--spacing-system-l)]'
60
+
61
+ /**
62
+ * Identity + duration: ONE line below `lg`, stacked from `lg` up.
63
+ *
64
+ * The panel is a 280px sidebar from `lg` and a full-width header strip below
65
+ * it — and a header strip has horizontal room the sidebar never had, so the
66
+ * duration moves up beside the host instead of taking a line of its own. Same
67
+ * two children, different axis; nothing is hidden at any width.
68
+ */
69
+ const IDENTITY_ROW =
70
+ 'flex items-center justify-between gap-[var(--spacing-system-m)] lg:flex-col lg:items-stretch lg:gap-[var(--spacing-system-l)]'
71
+
72
+ /**
73
+ * Everything under the back edge, in TWO groups: identity/title/duration, and
74
+ * the timezone field beside them.
75
+ *
76
+ * Side by side only on TABLET. There the panel is a full-width strip over the
77
+ * calendar, and it is competing with the calendar for the card's height — a
78
+ * timezone field on a line of its own costs 100px there, which is a third of
79
+ * what the month needs to render without scrolling. Sideways it costs nothing:
80
+ * the strip is ~725px wide and the identity block uses less than half of it.
81
+ *
82
+ * Below `md` (phone) and from `lg` up (280px sidebar) there is no horizontal
83
+ * room to spend, so both fall back to the plain stack.
84
+ */
85
+ const STRIP_BODY =
86
+ 'flex flex-col gap-[var(--spacing-system-l)] md:flex-row md:items-start lg:flex-col'
87
+ const STRIP_MAIN = 'flex min-w-0 flex-col gap-[var(--spacing-system-l)] md:flex-1'
88
+ /** 280px — the same width this panel has as a sidebar, so the field is the
89
+ * size the visitor meets at every other breakpoint. */
90
+ const STRIP_ASIDE = 'flex flex-col gap-[var(--spacing-system-xxs)] md:w-[280px] md:shrink-0 lg:w-full'
91
+
92
+ /** The back affordance sits flush with the panel's top padding: `BackButton`'s
93
+ * own 12px block padding would otherwise double the 24px stack gap under it. */
94
+ const BACK_BUTTON_CLASS = 'py-0'
95
+
96
+ /** Field labels ("Duration", "Timezone") — body-weight primary, 4px above
97
+ * their control, so the panel reads as a form rather than as a stack of
98
+ * section headers. */
99
+ const FIELD_LABEL_CLASS = 'text-h4 text-ods-text-primary'
100
+
41
101
  function zoneLabel(tz: string): string {
42
102
  try {
43
103
  const parts = new Intl.DateTimeFormat('en-US', {
@@ -56,27 +116,40 @@ function zoneLabel(tz: string): string {
56
116
  * STATIC labels ("Duration", "Timezone") render REAL; only data-driven
57
117
  * content (host identity, duration chips, the zone value) is skeleton.
58
118
  */
59
- export function ContextPanelSkeleton({ className }: { className?: string }) {
119
+ export function ContextPanelSkeleton({
120
+ className,
121
+ onBack,
122
+ backLabel = 'Back',
123
+ }: {
124
+ className?: string
125
+ onBack?: () => void
126
+ backLabel?: string
127
+ }) {
60
128
  return (
61
- <div className={cn('flex flex-col gap-[var(--spacing-system-mf)]', className)}>
62
- <div className="flex items-center gap-[var(--spacing-system-s)]">
63
- <Skeleton className="h-12 w-12 rounded-full shrink-0" />
64
- <div className="flex flex-1 flex-col gap-[var(--spacing-system-xxs)]">
65
- <Skeleton className="h-5 w-32" />
66
- <Skeleton className="h-4 w-24" />
129
+ <div className={cn(PANEL_STACK, className)}>
130
+ {/* The back edge renders REAL here — it needs no data, and holding the
131
+ space with a bar would just move on load. */}
132
+ {onBack && <BackButton label={backLabel} onClick={onBack} className={BACK_BUTTON_CLASS} />}
133
+ {/* Same two groups, same rows, as the loaded panel — so the header strip
134
+ keeps its height and nothing re-flows when it swaps in. */}
135
+ <div className={STRIP_BODY}>
136
+ <div className={STRIP_MAIN}>
137
+ <div className={IDENTITY_ROW}>
138
+ <div className="flex min-w-0 flex-1 items-center gap-[var(--spacing-system-xs)] lg:flex-none">
139
+ <Skeleton className="h-8 w-8 shrink-0 rounded-full" />
140
+ <div className="flex flex-1 flex-col gap-[var(--spacing-system-xxs)]">
141
+ <Skeleton className="h-5 w-32" />
142
+ <Skeleton className="h-4 w-24" />
143
+ </div>
144
+ </div>
145
+ <Skeleton className="h-4 w-20 shrink-0" />
146
+ </div>
67
147
  </div>
68
- </div>
69
- <div className="flex flex-col gap-[var(--spacing-system-xs)]">
70
- <p className="text-h5 text-ods-text-secondary">Duration</p>
71
- <div className="flex gap-[var(--spacing-system-xs)]">
72
- <Skeleton className="h-9 w-20" />
73
- <Skeleton className="h-9 w-20" />
148
+ <div className={STRIP_ASIDE}>
149
+ <p className={FIELD_LABEL_CLASS}>Timezone</p>
150
+ <Skeleton className="h-12 w-full" />
74
151
  </div>
75
152
  </div>
76
- <div className="flex flex-col gap-[var(--spacing-system-xs)]">
77
- <p className="text-h5 text-ods-text-secondary">Timezone</p>
78
- <Skeleton className="h-12 w-full" />
79
- </div>
80
153
  </div>
81
154
  )
82
155
  }
@@ -90,7 +163,10 @@ export function SchedulerContextPanel({
90
163
  onSelectDuration,
91
164
  timezone,
92
165
  onTimezoneChange,
166
+ onBack,
167
+ backLabel = 'Back',
93
168
  locked = false,
169
+ showTimezone = true,
94
170
  className,
95
171
  }: SchedulerContextPanelProps) {
96
172
  // All IANA zones with live GMT offsets — computed once, client-only (the
@@ -110,44 +186,63 @@ export function SchedulerContextPanel({
110
186
  }, [timezone])
111
187
 
112
188
  return (
113
- <div className={cn('flex flex-col gap-[var(--spacing-system-mf)]', className)}>
114
- {/* Host identity: full rows up to 3 hosts; larger teams (round-robin
115
- links can carry many members) collapse to a stacked-avatar cluster
116
- + count so the panel can never overflow. */}
117
- {hosts.length > 0 && hosts.length <= 3 && (
118
- <div className="flex flex-col gap-[var(--spacing-system-s)]">
119
- {hosts.map((host) => (
120
- <div key={host.name} className="flex items-center gap-[var(--spacing-system-s)]">
121
- <SquareAvatar variant="round" size="lg" src={host.avatarUrl ?? undefined} alt={host.name} fallback={host.name} />
122
- <div className="flex flex-col min-w-0">
123
- <p className="text-h4 text-ods-text-primary truncate">{host.name}</p>
124
- {host.title && <p className="text-h6 text-ods-text-secondary truncate">{host.title}</p>}
189
+ <div className={cn(PANEL_STACK, className)}>
190
+ {/* The card's ONE back edge, at every step the caller decides where it
191
+ goes (previous step, or out of the scheduler). It sits in this panel
192
+ because this is the half that never swaps. */}
193
+ {onBack && <BackButton label={backLabel} onClick={onBack} className={BACK_BUTTON_CLASS} />}
194
+
195
+ <div className={STRIP_BODY}>
196
+ <div className={STRIP_MAIN}>
197
+ <div className={IDENTITY_ROW}>
198
+ {/* Host identity: full rows up to 3 hosts; larger teams (round-robin
199
+ links can carry many members) collapse to a stacked-avatar cluster
200
+ + count so the panel can never overflow. */}
201
+ {hosts.length > 0 && hosts.length <= 3 && (
202
+ <div className="flex flex-col gap-[var(--spacing-system-s)]">
203
+ {hosts.map((host) => (
204
+ <div key={host.name} className="flex items-center gap-[var(--spacing-system-xs)]">
205
+ <SquareAvatar
206
+ variant="round"
207
+ size="sm"
208
+ src={host.avatarUrl ?? undefined}
209
+ alt={host.name}
210
+ fallback={host.name}
211
+ />
212
+ <div className="flex flex-col min-w-0">
213
+ <p className="text-h4 text-ods-text-primary truncate">{host.name}</p>
214
+ {host.title && <p className="text-h6 text-ods-text-secondary truncate">{host.title}</p>}
215
+ </div>
216
+ </div>
217
+ ))}
125
218
  </div>
126
- </div>
127
- ))}
128
- </div>
129
- )}
130
- {hosts.length > 3 && (
131
- <div className="flex flex-col gap-[var(--spacing-system-xs)]">
132
- <AvatarStack people={hosts} max={4} size="lg" />
133
- <p className="text-h6 text-ods-text-secondary">{hosts.length} hosts on this calendar</p>
134
- </div>
135
- )}
136
-
137
- {title && <p className="text-h3 text-ods-text-primary">{title}</p>}
138
- {description && <p className="text-h6 text-ods-text-secondary">{description}</p>}
139
-
140
- {locked ? (
141
- // Post-selection: selectors are GONE, not disabled — the step summary
142
- // owns the chosen time/zone; the panel keeps a single static line.
143
- selectedDurationMs != null && (
144
- <p className="text-h6 text-ods-text-secondary">{formatDurationCompact(selectedDurationMs / 1000)} call</p>
145
- )
146
- ) : (
147
- <>
148
- {durationsMs.length > 1 ? (
149
- <div className="flex flex-col gap-[var(--spacing-system-xs)]">
150
- <p className="text-h5 text-ods-text-secondary">Duration</p>
219
+ )}
220
+ {hosts.length > 3 && (
221
+ <div className="flex flex-col gap-[var(--spacing-system-xs)]">
222
+ <AvatarStack people={hosts} max={4} size="lg" />
223
+ <p className="text-h6 text-ods-text-secondary">{hosts.length} hosts on this calendar</p>
224
+ </div>
225
+ )}
226
+
227
+ {locked || durationsMs.length <= 1
228
+ ? // Post-selection, or a link that offers one length: a static line,
229
+ // which is what sits beside the host on the header-strip layouts.
230
+ selectedDurationMs != null && (
231
+ <p className="shrink-0 text-h6 text-ods-text-secondary">
232
+ {formatDurationCompact(selectedDurationMs / 1000)} call
233
+ </p>
234
+ )
235
+ : null}
236
+ </div>
237
+
238
+ {title && <p className="text-h3 text-ods-text-primary">{title}</p>}
239
+ {description && <p className="text-h6 text-ods-text-secondary">{description}</p>}
240
+
241
+ {/* Several lengths on offer: chips, which need a full row of their
242
+ own at every width — so they stay OUT of the identity row. */}
243
+ {!locked && durationsMs.length > 1 && (
244
+ <div className="flex flex-col gap-[var(--spacing-system-xxs)]">
245
+ <p className={FIELD_LABEL_CLASS}>Duration</p>
151
246
  <div className="flex flex-wrap gap-[var(--spacing-system-xs)]">
152
247
  {durationsMs.map((ms) => (
153
248
  <Button
@@ -161,14 +256,18 @@ export function SchedulerContextPanel({
161
256
  ))}
162
257
  </div>
163
258
  </div>
164
- ) : (
165
- selectedDurationMs != null && (
166
- <p className="text-h6 text-ods-text-secondary">{formatDurationCompact(selectedDurationMs / 1000)} call</p>
167
- )
168
259
  )}
260
+ </div>
169
261
 
170
- <div className="flex flex-col gap-[var(--spacing-system-xs)]">
171
- <p className="text-h5 text-ods-text-secondary">Timezone</p>
262
+ {showTimezone && (
263
+ // Once a slot is chosen the picker is desktop-only, which is what all
264
+ // three mocks draw. On the narrow layouts this panel is a strip
265
+ // COMPETING with the form for the screen's height, and the step's own
266
+ // summary line states the zone one row below — so the fact survives,
267
+ // only the control steps aside. The 280px sidebar has the room, so
268
+ // there it stays.
269
+ <div className={cn(STRIP_ASIDE, locked && 'max-lg:hidden')}>
270
+ <p className={FIELD_LABEL_CLASS}>Timezone</p>
172
271
  {timezone ? (
173
272
  <Autocomplete
174
273
  value={timezone}
@@ -177,7 +276,6 @@ export function SchedulerContextPanel({
177
276
  }}
178
277
  options={zoneOptions}
179
278
  placeholder="Search timezone…"
180
- startAdornment={<ClockIcon className="size-4 shrink-0 text-ods-text-secondary" />}
181
279
  noOptionsText="No matching timezone"
182
280
  showClearAll={false}
183
281
  />
@@ -185,8 +283,8 @@ export function SchedulerContextPanel({
185
283
  <Skeleton className="h-12 w-full" />
186
284
  )}
187
285
  </div>
188
- </>
189
- )}
286
+ )}
287
+ </div>
190
288
  </div>
191
289
  )
192
290
  }