mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
fix(packages): make tooltips visual-only and auto-forward media button labels (#1174)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,17 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import type { InferComponentState, InferMediaState, MediaUIComponent, StateAttrMap } from '@videojs/core';
|
||||
import type { InferComponentState, InferMediaState, MediaButtonComponent, StateAttrMap } from '@videojs/core';
|
||||
import { logMissingFeature } from '@videojs/core/dom';
|
||||
import type { Selector } from '@videojs/store';
|
||||
import type { ForwardedRef, ForwardRefExoticComponent, RefAttributes } from 'react';
|
||||
import { forwardRef, useState } from 'react';
|
||||
import { forwardRef, useLayoutEffect, useState } from 'react';
|
||||
|
||||
import { usePlayer } from '../player/context';
|
||||
import type { renderElement as renderElementFn } from '../utils/use-render';
|
||||
import { renderElement } from '../utils/use-render';
|
||||
import { useButton } from './hooks/use-button';
|
||||
import { useOptionalTooltipContext } from './tooltip/context';
|
||||
|
||||
interface MediaButtonConfig<Core extends Required<MediaUIComponent>> {
|
||||
interface MediaButtonConfig<Core extends Required<MediaButtonComponent>> {
|
||||
displayName: string;
|
||||
core: { new (): Core; defaultProps: Record<string, unknown> };
|
||||
stateAttrMap: StateAttrMap<InferComponentState<Core>>;
|
||||
@@ -20,7 +21,7 @@ interface MediaButtonConfig<Core extends Required<MediaUIComponent>> {
|
||||
}
|
||||
|
||||
/** Creates a media button React component from a core class and config. */
|
||||
export function createMediaButton<Core extends Required<MediaUIComponent>, Props extends object>(
|
||||
export function createMediaButton<Core extends Required<MediaButtonComponent>, Props extends object>(
|
||||
config: MediaButtonConfig<Core>
|
||||
): ForwardRefExoticComponent<Props & RefAttributes<HTMLButtonElement>> {
|
||||
const { displayName, core: CoreClass, stateAttrMap, selector, action } = config;
|
||||
@@ -45,6 +46,7 @@ export function createMediaButton<Core extends Required<MediaUIComponent>, Props
|
||||
}
|
||||
}
|
||||
|
||||
const tooltipCtx = useOptionalTooltipContext();
|
||||
const feature = usePlayer(selector);
|
||||
|
||||
const [core] = useState(() => new CoreClass());
|
||||
@@ -56,15 +58,26 @@ export function createMediaButton<Core extends Required<MediaUIComponent>, Props
|
||||
isDisabled: () => !!coreProps.disabled || !feature,
|
||||
});
|
||||
|
||||
if (!feature) {
|
||||
// Derive state and label before the hooks boundary so the
|
||||
// useLayoutEffect below (called unconditionally) can reference them.
|
||||
type State = InferComponentState<Core>;
|
||||
if (feature) core.setMedia(feature);
|
||||
const state = feature ? (core.getState() as State) : null;
|
||||
const label = state ? core.getLabel(state) : undefined;
|
||||
|
||||
// Forward label to tooltip popup content when inside a Tooltip.Root.
|
||||
useLayoutEffect(() => {
|
||||
if (!tooltipCtx) return;
|
||||
tooltipCtx.setContent(label);
|
||||
return () => tooltipCtx.setContent(undefined);
|
||||
}, [tooltipCtx, label]);
|
||||
|
||||
if (!feature || !state) {
|
||||
if (__DEV__) logMissingFeature(displayName, selector.displayName ?? displayName);
|
||||
return null;
|
||||
}
|
||||
|
||||
type State = InferComponentState<Core>;
|
||||
|
||||
core.setMedia(feature);
|
||||
const state = core.getState() as State;
|
||||
const attrs = core.getAttrs(state);
|
||||
|
||||
return renderElement(
|
||||
'button',
|
||||
@@ -73,7 +86,7 @@ export function createMediaButton<Core extends Required<MediaUIComponent>, Props
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: [forwardedRef, buttonRef],
|
||||
props: [core.getAttrs(state), elementProps, getButtonProps()],
|
||||
props: [attrs, elementProps, getButtonProps()],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,8 @@ export interface TooltipContextValue {
|
||||
stateAttrMap: StateAttrMap<TooltipCore.State>;
|
||||
anchorName: string;
|
||||
popupId: string;
|
||||
content: string | undefined;
|
||||
setContent: (content: string | undefined) => void;
|
||||
}
|
||||
|
||||
const TooltipContext = createContext<TooltipContextValue | null>(null);
|
||||
@@ -22,3 +24,7 @@ export function useTooltipContext(): TooltipContextValue {
|
||||
if (!ctx) throw new Error('Tooltip compound components must be used within a Tooltip.Root');
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function useOptionalTooltipContext(): TooltipContextValue | null {
|
||||
return useContext(TooltipContext);
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { type TooltipContextValue, useTooltipContext } from './context';
|
||||
export { type TooltipContextValue, useOptionalTooltipContext, useTooltipContext } from './context';
|
||||
export * as Tooltip from './index.parts';
|
||||
|
||||
@@ -21,7 +21,7 @@ export const TooltipPopup = forwardRef<HTMLDivElement, TooltipPopupProps>(functi
|
||||
{ render, className, style, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { core, tooltip, state, stateAttrMap, anchorName, popupId } = useTooltipContext();
|
||||
const { core, tooltip, state, stateAttrMap, anchorName, popupId, content } = useTooltipContext();
|
||||
const internalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const popupRef = useCallback(
|
||||
@@ -156,6 +156,8 @@ export const TooltipPopup = forwardRef<HTMLDivElement, TooltipPopupProps>(functi
|
||||
style: positioningStyle,
|
||||
...core.getPopupAttrs(state),
|
||||
},
|
||||
// Forwarded content as default children — explicit children override.
|
||||
{ children: content },
|
||||
{ ...restPopupProps, onBlur: onFocusOut },
|
||||
elementProps,
|
||||
],
|
||||
|
||||
@@ -73,6 +73,8 @@ export function TooltipRoot({
|
||||
return instance;
|
||||
});
|
||||
|
||||
const [content, setContent] = useState<string | undefined>();
|
||||
|
||||
const anchorName = useSafeId();
|
||||
const popupId = useSafeId('tooltip');
|
||||
|
||||
@@ -97,7 +99,9 @@ export function TooltipRoot({
|
||||
const state = core.getState();
|
||||
|
||||
return (
|
||||
<TooltipContextProvider value={{ core, tooltip, state, stateAttrMap: TooltipDataAttrs, anchorName, popupId }}>
|
||||
<TooltipContextProvider
|
||||
value={{ core, tooltip, state, stateAttrMap: TooltipDataAttrs, anchorName, popupId, content, setContent }}
|
||||
>
|
||||
{children}
|
||||
</TooltipContextProvider>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ export const TooltipTrigger = forwardRef<HTMLButtonElement, TooltipTriggerProps>
|
||||
{ render, className, style, ...elementProps },
|
||||
forwardedRef
|
||||
) {
|
||||
const { core, tooltip, state, stateAttrMap, anchorName, popupId } = useTooltipContext();
|
||||
const { tooltip, state, stateAttrMap, anchorName } = useTooltipContext();
|
||||
|
||||
const triggerRef = useCallback(
|
||||
(el: HTMLButtonElement | null) => {
|
||||
@@ -42,7 +42,6 @@ export const TooltipTrigger = forwardRef<HTMLButtonElement, TooltipTriggerProps>
|
||||
props: [
|
||||
{
|
||||
type: 'button' as const,
|
||||
...core.getTriggerAttrs(state, popupId),
|
||||
},
|
||||
{ ...restTriggerProps, onFocus: onFocusIn, onBlur: onFocusOut },
|
||||
elementProps,
|
||||
|
||||
Reference in New Issue
Block a user