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

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,79 @@
1
+ <template>
2
+ <div
3
+ v-if="activeFilters.length > 0 || sort"
4
+ class="flex flex-wrap items-center gap-1.5"
5
+ >
6
+ <!-- Sort chip -->
7
+ <span
8
+ v-if="sort"
9
+ class="inline-flex h-6 items-center gap-1 rounded border border-new-primary/20 bg-new-primary/10 pl-1.5 pr-0.5 text-[12px] leading-4 text-new-primary"
10
+ >
11
+ <component
12
+ :is="sort.direction === 'asc' ? RiArrowUpLine : RiArrowDownLine"
13
+ class="size-3.5"
14
+ aria-hidden="true"
15
+ />
16
+ <span class="font-medium">{{ sort.column }}</span>
17
+ <span>{{ sort.direction === 'asc' ? t('croissant') : t('décroissant') }}</span>
18
+ <button
19
+ :aria-label="t('Supprimer le tri')"
20
+ class="flex size-5 items-center justify-center rounded hover:bg-new-primary/15"
21
+ @click="sort = null"
22
+ >
23
+ <RiCloseLine
24
+ class="size-3.5"
25
+ aria-hidden="true"
26
+ />
27
+ </button>
28
+ </span>
29
+
30
+ <!-- Filter chips -->
31
+ <span
32
+ v-for="af in activeFilters"
33
+ :key="af.column"
34
+ :data-testid="`active-filter-${af.column}`"
35
+ class="inline-flex h-6 items-center gap-1 rounded border border-new-primary/20 bg-new-primary/10 pl-1.5 pr-0.5 text-[12px] leading-4 text-new-primary"
36
+ >
37
+ <component
38
+ :is="getColumnDisplay(af.column).icon"
39
+ class="size-3.5"
40
+ aria-hidden="true"
41
+ />
42
+ <span class="font-medium">{{ af.column }}</span>
43
+ <span>{{ af.label }}</span>
44
+ <button
45
+ :aria-label="t('Supprimer ce filtre')"
46
+ class="flex size-5 items-center justify-center rounded hover:bg-new-primary/15"
47
+ @click="removeFilter(af.column)"
48
+ >
49
+ <RiCloseLine
50
+ class="size-3.5"
51
+ aria-hidden="true"
52
+ />
53
+ </button>
54
+ </span>
55
+
56
+ <!-- Clear all -->
57
+ <BrandedButton
58
+ v-if="withClear"
59
+ color="tertiary"
60
+ size="2xs"
61
+ :icon="RiCloseLine"
62
+ @click="clearAllFilters(); sort = null"
63
+ >
64
+ {{ t('Tout effacer') }}
65
+ </BrandedButton>
66
+ </div>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import { RiArrowDownLine, RiArrowUpLine, RiCloseLine } from '@remixicon/vue'
71
+ import BrandedButton from '../BrandedButton.vue'
72
+ import { useTranslation } from '../../composables/useTranslation'
73
+ import { useTabularContext } from './useTabularContext'
74
+
75
+ defineProps<{ withClear?: boolean }>()
76
+
77
+ const { t } = useTranslation()
78
+ const { activeFilters, sort, removeFilter, clearAllFilters, getColumnDisplay } = useTabularContext()
79
+ </script>
@@ -0,0 +1,50 @@
1
+ <template>
2
+ <span
3
+ v-if="value == null || value === ''"
4
+ class="font-[Inconsolata,monospace] text-gray-low italic"
5
+ :class="compact ? 'text-xs' : 'text-sm'"
6
+ >null</span>
7
+ <span
8
+ v-else-if="columnType === 'boolean'"
9
+ class="inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs"
10
+ :class="isTruthy(value) ? 'bg-new-success-light text-new-success' : 'bg-new-warning-light text-new-error'"
11
+ >
12
+ <span
13
+ class="size-2 rounded-full"
14
+ :class="isTruthy(value) ? 'bg-new-success' : 'bg-new-error'"
15
+ />
16
+ {{ isTruthy(value) ? t('Vrai') : t('Faux') }}
17
+ </span>
18
+ <span
19
+ v-else-if="columnType === 'categorical'"
20
+ class="inline-block max-w-full truncate rounded bg-gray-lower px-2 py-0.5 text-xs text-gray-plain"
21
+ >{{ value }}</span>
22
+ <span
23
+ v-else-if="columnType === 'number' || columnType === 'year'"
24
+ :class="compact ? 'font-mono tabular-nums text-xs' : ''"
25
+ >{{ columnType === 'year' || noNumberFormat ? value : formatNumber(value) }}</span>
26
+ <span
27
+ v-else-if="columnType === 'date'"
28
+ :class="compact ? 'font-mono tabular-nums text-xs' : ''"
29
+ >{{ formatCellDate(value) }}</span>
30
+ <span
31
+ v-else
32
+ class="text-gray-title truncate block text-xs"
33
+ >{{ typeof value === 'object' ? JSON.stringify(value) : value }}</span>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { useTranslation } from '../../composables/useTranslation'
38
+ import { useFormatTabular, isTruthy } from '../../functions/tabular'
39
+ import type { ColumnType } from './types'
40
+
41
+ defineProps<{
42
+ value: unknown
43
+ columnType: ColumnType
44
+ compact?: boolean
45
+ noNumberFormat?: boolean
46
+ }>()
47
+
48
+ const { t } = useTranslation()
49
+ const { formatNumber, formatCellDate } = useFormatTabular()
50
+ </script>
@@ -0,0 +1,170 @@
1
+ <template>
2
+ <ClientOnly>
3
+ <Teleport to="#tooltips">
4
+ <div
5
+ v-if="cell"
6
+ ref="panel"
7
+ class="bg-white border border-black/10 rounded-lg shadow-md w-80 absolute z-[800]"
8
+ :style="floatingStyles"
9
+ >
10
+ <!-- Value -->
11
+ <div class="px-3 pt-3 pb-2 border-b border-gray-default">
12
+ <p class="text-[10px] text-gray-plain mb-0">
13
+ {{ t('Valeur brute') }}
14
+ </p>
15
+ <p class="text-xs text-gray-title mb-0">
16
+ {{ displayValue }}
17
+ </p>
18
+ </div>
19
+
20
+ <!-- Type -->
21
+ <div class="flex items-center gap-2 px-3 py-2 border-b border-gray-default">
22
+ <span class="text-[10px] text-gray-plain">{{ t('Type') }}</span>
23
+ <span class="inline-flex items-center gap-1 bg-gray-some rounded px-1.5 py-0.5 text-xs text-gray-plain">
24
+ <component
25
+ :is="typeIcon"
26
+ class="size-3"
27
+ aria-hidden="true"
28
+ />
29
+ {{ typeLabel }}
30
+ </span>
31
+ <span class="text-[10px] text-gray-plain shrink-0">·</span>
32
+ <span class="text-[10px] text-gray-plain truncate min-w-0">{{ cell.column }}</span>
33
+ </div>
34
+
35
+ <!-- Actions -->
36
+ <div class="p-1">
37
+ <button
38
+ class="flex items-center gap-2.5 w-full px-3 py-2 rounded-md text-xs font-medium hover:bg-gray-50"
39
+ @click="filterByValue"
40
+ >
41
+ <RiFilterLine
42
+ class="size-4"
43
+ aria-hidden="true"
44
+ />
45
+ {{ t('Filtrer par cette valeur') }}
46
+ </button>
47
+ <button
48
+ class="flex items-center gap-2.5 w-full px-3 py-2 rounded-md text-xs font-medium hover:bg-gray-50"
49
+ @click="copyValue"
50
+ >
51
+ <RiCheckLine
52
+ v-if="copied"
53
+ class="size-4 text-green-500"
54
+ aria-hidden="true"
55
+ />
56
+ <RiFileCopyLine
57
+ v-else
58
+ class="size-4 text-gray-plain"
59
+ aria-hidden="true"
60
+ />
61
+ {{ copied ? t('Copié !') : t('Copier la valeur') }}
62
+ </button>
63
+ </div>
64
+ </div>
65
+ </Teleport>
66
+ </ClientOnly>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import { computed, ref, useTemplateRef, watch } from 'vue'
71
+ import { flip, shift, autoUpdate, useFloating } from '@floating-ui/vue'
72
+ import { onClickOutside } from '@vueuse/core'
73
+ import {
74
+ RiFilterLine,
75
+ RiFileCopyLine,
76
+ RiCheckLine,
77
+ } from '@remixicon/vue'
78
+ import { toast } from 'vue-sonner'
79
+ import { useTranslation } from '../../composables/useTranslation'
80
+ import { buildTypeConfig } from '../../functions/tabular'
81
+ import ClientOnly from '../ClientOnly.vue'
82
+ import type { ColumnType, ColumnFilters } from './types'
83
+
84
+ export interface CellInfo {
85
+ column: string
86
+ columnType: ColumnType
87
+ value: unknown
88
+ element: HTMLElement
89
+ }
90
+
91
+ const cell = defineModel<CellInfo | null>('cell', { default: null })
92
+ const filters = defineModel<Record<string, ColumnFilters>>('filters', { default: () => ({}) })
93
+
94
+ const { t } = useTranslation()
95
+
96
+ const panelRef = useTemplateRef<HTMLElement>('panel')
97
+ const anchorRef = ref<HTMLElement | null>(null)
98
+
99
+ watch(cell, (c) => {
100
+ anchorRef.value = c?.element ?? null
101
+ })
102
+
103
+ const { floatingStyles } = useFloating(anchorRef, panelRef, {
104
+ placement: 'bottom-start',
105
+ middleware: [flip(), shift()],
106
+ whileElementsMounted: autoUpdate,
107
+ })
108
+
109
+ const displayValue = computed(() => {
110
+ if (!cell.value) return ''
111
+ const v = cell.value.value
112
+ if (v == null || v === '') return '–'
113
+ if (typeof v === 'object') return JSON.stringify(v)
114
+ return String(v)
115
+ })
116
+
117
+ const typeConfig = buildTypeConfig(t)
118
+
119
+ const typeIcon = computed(() => cell.value ? typeConfig[cell.value.columnType].icon : typeConfig.text.icon)
120
+ const typeLabel = computed(() => cell.value ? typeConfig[cell.value.columnType].label : '')
121
+
122
+ function close() {
123
+ cell.value = null
124
+ }
125
+
126
+ function filterByValue() {
127
+ if (!cell.value) return
128
+ const val = String(cell.value.value ?? '')
129
+ const col = cell.value.column
130
+ const existing = filters.value[col] ?? {}
131
+ if (cell.value.columnType === 'categorical' || cell.value.columnType === 'text' || cell.value.columnType === 'date' || cell.value.columnType === 'year') {
132
+ const current = existing.in ?? []
133
+ if (!current.includes(val)) {
134
+ filters.value = { ...filters.value, [col]: { ...existing, in: [...current, val] } }
135
+ }
136
+ }
137
+ else if (cell.value.columnType === 'number') {
138
+ const num = Number(cell.value.value)
139
+ if (Number.isFinite(num)) {
140
+ filters.value = { ...filters.value, [col]: { ...existing, min: num, max: num } }
141
+ }
142
+ }
143
+ else if (cell.value.columnType === 'boolean') {
144
+ filters.value = { ...filters.value, [col]: { ...existing, exact: val } }
145
+ }
146
+ close()
147
+ }
148
+
149
+ const copied = ref(false)
150
+
151
+ async function copyValue() {
152
+ try {
153
+ await navigator.clipboard.writeText(displayValue.value)
154
+ copied.value = true
155
+ setTimeout(() => {
156
+ copied.value = false
157
+ }, 1500)
158
+ }
159
+ catch {
160
+ toast.error(t('Impossible de copier dans le presse-papier'))
161
+ }
162
+ }
163
+
164
+ onClickOutside(panelRef, (e) => {
165
+ if (!cell.value) return
166
+ const clickedCell = (e.target as HTMLElement).closest('[data-cell]')
167
+ if (clickedCell && clickedCell === cell.value.element) return
168
+ close()
169
+ })
170
+ </script>
@@ -0,0 +1,112 @@
1
+ <template>
2
+ <Popover
3
+ ref="columnAnchor"
4
+ v-slot="{ open }"
5
+ class="relative"
6
+ >
7
+ <PopoverButton class="flex items-center gap-1.5 text-xs text-gray-plain rounded px-2 py-1 hover:bg-gray-50 focus:outline-none">
8
+ <RiLayoutColumnLine
9
+ class="size-3"
10
+ aria-hidden="true"
11
+ />
12
+ <span class="font-bold hidden md:inline">{{ t('Colonnes') }}</span>
13
+ <span class="font-mono tabular-nums">{{ visibleColumns.size }}/{{ allColumns.length }}</span>
14
+ <RiArrowDownSLine
15
+ class="size-3 opacity-50"
16
+ aria-hidden="true"
17
+ />
18
+ </PopoverButton>
19
+
20
+ <ClientOnly>
21
+ <Teleport to="#tooltips">
22
+ <PopoverPanel
23
+ v-show="open"
24
+ ref="columnPanel"
25
+ static
26
+ class="bg-white border border-gray-default rounded shadow-lg w-72 absolute z-[800]"
27
+ :style="columnFloatingStyles"
28
+ >
29
+ <div class="px-3 py-2 border-b border-gray-default space-y-1.5">
30
+ <span class="block text-xs font-medium text-gray-title">
31
+ {{ visibleColumns.size }} {{ t('sur') }} {{ allColumns.length }} {{ t('colonnes visibles') }}
32
+ </span>
33
+ <div class="flex items-center gap-1">
34
+ <BrandedButton
35
+ v-if="hiddenCount"
36
+ color="tertiary"
37
+ size="2xs"
38
+ @click="showAllColumns"
39
+ >
40
+ {{ t('Tout afficher') }}
41
+ </BrandedButton>
42
+ <BrandedButton
43
+ v-if="visibleColumns.size > 0"
44
+ color="tertiary"
45
+ size="2xs"
46
+ @click="hideAllColumns"
47
+ >
48
+ {{ t('Tout masquer') }}
49
+ </BrandedButton>
50
+ </div>
51
+ </div>
52
+ <div class="max-h-64 overflow-auto p-1">
53
+ <div
54
+ v-for="col in allColumns"
55
+ :key="col"
56
+ class="group/col flex items-center gap-2 px-2 py-1.5 rounded hover:bg-gray-50 text-xs"
57
+ >
58
+ <label class="flex items-center gap-2 flex-1 min-w-0 cursor-pointer">
59
+ <input
60
+ type="checkbox"
61
+ :checked="visibleColumns.has(col)"
62
+ class="size-3.5 accent-new-primary"
63
+ @change="toggleColumn(col)"
64
+ >
65
+ <span class="truncate">{{ col }}</span>
66
+ </label>
67
+ <button
68
+ class="shrink-0 px-1 text-new-primary hover:underline hidden group-hover/col:block group-focus-within/col:block"
69
+ @click="selectOnlyColumn(col)"
70
+ >
71
+ {{ t('Uniquement') }}
72
+ </button>
73
+ </div>
74
+ </div>
75
+ </PopoverPanel>
76
+ </Teleport>
77
+ </ClientOnly>
78
+ </Popover>
79
+ </template>
80
+
81
+ <script setup lang="ts">
82
+ import { computed, useTemplateRef } from 'vue'
83
+ import { autoUpdate, flip, shift, useFloating } from '@floating-ui/vue'
84
+ import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
85
+ import { RiArrowDownSLine, RiLayoutColumnLine } from '@remixicon/vue'
86
+ import ClientOnly from '../ClientOnly.vue'
87
+ import BrandedButton from '../BrandedButton.vue'
88
+ import { useTranslation } from '../../composables/useTranslation'
89
+ import { useTabularContext } from './useTabularContext'
90
+
91
+ const { t } = useTranslation()
92
+ const {
93
+ allColumns,
94
+ visibleColumns,
95
+ hiddenCount,
96
+ toggleColumn,
97
+ showAllColumns,
98
+ hideAllColumns,
99
+ selectOnlyColumn,
100
+ } = useTabularContext()
101
+
102
+ // The panel is teleported, so it's positioned against its trigger with floating-ui.
103
+ const columnAnchor = useTemplateRef<InstanceType<typeof Popover>>('columnAnchor')
104
+ const columnPanel = useTemplateRef<InstanceType<typeof PopoverPanel>>('columnPanel')
105
+ const columnAnchorEl = computed(() => columnAnchor.value?.$el as HTMLElement | undefined)
106
+ const columnPanelEl = computed(() => columnPanel.value?.$el as HTMLElement | undefined)
107
+ const { floatingStyles: columnFloatingStyles } = useFloating(columnAnchorEl, columnPanelEl, {
108
+ placement: 'bottom-start',
109
+ middleware: [flip(), shift()],
110
+ whileElementsMounted: autoUpdate,
111
+ })
112
+ </script>