feat: migrate examples from prototype and add CSS modules support

- Migrate HTML and React demo applications from prototype to examples/ directory
- Add examples to workspace configuration with selective build support
- Update workspace scripts for library-only builds (build:libs) and example dev servers
- Fix React MediaProvider context conflicts by consolidating exports
- Add esbuild-css-modules-plugin for CSS modules support in React package
- Configure tsup for flattened output structure with CSS processing
- Update package exports and imports for new @vjs-10/* scoped packages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2025-07-29 15:51:40 -07:00
co-authored by Claude
parent 7403cc7281
commit 19c85fc2f8
17 changed files with 2133 additions and 190 deletions
+1 -1
View File
@@ -18,4 +18,4 @@ export default defineConfig({
incremental: false,
},
},
});
});
+13 -181
View File
@@ -1,182 +1,14 @@
import React, { createContext, useContext, useEffect, useState, useRef, ReactNode } from 'react';
// Re-export everything from MediaProvider - this is the primary implementation
export {
MediaProvider,
MediaContext,
useMediaStore,
useMediaDispatch,
useMediaRef,
useMediaSelector
} from './MediaProvider.js';
// @ts-ignore - Placeholder types until media-store exports are updated
type MediaStore = any;
type MediaState = any;
type MediaStateOwner = any;
import { MediaElementRef } from '@vjs-10/react-media-elements';
const MediaStoreContext = createContext<MediaStore | null>(null);
export interface MediaStoreProviderProps {
children: ReactNode;
store?: MediaStore;
}
export const MediaStoreProvider: React.FC<MediaStoreProviderProps> = ({
children,
store = {} as MediaStore,
}) => {
return (
<MediaStoreContext.Provider value={store}>
{children}
</MediaStoreContext.Provider>
);
};
export function useMediaStore(): MediaStore {
const store = useContext(MediaStoreContext);
if (!store) {
throw new Error('useMediaStore must be used within a MediaStoreProvider');
}
return store;
}
export function useMediaState(): [MediaState, (updates: Partial<MediaState>) => void] {
const store = useMediaStore();
const [state, setState] = useState<MediaState>(store.getState());
useEffect(() => {
const owner: MediaStateOwner = {
getState: () => state,
setState: (newState: Partial<MediaState>) => {
setState((current: any) => ({ ...current, ...newState }));
},
};
store.addOwner(owner);
return () => {
store.removeOwner(owner);
};
}, [store, state]);
const updateState = (updates: Partial<MediaState>) => {
store.updateState(updates);
};
return [state, updateState];
}
export class ReactMediaStateOwner implements MediaStateOwner {
private elementRef: React.RefObject<MediaElementRef>;
private store: MediaStore;
private intervalId?: NodeJS.Timeout;
constructor(elementRef: React.RefObject<MediaElementRef>, store: MediaStore) {
this.elementRef = elementRef;
this.store = store;
this.startPolling();
}
getState(): MediaState {
const element = this.elementRef.current;
if (!element) {
return {
currentTime: 0,
duration: 0,
paused: true,
volume: 1,
muted: false,
};
}
return {
currentTime: element.currentTime,
duration: element.duration,
paused: element.paused,
volume: element.volume,
muted: element.muted,
};
}
setState(state: Partial<MediaState>): void {
const element = this.elementRef.current;
if (!element) return;
if (state.currentTime !== undefined && state.currentTime !== element.currentTime) {
element.currentTime = state.currentTime;
}
if (state.volume !== undefined && state.volume !== element.volume) {
element.volume = state.volume;
}
if (state.muted !== undefined && state.muted !== element.muted) {
element.muted = state.muted;
}
}
private startPolling() {
this.intervalId = setInterval(() => {
const currentState = this.getState();
this.store.updateState(currentState);
}, 100);
}
destroy() {
if (this.intervalId) {
clearInterval(this.intervalId);
}
}
}
export function useMediaElementStore(elementRef: React.RefObject<MediaElementRef>): MediaStore {
const store = useMediaStore();
const ownerRef = useRef<ReactMediaStateOwner | null>(null);
useEffect(() => {
if (elementRef.current && !ownerRef.current) {
ownerRef.current = new ReactMediaStateOwner(elementRef, store);
store.addOwner(ownerRef.current);
}
return () => {
if (ownerRef.current) {
store.removeOwner(ownerRef.current);
ownerRef.current.destroy();
ownerRef.current = null;
}
};
}, [elementRef, store]);
return store;
}
export function useCurrentTime(): [number, (time: number) => void] {
const [state, updateState] = useMediaState();
const setCurrentTime = (time: number) => {
updateState({ currentTime: time });
};
return [state.currentTime, setCurrentTime];
}
export function useVolume(): [number, boolean, (volume: number) => void, (muted: boolean) => void] {
const [state, updateState] = useMediaState();
const setVolume = (volume: number) => {
updateState({ volume });
};
const setMuted = (muted: boolean) => {
updateState({ muted });
};
return [state.volume, state.muted, setVolume, setMuted];
}
export function usePlaybackState(): [boolean, () => void, () => void] {
const [state, updateState] = useMediaState();
const play = () => {
updateState({ paused: false });
};
const pause = () => {
updateState({ paused: true });
};
return [state.paused, play, pause];
}
// Re-export placeholder types
export type { MediaStore, MediaState, MediaStateOwner };
export type MediaStore = any;
export type MediaState = any;
export type MediaStateOwner = any;
+1
View File
@@ -37,6 +37,7 @@
"react": ">=16.8.0"
},
"devDependencies": {
"esbuild-css-modules-plugin": "^3.1.5",
"typescript": "^5.3.0",
"@types/react": "^18.0.0",
"react": "^18.0.0"
@@ -3,7 +3,6 @@ import { PauseIcon, PlayIcon } from '@vjs-10/react-icons';
import PlayButton from '../components/connected-with-defaults/PlayButton';
import MuteButton from '../components/connected-with-defaults/MuteButton';
import { VolumeHighIcon, VolumeLowIcon, VolumeOffIcon } from '@vjs-10/react-icons';
/** @ts-ignore */
import styles from './styles.module.css';
export const MediaSkinDefault: React.FC<{ children: React.ReactNode }> = ({
+10 -2
View File
@@ -1,7 +1,10 @@
import { defineConfig } from 'tsup';
import cssPlugin from 'esbuild-css-modules-plugin';
export default defineConfig({
entry: ['src/index.tsx'],
entry: {
index: 'src/index.tsx',
},
format: ['cjs', 'esm'],
clean: true,
sourcemap: true,
@@ -11,6 +14,11 @@ export default defineConfig({
/^@vjs-10\//,
/^[^.]/,
],
// Add esbuild plugin for CSS modules
esbuildPlugins: [cssPlugin()],
esbuildOptions(options, context) {
options.outbase = './src'; // Ensure 'src' is the base for output paths
},
// Generate TypeScript declarations with custom config
dts: {
compilerOptions: {
@@ -18,4 +26,4 @@ export default defineConfig({
incremental: false,
},
},
});
});