mirror of
https://github.com/zoriya/v10.git
synced 2026-08-16 02:45:09 +00:00
feat(core): support nested component parts
This commit is contained in:
@@ -29,10 +29,10 @@ describe('skins compiler config', () => {
|
||||
expect(code).toContain('from "@/ui/play-button"');
|
||||
expect(code).toContain('from "@/ui/poster"');
|
||||
expect(code).toContain('from "@/ui/seek-indicator"');
|
||||
expect(code).toContain('from "@/ui/slider"');
|
||||
expect(code).toContain('from "@/ui/status-indicator"');
|
||||
expect(code).toContain('from "@/ui/volume-indicator"');
|
||||
expect(code).not.toContain('from "@/ui/text"');
|
||||
expect(code).not.toContain('from "@/ui/slider"');
|
||||
expect(code).toContain('from "@/utils/use-render"');
|
||||
expect(code).toContain('from "@videojs/utils/predicate"');
|
||||
expect(code).toContain('from "@/icons"');
|
||||
@@ -123,6 +123,10 @@ describe('skins compiler config', () => {
|
||||
expect(compactCode).toContain(
|
||||
compact('<TimeSlider.Thumb className={cn(slider.thumb.base, slider.thumb.interactive)} />')
|
||||
);
|
||||
expect(compactCode).toContain(compact('<Slider.Thumbnail.Root className={thumbnail.root}>'));
|
||||
expect(compactCode).toContain(compact('<Slider.Thumbnail.Image className={thumbnail.image} />'));
|
||||
expect(compactCode).toContain(compact('<TimeSlider.Value type="pointer" className={thumbnail.time} />'));
|
||||
expect(compactCode).toContain(compact('<SpinnerIcon className={cn(icon, thumbnail.spinner)} />'));
|
||||
expect(compactCode).toContain(compact('<TimeSlider.Preview className={slider.preview}>'));
|
||||
expect(compactCode).toContain(compact('<TimeSlider.Value type="pointer" className={slider.value} />'));
|
||||
expect(compactCode).not.toContain('SliderTrack');
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
poster as posterClass,
|
||||
seek,
|
||||
slider,
|
||||
thumbnail,
|
||||
time,
|
||||
} from '@videojs/skins/default/tailwind/video.tailwind';
|
||||
import { isString } from '@videojs/utils/predicate';
|
||||
@@ -62,6 +63,7 @@ import { PlayButton } from '@/ui/play-button';
|
||||
import { Poster } from '@/ui/poster';
|
||||
import { SeekButton } from '@/ui/seek-button';
|
||||
import { SeekIndicator } from '@/ui/seek-indicator';
|
||||
import { Slider } from '@/ui/slider';
|
||||
import { StatusAnnouncer } from '@/ui/status-announcer';
|
||||
import { StatusIndicator } from '@/ui/status-indicator';
|
||||
import { Time } from '@/ui/time';
|
||||
@@ -167,6 +169,11 @@ export function DefaultVideoSkin({ className, children, poster, ...rest }: Defau
|
||||
<TimeSlider.Buffer className={cn(slider.fill.base, slider.fill.buffer)} />
|
||||
</TimeSlider.Track>
|
||||
<TimeSlider.Thumb className={cn(slider.thumb.base, slider.thumb.interactive)} />
|
||||
<Slider.Thumbnail.Root className={thumbnail.root}>
|
||||
<Slider.Thumbnail.Image className={thumbnail.image} />
|
||||
<TimeSlider.Value type="pointer" className={thumbnail.time} />
|
||||
<SpinnerIcon className={cn(icon, thumbnail.spinner)} />
|
||||
</Slider.Thumbnail.Root>
|
||||
<TimeSlider.Preview className={slider.preview}>
|
||||
<TimeSlider.Value type="pointer" className={slider.value} />
|
||||
</TimeSlider.Preview>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
|
||||
import type { SliderState } from '@videojs/core';
|
||||
|
||||
import type { UIComponentProps } from '../../utils/types';
|
||||
import { createContextPart } from '../create-context-part';
|
||||
import { useSliderContext } from './context';
|
||||
|
||||
export interface SliderThumbnailRootProps extends UIComponentProps<'div', SliderState> {}
|
||||
|
||||
export const SliderThumbnailRoot = createContextPart<SliderThumbnailRootProps, SliderState>({
|
||||
displayName: 'SliderThumbnailRoot',
|
||||
tag: 'div',
|
||||
useContext: useSliderContext,
|
||||
});
|
||||
|
||||
export namespace SliderThumbnailRoot {
|
||||
export type Props = SliderThumbnailRootProps;
|
||||
}
|
||||
@@ -5,17 +5,25 @@ import { forwardRef } from 'react';
|
||||
|
||||
import { Thumbnail, type ThumbnailProps } from '../thumbnail/thumbnail';
|
||||
import { useSliderContext } from './context';
|
||||
import { SliderThumbnailRoot, type SliderThumbnailRootProps } from './slider-thumbnail-root';
|
||||
|
||||
export interface SliderThumbnailProps extends Omit<ThumbnailProps, 'time'> {}
|
||||
|
||||
export const SliderThumbnail = forwardRef<HTMLDivElement, SliderThumbnailProps>(
|
||||
const SliderThumbnailImage = forwardRef<HTMLDivElement, SliderThumbnailProps>(
|
||||
function SliderThumbnail(componentProps, forwardedRef) {
|
||||
const { pointerValue } = useSliderContext();
|
||||
return <Thumbnail ref={forwardedRef} {...componentProps} time={pointerValue} />;
|
||||
}
|
||||
);
|
||||
|
||||
export const SliderThumbnail = Object.assign(SliderThumbnailImage, {
|
||||
Root: SliderThumbnailRoot,
|
||||
Image: SliderThumbnailImage,
|
||||
});
|
||||
|
||||
export namespace SliderThumbnail {
|
||||
export type Props = SliderThumbnailProps;
|
||||
export type RootProps = SliderThumbnailRootProps;
|
||||
export type ImageProps = SliderThumbnailProps;
|
||||
export type State = ThumbnailCore.State;
|
||||
}
|
||||
|
||||
@@ -74,6 +74,19 @@ describe('SliderThumbnail', () => {
|
||||
expect(container.querySelector('[data-testid="thumbnail"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders namespaced root and image parts', () => {
|
||||
const { container } = render(
|
||||
<SliderRoot>
|
||||
<SliderThumbnail.Root data-testid="thumbnail-root">
|
||||
<SliderThumbnail.Image data-testid="thumbnail-image" />
|
||||
</SliderThumbnail.Root>
|
||||
</SliderRoot>
|
||||
);
|
||||
|
||||
expect(container.querySelector('[data-testid="thumbnail-root"]')).toBeTruthy();
|
||||
expect(container.querySelector('[data-testid="thumbnail-image"]')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('throws outside of SliderRoot', () => {
|
||||
expect(() => render(<SliderThumbnail />)).toThrow('Slider compound components must be used within a Slider.Root');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user