Skip to content

Commit

Permalink
feat: offline banner
Browse files Browse the repository at this point in the history
  • Loading branch information
ImLunaHey committed Jan 4, 2025
1 parent 0d26edd commit 6a9b71b
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/components/FeedSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import { Loading } from './ui/loading';
import { TabList } from './ui/tab-list';
import { Tab } from './ui/tab';
import { useQueryClient } from '@tanstack/react-query';
import { useOfflineStatus } from '@/hooks/useOfflineStatus';
import { cn } from '@/lib/utils';

export const FeedSelector = ({ columnNumber = 1 }: { columnNumber: number }) => {
const isOffline = useOfflineStatus();
const queryClient = useQueryClient();
const { setSettings, columns } = useSettings();
const { isAuthenticated } = useAuth();
Expand Down Expand Up @@ -66,7 +69,7 @@ export const FeedSelector = ({ columnNumber = 1 }: { columnNumber: number }) =>
{/* if there are less than 2 feeds, don't show the selector */}
{feeds.length >= 2 && (
<>
<TabList label="feed selector" className="sticky top-0 bg-background z-50">
<TabList label="feed selector" className={cn('sticky bg-background z-50', isOffline ? 'top-[46px]' : 'top-0')}>
{data?.map((feed) => {
return (
<Tab
Expand Down
38 changes: 38 additions & 0 deletions src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { cva, type VariantProps } from 'class-variance-authority';

import { cn } from '@/lib/utils';
import { forwardRef } from 'react';

const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive: 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: 'default',
},
},
);

export const Alert = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>>(
({ className, variant, ...props }, ref) => (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
),
);
Alert.displayName = 'Alert';

export const AlertTitle = forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h5 ref={ref} className={cn('mb-1 font-medium leading-none tracking-tight', className)} {...props} />
),
);
AlertTitle.displayName = 'AlertTitle';

export const AlertDescription = forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />,
);
AlertDescription.displayName = 'AlertDescription';
19 changes: 19 additions & 0 deletions src/components/ui/offline-banner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { WifiOff } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { useOfflineStatus } from '@/hooks/useOfflineStatus';

export const OfflineBanner = () => {
const isOffline = useOfflineStatus();

if (!isOffline) return null;

return (
<Alert
variant="destructive"
className="sticky top-0 left-0 right-0 z-50 rounded-none flex items-center justify-center animate-in fade-in slide-in-from-top duration-300 bg-background"
>
<WifiOff className="h-4 w-4" />
<AlertDescription className="ml-2">{"You're currently offline. Some features may be unavailable."}</AlertDescription>
</Alert>
);
};
20 changes: 20 additions & 0 deletions src/hooks/useOfflineStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useState, useEffect } from 'react';

export const useOfflineStatus = () => {
const [isOffline, setIsOffline] = useState(!navigator.onLine);

useEffect(() => {
const handleOnline = () => setIsOffline(false);
const handleOffline = () => setIsOffline(true);

window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);

return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);

return isOffline;
};
2 changes: 2 additions & 0 deletions src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Toaster } from '@/components/ui/sonner';
import { Helmet } from 'react-helmet';
import { appName } from '@/config';
import { useUnreadCount } from '@/lib/bluesky/hooks/useUnreadCount';
import { OfflineBanner } from '@/components/ui/offline-banner';

export const Route = createRootRoute({
component: Root,
Expand Down Expand Up @@ -50,6 +51,7 @@ function Root() {
return (
<>
<Helmet titleTemplate={`${unreadCount ? `(${unreadCount})` : ''} %s - ${appName}`} defaultTitle={appName} />
<OfflineBanner />
<main
dir={dir}
lang={language}
Expand Down

0 comments on commit 6a9b71b

Please sign in to comment.