@datagouv/components-next 1.0.2-dev.18 → 1.0.2-dev.181

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 (151) 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-CaxMyNif.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-CPmCyisP.js +36 -0
  8. package/dist/JsonPreview.client-CKOU-LBs.js +40 -0
  9. package/dist/{Map-BjUnLyj8.js → Map-BfqKMw0S.js} +1739 -1599
  10. package/dist/{MapContainer.client-B2BijMx5.js → MapContainer.client-IGYJWP5m.js} +41 -42
  11. package/dist/{OSM-s40W6sQ2.js → OSM-CcrUhItr.js} +15 -23
  12. package/dist/{PdfPreview.client-DJBMzrlC.js → PdfPreview.client-csUebU3A.js} +835 -856
  13. package/dist/Pmtiles.client-CLq7UHBg.js +26333 -0
  14. package/dist/PreviewWrapper.vue_vue_type_script_setup_true_lang-DeoHZXW1.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-C1toAbQ0.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 +193 -149
  23. package/dist/components.css +2 -2
  24. package/dist/{index-BFQX7Ccz.js → index-CaUlhl3k.js} +21842 -15810
  25. package/dist/{leaflet-src-7m1mB8LI.js → leaflet-src-ByYGrf8H.js} +185 -187
  26. package/dist/{main-vqhkVhVS.js → main-DD4ieUAo.js} +41500 -73223
  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-B1KPK4L4.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 +8 -5
  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 +219 -117
  91. package/src/components/Search/SearchInput.vue +5 -4
  92. package/src/components/TabularExplorer/TabularActiveFilters.vue +79 -0
  93. package/src/components/TabularExplorer/TabularCell.vue +50 -0
  94. package/src/components/TabularExplorer/TabularCellPopover.vue +170 -0
  95. package/src/components/TabularExplorer/TabularColumnsMenu.vue +112 -0
  96. package/src/components/TabularExplorer/TabularExplorer.vue +299 -0
  97. package/src/components/TabularExplorer/TabularFilterContent.vue +362 -0
  98. package/src/components/TabularExplorer/TabularFilterPopover.vue +106 -0
  99. package/src/components/TabularExplorer/TabularMobileFilterButton.vue +21 -0
  100. package/src/components/TabularExplorer/TabularMobileFilters.vue +150 -0
  101. package/src/components/TabularExplorer/TabularRowsInfo.vue +31 -0
  102. package/src/components/TabularExplorer/TabularSkeleton.vue +92 -0
  103. package/src/components/TabularExplorer/TabularTable.vue +481 -0
  104. package/src/components/TabularExplorer/TabularToolbar.vue +25 -0
  105. package/src/components/TabularExplorer/types.ts +81 -0
  106. package/src/components/TabularExplorer/useColumnMetadata.ts +98 -0
  107. package/src/components/TabularExplorer/useTabularContext.ts +82 -0
  108. package/src/composables/useActiveDescendant.ts +5 -0
  109. package/src/composables/useDatasetResources.ts +164 -0
  110. package/src/composables/useHasTabularData.ts +15 -1
  111. package/src/composables/useMetrics.ts +1 -1
  112. package/src/composables/useResizable.ts +46 -0
  113. package/src/composables/useResourceCapabilities.ts +47 -21
  114. package/src/composables/useSearchFilter.ts +120 -0
  115. package/src/composables/useStableQueryParams.ts +38 -6
  116. package/src/composables/useTabularProfile.ts +70 -0
  117. package/src/config.ts +21 -3
  118. package/src/functions/activities.ts +3 -3
  119. package/src/functions/api.ts +20 -42
  120. package/src/functions/api.types.ts +1 -0
  121. package/src/functions/charts.ts +99 -0
  122. package/src/functions/datasets.ts +0 -17
  123. package/src/functions/dates.ts +3 -2
  124. package/src/functions/metrics.ts +6 -4
  125. package/src/functions/resources.ts +136 -1
  126. package/src/functions/schemas.ts +27 -0
  127. package/src/functions/tabular.ts +214 -0
  128. package/src/functions/tabularApi.ts +96 -11
  129. package/src/main.ts +117 -14
  130. package/src/types/dataservices.ts +17 -0
  131. package/src/types/organizations.ts +10 -0
  132. package/src/types/pages.ts +0 -5
  133. package/src/types/posts.ts +2 -2
  134. package/src/types/reports.ts +5 -1
  135. package/src/types/resources.ts +0 -1
  136. package/src/types/reuses.ts +12 -1
  137. package/src/types/search.ts +63 -1
  138. package/src/types/site.ts +5 -3
  139. package/src/types/ui.ts +2 -0
  140. package/src/types/users.ts +2 -1
  141. package/src/types/visualizations.ts +93 -0
  142. package/assets/swagger-themes/newspaper.css +0 -1670
  143. package/dist/JsonPreview.client-CYxmz0yV.js +0 -72
  144. package/dist/Pmtiles.client-C_hvRDuy.js +0 -25185
  145. package/dist/Swagger.client-B4y0hXME.js +0 -4
  146. package/dist/XmlPreview.client-LETEfNBH.js +0 -64
  147. package/src/components/ResourceAccordion/Swagger.client.vue +0 -48
  148. package/src/functions/pagination.ts +0 -9
  149. /package/src/components/{DatasetQualityItem.vue → QualityItem.vue} +0 -0
  150. /package/src/components/{DatasetQualityItemWarning.vue → QualityItemWarning.vue} +0 -0
  151. /package/src/components/{DatasetQualityScore.vue → QualityScore.vue} +0 -0
@@ -0,0 +1,106 @@
1
+ <template>
2
+ <Popover
3
+ ref="anchor"
4
+ v-slot="{ open }"
5
+ class="relative shrink-0"
6
+ >
7
+ <PopoverButton
8
+ class="flex size-5 items-center justify-center rounded transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-new-primary"
9
+ :class="open ? 'bg-new-primary/10' : 'hover:bg-gray-100'"
10
+ >
11
+ <RiFilterLine
12
+ class="size-4"
13
+ :class="open || hasColumnFilter ? 'text-new-primary' : 'text-gray-low'"
14
+ aria-hidden="true"
15
+ />
16
+ <span class="sr-only">{{ t('Filtrer') }} {{ column }}</span>
17
+ </PopoverButton>
18
+
19
+ <ClientOnly>
20
+ <Teleport to="#tooltips">
21
+ <PopoverPanel
22
+ v-show="open"
23
+ ref="panel"
24
+ static
25
+ class="bg-white border border-black/10 rounded-lg shadow-md w-64 absolute z-[800]"
26
+ :style="floatingStyles"
27
+ :data-testid="`column-filter-${column}`"
28
+ >
29
+ <!-- Title -->
30
+ <div class="flex items-center justify-between px-3 py-2 border-b border-black/10">
31
+ <p class="text-sm font-medium mb-0">
32
+ {{ t('Filtrer') }} : {{ column }}
33
+ </p>
34
+ <BrandedButton
35
+ v-if="hasColumnFilter"
36
+ color="tertiary"
37
+ size="2xs"
38
+ :icon="RiCloseLine"
39
+ @click="clearColumnFilter"
40
+ >
41
+ {{ t('Effacer') }}
42
+ </BrandedButton>
43
+ </div>
44
+
45
+ <TabularFilterContent
46
+ v-model:sort="sort"
47
+ v-model:filters="filters"
48
+ :column="column"
49
+ :column-type="columnType"
50
+ :column-profile="columnProfile"
51
+ :null-percent="nullPercent"
52
+ :total-lines="totalLines"
53
+ :boolean-counts="booleanCounts"
54
+ />
55
+ </PopoverPanel>
56
+ </Teleport>
57
+ </ClientOnly>
58
+ </Popover>
59
+ </template>
60
+
61
+ <script setup lang="ts">
62
+ import { computed, useTemplateRef } from 'vue'
63
+ import { flip, shift, autoUpdate, useFloating } from '@floating-ui/vue'
64
+ import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
65
+ import { RiFilterLine, RiCloseLine } from '@remixicon/vue'
66
+ import { useTranslation } from '../../composables/useTranslation'
67
+ import { hasFilterForColumn as _hasFilterForColumn } from '../../functions/tabular'
68
+ import BrandedButton from '../BrandedButton.vue'
69
+ import ClientOnly from '../ClientOnly.vue'
70
+ import TabularFilterContent from './TabularFilterContent.vue'
71
+ import type { TabularColumnProfile, ColumnType, ColumnFilters, SortConfig } from './types'
72
+
73
+ const props = defineProps<{
74
+ column: string
75
+ columnType: ColumnType
76
+ columnProfile: TabularColumnProfile | null
77
+ nullPercent: string
78
+ totalLines: number
79
+ booleanCounts?: { trueCount: number, falseCount: number }
80
+ }>()
81
+
82
+ const sort = defineModel<SortConfig | null>('sort')
83
+ const filters = defineModel<Record<string, ColumnFilters>>('filters', { default: () => ({}) })
84
+
85
+ const { t } = useTranslation()
86
+
87
+ // Headless UI Popover manages open state, Escape-to-close, outside-click,
88
+ // focus handling and ARIA. floating-ui only positions the teleported panel.
89
+ const anchorComponent = useTemplateRef<InstanceType<typeof Popover>>('anchor')
90
+ const panelComponent = useTemplateRef<InstanceType<typeof PopoverPanel>>('panel')
91
+ const anchorEl = computed(() => anchorComponent.value?.$el as HTMLElement | undefined)
92
+ const panelEl = computed(() => panelComponent.value?.$el as HTMLElement | undefined)
93
+
94
+ const hasColumnFilter = computed(() => _hasFilterForColumn(filters.value, props.column))
95
+
96
+ function clearColumnFilter() {
97
+ const { [props.column]: _, ...rest } = filters.value
98
+ filters.value = rest
99
+ }
100
+
101
+ const { floatingStyles } = useFloating(anchorEl, panelEl, {
102
+ placement: 'bottom-start',
103
+ middleware: [flip(), shift()],
104
+ whileElementsMounted: autoUpdate,
105
+ })
106
+ </script>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <BrandedButton
3
+ color="tertiary"
4
+ size="2xs"
5
+ :icon="RiFilterLine"
6
+ keep-margins-even-without-borders
7
+ @click="mobileFilterOpen = true"
8
+ >
9
+ {{ t('Filtres & tri') }}
10
+ </BrandedButton>
11
+ </template>
12
+
13
+ <script setup lang="ts">
14
+ import { RiFilterLine } from '@remixicon/vue'
15
+ import BrandedButton from '../BrandedButton.vue'
16
+ import { useTranslation } from '../../composables/useTranslation'
17
+ import { useTabularContext } from './useTabularContext'
18
+
19
+ const { t } = useTranslation()
20
+ const { mobileFilterOpen } = useTabularContext()
21
+ </script>
@@ -0,0 +1,150 @@
1
+ <template>
2
+ <TransitionRoot
3
+ :show="mobileFilterOpen"
4
+ as="template"
5
+ >
6
+ <Dialog
7
+ class="relative z-[900]"
8
+ @close="mobileFilterOpen = false"
9
+ >
10
+ <!-- Backdrop -->
11
+ <TransitionChild
12
+ as="template"
13
+ enter="ease-out duration-300"
14
+ enter-from="opacity-0"
15
+ enter-to="opacity-100"
16
+ leave="ease-in duration-200"
17
+ leave-from="opacity-100"
18
+ leave-to="opacity-0"
19
+ >
20
+ <div class="fixed inset-0 bg-black/30" />
21
+ </TransitionChild>
22
+ <!-- Panel -->
23
+ <TransitionChild
24
+ as="template"
25
+ enter="ease-out duration-300"
26
+ enter-from="translate-y-full"
27
+ enter-to="translate-y-0"
28
+ leave="ease-in duration-200"
29
+ leave-from="translate-y-0"
30
+ leave-to="translate-y-full"
31
+ >
32
+ <div class="fixed inset-x-0 bottom-0 max-h-[80vh] flex flex-col bg-white rounded-t-2xl shadow-xl">
33
+ <DialogPanel class="flex flex-col max-h-[80vh]">
34
+ <div class="px-4 pt-4 pb-2 border-b border-gray-default">
35
+ <DialogTitle class="text-sm font-bold">
36
+ {{ t('Filtres & tri par colonne') }}
37
+ </DialogTitle>
38
+ <p class="text-xs text-gray-low">
39
+ {{ allColumns.length }} {{ t('colonnes') }} · {{ activeFilters.length }} {{ t('filtre') }}{{ activeFilters.length !== 1 ? 's' : '' }}
40
+ </p>
41
+ </div>
42
+ <!-- Column list -->
43
+ <div class="flex-1 overflow-y-auto">
44
+ <div
45
+ v-for="col in allColumns"
46
+ :key="col"
47
+ class="border-b border-gray-default last:border-b-0"
48
+ >
49
+ <button
50
+ class="flex items-center gap-2 w-full px-3 py-2.5 text-left"
51
+ :class="hasFilterForColumn(col) ? 'bg-new-primary/5' : ''"
52
+ @click="toggleColumn(col)"
53
+ >
54
+ <component
55
+ :is="getColumnDisplay(col).icon"
56
+ class="size-3.5 text-gray-low shrink-0"
57
+ aria-hidden="true"
58
+ />
59
+ <span class="flex-1 text-sm text-gray-title truncate">{{ col }}</span>
60
+ <RiArrowUpLine
61
+ v-if="sort?.column === col && sort.direction === 'asc'"
62
+ class="size-3 text-new-primary shrink-0"
63
+ aria-hidden="true"
64
+ />
65
+ <RiArrowDownLine
66
+ v-if="sort?.column === col && sort.direction === 'desc'"
67
+ class="size-3 text-new-primary shrink-0"
68
+ aria-hidden="true"
69
+ />
70
+ <span
71
+ v-if="hasFilterForColumn(col)"
72
+ class="size-2 rounded-full bg-new-primary shrink-0"
73
+ />
74
+ <RiArrowDownSLine
75
+ class="size-3.5 text-gray-low shrink-0 transition-transform"
76
+ :class="{ 'rotate-180': expandedCol === col }"
77
+ aria-hidden="true"
78
+ />
79
+ </button>
80
+ <div
81
+ v-if="expandedCol === col"
82
+ class="pb-1"
83
+ >
84
+ <TabularFilterContent
85
+ v-model:sort="sort"
86
+ v-model:filters="filters"
87
+ :column="col"
88
+ :column-type="getColumnType(col)"
89
+ :column-profile="getColumnProfile(col)"
90
+ :null-percent="getNullPercent(col)"
91
+ :total-lines="totalLines"
92
+ :boolean-counts="getColumnType(col) === 'boolean' ? getBooleanCounts(col) : undefined"
93
+ />
94
+ </div>
95
+ </div>
96
+ </div>
97
+ <!-- Reset all -->
98
+ <div
99
+ v-if="activeFilters.length > 0 || sort"
100
+ class="border-t border-gray-default px-4 py-3"
101
+ >
102
+ <BrandedButton
103
+ color="secondary"
104
+ size="xs"
105
+ class="w-full"
106
+ @click="clearAllFilters(); sort = null; mobileFilterOpen = false"
107
+ >
108
+ {{ t('Tout réinitialiser') }}
109
+ </BrandedButton>
110
+ </div>
111
+ </DialogPanel>
112
+ </div>
113
+ </TransitionChild>
114
+ </Dialog>
115
+ </TransitionRoot>
116
+ </template>
117
+
118
+ <script setup lang="ts">
119
+ import { ref } from 'vue'
120
+ import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
121
+ import { RiArrowDownLine, RiArrowDownSLine, RiArrowUpLine } from '@remixicon/vue'
122
+ import BrandedButton from '../BrandedButton.vue'
123
+ import TabularFilterContent from './TabularFilterContent.vue'
124
+ import { useTranslation } from '../../composables/useTranslation'
125
+ import { useTabularContext } from './useTabularContext'
126
+
127
+ const { t } = useTranslation()
128
+ const {
129
+ mobileFilterOpen,
130
+ allColumns,
131
+ activeFilters,
132
+ sort,
133
+ filters,
134
+ hasFilterForColumn,
135
+ clearAllFilters,
136
+ totalLines,
137
+ getColumnDisplay,
138
+ getColumnType,
139
+ getColumnProfile,
140
+ getNullPercent,
141
+ getBooleanCounts,
142
+ } = useTabularContext()
143
+
144
+ // The expanded column is local to the sheet.
145
+ const expandedCol = ref<string | null>(null)
146
+
147
+ function toggleColumn(col: string) {
148
+ expandedCol.value = expandedCol.value === col ? null : col
149
+ }
150
+ </script>
@@ -0,0 +1,31 @@
1
+ <template>
2
+ <span class="flex items-center gap-1.5 text-xs text-gray-plain">
3
+ <RiLoader5Line
4
+ v-if="isRefreshing"
5
+ class="size-3 text-new-primary animate-spin"
6
+ aria-hidden="true"
7
+ />
8
+ <RiLayoutRowLine
9
+ v-else
10
+ class="size-3 text-mention-grey"
11
+ aria-hidden="true"
12
+ />
13
+ <span class="font-bold hidden md:inline">{{ t('Lignes') }}</span>
14
+ <!-- The count stays in place while refreshing: the spinner already says it is
15
+ stale, and swapping it for a word of another width would shift the whole
16
+ toolbar on every search. -->
17
+ <span
18
+ data-testid="row-count"
19
+ class="font-mono tabular-nums"
20
+ >{{ (tableData?.meta.total ?? 0).toLocaleString() }}/{{ totalLines.toLocaleString() }}</span>
21
+ </span>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { RiLayoutRowLine, RiLoader5Line } from '@remixicon/vue'
26
+ import { useTranslation } from '../../composables/useTranslation'
27
+ import { useTabularContext } from './useTabularContext'
28
+
29
+ const { t } = useTranslation()
30
+ const { tableData, totalLines, isRefreshing } = useTabularContext()
31
+ </script>
@@ -0,0 +1,92 @@
1
+ <template>
2
+ <!-- Toolbar row — mirrors TabularToolbar: the mobile filter button on the left (it
3
+ only shows below md), and the columns menu (h-6 button, which drives the row
4
+ height) plus the rows info on the right. The active-filters area stays empty
5
+ until the user filters, so nothing stands in for it. -->
6
+ <div class="animate-pulse-placeholder flex shrink-0 items-center gap-2 border-b border-gray-default p-2">
7
+ <div class="h-6 w-24 rounded bg-gray-200 md:hidden" />
8
+ <div class="flex flex-1 items-center justify-end gap-4">
9
+ <div class="h-6 w-24 rounded bg-gray-200" />
10
+ <div class="h-5 w-24 rounded bg-gray-200" />
11
+ </div>
12
+ </div>
13
+
14
+ <!-- Table — mirrors TabularTable: tall header cells (name + type line) then rows.
15
+ Desktop only, like the real one. -->
16
+ <div
17
+ class="animate-pulse-placeholder hidden overflow-hidden md:block"
18
+ :class="fill ? 'min-h-0 flex-1' : 'max-h-[70vh]'"
19
+ role="status"
20
+ :aria-label="t('Chargement de l\'aperçu…')"
21
+ >
22
+ <table class="w-full table-fixed text-sm border-collapse">
23
+ <thead class="bg-gray-some shadow-[inset_0_-1px_0_0_#E5E5E5]">
24
+ <tr class="border-b border-gray-default">
25
+ <th
26
+ v-for="i in 6"
27
+ :key="i"
28
+ class="h-12 px-3 text-left align-middle border-r border-gray-default last:border-r-0"
29
+ >
30
+ <div class="flex items-center justify-between gap-1">
31
+ <div class="flex items-center gap-1">
32
+ <div class="size-4 shrink-0 rounded bg-gray-200" />
33
+ <div class="h-3.5 w-20 rounded bg-gray-200" />
34
+ </div>
35
+ <div class="size-4 shrink-0 rounded bg-gray-200" />
36
+ </div>
37
+ </th>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ <tr
42
+ v-for="row in 16"
43
+ :key="row"
44
+ class="h-10 border-b border-gray-default even:bg-gray-lowest-2"
45
+ >
46
+ <td
47
+ v-for="col in 6"
48
+ :key="col"
49
+ class="px-2 align-middle border-r border-gray-default last:border-r-0"
50
+ >
51
+ <div class="h-3.5 w-3/4 rounded bg-gray-200" />
52
+ </td>
53
+ </tr>
54
+ </tbody>
55
+ </table>
56
+ </div>
57
+
58
+ <!-- Mobile — the real table becomes a list of cards below md, each showing four
59
+ fields until it is expanded. -->
60
+ <div
61
+ class="animate-pulse-placeholder space-y-2 px-1 md:hidden"
62
+ role="status"
63
+ :aria-label="t('Chargement de l\'aperçu…')"
64
+ >
65
+ <div
66
+ v-for="card in 4"
67
+ :key="card"
68
+ class="space-y-2 rounded-lg border border-gray-default p-3"
69
+ :class="card % 2 === 0 ? 'bg-gray-lowest-2' : 'bg-white'"
70
+ >
71
+ <div
72
+ v-for="field in 4"
73
+ :key="field"
74
+ class="flex flex-col gap-0.5"
75
+ >
76
+ <div class="h-3 w-24 rounded bg-gray-200" />
77
+ <div class="h-4 w-2/3 rounded bg-gray-200" />
78
+ </div>
79
+ </div>
80
+ </div>
81
+ </template>
82
+
83
+ <script setup lang="ts">
84
+ import { useTranslation } from '../../composables/useTranslation'
85
+
86
+ defineProps<{
87
+ // Fill the available height (fullscreen) instead of the default capped height.
88
+ fill?: boolean
89
+ }>()
90
+
91
+ const { t } = useTranslation()
92
+ </script>