@datagouv/components-next 0.2.0 → 1.0.0

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 (140) hide show
  1. package/README.md +1 -1
  2. package/assets/main.css +56 -1
  3. package/dist/Control-BNCDn-8E.js +148 -0
  4. package/dist/{Datafair.client-x39O4yfF.js → Datafair.client-Dls5AHTE.js} +1 -1
  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-DPDTs433.js} +14 -14
  8. package/dist/Map-BdT3i2C4.js +7609 -0
  9. package/dist/MapContainer.client-BdAzd7bj.js +105 -0
  10. package/dist/OSM-CamriM9b.js +71 -0
  11. package/dist/{PdfPreview.client-COOkEkRA.js → PdfPreview.client-CopqSDyt.js} +3 -3
  12. package/dist/{Pmtiles.client-BaiIo4VZ.js → Pmtiles.client-mF6xaOO_.js} +2 -2
  13. package/dist/ScaleLine-BiesrgOv.js +165 -0
  14. package/dist/Swagger.client-eJ7gpfZA.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-C0OgBkSq.js} +7 -7
  19. package/dist/common-C4rDcQpp.js +243 -0
  20. package/dist/components-next.css +1 -1
  21. package/dist/components-next.js +153 -117
  22. package/dist/components.css +1 -1
  23. package/dist/{MapContainer.client-DeSo8EvG.js → index-BRGqW8aQ.js} +4975 -21416
  24. package/dist/leaflet-src-7m1mB8LI.js +6338 -0
  25. package/dist/{main-Dgri3TQL.js → main-CNHxAJ8J.js} +56758 -51450
  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-CmAdQfIy.js} +1 -1
  29. package/package.json +5 -1
  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 +83 -118
  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/DatasetQualityTooltipContent.vue +3 -3
  46. package/src/components/DescriptionList.vue +1 -4
  47. package/src/components/DescriptionListDetails.vue +5 -0
  48. package/src/components/DescriptionListTerm.vue +5 -0
  49. package/src/components/DiscussionMessageCard.vue +63 -0
  50. package/src/components/ExtraAccordion.vue +4 -4
  51. package/src/components/Form/BadgeSelect.vue +35 -0
  52. package/src/components/Form/FormatSelect.vue +28 -0
  53. package/src/components/Form/GeozoneSelect.vue +52 -0
  54. package/src/components/Form/GranularitySelect.vue +29 -0
  55. package/src/components/Form/LicenseSelect.vue +30 -0
  56. package/src/components/Form/OrganizationSelect.vue +62 -0
  57. package/src/components/Form/OrganizationTypeSelect.vue +34 -0
  58. package/src/components/Form/ReuseTopicSelect.vue +29 -0
  59. package/src/components/Form/SchemaSelect.vue +30 -0
  60. package/src/components/Form/SearchableSelect.vue +334 -0
  61. package/src/components/Form/SelectGroup.vue +132 -0
  62. package/src/components/Form/TagSelect.vue +38 -0
  63. package/src/components/LeafletMap.vue +31 -0
  64. package/src/components/LicenseBadge.vue +24 -0
  65. package/src/components/LoadingBlock.vue +23 -2
  66. package/src/components/MarkdownViewer.vue +3 -1
  67. package/src/components/ObjectCard.vue +42 -0
  68. package/src/components/ObjectCardBadge.vue +22 -0
  69. package/src/components/ObjectCardHeader.vue +35 -0
  70. package/src/components/ObjectCardOwner.vue +43 -0
  71. package/src/components/ObjectCardShortDescription.vue +28 -0
  72. package/src/components/OrganizationCard.vue +35 -20
  73. package/src/components/OrganizationLogo.vue +1 -1
  74. package/src/components/OrganizationNameWithCertificate.vue +13 -7
  75. package/src/components/OwnerTypeIcon.vue +1 -0
  76. package/src/components/Pagination.vue +1 -1
  77. package/src/components/Placeholder.vue +5 -2
  78. package/src/components/PostCard.vue +62 -0
  79. package/src/components/RadioGroup.vue +32 -0
  80. package/src/components/RadioInput.vue +64 -0
  81. package/src/components/ResourceAccordion/EditButton.vue +2 -3
  82. package/src/components/ResourceAccordion/MapContainer.client.vue +20 -16
  83. package/src/components/ResourceAccordion/Metadata.vue +11 -24
  84. package/src/components/ResourceAccordion/Pmtiles.client.vue +1 -1
  85. package/src/components/ResourceAccordion/Preview.vue +1 -1
  86. package/src/components/ResourceAccordion/ResourceAccordion.vue +30 -20
  87. package/src/components/ResourceAccordion/ResourceIcon.vue +1 -0
  88. package/src/components/ResourceAccordion/SchemaBadge.vue +2 -2
  89. package/src/components/ResourceExplorer/ResourceExplorer.vue +243 -0
  90. package/src/components/ResourceExplorer/ResourceExplorerSidebar.vue +116 -0
  91. package/src/components/ResourceExplorer/ResourceExplorerViewer.vue +361 -0
  92. package/src/components/ReuseCard.vue +8 -28
  93. package/src/components/ReuseHorizontalCard.vue +80 -0
  94. package/src/components/Search/BasicAndAdvancedFilters.vue +49 -0
  95. package/src/components/Search/Filter/AccessTypeFilter.vue +37 -0
  96. package/src/components/Search/Filter/DatasetBadgeFilter.vue +40 -0
  97. package/src/components/Search/Filter/FilterButtonGroup.vue +78 -0
  98. package/src/components/Search/Filter/FormatFamilyFilter.vue +39 -0
  99. package/src/components/Search/Filter/LastUpdateRangeFilter.vue +37 -0
  100. package/src/components/Search/Filter/ProducerTypeFilter.vue +39 -0
  101. package/src/components/Search/Filter/ReuseTypeFilter.vue +42 -0
  102. package/src/components/Search/GlobalSearch.vue +611 -0
  103. package/src/components/Search/SearchInput.vue +63 -0
  104. package/src/components/Search/Sidemenu.vue +38 -0
  105. package/src/components/StatBox.vue +5 -5
  106. package/src/components/Tag.vue +30 -0
  107. package/src/components/Toggletip.vue +6 -2
  108. package/src/components/Tooltip.vue +2 -3
  109. package/src/components/TopicCard.vue +134 -0
  110. package/src/components/radioGroupContext.ts +9 -0
  111. package/src/composables/useDebouncedRef.ts +31 -0
  112. package/src/composables/useMetrics.ts +4 -3
  113. package/src/composables/useResourceCapabilities.ts +118 -0
  114. package/src/composables/useRouteQueryBoolean.ts +10 -0
  115. package/src/composables/useSelectModelSync.ts +89 -0
  116. package/src/composables/useStableQueryParams.ts +84 -0
  117. package/src/config.ts +4 -0
  118. package/src/functions/api.ts +17 -6
  119. package/src/functions/api.types.ts +4 -2
  120. package/src/functions/datasets.ts +1 -29
  121. package/src/functions/description.ts +33 -0
  122. package/src/functions/helpers.ts +11 -0
  123. package/src/functions/markdown.ts +60 -16
  124. package/src/functions/metrics.ts +33 -0
  125. package/src/functions/organizations.ts +5 -5
  126. package/src/main.ts +89 -7
  127. package/src/types/dataservices.ts +14 -12
  128. package/src/types/datasets.ts +20 -7
  129. package/src/types/discussions.ts +20 -0
  130. package/src/types/licenses.ts +3 -3
  131. package/src/types/organizations.ts +13 -1
  132. package/src/types/owned.ts +4 -2
  133. package/src/types/pages.ts +70 -0
  134. package/src/types/posts.ts +27 -0
  135. package/src/types/resources.ts +6 -0
  136. package/src/types/reuses.ts +14 -5
  137. package/src/types/search.ts +379 -0
  138. package/src/types/users.ts +12 -3
  139. package/dist/Swagger.client-CpLgaLg6.js +0 -4
  140. package/src/components/DatasetInformationPanel.vue +0 -211
@@ -0,0 +1,35 @@
1
+ <template>
2
+ <h3 class="w-full text-base mb-0 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>
@@ -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
8
- class="mb-0 truncate flex-initial"
9
- :class="{ 'text-sm': size === 'sm' }"
7
+ <component
8
+ :is="as"
9
+ class="mb-0 truncate flex-initial font-normal"
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,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>
@@ -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">
@@ -18,22 +18,6 @@
18
18
  import { onMounted, ref, useTemplateRef } from 'vue'
19
19
  import { RiErrorWarningLine } from '@remixicon/vue'
20
20
 
21
- import View from 'ol/View'
22
- import Map from 'ol/Map'
23
- import ScaleLine from 'ol/control/ScaleLine'
24
- import TileLayer from 'ol/layer/Tile'
25
- import OSM from 'ol/source/OSM'
26
-
27
- import {
28
- CRS,
29
- GeoportalAttribution,
30
- GeoportalFullScreen,
31
- GeoportalZoom,
32
- LayerImport,
33
- LayerSwitcher,
34
- // @ts-expect-error no types provided
35
- } from 'geopf-extensions-openlayers'
36
-
37
21
  import SimpleBanner from '../SimpleBanner.vue'
38
22
  import type { Resource } from '../../types/resources'
39
23
  import { useTranslation } from '../../composables/useTranslation'
@@ -47,6 +31,26 @@ const mapRef = useTemplateRef('mapRef')
47
31
  const hasError = ref(false)
48
32
 
49
33
  async function displayMap() {
34
+ // Dynamic imports for client-only libraries
35
+ const [
36
+ { default: View },
37
+ { default: Map },
38
+ { default: ScaleLine },
39
+ { default: TileLayer },
40
+ { default: OSM },
41
+ geopf,
42
+ ] = await Promise.all([
43
+ import('ol/View'),
44
+ import('ol/Map'),
45
+ import('ol/control/ScaleLine'),
46
+ import('ol/layer/Tile'),
47
+ import('ol/source/OSM'),
48
+ // @ts-expect-error no types provided
49
+ import('geopf-extensions-openlayers'),
50
+ ])
51
+
52
+ const { CRS, GeoportalAttribution, GeoportalFullScreen, GeoportalZoom, LayerImport, LayerSwitcher } = geopf
53
+
50
54
  await import('ol/ol.css')
51
55
  await import('@gouvfr/dsfr/dist/dsfr.css')
52
56
  await import('@gouvfr/dsfr/dist/utility/icons/icons.css')
@@ -3,7 +3,6 @@ import { computed } from 'vue'
3
3
  import type { Resource } from '../../types/resources'
4
4
  import CopyButton from '../CopyButton.vue'
5
5
  import DescriptionDetails from '../DescriptionDetails.vue'
6
- import DescriptionList from '../DescriptionList.vue'
7
6
  import DescriptionTerm from '../DescriptionTerm.vue'
8
7
  import { useFormatDate } from '../../functions/dates'
9
8
  import { filesize } from '../../functions/helpers'
@@ -27,7 +26,7 @@ const { formatDate } = useFormatDate()
27
26
  <template>
28
27
  <div>
29
28
  <div class="flex flex-wrap gap-12 flex-col md:flex-row overflow-hidden">
30
- <DescriptionList class="flex-1 max-w-full">
29
+ <dl class="flex-1 max-w-full p-0 m-0">
31
30
  <DescriptionTerm>
32
31
  {{ t('URL') }}
33
32
  <CopyButton
@@ -90,8 +89,10 @@ const { formatDate } = useFormatDate()
90
89
  </code>
91
90
  </DescriptionDetails>
92
91
  </template>
93
- </DescriptionList>
94
- <DescriptionList style="flex-shrink: 0;">
92
+ </dl>
93
+ <dl
94
+ class="p-0 m-0 shrink-0"
95
+ >
95
96
  <DescriptionTerm>{{ t('Créée le') }}</DescriptionTerm>
96
97
  <DescriptionDetails>
97
98
  {{ formatDate(resource.created_at) }}
@@ -100,8 +101,10 @@ const { formatDate } = useFormatDate()
100
101
  <DescriptionDetails>
101
102
  {{ formatDate(resource.last_modified) }}
102
103
  </DescriptionDetails>
103
- </DescriptionList>
104
- <DescriptionList style="flex-shrink: 0;">
104
+ </dl>
105
+ <dl
106
+ class="p-0 m-0 shrink-0"
107
+ >
105
108
  <template v-if="resourceFilesize">
106
109
  <DescriptionTerm>{{ t('Taille') }}</DescriptionTerm>
107
110
  <DescriptionDetails>
@@ -120,12 +123,12 @@ const { formatDate } = useFormatDate()
120
123
  <code class="code truncate">{{ resource.mime }}</code>
121
124
  </DescriptionDetails>
122
125
  </template>
123
- </DescriptionList>
126
+ </dl>
124
127
  </div>
125
128
  <div>
126
129
  <ExtraAccordion
127
130
  v-if="hasExtras"
128
- class="pt-6 mt-6 border-top border-gray-default"
131
+ class="pt-6 mt-6 border-t border-gray-default"
129
132
  :button-text="t('Voir les extras')"
130
133
  :title-text="t('Extras de la ressource')"
131
134
  title-level="h5"
@@ -134,19 +137,3 @@ const { formatDate } = useFormatDate()
134
137
  </div>
135
138
  </div>
136
139
  </template>
137
-
138
- <style scoped>
139
- .gap-3rem {
140
- gap: 3rem;
141
- }
142
-
143
- .gap-3rem dl {
144
- padding-inline-start: 0;
145
- }
146
-
147
- @container (max-width: 600px) {
148
- .flex-col-on-small {
149
- flex-direction: column
150
- }
151
- }
152
- </style>
@@ -25,7 +25,7 @@
25
25
  <p class="fr-text--bold fr-m-0">
26
26
  {{ t("Explorer les données en détail") }}
27
27
  </p>
28
- <p class="fr-text--sm fr-m-0 f-italic">
28
+ <p class="fr-text--sm fr-m-0 italic">
29
29
  {{ t("Utiliser un visualisateur PMTiles pour obtenir un aperçu des données.") }}
30
30
  </p>
31
31
  </div>
@@ -23,7 +23,7 @@
23
23
  <p class="fr-text--bold fr-m-0">
24
24
  {{ t("Explorer les données en détail") }}
25
25
  </p>
26
- <p class="fr-text--sm fr-m-0 f-italic">
26
+ <p class="fr-text--sm fr-m-0 italic">
27
27
  {{ t("Utiliser notre outil pour obtenir un aperçu des données, en savoir plus sur les différentes colonnes ou réaliser des filtres et des tris.") }}
28
28
  </p>
29
29
  </div>