Documentation Index
Fetch the complete documentation index at: https://mintlify.com/mario1027/offlinetube/llms.txt
Use this file to discover all available pages before exploring further.
Overview
OfflineTube provides several custom React hooks to simplify common tasks like theme management, toast notifications, and responsive design.
Available Hooks
useTheme
Manages application theme (dark/light mode) with localStorage persistence.
Location: /workspace/source/src/lib/theme.tsx:241
Returns
interface ThemeContextType {
theme: 'dark' | 'light';
setTheme: (theme: 'dark' | 'light') => void;
toggleTheme: () => void;
}
Usage
import { useTheme } from '@/lib/theme';
function ThemeToggleButton() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>
Current theme: {theme}
Switch to {theme === 'dark' ? 'light' : 'dark'} mode
</button>
);
}
Features
- Automatic persistence: Saves theme preference to
localStorage with key 'yt-theme'
- Dynamic styling: Injects CSS overrides for light mode at runtime
- HTML class management: Adds/removes
'dark' and 'light' classes on <html> element
- SSR-safe: Handles initial theme load from localStorage in
useEffect
Theme Provider Setup
Wrap your app with ThemeProvider to enable the hook:
import { ThemeProvider } from '@/lib/theme';
function App() {
return (
<ThemeProvider>
<YourAppContent />
</ThemeProvider>
);
}
Implementation Details
The hook uses React Context under the hood and applies theme changes by:
- Adding/removing CSS classes on the HTML element
- Injecting or removing a runtime
<style> tag with light mode overrides
- Persisting the selection to localStorage
This approach allows Tailwind arbitrary values (like bg-[#0f0f0f]) to work without crashing the CSS parser in development.
useToast
Displays toast notifications for user feedback (success, error, info messages).
Location: /workspace/source/src/hooks/use-toast.ts:174
Inspired by: react-hot-toast
Returns
interface UseToastReturn {
toasts: ToasterToast[];
toast: (props: Toast) => { id: string; dismiss: () => void; update: (props: ToasterToast) => void };
dismiss: (toastId?: string) => void;
}
Usage
import { useToast } from '@/hooks/use-toast';
function DownloadButton() {
const { toast } = useToast();
const handleDownload = async () => {
try {
await downloadFile();
toast({
title: 'Success',
description: 'File downloaded successfully',
variant: 'default',
});
} catch (error) {
toast({
title: 'Error',
description: 'Failed to download file',
variant: 'destructive',
});
}
};
return <button onClick={handleDownload}>Download</button>;
}
Using with Sonner
OfflineTube also uses sonner for toast notifications. The recommended approach is to use sonner directly:
import { toast } from 'sonner';
function Example() {
const handleAction = () => {
toast.success('Operation completed!');
toast.error('Something went wrong');
toast.info('Did you know...');
};
return <button onClick={handleAction}>Trigger Toast</button>;
}
Toast Options
type Toast = {
title?: React.ReactNode;
description?: React.ReactNode;
action?: ToastActionElement;
variant?: 'default' | 'destructive';
};
Managing Toasts
const { toast, dismiss } = useToast();
// Show a toast
const { id } = toast({ title: 'Processing...' });
// Dismiss a specific toast
dismiss(id);
// Dismiss all toasts
dismiss();
Advanced: Update Toast
const { toast } = useToast();
const { update } = toast({
title: 'Uploading...',
description: '0%',
});
// Later, update the same toast
update({
title: 'Upload Complete',
description: '100%',
});
useIsMobile
Detects if the current viewport is mobile-sized (< 768px).
Location: /workspace/source/src/hooks/use-mobile.ts:5
Returns
boolean // true if viewport width < 768px, false otherwise
Usage
import { useIsMobile } from '@/hooks/use-mobile';
function ResponsiveComponent() {
const isMobile = useIsMobile();
return (
<div>
{isMobile ? (
<MobileLayout />
) : (
<DesktopLayout />
)}
</div>
);
}
Conditional Rendering
function Sidebar() {
const isMobile = useIsMobile();
if (isMobile) {
return <MobileDrawer />;
}
return <DesktopSidebar />;
}
Breakpoint
The hook uses a breakpoint of 768px, which corresponds to Tailwind’s md: breakpoint.
const MOBILE_BREAKPOINT = 768;
Features
- Reactive: Updates when window is resized
- SSR-safe: Returns
undefined initially, then resolves after mount
- Performant: Uses
matchMedia API with event listeners
- Cleanup: Automatically removes listeners on unmount
Advanced: Custom Breakpoint
If you need a different breakpoint, create your own hook:
import * as React from 'react';
function useCustomBreakpoint(breakpoint: number) {
const [matches, setMatches] = React.useState<boolean | undefined>(undefined);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${breakpoint - 1}px)`);
const onChange = () => setMatches(window.innerWidth < breakpoint);
mql.addEventListener('change', onChange);
setMatches(window.innerWidth < breakpoint);
return () => mql.removeEventListener('change', onChange);
}, [breakpoint]);
return !!matches;
}
// Usage
function Component() {
const isSmall = useCustomBreakpoint(640); // Tailwind 'sm'
const isMedium = useCustomBreakpoint(768); // Tailwind 'md'
const isLarge = useCustomBreakpoint(1024); // Tailwind 'lg'
}
Best Practices
Theme Hook
- Always wrap with ThemeProvider: Ensure
ThemeProvider wraps your app root
- Use theme-aware classes: The hook manages CSS classes, so use conditional classNames based on theme
- Avoid manual theme detection: Use the hook instead of checking
document.documentElement.classList
Toast Hook
- Prefer sonner: Use
sonner library directly for simpler toast notifications
- Keep messages concise: Toast messages should be brief and actionable
- Use appropriate variants:
default for success/info, destructive for errors
- Dismiss programmatically: Store toast IDs if you need to dismiss them later
Mobile Hook
- Combine with Tailwind: Use for JS logic, but prefer Tailwind responsive classes for styling
- Handle undefined state: The initial value is
undefined during SSR
- Avoid overuse: Use CSS media queries when possible for better performance
Hook Composition Example
Combine multiple hooks for complex components:
import { useTheme } from '@/lib/theme';
import { useIsMobile } from '@/hooks/use-mobile';
import { toast } from 'sonner';
function AdaptiveComponent() {
const { theme } = useTheme();
const isMobile = useIsMobile();
const handleAction = () => {
const message = isMobile ? 'Action on mobile' : 'Action on desktop';
toast.success(message);
};
const containerClass = isMobile
? 'p-2'
: theme === 'dark'
? 'p-6 bg-[#0f0f0f]'
: 'p-6 bg-white';
return (
<div className={containerClass}>
<button onClick={handleAction}>Perform Action</button>
</div>
);
}
Creating Custom Hooks
Follow these patterns when creating new hooks:
import { useState, useEffect } from 'react';
export function useCustomHook() {
const [state, setState] = useState<Type>(initialValue);
useEffect(() => {
// Setup
const listener = () => setState(newValue);
window.addEventListener('event', listener);
// Cleanup
return () => window.removeEventListener('event', listener);
}, [dependencies]);
return state;
}
Guidelines
- Prefix with
use: All hooks must start with use
- Return consistent types: Avoid changing return type based on conditions
- Document return values: Use JSDoc or TypeScript interfaces
- Handle cleanup: Always remove event listeners and clear timers
- Be mindful of dependencies: Include all dependencies in
useEffect arrays