@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,35 @@
1
+ <template>
2
+ <h3 class="w-full text-base flex">
3
+ <AppLink
4
+ :to="url"
5
+ class="text-gray-title text-base bg-none flex items-center w-full truncate gap-1"
6
+ :target="target"
7
+ >
8
+ <component
9
+ :is="icon"
10
+ aria-hidden="true"
11
+ class="size-4 flex-none"
12
+ />
13
+ <span
14
+ class="block truncate"
15
+ :class="$slots.extra ? 'flex-initial' : 'flex-1'"
16
+ >
17
+ <slot />
18
+ </span>
19
+ <slot name="extra" />
20
+ <span class="absolute inset-0" />
21
+ </AppLink>
22
+ </h3>
23
+ </template>
24
+
25
+ <script setup lang="ts">
26
+ import type { Component } from 'vue'
27
+ import type { RouteLocationRaw } from 'vue-router'
28
+ import AppLink from './AppLink.vue'
29
+
30
+ defineProps<{
31
+ icon: Component
32
+ url: RouteLocationRaw
33
+ target?: string
34
+ }>()
35
+ </script>
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div
3
+ v-if="organization"
4
+ class="-mr-0.5 flex-initial truncate"
5
+ >
6
+ <AppLink
7
+ class="link text-sm overflow-hidden flex items-center relative z-[2] truncate"
8
+ :to="organizationUrl || organization.page"
9
+ >
10
+ <OrganizationNameWithCertificate
11
+ :organization
12
+ size="sm"
13
+ />
14
+ </AppLink>
15
+ </div>
16
+ <div
17
+ v-else-if="ownerName"
18
+ class="mr-1 truncate"
19
+ >
20
+ {{ ownerName }}
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { computed } from 'vue'
26
+ import type { RouteLocationRaw } from 'vue-router'
27
+ import { getOwnerName } from '../functions/owned'
28
+ import type { OrganizationReference } from '../types/organizations'
29
+ import type { UserReference } from '../types/users'
30
+ import AppLink from './AppLink.vue'
31
+ import OrganizationNameWithCertificate from './OrganizationNameWithCertificate.vue'
32
+
33
+ const props = defineProps<{
34
+ organization?: OrganizationReference | null
35
+ owner?: UserReference | null
36
+ organizationUrl?: RouteLocationRaw
37
+ }>()
38
+
39
+ const ownerName = computed(() => {
40
+ if (!props.owner) return ''
41
+ return getOwnerName({ organization: null, owner: props.owner })
42
+ })
43
+ </script>
@@ -0,0 +1,28 @@
1
+ <template>
2
+ <p
3
+ v-if="shortDescription"
4
+ class="fr-text--sm fr-mt-1w fr-mb-0 overflow-wrap-anywhere hidden sm:line-clamp-2"
5
+ >
6
+ {{ shortDescription }}
7
+ </p>
8
+ </template>
9
+
10
+ <script setup lang="ts">
11
+ import { computed } from 'vue'
12
+ import { removeMarkdownSync } from '../functions/markdown'
13
+
14
+ const props = withDefaults(defineProps<{
15
+ text?: string | null
16
+ maxLength?: number
17
+ }>(), {
18
+ maxLength: 300,
19
+ })
20
+
21
+ // Truncate in JS to avoid sending very long markdown-cleaned text to the DOM.
22
+ // The visual clamp is handled by CSS line-clamp-2.
23
+ const shortDescription = computed(() => {
24
+ if (!props.text) return ''
25
+ const cleaned = removeMarkdownSync(props.text)
26
+ return cleaned.length > props.maxLength ? cleaned.substring(0, props.maxLength) + '…' : cleaned
27
+ })
28
+ </script>
@@ -11,7 +11,7 @@
11
11
  loading="lazy"
12
12
  >
13
13
  </div>
14
- <p class="mb-0.5 font-bold">
14
+ <div class="mb-0.5 font-bold">
15
15
  <AppLink
16
16
  :to="organization.page"
17
17
  class="overflow-hidden"
@@ -21,55 +21,70 @@
21
21
  :organization
22
22
  />
23
23
  </AppLink>
24
- </p>
24
+ </div>
25
25
  <div class="mb-2 flex flex-wrap gap-1 items-center">
26
26
  <template v-if="type !== 'other'">
27
27
  <OwnerType
28
28
  class="mb-0 text-sm"
29
29
  :type
30
30
  />
31
- <RiSubtractLine class="size-4 fill-gray-medium" />
31
+ <RiSubtractLine
32
+ aria-hidden="true"
33
+ class="size-4 fill-gray-medium"
34
+ />
32
35
  </template>
33
36
  <div>
34
37
  <div
35
- v-if="organization.metrics"
38
+ v-if="'metrics' in organization"
36
39
  class="text-gray-medium flex items-center text-sm gap-0.5"
40
+ :aria-label="t('{datasets} jeux de données, {dataservices} API et {reuses} réutilisations', {
41
+ datasets: organization.metrics.datasets,
42
+ dataservices: organization.metrics.dataservices,
43
+ reuses: organization.metrics.reuses,
44
+ })"
37
45
  >
38
- <RiDatabase2Line class="size-4 -mt-1" /> {{ organization.metrics.datasets }}
39
- <RiTerminalLine class="size-4 -mt-1 ml-1" /> {{ organization.metrics.dataservices }}
40
- <RiLineChartLine class="size-4 -mt-1 ml-1" /> {{ organization.metrics.reuses }}
46
+ <RiDatabase2Line
47
+ aria-hidden="true"
48
+ class="size-4 -mt-1"
49
+ /> {{ organization.metrics.datasets }}
50
+ <RiTerminalLine
51
+ aria-hidden="true"
52
+ class="size-4 -mt-1 ml-1"
53
+ /> {{ organization.metrics.dataservices }}
54
+ <RiLineChartLine
55
+ aria-hidden="true"
56
+ class="size-4 -mt-1 ml-1"
57
+ /> {{ organization.metrics.reuses }}
41
58
  </div>
42
59
  </div>
43
60
  </div>
44
- <p class="mt-1 mb-0">
61
+ <div class="mt-1 mb-0">
45
62
  <TextClamp
46
- v-if="description"
47
- :text="description"
63
+ v-if="'description' in organization"
64
+ :text="removeMarkdownSync(organization.description)"
48
65
  :max-lines="3"
49
66
  />
50
- </p>
67
+ </div>
51
68
  </div>
52
69
  </div>
53
70
  </template>
54
71
 
55
72
  <script setup lang="ts">
56
73
  import { RiLineChartLine, RiDatabase2Line, RiTerminalLine, RiSubtractLine } from '@remixicon/vue'
57
- import { computed, ref, watchEffect } from 'vue'
58
- import { removeMarkdown } from '../functions/markdown'
74
+ import { computed } from 'vue'
75
+ import { removeMarkdownSync } from '../functions/markdown'
59
76
  import { getOrganizationType } from '../functions/organizations'
60
- import type { Organization } from '../types/organizations'
77
+ import type { Organization, OrganizationReference } from '../types/organizations'
61
78
  import OwnerType from './OwnerType.vue'
62
79
  import OrganizationNameWithCertificate from './OrganizationNameWithCertificate.vue'
63
80
  import AppLink from './AppLink.vue'
81
+ import { useTranslation } from '../composables/useTranslation'
64
82
 
65
83
  const props = defineProps<{
66
- organization: Organization
84
+ organization: Organization | OrganizationReference
67
85
  }>()
68
86
 
69
- const type = computed(() => getOrganizationType(props.organization))
87
+ const { t } = useTranslation()
70
88
 
71
- const description = ref('')
72
- watchEffect(async () => {
73
- description.value = await removeMarkdown(props.organization.description)
74
- })
89
+ const type = computed(() => getOrganizationType(props.organization))
75
90
  </script>
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <ObjectCard>
3
+ <template #media>
4
+ <OrganizationLogo
5
+ :organization="organization"
6
+ size-class="size-12"
7
+ />
8
+ </template>
9
+ <ObjectCardHeader
10
+ :icon="RiBuilding2Line"
11
+ :url="organization.page"
12
+ >
13
+ <OrganizationNameWithCertificate
14
+ :show-type="false"
15
+ :organization
16
+ size="sm"
17
+ color-class="text-gray-title"
18
+ />
19
+ </ObjectCardHeader>
20
+ <div class="text-sm flex flex-wrap md:flex-nowrap gap-y-1 items-center truncate">
21
+ <template v-if="type !== 'other'">
22
+ <OwnerType
23
+ class="mb-0"
24
+ :type
25
+ />
26
+ <RiSubtractLine
27
+ v-if="'metrics' in organization"
28
+ aria-hidden="true"
29
+ class="hidden md:block size-4 flex-none fill-gray-medium"
30
+ />
31
+ </template>
32
+ <div
33
+ v-if="'metrics' in organization"
34
+ class="text-gray-medium flex items-center text-sm gap-0.5"
35
+ :aria-label="t('{datasets} jeux de donn\u00e9es, {dataservices} API et {reuses} r\u00e9utilisations', {
36
+ datasets: organization.metrics.datasets,
37
+ dataservices: organization.metrics.dataservices,
38
+ reuses: organization.metrics.reuses,
39
+ })"
40
+ >
41
+ <RiDatabase2Line
42
+ aria-hidden="true"
43
+ class="size-3.5"
44
+ /> {{ summarize(organization.metrics.datasets) }}
45
+ <RiTerminalLine
46
+ aria-hidden="true"
47
+ class="size-3.5 ml-1"
48
+ /> {{ summarize(organization.metrics.dataservices) }}
49
+ <RiLineChartLine
50
+ aria-hidden="true"
51
+ class="size-3.5 ml-1"
52
+ /> {{ summarize(organization.metrics.reuses) }}
53
+ <RiStarLine
54
+ aria-hidden="true"
55
+ class="size-3.5 ml-1"
56
+ /> {{ summarize(organization.metrics.followers) }}
57
+ </div>
58
+ </div>
59
+ <ObjectCardShortDescription
60
+ v-if="'description' in organization"
61
+ :text="organization.description"
62
+ />
63
+ </ObjectCard>
64
+ </template>
65
+
66
+ <script setup lang="ts">
67
+ import { RiBuilding2Line, RiDatabase2Line, RiLineChartLine, RiStarLine, RiSubtractLine, RiTerminalLine } from '@remixicon/vue'
68
+ import { computed } from 'vue'
69
+ import { getOrganizationType } from '../functions/organizations'
70
+ import { summarize } from '../functions/helpers'
71
+ import { useTranslation } from '../composables/useTranslation'
72
+ import type { Organization, OrganizationReference } from '../types/organizations'
73
+ import OrganizationLogo from './OrganizationLogo.vue'
74
+ import OrganizationNameWithCertificate from './OrganizationNameWithCertificate.vue'
75
+ import OwnerType from './OwnerType.vue'
76
+ import ObjectCard from './ObjectCard.vue'
77
+ import ObjectCardHeader from './ObjectCardHeader.vue'
78
+ import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
79
+
80
+ const props = defineProps<{
81
+ organization: Organization | OrganizationReference
82
+ }>()
83
+
84
+ const { t } = useTranslation()
85
+
86
+ const type = computed(() => getOrganizationType(props.organization))
87
+ </script>
@@ -16,7 +16,7 @@
16
16
  <script setup lang="ts">
17
17
  import { computed } from 'vue'
18
18
  import type { OrganizationOrSuggest } from '../types/organizations'
19
- import { throwOnNever } from '../main'
19
+ import { throwOnNever } from '../functions/never'
20
20
  import Placeholder from './Placeholder.vue'
21
21
 
22
22
  const props = defineProps<{
@@ -4,9 +4,10 @@
4
4
  v-if="showType"
5
5
  :type="getOrganizationType(organization)"
6
6
  />
7
- <div
7
+ <component
8
+ :is="as"
8
9
  class="mb-0 truncate flex-initial"
9
- :class="{ 'text-sm': size === 'sm' }"
10
+ :class="[colorClass, { 'text-xs': size === 'xs', 'text-sm': size === 'sm', 'text-base': size === 'base' }]"
10
11
  >
11
12
  {{ organization.name }}
12
13
  <small
@@ -15,11 +16,12 @@
15
16
  >
16
17
  {{ organization.acronym }}
17
18
  </small>
18
- </div>
19
+ </component>
19
20
  <Tooltip v-if="isOrganizationCertified(organization)">
20
21
  <RiCheckboxCircleLine
21
22
  class="flex-none"
22
23
  :class="{
24
+ 'size-3': size === 'xs',
23
25
  'size-4': size === 'sm',
24
26
  'size-5': size === 'base',
25
27
  }"
@@ -38,7 +40,7 @@
38
40
  <script setup lang="ts">
39
41
  import { RiCheckboxCircleLine } from '@remixicon/vue'
40
42
  import { getOrganizationType, isOrganizationCertified } from '../functions/organizations'
41
- import type { OrganizationReference } from '../types/organizations'
43
+ import type { Organization, OrganizationReference } from '../types/organizations'
42
44
  import { useComponentsConfig } from '../config'
43
45
  import { useTranslation } from '../composables/useTranslation'
44
46
  import OwnerTypeIcon from './OwnerTypeIcon.vue'
@@ -48,13 +50,17 @@ const config = useComponentsConfig()
48
50
 
49
51
  const { t } = useTranslation()
50
52
  withDefaults(defineProps<{
51
- organization: OrganizationReference
53
+ organization: Organization | OrganizationReference
52
54
  showAcronym?: boolean
53
55
  showType?: boolean
54
- size?: 'base' | 'sm'
56
+ size?: 'base' | 'sm' | 'xs'
57
+ colorClass?: string
58
+ as?: string
55
59
  }>(), {
56
60
  showAcronym: false,
57
61
  showType: true,
58
62
  size: 'base',
63
+ colorClass: 'text-new-primary',
64
+ as: 'div',
59
65
  })
60
66
  </script>
@@ -3,6 +3,7 @@
3
3
  :is="icon"
4
4
  v-if="icon"
5
5
  class="size-3.5 flex-none"
6
+ aria-hidden="true"
6
7
  />
7
8
  </template>
8
9
 
@@ -3,7 +3,7 @@
3
3
  v-if="totalResults > pageSize"
4
4
  ref="navRef"
5
5
  role="navigation"
6
- class="fr-pagination fr-pagination--centered"
6
+ class="fr-pagination flex justify-center"
7
7
  :aria-label="t('Pagination')"
8
8
  >
9
9
  <ul class="fr-pagination__list">
@@ -3,23 +3,26 @@
3
3
  <component
4
4
  :is="icon"
5
5
  class="size-1/2 text-gray-plain"
6
+ aria-hidden="true"
6
7
  />
7
8
  </div>
8
9
  </template>
9
10
 
10
11
  <script setup lang="ts">
11
12
  import { computed } from 'vue'
12
- import { RiBuilding2Line, RiDatabase2Line, RiLineChartLine } from '@remixicon/vue'
13
+ import { RiBookShelfLine, RiBuilding2Line, RiDatabase2Line, RiLineChartLine, RiTerminalLine } from '@remixicon/vue'
13
14
 
14
15
  const props = defineProps<{
15
- type: 'Dataset' | 'Reuse' | 'Organization'
16
+ type: 'Dataset' | 'Dataservice' | 'Reuse' | 'Organization' | 'Topic'
16
17
  }>()
17
18
 
18
19
  const icon = computed(() => {
19
20
  return {
20
21
  Dataset: RiDatabase2Line,
22
+ Dataservice: RiTerminalLine,
21
23
  Reuse: RiLineChartLine,
22
24
  Organization: RiBuilding2Line,
25
+ Topic: RiBookShelfLine,
23
26
  }[props.type]
24
27
  })
25
28
  </script>
@@ -0,0 +1,62 @@
1
+ <template>
2
+ <ObjectCard media-size="lg">
3
+ <template #media>
4
+ <img
5
+ v-if="post.image"
6
+ :src="post.image"
7
+ class="w-full h-full object-cover"
8
+ :alt="post.name"
9
+ >
10
+ <Placeholder
11
+ v-else
12
+ type="Dataset"
13
+ class="w-full h-full"
14
+ />
15
+ </template>
16
+
17
+ <ObjectCardHeader
18
+ :icon="RiArticleLine"
19
+ :url="postUrl || post.page || '#'"
20
+ >
21
+ {{ post.name }}
22
+ </ObjectCardHeader>
23
+
24
+ <ObjectCardShortDescription :text="post.headline || post.content" />
25
+
26
+ <div
27
+ v-if="post.published || post.created_at"
28
+ class="text-sm text-gray-medium mt-1"
29
+ >
30
+ {{ t('Publié {date}', { date: formatDate(post.published || post.created_at) }) }}
31
+ </div>
32
+
33
+ <slot />
34
+ </ObjectCard>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import { RiArticleLine } from '@remixicon/vue'
39
+ import type { RouteLocationRaw } from 'vue-router'
40
+ import { useFormatDate } from '../functions/dates'
41
+ import { useTranslation } from '../composables/useTranslation'
42
+ import type { Post } from '../types/posts'
43
+ import Placeholder from './Placeholder.vue'
44
+ import ObjectCard from './ObjectCard.vue'
45
+ import ObjectCardHeader from './ObjectCardHeader.vue'
46
+ import ObjectCardShortDescription from './ObjectCardShortDescription.vue'
47
+
48
+ defineProps<{
49
+ post: Post
50
+ postUrl?: RouteLocationRaw
51
+ }>()
52
+
53
+ const { t } = useTranslation()
54
+ const { formatRelativeIfRecentDate } = useFormatDate()
55
+
56
+ const formatDate = (dateString: string) => {
57
+ return formatRelativeIfRecentDate(dateString, {
58
+ dateStyle: 'long',
59
+ timeStyle: 'short',
60
+ })
61
+ }
62
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <div
3
+ role="meter"
4
+ :aria-valuenow="value"
5
+ aria-valuemin="0"
6
+ :aria-valuemax="max"
7
+ class="min-w-20 h-2.5 rounded-lg border border-gray-default bg-[#f5f5f5] overflow-hidden"
8
+ >
9
+ <div
10
+ class="h-full rounded-lg"
11
+ :class="barClass"
12
+ :style="{ width: percentage + '%' }"
13
+ />
14
+ <slot />
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { computed } from 'vue'
20
+
21
+ const props = withDefaults(defineProps<{
22
+ value: number
23
+ max?: number
24
+ barClass?: string
25
+ }>(), {
26
+ max: 1,
27
+ barClass: '',
28
+ })
29
+
30
+ const percentage = computed(() => Math.min(100, Math.max(0, (props.value / props.max) * 100)))
31
+ </script>
@@ -0,0 +1,32 @@
1
+ <template>
2
+ <fieldset class="flex flex-col gap-2 min-w-0">
3
+ <legend
4
+ v-if="legend"
5
+ class="fr-label mb-2"
6
+ >
7
+ {{ legend }}
8
+ </legend>
9
+ <slot />
10
+ </fieldset>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { provide, toRef } from 'vue'
15
+ import { radioGroupInjectionKey } from './radioGroupContext'
16
+
17
+ const props = defineProps<{
18
+ modelValue: string
19
+ name: string
20
+ legend?: string
21
+ }>()
22
+
23
+ const emit = defineEmits<{
24
+ (event: 'update:modelValue', value: string): void
25
+ }>()
26
+
27
+ provide(radioGroupInjectionKey, {
28
+ name: toRef(() => props.name),
29
+ modelValue: toRef(() => props.modelValue),
30
+ select: (value: string) => emit('update:modelValue', value),
31
+ })
32
+ </script>
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <label
3
+ class="flex items-center gap-2 p-1 rounded cursor-pointer transition has-[:focus-visible]:ring-2 has-[:focus-visible]:ring-blue-500"
4
+ :class="selectedClass"
5
+ >
6
+ <input
7
+ type="radio"
8
+ :name="group?.name.value"
9
+ :value="value"
10
+ :checked="isSelected"
11
+ class="sr-only"
12
+ @change="group?.select(value)"
13
+ >
14
+ <component
15
+ :is="icon"
16
+ v-if="icon"
17
+ class="w-4 h-4"
18
+ />
19
+ <span class="text-sm flex-1 min-w-0">
20
+ <slot />
21
+ </span>
22
+ <span
23
+ v-if="loading || count !== undefined"
24
+ class="text-xs font-bold px-1 py-0.5 rounded"
25
+ :class="isSelected && highlighted ? 'bg-white/20 text-white' : 'bg-gray-200 text-gray-600'"
26
+ >
27
+ <BouncingDots v-if="loading" />
28
+ <template v-else>{{ formattedCount }}</template>
29
+ </span>
30
+ </label>
31
+ </template>
32
+
33
+ <script setup lang="ts">
34
+ import { computed, inject, type Component } from 'vue'
35
+ import { radioGroupInjectionKey } from './radioGroupContext'
36
+ import { useTranslation } from '../composables/useTranslation'
37
+ import BouncingDots from './BouncingDots.vue'
38
+
39
+ type Props = {
40
+ value: string
41
+ count?: number
42
+ loading?: boolean
43
+ icon?: Component
44
+ highlighted?: boolean
45
+ }
46
+
47
+ const props = defineProps<Props>()
48
+
49
+ const group = inject(radioGroupInjectionKey)
50
+ const { locale } = useTranslation()
51
+
52
+ const isSelected = computed(() => group?.modelValue.value === props.value)
53
+
54
+ const selectedClass = computed(() => {
55
+ if (!isSelected.value) return 'hover:bg-gray-100'
56
+ if (props.highlighted) return 'bg-blue-800 text-white'
57
+ return 'bg-gray-200'
58
+ })
59
+
60
+ const formattedCount = computed(() => {
61
+ if (props.count === undefined) return ''
62
+ return new Intl.NumberFormat(locale, { notation: 'compact' }).format(props.count)
63
+ })
64
+ </script>
@@ -14,7 +14,7 @@
14
14
  type="warning"
15
15
  class="flex items-center space-x-2"
16
16
  >
17
- <RiErrorWarningLine class="shink-0 size-6" />
17
+ <RiErrorWarningLine class="shrink-0 size-6" />
18
18
  <span>{{ t("Erreur lors de l'affichage de l'aperçu.") }}</span>
19
19
  </SimpleBanner>
20
20
  </div>
@@ -4,10 +4,9 @@
4
4
  icon-only
5
5
  :icon="RiPencilLine"
6
6
  color="warning"
7
+ :title="t('Éditer le fichier')"
7
8
  data-testid="edit-button"
8
- >
9
- {{ t("Éditer le fichier") }}
10
- </BrandedButton>
9
+ />
11
10
  </template>
12
11
 
13
12
  <script setup lang="ts">
@@ -22,7 +22,7 @@
22
22
  type="warning"
23
23
  class="flex items-center space-x-2"
24
24
  >
25
- <RiErrorWarningLine class="shink-0 size-6" />
25
+ <RiErrorWarningLine class="shrink-0 size-6" />
26
26
  <span>{{ fileSizeBytes
27
27
  ? t("Fichier JSON trop volumineux pour l'aperçu. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
28
28
  : t("L'aperçu n'est pas disponible car la taille du fichier est inconnue. Pour consulter le fichier complet, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.")
@@ -33,7 +33,7 @@
33
33
  type="warning"
34
34
  class="flex items-center space-x-2"
35
35
  >
36
- <RiErrorWarningLine class="shink-0 size-6" />
36
+ <RiErrorWarningLine class="shrink-0 size-6" />
37
37
  <span>{{ t("Ce fichier JSON ne peut pas être prévisualisé, peut-être parce qu'il est hébergé sur un autre site qui ne l'autorise pas. Pour le consulter, téléchargez-le en cliquant sur le bouton bleu ou depuis l'onglet Téléchargements.") }}</span>
38
38
  </SimpleBanner>
39
39
  <SimpleBanner
@@ -41,7 +41,7 @@
41
41
  type="warning"
42
42
  class="flex items-center space-x-2"
43
43
  >
44
- <RiErrorWarningLine class="shink-0 size-6" />
44
+ <RiErrorWarningLine class="shrink-0 size-6" />
45
45
  <span>{{ t("Erreur lors du chargement de l'aperçu JSON.") }}</span>
46
46
  </SimpleBanner>
47
47
  </div>