mirror of
https://github.com/zoriya/v10.git
synced 2026-08-15 02:14:06 +00:00
feat: add a solution for React preview time display (#50)
This commit is contained in:
@@ -6,6 +6,7 @@ export interface TimeSliderState extends SliderState {
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
requestSeek: (time: number) => void;
|
||||
requestPreview: (time: number) => void;
|
||||
_currentTimeText: string;
|
||||
_durationText: string;
|
||||
}
|
||||
@@ -70,10 +71,13 @@ export class TimeSlider extends Slider {
|
||||
#handlePointerMove(event: PointerEvent) {
|
||||
super.handleEvent(event);
|
||||
|
||||
const { _dragging, _pointerRatio, duration, requestSeek } = super.getState() as TimeSliderState;
|
||||
const { _dragging, _pointerRatio, duration, requestSeek, requestPreview } = super.getState() as TimeSliderState;
|
||||
|
||||
const previewTime = _pointerRatio * duration;
|
||||
requestPreview(previewTime);
|
||||
|
||||
if (_dragging) {
|
||||
this.#seekingTime = _pointerRatio * duration;
|
||||
this.#seekingTime = previewTime;
|
||||
requestSeek(this.#seekingTime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* @fileoverview Preview time display component state definition
|
||||
*
|
||||
* This module provides the component state definition for preview time display
|
||||
* components across HTML, React, and React Native platforms. The preview time
|
||||
* display is a read-only component that shows the preview time when hovering
|
||||
* over the time slider.
|
||||
*/
|
||||
|
||||
/**
|
||||
* State interface for preview time display components
|
||||
*/
|
||||
export interface PreviewTimeDisplayState {
|
||||
/** The preview time value in seconds */
|
||||
previewTime: number | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview time display component state definition following VJS-10 patterns.
|
||||
* This provides a read-only display component that shows the preview time.
|
||||
*/
|
||||
export interface PreviewTimeDisplayStateDefinition {
|
||||
keys: (keyof PreviewTimeDisplayState)[];
|
||||
stateTransform: (rawState: any) => PreviewTimeDisplayState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview time display state definition
|
||||
* Defines the core state logic that can be shared between implementations
|
||||
*/
|
||||
export const previewTimeDisplayStateDefinition: PreviewTimeDisplayStateDefinition = {
|
||||
keys: ['previewTime'],
|
||||
stateTransform: (rawState: any) => ({
|
||||
previewTime: rawState.previewTime ?? 0,
|
||||
}),
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
export interface TimeSliderState {
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
previewTime: number;
|
||||
}
|
||||
|
||||
export interface TimeSliderMethods {
|
||||
requestSeek: (time: number) => void;
|
||||
requestPreview: (time: number) => void;
|
||||
}
|
||||
|
||||
export interface TimeSliderStateDefinition {
|
||||
@@ -18,14 +20,18 @@ export interface TimeSliderStateDefinition {
|
||||
* Defines the core state logic that can be shared between implementations
|
||||
*/
|
||||
export const timeSliderStateDefinition: TimeSliderStateDefinition = {
|
||||
keys: ['currentTime', 'duration'],
|
||||
keys: ['currentTime', 'duration', 'previewTime'],
|
||||
stateTransform: (rawState: any) => ({
|
||||
currentTime: rawState.currentTime ?? 0,
|
||||
duration: rawState.duration ?? 0,
|
||||
previewTime: rawState.previewTime ?? 0,
|
||||
}),
|
||||
createRequestMethods: dispatch => ({
|
||||
requestSeek: (time: number) => {
|
||||
dispatch({ type: 'seekrequest', detail: time });
|
||||
},
|
||||
requestPreview: (time: number) => {
|
||||
dispatch({ type: 'previewrequest', detail: time });
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface StateMediator {
|
||||
currentTime: FacadeProp<HTMLMediaElement['currentTime']>;
|
||||
duration: ReadonlyFacadeProp<HTMLMediaElement['duration']>;
|
||||
seekable: ReadonlyFacadeProp<[number, number] | undefined>;
|
||||
previewTime: FacadeProp<number>;
|
||||
fullscreen: FacadeProp<boolean>;
|
||||
}
|
||||
|
||||
@@ -58,7 +59,7 @@ export function createMediaStore({
|
||||
}: {
|
||||
media?: any;
|
||||
container?: any;
|
||||
stateMediator: Partial<StateMediator> & Pick<StateMediator, 'paused'>;
|
||||
stateMediator: Partial<StateMediator>;
|
||||
}): MediaStore {
|
||||
const stateOwners: StateOwners = {};
|
||||
const store = map<any>({});
|
||||
@@ -117,16 +118,21 @@ export function createMediaStore({
|
||||
} else if (type === 'containerstateownerchangerequest') {
|
||||
updateStateOwners({ container: detail });
|
||||
} else {
|
||||
for (const stateObject of Object.values(stateMediator).filter(
|
||||
(stateMediatorEntry): stateMediatorEntry is FacadeProp<any, any, any> => 'set' in stateMediatorEntry,
|
||||
)) {
|
||||
const { set, actions } = stateObject;
|
||||
if (actions[type]) {
|
||||
Object.entries(stateMediator).forEach(([stateName, stateObject]) => {
|
||||
const { set, actions } = stateObject as FacadeProp<any, any, any>;
|
||||
|
||||
if (actions?.[type]) {
|
||||
const actionFn = actions[type];
|
||||
const actionValue = actionFn(action);
|
||||
(set as FacadeSetter<any>)(actionValue, stateOwners);
|
||||
if (set) {
|
||||
(set as FacadeSetter<any>)(actionValue, stateOwners);
|
||||
} else {
|
||||
// If there is no setter, store the action value in the store.
|
||||
// Might need revisiting.
|
||||
store.setKey(stateName, actionValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -155,9 +161,10 @@ export function createMediaStore({
|
||||
};
|
||||
}
|
||||
|
||||
function getInitialState(stateMediator: Partial<StateMediator> & Pick<StateMediator, 'paused'>, stateOwners: any) {
|
||||
function getInitialState(stateMediator: Partial<StateMediator>, stateOwners: any) {
|
||||
const initialState: any = {};
|
||||
for (const [stateName, { get }] of Object.entries(stateMediator)) {
|
||||
if (!get) continue;
|
||||
initialState[stateName] = get(stateOwners);
|
||||
}
|
||||
return initialState;
|
||||
|
||||
@@ -3,6 +3,7 @@ export * from './component-state-definitions/duration-display';
|
||||
export * from './component-state-definitions/fullscreen-button';
|
||||
export * from './component-state-definitions/mute-button';
|
||||
export * from './component-state-definitions/play-button';
|
||||
export * from './component-state-definitions/preview-time-display';
|
||||
export * from './component-state-definitions/time-slider';
|
||||
export * from './component-state-definitions/volume-slider';
|
||||
export * from './factory';
|
||||
|
||||
@@ -4,11 +4,12 @@ import { createMediaStore as factory } from './factory';
|
||||
import { audible } from './state-mediators/audible';
|
||||
import { fullscreenable } from './state-mediators/fullscreenable';
|
||||
import { playable } from './state-mediators/playable';
|
||||
import { preview } from './state-mediators/preview';
|
||||
import { temporal } from './state-mediators/temporal';
|
||||
|
||||
// Example of default media store with default state mediator definitions. (CJP)
|
||||
// NOTE: We can also change the API to take an array of stateMediators (or either/both) (CJP)
|
||||
const stateMediator = { ...playable, ...audible, ...temporal, ...fullscreenable };
|
||||
const stateMediator = { ...playable, ...audible, ...temporal, ...fullscreenable, ...preview };
|
||||
|
||||
type Params = Partial<Parameters<typeof factory>[0]>;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export { audible } from './audible';
|
||||
export { fullscreenable } from './fullscreenable';
|
||||
export { playable } from './playable';
|
||||
export { preview } from './preview';
|
||||
export { temporal } from './temporal';
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const preview = {
|
||||
previewTime: {
|
||||
actions: {
|
||||
previewrequest: ({ detail }: Pick<CustomEvent<any>, 'detail'> = { detail: 0 }): number => +detail,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -20,8 +20,11 @@ export function useCurrentTimeDisplayState(_props: any): {
|
||||
};
|
||||
}
|
||||
|
||||
export type useCurrentTimeDisplayState = typeof useCurrentTimeDisplayState;
|
||||
export type CurrentTimeDisplayState = ReturnType<useCurrentTimeDisplayState>;
|
||||
export type CurrentTimeDisplayState = ReturnType<typeof useCurrentTimeDisplayState>;
|
||||
|
||||
export interface CurrentTimeDisplayProps extends React.ComponentProps<'span'> {
|
||||
showRemaining?: boolean;
|
||||
}
|
||||
|
||||
export function useCurrentTimeDisplayProps(
|
||||
props: PropsWithChildren,
|
||||
@@ -35,9 +38,6 @@ export function useCurrentTimeDisplayProps(
|
||||
return baseProps;
|
||||
}
|
||||
|
||||
export type useCurrentTimeDisplayProps = typeof useCurrentTimeDisplayProps;
|
||||
type CurrentTimeDisplayProps = ReturnType<useCurrentTimeDisplayProps>;
|
||||
|
||||
export function renderCurrentTimeDisplay(props: CurrentTimeDisplayProps, state: CurrentTimeDisplayState): JSX.Element {
|
||||
const { showRemaining, ...restProps } = props;
|
||||
|
||||
@@ -50,8 +50,6 @@ export function renderCurrentTimeDisplay(props: CurrentTimeDisplayProps, state:
|
||||
return <span {...restProps}>{timeLabel}</span>;
|
||||
}
|
||||
|
||||
export type renderCurrentTimeDisplay = typeof renderCurrentTimeDisplay;
|
||||
|
||||
export const CurrentTimeDisplay: ConnectedComponent<CurrentTimeDisplayProps, typeof renderCurrentTimeDisplay>
|
||||
= toConnectedComponent(
|
||||
useCurrentTimeDisplayState,
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import type { ConnectedComponent } from '../utils/component-factory';
|
||||
|
||||
import { formatDisplayTime, previewTimeDisplayStateDefinition } from '@vjs-10/media-store';
|
||||
import { shallowEqual, useMediaSelector } from '@vjs-10/react-media-store';
|
||||
|
||||
import { toConnectedComponent } from '../utils/component-factory';
|
||||
|
||||
export function usePreviewTimeDisplayState(_props: any): {
|
||||
previewTime: number;
|
||||
} {
|
||||
/** @TODO Fix type issues with hooks (CJP) */
|
||||
const mediaState = useMediaSelector(previewTimeDisplayStateDefinition.stateTransform, shallowEqual);
|
||||
|
||||
// Preview time display is read-only, no request methods needed
|
||||
return {
|
||||
previewTime: mediaState.previewTime ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
export type PreviewTimeDisplayState = ReturnType<typeof usePreviewTimeDisplayState>;
|
||||
|
||||
export interface PreviewTimeDisplayProps extends React.ComponentProps<'span'> {
|
||||
showRemaining?: boolean;
|
||||
}
|
||||
|
||||
export function getPreviewTimeDisplayProps(
|
||||
props: PropsWithChildren,
|
||||
_state: ReturnType<typeof usePreviewTimeDisplayState>,
|
||||
): PropsWithChildren<Record<string, unknown>> {
|
||||
const baseProps: Record<string, any> = {
|
||||
/** external props spread last to allow for overriding */
|
||||
...props,
|
||||
};
|
||||
|
||||
return baseProps;
|
||||
}
|
||||
|
||||
export function renderPreviewTimeDisplay(props: PreviewTimeDisplayProps, state: PreviewTimeDisplayState): JSX.Element {
|
||||
const { showRemaining, ...restProps } = props;
|
||||
|
||||
/** @TODO Should this live here or elsewhere? (CJP) */
|
||||
const timeLabel = formatDisplayTime(state.previewTime);
|
||||
|
||||
return <span {...restProps}>{timeLabel}</span>;
|
||||
}
|
||||
|
||||
export const PreviewTimeDisplay: ConnectedComponent<PreviewTimeDisplayProps, typeof renderPreviewTimeDisplay>
|
||||
= toConnectedComponent(
|
||||
usePreviewTimeDisplayState,
|
||||
getPreviewTimeDisplayProps,
|
||||
renderPreviewTimeDisplay,
|
||||
'PreviewTimeDisplay',
|
||||
);
|
||||
|
||||
export default PreviewTimeDisplay;
|
||||
@@ -7,6 +7,7 @@ import { shallowEqual, useMediaSelector, useMediaStore } from '@vjs-10/react-med
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { toConnectedComponent, toContextComponent, useCore } from '../utils/component-factory';
|
||||
import { useComposedRefs } from '../utils/useComposedRefs';
|
||||
|
||||
export interface TimeSliderState {
|
||||
currentTime: number;
|
||||
@@ -16,7 +17,7 @@ export interface TimeSliderState {
|
||||
orientation: 'horizontal' | 'vertical';
|
||||
}
|
||||
|
||||
export interface TimeSliderProps extends React.ComponentProps<'div'> {
|
||||
export interface TimeSliderProps extends React.ComponentPropsWithRef<'div'> {
|
||||
orientation?: 'horizontal' | 'vertical';
|
||||
}
|
||||
|
||||
@@ -48,13 +49,17 @@ export function useTimeSliderRootState(props: TimeSliderProps): TimeSliderState
|
||||
export function useTimeSliderRootProps(props: TimeSliderProps, state: TimeSliderState): TimeSliderRenderProps {
|
||||
const { _fillWidth, _pointerWidth, _currentTimeText, _durationText } = state.core.getState();
|
||||
|
||||
const { children, className, id, style, orientation = 'horizontal' } = props;
|
||||
const { children, className, id, style, orientation = 'horizontal', ref } = props;
|
||||
|
||||
const internalRef = useCallback((el: HTMLDivElement) => {
|
||||
if (!el) return;
|
||||
state.core?.attach(el);
|
||||
}, [state.core]);
|
||||
|
||||
const composedRef = useComposedRefs(ref, internalRef);
|
||||
|
||||
return {
|
||||
ref: useCallback((el: HTMLDivElement) => {
|
||||
if (!el) return;
|
||||
state.core?.attach(el);
|
||||
}, []),
|
||||
ref: composedRef,
|
||||
id,
|
||||
role: 'slider',
|
||||
'aria-label': 'Seek',
|
||||
@@ -95,7 +100,7 @@ export function useTimeSliderTrackProps(props: React.ComponentProps<'div'>, cont
|
||||
return {
|
||||
ref: useCallback((el: HTMLDivElement) => {
|
||||
context.core?.setState({ _trackElement: el });
|
||||
}, []),
|
||||
}, [context.core]),
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
style: {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
FloatingPortal,
|
||||
offset,
|
||||
shift,
|
||||
useClientPoint,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useFocus,
|
||||
@@ -42,12 +43,14 @@ interface TooltipContextType {
|
||||
getReferenceProps: ReturnType<typeof useInteractions>['getReferenceProps'];
|
||||
getFloatingProps: ReturnType<typeof useInteractions>['getFloatingProps'];
|
||||
updatePositioning: (props: UpdatePositioningProps) => void;
|
||||
trackCursorAxis?: 'x' | 'y' | 'both' | undefined;
|
||||
}
|
||||
|
||||
interface TooltipRootProps {
|
||||
delay?: number;
|
||||
closeDelay?: number;
|
||||
children: ReactNode;
|
||||
trackCursorAxis?: 'x' | 'y' | 'both';
|
||||
}
|
||||
|
||||
interface TooltipTriggerProps {
|
||||
@@ -101,7 +104,7 @@ function useTooltipPositionerContext(): TooltipPositionerContextType {
|
||||
return context;
|
||||
}
|
||||
|
||||
function TooltipRoot({ delay = 600, closeDelay = 0, children }: TooltipRootProps): JSX.Element {
|
||||
function TooltipRoot({ delay = 0, closeDelay = 0, trackCursorAxis, children }: TooltipRootProps): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [placement, setPlacement] = useState<Placement>('top');
|
||||
const [sideOffset, setSideOffset] = useState(0);
|
||||
@@ -136,7 +139,18 @@ function TooltipRoot({ delay = 600, closeDelay = 0, children }: TooltipRootProps
|
||||
const dismiss = useDismiss(context);
|
||||
const role = useRole(context, { role: 'tooltip' });
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([hover, focus, dismiss, role]);
|
||||
// Use client point hook when trackCursorAxis is enabled
|
||||
const clientPoint = useClientPoint(context, {
|
||||
axis: trackCursorAxis || 'both',
|
||||
enabled: !!trackCursorAxis,
|
||||
});
|
||||
|
||||
// Combine interactions based on whether cursor tracking is enabled
|
||||
const interactions = trackCursorAxis
|
||||
? [hover, focus, dismiss, role, clientPoint]
|
||||
: [hover, focus, dismiss, role];
|
||||
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions(interactions);
|
||||
|
||||
const updatePositioning = useCallback(({ side, sideOffset, collisionPadding }: UpdatePositioningProps) => {
|
||||
setPlacement(side);
|
||||
@@ -151,7 +165,9 @@ function TooltipRoot({ delay = 600, closeDelay = 0, children }: TooltipRootProps
|
||||
updatePositioning,
|
||||
arrowRef,
|
||||
transitionStatus,
|
||||
}), [getReferenceProps, getFloatingProps, context, updatePositioning, transitionStatus]);
|
||||
trackCursorAxis,
|
||||
};
|
||||
}), [getReferenceProps, getFloatingProps, context, updatePositioning, transitionStatus, trackCursorAxis]);
|
||||
|
||||
return <TooltipContext.Provider value={value}>{children}</TooltipContext.Provider>;
|
||||
}
|
||||
@@ -174,7 +190,7 @@ function TooltipPositioner({
|
||||
collisionPadding = 0,
|
||||
children,
|
||||
}: TooltipPositionerProps): JSX.Element | null {
|
||||
const { context, updatePositioning } = useTooltipContext();
|
||||
const { context, updatePositioning, trackCursorAxis } = useTooltipContext();
|
||||
const { refs, floatingStyles } = context;
|
||||
|
||||
// Update positioning when props change
|
||||
@@ -188,7 +204,13 @@ function TooltipPositioner({
|
||||
|
||||
return (
|
||||
<TooltipPositionerContext.Provider value={positionerContextValue}>
|
||||
<div ref={refs.setFloating} style={floatingStyles}>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={{
|
||||
...floatingStyles,
|
||||
pointerEvents: trackCursorAxis ? 'none' : undefined,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</TooltipPositionerContext.Provider>
|
||||
@@ -201,7 +223,7 @@ function TooltipPopup({ className = '', children }: TooltipPopupProps): JSX.Elem
|
||||
const triggerElement = refs.reference.current as HTMLElement | null;
|
||||
|
||||
// Copy data attributes from trigger element
|
||||
const dataAttributes = triggerElement
|
||||
const dataAttributes = triggerElement?.attributes
|
||||
? Object.fromEntries(
|
||||
Array.from(triggerElement.attributes)
|
||||
.filter(attr => attr.name.startsWith('data-'))
|
||||
|
||||
@@ -5,6 +5,7 @@ export { MediaContainer, useMediaContainerRef } from './components/MediaContaine
|
||||
export { MuteButton } from './components/MuteButton';
|
||||
export { PlayButton } from './components/PlayButton';
|
||||
export { Popover } from './components/Popover';
|
||||
export { PreviewTimeDisplay } from './components/PreviewTimeDisplay';
|
||||
export { TimeSlider } from './components/TimeSlider';
|
||||
export { Tooltip } from './components/Tooltip';
|
||||
export { MediaElementVideo, Video } from './components/Video';
|
||||
|
||||
@@ -11,13 +11,13 @@ import {
|
||||
} from '@vjs-10/react-icons';
|
||||
|
||||
import { CurrentTimeDisplay } from '../../components/CurrentTimeDisplay';
|
||||
|
||||
import { DurationDisplay } from '../../components/DurationDisplay';
|
||||
import { FullscreenButton } from '../../components/FullscreenButton';
|
||||
import { MediaContainer } from '../../components/MediaContainer';
|
||||
import MuteButton from '../../components/MuteButton';
|
||||
import PlayButton from '../../components/PlayButton';
|
||||
import { Popover } from '../../components/Popover';
|
||||
import { PreviewTimeDisplay } from '../../components/PreviewTimeDisplay';
|
||||
import { TimeSlider } from '../../components/TimeSlider';
|
||||
import { Tooltip } from '../../components/Tooltip';
|
||||
import { VolumeSlider } from '../../components/VolumeSlider';
|
||||
@@ -36,7 +36,7 @@ export default function MediaSkinDefault({ children, className = '' }: SkinProps
|
||||
<div className={styles.Overlay} aria-hidden="true" />
|
||||
|
||||
<div className={styles.Controls} data-testid="media-controls">
|
||||
<Tooltip.Root delay={500} closeDelay={0}>
|
||||
<Tooltip.Root delay={500}>
|
||||
<Tooltip.Trigger>
|
||||
<PlayButton className={`${styles.Button} ${styles.IconButton} ${styles.PlayButton}`}>
|
||||
<PlayIcon className={`${styles.PlayIcon} ${styles.Icon}`} />
|
||||
@@ -60,13 +60,24 @@ export default function MediaSkinDefault({ children, className = '' }: SkinProps
|
||||
className={styles.TimeDisplay}
|
||||
/>
|
||||
|
||||
<TimeSlider.Root className={styles.SliderRoot}>
|
||||
<TimeSlider.Track className={styles.SliderTrack}>
|
||||
<TimeSlider.Progress className={styles.SliderProgress} />
|
||||
<TimeSlider.Pointer className={styles.SliderPointer} />
|
||||
</TimeSlider.Track>
|
||||
<TimeSlider.Thumb className={styles.SliderThumb} />
|
||||
</TimeSlider.Root>
|
||||
<Tooltip.Root trackCursorAxis="x">
|
||||
<Tooltip.Trigger>
|
||||
<TimeSlider.Root className={styles.SliderRoot}>
|
||||
<TimeSlider.Track className={styles.SliderTrack}>
|
||||
<TimeSlider.Progress className={styles.SliderProgress} />
|
||||
<TimeSlider.Pointer className={styles.SliderPointer} />
|
||||
</TimeSlider.Track>
|
||||
<TimeSlider.Thumb className={styles.SliderThumb} />
|
||||
</TimeSlider.Root>
|
||||
</Tooltip.Trigger>
|
||||
<Tooltip.Portal>
|
||||
<Tooltip.Positioner side="top" sideOffset={18} collisionPadding={12}>
|
||||
<Tooltip.Popup className={`${styles.TooltipPopup}`}>
|
||||
<PreviewTimeDisplay />
|
||||
</Tooltip.Popup>
|
||||
</Tooltip.Positioner>
|
||||
</Tooltip.Portal>
|
||||
</Tooltip.Root>
|
||||
|
||||
<DurationDisplay className={styles.TimeDisplay} />
|
||||
</div>
|
||||
@@ -93,7 +104,7 @@ export default function MediaSkinDefault({ children, className = '' }: SkinProps
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
|
||||
<Tooltip.Root delay={500} closeDelay={0}>
|
||||
<Tooltip.Root delay={500}>
|
||||
<Tooltip.Trigger>
|
||||
<FullscreenButton className={`${styles.Button} ${styles.IconButton} ${styles.FullscreenButton}`}>
|
||||
<FullscreenEnterIcon className={`${styles.FullscreenEnterIcon} ${styles.Icon}`} />
|
||||
|
||||
@@ -143,7 +143,7 @@ const styles: MediaDefaultSkinStyles = {
|
||||
),
|
||||
SliderProgress: cn('bg-white rounded-[inherit]'),
|
||||
// TODO: Work out what we want to do here.
|
||||
SliderPointer: cn('rounded-[inherit]'),
|
||||
SliderPointer: cn('bg-white/20 rounded-[inherit]'),
|
||||
SliderThumb: cn(
|
||||
'bg-white z-10 select-none ring ring-black/10 rounded-full shadow-sm shadow-black/15',
|
||||
'opacity-0 transition-[opacity,height,width] ease-in-out',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { FC, ReactElement } from 'react';
|
||||
import type { ReactElement } from 'react';
|
||||
|
||||
import { createContext, forwardRef, useCallback, useContext, useEffect, useRef, useSyncExternalStore } from 'react';
|
||||
|
||||
@@ -34,13 +34,12 @@ export function toConnectedComponent<
|
||||
defaultRender: TRenderFn,
|
||||
displayName: string,
|
||||
): ConnectedComponent<TProps, TRenderFn> {
|
||||
const ConnectedComponent = forwardRef<any, TProps & { render?: TRenderFn }>(
|
||||
const ConnectedComponent = forwardRef<HTMLElement, TProps & { render?: TRenderFn }>(
|
||||
({ render = defaultRender, ...props }, ref) => {
|
||||
const connectedState = useStateHook(props as TProps);
|
||||
const connectedProps = usePropsHook(props as TProps, connectedState);
|
||||
// Add ref to connectedProps if it exists
|
||||
const propsWithRef = ref ? { ...connectedProps, ref } : connectedProps;
|
||||
return <Context.Provider value={connectedState}>{render(propsWithRef, connectedState)}</Context.Provider>;
|
||||
const propsWithRef = ref ? { ...props, ref } : props;
|
||||
const connectedState = useStateHook(propsWithRef as unknown as TProps);
|
||||
const connectedProps = usePropsHook(propsWithRef as unknown as TProps, connectedState);
|
||||
return <Context.Provider value={connectedState}>{render(connectedProps, connectedState)}</Context.Provider>;
|
||||
},
|
||||
);
|
||||
|
||||
@@ -52,8 +51,8 @@ export function toConnectedComponent<
|
||||
/**
|
||||
* Type helper to infer the component type from the factory
|
||||
*/
|
||||
export type ConnectedComponent<TProps extends Record<string, any>, TRenderFn extends RenderFn<any, any>> = FC<
|
||||
TProps & { render?: TRenderFn }
|
||||
export type ConnectedComponent<TProps extends Record<string, any>, TRenderFn extends RenderFn<any, any>> = React.ForwardRefExoticComponent<
|
||||
React.PropsWithoutRef<TProps & { render?: TRenderFn }> & React.RefAttributes<HTMLElement>
|
||||
>;
|
||||
|
||||
/**
|
||||
@@ -74,13 +73,12 @@ export function toContextComponent<
|
||||
defaultRender: TRenderFn,
|
||||
displayName: string,
|
||||
): ContextComponent<TProps, TRenderFn> {
|
||||
const ContextComponent = forwardRef<any, TProps & { render?: TRenderFn }>(
|
||||
const ContextComponent = forwardRef<HTMLElement, TProps & { render?: TRenderFn }>(
|
||||
({ render = defaultRender, ...props }, ref) => {
|
||||
const context = useContext(Context);
|
||||
const contextProps = usePropsHook(props as TProps, context);
|
||||
// Add ref to contextProps if it exists
|
||||
const propsWithRef = ref ? { ...contextProps, ref } : contextProps;
|
||||
return render(propsWithRef, context);
|
||||
const propsWithRef = ref ? { ...props, ref } : props;
|
||||
const contextProps = usePropsHook(propsWithRef as unknown as TProps, context);
|
||||
return render(contextProps, context);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -95,7 +93,9 @@ export function toContextComponent<
|
||||
export type ContextComponent<
|
||||
TProps extends Record<string, any>,
|
||||
TRenderFn extends (props: any, context: any) => ReactElement,
|
||||
> = FC<TProps & { render?: TRenderFn }>;
|
||||
> = React.ForwardRefExoticComponent<
|
||||
React.PropsWithoutRef<TProps & { render?: TRenderFn }> & React.RefAttributes<any>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Hook that manages a CoreClass instance and triggers re-renders when state changes.
|
||||
|
||||
Reference in New Issue
Block a user