fix(nextjs): Default Layout schema selection
This commit is contained in:
parent
04a1516a25
commit
8fac0673f0
45 changed files with 15 additions and 6184 deletions
|
|
@ -48,7 +48,12 @@ const LayoutSelection: React.FC<LayoutSelectionProps> = ({
|
|||
useEffect(() => {
|
||||
if (layoutGroups.length > 0 && !selectedLayoutGroup) {
|
||||
const defaultGroup = layoutGroups.find(g => g.isDefault) || layoutGroups[0];
|
||||
onSelectLayoutGroup(defaultGroup);
|
||||
const slides = getLayoutsByGroup(defaultGroup.id);
|
||||
|
||||
onSelectLayoutGroup({
|
||||
...defaultGroup,
|
||||
slides: slides,
|
||||
});
|
||||
}
|
||||
}, [layoutGroups, selectedLayoutGroup, onSelectLayoutGroup]);
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ export function OutlineItem({
|
|||
{isStreaming ? <textarea
|
||||
defaultValue={slideOutline.body || ''}
|
||||
onBlur={(e) => handleSlideChange({ ...slideOutline, body: e.target.value })}
|
||||
className="text-md sm:text-lg flex-1 font-semibold bg-transparent outline-none"
|
||||
className="text-sm flex-1 font-normal bg-transparent outline-none"
|
||||
placeholder="Content goes here"
|
||||
/> : <MarkdownEditor
|
||||
key={index}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { useRouter } from "next/navigation";
|
|||
import { toast } from "sonner";
|
||||
import { clearPresentationData, SlideOutline } from "@/store/slices/presentationGeneration";
|
||||
import { PresentationGenerationApi } from "../../services/api/presentation-generation";
|
||||
import { useLayout } from "../../context/LayoutContext";
|
||||
import { LayoutGroup, LoadingState, TABS } from "../types/index";
|
||||
|
||||
const DEFAULT_LOADING_STATE: LoadingState = {
|
||||
|
|
@ -22,7 +21,6 @@ export const usePresentationGeneration = (
|
|||
) => {
|
||||
const dispatch = useDispatch();
|
||||
const router = useRouter();
|
||||
const { getLayoutById } = useLayout();
|
||||
const [loadingState, setLoadingState] = useState<LoadingState>(DEFAULT_LOADING_STATE);
|
||||
|
||||
const validateInputs = useCallback(() => {
|
||||
|
|
@ -39,21 +37,24 @@ export const usePresentationGeneration = (
|
|||
});
|
||||
return false;
|
||||
}
|
||||
if(!selectedLayoutGroup.slides.length){
|
||||
toast.error("No Slide Schema found", {
|
||||
description: "Please select a Group before generating presentation",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, [outlines, selectedLayoutGroup]);
|
||||
|
||||
const prepareLayoutData = useCallback(() => {
|
||||
if (!selectedLayoutGroup) return null;
|
||||
|
||||
|
||||
|
||||
return {
|
||||
name: selectedLayoutGroup.name,
|
||||
ordered: selectedLayoutGroup.ordered,
|
||||
slides: selectedLayoutGroup.slides
|
||||
};
|
||||
}, [selectedLayoutGroup, getLayoutById]);
|
||||
}, [selectedLayoutGroup]);
|
||||
|
||||
const handleSubmit = useCallback(async () => {
|
||||
if (!selectedLayoutGroup) {
|
||||
|
|
@ -73,6 +74,7 @@ export const usePresentationGeneration = (
|
|||
|
||||
try {
|
||||
const layoutData = prepareLayoutData();
|
||||
|
||||
if (!layoutData) return;
|
||||
const response = await PresentationGenerationApi.presentationPrepare({
|
||||
presentation_id: presentationId,
|
||||
|
|
|
|||
|
|
@ -1,343 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from "@/components/ui/chart";
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
|
||||
export const layoutId = 'chart-table-slide'
|
||||
export const layoutName = 'Chart + Table Slide'
|
||||
export const layoutDescription = 'A layout for displaying data visualization alongside detailed tabular data for comprehensive analysis.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
const tableRowSchema = z.object({
|
||||
metric: z.string().meta({ description: "Metric name" }),
|
||||
value: z.string().meta({ description: "Metric value" }),
|
||||
change: z.string().optional().meta({ description: "Change percentage or indicator" }),
|
||||
status: z.string().optional().meta({ description: "Status or category" }),
|
||||
});
|
||||
|
||||
const chartTableSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Revenue Analysis & Breakdown').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(300).default('This comprehensive analysis combines visual trends with detailed metrics, providing both high-level insights and granular data for informed decision-making.').meta({
|
||||
description: "Bottom description text",
|
||||
}),
|
||||
chartType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('bar').meta({
|
||||
description: "Type of chart to display",
|
||||
}),
|
||||
chartData: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: 'Q1', value: 4000 },
|
||||
{ name: 'Q2', value: 3000 },
|
||||
{ name: 'Q3', value: 5000 },
|
||||
{ name: 'Q4', value: 4500 },
|
||||
]).meta({
|
||||
description: "Chart data points",
|
||||
}),
|
||||
tableData: z.array(tableRowSchema).min(3).max(15).default([
|
||||
{ metric: 'Total Revenue', value: '$16.5M', change: '+12.5%', status: 'Growth' },
|
||||
{ metric: 'Q1 Revenue', value: '$4.0M', change: '+8.2%', status: 'Stable' },
|
||||
{ metric: 'Q2 Revenue', value: '$3.0M', change: '-15.3%', status: 'Decline' },
|
||||
{ metric: 'Q3 Revenue', value: '$5.0M', change: '+25.8%', status: 'Growth' },
|
||||
{ metric: 'Q4 Revenue', value: '$4.5M', change: '+18.4%', status: 'Growth' },
|
||||
{ metric: 'Average Deal Size', value: '$125K', change: '+5.2%', status: 'Stable' },
|
||||
{ metric: 'Customer Count', value: '132', change: '+22.1%', status: 'Growth' },
|
||||
{ metric: 'Market Share', value: '18.3%', change: '+3.1%', status: 'Growth' },
|
||||
{ metric: 'Conversion Rate', value: '24.7%', change: '+1.8%', status: 'Stable' },
|
||||
{ metric: 'Customer Churn', value: '5.2%', change: '-2.1%', status: 'Improvement' },
|
||||
]).meta({
|
||||
description: "Table data rows",
|
||||
}),
|
||||
chartColor: z.string().default('#3b82f6').meta({
|
||||
description: "Primary color for chart elements",
|
||||
}),
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legend",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltip",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = chartTableSlideSchema
|
||||
|
||||
export type ChartTableSlideData = z.infer<typeof chartTableSlideSchema>
|
||||
|
||||
interface ChartTableSlideLayoutProps {
|
||||
data?: Partial<ChartTableSlideData>
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
const ChartTableSlideLayout: React.FC<ChartTableSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const chartData = slideData?.chartData || [];
|
||||
const tableData = slideData?.tableData || [];
|
||||
const chartType = slideData?.chartType || 'bar';
|
||||
const chartColor = slideData?.chartColor || '#3b82f6';
|
||||
const dataKey = slideData?.dataKey || 'value';
|
||||
const categoryKey = slideData?.categoryKey || 'name';
|
||||
const showLegend = slideData?.showLegend || false;
|
||||
const showTooltip = slideData?.showTooltip || true;
|
||||
|
||||
const renderChart = () => {
|
||||
const commonProps = {
|
||||
data: chartData,
|
||||
margin: { top: 20, right: 20, left: 20, bottom: 40 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey} fill={chartColor} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={chartColor}
|
||||
strokeWidth={3}
|
||||
dot={{ fill: chartColor, strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={chartColor}
|
||||
fill={chartColor}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<PieChart margin={{ top: 20, right: 20, left: 20, bottom: 40 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="45%"
|
||||
outerRadius={70}
|
||||
fill={chartColor}
|
||||
dataKey={dataKey}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" />
|
||||
<YAxis dataKey="y" type="number" />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={chartColor} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div>Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case 'growth':
|
||||
return 'text-green-600';
|
||||
case 'decline':
|
||||
return 'text-red-600';
|
||||
case 'stable':
|
||||
return 'text-blue-600';
|
||||
case 'improvement':
|
||||
return 'text-emerald-600';
|
||||
default:
|
||||
return 'text-gray-600';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-stone-100 relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-12 sm:h-16 flex items-center justify-center">
|
||||
<h1
|
||||
className="text-3xl font-bold text-gray-900 leading-tight text-center"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Revenue Analysis & Breakdown'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Chart and Table section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className="flex gap-6 h-full">
|
||||
{/* Chart section - Left side */}
|
||||
<div className="w-1/2 h-full">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 h-full overflow-hidden">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full max-h-[400px] sm:max-h-[420px] lg:max-h-[440px]">
|
||||
{renderChart()}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Table section - Right side */}
|
||||
<div className="w-1/2 h-full">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 h-full overflow-hidden">
|
||||
<div className="overflow-y-auto max-h-[360px] sm:max-h-[420px] lg:max-h-[440px]">
|
||||
<table className="w-full">
|
||||
<thead className="border-t border-b border-gray-300">
|
||||
<tr>
|
||||
<th
|
||||
className="text-left py-3 px-2 text-sm font-semibold text-gray-900"
|
||||
style={{ fontFamily: 'Space Grotesk, sans-serif' }}
|
||||
>
|
||||
Metric
|
||||
</th>
|
||||
<th
|
||||
className="text-right py-3 px-2 text-sm font-semibold text-gray-900"
|
||||
style={{ fontFamily: 'Space Grotesk, sans-serif' }}
|
||||
>
|
||||
Value
|
||||
</th>
|
||||
<th
|
||||
className="text-right py-3 px-2 text-sm font-semibold text-gray-900"
|
||||
style={{ fontFamily: 'Space Grotesk, sans-serif' }}
|
||||
>
|
||||
Change
|
||||
</th>
|
||||
<th
|
||||
className="text-center py-3 px-2 text-sm font-semibold text-gray-900"
|
||||
style={{ fontFamily: 'Space Grotesk, sans-serif' }}
|
||||
>
|
||||
Status
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tableData.map((row, index) => (
|
||||
<tr key={index} className="border-b border-gray-200 hover:bg-white/20">
|
||||
<td
|
||||
className="py-2 px-2 text-sm text-gray-700"
|
||||
style={{ fontFamily: 'Nunito, sans-serif' }}
|
||||
>
|
||||
{row.metric}
|
||||
</td>
|
||||
<td
|
||||
className="py-2 px-2 text-sm text-gray-900 text-right font-medium"
|
||||
style={{ fontFamily: 'Nunito, sans-serif' }}
|
||||
>
|
||||
{row.value}
|
||||
</td>
|
||||
<td
|
||||
className="py-2 px-2 text-sm text-right"
|
||||
style={{ fontFamily: 'Nunito, sans-serif' }}
|
||||
>
|
||||
{row.change}
|
||||
</td>
|
||||
<td
|
||||
className={`py-2 px-2 text-sm text-center font-medium ${getStatusColor(row.status || '')}`}
|
||||
style={{ fontFamily: 'Nunito, sans-serif' }}
|
||||
>
|
||||
{row.status}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Description section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
|
||||
<p
|
||||
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'This comprehensive analysis combines visual trends with detailed metrics, providing both high-level insights and granular data for informed decision-making.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChartTableSlideLayout
|
||||
|
|
@ -1,270 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from "@/components/ui/chart";
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
|
||||
export const layoutId = 'chart-with-summary-slide'
|
||||
export const layoutName = 'Chart with Summary Slide'
|
||||
export const layoutDescription = 'A layout for displaying data visualization with interpretation and summary points.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
const summaryPointSchema = z.object({
|
||||
point: z.string().min(5).max(200).meta({ description: "Summary point or insight" }),
|
||||
});
|
||||
|
||||
const chartWithSummarySlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Sales Performance Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
chartType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('bar').meta({
|
||||
description: "Type of chart to display",
|
||||
}),
|
||||
data: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: 'Q1', value: 4000 },
|
||||
{ name: 'Q2', value: 3000 },
|
||||
{ name: 'Q3', value: 5000 },
|
||||
{ name: 'Q4', value: 4500 },
|
||||
]).meta({
|
||||
description: "Chart data points",
|
||||
}),
|
||||
summaryPoints: z.array(summaryPointSchema).min(2).max(5).default([
|
||||
{ point: 'Q3 showed the highest performance with 25% growth' },
|
||||
{ point: 'Q2 experienced a temporary dip due to market conditions' },
|
||||
{ point: 'Overall annual growth trend remains positive' },
|
||||
{ point: 'Target exceeded by 12% for the fiscal year' },
|
||||
]).meta({
|
||||
description: "Key insights and summary points",
|
||||
}),
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
color: z.string().default('#3b82f6').meta({
|
||||
description: "Primary color for chart elements",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legend",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltip",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = chartWithSummarySlideSchema
|
||||
|
||||
export type ChartWithSummarySlideData = z.infer<typeof chartWithSummarySlideSchema>
|
||||
|
||||
interface ChartWithSummarySlideLayoutProps {
|
||||
data?: Partial<ChartWithSummarySlideData>
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
const ChartWithSummarySlideLayout: React.FC<ChartWithSummarySlideLayoutProps> = ({ data: slideData }) => {
|
||||
const chartData = slideData?.data || [];
|
||||
const chartType = slideData?.chartType || 'bar';
|
||||
const color = slideData?.color || '#3b82f6';
|
||||
const dataKey = slideData?.dataKey || 'value';
|
||||
const categoryKey = slideData?.categoryKey || 'name';
|
||||
const showLegend = slideData?.showLegend || false;
|
||||
const showTooltip = slideData?.showTooltip || true;
|
||||
const summaryPoints = slideData?.summaryPoints || [];
|
||||
|
||||
const renderChart = () => {
|
||||
const commonProps = {
|
||||
data: chartData,
|
||||
margin: { top: 20, right: 30, left: 40, bottom: 80 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey} fill={color} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
strokeWidth={3}
|
||||
dot={{ fill: color, strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<PieChart margin={{ top: 20, right: 30, left: 40, bottom: 80 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="40%"
|
||||
outerRadius={80}
|
||||
fill={color}
|
||||
dataKey={dataKey}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" />
|
||||
<YAxis dataKey="y" type="number" />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={color} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div>Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-stone-100 relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center">
|
||||
<h1
|
||||
className="text-3xl sm:text-3xl lg:text-4xl font-bold text-gray-900 leading-tight text-left"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Sales Performance Analysis'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Chart and Summary section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className="flex gap-6 h-full items-center">
|
||||
{/* Chart section - smaller flex */}
|
||||
<div className="flex-[5] h-full">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 h-full overflow-hidden">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full max-h-[500px] sm:max-h-[520px] lg:max-h-[540px]">
|
||||
{renderChart()}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Summary section - larger flex */}
|
||||
<div className="flex-[4] h-full flex items-center">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 h-full overflow-hidden">
|
||||
<div className="flex flex-col justify-center h-full">
|
||||
<h2
|
||||
className="text-xl sm:text-2xl font-bold text-gray-900 mb-4"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
Key Insights
|
||||
</h2>
|
||||
<ul className="space-y-3">
|
||||
{summaryPoints.map((item, index) => (
|
||||
<li
|
||||
key={index}
|
||||
className="flex items-start gap-3"
|
||||
>
|
||||
<div className="w-2 h-2 bg-blue-500 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<span
|
||||
className="text-sm sm:text-base text-gray-700 leading-relaxed"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{item.point}
|
||||
</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ChartWithSummarySlideLayout
|
||||
|
|
@ -1,149 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'image-background-text-slide'
|
||||
export const layoutName = 'Image Background + Text Slide'
|
||||
export const layoutDescription = 'A layout for quotes, key messages, and mood slides with full-slide background image and overlaid text.'
|
||||
|
||||
const imageBackgroundTextSlideSchema = z.object({
|
||||
title: z.string().min(3).max(200).default('Success is not final, failure is not fatal: it is the courage to continue that counts.').meta({
|
||||
description: "Main quote or message text",
|
||||
}),
|
||||
subtitle: z.string().optional().default('Winston Churchill').meta({
|
||||
description: "Optional subtitle, author, or attribution",
|
||||
}),
|
||||
backgroundImage: ImageSchema.default({
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2016/12/02/02/10/idea-1876659_1280.jpg',
|
||||
__image_prompt__: 'Inspirational workspace with lightbulb and motivation'
|
||||
}).meta({
|
||||
description: "Background image for the slide",
|
||||
}),
|
||||
overlayOpacity: z.number().min(0.3).max(0.8).default(0.5).meta({
|
||||
description: "Dark overlay opacity (0.3-0.8)",
|
||||
}),
|
||||
textAlignment: z.enum(['left', 'center', 'right']).default('center').meta({
|
||||
description: "Text alignment",
|
||||
}),
|
||||
textSize: z.enum(['large', 'extra-large', 'huge']).default('large').meta({
|
||||
description: "Text size variant",
|
||||
}),
|
||||
textColor: z.enum(['white', 'light-gray', 'yellow', 'blue']).default('white').meta({
|
||||
description: "Text color theme",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = imageBackgroundTextSlideSchema
|
||||
|
||||
export type ImageBackgroundTextSlideData = z.infer<typeof imageBackgroundTextSlideSchema>
|
||||
|
||||
interface ImageBackgroundTextSlideLayoutProps {
|
||||
data?: Partial<ImageBackgroundTextSlideData>
|
||||
}
|
||||
|
||||
const ImageBackgroundTextSlideLayout: React.FC<ImageBackgroundTextSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const getTextAlignment = () => {
|
||||
switch (slideData?.textAlignment) {
|
||||
case 'left':
|
||||
return 'text-left items-start justify-start pl-8 sm:pl-16 lg:pl-24';
|
||||
case 'right':
|
||||
return 'text-right items-end justify-end pr-8 sm:pr-16 lg:pr-24';
|
||||
default:
|
||||
return 'text-center items-center justify-center';
|
||||
}
|
||||
};
|
||||
|
||||
const getTextSize = () => {
|
||||
switch (slideData?.textSize) {
|
||||
case 'extra-large':
|
||||
return 'text-3xl sm:text-4xl lg:text-5xl xl:text-6xl';
|
||||
case 'huge':
|
||||
return 'text-4xl sm:text-5xl lg:text-6xl xl:text-7xl';
|
||||
default:
|
||||
return 'text-2xl sm:text-3xl lg:text-4xl xl:text-5xl';
|
||||
}
|
||||
};
|
||||
|
||||
const getTextColor = () => {
|
||||
switch (slideData?.textColor) {
|
||||
case 'light-gray':
|
||||
return 'text-gray-100';
|
||||
case 'yellow':
|
||||
return 'text-yellow-300';
|
||||
case 'blue':
|
||||
return 'text-blue-300';
|
||||
default:
|
||||
return 'text-white';
|
||||
}
|
||||
};
|
||||
|
||||
const getOverlayOpacity = () => {
|
||||
const opacity = slideData?.overlayOpacity || 0.5;
|
||||
return `rgba(0, 0, 0, ${opacity})`;
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Background Image */}
|
||||
<div className="absolute inset-0">
|
||||
<img
|
||||
src={slideData?.backgroundImage?.__image_url__ || ''}
|
||||
alt={slideData?.backgroundImage?.__image_prompt__ || 'Background image'}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Dark Overlay */}
|
||||
<div
|
||||
className="absolute inset-0 z-10"
|
||||
style={{
|
||||
backgroundColor: getOverlayOpacity()
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Text Overlay */}
|
||||
<div className={`absolute inset-0 z-20 flex flex-col ${getTextAlignment()} p-8 sm:p-12 lg:p-16`}>
|
||||
<div className="max-w-4xl">
|
||||
{/* Main Title/Quote */}
|
||||
<h1
|
||||
className={`${getTextSize()} ${getTextColor()} font-bold leading-tight mb-4 sm:mb-6 lg:mb-8`}
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif',
|
||||
textShadow: '2px 2px 4px rgba(0,0,0,0.3)'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Success is not final, failure is not fatal: it is the courage to continue that counts.'}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle/Attribution */}
|
||||
{slideData?.subtitle && (
|
||||
<p
|
||||
className={`text-lg sm:text-xl lg:text-2xl ${getTextColor()} font-medium opacity-90`}
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif',
|
||||
textShadow: '1px 1px 2px rgba(0,0,0,0.3)'
|
||||
}}
|
||||
>
|
||||
— {slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImageBackgroundTextSlideLayout
|
||||
|
|
@ -1,138 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'intro-slide'
|
||||
export const layoutName = 'Intro Slide'
|
||||
export const layoutDescription = 'A 2-1 split layout featuring title, description, and presenter info on the left (2/3), and full-height image on the right (1/3).'
|
||||
|
||||
const introSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Analytics Dashboard').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Welcome to our comprehensive analytics overview. This dashboard provides key insights and metrics to help you make data-driven decisions for your business growth.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
presenterName: z.string().min(2).max(50).default('Suraj Jha').meta({
|
||||
description: "Name of the presenter",
|
||||
}),
|
||||
presentationDate: z.string().min(2).max(50).default('December 2024').meta({
|
||||
description: "Date of the presentation",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2016/11/27/21/42/stock-1863880_1280.jpg',
|
||||
__image_prompt__: 'Analytics dashboard with charts and graphs'
|
||||
}).meta({
|
||||
description: "Main slide image",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = introSlideSchema
|
||||
|
||||
export type IntroSlideData = z.infer<typeof introSlideSchema>
|
||||
|
||||
interface IntroSlideLayoutProps {
|
||||
data?: Partial<IntroSlideData>
|
||||
}
|
||||
|
||||
const IntroSlideLayout: React.FC<IntroSlideLayoutProps> = ({ data: slideData }) => {
|
||||
// Generate initials from presenter name
|
||||
const getInitials = (name: string) => {
|
||||
return name.split(' ').map(word => word.charAt(0).toUpperCase()).join('');
|
||||
};
|
||||
|
||||
const presenterInitials = getInitials(slideData?.presenterName || 'Suraj Jha');
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md max-h-[720px] flex items-center aspect-video bg-stone-100 relative z-20 mx-auto"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex w-full h-full">
|
||||
{/* Left section - 2/3 width */}
|
||||
<div className="w-2/3 flex flex-col justify-center space-y-1 md:space-y-2 lg:space-y-6 pl-8 sm:pl-16 lg:pl-24 py-[10px] sm:py-[40px] lg:py-[86px] pr-6 lg:pr-12">
|
||||
{/* Title */}
|
||||
<h1
|
||||
className="text-3xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Analytics Dashboard'}
|
||||
</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p
|
||||
className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'Welcome to our comprehensive analytics overview. This dashboard provides key insights and metrics to help you make data-driven decisions for your business growth.'}
|
||||
</p>
|
||||
|
||||
{/* Presenter Box */}
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-lg p-4 lg:p-6 border border-slate-200 shadow-md">
|
||||
<div className="flex items-center gap-4">
|
||||
{/* Custom Initials Icon */}
|
||||
<div className="w-10 h-10 lg:w-12 lg:h-12 bg-blue-500 rounded-full flex items-center justify-center">
|
||||
<span
|
||||
className="text-white font-bold text-sm lg:text-base"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{presenterInitials}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Presenter Info */}
|
||||
<div className="flex flex-col">
|
||||
<span
|
||||
className="text-xl lg:text-2xl font-bold text-gray-900"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.presenterName || 'Suraj Jha'}
|
||||
</span>
|
||||
<span
|
||||
className="text-sm lg:text-base text-gray-600 font-medium"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.presentationDate || 'December 2024'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right section - 1/3 width */}
|
||||
<div className="w-1/3 h-full">
|
||||
<img
|
||||
src={slideData?.image?.__image_url__ || ''}
|
||||
alt={slideData?.image?.__image_prompt__ || slideData?.title || ''}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default IntroSlideLayout
|
||||
|
|
@ -1,262 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'kpi-summary-grid-slide'
|
||||
export const layoutName = 'KPI Summary Grid Slide'
|
||||
export const layoutDescription = 'A layout for displaying key performance indicators with big numbers, labels, and trend indicators in a grid format.'
|
||||
|
||||
const kpiItemSchema = z.object({
|
||||
value: z.string().min(1).max(20).meta({ description: "KPI value (e.g., '$2.4M', '95%', '1,234')" }),
|
||||
label: z.string().min(2).max(50).meta({ description: "KPI label" }),
|
||||
trend: z.enum(['up', 'down', 'flat', 'none']).default('none').meta({ description: "Trend direction" }),
|
||||
trendValue: z.string().optional().meta({ description: "Trend percentage or value" }),
|
||||
sparklineData: z.array(z.number()).optional().meta({ description: "Mini sparkline data points" }),
|
||||
});
|
||||
|
||||
const kpiSummaryGridSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Performance Indicators').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(300).default('These key metrics provide a comprehensive overview of business performance, tracking essential indicators that drive strategic decision-making and operational excellence.').meta({
|
||||
description: "Bottom description text",
|
||||
}),
|
||||
kpis: z.array(kpiItemSchema).min(2).max(6).default([
|
||||
{
|
||||
value: '$2.4M',
|
||||
label: 'Total Revenue',
|
||||
trend: 'up',
|
||||
trendValue: '+12.5%',
|
||||
sparklineData: [10, 15, 12, 18, 22, 25, 28, 24, 30, 35]
|
||||
},
|
||||
{
|
||||
value: '18.3%',
|
||||
label: 'Growth Rate',
|
||||
trend: 'up',
|
||||
trendValue: '+3.2%',
|
||||
sparklineData: [8, 12, 10, 15, 18, 20, 22, 19, 25, 28]
|
||||
},
|
||||
{
|
||||
value: '24.7%',
|
||||
label: 'Conversion Rate',
|
||||
trend: 'down',
|
||||
trendValue: '-1.8%',
|
||||
sparklineData: [30, 28, 25, 22, 26, 24, 20, 18, 22, 25]
|
||||
},
|
||||
{
|
||||
value: '1,234',
|
||||
label: 'Active Users',
|
||||
trend: 'up',
|
||||
trendValue: '+8.9%',
|
||||
sparklineData: [100, 120, 110, 140, 160, 180, 170, 200, 220, 240]
|
||||
},
|
||||
{
|
||||
value: '95.2%',
|
||||
label: 'Customer Satisfaction',
|
||||
trend: 'flat',
|
||||
trendValue: '+0.1%',
|
||||
sparklineData: [90, 92, 91, 93, 94, 95, 94, 96, 95, 95]
|
||||
},
|
||||
{
|
||||
value: '47s',
|
||||
label: 'Avg Response Time',
|
||||
trend: 'down',
|
||||
trendValue: '-12.3%',
|
||||
sparklineData: [60, 58, 55, 52, 50, 48, 45, 47, 46, 47]
|
||||
},
|
||||
]).meta({
|
||||
description: "Array of KPI items (2-6 items)",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = kpiSummaryGridSlideSchema
|
||||
|
||||
export type KPISummaryGridSlideData = z.infer<typeof kpiSummaryGridSlideSchema>
|
||||
|
||||
interface KPISummaryGridSlideLayoutProps {
|
||||
data?: Partial<KPISummaryGridSlideData>
|
||||
}
|
||||
|
||||
const KPISummaryGridSlideLayout: React.FC<KPISummaryGridSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const kpis = slideData?.kpis || [];
|
||||
|
||||
// Determine grid layout based on KPI count
|
||||
const getGridLayout = (count: number) => {
|
||||
switch (count) {
|
||||
case 2:
|
||||
return 'grid-cols-2';
|
||||
case 3:
|
||||
return 'grid-cols-3';
|
||||
case 4:
|
||||
return 'grid-cols-2 md:grid-cols-4';
|
||||
case 5:
|
||||
case 6:
|
||||
return 'grid-cols-2 md:grid-cols-3';
|
||||
default:
|
||||
return 'grid-cols-3';
|
||||
}
|
||||
};
|
||||
|
||||
// Render trend arrow
|
||||
const renderTrendArrow = (trend: string) => {
|
||||
switch (trend) {
|
||||
case 'up':
|
||||
return (
|
||||
<svg className="w-4 h-4 text-green-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M3.293 9.707a1 1 0 010-1.414l6-6a1 1 0 011.414 0l6 6a1 1 0 01-1.414 1.414L11 5.414V17a1 1 0 11-2 0V5.414L4.707 9.707a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
case 'down':
|
||||
return (
|
||||
<svg className="w-4 h-4 text-red-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 10.293a1 1 0 010 1.414l-6 6a1 1 0 01-1.414 0l-6-6a1 1 0 111.414-1.414L9 14.586V3a1 1 0 012 0v11.586l4.293-4.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
case 'flat':
|
||||
return (
|
||||
<svg className="w-4 h-4 text-gray-500" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Render mini sparkline
|
||||
const renderSparkline = (data: number[] | undefined) => {
|
||||
if (!data || data.length === 0) return null;
|
||||
|
||||
const max = Math.max(...data);
|
||||
const min = Math.min(...data);
|
||||
const range = max - min;
|
||||
|
||||
const points = data.map((value, index) => {
|
||||
const x = (index / (data.length - 1)) * 80;
|
||||
const y = 20 - ((value - min) / range) * 20;
|
||||
return `${x},${y}`;
|
||||
}).join(' ');
|
||||
|
||||
return (
|
||||
<svg className="w-20 h-5" viewBox="0 0 80 20">
|
||||
<polyline
|
||||
points={points}
|
||||
fill="none"
|
||||
stroke="#3b82f6"
|
||||
strokeWidth="1.5"
|
||||
className="opacity-70"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const getTrendColor = (trend: string) => {
|
||||
switch (trend) {
|
||||
case 'up':
|
||||
return 'text-green-600';
|
||||
case 'down':
|
||||
return 'text-red-600';
|
||||
case 'flat':
|
||||
return 'text-gray-600';
|
||||
default:
|
||||
return 'text-gray-600';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-stone-100 relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-12 sm:h-16 flex items-center justify-center">
|
||||
<h1
|
||||
className="text-3xl font-bold text-gray-900 leading-tight text-center"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Key Performance Indicators'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* KPI Grid section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className={`grid ${getGridLayout(kpis.length)} gap-6 h-full content-center`}>
|
||||
{kpis.map((kpi, index) => (
|
||||
<div key={index} className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 flex flex-col items-center justify-center text-center">
|
||||
{/* Big Number */}
|
||||
<div
|
||||
className="text-3xl sm:text-4xl lg:text-5xl font-bold text-gray-900 mb-2"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{kpi.value}
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<div
|
||||
className="text-sm uppercase text-gray-600 font-medium mb-3"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{kpi.label}
|
||||
</div>
|
||||
|
||||
{/* Trend and Sparkline */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Trend Arrow and Value */}
|
||||
{kpi.trend !== 'none' && (
|
||||
<div className="flex items-center gap-1">
|
||||
{renderTrendArrow(kpi.trend)}
|
||||
<span
|
||||
className={`text-xs font-medium ${getTrendColor(kpi.trend)}`}
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{kpi.trendValue}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Mini Sparkline */}
|
||||
{kpi.sparklineData && renderSparkline(kpi.sparklineData)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Description section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
|
||||
<p
|
||||
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'These key metrics provide a comprehensive overview of business performance, tracking essential indicators that drive strategic decision-making and operational excellence.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default KPISummaryGridSlideLayout
|
||||
|
|
@ -1,304 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from "@/components/ui/chart";
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
|
||||
export const layoutId = 'multi-chart-grid-slide'
|
||||
export const layoutName = 'Multi-Chart Grid Slide'
|
||||
export const layoutDescription = 'A layout for displaying 2-4 charts in a grid format for small multiples and trend comparisons.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
const chartItemSchema = z.object({
|
||||
type: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).meta({ description: "Chart type" }),
|
||||
data: z.array(chartDataSchema).min(2).max(8).meta({ description: "Chart data points" }),
|
||||
title: z.string().min(2).max(50).meta({ description: "Chart title/caption" }),
|
||||
color: z.string().meta({ description: "Chart color" }),
|
||||
});
|
||||
|
||||
const multiChartGridSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Market Performance Dashboard').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(300).default('This dashboard provides a comprehensive view of key performance indicators across multiple business segments, enabling quick identification of trends and comparative analysis.').meta({
|
||||
description: "Bottom description text",
|
||||
}),
|
||||
charts: z.array(chartItemSchema).min(2).max(4).default([
|
||||
{
|
||||
type: 'bar',
|
||||
data: [
|
||||
{ name: 'Q1', value: 4000 },
|
||||
{ name: 'Q2', value: 3000 },
|
||||
{ name: 'Q3', value: 5000 },
|
||||
{ name: 'Q4', value: 4500 },
|
||||
],
|
||||
title: 'Revenue Trends',
|
||||
color: '#3b82f6'
|
||||
},
|
||||
{
|
||||
type: 'line',
|
||||
data: [
|
||||
{ name: 'Q1', value: 2400 },
|
||||
{ name: 'Q2', value: 2210 },
|
||||
{ name: 'Q3', value: 2290 },
|
||||
{ name: 'Q4', value: 2000 },
|
||||
],
|
||||
title: 'Customer Growth',
|
||||
color: '#10b981'
|
||||
},
|
||||
{
|
||||
type: 'area',
|
||||
data: [
|
||||
{ name: 'Q1', value: 1800 },
|
||||
{ name: 'Q2', value: 1950 },
|
||||
{ name: 'Q3', value: 2100 },
|
||||
{ name: 'Q4', value: 2300 },
|
||||
],
|
||||
title: 'Market Share',
|
||||
color: '#f59e0b'
|
||||
},
|
||||
{
|
||||
type: 'bar',
|
||||
data: [
|
||||
{ name: 'Q1', value: 800 },
|
||||
{ name: 'Q2', value: 967 },
|
||||
{ name: 'Q3', value: 1200 },
|
||||
{ name: 'Q4', value: 1400 },
|
||||
],
|
||||
title: 'Cost Efficiency',
|
||||
color: '#ef4444'
|
||||
},
|
||||
]).meta({
|
||||
description: "Array of charts (2-4 charts)",
|
||||
}),
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legends",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltips",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = multiChartGridSlideSchema
|
||||
|
||||
export type MultiChartGridSlideData = z.infer<typeof multiChartGridSlideSchema>
|
||||
|
||||
interface MultiChartGridSlideLayoutProps {
|
||||
data?: Partial<MultiChartGridSlideData>
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
const MultiChartGridSlideLayout: React.FC<MultiChartGridSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const charts = slideData?.charts || [];
|
||||
const dataKey = slideData?.dataKey || 'value';
|
||||
const categoryKey = slideData?.categoryKey || 'name';
|
||||
const showLegend = slideData?.showLegend || false;
|
||||
const showTooltip = slideData?.showTooltip || true;
|
||||
|
||||
// Determine grid layout based on chart count
|
||||
const getGridLayout = (count: number) => {
|
||||
switch (count) {
|
||||
case 2:
|
||||
return 'grid-cols-2 grid-rows-1';
|
||||
case 3:
|
||||
return 'grid-cols-2 grid-rows-2';
|
||||
case 4:
|
||||
return 'grid-cols-2 grid-rows-2';
|
||||
default:
|
||||
return 'grid-cols-2 grid-rows-2';
|
||||
}
|
||||
};
|
||||
|
||||
const renderChart = (chartType: string, data: any[], color: string) => {
|
||||
const commonProps = {
|
||||
data: data,
|
||||
margin: { top: 5, right: 5, left: 5, bottom: 20 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} fontSize={10} />
|
||||
<YAxis fontSize={10} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey} fill={color} radius={[2, 2, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} fontSize={10} />
|
||||
<YAxis fontSize={10} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
strokeWidth={2}
|
||||
dot={{ fill: color, strokeWidth: 1, r: 2 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} fontSize={10} />
|
||||
<YAxis fontSize={10} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<PieChart margin={{ top: 5, right: 5, left: 5, bottom: 20 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
data={data}
|
||||
cx="50%"
|
||||
cy="45%"
|
||||
outerRadius={30}
|
||||
fill={color}
|
||||
dataKey={dataKey}
|
||||
label={({ percent }) => `${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" fontSize={10} />
|
||||
<YAxis dataKey="y" type="number" fontSize={10} />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={color} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div className="text-xs text-center">Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-stone-100 relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-12 sm:h-16 flex items-center justify-center">
|
||||
<h1
|
||||
className="text-3xl font-bold text-gray-900 leading-tight text-center"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Market Performance Dashboard'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Charts Grid section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className={`grid ${getGridLayout(charts.length)} gap-4 h-full`}>
|
||||
{charts.map((chart, index) => (
|
||||
<div key={index} className="flex flex-col h-full">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-2 sm:p-3 flex-1 overflow-hidden flex flex-col justify-end">
|
||||
<div className="h-40 w-full">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full">
|
||||
{renderChart(chart.type, chart.data, chart.color)}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<p
|
||||
className="text-xs text-center text-gray-700 font-medium"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{chart.title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Description section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
|
||||
<p
|
||||
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'This dashboard provides a comprehensive view of key performance indicators across multiple business segments, enabling quick identification of trends and comparative analysis.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default MultiChartGridSlideLayout
|
||||
|
|
@ -1,235 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from "@/components/ui/chart";
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
|
||||
export const layoutId = 'single-chart-slide'
|
||||
export const layoutName = 'Single Chart Slide'
|
||||
export const layoutDescription = 'A layout for displaying one key metric or data trend with title and interactive chart.'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
const singleChartSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Revenue Growth Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(300).default('This analysis reveals key performance trends across quarterly periods, identifying growth patterns and areas requiring strategic focus for sustained business success.').meta({
|
||||
description: "Bottom description text",
|
||||
}),
|
||||
chartType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('bar').meta({
|
||||
description: "Type of chart to display",
|
||||
}),
|
||||
data: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: 'Jan', value: 4000 },
|
||||
{ name: 'Feb', value: 3000 },
|
||||
{ name: 'Mar', value: 2000 },
|
||||
{ name: 'Apr', value: 2780 },
|
||||
{ name: 'May', value: 1890 },
|
||||
{ name: 'Jun', value: 2390 },
|
||||
]).meta({
|
||||
description: "Chart data points",
|
||||
}),
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
color: z.string().default('#3b82f6').meta({
|
||||
description: "Primary color for chart elements",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legend",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltip",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = singleChartSlideSchema
|
||||
|
||||
export type SingleChartSlideData = z.infer<typeof singleChartSlideSchema>
|
||||
|
||||
interface SingleChartSlideLayoutProps {
|
||||
data?: Partial<SingleChartSlideData>
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
const SingleChartSlideLayout: React.FC<SingleChartSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const chartData = slideData?.data || [];
|
||||
const chartType = slideData?.chartType || 'bar';
|
||||
const color = slideData?.color || '#3b82f6';
|
||||
const dataKey = slideData?.dataKey || 'value';
|
||||
const categoryKey = slideData?.categoryKey || 'name';
|
||||
const showLegend = slideData?.showLegend || false;
|
||||
const showTooltip = slideData?.showTooltip || true;
|
||||
|
||||
const renderChart = () => {
|
||||
const commonProps = {
|
||||
data: chartData,
|
||||
margin: { top: 20, right: 30, left: 40, bottom: 60 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey} fill={color} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
strokeWidth={3}
|
||||
dot={{ fill: color, strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<PieChart margin={{ top: 20, right: 30, left: 40, bottom: 60 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="40%"
|
||||
outerRadius={70}
|
||||
fill={color}
|
||||
dataKey={dataKey}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{chartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" />
|
||||
<YAxis dataKey="y" type="number" />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={color} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div>Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-stone-100 relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
|
||||
<h1
|
||||
className="text-3xl sm:text-3xl lg:text-4xl font-bold text-gray-900 leading-tight text-center"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Revenue Growth Analysis'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Chart section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 h-full overflow-hidden">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full max-h-[400px] sm:max-h-[420px] lg:max-h-[440px]">
|
||||
{renderChart()}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Description section */}
|
||||
<div className="flex-shrink-0 h-20 sm:h-24 flex items-center justify-center">
|
||||
<p
|
||||
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'This analysis reveals key performance trends across quarterly periods, identifying growth patterns and areas requiring strategic focus for sustained business success.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default SingleChartSlideLayout
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'text-icon-list-slide'
|
||||
export const layoutName = 'Text + Icon List Slide'
|
||||
export const layoutDescription = 'A layout for displaying informational content like features, checklists, and steps with icons and descriptions.'
|
||||
|
||||
const listItemSchema = z.object({
|
||||
icon: z.string().min(1).max(10).meta({ description: "Icon (emoji or simple text)" }),
|
||||
heading: z.string().min(2).max(60).meta({ description: "Item heading" }),
|
||||
description: z.string().min(10).max(200).meta({ description: "Item description" }),
|
||||
status: z.enum(['default', 'completed', 'important', 'warning']).default('default').meta({ description: "Item status for styling" }),
|
||||
});
|
||||
|
||||
const textIconListSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Features & Benefits').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(300).default('These essential features and capabilities provide comprehensive solutions to meet your business objectives and drive operational excellence.').meta({
|
||||
description: "Bottom description text",
|
||||
}),
|
||||
items: z.array(listItemSchema).min(2).max(8).default([
|
||||
{
|
||||
icon: '🚀',
|
||||
heading: 'Fast Performance',
|
||||
description: 'Optimized for speed with advanced caching and efficient algorithms to deliver exceptional user experience.',
|
||||
status: 'important'
|
||||
},
|
||||
{
|
||||
icon: '🔒',
|
||||
heading: 'Enterprise Security',
|
||||
description: 'Bank-level security with end-to-end encryption and compliance with industry standards.',
|
||||
status: 'completed'
|
||||
},
|
||||
{
|
||||
icon: '📊',
|
||||
heading: 'Advanced Analytics',
|
||||
description: 'Real-time insights and comprehensive reporting to track performance and make data-driven decisions.',
|
||||
status: 'default'
|
||||
},
|
||||
{
|
||||
icon: '🔧',
|
||||
heading: 'Easy Integration',
|
||||
description: 'Seamless integration with existing systems through RESTful APIs and pre-built connectors.',
|
||||
status: 'default'
|
||||
},
|
||||
{
|
||||
icon: '📱',
|
||||
heading: 'Mobile Responsive',
|
||||
description: 'Fully responsive design that works flawlessly across all devices and screen sizes.',
|
||||
status: 'default'
|
||||
},
|
||||
{
|
||||
icon: '⚡',
|
||||
heading: '24/7 Support',
|
||||
description: 'Round-the-clock technical support and dedicated account management for enterprise clients.',
|
||||
status: 'important'
|
||||
},
|
||||
]).meta({
|
||||
description: "List of items with icons (2-8 items)",
|
||||
}),
|
||||
layout: z.enum(['grid', 'single-column']).default('grid').meta({
|
||||
description: "Layout style - grid for 2 columns, single-column for vertical list",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = textIconListSlideSchema
|
||||
|
||||
export type TextIconListSlideData = z.infer<typeof textIconListSlideSchema>
|
||||
|
||||
interface TextIconListSlideLayoutProps {
|
||||
data?: Partial<TextIconListSlideData>
|
||||
}
|
||||
|
||||
const TextIconListSlideLayout: React.FC<TextIconListSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || [];
|
||||
const layout = slideData?.layout || 'grid';
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'text-green-600';
|
||||
case 'important':
|
||||
return 'text-blue-600';
|
||||
case 'warning':
|
||||
return 'text-amber-600';
|
||||
default:
|
||||
return 'text-gray-900';
|
||||
}
|
||||
};
|
||||
|
||||
const getBackgroundColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'bg-green-50 border-green-200';
|
||||
case 'important':
|
||||
return 'bg-blue-50 border-blue-200';
|
||||
case 'warning':
|
||||
return 'bg-amber-50 border-amber-200';
|
||||
default:
|
||||
return 'bg-white/30 border-slate-200';
|
||||
}
|
||||
};
|
||||
|
||||
const getGridLayout = () => {
|
||||
if (layout === 'single-column') {
|
||||
return 'grid-cols-1';
|
||||
}
|
||||
return 'grid-cols-1 md:grid-cols-2';
|
||||
};
|
||||
|
||||
const renderSVGIcon = (iconText: string) => {
|
||||
// If it's an emoji, return as is
|
||||
|
||||
|
||||
// For non-emoji, create a simple circle with text
|
||||
return (
|
||||
<div className="w-8 h-8 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center text-sm font-bold">
|
||||
{iconText.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-stone-100 relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-12 sm:h-16 flex items-center justify-center">
|
||||
<h1
|
||||
className="text-3xl font-bold text-gray-900 leading-tight text-center"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Key Features & Benefits'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Items Grid section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="w-full max-w-5xl">
|
||||
<div className={`grid ${getGridLayout()} gap-6 content-center`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`backdrop-blur-sm rounded-xl border shadow-md p-4 sm:p-6 ${getBackgroundColor(item.status)}`}
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
{/* Icon */}
|
||||
<div className="flex-shrink-0 mt-1">
|
||||
{renderSVGIcon(item.icon)}
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1">
|
||||
{/* Heading */}
|
||||
<h3
|
||||
className={`text-lg font-semibold leading-tight mb-2 ${getStatusColor(item.status)}`}
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{item.heading}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p
|
||||
className="text-sm text-gray-500 leading-relaxed"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Description section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
|
||||
<p
|
||||
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'These essential features and capabilities provide comprehensive solutions to meet your business objectives and drive operational excellence.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TextIconListSlideLayout
|
||||
|
|
@ -1,254 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'timeline-trend-slide'
|
||||
export const layoutName = 'Timeline / Trend Slide'
|
||||
export const layoutDescription = 'A layout for displaying time-based milestones with connecting lines and responsive timeline visualization.'
|
||||
|
||||
const timelineItemSchema = z.object({
|
||||
title: z.string().min(2).max(50).meta({ description: "Milestone title" }),
|
||||
date: z.string().min(2).max(30).meta({ description: "Date or time period" }),
|
||||
status: z.enum(['completed', 'current', 'upcoming', 'delayed']).default('upcoming').meta({ description: "Milestone status" }),
|
||||
icon: z.string().optional().meta({ description: "Icon identifier (optional)" }),
|
||||
description: z.string().optional().meta({ description: "Optional description" }),
|
||||
});
|
||||
|
||||
const timelineTrendSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Project Timeline & Milestones').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(300).default('This timeline tracks key project milestones and deliverables, providing a clear view of progress and upcoming objectives across the project lifecycle.').meta({
|
||||
description: "Bottom description text",
|
||||
}),
|
||||
timeline: z.array(timelineItemSchema).min(3).max(8).default([
|
||||
{
|
||||
title: 'Project Kickoff',
|
||||
date: 'Jan 2024',
|
||||
status: 'completed',
|
||||
description: 'Initial planning and team setup'
|
||||
},
|
||||
{
|
||||
title: 'Research Phase',
|
||||
date: 'Feb 2024',
|
||||
status: 'completed',
|
||||
description: 'Market research and requirements gathering'
|
||||
},
|
||||
{
|
||||
title: 'Design Phase',
|
||||
date: 'Mar 2024',
|
||||
status: 'completed',
|
||||
description: 'UI/UX design and prototyping'
|
||||
},
|
||||
{
|
||||
title: 'Development',
|
||||
date: 'Apr-Jun 2024',
|
||||
status: 'current',
|
||||
description: 'Core development and testing'
|
||||
},
|
||||
{
|
||||
title: 'Beta Testing',
|
||||
date: 'Jul 2024',
|
||||
status: 'upcoming',
|
||||
description: 'User testing and feedback collection'
|
||||
},
|
||||
{
|
||||
title: 'Launch',
|
||||
date: 'Aug 2024',
|
||||
status: 'upcoming',
|
||||
description: 'Product launch and go-to-market'
|
||||
},
|
||||
]).meta({
|
||||
description: "Timeline milestone items (3-8 items)",
|
||||
}),
|
||||
showConnectingLines: z.boolean().default(true).meta({
|
||||
description: "Whether to show connecting lines between milestones",
|
||||
}),
|
||||
lineStyle: z.enum(['solid', 'dotted', 'dashed']).default('solid').meta({
|
||||
description: "Style of connecting lines",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = timelineTrendSlideSchema
|
||||
|
||||
export type TimelineTrendSlideData = z.infer<typeof timelineTrendSlideSchema>
|
||||
|
||||
interface TimelineTrendSlideLayoutProps {
|
||||
data?: Partial<TimelineTrendSlideData>
|
||||
}
|
||||
|
||||
const TimelineTrendSlideLayout: React.FC<TimelineTrendSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const timeline = slideData?.timeline || [];
|
||||
const showConnectingLines = slideData?.showConnectingLines ?? true;
|
||||
const lineStyle = slideData?.lineStyle || 'solid';
|
||||
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'bg-green-500 border-green-500';
|
||||
case 'current':
|
||||
return 'bg-blue-500 border-blue-500';
|
||||
case 'upcoming':
|
||||
return 'bg-gray-300 border-gray-300';
|
||||
case 'delayed':
|
||||
return 'bg-red-500 border-red-500';
|
||||
default:
|
||||
return 'bg-gray-300 border-gray-300';
|
||||
}
|
||||
};
|
||||
|
||||
const getTextColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return 'text-green-600';
|
||||
case 'current':
|
||||
return 'text-blue-600';
|
||||
case 'upcoming':
|
||||
return 'text-gray-600';
|
||||
case 'delayed':
|
||||
return 'text-red-600';
|
||||
default:
|
||||
return 'text-gray-600';
|
||||
}
|
||||
};
|
||||
|
||||
const getLineStyle = () => {
|
||||
switch (lineStyle) {
|
||||
case 'dotted':
|
||||
return 'border-dotted';
|
||||
case 'dashed':
|
||||
return 'border-dashed';
|
||||
default:
|
||||
return 'border-solid';
|
||||
}
|
||||
};
|
||||
|
||||
const renderIcon = (status: string) => {
|
||||
switch (status) {
|
||||
case 'completed':
|
||||
return (
|
||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
case 'current':
|
||||
return (
|
||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.415L11 9.586V6z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
case 'delayed':
|
||||
return (
|
||||
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-white relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-12 sm:h-16 flex items-center justify-center">
|
||||
<h1
|
||||
className="text-3xl font-bold text-gray-900 leading-tight text-center"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Project Timeline & Milestones'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Timeline section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="w-full max-w-6xl">
|
||||
{/* Timeline container */}
|
||||
<div className="relative">
|
||||
{/* Timeline items */}
|
||||
<div className="flex justify-between items-center relative">
|
||||
{timeline.map((item, index) => (
|
||||
<div key={index} className="flex flex-col items-center relative z-10">
|
||||
{/* Circle/Icon */}
|
||||
<div className={`w-12 h-12 rounded-full border-4 ${getStatusColor(item.status)} flex items-center justify-center shadow-lg bg-white/10 backdrop-blur-sm`}>
|
||||
{renderIcon(item.status)}
|
||||
</div>
|
||||
|
||||
{/* Label */}
|
||||
<div className="mt-3 text-center max-w-20">
|
||||
<div
|
||||
className={`text-xs font-semibold ${getTextColor(item.status)} mb-1`}
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{item.title}
|
||||
</div>
|
||||
<div
|
||||
className="text-xs text-gray-600"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{item.date}
|
||||
</div>
|
||||
{item.description && (
|
||||
<div
|
||||
className="text-xs text-gray-500 mt-1 leading-tight"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{item.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* Connecting line */}
|
||||
{showConnectingLines && (
|
||||
<div className={`absolute top-6 left-6 right-6 h-0 border-t-2 border-gray-400 ${getLineStyle()} -z-10`} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Description section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
|
||||
<p
|
||||
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'This timeline tracks key project milestones and deliverables, providing a clear view of progress and upcoming objectives across the project lifecycle.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineTrendSlideLayout
|
||||
|
|
@ -1,297 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ChartContainer, ChartTooltip, ChartTooltipContent, ChartLegend, ChartLegendContent } from "@/components/ui/chart";
|
||||
import { BarChart, Bar, LineChart, Line, PieChart, Pie, AreaChart, Area, ScatterChart, Scatter, XAxis, YAxis, CartesianGrid, Cell, ResponsiveContainer } from "recharts";
|
||||
|
||||
export const layoutId = 'two-charts-slide'
|
||||
export const layoutName = 'Two Charts Slide'
|
||||
export const layoutDescription = 'A layout for comparing two data visualizations side-by-side (e.g., Plan vs Actual).'
|
||||
|
||||
const chartDataSchema = z.object({
|
||||
name: z.string().meta({ description: "Data point name" }),
|
||||
value: z.number().meta({ description: "Data point value" }),
|
||||
category: z.string().optional().meta({ description: "Category for grouping" }),
|
||||
x: z.number().optional().meta({ description: "X coordinate for scatter plots" }),
|
||||
y: z.number().optional().meta({ description: "Y coordinate for scatter plots" }),
|
||||
});
|
||||
|
||||
const twoChartsSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Plan vs Actual Comparison').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(300).default('This comparison shows the variance between planned targets and actual performance across quarterly metrics, highlighting areas of success and opportunities for improvement.').meta({
|
||||
description: "Bottom description text",
|
||||
}),
|
||||
// Chart A (Left)
|
||||
chartAType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('bar').meta({
|
||||
description: "Type of chart A to display",
|
||||
}),
|
||||
chartAData: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: 'Q1', value: 4000 },
|
||||
{ name: 'Q2', value: 3500 },
|
||||
{ name: 'Q3', value: 5000 },
|
||||
{ name: 'Q4', value: 4500 },
|
||||
]).meta({
|
||||
description: "Chart A data points",
|
||||
}),
|
||||
chartATitle: z.string().min(2).max(50).default('Planned Revenue').meta({
|
||||
description: "Title/caption for chart A",
|
||||
}),
|
||||
chartAColor: z.string().default('#3b82f6').meta({
|
||||
description: "Primary color for chart A elements",
|
||||
}),
|
||||
// Chart B (Right)
|
||||
chartBType: z.enum(['bar', 'line', 'pie', 'area', 'scatter']).default('bar').meta({
|
||||
description: "Type of chart B to display",
|
||||
}),
|
||||
chartBData: z.array(chartDataSchema).min(2).max(10).default([
|
||||
{ name: 'Q1', value: 3800 },
|
||||
{ name: 'Q2', value: 3200 },
|
||||
{ name: 'Q3', value: 5200 },
|
||||
{ name: 'Q4', value: 4800 },
|
||||
]).meta({
|
||||
description: "Chart B data points",
|
||||
}),
|
||||
chartBTitle: z.string().min(2).max(50).default('Actual Revenue').meta({
|
||||
description: "Title/caption for chart B",
|
||||
}),
|
||||
chartBColor: z.string().default('#10b981').meta({
|
||||
description: "Primary color for chart B elements",
|
||||
}),
|
||||
// Common settings
|
||||
dataKey: z.string().default('value').meta({
|
||||
description: "Key field for chart values",
|
||||
}),
|
||||
categoryKey: z.string().default('name').meta({
|
||||
description: "Key field for chart categories",
|
||||
}),
|
||||
showLegend: z.boolean().default(false).meta({
|
||||
description: "Whether to show chart legends",
|
||||
}),
|
||||
showTooltip: z.boolean().default(true).meta({
|
||||
description: "Whether to show chart tooltips",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = twoChartsSlideSchema
|
||||
|
||||
export type TwoChartsSlideData = z.infer<typeof twoChartsSlideSchema>
|
||||
|
||||
interface TwoChartsSlideLayoutProps {
|
||||
data?: Partial<TwoChartsSlideData>
|
||||
}
|
||||
|
||||
const chartConfig = {
|
||||
value: {
|
||||
label: "Value",
|
||||
},
|
||||
name: {
|
||||
label: "Name",
|
||||
},
|
||||
};
|
||||
|
||||
const CHART_COLORS = [
|
||||
'#3b82f6', '#ef4444', '#10b981', '#f59e0b', '#8b5cf6',
|
||||
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
|
||||
];
|
||||
|
||||
const TwoChartsSlideLayout: React.FC<TwoChartsSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const chartAData = slideData?.chartAData || [];
|
||||
const chartBData = slideData?.chartBData || [];
|
||||
const chartAType = slideData?.chartAType || 'bar';
|
||||
const chartBType = slideData?.chartBType || 'bar';
|
||||
const chartAColor = slideData?.chartAColor || '#3b82f6';
|
||||
const chartBColor = slideData?.chartBColor || '#10b981';
|
||||
const dataKey = slideData?.dataKey || 'value';
|
||||
const categoryKey = slideData?.categoryKey || 'name';
|
||||
const showLegend = slideData?.showLegend || false;
|
||||
const showTooltip = slideData?.showTooltip || true;
|
||||
|
||||
const renderChart = (chartType: string, data: any[], color: string) => {
|
||||
const commonProps = {
|
||||
data: data,
|
||||
margin: { top: 15, right: 15, left: 15, bottom: 35 },
|
||||
};
|
||||
|
||||
switch (chartType) {
|
||||
case 'bar':
|
||||
return (
|
||||
<BarChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Bar dataKey={dataKey} fill={color} radius={[4, 4, 0, 0]} />
|
||||
</BarChart>
|
||||
);
|
||||
|
||||
case 'line':
|
||||
return (
|
||||
<LineChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
strokeWidth={3}
|
||||
dot={{ fill: color, strokeWidth: 2, r: 4 }}
|
||||
/>
|
||||
</LineChart>
|
||||
);
|
||||
|
||||
case 'area':
|
||||
return (
|
||||
<AreaChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey={categoryKey} />
|
||||
<YAxis />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
fillOpacity={0.6}
|
||||
/>
|
||||
</AreaChart>
|
||||
);
|
||||
|
||||
case 'pie':
|
||||
return (
|
||||
<PieChart margin={{ top: 15, right: 15, left: 15, bottom: 35 }}>
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Pie
|
||||
data={data}
|
||||
cx="50%"
|
||||
cy="40%"
|
||||
outerRadius={50}
|
||||
fill={color}
|
||||
dataKey={dataKey}
|
||||
label={({ name, percent }) => `${name} ${(percent * 100).toFixed(0)}%`}
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={CHART_COLORS[index % CHART_COLORS.length]} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
);
|
||||
|
||||
case 'scatter':
|
||||
return (
|
||||
<ScatterChart {...commonProps}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="x" type="number" />
|
||||
<YAxis dataKey="y" type="number" />
|
||||
{showTooltip && <ChartTooltip content={<ChartTooltipContent />} />}
|
||||
{showLegend && <ChartLegend content={<ChartLegendContent />} />}
|
||||
<Scatter dataKey="value" fill={color} />
|
||||
</ScatterChart>
|
||||
);
|
||||
|
||||
default:
|
||||
return <div>Unsupported chart type</div>;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Import Google Fonts */}
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<div
|
||||
className="w-full rounded-sm max-w-[1280px] shadow-md h-[720px] flex flex-col aspect-video bg-white relative z-20 mx-auto overflow-hidden"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{/* Glass overlay background */}
|
||||
<div className="absolute inset-0 bg-white/20 backdrop-blur-sm rounded-sm border border-slate-200"></div>
|
||||
|
||||
<div className="relative z-10 flex flex-col w-full h-full p-4 sm:p-6 lg:p-8">
|
||||
{/* Header section */}
|
||||
<div className="flex-shrink-0 h-16 sm:h-20 flex items-center justify-center">
|
||||
<h1
|
||||
className="text-3xl sm:text-3xl lg:text-4xl font-bold text-gray-900 leading-tight text-center"
|
||||
style={{
|
||||
fontFamily: 'Space Grotesk, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.title || 'Plan vs Actual Comparison'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{/* Two Charts section */}
|
||||
<div className="flex-1 w-full overflow-hidden">
|
||||
<div className="flex gap-6 h-full items-center">
|
||||
{/* Chart A - Left side */}
|
||||
<div className="w-1/2 h-full">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 h-full overflow-hidden flex flex-col">
|
||||
<div className="flex-1">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full max-h-[360px] sm:max-h-[380px] lg:max-h-[400px]">
|
||||
{renderChart(chartAType, chartAData, chartAColor)}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<p
|
||||
className="text-sm text-center text-gray-700 font-medium"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.chartATitle || 'Planned Revenue'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chart B - Right side */}
|
||||
<div className="w-1/2 h-full">
|
||||
<div className="bg-white/30 backdrop-blur-sm rounded-xl border border-slate-200 shadow-md p-4 sm:p-6 h-full overflow-hidden flex flex-col">
|
||||
<div className="flex-1">
|
||||
<ChartContainer config={chartConfig} className="h-full w-full max-h-[360px] sm:max-h-[380px] lg:max-h-[400px]">
|
||||
{renderChart(chartBType, chartBData, chartBColor)}
|
||||
</ChartContainer>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
<p
|
||||
className="text-sm text-center text-gray-700 font-medium"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.chartBTitle || 'Actual Revenue'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Description section */}
|
||||
<div className="flex-shrink-0 h-20 sm:h-24 flex items-center justify-center">
|
||||
<p
|
||||
className="text-sm sm:text-base text-center text-gray-700 leading-relaxed max-w-4xl px-4"
|
||||
style={{
|
||||
fontFamily: 'Nunito, sans-serif'
|
||||
}}
|
||||
>
|
||||
{slideData?.description || 'This comparison shows the variance between planned targets and actual performance across quarterly metrics, highlighting areas of success and opportunities for improvement.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TwoChartsSlideLayout
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
|
||||
"description": "Data-focused layouts with glass visual style and modern typography",
|
||||
"ordered": true,
|
||||
"isDefault": false
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type1-slide'
|
||||
export const layoutName = 'Type1 Slide'
|
||||
export const layoutDescription = 'A clean two-column layout with title and description on the left and a featured image on the right.'
|
||||
|
||||
const type1SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Hot NOT Reload Working!').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('This is a test of the hot reload system! If you can see this text, hot reload is working perfectly. Changes should appear instantly without page refresh.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__image_prompt__: 'A beautiful road in the mountains'
|
||||
}).meta({
|
||||
description: "Main slide image",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type1SlideSchema
|
||||
|
||||
export type Type1SlideData = z.infer<typeof type1SlideSchema>
|
||||
|
||||
interface Type1SlideLayoutProps {
|
||||
data?: Partial<Type1SlideData>
|
||||
}
|
||||
|
||||
const Type1SlideLayout: React.FC<Type1SlideLayoutProps> = ({ data: slideData }) => {
|
||||
return (
|
||||
<div
|
||||
className=" w-full rounded-sm max-w-[1280px] shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] max-h-[720px] flex items-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3 sm:gap-8 md:gap-12 lg:gap-16 w-full">
|
||||
<div className="flex flex-col w-full items-start justify-center space-y-1 md:space-y-2 lg:space-y-6">
|
||||
{/* Title */}
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || ' This is the title of slide'}
|
||||
</h1>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'This is a test of the hot reload system! If you can see this text, hot reload is working perfectly. Changes should appear instantly without page refresh.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Image */}
|
||||
<div className="w-full h-full min-h-[200px] lg:min-h-[300px]">
|
||||
<img
|
||||
src={slideData?.image?.__image_url__ || ''}
|
||||
alt={slideData?.image?.__image_prompt__ || slideData?.title || ''}
|
||||
className="w-full h-full object-cover rounded-lg shadow-md"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type1SlideLayout
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-numbered-slide'
|
||||
export const layoutName = 'Type2 Numbered Slide'
|
||||
export const layoutDescription = 'A content layout with title and numbered content items with large numerals and shadow boxes.'
|
||||
|
||||
const type2NumberedSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2NumberedSlideSchema
|
||||
|
||||
export type Type2NumberedSlideData = z.infer<typeof type2NumberedSlideSchema>
|
||||
|
||||
interface Type2NumberedSlideLayoutProps {
|
||||
data?: Partial<Type2NumberedSlideData>
|
||||
}
|
||||
|
||||
const Type2NumberedSlideLayout: React.FC<Type2NumberedSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 bg-white lg:grid-cols-2 relative gap-4 lg:gap-8 mt-4 lg:mt-12">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full relative shadow-lg rounded-lg p-3 lg:p-6"
|
||||
>
|
||||
<div className="flex gap-3">
|
||||
<div className="text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row bg-white mt-4 lg:mt-12 w-full relative gap-4 lg:gap-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full relative shadow-lg rounded-lg p-3 lg:p-6"
|
||||
>
|
||||
<div className="text-[32px] leading-[40px] font-semibold lg:mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-2 lg:space-y-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Main Title'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2NumberedSlideLayout
|
||||
|
|
@ -1,111 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-slide'
|
||||
export const layoutName = 'Type2 Slide'
|
||||
export const layoutDescription = 'A flexible content layout with title and multiple content items in default presentation style.'
|
||||
|
||||
const type2SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2SlideSchema
|
||||
|
||||
export type Type2SlideData = z.infer<typeof type2SlideSchema>
|
||||
|
||||
interface Type2SlideLayoutProps {
|
||||
data?: Partial<Type2SlideData>
|
||||
}
|
||||
|
||||
const Type2SlideLayout: React.FC<Type2SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 relative gap-6 md:gap-12 mt-4 lg:mt-12">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="w-full relative p-3 lg:p-6 rounded-md"
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className="flex flex-col lg:flex-row mt-4 lg:mt-12 w-full relative gap-12">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="w-full relative p-3 lg:p-6 rounded-md"
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
>
|
||||
<div className="space-y-2 lg:space-y-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Main Title'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2SlideLayout
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-timeline-slide'
|
||||
export const layoutName = 'Type2 Timeline Slide'
|
||||
export const layoutDescription = 'A timeline layout with title and content items arranged horizontally with numbered circles and connecting line.'
|
||||
|
||||
const type2TimelineSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2TimelineSlideSchema
|
||||
|
||||
export type Type2TimelineSlideData = z.infer<typeof type2TimelineSlideSchema>
|
||||
|
||||
interface Type2TimelineSlideLayoutProps {
|
||||
data?: Partial<Type2TimelineSlideData>
|
||||
}
|
||||
|
||||
const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
const renderTimelineContent = () => {
|
||||
return (
|
||||
<div className="w-full flex flex-col relative mt-4 lg:mt-16">
|
||||
{/* Timeline Header with Numbers and Line */}
|
||||
<div className="relative flex justify-between w-[85%] mx-auto items-center mb-8 px-8">
|
||||
{/* Horizontal Line */}
|
||||
<div className="absolute z-10 top-1/2 w-[87%] left-1/2 -translate-x-1/2 h-[2px] bg-blue-600" />
|
||||
|
||||
{/* Timeline Numbers */}
|
||||
{items.map((_, index) => (
|
||||
<div
|
||||
key={`timeline-${index}`}
|
||||
className="relative z-10 w-12 h-12 rounded-full bg-blue-600 px-1 text-white flex items-center justify-center font-bold text-lg"
|
||||
>
|
||||
<span>{numberTranslations[index] || `0${index + 1}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Timeline Content */}
|
||||
<div className="flex justify-between gap-8">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="flex-1 text-center relative">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Main Title'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{renderTimelineContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2TimelineSlideLayout
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type3-slide'
|
||||
export const layoutName = 'Type3 Slide'
|
||||
export const layoutDescription = 'A centered title with a grid of image cards, each containing a heading and description.'
|
||||
|
||||
const type3SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Featured Content').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
image: ImageSchema.meta({
|
||||
description: "Item image",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Feature',
|
||||
description: 'Description for the first featured item with detailed information',
|
||||
image: {
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__image_prompt__: 'A beautiful road in the mountains'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Second Feature',
|
||||
description: 'Description for the second featured item with relevant details',
|
||||
image: {
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__image_prompt__: 'Modern office workspace'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Third Feature',
|
||||
description: 'Description for the third featured item with important points',
|
||||
image: {
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__image_prompt__: 'Laptop with code on screen'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of featured items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type3SlideSchema
|
||||
|
||||
export type Type3SlideData = z.infer<typeof type3SlideSchema>
|
||||
|
||||
interface Type3SlideLayoutProps {
|
||||
data?: Partial<Type3SlideData>
|
||||
}
|
||||
|
||||
const Type3SlideLayout: React.FC<Type3SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" shadow-lg rounded-sm w-full max-w-[1280px] px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] font-inter flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center mb-4 lg:mb-16 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Featured Content'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div className={`grid grid-cols-1 lg:grid-cols-2 ${getGridCols(items.length)} gap-3 lg:gap-6 w-full`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="flex flex-col w-full rounded-lg overflow-hidden relative"
|
||||
>
|
||||
{/* Image */}
|
||||
<div className="max-md:h-[140px] max-lg:h-[180px] h-48 w-full">
|
||||
<img
|
||||
src={item.image?.__image_url__ || ''}
|
||||
alt={item.image?.__image_prompt__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="space-y-2 p-3 lg:p-6">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type3SlideLayout
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type4-slide'
|
||||
export const layoutName = 'Type4 Slide'
|
||||
export const layoutDescription = 'A chart-focused layout with title, chart visualization, and description text.'
|
||||
|
||||
const type4SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Chart Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('This chart shows important data trends and insights that help understand the current situation and make informed decisions.').meta({
|
||||
description: "Description text for the chart",
|
||||
}),
|
||||
chartData: z.any().optional().meta({
|
||||
description: "Chart data object",
|
||||
}),
|
||||
isFullSizeChart: z.boolean().default(false).meta({
|
||||
description: "Whether to display chart in full size mode",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type4SlideSchema
|
||||
|
||||
export type Type4SlideData = z.infer<typeof type4SlideSchema>
|
||||
|
||||
interface Type4SlideLayoutProps {
|
||||
data?: Partial<Type4SlideData>
|
||||
}
|
||||
|
||||
const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const isFullSizeGraph = slideData?.isFullSizeChart || false
|
||||
|
||||
// Simple placeholder chart component
|
||||
const ChartPlaceholder = () => (
|
||||
<div className="w-full h-64 lg:h-80 bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg border-2 border-blue-200 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-blue-600 text-4xl mb-4">📊</div>
|
||||
<p className="text-blue-700 font-semibold">Chart Component</p>
|
||||
<p className="text-blue-600 text-sm mt-1">Data visualization will appear here</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] px-3 py-[10px] sm:px-12 lg:px-20 sm:py-[40px] lg:py-[86px] shadow-lg max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight mb-4 lg:mb-8">
|
||||
{slideData?.title || 'Chart Analysis'}
|
||||
</h1>
|
||||
|
||||
<div className={`flex w-full items-center ${isFullSizeGraph
|
||||
? "flex-col mt-4 lg:mt-10 gap-2 sm:gap-4 md:gap-6 lg:gap-10"
|
||||
: "mt-4 lg:mt-16 gap-4 sm:gap-8 md:gap-12 lg:gap-16"
|
||||
}`}>
|
||||
<div className="w-full">
|
||||
<ChartPlaceholder />
|
||||
</div>
|
||||
<div className="w-full text-center">
|
||||
<p className={`text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed ${isFullSizeGraph ? 'text-center' : ''}`}>
|
||||
{slideData?.description || 'This chart shows important data trends and insights that help understand the current situation and make informed decisions.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type4SlideLayout
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type5-slide'
|
||||
export const layoutName = 'Type5 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and numbered items with large numerals on the right.'
|
||||
|
||||
const type5SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Points').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Here is the main description that provides context and introduction to the numbered points on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Key Point',
|
||||
description: 'Detailed explanation of the first important point that supports the main topic'
|
||||
},
|
||||
{
|
||||
heading: 'Second Key Point',
|
||||
description: 'Detailed explanation of the second important point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Key Point',
|
||||
description: 'Detailed explanation of the third important point that concludes the discussion'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of numbered items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type5SlideSchema
|
||||
|
||||
export type Type5SlideData = z.infer<typeof type5SlideSchema>
|
||||
|
||||
interface Type5SlideLayoutProps {
|
||||
data?: Partial<Type5SlideData>
|
||||
}
|
||||
|
||||
const Type5SlideLayout: React.FC<Type5SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-4 sm:gap-18 md:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="lg:w-1/2 lg:space-y-8 ">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Key Points'}
|
||||
</h1>
|
||||
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Here is the main description that provides context and introduction to the numbered points on the right side.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right section - Numbered items */}
|
||||
<div className="lg:w-1/2 relative">
|
||||
<div className="space-y-3 lg:space-y-6">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex gap-6">
|
||||
<div className="text-[26px] lg:text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type5SlideLayout
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type6-slide'
|
||||
export const layoutName = 'Type6 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type6SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema,
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Professional Service'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Expert Consultation'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Quality Assurance'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Customer Support'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type6SlideSchema
|
||||
|
||||
export type Type6SlideData = z.infer<typeof type6SlideSchema>
|
||||
|
||||
interface Type6SlideLayoutProps {
|
||||
data?: Partial<Type6SlideData>
|
||||
}
|
||||
|
||||
const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items.length > 4 ? 'md:grid-cols-3' : 'md:grid-cols-2'} gap-4 sm:gap-6 lg:gap-8 mt-4 lg:mt-12 w-full`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 mg:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center text-white text-xl lg:text-2xl">
|
||||
<img src={item.icon.__icon_url__} className='w-full h-full object-contain' alt={item.icon.__icon_query__} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight mb-2">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items.length)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center text-white text-2xl lg:text-3xl mx-auto mb-4">
|
||||
<img src={item.icon.__icon_url__} className='w-full h-full object-contain' alt={item.icon.__icon_query__} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-4 mt-2 lg:mt-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Our Services'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type6SlideLayout
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type7-slide'
|
||||
export const layoutName = 'Type7 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type7SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Professional service icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__icon_query__: 'Expert consultation icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__icon_query__: 'Quality assurance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Customer support icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type7SlideSchema
|
||||
|
||||
export type Type7SlideData = z.infer<typeof type7SlideSchema>
|
||||
|
||||
interface Type7SlideLayoutProps {
|
||||
data?: Partial<Type7SlideData>
|
||||
}
|
||||
|
||||
const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items.length > 4 ? 'md:grid-cols-3' : 'md:grid-cols-2'} gap-4 sm:gap-6 lg:gap-8 mt-4 lg:mt-12 w-full`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 mg:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight mb-2">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items.length)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-4 mt-2 lg:mt-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Our Services'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type7SlideLayout
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type8-slide'
|
||||
export const layoutName = 'Type8 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and icon-based items on the right.'
|
||||
|
||||
const type8SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Here is the main description that provides context and introduces the key features outlined on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'Advanced Features',
|
||||
description: 'Cutting-edge functionality designed to enhance productivity and user experience',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Advanced features icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Reliable Performance',
|
||||
description: 'Consistent and dependable performance across all platforms and devices',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__icon_query__: 'Reliable performance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Secure Environment',
|
||||
description: 'Enterprise-grade security measures to protect your data and privacy',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__icon_query__: 'Secure environment icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of featured items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type8SlideSchema
|
||||
|
||||
export type Type8SlideData = z.infer<typeof type8SlideSchema>
|
||||
|
||||
interface Type8SlideLayoutProps {
|
||||
data?: Partial<Type8SlideData>
|
||||
}
|
||||
|
||||
const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
|
||||
const renderItems = () => {
|
||||
if (items.length === 2) {
|
||||
// Vertical stacked layout for 2 items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 lg:space-y-3">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
// Horizontal layout with side icons for 3+ items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-[32px] md:w-[64px] h-[32px] md:h-[64px]">
|
||||
<div className="w-full h-full bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-3">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" shadow-lg w-full max-w-[1280px] rounded-sm font-inter px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-8 lg:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="space-y-2 lg:space-y-6">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Key Features'}
|
||||
</h1>
|
||||
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Here is the main description that provides context and introduces the key features outlined on the right side.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right section - Items */}
|
||||
<div className="relative">
|
||||
{renderItems()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type8SlideLayout
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"description": "Default layout for presentations",
|
||||
"ordered": false,
|
||||
"isDefault": true
|
||||
}
|
||||
|
|
@ -1,141 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'card-slide'
|
||||
export const layoutName = 'Card Slide'
|
||||
export const layoutDescription = 'A professional slide featuring feature cards with icons, titles, and descriptions.'
|
||||
|
||||
const cardSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
cards: z.array(z.object({
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Default card icon'
|
||||
}).meta({
|
||||
description: "Icon for the card",
|
||||
}),
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for the card",
|
||||
}),
|
||||
description: z.string().min(10).max(150).meta({
|
||||
description: "Description of the feature",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Lightning fast icon'
|
||||
},
|
||||
title: 'Lightning Fast',
|
||||
description: 'Optimized performance for quick results and seamless user experience'
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__icon_query__: 'Secure and safe icon'
|
||||
},
|
||||
title: 'Secure & Safe',
|
||||
description: 'Enterprise-grade security with advanced encryption and protection'
|
||||
},
|
||||
{
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__icon_query__: 'Precise targeting icon'
|
||||
},
|
||||
title: 'Precise Targeting',
|
||||
description: 'Advanced analytics to reach your exact audience with precision'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of feature cards (2-6 items)",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = cardSlideSchema
|
||||
|
||||
export type CardSlideData = z.infer<typeof cardSlideSchema>
|
||||
|
||||
interface CardSlideLayoutProps {
|
||||
data?: Partial<CardSlideData>
|
||||
}
|
||||
|
||||
const CardSlideLayout: React.FC<CardSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-8 print:px-8 print:py-6 relative z-10">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Our Features'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Cards section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6 relative z-10">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 h-full items-center">
|
||||
{slideData?.cards?.map((card, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative bg-white/95 backdrop-blur-sm rounded-2xl p-6 shadow-2xl border border-white/50 text-center group hover:transform hover:scale-105 transition-all duration-300 print:shadow-md print:p-4"
|
||||
>
|
||||
{/* Card accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
|
||||
{/* Icon */}
|
||||
<div className="mb-4 group-hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-16 h-16 mx-auto bg-blue-100 rounded-xl flex items-center justify-center overflow-hidden print:w-12 print:h-12">
|
||||
<img
|
||||
src={card.icon?.__icon_url__ || ''}
|
||||
alt={card.icon?.__icon_query__ || card.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h3 className="text-xl font-bold text-blue-600 mb-3 leading-tight print:text-lg">
|
||||
{card.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{card.description}
|
||||
</p>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className="absolute bottom-0 right-0 w-16 h-16 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CardSlideLayout
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'comparison-slide'
|
||||
export const layoutName = 'Comparison Slide'
|
||||
export const layoutDescription = 'A professional slide for comparing features, options, or before/after scenarios.'
|
||||
|
||||
const comparisonSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Feature Comparison').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
leftColumn: z.object({
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for left column",
|
||||
}),
|
||||
icon: z.string().default('📊').meta({
|
||||
description: "Icon for left column",
|
||||
}),
|
||||
items: z.array(z.string().min(5).max(100)).min(2).max(8).meta({
|
||||
description: "List of items for left column",
|
||||
})
|
||||
}).default({
|
||||
title: 'Before',
|
||||
icon: '📊',
|
||||
items: [
|
||||
'Manual processes and workflows',
|
||||
'Limited scalability options',
|
||||
'Disconnected systems',
|
||||
'Time-consuming operations'
|
||||
]
|
||||
}).meta({
|
||||
description: "Left comparison column",
|
||||
}),
|
||||
rightColumn: z.object({
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for right column",
|
||||
}),
|
||||
icon: z.string().default('🚀').meta({
|
||||
description: "Icon for right column",
|
||||
}),
|
||||
items: z.array(z.string().min(5).max(100)).min(2).max(8).meta({
|
||||
description: "List of items for right column",
|
||||
})
|
||||
}).default({
|
||||
title: 'After',
|
||||
icon: '🚀',
|
||||
items: [
|
||||
'Automated intelligent workflows',
|
||||
'Unlimited scaling capabilities',
|
||||
'Seamlessly integrated ecosystem',
|
||||
'Lightning-fast performance'
|
||||
]
|
||||
}).meta({
|
||||
description: "Right comparison column",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = comparisonSlideSchema
|
||||
|
||||
export type ComparisonSlideData = z.infer<typeof comparisonSlideSchema>
|
||||
|
||||
interface ComparisonSlideLayoutProps {
|
||||
data?: Partial<ComparisonSlideData>
|
||||
}
|
||||
|
||||
const ComparisonSlideLayout: React.FC<ComparisonSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-6 print:px-8 print:py-4 relative z-10">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Feature Comparison'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Comparison section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6 relative z-10">
|
||||
<div className="flex gap-8 h-full items-stretch">
|
||||
{/* Left Column */}
|
||||
<div className="flex-1 bg-white/90 backdrop-blur-sm rounded-2xl p-6 shadow-xl border border-white/50 relative overflow-hidden print:shadow-md print:p-4">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-6">
|
||||
<div className="text-4xl mb-3 print:text-3xl">
|
||||
{slideData?.leftColumn?.icon || '📊'}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-gray-700 mb-2 print:text-xl">
|
||||
{slideData?.leftColumn?.title || 'Before'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Items */}
|
||||
<div className="space-y-3">
|
||||
{slideData?.leftColumn?.items?.map((item, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{item}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-gray-400 to-gray-600" />
|
||||
</div>
|
||||
|
||||
{/* VS Divider */}
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
<div className="w-16 h-16 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full flex items-center justify-center shadow-2xl print:w-12 print:h-12">
|
||||
<span className="text-white font-bold text-lg print:text-base">VS</span>
|
||||
</div>
|
||||
<div className="w-0.5 h-12 bg-gradient-to-b from-blue-600 to-blue-800 mt-4 print:h-8"></div>
|
||||
</div>
|
||||
|
||||
{/* Right Column */}
|
||||
<div className="flex-1 bg-gradient-to-br from-blue-50 to-white rounded-2xl p-6 shadow-xl border border-blue-200 relative overflow-hidden print:shadow-md print:p-4">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-6">
|
||||
<div className="text-4xl mb-3 print:text-3xl">
|
||||
{slideData?.rightColumn?.icon || '🚀'}
|
||||
</div>
|
||||
<h3 className="text-2xl font-bold text-blue-600 mb-2 print:text-xl">
|
||||
{slideData?.rightColumn?.title || 'After'}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Items */}
|
||||
<div className="space-y-3">
|
||||
{slideData?.rightColumn?.items?.map((item, index) => (
|
||||
<div key={index} className="flex items-start gap-3">
|
||||
<div className="w-2 h-2 bg-blue-600 rounded-full mt-2 flex-shrink-0"></div>
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{item}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComparisonSlideLayout
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'number-box-slide'
|
||||
export const layoutName = 'Number Box Slide'
|
||||
export const layoutDescription = 'A professional slide featuring numbered content boxes with titles, descriptions, and clean typography.'
|
||||
|
||||
|
||||
|
||||
const numberBoxSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Metrics').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
numberBoxes: z.array(z.object({
|
||||
number: z.string().min(1).max(20).meta({
|
||||
description: "Number or statistic to display",
|
||||
}),
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Title for the number",
|
||||
}),
|
||||
description: z.string().min(10).max(100).meta({
|
||||
description: "Brief description of the metric",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
number: '150+',
|
||||
title: 'Projects Completed',
|
||||
description: 'Successfully delivered across various industries'
|
||||
},
|
||||
{
|
||||
number: '98%',
|
||||
title: 'Client Satisfaction',
|
||||
description: 'Consistently exceeding expectations'
|
||||
},
|
||||
{
|
||||
number: '24/7',
|
||||
title: 'Support Available',
|
||||
description: 'Round-the-clock assistance for all clients'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of number boxes (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = numberBoxSlideSchema
|
||||
|
||||
export type NumberBoxSlideData = z.infer<typeof numberBoxSlideSchema>
|
||||
|
||||
interface NumberBoxSlideLayoutProps {
|
||||
data?: Partial<NumberBoxSlideData>
|
||||
}
|
||||
|
||||
const NumberBoxSlideLayout: React.FC<NumberBoxSlideLayoutProps> = ({ data: slideData }) => {
|
||||
// Parse and validate data with defaults
|
||||
// const data = numberBoxSlideSchema.parse(slideData || {})
|
||||
|
||||
return (
|
||||
<div className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300">
|
||||
{/* Subtle background pattern */}
|
||||
<div className="absolute inset-0 opacity-[0.02] print:opacity-[0.01]">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-gray-200 to-transparent transform -rotate-45 scale-150"></div>
|
||||
</div>
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-8 print:px-8 print:py-6">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Key Metrics'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Content section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{slideData?.numberBoxes?.map((box, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative bg-white rounded-2xl p-6 border-blue-200 border-2 shadow-blue-200 shadow-lg print:shadow-md print:p-4"
|
||||
>
|
||||
{/* Number display */}
|
||||
<div className="text-center mb-4">
|
||||
<div className="text-4xl font-black text-blue-600 mb-2 print:text-3xl">
|
||||
{box.number}
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-blue-600 mb-3 leading-tight print:text-lg">
|
||||
{box.title}
|
||||
</h3>
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{box.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Decorative corner accent */}
|
||||
<div className="absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full"></div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NumberBoxSlideLayout
|
||||
|
|
@ -1,174 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema, IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'timeline-slide'
|
||||
export const layoutName = 'Timeline Slide'
|
||||
export const layoutDescription = 'A professional slide featuring a chronological timeline with dates, events, and descriptions.'
|
||||
|
||||
const timelineSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Journey').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
events: z.array(z.object({
|
||||
date: z.string().min(2).max(20).meta({
|
||||
description: "Date or time period",
|
||||
}),
|
||||
title: z.string().min(2).max(50).meta({
|
||||
description: "Event title",
|
||||
}),
|
||||
description: z.string().min(10).max(150).meta({
|
||||
description: "Event description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Default event icon'
|
||||
}).meta({
|
||||
description: "Icon for the event",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
date: '2020',
|
||||
title: 'Foundation',
|
||||
description: 'Company founded with a vision to transform digital experiences',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Foundation icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
date: '2021',
|
||||
title: 'First Success',
|
||||
description: 'Launched first product and gained initial market traction',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__icon_query__: 'First success icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
date: '2022',
|
||||
title: 'Expansion',
|
||||
description: 'Expanded team and entered new markets with innovative solutions',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__icon_query__: 'Expansion icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
date: '2023',
|
||||
title: 'Innovation',
|
||||
description: 'Introduced breakthrough technology and achieved industry recognition',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Innovation icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of timeline events (2-6 items)",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = timelineSlideSchema
|
||||
|
||||
export type TimelineSlideData = z.infer<typeof timelineSlideSchema>
|
||||
|
||||
interface TimelineSlideLayoutProps {
|
||||
data?: Partial<TimelineSlideData>
|
||||
}
|
||||
|
||||
const TimelineSlideLayout: React.FC<TimelineSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
{/* Background Effects */}
|
||||
<div className="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-blue-600 rounded-full transform translate-x-32 -translate-y-32 blur-3xl opacity-10" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-600 rounded-full transform -translate-x-16 translate-y-16 blur-2xl opacity-10" />
|
||||
</div>
|
||||
|
||||
{/* Header section */}
|
||||
<div className="text-center px-12 py-6 print:px-8 print:py-4 relative z-10">
|
||||
<h1 className="text-4xl font-bold text-blue-600 mb-4 leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Our Journey'}
|
||||
</h1>
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 max-w-2xl mx-auto leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 mx-auto rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Timeline section */}
|
||||
<div className="flex-1 px-12 pb-8 print:px-8 print:pb-6 relative z-10">
|
||||
<div className="relative max-w-6xl mx-auto">
|
||||
{/* Timeline line */}
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 w-1 h-full bg-gradient-to-b from-blue-600 to-blue-800 rounded-full"></div>
|
||||
|
||||
{/* Timeline events */}
|
||||
<div className="space-y-8 print:space-y-6">
|
||||
{slideData?.events?.map((event, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`flex items-center ${index % 2 === 0 ? 'justify-start' : 'justify-end'} relative`}
|
||||
>
|
||||
{/* Event content */}
|
||||
<div className={`w-5/12 ${index % 2 === 0 ? 'text-right pr-8' : 'text-left pl-8'} print:w-2/5`}>
|
||||
<div className="bg-white/95 backdrop-blur-sm rounded-2xl p-6 shadow-xl border border-white/50 group hover:transform hover:scale-105 transition-all duration-300 print:shadow-md print:p-4">
|
||||
{/* Date */}
|
||||
<div className="text-blue-600 font-bold text-lg mb-2 print:text-base">
|
||||
{event.date}
|
||||
</div>
|
||||
|
||||
{/* Title with icon */}
|
||||
<h3 className="text-xl font-bold text-gray-800 mb-3 leading-tight flex items-center gap-2 print:text-lg">
|
||||
<div className="w-8 h-8 bg-blue-100 rounded-lg flex items-center justify-center overflow-hidden flex-shrink-0 print:w-6 print:h-6">
|
||||
<img
|
||||
src={event.icon?.__icon_url__ || ''}
|
||||
alt={event.icon?.__icon_query__ || event.title}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
{event.title}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-gray-700 leading-relaxed text-sm print:text-xs">
|
||||
{event.description}
|
||||
</p>
|
||||
|
||||
{/* Accent */}
|
||||
<div className={`absolute top-0 ${index % 2 === 0 ? 'left-0' : 'right-0'} ${index % 2 === 0 ? 'right-0' : 'left-0'} h-1 bg-gradient-to-r from-blue-600 to-blue-800`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Timeline dot */}
|
||||
<div className="absolute left-1/2 transform -translate-x-1/2 w-6 h-6 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full border-4 border-white shadow-xl z-10 print:w-4 print:h-4 print:border-2">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full animate-pulse opacity-50"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TimelineSlideLayout
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type2-timeline-slide'
|
||||
export const layoutName = 'Type2 Timeline Slide'
|
||||
export const layoutDescription = 'A timeline layout with title and content items arranged horizontally with numbered circles and connecting line.'
|
||||
|
||||
const type2TimelineSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Main Title').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(4).default([
|
||||
{
|
||||
heading: 'First Point',
|
||||
description: 'Description for the first key point that explains important details'
|
||||
},
|
||||
{
|
||||
heading: 'Second Point',
|
||||
description: 'Description for the second key point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Point',
|
||||
description: 'Description for the third key point highlighting crucial aspects'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of content items (2-4 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type2TimelineSlideSchema
|
||||
|
||||
export type Type2TimelineSlideData = z.infer<typeof type2TimelineSlideSchema>
|
||||
|
||||
interface Type2TimelineSlideLayoutProps {
|
||||
data?: Partial<Type2TimelineSlideData>
|
||||
}
|
||||
|
||||
const Type2TimelineSlideLayout: React.FC<Type2TimelineSlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
const renderTimelineContent = () => {
|
||||
return (
|
||||
<div className="w-full flex flex-col relative mt-4 lg:mt-16">
|
||||
{/* Timeline Header with Numbers and Line */}
|
||||
<div className="relative flex justify-between w-[85%] mx-auto items-center mb-8 px-8">
|
||||
{/* Horizontal Line */}
|
||||
<div className="absolute top-1/2 w-[87%] left-1/2 -translate-x-1/2 h-[2px] bg-blue-600" />
|
||||
|
||||
{/* Timeline Numbers */}
|
||||
{items.map((_, index) => (
|
||||
<div
|
||||
key={`timeline-${index}`}
|
||||
className="relative z-10 w-12 h-12 rounded-full bg-blue-600 px-1 text-white flex items-center justify-center font-bold text-lg"
|
||||
>
|
||||
<span>{numberTranslations[index] || `0${index + 1}`}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Timeline Content */}
|
||||
<div className="flex justify-between gap-8">
|
||||
{items.map((item, index) => (
|
||||
<div key={index} className="flex-1 text-center relative">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm max-w-[1280px] w-full shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Main Title'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{renderTimelineContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type2TimelineSlideLayout
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type5-slide'
|
||||
export const layoutName = 'Type5 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and numbered items with large numerals on the right.'
|
||||
|
||||
const type5SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Points').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Here is the main description that provides context and introduction to the numbered points on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'First Key Point',
|
||||
description: 'Detailed explanation of the first important point that supports the main topic'
|
||||
},
|
||||
{
|
||||
heading: 'Second Key Point',
|
||||
description: 'Detailed explanation of the second important point with relevant information'
|
||||
},
|
||||
{
|
||||
heading: 'Third Key Point',
|
||||
description: 'Detailed explanation of the third important point that concludes the discussion'
|
||||
}
|
||||
]).meta({
|
||||
description: "List of numbered items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type5SlideSchema
|
||||
|
||||
export type Type5SlideData = z.infer<typeof type5SlideSchema>
|
||||
|
||||
interface Type5SlideLayoutProps {
|
||||
data?: Partial<Type5SlideData>
|
||||
}
|
||||
|
||||
const Type5SlideLayout: React.FC<Type5SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const numberTranslations: string[] = ['01', '02', '03', '04', '05', '06']
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-4 sm:gap-18 md:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="lg:w-1/2 lg:space-y-8 ">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Key Points'}
|
||||
</h1>
|
||||
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Here is the main description that provides context and introduction to the numbered points on the right side.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right section - Numbered items */}
|
||||
<div className="lg:w-1/2 relative">
|
||||
<div className="space-y-3 lg:space-y-6">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex gap-6">
|
||||
<div className="text-[26px] lg:text-[32px] leading-[40px] px-1 font-bold mb-4 text-blue-600">
|
||||
{numberTranslations[index] || `0${index + 1}`}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type5SlideLayout
|
||||
|
|
@ -1,161 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type6-slide'
|
||||
export const layoutName = 'Type6 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type6SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema,
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Professional Service'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Expert Consultation'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Quality Assurance'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.jsdelivr.net/npm/remixicon@2.5.0/fonts/remixicon.css',
|
||||
__icon_query__: 'Customer Support'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type6SlideSchema
|
||||
|
||||
export type Type6SlideData = z.infer<typeof type6SlideSchema>
|
||||
|
||||
interface Type6SlideLayoutProps {
|
||||
data?: Partial<Type6SlideData>
|
||||
}
|
||||
|
||||
const Type6SlideLayout: React.FC<Type6SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items.length > 4 ? 'md:grid-cols-3' : 'md:grid-cols-2'} gap-4 sm:gap-6 lg:gap-8 mt-4 lg:mt-12 w-full`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 mg:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center text-white text-xl lg:text-2xl">
|
||||
<img src={item.icon.__icon_url__} className='w-full h-full object-contain' alt={item.icon.__icon_query__} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight mb-2">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items.length)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center text-white text-2xl lg:text-3xl mx-auto mb-4">
|
||||
<img src={item.icon.__icon_url__} className='w-full h-full object-contain' alt={item.icon.__icon_query__} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-4 mt-2 lg:mt-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Our Services'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type6SlideLayout
|
||||
|
|
@ -1,173 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type7-slide'
|
||||
export const layoutName = 'Type7 Slide'
|
||||
export const layoutDescription = 'A centered title with a flexible grid of icon-based content items, adapting layout based on item count.'
|
||||
|
||||
const type7SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Services').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
heading: 'Professional Service',
|
||||
description: 'High-quality professional services tailored to your specific needs and requirements',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Professional service icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Expert Consultation',
|
||||
description: 'Expert advice and consultation from experienced professionals in the field',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__icon_query__: 'Expert consultation icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Quality Assurance',
|
||||
description: 'Comprehensive quality assurance processes to ensure excellent results',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__icon_query__: 'Quality assurance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Customer Support',
|
||||
description: 'Dedicated customer support available to assist you throughout the process',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Customer support icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of service items (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type7SlideSchema
|
||||
|
||||
export type Type7SlideData = z.infer<typeof type7SlideSchema>
|
||||
|
||||
interface Type7SlideLayoutProps {
|
||||
data?: Partial<Type7SlideData>
|
||||
}
|
||||
|
||||
const Type7SlideLayout: React.FC<Type7SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
const isGridLayout = items.length >= 4
|
||||
|
||||
const getGridCols = (length: number) => {
|
||||
switch (length) {
|
||||
case 1: return 'lg:grid-cols-1';
|
||||
case 2: return 'lg:grid-cols-2';
|
||||
case 3: return 'lg:grid-cols-3';
|
||||
case 4: return 'lg:grid-cols-4';
|
||||
case 5: return 'lg:grid-cols-5';
|
||||
case 6: return 'lg:grid-cols-6';
|
||||
default: return 'lg:grid-cols-1';
|
||||
}
|
||||
}
|
||||
|
||||
const renderGridContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 ${items.length > 4 ? 'md:grid-cols-3' : 'md:grid-cols-2'} gap-4 sm:gap-6 lg:gap-8 mt-4 lg:mt-12 w-full`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-2 mg:gap-4">
|
||||
<div className="flex-shrink-0 lg:w-16">
|
||||
<div className="w-12 h-12 lg:w-16 lg:h-16 bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight mb-2">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderHorizontalContent = () => {
|
||||
return (
|
||||
<div className={`grid grid-cols-1 sm:grid-cols-2 ${getGridCols(items.length)} w-full gap-3 lg:gap-8 mt-4 lg:mt-12`}>
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="w-full rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-4 mt-2 lg:mt-4">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] font-inter shadow-lg px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex flex-col items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="text-center sm:pb-2 lg:pb-8 w-full">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Our Services'}
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{isGridLayout ? renderGridContent() : renderHorizontalContent()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type7SlideLayout
|
||||
|
|
@ -1,167 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'type8-slide'
|
||||
export const layoutName = 'Type8 Slide'
|
||||
export const layoutDescription = 'A two-column layout with title and description on the left, and icon-based items on the right.'
|
||||
|
||||
const type8SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('Here is the main description that provides context and introduces the key features outlined on the right side.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
items: z.array(z.object({
|
||||
heading: z.string().min(2).max(100).meta({
|
||||
description: "Item heading",
|
||||
}),
|
||||
description: z.string().min(10).max(300).meta({
|
||||
description: "Item description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Default icon'
|
||||
}).meta({
|
||||
description: "Icon for the item",
|
||||
})
|
||||
})).min(2).max(3).default([
|
||||
{
|
||||
heading: 'Advanced Features',
|
||||
description: 'Cutting-edge functionality designed to enhance productivity and user experience',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'Advanced features icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Reliable Performance',
|
||||
description: 'Consistent and dependable performance across all platforms and devices',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2016/02/19/11/19/office-1209640_1280.jpg',
|
||||
__icon_query__: 'Reliable performance icon'
|
||||
}
|
||||
},
|
||||
{
|
||||
heading: 'Secure Environment',
|
||||
description: 'Enterprise-grade security measures to protect your data and privacy',
|
||||
icon: {
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2017/08/10/08/47/laptop-2619235_1280.jpg',
|
||||
__icon_query__: 'Secure environment icon'
|
||||
}
|
||||
}
|
||||
]).meta({
|
||||
description: "List of featured items (2-3 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type8SlideSchema
|
||||
|
||||
export type Type8SlideData = z.infer<typeof type8SlideSchema>
|
||||
|
||||
interface Type8SlideLayoutProps {
|
||||
data?: Partial<Type8SlideData>
|
||||
}
|
||||
|
||||
const Type8SlideLayout: React.FC<Type8SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const items = slideData?.items || []
|
||||
|
||||
const renderItems = () => {
|
||||
if (items.length === 2) {
|
||||
// Vertical stacked layout for 2 items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<div className="w-16 h-16 lg:w-20 lg:h-20 bg-blue-600 rounded-lg flex items-center justify-center mx-auto mb-4 overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-1 lg:space-y-3">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight text-center">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed text-center">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
} else {
|
||||
// Horizontal layout with side icons for 3+ items
|
||||
return (
|
||||
<div className="space-y-4 lg:space-y-8">
|
||||
{items.map((item, index) => (
|
||||
<div
|
||||
key={index}
|
||||
style={{
|
||||
boxShadow: "0 2px 10px 0 rgba(43, 43, 43, 0.2)",
|
||||
}}
|
||||
className="rounded-lg p-3 lg:p-6 relative"
|
||||
>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="w-[32px] md:w-[64px] h-[32px] md:h-[64px]">
|
||||
<div className="w-full h-full bg-blue-600 rounded-lg flex items-center justify-center overflow-hidden">
|
||||
<img
|
||||
src={item.icon?.__icon_url__ || ''}
|
||||
alt={item.icon?.__icon_query__ || item.heading}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="lg:space-y-3">
|
||||
<h3 className="text-lg sm:text-xl lg:text-2xl font-bold text-gray-900 leading-tight">
|
||||
{item.heading}
|
||||
</h3>
|
||||
<p className="text-sm sm:text-base lg:text-lg text-gray-700 leading-relaxed">
|
||||
{item.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" shadow-lg w-full max-w-[1280px] rounded-sm font-inter px-3 sm:px-12 lg:px-20 py-[10px] sm:py-[40px] lg:py-[86px] flex items-center justify-center max-h-[720px] aspect-video bg-white relative z-20 mx-auto"
|
||||
>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 sm:gap-8 lg:gap-16 items-center w-full">
|
||||
{/* Left section - Title and Description */}
|
||||
<div className="space-y-2 lg:space-y-6">
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight">
|
||||
{slideData?.title || 'Key Features'}
|
||||
</h1>
|
||||
|
||||
<p className="text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed">
|
||||
{slideData?.description || 'Here is the main description that provides context and introduces the key features outlined on the right side.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Right section - Items */}
|
||||
<div className="relative">
|
||||
{renderItems()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type8SlideLayout
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"description": "Contemporary designs with clean lines and sophisticated layouts",
|
||||
"ordered": false,
|
||||
"isDefault": false
|
||||
}
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'bullet-point-slide'
|
||||
export const layoutName = 'Bullet Point Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and a list of bullet points.'
|
||||
|
||||
|
||||
const bulletPointSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Points').meta({
|
||||
description: "Title of the slide",
|
||||
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
icon: z.string().optional().meta({
|
||||
description: "Icon to display in the slide",
|
||||
}),
|
||||
bulletPoints: z.array(z.string().min(5).max(200)).min(2).max(8).default([
|
||||
'First key point that highlights important information',
|
||||
'Second bullet point with valuable insights',
|
||||
'Third point demonstrating clear benefits',
|
||||
'Fourth item showcasing key features'
|
||||
]).meta({
|
||||
description: "List of bullet points (2-8 items)",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
}),
|
||||
})
|
||||
export const Schema = bulletPointSlideSchema
|
||||
|
||||
|
||||
export type BulletPointSlideData = z.infer<typeof bulletPointSlideSchema>
|
||||
|
||||
interface BulletPointSlideLayoutProps {
|
||||
data?: Partial<BulletPointSlideData>
|
||||
}
|
||||
|
||||
const BulletPointSlideLayout: React.FC<BulletPointSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col px-16 py-12">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-12">
|
||||
<h1 className="text-6xl font-black mb-8 leading-tight tracking-tight max-w-4xl mx-auto">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<div className="relative mb-8">
|
||||
<div className="relative flex justify-center">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
<p className="text-xl text-gray-600 font-light mt-6 max-w-2xl mx-auto leading-relaxed">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Corner accent */}
|
||||
<div className={`absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
|
||||
{/* Bullet Points */}
|
||||
<div className="flex-1 max-w-4xl mx-auto">
|
||||
<div className="grid gap-6">
|
||||
{slideData?.bulletPoints?.map((point, index) => (
|
||||
<div key={index} className="group flex items-start gap-6 p-6 rounded-2xl bg-white/80 backdrop-blur-sm hover:bg-white/90 transition-all duration-200 hover:shadow-lg">
|
||||
<div className="relative flex-shrink-0 mt-1">
|
||||
<div className={`bg-blue-600 w-4 h-4 rounded-full shadow-lg group-hover:scale-125 transition-all duration-200 relative z-10`} />
|
||||
<div className={`absolute inset-0 bg-blue-600 w-4 h-4 rounded-full blur-sm opacity-50 group-hover:opacity-75 transition-opacity duration-200`} />
|
||||
</div>
|
||||
<p className="text-lg text-gray-800 leading-relaxed font-medium">
|
||||
{point}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0">
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
{/* Corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default BulletPointSlideLayout
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'content-slide'
|
||||
export const layoutName = 'Content Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and content'
|
||||
|
||||
const contentSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Slide Title').meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
content: z.string().min(10).max(1000).default('Your slide content goes here. This is where you can add detailed information, explanations, or any other text content that supports your presentation.').meta({
|
||||
description: "Main content text",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = contentSlideSchema
|
||||
|
||||
export type ContentSlideData = z.infer<typeof contentSlideSchema>
|
||||
|
||||
interface ContentSlideLayoutProps {
|
||||
data?: Partial<ContentSlideData>
|
||||
}
|
||||
|
||||
const ContentSlideLayout: React.FC<ContentSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
|
||||
{/* Grid overlay for professional look */}
|
||||
<div className="absolute inset-0 opacity-[0.015]" style={{
|
||||
backgroundImage: `linear-gradient(0deg, rgba(0,0,0,0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px)`,
|
||||
backgroundSize: '40px 40px'
|
||||
}} />
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col px-16 py-12">
|
||||
{/* Header */}
|
||||
<div className="text-center mb-16">
|
||||
<h1 className="text-6xl font-black mb-8 leading-tight tracking-tight max-w-4xl mx-auto">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<div className="relative mb-8">
|
||||
<div className="relative flex justify-center">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
<p className="text-xl text-gray-600 font-light mt-6 max-w-2xl mx-auto leading-relaxed">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Corner accent */}
|
||||
<div className={`absolute top-0 right-0 w-24 h-24 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 max-w-4xl mx-auto">
|
||||
<div className="prose prose-lg prose-gray max-w-none">
|
||||
<div className="text-lg text-gray-700 leading-relaxed">
|
||||
{slideData?.content?.split('\n').map((paragraph, index) => (
|
||||
<p key={index} className="mb-6">
|
||||
{paragraph}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0">
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
{/* Corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentSlideLayout
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'first-slide'
|
||||
export const layoutName = 'First Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, author, date, company, and background image'
|
||||
|
||||
const firstSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Welcome to Our Presentation').meta({
|
||||
description: "Main title of the presentation",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).default('Subtitle for the slide').optional().meta({
|
||||
description: "Optional subtitle or tagline",
|
||||
}),
|
||||
author: z.string().max(100).default('John Doe').optional().meta({
|
||||
description: "Author or presenter name",
|
||||
}),
|
||||
date: z.string().optional().meta({
|
||||
description: "Presentation date",
|
||||
}),
|
||||
company: z.string().max(100).default('Company Name').optional().meta({
|
||||
description: "Company or organization name",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = firstSlideSchema
|
||||
|
||||
export type FirstSlideData = z.infer<typeof firstSlideSchema>
|
||||
|
||||
interface FirstSlideLayoutProps {
|
||||
data?: Partial<FirstSlideData>
|
||||
}
|
||||
|
||||
const FirstSlideLayout: React.FC<FirstSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col">
|
||||
{/* Header */}
|
||||
<div className="flex-1 flex flex-col justify-center items-center text-center px-16 py-12">
|
||||
{/* Title */}
|
||||
<h1 className="text-7xl font-black mb-8 leading-tight tracking-tight max-w-4xl">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<div className="relative mb-12">
|
||||
<div className="relative">
|
||||
<div className={`w-32 h-1.5 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1.5 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
<p className="text-2xl text-gray-600 font-light mt-6 max-w-2xl leading-relaxed">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Date */}
|
||||
{slideData?.date && (
|
||||
<p className={`text-base font-medium break-words from-blue-600 to-blue-800 bg-gradient-to-r bg-clip-text text-transparent`}>
|
||||
{slideData.date}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Footer with Author and Company */}
|
||||
<div className="px-16 pb-8">
|
||||
<div className="flex justify-between items-end">
|
||||
<div className="text-left">
|
||||
{slideData?.author && (
|
||||
<p className="text-lg font-semibold text-gray-800 mb-1">{slideData.author}</p>
|
||||
)}
|
||||
{slideData?.company && (
|
||||
<p className="text-sm text-gray-600">{slideData.company}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0">
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
{/* Glow effect */}
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
{/* Corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
<div className={`absolute bottom-0 left-0 w-24 h-24 bg-gradient-to-tr from-blue-600 to-blue-800 opacity-5 rounded-tr-full`} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default FirstSlideLayout
|
||||
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { IconSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'icon-slide'
|
||||
export const layoutName = 'Icon Slide'
|
||||
export const layoutDescription = 'A professional slide featuring a prominent icon with title, description, and key points.'
|
||||
|
||||
|
||||
|
||||
const iconSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Features').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
icon: IconSchema.default({
|
||||
__icon_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__icon_query__: 'A beautiful road in the mountains'
|
||||
}).meta({
|
||||
description: "Main slide icon",
|
||||
}),
|
||||
features: z.array(z.string().min(10).max(150)).min(2).max(6).default([
|
||||
'Advanced analytics and reporting capabilities',
|
||||
'Seamless integration with existing systems',
|
||||
'Real-time collaboration and communication',
|
||||
'Enhanced security and data protection'
|
||||
]).meta({
|
||||
description: "List of key features (2-6 items)",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = iconSlideSchema
|
||||
|
||||
export type IconSlideData = z.infer<typeof iconSlideSchema>
|
||||
|
||||
interface IconSlideLayoutProps {
|
||||
data?: Partial<IconSlideData>
|
||||
}
|
||||
|
||||
const IconSlideLayout: React.FC<IconSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="relative w-full aspect-[16/9] flex bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300">
|
||||
{/* Subtle background pattern for print compatibility */}
|
||||
<div className="absolute inset-0 opacity-[0.02] print:opacity-[0.01]">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-gray-200 to-transparent transform rotate-45 scale-150"></div>
|
||||
</div>
|
||||
|
||||
{/* Left side - Icon and title */}
|
||||
<div className="flex-1 flex flex-col justify-center items-center p-12 relative">
|
||||
{/* Icon container */}
|
||||
<div className="relative mb-8 p-8 rounded-3xl bg-white border-2 shadow-xl print:shadow-md">
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-blue-600 to-blue-800 opacity-5 rounded-3xl"></div>
|
||||
<img
|
||||
src={slideData?.icon?.__icon_url__ || ''}
|
||||
alt={slideData?.icon?.__icon_query__ || ''}
|
||||
className="w-24 h-24 object-contain relative z-10 print:w-20 print:h-20"
|
||||
/>
|
||||
<div className="absolute -bottom-2 -right-2 w-6 h-6 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full opacity-80"></div>
|
||||
</div>
|
||||
|
||||
{/* Title */}
|
||||
<h1 className="text-4xl font-bold text-center text-blue-700 mb-4 max-w-sm leading-tight print:text-3xl">
|
||||
{slideData?.title || 'Key Features'}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-lg text-gray-600 text-center max-w-md leading-relaxed print:text-base">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Decorative element */}
|
||||
<div className="mt-6 w-16 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full"></div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Content */}
|
||||
<div className="flex-1 flex flex-col justify-center p-12 bg-white/50 print:bg-transparent">
|
||||
{/* Features */}
|
||||
<div className="space-y-4">
|
||||
{slideData?.features?.map((feature, index) => (
|
||||
<div key={index} className="flex items-start space-x-4">
|
||||
<div className="flex-shrink-0 w-8 h-8 bg-gradient-to-br from-blue-600 to-blue-800 rounded-full flex items-center justify-center mt-1 print:w-6 print:h-6">
|
||||
<span className="text-white font-semibold text-sm print:text-xs">
|
||||
{index + 1}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-700 leading-relaxed print:text-sm">
|
||||
{feature}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Bottom accent line */}
|
||||
<div className="mt-8 w-full h-0.5 bg-gradient-to-r from-blue-600 to-blue-800 opacity-20"></div>
|
||||
</div>
|
||||
|
||||
{/* Top decorative border */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default IconSlideLayout
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'image-slide'
|
||||
export const layoutName = 'Image Slide'
|
||||
export const layoutDescription = 'A professional slide featuring a prominent image with overlaid text content and call-to-action elements.'
|
||||
|
||||
|
||||
|
||||
const imageSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Our Vision').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(10).max(200).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
description: z.string().min(20).max(500).default('Transform your ideas into reality with innovative solutions that drive success and growth.').meta({
|
||||
description: "Main description text",
|
||||
}),
|
||||
image: ImageSchema.default({
|
||||
__image_url__: 'https://cdn.pixabay.com/photo/2015/12/01/20/28/road-1072823_1280.jpg',
|
||||
__image_prompt__: 'A beautiful road in the mountains'
|
||||
}).meta({
|
||||
description: "Main slide image",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = imageSlideSchema
|
||||
|
||||
export type ImageSlideData = z.infer<typeof imageSlideSchema>
|
||||
|
||||
interface ImageSlideLayoutProps {
|
||||
data?: Partial<ImageSlideData>
|
||||
}
|
||||
|
||||
const ImageSlideLayout: React.FC<ImageSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div className="relative w-full aspect-[16/9] flex bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300">
|
||||
{/* Left panel - Image */}
|
||||
<div className="flex-1 relative">
|
||||
<img
|
||||
src={slideData?.image?.__image_url__ || ''}
|
||||
alt={slideData?.image?.__image_prompt__ || ''}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
{/* Overlay gradient */}
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-black/70 via-black/50 to-transparent"></div>
|
||||
|
||||
{/* Content overlay */}
|
||||
<div className="absolute inset-0 flex flex-col justify-center p-12 text-white print:p-8">
|
||||
{/* Title */}
|
||||
<h1 className="text-5xl font-bold mb-6 leading-tight print:text-4xl">
|
||||
{slideData?.title || 'Our Vision'}
|
||||
</h1>
|
||||
|
||||
{/* Subtitle */}
|
||||
{slideData?.subtitle && (
|
||||
<p className="text-xl text-gray-200 mb-6 max-w-xl leading-relaxed print:text-lg">
|
||||
{slideData.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Description */}
|
||||
<p className="text-lg text-gray-100 mb-8 max-w-2xl leading-relaxed print:text-base">
|
||||
{slideData?.description || 'Transform your ideas into reality with innovative solutions that drive success and growth.'}
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
{/* Decorative line */}
|
||||
<div className="mt-8 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right panel for additional context */}
|
||||
<div className="w-80 bg-white/95 backdrop-blur-sm flex flex-col justify-center p-8 print:w-64 print:bg-white print:p-6">
|
||||
{/* Feature card */}
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center space-x-4 p-4 rounded-xl bg-white shadow-md print:shadow-sm">
|
||||
<div className="w-12 h-12 bg-blue-100 rounded-lg flex items-center justify-center flex-shrink-0 print:w-10 print:h-10">
|
||||
<span className="text-blue-600 font-bold text-lg print:text-base">✓</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 text-sm print:text-xs">Professional Quality</h3>
|
||||
<p className="text-gray-600 text-xs print:text-xs">Excellence in every detail</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom accent line */}
|
||||
<div className="mt-8 w-full h-2 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full opacity-20"></div>
|
||||
</div>
|
||||
|
||||
{/* Top decorative border */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800"></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImageSlideLayout
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'quote-slide'
|
||||
export const layoutName = 'Quote Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, quote, author, author title, company, and author image'
|
||||
|
||||
const quoteSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Testimonials').meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
quote: z.string().min(10).max(500).default('This solution has transformed our business operations and exceeded all expectations.').meta({
|
||||
description: "The main quote or testimonial",
|
||||
}),
|
||||
author: z.string().min(2).max(100).default('John Smith').meta({
|
||||
description: "Quote author name",
|
||||
}),
|
||||
authorTitle: z.string().min(2).max(100).optional().meta({
|
||||
description: "Author job title or position",
|
||||
}),
|
||||
company: z.string().min(2).max(100).optional().meta({
|
||||
description: "Author company or organization",
|
||||
}),
|
||||
authorImage: z.string().optional().meta({
|
||||
description: "URL to author photo",
|
||||
}),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = quoteSlideSchema
|
||||
|
||||
export type QuoteSlideData = z.infer<typeof quoteSlideSchema>
|
||||
|
||||
interface QuoteSlideLayoutProps {
|
||||
data?: Partial<QuoteSlideData>
|
||||
}
|
||||
|
||||
const QuoteSlideLayout: React.FC<QuoteSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] flex flex-col bg-white overflow-hidden shadow-2xl border border-slate-200"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `linear-gradient(135deg, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url(${slideData?.backgroundImage.__image_url__})`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center'
|
||||
} : {}}
|
||||
>
|
||||
|
||||
|
||||
{/* Content */}
|
||||
<div className="relative z-10 h-full flex flex-col justify-center items-center px-16 py-12">
|
||||
{/* Title */}
|
||||
<h1 className="text-5xl font-black mb-16 leading-tight tracking-tight text-center max-w-4xl">
|
||||
<span className="text-gray-900">{slideData?.title?.split(' ').slice(0, -1).join(' ')}</span>{' '}
|
||||
<span className={`bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent`}>
|
||||
{slideData?.title?.split(' ').slice(-1)[0]}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{/* Quote */}
|
||||
<div className="max-w-4xl mx-auto text-center mb-16">
|
||||
{/* Decorative line */}
|
||||
<div className="relative flex justify-center mb-12">
|
||||
<div className={`w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
|
||||
{/* Quote Text */}
|
||||
<blockquote className={`text-2xl md:text-3xl leading-relaxed mb-8 italic break-words relative z-10 font-light text-slate-700`}>
|
||||
"{slideData?.quote}"
|
||||
</blockquote>
|
||||
|
||||
{/* Author Attribution */}
|
||||
<div className="flex items-center justify-center space-x-4 relative z-10">
|
||||
{/* Author Avatar */}
|
||||
<div className="flex-shrink-0">
|
||||
{slideData?.authorImage ? (
|
||||
<img
|
||||
src={slideData?.authorImage}
|
||||
alt={slideData?.author}
|
||||
className="w-16 h-16 rounded-full object-cover shadow-xl border-4 border-white"
|
||||
/>
|
||||
) : (
|
||||
<div className={`w-16 h-16 rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-xl shadow-xl border-4 border-white`}>
|
||||
{slideData?.author?.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Author Details */}
|
||||
<div className="text-left">
|
||||
<p className="text-xl font-bold text-slate-900 break-words">
|
||||
{slideData?.author}
|
||||
</p>
|
||||
|
||||
{slideData?.authorTitle && (
|
||||
<p className={`text-base font-semibold bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent break-words`}>
|
||||
{slideData?.authorTitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{slideData?.company && (
|
||||
<p className="text-sm text-slate-600 font-medium break-words">
|
||||
{slideData?.company}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quote Accent Line */}
|
||||
<div className="flex justify-center mt-6">
|
||||
<div className="relative">
|
||||
<div className={`w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg`} />
|
||||
<div className={`absolute inset-0 w-24 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className={`absolute bottom-0 right-0 w-20 h-20 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full`} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Decorative accent */}
|
||||
<div className={`absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg`}>
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className={`absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50`} />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className={`absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default QuoteSlideLayout
|
||||
|
|
@ -1,193 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
|
||||
export const layoutId = 'statistics-slide'
|
||||
export const layoutName = 'Statistics Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and statistics'
|
||||
|
||||
const statisticsSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Key Statistics').meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or description",
|
||||
}),
|
||||
statistics: z.array(z.object({
|
||||
value: z.string().min(1).max(20).meta({
|
||||
description: "Statistical value (e.g., '250%', '$1.2M', '99.9%')",
|
||||
}),
|
||||
label: z.string().min(3).max(100).meta({
|
||||
description: "Description of the statistic",
|
||||
}),
|
||||
trend: z.enum(['up', 'down', 'neutral']).optional().meta({
|
||||
description: "Trend direction indicator",
|
||||
}),
|
||||
context: z.string().min(5).max(200).optional().meta({
|
||||
description: "Additional context or time period",
|
||||
})
|
||||
})).min(2).max(6).default([
|
||||
{
|
||||
value: '250%',
|
||||
label: 'Revenue Growth',
|
||||
trend: 'up',
|
||||
context: 'Year over year increase'
|
||||
},
|
||||
{
|
||||
value: '50M+',
|
||||
label: 'Active Users',
|
||||
trend: 'up',
|
||||
context: 'Global user base'
|
||||
},
|
||||
{
|
||||
value: '99.9%',
|
||||
label: 'Uptime',
|
||||
trend: 'neutral',
|
||||
context: 'Service reliability'
|
||||
},
|
||||
{
|
||||
value: '24/7',
|
||||
label: 'Support',
|
||||
trend: 'neutral',
|
||||
context: 'Customer service'
|
||||
}
|
||||
]).describe('List of statistics (2-6 items)'),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = statisticsSlideSchema
|
||||
|
||||
export type StatisticsSlideData = z.infer<typeof statisticsSlideSchema>
|
||||
|
||||
interface StatisticsSlideLayoutProps {
|
||||
data?: Partial<StatisticsSlideData>
|
||||
}
|
||||
|
||||
const StatisticsSlideLayout: React.FC<StatisticsSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
const statsCount = slideData?.statistics?.length || 0
|
||||
|
||||
const getTrendIcon = (trend?: string) => {
|
||||
switch (trend) {
|
||||
case 'up':
|
||||
return '↗'
|
||||
case 'down':
|
||||
return '↘'
|
||||
case 'neutral':
|
||||
return '→'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
const getGridCols = () => {
|
||||
if (statsCount <= 2) return 'grid-cols-2'
|
||||
if (statsCount <= 3) return 'grid-cols-3'
|
||||
if (statsCount <= 4) return 'grid-cols-2 lg:grid-cols-4'
|
||||
return 'grid-cols-2 lg:grid-cols-3'
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `url("${slideData.backgroundImage.__image_url__}")`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-blue-600 rounded-full transform translate-x-32 -translate-y-32 blur-3xl" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-600 rounded-full transform -translate-x-16 translate-y-16 blur-2xl" />
|
||||
</div>
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className="bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className="w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg" />
|
||||
<div className="absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Statistics Grid */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className={`grid ${getGridCols()} gap-6 w-full max-w-6xl`}>
|
||||
{slideData?.statistics?.map((stat, index) => (
|
||||
<div key={index} className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 text-center relative overflow-hidden group hover:transform hover:scale-105 transition-all duration-300">
|
||||
{/* Card accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
|
||||
{/* Statistic Value */}
|
||||
<div className="text-4xl md:text-5xl font-black mb-2 bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{stat?.value}
|
||||
</div>
|
||||
|
||||
{/* Statistic Label */}
|
||||
<h3 className="text-lg font-bold text-slate-900 mb-3 break-words leading-tight">
|
||||
{stat?.label}
|
||||
</h3>
|
||||
|
||||
{/* Description */}
|
||||
{stat?.context && (
|
||||
<p className="text-sm text-slate-600 leading-relaxed break-words mb-4">
|
||||
{stat?.context}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Trend Indicator */}
|
||||
{stat?.trend && (
|
||||
<div className={`inline-flex items-center px-3 py-1 rounded-full text-sm font-semibold border ${stat?.trend === 'up' ? 'text-emerald-600 bg-emerald-50 border-emerald-200' :
|
||||
stat?.trend === 'down' ? 'text-red-600 bg-red-50 border-red-200' :
|
||||
'text-slate-600 bg-slate-50 border-slate-200'
|
||||
}`}>
|
||||
<span className="mr-1">{getTrendIcon(stat?.trend)}</span>
|
||||
{stat?.trend === 'up' ? 'Trending Up' :
|
||||
stat?.trend === 'down' ? 'Trending Down' : 'Stable'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className="absolute bottom-0 right-0 w-16 h-16 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50" />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default StatisticsSlideLayout
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
import { ImageSchema } from '@/presentation-layouts/defaultSchemes';
|
||||
|
||||
export const layoutId = 'team-slide'
|
||||
export const layoutName = 'Team Slide'
|
||||
export const layoutDescription = 'A slide with a title, subtitle, and team members'
|
||||
|
||||
const teamSlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Meet Our Team').meta({
|
||||
description: "Title of the slide",
|
||||
}),
|
||||
subtitle: z.string().min(3).max(150).optional().meta({
|
||||
description: "Optional subtitle or team description",
|
||||
}),
|
||||
teamMembers: z.array(z.object({
|
||||
name: z.string().min(2).max(100).meta({
|
||||
description: "Team member name",
|
||||
}),
|
||||
title: z.string().min(2).max(100).meta({
|
||||
description: "Job title or role",
|
||||
}),
|
||||
image: z.string().optional().meta({
|
||||
description: "URL to team member photo",
|
||||
}),
|
||||
bio: z.string().min(10).max(300).optional().meta({
|
||||
description: "Brief biography or description",
|
||||
}),
|
||||
email: z.email().optional().meta({
|
||||
description: "Contact email",
|
||||
}),
|
||||
linkedin: z.string().optional().meta({
|
||||
description: "LinkedIn profile URL",
|
||||
})
|
||||
})).min(1).max(6).default([
|
||||
{
|
||||
name: 'Sarah Johnson',
|
||||
title: 'Chief Executive Officer',
|
||||
image: 'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=300&h=300&fit=crop&crop=face',
|
||||
bio: 'Strategic leader with 15+ years experience driving innovation and growth in technology companies.',
|
||||
email: 'sarah@company.com',
|
||||
linkedin: 'https://linkedin.com/in/sarahjohnson'
|
||||
},
|
||||
{
|
||||
name: 'Michael Chen',
|
||||
title: 'Chief Technology Officer',
|
||||
image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=300&h=300&fit=crop&crop=face',
|
||||
bio: 'Technology visionary specializing in scalable architecture and emerging technologies.',
|
||||
email: 'michael@company.com',
|
||||
linkedin: 'https://linkedin.com/in/michaelchen'
|
||||
},
|
||||
{
|
||||
name: 'Emma Rodriguez',
|
||||
title: 'Head of Design',
|
||||
image: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=300&h=300&fit=crop&crop=face',
|
||||
bio: 'Creative director passionate about user-centered design and innovative digital experiences.',
|
||||
email: 'emma@company.com',
|
||||
linkedin: 'https://linkedin.com/in/emmarodriguez'
|
||||
}
|
||||
]).describe('Team members (1-6 people)'),
|
||||
backgroundImage: ImageSchema.optional().meta({
|
||||
description: "Background image for the slide",
|
||||
}),
|
||||
})
|
||||
|
||||
export const Schema = teamSlideSchema
|
||||
|
||||
export type TeamSlideData = z.infer<typeof teamSlideSchema>
|
||||
|
||||
interface TeamSlideLayoutProps {
|
||||
data?: Partial<TeamSlideData>
|
||||
}
|
||||
|
||||
const TeamSlideLayout: React.FC<TeamSlideLayoutProps> = ({ data: slideData }) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative w-full aspect-[16/9] bg-white overflow-hidden shadow-2xl border border-slate-200 print:shadow-none print:border-gray-300"
|
||||
style={slideData?.backgroundImage ? {
|
||||
backgroundImage: `url("${slideData.backgroundImage.__image_url__}")`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat'
|
||||
} : {}}
|
||||
>
|
||||
{/* Enhanced geometric background decoration */}
|
||||
<div className="absolute inset-0 opacity-[0.03]">
|
||||
<div className="absolute top-0 right-0 w-96 h-96 bg-blue-600 rounded-full transform translate-x-32 -translate-y-32 blur-3xl" />
|
||||
<div className="absolute bottom-0 left-0 w-64 h-64 bg-blue-600 rounded-full transform -translate-x-16 translate-y-16 blur-2xl" />
|
||||
</div>
|
||||
|
||||
{/* Grid overlay for professional look */}
|
||||
<div className="absolute inset-0 opacity-[0.02]" style={{
|
||||
backgroundImage: `linear-gradient(0deg, rgba(0,0,0,0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,0,0,0.1) 1px, transparent 1px)`,
|
||||
backgroundSize: '60px 60px'
|
||||
}} />
|
||||
|
||||
<div className="relative z-10 flex flex-col h-full px-8 py-8">
|
||||
{/* Professional Header */}
|
||||
<header className="mb-6">
|
||||
<h1 className={`text-4xl md:text-5xl font-bold mb-3 tracking-tight leading-tight break-words ${slideData?.backgroundImage?.__image_prompt__
|
||||
? 'text-white drop-shadow-lg'
|
||||
: 'text-slate-900'
|
||||
}`}>
|
||||
<span className="bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{slideData?.title}
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{slideData?.subtitle && (
|
||||
<p className={`text-xl font-light leading-relaxed break-words ${slideData?.backgroundImage?.__image_prompt__
|
||||
? 'text-slate-200 drop-shadow-md'
|
||||
: 'text-slate-600'
|
||||
}`}>
|
||||
{slideData?.subtitle}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="relative mt-4">
|
||||
<div className="w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full shadow-lg" />
|
||||
<div className="absolute inset-0 w-32 h-1 bg-gradient-to-r from-blue-600 to-blue-800 rounded-full blur-sm opacity-50" />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Enhanced Team Grid */}
|
||||
<main className="flex-1 flex items-center justify-center">
|
||||
<div className="bg-white/95 backdrop-blur-lg rounded-2xl p-8 shadow-2xl border border-white/50 w-full max-w-6xl relative overflow-hidden">
|
||||
{/* Team grid with responsive layout */}
|
||||
<div className={`grid gap-6 relative z-10 ${slideData?.teamMembers?.length === 1 ? 'grid-cols-1 max-w-md mx-auto' :
|
||||
slideData?.teamMembers?.length === 2 ? 'grid-cols-1 md:grid-cols-2 max-w-4xl mx-auto' :
|
||||
slideData?.teamMembers?.length === 3 ? 'grid-cols-1 md:grid-cols-3' :
|
||||
slideData?.teamMembers?.length === 4 ? 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4' :
|
||||
slideData?.teamMembers?.length === 5 ? 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5' :
|
||||
'grid-cols-1 md:grid-cols-2 lg:grid-cols-3'
|
||||
}`}>
|
||||
{slideData?.teamMembers?.map((member, index) => (
|
||||
<div key={index} className="bg-white/95 backdrop-blur-lg rounded-2xl p-6 shadow-2xl border border-white/50 text-center relative overflow-hidden group hover:transform hover:scale-105 transition-all duration-300">
|
||||
{/* Card accent */}
|
||||
<div className="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-600 to-blue-800" />
|
||||
|
||||
{/* Professional Avatar */}
|
||||
<div className="relative mb-4 mx-auto w-24 h-24 group-hover:scale-110 transition-transform duration-300">
|
||||
<div className="w-full h-full rounded-full overflow-hidden shadow-2xl border-4 border-white relative">
|
||||
{member.image ? (
|
||||
<img
|
||||
src={member.image}
|
||||
alt={member.name}
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-full h-full rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-xl shadow-xl border-4 border-white">
|
||||
{member.name.split(' ').map(n => n[0]).join('')}
|
||||
</div>
|
||||
)}
|
||||
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-blue-600 to-blue-800 opacity-20 group-hover:opacity-30 transition-opacity duration-300" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Member Details */}
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-lg font-bold text-slate-900 break-words leading-tight">
|
||||
{member.name}
|
||||
</h3>
|
||||
<p className="text-sm font-semibold mb-3 bg-gradient-to-r from-blue-600 to-blue-800 bg-clip-text text-transparent">
|
||||
{member.title}
|
||||
</p>
|
||||
|
||||
{member.bio && (
|
||||
<p className="text-xs text-slate-600 leading-relaxed break-words">
|
||||
{member.bio}
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* Contact Icons */}
|
||||
<div className="flex justify-center space-x-3 pt-3">
|
||||
{member.email && (
|
||||
<a
|
||||
href={`mailto:${member.email}`}
|
||||
className="p-2 rounded-full bg-slate-100 hover:bg-slate-200 text-blue-600 transition-all duration-200 hover:transform hover:scale-110"
|
||||
title="Email"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884z" />
|
||||
<path d="M18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{member.linkedin && (
|
||||
<a
|
||||
href={member.linkedin}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2 rounded-full bg-slate-100 hover:bg-slate-200 text-blue-600 transition-all duration-200 hover:transform hover:scale-110"
|
||||
title="LinkedIn"
|
||||
>
|
||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M16.338 16.338H13.67V12.16c0-.995-.017-2.277-1.387-2.277-1.39 0-1.601 1.086-1.601 2.207v4.248H8.014v-8.59h2.559v1.174h.037c.356-.675 1.227-1.387 2.526-1.387 2.703 0 3.203 1.778 3.203 4.092v4.711zM5.005 6.575a1.548 1.548 0 11-.003-3.096 1.548 1.548 0 01.003 3.096zm-1.337 9.763H6.34v-8.59H3.667v8.59zM17.668 1H2.328C1.595 1 1 1.581 1 2.298v15.403C1 18.418 1.595 19 2.328 19h15.34c.734 0 1.332-.582 1.332-1.299V2.298C19 1.581 18.402 1 17.668 1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Background decoration */}
|
||||
<div className="absolute bottom-0 right-0 w-12 h-12 bg-gradient-to-tl from-blue-600 to-blue-800 opacity-5 rounded-tl-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
{/* Enhanced decorative accent */}
|
||||
<div className="absolute bottom-0 left-0 right-0 h-3 bg-gradient-to-r from-blue-600 to-blue-800 shadow-lg">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-white/30 to-transparent" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-blue-600 to-blue-800 blur-sm opacity-50" />
|
||||
</div>
|
||||
|
||||
{/* Professional corner accents */}
|
||||
<div className="absolute top-0 right-0 w-32 h-32 bg-gradient-to-bl from-blue-600 to-blue-800 opacity-5 rounded-bl-full" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default TeamSlideLayout
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import React from 'react'
|
||||
import * as z from "zod";
|
||||
|
||||
export const layoutId = 'type4-slide'
|
||||
export const layoutName = 'Type4 Slide'
|
||||
export const layoutDescription = 'A chart-focused layout with title, chart visualization, and description text.'
|
||||
|
||||
const type4SlideSchema = z.object({
|
||||
title: z.string().min(3).max(100).default('Chart Analysis').meta({
|
||||
description: "Main title of the slide",
|
||||
}),
|
||||
description: z.string().min(10).max(500).default('This chart shows important data trends and insights that help understand the current situation and make informed decisions.').meta({
|
||||
description: "Description text for the chart",
|
||||
}),
|
||||
chartData: z.any().optional().meta({
|
||||
description: "Chart data object",
|
||||
}),
|
||||
isFullSizeChart: z.boolean().default(false).meta({
|
||||
description: "Whether to display chart in full size mode",
|
||||
})
|
||||
})
|
||||
|
||||
export const Schema = type4SlideSchema
|
||||
|
||||
export type Type4SlideData = z.infer<typeof type4SlideSchema>
|
||||
|
||||
interface Type4SlideLayoutProps {
|
||||
data?: Partial<Type4SlideData>
|
||||
}
|
||||
|
||||
const Type4SlideLayout: React.FC<Type4SlideLayoutProps> = ({ data: slideData }) => {
|
||||
const isFullSizeGraph = slideData?.isFullSizeChart || false
|
||||
|
||||
// Simple placeholder chart component
|
||||
const ChartPlaceholder = () => (
|
||||
<div className="w-full h-64 lg:h-80 bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg border-2 border-blue-200 flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<div className="text-blue-600 text-4xl mb-4">📊</div>
|
||||
<p className="text-blue-700 font-semibold">Chart Component</p>
|
||||
<p className="text-blue-600 text-sm mt-1">Data visualization will appear here</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className=" rounded-sm w-full max-w-[1280px] px-3 py-[10px] sm:px-12 lg:px-20 sm:py-[40px] lg:py-[86px] shadow-lg max-h-[720px] flex flex-col items-center justify-center aspect-video bg-white relative z-20 mx-auto"
|
||||
|
||||
>
|
||||
<h1 className="text-2xl sm:text-3xl lg:text-4xl xl:text-5xl font-bold text-gray-900 leading-tight mb-4 lg:mb-8">
|
||||
{slideData?.title || 'Chart Analysis'}
|
||||
</h1>
|
||||
|
||||
<div className={`flex w-full items-center ${isFullSizeGraph
|
||||
? "flex-col mt-4 lg:mt-10 gap-2 sm:gap-4 md:gap-6 lg:gap-10"
|
||||
: "mt-4 lg:mt-16 gap-4 sm:gap-8 md:gap-12 lg:gap-16"
|
||||
}`}>
|
||||
<div className="w-full">
|
||||
<ChartPlaceholder />
|
||||
</div>
|
||||
<div className="w-full text-center">
|
||||
<p className={`text-base sm:text-lg lg:text-xl text-gray-700 leading-relaxed ${isFullSizeGraph ? 'text-center' : ''}`}>
|
||||
{slideData?.description || 'This chart shows important data trends and insights that help understand the current situation and make informed decisions.'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Type4SlideLayout
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"description": "Clean, corporate designs perfect for business presentations",
|
||||
"ordered": false,
|
||||
"isDefault": false
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue