Skip to content

mieweb/news-widget

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

61 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

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]
Loading

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,
      },
    },
  },
])

Packages

 
 
 

Contributors