Skip to content

Commit

Permalink
feat(player): new spinner component
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Dec 2, 2023
1 parent 273d30b commit e0b0ab1
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 43 deletions.
26 changes: 7 additions & 19 deletions packages/react/src/components/layouts/default/video-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMediaState } from '../../../hooks/use-media-state';
import { Captions } from '../../ui/captions';
import * as Controls from '../../ui/controls';
import { Gesture } from '../../ui/gesture';
import * as Spinner from '../../ui/spinner';
import { Time } from '../../ui/time';
import { DefaultLayoutContext } from './context';
import {
Expand Down Expand Up @@ -180,23 +181,10 @@ export { DefaultVideoGestures };
function DefaultBufferingIndicator() {
return (
<div className="vds-buffering-indicator">
<svg className="vds-buffering-icon" fill="none" viewBox="0 0 120 120" aria-hidden="true">
<circle
className="vds-buffering-track"
cx="60"
cy="60"
r="54"
stroke="currentColor"
></circle>
<circle
className="vds-buffering-track-fill"
cx="60"
cy="60"
r="54"
stroke="currentColor"
pathLength="100"
></circle>
</svg>
<Spinner.Root className="vds-buffering-spinner">
<Spinner.Track className="vds-buffering-track" />
<Spinner.TrackFill className="vds-buffering-track-fill" />
</Spinner.Root>
</div>
);
}
Expand All @@ -215,8 +203,8 @@ function DefaultVideoMenus() {
placement = noModal
? (`${side} end` as const)
: !isSmallLayout
? (`${side} end` as const)
: null;
? (`${side} end` as const)
: null;
return (
<>
<DefaultChaptersMenu tooltip={tooltip} placement={placement} portalClass="vds-video-layout" />
Expand Down
103 changes: 103 additions & 0 deletions packages/react/src/components/ui/spinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import * as React from 'react';

/* -------------------------------------------------------------------------------------------------
* Spinner
* -----------------------------------------------------------------------------------------------*/

export interface RootProps
extends React.PropsWithoutRef<React.SVGProps<SVGSVGElement>>,
React.RefAttributes<SVGElement | SVGSVGElement> {
/**
* The horizontal (width) and vertical (height) length of the spinner.
*/
size?: number;
}

/**
* @docs {@link https://www.vidstack.io/docs/player/components/display/buffering-indicator}
* @example
* ```html
* <Spinner.Root>
* <Spinner.Track />
* <Spinner.TrackFill />
* </Spinner>
* ```
*/
const Root = React.forwardRef<SVGElement | SVGSVGElement, RootProps>(
({ size, children, ...props }: RootProps, forwardRef) => {
return (
<svg
width={size}
height={size}
fill="none"
viewBox="0 0 120 120"
aria-hidden="true"
data-part="root"
{...props}
ref={forwardRef as any}
>
{children}
</svg>
);
},
);

/* -------------------------------------------------------------------------------------------------
* Track
* -----------------------------------------------------------------------------------------------*/

export interface TrackProps
extends React.PropsWithoutRef<React.SVGProps<SVGCircleElement>>,
React.RefAttributes<SVGCircleElement> {}

const Track = React.forwardRef<SVGCircleElement, TrackProps>(
({ width, children, ...props }, ref) => (
<circle
cx="60"
cy="60"
r="54"
stroke="currentColor"
strokeWidth={width}
data-part="track"
{...props}
ref={ref}
>
{children}
</circle>
),
);

/* -------------------------------------------------------------------------------------------------
* TrackFill
* -----------------------------------------------------------------------------------------------*/

export interface TrackFillProps
extends React.PropsWithoutRef<React.SVGProps<SVGCircleElement>>,
React.RefAttributes<SVGCircleElement> {
/**
* The percentage of the track that should be filled.
*/
fillPercent?: number;
}

const TrackFill = React.forwardRef<SVGCircleElement, TrackFillProps>(
({ width, fillPercent = 50, children, ...props }, ref) => (
<circle
cx="60"
cy="60"
r="54"
stroke="currentColor"
pathLength="100"
strokeWidth={width}
strokeDasharray={100}
strokeDashoffset={100 - fillPercent}
data-part="track-fill"
{...props}
ref={ref}
>
{children}
</circle>
),
);

export { Root, Track, TrackFill };
16 changes: 14 additions & 2 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,19 +78,31 @@ export type {
export { ChapterTitle, type ChapterTitleProps } from './components/ui/chapter-title';
export { type GestureProps, Gesture } from './components/ui/gesture';
export { Captions, type CaptionsProps } from './components/ui/captions';
export { type PosterProps, Poster } from './components/ui/poster';
export { type TimeProps, Time } from './components/ui/time';

// Caption
export * as Caption from './components/ui/caption';
export type {
RootProps as CaptionProps,
TextProps as CaptionTextProps,
} from './components/ui/caption';
export { type PosterProps, Poster } from './components/ui/poster';
export { type TimeProps, Time } from './components/ui/time';

// Thumbnail
export * as Thumbnail from './components/ui/thumbnail';
export type {
RootProps as ThumbnailProps,
ImgProps as ThumbnailImgProps,
} from './components/ui/thumbnail';

// Spinner
export * as Spinner from './components/ui/spinner';
export type {
RootProps as SpinnerProps,
TrackProps as SpinnerTrackProps,
TrackFillProps as SpinnerTrackFillProps,
} from './components/ui/spinner';

// Hooks
export * from './hooks/use-state';
export * from './hooks/use-media-player';
Expand Down
26 changes: 15 additions & 11 deletions packages/vidstack/player/styles/default/buffering.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,33 @@
z-index: 1;
}

:where(.vds-buffering-indicator .vds-buffering-icon) {
width: var(--media-buffering-size, 84px);
height: var(--media-buffering-size, 84px);
:where(.vds-buffering-indicator) :where(.vds-buffering-icon, .vds-buffering-spinner) {
opacity: 0;
pointer-events: none;
transition: var(--media-buffering-transition, opacity 200ms ease);
}

:where(.vds-buffering-indicator .vds-buffering-track) {
color: var(--media-buffering-track-color, #f5f5f5);
:where(.vds-buffering-indicator)
:where(.vds-buffering-icon, svg.vds-buffering-spinner, .vds-buffering-spinner svg) {
width: var(--media-buffering-size, 84px) !important;
height: var(--media-buffering-size, 84px) !important;
}

:where(.vds-buffering-indicator) :where(.vds-buffering-track, circle[data-part='track']) {
color: var(--media-buffering-track-color, #f5f5f5) !important;
opacity: var(--media-buffering-track-opacity, 0.25);
stroke-width: var(--media-buffering-track-width, 8);
stroke-width: var(--media-buffering-track-width, 8) !important;
}

:where(.vds-buffering-indicator .vds-buffering-track-fill) {
color: var(--media-buffering-track-fill-color, var(--media-brand));
:where(.vds-buffering-indicator) :where(.vds-buffering-track-fill, circle[data-part='track-fill']) {
color: var(--media-buffering-track-fill-color, var(--media-brand)) !important;
opacity: var(--media-buffering-track-fill-opacity, 0.75);
stroke-width: var(--media-buffering-track-fill-width, 9);
stroke-width: var(--media-buffering-track-fill-width, 9) !important;
stroke-dasharray: 100;
stroke-dashoffset: var(--media-buffering-track-fill-offset, 50);
stroke-dashoffset: var(--media-buffering-track-fill-offset, 50) !important;
}

:where([data-buffering] .vds-buffering-icon) {
:where([data-buffering]) :where(.vds-buffering-icon, .vds-buffering-spinner) {
opacity: 1;
animation: var(--media-buffering-animation, vds-buffering-spin 1s linear infinite);
}
Expand Down
2 changes: 2 additions & 0 deletions packages/vidstack/src/elements/bundles/player-ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { MediaSliderValueElement } from '../define/sliders/slider-value-element'
import { MediaSliderVideoElement } from '../define/sliders/slider-video-element';
import { MediaTimeSliderElement } from '../define/sliders/time-slider-element';
import { MediaVolumeSliderElement } from '../define/sliders/volume-slider-element';
import { MediaSpinnerElement } from '../define/spinner-element';
import { MediaThumbnailElement } from '../define/thumbnail-element';
import { MediaTimeElement } from '../define/time-element';
import { MediaTooltipContentElement } from '../define/tooltips/tooltip-content-element';
Expand Down Expand Up @@ -86,3 +87,4 @@ defineCustomElement(MediaCaptionsElement);
defineCustomElement(MediaLiveButtonElement);
defineCustomElement(MediaTimeElement);
defineCustomElement(MediaChapterTitleElement);
defineCustomElement(MediaSpinnerElement);
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,7 @@ function StartDuration() {
export function DefaultBufferingIndicator() {
return html`
<div class="vds-buffering-indicator">
<svg class="vds-buffering-icon" fill="none" viewBox="0 0 120 120" aria-hidden="true">
<circle class="vds-buffering-track" cx="60" cy="60" r="54" stroke="currentColor"></circle>
<circle
class="vds-buffering-track-fill"
cx="60"
cy="60"
r="54"
stroke="currentColor"
pathLength="100"
></circle>
</svg>
<media-spinner class="vds-buffering-spinner"></media-spinner>
</div>
`;
}
Expand Down
61 changes: 61 additions & 0 deletions packages/vidstack/src/elements/define/spinner-element.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { html } from 'lit-html';

import { LitElement } from '../lit/lit-element';

/**
* @docs {@link https://www.vidstack.io/docs/wc/player/components/display/buffering-indicator}
* @example
* ```html
* <media-spinner></media-spinner>
* ```
* @example
* ```css
* media-spinner {
* --size: 84px;
* --track-width: 8px;
* --track-color: rgb(255 255 255 / 0.5);
* --track-fill-color: white;
* --track-fill-percent: 50;
* }
* ```
*/
export class MediaSpinnerElement extends LitElement {
static tagName = 'media-spinner';

render() {
return html`
<svg
fill="none"
viewBox="0 0 120 120"
aria-hidden="true"
data-part="root"
style="width: var(--size); height: var(--size)"
>
<circle
cx="60"
cy="60"
r="54"
stroke="currentColor"
data-part="track"
style="color: var(--track-color); stroke-width: var(--track-width);"
></circle>
<circle
cx="60"
cy="60"
r="54"
stroke="currentColor"
pathLength="100"
stroke-dasharray="100"
data-part="track-fill"
style="color: var(--track-fill-color); stroke-width: var(--track-width); stroke-dashoffset: calc(100 - var(--track-fill-percent, 50));"
></circle>
</svg>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
'media-spinner': MediaSpinnerElement;
}
}
1 change: 1 addition & 0 deletions packages/vidstack/src/elements/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export { MediaTimeElement } from './define/time-element';
export { MediaControlsElement } from './define/controls-element';
export { MediaControlsGroupElement } from './define/controls-group-element';
export { MediaChapterTitleElement } from './define/chapter-title-element';
export { MediaSpinnerElement } from './define/spinner-element';

// Layouts
export { MediaLayoutElement } from './define/layouts/layout-element';
Expand Down

0 comments on commit e0b0ab1

Please sign in to comment.