@fnd-platform/cms 1.0.0-alpha.1

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +283 -0
  3. package/lib/cms-project.d.ts +127 -0
  4. package/lib/cms-project.d.ts.map +1 -0
  5. package/lib/cms-project.js +343 -0
  6. package/lib/cms-project.js.map +1 -0
  7. package/lib/index.d.ts +11 -0
  8. package/lib/index.d.ts.map +1 -0
  9. package/lib/index.js +20 -0
  10. package/lib/index.js.map +1 -0
  11. package/lib/options.d.ts +59 -0
  12. package/lib/options.d.ts.map +1 -0
  13. package/lib/options.js +3 -0
  14. package/lib/options.js.map +1 -0
  15. package/lib/templates/admin-breadcrumbs.d.ts +13 -0
  16. package/lib/templates/admin-breadcrumbs.d.ts.map +1 -0
  17. package/lib/templates/admin-breadcrumbs.js +80 -0
  18. package/lib/templates/admin-breadcrumbs.js.map +1 -0
  19. package/lib/templates/admin-content-route.d.ts +18 -0
  20. package/lib/templates/admin-content-route.d.ts.map +1 -0
  21. package/lib/templates/admin-content-route.js +100 -0
  22. package/lib/templates/admin-content-route.js.map +1 -0
  23. package/lib/templates/admin-content-type-route.d.ts +9 -0
  24. package/lib/templates/admin-content-type-route.d.ts.map +1 -0
  25. package/lib/templates/admin-content-type-route.js +96 -0
  26. package/lib/templates/admin-content-type-route.js.map +1 -0
  27. package/lib/templates/admin-header.d.ts +13 -0
  28. package/lib/templates/admin-header.d.ts.map +1 -0
  29. package/lib/templates/admin-header.js +123 -0
  30. package/lib/templates/admin-header.js.map +1 -0
  31. package/lib/templates/admin-index.d.ts +9 -0
  32. package/lib/templates/admin-index.d.ts.map +1 -0
  33. package/lib/templates/admin-index.js +60 -0
  34. package/lib/templates/admin-index.js.map +1 -0
  35. package/lib/templates/admin-layout.d.ts +10 -0
  36. package/lib/templates/admin-layout.d.ts.map +1 -0
  37. package/lib/templates/admin-layout.js +46 -0
  38. package/lib/templates/admin-layout.js.map +1 -0
  39. package/lib/templates/admin-sidebar.d.ts +13 -0
  40. package/lib/templates/admin-sidebar.d.ts.map +1 -0
  41. package/lib/templates/admin-sidebar.js +149 -0
  42. package/lib/templates/admin-sidebar.js.map +1 -0
  43. package/lib/templates/content-editor.d.ts +10 -0
  44. package/lib/templates/content-editor.d.ts.map +1 -0
  45. package/lib/templates/content-editor.js +354 -0
  46. package/lib/templates/content-editor.js.map +1 -0
  47. package/lib/templates/content-schema.d.ts +10 -0
  48. package/lib/templates/content-schema.d.ts.map +1 -0
  49. package/lib/templates/content-schema.js +274 -0
  50. package/lib/templates/content-schema.js.map +1 -0
  51. package/lib/templates/content-table.d.ts +13 -0
  52. package/lib/templates/content-table.d.ts.map +1 -0
  53. package/lib/templates/content-table.js +177 -0
  54. package/lib/templates/content-table.js.map +1 -0
  55. package/lib/templates/content-types-examples.d.ts +19 -0
  56. package/lib/templates/content-types-examples.d.ts.map +1 -0
  57. package/lib/templates/content-types-examples.js +275 -0
  58. package/lib/templates/content-types-examples.js.map +1 -0
  59. package/lib/templates/content-types-registry.d.ts +10 -0
  60. package/lib/templates/content-types-registry.d.ts.map +1 -0
  61. package/lib/templates/content-types-registry.js +87 -0
  62. package/lib/templates/content-types-registry.js.map +1 -0
  63. package/lib/templates/content-types.d.ts +10 -0
  64. package/lib/templates/content-types.d.ts.map +1 -0
  65. package/lib/templates/content-types.js +384 -0
  66. package/lib/templates/content-types.js.map +1 -0
  67. package/lib/templates/dashboard-stats.d.ts +13 -0
  68. package/lib/templates/dashboard-stats.d.ts.map +1 -0
  69. package/lib/templates/dashboard-stats.js +117 -0
  70. package/lib/templates/dashboard-stats.js.map +1 -0
  71. package/lib/templates/editor/index.d.ts +6 -0
  72. package/lib/templates/editor/index.d.ts.map +1 -0
  73. package/lib/templates/editor/index.js +21 -0
  74. package/lib/templates/editor/index.js.map +1 -0
  75. package/lib/templates/editor/rich-text-editor.d.ts +7 -0
  76. package/lib/templates/editor/rich-text-editor.d.ts.map +1 -0
  77. package/lib/templates/editor/rich-text-editor.js +115 -0
  78. package/lib/templates/editor/rich-text-editor.js.map +1 -0
  79. package/lib/templates/editor/toolbar.d.ts +7 -0
  80. package/lib/templates/editor/toolbar.d.ts.map +1 -0
  81. package/lib/templates/editor/toolbar.js +272 -0
  82. package/lib/templates/editor/toolbar.js.map +1 -0
  83. package/lib/templates/form-fields/boolean-field.d.ts +7 -0
  84. package/lib/templates/form-fields/boolean-field.d.ts.map +1 -0
  85. package/lib/templates/form-fields/boolean-field.js +76 -0
  86. package/lib/templates/form-fields/boolean-field.js.map +1 -0
  87. package/lib/templates/form-fields/date-field.d.ts +7 -0
  88. package/lib/templates/form-fields/date-field.d.ts.map +1 -0
  89. package/lib/templates/form-fields/date-field.js +61 -0
  90. package/lib/templates/form-fields/date-field.js.map +1 -0
  91. package/lib/templates/form-fields/datetime-field.d.ts +7 -0
  92. package/lib/templates/form-fields/datetime-field.d.ts.map +1 -0
  93. package/lib/templates/form-fields/datetime-field.js +87 -0
  94. package/lib/templates/form-fields/datetime-field.js.map +1 -0
  95. package/lib/templates/form-fields/index.d.ts +23 -0
  96. package/lib/templates/form-fields/index.d.ts.map +1 -0
  97. package/lib/templates/form-fields/index.js +275 -0
  98. package/lib/templates/form-fields/index.js.map +1 -0
  99. package/lib/templates/form-fields/media-field.d.ts +10 -0
  100. package/lib/templates/form-fields/media-field.d.ts.map +1 -0
  101. package/lib/templates/form-fields/media-field.js +225 -0
  102. package/lib/templates/form-fields/media-field.js.map +1 -0
  103. package/lib/templates/form-fields/multiselect-field.d.ts +7 -0
  104. package/lib/templates/form-fields/multiselect-field.d.ts.map +1 -0
  105. package/lib/templates/form-fields/multiselect-field.js +121 -0
  106. package/lib/templates/form-fields/multiselect-field.js.map +1 -0
  107. package/lib/templates/form-fields/number-field.d.ts +7 -0
  108. package/lib/templates/form-fields/number-field.d.ts.map +1 -0
  109. package/lib/templates/form-fields/number-field.js +87 -0
  110. package/lib/templates/form-fields/number-field.js.map +1 -0
  111. package/lib/templates/form-fields/reference-field.d.ts +9 -0
  112. package/lib/templates/form-fields/reference-field.d.ts.map +1 -0
  113. package/lib/templates/form-fields/reference-field.js +145 -0
  114. package/lib/templates/form-fields/reference-field.js.map +1 -0
  115. package/lib/templates/form-fields/richtext-field.d.ts +9 -0
  116. package/lib/templates/form-fields/richtext-field.d.ts.map +1 -0
  117. package/lib/templates/form-fields/richtext-field.js +60 -0
  118. package/lib/templates/form-fields/richtext-field.js.map +1 -0
  119. package/lib/templates/form-fields/select-field.d.ts +7 -0
  120. package/lib/templates/form-fields/select-field.d.ts.map +1 -0
  121. package/lib/templates/form-fields/select-field.js +70 -0
  122. package/lib/templates/form-fields/select-field.js.map +1 -0
  123. package/lib/templates/form-fields/slug-field.d.ts +7 -0
  124. package/lib/templates/form-fields/slug-field.d.ts.map +1 -0
  125. package/lib/templates/form-fields/slug-field.js +143 -0
  126. package/lib/templates/form-fields/slug-field.js.map +1 -0
  127. package/lib/templates/form-fields/tags-field.d.ts +7 -0
  128. package/lib/templates/form-fields/tags-field.d.ts.map +1 -0
  129. package/lib/templates/form-fields/tags-field.js +172 -0
  130. package/lib/templates/form-fields/tags-field.js.map +1 -0
  131. package/lib/templates/form-fields/text-field.d.ts +7 -0
  132. package/lib/templates/form-fields/text-field.d.ts.map +1 -0
  133. package/lib/templates/form-fields/text-field.js +63 -0
  134. package/lib/templates/form-fields/text-field.js.map +1 -0
  135. package/lib/templates/form-fields/textarea-field.d.ts +7 -0
  136. package/lib/templates/form-fields/textarea-field.d.ts.map +1 -0
  137. package/lib/templates/form-fields/textarea-field.js +64 -0
  138. package/lib/templates/form-fields/textarea-field.js.map +1 -0
  139. package/lib/templates/index.d.ts +34 -0
  140. package/lib/templates/index.d.ts.map +1 -0
  141. package/lib/templates/index.js +92 -0
  142. package/lib/templates/index.js.map +1 -0
  143. package/lib/templates/media/index.d.ts +12 -0
  144. package/lib/templates/media/index.d.ts.map +1 -0
  145. package/lib/templates/media/index.js +50 -0
  146. package/lib/templates/media/index.js.map +1 -0
  147. package/lib/templates/media/media-api.d.ts +13 -0
  148. package/lib/templates/media/media-api.d.ts.map +1 -0
  149. package/lib/templates/media/media-api.js +274 -0
  150. package/lib/templates/media/media-api.js.map +1 -0
  151. package/lib/templates/media/media-grid.d.ts +14 -0
  152. package/lib/templates/media/media-grid.d.ts.map +1 -0
  153. package/lib/templates/media/media-grid.js +314 -0
  154. package/lib/templates/media/media-grid.js.map +1 -0
  155. package/lib/templates/media/media-library-route.d.ts +13 -0
  156. package/lib/templates/media/media-library-route.d.ts.map +1 -0
  157. package/lib/templates/media/media-library-route.js +105 -0
  158. package/lib/templates/media/media-library-route.js.map +1 -0
  159. package/lib/templates/media/media-picker.d.ts +13 -0
  160. package/lib/templates/media/media-picker.d.ts.map +1 -0
  161. package/lib/templates/media/media-picker.js +152 -0
  162. package/lib/templates/media/media-picker.js.map +1 -0
  163. package/lib/templates/media/media-uploader.d.ts +14 -0
  164. package/lib/templates/media/media-uploader.d.ts.map +1 -0
  165. package/lib/templates/media/media-uploader.js +318 -0
  166. package/lib/templates/media/media-uploader.js.map +1 -0
  167. package/lib/templates/recent-content.d.ts +13 -0
  168. package/lib/templates/recent-content.d.ts.map +1 -0
  169. package/lib/templates/recent-content.js +138 -0
  170. package/lib/templates/recent-content.js.map +1 -0
  171. package/lib/templates/slug-utils.d.ts +10 -0
  172. package/lib/templates/slug-utils.d.ts.map +1 -0
  173. package/lib/templates/slug-utils.js +194 -0
  174. package/lib/templates/slug-utils.js.map +1 -0
  175. package/lib/templates/ui-avatar.d.ts +8 -0
  176. package/lib/templates/ui-avatar.d.ts.map +1 -0
  177. package/lib/templates/ui-avatar.js +60 -0
  178. package/lib/templates/ui-avatar.js.map +1 -0
  179. package/lib/templates/ui-badge.d.ts +8 -0
  180. package/lib/templates/ui-badge.d.ts.map +1 -0
  181. package/lib/templates/ui-badge.js +52 -0
  182. package/lib/templates/ui-badge.js.map +1 -0
  183. package/lib/templates/ui-dialog.d.ts +10 -0
  184. package/lib/templates/ui-dialog.d.ts.map +1 -0
  185. package/lib/templates/ui-dialog.js +134 -0
  186. package/lib/templates/ui-dialog.js.map +1 -0
  187. package/lib/templates/ui-dropdown-menu.d.ts +8 -0
  188. package/lib/templates/ui-dropdown-menu.d.ts.map +1 -0
  189. package/lib/templates/ui-dropdown-menu.js +210 -0
  190. package/lib/templates/ui-dropdown-menu.js.map +1 -0
  191. package/lib/templates/ui-popover.d.ts +8 -0
  192. package/lib/templates/ui-popover.d.ts.map +1 -0
  193. package/lib/templates/ui-popover.js +43 -0
  194. package/lib/templates/ui-popover.js.map +1 -0
  195. package/lib/templates/ui-progress.d.ts +10 -0
  196. package/lib/templates/ui-progress.d.ts.map +1 -0
  197. package/lib/templates/ui-progress.js +40 -0
  198. package/lib/templates/ui-progress.js.map +1 -0
  199. package/lib/templates/ui-table.d.ts +8 -0
  200. package/lib/templates/ui-table.d.ts.map +1 -0
  201. package/lib/templates/ui-table.js +129 -0
  202. package/lib/templates/ui-table.js.map +1 -0
  203. package/lib/templates/ui-tabs.d.ts +10 -0
  204. package/lib/templates/ui-tabs.d.ts.map +1 -0
  205. package/lib/templates/ui-tabs.js +67 -0
  206. package/lib/templates/ui-tabs.js.map +1 -0
  207. package/package.json +52 -0
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.getRecentContentTemplate = getRecentContentTemplate;
4
+ /**
5
+ * Generates the recent content component template.
6
+ *
7
+ * This component displays:
8
+ * - List of recently updated content items
9
+ * - Status badges
10
+ * - Content type labels
11
+ * - Time since last update
12
+ *
13
+ * @returns Template string for app/components/admin/recent-content.tsx
14
+ */
15
+ function getRecentContentTemplate() {
16
+ return `import { Link } from '@remix-run/react';
17
+ import { FileText } from 'lucide-react';
18
+ import { Card, CardContent, CardHeader, CardTitle } from '~/components/ui/card';
19
+ import { Badge } from '~/components/ui/badge';
20
+
21
+ export interface ContentItem {
22
+ id: string;
23
+ title: string;
24
+ type: string;
25
+ typeLabel: string;
26
+ status: 'draft' | 'published' | 'scheduled' | 'archived';
27
+ updatedAt: string;
28
+ }
29
+
30
+ interface RecentContentProps {
31
+ items: ContentItem[];
32
+ }
33
+
34
+ function getStatusBadgeVariant(
35
+ status: string
36
+ ): 'default' | 'secondary' | 'success' | 'warning' | 'outline' {
37
+ switch (status) {
38
+ case 'published':
39
+ return 'success';
40
+ case 'draft':
41
+ return 'secondary';
42
+ case 'scheduled':
43
+ return 'warning';
44
+ case 'archived':
45
+ return 'outline';
46
+ default:
47
+ return 'default';
48
+ }
49
+ }
50
+
51
+ function formatRelativeTime(dateString: string): string {
52
+ const date = new Date(dateString);
53
+ const now = new Date();
54
+ const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
55
+
56
+ if (diffInSeconds < 60) {
57
+ return 'Just now';
58
+ }
59
+
60
+ const diffInMinutes = Math.floor(diffInSeconds / 60);
61
+ if (diffInMinutes < 60) {
62
+ return \`\${diffInMinutes} minute\${diffInMinutes !== 1 ? 's' : ''} ago\`;
63
+ }
64
+
65
+ const diffInHours = Math.floor(diffInMinutes / 60);
66
+ if (diffInHours < 24) {
67
+ return \`\${diffInHours} hour\${diffInHours !== 1 ? 's' : ''} ago\`;
68
+ }
69
+
70
+ const diffInDays = Math.floor(diffInHours / 24);
71
+ if (diffInDays < 7) {
72
+ return \`\${diffInDays} day\${diffInDays !== 1 ? 's' : ''} ago\`;
73
+ }
74
+
75
+ return date.toLocaleDateString();
76
+ }
77
+
78
+ export function RecentContent({ items }: RecentContentProps) {
79
+ if (items.length === 0) {
80
+ return (
81
+ <Card>
82
+ <CardHeader>
83
+ <CardTitle className="text-lg">Recent Content</CardTitle>
84
+ </CardHeader>
85
+ <CardContent>
86
+ <div className="flex flex-col items-center justify-center py-8 text-center">
87
+ <FileText className="h-12 w-12 text-muted-foreground/50" />
88
+ <p className="mt-4 text-sm text-muted-foreground">
89
+ No content yet. Create your first content item to get started.
90
+ </p>
91
+ </div>
92
+ </CardContent>
93
+ </Card>
94
+ );
95
+ }
96
+
97
+ return (
98
+ <Card>
99
+ <CardHeader>
100
+ <CardTitle className="text-lg">Recent Content</CardTitle>
101
+ </CardHeader>
102
+ <CardContent>
103
+ <div className="space-y-4">
104
+ {items.map((item) => (
105
+ <div
106
+ key={item.id}
107
+ className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50"
108
+ >
109
+ <div className="flex items-center gap-3 min-w-0">
110
+ <FileText className="h-5 w-5 shrink-0 text-muted-foreground" />
111
+ <div className="min-w-0">
112
+ <Link
113
+ to={\`/admin/content/\${item.type}/\${item.id}\`}
114
+ className="block truncate font-medium hover:underline"
115
+ >
116
+ {item.title}
117
+ </Link>
118
+ <p className="text-xs text-muted-foreground">
119
+ {item.typeLabel} &middot; {formatRelativeTime(item.updatedAt)}
120
+ </p>
121
+ </div>
122
+ </div>
123
+ <Badge
124
+ variant={getStatusBadgeVariant(item.status)}
125
+ className="shrink-0 capitalize"
126
+ >
127
+ {item.status}
128
+ </Badge>
129
+ </div>
130
+ ))}
131
+ </div>
132
+ </CardContent>
133
+ </Card>
134
+ );
135
+ }
136
+ `;
137
+ }
138
+ //# sourceMappingURL=recent-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recent-content.js","sourceRoot":"","sources":["../../src/templates/recent-content.ts"],"names":[],"mappings":";;AAWA,4DA0HC;AArID;;;;;;;;;;GAUG;AACH,SAAgB,wBAAwB;IACtC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwHR,CAAC;AACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generates the slug utilities template.
3
+ *
4
+ * This template provides utilities for generating URL-friendly slugs
5
+ * from text content.
6
+ *
7
+ * @returns Template string for app/lib/slug-utils.ts
8
+ */
9
+ export declare function getSlugUtilsTemplate(): string;
10
+ //# sourceMappingURL=slug-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug-utils.d.ts","sourceRoot":"","sources":["../../src/templates/slug-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,CAqL7C"}
@@ -0,0 +1,194 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.getSlugUtilsTemplate = getSlugUtilsTemplate;
4
+ /**
5
+ * Generates the slug utilities template.
6
+ *
7
+ * This template provides utilities for generating URL-friendly slugs
8
+ * from text content.
9
+ *
10
+ * @returns Template string for app/lib/slug-utils.ts
11
+ */
12
+ function getSlugUtilsTemplate() {
13
+ return `/**
14
+ * Slug Utilities
15
+ *
16
+ * Utilities for generating URL-friendly slugs from text content.
17
+ */
18
+
19
+ /**
20
+ * Options for slug generation.
21
+ */
22
+ export interface SlugOptions {
23
+ /** Maximum length for the slug */
24
+ maxLength?: number;
25
+ /** Replace spaces with this character (default: '-') */
26
+ separator?: string;
27
+ /** Convert to lowercase (default: true) */
28
+ lowercase?: boolean;
29
+ /** Remove stop words like 'a', 'the', 'is' (default: false) */
30
+ removeStopWords?: boolean;
31
+ }
32
+
33
+ /**
34
+ * Common stop words to optionally remove from slugs.
35
+ */
36
+ const STOP_WORDS = new Set([
37
+ 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for',
38
+ 'from', 'has', 'he', 'in', 'is', 'it', 'its', 'of', 'on',
39
+ 'that', 'the', 'to', 'was', 'were', 'will', 'with',
40
+ ]);
41
+
42
+ /**
43
+ * Character replacements for common diacritics and special characters.
44
+ */
45
+ const CHAR_REPLACEMENTS: Record<string, string> = {
46
+ // Latin Extended
47
+ 'à': 'a', 'á': 'a', 'â': 'a', 'ã': 'a', 'ä': 'a', 'å': 'a', 'æ': 'ae',
48
+ 'ç': 'c', 'è': 'e', 'é': 'e', 'ê': 'e', 'ë': 'e',
49
+ 'ì': 'i', 'í': 'i', 'î': 'i', 'ï': 'i',
50
+ 'ð': 'd', 'ñ': 'n',
51
+ 'ò': 'o', 'ó': 'o', 'ô': 'o', 'õ': 'o', 'ö': 'o', 'ø': 'o',
52
+ 'ù': 'u', 'ú': 'u', 'û': 'u', 'ü': 'u',
53
+ 'ý': 'y', 'þ': 'th', 'ÿ': 'y',
54
+ // Special characters
55
+ '&': 'and',
56
+ '@': 'at',
57
+ // Currencies
58
+ '$': 'dollar',
59
+ '€': 'euro',
60
+ '£': 'pound',
61
+ '¥': 'yen',
62
+ };
63
+
64
+ /**
65
+ * Generate a URL-friendly slug from text.
66
+ *
67
+ * @example
68
+ * \`\`\`typescript
69
+ * generateSlug('Hello World!'); // 'hello-world'
70
+ * generateSlug('My Blog Post Title', { maxLength: 20 }); // 'my-blog-post-title'
71
+ * generateSlug('Café & Restaurant'); // 'cafe-and-restaurant'
72
+ * \`\`\`
73
+ */
74
+ export function generateSlug(text: string, options: SlugOptions = {}): string {
75
+ const {
76
+ maxLength = 100,
77
+ separator = '-',
78
+ lowercase = true,
79
+ removeStopWords = false,
80
+ } = options;
81
+
82
+ if (!text || typeof text !== 'string') {
83
+ return '';
84
+ }
85
+
86
+ let slug = text;
87
+
88
+ // Convert to lowercase if requested
89
+ if (lowercase) {
90
+ slug = slug.toLowerCase();
91
+ }
92
+
93
+ // Replace special characters
94
+ slug = slug
95
+ .split('')
96
+ .map((char) => CHAR_REPLACEMENTS[char] ?? char)
97
+ .join('');
98
+
99
+ // Remove HTML tags
100
+ slug = slug.replace(/<[^>]*>/g, '');
101
+
102
+ // Replace non-alphanumeric characters with separator
103
+ slug = slug.replace(/[^a-z0-9]+/gi, separator);
104
+
105
+ // Optionally remove stop words
106
+ if (removeStopWords) {
107
+ const words = slug.split(separator).filter((word) => !STOP_WORDS.has(word));
108
+ slug = words.join(separator);
109
+ }
110
+
111
+ // Remove leading/trailing separators
112
+ slug = slug.replace(new RegExp(\`^\${separator}+|\${separator}+$\`, 'g'), '');
113
+
114
+ // Collapse multiple separators
115
+ slug = slug.replace(new RegExp(\`\${separator}+\`, 'g'), separator);
116
+
117
+ // Enforce max length (trim at word boundary if possible)
118
+ if (maxLength && slug.length > maxLength) {
119
+ slug = slug.substring(0, maxLength);
120
+ const lastSeparator = slug.lastIndexOf(separator);
121
+ if (lastSeparator > maxLength / 2) {
122
+ slug = slug.substring(0, lastSeparator);
123
+ }
124
+ }
125
+
126
+ // Final cleanup
127
+ slug = slug.replace(new RegExp(\`\${separator}+$\`, 'g'), '');
128
+
129
+ return slug;
130
+ }
131
+
132
+ /**
133
+ * Validate a slug format.
134
+ *
135
+ * Valid slugs:
136
+ * - Contain only lowercase letters, numbers, and hyphens
137
+ * - Don't start or end with hyphens
138
+ * - Don't contain consecutive hyphens
139
+ */
140
+ export function isValidSlug(slug: string): boolean {
141
+ if (!slug || typeof slug !== 'string') {
142
+ return false;
143
+ }
144
+
145
+ // Check pattern
146
+ const validPattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
147
+ return validPattern.test(slug);
148
+ }
149
+
150
+ /**
151
+ * Normalize a slug to ensure it's valid.
152
+ * If the input is already a valid slug, it's returned unchanged.
153
+ * Otherwise, it's transformed using generateSlug.
154
+ */
155
+ export function normalizeSlug(input: string): string {
156
+ if (isValidSlug(input)) {
157
+ return input;
158
+ }
159
+ return generateSlug(input);
160
+ }
161
+
162
+ /**
163
+ * Generate a unique slug by appending a number if needed.
164
+ *
165
+ * @param baseSlug - The desired slug
166
+ * @param existingSlugs - Array of slugs that already exist
167
+ * @returns A unique slug
168
+ *
169
+ * @example
170
+ * \`\`\`typescript
171
+ * ensureUniqueSlug('my-post', ['my-post', 'my-post-2']);
172
+ * // Returns 'my-post-3'
173
+ * \`\`\`
174
+ */
175
+ export function ensureUniqueSlug(baseSlug: string, existingSlugs: string[]): string {
176
+ const slugSet = new Set(existingSlugs);
177
+
178
+ if (!slugSet.has(baseSlug)) {
179
+ return baseSlug;
180
+ }
181
+
182
+ let counter = 2;
183
+ let candidateSlug = \`\${baseSlug}-\${counter}\`;
184
+
185
+ while (slugSet.has(candidateSlug)) {
186
+ counter++;
187
+ candidateSlug = \`\${baseSlug}-\${counter}\`;
188
+ }
189
+
190
+ return candidateSlug;
191
+ }
192
+ `;
193
+ }
194
+ //# sourceMappingURL=slug-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug-utils.js","sourceRoot":"","sources":["../../src/templates/slug-utils.ts"],"names":[],"mappings":";;AAQA,oDAqLC;AA7LD;;;;;;;GAOG;AACH,SAAgB,oBAAoB;IAClC,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmLR,CAAC;AACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generates the Avatar component file content.
3
+ * Standard shadcn/ui avatar with Radix UI primitives.
4
+ *
5
+ * @returns app/components/ui/avatar.tsx file content
6
+ */
7
+ export declare function getAvatarTemplate(): string;
8
+ //# sourceMappingURL=ui-avatar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-avatar.d.ts","sourceRoot":"","sources":["../../src/templates/ui-avatar.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAiD1C"}
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.getAvatarTemplate = getAvatarTemplate;
4
+ /**
5
+ * Generates the Avatar component file content.
6
+ * Standard shadcn/ui avatar with Radix UI primitives.
7
+ *
8
+ * @returns app/components/ui/avatar.tsx file content
9
+ */
10
+ function getAvatarTemplate() {
11
+ return `import * as React from 'react';
12
+ import * as AvatarPrimitive from '@radix-ui/react-avatar';
13
+ import { cn } from '~/lib/utils';
14
+
15
+ const Avatar = React.forwardRef<
16
+ React.ElementRef<typeof AvatarPrimitive.Root>,
17
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
18
+ >(({ className, ...props }, ref) => (
19
+ <AvatarPrimitive.Root
20
+ ref={ref}
21
+ className={cn(
22
+ 'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ));
28
+ Avatar.displayName = AvatarPrimitive.Root.displayName;
29
+
30
+ const AvatarImage = React.forwardRef<
31
+ React.ElementRef<typeof AvatarPrimitive.Image>,
32
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
33
+ >(({ className, ...props }, ref) => (
34
+ <AvatarPrimitive.Image
35
+ ref={ref}
36
+ className={cn('aspect-square h-full w-full', className)}
37
+ {...props}
38
+ />
39
+ ));
40
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName;
41
+
42
+ const AvatarFallback = React.forwardRef<
43
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
44
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
45
+ >(({ className, ...props }, ref) => (
46
+ <AvatarPrimitive.Fallback
47
+ ref={ref}
48
+ className={cn(
49
+ 'flex h-full w-full items-center justify-center rounded-full bg-muted',
50
+ className
51
+ )}
52
+ {...props}
53
+ />
54
+ ));
55
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
56
+
57
+ export { Avatar, AvatarImage, AvatarFallback };
58
+ `;
59
+ }
60
+ //# sourceMappingURL=ui-avatar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-avatar.js","sourceRoot":"","sources":["../../src/templates/ui-avatar.ts"],"names":[],"mappings":";;AAMA,8CAiDC;AAvDD;;;;;GAKG;AACH,SAAgB,iBAAiB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CR,CAAC;AACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generates the Badge component file content.
3
+ * Standard shadcn/ui badge with variants for status display.
4
+ *
5
+ * @returns app/components/ui/badge.tsx file content
6
+ */
7
+ export declare function getBadgeTemplate(): string;
8
+ //# sourceMappingURL=ui-badge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-badge.d.ts","sourceRoot":"","sources":["../../src/templates/ui-badge.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAyCzC"}
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.getBadgeTemplate = getBadgeTemplate;
4
+ /**
5
+ * Generates the Badge component file content.
6
+ * Standard shadcn/ui badge with variants for status display.
7
+ *
8
+ * @returns app/components/ui/badge.tsx file content
9
+ */
10
+ function getBadgeTemplate() {
11
+ return `import * as React from 'react';
12
+ import { cva, type VariantProps } from 'class-variance-authority';
13
+ import { cn } from '~/lib/utils';
14
+
15
+ const badgeVariants = cva(
16
+ 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
17
+ {
18
+ variants: {
19
+ variant: {
20
+ default:
21
+ 'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
22
+ secondary:
23
+ 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
24
+ destructive:
25
+ 'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
26
+ outline: 'text-foreground',
27
+ success:
28
+ 'border-transparent bg-green-500 text-white hover:bg-green-500/80',
29
+ warning:
30
+ 'border-transparent bg-yellow-500 text-white hover:bg-yellow-500/80',
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: 'default',
35
+ },
36
+ }
37
+ );
38
+
39
+ export interface BadgeProps
40
+ extends React.HTMLAttributes<HTMLDivElement>,
41
+ VariantProps<typeof badgeVariants> {}
42
+
43
+ function Badge({ className, variant, ...props }: BadgeProps) {
44
+ return (
45
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
46
+ );
47
+ }
48
+
49
+ export { Badge, badgeVariants };
50
+ `;
51
+ }
52
+ //# sourceMappingURL=ui-badge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-badge.js","sourceRoot":"","sources":["../../src/templates/ui-badge.ts"],"names":[],"mappings":";;AAMA,4CAyCC;AA/CD;;;;;GAKG;AACH,SAAgB,gBAAgB;IAC9B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCR,CAAC;AACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generates the Dialog component template.
3
+ *
4
+ * This is a shadcn/ui-style dialog component based on Radix Dialog
5
+ * primitives, used for the media picker and other modal interactions.
6
+ *
7
+ * @returns Template string for app/components/ui/dialog.tsx
8
+ */
9
+ export declare function getDialogTemplate(): string;
10
+ //# sourceMappingURL=ui-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-dialog.d.ts","sourceRoot":"","sources":["../../src/templates/ui-dialog.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAyH1C"}
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+ Object.defineProperty(exports, '__esModule', { value: true });
3
+ exports.getDialogTemplate = getDialogTemplate;
4
+ /**
5
+ * Generates the Dialog component template.
6
+ *
7
+ * This is a shadcn/ui-style dialog component based on Radix Dialog
8
+ * primitives, used for the media picker and other modal interactions.
9
+ *
10
+ * @returns Template string for app/components/ui/dialog.tsx
11
+ */
12
+ function getDialogTemplate() {
13
+ return `import * as React from 'react';
14
+ import * as DialogPrimitive from '@radix-ui/react-dialog';
15
+ import { X } from 'lucide-react';
16
+ import { cn } from '~/lib/utils';
17
+
18
+ const Dialog = DialogPrimitive.Root;
19
+
20
+ const DialogTrigger = DialogPrimitive.Trigger;
21
+
22
+ const DialogPortal = DialogPrimitive.Portal;
23
+
24
+ const DialogClose = DialogPrimitive.Close;
25
+
26
+ const DialogOverlay = React.forwardRef<
27
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
28
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
29
+ >(({ className, ...props }, ref) => (
30
+ <DialogPrimitive.Overlay
31
+ ref={ref}
32
+ className={cn(
33
+ 'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ ));
39
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
40
+
41
+ const DialogContent = React.forwardRef<
42
+ React.ElementRef<typeof DialogPrimitive.Content>,
43
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
44
+ >(({ className, children, ...props }, ref) => (
45
+ <DialogPortal>
46
+ <DialogOverlay />
47
+ <DialogPrimitive.Content
48
+ ref={ref}
49
+ className={cn(
50
+ 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
51
+ className
52
+ )}
53
+ {...props}
54
+ >
55
+ {children}
56
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
57
+ <X className="h-4 w-4" />
58
+ <span className="sr-only">Close</span>
59
+ </DialogPrimitive.Close>
60
+ </DialogPrimitive.Content>
61
+ </DialogPortal>
62
+ ));
63
+ DialogContent.displayName = DialogPrimitive.Content.displayName;
64
+
65
+ const DialogHeader = ({
66
+ className,
67
+ ...props
68
+ }: React.HTMLAttributes<HTMLDivElement>) => (
69
+ <div
70
+ className={cn(
71
+ 'flex flex-col space-y-1.5 text-center sm:text-left',
72
+ className
73
+ )}
74
+ {...props}
75
+ />
76
+ );
77
+ DialogHeader.displayName = 'DialogHeader';
78
+
79
+ const DialogFooter = ({
80
+ className,
81
+ ...props
82
+ }: React.HTMLAttributes<HTMLDivElement>) => (
83
+ <div
84
+ className={cn(
85
+ 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ );
91
+ DialogFooter.displayName = 'DialogFooter';
92
+
93
+ const DialogTitle = React.forwardRef<
94
+ React.ElementRef<typeof DialogPrimitive.Title>,
95
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
96
+ >(({ className, ...props }, ref) => (
97
+ <DialogPrimitive.Title
98
+ ref={ref}
99
+ className={cn(
100
+ 'text-lg font-semibold leading-none tracking-tight',
101
+ className
102
+ )}
103
+ {...props}
104
+ />
105
+ ));
106
+ DialogTitle.displayName = DialogPrimitive.Title.displayName;
107
+
108
+ const DialogDescription = React.forwardRef<
109
+ React.ElementRef<typeof DialogPrimitive.Description>,
110
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
111
+ >(({ className, ...props }, ref) => (
112
+ <DialogPrimitive.Description
113
+ ref={ref}
114
+ className={cn('text-sm text-muted-foreground', className)}
115
+ {...props}
116
+ />
117
+ ));
118
+ DialogDescription.displayName = DialogPrimitive.Description.displayName;
119
+
120
+ export {
121
+ Dialog,
122
+ DialogPortal,
123
+ DialogOverlay,
124
+ DialogClose,
125
+ DialogTrigger,
126
+ DialogContent,
127
+ DialogHeader,
128
+ DialogFooter,
129
+ DialogTitle,
130
+ DialogDescription,
131
+ };
132
+ `;
133
+ }
134
+ //# sourceMappingURL=ui-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-dialog.js","sourceRoot":"","sources":["../../src/templates/ui-dialog.ts"],"names":[],"mappings":";;AAQA,8CAyHC;AAjID;;;;;;;GAOG;AACH,SAAgB,iBAAiB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuHR,CAAC;AACF,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Generates the DropdownMenu component file content.
3
+ * Standard shadcn/ui dropdown menu with Radix UI primitives.
4
+ *
5
+ * @returns app/components/ui/dropdown-menu.tsx file content
6
+ */
7
+ export declare function getDropdownMenuTemplate(): string;
8
+ //# sourceMappingURL=ui-dropdown-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-dropdown-menu.d.ts","sourceRoot":"","sources":["../../src/templates/ui-dropdown-menu.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,wBAAgB,uBAAuB,IAAI,MAAM,CAuMhD"}