@livepeer-frameworks/player-react 0.0.3
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/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/components/DevModePanel.d.ts +47 -0
- package/dist/types/components/DvdLogo.d.ts +4 -0
- package/dist/types/components/Icons.d.ts +33 -0
- package/dist/types/components/IdleScreen.d.ts +16 -0
- package/dist/types/components/LoadingScreen.d.ts +6 -0
- package/dist/types/components/LogoOverlay.d.ts +11 -0
- package/dist/types/components/Player.d.ts +11 -0
- package/dist/types/components/PlayerControls.d.ts +60 -0
- package/dist/types/components/PlayerErrorBoundary.d.ts +23 -0
- package/dist/types/components/SeekBar.d.ts +33 -0
- package/dist/types/components/SkipIndicator.d.ts +14 -0
- package/dist/types/components/SpeedIndicator.d.ts +12 -0
- package/dist/types/components/StatsPanel.d.ts +31 -0
- package/dist/types/components/StreamStateOverlay.d.ts +24 -0
- package/dist/types/components/SubtitleRenderer.d.ts +69 -0
- package/dist/types/components/ThumbnailOverlay.d.ts +4 -0
- package/dist/types/components/TitleOverlay.d.ts +13 -0
- package/dist/types/components/players/DashJsPlayer.d.ts +18 -0
- package/dist/types/components/players/HlsJsPlayer.d.ts +18 -0
- package/dist/types/components/players/MewsWsPlayer/index.d.ts +18 -0
- package/dist/types/components/players/MistPlayer.d.ts +20 -0
- package/dist/types/components/players/MistWebRTCPlayer/index.d.ts +20 -0
- package/dist/types/components/players/NativePlayer.d.ts +19 -0
- package/dist/types/components/players/VideoJsPlayer.d.ts +18 -0
- package/dist/types/context/PlayerContext.d.ts +40 -0
- package/dist/types/context/index.d.ts +5 -0
- package/dist/types/hooks/useMetaTrack.d.ts +54 -0
- package/dist/types/hooks/usePlaybackQuality.d.ts +42 -0
- package/dist/types/hooks/usePlayerController.d.ts +163 -0
- package/dist/types/hooks/usePlayerSelection.d.ts +47 -0
- package/dist/types/hooks/useStreamState.d.ts +27 -0
- package/dist/types/hooks/useTelemetry.d.ts +57 -0
- package/dist/types/hooks/useViewerEndpoints.d.ts +14 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/types.d.ts +94 -0
- package/dist/types/ui/badge.d.ts +9 -0
- package/dist/types/ui/button.d.ts +11 -0
- package/dist/types/ui/context-menu.d.ts +27 -0
- package/dist/types/ui/select.d.ts +10 -0
- package/dist/types/ui/slider.d.ts +13 -0
- package/package.json +71 -0
- package/src/assets/logomark.svg +56 -0
- package/src/components/DevModePanel.tsx +822 -0
- package/src/components/DvdLogo.tsx +201 -0
- package/src/components/Icons.tsx +282 -0
- package/src/components/IdleScreen.tsx +664 -0
- package/src/components/LoadingScreen.tsx +710 -0
- package/src/components/LogoOverlay.tsx +75 -0
- package/src/components/Player.tsx +419 -0
- package/src/components/PlayerControls.tsx +820 -0
- package/src/components/PlayerErrorBoundary.tsx +70 -0
- package/src/components/SeekBar.tsx +291 -0
- package/src/components/SkipIndicator.tsx +113 -0
- package/src/components/SpeedIndicator.tsx +57 -0
- package/src/components/StatsPanel.tsx +150 -0
- package/src/components/StreamStateOverlay.tsx +200 -0
- package/src/components/SubtitleRenderer.tsx +235 -0
- package/src/components/ThumbnailOverlay.tsx +90 -0
- package/src/components/TitleOverlay.tsx +48 -0
- package/src/components/players/DashJsPlayer.tsx +56 -0
- package/src/components/players/HlsJsPlayer.tsx +56 -0
- package/src/components/players/MewsWsPlayer/index.tsx +56 -0
- package/src/components/players/MistPlayer.tsx +60 -0
- package/src/components/players/MistWebRTCPlayer/index.tsx +59 -0
- package/src/components/players/NativePlayer.tsx +58 -0
- package/src/components/players/VideoJsPlayer.tsx +56 -0
- package/src/context/PlayerContext.tsx +71 -0
- package/src/context/index.ts +11 -0
- package/src/global.d.ts +4 -0
- package/src/hooks/useMetaTrack.ts +187 -0
- package/src/hooks/usePlaybackQuality.ts +126 -0
- package/src/hooks/usePlayerController.ts +525 -0
- package/src/hooks/usePlayerSelection.ts +117 -0
- package/src/hooks/useStreamState.ts +381 -0
- package/src/hooks/useTelemetry.ts +138 -0
- package/src/hooks/useViewerEndpoints.ts +120 -0
- package/src/index.tsx +75 -0
- package/src/player.css +2 -0
- package/src/types.ts +135 -0
- package/src/ui/badge.tsx +27 -0
- package/src/ui/button.tsx +47 -0
- package/src/ui/context-menu.tsx +193 -0
- package/src/ui/select.tsx +105 -0
- package/src/ui/slider.tsx +67 -0
package/src/index.tsx
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @livepeer-frameworks/player-react
|
|
3
|
+
*
|
|
4
|
+
* React components for FrameWorks streaming player.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Main player component
|
|
8
|
+
export { default as Player } from './components/Player';
|
|
9
|
+
export { default as PlayerControls } from './components/PlayerControls';
|
|
10
|
+
|
|
11
|
+
// Overlay components
|
|
12
|
+
export { default as LoadingScreen } from './components/LoadingScreen';
|
|
13
|
+
export { default as IdleScreen } from './components/IdleScreen';
|
|
14
|
+
export { default as ThumbnailOverlay } from './components/ThumbnailOverlay';
|
|
15
|
+
export { default as TitleOverlay } from './components/TitleOverlay';
|
|
16
|
+
export { default as StreamStateOverlay } from './components/StreamStateOverlay';
|
|
17
|
+
export { default as StatsPanel } from './components/StatsPanel';
|
|
18
|
+
export { default as DevModePanel } from './components/DevModePanel';
|
|
19
|
+
export { default as PlayerErrorBoundary } from './components/PlayerErrorBoundary';
|
|
20
|
+
|
|
21
|
+
// Icon components
|
|
22
|
+
export * from './components/Icons';
|
|
23
|
+
|
|
24
|
+
// UI primitives
|
|
25
|
+
export { Button } from './ui/button';
|
|
26
|
+
export { Badge } from './ui/badge';
|
|
27
|
+
export { Slider } from './ui/slider';
|
|
28
|
+
|
|
29
|
+
// Context
|
|
30
|
+
export { PlayerProvider, usePlayerContext, usePlayerContextOptional, PlayerContext } from './context/PlayerContext';
|
|
31
|
+
export type { PlayerContextValue } from './context/PlayerContext';
|
|
32
|
+
|
|
33
|
+
// Hooks
|
|
34
|
+
export { useStreamState } from './hooks/useStreamState';
|
|
35
|
+
export { usePlaybackQuality } from './hooks/usePlaybackQuality';
|
|
36
|
+
export { useViewerEndpoints } from './hooks/useViewerEndpoints';
|
|
37
|
+
export { useMetaTrack } from './hooks/useMetaTrack';
|
|
38
|
+
export { useTelemetry } from './hooks/useTelemetry';
|
|
39
|
+
export { usePlayerSelection } from './hooks/usePlayerSelection';
|
|
40
|
+
export type { UsePlayerSelectionOptions, UsePlayerSelectionReturn } from './hooks/usePlayerSelection';
|
|
41
|
+
export { usePlayerController } from './hooks/usePlayerController';
|
|
42
|
+
export type {
|
|
43
|
+
UsePlayerControllerConfig,
|
|
44
|
+
UsePlayerControllerReturn,
|
|
45
|
+
PlayerControllerState,
|
|
46
|
+
} from './hooks/usePlayerController';
|
|
47
|
+
|
|
48
|
+
// Types
|
|
49
|
+
export * from './types';
|
|
50
|
+
|
|
51
|
+
// Re-export commonly used core items
|
|
52
|
+
export {
|
|
53
|
+
PlayerManager,
|
|
54
|
+
globalPlayerManager,
|
|
55
|
+
PlayerController,
|
|
56
|
+
GatewayClient,
|
|
57
|
+
StreamStateClient,
|
|
58
|
+
QualityMonitor,
|
|
59
|
+
cn,
|
|
60
|
+
} from '@livepeer-frameworks/player-core';
|
|
61
|
+
|
|
62
|
+
export type {
|
|
63
|
+
PlayerState,
|
|
64
|
+
PlayerStateContext,
|
|
65
|
+
StreamState,
|
|
66
|
+
StreamStatus,
|
|
67
|
+
MistStreamInfo,
|
|
68
|
+
PlaybackQuality,
|
|
69
|
+
PlaybackMode,
|
|
70
|
+
ContentEndpoints,
|
|
71
|
+
EndpointInfo,
|
|
72
|
+
PlayerSelection,
|
|
73
|
+
PlayerCombination,
|
|
74
|
+
PlayerManagerEvents,
|
|
75
|
+
} from '@livepeer-frameworks/player-core';
|
package/src/player.css
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React-specific types for FrameWorks player
|
|
3
|
+
*/
|
|
4
|
+
import type React from 'react';
|
|
5
|
+
import type {
|
|
6
|
+
PlayerOptions,
|
|
7
|
+
PlayerState,
|
|
8
|
+
PlayerStateContext,
|
|
9
|
+
ContentEndpoints,
|
|
10
|
+
MetaTrackSubscription,
|
|
11
|
+
PlaybackQuality,
|
|
12
|
+
QualityThresholds,
|
|
13
|
+
ContentType
|
|
14
|
+
} from '@livepeer-frameworks/player-core';
|
|
15
|
+
|
|
16
|
+
export interface PlayerProps {
|
|
17
|
+
/** Content identifier or stream name */
|
|
18
|
+
contentId: string;
|
|
19
|
+
/** Content type */
|
|
20
|
+
contentType: ContentType;
|
|
21
|
+
/** Pre-resolved endpoints/capabilities from Gateway/Foghorn */
|
|
22
|
+
endpoints?: ContentEndpoints;
|
|
23
|
+
/** Optional thumbnail/poster image */
|
|
24
|
+
thumbnailUrl?: string | null;
|
|
25
|
+
/** Unified options (branding, playback prefs, etc.) */
|
|
26
|
+
options?: Partial<PlayerOptions>;
|
|
27
|
+
/** Detailed state updates for UI (booting, gateway, connecting, playing, etc.) */
|
|
28
|
+
onStateChange?: (state: PlayerState, context?: PlayerStateContext) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MistPlayerProps {
|
|
32
|
+
streamName: string;
|
|
33
|
+
htmlUrl?: string;
|
|
34
|
+
playerJsUrl?: string;
|
|
35
|
+
developmentMode?: boolean;
|
|
36
|
+
muted?: boolean;
|
|
37
|
+
poster?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LoadingScreenProps {
|
|
41
|
+
message?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ThumbnailOverlayProps {
|
|
45
|
+
thumbnailUrl?: string | null | undefined;
|
|
46
|
+
onPlay?: () => void;
|
|
47
|
+
message?: string | null;
|
|
48
|
+
showUnmuteMessage?: boolean;
|
|
49
|
+
style?: React.CSSProperties;
|
|
50
|
+
className?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AnimatedBubbleProps {
|
|
54
|
+
index: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface CenterLogoProps {
|
|
58
|
+
containerRef: React.RefObject<HTMLDivElement>;
|
|
59
|
+
scale?: number;
|
|
60
|
+
onHitmarker?: (event: React.MouseEvent) => void;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface HitmarkerData {
|
|
64
|
+
id: number;
|
|
65
|
+
x: number;
|
|
66
|
+
y: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface DvdLogoProps {
|
|
70
|
+
parentRef: React.RefObject<HTMLDivElement>;
|
|
71
|
+
scale?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// React-specific hook option types
|
|
75
|
+
export interface UsePlaybackQualityOptions {
|
|
76
|
+
videoElement: HTMLVideoElement | null;
|
|
77
|
+
enabled?: boolean;
|
|
78
|
+
sampleInterval?: number;
|
|
79
|
+
thresholds?: Partial<QualityThresholds>;
|
|
80
|
+
onQualityDegraded?: (quality: PlaybackQuality) => void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface UseMetaTrackOptions {
|
|
84
|
+
mistBaseUrl: string;
|
|
85
|
+
streamName: string;
|
|
86
|
+
enabled?: boolean;
|
|
87
|
+
subscriptions?: MetaTrackSubscription[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface UseStreamStateOptions {
|
|
91
|
+
/** MistServer base URL */
|
|
92
|
+
mistBaseUrl: string;
|
|
93
|
+
/** Stream name */
|
|
94
|
+
streamName: string;
|
|
95
|
+
/** Poll interval in ms (default: 3000) */
|
|
96
|
+
pollInterval?: number;
|
|
97
|
+
/** Enable/disable the hook (default: true) */
|
|
98
|
+
enabled?: boolean;
|
|
99
|
+
/** Use WebSocket instead of HTTP polling (default: true) */
|
|
100
|
+
useWebSocket?: boolean;
|
|
101
|
+
/** Enable debug logging (default: false) */
|
|
102
|
+
debug?: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface UseTelemetryOptions {
|
|
106
|
+
enabled?: boolean;
|
|
107
|
+
endpoint?: string;
|
|
108
|
+
authToken?: string;
|
|
109
|
+
interval?: number;
|
|
110
|
+
batchSize?: number;
|
|
111
|
+
contentId?: string;
|
|
112
|
+
contentType?: string;
|
|
113
|
+
playerType?: string;
|
|
114
|
+
protocol?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Re-export core types for convenience
|
|
118
|
+
export type {
|
|
119
|
+
PlayerState,
|
|
120
|
+
PlayerStateContext,
|
|
121
|
+
ContentEndpoints,
|
|
122
|
+
PlayerOptions,
|
|
123
|
+
PlaybackQuality,
|
|
124
|
+
TelemetryOptions,
|
|
125
|
+
TelemetryPayload,
|
|
126
|
+
MetaTrackEvent,
|
|
127
|
+
MetaTrackEventType,
|
|
128
|
+
SubtitleCue,
|
|
129
|
+
PlaybackMode,
|
|
130
|
+
MistStreamInfo,
|
|
131
|
+
StreamState,
|
|
132
|
+
StreamStatus,
|
|
133
|
+
EndpointInfo,
|
|
134
|
+
ContentMetadata,
|
|
135
|
+
} from '@livepeer-frameworks/player-core';
|
package/src/ui/badge.tsx
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
3
|
+
import { cn } from "@livepeer-frameworks/player-core";
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
"inline-flex items-center rounded-full border border-transparent px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ring-offset-background",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
11
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
12
|
+
outline: "border-border text-foreground"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
export interface BadgeProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof badgeVariants> {}
|
|
22
|
+
|
|
23
|
+
function Badge({ className, variant, ...props }: BadgeProps) {
|
|
24
|
+
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { cn } from "@livepeer-frameworks/player-core";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 ring-offset-background",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
12
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
13
|
+
ghost: "hover:bg-accent hover:text-accent-foreground",
|
|
14
|
+
outline: "border border-border bg-transparent hover:bg-accent hover:text-accent-foreground",
|
|
15
|
+
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
16
|
+
subtle: "bg-muted text-muted-foreground hover:bg-muted/80",
|
|
17
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
18
|
+
},
|
|
19
|
+
size: {
|
|
20
|
+
default: "h-10 px-4 py-2",
|
|
21
|
+
sm: "h-9 rounded-md px-3",
|
|
22
|
+
lg: "h-11 rounded-md px-8",
|
|
23
|
+
icon: "h-9 w-9"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: {
|
|
27
|
+
variant: "default",
|
|
28
|
+
size: "default"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
export interface ButtonProps
|
|
34
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
35
|
+
VariantProps<typeof buttonVariants> {
|
|
36
|
+
asChild?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
40
|
+
({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
41
|
+
const Comp = asChild ? Slot : "button";
|
|
42
|
+
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
Button.displayName = "Button";
|
|
46
|
+
|
|
47
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
|
3
|
+
import { Check, ChevronRight, Circle } from "lucide-react"
|
|
4
|
+
import { cn } from "@livepeer-frameworks/player-core"
|
|
5
|
+
|
|
6
|
+
const ContextMenu = ContextMenuPrimitive.Root
|
|
7
|
+
|
|
8
|
+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
|
9
|
+
|
|
10
|
+
const ContextMenuGroup = ContextMenuPrimitive.Group
|
|
11
|
+
|
|
12
|
+
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
|
13
|
+
|
|
14
|
+
const ContextMenuSub = ContextMenuPrimitive.Sub
|
|
15
|
+
|
|
16
|
+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
|
17
|
+
|
|
18
|
+
const ContextMenuSubTrigger = React.forwardRef<
|
|
19
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
|
21
|
+
inset?: boolean
|
|
22
|
+
}
|
|
23
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
24
|
+
<ContextMenuPrimitive.SubTrigger
|
|
25
|
+
ref={ref}
|
|
26
|
+
className={cn(
|
|
27
|
+
"fw-context-menu-item",
|
|
28
|
+
inset && "fw-context-menu-item--inset",
|
|
29
|
+
className
|
|
30
|
+
)}
|
|
31
|
+
{...props}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
<ChevronRight className="ml-auto h-4 w-4" />
|
|
35
|
+
</ContextMenuPrimitive.SubTrigger>
|
|
36
|
+
))
|
|
37
|
+
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
|
38
|
+
|
|
39
|
+
const ContextMenuSubContent = React.forwardRef<
|
|
40
|
+
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
|
41
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
|
42
|
+
>(({ className, ...props }, ref) => (
|
|
43
|
+
<ContextMenuPrimitive.SubContent
|
|
44
|
+
ref={ref}
|
|
45
|
+
className={cn("fw-player-surface fw-context-menu", className)}
|
|
46
|
+
{...props}
|
|
47
|
+
/>
|
|
48
|
+
))
|
|
49
|
+
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
|
50
|
+
|
|
51
|
+
const ContextMenuContent = React.forwardRef<
|
|
52
|
+
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
|
53
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
|
54
|
+
>(({ className, style, ...props }, ref) => (
|
|
55
|
+
<ContextMenuPrimitive.Portal>
|
|
56
|
+
<ContextMenuPrimitive.Content
|
|
57
|
+
ref={ref}
|
|
58
|
+
className={cn("fw-player-surface fw-context-menu", className)}
|
|
59
|
+
style={{
|
|
60
|
+
// Inline styles for portal elements (rendered outside .fw-player-root)
|
|
61
|
+
// These ensure styles work even without CSS variable inheritance
|
|
62
|
+
backgroundColor: 'hsl(235 19% 13%)',
|
|
63
|
+
color: 'hsl(229 35% 75%)',
|
|
64
|
+
border: '1px solid rgba(90, 96, 127, 0.3)',
|
|
65
|
+
...style
|
|
66
|
+
}}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
</ContextMenuPrimitive.Portal>
|
|
70
|
+
))
|
|
71
|
+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
|
72
|
+
|
|
73
|
+
const ContextMenuItem = React.forwardRef<
|
|
74
|
+
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
|
75
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
|
76
|
+
inset?: boolean
|
|
77
|
+
}
|
|
78
|
+
>(({ className, inset, ...props }, ref) => (
|
|
79
|
+
<ContextMenuPrimitive.Item
|
|
80
|
+
ref={ref}
|
|
81
|
+
className={cn(
|
|
82
|
+
"fw-context-menu-item",
|
|
83
|
+
inset && "fw-context-menu-item--inset",
|
|
84
|
+
className
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
))
|
|
89
|
+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
|
90
|
+
|
|
91
|
+
const ContextMenuCheckboxItem = React.forwardRef<
|
|
92
|
+
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
|
93
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
|
94
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
95
|
+
<ContextMenuPrimitive.CheckboxItem
|
|
96
|
+
ref={ref}
|
|
97
|
+
className={cn("fw-context-menu-checkbox", className)}
|
|
98
|
+
checked={checked}
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
<span className="fw-context-menu-indicator">
|
|
102
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
103
|
+
<Check className="h-4 w-4" />
|
|
104
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
105
|
+
</span>
|
|
106
|
+
{children}
|
|
107
|
+
</ContextMenuPrimitive.CheckboxItem>
|
|
108
|
+
))
|
|
109
|
+
ContextMenuCheckboxItem.displayName =
|
|
110
|
+
ContextMenuPrimitive.CheckboxItem.displayName
|
|
111
|
+
|
|
112
|
+
const ContextMenuRadioItem = React.forwardRef<
|
|
113
|
+
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
|
114
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
|
115
|
+
>(({ className, children, ...props }, ref) => (
|
|
116
|
+
<ContextMenuPrimitive.RadioItem
|
|
117
|
+
ref={ref}
|
|
118
|
+
className={cn("fw-context-menu-checkbox", className)}
|
|
119
|
+
{...props}
|
|
120
|
+
>
|
|
121
|
+
<span className="fw-context-menu-indicator">
|
|
122
|
+
<ContextMenuPrimitive.ItemIndicator>
|
|
123
|
+
<Circle className="h-2 w-2 fill-current" />
|
|
124
|
+
</ContextMenuPrimitive.ItemIndicator>
|
|
125
|
+
</span>
|
|
126
|
+
{children}
|
|
127
|
+
</ContextMenuPrimitive.RadioItem>
|
|
128
|
+
))
|
|
129
|
+
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
|
130
|
+
|
|
131
|
+
const ContextMenuLabel = React.forwardRef<
|
|
132
|
+
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
|
133
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
|
134
|
+
inset?: boolean
|
|
135
|
+
}
|
|
136
|
+
>(({ className, inset, ...props }, ref) => (
|
|
137
|
+
<ContextMenuPrimitive.Label
|
|
138
|
+
ref={ref}
|
|
139
|
+
className={cn(
|
|
140
|
+
"fw-context-menu-label",
|
|
141
|
+
inset && "fw-context-menu-item--inset",
|
|
142
|
+
className
|
|
143
|
+
)}
|
|
144
|
+
{...props}
|
|
145
|
+
/>
|
|
146
|
+
))
|
|
147
|
+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
|
148
|
+
|
|
149
|
+
const ContextMenuSeparator = React.forwardRef<
|
|
150
|
+
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
|
151
|
+
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
|
152
|
+
>(({ className, ...props }, ref) => (
|
|
153
|
+
<ContextMenuPrimitive.Separator
|
|
154
|
+
ref={ref}
|
|
155
|
+
className={cn("fw-context-menu-separator", className)}
|
|
156
|
+
{...props}
|
|
157
|
+
/>
|
|
158
|
+
))
|
|
159
|
+
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
|
160
|
+
|
|
161
|
+
const ContextMenuShortcut = ({
|
|
162
|
+
className,
|
|
163
|
+
...props
|
|
164
|
+
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
165
|
+
return (
|
|
166
|
+
<span
|
|
167
|
+
className={cn(
|
|
168
|
+
"ml-auto text-xs tracking-widest text-muted-foreground",
|
|
169
|
+
className
|
|
170
|
+
)}
|
|
171
|
+
{...props}
|
|
172
|
+
/>
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
|
176
|
+
|
|
177
|
+
export {
|
|
178
|
+
ContextMenu,
|
|
179
|
+
ContextMenuTrigger,
|
|
180
|
+
ContextMenuContent,
|
|
181
|
+
ContextMenuItem,
|
|
182
|
+
ContextMenuCheckboxItem,
|
|
183
|
+
ContextMenuRadioItem,
|
|
184
|
+
ContextMenuLabel,
|
|
185
|
+
ContextMenuSeparator,
|
|
186
|
+
ContextMenuShortcut,
|
|
187
|
+
ContextMenuGroup,
|
|
188
|
+
ContextMenuPortal,
|
|
189
|
+
ContextMenuSub,
|
|
190
|
+
ContextMenuSubContent,
|
|
191
|
+
ContextMenuSubTrigger,
|
|
192
|
+
ContextMenuRadioGroup,
|
|
193
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
2
|
+
import { Check, ChevronDown } from "lucide-react";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "@livepeer-frameworks/player-core";
|
|
5
|
+
|
|
6
|
+
const Select = SelectPrimitive.Root;
|
|
7
|
+
|
|
8
|
+
const SelectValue = SelectPrimitive.Value;
|
|
9
|
+
|
|
10
|
+
const SelectTrigger = React.forwardRef<
|
|
11
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
12
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
13
|
+
>(({ className, children, ...props }, ref) => (
|
|
14
|
+
<SelectPrimitive.Trigger
|
|
15
|
+
ref={ref}
|
|
16
|
+
className={cn(
|
|
17
|
+
"inline-flex h-9 w-full items-center justify-between gap-2 whitespace-nowrap rounded-md border border-border bg-background px-3 py-1 text-sm text-foreground shadow-sm transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
<SelectPrimitive.Icon asChild>
|
|
24
|
+
<ChevronDown className="fw-h-4 w-4 opacity-60" />
|
|
25
|
+
</SelectPrimitive.Icon>
|
|
26
|
+
</SelectPrimitive.Trigger>
|
|
27
|
+
));
|
|
28
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
|
29
|
+
|
|
30
|
+
const SelectContent = React.forwardRef<
|
|
31
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
32
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
33
|
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
|
34
|
+
<SelectPrimitive.Portal>
|
|
35
|
+
<SelectPrimitive.Content
|
|
36
|
+
ref={ref}
|
|
37
|
+
className={cn(
|
|
38
|
+
// Explicit Tokyo Night colors for portal (doesn't inherit CSS vars)
|
|
39
|
+
"z-50 min-w-[8rem] overflow-hidden border border-[#414868]/50 bg-[#1a1b26] text-[#a9b1d6] shadow-md",
|
|
40
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
41
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=top]:-translate-y-1",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
style={{ backgroundColor: '#1a1b26' }} // Inline fallback for opaque background
|
|
45
|
+
position={position}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<SelectPrimitive.Viewport className="p-1">
|
|
49
|
+
{children}
|
|
50
|
+
</SelectPrimitive.Viewport>
|
|
51
|
+
</SelectPrimitive.Content>
|
|
52
|
+
</SelectPrimitive.Portal>
|
|
53
|
+
));
|
|
54
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
|
55
|
+
|
|
56
|
+
const SelectItem = React.forwardRef<
|
|
57
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
58
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
59
|
+
>(({ className, children, ...props }, ref) => (
|
|
60
|
+
<SelectPrimitive.Item
|
|
61
|
+
ref={ref}
|
|
62
|
+
className={cn(
|
|
63
|
+
// Explicit Tokyo Night colors for portal context
|
|
64
|
+
"relative flex w-full cursor-pointer select-none items-center py-1.5 pl-8 pr-2 text-sm outline-none",
|
|
65
|
+
"focus:bg-[#292e42] focus:text-[#c0caf5] hover:bg-[#292e42]",
|
|
66
|
+
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
|
67
|
+
className
|
|
68
|
+
)}
|
|
69
|
+
{...props}
|
|
70
|
+
>
|
|
71
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
72
|
+
<SelectPrimitive.ItemIndicator>
|
|
73
|
+
<Check className="h-4 w-4" />
|
|
74
|
+
</SelectPrimitive.ItemIndicator>
|
|
75
|
+
</span>
|
|
76
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
77
|
+
</SelectPrimitive.Item>
|
|
78
|
+
));
|
|
79
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
|
80
|
+
|
|
81
|
+
const SelectLabel = React.forwardRef<
|
|
82
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
83
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
84
|
+
>(({ className, ...props }, ref) => (
|
|
85
|
+
<SelectPrimitive.Label ref={ref} className={cn("px-2 py-1.5 text-xs font-semibold text-muted-foreground", className)} {...props} />
|
|
86
|
+
));
|
|
87
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
|
88
|
+
|
|
89
|
+
const SelectSeparator = React.forwardRef<
|
|
90
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
91
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
92
|
+
>(({ className, ...props }, ref) => (
|
|
93
|
+
<SelectPrimitive.Separator ref={ref} className={cn("-mx-1 my-1 h-px bg-muted", className)} {...props} />
|
|
94
|
+
));
|
|
95
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
|
96
|
+
|
|
97
|
+
export {
|
|
98
|
+
Select,
|
|
99
|
+
SelectContent,
|
|
100
|
+
SelectItem,
|
|
101
|
+
SelectLabel,
|
|
102
|
+
SelectSeparator,
|
|
103
|
+
SelectTrigger,
|
|
104
|
+
SelectValue
|
|
105
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import * as SliderPrimitive from "@radix-ui/react-slider";
|
|
3
|
+
import { cn } from "@livepeer-frameworks/player-core";
|
|
4
|
+
|
|
5
|
+
export interface SliderProps extends React.ComponentPropsWithoutRef<typeof SliderPrimitive.Root> {
|
|
6
|
+
showTrack?: boolean;
|
|
7
|
+
trackClassName?: string;
|
|
8
|
+
thumbClassName?: string;
|
|
9
|
+
/** Show thumb only on hover (YouTube-style) - but always shows a smaller thumb when not hovered */
|
|
10
|
+
hoverThumb?: boolean;
|
|
11
|
+
/** Use cyan accent color (matches SeekBar styling) */
|
|
12
|
+
accentColor?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Slider = React.forwardRef<React.ElementRef<typeof SliderPrimitive.Root>, SliderProps>(
|
|
16
|
+
({ className, trackClassName, thumbClassName, showTrack = true, hoverThumb = false, accentColor = false, orientation = "horizontal", ...props }, ref) => {
|
|
17
|
+
// Colors based on accentColor prop
|
|
18
|
+
const rangeColorClass = accentColor ? "bg-[hsl(var(--tn-cyan,195_100%_50%))]" : "bg-white/90";
|
|
19
|
+
const thumbColorClass = accentColor ? "bg-[hsl(var(--tn-cyan,195_100%_50%))]" : "bg-white";
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<SliderPrimitive.Root
|
|
23
|
+
ref={ref}
|
|
24
|
+
orientation={orientation}
|
|
25
|
+
className={cn(
|
|
26
|
+
"group relative flex touch-none select-none items-center cursor-pointer",
|
|
27
|
+
orientation === "horizontal" ? "w-full h-5" : "h-full flex-col w-5",
|
|
28
|
+
className
|
|
29
|
+
)}
|
|
30
|
+
{...props}
|
|
31
|
+
>
|
|
32
|
+
{showTrack && (
|
|
33
|
+
<SliderPrimitive.Track
|
|
34
|
+
className={cn(
|
|
35
|
+
"absolute rounded-full bg-white/30 transition-all duration-150",
|
|
36
|
+
orientation === "horizontal"
|
|
37
|
+
? "inset-x-0 h-1 group-hover:h-1.5"
|
|
38
|
+
: "inset-y-0 w-1 group-hover:w-1.5",
|
|
39
|
+
trackClassName
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
<SliderPrimitive.Range
|
|
43
|
+
className={cn(
|
|
44
|
+
"absolute rounded-full transition-all duration-150",
|
|
45
|
+
orientation === "horizontal" ? "h-full" : "w-full bottom-0",
|
|
46
|
+
rangeColorClass
|
|
47
|
+
)}
|
|
48
|
+
/>
|
|
49
|
+
</SliderPrimitive.Track>
|
|
50
|
+
)}
|
|
51
|
+
<SliderPrimitive.Thumb
|
|
52
|
+
className={cn(
|
|
53
|
+
"block rounded-full border-0 cursor-pointer shadow-md transition-all duration-150",
|
|
54
|
+
"w-2.5 h-2.5 group-hover:w-3.5 group-hover:h-3.5",
|
|
55
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50",
|
|
56
|
+
"disabled:pointer-events-none disabled:opacity-50",
|
|
57
|
+
thumbColorClass,
|
|
58
|
+
thumbClassName
|
|
59
|
+
)}
|
|
60
|
+
/>
|
|
61
|
+
</SliderPrimitive.Root>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
Slider.displayName = SliderPrimitive.Root.displayName;
|
|
66
|
+
|
|
67
|
+
export { Slider };
|