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

@mieweb/news-widget

v1.0.0-alpha3

Published

Embeddable Instagram-style news feed widget with video playback, swipe gestures, and real-time comments

Downloads

64

Readme

News Widget - Instagram-Style Video Feed

An embeddable, responsive news feed component with Instagram-style video playback, swipe gestures, and real-time comments. Built with React 19, TypeScript, and Vite.

✨ Features

  • 📱 Instagram-style UI - Vertical scrolling feed with full-screen video viewer
  • 🎥 Multi-format media - Supports YouTube videos, MP4 files, and images
  • 👆 Touch-optimized - Swipe gestures for navigation (mobile-first)
  • 🎬 Auto-play - Videos play automatically when visible, pause when scrolled away
  • 💬 Real-time comments - Fetch and post comments via Discourse integration
  • 🎨 Themeable - Respects parent page color schemes via CSS custom properties
  • Accessibility-first - Full ARIA support, keyboard navigation, screen reader tested
  • 🌐 RSS-powered - Parses standard RSS feeds with media enclosures

🚀 Quick Start

Development

cd news-widget
npm install
npm run dev        # Start dev server at http://localhost:5173
npm run build      # Build for production
npm run preview    # Preview production build

Testing

npm test           # Run Playwright E2E tests
npm run test:ui    # Open Playwright UI
npm run test:headed # Run tests in headed mode

📦 Embedding the Widget

Option 1: NPM Package (Recommended for React Apps)

Install the widget as an NPM dependency:

npm install @mieweb/news-widget
# or
yarn add @mieweb/news-widget
# or
pnpm add @mieweb/news-widget

Using as a React Component

import { NewsWidget } from '@mieweb/news-widget';
import '@mieweb/news-widget/style.css';

function App() {
  return (
    <div className="my-app">
      <h1>Latest News</h1>
      {/* Show landing page with all feeds */}
      <NewsWidget />

      {/* Or render a specific feed directly */}
      <NewsWidget feedId="features" />
    </div>
  );
}

Using with Vanilla JavaScript

import { renderNewsWidget } from '@mieweb/news-widget';
import '@mieweb/news-widget/style.css';

// Show landing page with all feeds
renderNewsWidget(document.getElementById('news-feed'));

// Or render a specific registered feed directly (no landing page)
renderNewsWidget(document.getElementById('news-feed'), { feedId: 'features' });

Advanced Usage: Custom Hooks & Components

import { useFeed, Feed, FeedCard } from '@mieweb/news-widget';
import '@mieweb/news-widget/style.css';
import type { Post } from '@mieweb/news-widget';

function CustomFeed() {
  const { posts, loading, error } = useFeed('https://example.com/feed.rss');
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error}</div>;
  
  return (
    <div>
      {posts.map((post: Post) => (
        <FeedCard key={post.id} post={post} />
      ))}
    </div>
  );
}

Option 2: Build from Source

Build the widget yourself for full control:

npm run build

This creates optimized files in the dist/ folder:

  • dist/index.html - Main HTML entry point
  • dist/assets/*.js - JavaScript bundles
  • dist/assets/*.css - Stylesheets

Option 3: Basic HTML Embedding (No Build Tools)

Copy the dist/ folder to your web server and embed with an iframe:

<!DOCTYPE html>
<html>
<head>
  <title>My Website</title>
  <style>
    /* Make iframe responsive and full-height */
    .news-widget-container {
      width: 100%;
      height: 600px; /* Or use 100vh for full viewport */
      border: none;
      overflow: hidden;
    }
  </style>
</head>
<body>
  <h1>Latest News</h1>
  
  <!-- Embed the news widget -->
  <iframe 
    src="/dist/index.html" 
    class="news-widget-container"
    title="News Feed Widget"
    sandbox="allow-scripts allow-same-origin allow-popups"
  ></iframe>
</body>
</html>

Embedding a Specific Feed via iframe

To show a specific feed without the landing page, use the IIFE build with feedId:

<!-- widget.html -->
<!DOCTYPE html>
<html>
<body>
  <div id="root"></div>
  <script src="news-widget.iife.js"></script>
  <script>
    var params = new URLSearchParams(window.location.search);
    var feedId = params.get('feedId');
    NewsWidget.renderNewsWidget(
      document.getElementById('root'),
      feedId ? { feedId: feedId } : {}
    );
  </script>
</body>
</html>

Then embed with a query parameter to select the feed:

<!-- Enterprise Health feeds (pre-registered) -->
<iframe src="widget.html?feedId=features" title="Features Feed"></iframe>
<iframe src="widget.html?feedId=testing" title="Testing Feed"></iframe>
<iframe src="widget.html?feedId=public" title="Public Feed"></iframe>

Embedding a Custom Feed via iframe

Use registerFeed() to add a custom feed at runtime before rendering:

<!-- custom-widget.html -->
<!DOCTYPE html>
<html>
<body>
  <div id="root"></div>
  <script src="news-widget.iife.js"></script>
  <script>
    var params = new URLSearchParams(window.location.search);
    var feedUrl = params.get('feed');
    var feedName = params.get('name') || 'News';

    if (feedUrl) {
      NewsWidget.registerFeed({
        id: 'custom',
        name: feedName,
        url: feedUrl,
        description: '',
        emoji: '📰',
        capabilities: { supportsLikes: true, supportsComments: true }
      });
    }

    NewsWidget.renderNewsWidget(
      document.getElementById('root'),
      feedUrl ? { feedId: 'custom' } : {}
    );
  </script>
</body>
</html>
<iframe src="custom-widget.html?feed=https://example.com/feed.rss&name=My+Feed"></iframe>

Direct Integration (No iframe)

For tighter integration, include the built assets directly:

<!DOCTYPE html>
<html>
<head>
  <title>My Website</title>
  <!-- Include widget styles -->
  <link rel="stylesheet" href="/dist/assets/index-[hash].css">
</head>
<body>
  <!-- Widget mounts here -->
  <div id="root"></div>
  
  <!-- Include widget JavaScript -->
  <script type="module" src="/dist/assets/index-[hash].js"></script>
</body>
</html>

Note: Replace [hash] with the actual hash from your build output.

Option 4: CDN (Coming Soon)

Use a CDN for quick prototyping without installation:

<link rel="stylesheet" href="https://unpkg.com/@mieweb/news-widget/style.css">
<script type="module">
  import { renderNewsWidget } from 'https://unpkg.com/@mieweb/news-widget';
  renderNewsWidget(document.getElementById('news-feed'));
</script>

🎨 Customizing Colors & Styles

The widget uses the @mieweb/ui design system with Tailwind CSS 4 and CSS custom properties for theming. The default brand is BlueHive.

Theme Architecture

Theming is handled via @mieweb/ui brand CSS files imported in src/index.css:

@import '@mieweb/ui/brands/bluehive.css' layer(theme);
@import 'tailwindcss';

All component colors use var(--mieweb-*) CSS custom properties, which are mapped from the brand's Tailwind color tokens in the @theme block.

Override Default Colors

Override the CSS custom properties on your parent page:

<style>
  :root {
    /* Primary brand colors */
    --mieweb-primary-500: #0066cc;
    --mieweb-primary-600: #0055aa;
    
    /* Semantic tokens */
    --mieweb-background: #ffffff;
    --mieweb-foreground: #333333;
    --mieweb-card: #f9f9f9;
    --mieweb-border: #e0e0e0;
    --mieweb-muted-foreground: #666666;
  }
</style>

Dark Mode Support

Dark mode is activated via data-theme="dark" attribute on a parent element:

<div data-theme="dark">
  <!-- Widget renders in dark mode -->
</div>

Or override CSS variables for dark mode:

<style>
  @media (prefers-color-scheme: dark) {
    :root {
      --mieweb-background: #0a0a0a;
      --mieweb-foreground: #fafafa;
      --mieweb-card: #1a1a1a;
      --mieweb-border: #2a2a2a;
      --mieweb-muted-foreground: #a0a0a0;
    }
  }
</style>

Available CSS Custom Properties

| Property | Default (Light) | Purpose | |----------|-----------------|---------| | --mieweb-background | #fafafa | Main background color | | --mieweb-foreground | #0a0a0a | Primary text color | | --mieweb-card | #ffffff | Card/container background | | --mieweb-border | #e5e7eb | Border and divider color | | --mieweb-muted-foreground | #737373 | Secondary/muted text | | --mieweb-primary-500 | #3b82f6 | Primary accent (links, buttons) | | --mieweb-destructive-500 | #ef4444 | Error/destructive actions | | --mieweb-success-500 | #22c55e | Success indicators | | --mieweb-ring | #3b82f6 | Focus ring color |

See src/index.css for the full list of color scale variables (--mieweb-primary-50 through --mieweb-primary-950, etc.).

Example: Brand Integration

Match your brand colors:

<style>
  :root {
    --mieweb-background: var(--your-site-bg, #f5f5f5);
    --mieweb-primary-500: var(--your-brand-primary, #ff6b35);
    --mieweb-foreground: var(--your-site-text, #2d3748);
  }
</style>

🔧 Configuration

RSS Feed Sources

Feeds can be configured statically in src/data/feedRegistry.ts, or registered at runtime.

Static Registration (build-time)

Add feeds to the FEED_SECTIONS array in feedRegistry.ts:

{
  id: 'my-feed',
  name: 'My News Feed',
  description: 'Latest updates',
  url: 'https://example.com/feed.rss',
  emoji: '📰',
  capabilities: { supportsLikes: true, supportsComments: true },
}

Runtime Registration

Use registerFeed() to add or override feeds before rendering — useful for iframe embedding or dynamic configuration:

import { registerFeed, renderNewsWidget } from '@mieweb/news-widget';

registerFeed({
  id: 'custom',
  name: 'Custom Feed',
  description: 'Dynamically registered',
  url: 'https://example.com/feed.rss',
  emoji: '📰',
  capabilities: { supportsLikes: true, supportsComments: true },
});

renderNewsWidget(document.getElementById('root'), { feedId: 'custom' });

Available Feed IDs

| Feed ID | Name | Source | |---------|------|--------| | features | Features | Enterprise Health product announcements | | testing | Test | Enterprise Health testing discussions | | public | Public | Enterprise Health public community | | test-server | Test Server | Local development test server | | sample | Sample Feed | Built-in demo content |

Proxy Configuration

For development, configure CORS proxies in vite.config.ts:

server: {
  proxy: {
    '/api/rss': {
      target: 'https://your-discourse-instance.com',
      changeOrigin: true,
      rewrite: (path) => path.replace(/^\/api\/rss/, ''),
    },
  },
}

📱 Mobile Optimization

The widget is mobile-first with touch gestures:

  • Swipe up/down - Navigate between posts
  • Tap video - Toggle play/pause
  • Tap muted icon - Unmute audio
  • Tap comment icon - Open comment panel
  • Tap outside - Close comment panel

Responsive Breakpoints

/* Mobile: default styles */
/* Tablet: 768px+ */
/* Desktop: 1024px+ */

♿ Accessibility

Built with WCAG 2.1 AA compliance:

  • ✅ Full keyboard navigation (Tab, Enter, Escape)
  • ✅ ARIA labels on all interactive elements
  • ✅ Screen reader tested (VoiceOver, NVDA)
  • ✅ Focus indicators on all controls
  • ✅ Semantic HTML structure
  • ✅ Color contrast meets AA standards

Keyboard Shortcuts

| Key | Action | |-----|--------| | Tab | Navigate between elements | | Enter / Space | Activate buttons/links | | Escape | Close comment panel or fullscreen viewer | | Arrow Up/Down | Scroll feed (when focused) |

🧪 Testing

Playwright E2E tests verify:

  • Video playback and autoplay
  • Comment posting and syncing
  • Like/unlike functionality
  • Swipe gesture navigation
  • Fullscreen viewer interactions
  • Accessibility (ARIA roles, keyboard nav)
npm test                 # Run all tests
npm run test:ui          # Interactive test UI
npm run test:headed      # See tests in browser

🏗️ Architecture

news-widget/
├── src/
│   ├── components/      # React components (using @mieweb/ui)
│   │   ├── Feed.tsx           # Main feed container
│   │   ├── FeedCard.tsx       # Individual post card (Card, CardHeader, CardActions)
│   │   ├── FullscreenViewer.tsx  # Fullscreen video viewer (dialog)
│   │   ├── CommentsPanel.tsx  # Comment sidebar (Input, Button)
│   │   ├── Avatar.tsx         # User avatar (wraps @mieweb/ui Avatar)
│   │   ├── ClickTooltip.tsx   # Tooltip trigger (wraps @mieweb/ui Tooltip)
│   │   └── LandingPage.tsx    # Feed selection landing page (Card, Tooltip)
│   ├── hooks/           # Custom React hooks
│   │   ├── useFeed.ts         # RSS feed fetching/parsing
│   │   ├── useComments.ts     # Comment state management
│   │   ├── useVisibility.ts   # IntersectionObserver for autoplay
│   │   ├── useRouter.ts       # URL routing
│   │   └── useDiscourseAuth.ts # Authentication
│   ├── types/           # TypeScript interfaces
│   └── data/            # Feed configuration
└── test-server/         # Development test server

🔒 Security Notes

When embedding via iframe, use appropriate sandbox attributes:

<iframe 
  sandbox="allow-scripts allow-same-origin allow-popups allow-forms"
  src="/dist/index.html">
</iframe>
  • allow-scripts - Required for JavaScript execution
  • allow-same-origin - Required for API calls to parent domain
  • allow-popups - For external links
  • allow-forms - For comment submission

Content Security Policy (CSP)

The IIFE build injects CSS at runtime by creating a <style> element via JavaScript. This requires the style-src 'unsafe-inline' directive (or a matching nonce/hash) in the page's Content Security Policy. If your site uses a strict CSP that disallows inline styles, the IIFE bundle's CSS will be blocked.

For environments with strict CSP, use the ES or UMD build with the separate dist/news-widget.css stylesheet instead.

📝 License

[Add your license here]

🤝 Contributing

See project copilot-instructions.md for code quality guidelines.

👨‍💻 Development

Tech Stack

  • React 19 - Latest React with concurrent features
  • TypeScript 5.9 - Type-safe development
  • Vite 7 - Fast build tool and dev server
  • @mieweb/ui - MIE design system components (Button, Card, Avatar, Tooltip, Input, Alert, etc.)
  • Tailwind CSS 4 - Utility-first CSS framework (via @tailwindcss/vite plugin)
  • lucide-react - SVG icon library (consistent with @mieweb/ui)
  • react-player v3 - Multi-format video playback
  • react-swipeable - Touch gesture handling
  • Playwright - E2E testing

Project Commands

npm run dev        # Start dev server (http://localhost:5173)
npm run build      # Production build → dist/ (for standalone app)
npm run build:lib  # Library build → dist/ (for NPM package)
npm run preview    # Preview production build
npm run lint       # Run ESLint
npm test           # Run Playwright tests
npm run test:ui    # Open Playwright UI for debugging

Building for NPM

To build the library version for NPM distribution:

npm run build:lib

This generates:

  • dist/news-widget.js - ES module
  • dist/news-widget.umd.cjs - UMD module (browser globals)
  • dist/news-widget.iife.js - Standalone IIFE bundle (all CSS inlined)
  • dist/news-widget.css - Compiled styles (for ES/UMD consumers)
  • dist/index.d.ts - TypeScript declarations

The IIFE build injects all CSS (Tailwind, component styles, @mieweb/ui) into the page at runtime via a <style> tag, so no separate stylesheet is needed — just a single <script> tag.

Publishing to NPM

Automated Publishing (Recommended)

Create a GitHub Release and the package is automatically published via GitHub Actions:

graph LR
    updateVersion[Update Version] --> commitPush[Commit & Push]
    commitPush --> createRelease[Create GitHub Release]
    createRelease --> githubActions{GitHub Actions}
    githubActions --> runTests[Run Tests]
    githubActions --> runLinter[Run Linter]
    githubActions --> buildLibrary[Build Library]
    runTests --> checkPass{All Pass?}
    runLinter --> checkPass
    buildLibrary --> checkPass
    checkPass -->|Yes| publishNPM[Publish to NPM]
    checkPass -->|No| failed[❌ Failed]
    publishNPM --> published[✅ Published]

Steps:

  1. Update version in package.json (npm version patch/minor/major)
  2. Commit and push the version bump
  3. Create a GitHub Release with tag vX.Y.Z
  4. GitHub Actions workflow automatically runs and publishes to NPM

Manual Publishing

# Test the package locally first
npm run build:lib
npm pack

# Publish to NPM (requires auth)
npm login
npm publish --access public

Resources:

Code Quality

This project follows strict quality guidelines:

  • DRY principle - No code duplication
  • KISS principle - Simplest solution that works
  • Accessibility-first - ARIA labels, keyboard navigation
  • Test-driven - E2E tests for all features
  • Type-safe - Full TypeScript coverage

See .github/copilot-instructions.md for complete guidelines.

ESLint Configuration

The project uses flat config ESLint 9 with TypeScript support. To enable stricter type-aware rules:

// eslint.config.js
import tseslint from 'typescript-eslint';

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      tseslint.configs.recommendedTypeChecked,
      // or tseslint.configs.strictTypeChecked for stricter rules
    ],
    languageOptions: {
      parserOptions: {
        project: ['./tsconfig.node.json', './tsconfig.app.json'],
        tsconfigRootDir: import.meta.dirname,
      },
    },
  },
])