mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(packages): error dialog component (#1077)
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
'use client';
|
||||
|
||||
import type { MediaError } from '@videojs/core';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export interface ErrorDialogContextValue {
|
||||
lastError: MediaError | null;
|
||||
}
|
||||
|
||||
const ErrorDialogContext = createContext<ErrorDialogContextValue | null>(null);
|
||||
|
||||
export const ErrorDialogContextProvider = ErrorDialogContext.Provider;
|
||||
|
||||
export function useErrorDialogContext(): ErrorDialogContextValue {
|
||||
const ctx = useContext(ErrorDialogContext);
|
||||
if (!ctx) throw new Error('ErrorDialog compound components must be used within an ErrorDialog.Root');
|
||||
return ctx;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import type { AlertDialogCore } from '@videojs/core';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
import { useAlertDialogContext } from '../alert-dialog/context';
|
||||
import { useErrorDialogContext } from './context';
|
||||
|
||||
const FALLBACK_MESSAGE = 'An error occurred. Please try again.';
|
||||
|
||||
export interface ErrorDialogDescriptionProps extends UIComponentProps<'p', AlertDialogCore.State> {}
|
||||
|
||||
export const ErrorDialogDescription = forwardRef<HTMLParagraphElement, ErrorDialogDescriptionProps>(
|
||||
function ErrorDialogDescription({ render, className, style, children, ...elementProps }, forwardedRef) {
|
||||
const { state, stateAttrMap } = useAlertDialogContext();
|
||||
const { lastError } = useErrorDialogContext();
|
||||
|
||||
return renderElement(
|
||||
'p',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
stateAttrMap,
|
||||
ref: forwardedRef,
|
||||
props: [
|
||||
{ id: state.descriptionId, children: children ?? lastError?.message ?? FALLBACK_MESSAGE },
|
||||
elementProps,
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export namespace ErrorDialogDescription {
|
||||
export type Props = ErrorDialogDescriptionProps;
|
||||
export type State = AlertDialogCore.State;
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
'use client';
|
||||
|
||||
import { AlertDialogDataAttrs, ErrorDialogCore } from '@videojs/core';
|
||||
import { createAlertDialog, createTransition, selectError } from '@videojs/core/dom';
|
||||
import { useSnapshot } from '@videojs/store/react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { usePlayer } from '../../player/context';
|
||||
import { useDestroy } from '../../utils/use-destroy';
|
||||
import { useLatestRef } from '../../utils/use-latest-ref';
|
||||
import { useSafeId } from '../../utils/use-safe-id';
|
||||
import { AlertDialogContextProvider } from '../alert-dialog/context';
|
||||
import { ErrorDialogContextProvider } from './context';
|
||||
|
||||
export interface ErrorDialogRootProps {
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export function ErrorDialogRoot({ children }: ErrorDialogRootProps): ReactNode {
|
||||
const [core] = useState(() => new ErrorDialogCore());
|
||||
const errorState = usePlayer(selectError);
|
||||
const lastError = useRef(errorState?.error ?? null);
|
||||
|
||||
if (errorState?.error) lastError.current = errorState.error;
|
||||
|
||||
const errorStateRef = useLatestRef(errorState);
|
||||
|
||||
const [dialog] = useState(() =>
|
||||
createAlertDialog({
|
||||
transition: createTransition(),
|
||||
onOpenChange: (nextOpen: boolean) => {
|
||||
if (!nextOpen) {
|
||||
errorStateRef.current?.dismissError();
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
const titleId = useSafeId('error-dialog-title');
|
||||
const descriptionId = useSafeId('error-dialog-desc');
|
||||
|
||||
core.setTitleId(titleId);
|
||||
core.setDescriptionId(descriptionId);
|
||||
|
||||
useEffect(() => {
|
||||
const hasError = Boolean(errorState?.error);
|
||||
const { active: isOpen } = dialog.input.current;
|
||||
|
||||
if (hasError && !isOpen) {
|
||||
dialog.open();
|
||||
} else if (!hasError && isOpen) {
|
||||
dialog.close();
|
||||
}
|
||||
}, [errorState?.error, dialog]);
|
||||
|
||||
useDestroy(dialog);
|
||||
|
||||
const input = useSnapshot(dialog.input);
|
||||
core.setInput(input);
|
||||
const state = core.getState();
|
||||
|
||||
if (!errorState) return null;
|
||||
|
||||
return (
|
||||
<ErrorDialogContextProvider value={{ lastError: lastError.current }}>
|
||||
<AlertDialogContextProvider value={{ core, dialog, state, stateAttrMap: AlertDialogDataAttrs }}>
|
||||
{children}
|
||||
</AlertDialogContextProvider>
|
||||
</ErrorDialogContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export namespace ErrorDialogRoot {
|
||||
export type Props = ErrorDialogRootProps;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
export {
|
||||
AlertDialogClose as Close,
|
||||
type AlertDialogCloseProps as CloseProps,
|
||||
} from '../alert-dialog/alert-dialog-close';
|
||||
export {
|
||||
AlertDialogPopup as Popup,
|
||||
type AlertDialogPopupProps as PopupProps,
|
||||
} from '../alert-dialog/alert-dialog-popup';
|
||||
export {
|
||||
AlertDialogTitle as Title,
|
||||
type AlertDialogTitleProps as TitleProps,
|
||||
} from '../alert-dialog/alert-dialog-title';
|
||||
export {
|
||||
ErrorDialogDescription as Description,
|
||||
type ErrorDialogDescriptionProps as DescriptionProps,
|
||||
} from './error-dialog-description';
|
||||
export { ErrorDialogRoot as Root, type ErrorDialogRootProps as RootProps } from './error-dialog-root';
|
||||
@@ -0,0 +1,2 @@
|
||||
export { type ErrorDialogContextValue, useErrorDialogContext } from './context';
|
||||
export * as ErrorDialog from './index.parts';
|
||||
Reference in New Issue
Block a user