# Widget 1

> A real-time activity stream displaying recent system events and errors in a chronological layout.

A shadcn/ui block from the [@pacekit](https://ui.pacekit.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/pacekit/dashboard-widget-1).

- Page, with a live preview: https://shadcn.exchange/r/pacekit/dashboard-widget-1
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/pacekit/dashboard-widget-1-light-2a983fa396addccd.png
- Registry item: https://ui.pacekit.dev/r/dashboard-widget-1.json
- Listed: 2026-10-21, source last changed 2026-10-21

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/pacekit/dashboard-widget-1.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `motion`
- Registry items: `card`, `button`, `scroll-area`

## Files

### `components/blocks/dashboard/widget/widget-1.tsx`

```tsx
import { useEffect, useRef, useState } from "react";

import { ActivityIcon, PauseIcon, PlayIcon, TerminalIcon, Trash2Icon } from "lucide-react";
import { AnimatePresence, motion } from "motion/react";

import { cn } from "@/lib/utils";

import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { ScrollArea } from "@/components/ui/scroll-area";

type LogEntry = {
    id: string;
    timestamp: string;
    method: string;
    path: string;
    status: number;
    latency: string;
};

const methods = ["GET", "POST", "PUT", "DELETE"];
const paths = ["/api/v1/users", "/api/auth/login", "/api/products", "/api/settings", "/health"];
const statuses = [200, 200, 200, 201, 400, 401, 404, 500];

export const Widget1 = () => {
    const [logs, setLogs] = useState<LogEntry[]>([]);
    const [isPaused, setIsPaused] = useState(false);
    const scrollRef = useRef<HTMLDivElement>(null);

    const generateLog = (): LogEntry => {
        const date = new Date();
        return {
            id: Math.random().toString(36).slice(2, 9),
            timestamp: date.toLocaleTimeString("en-US", {
                hour12: false,
                hour: "2-digit",
                minute: "2-digit",
                second: "2-digit",
            }),
            method: methods[Math.floor(Math.random() * methods.length)],
            path: paths[Math.floor(Math.random() * paths.length)],
            status: statuses[Math.floor(Math.random() * statuses.length)],
            latency: `${Math.floor(Math.random() * 200) + 20}ms`,
        };
    };

    useEffect(() => {
        if (isPaused) return;

        const interval = setInterval(() => {
            setLogs((prev) => {
                const newLog = generateLog();
                const newLogs = [...prev, newLog];
                if (newLogs.length > 20) return newLogs.slice(newLogs.length - 20);
                return newLogs;
            });
        }, 2000);

        return () => clearInterval(interval);
    }, [isPaused]);

    useEffect(() => {
        if (scrollRef.current && !isPaused) {
            const viewport = scrollRef.current;
            if (viewport && viewport.scrollHeight - (viewport.scrollTop + viewport.clientHeight) < 50) {
                viewport?.scrollTo({ top: viewport?.scrollHeight, behavior: "smooth" });
            }
        }
    }, [logs, isPaused]);

    return (
        <Card className="flex h-full flex-col gap-0 pt-3 pb-0">
            <CardHeader className="flex items-center justify-between space-y-0 border-b px-4 pb-3!">
                <div className="flex items-center gap-2">
                    <TerminalIcon className="size-4" />
                    <CardTitle className="flex items-center gap-3 whitespace-nowrap">
                        <p>Live Console</p>
                        <div className="bg-foreground/15 size-1.25 rounded-full max-sm:hidden"></div>
                        <span className="text-muted-foreground text-xs max-sm:hidden">Last {logs.length} events</span>
                    </CardTitle>
                </div>
                <div className="flex items-center gap-0">
                    <Button
                        variant="ghost"
                        size="icon-sm"
                        onClick={() => setIsPaused(!isPaused)}
                        aria-label={isPaused ? "Resume Logs" : "Pause Logs"}>
                        {isPaused ? <PlayIcon className="size-4" /> : <PauseIcon className="size-4" />}
                    </Button>
                    <Button
                        aria-label="Clear Logs"
                        variant="destructive"
                        className="bg-transparent"
                        size="icon-sm"
                        onClick={() => setLogs([])}>
                        <Trash2Icon className="size-4" />
                    </Button>
                </div>
            </CardHeader>

            <CardContent className="h-64 min-h-0 grow px-1.5 pt-0">
                {logs.length === 0 && (
                    <div className="text-muted-foreground flex h-full flex-col items-center justify-center gap-2 py-4">
                        <ActivityIcon className="size-6" />
                        <span>Waiting for incoming requests...</span>
                    </div>
                )}
                <ScrollArea ref={scrollRef} className="flex h-full flex-col gap-px">
                    <div className="my-1.5">
                        <AnimatePresence initial={false}>
                            {logs.map((log) => (
                                <motion.div
                                    key={log.id}
                                    initial={{ opacity: 0, x: -10 }}
                                    animate={{ opacity: 1, x: 0 }}
                                    transition={{ duration: 0.2 }}
                                    className="group hover:bg-accent flex cursor-pointer items-center gap-3 rounded-md px-2.5 py-1.5">
                                    <span className="text-muted-foreground w-20">{log.timestamp}</span>
                                    <span
                                        className={cn("w-12 text-xs font-medium", {
                                            "text-primary": ["GET", "POST"].includes(log.method),
                                            "text-destructive": log.method === "DELETE",
                                        })}>
                                        {log.method}
                                    </span>
                                    <span
                                        className={cn("w-10 font-medium", {
                                            "text-destructive": log.status > 400,
                                        })}>
                                        {log.status}
                                    </span>
                                    <span className="text-muted-foreground grow truncate">{log.path}</span>
                                    <span className="text-muted-foreground min-w-12 text-end text-xs">
                                        {log.latency}
                                    </span>
                                </motion.div>
                            ))}
                        </AnimatePresence>
                    </div>
                </ScrollArea>
            </CardContent>
        </Card>
    );
};
```
