feat: complete website implementation
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
import Link from "next/link";
|
||||
import {
|
||||
Wind, Wrench, ThermometerSun, Paintbrush, Droplet, Tv, HelpCircle, ArrowRight,
|
||||
PaintBucket, Refrigerator, Hammer, Key, Bath, PaintRoller, Grid, Layers,
|
||||
AlertTriangle, Waves, Droplets, WashingMachine, Snowflake, Store, ScanLine, Settings
|
||||
} from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface IconCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
iconName: string;
|
||||
href?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const iconMap: Record<string, React.ComponentType<{ className?: string }>> = {
|
||||
Wind,
|
||||
Wrench,
|
||||
ThermometerSun,
|
||||
Paintbrush,
|
||||
Droplet,
|
||||
Tv,
|
||||
PaintBucket,
|
||||
Refrigerator,
|
||||
Hammer,
|
||||
Key,
|
||||
Bath,
|
||||
PaintRoller,
|
||||
Grid,
|
||||
Layers,
|
||||
AlertTriangle,
|
||||
Waves,
|
||||
Droplets,
|
||||
WashingMachine,
|
||||
Snowflake,
|
||||
Store,
|
||||
ScanLine,
|
||||
Settings,
|
||||
};
|
||||
|
||||
export function IconCard({ title, description, iconName, href, className }: IconCardProps) {
|
||||
const Icon = iconMap[iconName] || HelpCircle;
|
||||
|
||||
const cardContent = (
|
||||
<>
|
||||
<div className="p-3 rounded-lg bg-slate-100 text-primary w-fit group-hover:bg-accent-light group-hover:text-accent transition-colors duration-300">
|
||||
<Icon className="size-6" />
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold text-primary font-heading mt-4 group-hover:text-accent transition-colors">
|
||||
{title}
|
||||
</h3>
|
||||
<p className="text-sm text-text-muted mt-2 leading-relaxed flex-grow">
|
||||
{description}
|
||||
</p>
|
||||
{href && (
|
||||
<div className="flex items-center gap-1 text-xs font-semibold text-primary group-hover:text-accent mt-4 transition-colors">
|
||||
<span>Dowiedz się więcej</span>
|
||||
<ArrowRight className="size-3.5 group-hover:translate-x-1 transition-transform" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const cardClasses = cn(
|
||||
"group border border-border rounded-xl p-6 bg-white hover:shadow-md transition-all duration-300 flex flex-col justify-between h-full",
|
||||
className
|
||||
);
|
||||
|
||||
if (href) {
|
||||
return (
|
||||
<Link href={href} className={cardClasses}>
|
||||
{cardContent}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className={cardClasses}>{cardContent}</div>;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Image as ImageIcon } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface PlaceholderProps {
|
||||
text: string;
|
||||
className?: string;
|
||||
aspect?: "video" | "square" | "portrait" | "auto" | "banner";
|
||||
}
|
||||
|
||||
export function Placeholder({ text, className, aspect = "video" }: PlaceholderProps) {
|
||||
const aspectClasses = {
|
||||
video: "aspect-video",
|
||||
square: "aspect-square",
|
||||
portrait: "aspect-[3/4]",
|
||||
banner: "aspect-[21/9] min-h-[300px]",
|
||||
auto: "h-full w-full",
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative w-full bg-slate-100 dark:bg-slate-800 border-2 border-dashed border-slate-200 dark:border-slate-700 rounded-xl flex flex-col items-center justify-center text-center p-6 text-slate-400 dark:text-slate-500 overflow-hidden group hover:bg-slate-150 transition-colors duration-250",
|
||||
aspectClasses[aspect],
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* TODO: docelowe zdjęcie — {text} */}
|
||||
<ImageIcon className="size-10 mb-3 group-hover:scale-110 transition-transform duration-300" />
|
||||
<span className="text-sm font-medium px-4 leading-normal">{text}</span>
|
||||
<span className="text-[10px] text-slate-400 mt-2 uppercase tracking-widest font-semibold font-heading opacity-70">
|
||||
Placeholder Zdjęcia
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface SectionHeadingProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
eyebrow?: string;
|
||||
align?: "left" | "center";
|
||||
dark?: boolean;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function SectionHeading({
|
||||
title,
|
||||
subtitle,
|
||||
eyebrow,
|
||||
align = "center",
|
||||
dark = false,
|
||||
className,
|
||||
}: SectionHeadingProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col gap-2 mb-10 md:mb-14",
|
||||
align === "center" ? "items-center text-center" : "items-start text-left",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{eyebrow && (
|
||||
<span
|
||||
className={cn(
|
||||
"text-xs md:text-sm font-semibold uppercase tracking-wider font-heading",
|
||||
dark ? "text-accent" : "text-cta"
|
||||
)}
|
||||
>
|
||||
{eyebrow}
|
||||
</span>
|
||||
)}
|
||||
<h2
|
||||
className={cn(
|
||||
"text-3xl md:text-4xl font-bold leading-tight font-heading max-w-3xl",
|
||||
dark ? "text-white" : "text-primary"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
{subtitle && (
|
||||
<p
|
||||
className={cn(
|
||||
"text-base md:text-lg max-w-2xl leading-relaxed mt-1",
|
||||
dark ? "text-slate-300" : "text-text-muted"
|
||||
)}
|
||||
>
|
||||
{subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user