@datagouv/components-next 1.0.2-dev.19 → 1.0.2-dev.190

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 (156) hide show
  1. package/README.md +7 -6
  2. package/assets/main.css +4 -0
  3. package/dist/{Control-DuZJdKV_.js → Control-B9WQxlpJ.js} +1 -1
  4. package/dist/{Datafair.client-Ds3VQrRi.js → Datafair.client-VsEj9A00.js} +9 -9
  5. package/dist/{Event--kp8kMdJ.js → Event-iXDrp-fy.js} +31 -31
  6. package/dist/{Image-34hvypZI.js → Image-mNMBOofs.js} +9 -9
  7. package/dist/ImagePreview.client-cv32--L0.js +36 -0
  8. package/dist/JsonPreview.client-JmyzznjY.js +40 -0
  9. package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
  10. package/dist/{MapContainer.client-B2BijMx5.js → MapContainer.client-BxGjbFJV.js} +41 -42
  11. package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
  12. package/dist/{PdfPreview.client-DJBMzrlC.js → PdfPreview.client-CkrjnmJP.js} +835 -856
  13. package/dist/Pmtiles.client-EAekMgpr.js +26333 -0
  14. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-B0OeAb39.js +60 -0
  15. package/dist/{ScaleLine-KW-nXqp3.js → ScaleLine-VGQ7nkMS.js} +2 -2
  16. package/dist/{Tile-DbNFNPfU.js → Tile-CSpn0Ca8.js} +160 -163
  17. package/dist/{TileImage-BsXBxMtq.js → TileImage-CpbaEFwd.js} +143 -131
  18. package/dist/{View-BR92hTWP.js → View-BYBpt-AU.js} +483 -422
  19. package/dist/XmlPreview.client-WcOMRUAD.js +34 -0
  20. package/dist/{common-PJfpC179.js → common-BoeTUnDZ.js} +52 -52
  21. package/dist/components-next.css +1 -52
  22. package/dist/components-next.js +194 -149
  23. package/dist/components.css +2 -2
  24. package/dist/{index-BFQX7Ccz.js → index-DLsJxwiN.js} +21842 -15810
  25. package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-D0FPslrZ.js} +185 -187
  26. package/dist/{main-vqhkVhVS.js → main-HzoTSLAM.js} +41511 -73246
  27. package/dist/{proj-DsetBcW7.js → proj-DxGbobp7.js} +662 -626
  28. package/dist/{text-clamp.esm-B5kW_XMt.js → text-clamp.esm-DVseBqZZ.js} +4 -4
  29. package/dist/{tilecoord-Db24Px13.js → tilecoord-DvX7WWiA.js} +273 -264
  30. package/dist/{vue3-json-viewer-BXwup7nO.js → vue3-json-viewer-CJLQFzXZ.js} +10 -16
  31. package/dist/{vue3-xml-viewer.common-BcNJo35o.js → vue3-xml-viewer.common-iq0K0tIG.js} +16 -16
  32. package/package.json +47 -38
  33. package/src/chart.ts +5 -0
  34. package/src/components/ActivityList/ActivityList.vue +3 -2
  35. package/src/components/AppLink.vue +3 -0
  36. package/src/components/BannerAction.vue +6 -2
  37. package/src/components/Chart/ChartViewer.vue +320 -0
  38. package/src/components/Chart/ChartViewerWrapper.vue +182 -0
  39. package/src/components/CopyButton.vue +6 -2
  40. package/src/components/DataserviceCard.vue +9 -0
  41. package/src/components/DataserviceQuality.vue +82 -0
  42. package/src/components/DataserviceQualityInline.vue +31 -0
  43. package/src/components/DataserviceQualityTooltipContent.vue +79 -0
  44. package/src/components/DatasetCard.vue +9 -4
  45. package/src/components/DatasetQuality.vue +11 -11
  46. package/src/components/DatasetQualityInline.vue +2 -2
  47. package/src/components/DatasetQualityTooltipContent.vue +1 -1
  48. package/src/components/Form/Listbox.vue +101 -0
  49. package/src/components/Form/SchemaSelect.vue +2 -0
  50. package/src/components/InfiniteLoader.vue +53 -0
  51. package/src/components/ObjectCardHeader.vue +11 -4
  52. package/src/components/OpenApiViewer/ContentTypeSelect.vue +48 -0
  53. package/src/components/OpenApiViewer/EndpointRequest.vue +164 -0
  54. package/src/components/OpenApiViewer/EndpointResponses.vue +149 -0
  55. package/src/components/OpenApiViewer/OpenApiViewer.vue +308 -0
  56. package/src/components/OpenApiViewer/SchemaPanel.vue +53 -0
  57. package/src/components/OpenApiViewer/SchemaTree.vue +77 -0
  58. package/src/components/OpenApiViewer/openapi.ts +150 -0
  59. package/src/components/OrganizationNameWithCertificate.vue +3 -2
  60. package/src/components/Pagination.vue +23 -58
  61. package/src/components/RadioInput.vue +7 -2
  62. package/src/components/ResourceAccordion/DataStructure.vue +11 -33
  63. package/src/components/ResourceAccordion/Datafair.client.vue +1 -1
  64. package/src/components/ResourceAccordion/Downloads.vue +160 -0
  65. package/src/components/ResourceAccordion/ImagePreview.client.vue +48 -0
  66. package/src/components/ResourceAccordion/JsonPreview.client.vue +23 -104
  67. package/src/components/ResourceAccordion/MapContainer.client.vue +2 -11
  68. package/src/components/ResourceAccordion/Metadata.vue +1 -2
  69. package/src/components/ResourceAccordion/PdfPreview.client.vue +58 -97
  70. package/src/components/ResourceAccordion/PdfSkeleton.vue +44 -0
  71. package/src/components/ResourceAccordion/Pmtiles.client.vue +28 -13
  72. package/src/components/ResourceAccordion/Preview.vue +11 -11
  73. package/src/components/ResourceAccordion/PreviewWrapper.vue +89 -0
  74. package/src/components/ResourceAccordion/ResourceAccordion.vue +23 -114
  75. package/src/components/ResourceAccordion/XmlPreview.client.vue +16 -98
  76. package/src/components/ResourceExplorer/ResourceExplorer.vue +102 -210
  77. package/src/components/ResourceExplorer/ResourceExplorerHeader.vue +74 -0
  78. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +103 -71
  79. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +143 -291
  80. package/src/components/ResourceExplorer/ResourceMainAction.vue +240 -0
  81. package/src/components/ResourceExplorer/ResourceSelector.vue +51 -0
  82. package/src/components/ResourceExplorer/ResourceViewerHeader.vue +115 -0
  83. package/src/components/ResourceExplorer/ResourceViewerSkeleton.vue +94 -0
  84. package/src/components/ResourceListItem.vue +154 -0
  85. package/src/components/ReuseCard.vue +14 -6
  86. package/src/components/ReuseDetails.vue +2 -2
  87. package/src/components/ReuseHorizontalCard.vue +2 -2
  88. package/src/components/SchemaCard.vue +10 -1
  89. package/src/components/Search/Filter/AccessTypeFilter.vue +2 -2
  90. package/src/components/Search/GlobalSearch.vue +220 -117
  91. package/src/components/Search/SearchInput.vue +5 -4
  92. package/src/components/Search/Sidemenu.vue +4 -3
  93. package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
  94. package/src/components/TabularExplorer/TabularCell.vue +50 -0
  95. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  96. package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
  97. package/src/components/TabularExplorer/TabularExplorer.vue +299 -0
  98. package/src/components/TabularExplorer/TabularFilterContent.vue +362 -0
  99. package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
  100. package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
  101. package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
  102. package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
  103. package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
  104. package/src/components/TabularExplorer/TabularTable.vue +481 -0
  105. package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
  106. package/src/components/TabularExplorer/types.ts +81 -0
  107. package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
  108. package/src/components/TabularExplorer/useTabularContext.ts +82 -0
  109. package/src/composables/useActiveDescendant.ts +5 -0
  110. package/src/composables/useDatasetResources.ts +164 -0
  111. package/src/composables/useHasTabularData.ts +15 -1
  112. package/src/composables/useMetrics.ts +1 -1
  113. package/src/composables/useResizable.ts +46 -0
  114. package/src/composables/useResourceCapabilities.ts +48 -40
  115. package/src/composables/useSearchFilter.ts +120 -0
  116. package/src/composables/useStableQueryParams.ts +38 -6
  117. package/src/composables/useTabularProfile.ts +70 -0
  118. package/src/composables/useTranslation.ts +24 -9
  119. package/src/config.ts +21 -3
  120. package/src/functions/activities.ts +3 -3
  121. package/src/functions/api.ts +20 -42
  122. package/src/functions/api.types.ts +1 -0
  123. package/src/functions/charts.ts +99 -0
  124. package/src/functions/datasets.ts +0 -17
  125. package/src/functions/dates.ts +3 -2
  126. package/src/functions/helpers.ts +15 -0
  127. package/src/functions/metrics.ts +63 -60
  128. package/src/functions/paginate.ts +46 -0
  129. package/src/functions/resourceCapabilities.ts +34 -0
  130. package/src/functions/resources.ts +136 -1
  131. package/src/functions/schemas.ts +27 -0
  132. package/src/functions/tabular.ts +214 -0
  133. package/src/functions/tabularApi.ts +96 -11
  134. package/src/main.ts +117 -14
  135. package/src/types/dataservices.ts +17 -0
  136. package/src/types/organizations.ts +10 -0
  137. package/src/types/pages.ts +0 -5
  138. package/src/types/posts.ts +2 -2
  139. package/src/types/reports.ts +5 -1
  140. package/src/types/resources.ts +0 -1
  141. package/src/types/reuses.ts +12 -1
  142. package/src/types/search.ts +63 -1
  143. package/src/types/site.ts +5 -3
  144. package/src/types/ui.ts +2 -0
  145. package/src/types/users.ts +2 -1
  146. package/src/types/visualizations.ts +93 -0
  147. package/assets/swagger-themes/newspaper.css +0 -1670
  148. package/dist/JsonPreview.client-CYxmz0yV.js +0 -72
  149. package/dist/Pmtiles.client-C_hvRDuy.js +0 -25185
  150. package/dist/Swagger.client-B4y0hXME.js +0 -4
  151. package/dist/XmlPreview.client-LETEfNBH.js +0 -64
  152. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  153. package/src/functions/pagination.ts +0 -9
  154. /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
  155. /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
  156. /package/src/components/{DatasetQualityScore.vue → QualityScore.vue} +0 -0
@@ -0,0 +1,79 @@
1
+ <template>
2
+ <p class="text-sm text-gray-plain font-bold mb-0">
3
+ {{ t("Qualité des métadonnées :") }}
4
+ </p>
5
+ <ul class="list-none pl-0 space-y-2">
6
+ <QualityItem
7
+ :passed="quality.has_description"
8
+ :message-passed="t('Description renseignée')"
9
+ :message-failed="t('Description non renseignée')"
10
+ class="pb-0"
11
+ />
12
+ <QualityItem
13
+ :passed="quality.has_machine_documentation"
14
+ :message-passed="t('Documentation machine (OpenAPI) disponible')"
15
+ :message-failed="t('Documentation machine (OpenAPI) manquante')"
16
+ class="pb-0"
17
+ />
18
+ <QualityItem
19
+ :passed="quality.has_technical_documentation"
20
+ :message-passed="t('Documentation technique renseignée')"
21
+ :message-failed="t('Documentation technique manquante')"
22
+ class="pb-0"
23
+ />
24
+ <QualityItem
25
+ :passed="quality.has_business_documentation"
26
+ :message-passed="t('Documentation métier renseignée')"
27
+ :message-failed="t('Documentation métier manquante')"
28
+ class="pb-0"
29
+ />
30
+ <QualityItem
31
+ :passed="quality.license"
32
+ :message-passed="t('Licence renseignée')"
33
+ :message-failed="t('Licence non renseignée')"
34
+ class="pb-0"
35
+ />
36
+ <QualityItem
37
+ :passed="quality.has_contact_point"
38
+ :message-passed="t('Point de contact renseigné')"
39
+ :message-failed="t('Point de contact non renseigné')"
40
+ class="pb-0"
41
+ />
42
+ <QualityItem
43
+ :passed="quality.has_base_api_url"
44
+ :message-passed="t(`URL de base de l'API renseignée`)"
45
+ :message-failed="t(`URL de base de l'API non renseignée`)"
46
+ class="pb-0"
47
+ />
48
+ <QualityItem
49
+ :passed="quality.availability_documented"
50
+ :message-passed="t('Disponibilité documentée')"
51
+ :message-failed="t('Disponibilité non documentée')"
52
+ class="pb-0"
53
+ />
54
+ <QualityItem
55
+ :passed="quality.rate_limiting_documented"
56
+ :message-passed="t(`Limite d'appels documentée`)"
57
+ :message-failed="t(`Limite d'appels non documentée`)"
58
+ class="pb-0"
59
+ />
60
+ <QualityItem
61
+ :passed="quality.access_conditions_clear"
62
+ :message-passed="t(`Conditions d'accès claires`)"
63
+ :message-failed="t(`Conditions d'accès non précisées`)"
64
+ class="pb-0"
65
+ />
66
+ </ul>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import type { DataserviceQuality } from '../types/dataservices'
71
+ import { useTranslation } from '../composables/useTranslation'
72
+ import QualityItem from './QualityItem.vue'
73
+
74
+ defineProps<{
75
+ quality: DataserviceQuality
76
+ }>()
77
+
78
+ const { t } = useTranslation()
79
+ </script>
@@ -36,6 +36,7 @@
36
36
  :icon="RiDatabase2Line"
37
37
  :url="datasetUrl || dataset.page"
38
38
  :target="datasetUrlInNewTab ? '_blank' : undefined"
39
+ :title-tag="titleTag"
39
40
  >
40
41
  {{ dataset.title }}
41
42
  <template
@@ -106,10 +107,12 @@
106
107
  </p>
107
108
  </div>
108
109
  </div>
109
- <ObjectCardShortDescription
110
- v-if="showDescriptionShort"
111
- :text="getDescriptionShort(props.dataset)"
112
- />
110
+ <slot>
111
+ <ObjectCardShortDescription
112
+ v-if="showDescriptionShort"
113
+ :text="getDescriptionShort(props.dataset)"
114
+ />
115
+ </slot>
113
116
  </ObjectCard>
114
117
  </template>
115
118
 
@@ -117,6 +120,7 @@
117
120
  import type { RouteLocationRaw } from 'vue-router'
118
121
  import { RiArchiveLine, RiDatabase2Line, RiDownloadLine, RiEyeLine, RiLineChartLine, RiLockLine, RiStarLine, RiSubtractLine } from '@remixicon/vue'
119
122
  import type { Dataset, DatasetV2 } from '../types/datasets'
123
+ import type { TitleTag } from '../types/ui'
120
124
  import { summarize } from '../functions/helpers'
121
125
  import { useFormatDate } from '../functions/dates'
122
126
  import { getDescriptionShort } from '../functions/description'
@@ -137,6 +141,7 @@ type Props = {
137
141
  datasetUrlInNewTab?: boolean
138
142
  organizationUrl?: RouteLocationRaw
139
143
  showDescriptionShort?: boolean
144
+ titleTag?: TitleTag
140
145
  }
141
146
 
142
147
  const props = withDefaults(defineProps<Props>(), {
@@ -13,7 +13,7 @@
13
13
  {{ t('Qualité des métadonnées:') }}
14
14
  </span>
15
15
  </span>
16
- <DatasetQualityScore
16
+ <QualityScore
17
17
  :score="quality.score"
18
18
  class="w-full"
19
19
  />
@@ -23,35 +23,35 @@
23
23
  </Toggletip>
24
24
  <template v-if="!hideWarnings">
25
25
  <ul class="list-none pl-0">
26
- <DatasetQualityItemWarning
26
+ <QualityItemWarning
27
27
  :quality-item="quality.dataset_description_quality"
28
28
  :message="t('Description des données non renseignée')"
29
29
  />
30
- <DatasetQualityItemWarning
30
+ <QualityItemWarning
31
31
  :quality-item="quality.resources_documentation"
32
32
  :message="t('Documentation des fichiers manquante')"
33
33
  />
34
- <DatasetQualityItemWarning
34
+ <QualityItemWarning
35
35
  :quality-item="quality.license"
36
36
  :message="t('Licence non renseignée')"
37
37
  />
38
- <DatasetQualityItemWarning
38
+ <QualityItemWarning
39
39
  :quality-item="quality.update_frequency && quality.update_fulfilled_in_time"
40
40
  :message="quality.update_frequency ? t('Fréquence de mise à jour non respectée') : t('Fréquence de mise à jour non renseignée')"
41
41
  />
42
- <DatasetQualityItemWarning
42
+ <QualityItemWarning
43
43
  :quality-item="quality.has_open_format"
44
44
  :message="t('Formats de fichiers non standards')"
45
45
  />
46
- <DatasetQualityItemWarning
46
+ <QualityItemWarning
47
47
  :quality-item="quality.temporal_coverage"
48
48
  :message="t('Couverture temporelle non renseignée')"
49
49
  />
50
- <DatasetQualityItemWarning
50
+ <QualityItemWarning
51
51
  :quality-item="quality.spatial"
52
52
  :message="t('Couverture spatiale non renseignée')"
53
53
  />
54
- <DatasetQualityItemWarning
54
+ <QualityItemWarning
55
55
  :quality-item="quality.all_resources_available"
56
56
  :message="t('Certains fichiers ne sont pas disponibles')"
57
57
  />
@@ -62,8 +62,8 @@
62
62
  <script setup lang="ts">
63
63
  import { RiInformationLine } from '@remixicon/vue'
64
64
  import type { Quality } from '../types/datasets'
65
- import DatasetQualityItemWarning from './DatasetQualityItemWarning.vue'
66
- import DatasetQualityScore from './DatasetQualityScore.vue'
65
+ import QualityItemWarning from './QualityItemWarning.vue'
66
+ import QualityScore from './QualityScore.vue'
67
67
  import Toggletip from './Toggletip.vue'
68
68
  import DatasetQualityTooltipContent from './DatasetQualityTooltipContent.vue'
69
69
  import { useTranslation } from '../composables/useTranslation'
@@ -7,7 +7,7 @@
7
7
  >
8
8
  <RiInformationLine class="size-3.5 shrink-0" />
9
9
  <span class="text-gray-medium text-sm group-hover/quality:underline">{{ t('Métadonnées :') }}</span>
10
- <DatasetQualityScore :score="quality.score" />
10
+ <QualityScore :score="quality.score" />
11
11
  <template #toggletip>
12
12
  <DatasetQualityTooltipContent :quality />
13
13
  </template>
@@ -18,7 +18,7 @@
18
18
  <script setup lang="ts">
19
19
  import { RiInformationLine } from '@remixicon/vue'
20
20
  import type { Quality } from '../types/datasets'
21
- import DatasetQualityScore from './DatasetQualityScore.vue'
21
+ import QualityScore from './QualityScore.vue'
22
22
  import DatasetQualityTooltipContent from './DatasetQualityTooltipContent.vue'
23
23
  import Toggletip from './Toggletip.vue'
24
24
  import { useTranslation } from '../composables/useTranslation'
@@ -68,7 +68,7 @@
68
68
  import type { Quality } from '../types/datasets'
69
69
  import { useComponentsConfig } from '../config'
70
70
  import { useTranslation } from '../composables/useTranslation'
71
- import DatasetQualityItem from './DatasetQualityItem.vue'
71
+ import DatasetQualityItem from './QualityItem.vue'
72
72
 
73
73
  defineProps<{
74
74
  quality: Quality
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <Listbox v-model="model">
3
+ <div class="relative min-w-0">
4
+ <div
5
+ ref="floatingReference"
6
+ class="relative w-full cursor-default overflow-hidden bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm"
7
+ >
8
+ <ListboxButton class="input shadow-input text-sm flex items-center gap-2">
9
+ <slot name="button">
10
+ <div class="w-full flex items-center justify-between gap-2">
11
+ <div
12
+ class="truncate"
13
+ :class="{ 'text-new-disabled-text': isDisabled(model) }"
14
+ >
15
+ {{ displayValue(model) }}
16
+ </div>
17
+ <RiArrowDownSLine class="flex-none size-4 justify-self-end" />
18
+ </div>
19
+ </slot>
20
+ </ListboxButton>
21
+ </div>
22
+
23
+ <ListboxOptions
24
+ ref="popover"
25
+ :style="floatingStyles"
26
+ class="z-10 mt-1 absolute max-h-60 min-w-80 w-full overflow-auto rounded-md bg-white text-base shadow-lg ring-1 ring-black/5 focus:outline-none sm:text-sm pl-0"
27
+ >
28
+ <ListboxOption
29
+ v-for="option in options"
30
+ :key="getOptionId(toValue(option))"
31
+ v-slot="{ active, selected }"
32
+ as="template"
33
+ :value="option"
34
+ >
35
+ <li
36
+ class="relative cursor-default select-none py-2 pr-4 list-none flex items-center gap-2 text-gray-900"
37
+ :class="{
38
+ 'bg-gray-lower': active && !isDisabled(toValue(option)),
39
+ 'text-new-disabled-text': isDisabled(toValue(option)),
40
+ 'pl-2': selected,
41
+ 'pl-6': !selected,
42
+ }"
43
+ >
44
+ <div class="flex items-center justify-center aspect-square">
45
+ <RiCheckLine
46
+ v-if="selected"
47
+ class="size-4"
48
+ :class="isDisabled(toValue(option)) ?' text-new-disabled-text' : 'text-new-primary'"
49
+ />
50
+ </div>
51
+ <slot
52
+ name="option"
53
+ v-bind="{ option, active }"
54
+ >
55
+ {{ displayValue(option) }}
56
+ </slot>
57
+ </li>
58
+ </ListboxOption>
59
+ </ListboxOptions>
60
+ </div>
61
+ </Listbox>
62
+ </template>
63
+
64
+ <script setup lang="ts" generic="T extends string | number | object">
65
+ import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/vue'
66
+ import { useFloating, autoUpdate, autoPlacement } from '@floating-ui/vue'
67
+ import { toValue, useTemplateRef } from 'vue'
68
+ import { RiArrowDownSLine, RiCheckLine } from '@remixicon/vue'
69
+
70
+ withDefaults(defineProps<{
71
+ options?: Array<T>
72
+ getOptionId?: (option: T) => string | number
73
+ displayValue: (option: T | null) => string
74
+ isDisabled?: (option: T | null) => boolean
75
+ }>(), {
76
+ getOptionId: (option: T): string | number => {
77
+ if (typeof option === 'string') return option
78
+ if (typeof option === 'number') return option
79
+ if (typeof option === 'object' && 'id' in option) return option.id as string
80
+
81
+ throw new Error('Please set getOptionId()')
82
+ },
83
+ isDisabled: (option: T | null): boolean => {
84
+ if (option && typeof option === 'object' && 'disabled' in option) return option.disabled as boolean
85
+
86
+ return false
87
+ },
88
+ })
89
+
90
+ const model = defineModel<T | null>({ required: true })
91
+
92
+ const referenceRef = useTemplateRef('floatingReference')
93
+ const floatingRef = useTemplateRef<InstanceType<typeof ListboxOptions>>('popover')
94
+ const { floatingStyles } = useFloating(referenceRef, floatingRef, {
95
+ middleware: [autoPlacement({
96
+ allowedPlacements: ['bottom-start', 'bottom', 'bottom-end'],
97
+ crossAxis: true,
98
+ })],
99
+ whileElementsMounted: autoUpdate,
100
+ })
101
+ </script>
@@ -8,6 +8,7 @@
8
8
  :placeholder="t('Tous les schémas')"
9
9
  :get-option-id="(s: RegisteredSchema) => s.name"
10
10
  :display-value="(s: RegisteredSchema) => s.name"
11
+ :filter="schemaMatchesQuery"
11
12
  :multiple="false"
12
13
  />
13
14
  </template>
@@ -16,6 +17,7 @@
16
17
  import { useTranslation } from '../../composables/useTranslation'
17
18
  import { useSelectModelSync } from '../../composables/useSelectModelSync'
18
19
  import { useFetch } from '../../functions/api'
20
+ import { schemaMatchesQuery } from '../../functions/schemas'
19
21
  import type { RegisteredSchema } from '../../types/schemas'
20
22
  import SearchableSelect from './SearchableSelect.vue'
21
23
 
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <div ref="sentinel">
3
+ <slot>
4
+ <div class="flex items-center justify-center p-4">
5
+ <span class="inline-flex items-center gap-2 text-xs text-gray-medium">
6
+ <RiLoader4Line
7
+ class="size-4 animate-spin"
8
+ aria-hidden="true"
9
+ />
10
+ {{ t('Chargement…') }}
11
+ </span>
12
+ </div>
13
+ </slot>
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { onMounted, onUnmounted, useTemplateRef, watch } from 'vue'
19
+ import { RiLoader4Line } from '@remixicon/vue'
20
+ import { useTranslation } from '../composables/useTranslation'
21
+
22
+ const props = defineProps<{
23
+ root?: HTMLElement | null
24
+ }>()
25
+
26
+ const emit = defineEmits<{
27
+ intersect: []
28
+ }>()
29
+
30
+ const { t } = useTranslation()
31
+
32
+ const sentinelRef = useTemplateRef<HTMLElement>('sentinel')
33
+ let observer: IntersectionObserver | null = null
34
+
35
+ function setupObserver() {
36
+ observer?.disconnect()
37
+ const el = sentinelRef.value
38
+ if (!el) return
39
+ observer = new IntersectionObserver(
40
+ (entries) => {
41
+ if (entries[0]?.isIntersecting) {
42
+ emit('intersect')
43
+ }
44
+ },
45
+ { root: props.root ?? null, rootMargin: '200px' },
46
+ )
47
+ observer.observe(el)
48
+ }
49
+
50
+ onMounted(setupObserver)
51
+ watch([sentinelRef, () => props.root], setupObserver)
52
+ onUnmounted(() => observer?.disconnect())
53
+ </script>
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <h3 class="w-full text-base flex">
2
+ <component
3
+ :is="titleTag"
4
+ class="w-full text-base font-bold flex"
5
+ >
3
6
  <AppLink
4
7
  :to="url"
5
8
  class="text-gray-title text-base bg-none flex items-center w-full truncate gap-1"
@@ -19,17 +22,21 @@
19
22
  <slot name="extra" />
20
23
  <span class="absolute inset-0" />
21
24
  </AppLink>
22
- </h3>
25
+ </component>
23
26
  </template>
24
27
 
25
28
  <script setup lang="ts">
26
29
  import type { Component } from 'vue'
27
30
  import type { RouteLocationRaw } from 'vue-router'
31
+ import type { TitleTag } from '../types/ui'
28
32
  import AppLink from './AppLink.vue'
29
33
 
30
- defineProps<{
34
+ withDefaults(defineProps<{
31
35
  icon: Component
32
36
  url: RouteLocationRaw
33
37
  target?: string
34
- }>()
38
+ titleTag?: TitleTag
39
+ }>(), {
40
+ titleTag: 'h3',
41
+ })
35
42
  </script>
@@ -0,0 +1,48 @@
1
+ <template>
2
+ <Tooltip v-if="contentTypes.length > 1">
3
+ <div class="relative shrink-0">
4
+ <select
5
+ :value="modelValue"
6
+ class="appearance-none text-xs font-mono bg-white border border-gray-default rounded pl-2 pr-6 py-1 text-gray-medium cursor-pointer hover:border-gray-400 transition-colors"
7
+ @change="$emit('update:modelValue', ($event.target as HTMLSelectElement).value)"
8
+ >
9
+ <option
10
+ v-for="ct in contentTypes"
11
+ :key="ct"
12
+ :value="ct"
13
+ >
14
+ {{ contentTypeLabel(ct) }}
15
+ </option>
16
+ </select>
17
+ <RiArrowDownSLine class="pointer-events-none absolute right-1 top-1/2 -translate-y-1/2 size-3.5 text-gray-medium" />
18
+ </div>
19
+ <template #tooltip>
20
+ {{ modelValue }}
21
+ </template>
22
+ </Tooltip>
23
+ <Tooltip v-else-if="contentTypes.length === 1">
24
+ <span
25
+ class="text-xs font-mono bg-white border border-gray-default rounded px-2 py-1 text-gray-medium shrink-0"
26
+ >
27
+ {{ contentTypeLabel(contentTypes[0]!) }}
28
+ </span>
29
+ <template #tooltip>
30
+ {{ contentTypes[0] }}
31
+ </template>
32
+ </Tooltip>
33
+ </template>
34
+
35
+ <script setup lang="ts">
36
+ import { RiArrowDownSLine } from '@remixicon/vue'
37
+ import Tooltip from '../Tooltip.vue'
38
+ import { contentTypeLabel } from './openapi'
39
+
40
+ defineProps<{
41
+ contentTypes: string[]
42
+ modelValue: string
43
+ }>()
44
+
45
+ defineEmits<{
46
+ 'update:modelValue': [value: string]
47
+ }>()
48
+ </script>
@@ -0,0 +1,164 @@
1
+ <template>
2
+ <div class="border border-gray-default rounded">
3
+ <div
4
+ v-if="!tabs.length"
5
+ class="p-3 text-xs text-gray-medium"
6
+ >
7
+ {{ t("Aucun paramètre de requête") }}
8
+ </div>
9
+ <TabGroup
10
+ v-else
11
+ size="sm"
12
+ @change="onTabChange"
13
+ >
14
+ <div class="bg-gray-100 px-3 py-2 border-b border-gray-default flex items-center justify-between gap-2">
15
+ <TabList>
16
+ <Tab
17
+ v-for="tab in tabs"
18
+ :key="tab.key"
19
+ >
20
+ {{ tab.label }}
21
+ </Tab>
22
+ </TabList>
23
+ <ContentTypeSelect
24
+ v-if="activeTab === 'body' && bodyContentTypes.length"
25
+ :content-types="bodyContentTypes"
26
+ :model-value="selectedBodyContentType"
27
+ @update:model-value="selectedBodyContentType = $event"
28
+ />
29
+ </div>
30
+ <TabPanels>
31
+ <TabPanel
32
+ v-for="tab in tabs"
33
+ :key="tab.key"
34
+ >
35
+ <div
36
+ v-if="tab.key === 'query'"
37
+ class="p-3"
38
+ >
39
+ <div class="space-y-0 divide-y divide-gray-100">
40
+ <div
41
+ v-for="param in queryParams"
42
+ :key="param.name"
43
+ class="py-2"
44
+ >
45
+ <div class="flex items-baseline gap-2">
46
+ <span class="font-mono text-xs text-gray-title">
47
+ {{ param.name }}
48
+ <span
49
+ v-if="param.required"
50
+ class="text-red-600"
51
+ >*</span>
52
+ </span>
53
+ <span class="font-mono text-xs text-gray-medium">{{ getSchemaType(endpoint.spec, param.schema) }}</span>
54
+ </div>
55
+ <p
56
+ v-if="param.description"
57
+ class="text-xs text-gray-medium mt-0.5 mb-0"
58
+ >
59
+ {{ param.description }}
60
+ </p>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ <div
65
+ v-if="tab.key === 'path'"
66
+ class="p-3"
67
+ >
68
+ <div class="space-y-0 divide-y divide-gray-100">
69
+ <div
70
+ v-for="param in pathParams"
71
+ :key="param.name"
72
+ class="py-2"
73
+ >
74
+ <div class="flex items-baseline gap-2">
75
+ <span class="font-mono text-xs text-gray-title">
76
+ {{ param.name }}
77
+ <span class="text-red-600">*</span>
78
+ </span>
79
+ <span class="font-mono text-xs text-gray-medium">{{ getSchemaType(endpoint.spec, param.schema) }}</span>
80
+ </div>
81
+ <p
82
+ v-if="param.description"
83
+ class="text-xs text-gray-medium mt-0.5 mb-0"
84
+ >
85
+ {{ param.description }}
86
+ </p>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ <div
91
+ v-if="tab.key === 'body'"
92
+ class="p-3"
93
+ >
94
+ <SchemaPanel
95
+ v-if="currentBodyMediaType?.schema"
96
+ :spec="endpoint.spec"
97
+ :schema="currentBodyMediaType.schema"
98
+ />
99
+ </div>
100
+ </TabPanel>
101
+ </TabPanels>
102
+ </TabGroup>
103
+ </div>
104
+ </template>
105
+
106
+ <script setup lang="ts">
107
+ import { computed, ref, watch } from 'vue'
108
+ import TabGroup from '../Tabs/TabGroup.vue'
109
+ import TabList from '../Tabs/TabList.vue'
110
+ import Tab from '../Tabs/Tab.vue'
111
+ import TabPanels from '../Tabs/TabPanels.vue'
112
+ import TabPanel from '../Tabs/TabPanel.vue'
113
+ import ContentTypeSelect from './ContentTypeSelect.vue'
114
+ import { useTranslation } from '../../composables/useTranslation'
115
+ import SchemaPanel from './SchemaPanel.vue'
116
+ import { getSchemaType, type Endpoint } from './openapi'
117
+
118
+ const props = defineProps<{
119
+ endpoint: Endpoint
120
+ }>()
121
+
122
+ const { t } = useTranslation()
123
+
124
+ const queryParams = computed(() => props.endpoint.parameters.filter(p => p.in === 'query'))
125
+ const pathParams = computed(() => props.endpoint.parameters.filter(p => p.in === 'path'))
126
+
127
+ const tabs = computed(() => {
128
+ const result: { key: string, label: string }[] = []
129
+ if (pathParams.value.length) {
130
+ result.push({ key: 'path', label: t('Path') })
131
+ }
132
+ if (queryParams.value.length) {
133
+ result.push({ key: 'query', label: t('Query') })
134
+ }
135
+ if (props.endpoint.requestBody) {
136
+ result.push({ key: 'body', label: t('Body') })
137
+ }
138
+ return result
139
+ })
140
+
141
+ const activeTabIndex = ref(0)
142
+ const activeTab = computed(() => tabs.value[activeTabIndex.value]?.key || '')
143
+ const selectedBodyContentType = ref('')
144
+
145
+ const bodyContentTypes = computed(() => {
146
+ if (!props.endpoint.requestBody?.content) return []
147
+ return Object.keys(props.endpoint.requestBody.content)
148
+ })
149
+
150
+ const currentBodyMediaType = computed(() => {
151
+ if (!props.endpoint.requestBody?.content) return null
152
+ const ct = selectedBodyContentType.value || bodyContentTypes.value[0]
153
+ if (!ct) return null
154
+ return props.endpoint.requestBody.content[ct] || null
155
+ })
156
+
157
+ watch(bodyContentTypes, (types) => {
158
+ selectedBodyContentType.value = types[0] || ''
159
+ }, { immediate: true })
160
+
161
+ function onTabChange(index: number) {
162
+ activeTabIndex.value = index
163
+ }
164
+ </script>