wip
This commit is contained in:
64
components/blog/Callout.tsx
Normal file
64
components/blog/Callout.tsx
Normal file
@@ -0,0 +1,64 @@
|
||||
import React from 'react';
|
||||
|
||||
interface CalloutProps {
|
||||
type?: 'info' | 'warning' | 'success' | 'tip';
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const icons = {
|
||||
info: (
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
warning: (
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
success: (
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
),
|
||||
tip: (
|
||||
<svg className="w-6 h-6" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
const styles = {
|
||||
info: 'bg-blue-50 border-blue-200 text-blue-900',
|
||||
warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
|
||||
success: 'bg-green-50 border-green-200 text-green-900',
|
||||
tip: 'bg-purple-50 border-purple-200 text-purple-900',
|
||||
};
|
||||
|
||||
const iconColors = {
|
||||
info: 'text-blue-500',
|
||||
warning: 'text-yellow-500',
|
||||
success: 'text-green-500',
|
||||
tip: 'text-purple-500',
|
||||
};
|
||||
|
||||
export default function Callout({ type = 'info', title, children }: CalloutProps) {
|
||||
return (
|
||||
<div className={`my-8 p-6 rounded-xl border-2 ${styles[type]}`}>
|
||||
<div className="flex gap-4">
|
||||
<div className={`flex-shrink-0 ${iconColors[type]}`}>
|
||||
{icons[type]}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{title && (
|
||||
<h4 className="font-bold text-lg mb-2">{title}</h4>
|
||||
)}
|
||||
<div className="prose prose-sm max-w-none">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
components/blog/HighlightBox.tsx
Normal file
29
components/blog/HighlightBox.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
|
||||
interface HighlightBoxProps {
|
||||
title?: string;
|
||||
children: React.ReactNode;
|
||||
color?: 'primary' | 'secondary' | 'accent';
|
||||
}
|
||||
|
||||
const colorStyles = {
|
||||
primary: 'bg-gradient-to-br from-primary/10 to-primary/5 border-primary/30',
|
||||
secondary: 'bg-gradient-to-br from-blue-50 to-blue-100/50 border-blue-200',
|
||||
accent: 'bg-gradient-to-br from-green-50 to-green-100/50 border-green-200',
|
||||
};
|
||||
|
||||
export default function HighlightBox({ title, children, color = 'primary' }: HighlightBoxProps) {
|
||||
return (
|
||||
<div className={`my-10 p-8 rounded-2xl border-2 ${colorStyles[color]} shadow-sm`}>
|
||||
{title && (
|
||||
<h3 className="text-2xl font-bold text-text-primary mb-4 flex items-center gap-3">
|
||||
<span className="w-1.5 h-8 bg-primary rounded-full"></span>
|
||||
{title}
|
||||
</h3>
|
||||
)}
|
||||
<div className="prose prose-lg max-w-none">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
components/blog/Stats.tsx
Normal file
36
components/blog/Stats.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
|
||||
interface Stat {
|
||||
value: string;
|
||||
label: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface StatsProps {
|
||||
stats: Stat[];
|
||||
}
|
||||
|
||||
export default function Stats({ stats }: StatsProps) {
|
||||
return (
|
||||
<div className="my-12 grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
{stats.map((stat, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="bg-gradient-to-br from-primary/5 to-primary/10 p-6 rounded-xl border border-primary/20 text-center hover:shadow-md transition-shadow"
|
||||
>
|
||||
{stat.icon && (
|
||||
<div className="text-primary mb-3 flex justify-center">
|
||||
{stat.icon}
|
||||
</div>
|
||||
)}
|
||||
<div className="text-4xl font-bold text-primary mb-2">
|
||||
{stat.value}
|
||||
</div>
|
||||
<div className="text-text-secondary font-medium">
|
||||
{stat.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
41
components/blog/VisualLinkPreview.tsx
Normal file
41
components/blog/VisualLinkPreview.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link';
|
||||
|
||||
interface VisualLinkPreviewProps {
|
||||
url: string;
|
||||
title: string;
|
||||
summary: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
export default function VisualLinkPreview({ url, title, summary, image }: VisualLinkPreviewProps) {
|
||||
return (
|
||||
<Link href={url} target="_blank" rel="noopener noreferrer" className="block my-8 no-underline group">
|
||||
<div className="flex flex-col md:flex-row border border-neutral-dark rounded-lg overflow-hidden bg-white hover:shadow-md transition-shadow">
|
||||
<div className="relative w-full md:w-48 h-48 md:h-auto flex-shrink-0 bg-neutral-light flex items-center justify-center">
|
||||
{image ? (
|
||||
<img
|
||||
src={image}
|
||||
alt={title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-neutral-dark">No Image</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="p-4 flex flex-col justify-center">
|
||||
<h3 className="text-lg font-bold text-primary mb-2 group-hover:underline line-clamp-2">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-text-secondary text-sm line-clamp-3">
|
||||
{summary}
|
||||
</p>
|
||||
<span className="text-xs text-text-secondary mt-2 opacity-70">
|
||||
{new URL(url).hostname}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user