@flamingo-stack/openframe-frontend-core 0.0.515 → 0.0.517-1651.4264.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 (100) hide show
  1. package/dist/{chunk-7QYBXWYI.cjs → chunk-2LMIHPQH.cjs} +4 -4
  2. package/dist/{chunk-7QYBXWYI.cjs.map → chunk-2LMIHPQH.cjs.map} +1 -1
  3. package/dist/{chunk-U7RF7YXN.js → chunk-45TSVW73.js} +2 -2
  4. package/dist/{chunk-OUPSNYRI.cjs → chunk-476N4NBC.cjs} +26 -26
  5. package/dist/{chunk-OUPSNYRI.cjs.map → chunk-476N4NBC.cjs.map} +1 -1
  6. package/dist/{chunk-WQ4QJWEE.js → chunk-A5WEANTA.js} +2 -2
  7. package/dist/{chunk-UEIW2ISS.cjs → chunk-ARB3MVHH.cjs} +66 -7
  8. package/dist/chunk-ARB3MVHH.cjs.map +1 -0
  9. package/dist/{chunk-MHW4WBMB.js → chunk-CJ55TI4A.js} +4 -4
  10. package/dist/{chunk-5YX7F5G6.cjs → chunk-CV4IMEEH.cjs} +37 -37
  11. package/dist/{chunk-5YX7F5G6.cjs.map → chunk-CV4IMEEH.cjs.map} +1 -1
  12. package/dist/{chunk-7PGYALRK.cjs → chunk-GWS2J3VC.cjs} +31 -31
  13. package/dist/{chunk-7PGYALRK.cjs.map → chunk-GWS2J3VC.cjs.map} +1 -1
  14. package/dist/{chunk-J4AL3YOT.js → chunk-GXSKS5RO.js} +2 -2
  15. package/dist/{chunk-AW66QABG.cjs → chunk-HNWJSFEV.cjs} +3 -3
  16. package/dist/{chunk-AW66QABG.cjs.map → chunk-HNWJSFEV.cjs.map} +1 -1
  17. package/dist/{chunk-JT7FFD3R.cjs → chunk-HOPPNUGS.cjs} +5 -5
  18. package/dist/{chunk-JT7FFD3R.cjs.map → chunk-HOPPNUGS.cjs.map} +1 -1
  19. package/dist/{chunk-S7YXEFDC.js → chunk-I75L6RSN.js} +2 -2
  20. package/dist/{chunk-YY4FGIRU.cjs → chunk-J4C4IY2F.cjs} +9 -9
  21. package/dist/{chunk-YY4FGIRU.cjs.map → chunk-J4C4IY2F.cjs.map} +1 -1
  22. package/dist/{chunk-5WKVLIP3.js → chunk-J6TFTLXF.js} +2 -2
  23. package/dist/{chunk-E4IB3CTA.cjs → chunk-KG52CG6Y.cjs} +14 -14
  24. package/dist/{chunk-E4IB3CTA.cjs.map → chunk-KG52CG6Y.cjs.map} +1 -1
  25. package/dist/{chunk-RK6YSRIC.cjs → chunk-N2LZPULX.cjs} +11 -11
  26. package/dist/{chunk-RK6YSRIC.cjs.map → chunk-N2LZPULX.cjs.map} +1 -1
  27. package/dist/{chunk-XAAF6UPI.js → chunk-O3CJ6IWP.js} +2 -2
  28. package/dist/{chunk-MTII5NXM.js → chunk-OCNHD5XM.js} +6 -6
  29. package/dist/{chunk-Y4LJ52RB.js → chunk-QRAGH5CS.js} +2 -2
  30. package/dist/{chunk-QHTFPOEL.cjs → chunk-R7JZC5RX.cjs} +7 -7
  31. package/dist/{chunk-QHTFPOEL.cjs.map → chunk-R7JZC5RX.cjs.map} +1 -1
  32. package/dist/{chunk-C646ZLOV.cjs → chunk-SXWPJHP7.cjs} +26 -26
  33. package/dist/{chunk-C646ZLOV.cjs.map → chunk-SXWPJHP7.cjs.map} +1 -1
  34. package/dist/{chunk-LBIWRR75.cjs → chunk-U5ZE22FV.cjs} +61 -61
  35. package/dist/{chunk-LBIWRR75.cjs.map → chunk-U5ZE22FV.cjs.map} +1 -1
  36. package/dist/{chunk-P57FCOI6.js → chunk-UL7QF323.js} +2 -2
  37. package/dist/{chunk-NVMRBO7R.js → chunk-V6FISTDB.js} +2 -2
  38. package/dist/{chunk-IA4USSHI.js → chunk-VGK6CXYG.js} +5 -5
  39. package/dist/{chunk-B6YOWMZF.cjs → chunk-VSUGMXAV.cjs} +87 -87
  40. package/dist/{chunk-B6YOWMZF.cjs.map → chunk-VSUGMXAV.cjs.map} +1 -1
  41. package/dist/{chunk-5RHFQ4JG.js → chunk-VXMKRJYN.js} +65 -6
  42. package/dist/{chunk-5RHFQ4JG.js.map → chunk-VXMKRJYN.js.map} +1 -1
  43. package/dist/{chunk-XM5DPWKV.js → chunk-YXF4JKKQ.js} +4 -4
  44. package/dist/components/case-studies/index.cjs +8 -8
  45. package/dist/components/case-studies/index.js +2 -2
  46. package/dist/components/chat/index.cjs +2 -2
  47. package/dist/components/chat/index.js +1 -1
  48. package/dist/components/contact/index.cjs +3 -3
  49. package/dist/components/contact/index.js +2 -2
  50. package/dist/components/docs/index.cjs +5 -5
  51. package/dist/components/docs/index.js +4 -4
  52. package/dist/components/embeds/index.cjs +3 -3
  53. package/dist/components/embeds/index.js +2 -2
  54. package/dist/components/faq/index.cjs +3 -3
  55. package/dist/components/faq/index.js +2 -2
  56. package/dist/components/features/floating-walkthrough-video.d.ts.map +1 -1
  57. package/dist/components/features/index.cjs +2 -2
  58. package/dist/components/features/index.js +1 -1
  59. package/dist/components/help-center-pages/index.cjs +22 -22
  60. package/dist/components/help-center-pages/index.js +13 -13
  61. package/dist/components/index.cjs +136 -132
  62. package/dist/components/index.cjs.map +1 -1
  63. package/dist/components/index.js +15 -11
  64. package/dist/components/index.js.map +1 -1
  65. package/dist/components/meeting-scheduler/index.cjs +34 -34
  66. package/dist/components/meeting-scheduler/index.js +3 -3
  67. package/dist/components/navigation/index.cjs +2 -2
  68. package/dist/components/navigation/index.js +1 -1
  69. package/dist/components/onboarding-guides/index.cjs +5 -5
  70. package/dist/components/onboarding-guides/index.js +4 -4
  71. package/dist/components/related-content/index.cjs +3 -3
  72. package/dist/components/related-content/index.js +2 -2
  73. package/dist/components/tickets/index.cjs +6 -6
  74. package/dist/components/tickets/index.js +5 -5
  75. package/dist/components/ui/index.cjs +6 -2
  76. package/dist/components/ui/index.cjs.map +1 -1
  77. package/dist/components/ui/index.js +5 -1
  78. package/dist/components/ui/test-run.d.ts +13 -0
  79. package/dist/components/ui/test-run.d.ts.map +1 -1
  80. package/dist/index.cjs +6 -2
  81. package/dist/index.cjs.map +1 -1
  82. package/dist/index.js +5 -1
  83. package/package.json +1 -1
  84. package/src/components/features/floating-walkthrough-video.tsx +42 -6
  85. package/src/components/ui/test-run.tsx +59 -6
  86. package/src/stories/TestRun.stories.tsx +20 -2
  87. package/dist/chunk-UEIW2ISS.cjs.map +0 -1
  88. /package/dist/{chunk-U7RF7YXN.js.map → chunk-45TSVW73.js.map} +0 -0
  89. /package/dist/{chunk-WQ4QJWEE.js.map → chunk-A5WEANTA.js.map} +0 -0
  90. /package/dist/{chunk-MHW4WBMB.js.map → chunk-CJ55TI4A.js.map} +0 -0
  91. /package/dist/{chunk-J4AL3YOT.js.map → chunk-GXSKS5RO.js.map} +0 -0
  92. /package/dist/{chunk-S7YXEFDC.js.map → chunk-I75L6RSN.js.map} +0 -0
  93. /package/dist/{chunk-5WKVLIP3.js.map → chunk-J6TFTLXF.js.map} +0 -0
  94. /package/dist/{chunk-XAAF6UPI.js.map → chunk-O3CJ6IWP.js.map} +0 -0
  95. /package/dist/{chunk-MTII5NXM.js.map → chunk-OCNHD5XM.js.map} +0 -0
  96. /package/dist/{chunk-Y4LJ52RB.js.map → chunk-QRAGH5CS.js.map} +0 -0
  97. /package/dist/{chunk-P57FCOI6.js.map → chunk-UL7QF323.js.map} +0 -0
  98. /package/dist/{chunk-NVMRBO7R.js.map → chunk-V6FISTDB.js.map} +0 -0
  99. /package/dist/{chunk-IA4USSHI.js.map → chunk-VGK6CXYG.js.map} +0 -0
  100. /package/dist/{chunk-XM5DPWKV.js.map → chunk-YXF4JKKQ.js.map} +0 -0
@@ -698,7 +698,22 @@ export function FloatingWalkthroughVideo({
698
698
  // receives pointer events — the media layer is pointer-events-none, so
699
699
  // a group inside it can never match :hover.
700
700
  'group/card pointer-events-auto relative overflow-hidden rounded-lg border border-ods-border bg-ods-card shadow-2xl',
701
- 'aspect-video w-60 sm:w-80 transition-opacity duration-200',
701
+ // MOBILE = PiP dimensions, not a shrunken desktop card. This is an
702
+ // UNINVITED overlay on the smallest screens, so it is sized against
703
+ // the corner-PiP conventions, NOT against system PiP (which is
704
+ // user-invoked content and is allowed ~60% of the shortest edge):
705
+ // - AOSP's legacy corner spec asks for 23% of screen width, then
706
+ // clamps up to `default_minimal_size_pip_resizable_task` (108dp in
707
+ // BOTH dimensions) — at 16:9 that floor IS 192x108, and on any
708
+ // modern phone the clamp is what actually applies. Hence the cap.
709
+ // - 192x108 lands at ~6% of a 390x844 screen, inside the "small
710
+ // player" band (<=20% of screen) rather than the "large" one.
711
+ // 52vw keeps it proportional on narrow devices (320px -> 166x93) and
712
+ // the 192px cap takes over from ~369px up, i.e. on most phones.
713
+ // Do NOT go below ~150px: the corner chrome (32px transport pair +
714
+ // 32px dismiss + the title pill) stops fitting, and the card's own
715
+ // controls — not the poster — are what set the real floor here.
716
+ 'aspect-video w-[min(52vw,192px)] sm:w-80 transition-opacity duration-200',
702
717
  footerHidden ? 'opacity-0 pointer-events-none' : 'opacity-100',
703
718
  className,
704
719
  )}
@@ -754,7 +769,9 @@ export function FloatingWalkthroughVideo({
754
769
  isClone={false}
755
770
  badge="play"
756
771
  fit="cover"
757
- posterSizes="320px"
772
+ // Mirrors the card's own responsive width above — a flat 320px hint
773
+ // made every phone fetch a poster ~1.7x wider than the box.
774
+ posterSizes="(max-width: 639px) 192px, 320px"
758
775
  preload="none"
759
776
  hideMutedBadge
760
777
  onMutedFallbackChange={onCardFallbackChange}
@@ -804,7 +821,19 @@ export function FloatingWalkthroughVideo({
804
821
  unmounts and the same click lands on the hit layer and opens the
805
822
  theater. `size="icon-glyph"` keeps the glyph at its own 56px instead
806
823
  of the DS default `[&_svg]:h-5`; `variant="glyph"` means no button
807
- surface paints behind a glyph that already carries its own scrim. */}
824
+ surface paints behind a glyph that already carries its own scrim.
825
+
826
+ BELOW `sm` IT IS NOT RENDERED — see the corner mute button's
827
+ mirrored `sm:hidden` below; the two form ONE breakpoint switch and
828
+ must always be edited together. At the mobile PiP height (~108px)
829
+ the 56px glyph physically cannot clear both the 32px transport band
830
+ and the title pill: the three need ~124px of vertical stack, which
831
+ 16:9 does not give back until the card is ~224px wide — i.e. barely
832
+ smaller than the oversized card this sizing exists to fix. Nothing
833
+ is lost, because the ONLY state that shows this glyph is resume mode
834
+ (`cardMode !== 'poster'`), and resume mode always mounts the corner
835
+ transport pair, which offers the same play AND unmute actions at
836
+ 32px — above the 24px WCAG 2.5.8 (AA) target floor. */}
808
837
  {showBigUnmute && (
809
838
  <Button
810
839
  variant="glyph"
@@ -813,7 +842,7 @@ export function FloatingWalkthroughVideo({
813
842
  title={controlIsPlay ? 'Play' : 'Unmute'}
814
843
  onPointerDown={e => e.stopPropagation()}
815
844
  onClick={onUnmuteOrPlay}
816
- className="absolute inset-0 z-30 m-auto"
845
+ className="absolute inset-0 z-30 m-auto hidden sm:inline-flex"
817
846
  >
818
847
  {controlIsPlay ? <VideoPlayBadge /> : <VideoUnmuteGlyph />}
819
848
  </Button>
@@ -827,7 +856,14 @@ export function FloatingWalkthroughVideo({
827
856
  {showPlaybackControls && (
828
857
  <div className="absolute left-[var(--spacing-system-xsf)] top-[var(--spacing-system-xsf)] z-30 flex items-center gap-[var(--spacing-system-xxs)]">
829
858
  {/* Hidden while the big centre glyph owns unmuting, so the two are
830
- never on screen with the same label and different behaviour. */}
859
+ never on screen with the same label and different behaviour —
860
+ but ONLY from `sm` up, because below `sm` that glyph is not
861
+ rendered at all (see its comment above). `sm:hidden`, not
862
+ `hidden`: a plain `hidden` here would combine with the glyph's
863
+ own `hidden sm:inline-flex` to leave the mobile card with NO
864
+ unmute affordance whatsoever — precisely the self-hiding dead
865
+ end called out in the state-model note at the top of this file.
866
+ These two classNames are ONE switch; never change one alone. */}
831
867
  <Button
832
868
  variant="overlay"
833
869
  size="icon-sm"
@@ -835,7 +871,7 @@ export function FloatingWalkthroughVideo({
835
871
  title={cardMuted ? 'Unmute' : 'Mute'}
836
872
  onPointerDown={e => e.stopPropagation()}
837
873
  onClick={onToggleMute}
838
- className={cn(showBigUnmute && !controlIsPlay && 'hidden')}
874
+ className={cn(showBigUnmute && !controlIsPlay && 'sm:hidden')}
839
875
  >
840
876
  {cardMuted ? <VolumeXmarkIcon /> : <VolumeUpIcon />}
841
877
  </Button>
@@ -2,7 +2,8 @@
2
2
 
3
3
  import type { ColumnDef, Row } from '@tanstack/react-table'
4
4
  import * as React from 'react'
5
- import { SearchIcon } from '../icons-v2-generated'
5
+ import { Copy02Icon, SearchIcon } from '../icons-v2-generated'
6
+ import { useCopyToClipboard } from '../../hooks/use-copy-to-clipboard'
6
7
  import { cn } from '../../utils/cn'
7
8
  import { formatTime } from '../../utils/format-date'
8
9
  import { DataTable } from './data-table'
@@ -10,6 +11,7 @@ import { useDataTable } from './data-table/use-data-table'
10
11
  import { NoData } from './no-data'
11
12
  import type { QueryResultRow } from './query-report-table'
12
13
  import { ScrollShadow } from './scroll-fade'
14
+ import { Tag } from './tag'
13
15
 
14
16
  /*
15
17
  * Live test-run building blocks: the UI (timing stats, one-row skeleton,
@@ -129,15 +131,37 @@ export interface TestRunResultsProps {
129
131
  className?: string
130
132
  }
131
133
 
134
+ /** Red error banner with a copy-to-clipboard action, per the test-run design. */
135
+ export function TestRunErrorBanner({ error, className }: { error: string; className?: string }) {
136
+ const { copy } = useCopyToClipboard({ successDescription: 'Error message copied to clipboard' })
137
+ return (
138
+ <div
139
+ role="alert"
140
+ className={cn(
141
+ 'flex items-center justify-between gap-[var(--spacing-system-s)]',
142
+ 'rounded-[6px] border border-ods-error bg-ods-error-secondary',
143
+ 'px-[var(--spacing-system-mf)] py-[var(--spacing-system-s)]',
144
+ className,
145
+ )}
146
+ >
147
+ <p className="text-h4 text-ods-error min-w-0 break-words">{error}</p>
148
+ <button
149
+ type="button"
150
+ aria-label="Copy error message"
151
+ onClick={() => copy(error)}
152
+ className="shrink-0 text-ods-error hover:opacity-80 transition-opacity focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ods-focus rounded-sm"
153
+ >
154
+ <Copy02Icon className="size-5" />
155
+ </button>
156
+ </div>
157
+ )
158
+ }
159
+
132
160
  /** Error line + empty state + result table for a finished/running test run. */
133
161
  export function TestRunResults({ isActive, displayRows, firstError, className }: TestRunResultsProps) {
134
162
  return (
135
163
  <div className={cn('flex flex-col gap-[var(--spacing-system-xsf)]', className)}>
136
- {firstError && (
137
- <p role="alert" className="text-h6 text-ods-error">
138
- {firstError}
139
- </p>
140
- )}
164
+ {firstError && <TestRunErrorBanner error={firstError} />}
141
165
  {!isActive && displayRows.length === 0 ? (
142
166
  // With zero rows the error line alone explains the outcome; a
143
167
  // "No results returned" empty state next to it would mislead.
@@ -164,6 +188,30 @@ export function TimingStat({ value, label, className }: { value: string; label:
164
188
  )
165
189
  }
166
190
 
191
+ /** Overall run outcome shown in a test-run controls row. */
192
+ export type TestRunStatus = 'idle' | 'running' | 'success' | 'error'
193
+
194
+ const TEST_RUN_STATUS_TAGS: Record<Exclude<TestRunStatus, 'idle'>, { label: string; variant: 'warning' | 'success' | 'error' }> = {
195
+ running: { label: 'IN PROCESS', variant: 'warning' },
196
+ success: { label: 'SUCCESS', variant: 'success' },
197
+ error: { label: 'ERROR', variant: 'error' },
198
+ }
199
+
200
+ /** Status stat cell: "-" before the first run, then a colored state tag. */
201
+ export function TestRunStatusStat({ status, className }: { status: TestRunStatus; className?: string }) {
202
+ const tag = status === 'idle' ? null : TEST_RUN_STATUS_TAGS[status]
203
+ return (
204
+ <div className={cn('flex flex-col justify-center items-start min-w-0 gap-[var(--spacing-system-xxs)]', className)}>
205
+ {tag ? (
206
+ <Tag label={tag.label} variant={tag.variant} className="pointer-events-none" />
207
+ ) : (
208
+ <span className="text-h4 text-ods-text-secondary truncate">-</span>
209
+ )}
210
+ <span className="text-h6 text-ods-text-secondary truncate">Status</span>
211
+ </div>
212
+ )
213
+ }
214
+
167
215
  /**
168
216
  * The transport-side contract `useTestRunState` observes. Matches the shape
169
217
  * of a live-campaign hook (e.g. Fleet live queries in OpenFrame) without
@@ -248,6 +296,10 @@ export function useTestRunState(campaign: TestRunCampaignState) {
248
296
 
249
297
  const firstError = isFinished && campaign.errors.length > 0 ? campaign.errors[0].error : null
250
298
 
299
+ // Overall run outcome for the Status stat: '-' until a run starts, then
300
+ // IN PROCESS while active, and SUCCESS/ERROR once the campaign finishes.
301
+ const status: TestRunStatus = isActive ? 'running' : hasRun && isFinished ? (firstError ? 'error' : 'success') : 'idle'
302
+
251
303
  // Started/Duration show zeros until the current run's timing is real:
252
304
  // hasRun=false after a reset, and campaignStatus resets while the next
253
305
  // run is being created, so stale values never linger on screen.
@@ -271,6 +323,7 @@ export function useTestRunState(campaign: TestRunCampaignState) {
271
323
  isActive,
272
324
  isFinished,
273
325
  showResults,
326
+ status,
274
327
  firstError,
275
328
  startedLabel,
276
329
  durationLabel,
@@ -1,5 +1,5 @@
1
1
  import type { Meta, StoryObj } from '@storybook/nextjs-vite';
2
- import { TestResultsSkeleton, TestRunResults, TimingStat } from '../components/ui/test-run';
2
+ import { TestResultsSkeleton, TestRunResults, TestRunStatusStat, TimingStat } from '../components/ui/test-run';
3
3
 
4
4
  const meta = {
5
5
  title: 'UI/TestRun',
@@ -85,7 +85,7 @@ export const EmptyResult: Story = {
85
85
  ),
86
86
  };
87
87
 
88
- /** Finished with an error: the alert line replaces the empty state. */
88
+ /** Finished with an error: red banner with a copy action replaces the empty state. */
89
89
  export const WithError: Story = {
90
90
  args: {
91
91
  isActive: false,
@@ -111,8 +111,26 @@ export const TimingAndSkeleton: Story = {
111
111
  <div className="flex items-center gap-[var(--spacing-system-m)]">
112
112
  <TimingStat value="02:15 PM" label="Started" className="flex-1" />
113
113
  <TimingStat value="00:00:05" label="Duration" className="flex-1" />
114
+ <TestRunStatusStat status="running" className="flex-1" />
114
115
  </div>
115
116
  <TestResultsSkeleton />
116
117
  </div>
117
118
  ),
118
119
  };
120
+
121
+ /** The four Status stat states: "-" before a run, then a colored tag. */
122
+ export const StatusStates: Story = {
123
+ args: {
124
+ isActive: false,
125
+ displayRows: [],
126
+ firstError: null,
127
+ },
128
+ render: () => (
129
+ <div className="flex max-w-[720px] items-center gap-[var(--spacing-system-l)] bg-ods-bg p-[var(--spacing-system-mf)]">
130
+ <TestRunStatusStat status="idle" />
131
+ <TestRunStatusStat status="running" />
132
+ <TestRunStatusStat status="success" />
133
+ <TestRunStatusStat status="error" />
134
+ </div>
135
+ ),
136
+ };