@flamingo-stack/openframe-frontend-core 0.0.397-snapshot.20260710044003 → 0.0.397
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-MNQVBME4.js → chunk-2OOZASQE.js} +3 -3
- package/dist/{chunk-WBLRUO3F.cjs → chunk-2U3YPWHG.cjs} +3 -3
- package/dist/{chunk-WBLRUO3F.cjs.map → chunk-2U3YPWHG.cjs.map} +1 -1
- package/dist/{chunk-MGJZQ3EE.js → chunk-3E4BDAHJ.js} +2 -2
- package/dist/{chunk-WJSUUOPU.cjs → chunk-43C5ECNT.cjs} +40 -40
- package/dist/{chunk-WJSUUOPU.cjs.map → chunk-43C5ECNT.cjs.map} +1 -1
- package/dist/{chunk-PE37UDPF.cjs → chunk-55SNCMFS.cjs} +40 -40
- package/dist/{chunk-PE37UDPF.cjs.map → chunk-55SNCMFS.cjs.map} +1 -1
- package/dist/{chunk-JWBRDNYH.cjs → chunk-743FCO64.cjs} +31 -31
- package/dist/{chunk-JWBRDNYH.cjs.map → chunk-743FCO64.cjs.map} +1 -1
- package/dist/{chunk-LBDKAMOH.cjs → chunk-BB7TZ6KS.cjs} +75 -75
- package/dist/{chunk-LBDKAMOH.cjs.map → chunk-BB7TZ6KS.cjs.map} +1 -1
- package/dist/{chunk-B2K5COVE.js → chunk-CK5UZLIN.js} +3189 -3305
- package/dist/chunk-CK5UZLIN.js.map +1 -0
- package/dist/{chunk-WXOIEYXW.js → chunk-D4F7ZLJG.js} +5 -5
- package/dist/{chunk-7Q2DZEUA.cjs → chunk-DB6CUWTL.cjs} +32 -32
- package/dist/{chunk-7Q2DZEUA.cjs.map → chunk-DB6CUWTL.cjs.map} +1 -1
- package/dist/{chunk-FZIMFLJG.cjs → chunk-HPQKX5IW.cjs} +15 -15
- package/dist/{chunk-FZIMFLJG.cjs.map → chunk-HPQKX5IW.cjs.map} +1 -1
- package/dist/{chunk-N4R7TRF3.js → chunk-IVXOR2IA.js} +2 -2
- package/dist/{chunk-URODM26E.js → chunk-J6TAK6HE.js} +6 -6
- package/dist/{chunk-VDDNJ6XC.js → chunk-JNU3YPLP.js} +5 -5
- package/dist/{chunk-2TBXIW7L.js → chunk-K5FRWPWF.js} +3 -3
- package/dist/{chunk-ZSZRJWY3.js → chunk-KQESCGVG.js} +2 -2
- package/dist/{chunk-N7YBNHVT.js → chunk-LQHMXPOJ.js} +509 -563
- package/dist/chunk-LQHMXPOJ.js.map +1 -0
- package/dist/{chunk-LPH2JDOW.cjs → chunk-LWNPMLIH.cjs} +2 -56
- package/dist/chunk-LWNPMLIH.cjs.map +1 -0
- package/dist/{chunk-YEWGFWNM.cjs → chunk-M6NPDNVB.cjs} +29 -29
- package/dist/{chunk-YEWGFWNM.cjs.map → chunk-M6NPDNVB.cjs.map} +1 -1
- package/dist/{chunk-C4DHYDXB.cjs → chunk-OAENYOKC.cjs} +12 -12
- package/dist/{chunk-C4DHYDXB.cjs.map → chunk-OAENYOKC.cjs.map} +1 -1
- package/dist/{chunk-XYT5PIKZ.cjs → chunk-SBWTMJPQ.cjs} +5 -5
- package/dist/{chunk-XYT5PIKZ.cjs.map → chunk-SBWTMJPQ.cjs.map} +1 -1
- package/dist/{chunk-2DGA5LHT.cjs → chunk-T5F54MN4.cjs} +19 -19
- package/dist/{chunk-2DGA5LHT.cjs.map → chunk-T5F54MN4.cjs.map} +1 -1
- package/dist/{chunk-Y3FREU4B.js → chunk-TTHTO5H5.js} +7 -7
- package/dist/{chunk-MQXUIYYN.js → chunk-VTR4QDL2.js} +4 -4
- package/dist/{chunk-GKRJOS6S.js → chunk-WCDM226S.js} +3 -3
- package/dist/{chunk-4KWIQEZT.js → chunk-WWHPTP4B.js} +2 -2
- package/dist/{chunk-4BVRLCCA.cjs → chunk-XNAYZHPK.cjs} +73 -73
- package/dist/{chunk-4BVRLCCA.cjs.map → chunk-XNAYZHPK.cjs.map} +1 -1
- package/dist/{chunk-HMTOQGYU.cjs → chunk-Z6PAD2UN.cjs} +7 -7
- package/dist/{chunk-HMTOQGYU.cjs.map → chunk-Z6PAD2UN.cjs.map} +1 -1
- package/dist/{chunk-G42EXM23.js → chunk-ZFK4HHRX.js} +2 -2
- package/dist/{chunk-VVO7AZKZ.cjs → chunk-ZUKBC2ZX.cjs} +190 -306
- package/dist/chunk-ZUKBC2ZX.cjs.map +1 -0
- package/dist/components/case-studies/index.cjs +14 -14
- package/dist/components/case-studies/index.js +4 -4
- package/dist/components/chat/index.cjs +4 -4
- package/dist/components/chat/index.js +3 -3
- package/dist/components/contact/index.cjs +5 -5
- package/dist/components/contact/index.js +4 -4
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +5 -5
- package/dist/components/embeds/index.js +4 -4
- package/dist/components/faq/index.cjs +6 -6
- package/dist/components/faq/index.js +5 -5
- package/dist/components/features/index.cjs +4 -8
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.d.ts +0 -1
- package/dist/components/features/index.d.ts.map +1 -1
- package/dist/components/features/index.js +3 -7
- package/dist/components/features/video-bites-strip.d.ts.map +1 -1
- package/dist/components/features/video.d.ts +4 -7
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/icons/index.cjs +2 -6
- package/dist/components/icons/index.cjs.map +1 -1
- package/dist/components/icons/index.d.ts +0 -2
- package/dist/components/icons/index.d.ts.map +1 -1
- package/dist/components/icons/index.js +1 -5
- package/dist/components/index.cjs +142 -152
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +12 -22
- package/dist/components/index.js.map +1 -1
- package/dist/components/media-carousel.d.ts.map +1 -1
- package/dist/components/navigation/index.cjs +4 -4
- package/dist/components/navigation/index.js +3 -3
- package/dist/components/onboarding-guides/index.cjs +8 -8
- package/dist/components/onboarding-guides/index.js +7 -7
- package/dist/components/related-content/index.cjs +6 -6
- package/dist/components/related-content/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +4 -6
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.d.ts +0 -1
- package/dist/components/ui/index.d.ts.map +1 -1
- package/dist/components/ui/index.js +3 -5
- package/dist/components/ui/tab-navigation.d.ts +2 -6
- package/dist/components/ui/tab-navigation.d.ts.map +1 -1
- package/dist/hooks/index.cjs +3 -3
- package/dist/hooks/index.js +2 -2
- package/dist/index.cjs +4 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -13
- package/package.json +4 -4
- package/src/components/features/index.ts +0 -1
- package/src/components/features/video-bites-strip.tsx +15 -73
- package/src/components/features/video.tsx +28 -51
- package/src/components/icons/index.ts +0 -2
- package/src/components/media-carousel.tsx +4 -2
- package/src/components/ui/index.ts +0 -1
- package/src/components/ui/tab-navigation.tsx +14 -31
- package/src/styles/app-globals.css +0 -23
- package/dist/chunk-B2K5COVE.js.map +0 -1
- package/dist/chunk-LPH2JDOW.cjs.map +0 -1
- package/dist/chunk-N7YBNHVT.js.map +0 -1
- package/dist/chunk-VVO7AZKZ.cjs.map +0 -1
- package/dist/components/features/video-center-badge.d.ts +0 -40
- package/dist/components/features/video-center-badge.d.ts.map +0 -1
- package/dist/components/icons/play-filled-icon.d.ts +0 -15
- package/dist/components/icons/play-filled-icon.d.ts.map +0 -1
- package/dist/components/icons/volume-off-filled-icon.d.ts +0 -14
- package/dist/components/icons/volume-off-filled-icon.d.ts.map +0 -1
- package/dist/components/ui/crossfade-panels.d.ts +0 -52
- package/dist/components/ui/crossfade-panels.d.ts.map +0 -1
- package/src/components/features/video-center-badge.tsx +0 -77
- package/src/components/icons/play-filled-icon.tsx +0 -36
- package/src/components/icons/volume-off-filled-icon.tsx +0 -38
- package/src/components/ui/crossfade-panels.tsx +0 -88
- /package/dist/{chunk-MNQVBME4.js.map → chunk-2OOZASQE.js.map} +0 -0
- /package/dist/{chunk-MGJZQ3EE.js.map → chunk-3E4BDAHJ.js.map} +0 -0
- /package/dist/{chunk-WXOIEYXW.js.map → chunk-D4F7ZLJG.js.map} +0 -0
- /package/dist/{chunk-N4R7TRF3.js.map → chunk-IVXOR2IA.js.map} +0 -0
- /package/dist/{chunk-URODM26E.js.map → chunk-J6TAK6HE.js.map} +0 -0
- /package/dist/{chunk-VDDNJ6XC.js.map → chunk-JNU3YPLP.js.map} +0 -0
- /package/dist/{chunk-2TBXIW7L.js.map → chunk-K5FRWPWF.js.map} +0 -0
- /package/dist/{chunk-ZSZRJWY3.js.map → chunk-KQESCGVG.js.map} +0 -0
- /package/dist/{chunk-Y3FREU4B.js.map → chunk-TTHTO5H5.js.map} +0 -0
- /package/dist/{chunk-MQXUIYYN.js.map → chunk-VTR4QDL2.js.map} +0 -0
- /package/dist/{chunk-GKRJOS6S.js.map → chunk-WCDM226S.js.map} +0 -0
- /package/dist/{chunk-4KWIQEZT.js.map → chunk-WWHPTP4B.js.map} +0 -0
- /package/dist/{chunk-G42EXM23.js.map → chunk-ZFK4HHRX.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flamingo-stack/openframe-frontend-core",
|
|
3
|
-
"version": "0.0.397
|
|
3
|
+
"version": "0.0.397",
|
|
4
4
|
"description": "Shared design system and components for all Flamingo platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -257,9 +257,9 @@
|
|
|
257
257
|
"test:ui": "vitest --ui",
|
|
258
258
|
"test:run": "vitest run",
|
|
259
259
|
"test:coverage": "vitest run --coverage",
|
|
260
|
-
"yalc:watch": "NODE_OPTIONS='--max-old-space-size=4096' nodemon --watch src --watch tsconfig.json --watch tsup.config.ts --watch tailwind.config.ts --watch package.json -e ts,tsx,json,css --ignore dist --ignore node_modules --ignore '*.lock' --ignore .yalc --ignore yalc.lock --exec 'rm -rf dist && tsup && tsc -p tsconfig.declarations.json && yalc push --changed
|
|
261
|
-
"yalc:push": "yalc push
|
|
262
|
-
"yalc:publish": "yalc publish
|
|
260
|
+
"yalc:watch": "NODE_OPTIONS='--max-old-space-size=4096' nodemon --watch src --watch tsconfig.json --watch tsup.config.ts --watch tailwind.config.ts --watch package.json -e ts,tsx,json,css --ignore dist --ignore node_modules --ignore '*.lock' --ignore .yalc --ignore yalc.lock --exec 'rm -rf dist && tsup && tsc -p tsconfig.declarations.json && yalc push --changed'",
|
|
261
|
+
"yalc:push": "yalc push",
|
|
262
|
+
"yalc:publish": "yalc publish",
|
|
263
263
|
"generate:icons": "node scripts/generate-icons.mjs",
|
|
264
264
|
"storybook": "storybook dev -p 6006",
|
|
265
265
|
"build-storybook": "storybook build"
|
|
@@ -60,7 +60,6 @@ export * from './warning-block'
|
|
|
60
60
|
// `video-bites-shared` is the SERVER-SAFE leaf (also its own published subpath).
|
|
61
61
|
export * from './cards-strip'
|
|
62
62
|
export * from './video'
|
|
63
|
-
export * from './video-center-badge'
|
|
64
63
|
export * from './video-ratio-tabs'
|
|
65
64
|
export * from './video-bites-shared'
|
|
66
65
|
export * from './video-bites-strip'
|
|
@@ -36,12 +36,9 @@ import {
|
|
|
36
36
|
sortBitesByCreatedAtDesc,
|
|
37
37
|
type VideoBiteStripProfile,
|
|
38
38
|
} from './video-bites-shared';
|
|
39
|
-
import Image from '../../embed-shims/next-image';
|
|
40
39
|
import { CardsStrip, STRIP_CELL_MAX_WIDTH } from './cards-strip';
|
|
41
|
-
import { useCoverImageFallback } from '../chat/entity-cards/use-cover-image-fallback';
|
|
42
40
|
import { UserDisplay } from '../user-display';
|
|
43
41
|
import { Chevron02RightIcon } from '../icons-v2-generated/arrows/chevron-02-right-icon';
|
|
44
|
-
import { VideoPlayBadge } from './video-center-badge';
|
|
45
42
|
|
|
46
43
|
// NOTE: the title constant / profile adapter / sort comparator live in the
|
|
47
44
|
// server-safe leaf `video-bites-shared.ts` (its own published subpath). The
|
|
@@ -255,23 +252,7 @@ export function VideoBiteCard({
|
|
|
255
252
|
io.observe(el);
|
|
256
253
|
return () => io.disconnect();
|
|
257
254
|
}, [gateControlled]);
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
// Player mounts at NEAR-VIEWPORT (not on first hover) so hover playback is
|
|
261
|
-
// INSTANT for any card the user can reach — mounting on activation made
|
|
262
|
-
// freshly-scrolled cards stall for seconds while MuxPlayer initialized and
|
|
263
|
-
// buffered. Always-mounted players are visually safe now that every bite
|
|
264
|
-
// carries a poster: the player renders with a transparent media background
|
|
265
|
-
// over the card's poster layer, so a not-yet-decoded player never shows as
|
|
266
|
-
// a black rectangle (the original wrap-seam flash came from POSTERLESS
|
|
267
|
-
// mounted players, not from mounting itself).
|
|
268
|
-
const showPlayer = showMedia;
|
|
269
|
-
|
|
270
|
-
// Poster resolution — THE shared entity-card cover fallback chain
|
|
271
|
-
// (use-cover-image-fallback): the bite's real thumbnail_url, dropped on
|
|
272
|
-
// load error. When it resolves to null (no thumbnail yet, or it failed)
|
|
273
|
-
// the media zone falls back to the first-frame <video> facade below.
|
|
274
|
-
const { src: posterSrc, onError: onPosterError } = useCoverImageFallback(bite.thumbnail_url);
|
|
255
|
+
const showPlayer = gateControlled ? playerMounted : isNear;
|
|
275
256
|
|
|
276
257
|
const navigate = () => {
|
|
277
258
|
if (bite.onNavigate) bite.onNavigate();
|
|
@@ -361,65 +342,26 @@ export function VideoBiteCard({
|
|
|
361
342
|
// same as the old single-box layout). Editor mode: width-driven.
|
|
362
343
|
style={{ aspectRatio: cssAspect, ...(height !== undefined ? { height, maxWidth: STRIP_CELL_MAX_WIDTH } : {}) }}
|
|
363
344
|
>
|
|
364
|
-
{
|
|
345
|
+
{showPlayer ? (
|
|
365
346
|
// Clones: `inert` (not just the wrapper's aria-hidden) — the player's
|
|
366
347
|
// shadow-DOM center control is otherwise still tab-reachable inside a
|
|
367
348
|
// hidden subtree (axe aria-hidden-focus). Hover preview on clones
|
|
368
349
|
// keeps working: playback is driven imperatively from the CARD's own
|
|
369
350
|
// pointer handlers, never from focus/clicks on the player chrome.
|
|
370
351
|
<div className="absolute inset-0" inert={isClone || undefined}>
|
|
371
|
-
{/*
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
unoptimized
|
|
385
|
-
onError={onPosterError}
|
|
386
|
-
className="object-cover"
|
|
387
|
-
/>
|
|
388
|
-
) : (
|
|
389
|
-
<Video kind="file" url={bite.url} firstFrameOnly layout="fill" />
|
|
390
|
-
)}
|
|
391
|
-
|
|
392
|
-
{/* Play affordance for every non-playing state (resting poster,
|
|
393
|
-
facade, mounted-but-paused): <VideoPlayBadge> — a pixel replica
|
|
394
|
-
of MuxPlayer's pre-play center button (media-chrome's own PAUSED
|
|
395
|
-
center control is a different, smaller bare glyph, so the
|
|
396
|
-
mounted player runs chromeless and the replica keeps all states
|
|
397
|
-
identical to the mux button). Hidden while playing. */}
|
|
398
|
-
{!isActive && <VideoPlayBadge className="absolute inset-0 z-10 m-auto" />}
|
|
399
|
-
{showPlayer && (
|
|
400
|
-
// `--media-background-color: transparent` (inherited into
|
|
401
|
-
// media-chrome) — the freshly-mounted player must not blank the
|
|
402
|
-
// facade behind it with its default black fill while it loads.
|
|
403
|
-
<div
|
|
404
|
-
className="absolute inset-0"
|
|
405
|
-
style={{ '--media-background-color': 'transparent' } as React.CSSProperties}
|
|
406
|
-
>
|
|
407
|
-
{/* CONTROLLED hover playback keyed to CARD hover (`isActive`): the
|
|
408
|
-
detail overlay is part of the card, so moving the pointer onto
|
|
409
|
-
it keeps playing. Sound at 50% (pre-activation: muted start +
|
|
410
|
-
live unmute on the user's first gesture); CHROMELESS — the
|
|
411
|
-
card's mux-replica badge above is the play affordance for
|
|
412
|
-
every non-playing state. */}
|
|
413
|
-
<Video
|
|
414
|
-
kind="file"
|
|
415
|
-
url={bite.url}
|
|
416
|
-
poster={bite.thumbnail_url}
|
|
417
|
-
playWhenHovered={isActive}
|
|
418
|
-
chromeless
|
|
419
|
-
layout="fill"
|
|
420
|
-
/>
|
|
421
|
-
</div>
|
|
422
|
-
)}
|
|
352
|
+
{/* CONTROLLED hover playback keyed to CARD hover (`isActive`): the
|
|
353
|
+
detail overlay is part of the card, so moving the pointer onto
|
|
354
|
+
it keeps playing. Sound at 50% (pre-activation: muted start +
|
|
355
|
+
live unmute on the user's first gesture); chrome = center
|
|
356
|
+
play/pause only (Figma card look). */}
|
|
357
|
+
<Video
|
|
358
|
+
kind="file"
|
|
359
|
+
url={bite.url}
|
|
360
|
+
poster={bite.thumbnail_url}
|
|
361
|
+
playWhenHovered={isActive}
|
|
362
|
+
centerControlsOnly
|
|
363
|
+
layout="fill"
|
|
364
|
+
/>
|
|
423
365
|
</div>
|
|
424
366
|
) : (
|
|
425
367
|
// Aspect-matched placeholder until the card nears the viewport (CLS-free).
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
|
|
33
33
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
34
34
|
import MuxPlayer from '@mux/mux-player-react';
|
|
35
|
-
import {
|
|
35
|
+
import { PlayIcon } from '../icons-v2-generated/media-playback/play-icon';
|
|
36
|
+
import { VolumeXmarkIcon } from '../icons-v2-generated/audio-and-visual/volume-xmark-icon';
|
|
36
37
|
import { fetchPriorityProp } from '../../utils/fetch-priority';
|
|
37
38
|
|
|
38
39
|
// =============================================================================
|
|
@@ -249,12 +250,9 @@ interface VideoFileProps extends VideoCommonProps {
|
|
|
249
250
|
* true → start hover playback, false → pause. When provided, the internal
|
|
250
251
|
* pointer handlers are disabled. */
|
|
251
252
|
playWhenHovered?: boolean;
|
|
252
|
-
/**
|
|
253
|
-
*
|
|
254
|
-
|
|
255
|
-
* MuxPlayer cost — the resting facade layer under strip cards when no
|
|
256
|
-
* poster asset exists. All player props are ignored. */
|
|
257
|
-
firstFrameOnly?: boolean;
|
|
253
|
+
/** Hide the bottom control bar, keep only the CENTER play/pause control
|
|
254
|
+
* (bite-strip cards per Figma). */
|
|
255
|
+
centerControlsOnly?: boolean;
|
|
258
256
|
}
|
|
259
257
|
|
|
260
258
|
interface VideoYouTubeProps extends VideoCommonProps {
|
|
@@ -274,7 +272,7 @@ interface VideoAutoProps extends VideoCommonProps {
|
|
|
274
272
|
chromeless?: boolean;
|
|
275
273
|
playOnHover?: boolean;
|
|
276
274
|
playWhenHovered?: boolean;
|
|
277
|
-
|
|
275
|
+
centerControlsOnly?: boolean;
|
|
278
276
|
}
|
|
279
277
|
|
|
280
278
|
export type VideoProps = VideoFileProps | VideoYouTubeProps | VideoAutoProps;
|
|
@@ -299,8 +297,6 @@ export function Video(props: VideoProps): React.ReactElement | null {
|
|
|
299
297
|
className={props.className}
|
|
300
298
|
minimalControls={props.minimalControls}
|
|
301
299
|
/>
|
|
302
|
-
) : 'firstFrameOnly' in props && props.firstFrameOnly ? (
|
|
303
|
-
<FirstFramePreview url={url} className={props.className} />
|
|
304
300
|
) : (
|
|
305
301
|
<FilePlayer
|
|
306
302
|
url={url}
|
|
@@ -313,6 +309,7 @@ export function Video(props: VideoProps): React.ReactElement | null {
|
|
|
313
309
|
chromeless={'chromeless' in props ? props.chromeless : undefined}
|
|
314
310
|
playOnHover={'playOnHover' in props ? props.playOnHover : undefined}
|
|
315
311
|
playWhenHovered={'playWhenHovered' in props ? props.playWhenHovered : undefined}
|
|
312
|
+
centerControlsOnly={'centerControlsOnly' in props ? props.centerControlsOnly : undefined}
|
|
316
313
|
className={props.className}
|
|
317
314
|
/>
|
|
318
315
|
);
|
|
@@ -376,35 +373,6 @@ function wrapWithLayout(
|
|
|
376
373
|
}
|
|
377
374
|
}
|
|
378
375
|
|
|
379
|
-
// -----------------------------------------------------------------------------
|
|
380
|
-
// First-frame preview — the `firstFrameOnly` facade branch
|
|
381
|
-
// -----------------------------------------------------------------------------
|
|
382
|
-
|
|
383
|
-
/** First-frame paint through the SAME FilePlayer/MuxPlayer pipeline as
|
|
384
|
-
* playback (one rendering path for every file source — HLS included, which a
|
|
385
|
-
* plain `<video>` couldn't decode in Chromium): a chromeless, muted,
|
|
386
|
-
* metadata-only instance whose `#t=0.1` media fragment seeks-and-paints the
|
|
387
|
-
* first frame (also fixes iOS Safari, which stays blank on a fragmentless
|
|
388
|
-
* metadata load). The transparent media background keeps the box showing the
|
|
389
|
-
* card surface (never black) until the frame decodes. */
|
|
390
|
-
function FirstFramePreview({ url, className }: { url: string; className?: string }): React.ReactElement {
|
|
391
|
-
return (
|
|
392
|
-
<div
|
|
393
|
-
aria-hidden
|
|
394
|
-
className="h-full w-full"
|
|
395
|
-
style={{ '--media-background-color': 'transparent' } as React.CSSProperties}
|
|
396
|
-
>
|
|
397
|
-
<FilePlayer
|
|
398
|
-
url={`${url}#t=0.1`}
|
|
399
|
-
preload="metadata"
|
|
400
|
-
muted
|
|
401
|
-
chromeless
|
|
402
|
-
className={className}
|
|
403
|
-
/>
|
|
404
|
-
</div>
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
376
|
// -----------------------------------------------------------------------------
|
|
409
377
|
// File branch — MuxPlayer (handles both .m3u8 HLS and plain .mp4)
|
|
410
378
|
// -----------------------------------------------------------------------------
|
|
@@ -420,8 +388,7 @@ interface FilePlayerProps {
|
|
|
420
388
|
chromeless?: boolean;
|
|
421
389
|
playOnHover?: boolean;
|
|
422
390
|
playWhenHovered?: boolean;
|
|
423
|
-
|
|
424
|
-
preload?: 'none' | 'metadata' | 'auto';
|
|
391
|
+
centerControlsOnly?: boolean;
|
|
425
392
|
className?: string;
|
|
426
393
|
}
|
|
427
394
|
|
|
@@ -436,9 +403,12 @@ function FilePlayer({
|
|
|
436
403
|
chromeless,
|
|
437
404
|
playOnHover,
|
|
438
405
|
playWhenHovered,
|
|
439
|
-
|
|
406
|
+
centerControlsOnly,
|
|
440
407
|
className,
|
|
441
408
|
}: FilePlayerProps): React.ReactElement {
|
|
409
|
+
// centerControlsOnly: the center play button shows at REST only — while
|
|
410
|
+
// playing, ALL chrome hides (no pause sign over the hover-preview).
|
|
411
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
442
412
|
// True while hover playback is running MUTED because the browser's autoplay
|
|
443
413
|
// policy blocked sound (no user activation yet). Drives the center unmute
|
|
444
414
|
// control — the industry pattern (muted autoplay + explicit unmute button)
|
|
@@ -576,10 +546,11 @@ function FilePlayer({
|
|
|
576
546
|
const player = (
|
|
577
547
|
<MuxPlayer
|
|
578
548
|
ref={hoverPlayerRef as React.Ref<never>}
|
|
549
|
+
onPlay={centerControlsOnly ? () => setIsPlaying(true) : undefined}
|
|
550
|
+
onPause={centerControlsOnly ? () => setIsPlaying(false) : undefined}
|
|
579
551
|
src={url}
|
|
580
552
|
poster={poster || undefined}
|
|
581
553
|
streamType="on-demand"
|
|
582
|
-
preload={preload}
|
|
583
554
|
playsInline
|
|
584
555
|
muted={muted}
|
|
585
556
|
preferCmcd="header"
|
|
@@ -610,6 +581,15 @@ function FilePlayer({
|
|
|
610
581
|
width: '100%',
|
|
611
582
|
height: '100%',
|
|
612
583
|
...(chromeless ? ({ '--controls': 'none' } as React.CSSProperties) : {}),
|
|
584
|
+
...(centerControlsOnly && !chromeless
|
|
585
|
+
? ({
|
|
586
|
+
'--bottom-controls': 'none',
|
|
587
|
+
'--top-controls': 'none',
|
|
588
|
+
// While playing, hide the center control too — no pause sign
|
|
589
|
+
// over the hover-preview; it reappears when playback pauses.
|
|
590
|
+
...(isPlaying ? { '--center-controls': 'none' } : {}),
|
|
591
|
+
} as React.CSSProperties)
|
|
592
|
+
: {}),
|
|
613
593
|
}}
|
|
614
594
|
>
|
|
615
595
|
{captionsUrl ? (
|
|
@@ -636,11 +616,9 @@ function FilePlayer({
|
|
|
636
616
|
aria-label="Unmute"
|
|
637
617
|
title="Unmute"
|
|
638
618
|
onClick={unmuteNow}
|
|
639
|
-
|
|
640
|
-
// hover language as every mux control icon (see app-globals.css).
|
|
641
|
-
className="absolute inset-0 z-10 m-auto flex h-14 w-14 items-center justify-center text-ods-text-primary transition-colors hover:text-ods-accent"
|
|
619
|
+
className="absolute inset-0 z-10 m-auto flex h-14 w-14 items-center justify-center text-ods-text-primary transition-opacity hover:opacity-75"
|
|
642
620
|
>
|
|
643
|
-
<
|
|
621
|
+
<VolumeXmarkIcon size={56} />
|
|
644
622
|
</button>
|
|
645
623
|
) : null;
|
|
646
624
|
|
|
@@ -904,10 +882,9 @@ function YouTubeFacadeInner({
|
|
|
904
882
|
/>
|
|
905
883
|
</picture>
|
|
906
884
|
<div className="absolute inset-0 flex items-center justify-center bg-ods-bg-inverse bg-opacity-20 transition-opacity duration-200 group-hover:bg-opacity-30">
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
<VideoPlayBadge size="lg" className="transition-transform duration-200 group-hover:scale-110 group-hover:text-ods-accent" />
|
|
885
|
+
<span className="flex items-center justify-center w-16 h-16 rounded-full bg-ods-accent text-ods-text-on-accent shadow-lg transition-transform duration-200 group-hover:scale-110">
|
|
886
|
+
<PlayIcon size={24} color="currentColor" className="ml-1" />
|
|
887
|
+
</span>
|
|
911
888
|
</div>
|
|
912
889
|
</button>
|
|
913
890
|
</div>
|
|
@@ -35,8 +35,6 @@ export { VendorDirectoryIcon } from './vendor-directory-icon';
|
|
|
35
35
|
export { OpenSourceIcon } from './open-source-icon';
|
|
36
36
|
export { CommunityHubIcon } from './community-hub-icon';
|
|
37
37
|
export { AboutIcon } from './about-icon';
|
|
38
|
-
export { PlayFilledIcon } from './play-filled-icon';
|
|
39
|
-
export { VolumeOffFilledIcon } from './volume-off-filled-icon';
|
|
40
38
|
export { UserIcon } from './user-icon';
|
|
41
39
|
export { HamburgerIcon } from './hamburger-icon';
|
|
42
40
|
export { MenuIcon } from './menu-icon';
|
|
@@ -4,7 +4,7 @@ import { useState, useRef, useEffect, memo, useCallback } from 'react';
|
|
|
4
4
|
import { cn } from "../utils/cn";
|
|
5
5
|
import { MediaItem } from '../utils/media-carousel-utils-stub';
|
|
6
6
|
import { Video, extractYouTubeId } from './features/video';
|
|
7
|
-
import {
|
|
7
|
+
import { PlayIcon } from './icons-v2-generated/media-playback/play-icon';
|
|
8
8
|
|
|
9
9
|
// Navigation icons
|
|
10
10
|
const ChevronLeftIcon = () => (
|
|
@@ -215,7 +215,9 @@ export const MediaCarousel = memo(function MediaCarousel({
|
|
|
215
215
|
{/* Play icon overlay for videos */}
|
|
216
216
|
{(item.type === 'video' || item.type === 'youtube') && (
|
|
217
217
|
<div className="absolute inset-0 flex items-center justify-center bg-black/30">
|
|
218
|
-
<
|
|
218
|
+
<div className="bg-black/70 rounded-full p-1">
|
|
219
|
+
<PlayIcon size={12} color="white" />
|
|
220
|
+
</div>
|
|
219
221
|
</div>
|
|
220
222
|
)}
|
|
221
223
|
|
|
@@ -47,7 +47,6 @@ export * from './breadcrumb'
|
|
|
47
47
|
export * from './dropdown-menu'
|
|
48
48
|
export * from './menubar'
|
|
49
49
|
export * from './navigation-menu'
|
|
50
|
-
export * from './crossfade-panels'
|
|
51
50
|
export * from './tab-content'
|
|
52
51
|
export * from './tab-navigation'
|
|
53
52
|
// Feedback components
|
|
@@ -7,8 +7,7 @@ import { cn } from '../../utils/cn'
|
|
|
7
7
|
export interface TabItem {
|
|
8
8
|
id: string
|
|
9
9
|
label: string
|
|
10
|
-
|
|
11
|
-
icon?: React.ComponentType<React.SVGProps<SVGSVGElement>>
|
|
10
|
+
icon: React.ComponentType<React.SVGProps<SVGSVGElement>>
|
|
12
11
|
component?: React.ComponentType<any>
|
|
13
12
|
indicator?: 'success' | 'warning' | 'error'
|
|
14
13
|
}
|
|
@@ -30,9 +29,6 @@ interface TabNavigationProps {
|
|
|
30
29
|
showRightGradient?: boolean
|
|
31
30
|
/** Force the left-edge gradient to always render, independent of scroll state. */
|
|
32
31
|
showLeftGradient?: boolean
|
|
33
|
-
/** Tabs grow to share the bar's full width equally (Figma segmented-underline
|
|
34
|
-
* look, e.g. the 480px homepage strip switcher). Default: natural width. */
|
|
35
|
-
stretchTabs?: boolean
|
|
36
32
|
|
|
37
33
|
// URL sync mode
|
|
38
34
|
urlSync?: boolean | TabNavigationUrlSyncOptions
|
|
@@ -50,7 +46,6 @@ export function TabNavigation({
|
|
|
50
46
|
shadowClassName,
|
|
51
47
|
showRightGradient = false,
|
|
52
48
|
showLeftGradient = false,
|
|
53
|
-
stretchTabs = false,
|
|
54
49
|
urlSync = false,
|
|
55
50
|
defaultTab,
|
|
56
51
|
children
|
|
@@ -227,36 +222,24 @@ export function TabNavigation({
|
|
|
227
222
|
className={cn(
|
|
228
223
|
"flex gap-[var(--spacing-system-xxs)] items-center justify-center p-[var(--spacing-system-m)] relative shrink-0 cursor-pointer",
|
|
229
224
|
"transition-all duration-200 bg-transparent border-none outline-none",
|
|
230
|
-
stretchTabs && 'flex-1',
|
|
231
225
|
isActive
|
|
232
226
|
? 'bg-gradient-to-b from-[rgba(255,255,255,0)] to-[rgba(255,255,255,0.1)]'
|
|
233
227
|
: 'hover:bg-gradient-to-b hover:from-[rgba(255,255,255,0)] hover:to-[rgba(255,255,255,0.1)]'
|
|
234
228
|
)}
|
|
235
229
|
>
|
|
236
|
-
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
{
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
</div>
|
|
250
|
-
) : tab.indicator ? (
|
|
251
|
-
// Text-only tabs keep their status badge — inline dot before
|
|
252
|
-
// the label (there's no icon corner to anchor to).
|
|
253
|
-
<div className={cn(
|
|
254
|
-
"w-3 h-3 shrink-0 rounded-full border-2 border-ods-bg",
|
|
255
|
-
tab.indicator === 'error' && 'bg-ods-error',
|
|
256
|
-
tab.indicator === 'warning' && 'bg-ods-accent',
|
|
257
|
-
tab.indicator === 'success' && 'bg-green-500'
|
|
258
|
-
)} />
|
|
259
|
-
) : null}
|
|
230
|
+
<div className="relative flex items-center justify-center">
|
|
231
|
+
<tab.icon
|
|
232
|
+
className={cn("h-4 w-4 md:h-6 md:w-6 transition-colors", isActive ? 'text-ods-accent' : 'text-ods-text-secondary')}
|
|
233
|
+
/>
|
|
234
|
+
{tab.indicator && (
|
|
235
|
+
<div className={cn(
|
|
236
|
+
"absolute right-0 top-[-3px] w-3 h-3 rounded-full border-2 border-ods-bg",
|
|
237
|
+
tab.indicator === 'error' && 'bg-ods-error',
|
|
238
|
+
tab.indicator === 'warning' && 'bg-ods-accent',
|
|
239
|
+
tab.indicator === 'success' && 'bg-green-500'
|
|
240
|
+
)} />
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
260
243
|
|
|
261
244
|
<span className={cn(
|
|
262
245
|
"text-h4 whitespace-nowrap transition-colors",
|
|
@@ -182,26 +182,3 @@ body {
|
|
|
182
182
|
--ring: 180 100% 69%;
|
|
183
183
|
--radius: 0.5rem;
|
|
184
184
|
}
|
|
185
|
-
|
|
186
|
-
/* ============================================================================
|
|
187
|
-
Video center-control identity — ONE look across every video surface:
|
|
188
|
-
a bare solid-white glyph, NO background disc (see
|
|
189
|
-
components/features/video-center-badge.tsx for the React consumers).
|
|
190
|
-
This strips media-chrome's default dark circle off MuxPlayer's big
|
|
191
|
-
center pre-play button so full players match the strip/facade glyphs.
|
|
192
|
-
========================================================================== */
|
|
193
|
-
/* REAL players (any mounted MuxPlayer): icons are white at rest and turn
|
|
194
|
-
the platform ACCENT color while the icon's control is hovered — every
|
|
195
|
-
media-chrome control carries the `button` part token, so this covers
|
|
196
|
-
center play, bar play/mute/fullscreen/etc. uniformly. */
|
|
197
|
-
mux-player::part(button):hover {
|
|
198
|
-
--media-icon-color: var(--ods-accent, var(--color-accent-primary));
|
|
199
|
-
}
|
|
200
|
-
mux-player::part(center play button) {
|
|
201
|
-
background: transparent;
|
|
202
|
-
filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.5));
|
|
203
|
-
}
|
|
204
|
-
mux-player::part(center play button):hover {
|
|
205
|
-
background: transparent;
|
|
206
|
-
opacity: 0.85;
|
|
207
|
-
}
|