@contractspec/bundle.marketing 3.8.8 → 3.8.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +73 -29
- package/CHANGELOG.md +63 -0
- package/dist/browser/components/templates/TemplateCard.js +83 -0
- package/dist/browser/components/templates/TemplateCommandDialog.js +110 -0
- package/dist/browser/components/templates/TemplatePreviewContent.js +96 -0
- package/dist/browser/components/templates/TemplatesBrowseControls.js +130 -0
- package/dist/browser/components/templates/TemplatesCatalogSection.js +307 -0
- package/dist/browser/components/templates/TemplatesClientPage.js +1020 -917
- package/dist/browser/components/templates/TemplatesHeroSection.js +87 -0
- package/dist/browser/components/templates/TemplatesNextStepsSection.js +126 -0
- package/dist/browser/components/templates/TemplatesOverlays.js +2874 -0
- package/dist/browser/components/templates/TemplatesPreviewModal.js +136 -126
- package/dist/browser/components/templates/index.js +1055 -952
- package/dist/browser/components/templates/template-catalog.js +83 -0
- package/dist/browser/components/templates/template-filters.js +99 -0
- package/dist/browser/components/templates/template-new.js +23 -0
- package/dist/browser/components/templates/template-preview.js +43 -0
- package/dist/browser/components/templates/template-source.js +19 -0
- package/dist/browser/components/templates/template-tag-visibility.js +40 -0
- package/dist/browser/components/templates/useTemplateBrowseState.js +191 -0
- package/dist/browser/index.js +1055 -952
- package/dist/components/templates/TemplateCard.d.ts +12 -0
- package/dist/components/templates/TemplateCard.js +78 -0
- package/dist/components/templates/TemplateCommandDialog.d.ts +6 -0
- package/dist/components/templates/TemplateCommandDialog.js +105 -0
- package/dist/components/templates/TemplatePreviewContent.d.ts +5 -0
- package/dist/components/templates/TemplatePreviewContent.js +91 -0
- package/dist/components/templates/TemplatesBrowseControls.d.ts +18 -0
- package/dist/components/templates/TemplatesBrowseControls.js +125 -0
- package/dist/components/templates/TemplatesCatalogSection.d.ts +17 -0
- package/dist/components/templates/TemplatesCatalogSection.js +302 -0
- package/dist/components/templates/TemplatesClientPage.js +1020 -917
- package/dist/components/templates/TemplatesHeroSection.d.ts +5 -0
- package/dist/components/templates/TemplatesHeroSection.js +82 -0
- package/dist/components/templates/TemplatesNextStepsSection.d.ts +1 -0
- package/dist/components/templates/TemplatesNextStepsSection.js +121 -0
- package/dist/components/templates/TemplatesOverlays.d.ts +10 -0
- package/dist/components/templates/TemplatesOverlays.js +2869 -0
- package/dist/components/templates/TemplatesPreviewModal.d.ts +3 -4
- package/dist/components/templates/TemplatesPreviewModal.js +136 -126
- package/dist/components/templates/index.js +1055 -952
- package/dist/components/templates/template-catalog.d.ts +28 -0
- package/dist/components/templates/template-catalog.js +78 -0
- package/dist/components/templates/template-catalog.test.d.ts +1 -0
- package/dist/components/templates/template-filters.d.ts +12 -0
- package/dist/components/templates/template-filters.js +94 -0
- package/dist/components/templates/template-new.d.ts +2 -0
- package/dist/components/templates/template-new.js +18 -0
- package/dist/components/templates/template-preview.d.ts +18 -0
- package/dist/components/templates/template-preview.js +38 -0
- package/dist/components/templates/template-source.d.ts +3 -0
- package/dist/components/templates/template-source.js +14 -0
- package/dist/components/templates/template-tag-visibility.d.ts +10 -0
- package/dist/components/templates/template-tag-visibility.js +35 -0
- package/dist/components/templates/useTemplateBrowseState.d.ts +22 -0
- package/dist/components/templates/useTemplateBrowseState.js +186 -0
- package/dist/index.js +1055 -952
- package/dist/node/components/templates/TemplateCard.js +78 -0
- package/dist/node/components/templates/TemplateCommandDialog.js +105 -0
- package/dist/node/components/templates/TemplatePreviewContent.js +91 -0
- package/dist/node/components/templates/TemplatesBrowseControls.js +125 -0
- package/dist/node/components/templates/TemplatesCatalogSection.js +302 -0
- package/dist/node/components/templates/TemplatesClientPage.js +1020 -917
- package/dist/node/components/templates/TemplatesHeroSection.js +82 -0
- package/dist/node/components/templates/TemplatesNextStepsSection.js +121 -0
- package/dist/node/components/templates/TemplatesOverlays.js +2869 -0
- package/dist/node/components/templates/TemplatesPreviewModal.js +136 -126
- package/dist/node/components/templates/index.js +1055 -952
- package/dist/node/components/templates/template-catalog.js +78 -0
- package/dist/node/components/templates/template-filters.js +94 -0
- package/dist/node/components/templates/template-new.js +18 -0
- package/dist/node/components/templates/template-preview.js +38 -0
- package/dist/node/components/templates/template-source.js +14 -0
- package/dist/node/components/templates/template-tag-visibility.js +35 -0
- package/dist/node/components/templates/useTemplateBrowseState.js +186 -0
- package/dist/node/index.js +1055 -952
- package/package.json +237 -26
- package/src/components/templates/TemplateCard.tsx +74 -0
- package/src/components/templates/TemplateCommandDialog.tsx +92 -0
- package/src/components/templates/TemplatePreviewContent.tsx +182 -0
- package/src/components/templates/TemplatesBrowseControls.tsx +144 -0
- package/src/components/templates/TemplatesCatalogSection.tsx +191 -0
- package/src/components/templates/TemplatesClientPage.tsx +85 -773
- package/src/components/templates/TemplatesHeroSection.tsx +41 -0
- package/src/components/templates/TemplatesNextStepsSection.tsx +80 -0
- package/src/components/templates/TemplatesOverlays.tsx +65 -0
- package/src/components/templates/TemplatesPreviewModal.tsx +19 -294
- package/src/components/templates/template-catalog.test.ts +162 -0
- package/src/components/templates/template-catalog.ts +140 -0
- package/src/components/templates/template-filters.ts +57 -0
- package/src/components/templates/template-new.ts +12 -0
- package/src/components/templates/template-preview.ts +57 -0
- package/src/components/templates/template-source.ts +13 -0
- package/src/components/templates/template-tag-visibility.ts +58 -0
- package/src/components/templates/useTemplateBrowseState.ts +101 -0
- package/.turbo/turbo-prebuild.log +0 -1
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type TemplateId,
|
|
5
|
+
TemplateShell,
|
|
6
|
+
} from '@contractspec/lib.example-shared-ui';
|
|
7
|
+
import { LoadingSpinner } from '@contractspec/lib.ui-kit-web/ui/atoms/LoadingSpinner';
|
|
8
|
+
import dynamic from 'next/dynamic';
|
|
9
|
+
import type { ComponentType } from 'react';
|
|
10
|
+
|
|
11
|
+
const SaasDashboard = dynamic(
|
|
12
|
+
() =>
|
|
13
|
+
import('@contractspec/example.saas-boilerplate').then(
|
|
14
|
+
(module) => module.SaasDashboard
|
|
15
|
+
),
|
|
16
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const CrmDashboard = dynamic(
|
|
20
|
+
() =>
|
|
21
|
+
import('@contractspec/example.crm-pipeline').then(
|
|
22
|
+
(module) => module.CrmDashboard
|
|
23
|
+
),
|
|
24
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const DataGridShowcase = dynamic(
|
|
28
|
+
() =>
|
|
29
|
+
import('@contractspec/example.data-grid-showcase/ui').then(
|
|
30
|
+
(module) => module.DataGridShowcase
|
|
31
|
+
),
|
|
32
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const VisualizationShowcase = dynamic(
|
|
36
|
+
() =>
|
|
37
|
+
import('@contractspec/example.visualization-showcase/ui').then(
|
|
38
|
+
(module) => module.VisualizationShowcase
|
|
39
|
+
),
|
|
40
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const AgentDashboard = dynamic(
|
|
44
|
+
() =>
|
|
45
|
+
import('@contractspec/example.agent-console/ui').then(
|
|
46
|
+
(module) => module.AgentDashboard
|
|
47
|
+
),
|
|
48
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const AiChatAssistantDashboard = dynamic(
|
|
52
|
+
() =>
|
|
53
|
+
import('@contractspec/example.ai-chat-assistant').then(
|
|
54
|
+
(module) => module.AiChatAssistantDashboard
|
|
55
|
+
),
|
|
56
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const WorkflowDashboard = dynamic(
|
|
60
|
+
() =>
|
|
61
|
+
import('@contractspec/example.workflow-system/ui').then(
|
|
62
|
+
(module) => module.WorkflowDashboard
|
|
63
|
+
),
|
|
64
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const MarketplaceDashboard = dynamic(
|
|
68
|
+
() =>
|
|
69
|
+
import('@contractspec/example.marketplace/ui').then(
|
|
70
|
+
(module) => module.MarketplaceDashboard
|
|
71
|
+
),
|
|
72
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const IntegrationDashboard = dynamic(
|
|
76
|
+
() =>
|
|
77
|
+
import('@contractspec/example.integration-hub/ui').then(
|
|
78
|
+
(module) => module.IntegrationDashboard
|
|
79
|
+
),
|
|
80
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const AnalyticsDashboard = dynamic(
|
|
84
|
+
() =>
|
|
85
|
+
import('@contractspec/example.analytics-dashboard').then(
|
|
86
|
+
(module) => module.AnalyticsDashboard
|
|
87
|
+
),
|
|
88
|
+
{ ssr: false, loading: () => <LoadingSpinner /> }
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
interface PreviewDefinition {
|
|
92
|
+
title: string;
|
|
93
|
+
description: string;
|
|
94
|
+
component: ComponentType;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const PREVIEW_DEFINITIONS: Partial<Record<TemplateId, PreviewDefinition>> = {
|
|
98
|
+
'saas-boilerplate': {
|
|
99
|
+
title: 'SaaS Boilerplate',
|
|
100
|
+
description:
|
|
101
|
+
'Multi-tenant organizations, projects, settings, and billing usage tracking.',
|
|
102
|
+
component: SaasDashboard,
|
|
103
|
+
},
|
|
104
|
+
'crm-pipeline': {
|
|
105
|
+
title: 'CRM Pipeline',
|
|
106
|
+
description:
|
|
107
|
+
'Sales CRM with contacts, companies, deals, and pipeline stages.',
|
|
108
|
+
component: CrmDashboard,
|
|
109
|
+
},
|
|
110
|
+
'data-grid-showcase': {
|
|
111
|
+
title: 'Data Grid Showcase',
|
|
112
|
+
description:
|
|
113
|
+
'Shared ContractSpec table primitives with client, server, and DataView-driven lanes.',
|
|
114
|
+
component: DataGridShowcase,
|
|
115
|
+
},
|
|
116
|
+
'visualization-showcase': {
|
|
117
|
+
title: 'Visualization Showcase',
|
|
118
|
+
description:
|
|
119
|
+
'ContractSpec-owned chart primitives rendered through shared visualization contracts and design-system wrappers.',
|
|
120
|
+
component: VisualizationShowcase,
|
|
121
|
+
},
|
|
122
|
+
'agent-console': {
|
|
123
|
+
title: 'AI Agent Console',
|
|
124
|
+
description:
|
|
125
|
+
'AI agent orchestration with tools, agents, runs, and execution logs.',
|
|
126
|
+
component: AgentDashboard,
|
|
127
|
+
},
|
|
128
|
+
'ai-chat-assistant': {
|
|
129
|
+
title: 'AI Chat Assistant',
|
|
130
|
+
description:
|
|
131
|
+
'Focused assistant surface with reasoning, sources, suggestions, and MCP-aware tools.',
|
|
132
|
+
component: AiChatAssistantDashboard,
|
|
133
|
+
},
|
|
134
|
+
'workflow-system': {
|
|
135
|
+
title: 'Workflow System',
|
|
136
|
+
description: 'Multi-step workflows with role-based approvals.',
|
|
137
|
+
component: WorkflowDashboard,
|
|
138
|
+
},
|
|
139
|
+
marketplace: {
|
|
140
|
+
title: 'Marketplace',
|
|
141
|
+
description:
|
|
142
|
+
'Two-sided marketplace with stores, products, orders, and payouts.',
|
|
143
|
+
component: MarketplaceDashboard,
|
|
144
|
+
},
|
|
145
|
+
'integration-hub': {
|
|
146
|
+
title: 'Integration Hub',
|
|
147
|
+
description:
|
|
148
|
+
'Third-party integrations with connections, sync configs, and field mapping.',
|
|
149
|
+
component: IntegrationDashboard,
|
|
150
|
+
},
|
|
151
|
+
'analytics-dashboard': {
|
|
152
|
+
title: 'Analytics Dashboard',
|
|
153
|
+
description: 'Custom dashboards with widgets and queries.',
|
|
154
|
+
component: AnalyticsDashboard,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export interface TemplatePreviewContentProps {
|
|
159
|
+
templateId: TemplateId;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function TemplatePreviewContent({
|
|
163
|
+
templateId,
|
|
164
|
+
}: TemplatePreviewContentProps) {
|
|
165
|
+
const preview = PREVIEW_DEFINITIONS[templateId];
|
|
166
|
+
|
|
167
|
+
if (!preview) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const PreviewComponent = preview.component;
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<TemplateShell
|
|
175
|
+
title={preview.title}
|
|
176
|
+
description={preview.description}
|
|
177
|
+
showSaveAction={false}
|
|
178
|
+
>
|
|
179
|
+
<PreviewComponent />
|
|
180
|
+
</TemplateShell>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '@contractspec/lib.ui-kit-core/utils';
|
|
4
|
+
import { Search } from 'lucide-react';
|
|
5
|
+
import type { TemplateSource } from './template-source';
|
|
6
|
+
import type { TemplateTagFacet } from './template-tag-visibility';
|
|
7
|
+
|
|
8
|
+
export interface TemplatesBrowseControlsProps {
|
|
9
|
+
registryConfigured: boolean;
|
|
10
|
+
availableSources: readonly TemplateSource[];
|
|
11
|
+
source: TemplateSource;
|
|
12
|
+
onSourceChange: (source: TemplateSource) => void;
|
|
13
|
+
search: string;
|
|
14
|
+
onSearchChange: (value: string) => void;
|
|
15
|
+
selectedTag: string | null;
|
|
16
|
+
onTagChange: (tag: string | null) => void;
|
|
17
|
+
showTagFilters: boolean;
|
|
18
|
+
visibleTagFacets: readonly TemplateTagFacet[];
|
|
19
|
+
hiddenTagFacets: readonly TemplateTagFacet[];
|
|
20
|
+
showAllTags: boolean;
|
|
21
|
+
onShowAllTagsChange: (expanded: boolean) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function TemplatesBrowseControls({
|
|
25
|
+
registryConfigured,
|
|
26
|
+
availableSources,
|
|
27
|
+
source,
|
|
28
|
+
onSourceChange,
|
|
29
|
+
search,
|
|
30
|
+
onSearchChange,
|
|
31
|
+
selectedTag,
|
|
32
|
+
onTagChange,
|
|
33
|
+
showTagFilters,
|
|
34
|
+
visibleTagFacets,
|
|
35
|
+
hiddenTagFacets,
|
|
36
|
+
showAllTags,
|
|
37
|
+
onShowAllTagsChange,
|
|
38
|
+
}: TemplatesBrowseControlsProps) {
|
|
39
|
+
return (
|
|
40
|
+
<section className="editorial-section">
|
|
41
|
+
<div className="editorial-shell space-y-6">
|
|
42
|
+
<div className="flex flex-col gap-6 lg:flex-row lg:items-end lg:justify-between">
|
|
43
|
+
<div className="max-w-3xl space-y-3">
|
|
44
|
+
<p className="editorial-kicker">Browse by source</p>
|
|
45
|
+
<h2 className="font-serif text-4xl tracking-[-0.04em]">
|
|
46
|
+
Use local scenarios for core proof, then scan the community.
|
|
47
|
+
</h2>
|
|
48
|
+
<p className="text-muted-foreground text-sm leading-7">
|
|
49
|
+
{registryConfigured
|
|
50
|
+
? 'Local templates show the official adoption path. Community templates show where the ecosystem is pushing the system next.'
|
|
51
|
+
: 'Local templates show the official adoption path. Community browsing appears automatically when a registry URL is configured.'}
|
|
52
|
+
</p>
|
|
53
|
+
</div>
|
|
54
|
+
{registryConfigured ? (
|
|
55
|
+
<div className="flex gap-2">
|
|
56
|
+
{availableSources.map((option) => (
|
|
57
|
+
<button
|
|
58
|
+
key={option}
|
|
59
|
+
onClick={() => onSourceChange(option)}
|
|
60
|
+
className={cn(
|
|
61
|
+
'rounded-full px-4 py-2 font-medium text-sm transition-colors',
|
|
62
|
+
{
|
|
63
|
+
'bg-primary text-primary-foreground': source === option,
|
|
64
|
+
'border border-border bg-card hover:bg-card/80':
|
|
65
|
+
source !== option,
|
|
66
|
+
}
|
|
67
|
+
)}
|
|
68
|
+
aria-pressed={source === option}
|
|
69
|
+
>
|
|
70
|
+
{option === 'local' ? 'Local' : 'Community'}
|
|
71
|
+
</button>
|
|
72
|
+
))}
|
|
73
|
+
</div>
|
|
74
|
+
) : null}
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<div className="editorial-panel space-y-5">
|
|
78
|
+
<div className="relative">
|
|
79
|
+
<Search
|
|
80
|
+
className="absolute top-3.5 left-4 text-muted-foreground"
|
|
81
|
+
size={18}
|
|
82
|
+
/>
|
|
83
|
+
<input
|
|
84
|
+
type="text"
|
|
85
|
+
placeholder="Search scenarios, industries, or tags"
|
|
86
|
+
value={search}
|
|
87
|
+
onChange={(event) => onSearchChange(event.target.value)}
|
|
88
|
+
className="w-full rounded-full border border-border bg-background px-12 py-3 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
89
|
+
aria-label="Search templates"
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
{showTagFilters ? (
|
|
93
|
+
<div className="space-y-3">
|
|
94
|
+
<div className="flex flex-wrap gap-2">
|
|
95
|
+
<button
|
|
96
|
+
onClick={() => onTagChange(null)}
|
|
97
|
+
className={cn(
|
|
98
|
+
'rounded-full px-4 py-2 font-medium text-sm transition-colors',
|
|
99
|
+
{
|
|
100
|
+
'bg-primary text-primary-foreground':
|
|
101
|
+
selectedTag === null,
|
|
102
|
+
'border border-border bg-card hover:bg-card/80':
|
|
103
|
+
selectedTag !== null,
|
|
104
|
+
}
|
|
105
|
+
)}
|
|
106
|
+
aria-pressed={selectedTag === null}
|
|
107
|
+
>
|
|
108
|
+
All
|
|
109
|
+
</button>
|
|
110
|
+
{visibleTagFacets.map((facet) => (
|
|
111
|
+
<button
|
|
112
|
+
key={facet.tag}
|
|
113
|
+
onClick={() => onTagChange(facet.tag)}
|
|
114
|
+
className={cn(
|
|
115
|
+
'rounded-full px-4 py-2 font-medium text-sm transition-colors',
|
|
116
|
+
{
|
|
117
|
+
'bg-primary text-primary-foreground':
|
|
118
|
+
selectedTag === facet.tag,
|
|
119
|
+
'border border-border bg-card hover:bg-card/80':
|
|
120
|
+
selectedTag !== facet.tag,
|
|
121
|
+
}
|
|
122
|
+
)}
|
|
123
|
+
aria-pressed={selectedTag === facet.tag}
|
|
124
|
+
>
|
|
125
|
+
{facet.tag}
|
|
126
|
+
</button>
|
|
127
|
+
))}
|
|
128
|
+
</div>
|
|
129
|
+
{hiddenTagFacets.length > 0 || showAllTags ? (
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
onClick={() => onShowAllTagsChange(!showAllTags)}
|
|
133
|
+
className="text-muted-foreground text-sm transition-colors hover:text-foreground"
|
|
134
|
+
>
|
|
135
|
+
{showAllTags ? 'Show fewer' : 'More tags'}
|
|
136
|
+
</button>
|
|
137
|
+
) : null}
|
|
138
|
+
</div>
|
|
139
|
+
) : null}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</section>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { RegistryTemplate } from '@contractspec/lib.example-shared-ui';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { TemplateCard } from './TemplateCard';
|
|
6
|
+
import {
|
|
7
|
+
formatExampleKindLabel,
|
|
8
|
+
formatStabilityLabel,
|
|
9
|
+
type LocalTemplateCatalogItem,
|
|
10
|
+
} from './template-catalog';
|
|
11
|
+
import {
|
|
12
|
+
getLocalTemplatePreviewAction,
|
|
13
|
+
getRegistryTemplatePreviewAction,
|
|
14
|
+
} from './template-preview';
|
|
15
|
+
import type { TemplateSource } from './template-source';
|
|
16
|
+
|
|
17
|
+
export interface TemplatesCatalogSectionProps {
|
|
18
|
+
source: TemplateSource;
|
|
19
|
+
registryConfigured: boolean;
|
|
20
|
+
registryLoading: boolean;
|
|
21
|
+
registryHasTemplates: boolean;
|
|
22
|
+
localTemplates: readonly LocalTemplateCatalogItem[];
|
|
23
|
+
registryTemplates: readonly RegistryTemplate[];
|
|
24
|
+
localTemplateById: ReadonlyMap<string, LocalTemplateCatalogItem>;
|
|
25
|
+
onPreview: (templateId: string) => void;
|
|
26
|
+
onUseTemplate: (templateId: string, source: TemplateSource) => void;
|
|
27
|
+
hasSearch: boolean;
|
|
28
|
+
selectedTag: string | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function TemplatesCatalogSection({
|
|
32
|
+
source,
|
|
33
|
+
registryConfigured,
|
|
34
|
+
registryLoading,
|
|
35
|
+
registryHasTemplates,
|
|
36
|
+
localTemplates,
|
|
37
|
+
registryTemplates,
|
|
38
|
+
localTemplateById,
|
|
39
|
+
onPreview,
|
|
40
|
+
onUseTemplate,
|
|
41
|
+
hasSearch,
|
|
42
|
+
selectedTag,
|
|
43
|
+
}: TemplatesCatalogSectionProps) {
|
|
44
|
+
const showRegistry = source === 'registry' && registryConfigured;
|
|
45
|
+
const emptyStateMessage = getEmptyStateMessage(hasSearch, selectedTag);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<section className="section-padding">
|
|
49
|
+
<div className="editorial-shell">
|
|
50
|
+
{showRegistry ? (
|
|
51
|
+
registryLoading ? (
|
|
52
|
+
<div className="py-12 text-center">
|
|
53
|
+
<p className="text-muted-foreground">
|
|
54
|
+
Loading community templates…
|
|
55
|
+
</p>
|
|
56
|
+
</div>
|
|
57
|
+
) : !registryHasTemplates ? (
|
|
58
|
+
<div className="py-12 text-center">
|
|
59
|
+
<p className="text-muted-foreground">
|
|
60
|
+
No community templates found.
|
|
61
|
+
</p>
|
|
62
|
+
</div>
|
|
63
|
+
) : registryTemplates.length === 0 ? (
|
|
64
|
+
<div className="py-12 text-center">
|
|
65
|
+
<p className="text-muted-foreground">{emptyStateMessage}</p>
|
|
66
|
+
</div>
|
|
67
|
+
) : (
|
|
68
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
69
|
+
{registryTemplates.map((template) => {
|
|
70
|
+
const localTemplate = localTemplateById.get(template.id);
|
|
71
|
+
const previewAction = getRegistryTemplatePreviewAction(
|
|
72
|
+
template,
|
|
73
|
+
localTemplate
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<TemplateCard
|
|
78
|
+
key={template.id}
|
|
79
|
+
title={template.name}
|
|
80
|
+
description={template.description}
|
|
81
|
+
metaBadges={['Community']}
|
|
82
|
+
tags={template.tags}
|
|
83
|
+
previewAction={
|
|
84
|
+
previewAction.kind === 'modal' ? (
|
|
85
|
+
<button
|
|
86
|
+
className="btn-ghost flex-1 text-center text-xs"
|
|
87
|
+
onClick={() => onPreview(template.id)}
|
|
88
|
+
>
|
|
89
|
+
Preview
|
|
90
|
+
</button>
|
|
91
|
+
) : previewAction.kind === 'sandbox' ? (
|
|
92
|
+
<Link
|
|
93
|
+
href={previewAction.href}
|
|
94
|
+
className="btn-ghost flex-1 text-center text-xs"
|
|
95
|
+
>
|
|
96
|
+
Open Sandbox
|
|
97
|
+
</Link>
|
|
98
|
+
) : (
|
|
99
|
+
<button
|
|
100
|
+
className="btn-ghost flex-1 cursor-not-allowed text-center text-xs opacity-60"
|
|
101
|
+
type="button"
|
|
102
|
+
disabled
|
|
103
|
+
>
|
|
104
|
+
Preview Unavailable
|
|
105
|
+
</button>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
useAction={
|
|
109
|
+
<button
|
|
110
|
+
className="btn-primary flex-1 text-center text-xs"
|
|
111
|
+
onClick={() => onUseTemplate(template.id, 'registry')}
|
|
112
|
+
>
|
|
113
|
+
Use Template
|
|
114
|
+
</button>
|
|
115
|
+
}
|
|
116
|
+
/>
|
|
117
|
+
);
|
|
118
|
+
})}
|
|
119
|
+
</div>
|
|
120
|
+
)
|
|
121
|
+
) : localTemplates.length === 0 ? (
|
|
122
|
+
<div className="py-12 text-center">
|
|
123
|
+
<p className="text-muted-foreground">{emptyStateMessage}</p>
|
|
124
|
+
</div>
|
|
125
|
+
) : (
|
|
126
|
+
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
127
|
+
{localTemplates.map((template) => {
|
|
128
|
+
const previewAction = getLocalTemplatePreviewAction(template);
|
|
129
|
+
|
|
130
|
+
return (
|
|
131
|
+
<TemplateCard
|
|
132
|
+
key={template.id}
|
|
133
|
+
title={template.title}
|
|
134
|
+
description={template.description}
|
|
135
|
+
isNew={template.isNew}
|
|
136
|
+
metaBadges={[
|
|
137
|
+
formatExampleKindLabel(template.kind),
|
|
138
|
+
formatStabilityLabel(template.stability),
|
|
139
|
+
]}
|
|
140
|
+
tags={template.tags}
|
|
141
|
+
featureList={template.featureList}
|
|
142
|
+
previewAction={
|
|
143
|
+
previewAction.kind === 'modal' ? (
|
|
144
|
+
<button
|
|
145
|
+
className="btn-ghost flex-1 text-center text-xs"
|
|
146
|
+
onClick={() => onPreview(template.id)}
|
|
147
|
+
>
|
|
148
|
+
Preview
|
|
149
|
+
</button>
|
|
150
|
+
) : (
|
|
151
|
+
<Link
|
|
152
|
+
href={previewAction.href}
|
|
153
|
+
className="btn-ghost flex-1 text-center text-xs"
|
|
154
|
+
>
|
|
155
|
+
Open Sandbox
|
|
156
|
+
</Link>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
useAction={
|
|
160
|
+
<button
|
|
161
|
+
className="btn-primary flex-1 text-center text-xs"
|
|
162
|
+
onClick={() => onUseTemplate(template.id, 'local')}
|
|
163
|
+
>
|
|
164
|
+
Use Template
|
|
165
|
+
</button>
|
|
166
|
+
}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
})}
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
173
|
+
</section>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getEmptyStateMessage(
|
|
178
|
+
hasSearch: boolean,
|
|
179
|
+
selectedTag: string | null
|
|
180
|
+
): string {
|
|
181
|
+
if (selectedTag !== null && hasSearch) {
|
|
182
|
+
return 'No templates match this tag for the current search.';
|
|
183
|
+
}
|
|
184
|
+
if (selectedTag !== null) {
|
|
185
|
+
return 'No templates match this tag. Try another tag or reset filters.';
|
|
186
|
+
}
|
|
187
|
+
if (hasSearch) {
|
|
188
|
+
return 'No templates match your search. Try a different keyword.';
|
|
189
|
+
}
|
|
190
|
+
return 'No templates match your filters. Try a different search.';
|
|
191
|
+
}
|