@edgedev/create-edge-app 1.1.23 → 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 (116) hide show
  1. package/.env +1 -0
  2. package/.env.dev +1 -0
  3. package/README.md +55 -20
  4. package/{agent.md → agents.md} +2 -0
  5. package/bin/cli.js +6 -6
  6. package/edge/components/auth/login.vue +384 -0
  7. package/edge/components/auth/register.vue +396 -0
  8. package/edge/components/auth.vue +108 -0
  9. package/edge/components/autoFileUpload.vue +215 -0
  10. package/edge/components/billing.vue +8 -0
  11. package/edge/components/buttonDivider.vue +14 -0
  12. package/edge/components/chip.vue +34 -0
  13. package/edge/components/clipboardButton.vue +42 -0
  14. package/edge/components/cms/block.vue +529 -0
  15. package/edge/components/cms/blockApi.vue +212 -0
  16. package/edge/components/cms/blockEditor.vue +725 -0
  17. package/edge/components/cms/blockInput.vue +66 -0
  18. package/edge/components/cms/blockPicker.vue +486 -0
  19. package/edge/components/cms/blockRender.vue +78 -0
  20. package/edge/components/cms/blockSheetContent.vue +28 -0
  21. package/edge/components/cms/codeEditor.vue +466 -0
  22. package/edge/components/cms/fontUpload.vue +327 -0
  23. package/edge/components/cms/htmlContent.vue +807 -0
  24. package/edge/components/cms/init_blocks/api_with_subarrays.html +17 -0
  25. package/edge/components/cms/init_blocks/array_with_collection.html +7 -0
  26. package/edge/components/cms/init_blocks/array_with_objects.html +7 -0
  27. package/edge/components/cms/init_blocks/carousel.html +103 -0
  28. package/edge/components/cms/init_blocks/contact_us.html +69 -0
  29. package/edge/components/cms/init_blocks/content_with_left_image.html +27 -0
  30. package/edge/components/cms/init_blocks/footer.html +24 -0
  31. package/edge/components/cms/init_blocks/header_divider.html +7 -0
  32. package/edge/components/cms/init_blocks/hero.html +35 -0
  33. package/edge/components/cms/init_blocks/hero_carousel.html +52 -0
  34. package/edge/components/cms/init_blocks/newsletter.html +117 -0
  35. package/edge/components/cms/init_blocks/post_content.html +7 -0
  36. package/edge/components/cms/init_blocks/post_title_header.html +21 -0
  37. package/edge/components/cms/init_blocks/posts_list.html +20 -0
  38. package/edge/components/cms/init_blocks/properties_showcase.html +100 -0
  39. package/edge/components/cms/init_blocks/property_carousel.html +59 -0
  40. package/edge/components/cms/init_blocks/property_detail.html +112 -0
  41. package/edge/components/cms/init_blocks/property_detail_header.html +34 -0
  42. package/edge/components/cms/init_blocks/property_results.html +137 -0
  43. package/edge/components/cms/init_blocks/property_search.html +75 -0
  44. package/edge/components/cms/init_blocks/simple_array.html +7 -0
  45. package/edge/components/cms/mediaCard.vue +116 -0
  46. package/edge/components/cms/mediaManager.vue +386 -0
  47. package/edge/components/cms/menu.vue +1103 -0
  48. package/edge/components/cms/optionsSelect.vue +107 -0
  49. package/edge/components/cms/page.vue +1785 -0
  50. package/edge/components/cms/posts.vue +1083 -0
  51. package/edge/components/cms/site.vue +1298 -0
  52. package/edge/components/cms/themeDefaultMenu.vue +548 -0
  53. package/edge/components/cms/themeEditor.vue +426 -0
  54. package/edge/components/dashboard.vue +776 -0
  55. package/edge/components/editor.vue +671 -0
  56. package/edge/components/fileTree.vue +72 -0
  57. package/edge/components/files.vue +89 -0
  58. package/edge/components/formSubtypes/myOrgs.vue +214 -0
  59. package/edge/components/formSubtypes/users.vue +336 -0
  60. package/edge/components/functionChips.vue +57 -0
  61. package/edge/components/gError.vue +98 -0
  62. package/edge/components/gHelper.vue +67 -0
  63. package/edge/components/gInput.vue +1331 -0
  64. package/edge/components/loggingIn.vue +41 -0
  65. package/edge/components/menu.vue +137 -0
  66. package/edge/components/menuContent.vue +132 -0
  67. package/edge/components/myAccount.vue +317 -0
  68. package/edge/components/myOrganizations.vue +75 -0
  69. package/edge/components/myProfile.vue +122 -0
  70. package/edge/components/orgSwitcher.vue +25 -0
  71. package/edge/components/organizationMembers.vue +522 -0
  72. package/edge/components/organizationSettings.vue +271 -0
  73. package/edge/components/shad/breadcrumbs.vue +35 -0
  74. package/edge/components/shad/button.vue +43 -0
  75. package/edge/components/shad/checkbox.vue +73 -0
  76. package/edge/components/shad/combobox.vue +238 -0
  77. package/edge/components/shad/datepicker.vue +184 -0
  78. package/edge/components/shad/dialog.vue +32 -0
  79. package/edge/components/shad/dropdownMenu.vue +54 -0
  80. package/edge/components/shad/dropdownMenuItem.vue +21 -0
  81. package/edge/components/shad/form.vue +59 -0
  82. package/edge/components/shad/html.vue +877 -0
  83. package/edge/components/shad/input.vue +139 -0
  84. package/edge/components/shad/number.vue +109 -0
  85. package/edge/components/shad/select.vue +151 -0
  86. package/edge/components/shad/selectTags.vue +278 -0
  87. package/edge/components/shad/switch.vue +67 -0
  88. package/edge/components/shad/tags.vue +137 -0
  89. package/edge/components/shad/textarea.vue +102 -0
  90. package/edge/components/shad/typeMoney.vue +167 -0
  91. package/edge/components/sideBar.vue +288 -0
  92. package/edge/components/sideBarContent.vue +268 -0
  93. package/edge/components/sidebarProvider.vue +33 -0
  94. package/edge/components/tooltip.vue +16 -0
  95. package/edge/components/userMenu.vue +148 -0
  96. package/edge/components/v/alert.vue +59 -0
  97. package/edge/components/v/alertTitle.vue +18 -0
  98. package/edge/components/v/card.vue +53 -0
  99. package/edge/components/v/cardActions.vue +18 -0
  100. package/edge/components/v/cardText.vue +18 -0
  101. package/edge/components/v/cardTitle.vue +20 -0
  102. package/edge/components/v/col.vue +56 -0
  103. package/edge/components/v/list.vue +46 -0
  104. package/edge/components/v/listItem.vue +26 -0
  105. package/edge/components/v/listItemTitle.vue +18 -0
  106. package/edge/components/v/row.vue +42 -0
  107. package/edge/components/v/toolbar.vue +24 -0
  108. package/edge/composables/global.ts +519 -0
  109. package/edge-pull.sh +2 -0
  110. package/edge-push.sh +1 -0
  111. package/edge-status.sh +14 -0
  112. package/firebase.json +5 -2
  113. package/firebase_init.sh +21 -6
  114. package/package.json +1 -1
  115. package/plugins/firebase.client.ts +1 -0
  116. package/edge-components-install.sh +0 -1
@@ -0,0 +1,139 @@
1
+ <script setup>
2
+ import { useVModel } from '@vueuse/core'
3
+ const props = defineProps({
4
+ name: {
5
+ type: String,
6
+ required: true,
7
+ },
8
+ type: {
9
+ type: String,
10
+ required: false,
11
+ default: 'text',
12
+ },
13
+ defaultValue: {
14
+ type: [String, Number],
15
+ required: false,
16
+ },
17
+ modelValue: {
18
+ type: [String, Number],
19
+ required: false,
20
+ },
21
+ class: {
22
+ type: null,
23
+ required: false,
24
+ },
25
+ placeholder: {
26
+ type: String,
27
+ required: false,
28
+ },
29
+ label: {
30
+ type: String,
31
+ required: false,
32
+ },
33
+ description: {
34
+ type: String,
35
+ required: false,
36
+ },
37
+ maskOptions: {
38
+ type: [Object],
39
+ required: false,
40
+ default: null,
41
+ },
42
+ disabled: {
43
+ type: Boolean,
44
+ required: false,
45
+ default: false,
46
+ },
47
+ })
48
+
49
+ const emits = defineEmits(['update:modelValue', 'blur'])
50
+
51
+ const state = reactive({
52
+ showPassword: false,
53
+ type: '',
54
+ })
55
+
56
+ onBeforeMount(() => {
57
+ state.type = props.type
58
+ })
59
+
60
+ const modelValue = useVModel(props, 'modelValue', emits, {
61
+ passive: false,
62
+ emits,
63
+ prop: 'modelValue',
64
+ })
65
+
66
+ const classComputed = computed(() => {
67
+ if (props.type === 'password') {
68
+ return `${props.class} pr-10`
69
+ }
70
+ return props.class
71
+ })
72
+
73
+ const inputRef = ref(null) // Reference to the input field
74
+
75
+ const onInputChange = (e) => {
76
+ if (e.target.value !== modelValue.value) {
77
+ modelValue.value = e.target.value // Sync value only if changed
78
+ }
79
+ }
80
+
81
+ // Check for pre-filled (autofilled) value on mount
82
+ onMounted(() => {
83
+ nextTick(() => {
84
+ if (inputRef.value?.value && inputRef.value.value !== modelValue.value) {
85
+ modelValue.value = inputRef.value.value
86
+ }
87
+ })
88
+ })
89
+ </script>
90
+
91
+ <template>
92
+ <div>
93
+ <FormField v-slot="{ componentField }" :name="props.name">
94
+ <FormItem>
95
+ <FormLabel class="flex">
96
+ {{ props.label }}
97
+ <div class="ml-auto inline-block">
98
+ <slot />
99
+ </div>
100
+ </FormLabel>
101
+ <FormControl>
102
+ <div class="relative w-full items-center">
103
+ <Input
104
+ :id="props.name"
105
+ ref="inputRef"
106
+ v-model="modelValue"
107
+ v-maska:[props.maskOptions]
108
+ :name="props.name"
109
+ :default-value="props.modelValue"
110
+ :class="classComputed"
111
+ :type="state.type"
112
+ v-bind="componentField"
113
+ :placeholder="props.placeholder"
114
+ :disabled="props.disabled"
115
+ @change="onInputChange"
116
+ @blur="$emit('blur', $event)"
117
+ />
118
+ <span class="absolute end-0 inset-y-0 flex items-center justify-center px-2">
119
+ <slot name="icon" />
120
+ <template v-if="props.type === 'password'">
121
+ <Eye v-if="state.type === 'text'" class="size-6 text-muted-foreground cursor-pointer" @click="state.type = 'password'" />
122
+ <EyeOff v-else class="size-6 text-muted-foreground cursor-pointer" @click="state.type = 'text'" />
123
+ </template>
124
+ </span>
125
+ </div>
126
+ </FormControl>
127
+ <FormDescription>
128
+ {{ props.description }}
129
+ <slot name="description" />
130
+ </FormDescription>
131
+ <FormMessage />
132
+ </FormItem>
133
+ </FormField>
134
+ </div>
135
+ </template>
136
+
137
+ <style lang="scss" scoped>
138
+
139
+ </style>
@@ -0,0 +1,109 @@
1
+ <script setup>
2
+ import { useVModel } from '@vueuse/core'
3
+
4
+ const props = defineProps({
5
+ name: { type: String, required: true },
6
+ type: { type: String, default: 'text' },
7
+ defaultValue: { type: [String, Number], required: false },
8
+ modelValue: { type: [String, Number], required: false },
9
+ class: { type: null, required: false },
10
+ placeholder: { type: String, required: false },
11
+ label: { type: String, required: false },
12
+ description: { type: String, required: false },
13
+ maskOptions: { type: Object, required: false, default: null },
14
+ disabled: { type: Boolean, default: false },
15
+ min: { type: Number, required: false },
16
+ max: { type: Number, required: false },
17
+ formatOptions: { type: Object, required: false, default: () => ({}) },
18
+ step: { type: Number, default: 1 },
19
+ })
20
+
21
+ const emits = defineEmits(['update:modelValue'])
22
+
23
+ const state = reactive({
24
+ showPassword: false,
25
+ type: '',
26
+ })
27
+
28
+ onBeforeMount(() => {
29
+ state.type = props.type
30
+ })
31
+
32
+ // Base v-model (can be string or number)
33
+ const rawModel = useVModel(props, 'modelValue', emits, {
34
+ passive: false,
35
+ defaultValue: props.defaultValue,
36
+ })
37
+
38
+ // Coercion helper: returns number | null
39
+ function toNumberOrNull(v) {
40
+ if (v === '' || v === undefined || v === null)
41
+ return null
42
+ if (typeof v === 'number')
43
+ return Number.isNaN(v) ? null : v
44
+ // Strip common formatting (e.g., "1,234.56")
45
+ const cleaned = String(v).replace(/,/g, '').trim()
46
+ const n = Number(cleaned)
47
+ return Number.isNaN(n) ? null : n
48
+ }
49
+
50
+ // Internal numeric ref exposed to NumberField
51
+ const numericValue = computed({
52
+ get() {
53
+ // Prefer current raw value; fall back to default
54
+ const base = rawModel.value ?? props.defaultValue ?? null
55
+ return toNumberOrNull(base)
56
+ },
57
+ set(val) {
58
+ // NumberField may pass string or number; normalize to number | null
59
+ const n = toNumberOrNull(val)
60
+ emits('update:modelValue', n)
61
+ },
62
+ })
63
+ </script>
64
+
65
+ <template>
66
+ <div>
67
+ <FormField v-slot="{ componentField }" :name="props.name">
68
+ <FormItem>
69
+ <FormLabel class="flex">
70
+ {{ props.label }}
71
+ <div class="ml-auto inline-block">
72
+ <slot />
73
+ </div>
74
+ </FormLabel>
75
+
76
+ <NumberField
77
+ v-model="numericValue"
78
+ :default-value="numericValue ?? undefined"
79
+ :class="props.class"
80
+ :min="props.min"
81
+ :max="props.max"
82
+ :format-options="props.formatOptions"
83
+ :step="props.step"
84
+ :disabled="props.disabled"
85
+ >
86
+ <NumberFieldContent>
87
+ <NumberFieldDecrement class="mt-5" />
88
+ <FormControl>
89
+ <NumberFieldInput />
90
+ </FormControl>
91
+ <NumberFieldIncrement class="mt-5" />
92
+ </NumberFieldContent>
93
+ </NumberField>
94
+
95
+ <!-- keep validation/form integration without polluting NumberField props -->
96
+ <input type="hidden" v-bind="componentField" :value="numericValue ?? ''">
97
+
98
+ <span class="absolute end-0 inset-y-0 flex items-center justify-center px-2">
99
+ <slot name="icon" />
100
+ </span>
101
+
102
+ <FormDescription>
103
+ {{ props.description }} <slot name="description" />
104
+ </FormDescription>
105
+ <FormMessage />
106
+ </FormItem>
107
+ </FormField>
108
+ </div>
109
+ </template>
@@ -0,0 +1,151 @@
1
+ <script setup>
2
+ import { useVModel } from '@vueuse/core'
3
+ const props = defineProps({
4
+ name: {
5
+ type: String,
6
+ required: false,
7
+ },
8
+ modelValue: {
9
+ type: [String, Boolean, Number, Array],
10
+ required: false,
11
+ },
12
+ class: {
13
+ type: null,
14
+ required: false,
15
+ default: 'w-full',
16
+ },
17
+ placeholder: {
18
+ type: String,
19
+ required: false,
20
+ },
21
+ label: {
22
+ type: String,
23
+ required: false,
24
+ },
25
+ description: {
26
+ type: String,
27
+ required: false,
28
+ },
29
+ disabled: {
30
+ type: Boolean,
31
+ required: false,
32
+ default: false,
33
+ },
34
+ items: {
35
+ type: Array,
36
+ required: false,
37
+ default: () => [],
38
+ },
39
+ itemTitle: {
40
+ type: String,
41
+ required: false,
42
+ default: 'title',
43
+ },
44
+ itemValue: {
45
+ type: String,
46
+ required: false,
47
+ default: 'name',
48
+ },
49
+ multiple: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ })
54
+
55
+ const emits = defineEmits(['update:modelValue'])
56
+
57
+ const computedItems = computed(() => {
58
+ return props.items.map((item) => {
59
+ if (typeof item === 'string') {
60
+ return { [props.itemTitle]: item, [props.itemValue]: item }
61
+ }
62
+ const getNestedValue = (obj, path) => path.split('.').reduce((acc, key) => acc && acc[key], obj)
63
+ return {
64
+ [props.itemTitle]: getNestedValue(item, props.itemTitle),
65
+ [props.itemValue]: getNestedValue(item, props.itemValue),
66
+ }
67
+ })
68
+ })
69
+
70
+ const modelValue = useVModel(props, 'modelValue', emits, {
71
+ passive: false,
72
+ prop: 'modelValue',
73
+ })
74
+ </script>
75
+
76
+ <template>
77
+ <template v-if="props.name">
78
+ <FormField v-slot="{ componentField }" :name="props.name">
79
+ <FormItem>
80
+ <FormLabel class="flex">
81
+ {{ props.label }}
82
+ <div class="ml-auto inline-block">
83
+ <slot />
84
+ </div>
85
+ </FormLabel>
86
+ <div class="relative w-full items-center">
87
+ <Select v-model="modelValue" :disabled="props.disabled" :multiple="props.multiple" :default-value="modelValue" v-bind="componentField">
88
+ <FormControl>
89
+ <SelectTrigger class="text-foreground" :class="[$slots.icon ? 'pr-8' : '', props.class]">
90
+ <SelectValue :placeholder="props.placeholder" />
91
+ </SelectTrigger>
92
+ </FormControl>
93
+ <SelectContent>
94
+ <SelectGroup>
95
+ <SelectItem
96
+ v-for="item in computedItems"
97
+ :key="item[props.itemTitle]"
98
+ :value="item[props.itemValue]"
99
+ >
100
+ {{ item[props.itemTitle] }}
101
+ </SelectItem>
102
+ </SelectGroup>
103
+ </SelectContent>
104
+ </Select>
105
+ <span class="absolute end-0 inset-y-0 flex items-center justify-center pl-2 pr-2">
106
+ <slot name="icon" />
107
+ </span>
108
+ </div>
109
+ <FormDescription>
110
+ {{ props.description }}
111
+ </FormDescription>
112
+ <FormMessage />
113
+ </FormItem>
114
+ </FormField>
115
+ </template>
116
+
117
+ <template v-else>
118
+ <div class="w-full">
119
+ <label class="flex mb-1">
120
+ {{ props.label }}
121
+ </label>
122
+ <div class="relative w-full items-center">
123
+ <Select v-model="modelValue" :disabled="props.disabled" :default-value="modelValue">
124
+ <SelectTrigger class="text-foreground" :class="[$slots.icon ? 'pr-8' : '', props.class]">
125
+ <SelectValue />
126
+ </SelectTrigger>
127
+ <SelectContent>
128
+ <SelectGroup>
129
+ <SelectItem
130
+ v-for="item in computedItems"
131
+ :key="item[props.itemTitle]"
132
+ :value="item[props.itemValue]"
133
+ >
134
+ {{ item[props.itemTitle] }}
135
+ </SelectItem>
136
+ </SelectGroup>
137
+ </SelectContent>
138
+ </Select>
139
+ <span class="absolute end-0 inset-y-0 flex items-center justify-center pl-2 pr-2">
140
+ <slot name="icon" />
141
+ </span>
142
+ </div>
143
+ <p class="text-sm text-muted-foreground mt-1">
144
+ {{ props.description }}
145
+ </p>
146
+ </div>
147
+ </template>
148
+ </template>
149
+
150
+ <style lang="scss" scoped>
151
+ </style>
@@ -0,0 +1,278 @@
1
+ <script setup>
2
+ import { useFilter } from 'reka-ui'
3
+ import { useVModel } from '@vueuse/core'
4
+ import { cn } from '~/lib/utils'
5
+ const props = defineProps({
6
+ name: {
7
+ type: String,
8
+ required: false,
9
+ },
10
+ modelValue: {
11
+ type: Array,
12
+ required: false,
13
+ },
14
+ class: {
15
+ type: null,
16
+ required: false,
17
+ default: 'w-full',
18
+ },
19
+ placeholder: {
20
+ type: String,
21
+ required: false,
22
+ },
23
+ label: {
24
+ type: String,
25
+ required: false,
26
+ },
27
+ description: {
28
+ type: String,
29
+ required: false,
30
+ },
31
+ disabled: {
32
+ type: Boolean,
33
+ required: false,
34
+ default: false,
35
+ },
36
+ items: {
37
+ type: Array,
38
+ required: false,
39
+ default: () => [],
40
+ },
41
+ itemTitle: {
42
+ type: String,
43
+ required: false,
44
+ default: 'title',
45
+ },
46
+ itemValue: {
47
+ type: String,
48
+ required: false,
49
+ default: 'name',
50
+ },
51
+ allowAdditions: {
52
+ type: Boolean,
53
+ default: false,
54
+ },
55
+ })
56
+
57
+ const emits = defineEmits(['update:modelValue', 'add'])
58
+
59
+ const computedItems = computed(() => {
60
+ return props.items.map((item) => {
61
+ if (typeof item === 'string') {
62
+ return { [props.itemTitle]: item, [props.itemValue]: item }
63
+ }
64
+ const getNestedValue = (obj, path) => path.split('.').reduce((acc, key) => acc && acc[key], obj)
65
+ return {
66
+ [props.itemTitle]: getNestedValue(item, props.itemTitle),
67
+ [props.itemValue]: getNestedValue(item, props.itemValue),
68
+ }
69
+ })
70
+ })
71
+
72
+ const modelValue = useVModel(props, 'modelValue', emits, {
73
+ passive: false,
74
+ prop: 'modelValue',
75
+ })
76
+ const open = ref(false)
77
+ const searchTerm = ref('')
78
+
79
+ const { contains } = useFilter({ sensitivity: 'base' })
80
+
81
+ const forceOpen = () => {
82
+ if (!props.disabled)
83
+ open.value = true
84
+ }
85
+
86
+ const filteredItems = computed(() => {
87
+ const options = computedItems.value.filter(i => !modelValue.value.includes(i[props.itemValue]))
88
+ return searchTerm.value ? options.filter(option => contains(option[props.itemTitle], searchTerm.value)) : options
89
+ })
90
+
91
+ const valueToTitle = computed(() => {
92
+ const map = {}
93
+ for (const it of computedItems.value) {
94
+ map[it[props.itemValue]] = it[props.itemTitle]
95
+ }
96
+ return map
97
+ })
98
+
99
+ const existingValueMap = computed(() => {
100
+ const map = {}
101
+ for (const item of computedItems.value) {
102
+ const value = String(item[props.itemValue])
103
+ map[value.toLowerCase()] = value
104
+ }
105
+ return map
106
+ })
107
+
108
+ const normalize = value => String(value || '').trim()
109
+
110
+ const addValueToModel = (value) => {
111
+ const normalized = normalize(value)
112
+ if (!normalized)
113
+ return
114
+ if (!modelValue.value.includes(normalized))
115
+ modelValue.value = [...modelValue.value, normalized]
116
+ }
117
+
118
+ const addFromSearch = () => {
119
+ if (!props.allowAdditions)
120
+ return
121
+ const raw = normalize(searchTerm.value)
122
+ if (!raw)
123
+ return
124
+
125
+ const existingValue = existingValueMap.value[raw.toLowerCase()]
126
+ if (existingValue) {
127
+ addValueToModel(existingValue)
128
+ searchTerm.value = ''
129
+ open.value = false
130
+ return
131
+ }
132
+
133
+ addValueToModel(raw)
134
+ emits('add', raw)
135
+ searchTerm.value = ''
136
+ open.value = false
137
+ }
138
+
139
+ const onEnter = () => {
140
+ if (!props.allowAdditions)
141
+ return
142
+ addFromSearch()
143
+ }
144
+
145
+ const onSelectItem = (ev) => {
146
+ const value = ev?.detail?.value
147
+ if (typeof value === 'string') {
148
+ searchTerm.value = ''
149
+ addValueToModel(value)
150
+ }
151
+ if (filteredItems.value.length === 0)
152
+ open.value = false
153
+ }
154
+ </script>
155
+
156
+ <template>
157
+ <template v-if="props.name">
158
+ <FormField :name="props.name">
159
+ <FormItem>
160
+ <FormLabel class="flex">
161
+ {{ props.label }}
162
+ <div class="ml-auto inline-block">
163
+ <slot />
164
+ </div>
165
+ </FormLabel>
166
+ <div class="relative w-full items-center">
167
+ <FormControl>
168
+ <Combobox v-model="modelValue" v-model:open="open" :ignore-filter="true" :disabled="props.disabled">
169
+ <ComboboxAnchor as-child>
170
+ <TagsInput
171
+ v-model="modelValue"
172
+ :class="cn('relative flex items-center gap-1 flex-nowrap pl-2 pr-1 pt-[7px] pb-[7px] w-80', props.class)"
173
+ :disabled="props.disabled"
174
+ @click="forceOpen"
175
+ >
176
+ <!-- Wrapping area for tags + input -->
177
+ <div class="flex flex-wrap items-center gap-2 flex-1 min-w-0">
178
+ <TagsInputItem v-for="val in modelValue" :key="val" class="h-6" :value="val">
179
+ <span class="px-1">{{ valueToTitle[val] ?? val }}</span>
180
+ <TagsInputItemDelete v-if="!props.disabled" />
181
+ </TagsInputItem>
182
+
183
+ <ComboboxInput v-model="searchTerm" as-child>
184
+ <TagsInputInput
185
+ :disabled="props.disabled"
186
+ :placeholder="props.placeholder"
187
+ class="p-0 border-none shadow-none focus-visible:ring-0 h-auto grow min-w-[6rem] w-auto"
188
+ @input="forceOpen"
189
+ @keydown.enter.prevent="onEnter"
190
+ />
191
+ </ComboboxInput>
192
+ </div>
193
+
194
+ <!-- Non-wrapping trigger -->
195
+ <ComboboxTrigger as-child>
196
+ <Button variant="ghost" size="icon" class="h-6 w-6 z-10">
197
+ <ChevronsUpDown class="h-4 w-4 shrink-0 opacity-50" />
198
+ </Button>
199
+ </ComboboxTrigger>
200
+ </TagsInput>
201
+
202
+ <ComboboxList class="w-[--reka-popper-anchor-width]">
203
+ <ComboboxEmpty>
204
+ <button
205
+ v-if="props.allowAdditions && searchTerm && searchTerm.trim() && !computedItems.some(item => String(item[props.itemValue]).toLowerCase() === searchTerm.trim().toLowerCase())"
206
+ type="button"
207
+ class="w-full rounded-sm px-2 py-1 text-left text-sm hover:bg-muted"
208
+ @click.prevent="addFromSearch()"
209
+ >
210
+ Create "{{ searchTerm.trim() }}"
211
+ </button>
212
+ <span v-else-if="searchTerm && searchTerm.trim()" class="block px-2 py-1 text-sm text-muted-foreground">
213
+ No results found
214
+ </span>
215
+ <span v-else class="block px-2 py-1 text-sm text-muted-foreground">
216
+ Start typing to search
217
+ </span>
218
+ </ComboboxEmpty>
219
+ <ComboboxGroup>
220
+ <ComboboxItem
221
+ v-for="item in filteredItems" :key="item[props.itemValue]" :value="item[props.itemValue]"
222
+ @select.prevent="onSelectItem"
223
+ >
224
+ {{ item[props.itemTitle] }}
225
+ </ComboboxItem>
226
+ </ComboboxGroup>
227
+ </ComboboxList>
228
+ </ComboboxAnchor>
229
+ </Combobox>
230
+ </FormControl>
231
+
232
+ <span class="absolute end-0 inset-y-0 flex items-center justify-center pl-2 pr-2">
233
+ <slot name="icon" />
234
+ </span>
235
+ </div>
236
+ <FormDescription>
237
+ {{ props.description }}
238
+ </FormDescription>
239
+ <FormMessage />
240
+ </FormItem>
241
+ </FormField>
242
+ </template>
243
+
244
+ <template v-else>
245
+ <div class="w-full">
246
+ <label class="flex mb-1">
247
+ {{ props.label }}
248
+ </label>
249
+ <div class="relative w-full items-center">
250
+ <Select v-model="modelValue" :disabled="props.disabled" :default-value="modelValue">
251
+ <SelectTrigger class="text-foreground" :class="[$slots.icon ? 'pr-8' : '', props.class]">
252
+ <SelectValue />
253
+ </SelectTrigger>
254
+ <SelectContent>
255
+ <SelectGroup>
256
+ <SelectItem
257
+ v-for="item in computedItems"
258
+ :key="item[props.itemTitle]"
259
+ :value="item[props.itemValue]"
260
+ >
261
+ {{ item[props.itemTitle] }}
262
+ </SelectItem>
263
+ </SelectGroup>
264
+ </SelectContent>
265
+ </Select>
266
+ <span class="absolute end-0 inset-y-0 flex items-center justify-center pl-2 pr-2">
267
+ <slot name="icon" />
268
+ </span>
269
+ </div>
270
+ <p class="text-sm text-muted-foreground mt-1">
271
+ {{ props.description }}
272
+ </p>
273
+ </div>
274
+ </template>
275
+ </template>
276
+
277
+ <style lang="scss" scoped>
278
+ </style>