Template
Initial react editor Next.js template
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import type { ComponentConfig } from '@reacteditor/core';
|
||||
import { PanelBottom } from 'lucide-react';
|
||||
import Footer, { type FooterProps } from './footer';
|
||||
|
||||
const footerConfig: ComponentConfig<FooterProps> = {
|
||||
label: 'Footer',
|
||||
icon: <PanelBottom size={16} />,
|
||||
category: 'navigation',
|
||||
global: true,
|
||||
defaultProps: {
|
||||
copyright: '© 2026 Next.js Editor Starter',
|
||||
},
|
||||
fields: {
|
||||
copyright: { label: 'Copyright', type: 'text', contentEditable: true },
|
||||
},
|
||||
render: (props) => <Footer {...props} />,
|
||||
};
|
||||
|
||||
export default footerConfig;
|
||||
@@ -0,0 +1,13 @@
|
||||
export type FooterProps = {
|
||||
copyright?: string;
|
||||
};
|
||||
|
||||
export default function Footer({ copyright = '© 2026 Next.js Editor Starter' }: FooterProps) {
|
||||
return (
|
||||
<footer className="h-16 bg-background">
|
||||
<div className="page-shell flex h-full items-center text-xs text-muted-foreground">
|
||||
{copyright}
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { ComponentConfig } from '@reacteditor/core';
|
||||
import { PanelTop } from 'lucide-react';
|
||||
import Header, { type HeaderProps } from './header';
|
||||
|
||||
const headerConfig: ComponentConfig<HeaderProps> = {
|
||||
label: 'Header',
|
||||
icon: <PanelTop size={16} />,
|
||||
category: 'navigation',
|
||||
global: true,
|
||||
defaultProps: {
|
||||
siteName: 'Next.js',
|
||||
links: [
|
||||
{ label: 'Home', url: '/' },
|
||||
{ label: 'Editor', url: '/editor' },
|
||||
{ label: 'Next.js Docs', url: 'https://nextjs.org/docs' },
|
||||
],
|
||||
},
|
||||
fields: {
|
||||
siteName: { label: 'Site name', type: 'text', contentEditable: true },
|
||||
links: {
|
||||
label: 'Navigation links',
|
||||
type: 'array',
|
||||
defaultItemProps: { label: 'Link', url: '/' },
|
||||
getItemSummary: (item) => item?.label || 'Link',
|
||||
arrayFields: {
|
||||
label: { label: 'Label', type: 'text', contentEditable: true },
|
||||
url: { label: 'URL', type: 'text' },
|
||||
},
|
||||
},
|
||||
},
|
||||
render: (props) => <Header {...props} />,
|
||||
};
|
||||
|
||||
export default headerConfig;
|
||||
@@ -0,0 +1,46 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
export type NavigationLink = { label: string; url: string };
|
||||
|
||||
export type HeaderProps = {
|
||||
siteName?: string;
|
||||
links?: NavigationLink[];
|
||||
};
|
||||
|
||||
export default function Header({ siteName = 'Next.js', links = [] }: HeaderProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<header className="relative z-50 h-16 bg-background">
|
||||
<div className="page-shell flex h-full items-center justify-between">
|
||||
<Link href="/" className="text-sm font-semibold tracking-tight">{siteName}</Link>
|
||||
<nav aria-label="Primary navigation" className="hidden items-center gap-7 md:flex">
|
||||
{links.map((link) => (
|
||||
<Link key={`${link.label}-${link.url}`} href={link.url} className="text-sm text-muted-foreground transition-colors hover:text-foreground">
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</nav>
|
||||
<Button type="button" variant="ghost" size="icon" className="md:hidden" aria-label={open ? 'Close navigation' : 'Open navigation'} aria-expanded={open} onClick={() => setOpen((value) => !value)}>
|
||||
{open ? <X aria-hidden="true" /> : <Menu aria-hidden="true" />}
|
||||
</Button>
|
||||
</div>
|
||||
{open && (
|
||||
<nav aria-label="Mobile navigation" className="absolute inset-x-0 top-16 bg-background px-4 py-4 md:hidden">
|
||||
<div className="page-shell flex flex-col gap-1">
|
||||
{links.map((link) => (
|
||||
<Link key={`${link.label}-${link.url}`} href={link.url} onClick={() => setOpen(false)} className="rounded-md px-3 py-2.5 text-sm hover:bg-muted">
|
||||
{link.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
)}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { ComponentConfig } from '@reacteditor/core';
|
||||
import { SquareDashed } from 'lucide-react';
|
||||
import Starter, { type StarterProps } from './starter';
|
||||
|
||||
const starterConfig: ComponentConfig<StarterProps> = {
|
||||
label: 'Starter',
|
||||
icon: <SquareDashed size={16} />,
|
||||
category: 'content',
|
||||
defaultProps: {
|
||||
logoUrl: '/next.svg',
|
||||
message: 'To edit this page navigate to /editor.',
|
||||
},
|
||||
fields: {
|
||||
logoUrl: { label: 'Logo', type: 'image' },
|
||||
message: { label: 'Message', type: 'text', contentEditable: true },
|
||||
},
|
||||
render: (props) => <Starter {...props} />,
|
||||
};
|
||||
|
||||
export default starterConfig;
|
||||
@@ -0,0 +1,15 @@
|
||||
export type StarterProps = {
|
||||
logoUrl?: string;
|
||||
message?: string;
|
||||
};
|
||||
|
||||
export default function Starter({ logoUrl = '/next.svg', message = 'To edit this page navigate to /editor.' }: StarterProps) {
|
||||
return (
|
||||
<main className="flex min-h-[calc(100svh-8rem)] items-center justify-center px-6 py-20">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
{logoUrl && <img src={logoUrl} alt="Next.js logo" className="h-auto w-[197px] object-contain" />}
|
||||
<p className="mt-8 text-base text-muted-foreground sm:text-lg">{message}</p>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user