feat: improve README markdown styling to match npm.com quality

**Enhanced Markdown Rendering:**
- Use prose-slate for better default typography
- Add proper heading hierarchy with bottom borders on h1/h2
- Improve code block styling with better backgrounds and shadows
- Style inline code with pink/red accent colors like npm
- Better table styling with proper borders and rounded corners
- Improve link colors (blue) with hover effects
- Add better spacing throughout (margins, padding, line-height)
- Enhance blockquote styling with background colors
- Better list spacing with space-y-2
- Add proper light/dark mode support with zinc color palette

**Component Updates:**
- Custom pre component with better background and border
- Custom table wrapper with overflow handling
- Improved link component with external link detection
- Better inline code styling

The README now renders beautifully like npm.com instead of looking plain.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-29 23:08:00 +10:00
parent d5e8d50082
commit bba538336a
2 changed files with 90 additions and 26 deletions

View file

@ -13,44 +13,49 @@ interface MarkdownProps {
/**
* Markdown renderer component
* Safely renders markdown content with GitHub Flavored Markdown support
* Styled to match npm.com's beautiful README rendering
*/
export function Markdown({ content, className = '' }: MarkdownProps): React.ReactElement {
return (
<div
className={`prose prose-invert max-w-none
prose-headings:text-foreground prose-headings:font-bold
prose-h1:text-3xl prose-h1:mb-4 prose-h1:mt-6
prose-h2:text-2xl prose-h2:mb-3 prose-h2:mt-5
prose-h3:text-xl prose-h3:mb-2 prose-h3:mt-4
prose-p:text-foreground-secondary prose-p:leading-relaxed
prose-a:text-primary prose-a:no-underline hover:prose-a:underline
prose-code:text-foreground prose-code:bg-surface prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:text-sm prose-code:font-mono prose-code:before:content-none prose-code:after:content-none
prose-pre:bg-surface prose-pre:border prose-pre:border-border prose-pre:rounded-lg prose-pre:p-4 prose-pre:overflow-x-auto
prose-pre:text-foreground-secondary
prose-blockquote:border-l-4 prose-blockquote:border-border prose-blockquote:pl-4 prose-blockquote:italic prose-blockquote:text-foreground-secondary
prose-ul:text-foreground-secondary prose-ul:list-disc prose-ul:pl-6
prose-ol:text-foreground-secondary prose-ol:list-decimal prose-ol:pl-6
prose-li:my-1
prose-table:border-collapse prose-table:w-full
prose-thead:bg-surface
prose-th:border prose-th:border-border prose-th:px-4 prose-th:py-2 prose-th:text-left prose-th:font-semibold prose-th:text-foreground
prose-td:border prose-td:border-border prose-td:px-4 prose-td:py-2 prose-td:text-foreground-secondary
prose-img:rounded-lg prose-img:shadow-md
prose-hr:border-border prose-hr:my-8
prose-strong:text-foreground prose-strong:font-semibold
prose-em:text-foreground-secondary
className={`prose prose-slate dark:prose-invert max-w-none
prose-headings:font-semibold prose-headings:tracking-tight
prose-h1:text-4xl prose-h1:mb-6 prose-h1:mt-8 prose-h1:pb-3 prose-h1:border-b prose-h1:border-border
prose-h2:text-2xl prose-h2:mb-4 prose-h2:mt-8 prose-h2:pb-2 prose-h2:border-b prose-h2:border-border
prose-h3:text-xl prose-h3:mb-3 prose-h3:mt-6
prose-h4:text-lg prose-h4:mb-2 prose-h4:mt-5
prose-p:text-base prose-p:leading-7 prose-p:mb-4
prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-a:no-underline hover:prose-a:underline prose-a:font-medium
prose-code:text-sm prose-code:font-mono prose-code:px-1.5 prose-code:py-0.5 prose-code:rounded prose-code:bg-zinc-100 dark:prose-code:bg-zinc-800 prose-code:text-pink-600 dark:prose-code:text-pink-400 prose-code:before:content-none prose-code:after:content-none prose-code:font-semibold
prose-pre:bg-zinc-50 dark:prose-pre:bg-zinc-900 prose-pre:border prose-pre:border-zinc-200 dark:prose-pre:border-zinc-800 prose-pre:rounded-lg prose-pre:p-4 prose-pre:overflow-x-auto prose-pre:shadow-sm
prose-pre:my-5
prose-blockquote:border-l-4 prose-blockquote:border-zinc-300 dark:prose-blockquote:border-zinc-700 prose-blockquote:pl-4 prose-blockquote:py-1 prose-blockquote:italic prose-blockquote:text-zinc-600 dark:prose-blockquote:text-zinc-400 prose-blockquote:bg-zinc-50 dark:prose-blockquote:bg-zinc-900/50 prose-blockquote:my-4
prose-ul:list-disc prose-ul:pl-6 prose-ul:my-4 prose-ul:space-y-2
prose-ol:list-decimal prose-ol:pl-6 prose-ol:my-4 prose-ol:space-y-2
prose-li:text-base prose-li:leading-7
prose-table:border-collapse prose-table:w-full prose-table:my-6 prose-table:text-sm
prose-thead:bg-zinc-50 dark:prose-thead:bg-zinc-800/50
prose-th:border prose-th:border-zinc-300 dark:prose-th:border-zinc-700 prose-th:px-4 prose-th:py-3 prose-th:text-left prose-th:font-semibold
prose-td:border prose-td:border-zinc-300 dark:prose-td:border-zinc-700 prose-td:px-4 prose-td:py-3
prose-tr:border-b prose-tr:border-zinc-200 dark:prose-tr:border-zinc-800
prose-img:rounded-lg prose-img:shadow-lg prose-img:my-6
prose-hr:border-zinc-300 dark:prose-hr:border-zinc-700 prose-hr:my-8
prose-strong:font-semibold
${className}`}
>
<ReactMarkdown
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw, rehypeSanitize]}
components={{
// Custom component for code blocks to match the site's theme
// Custom component for code blocks with better styling
code: ({ className, children, ...props }) => {
const isInline = !className;
if (isInline) {
return (
<code className="bg-surface px-1.5 py-0.5 rounded text-sm font-mono" {...props}>
<code
className="bg-zinc-100 dark:bg-zinc-800 text-pink-600 dark:text-pink-400 px-1.5 py-0.5 rounded text-sm font-mono font-semibold"
{...props}
>
{children}
</code>
);
@ -61,7 +66,18 @@ export function Markdown({ content, className = '' }: MarkdownProps): React.Reac
</code>
);
},
// Make links open in new tab
// Better styled pre blocks
pre: ({ children, ...props }) => {
return (
<pre
className="bg-zinc-50 dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-800 rounded-lg p-4 overflow-x-auto my-5 shadow-sm"
{...props}
>
{children}
</pre>
);
},
// Make links open in new tab with better styling
a: ({ href, children, ...props }) => {
const isExternal = href?.startsWith('http');
return (
@ -69,13 +85,26 @@ export function Markdown({ content, className = '' }: MarkdownProps): React.Reac
href={href}
target={isExternal ? '_blank' : undefined}
rel={isExternal ? 'noopener noreferrer' : undefined}
className="text-primary hover:underline"
className="text-blue-600 dark:text-blue-400 hover:underline font-medium"
{...props}
>
{children}
</a>
);
},
// Better table styling
table: ({ children, ...props }) => {
return (
<div className="overflow-x-auto my-6">
<table
className="min-w-full divide-y divide-zinc-300 dark:divide-zinc-700 border border-zinc-300 dark:border-zinc-700 rounded-lg"
{...props}
>
{children}
</table>
</div>
);
},
}}
>
{content}

View file

@ -0,0 +1,35 @@
import { PrismaClient } from '@prisma/client';
import { fetchLatestPackageWithMetadata } from '@tpmjs/npm-client';
const prisma = new PrismaClient();
async function main() {
const packageName = '@tpmjs/text-transformer';
console.log(`Fetching metadata for ${packageName}...`);
const pkg = await fetchLatestPackageWithMetadata(packageName);
if (!pkg) {
console.error('Package not found!');
return;
}
console.log(`Found ${packageName} v${pkg.version}`);
console.log(`README length: ${pkg.readme ? pkg.readme.length : 0} chars`);
console.log(`Keywords: ${(pkg.topLevelKeywords || pkg.keywords || []).join(', ')}`);
// Update the tool in the database
await prisma.tool.update({
where: { npmPackageName: packageName },
data: {
npmReadme: pkg.readme || null,
npmKeywords: pkg.topLevelKeywords || pkg.keywords || [],
npmAuthor: pkg.author || null,
npmMaintainers: pkg.maintainers || null,
}
});
console.log('\n✅ Tool updated successfully!');
}
main().finally(() => prisma.$disconnect());