Files
techtracker/apps/next/src/components/providers/TVModeProvider.tsx

165 lines
4.6 KiB
TypeScript

'use client';
import React, { createContext, useContext, useState } from 'react';
import type { ReactNode } from 'react';
import { Button } from '@/components/ui';
import { type ComponentProps } from 'react';
import { cn } from '@/lib/utils';
type TVModeContextProps = {
tvMode: boolean;
toggleTVMode: () => void;
};
type TVToggleProps = {
buttonClassName?: ComponentProps<typeof Button>['className'];
buttonProps?: Omit<ComponentProps<typeof Button>, 'className'>;
size?: number;
};
const TVModeContext = createContext<TVModeContextProps | undefined>(undefined);
const TVModeProvider = ({ children }: { children: ReactNode }) => {
const [tvMode, setTVMode] = useState(false);
const toggleTVMode = () => {
setTVMode((prev) => !prev);
};
return (
<TVModeContext.Provider value={{ tvMode, toggleTVMode }}>
{children}
</TVModeContext.Provider>
);
};
const useTVMode = () => {
const context = useContext(TVModeContext);
if (!context) {
throw new Error('useTVMode must be used within a TVModeProvider');
}
return context;
};
// TV Icon Component with animations
const TVIcon = ({ tvMode, size = 25 }: { tvMode: boolean; size?: number }) => {
return (
<div
className='relative transition-all duration-300 ease-in-out'
style={{ width: size, height: size }}
>
<svg
width={size}
height={size}
viewBox='0 0 24 24'
fill='none'
className='transition-all duration-300 ease-in-out'
>
{/* TV Screen */}
<rect
x='3'
y='6'
width='18'
height='12'
rx='2'
className={cn(
'stroke-current stroke-2 fill-none transition-all duration-300',
tvMode ? 'stroke-blue-500 animate-pulse' : 'stroke-current',
)}
/>
{/* TV Stand */}
<path
d='M8 18h8M12 18v2'
className='stroke-current stroke-2 transition-all duration-300'
/>
{/* Corner arrows - animate based on mode */}
<g
className={cn(
'transition-all duration-300 ease-in-out origin-center',
tvMode ? 'scale-75 opacity-100' : 'scale-100 opacity-70',
)}
>
{tvMode ? (
// Exit fullscreen arrows (pointing inward)
<>
<path
d='M6 8l2 2M6 8h2M6 8v2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
<path
d='M18 8l-2 2M18 8h-2M18 8v2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
<path
d='M6 16l2-2M6 16h2M6 16v-2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
<path
d='M18 16l-2-2M18 16h-2M18 16v-2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
</>
) : (
// Enter fullscreen arrows (pointing outward)
<>
<path
d='M8 6l-2 2M8 6v2M8 6h-2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
<path
d='M16 6l2 2M16 6v2M16 6h2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
<path
d='M8 18l-2-2M8 18v-2M8 18h-2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
<path
d='M16 18l2-2M16 18v-2M16 18h2'
className='stroke-current stroke-1.5 transition-all duration-300'
/>
</>
)}
</g>
{/* Optional: Screen content indicator */}
<circle
cx='12'
cy='12'
r='1'
className={cn(
'transition-all duration-300',
tvMode ? 'fill-blue-400 animate-ping' : 'fill-current opacity-30',
)}
/>
</svg>
</div>
);
};
const TVToggle = ({
buttonClassName,
buttonProps = {
variant: 'outline',
size: 'default',
},
size = 25,
}: TVToggleProps) => {
const { tvMode, toggleTVMode } = useTVMode();
return (
<Button
onClick={toggleTVMode}
className={cn(
'my-auto cursor-pointer transition-all duration-200 hover:scale-105 active:scale-95',
buttonClassName,
)}
aria-label={tvMode ? 'Exit TV Mode' : 'Enter TV Mode'}
{...buttonProps}
>
<TVIcon tvMode={tvMode} size={size} />
</Button>
);
};
export { TVModeProvider, useTVMode, TVToggle };