@datagouv/components-next 0.2.0 → 1.0.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 (155) hide show
  1. package/README.md +1 -1
  2. package/assets/main.css +49 -22
  3. package/dist/Control-BNCDn-8E.js +148 -0
  4. package/dist/{Datafair.client-x39O4yfF.js → Datafair.client-B5lBpOl8.js} +2 -2
  5. package/dist/Event-BOgJUhNR.js +738 -0
  6. package/dist/Image-BN-4XkIn.js +247 -0
  7. package/dist/{JsonPreview.client-BMsC5JcY.js → JsonPreview.client-Doz1Z0BS.js} +23 -23
  8. package/dist/Map-BdT3i2C4.js +7609 -0
  9. package/dist/MapContainer.client-oiieO8H-.js +105 -0
  10. package/dist/OSM-CamriM9b.js +71 -0
  11. package/dist/PdfPreview.client-CdAhkDFJ.js +14513 -0
  12. package/dist/{Pmtiles.client-BaiIo4VZ.js → Pmtiles.client-B0v8tGJQ.js} +3 -3
  13. package/dist/ScaleLine-BiesrgOv.js +165 -0
  14. package/dist/Swagger.client-CsK65JnG.js +4 -0
  15. package/dist/Tile-DCuqwNOI.js +1206 -0
  16. package/dist/TileImage-CmZf8EdU.js +1067 -0
  17. package/dist/View-DcDc7N2K.js +2858 -0
  18. package/dist/{XmlPreview.client-CAdN0w_Y.js → XmlPreview.client-CrjHf74q.js} +17 -17
  19. package/dist/common-C4rDcQpp.js +243 -0
  20. package/dist/components-next.css +1 -1
  21. package/dist/components-next.js +158 -117
  22. package/dist/components.css +1 -1
  23. package/dist/{MapContainer.client-DeSo8EvG.js → index-Bbu9rOHt.js} +4975 -21416
  24. package/dist/leaflet-src-7m1mB8LI.js +6338 -0
  25. package/dist/{main-Dgri3TQL.js → main-CiH8ZmBI.js} +56973 -51462
  26. package/dist/proj-CKwYjU38.js +1569 -0
  27. package/dist/tilecoord-YW3qEH_j.js +884 -0
  28. package/dist/{vue3-xml-viewer.common-D6skc_Ai.js → vue3-xml-viewer.common-Bi_bsV6C.js} +1 -1
  29. package/package.json +6 -2
  30. package/src/components/ActivityList/ActivityList.vue +6 -2
  31. package/src/components/AppLink.vue +4 -1
  32. package/src/components/Avatar.vue +2 -2
  33. package/src/components/AvatarWithName.vue +8 -4
  34. package/src/components/BouncingDots.vue +21 -0
  35. package/src/components/BrandedButton.vue +2 -0
  36. package/src/components/CopyButton.vue +19 -7
  37. package/src/components/DataserviceCard.vue +85 -120
  38. package/src/components/DatasetCard.vue +110 -171
  39. package/src/components/DatasetInformation/DatasetEmbedSection.vue +43 -0
  40. package/src/components/DatasetInformation/DatasetInformationSection.vue +73 -0
  41. package/src/components/DatasetInformation/DatasetSchemaSection.vue +74 -0
  42. package/src/components/DatasetInformation/DatasetSpatialSection.vue +59 -0
  43. package/src/components/DatasetInformation/DatasetTemporalitySection.vue +45 -0
  44. package/src/components/DatasetInformation/index.ts +5 -0
  45. package/src/components/DatasetQuality.vue +23 -16
  46. package/src/components/DatasetQualityInline.vue +13 -17
  47. package/src/components/DatasetQualityScore.vue +12 -15
  48. package/src/components/DatasetQualityTooltipContent.vue +3 -3
  49. package/src/components/DescriptionList.vue +1 -4
  50. package/src/components/DescriptionListDetails.vue +5 -0
  51. package/src/components/DescriptionListTerm.vue +5 -0
  52. package/src/components/DiscussionMessageCard.vue +63 -0
  53. package/src/components/ExtraAccordion.vue +4 -4
  54. package/src/components/Form/BadgeSelect.vue +35 -0
  55. package/src/components/Form/FormatSelect.vue +28 -0
  56. package/src/components/Form/GeozoneSelect.vue +52 -0
  57. package/src/components/Form/GranularitySelect.vue +29 -0
  58. package/src/components/Form/LicenseSelect.vue +30 -0
  59. package/src/components/Form/OrganizationSelect.vue +62 -0
  60. package/src/components/Form/OrganizationTypeSelect.vue +34 -0
  61. package/src/components/Form/ReuseTopicSelect.vue +29 -0
  62. package/src/components/Form/SchemaSelect.vue +30 -0
  63. package/src/components/Form/SearchableSelect.vue +334 -0
  64. package/src/components/Form/SelectGroup.vue +132 -0
  65. package/src/components/Form/TagSelect.vue +38 -0
  66. package/src/components/LeafletMap.vue +31 -0
  67. package/src/components/LicenseBadge.vue +24 -0
  68. package/src/components/LoadingBlock.vue +23 -2
  69. package/src/components/MarkdownViewer.vue +3 -1
  70. package/src/components/ObjectCard.vue +42 -0
  71. package/src/components/ObjectCardBadge.vue +22 -0
  72. package/src/components/ObjectCardHeader.vue +35 -0
  73. package/src/components/ObjectCardOwner.vue +43 -0
  74. package/src/components/ObjectCardShortDescription.vue +28 -0
  75. package/src/components/OrganizationCard.vue +35 -20
  76. package/src/components/OrganizationHorizontalCard.vue +87 -0
  77. package/src/components/OrganizationLogo.vue +1 -1
  78. package/src/components/OrganizationNameWithCertificate.vue +12 -6
  79. package/src/components/OwnerTypeIcon.vue +1 -0
  80. package/src/components/Pagination.vue +1 -1
  81. package/src/components/Placeholder.vue +5 -2
  82. package/src/components/PostCard.vue +62 -0
  83. package/src/components/ProgressBar.vue +31 -0
  84. package/src/components/RadioGroup.vue +32 -0
  85. package/src/components/RadioInput.vue +64 -0
  86. package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
  87. package/src/components/ResourceAccordion/EditButton.vue +2 -3
  88. package/src/components/ResourceAccordion/JsonPreview.client.vue +3 -3
  89. package/src/components/ResourceAccordion/MapContainer.client.vue +21 -17
  90. package/src/components/ResourceAccordion/Metadata.vue +11 -24
  91. package/src/components/ResourceAccordion/PdfPreview.client.vue +70 -74
  92. package/src/components/ResourceAccordion/Pmtiles.client.vue +2 -2
  93. package/src/components/ResourceAccordion/Preview.vue +2 -2
  94. package/src/components/ResourceAccordion/ResourceAccordion.vue +35 -28
  95. package/src/components/ResourceAccordion/ResourceIcon.vue +1 -0
  96. package/src/components/ResourceAccordion/SchemaBadge.vue +2 -2
  97. package/src/components/ResourceAccordion/XmlPreview.client.vue +3 -3
  98. package/src/components/ResourceExplorer/ResourceExplorer.vue +243 -0
  99. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +116 -0
  100. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +410 -0
  101. package/src/components/ReuseCard.vue +8 -28
  102. package/src/components/ReuseHorizontalCard.vue +80 -0
  103. package/src/components/Search/BasicAndAdvancedFilters.vue +49 -0
  104. package/src/components/Search/Filter/AccessTypeFilter.vue +37 -0
  105. package/src/components/Search/Filter/DatasetBadgeFilter.vue +40 -0
  106. package/src/components/Search/Filter/FilterButtonGroup.vue +78 -0
  107. package/src/components/Search/Filter/FormatFamilyFilter.vue +39 -0
  108. package/src/components/Search/Filter/LastUpdateRangeFilter.vue +37 -0
  109. package/src/components/Search/Filter/ProducerTypeFilter.vue +49 -0
  110. package/src/components/Search/Filter/ReuseTypeFilter.vue +42 -0
  111. package/src/components/Search/GlobalSearch.vue +707 -0
  112. package/src/components/Search/SearchInput.vue +63 -0
  113. package/src/components/Search/Sidemenu.vue +38 -0
  114. package/src/components/StatBox.vue +5 -5
  115. package/src/components/Tag.vue +30 -0
  116. package/src/components/Toggletip.vue +11 -4
  117. package/src/components/Tooltip.vue +2 -3
  118. package/src/components/TopicCard.vue +134 -0
  119. package/src/components/radioGroupContext.ts +9 -0
  120. package/src/composables/useDebouncedRef.ts +31 -0
  121. package/src/composables/useHasTabularData.ts +15 -0
  122. package/src/composables/useMetrics.ts +4 -3
  123. package/src/composables/useResourceCapabilities.ts +131 -0
  124. package/src/composables/useRouteQueryBoolean.ts +10 -0
  125. package/src/composables/useSelectModelSync.ts +89 -0
  126. package/src/composables/useStableQueryParams.ts +84 -0
  127. package/src/composables/useTranslation.ts +2 -1
  128. package/src/config.ts +4 -0
  129. package/src/functions/api.ts +25 -6
  130. package/src/functions/api.types.ts +5 -3
  131. package/src/functions/datasets.ts +1 -29
  132. package/src/functions/description.ts +33 -0
  133. package/src/functions/helpers.ts +11 -0
  134. package/src/functions/markdown.ts +60 -16
  135. package/src/functions/metrics.ts +33 -0
  136. package/src/functions/organizations.ts +5 -5
  137. package/src/functions/resourceCapabilities.ts +55 -0
  138. package/src/main.ts +96 -7
  139. package/src/types/dataservices.ts +14 -12
  140. package/src/types/datasets.ts +20 -7
  141. package/src/types/discussions.ts +20 -0
  142. package/src/types/licenses.ts +3 -3
  143. package/src/types/organizations.ts +13 -1
  144. package/src/types/owned.ts +4 -2
  145. package/src/types/pages.ts +70 -0
  146. package/src/types/posts.ts +27 -0
  147. package/src/types/resources.ts +16 -0
  148. package/src/types/reuses.ts +14 -5
  149. package/src/types/search.ts +407 -0
  150. package/src/types/users.ts +12 -3
  151. package/dist/PdfPreview.client-COOkEkRA.js +0 -107
  152. package/dist/Swagger.client-CpLgaLg6.js +0 -4
  153. package/dist/pdf-vue3-IkJO65RH.js +0 -273
  154. package/dist/pdf.min-f72cfa08-CdgJTooZ.js +0 -9501
  155. package/src/components/DatasetInformationPanel.vue +0 -211
@@ -0,0 +1,243 @@
1
+ <template>
2
+ <div v-if="allResources.length || hasAnyResources">
3
+ <div class="flex gap-6">
4
+ <div class="flex-1 min-w-0">
5
+ <ResourceExplorerViewer
6
+ v-if="selectedResource && allResources.length"
7
+ :key="selectedResource.id"
8
+ :dataset
9
+ :resource="selectedResource"
10
+ />
11
+ <div
12
+ v-else-if="search"
13
+ class="flex flex-col items-center py-12"
14
+ >
15
+ <slot name="no-results-image">
16
+ <img
17
+ :src="noResultsImage"
18
+ class="h-20"
19
+ alt=""
20
+ >
21
+ </slot>
22
+ <p class="fr-text--bold fr-my-3v">
23
+ {{ t('Pas de résultats pour « {q} »', { q: search }) }}
24
+ </p>
25
+ <BrandedButton
26
+ color="primary"
27
+ @click="updateSearch('')"
28
+ >
29
+ {{ t('Réinitialiser la recherche') }}
30
+ </BrandedButton>
31
+ </div>
32
+ </div>
33
+ <ResourceExplorerSidebar
34
+ :resources="allResources"
35
+ :selected-resource-id="selectedResource?.id ?? null"
36
+ :collapsed="sidebarCollapsed"
37
+ :search
38
+ @select="selectResource"
39
+ @load-more="loadMore"
40
+ @update:collapsed="sidebarCollapsed = $event"
41
+ @update:search="updateSearch($event)"
42
+ />
43
+ </div>
44
+ </div>
45
+ <div
46
+ v-else
47
+ class="flex flex-col items-center py-12"
48
+ >
49
+ <slot name="empty-image">
50
+ <img
51
+ :src="noResultsImage"
52
+ class="h-20"
53
+ alt=""
54
+ >
55
+ </slot>
56
+ <p class="fr-text--bold fr-my-3v">
57
+ {{ t('Ce jeu de données ne contient aucune ressource.') }}
58
+ </p>
59
+ </div>
60
+ </template>
61
+
62
+ <script setup lang="ts">
63
+ import { computed, ref, watch, type Ref } from 'vue'
64
+ import { useRouter } from 'vue-router'
65
+ import { useRouteQuery } from '@vueuse/router'
66
+ import { useComponentsConfig } from '../../config'
67
+ import { useTranslation } from '../../composables/useTranslation'
68
+ import { useDebouncedRef } from '../../composables/useDebouncedRef'
69
+ import { useFetch } from '../../functions/api'
70
+ import { RESOURCE_TYPE } from '../../functions/resources'
71
+ import type { PaginatedArray } from '../../types/api'
72
+ import type { DatasetV2 } from '../../types/datasets'
73
+ import type { Resource, ResourceGroup, ResourceType } from '../../types/resources'
74
+ import ResourceExplorerSidebar from './ResourceExplorerSidebar.vue'
75
+ import ResourceExplorerViewer from './ResourceExplorerViewer.vue'
76
+ import BrandedButton from '../BrandedButton.vue'
77
+
78
+ const props = withDefaults(defineProps<{
79
+ dataset: DatasetV2
80
+ noResultsImage?: string
81
+ }>(), {
82
+ noResultsImage: '',
83
+ })
84
+
85
+ const { t } = useTranslation()
86
+ const router = useRouter()
87
+ const config = useComponentsConfig()
88
+
89
+ const sidebarCollapsed = ref(false)
90
+ const search = ref('')
91
+ const { debounced: searchDebounced, flush } = useDebouncedRef(search, config.searchDebounce ?? 300)
92
+
93
+ const PAGE_SIZE = 10
94
+ const url = computed(() => `/api/2/datasets/${props.dataset.id}/resources/`)
95
+
96
+ // Resource ID from URL query
97
+ const resourceIdQuery = useRouteQuery<string | undefined>('resource_id')
98
+
99
+ // Fetch resources for each type
100
+ const mainParams = computed(() => ({
101
+ type: 'main' as const,
102
+ page_size: PAGE_SIZE,
103
+ q: searchDebounced.value || undefined,
104
+ }))
105
+ const documentationParams = computed(() => ({
106
+ type: 'documentation' as const,
107
+ page_size: PAGE_SIZE,
108
+ q: searchDebounced.value || undefined,
109
+ }))
110
+ const updateParams = computed(() => ({
111
+ type: 'update' as const,
112
+ page_size: PAGE_SIZE,
113
+ q: searchDebounced.value || undefined,
114
+ }))
115
+ const apiParams = computed(() => ({
116
+ type: 'api' as const,
117
+ page_size: PAGE_SIZE,
118
+ q: searchDebounced.value || undefined,
119
+ }))
120
+ const codeParams = computed(() => ({
121
+ type: 'code' as const,
122
+ page_size: PAGE_SIZE,
123
+ q: searchDebounced.value || undefined,
124
+ }))
125
+ const otherParams = computed(() => ({
126
+ type: 'other' as const,
127
+ page_size: PAGE_SIZE,
128
+ q: searchDebounced.value || undefined,
129
+ }))
130
+
131
+ const { data: mainData, status: mainStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: mainParams })
132
+ const { data: documentationData, status: documentationStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: documentationParams, server: false })
133
+ const { data: updateData, status: updateStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: updateParams, server: false })
134
+ const { data: apiData, status: apiStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: apiParams, server: false })
135
+ const { data: codeData, status: codeStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: codeParams, server: false })
136
+ const { data: otherData, status: otherStatus } = await useFetch<PaginatedArray<Resource>>(url, { params: otherParams, server: false })
137
+
138
+ const rawResourcesByTypes = [
139
+ { data: mainData, status: mainStatus },
140
+ { data: documentationData, status: documentationStatus },
141
+ { data: updateData, status: updateStatus },
142
+ { data: apiData, status: apiStatus },
143
+ { data: codeData, status: codeStatus },
144
+ { data: otherData, status: otherStatus },
145
+ ]
146
+
147
+ // Evaluated once at setup (before any search) — never changes afterwards
148
+ const hasAnyResources = computed(() => {
149
+ return props.dataset.resources.total > 0
150
+ })
151
+
152
+ const extraResourcesByType: Ref<Resource[]>[] = RESOURCE_TYPE.map(() => ref<Resource[]>([]))
153
+ const pageByType: Ref<number>[] = RESOURCE_TYPE.map(() => ref(1))
154
+
155
+ watch(searchDebounced, () => {
156
+ for (let i = 0; i < RESOURCE_TYPE.length; i++) {
157
+ extraResourcesByType[i]!.value = []
158
+ pageByType[i]!.value = 1
159
+ }
160
+ })
161
+
162
+ const loadMore = async (type: ResourceType) => {
163
+ const index = RESOURCE_TYPE.indexOf(type)
164
+ if (index === -1) return
165
+ const pageRef = pageByType[index]!
166
+ const extraRef = extraResourcesByType[index]!
167
+ pageRef.value++
168
+
169
+ const { data } = await useFetch<PaginatedArray<Resource>>(url, {
170
+ params: {
171
+ type,
172
+ page_size: PAGE_SIZE,
173
+ page: pageRef.value,
174
+ q: searchDebounced.value || undefined,
175
+ },
176
+ })
177
+
178
+ if (data.value) {
179
+ extraRef.value = [...extraRef.value, ...data.value.data]
180
+ }
181
+ }
182
+
183
+ const allResources = computed<ResourceGroup[]>(() => {
184
+ return RESOURCE_TYPE
185
+ .map((type, index) => {
186
+ const rawData = rawResourcesByTypes[index]!
187
+ const extraData = extraResourcesByType[index]!
188
+ return {
189
+ type: type as ResourceType,
190
+ total: rawData.data.value?.total ?? 0,
191
+ items: [...(rawData.data.value?.data ?? []), ...extraData.value],
192
+ }
193
+ })
194
+ .filter(group => group.items.length > 0)
195
+ })
196
+
197
+ const flatResources = computed(() =>
198
+ allResources.value.flatMap(g => g.items),
199
+ )
200
+
201
+ // Fetch resource by ID if specified in URL (for SSR)
202
+ const initialResourceId = resourceIdQuery.value
203
+ const { data: fetchedResource } = initialResourceId
204
+ ? await useFetch<Resource>(`/api/1/datasets/${props.dataset.id}/resources/${initialResourceId}/`)
205
+ : { data: ref(null) }
206
+
207
+ // Initial selection (synchronous for SSR hydration)
208
+ function getInitialResource(): Resource | null {
209
+ const resourceId = resourceIdQuery.value
210
+ if (resourceId) {
211
+ // First check in already loaded resources
212
+ const existing = flatResources.value.find(r => r.id === resourceId)
213
+ if (existing) return existing
214
+ // Use fetched resource if available
215
+ if (fetchedResource.value) return fetchedResource.value
216
+ }
217
+ // Default to first resource
218
+ return flatResources.value[0] ?? null
219
+ }
220
+
221
+ const selectedResource = ref<Resource | null>(getInitialResource())
222
+
223
+ function updateSearch(newSearch: string) {
224
+ search.value = newSearch
225
+ flush()
226
+ }
227
+
228
+ const selectResource = (resource: Resource) => {
229
+ selectedResource.value = resource
230
+ router.replace({
231
+ query: { ...router.currentRoute.value.query, resource_id: resource.id },
232
+ })
233
+ }
234
+
235
+ // Update selection when resources change (e.g., after client-side fetch completes)
236
+ watch(flatResources, () => {
237
+ if (selectedResource.value) return
238
+ const firstResource = flatResources.value[0]
239
+ if (firstResource) {
240
+ selectedResource.value = firstResource
241
+ }
242
+ })
243
+ </script>
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <aside
3
+ v-if="!collapsed"
4
+ class="w-72 shrink-0 pl-4"
5
+ >
6
+ <div class="flex items-center justify-between mb-3">
7
+ <h3 class="text-sm font-bold uppercase mb-0">
8
+ {{ t('Ressources') }}
9
+ </h3>
10
+ <button
11
+ type="button"
12
+ :title="t('Masquer le panneau')"
13
+ class="p-1 hover:bg-gray-100 rounded"
14
+ @click="$emit('update:collapsed', true)"
15
+ >
16
+ <RiArrowRightSLine class="size-5" />
17
+ </button>
18
+ </div>
19
+
20
+ <div class="mb-3">
21
+ <label
22
+ :for="searchId"
23
+ class="sr-only"
24
+ >{{ t('Rechercher') }}</label>
25
+ <input
26
+ :id="searchId"
27
+ :value="search"
28
+ type="search"
29
+ class="w-full border border-gray-default rounded px-2.5 py-1.5 text-sm"
30
+ :placeholder="t('Rechercher')"
31
+ @input="$emit('update:search', ($event.target as HTMLInputElement).value)"
32
+ >
33
+ </div>
34
+
35
+ <div class="space-y-4 overflow-y-auto">
36
+ <div
37
+ v-for="group in resources"
38
+ :key="group.type"
39
+ >
40
+ <div class="text-xs text-gray-plain font-bold uppercase mb-1.5 border-b border-gray-default pb-1">
41
+ {{ getResourceLabel(group.type, group.total) }}
42
+ </div>
43
+ <ul class="list-none p-0 m-0 space-y-0.5">
44
+ <li
45
+ v-for="resource in group.items"
46
+ :key="resource.id"
47
+ >
48
+ <button
49
+ type="button"
50
+ class="w-full text-left px-2 py-1.5 rounded text-sm flex items-center gap-1.5 hover:bg-gray-100"
51
+ :class="{
52
+ 'font-bold bg-blue-50': resource.id === selectedResourceId,
53
+ }"
54
+ @click="$emit('select', resource)"
55
+ >
56
+ <ResourceIcon
57
+ :resource
58
+ class="size-3.5 shrink-0"
59
+ />
60
+ <span class="truncate">{{ resource.title || t('Fichier sans nom') }}</span>
61
+ </button>
62
+ </li>
63
+ </ul>
64
+ <button
65
+ v-if="group.items.length < group.total"
66
+ type="button"
67
+ class="w-full text-left px-2 py-1.5 text-sm text-blue-default hover:underline"
68
+ @click="$emit('load-more', group.type)"
69
+ >
70
+ {{ t('Charger plus…') }}
71
+ </button>
72
+ </div>
73
+ </div>
74
+ </aside>
75
+
76
+ <div
77
+ v-else
78
+ class="shrink-0 flex items-start pt-1"
79
+ >
80
+ <button
81
+ type="button"
82
+ :title="t('Afficher le panneau des ressources')"
83
+ class="p-1 hover:bg-gray-100 rounded border border-gray-default"
84
+ @click="$emit('update:collapsed', false)"
85
+ >
86
+ <RiArrowLeftSLine class="size-5" />
87
+ </button>
88
+ </div>
89
+ </template>
90
+
91
+ <script setup lang="ts">
92
+ import { useId } from 'vue'
93
+ import { RiArrowRightSLine, RiArrowLeftSLine } from '@remixicon/vue'
94
+ import ResourceIcon from '../ResourceAccordion/ResourceIcon.vue'
95
+ import { getResourceLabel } from '../../functions/resources'
96
+ import { useTranslation } from '../../composables/useTranslation'
97
+ import type { Resource, ResourceGroup, ResourceType } from '../../types/resources'
98
+
99
+ const { t } = useTranslation()
100
+
101
+ defineProps<{
102
+ resources: ResourceGroup[]
103
+ selectedResourceId: string | null
104
+ collapsed: boolean
105
+ search: string
106
+ }>()
107
+
108
+ defineEmits<{
109
+ 'select': [resource: Resource]
110
+ 'load-more': [type: ResourceType]
111
+ 'update:collapsed': [value: boolean]
112
+ 'update:search': [value: string]
113
+ }>()
114
+
115
+ const searchId = useId()
116
+ </script>