mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(core): add time display component (#460)
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
export { Group, type GroupProps } from './time-group';
|
||||
export { Separator, type SeparatorProps } from './time-separator';
|
||||
export { Value, type ValueProps } from './time-value';
|
||||
@@ -0,0 +1 @@
|
||||
export * as Time from './index.parts';
|
||||
@@ -0,0 +1,50 @@
|
||||
'use client';
|
||||
|
||||
import type { ForwardedRef, ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
|
||||
// Empty state for Group (no dynamic state)
|
||||
type GroupState = Record<string, never>;
|
||||
|
||||
export interface GroupProps extends UIComponentProps<'span', GroupState> {
|
||||
/** Time value components to render inside the group. */
|
||||
children?: ReactNode | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Container for composed time displays. Renders a `<span>` element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Time.Group>
|
||||
* <Time.Value type="current" />
|
||||
* <Time.Separator />
|
||||
* <Time.Value type="duration" />
|
||||
* </Time.Group>
|
||||
* ```
|
||||
*/
|
||||
export const Group = forwardRef(function Group(
|
||||
componentProps: GroupProps,
|
||||
forwardedRef: ForwardedRef<HTMLSpanElement>
|
||||
) {
|
||||
const { render, className, style, children, ...elementProps } = componentProps;
|
||||
|
||||
const state: GroupState = {};
|
||||
|
||||
return renderElement(
|
||||
'span',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
ref: [forwardedRef],
|
||||
props: [{ children }, elementProps],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export namespace Group {
|
||||
export type Props = GroupProps;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
'use client';
|
||||
|
||||
import type { ForwardedRef, ReactNode } from 'react';
|
||||
import { forwardRef } from 'react';
|
||||
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
|
||||
// Empty state for Separator (no dynamic state)
|
||||
type SeparatorState = Record<string, never>;
|
||||
|
||||
export interface SeparatorProps extends UIComponentProps<'span', SeparatorState> {
|
||||
/** Separator content. Defaults to "/". */
|
||||
children?: ReactNode | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Divider between time values. Hidden from screen readers.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Time.Separator />
|
||||
* <Time.Separator> of </Time.Separator>
|
||||
* ```
|
||||
*/
|
||||
export const Separator = forwardRef(function Separator(
|
||||
componentProps: SeparatorProps,
|
||||
forwardedRef: ForwardedRef<HTMLSpanElement>
|
||||
) {
|
||||
const { render, className, style, children = '/', ...elementProps } = componentProps;
|
||||
|
||||
const state: SeparatorState = {};
|
||||
|
||||
return renderElement(
|
||||
'span',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
ref: [forwardedRef],
|
||||
props: [{ 'aria-hidden': 'true', children }, elementProps],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export namespace Separator {
|
||||
export type Props = SeparatorProps;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
'use client';
|
||||
|
||||
import { TimeCore } from '@videojs/core';
|
||||
import { logMissingFeature, selectTime } from '@videojs/core/dom';
|
||||
import type { ForwardedRef } from 'react';
|
||||
import { forwardRef, useState } from 'react';
|
||||
|
||||
import { usePlayer } from '../../player/context';
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { renderElement } from '../../utils/use-render';
|
||||
|
||||
export interface ValueProps extends Omit<UIComponentProps<'time', TimeCore.State>, 'children'>, TimeCore.Props {}
|
||||
|
||||
/**
|
||||
* Displays a formatted time value (current, duration, or remaining).
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <Time.Value />
|
||||
* <Time.Value type="duration" />
|
||||
* <Time.Value type="remaining" negativeSign="−" />
|
||||
* ```
|
||||
*/
|
||||
export const Value = forwardRef(function Value(
|
||||
componentProps: ValueProps,
|
||||
forwardedRef: ForwardedRef<HTMLTimeElement>
|
||||
) {
|
||||
const { render, className, style, type, negativeSign, label, ...elementProps } = componentProps;
|
||||
|
||||
const time = usePlayer(selectTime);
|
||||
|
||||
const [core] = useState(() => new TimeCore());
|
||||
core.setProps({ type, negativeSign, label });
|
||||
|
||||
if (!time) {
|
||||
logMissingFeature('Time.Value', 'time');
|
||||
return null;
|
||||
}
|
||||
|
||||
const state = core.getState(time);
|
||||
|
||||
// Render negative sign as aria-hidden span for remaining time
|
||||
const content =
|
||||
state.type === 'remaining' && state.seconds < 0 ? (
|
||||
<>
|
||||
<span aria-hidden="true">{negativeSign ?? '-'}</span>
|
||||
{state.text.replace(/^-/, '')}
|
||||
</>
|
||||
) : (
|
||||
state.text
|
||||
);
|
||||
|
||||
return renderElement(
|
||||
'time',
|
||||
{ render, className, style },
|
||||
{
|
||||
state,
|
||||
ref: [forwardedRef],
|
||||
props: [
|
||||
{
|
||||
datetime: state.datetime,
|
||||
children: content,
|
||||
...core.getAttrs(time),
|
||||
},
|
||||
elementProps,
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export namespace Value {
|
||||
export type Props = ValueProps;
|
||||
export type State = TimeCore.State;
|
||||
}
|
||||
Reference in New Issue
Block a user