@edgedev/create-edge-app 1.1.25 → 1.1.26

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 (111) hide show
  1. package/README.md +55 -20
  2. package/{agent.md → agents.md} +2 -0
  3. package/bin/cli.js +6 -6
  4. package/edge/components/auth/login.vue +384 -0
  5. package/edge/components/auth/register.vue +396 -0
  6. package/edge/components/auth.vue +108 -0
  7. package/edge/components/autoFileUpload.vue +215 -0
  8. package/edge/components/billing.vue +8 -0
  9. package/edge/components/buttonDivider.vue +14 -0
  10. package/edge/components/chip.vue +34 -0
  11. package/edge/components/clipboardButton.vue +42 -0
  12. package/edge/components/cms/block.vue +529 -0
  13. package/edge/components/cms/blockApi.vue +212 -0
  14. package/edge/components/cms/blockEditor.vue +725 -0
  15. package/edge/components/cms/blockInput.vue +66 -0
  16. package/edge/components/cms/blockPicker.vue +486 -0
  17. package/edge/components/cms/blockRender.vue +78 -0
  18. package/edge/components/cms/blockSheetContent.vue +28 -0
  19. package/edge/components/cms/codeEditor.vue +466 -0
  20. package/edge/components/cms/fontUpload.vue +327 -0
  21. package/edge/components/cms/htmlContent.vue +807 -0
  22. package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
  23. package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
  24. package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
  25. package/edge/components/cms/init_blocks/carousel.html +103 -0
  26. package/edge/components/cms/init_blocks/contact_us.html +69 -0
  27. package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
  28. package/edge/components/cms/init_blocks/footer.html +24 -0
  29. package/edge/components/cms/init_blocks/header_divider.html +7 -0
  30. package/edge/components/cms/init_blocks/hero.html +35 -0
  31. package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
  32. package/edge/components/cms/init_blocks/newsletter.html +117 -0
  33. package/edge/components/cms/init_blocks/post_content.html +7 -0
  34. package/edge/components/cms/init_blocks/post_title_header.html +21 -0
  35. package/edge/components/cms/init_blocks/posts_list.html +20 -0
  36. package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
  37. package/edge/components/cms/init_blocks/property_carousel.html +59 -0
  38. package/edge/components/cms/init_blocks/property_detail.html +112 -0
  39. package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
  40. package/edge/components/cms/init_blocks/property_results.html +137 -0
  41. package/edge/components/cms/init_blocks/property_search.html +75 -0
  42. package/edge/components/cms/init_blocks/simple_array.html +7 -0
  43. package/edge/components/cms/mediaCard.vue +116 -0
  44. package/edge/components/cms/mediaManager.vue +386 -0
  45. package/edge/components/cms/menu.vue +1103 -0
  46. package/edge/components/cms/optionsSelect.vue +107 -0
  47. package/edge/components/cms/page.vue +1785 -0
  48. package/edge/components/cms/posts.vue +1083 -0
  49. package/edge/components/cms/site.vue +1298 -0
  50. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  51. package/edge/components/cms/themeEditor.vue +426 -0
  52. package/edge/components/dashboard.vue +776 -0
  53. package/edge/components/editor.vue +671 -0
  54. package/edge/components/fileTree.vue +72 -0
  55. package/edge/components/files.vue +89 -0
  56. package/edge/components/formSubtypes/myOrgs.vue +214 -0
  57. package/edge/components/formSubtypes/users.vue +336 -0
  58. package/edge/components/functionChips.vue +57 -0
  59. package/edge/components/gError.vue +98 -0
  60. package/edge/components/gHelper.vue +67 -0
  61. package/edge/components/gInput.vue +1331 -0
  62. package/edge/components/loggingIn.vue +41 -0
  63. package/edge/components/menu.vue +137 -0
  64. package/edge/components/menuContent.vue +132 -0
  65. package/edge/components/myAccount.vue +317 -0
  66. package/edge/components/myOrganizations.vue +75 -0
  67. package/edge/components/myProfile.vue +122 -0
  68. package/edge/components/orgSwitcher.vue +25 -0
  69. package/edge/components/organizationMembers.vue +522 -0
  70. package/edge/components/organizationSettings.vue +271 -0
  71. package/edge/components/shad/breadcrumbs.vue +35 -0
  72. package/edge/components/shad/button.vue +43 -0
  73. package/edge/components/shad/checkbox.vue +73 -0
  74. package/edge/components/shad/combobox.vue +238 -0
  75. package/edge/components/shad/datepicker.vue +184 -0
  76. package/edge/components/shad/dialog.vue +32 -0
  77. package/edge/components/shad/dropdownMenu.vue +54 -0
  78. package/edge/components/shad/dropdownMenuItem.vue +21 -0
  79. package/edge/components/shad/form.vue +59 -0
  80. package/edge/components/shad/html.vue +877 -0
  81. package/edge/components/shad/input.vue +139 -0
  82. package/edge/components/shad/number.vue +109 -0
  83. package/edge/components/shad/select.vue +151 -0
  84. package/edge/components/shad/selectTags.vue +278 -0
  85. package/edge/components/shad/switch.vue +67 -0
  86. package/edge/components/shad/tags.vue +137 -0
  87. package/edge/components/shad/textarea.vue +102 -0
  88. package/edge/components/shad/typeMoney.vue +167 -0
  89. package/edge/components/sideBar.vue +288 -0
  90. package/edge/components/sideBarContent.vue +268 -0
  91. package/edge/components/sidebarProvider.vue +33 -0
  92. package/edge/components/tooltip.vue +16 -0
  93. package/edge/components/userMenu.vue +148 -0
  94. package/edge/components/v/alert.vue +59 -0
  95. package/edge/components/v/alertTitle.vue +18 -0
  96. package/edge/components/v/card.vue +53 -0
  97. package/edge/components/v/cardActions.vue +18 -0
  98. package/edge/components/v/cardText.vue +18 -0
  99. package/edge/components/v/cardTitle.vue +20 -0
  100. package/edge/components/v/col.vue +56 -0
  101. package/edge/components/v/list.vue +46 -0
  102. package/edge/components/v/listItem.vue +26 -0
  103. package/edge/components/v/listItemTitle.vue +18 -0
  104. package/edge/components/v/row.vue +42 -0
  105. package/edge/components/v/toolbar.vue +24 -0
  106. package/edge/composables/global.ts +519 -0
  107. package/edge-pull.sh +2 -0
  108. package/edge-push.sh +1 -0
  109. package/edge-status.sh +14 -0
  110. package/package.json +1 -1
  111. package/edge-components-install.sh +0 -1
@@ -0,0 +1,66 @@
1
+ <script setup>
2
+ import { useVModel } from '@vueuse/core'
3
+
4
+ const props = defineProps({
5
+ field: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ label: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ modelValue: {
14
+ type: [Object, String, Array, Number, Boolean],
15
+ required: true,
16
+ },
17
+ type: {
18
+ type: String,
19
+ required: true,
20
+ },
21
+ schema: {
22
+ type: Object,
23
+ required: false,
24
+ default: () => ({}),
25
+ },
26
+ })
27
+
28
+ const emit = defineEmits(['update:modelValue', 'delete'])
29
+
30
+ const modelValue = useVModel(props, 'modelValue', emit)
31
+
32
+ const state = reactive({
33
+ mounted: false,
34
+ })
35
+
36
+ onBeforeMount(async () => {
37
+ if (props.type === 'option' && !modelValue.value) {
38
+ modelValue.value = props.schema.value
39
+ }
40
+ await nextTick()
41
+ state.mounted = true
42
+ })
43
+ </script>
44
+
45
+ <template>
46
+ <div v-if="state.mounted">
47
+ <div v-if="props.type === 'richtext'">
48
+ <edge-shad-html v-model="modelValue" :enabled-toggles="['bold', 'italic', 'strike', 'bulletlist', 'orderedlist', 'underline']" :name="field" :label="label" />
49
+ </div>
50
+ <div v-else-if="props.type === 'textarea'">
51
+ <edge-shad-textarea v-model="modelValue" :name="field" :label="label" />
52
+ </div>
53
+ <div v-else-if="props.type === 'array'">
54
+ <edge-shad-tags v-model="modelValue" :label="label" :name="field" />
55
+ </div>
56
+ <div v-else-if="props.type === 'number'">
57
+ <edge-shad-number v-model="modelValue" :name="field" :label="label" />
58
+ </div>
59
+ <div v-else-if="props.type === 'option'">
60
+ <edge-shad-select v-model="modelValue" :name="field" :label="label" :item-title="props.schema.option.optionsKey" :item-value="props.schema.option.optionsValue" :items="props.schema.option.options" />
61
+ </div>
62
+ <div v-else>
63
+ <edge-shad-input v-model="modelValue" :name="field" :label="label" />
64
+ </div>
65
+ </div>
66
+ </template>
@@ -0,0 +1,486 @@
1
+ <script setup>
2
+ import { Plus } from 'lucide-vue-next'
3
+
4
+ const SHARED_PICKER_STATE = reactive({
5
+ hostId: null,
6
+ open: false,
7
+ activeInstance: null,
8
+ activeProps: {
9
+ siteId: '',
10
+ theme: null,
11
+ viewportMode: 'auto',
12
+ },
13
+ blocksLoaded: [],
14
+ selectedTags: ['Quick Picks'],
15
+ })
16
+
17
+ const HANDLERS = new Map()
18
+ let blocksSnapshotStarted = false
19
+
20
+ const props = defineProps({
21
+ blockOverride: {
22
+ type: Object,
23
+ default: null,
24
+ },
25
+ theme: {
26
+ type: Object,
27
+ default: null,
28
+ },
29
+ listOnly: {
30
+ type: Boolean,
31
+ default: false,
32
+ },
33
+ siteId: {
34
+ type: String,
35
+ default: '',
36
+ },
37
+ viewportMode: {
38
+ type: String,
39
+ default: 'auto',
40
+ },
41
+ })
42
+
43
+ const emit = defineEmits(['pick'])
44
+
45
+ const localState = reactive({
46
+ open: false,
47
+ blocksLoaded: [],
48
+ selectedTags: ['Quick Picks'],
49
+ })
50
+
51
+ const isSharedMode = computed(() => !props.blockOverride && !props.listOnly)
52
+ const instanceId = Symbol('blockPicker')
53
+
54
+ const edgeFirebase = inject('edgeFirebase')
55
+
56
+ const activeState = computed(() => isSharedMode.value ? SHARED_PICKER_STATE : localState)
57
+ const pickerState = activeState
58
+
59
+ const sheetOpen = computed({
60
+ get: () => isSharedMode.value ? SHARED_PICKER_STATE.open : localState.open,
61
+ set: (val) => {
62
+ if (isSharedMode.value)
63
+ SHARED_PICKER_STATE.open = val
64
+ else
65
+ localState.open = val
66
+ },
67
+ })
68
+
69
+ const isSharedHost = computed(() => isSharedMode.value && SHARED_PICKER_STATE.hostId === instanceId)
70
+
71
+ const activeProps = computed(() => isSharedMode.value ? SHARED_PICKER_STATE.activeProps : {
72
+ siteId: props.siteId,
73
+ theme: props.theme,
74
+ viewportMode: props.viewportMode,
75
+ })
76
+
77
+ const themeId = computed(() => {
78
+ const siteId = activeProps.value.siteId
79
+ if (!siteId)
80
+ return null
81
+ const site = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites`][siteId]
82
+ return site?.theme || null
83
+ })
84
+
85
+ const activeSiteId = computed(() => activeProps.value.siteId || '')
86
+ const pickerTheme = computed(() => activeProps.value.theme || null)
87
+ const pickerViewport = computed(() => activeProps.value.viewportMode || 'auto')
88
+
89
+ const blocks = computed(() => {
90
+ let blocks = []
91
+ if (edgeFirebase?.data?.[`${edgeGlobal.edgeState.organizationDocPath}/blocks`]) {
92
+ blocks = Object.values(edgeFirebase.data[`${edgeGlobal.edgeState.organizationDocPath}/blocks`])
93
+ }
94
+ if (themeId.value) {
95
+ blocks = blocks.filter(block => block.themes && block.themes.includes(themeId.value))
96
+ }
97
+ return blocks
98
+ })
99
+
100
+ const filteredBlocks = computed(() => {
101
+ const selected = pickerState.value.selectedTags
102
+ if (!selected.length)
103
+ return blocks.value
104
+
105
+ return blocks.value.filter((block) => {
106
+ const blockTags = Array.isArray(block.tags) ? block.tags : []
107
+ return selected.some(tag => blockTags.includes(tag))
108
+ })
109
+ })
110
+
111
+ async function ensureBlocksSnapshot() {
112
+ if (blocksSnapshotStarted)
113
+ return
114
+ blocksSnapshotStarted = true
115
+ await edgeFirebase.startSnapshot(`${edgeGlobal.edgeState.organizationDocPath}/blocks`)
116
+ }
117
+
118
+ // --- Begin: auto-size buttons to visual (scaled) height ---
119
+ const btnRefs = {}
120
+ const innerRefs = {}
121
+ let ro
122
+
123
+ function ensureObserver() {
124
+ if (!ro) {
125
+ ro = new ResizeObserver((entries) => {
126
+ for (const entry of entries) {
127
+ const id = entry.target.getAttribute('data-block-id')
128
+ const h = entry.target.getBoundingClientRect().height
129
+ const btn = btnRefs[id]
130
+ if (btn)
131
+ btn.style.height = `${Math.ceil(h)}px`
132
+ }
133
+ })
134
+ }
135
+ }
136
+
137
+ function setBtnRef(id, el) {
138
+ if (el)
139
+ btnRefs[id] = el
140
+ else delete btnRefs[id]
141
+ }
142
+
143
+ function setInnerRef(id, el) {
144
+ if (el) {
145
+ innerRefs[id] = el
146
+ ensureObserver()
147
+ el.setAttribute('data-block-id', id)
148
+ ro.observe(el)
149
+ }
150
+ else if (innerRefs[id]) {
151
+ ro?.unobserve(innerRefs[id])
152
+ delete innerRefs[id]
153
+ }
154
+ }
155
+
156
+ function syncAllHeights() {
157
+ for (const id in innerRefs) {
158
+ const el = innerRefs[id]
159
+ const h = el.getBoundingClientRect().height
160
+ const btn = btnRefs[id]
161
+ if (btn)
162
+ btn.style.height = `${Math.ceil(h)}px`
163
+ }
164
+ }
165
+
166
+ onBeforeUnmount(() => {
167
+ ro?.disconnect()
168
+ })
169
+ // --- End: auto-size buttons to visual (scaled) height ---
170
+
171
+ // Make object-literal-ish configs JSON-parseable (handles: title: "Main Header")
172
+
173
+ // --- End robust tag parsing ---
174
+
175
+ watch(sheetOpen, async (open) => {
176
+ if (open) {
177
+ await nextTick()
178
+ syncAllHeights()
179
+ }
180
+ })
181
+
182
+ onBeforeMount(async () => {
183
+ await ensureBlocksSnapshot()
184
+ })
185
+
186
+ onMounted(() => {
187
+ HANDLERS.set(instanceId, block => emit('pick', block))
188
+ if (isSharedMode.value && !SHARED_PICKER_STATE.hostId)
189
+ SHARED_PICKER_STATE.hostId = instanceId
190
+ })
191
+
192
+ onBeforeUnmount(() => {
193
+ HANDLERS.delete(instanceId)
194
+ if (SHARED_PICKER_STATE.hostId === instanceId) {
195
+ const next = HANDLERS.keys().next().value || null
196
+ SHARED_PICKER_STATE.hostId = next
197
+ if (!next)
198
+ SHARED_PICKER_STATE.open = false
199
+ }
200
+ })
201
+
202
+ const openPicker = () => {
203
+ if (isSharedMode.value) {
204
+ SHARED_PICKER_STATE.activeInstance = instanceId
205
+ SHARED_PICKER_STATE.activeProps = {
206
+ siteId: props.siteId,
207
+ theme: props.theme,
208
+ viewportMode: props.viewportMode,
209
+ }
210
+ if (!SHARED_PICKER_STATE.hostId)
211
+ SHARED_PICKER_STATE.hostId = instanceId
212
+ sheetOpen.value = true
213
+ }
214
+ else {
215
+ sheetOpen.value = true
216
+ }
217
+ }
218
+
219
+ const chooseBlock = (block) => {
220
+ let blockModelData = edgeGlobal.dupObject(block)
221
+
222
+ if (blockModelData?.synced && activeSiteId.value) {
223
+ console.log('Original block data:', blockModelData)
224
+ const pages = edgeFirebase.data?.[`${edgeGlobal.edgeState.organizationDocPath}/sites/${activeSiteId.value}/pages`] || {}
225
+ const pageEntries = Object.entries(pages)
226
+ for (const [pageId, pageData] of pageEntries) {
227
+ console.log('Checking page for synced block data:', pageId, pageData)
228
+ if (pageData.content && Array.isArray(pageData.content)) {
229
+ for (const pageBlock of pageData.content) {
230
+ if (pageBlock.blockId === block.docId) {
231
+ blockModelData = edgeGlobal.dupObject(pageBlock)
232
+ break
233
+ }
234
+ }
235
+ }
236
+ if (pageData.postContent && Array.isArray(pageData.postContent)) {
237
+ for (const pageBlock of pageData.postContent) {
238
+ if (pageBlock.blockId === block.docId) {
239
+ blockModelData = edgeGlobal.dupObject(pageBlock)
240
+ break
241
+ }
242
+ }
243
+ }
244
+ }
245
+ }
246
+ blockModelData.name = block.name
247
+ blockModelData.blockId = block.docId
248
+ console.log('Chosen block:', blockModelData)
249
+ if (isSharedMode.value) {
250
+ const handler = HANDLERS.get(SHARED_PICKER_STATE.activeInstance)
251
+ handler?.(blockModelData)
252
+ sheetOpen.value = false
253
+ }
254
+ else {
255
+ emit('pick', blockModelData)
256
+ sheetOpen.value = false
257
+ }
258
+ }
259
+ const loadingRender = (content) => {
260
+ content = content.replaceAll('{{loading}}', '')
261
+ content = content.replaceAll('{{loaded}}', 'hidden')
262
+ return content
263
+ }
264
+
265
+ const blockLoaded = (isLoading, index) => {
266
+ if (!isLoading && !pickerState.value.blocksLoaded.includes(index)) {
267
+ pickerState.value.blocksLoaded.push(index)
268
+ }
269
+ }
270
+
271
+ const getTagsFromBlocks = computed(() => {
272
+ const tagsSet = new Set()
273
+
274
+ Object.values(blocks.value || {}).forEach((block) => {
275
+ if (block.tags && Array.isArray(block.tags)) {
276
+ block.tags.forEach(tag => tagsSet.add(tag))
277
+ }
278
+ })
279
+
280
+ // Convert to array of objects
281
+ const tagsArray = Array.from(tagsSet).map(tag => ({ name: tag, title: tag }))
282
+
283
+ // Sort alphabetically
284
+ tagsArray.sort((a, b) => a.title.localeCompare(b.title))
285
+
286
+ // Remove "Quick Picks" if it exists
287
+ const filtered = tagsArray.filter(tag => tag.name !== 'Quick Picks')
288
+
289
+ // Always prepend it
290
+ return [{ name: 'Quick Picks', title: 'Quick Picks' }, ...filtered]
291
+ })
292
+
293
+ const hasActiveFilters = computed(() => pickerState.value.selectedTags.length > 0)
294
+
295
+ watch(getTagsFromBlocks, (tags) => {
296
+ const available = new Set(tags.map(tag => tag.name))
297
+ const filtered = pickerState.value.selectedTags.filter(tag => available.has(tag))
298
+ if (filtered.length !== pickerState.value.selectedTags.length)
299
+ pickerState.value.selectedTags = filtered
300
+ })
301
+
302
+ const toggleTag = (tag) => {
303
+ pickerState.value.selectedTags = []
304
+ pickerState.value.selectedTags.push(tag)
305
+ }
306
+
307
+ const clearTagFilters = () => {
308
+ pickerState.value.selectedTags = []
309
+ }
310
+ </script>
311
+
312
+ <template>
313
+ <div v-if="props.blockOverride">
314
+ <edge-cms-block-api
315
+ :content="props.blockOverride.content"
316
+ :values="props.blockOverride.values"
317
+ :meta="props.blockOverride.meta"
318
+ :theme="pickerTheme"
319
+ :site-id="activeSiteId"
320
+ :viewport-mode="pickerViewport"
321
+ @pending="blockLoaded($event, 'block')"
322
+ />
323
+ <edge-cms-block-render
324
+ v-if="!pickerState.blocksLoaded.includes('block')"
325
+ :content="loadingRender(props.blockOverride.content)"
326
+ :values="props.blockOverride.values"
327
+ :meta="props.blockOverride.meta"
328
+ :theme="pickerTheme"
329
+ :site-id="activeSiteId"
330
+ :viewport-mode="pickerViewport"
331
+ />
332
+ </div>
333
+ <div v-else-if="props.listOnly" class="p-6 h-[calc(100vh-50px)] overflow-hidden flex flex-col gap-4">
334
+ <div v-if="getTagsFromBlocks.length" class="flex flex-wrap items-center gap-2 text-sm">
335
+ <button
336
+ v-for="tagOption in getTagsFromBlocks"
337
+ :key="tagOption.name"
338
+ type="button"
339
+ class="px-3 py-1 rounded-full border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
340
+ :class="pickerState.selectedTags.includes(tagOption.name) ? 'bg-primary text-primary-foreground border-primary shadow-sm' : 'bg-background text-muted-foreground hover:bg-muted border-border'"
341
+ @click="toggleTag(tagOption.name)"
342
+ >
343
+ {{ tagOption.title }}
344
+ </button>
345
+ <button
346
+ v-if="hasActiveFilters"
347
+ type="button"
348
+ class="ml-auto px-3 py-1 text-xs font-medium uppercase tracking-wide text-muted-foreground border border-transparent hover:text-primary hover:border-primary/30 rounded-full transition-colors"
349
+ @click="clearTagFilters"
350
+ >
351
+ Clear filters
352
+ </button>
353
+ </div>
354
+ <div class="space-y-4 overflow-y-auto pr-1">
355
+ <template v-if="filteredBlocks.length">
356
+ <template v-for="block in filteredBlocks" :key="block.docId">
357
+ <button
358
+ :ref="el => setBtnRef(block.docId, el)"
359
+ type="button"
360
+ class="p-0 text-left hover:bg-primary text-slate-500 border !hover:text-white border-dashed cursor-pointer w-full overflow-hidden relative"
361
+ >
362
+ <div class="scale-wrapper">
363
+ <div
364
+ :ref="el => setInnerRef(block.docId, el)"
365
+ class="scale-inner scale p-4"
366
+ :data-block-id="block.docId"
367
+ >
368
+ <div class="text-4xl relative text-inherit text-center">
369
+ {{ block.name }}
370
+ </div>
371
+ <edge-cms-block-api :site-id="activeSiteId" :content="block.content" :theme="pickerTheme" :values="block.values" :meta="block.meta" :viewport-mode="pickerViewport" @pending="blockLoaded($event, block.docId)" />
372
+ <edge-cms-block-render
373
+ v-if="!pickerState.blocksLoaded.includes(block.docId)"
374
+ :content="loadingRender(block.content)"
375
+ :values="block.values"
376
+ :meta="block.meta"
377
+ :theme="pickerTheme"
378
+ :viewport-mode="pickerViewport"
379
+ />
380
+ </div>
381
+ </div>
382
+ </button>
383
+ </template>
384
+ </template>
385
+ <p v-else class="text-sm text-muted-foreground">
386
+ No blocks match the selected tags yet.
387
+ </p>
388
+ </div>
389
+ </div>
390
+ <div v-else>
391
+ <div class="flex justify-center items-center">
392
+ <edge-shad-button
393
+ class="!my-1 px-2 h-[24px] bg-secondary text-secondary-foreground hover:text-white"
394
+ @click="openPicker"
395
+ >
396
+ <Plus class="w-4 h-4" />
397
+ </edge-shad-button>
398
+ </div>
399
+ <Sheet v-if="!isSharedMode || isSharedHost" v-model:open="sheetOpen">
400
+ <SheetContent side="left" class="w-full md:w-1/2 max-w-none sm:max-w-none max-w-2xl">
401
+ <SheetHeader>
402
+ <SheetTitle>Pick a Block</SheetTitle>
403
+ <SheetDescription />
404
+ </SheetHeader>
405
+
406
+ <edge-shad-form>
407
+ <div class="p-6 h-[calc(100vh-50px)] overflow-hidden flex flex-col gap-4">
408
+ <span class="text-xs text-muted-foreground">Block Filter</span>
409
+ <div v-if="getTagsFromBlocks.length" class="flex flex-wrap items-center gap-2 text-sm">
410
+ <button
411
+ v-for="tagOption in getTagsFromBlocks"
412
+ :key="tagOption.name"
413
+ type="button"
414
+ class="px-3 py-1 rounded-full border transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
415
+ :class="pickerState.selectedTags.includes(tagOption.name) ? 'bg-primary text-primary-foreground border-primary shadow-sm' : 'bg-background text-muted-foreground hover:bg-muted border-border'"
416
+ @click="toggleTag(tagOption.name)"
417
+ >
418
+ {{ tagOption.title }}
419
+ </button>
420
+ <button
421
+ v-if="hasActiveFilters"
422
+ type="button"
423
+ class="ml-auto px-3 py-1 text-xs font-medium uppercase tracking-wide text-muted-foreground border border-transparent hover:text-primary hover:border-primary/30 rounded-full transition-colors"
424
+ @click="clearTagFilters"
425
+ >
426
+ Clear filters
427
+ </button>
428
+ </div>
429
+ <div class="space-y-4 overflow-y-auto pr-1">
430
+ <template v-if="filteredBlocks.length">
431
+ <template v-for="block in filteredBlocks" :key="block.docId">
432
+ <button
433
+ :ref="el => setBtnRef(block.docId, el)"
434
+ type="button"
435
+ class="p-0 text-left hover:bg-primary text-slate-500 border !hover:text-white border-dashed cursor-pointer w-full overflow-hidden relative"
436
+ @click="chooseBlock(block)"
437
+ >
438
+ <div class="scale-wrapper">
439
+ <div
440
+ :ref="el => setInnerRef(block.docId, el)"
441
+ class="scale-inner scale p-4"
442
+ :data-block-id="block.docId"
443
+ >
444
+ <div class="text-4xl relative text-inherit text-center">
445
+ {{ block.name }}
446
+ </div>
447
+ <edge-cms-block-api :site-id="activeSiteId" :content="block.content" :theme="pickerTheme" :values="block.values" :meta="block.meta" :viewport-mode="pickerViewport" @pending="blockLoaded($event, block.docId)" />
448
+ <edge-cms-block-render
449
+ v-if="!pickerState.blocksLoaded.includes(block.docId)"
450
+ :content="loadingRender(block.content)"
451
+ :values="block.values"
452
+ :meta="block.meta"
453
+ :theme="pickerTheme"
454
+ :viewport-mode="pickerViewport"
455
+ />
456
+ </div>
457
+ </div>
458
+ </button>
459
+ </template>
460
+ </template>
461
+ <p v-else class="text-sm text-muted-foreground">
462
+ No blocks match the selected tags yet.
463
+ </p>
464
+ </div>
465
+ </div>
466
+ </edge-shad-form>
467
+ </SheetContent>
468
+ </Sheet>
469
+ </div>
470
+ </template>
471
+
472
+ <style lang="scss">
473
+ .scale-wrapper {
474
+ width: 100%;
475
+ overflow: hidden;
476
+ position: relative;
477
+ }
478
+ .scale-inner {
479
+ transform-origin: top left;
480
+ display: inline-block;
481
+ }
482
+ .scale {
483
+ transform: scale(0.5);
484
+ width: 200%; /* 100 / 0.25 */
485
+ }
486
+ </style>
@@ -0,0 +1,78 @@
1
+ <script setup>
2
+ import { renderTemplate } from '@edgedev/template-engine'
3
+
4
+ const props = defineProps({
5
+ content: {
6
+ type: String,
7
+ required: true,
8
+ },
9
+ values: {
10
+ type: Object,
11
+ required: true,
12
+ },
13
+ meta: {
14
+ type: Object,
15
+ required: true,
16
+ },
17
+ theme: {
18
+ type: Object,
19
+ default: null,
20
+ },
21
+ isolated: {
22
+ type: Boolean,
23
+ default: true,
24
+ },
25
+ viewportMode: {
26
+ type: String,
27
+ default: 'auto',
28
+ },
29
+ })
30
+
31
+ const emit = defineEmits(['loaded'])
32
+
33
+ const defaultTheme = {
34
+ extend: {
35
+ colors: {
36
+ brand: '#3B82F6',
37
+ accent: '#F59E0B',
38
+ surface: '#FAFAFA',
39
+ subtle: '#F3F4F6',
40
+ text: '#1F2937',
41
+ muted: '#9CA3AF',
42
+ success: '#22C55E',
43
+ danger: '#EF4444',
44
+ },
45
+ fontFamily: {
46
+ sans: ['Overpass', 'sans-serif'],
47
+ serif: ['Kode Mono', 'monospace'],
48
+ mono: ['Overpass', 'sans-serif'],
49
+ brand: ['Kode Mono', 'monospace'],
50
+ },
51
+ },
52
+ apply: {},
53
+ slots: {},
54
+ variants: {
55
+ light: { apply: {} },
56
+ dark: { apply: {}, slots: {} },
57
+ },
58
+ }
59
+
60
+ const theme = computed(() => props.theme ?? defaultTheme)
61
+
62
+ const rendered = computed(() => {
63
+ return renderTemplate(props.content, props.values, props.meta)
64
+ })
65
+ </script>
66
+
67
+ <template>
68
+ <edge-cms-html-content
69
+ :html="rendered"
70
+ :theme="theme"
71
+ :isolated="props.isolated"
72
+ :viewport-mode="props.viewportMode"
73
+ @loaded="emit('loaded')"
74
+ />
75
+ </template>
76
+
77
+ <style scoped>
78
+ </style>
@@ -0,0 +1,28 @@
1
+ <script setup>
2
+ import { injectDialogRootContext } from 'reka-ui'
3
+
4
+ defineOptions({
5
+ inheritAttrs: false,
6
+ })
7
+
8
+ const rootContext = injectDialogRootContext()
9
+
10
+ const handleCloseAutoFocus = (event) => {
11
+ event.preventDefault()
12
+ const triggerEl = rootContext.triggerElement.value
13
+ if (!triggerEl)
14
+ return
15
+ try {
16
+ triggerEl.focus({ preventScroll: true })
17
+ }
18
+ catch {
19
+ triggerEl.focus()
20
+ }
21
+ }
22
+ </script>
23
+
24
+ <template>
25
+ <SheetContent v-bind="$attrs" @close-auto-focus="handleCloseAutoFocus">
26
+ <slot />
27
+ </SheetContent>
28
+ </template>