Merge pull request #178 from presenton/feat/new-dark-theme-layout

feat(nextjs): adds new classic dark layout
This commit is contained in:
Saurav Niraula 2025-08-01 21:47:17 +05:45 committed by GitHub
commit 1ec938034c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 768 additions and 0 deletions

View file

@ -0,0 +1,87 @@
import React from 'react'
import * as z from "zod";
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
export const layoutId = 'classic-dark-title-slide'
export const layoutName = 'Classic Dark Title Slide'
export const layoutDescription = 'A modern title slide with dark gradient background, gradient text, and geographical elements.'
const titleSlideSchema = z.object({
title: z.string().min(3).max(100).default('Nepal\'s Imports and\nExports: A Data-Driven\nOverview').meta({
description: "Main title of the slide",
}),
subtitle: z.string().min(3).max(100).default('Key Trade Statistics and Trends (20222025)').meta({
description: "Subtitle text",
}),
presenter: z.string().min(3).max(50).default('[Your Name]').meta({
description: "Presenter name",
}),
date: z.string().min(3).max(50).default('April 13, 2025').meta({
description: "Presentation date",
}),
image: ImageSchema.default({
__image_url__: 'https://images.pexels.com/photos/9669089/pexels-photo-9669089.jpeg',
__image_prompt__: 'Map of Nepal with gradient coloring from orange to red-brown'
}).meta({
description: "Image of the title slide of the presentation",
}),
})
export const Schema = titleSlideSchema
export type TitleSlideData = z.infer<typeof titleSlideSchema>
interface TitleSlideLayoutProps {
data: Partial<TitleSlideData>
}
const TitleSlideLayout: React.FC<TitleSlideLayoutProps> = ({ data: slideData }) => {
const { title, subtitle, presenter, date, image } = slideData;
return (
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
<div className="flex w-full h-full">
{/* Left side - Text content */}
<div className="flex flex-col flex-1 items-start justify-center space-y-6 p-10">
{/* Title */}
{title && (
<h1 className="text-4xl leading-tight font-bold text-purple-400">
{title}
</h1>
)}
{/* Subtitle */}
{subtitle && (
<p className="text-white text-xl leading-relaxed font-normal opacity-90">
{subtitle}
</p>
)}
{/* Presenter and date */}
<div className="text-white text-base leading-relaxed font-normal opacity-75">
Presenter: {presenter} | {date}
</div>
</div>
{/* Right side - Visual elements */}
<div className="flex flex-col flex-1 items-end justify-center relative">
{image && (
<div className="w-full h-full">
<img
src={image.__image_url__}
alt={image.__image_prompt__}
className="w-full h-full object-cover"
style={{
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.3))'
}}
/>
</div>
)}
</div>
</div>
</div>
)
}
export default TitleSlideLayout

View file

@ -0,0 +1,160 @@
import React from 'react'
import * as z from "zod";
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
import { PieChart, Pie, Cell } from "recharts";
export const layoutId = 'classic-dark-piechart-and-metrics'
export const layoutName = 'Classic Dark Pie Chart and Metrics'
export const layoutDescription = 'A modern slide with dark background, metrics on the left, and pie chart visualization on the right.'
const chartDataSchema = z.object({
name: z.string().meta({ description: "Data point name" }),
value: z.number().meta({ description: "Data point value" }),
});
const pieChartAndMetricsSchema = z.object({
title: z.string().min(3).max(100).default('Introduction to Nepal\'s Trade').meta({
description: "Main title of the slide",
}),
description: z.string().min(10).max(200).default('Nepal\'s landlocked geography heavily influences its trade, fostering reliance on India and China.').meta({
description: "Description text",
}),
metrics: z.array(z.object({
label: z.string().meta({ description: "Metric label" }),
value: z.string().meta({ description: "Metric value" }),
percentage: z.string().optional().meta({ description: "Optional percentage" }),
})).min(2).max(4).default([
{ label: 'Exports (2023)', value: '$2.85 billion', percentage: '6.76% of GDP' },
{ label: 'Imports (2023)', value: '$17.39 billion', percentage: '42.64% of GDP' },
{ label: 'GDP (2022)', value: '$40.83 billion' },
{ label: 'Trade Deficit (2022)', value: '-$12.44 billion' },
]).meta({
description: "List of key metrics",
}),
chartData: z.array(chartDataSchema).min(2).max(4).default([
{ name: 'Imports', value: 42.64 },
{ name: 'Exports', value: 6.76 },
{ name: 'Other GDP', value: 50.6 },
]).meta({
description: "Pie chart data",
}),
showLegend: z.boolean().default(true).meta({
description: "Whether to show chart legend",
}),
showTooltip: z.boolean().default(true).meta({
description: "Whether to show chart tooltip",
}),
})
const chartConfig = {
value: {
label: "Value",
},
name: {
label: "Name",
},
};
const CHART_COLORS = [
'#8b5cf6',
'#3b82f6',
'#a855f7',
];
export const Schema = pieChartAndMetricsSchema
export type PieChartAndMetricsData = z.infer<typeof pieChartAndMetricsSchema>
interface PieChartAndMetricsLayoutProps {
data: Partial<PieChartAndMetricsData>
}
const PieChartAndMetricsLayout: React.FC<PieChartAndMetricsLayoutProps> = ({ data: slideData }) => {
const { title, description, metrics, chartData, showLegend = true, showTooltip = true } = slideData;
const CustomLegend = () => (
<div className="flex justify-center space-x-8 mt-4">
{chartData?.map((entry, index) => (
<div key={index} className="flex items-center space-x-2">
<div
className="w-4 h-4 rounded-sm"
style={{ backgroundColor: CHART_COLORS[index % CHART_COLORS.length] }}
/>
<span className="text-white text-lg font-medium">{entry.name}</span>
</div>
))}
</div>
);
const renderPieChart = () => {
return (
<PieChart>
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
<Pie
data={chartData}
fill="#8b5cf6"
outerRadius="50%"
dataKey="value"
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
labelLine={false}
fontSize={18}
>
{chartData?.map((entry, index) => (
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
))}
</Pie>
</PieChart>
);
};
return (
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
<div className="flex w-full h-full">
{/* Left side - Text content and metrics */}
<div className="flex flex-col basis-1/2 items-start justify-start space-y-8 p-12">
{/* Title */}
{title && (
<h1 className="text-5xl leading-tight font-bold text-purple-400">
{title}
</h1>
)}
{/* Description */}
{description && (
<p className="text-white text-2xl leading-relaxed font-normal opacity-90">
{description}
</p>
)}
{/* Metrics */}
{metrics && metrics.length > 0 && (
<div className="space-y-4">
{metrics.map((metric, index) => (
<div key={index} className="text-white text-xl leading-relaxed font-normal">
<span className="opacity-90"> {metric.label}: </span>
<span className="font-bold text-purple-300">{metric.value}</span>
{metric.percentage && (
<span className="opacity-75"> ({metric.percentage})</span>
)}
</div>
))}
</div>
)}
</div>
{/* Right side - Chart */}
<div className="flex flex-col basis-1/2 items-center justify-center">
<div className="w-full h-full flex flex-col items-center justify-center">
<ChartContainer config={chartConfig} className="h-[500px] w-[500px]">
{renderPieChart()}
</ChartContainer>
{showLegend && <CustomLegend />}
</div>
</div>
</div>
</div>
)
}
export default PieChartAndMetricsLayout

View file

@ -0,0 +1,152 @@
import React from 'react'
import * as z from "zod";
import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent } from '@/components/ui/chart';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Cell } from "recharts";
export const layoutId = 'classic-dark-bar-graph'
export const layoutName = 'Classic Dark Bar Graph'
export const layoutDescription = 'A modern slide with dark background, gradient title, bar chart visualization, and footer text.'
const barDataSchema = z.object({
name: z.string().meta({ description: "Product name" }),
value: z.number().meta({ description: "Export value in millions" }),
});
const barGraphSchema = z.object({
title: z.string().min(3).max(100).default('Export Overview: Key Products').meta({
description: "Main title of the slide",
}),
description: z.string().min(10).max(150).default('Nepal\'s total exports were $1.3 billion in 2022, a 21% decrease from 2021, but showed a 47.5% YoY increase by Nov 2024.').meta({
description: "Description text",
}),
chartData: z.array(barDataSchema).min(2).max(6).default([
{ name: 'Soybean Oil (non-crude)', value: 180 },
{ name: 'Palm Oil (non-crude)', value: 180 },
{ name: 'Carpets/Textile Floor...', value: 80 },
{ name: 'Cardamom', value: 50 },
{ name: 'Felt Products', value: 40 },
]).meta({
description: "Bar chart data",
}),
showLegend: z.boolean().default(true).meta({
description: "Whether to show chart legend",
}),
showTooltip: z.boolean().default(true).meta({
description: "Whether to show chart tooltip",
}),
})
const chartConfig = {
value: {
label: "Value ($M)",
},
name: {
label: "Product",
},
};
const BAR_COLORS = [
'#8b5cf6', // Dark purple for top products
'#8b5cf6', // Dark purple for top products
'#a855f7', // Light purple for other products
'#a855f7', // Light purple for other products
'#a855f7', // Light purple for other products
];
export const Schema = barGraphSchema
export type BarGraphData = z.infer<typeof barGraphSchema>
interface BarGraphLayoutProps {
data: Partial<BarGraphData>
}
const BarGraphLayout: React.FC<BarGraphLayoutProps> = ({ data: slideData }) => {
const { title, description, chartData, showLegend = false, showTooltip = true } = slideData;
const CustomLegend = () => (
<div className="flex justify-center space-x-8 mt-8">
{chartData?.map((entry, index) => (
<div key={index} className="flex items-center space-x-2">
<div
className="w-4 h-4 rounded-sm"
style={{ backgroundColor: BAR_COLORS[index % BAR_COLORS.length] }}
/>
<span className="text-white text-lg font-medium">{entry.name}</span>
</div>
))}
</div>
);
const renderBarChart = () => {
return (
<BarChart
data={chartData}
margin={{ top: 0, right: 30, left: 20, bottom: 0 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="#374151" />
<XAxis
dataKey="name"
height={100}
tick={{ fill: '#ffffff', fontSize: 16, fontWeight: 600 }}
tickFormatter={(value) => {
if (value.length > 15) {
return value.substring(0, 15) + '...';
}
return value;
}}
/>
<YAxis
tick={{ fill: '#ffffff', fontSize: 16, fontWeight: 600 }}
tickFormatter={(value) => `$${value.toFixed(0)}.00`}
/>
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
<Bar
dataKey="value"
fill="#8b5cf6"
radius={[4, 4, 0, 0]}
>
{chartData?.map((entry, index) => (
<Cell key={`cell-${index}`} fill={BAR_COLORS[index % BAR_COLORS.length]} />
))}
</Bar>
</BarChart>
);
};
return (
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
<div className="flex flex-col w-full h-full">
{/* Header section */}
<div className="flex flex-col items-start justify-start space-y-6 p-12">
{/* Title */}
{title && (
<h1 className="text-5xl leading-tight font-bold text-purple-400">
{title}
</h1>
)}
{/* Description */}
{description && (
<p className="text-white text-2xl leading-relaxed font-normal opacity-90">
{description}
</p>
)}
</div>
{/* Chart section */}
<div className="flex flex-col flex-1 items-center justify-center px-12">
<div className="w-full h-full flex flex-col items-center justify-center space-y-4">
<ChartContainer config={chartConfig} className="h-[300px] w-full">
{renderBarChart()}
</ChartContainer>
{showLegend && <CustomLegend />}
</div>
</div>
</div>
</div>
)
}
export default BarGraphLayout

View file

@ -0,0 +1,120 @@
import React from 'react'
import * as z from "zod";
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
export const layoutId = 'classic-dark-comparison'
export const layoutName = 'Classic Dark Comparison'
export const layoutDescription = 'A modern slide with dark background, image on the left (2/5), and comparison content on the right (3/5).'
const comparisonItemSchema = z.object({
name: z.string().min(3).max(30).meta({ description: "Commodity name" }),
value: z.string().min(3).max(30).meta({ description: "Value" }),
when: z.string().min(3).max(30).meta({ description: "When the value was recorded" }),
details: z.string().min(3).max(50).optional().meta({ description: "Additional details" }),
});
const comparisonSectionSchema = z.object({
title: z.string().min(3).max(50).meta({ description: "Section title" }),
items: z.array(comparisonItemSchema).min(1).max(3).meta({ description: "List of items in the section" }),
});
const comparisonSchema = z.object({
title: z.string().min(3).max(100).default('Key Commodities in Focus').meta({
description: "Main title of the slide",
}),
comparisonSections: z.array(comparisonSectionSchema).min(2).max(2).default([
{
title: 'Exports',
items: [
{ name: 'Soybean Oil', value: '$186.91 million', when: '2022' },
{ name: 'Cardamom', value: '$46.64 million', when: '2022', details: 'primarily to India' },
]
},
{
title: 'Imports',
items: [
{ name: 'Crude Soybean Oil', value: '$347.77 million', when: '2022' },
{ name: 'Petroleum Products', value: '$3.15 billion', when: '2022', details: '22% of total imports' },
{ name: 'Vehicles/Parts', value: '$526 million', when: '2022', details: 'down 45% from 2021' },
]
}
]).meta({
description: "Comparison sections with title and data items",
}),
image: ImageSchema.default({
__image_url__: 'https://images.pexels.com/photos/9669089/pexels-photo-9669089.jpeg',
__image_prompt__: 'Map of South Asia showing Nepal and neighboring countries with trade routes highlighted'
}).meta({
description: "Comparison visualization image",
}),
})
export const Schema = comparisonSchema
export type ComparisonData = z.infer<typeof comparisonSchema>
interface ComparisonLayoutProps {
data: Partial<ComparisonData>
}
const ComparisonLayout: React.FC<ComparisonLayoutProps> = ({ data: slideData }) => {
const { title, comparisonSections, image } = slideData;
return (
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
<div className="flex w-full h-full">
{/* Left side - Image (2/5) */}
<div className="flex flex-col basis-2/5 h-full">
{image && (
<div className="w-full h-full">
<img
src={image.__image_url__}
alt={image.__image_prompt__}
className="w-full h-full object-cover"
style={{
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.3))'
}}
/>
</div>
)}
</div>
{/* Right side - Content (3/5) */}
<div className="flex flex-col basis-3/5 items-start justify-start space-y-8 p-12">
{/* Title */}
{title && (
<h1 className="text-5xl leading-tight font-bold text-purple-400">
{title}
</h1>
)}
{/* Comparison Sections */}
<div className="flex w-full space-x-12 flex pt-10">
{comparisonSections && comparisonSections.map((section, sectionIndex) => (
<div key={sectionIndex} className="flex flex-col flex-1 space-y-4">
<h2 className="text-3xl leading-tight font-semibold text-purple-300">
{section.title}
</h2>
{section.items && section.items.length > 0 && (
<div className="space-y-3">
{section.items.map((item, index) => (
<div key={index} className="text-white text-lg leading-relaxed font-normal">
<span className="opacity-90"> <span className="font-bold">{item.name}</span>: {item.value} ({item.when})</span>
{item.details && (
<span className="opacity-75">, {item.details}</span>
)}
</div>
))}
</div>
)}
</div>
))}
</div>
</div>
</div>
</div>
)
}
export default ComparisonLayout

View file

@ -0,0 +1,141 @@
import React from 'react'
import * as z from "zod";
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
export const layoutId = 'classic-dark-metrics'
export const layoutName = 'Classic Dark Metrics'
export const layoutDescription = 'A modern slide with dark background, metric cards arranged in a grid with icons and data.'
const metricItemSchema = z.object({
title: z.string().min(3).max(50).meta({ description: "Metric title" }),
value: z.string().min(3).max(30).meta({ description: "Metric value" }),
percentage: z.string().min(3).max(30).meta({ description: "Percentage value" }),
icon: IconSchema.meta({ description: "Icon for the metric" }),
});
const metricsSchema = z.object({
title: z.string().min(3).max(100).default('Top Export Destinations').meta({
description: "Main title of the slide",
}),
description: z.string().min(10).max(200).default('Nepal exports 760 products to 132 countries, with a strong focus on regional trade.').meta({
description: "Description text",
}),
metrics: z.array(metricItemSchema).min(2).max(6).default([
{
title: 'India',
value: '$935 million',
percentage: '71.93%',
icon: {
__icon_url__: '/static/icons/placeholder.png',
__icon_query__: 'star rating'
}
},
{
title: 'United States',
value: '$147 million',
percentage: '11.32%',
icon: {
__icon_url__: '/static/icons/placeholder.png',
__icon_query__: 'flag country'
}
},
{
title: 'Germany',
value: '$33 million',
percentage: '2.51%',
icon: {
__icon_url__: '/static/icons/placeholder.png',
__icon_query__: 'user person'
}
},
{
title: 'Turkey',
value: '$26 million',
percentage: '2.01%',
icon: {
__icon_url__: '/static/icons/placeholder.png',
__icon_query__: 'pen tool'
}
},
{
title: 'United Kingdom',
value: '$24 million',
percentage: '1.83%',
icon: {
__icon_url__: '/static/icons/placeholder.png',
__icon_query__: 'message chat'
}
},
]).meta({
description: "Metric cards data",
}),
})
export const Schema = metricsSchema
export type MetricsData = z.infer<typeof metricsSchema>
interface MetricsLayoutProps {
data: Partial<MetricsData>
}
const MetricsLayout: React.FC<MetricsLayoutProps> = ({ data: slideData }) => {
const { title, description, metrics } = slideData;
return (
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
<div className="flex flex-col w-full h-full">
{/* Header section */}
<div className="flex flex-col items-start justify-start space-y-6 p-10">
{/* Title */}
{title && (
<h1 className="text-5xl leading-tight font-bold text-purple-400">
{title}
</h1>
)}
{/* Description */}
{description && (
<p className="text-white text-xl leading-relaxed font-normal opacity-90">
{description}
</p>
)}
</div>
{/* Metrics Cards Grid */}
<div className="flex flex-col flex-1 items-center justify-center px-12">
<div className="grid grid-cols-3 gap-x-8 gap-y-12 w-full max-w-4xl">
{metrics && metrics.map((metric, index) => (
<div key={index} className="flex flex-col items-center">
{/* Metric Card with overlapping icon */}
<div className="relative w-full bg-gray-800 rounded-lg p-6 text-center border border-purple-500 border-opacity-30 shadow-lg">
{/* Icon overlapping the top */}
<div className="absolute -top-8 left-1/2 transform -translate-x-1/2 w-16 h-16 bg-purple-600 rounded-full flex items-center justify-center shadow-lg">
<img
src={metric.icon.__icon_url__}
alt={metric.icon.__icon_query__}
className="w-6 h-6 object-contain text-white"
/>
</div>
{/* Content with top padding for icon space */}
<div className="pt-8">
<h3 className="text-2xl font-bold text-white mb-2">
{metric.title}
</h3>
<p className="text-lg text-white opacity-90">
{metric.value} ({metric.percentage})
</p>
</div>
</div>
</div>
))}
</div>
</div>
</div>
</div>
)
}
export default MetricsLayout

View file

@ -0,0 +1,103 @@
import React from 'react'
import * as z from "zod";
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
export const layoutId = 'classic-dark-bullet-point-with-description'
export const layoutName = 'Classic Dark Bullet Point with Description'
export const layoutDescription = 'A modern slide with dark background, image on the left (2/5), and bullet points with descriptions in boxes on the right (3/5).'
const bulletPointSchema = z.object({
title: z.string().min(3).max(80).meta({ description: "Bullet point title" }),
content: z.string().min(10).max(150).meta({ description: "Bullet point content (max 150 characters)" }),
});
const bulletPointWithDescriptionSchema = z.object({
title: z.string().min(3).max(100).default('Trade Policies and Challenges').meta({
description: "Main title of the slide",
}),
bulletPoints: z.array(bulletPointSchema).min(2).max(3).default([
{
title: 'Tariffs',
content: 'Effectively Applied Tariff (2022): 11.59%. Most Favored Nation Tariff (2022): 12.87%. Duty-free imports: $412.11 million.'
},
{
title: 'Forex Reserves',
content: '$8.18 billion in 2019, covering 8 months of imports.'
},
{
title: 'Import Ban Impact',
content: 'Luxury goods ban (Apr-Dec 2022) cut deficit by 15.45% but reduced export earnings by 21.44%.'
}
]).meta({
description: "Bullet points with descriptions (max 3 items)",
}),
image: ImageSchema.default({
__image_url__: 'https://images.pexels.com/photos/9669089/pexels-photo-9669089.jpeg',
__image_prompt__: 'Stylized mountainous landscape with trade arrows and network connections, dark gradient background with orange sun and purple mountains'
}).meta({
description: "Visual representation image",
}),
})
export const Schema = bulletPointWithDescriptionSchema
export type BulletPointWithDescriptionData = z.infer<typeof bulletPointWithDescriptionSchema>
interface BulletPointWithDescriptionLayoutProps {
data: Partial<BulletPointWithDescriptionData>
}
const BulletPointWithDescriptionLayout: React.FC<BulletPointWithDescriptionLayoutProps> = ({ data: slideData }) => {
const { title, bulletPoints, image } = slideData;
return (
<div className="w-full h-full shadow-lg flex items-center aspect-video relative z-20 mx-auto bg-gray-900">
<div className="flex w-full h-full">
{/* Left side - Image (2/5) */}
<div className="flex flex-col basis-2/5 h-full">
{image && (
<div className="w-full h-full">
<img
src={image.__image_url__}
alt={image.__image_prompt__}
className="w-full h-full object-cover"
style={{
filter: 'drop-shadow(0 4px 8px rgba(0,0,0,0.3))'
}}
/>
</div>
)}
</div>
{/* Right side - Content (3/5) */}
<div className="flex flex-col basis-3/5 items-start justify-start space-y-8 p-10">
{/* Title */}
{title && (
<h1 className="text-5xl leading-tight font-bold text-purple-400">
{title}
</h1>
)}
{/* Bullet Points */}
{bulletPoints && bulletPoints.length > 0 && (
<div className="space-y-6 w-full h-full flex flex-col justify-center">
{bulletPoints.map((point, index) => (
<div key={index} className="w-full bg-gray-800 rounded-lg py-2 px-4 border border-gray-600 border-opacity-30">
<h3 className="text-2xl font-bold text-white mb-4">
{point.title}
</h3>
<p className="text-white text-lg leading-relaxed font-normal opacity-90">
{point.content}
</p>
</div>
))}
</div>
)}
</div>
</div>
</div>
)
}
export default BulletPointWithDescriptionLayout

View file

@ -0,0 +1,5 @@
{
"description": "Classic dark layout for presentations",
"ordered": false,
"default": false
}