Package Exports
- @punkbit/react-hn-reader
Readme
Hacker News Reader
An experimental Hacker News reader built with React, TypeScript, and modern tooling. Features parallax scrolling effects and a polished UX.
Overview
This project is a modern Hacker News reader that consumes data directly from the Hacker News Firebase REST API. It features:
- ✨ A parallax-like scrolling inspired in starwars
- ⚡ Lightning-fast development with Vite
- 🎨 Customizable themes via styled-components
- 📱 Responsive design
- 🔧 Full TypeScript support
Installation
npm install react-hn-reader styled-componentsPeer dependencies: react >=16.8 || >=17 || >=18 || >=19, react-dom >=16.8 || >=17 || >=18 || >=19, styled-components >=5.0 || >=6.0
Quick Start
Standard Usage (Full-Page Apps)
For apps where this component is the main content:
import { HackerNewsReader, ThemeProvider, DefaultStyles } from 'react-hn-reader'
function App() {
return (
<ThemeProvider>
<DefaultStyles />
<HackerNewsReader />
</ThemeProvider>
)
}⚠️ Astro.js / iframe / Component Usage (IMPORTANT!)
Do NOT use DefaultStyles - it applies global CSS that breaks scoped styling. Use ScopedDefaultStyles instead:
import {
HackerNewsReader,
ThemeProvider,
ScopedDefaultStyles // ✅ Use this, not DefaultStyles
} from 'react-hn-reader'
function App() {
return (
<ThemeProvider>
{/* Wrap in div with data attribute for scoping */}
<div data-react-hn-reader>
<ScopedDefaultStyles />
<HackerNewsReader
selfContained={true} // ✅ Required for Astro/iframes
scrollContainerHeight="100vh"
/>
</div>
</ThemeProvider>
)
}Why this matters:
DefaultStylesapplies CSS resets to the entire page (body,html,*)ScopedDefaultStylesonly applies within[data-react-hn-reader]elementsselfContained={true}creates an internal scrollable container (required when parent hasoverflow: hidden)
✅ Fonts are loaded automatically - Google Fonts (Bitter, Work Sans, Nanum Gothic) are injected dynamically when the component mounts.
Features
- ✨ Parallax scrolling - 3D depth effect with three viewport layers
- 🖱️ Scroll indicator - Animated mouse icon in top-right (fades on scroll)
- 🖤 Fixed black logo - Appears at bottom-right when scrolling down
- ➡️ Arrow icons - Each story displays an arrow-out icon
- 🎨 Customizable themes - Override all colors via ThemeProvider
- 🔧 TypeScript - Full type support
- 📦 Astro.js Compatible - Works in scoped environments with
selfContainedmode
Props
| Prop | Type | Default | Description |
|---|---|---|---|
apiUrl |
string |
HN Firebase API | Base URL for stories API |
initialCount |
number |
30 | Initial stories to load |
enableLoadMore |
boolean |
true | Show load more button |
onStoryClick |
(story) => void |
- | Click handler |
theme |
object |
- | Override theme colors |
renderStory |
(story, index) => ReactNode |
- | Custom story renderer |
selfContained |
boolean |
false | Enable internal scrolling container |
scrollContainerHeight |
string | number |
'100vh' | Height of scrollable area |
containerClassName |
string |
- | Additional CSS class for scroll container |
Usage Examples
Custom Theme
<ThemeProvider theme={{ orange: '#ff4500', onyx: '#1a1a1a' }}>
<DefaultStyles />
<HackerNewsReader />
</ThemeProvider>Custom API
<HackerNewsReader
apiUrl="https://your-api.com/v0"
initialCount={50}
/>Handle Clicks
<HackerNewsReader
onStoryClick={(story) => console.log(story.title)}
/>🚀 Astro.js Integration
This component is specially designed to work with Astro.js scoped styles. Here's the complete setup:
1. Create a React wrapper component (e.g., src/components/HNReader.tsx):
import React from 'react'
import {
HackerNewsReader,
ThemeProvider,
ScopedDefaultStyles
} from '@punkbit/react-hn-reader'
export function HNReader() {
return (
<ThemeProvider>
<div data-react-hn-reader style={{ height: '100vh' }}>
<ScopedDefaultStyles />
<HackerNewsReader
selfContained={true}
scrollContainerHeight="100vh"
/>
</div>
</ThemeProvider>
)
}2. Use in your Astro page:
---
import HNReader from '../components/HNReader';
---
<html>
<head>
<title>Hacker News Reader</title>
</head>
<body>
<!-- This creates an isolated React island -->
<HNReader client:load />
</body>
</html>⚠️ Important Notes for Astro.js:
- Always use
ScopedDefaultStylesinstead ofDefaultStyles - Always wrap in
<div data-react-hn-reader>for CSS scoping - Always set
selfContained={true}because Astro adds scoped styles withoverflow: hidden - Set explicit height on the wrapper div (e.g.,
height: '100vh')
Why Astro.js needs special handling:
- Astro applies scoped styles that add
overflow: hiddento containers selfContained={true}creates an internal scrollable containerScopedDefaultStylesprevents CSS from leaking to other parts of your Astro page
📦 CDN Usage
For direct browser usage without npm:
<!DOCTYPE html>
<html>
<head>
<title>HN Reader</title>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/@punkbit/react-hn-reader@latest/dist/cdn.js"></script>
<script>
const { React, ReactDOM, HackerNewsReader, ThemeProvider, ScopedDefaultStyles } = window.ReactHNReader;
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
React.createElement(ThemeProvider, null,
React.createElement('div', { 'data-react-hn-reader': true, style: { height: '100vh' } },
React.createElement(ScopedDefaultStyles, null),
React.createElement(HackerNewsReader, { selfContained: true, scrollContainerHeight: '100vh' })
)
)
);
</script>
</body>
</html>🔧 Self-Contained Mode Details
When selfContained={true}, the component:
- Creates an internal scrollable container with
overflow-y: auto - Changes
position: fixedtoposition: relative/absoluteinternally - Listens to container scroll events instead of window scroll
- Works inside iframes, modals, and containers with
overflow: hidden
Use cases:
- Astro.js: Scoped styles break window-based scrolling
- iframes: Isolated scrolling context
- Modals/Dialogs: Fixed containers without body scroll
- Overflow hidden parents: Any ancestor with
overflow: hidden
Self-Contained with Custom Height
// Fixed pixel height
<HackerNewsReader
selfContained={true}
scrollContainerHeight={800}
containerClassName="my-scroll-container"
/>
// CSS string value
<HackerNewsReader
selfContained={true}
scrollContainerHeight="calc(100vh - 100px)"
/>🎨 Styling Override Examples
Custom Scrollbar
/* Target the self-contained scroll container */
.my-scroll-container::-webkit-scrollbar {
width: 12px;
}
.my-scroll-container::-webkit-scrollbar-thumb {
background: #ff6600;
border-radius: 6px;
}Override Position in Self-Contained Mode
<HackerNewsReader
selfContained={true}
containerClassName="hn-reader-wrapper"
/>.hn-reader-wrapper {
border: 2px solid #ff6600;
border-radius: 8px;
}Fonts
Google Fonts are loaded automatically when the component mounts:
- Bitter - Primary text font
- Work Sans - Headings
- Nanum Gothic - Author names
No manual configuration needed!
Development
npm install
npm run build # Build package (ESM)
npm run build:cdn # Build CDN version (IIFE)
npm run dev # Development mode with exampleLocal Testing with Preview Build
Build a standalone preview version that bundles React and all dependencies:
npm run build:preview # Build standalone preview to dist/preview/
npm run serve # Serve with http-server on http://localhost:8080Or manually:
npm run build:preview
npx http-server ./dist/preview -p 8080 -oThe preview build is a fully self-contained version that doesn't require external React dependencies, making it perfect for testing the component in isolation before publishing.
Build Everything
To build all versions (preview, library, and CDN):
npm run build:all # Builds preview, library, and CDNTroubleshooting
"Can't scroll" in Astro.js
Problem: Astro's scoped styles add overflow: hidden that prevents scrolling.
Solution: Use selfContained={true} + ScopedDefaultStyles:
<div data-react-hn-reader style={{ height: '100vh' }}>
<ScopedDefaultStyles />
<HackerNewsReader selfContained={true} scrollContainerHeight="100vh" />
</div>"CSS leaking to other parts of page"
Problem: DefaultStyles applies global CSS resets.
Solution: Use ScopedDefaultStyles with data-react-hn-reader wrapper.
"Parallax effect not working in iframe"
Problem: position: fixed elements don't work inside scrollable containers.
Solution: Enable selfContained={true} - it automatically switches to position: relative/absolute.
Publish
npm run build
npm publishLicense
MIT © Punkbit