update components

This commit is contained in:
Rami Bitar
2026-06-06 14:21:55 -04:00
parent ed346476e5
commit ef9e10256d
60 changed files with 85 additions and 1035 deletions

View File

@@ -0,0 +1,82 @@
import { ComponentConfig } from "@reacteditor/core";
import { LayoutTemplate } from "lucide-react";
import { Cover, type CoverProps } from "@/components/cover/cover";
const coverEditor: ComponentConfig<CoverProps> = {
label: "Cover",
icon: <LayoutTemplate size={16} />,
category: "hero",
defaultProps: {
tagline: "Spring 2026",
heading: "Made for the way you move",
subheading:
"A considered wardrobe of essentials, cut from natural fibers and designed to last.",
buttons: [
{ label: "Shop the collection", href: "/collections", variant: "primary" },
{ label: "Our story", href: "/about", variant: "secondary" },
],
imageUrl:
"https://images.unsplash.com/photo-1490481651871-ab68de25d43d?auto=format&fit=crop&w=2400&q=80",
align: "left",
height: "lg",
tone: "dark",
},
fields: {
tagline: { label: "Tagline", type: "text", contentEditable: true },
heading: { label: "Heading", type: "textarea", contentEditable: true },
subheading: { label: "Subheading", type: "textarea", contentEditable: true },
buttons: {
label: "Buttons",
type: "array",
arrayFields: {
label: { label: "Label", type: "text", contentEditable: true },
href: { label: "Link", type: "text" },
variant: {
label: "Variant",
type: "select",
options: [
{ label: "Primary (filled)", value: "primary" },
{ label: "Secondary (outline)", value: "secondary" },
{ label: "Outline", value: "outline" },
{ label: "Ghost", value: "ghost" },
],
},
},
defaultItemProps: {
label: "Button",
href: "/",
variant: "primary",
},
getItemSummary: (item) => item?.label || "Button",
},
imageUrl: { label: "Background image", type: "image" },
align: {
label: "Alignment",
type: "radio",
options: [
{ label: "Left", value: "left" },
{ label: "Center", value: "center" },
],
},
height: {
label: "Height",
type: "select",
options: [
{ label: "Medium", value: "md" },
{ label: "Large", value: "lg" },
{ label: "Full", value: "full" },
],
},
tone: {
label: "Tone",
type: "radio",
options: [
{ label: "Light", value: "light" },
{ label: "Dark", value: "dark" },
],
},
},
render: (props) => <Cover {...props} />,
};
export default coverEditor;

141
components/cover/cover.tsx Normal file
View File

@@ -0,0 +1,141 @@
import Link from "next/link";
import { cn } from "@/lib/utils";
import { Typography } from "@/components/Typography";
export type CoverButtonVariant = "primary" | "secondary" | "outline" | "ghost";
export type CoverButton = {
label: string;
href: string;
variant: CoverButtonVariant;
};
export type CoverProps = {
tagline: string;
heading: string;
subheading: string;
buttons: CoverButton[];
imageUrl: string;
align: "left" | "center";
height: "md" | "lg" | "full";
tone: "light" | "dark";
};
const heightClass: Record<CoverProps["height"], string> = {
md: "min-h-[60vh]",
lg: "min-h-[80vh]",
full: "min-h-screen",
};
function buttonClass(variant: CoverButtonVariant, isDark: boolean): string {
switch (variant) {
case "primary":
return cn(
"inline-flex items-center justify-center rounded-md px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-90",
isDark ? "bg-white text-black" : "bg-foreground text-background",
);
case "secondary":
case "outline":
return cn(
"inline-flex items-center justify-center rounded-md border px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-80",
isDark ? "border-white text-white" : "border-foreground text-foreground",
);
case "ghost":
return cn(
"inline-flex items-center justify-center rounded-md px-6 py-3 text-sm font-medium tracking-wide transition-opacity hover:opacity-80",
isDark ? "text-white" : "text-foreground",
);
}
}
export function Cover({
tagline,
heading,
subheading,
buttons,
imageUrl,
align,
height,
tone,
}: CoverProps) {
const isDark = tone === "dark";
const visibleButtons = (buttons ?? []).filter((b) => b?.label);
return (
<section
className={cn(
"relative flex w-full items-end overflow-hidden isolate",
heightClass[height],
)}
>
{imageUrl ? (
<img
src={imageUrl}
alt=""
className="absolute inset-0 z-0 h-full w-full object-cover"
/>
) : null}
<div
className="absolute inset-0 z-[1]"
style={{
background: isDark
? "linear-gradient(180deg, rgba(0,0,0,0.15) 0%, rgba(0,0,0,0.55) 100%)"
: "linear-gradient(180deg, rgba(255,255,255,0.0) 30%, rgba(255,255,255,0.85) 100%)",
}}
/>
<div
className={cn(
"container relative z-[2] mx-auto flex max-w-7xl flex-col px-6 py-20 md:py-28",
align === "center" ? "items-center text-center" : "items-start",
isDark ? "text-white" : "text-foreground",
)}
>
{tagline ? (
<Typography
variant="caption"
className={cn(
"mb-5",
isDark ? "text-white/80" : "text-foreground/70",
)}
>
{tagline}
</Typography>
) : null}
<Typography variant="h1" className="max-w-3xl">
{heading}
</Typography>
{subheading ? (
<Typography
variant="subtitle1"
className={cn(
"mt-6 max-w-xl",
isDark ? "text-white/80" : "text-foreground/70",
)}
>
{subheading}
</Typography>
) : null}
{visibleButtons.length > 0 ? (
<div
className={cn(
"mt-10 flex flex-wrap gap-3",
align === "center" && "justify-center",
)}
>
{visibleButtons.map((b, i) => (
<Link
key={`${b.href}-${b.label}-${i}`}
href={b.href || "#"}
className={buttonClass(b.variant, isDark)}
>
{b.label}
</Link>
))}
</div>
) : null}
</div>
</section>
);
}