Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
400 changes: 193 additions & 207 deletions README.md

Large diffs are not rendered by default.

154 changes: 154 additions & 0 deletions TELEBUBBLE_SHOWCASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# 🌀 TelebubblePlayer New Showcase

This document describes the new TelebubblePlayer showcase that demonstrates the sub-component API structure.

## 🚀 Quick Start

1. Run the development server:
```bash
npm run dev
```

2. Open your browser to see both showcases with a toggle button in the top-right corner

3. Click "New TelebubblePlayer Showcase" to explore the new component structure

## 📋 Showcase Features

The new showcase demonstrates:

### 🎯 **Basic Usage**
- Simple TelebubblePlayer with default styling
- Essential props: `src`, `size`, `playing`, `onPlay`, `onPause`, `onEnd`

### 🎨 **Track Variations**
- Thin and thick track configurations
- Different stroke colors and widths
- Customizable background tracks

### ⚡ **Progress Styles**
- Gradient and solid progress indicators
- Minimal and bold styling options
- Color-matched thumbs

### 🎯 **Thumb Styles**
- Large and small thumb variants
- Color coordination with progress
- Custom radius options

### 🎭 **Custom Icons**
- Heart-shaped play buttons
- Star-shaped play buttons
- Square pause buttons
- Custom SVG icons

### 🎨 **Overlay Customization**
- Custom overlay positioning
- Additional UI elements (settings, quality indicators)
- Advanced button placement

### 🚀 **Advanced Combinations**
- Complex overlay layouts
- Multiple custom elements
- Professional video player styling

## 🔧 Component API Structure

```tsx
<TelebubblePlayer src="video.mp4" size="200px">
<TelebubblePlayer.Track strokeColor="#666" strokeWidth={8} />
<TelebubblePlayer.Progress strokeColor="#00aaff" strokeWidth={10} />
<TelebubblePlayer.Thumb radius={5} fill="#fff" />
<TelebubblePlayer.Overlay>
<TelebubblePlayer.PlayButton
icon={<CustomIcon />}
className="custom-play-btn"
/>
<TelebubblePlayer.PauseButton
icon={<CustomIcon />}
className="custom-pause-btn"
/>
</TelebubblePlayer.Overlay>
</TelebubblePlayer>
```

## 🎨 Customization Options

### Track Props
- `strokeColor`: Background track color
- `strokeWidth`: Track thickness
- `fill`: Track fill color
- `strokeLinecap`: Track line cap style

### Progress Props
- `strokeColor`: Progress indicator color
- `strokeWidth`: Progress thickness
- `fill`: Progress fill color
- `strokeLinecap`: Progress line cap style

### Thumb Props
- `radius`: Thumb size
- `fill`: Thumb color
- `stroke`: Thumb border color
- `strokeWidth`: Thumb border thickness

### PlayButton/PauseButton Props
- `icon`: Custom icon component
- `className`: CSS class for styling
- Automatically shows/hides based on playing state

### Overlay Props
- `className`: CSS class for overlay container
- `children`: Any custom elements to display

## 🎨 CSS Styling

The showcase includes custom CSS for various button styles:

- `.basic-play-btn`, `.basic-pause-btn` - Simple circular buttons
- `.heart-play-btn`, `.heart-pause-btn` - Gradient heart-styled buttons
- `.star-play-btn`, `.star-pause-btn` - Star-themed buttons
- `.overlay-play-btn`, `.overlay-pause-btn` - Overlay-specific styling
- `.advanced-play-btn`, `.advanced-pause-btn` - Professional button styling

## 🔄 Migration from Old API

The new API provides a more declarative and composable structure:

**Old API:**
```tsx
<VideoPlayer
src="video.mp4"
size="200px"
playButtonIcon={customIcon}
progressRingStrokeColor="#00aaff"
// ... many individual props
/>
```

**New API:**
```tsx
<TelebubblePlayer src="video.mp4" size="200px">
<TelebubblePlayer.Track strokeColor="#666" />
<TelebubblePlayer.Progress strokeColor="#00aaff" />
<TelebubblePlayer.Thumb radius={5} />
<TelebubblePlayer.Overlay>
<TelebubblePlayer.PlayButton icon={customIcon} />
</TelebubblePlayer.Overlay>
</TelebubblePlayer>
```

## 🎯 Benefits

1. **Composability**: Mix and match components as needed
2. **Flexibility**: Custom styling per component
3. **Maintainability**: Clear component separation
4. **Extensibility**: Easy to add new sub-components
5. **Developer Experience**: Intuitive API structure

## 🚀 Next Steps

- Explore the showcase by clicking different examples
- Customize the styling by modifying CSS classes
- Create your own custom icons and components
- Experiment with different combinations of sub-components
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"prepare": "npm run build"
},
"dependencies": {
"clsx": "^2.1.1",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
Expand Down
78 changes: 78 additions & 0 deletions src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { useCallback } from 'react';
import { ProgressRingInput } from '@/components/ProgressRing/ProgressRingInput';
import { Track } from '@/components/ProgressRing/Track';
import { Progress } from '@/components/ProgressRing/Progress';
import { Thumb } from '@/components/ProgressRing/Thumb';

export type InputProps = {
value: number;
onChange?: (value: number) => void;
clickTolerance?: number;
hasStarted?: boolean;
// Styling props
trackStrokeColor?: string;
trackStrokeWidth?: number;
trackFill?: string;
trackStrokeLinecap?: 'butt' | 'round' | 'square';
progressStrokeColor?: string;
progressStrokeWidth?: number;
className?: string;
};

export const Input: React.FC<InputProps> = ({
value = 25,
onChange = () => { },
clickTolerance = 5,
hasStarted = false,
// Styling props with defaults
trackStrokeColor = "#ccc",
trackStrokeWidth = 8,
trackFill = "none",
trackStrokeLinecap = "round",
progressStrokeColor = "#0af",
progressStrokeWidth = 10,
className,
}) => {
// Handle seeking - just call onChange, let parent handle video seeking
const handleSeek = useCallback((newProgress: number) => {
onChange(newProgress);
}, [onChange]);

return (
<ProgressRingInput
value={value}
onChange={handleSeek}
clickTolerance={clickTolerance}
hasStarted={hasStarted}
className={className}
style={{
position: 'absolute',
top: 0,
left: 0,
zIndex: 2,
width: '100%',
height: '100%',
pointerEvents: 'auto',
}}
>
<Track
stroke={trackStrokeColor}
strokeWidth={trackStrokeWidth}
fill={trackFill}
strokeLinecap={trackStrokeLinecap}
/>
<Progress
strokeColor={progressStrokeColor}
strokeWidth={progressStrokeWidth}
fill="none"
strokeLinecap="round"
/>
<Thumb
fill="#ffffff"
stroke={progressStrokeColor}
strokeWidth={3}
radius={8}
/>
</ProgressRingInput>
);
};
52 changes: 52 additions & 0 deletions src/components/ProgressRing/Overlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from "react";
import { clsx } from "@/utils";

export interface OverlayProps {
children?: React.ReactNode;
className?: string;
isPlaying?: boolean;
togglePlay?: () => void;
handleKeyPress?: (e: React.KeyboardEvent) => void;
}

export const Overlay: React.FC<OverlayProps> = ({
children,
className,
isPlaying,
togglePlay,
handleKeyPress,
}) => {
// Clone children and pass props if they're ToggleButton
const enhancedChildren = React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
// Check if it's a ToggleButton by type
const childType = child.type as { name?: string };
if (childType && childType.name === 'ToggleButton') {
return React.cloneElement(child as React.ReactElement<Record<string, unknown>>, {
isPlaying,
onToggle: togglePlay,
onKeyDown: handleKeyPress,
...(child.props as Record<string, unknown>),
});
}
}
return child;
});

return (
<div
className={clsx("telebubble-overlay", className)}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
pointerEvents: 'none',
zIndex: 10,
}}
>
{enhancedChildren}
</div>
);
};
48 changes: 48 additions & 0 deletions src/components/ProgressRing/Progress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { useCallback } from 'react';
import { useProgressRingInputContext } from '@/components/ProgressRing/ProgressRingInputContext';
import { DEG_360_IN_RAD } from '@/utils/progressRing';

export interface ProgressProps {
strokeColor?: string;
strokeWidth?: number;
fill?: string;
strokeLinecap?: 'butt' | 'round' | 'square';
className?: string;
}

export const Progress: React.FC<ProgressProps> = ({
strokeColor = "#0af",
strokeWidth = 10,
fill = "none",
strokeLinecap = "round",
className,
}) => {
const { value, radius, center, getValueFromPointerEvent, onChange } = useProgressRingInputContext();
const innerCircumference = DEG_360_IN_RAD * radius;

const handleClick = useCallback((e: React.MouseEvent<SVGCircleElement>) => {
e.stopPropagation();
if (onChange) {
const nearestValue = getValueFromPointerEvent(e.nativeEvent);
onChange(nearestValue);
}
}, [onChange, getValueFromPointerEvent]);

return (
<circle
stroke={strokeColor}
strokeWidth={strokeWidth}
fill={fill}
strokeLinecap={strokeLinecap}
strokeDasharray={innerCircumference}
strokeDashoffset={innerCircumference * (1 - value / 100)}
transform={`rotate(-90 ${center.x} ${center.y})`}
cx={center.x}
cy={center.y}
r={radius}
onClick={handleClick}
className={className}
style={{ cursor: 'pointer' }}
/>
);
};
Loading