Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve Next.js example design and functionality for better demo #30418

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,302 changes: 869 additions & 433 deletions examples/nextjs/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
},
"dependencies": {
"@dotcms/client": "latest",
"@dotcms/react": "latest",
"@dotcms/experiments": "latest",
"@dotcms/react": "latest",
"next": "14.1.1",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-player": "^2.16.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.13",
Expand Down
8 changes: 8 additions & 0 deletions examples/nextjs/public/local/nextjs.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion examples/nextjs/public/next.svg

This file was deleted.

1 change: 0 additions & 1 deletion examples/nextjs/public/vercel.svg

This file was deleted.

54 changes: 34 additions & 20 deletions examples/nextjs/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,41 @@
@tailwind components;
@tailwind utilities;

@layer utilities {
.line-clamp-1 {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}

.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
@layer base {
html {
scroll-behavior: smooth;
}

main > div {
@apply my-8;
}
}

/* ROW CLASSES */
.uncontained .container {
@apply max-w-none;
}

.vertical-fill,
.vertical-fill [data-dot-object="container"] {
@apply grid gap-4 h-full;
grid-template-rows: repeat(auto-fit, minmax(0, 1fr));
}

/* Custom classes, example to BlockEditorRenderer */
table, th, td {
border: 1px solid;
.spacing-y-md,
.spacing-y-md [data-dot-object="container"] {
@apply space-y-4;
}

.blocks {
border: 5px solid red;
}
.row-gradient {
@apply relative
}

.row-gradient::after {
@apply absolute left-0 top-40 z-0 w-full h-4/5 bg-gradient-to-t from-lime-50 to-lime-200 border-t-8 border-lime-300 origin-top-right skew-y-3;
content: "";
}

.row-gradient .container {
@apply relative z-10;
}
56 changes: 56 additions & 0 deletions examples/nextjs/src/components/content-types/SimpleWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { client } from "@/utils/dotcmsClient";
import { useEffect, useState } from "react";

export function SimpleWidget() {
const [destination, setDestination] = useState([]);

useEffect(() => {
client.content
.getCollection("Destination")
.limit(10)
.then((response) => {
setDestination(response.contentlets);
})
.catch((error) => {
console.error(`Error fetching Destinations`, error);
});
}, []);

return (
<div className="relative z-10 p-6 -mt-24 w-full bg-white rounded-lg shadow-lg lg:mx-auto lg:max-w-5xl">
<h2 className="mb-6 text-3xl font-bold text-gray-800">Find your next destination</h2>
<form className="flex flex-wrap gap-4 items-end">
<div className="flex-grow min-w-[200px]">
<label htmlFor="destination" className="block mb-1 text-sm font-medium text-gray-700">Destination</label>
<div className="relative">
<select id="destination" className="py-2 pr-10 pl-3 w-full text-base bg-white rounded-md border border-gray-300 appearance-none focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">
{destination.map((destination) => (
<option key={destination.identifier} value={destination.identifier}>
{destination.title}
</option>
))}
</select>
<div className="flex absolute inset-y-0 right-0 items-center px-2 text-gray-700 pointer-events-none">
<svg className="w-5 h-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z" />
</svg>
</div>
</div>
</div>
<div className="flex-shrink-0 w-40">
<label htmlFor="start-date" className="block mb-1 text-sm font-medium text-gray-700">Start date</label>
<input type="date" id="start-date" className="py-2 pr-3 pl-3 w-full text-base rounded-md border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="mm/dd/yyyy" />
</div>
<div className="flex-shrink-0 w-40">
<label htmlFor="end-date" className="block mb-1 text-sm font-medium text-gray-700">End date</label>
<input type="date" id="end-date" className="py-2 pr-3 pl-3 w-full text-base rounded-md border border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" placeholder="mm/dd/yyyy" />
</div>
<div className="flex-shrink-0">
<button type="submit" className="flex justify-center px-8 py-2 text-sm font-medium text-white bg-orange-600 rounded-md border border-transparent shadow-sm hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500">
BOOK NOW
</button>
</div>
</form>
</div>
);
}
45 changes: 45 additions & 0 deletions examples/nextjs/src/components/content-types/Video.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"use client";

import { useState } from "react";
import ReactPlayer from "react-player";
import Image from "next/image";

export function Video({ thumbnailCustom }) {
const [isPlaying, setIsPlaying] = useState(false);

return (
<div className="relative w-full h-full min-h-96">
{!isPlaying ? (
<>
<Image
src={thumbnailCustom}
alt="Video thumbnail"
fill={true}
className="object-cover w-full h-full"
objectFit="cover"
/>
<button
className="absolute top-1/2 left-1/2 p-4 bg-white bg-opacity-75 rounded-full transform -translate-x-1/2 -translate-y-1/2"
onClick={() => setIsPlaying(true)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-12 h-12 text-black"
viewBox="0 0 24 24"
fill="currentColor"
>
<path d="M8 5v14l11-7z" />
</svg>
</button>
</>
) : (
<ReactPlayer
url="https://www.youtube.com/watch?v=LXb3EKWsInQ"
width="100%"
height="100%"
onPause={() => setIsPlaying(false)}
/>
)}
</div>
);
}
6 changes: 3 additions & 3 deletions examples/nextjs/src/components/content-types/banner.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function Banner(contentlet) {
const { title, caption, image, link, buttonText } = contentlet;

return (
<div className="relative w-full p-4 bg-gray-200 h-96">
<div className="relative w-full h-[50vh]">
{image && (
<Image
src={image?.idPath ?? image}
Expand All @@ -15,7 +15,7 @@ function Banner(contentlet) {
alt={title}
/>
)}
<div className="absolute inset-0 flex flex-col items-center justify-center p-4 text-center text-white">
<div className="flex absolute inset-0 flex-col justify-center items-center text-center text-white">
<h2 className="mb-2 text-6xl font-bold text-shadow">
<DotEditableText
contentlet={contentlet}
Expand All @@ -24,7 +24,7 @@ function Banner(contentlet) {
</h2>
<p className="mb-4 text-xl text-shadow">{caption}</p>
<Link
className="p-4 text-xl transition duration-300 bg-purple-500 rounded hover:bg-purple-600"
className="p-4 text-xl bg-purple-500 rounded transition duration-300 hover:bg-purple-600"
href={link || "#"}
>
{buttonText}
Expand Down
1 change: 0 additions & 1 deletion examples/nextjs/src/components/content-types/blog.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ function BlogWithBlockEditor({blockEditorItem}){
'paragraph': CustomParagraph
}}
style={{ backgroundColor: 'lightblue', padding: '10px', fontSize: '40px' }}
className="blocks"
/>
}
export default BlogWithBlockEditor;
22 changes: 10 additions & 12 deletions examples/nextjs/src/components/content-types/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ import Image from "next/image";

function ImageComponent({ fileAsset, title, description }) {
return (
<div className="relative overflow-hidden bg-white rounded shadow-lg group">
<div className="relative w-full bg-gray-200 h-96">
{fileAsset && (
<Image
src={fileAsset?.idPath ?? fileAsset}
fill={true}
className="object-cover w-full h-full"
alt={title}
/>
)}
</div>
<div className="absolute bottom-0 w-full px-6 py-8 text-white transition-transform duration-300 translate-y-full bg-orange-500 bg-opacity-80 w-100 group-hover:translate-y-0">
<div className="overflow-hidden relative h-full bg-white rounded shadow-lg min-h-96 group">
{fileAsset && (
<Image
src={fileAsset?.idPath ?? fileAsset}
fill={true}
className="object-cover w-full h-full"
alt={title}
/>
)}
<div className="absolute bottom-0 px-6 py-8 w-full text-white bg-orange-500 bg-opacity-80 transition-transform duration-300 translate-y-full w-100 group-hover:translate-y-0">
<div className="mb-2 text-2xl font-bold">{title}</div>
<p className="text-base">{description}</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion examples/nextjs/src/components/graphql-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function MyGraphQLPage({ pageAsset, nav, query }) {
: DotcmsLayout;

return (
<div className="flex flex-col gap-6 min-h-screen bg-lime-50">
<div className="min-h-screen bg-lime-50">
{pageAsset.layout.header && (
<Header>
<Navigation items={nav} />
Expand Down
4 changes: 2 additions & 2 deletions examples/nextjs/src/components/layout/footer/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import Destinations from "./components/destinations";
// Footer component
function Footer() {
return (
<footer className="p-4 text-white bg-purple-100 py-24">
<div className="grid md:grid-cols-3 sm:grid-cols-1 md:grid-rows-1 sm:grid-rows-3 gap-7 mx-24">
<footer className="p-4 py-12 text-white bg-purple-100">
<div className="grid gap-7 mx-24 md:grid-cols-3 sm:grid-cols-1 md:grid-rows-1 sm:grid-rows-3">
<AboutUs />
<Blogs />
<Destinations />
Expand Down
27 changes: 20 additions & 7 deletions examples/nextjs/src/components/layout/header.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,27 @@
import Link from 'next/link';
import Link from "next/link";
import Image from "next/image";

function Header({ children }) {
return (
<header className="flex items-center justify-between p-4 bg-purple-500">
<div className="flex items-center">
<h2 className="text-3xl font-bold text-white">
<Link href="/">TravelLux in NextJS</Link>
</h2>
<header className="bg-purple-800">
<div className="container">
<div className="flex justify-between items-center p-4">
<Link
href="/"
className="flex gap-2 items-center text-3xl font-medium text-white"
>
<Image
className="invert"
src="/local/nextjs.svg"
alt="TravelLux"
width={50}
height={50}
/>
TravelLux
</Link>
{children}
</div>
</div>
{children}
</header>
);
}
Expand Down
16 changes: 11 additions & 5 deletions examples/nextjs/src/components/layout/navigation.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useSearchParams, usePathname } from "next/navigation";

function Navigation({ items, className }) {
const searchParams = useSearchParams();
// Add usePathname hook to get the current path
const pathname = usePathname();

return (
<nav className={className}>
Expand All @@ -11,8 +13,10 @@ function Navigation({ items, className }) {
<Link
href={{
pathname: '/',
query: Object.fromEntries(searchParams.entries()) // We need to maintain the query params on the navigation, this way next loads the page with the same query params
}}>
query: Object.fromEntries(searchParams.entries())
}}
// Add active class if the current path is home
className={pathname === '/' ? 'font-bold' : ''}>
Home
</Link>
</li>
Expand All @@ -21,9 +25,11 @@ function Navigation({ items, className }) {
<Link
href={{
pathname: item.href,
query: Object.fromEntries(searchParams.entries()) // We need to maintain the query params on the navigation, this way next loads the page with the same query params
query: Object.fromEntries(searchParams.entries())
}}
target={item.target}>
target={item.target}
// Add active class if the current path matches the item's href
className={pathname === item.href ? 'font-bold' : ''}>
{item.title}
</Link>
</li>
Expand Down
10 changes: 6 additions & 4 deletions examples/nextjs/src/components/my-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import { CustomNoComponent } from "./content-types/empty";

import { usePageAsset } from "../hooks/usePageAsset";
import BlogWithBlockEditor from "./content-types/blog";
import { DotCmsClient } from "@dotcms/client";

import { SimpleWidget } from "./content-types/SimpleWidget";
import { Video } from "./content-types/Video";
/**
* Configure experiment settings below. If you are not using experiments,
* you can ignore or remove the experiment-related code and imports.
Expand All @@ -41,6 +41,8 @@ const componentsMap = {
CallToAction: CallToAction,
CustomNoComponent: CustomNoComponent,
BlockEditorItem: BlogWithBlockEditor,
SimpleWidget: SimpleWidget,
Video: Video
};

export function MyPage({ pageAsset, nav }) {
Expand All @@ -63,14 +65,14 @@ export function MyPage({ pageAsset, nav }) {
pageAsset = usePageAsset(pageAsset);

return (
<div className="flex flex-col gap-6 min-h-screen bg-lime-50">
<div className="min-h-screen bg-lime-50">
{pageAsset.layout.header && (
<Header>
<Navigation items={nav} />
</Header>
)}

<main className="flex flex-col gap-8 m-auto">
<main>
<DotLayoutComponent
pageContext={{
components: componentsMap,
Expand Down
Loading
Loading