IF.docs.md2html/src/components/ViewerContent.tsx
Danny Stocker f950357096 feat: Initialize IF.docs.md2html - Magazine-style Markdown viewer
Features:
- Magazine-style layout with CSS Grid (2-column + sidebar)
- ICW NextSpread design system (colors, typography, animations)
- Adaptive animation timing based on scroll speed
- URL query parameter support (?parse=https://url/file.md)
- Drag-and-drop file upload
- Syntax highlighting (VS Code Dark+ theme)
- Full-bleed hero section with gradient overlay
- Responsive design (mobile-first, 44px touch targets)
- Static export ready for StackCP deployment

Tech Stack:
- Next.js 14 (static export)
- React 18 + TypeScript
- Framer Motion (animations)
- markdown-it + highlight.js
- Tailwind CSS

Design Ported From:
- ICW NextSpread property pages (icantwait.ca)
- useAdaptiveDuration, useScrollSpeed animation hooks
- Webflow-style easing curves
- Gallery staggered reveal patterns

Deployment:
- Configured for digital-lab.ca/infrafabric/IF.docs.md2html
- .htaccess with SPA routing and CORS headers
- Static files in /out/ directory

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-14 17:51:52 +01:00

78 lines
2.6 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use client';
import { useState, useEffect } from 'react';
import { useSearchParams } from 'next/navigation';
import { MarkdownViewer } from '@/components/MarkdownViewer';
import { FileUploader } from '@/components/FileUploader';
export function ViewerContent() {
const [markdown, setMarkdown] = useState('');
const [title, setTitle] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const searchParams = useSearchParams();
// Load markdown from URL query parameter ?parse=https://url/file.md
useEffect(() => {
const parseUrl = searchParams.get('parse');
if (parseUrl) {
setLoading(true);
fetch(parseUrl)
.then((res) => {
if (!res.ok) throw new Error(`Failed to fetch: ${res.statusText}`);
return res.text();
})
.then((content) => {
// Extract title from first H1
const h1Match = content.match(/^#\s+(.+)$/m);
const extractedTitle = h1Match ? h1Match[1] : 'Document';
setTitle(extractedTitle);
setMarkdown(content);
setLoading(false);
})
.catch((err) => {
setError(`Error loading document: ${err.message}`);
setLoading(false);
});
}
}, [searchParams]);
if (loading) {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center">
<div className="inline-block w-8 h-8 border-4 border-primary-500 border-t-transparent rounded-full animate-spin mb-4"></div>
<p className="regular-m text-neutral-600">Loading document...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen flex items-center justify-center p-6">
<div className="max-w-lg text-center">
<div className="text-6xl mb-4"></div>
<h2 className="h3 mb-2">Error Loading Document</h2>
<p className="regular-m text-neutral-600 mb-6">{error}</p>
<button
onClick={() => { setError(''); }}
className="inline-flex items-center gap-2 px-6 py-3 bg-neutral-900 text-white rounded-full hover:bg-neutral-800 transition-all"
>
Try Again
</button>
</div>
</div>
);
}
return (
<main className="min-h-screen bg-neutral-50">
{!markdown ? (
<FileUploader onMarkdownLoad={setMarkdown} onTitleExtract={setTitle} />
) : (
<MarkdownViewer markdown={markdown} title={title} onReset={() => { setMarkdown(''); setTitle(''); }} />
)}
</main>
);
}