Move to monorepo for React Native!
This commit is contained in:
164
apps/next/src/components/providers/TVModeProvider.tsx
Normal file
164
apps/next/src/components/providers/TVModeProvider.tsx
Normal file
@@ -0,0 +1,164 @@
|
||||
'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 };
|
||||
Reference in New Issue
Block a user