mirror of
https://github.com/zoriya/v10.git
synced 2026-08-05 05:37:21 +00:00
fix: design tweaks to toasted skin, lint rule tweaks (#44)
This commit is contained in:
+6
-2
@@ -1,5 +1,4 @@
|
||||
import antfu from '@antfu/eslint-config';
|
||||
|
||||
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
||||
|
||||
export default antfu({
|
||||
@@ -23,9 +22,14 @@ export default antfu({
|
||||
plugins: {
|
||||
'jsx-a11y': jsxA11y,
|
||||
},
|
||||
}, {
|
||||
rules: {
|
||||
// Allow single line bracing.
|
||||
'antfu/if-newline': 'off',
|
||||
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }],
|
||||
// Only quote props when necessary.
|
||||
'style/quote-props': ['error', 'as-needed'],
|
||||
// JSX A11Y plugin rules
|
||||
...jsxA11y.configs.recommended.rules,
|
||||
},
|
||||
}, {
|
||||
files: ['**/*.md'],
|
||||
|
||||
@@ -52,8 +52,8 @@ const DEFAULT_SKIN: SkinKey = 'toasted';
|
||||
const DEFAULT_MEDIA_SOURCE: MediaSourceKey = '1';
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
const [skinKey, setSkinKey] = useState<SkinKey>(getParam('skin', DEFAULT_SKIN));
|
||||
const [mediaSourceKey, setMediaSourceKey] = useState<MediaSourceKey>(getParam('source', DEFAULT_MEDIA_SOURCE));
|
||||
const [skinKey, setSkinKey] = useState<SkinKey>(() => getParam('skin', DEFAULT_SKIN));
|
||||
const [mediaSourceKey, setMediaSourceKey] = useState<MediaSourceKey>(() => getParam('source', DEFAULT_MEDIA_SOURCE));
|
||||
|
||||
const mediaSource = useMemo(() => {
|
||||
let match = mediaSources.find(m => m.key === mediaSourceKey);
|
||||
@@ -138,10 +138,10 @@ export default function App(): JSX.Element {
|
||||
</header>
|
||||
|
||||
<main className="min-h-screen flex justify-center items-center bg-radial bg-size-[16px_16px] from-stone-300 dark:from-stone-700 via-10% via-transparent to-transparent">
|
||||
<div className="w-full max-w-4xl mx-auto p-6">
|
||||
<div className="w-full max-w-5xl mx-auto p-6">
|
||||
<MediaProvider key={key}>
|
||||
<Skin className={skinClassName}>
|
||||
{/* @ts-ignore -- types are incorrect */}
|
||||
{/* @ts-expect-error -- types are incorrect */}
|
||||
<Video src={mediaSource} poster={poster} />
|
||||
</Skin>
|
||||
</MediaProvider>
|
||||
|
||||
+4
-4
@@ -38,12 +38,12 @@
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@prettier/plugin-xml": "^3.4.2",
|
||||
"@types/node": "^22.18.6",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint": "^9.37.0",
|
||||
"eslint-plugin-format": "^1.0.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.21",
|
||||
"lint-staged": "^16.2.0",
|
||||
"eslint-plugin-react-hooks": "^6.1.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.23",
|
||||
"lint-staged": "^16.2.3",
|
||||
"prettier": "^3.6.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
|
||||
@@ -23,12 +23,10 @@ export class TimeRange extends Range {
|
||||
let _fillWidth = 0;
|
||||
if (state._dragging) {
|
||||
_fillWidth = state._pointerRatio * 100;
|
||||
}
|
||||
else if (state.duration > 0) {
|
||||
} else if (state.duration > 0) {
|
||||
if (this.#seekingTime !== null && this.#oldCurrentTime === state.currentTime) {
|
||||
_fillWidth = (this.#seekingTime / state.duration) * 100;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
_fillWidth = (state.currentTime / state.duration) * 100;
|
||||
this.#seekingTime = null;
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ export class VolumeRange extends Range {
|
||||
let _fillWidth = 0;
|
||||
if (state._dragging) {
|
||||
_fillWidth = state._pointerRatio * 100;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
_fillWidth = state.muted ? 0 : (state.volume || 0) * 100;
|
||||
}
|
||||
|
||||
|
||||
@@ -114,11 +114,9 @@ export function createMediaStore({
|
||||
|
||||
if (type === 'mediastateownerchangerequest') {
|
||||
updateStateOwners({ media: detail });
|
||||
}
|
||||
else if (type === 'containerstateownerchangerequest') {
|
||||
} else if (type === 'containerstateownerchangerequest') {
|
||||
updateStateOwners({ container: detail });
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
for (const stateObject of Object.values(stateMediator).filter(
|
||||
(stateMediatorEntry): stateMediatorEntry is FacadeProp<any, any, any> => 'set' in stateMediatorEntry,
|
||||
)) {
|
||||
|
||||
@@ -73,41 +73,33 @@ export const fullscreenable = {
|
||||
// Enter fullscreen
|
||||
if (container.requestFullscreen) {
|
||||
container.requestFullscreen();
|
||||
}
|
||||
else if (container.webkitRequestFullscreen) {
|
||||
} else if (container.webkitRequestFullscreen) {
|
||||
// Safari support
|
||||
container.webkitRequestFullscreen();
|
||||
}
|
||||
else if (container.mozRequestFullScreen) {
|
||||
} else if (container.mozRequestFullScreen) {
|
||||
// Firefox support
|
||||
container.mozRequestFullScreen();
|
||||
}
|
||||
else if (container.msRequestFullscreen) {
|
||||
} else if (container.msRequestFullscreen) {
|
||||
// IE/Edge support
|
||||
container.msRequestFullscreen();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Exit fullscreen
|
||||
const doc = globalThis.document as any;
|
||||
if (doc.exitFullscreen) {
|
||||
doc.exitFullscreen();
|
||||
}
|
||||
else if (doc.webkitExitFullscreen) {
|
||||
} else if (doc.webkitExitFullscreen) {
|
||||
// Safari support
|
||||
doc.webkitExitFullscreen();
|
||||
}
|
||||
else if (doc.mozCancelFullScreen) {
|
||||
} else if (doc.mozCancelFullScreen) {
|
||||
// Firefox support
|
||||
doc.mozCancelFullScreen();
|
||||
}
|
||||
else if (doc.msExitFullscreen) {
|
||||
} else if (doc.msExitFullscreen) {
|
||||
// IE/Edge support
|
||||
doc.msExitFullscreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
} catch (error) {
|
||||
// Gracefully handle fullscreen API errors (e.g., user interaction required)
|
||||
console.warn('Fullscreen operation failed:', error);
|
||||
}
|
||||
|
||||
@@ -21,8 +21,7 @@ export class FullscreenButtonBase extends MediaChromeButton {
|
||||
if (state && type === 'click') {
|
||||
if (state.fullscreen) {
|
||||
state.requestExitFullscreen();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.requestEnterFullscreen();
|
||||
}
|
||||
}
|
||||
@@ -61,7 +60,7 @@ export const useFullscreenButtonProps: PropsHook<{ fullscreen: boolean }> = (sta
|
||||
'data-fullscreen': state.fullscreen,
|
||||
/** @TODO Need another state provider in core for i18n (CJP) */
|
||||
/** aria attributes/props */
|
||||
'role': 'button',
|
||||
role: 'button',
|
||||
'aria-label': state.fullscreen ? 'exit fullscreen' : 'enter fullscreen',
|
||||
/** tooltip */
|
||||
'data-tooltip': state.fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen',
|
||||
|
||||
@@ -24,8 +24,7 @@ export class MuteButtonBase extends MediaChromeButton {
|
||||
if (type === 'click') {
|
||||
if (state.volumeLevel === 'off') {
|
||||
state.requestUnmute();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.requestMute();
|
||||
}
|
||||
}
|
||||
@@ -73,7 +72,7 @@ export const useMuteButtonProps: PropsHook<{
|
||||
'data-volume-level': state.volumeLevel,
|
||||
/** @TODO Need another state provider in core for i18n (CJP) */
|
||||
/** aria attributes/props */
|
||||
'role': 'button',
|
||||
role: 'button',
|
||||
'aria-label': state.muted ? 'unmute' : 'mute',
|
||||
/** tooltip */
|
||||
'data-tooltip': state.muted ? 'Unmute' : 'Mute',
|
||||
|
||||
@@ -19,8 +19,7 @@ export class PlayButtonBase extends MediaChromeButton {
|
||||
if (state && type === 'click') {
|
||||
if (state.paused) {
|
||||
state.requestPlay();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.requestPause();
|
||||
}
|
||||
}
|
||||
@@ -55,7 +54,7 @@ export const usePlayButtonProps: PropsHook<{ paused: boolean }> = (state, _eleme
|
||||
'data-paused': state.paused,
|
||||
/** @TODO Need another state provider in core for i18n (CJP) */
|
||||
/** aria attributes/props */
|
||||
'role': 'button',
|
||||
role: 'button',
|
||||
'aria-label': state.paused ? 'play' : 'pause',
|
||||
/** tooltip */
|
||||
'data-tooltip': state.paused ? 'Play' : 'Pause',
|
||||
|
||||
@@ -63,8 +63,7 @@ export class MediaPopoverRoot extends HTMLElement {
|
||||
|
||||
if (open) {
|
||||
this.#setupFloating();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.#cleanup?.();
|
||||
this.#cleanup = null;
|
||||
}
|
||||
|
||||
@@ -91,8 +91,7 @@ export class TimeRangeTrackBase extends HTMLElement {
|
||||
if (orientation === 'horizontal') {
|
||||
this.style.width = '100%';
|
||||
this.style.removeProperty('height');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.style.height = '100%';
|
||||
this.style.removeProperty('width');
|
||||
}
|
||||
@@ -117,8 +116,7 @@ export class TimeRangeProgressBase extends HTMLElement {
|
||||
this.style.height = '100%';
|
||||
this.style.top = '0';
|
||||
this.style.removeProperty('bottom');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.style.height = 'var(--slider-fill, 0%)';
|
||||
this.style.width = '100%';
|
||||
this.style.bottom = '0';
|
||||
@@ -145,8 +143,7 @@ export class TimeRangePointerBase extends HTMLElement {
|
||||
this.style.height = '100%';
|
||||
this.style.top = '0';
|
||||
this.style.removeProperty('bottom');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.style.height = 'var(--slider-pointer, 0%)';
|
||||
this.style.width = '100%';
|
||||
this.style.bottom = '0';
|
||||
@@ -170,8 +167,7 @@ export class TimeRangeThumbBase extends HTMLElement {
|
||||
this.style.left = 'var(--slider-fill, 0%)';
|
||||
this.style.top = '50%';
|
||||
this.style.transform = 'translate(-50%, -50%)';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.style.bottom = 'var(--slider-fill, 0%)';
|
||||
this.style.left = '50%';
|
||||
this.style.transform = 'translate(-50%, 50%)';
|
||||
|
||||
@@ -19,8 +19,7 @@ export class MediaTooltipRoot extends HTMLElement {
|
||||
handleEvent(event: Event): void {
|
||||
if (event.type === 'mouseenter') {
|
||||
this.#handleMouseEnter();
|
||||
}
|
||||
else if (event.type === 'mouseleave') {
|
||||
} else if (event.type === 'mouseleave') {
|
||||
this.#handleMouseLeave();
|
||||
}
|
||||
}
|
||||
@@ -70,8 +69,7 @@ export class MediaTooltipRoot extends HTMLElement {
|
||||
|
||||
if (open) {
|
||||
this.#setupFloating();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.#cleanup?.();
|
||||
this.#cleanup = null;
|
||||
}
|
||||
@@ -203,8 +201,7 @@ export class MediaTooltipTrigger extends HTMLElement {
|
||||
const attributeValue = triggerElement.getAttribute(attributeName);
|
||||
if (attributeValue !== null) {
|
||||
popupElement.setAttribute(attributeName, attributeValue);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
popupElement.removeAttribute(attributeName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +102,7 @@ export class VolumeRangeTrackBase extends HTMLElement {
|
||||
if (orientation === 'horizontal') {
|
||||
this.style.width = '100%';
|
||||
this.style.removeProperty('height');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.style.height = '100%';
|
||||
this.style.removeProperty('width');
|
||||
}
|
||||
@@ -131,8 +130,7 @@ export class VolumeRangeProgressBase extends HTMLElement {
|
||||
this.style.height = '100%';
|
||||
this.style.top = '0';
|
||||
this.style.removeProperty('bottom');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.style.height = 'var(--slider-fill, 0%)';
|
||||
this.style.width = '100%';
|
||||
this.style.bottom = '0';
|
||||
@@ -159,8 +157,7 @@ export class VolumeRangeThumbBase extends HTMLElement {
|
||||
this.style.left = 'var(--slider-fill, 0%)';
|
||||
this.style.top = '50%';
|
||||
this.style.transform = 'translate(-50%, -50%)';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.style.bottom = 'var(--slider-fill, 0%)';
|
||||
this.style.left = '50%';
|
||||
this.style.transform = 'translate(-50%, 50%)';
|
||||
|
||||
@@ -12,9 +12,9 @@ export default {
|
||||
replaceAttrValues: {
|
||||
'#000': '{color}',
|
||||
'#fff': '{color}',
|
||||
'black': '{color}',
|
||||
'white': '{color}',
|
||||
'currentColor': '{color}',
|
||||
black: '{color}',
|
||||
white: '{color}',
|
||||
currentColor: '{color}',
|
||||
},
|
||||
template: (variables, { tpl }) => {
|
||||
return tpl`
|
||||
|
||||
@@ -52,8 +52,7 @@ export function useSyncExternalStoreWithSelector<Snapshot, Selection>(
|
||||
value: null,
|
||||
};
|
||||
instRef.current = inst as SnapshotRef<Selection>;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
inst = instRef.current;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ export function useFullscreenButtonProps(props: PropsWithChildren, state: Return
|
||||
const baseProps: Record<string, any> = {
|
||||
/** @TODO Need another state provider in core for i18n (CJP) */
|
||||
/** aria attributes/props */
|
||||
'role': 'button',
|
||||
role: 'button',
|
||||
'aria-label': state.fullscreen ? 'exit fullscreen' : 'enter fullscreen',
|
||||
/** tooltip */
|
||||
'data-tooltip': state.fullscreen ? 'Exit Fullscreen' : 'Enter Fullscreen',
|
||||
@@ -64,8 +64,7 @@ export function renderFullscreenButton(props: FullscreenButtonProps, state: Full
|
||||
if (props.disabled) return;
|
||||
if (state.fullscreen) {
|
||||
state.requestExitFullscreen();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.requestEnterFullscreen();
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function useMuteButtonProps(props: PropsWithChildren, state: ReturnType<t
|
||||
'data-volume-level': state.volumeLevel,
|
||||
/** @TODO Need another state provider in core for i18n (CJP) */
|
||||
/** aria attributes/props */
|
||||
'role': 'button',
|
||||
role: 'button',
|
||||
'aria-label': state.muted ? 'unmute' : 'mute',
|
||||
/** tooltip */
|
||||
'data-tooltip': state.muted ? 'Unmute' : 'Mute',
|
||||
@@ -66,8 +66,7 @@ export function renderMuteButton(props: MuteButtonProps, state: MuteButtonState)
|
||||
if (props.disabled) return;
|
||||
if (state.volumeLevel === 'off') {
|
||||
state.requestUnmute();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.requestMute();
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function usePlayButtonProps(props: Record<string, unknown>, state: Return
|
||||
const baseProps: Record<string, any> = {
|
||||
/** @TODO Need another state provider in core for i18n (CJP) */
|
||||
/** aria attributes/props */
|
||||
'role': 'button',
|
||||
role: 'button',
|
||||
'aria-label': state.paused ? 'play' : 'pause',
|
||||
/** tooltip */
|
||||
'data-tooltip': state.paused ? 'Play' : 'Pause',
|
||||
@@ -62,8 +62,7 @@ export function renderPlayButton(props: PlayButtonProps, state: PlayButtonState)
|
||||
if (props.disabled) return;
|
||||
if (state.paused) {
|
||||
state.requestPlay();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.requestPause();
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -53,12 +53,12 @@ export function useTimeRangeRootProps(props: TimeRange.Props, state: TimeRange.S
|
||||
const { children, className, id, style, orientation = 'horizontal' } = props;
|
||||
|
||||
return {
|
||||
'ref': useCallback((el: HTMLDivElement) => {
|
||||
ref: useCallback((el: HTMLDivElement) => {
|
||||
if (!el) return;
|
||||
state.core?.attach(el);
|
||||
}, []),
|
||||
id,
|
||||
'role': 'slider',
|
||||
role: 'slider',
|
||||
'aria-label': 'Seek',
|
||||
'aria-valuemin': 0,
|
||||
'aria-valuemax': 100,
|
||||
@@ -69,10 +69,10 @@ export function useTimeRangeRootProps(props: TimeRange.Props, state: TimeRange.S
|
||||
'data-current-time': state.currentTime,
|
||||
'data-duration': state.duration,
|
||||
className,
|
||||
'style': {
|
||||
style: {
|
||||
...style,
|
||||
'--slider-fill': `${_fillWidth.toFixed(3)}%`,
|
||||
'--slider-pointer': `${_pointerWidth.toFixed(3)}%`,
|
||||
'--slider-pointer': `${(_pointerWidth * 100).toFixed(3)}%`,
|
||||
} as React.CSSProperties,
|
||||
children,
|
||||
};
|
||||
@@ -98,12 +98,12 @@ const TimeRangeRoot: ConnectedComponent<TimeRange.Props, typeof renderTimeRangeR
|
||||
|
||||
export function useTimeRangeTrackProps(props: React.ComponentProps<'div'>, context: TimeRange.State): TimeRangeRenderProps {
|
||||
return {
|
||||
'ref': useCallback((el: HTMLDivElement) => {
|
||||
ref: useCallback((el: HTMLDivElement) => {
|
||||
context.core?.setState({ _trackElement: el });
|
||||
}, []),
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
'style': {
|
||||
style: {
|
||||
...props.style,
|
||||
[context.orientation === 'horizontal' ? 'width' : 'height']: '100%',
|
||||
},
|
||||
@@ -130,7 +130,7 @@ export function useTimeRangeThumbProps(props: React.ComponentProps<'div'>, conte
|
||||
return {
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
'style': {
|
||||
style: {
|
||||
...props.style,
|
||||
[context.orientation === 'horizontal' ? 'insetInlineStart' : 'insetBlockEnd']: 'var(--slider-fill)',
|
||||
[context.orientation === 'horizontal' ? 'top' : 'left']: '50%',
|
||||
@@ -160,7 +160,7 @@ export function useTimeRangePointerProps(props: React.ComponentProps<'div'>, con
|
||||
return {
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
'style': {
|
||||
style: {
|
||||
...props.style,
|
||||
[context.orientation === 'horizontal' ? 'width' : 'height']: 'var(--slider-pointer, 0%)',
|
||||
[context.orientation === 'horizontal' ? 'height' : 'width']: '100%',
|
||||
@@ -188,7 +188,7 @@ export function useTimeRangeProgressProps(props: React.ComponentProps<'div'>, co
|
||||
return {
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
'style': {
|
||||
style: {
|
||||
...props.style,
|
||||
[context.orientation === 'horizontal' ? 'width' : 'height']: 'var(--slider-fill, 0%)',
|
||||
[context.orientation === 'horizontal' ? 'height' : 'width']: '100%',
|
||||
|
||||
@@ -150,7 +150,7 @@ function TooltipTrigger({ children }: TooltipTriggerProps): JSX.Element {
|
||||
const { refs, open } = context;
|
||||
|
||||
return React.cloneElement(React.Children.only(children) as JSX.Element, {
|
||||
'ref': refs.setReference,
|
||||
ref: refs.setReference,
|
||||
...getReferenceProps(),
|
||||
'data-popup-open': open ? '' : undefined,
|
||||
});
|
||||
|
||||
@@ -57,12 +57,12 @@ export function useVolumeRangeRootProps(props: VolumeRange.Props, state: VolumeR
|
||||
const { children, className, id, style, orientation = 'horizontal' } = props;
|
||||
|
||||
return {
|
||||
'ref': useCallback((el: HTMLDivElement) => {
|
||||
ref: useCallback((el: HTMLDivElement) => {
|
||||
if (!el) return;
|
||||
state.core?.attach(el);
|
||||
}, []),
|
||||
id,
|
||||
'role': 'slider',
|
||||
role: 'slider',
|
||||
'aria-label': 'Volume',
|
||||
'aria-valuemin': 0,
|
||||
'aria-valuemax': 100,
|
||||
@@ -73,7 +73,7 @@ export function useVolumeRangeRootProps(props: VolumeRange.Props, state: VolumeR
|
||||
'data-muted': state.muted,
|
||||
'data-volume-level': state.volumeLevel,
|
||||
className,
|
||||
'style': {
|
||||
style: {
|
||||
...style,
|
||||
'--slider-fill': `${_fillWidth.toFixed(3)}%`,
|
||||
'--slider-pointer': `${_pointerWidth.toFixed(3)}%`,
|
||||
@@ -102,12 +102,12 @@ const VolumeRangeRoot: ConnectedComponent<VolumeRange.Props, typeof renderVolume
|
||||
|
||||
export function useVolumeRangeTrackProps(props: React.ComponentProps<'div'>, context: VolumeRange.State): VolumeRangeRenderProps {
|
||||
return {
|
||||
'ref': useCallback((el: HTMLDivElement) => {
|
||||
ref: useCallback((el: HTMLDivElement) => {
|
||||
context.core?.setState({ _trackElement: el });
|
||||
}, []),
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
'style': {
|
||||
style: {
|
||||
...props.style,
|
||||
[context.orientation === 'horizontal' ? 'width' : 'height']: '100%',
|
||||
},
|
||||
@@ -133,7 +133,7 @@ export function useVolumeRangeThumbProps(props: React.ComponentProps<'div'>, con
|
||||
return {
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
'style': {
|
||||
style: {
|
||||
...props.style,
|
||||
[context.orientation === 'horizontal' ? 'insetInlineStart' : 'insetBlockEnd']: 'var(--slider-fill)',
|
||||
[context.orientation === 'horizontal' ? 'top' : 'left']: '50%',
|
||||
@@ -162,7 +162,7 @@ export function useVolumeRangeProgressProps(props: React.ComponentProps<'div'>,
|
||||
return {
|
||||
'data-orientation': context.orientation,
|
||||
...props,
|
||||
'style': {
|
||||
style: {
|
||||
...props.style,
|
||||
[context.orientation === 'horizontal' ? 'width' : 'height']: 'var(--slider-fill, 0%)',
|
||||
[context.orientation === 'horizontal' ? 'height' : 'width']: '100%',
|
||||
|
||||
@@ -75,7 +75,7 @@ export default function MediaSkinDefault({ children, className = '' }: SkinProps
|
||||
|
||||
<Popover.Root openOnHover delay={200} closeDelay={100}>
|
||||
<Popover.Trigger>
|
||||
<MuteButton className={`${styles.Button} ${styles.IconButton} ${styles.VolumeButton}`}>
|
||||
<MuteButton className={`${styles.Button} ${styles.IconButton} ${styles.MuteButton}`}>
|
||||
<VolumeHighIcon className={styles.VolumeHighIcon} />
|
||||
<VolumeLowIcon className={styles.VolumeLowIcon} />
|
||||
<VolumeOffIcon className={styles.VolumeOffIcon} />
|
||||
|
||||
@@ -88,7 +88,7 @@ const styles: MediaDefaultSkinStyles = {
|
||||
),
|
||||
PlayTooltip: cn('play-tooltip'),
|
||||
PauseTooltip: cn('pause-tooltip'),
|
||||
VolumeButton: cn(
|
||||
MuteButton: cn(
|
||||
'[&_svg]:opacity-0',
|
||||
'[&[data-volume-level="high"]_.volume-high-icon]:opacity-100',
|
||||
'[&[data-volume-level="medium"]_.volume-low-icon]:opacity-100',
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface MediaDefaultSkinStyles {
|
||||
readonly PlayTooltip: string;
|
||||
readonly PauseTooltip: string;
|
||||
readonly TooltipPopup: string;
|
||||
readonly VolumeButton: string;
|
||||
readonly MuteButton: string;
|
||||
readonly VolumeHighIcon: string;
|
||||
readonly VolumeLowIcon: string;
|
||||
readonly VolumeOffIcon: string;
|
||||
|
||||
@@ -17,7 +17,6 @@ import { MediaContainer } from '../../components/MediaContainer';
|
||||
import MuteButton from '../../components/MuteButton';
|
||||
import PlayButton from '../../components/PlayButton';
|
||||
import { TimeRange } from '../../components/TimeRange';
|
||||
import { VolumeRange } from '../../components/VolumeRange';
|
||||
import styles from './styles';
|
||||
|
||||
type SkinProps = PropsWithChildren<{
|
||||
@@ -29,63 +28,43 @@ export default function MediaSkinDefault({ children, className = '' }: SkinProps
|
||||
<MediaContainer className={`${styles.MediaContainer} ${className}`}>
|
||||
{children}
|
||||
|
||||
<div className={styles.Controls} data-testid="media-controls">
|
||||
{/* <header className='py-2 px-2.5 text-shadow-sm text-shadow-black/10'>
|
||||
<h1 className="text-base font-medium">Example Video</h1>
|
||||
<p className="text-stone-400">This is just a description for the example video.</p>
|
||||
</header> */}
|
||||
<div className={styles.Overlay} aria-hidden="true" />
|
||||
|
||||
<div className={styles.ControlsRow}>
|
||||
<TimeRange.Root className={styles.SliderRoot}>
|
||||
<TimeRange.Track className={styles.SliderTrack}>
|
||||
<TimeRange.Progress className={styles.SliderProgress} />
|
||||
<TimeRange.Pointer className={styles.SliderPointer} />
|
||||
</TimeRange.Track>
|
||||
<TimeRange.Thumb className={`${styles.SliderThumb} ${styles.TimeSliderThumb}`} />
|
||||
</TimeRange.Root>
|
||||
<div className={styles.Controls}>
|
||||
<PlayButton className={`${styles.Button} ${styles.IconButton} ${styles.PlayButton}`}>
|
||||
<PlayIcon className={styles.PlayIcon}></PlayIcon>
|
||||
<PauseIcon className={styles.PauseIcon}></PauseIcon>
|
||||
</PlayButton>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<CurrentTimeDisplay
|
||||
// Use showRemaining to show count down/remaining time
|
||||
// showRemaining
|
||||
className={styles.TimeDisplay}
|
||||
/>
|
||||
<span className="opacity-50">/</span>
|
||||
<DurationDisplay className={`${styles.TimeDisplay} opacity-50`} />
|
||||
</div>
|
||||
|
||||
<div className={styles.ControlsRow}>
|
||||
<div className="flex items-center gap-3">
|
||||
<PlayButton className={`${styles.Button} ${styles.IconButton} ${styles.PlayButton}`}>
|
||||
<PlayIcon className={styles.PlayIcon}></PlayIcon>
|
||||
<PauseIcon className={styles.PauseIcon}></PauseIcon>
|
||||
</PlayButton>
|
||||
<TimeRange.Root className={`${styles.SliderRoot} ${styles.TimeSliderRoot}`}>
|
||||
<TimeRange.Track className={styles.SliderTrack}>
|
||||
<TimeRange.Progress className={styles.SliderProgress} />
|
||||
<TimeRange.Pointer className={styles.SliderPointer} />
|
||||
</TimeRange.Track>
|
||||
<TimeRange.Thumb className={`${styles.SliderThumb} ${styles.TimeSliderThumb}`} />
|
||||
</TimeRange.Root>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<CurrentTimeDisplay
|
||||
// Use showRemaining to show count down/remaining time
|
||||
// showRemaining
|
||||
className={styles.TimeDisplay}
|
||||
/>
|
||||
<span className="opacity-50">/</span>
|
||||
<DurationDisplay className={`${styles.TimeDisplay} opacity-50`} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.ButtonGroup}>
|
||||
<MuteButton className={`${styles.Button} ${styles.IconButton} ${styles.MuteButton}`}>
|
||||
<VolumeHighIcon className={styles.VolumeHighIcon} />
|
||||
<VolumeLowIcon className={styles.VolumeLowIcon} />
|
||||
<VolumeOffIcon className={styles.VolumeOffIcon} />
|
||||
</MuteButton>
|
||||
|
||||
<div className="flex items-center gap-0.5">
|
||||
<div className={styles.VolumeControls}>
|
||||
<MuteButton className={`${styles.Button} ${styles.IconButton} ${styles.VolumeButton}`}>
|
||||
<VolumeHighIcon className={styles.VolumeHighIcon} />
|
||||
<VolumeLowIcon className={styles.VolumeLowIcon} />
|
||||
<VolumeOffIcon className={styles.VolumeOffIcon} />
|
||||
</MuteButton>
|
||||
|
||||
<div className={styles.VolumeSlider}>
|
||||
<VolumeRange.Root className={styles.SliderRoot}>
|
||||
<VolumeRange.Track className={styles.SliderTrack}>
|
||||
<VolumeRange.Progress className={styles.SliderProgress} />
|
||||
</VolumeRange.Track>
|
||||
<VolumeRange.Thumb className={styles.SliderThumb} />
|
||||
</VolumeRange.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FullscreenButton className={`${styles.Button} ${styles.IconButton} ${styles.FullScreenButton}`}>
|
||||
<FullscreenEnterIcon className={styles.FullScreenEnterIcon} />
|
||||
<FullscreenExitIcon className={styles.FullScreenExitIcon} />
|
||||
</FullscreenButton>
|
||||
</div>
|
||||
<FullscreenButton className={`${styles.Button} ${styles.IconButton} ${styles.FullScreenButton}`}>
|
||||
<FullscreenEnterIcon className={styles.FullScreenEnterIcon} />
|
||||
<FullscreenExitIcon className={styles.FullScreenExitIcon} />
|
||||
</FullscreenButton>
|
||||
</div>
|
||||
</div>
|
||||
</MediaContainer>
|
||||
|
||||
@@ -5,7 +5,7 @@ const styles: MediaToastedSkinStyles = {
|
||||
MediaContainer: cn(
|
||||
'relative @container/root group/root overflow-clip bg-black',
|
||||
// Base typography
|
||||
'text-sm',
|
||||
'text-[0.8125rem]', // 13px
|
||||
// 'ring-1 ring-inset ring-black/10 dark:ring-white/10',
|
||||
'after:absolute after:inset-0 after:ring-black/10 after:ring-1 dark:after:ring-white/10 after:ring-inset after:z-10 after:pointer-events-none after:rounded-[inherit]',
|
||||
// Prevent rounded corners in fullscreen.
|
||||
@@ -13,36 +13,43 @@ const styles: MediaToastedSkinStyles = {
|
||||
// Ensure the nested video inherits the radius.
|
||||
'[&_video]:rounded-[inherit] [&_video]:w-full [&_video]:h-auto',
|
||||
),
|
||||
Overlay: cn(
|
||||
'absolute inset-0 rounded-[inherit] bg-black/30',
|
||||
'bg-gradient-to-t from-black/30 to-transparent to-[120px]',
|
||||
'opacity-0 delay-500 duration-300',
|
||||
// FIXME: Temporary hide/show logic
|
||||
'has-[+.controls_[data-paused]]:opacity-100 has-[+.controls_[data-paused]]:delay-0 has-[+.controls_[data-paused]]:duration-75',
|
||||
'group-hover/root:opacity-100 group-hover/root:delay-0 group-hover/root:duration-75',
|
||||
),
|
||||
Controls: cn(
|
||||
'@container/controls absolute inset-x-0 bottom-0 top-1/3 flex flex-col justify-end z-20 px-2.5 pb-2.5 text-white text-shadow',
|
||||
'controls', // FIXME: Temporary className hook for above logic in the overlay. Can be removed once have a proper way to handle controls visibility.
|
||||
'@container/controls absolute inset-x-0 bottom-0 flex items-center gap-3.5 z-20 px-6 pb-6 pt-10 text-white text-shadow',
|
||||
'shadow-sm shadow-black/15',
|
||||
// Background
|
||||
'bg-gradient-to-t from-stone-950/70 via-stone-950/60 via-35% to-transparent',
|
||||
// Animation
|
||||
'transition ease-in-out',
|
||||
// FIXME: Temporary hide/show logic
|
||||
'translate-y-full opacity-0 delay-500 pointer-events-none',
|
||||
'has-[[data-paused]]:translate-y-0 has-[[data-paused]]:opacity-100 has-[[data-paused]]:delay-0 has-[[data-paused]]:pointer-events-auto',
|
||||
'group-hover/root:translate-y-0 group-hover/root:opacity-100 group-hover/root:delay-0 group-hover/root:pointer-events-auto',
|
||||
'translate-y-full opacity-0 delay-500 duration-300',
|
||||
'has-[[data-paused]]:translate-y-0 has-[[data-paused]]:opacity-100 has-[[data-paused]]:delay-0 has-[[data-paused]]:duration-75',
|
||||
'group-hover/root:translate-y-0 group-hover/root:opacity-100 group-hover/root:delay-0 group-hover/root:duration-75',
|
||||
),
|
||||
ControlsRow: cn('flex items-center justify-between'),
|
||||
Button: cn(
|
||||
'group/button cursor-pointer relative shrink-0 transition select-none p-2 rounded-md',
|
||||
// Background/foreground
|
||||
'bg-transparent text-white/90',
|
||||
'bg-transparent text-white',
|
||||
// Hover and focus states
|
||||
'hover:no-underline hover:bg-stone-100/10 hover:backdrop-blur-md hover:text-white focus-visible:no-underline focus-visible:bg-stone-100/10 focus-visible:text-white',
|
||||
'hover:text-white/70 focus-visible:text-white/70',
|
||||
// Focus state
|
||||
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500',
|
||||
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white',
|
||||
// Disabled state
|
||||
'aria-disabled:grayscale aria-disabled:opacity-50 aria-disabled:cursor-not-allowed',
|
||||
// Loading state
|
||||
'aria-busy:pointer-events-none aria-busy:cursor-not-allowed',
|
||||
// Expanded state
|
||||
'aria-expanded:bg-stone-100/10 aria-expanded:text-white',
|
||||
'aria-expanded:text-white/70',
|
||||
// Pressed state
|
||||
'active:scale-95',
|
||||
),
|
||||
ButtonGroup: cn('flex items-center gap-1.5'),
|
||||
IconButton: cn(
|
||||
'grid [&_svg]:[grid-area:1/1]',
|
||||
'[&_svg]:shrink-0 [&_svg]:transition [&_svg]:duration-300 [&_svg]:ease-out [&_svg]:drop-shadow-[0_1px_0_var(--tw-shadow-color)] [&_svg]:shadow-black/20',
|
||||
@@ -53,13 +60,7 @@ const styles: MediaToastedSkinStyles = {
|
||||
),
|
||||
PlayIcon: cn('play-icon'),
|
||||
PauseIcon: cn('pause-icon'),
|
||||
VolumeControls: cn('flex items-center flex-row-reverse group/volume'),
|
||||
VolumeSlider: cn(
|
||||
'w-0 px-3 overflow-hidden pointer-events-none transition-[opacity,width] opacity-0 ease-out delay-500',
|
||||
'group-hover/volume:w-28 group-hover/volume:pointer-events-auto group-hover/volume:opacity-100 group-hover/volume:delay-0',
|
||||
'group-focus-within/volume:w-28 group-focus-within/volume:pointer-events-auto group-focus-within/volume:opacity-100 group-focus-within/volume:delay-0',
|
||||
),
|
||||
VolumeButton: cn(
|
||||
MuteButton: cn(
|
||||
'[&_svg]:hidden',
|
||||
'[&[data-volume-level="high"]_.volume-high-icon]:inline',
|
||||
'[&[data-volume-level="medium"]_.volume-low-icon]:inline',
|
||||
@@ -86,10 +87,8 @@ const styles: MediaToastedSkinStyles = {
|
||||
'group-hover/button:[&_.arrow-1]:translate-0',
|
||||
'group-hover/button:[&_.arrow-2]:translate-0',
|
||||
),
|
||||
TimeSliderThumb: cn(
|
||||
'opacity-0',
|
||||
'group-hover/slider:opacity-100 group-focus-within/slider:opacity-100',
|
||||
),
|
||||
TimeSliderRoot: cn('mx-2'),
|
||||
TimeSliderThumb: cn('opacity-0'),
|
||||
TimeDisplay: cn('tabular-nums text-shadow-2xs shadow-black/50'),
|
||||
SliderRoot: cn(
|
||||
'flex items-center justify-center flex-1 group/slider relative',
|
||||
@@ -97,13 +96,12 @@ const styles: MediaToastedSkinStyles = {
|
||||
'[&[data-orientation="vertical"]]:w-5 [&[data-orientation="vertical"]]:h-20',
|
||||
),
|
||||
SliderTrack: cn(
|
||||
'relative select-none rounded-full bg-white/25 backdrop-blur-sm backdrop-brightness-90 backdrop-saturate-150 shadow-sm shadow-black/10',
|
||||
'relative select-none rounded-full bg-white/10',
|
||||
'[&[data-orientation="horizontal"]]:w-full [&[data-orientation="horizontal"]]:h-1',
|
||||
'[&[data-orientation="vertical"]]:w-1',
|
||||
),
|
||||
SliderProgress: cn('bg-amber-500 rounded-[inherit]'),
|
||||
// TODO: Work out what we want to do here.
|
||||
SliderPointer: cn('rounded-[inherit]'),
|
||||
SliderProgress: cn('bg-white rounded-[inherit]'),
|
||||
SliderPointer: cn('hidden'),
|
||||
SliderThumb: cn(
|
||||
'bg-white z-10 select-none ring ring-black/10 rounded-full shadow-sm shadow-black/15 transition-[opacity,height,width] ease-in-out',
|
||||
'-outline-offset-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-500',
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export interface MediaToastedSkinStyles {
|
||||
readonly MediaContainer: string;
|
||||
readonly Overlay: string;
|
||||
readonly Controls: string;
|
||||
readonly ControlsRow: string;
|
||||
readonly Button: string;
|
||||
readonly ButtonGroup: string;
|
||||
readonly IconButton: string;
|
||||
readonly PlayButton: string;
|
||||
readonly PlayIcon: string;
|
||||
readonly PauseIcon: string;
|
||||
readonly VolumeControls: string;
|
||||
readonly VolumeSlider: string;
|
||||
readonly VolumeButton: string;
|
||||
readonly MuteButton: string;
|
||||
readonly VolumeHighIcon: string;
|
||||
readonly VolumeLowIcon: string;
|
||||
readonly VolumeOffIcon: string;
|
||||
readonly FullScreenButton: string;
|
||||
readonly FullScreenEnterIcon: string;
|
||||
readonly FullScreenExitIcon: string;
|
||||
readonly TimeSliderRoot: string;
|
||||
readonly TimeSliderThumb: string;
|
||||
readonly TimeDisplay: string;
|
||||
readonly SliderRoot: string;
|
||||
|
||||
@@ -9,8 +9,7 @@ type PossibleRef<T> = React.Ref<T> | undefined;
|
||||
function setRef<T>(ref: PossibleRef<T>, value: T): (() => void) | void | undefined {
|
||||
if (typeof ref === 'function') {
|
||||
return ref(value);
|
||||
}
|
||||
else if (ref !== null && ref !== undefined) {
|
||||
} else if (ref !== null && ref !== undefined) {
|
||||
(ref as React.MutableRefObject<T>).current = value;
|
||||
}
|
||||
}
|
||||
@@ -40,8 +39,7 @@ function composeRefs<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {
|
||||
const cleanup = cleanups[i];
|
||||
if (typeof cleanup == 'function') {
|
||||
cleanup();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
setRef(refs[i], null);
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+286
-254
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -16,11 +16,11 @@
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
|
||||
@@ -37,8 +37,7 @@ export function Selectors({ currentFramework, currentStyle }: SelectorProps) {
|
||||
const firstGuide = findFirstGuide(newFramework, getDefaultStyle(newFramework));
|
||||
if (firstGuide) {
|
||||
navigate(`/docs/framework/${newFramework}/style/${getDefaultStyle(newFramework)}/${firstGuide}/`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
navigate('/docs/');
|
||||
}
|
||||
return;
|
||||
@@ -50,8 +49,7 @@ export function Selectors({ currentFramework, currentStyle }: SelectorProps) {
|
||||
const firstGuide = findFirstGuide(newFramework, getDefaultStyle(newFramework));
|
||||
if (firstGuide) {
|
||||
navigate(`/docs/framework/${newFramework}/style/${getDefaultStyle(newFramework)}/${firstGuide}/`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
navigate('/docs/');
|
||||
}
|
||||
return;
|
||||
@@ -65,8 +63,7 @@ export function Selectors({ currentFramework, currentStyle }: SelectorProps) {
|
||||
const firstGuide = findFirstGuide(newFramework, getDefaultStyle(newFramework));
|
||||
if (firstGuide) {
|
||||
navigate(`/docs/framework/${newFramework}/style/${getDefaultStyle(newFramework)}/${firstGuide}/`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
navigate('/docs/');
|
||||
}
|
||||
return;
|
||||
@@ -93,8 +90,7 @@ export function Selectors({ currentFramework, currentStyle }: SelectorProps) {
|
||||
const firstGuide = findFirstGuide(currentFramework, newStyle);
|
||||
if (firstGuide) {
|
||||
navigate(`/docs/framework/${currentFramework}/style/${newStyle}/${firstGuide}/`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
navigate('/docs/');
|
||||
}
|
||||
return;
|
||||
@@ -108,8 +104,7 @@ export function Selectors({ currentFramework, currentStyle }: SelectorProps) {
|
||||
const firstGuide = findFirstGuide(currentFramework, newStyle);
|
||||
if (firstGuide) {
|
||||
navigate(`/docs/framework/${currentFramework}/style/${newStyle}/${firstGuide}/`);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
navigate('/docs/');
|
||||
}
|
||||
return;
|
||||
|
||||
@@ -31,8 +31,7 @@ async function getGitLastModifiedDate(filePath: string): Promise<Date | null> {
|
||||
if (!log.latest) return null;
|
||||
|
||||
return new Date(log.latest.date);
|
||||
}
|
||||
catch {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,8 +92,7 @@ export function findFirstGuide(
|
||||
// Recursively search section contents
|
||||
const guide = findFirstGuide(framework, style, item.contents);
|
||||
if (guide) return guide;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// It's a Guide, return its slug
|
||||
return item.slug;
|
||||
}
|
||||
@@ -118,8 +117,7 @@ export function getAllGuideSlugs(sidebarToExtract: Sidebar = sidebar): string[]
|
||||
if (isSection(item)) {
|
||||
// Recursively get slugs from section contents
|
||||
slugs.push(...getAllGuideSlugs(item.contents));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// It's a Guide, add its slug
|
||||
slugs.push(item.slug);
|
||||
}
|
||||
@@ -144,8 +142,7 @@ export function findGuideBySlug(
|
||||
// Recursively search section contents
|
||||
const guide = findGuideBySlug(slug, item.contents);
|
||||
if (guide) return guide;
|
||||
}
|
||||
else if (item.slug === slug) {
|
||||
} else if (item.slug === slug) {
|
||||
// Found the guide
|
||||
return item;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user