npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

glide-frame

v2.0.0

Published

iOS-style draggable, resizable floating container for React & Next.js. YouTube mini-player inspired picture-in-picture component with momentum physics, dock-to-edge, and glassmorphism styling.

Readme

GlideFrame

Deploy to GitHub Pages License: MIT

A YouTube mini-player inspired draggable and resizable floating container component for Next.js 16. Create picture-in-picture style floating windows that persist while users navigate your site.

🎬 Live Demo

View Live Demo →

✨ Features

  • 🖱️ Draggable - Drag from header to reposition anywhere on screen
  • 📐 Resizable - Resize from edges and corners with smooth animations
  • 📱 Mobile First - Full touch support with responsive design
  • 🚀 iOS-Style Momentum - Physics-based throwing with velocity and friction
  • 🎯 Dock to Edge - Swipe to edge to minimize, tap handle to restore
  • 🎯 Multi-Instance - Multiple frames with automatic z-index management
  • 💾 Persistent State - Position and size saved to localStorage
  • Glassmorphism - Modern blur backdrop with beautiful styling
  • 🌙 Dark Mode - Full support for light/dark themes via shadcn/ui
  • 60 FPS - Hardware-accelerated animations for smooth performance
  • 🔧 Fully Typed - Complete TypeScript support with exported types
  • 🎥 Stateful Detach - Pop-out iframe/video without reloading (preserves state)

📦 Installation

# Install dependencies
pnpm add react-rnd lucide-react

# Initialize shadcn/ui (if not already done)
pnpm dlx shadcn@latest init

🚀 Quick Start

Basic Usage

import { GlideFrame } from "@/components/glide-frame";

function App() {
  const [isOpen, setIsOpen] = useState(true);

  if (!isOpen) return null;

  return (
    <GlideFrame
      id="my-frame"
      title="My Floating Window"
      defaultPosition={{ x: 100, y: 100 }}
      defaultSize={{ width: 480, height: 320 }}
      onClose={() => setIsOpen(false)}
    >
      {/* Any content: iframe, video, React components */}
      <iframe
        src="https://example.com"
        className="w-full h-full border-0"
      />
    </GlideFrame>
  );
}

Persistent Frames Across Pages

Use GlideFrameProvider in your layout to keep frames visible while navigating:

// app/layout.tsx
import { GlideFrameProvider } from "@/components/glide-frame";

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <GlideFrameProvider>
          {children}
        </GlideFrameProvider>
      </body>
    </html>
  );
}

// Any page component
import { useGlideFrameContext } from "@/components/glide-frame";

function MyPage() {
  const { openFrame, closeFrame } = useGlideFrameContext();

  const handleOpenVideo = () => {
    openFrame({
      id: "video-player",
      title: "Video Player",
      content: <iframe src="https://youtube.com/embed/..." />,
      defaultSize: { width: 480, height: 320 },
      headerStyle: { backgroundColor: "#dc2626", buttonColor: "#fff" },
    });
  };

  return <button onClick={handleOpenVideo}>Open Video</button>;
}

📖 API Reference

Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | id | string | required | Unique identifier for the frame instance | | title | string | undefined | Title displayed in the header bar | | defaultPosition | { x: number, y: number } | Top-right corner | Initial position on screen | | defaultSize | { width: number, height: number } | 800x600 | Initial dimensions | | minSize | { width: number, height: number } | 400x300 (desktop) / 280x200 (mobile) | Minimum resize constraints | | maxSize | { width: number, height: number } | Screen size - 40px | Maximum resize constraints | | onClose | () => void | undefined | Callback when close button is clicked | | onStateChange | (state: GlideFrameState) => void | undefined | Callback when state changes | | persist | boolean | true | Whether to persist position/size to localStorage | | className | string | undefined | Additional CSS classes for the container | | children | ReactNode | undefined | Content to render inside the frame |

State Object

The onStateChange callback receives a state object with:

interface GlideFrameState {
  position: { x: number; y: number };
  size: { width: number; height: number };
  isMinimized: boolean;
  isMaximized: boolean;
  isDocked: boolean;
  dockedSide: 'left' | 'right' | null;
  isVisible: boolean;
  zIndex: number;
}

🎮 Controls & Interactions

Header Buttons

| Button | Action | |--------|--------| | □ | Maximize to fullscreen | | ↺ | Restore from maximized/docked state | | × | Close the frame |

Gestures

  • Drag Header - Move the frame around
  • Double-click/tap Header - Toggle maximize
  • Throw to Edge - Momentum-based dock (swipe fast toward edge)
  • Tap Dock Handle - Restore from docked state
  • Resize Edges/Corners - Resize the frame

Keyboard (when focused)

  • Frame receives focus on interaction for accessibility

🎨 Customization

Header Style Options

<GlideFrame
  id="styled-frame"
  title="Custom Header"
  headerStyle={{
    backgroundColor: "#dc2626",      // Background color or gradient
    textColor: "#ffffff",            // Title text color
    buttonColor: "#ffffff",          // Icon button color
    buttonHoverColor: "#ffcccc",     // Button hover color
    height: 40,                      // Header height in pixels
    showMaximize: true,              // Show/hide maximize button
    showClose: true,                 // Show/hide close button
  }}
>
  <YourContent />
</GlideFrame>

Frame Style Options

<GlideFrame
  id="styled-frame"
  title="Custom Frame"
  frameStyle={{
    backgroundColor: "#1e293b",      // Frame background color
    borderColor: "#dc2626",          // Border color
    borderWidth: 2,                  // Border width in pixels
    borderRadius: 12,                // Corner radius in pixels
    boxShadow: "0 0 30px rgba(0,0,0,0.3)", // Custom shadow
  }}
>
  <YourContent />
</GlideFrame>

Combined Example

<GlideFrame
  id="video-player"
  title="Video Player"
  headerStyle={{
    backgroundColor: "linear-gradient(90deg, #f59e0b, #ef4444)",
    textColor: "#fff",
    buttonColor: "#fff",
    height: 36,
  }}
  frameStyle={{
    borderRadius: 16,
    boxShadow: "0 0 30px rgba(245, 158, 11, 0.3)",
  }}
>
  <iframe src="https://youtube.com/embed/..." />
</GlideFrame>

DetachableContent - Stateful Pop-out

Convert any inline content (iframe, video, component) to a floating window without losing state:

import { DetachableContent } from "@/components/glide-frame";

function Page() {
  return (
    <DetachableContent
      id="video-player"
      title="YouTube Video"
      headerStyle={{ backgroundColor: "#dc2626", buttonColor: "#fff" }}
      frameStyle={{ borderRadius: 12, borderColor: "#dc2626", borderWidth: 2 }}
    >
      {/* iframe won't reload when detached! */}
      <iframe
        src="https://www.youtube.com/embed/dQw4w9WgXcQ"
        className="w-full aspect-video"
        allowFullScreen
      />
    </DetachableContent>
  );
}

How it works:

  • Hover over content → pop-out button appears
  • Click pop-out → content floats without reloading
  • Placeholder shows where content was
  • Click "Restore here" or close → content returns to original position

This is perfect for:

  • 🎥 Video players that shouldn't restart
  • 🎮 Games with state (canvas, WebGL)
  • 📊 Live dashboards with WebSocket connections
  • 📝 Forms with user input

Momentum Physics

Adjust the physics constants in types.ts:

export const MOMENTUM_FRICTION = 0.92;    // 0-1, higher = slides further
export const MOMENTUM_MIN_VELOCITY = 0.5; // Stop threshold
export const MOMENTUM_MULTIPLIER = 8;     // Velocity amplification
export const DOCK_MIN_VELOCITY = 2;       // Min speed to trigger dock

📁 Project Structure

components/glide-frame/
├── GlideFrame.tsx          # Main component with react-rnd integration
├── GlideFrameHeader.tsx    # Header bar with control buttons
├── GlideFrameProvider.tsx  # Context provider for persistent frames
├── DetachableContent.tsx   # Stateful pop-out wrapper (preserves iframe state)
├── types.ts                # TypeScript interfaces and constants
├── index.ts                # Public exports
└── hooks/
    └── useGlideFrame.ts    # State management hook with localStorage

🛠️ Tech Stack

| Technology | Purpose | |------------|---------| | Next.js 16 | React framework with App Router | | React 19 | UI library | | TypeScript | Type safety | | react-rnd | Drag and resize functionality | | shadcn/ui | UI components and theming | | Tailwind CSS 4 | Styling | | Lucide React | Icons |

💻 Development

# Clone the repository
git clone https://github.com/atknatk/glide-frame.git
cd glide-frame

# Install dependencies
pnpm install

# Start development server
pnpm dev

# Build for production
pnpm build

# Run linting
pnpm lint

🚀 Deployment

This project uses GitHub Actions for automatic deployment to GitHub Pages.

Every push to main triggers:

  1. Install dependencies
  2. Build the Next.js application
  3. Deploy to GitHub Pages

📄 License

MIT License - see LICENSE for details.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request