-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTemplatesGallery.tsx
More file actions
126 lines (114 loc) · 3.91 KB
/
Copy pathTemplatesGallery.tsx
File metadata and controls
126 lines (114 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
'use client';
import { useRouter } from 'next/navigation';
import { useMemo } from 'react';
import { ParseError, parseDeck } from '@/ir/parse';
import { planDeck } from '@/ir/plan';
import { createDeck } from '@/storage/deck-store';
import { DeckRenderer } from '@/render/DeckRenderer';
import {
AppTopbar,
Caption,
GalleryGrid,
Heading,
Label,
PageMain,
PageShell,
PageWorkbar,
} from '@/components';
import { TEMPLATE_PRESETS, type TemplatePreset } from './template-presets';
export function TemplatesGallery() {
const router = useRouter();
const applyTemplate = async (preset: TemplatePreset) => {
try {
const deck = await createDeck({
source: preset.seed,
theme: {
styleId: preset.styleId,
paletteId: preset.paletteId,
density: preset.density,
mode: preset.mode,
},
templateName: preset.name,
});
router.push(`/d/${deck.id}/edit`);
} catch (err) {
const message = err instanceof Error ? err.message : 'Could not create deck';
window.alert(`Could not create the deck: ${message}`);
}
};
return (
<PageShell className="templates-page">
<AppTopbar />
<PageWorkbar
back={{ href: '/', label: 'Library', ariaLabel: 'Back to library' }}
title="Templates"
count={`${TEMPLATE_PRESETS.length} ${TEMPLATE_PRESETS.length === 1 ? 'preset' : 'presets'}`}
subtitle="Premium starting points. Each is a fully composed deck, ready to customize."
/>
<PageMain>
<GalleryGrid>
{TEMPLATE_PRESETS.map((preset) => (
<TemplateCard key={preset.id} preset={preset} onClick={() => applyTemplate(preset)} />
))}
</GalleryGrid>
</PageMain>
</PageShell>
);
}
function TemplateCard({ preset, onClick }: { preset: TemplatePreset; onClick: () => void }) {
const previewDeck = useMemo(() => {
try {
const parsed = parseDeck(preset.seed, {
theme: {
styleId: preset.styleId,
paletteId: preset.paletteId,
density: preset.density,
mode: preset.mode,
},
});
const planned = planDeck(parsed);
return { ok: true as const, deck: { ...planned, slides: planned.slides.slice(0, 3) } };
} catch (e) {
const message = e instanceof ParseError ? e.message : (e as Error).message;
return { ok: false as const, error: message };
}
}, [preset]);
const templateAttr = templateSlug(preset.id);
return (
<button
type="button"
className="surface-card template-card template-card--multi"
data-template={templateAttr}
onClick={onClick}
>
<div className="template-card__preview template-card__preview--multi">
<div className="template-card__scaler template-card__scaler--multi">
{previewDeck.ok ? <DeckRenderer deck={previewDeck.deck} /> : null}
</div>
<span className="template-card__chip" data-template={templateAttr}>
{preset.category}
</span>
<span className="template-card__count" aria-label={`${preset.slideCount} slides`}>
{preset.slideCount} slides
</span>
</div>
<div className="template-card__meta">
<Heading level={3} size="md">
{preset.name}
</Heading>
<Caption>{preset.vibe}</Caption>
<div className="template-card__tags">
<Label className="template-card__tag">{preset.styleId}</Label>
<Label className="template-card__tag">{preset.paletteId}</Label>
<Label className="template-card__tag">{preset.density}</Label>
<Label className="template-card__tag">{preset.mode}</Label>
</div>
</div>
</button>
);
}
function templateSlug(id: string): 'case-study-pro' | 'case-study-editorial' | 'other' {
if (id === 'case-study-pro') return 'case-study-pro';
if (id === 'case-study-editorial') return 'case-study-editorial';
return 'other';
}