@contractspec/bundle.marketing 3.8.9 → 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.
Files changed (49) hide show
  1. package/.turbo/turbo-build.log +54 -42
  2. package/CHANGELOG.md +33 -0
  3. package/dist/browser/components/templates/TemplatesBrowseControls.js +37 -22
  4. package/dist/browser/components/templates/TemplatesCatalogSection.js +29 -6
  5. package/dist/browser/components/templates/TemplatesClientPage.js +269 -89
  6. package/dist/browser/components/templates/TemplatesOverlays.js +2874 -0
  7. package/dist/browser/components/templates/index.js +301 -121
  8. package/dist/browser/components/templates/template-catalog.js +5 -3
  9. package/dist/browser/components/templates/template-filters.js +99 -0
  10. package/dist/browser/components/templates/template-tag-visibility.js +40 -0
  11. package/dist/browser/components/templates/useTemplateBrowseState.js +191 -0
  12. package/dist/browser/index.js +301 -121
  13. package/dist/components/templates/TemplatesBrowseControls.d.ts +7 -2
  14. package/dist/components/templates/TemplatesBrowseControls.js +37 -22
  15. package/dist/components/templates/TemplatesCatalogSection.d.ts +4 -1
  16. package/dist/components/templates/TemplatesCatalogSection.js +29 -6
  17. package/dist/components/templates/TemplatesClientPage.js +269 -89
  18. package/dist/components/templates/TemplatesOverlays.d.ts +10 -0
  19. package/dist/components/templates/TemplatesOverlays.js +2869 -0
  20. package/dist/components/templates/index.js +301 -121
  21. package/dist/components/templates/template-catalog.d.ts +1 -0
  22. package/dist/components/templates/template-catalog.js +5 -3
  23. package/dist/components/templates/template-filters.d.ts +12 -0
  24. package/dist/components/templates/template-filters.js +94 -0
  25. package/dist/components/templates/template-tag-visibility.d.ts +10 -0
  26. package/dist/components/templates/template-tag-visibility.js +35 -0
  27. package/dist/components/templates/useTemplateBrowseState.d.ts +22 -0
  28. package/dist/components/templates/useTemplateBrowseState.js +186 -0
  29. package/dist/index.js +301 -121
  30. package/dist/node/components/templates/TemplatesBrowseControls.js +37 -22
  31. package/dist/node/components/templates/TemplatesCatalogSection.js +29 -6
  32. package/dist/node/components/templates/TemplatesClientPage.js +269 -89
  33. package/dist/node/components/templates/TemplatesOverlays.js +2869 -0
  34. package/dist/node/components/templates/index.js +301 -121
  35. package/dist/node/components/templates/template-catalog.js +5 -3
  36. package/dist/node/components/templates/template-filters.js +94 -0
  37. package/dist/node/components/templates/template-tag-visibility.js +35 -0
  38. package/dist/node/components/templates/useTemplateBrowseState.js +186 -0
  39. package/dist/node/index.js +301 -121
  40. package/package.json +82 -26
  41. package/src/components/templates/TemplatesBrowseControls.tsx +59 -35
  42. package/src/components/templates/TemplatesCatalogSection.tsx +29 -4
  43. package/src/components/templates/TemplatesClientPage.tsx +41 -97
  44. package/src/components/templates/TemplatesOverlays.tsx +65 -0
  45. package/src/components/templates/template-catalog.test.ts +96 -0
  46. package/src/components/templates/template-catalog.ts +14 -6
  47. package/src/components/templates/template-filters.ts +57 -0
  48. package/src/components/templates/template-tag-visibility.ts +58 -0
  49. package/src/components/templates/useTemplateBrowseState.ts +101 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/bundle.marketing",
3
- "version": "3.8.9",
3
+ "version": "3.8.10",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "clean": "rm -rf dist",
@@ -211,6 +211,13 @@
211
211
  "node": "./dist/node/components/templates/template-catalog.js",
212
212
  "default": "./dist/components/templates/template-catalog.js"
213
213
  },
214
+ "./components/templates/template-filters": {
215
+ "types": "./dist/components/templates/template-filters.d.ts",
216
+ "browser": "./dist/browser/components/templates/template-filters.js",
217
+ "bun": "./dist/components/templates/template-filters.js",
218
+ "node": "./dist/node/components/templates/template-filters.js",
219
+ "default": "./dist/components/templates/template-filters.js"
220
+ },
214
221
  "./components/templates/template-new": {
215
222
  "types": "./dist/components/templates/template-new.d.ts",
216
223
  "browser": "./dist/browser/components/templates/template-new.js",
@@ -232,6 +239,13 @@
232
239
  "node": "./dist/node/components/templates/template-source.js",
233
240
  "default": "./dist/components/templates/template-source.js"
234
241
  },
242
+ "./components/templates/template-tag-visibility": {
243
+ "types": "./dist/components/templates/template-tag-visibility.d.ts",
244
+ "browser": "./dist/browser/components/templates/template-tag-visibility.js",
245
+ "bun": "./dist/components/templates/template-tag-visibility.js",
246
+ "node": "./dist/node/components/templates/template-tag-visibility.js",
247
+ "default": "./dist/components/templates/template-tag-visibility.js"
248
+ },
235
249
  "./components/templates/TemplateCard": {
236
250
  "types": "./dist/components/templates/TemplateCard.d.ts",
237
251
  "browser": "./dist/browser/components/templates/TemplateCard.js",
@@ -288,6 +302,13 @@
288
302
  "node": "./dist/node/components/templates/TemplatesNextStepsSection.js",
289
303
  "default": "./dist/components/templates/TemplatesNextStepsSection.js"
290
304
  },
305
+ "./components/templates/TemplatesOverlays": {
306
+ "types": "./dist/components/templates/TemplatesOverlays.d.ts",
307
+ "browser": "./dist/browser/components/templates/TemplatesOverlays.js",
308
+ "bun": "./dist/components/templates/TemplatesOverlays.js",
309
+ "node": "./dist/node/components/templates/TemplatesOverlays.js",
310
+ "default": "./dist/components/templates/TemplatesOverlays.js"
311
+ },
291
312
  "./components/templates/TemplatesPage": {
292
313
  "types": "./dist/components/templates/TemplatesPage.d.ts",
293
314
  "browser": "./dist/browser/components/templates/TemplatesPage.js",
@@ -302,6 +323,13 @@
302
323
  "node": "./dist/node/components/templates/TemplatesPreviewModal.js",
303
324
  "default": "./dist/components/templates/TemplatesPreviewModal.js"
304
325
  },
326
+ "./components/templates/useTemplateBrowseState": {
327
+ "types": "./dist/components/templates/useTemplateBrowseState.d.ts",
328
+ "browser": "./dist/browser/components/templates/useTemplateBrowseState.js",
329
+ "bun": "./dist/components/templates/useTemplateBrowseState.js",
330
+ "node": "./dist/node/components/templates/useTemplateBrowseState.js",
331
+ "default": "./dist/components/templates/useTemplateBrowseState.js"
332
+ },
305
333
  "./libs/email/client": {
306
334
  "types": "./dist/libs/email/client.d.ts",
307
335
  "browser": "./dist/browser/libs/email/client.js",
@@ -417,29 +445,29 @@
417
445
  },
418
446
  "types": "./dist/index.d.ts",
419
447
  "dependencies": {
420
- "@contractspec/bundle.library": "3.8.9",
421
- "@contractspec/lib.surface-runtime": "0.5.16",
422
- "@contractspec/example.agent-console": "3.8.8",
423
- "@contractspec/example.ai-chat-assistant": "3.8.8",
424
- "@contractspec/example.analytics-dashboard": "3.9.8",
425
- "@contractspec/example.crm-pipeline": "3.7.16",
426
- "@contractspec/example.data-grid-showcase": "3.8.8",
427
- "@contractspec/example.integration-hub": "3.8.8",
428
- "@contractspec/example.marketplace": "3.8.8",
429
- "@contractspec/example.saas-boilerplate": "3.8.8",
430
- "@contractspec/example.visualization-showcase": "3.9.8",
431
- "@contractspec/example.workflow-system": "3.8.8",
432
- "@contractspec/lib.contracts-spec": "5.0.4",
433
- "@contractspec/lib.contracts-runtime-client-react": "3.8.4",
434
- "@contractspec/lib.design-system": "3.8.9",
435
- "@contractspec/lib.email": "3.7.12",
436
- "@contractspec/lib.example-shared-ui": "6.0.16",
437
- "@contractspec/lib.logger": "3.7.12",
438
- "@contractspec/lib.runtime-sandbox": "2.7.13",
439
- "@contractspec/lib.ui-kit-core": "3.7.12",
440
- "@contractspec/lib.ui-kit-web": "3.9.8",
441
- "@contractspec/lib.ui-link": "3.7.12",
442
- "@contractspec/module.examples": "3.8.8",
448
+ "@contractspec/bundle.library": "3.8.10",
449
+ "@contractspec/lib.surface-runtime": "0.5.17",
450
+ "@contractspec/example.agent-console": "3.8.9",
451
+ "@contractspec/example.ai-chat-assistant": "3.8.9",
452
+ "@contractspec/example.analytics-dashboard": "3.9.9",
453
+ "@contractspec/example.crm-pipeline": "3.7.17",
454
+ "@contractspec/example.data-grid-showcase": "3.8.9",
455
+ "@contractspec/example.integration-hub": "3.8.9",
456
+ "@contractspec/example.marketplace": "3.8.9",
457
+ "@contractspec/example.saas-boilerplate": "3.8.9",
458
+ "@contractspec/example.visualization-showcase": "3.9.9",
459
+ "@contractspec/example.workflow-system": "3.8.9",
460
+ "@contractspec/lib.contracts-spec": "5.1.0",
461
+ "@contractspec/lib.contracts-runtime-client-react": "3.8.5",
462
+ "@contractspec/lib.design-system": "3.8.10",
463
+ "@contractspec/lib.email": "3.7.13",
464
+ "@contractspec/lib.example-shared-ui": "6.0.17",
465
+ "@contractspec/lib.logger": "3.7.13",
466
+ "@contractspec/lib.runtime-sandbox": "2.7.14",
467
+ "@contractspec/lib.ui-kit-core": "3.7.13",
468
+ "@contractspec/lib.ui-kit-web": "3.9.9",
469
+ "@contractspec/lib.ui-link": "3.7.13",
470
+ "@contractspec/module.examples": "3.8.9",
443
471
  "@electric-sql/pglite": "^0.4.2",
444
472
  "@hookform/resolvers": "^5.2.2",
445
473
  "@scaleway/sdk": "^3.4.1",
@@ -454,9 +482,9 @@
454
482
  },
455
483
  "devDependencies": {
456
484
  "@types/react": "~19.2.14",
457
- "@contractspec/tool.typescript": "3.7.12",
485
+ "@contractspec/tool.typescript": "3.7.13",
458
486
  "typescript": "^5.9.3",
459
- "@contractspec/tool.bun": "3.7.12"
487
+ "@contractspec/tool.bun": "3.7.13"
460
488
  },
461
489
  "publishConfig": {
462
490
  "access": "public",
@@ -651,6 +679,13 @@
651
679
  "node": "./dist/node/components/templates/template-catalog.js",
652
680
  "default": "./dist/components/templates/template-catalog.js"
653
681
  },
682
+ "./components/templates/template-filters": {
683
+ "types": "./dist/components/templates/template-filters.d.ts",
684
+ "browser": "./dist/browser/components/templates/template-filters.js",
685
+ "bun": "./dist/components/templates/template-filters.js",
686
+ "node": "./dist/node/components/templates/template-filters.js",
687
+ "default": "./dist/components/templates/template-filters.js"
688
+ },
654
689
  "./components/templates/template-new": {
655
690
  "types": "./dist/components/templates/template-new.d.ts",
656
691
  "browser": "./dist/browser/components/templates/template-new.js",
@@ -672,6 +707,13 @@
672
707
  "node": "./dist/node/components/templates/template-source.js",
673
708
  "default": "./dist/components/templates/template-source.js"
674
709
  },
710
+ "./components/templates/template-tag-visibility": {
711
+ "types": "./dist/components/templates/template-tag-visibility.d.ts",
712
+ "browser": "./dist/browser/components/templates/template-tag-visibility.js",
713
+ "bun": "./dist/components/templates/template-tag-visibility.js",
714
+ "node": "./dist/node/components/templates/template-tag-visibility.js",
715
+ "default": "./dist/components/templates/template-tag-visibility.js"
716
+ },
675
717
  "./components/templates/TemplateCard": {
676
718
  "types": "./dist/components/templates/TemplateCard.d.ts",
677
719
  "browser": "./dist/browser/components/templates/TemplateCard.js",
@@ -728,6 +770,13 @@
728
770
  "node": "./dist/node/components/templates/TemplatesNextStepsSection.js",
729
771
  "default": "./dist/components/templates/TemplatesNextStepsSection.js"
730
772
  },
773
+ "./components/templates/TemplatesOverlays": {
774
+ "types": "./dist/components/templates/TemplatesOverlays.d.ts",
775
+ "browser": "./dist/browser/components/templates/TemplatesOverlays.js",
776
+ "bun": "./dist/components/templates/TemplatesOverlays.js",
777
+ "node": "./dist/node/components/templates/TemplatesOverlays.js",
778
+ "default": "./dist/components/templates/TemplatesOverlays.js"
779
+ },
731
780
  "./components/templates/TemplatesPage": {
732
781
  "types": "./dist/components/templates/TemplatesPage.d.ts",
733
782
  "browser": "./dist/browser/components/templates/TemplatesPage.js",
@@ -742,6 +791,13 @@
742
791
  "node": "./dist/node/components/templates/TemplatesPreviewModal.js",
743
792
  "default": "./dist/components/templates/TemplatesPreviewModal.js"
744
793
  },
794
+ "./components/templates/useTemplateBrowseState": {
795
+ "types": "./dist/components/templates/useTemplateBrowseState.d.ts",
796
+ "browser": "./dist/browser/components/templates/useTemplateBrowseState.js",
797
+ "bun": "./dist/components/templates/useTemplateBrowseState.js",
798
+ "node": "./dist/node/components/templates/useTemplateBrowseState.js",
799
+ "default": "./dist/components/templates/useTemplateBrowseState.js"
800
+ },
745
801
  "./libs/email/client": {
746
802
  "types": "./dist/libs/email/client.d.ts",
747
803
  "browser": "./dist/browser/libs/email/client.js",
@@ -3,6 +3,7 @@
3
3
  import { cn } from '@contractspec/lib.ui-kit-core/utils';
4
4
  import { Search } from 'lucide-react';
5
5
  import type { TemplateSource } from './template-source';
6
+ import type { TemplateTagFacet } from './template-tag-visibility';
6
7
 
7
8
  export interface TemplatesBrowseControlsProps {
8
9
  registryConfigured: boolean;
@@ -13,7 +14,11 @@ export interface TemplatesBrowseControlsProps {
13
14
  onSearchChange: (value: string) => void;
14
15
  selectedTag: string | null;
15
16
  onTagChange: (tag: string | null) => void;
16
- availableTags: readonly string[];
17
+ showTagFilters: boolean;
18
+ visibleTagFacets: readonly TemplateTagFacet[];
19
+ hiddenTagFacets: readonly TemplateTagFacet[];
20
+ showAllTags: boolean;
21
+ onShowAllTagsChange: (expanded: boolean) => void;
17
22
  }
18
23
 
19
24
  export function TemplatesBrowseControls({
@@ -25,7 +30,11 @@ export function TemplatesBrowseControls({
25
30
  onSearchChange,
26
31
  selectedTag,
27
32
  onTagChange,
28
- availableTags,
33
+ showTagFilters,
34
+ visibleTagFacets,
35
+ hiddenTagFacets,
36
+ showAllTags,
37
+ onShowAllTagsChange,
29
38
  }: TemplatesBrowseControlsProps) {
30
39
  return (
31
40
  <section className="editorial-section">
@@ -80,39 +89,54 @@ export function TemplatesBrowseControls({
80
89
  aria-label="Search templates"
81
90
  />
82
91
  </div>
83
- <div className="flex flex-wrap gap-2">
84
- <button
85
- onClick={() => onTagChange(null)}
86
- className={cn(
87
- 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
88
- {
89
- 'bg-primary text-primary-foreground': selectedTag === null,
90
- 'border border-border bg-card hover:bg-card/80':
91
- selectedTag !== null,
92
- }
93
- )}
94
- aria-pressed={selectedTag === null}
95
- >
96
- All
97
- </button>
98
- {availableTags.map((tag) => (
99
- <button
100
- key={tag}
101
- onClick={() => onTagChange(tag)}
102
- className={cn(
103
- 'rounded-full px-4 py-2 font-medium text-sm transition-colors',
104
- {
105
- 'bg-primary text-primary-foreground': selectedTag === tag,
106
- 'border border-border bg-card hover:bg-card/80':
107
- selectedTag !== tag,
108
- }
109
- )}
110
- aria-pressed={selectedTag === tag}
111
- >
112
- {tag}
113
- </button>
114
- ))}
115
- </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}
116
140
  </div>
117
141
  </div>
118
142
  </section>
@@ -18,24 +18,31 @@ export interface TemplatesCatalogSectionProps {
18
18
  source: TemplateSource;
19
19
  registryConfigured: boolean;
20
20
  registryLoading: boolean;
21
+ registryHasTemplates: boolean;
21
22
  localTemplates: readonly LocalTemplateCatalogItem[];
22
23
  registryTemplates: readonly RegistryTemplate[];
23
24
  localTemplateById: ReadonlyMap<string, LocalTemplateCatalogItem>;
24
25
  onPreview: (templateId: string) => void;
25
26
  onUseTemplate: (templateId: string, source: TemplateSource) => void;
27
+ hasSearch: boolean;
28
+ selectedTag: string | null;
26
29
  }
27
30
 
28
31
  export function TemplatesCatalogSection({
29
32
  source,
30
33
  registryConfigured,
31
34
  registryLoading,
35
+ registryHasTemplates,
32
36
  localTemplates,
33
37
  registryTemplates,
34
38
  localTemplateById,
35
39
  onPreview,
36
40
  onUseTemplate,
41
+ hasSearch,
42
+ selectedTag,
37
43
  }: TemplatesCatalogSectionProps) {
38
44
  const showRegistry = source === 'registry' && registryConfigured;
45
+ const emptyStateMessage = getEmptyStateMessage(hasSearch, selectedTag);
39
46
 
40
47
  return (
41
48
  <section className="section-padding">
@@ -47,12 +54,16 @@ export function TemplatesCatalogSection({
47
54
  Loading community templates…
48
55
  </p>
49
56
  </div>
50
- ) : registryTemplates.length === 0 ? (
57
+ ) : !registryHasTemplates ? (
51
58
  <div className="py-12 text-center">
52
59
  <p className="text-muted-foreground">
53
60
  No community templates found.
54
61
  </p>
55
62
  </div>
63
+ ) : registryTemplates.length === 0 ? (
64
+ <div className="py-12 text-center">
65
+ <p className="text-muted-foreground">{emptyStateMessage}</p>
66
+ </div>
56
67
  ) : (
57
68
  <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
58
69
  {registryTemplates.map((template) => {
@@ -109,9 +120,7 @@ export function TemplatesCatalogSection({
109
120
  )
110
121
  ) : localTemplates.length === 0 ? (
111
122
  <div className="py-12 text-center">
112
- <p className="text-muted-foreground">
113
- No templates match your filters. Try a different search.
114
- </p>
123
+ <p className="text-muted-foreground">{emptyStateMessage}</p>
115
124
  </div>
116
125
  ) : (
117
126
  <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
@@ -164,3 +173,19 @@ export function TemplatesCatalogSection({
164
173
  </section>
165
174
  );
166
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
+ }
@@ -4,37 +4,15 @@ import {
4
4
  analyticsEventNames,
5
5
  captureAnalyticsEvent,
6
6
  } from '@contractspec/bundle.library/libs/posthog/client';
7
- import { useRegistryTemplates } from '@contractspec/lib.example-shared-ui';
8
- import {
9
- Dialog,
10
- DialogContent,
11
- DialogDescription,
12
- DialogHeader,
13
- DialogTitle,
14
- } from '@contractspec/lib.ui-kit-web/ui/dialog';
15
- import { useMemo, useState } from 'react';
16
- import { StudioSignupSection } from '../marketing';
17
- import { TemplateCommandDialog } from './TemplateCommandDialog';
7
+ import { useState } from 'react';
18
8
  import { TemplatesBrowseControls } from './TemplatesBrowseControls';
19
9
  import { TemplatesCatalogSection } from './TemplatesCatalogSection';
20
10
  import { TemplatesHeroSection } from './TemplatesHeroSection';
21
11
  import { TemplatesNextStepsSection } from './TemplatesNextStepsSection';
22
- import { TemplatePreviewModal } from './TemplatesPreviewModal';
23
- import {
24
- buildLocalTemplateCatalog,
25
- matchesTemplateFilters,
26
- } from './template-catalog';
27
- import {
28
- getAvailableTemplateSources,
29
- isRegistryConfigured,
30
- type TemplateSource,
31
- } from './template-source';
32
-
33
- const REGISTRY_URL = process.env.NEXT_PUBLIC_CONTRACTSPEC_REGISTRY_URL;
12
+ import { TemplatesOverlays } from './TemplatesOverlays';
13
+ import { useTemplateBrowseState } from './useTemplateBrowseState';
34
14
 
35
15
  export const TemplatesPage = () => {
36
- const [selectedTag, setSelectedTag] = useState<string | null>(null);
37
- const [search, setSearch] = useState('');
38
16
  const [previewTemplateId, setPreviewTemplateId] = useState<string | null>(
39
17
  null
40
18
  );
@@ -42,49 +20,27 @@ export const TemplatesPage = () => {
42
20
  const [selectedTemplateId, setSelectedTemplateId] = useState<string | null>(
43
21
  null
44
22
  );
45
- const [source, setSource] = useState<TemplateSource>('local');
46
-
47
- const registryConfigured = isRegistryConfigured(REGISTRY_URL);
48
- const availableSources = getAvailableTemplateSources(REGISTRY_URL);
49
- const localTemplates = useMemo(() => buildLocalTemplateCatalog(), []);
50
- const localTemplateById = useMemo(
51
- () => new Map(localTemplates.map((template) => [template.id, template])),
52
- [localTemplates]
53
- );
54
- const availableTags = useMemo(
55
- () =>
56
- Array.from(
57
- new Set(localTemplates.flatMap((template) => template.tags))
58
- ).sort((left, right) => left.localeCompare(right)),
59
- [localTemplates]
60
- );
61
-
62
- const { data: registryTemplates = [], isLoading: registryLoading } =
63
- useRegistryTemplates();
64
-
65
- const filteredLocalTemplates = useMemo(
66
- () =>
67
- localTemplates.filter((template) =>
68
- matchesTemplateFilters(template, search, selectedTag)
69
- ),
70
- [localTemplates, search, selectedTag]
71
- );
72
-
73
- const filteredRegistryTemplates = useMemo(
74
- () =>
75
- registryTemplates.filter((template) =>
76
- matchesTemplateFilters(
77
- {
78
- title: template.name,
79
- description: template.description,
80
- tags: template.tags,
81
- },
82
- search,
83
- selectedTag
84
- )
85
- ),
86
- [registryTemplates, search, selectedTag]
87
- );
23
+ const {
24
+ selectedTag,
25
+ setSelectedTag,
26
+ search,
27
+ setSearch,
28
+ source,
29
+ setSource,
30
+ showAllTags,
31
+ setShowAllTags,
32
+ registryConfigured,
33
+ availableSources,
34
+ localTemplates,
35
+ localTemplateById,
36
+ registryTemplates,
37
+ registryLoading,
38
+ localFilterState,
39
+ registryFilterState,
40
+ visibleTagFacets,
41
+ hiddenTagFacets,
42
+ showTagFilters,
43
+ } = useTemplateBrowseState();
88
44
 
89
45
  return (
90
46
  <>
@@ -102,14 +58,19 @@ export const TemplatesPage = () => {
102
58
  onSearchChange={setSearch}
103
59
  selectedTag={selectedTag}
104
60
  onTagChange={setSelectedTag}
105
- availableTags={availableTags}
61
+ showTagFilters={showTagFilters}
62
+ visibleTagFacets={visibleTagFacets}
63
+ hiddenTagFacets={hiddenTagFacets}
64
+ showAllTags={showAllTags}
65
+ onShowAllTagsChange={setShowAllTags}
106
66
  />
107
67
  <TemplatesCatalogSection
108
68
  source={source}
109
69
  registryConfigured={registryConfigured}
110
70
  registryLoading={registryLoading}
111
- localTemplates={filteredLocalTemplates}
112
- registryTemplates={filteredRegistryTemplates}
71
+ registryHasTemplates={registryTemplates.length > 0}
72
+ localTemplates={localFilterState.finalTemplates}
73
+ registryTemplates={registryFilterState.finalTemplates}
113
74
  localTemplateById={localTemplateById}
114
75
  onPreview={setPreviewTemplateId}
115
76
  onUseTemplate={(templateId, templateSource) => {
@@ -120,36 +81,19 @@ export const TemplatesPage = () => {
120
81
  });
121
82
  setSelectedTemplateId(templateId);
122
83
  }}
84
+ hasSearch={search.trim().length > 0}
85
+ selectedTag={selectedTag}
123
86
  />
124
87
  <TemplatesNextStepsSection />
125
88
  </main>
126
89
 
127
- {previewTemplateId ? (
128
- <TemplatePreviewModal
129
- templateId={previewTemplateId}
130
- onClose={() => setPreviewTemplateId(null)}
131
- />
132
- ) : null}
133
-
134
- <Dialog
135
- open={studioSignupModalOpen}
136
- onOpenChange={setStudioSignupModalOpen}
137
- >
138
- <DialogContent className="max-h-[90vh] max-w-2xl overflow-y-auto">
139
- <DialogHeader>
140
- <DialogTitle>Deploy in Studio</DialogTitle>
141
- <DialogDescription>
142
- Deploy templates in ContractSpec Studio and run the full
143
- evidence-to-spec loop with your team.
144
- </DialogDescription>
145
- </DialogHeader>
146
- <StudioSignupSection variant="compact" />
147
- </DialogContent>
148
- </Dialog>
149
-
150
- <TemplateCommandDialog
151
- templateId={selectedTemplateId}
152
- onClose={() => setSelectedTemplateId(null)}
90
+ <TemplatesOverlays
91
+ previewTemplateId={previewTemplateId}
92
+ onPreviewClose={() => setPreviewTemplateId(null)}
93
+ studioSignupModalOpen={studioSignupModalOpen}
94
+ onStudioSignupModalOpenChange={setStudioSignupModalOpen}
95
+ selectedTemplateId={selectedTemplateId}
96
+ onTemplateCommandClose={() => setSelectedTemplateId(null)}
153
97
  onDeployStudio={() => {
154
98
  setSelectedTemplateId(null);
155
99
  setStudioSignupModalOpen(true);
@@ -0,0 +1,65 @@
1
+ 'use client';
2
+
3
+ import {
4
+ Dialog,
5
+ DialogContent,
6
+ DialogDescription,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ } from '@contractspec/lib.ui-kit-web/ui/dialog';
10
+ import { StudioSignupSection } from '../marketing';
11
+ import { TemplateCommandDialog } from './TemplateCommandDialog';
12
+ import { TemplatePreviewModal } from './TemplatesPreviewModal';
13
+
14
+ export interface TemplatesOverlaysProps {
15
+ previewTemplateId: string | null;
16
+ onPreviewClose: () => void;
17
+ studioSignupModalOpen: boolean;
18
+ onStudioSignupModalOpenChange: (open: boolean) => void;
19
+ selectedTemplateId: string | null;
20
+ onTemplateCommandClose: () => void;
21
+ onDeployStudio: () => void;
22
+ }
23
+
24
+ export function TemplatesOverlays({
25
+ previewTemplateId,
26
+ onPreviewClose,
27
+ studioSignupModalOpen,
28
+ onStudioSignupModalOpenChange,
29
+ selectedTemplateId,
30
+ onTemplateCommandClose,
31
+ onDeployStudio,
32
+ }: TemplatesOverlaysProps) {
33
+ return (
34
+ <>
35
+ {previewTemplateId ? (
36
+ <TemplatePreviewModal
37
+ templateId={previewTemplateId}
38
+ onClose={onPreviewClose}
39
+ />
40
+ ) : null}
41
+
42
+ <Dialog
43
+ open={studioSignupModalOpen}
44
+ onOpenChange={onStudioSignupModalOpenChange}
45
+ >
46
+ <DialogContent className="max-h-[90vh] max-w-2xl overflow-y-auto">
47
+ <DialogHeader>
48
+ <DialogTitle>Deploy in Studio</DialogTitle>
49
+ <DialogDescription>
50
+ Deploy templates in ContractSpec Studio and run the full
51
+ evidence-to-spec loop with your team.
52
+ </DialogDescription>
53
+ </DialogHeader>
54
+ <StudioSignupSection variant="compact" />
55
+ </DialogContent>
56
+ </Dialog>
57
+
58
+ <TemplateCommandDialog
59
+ templateId={selectedTemplateId}
60
+ onClose={onTemplateCommandClose}
61
+ onDeployStudio={onDeployStudio}
62
+ />
63
+ </>
64
+ );
65
+ }