@datagouv/components-next 1.0.2-dev.242 → 1.0.2-dev.244

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.
@@ -0,0 +1,78 @@
1
+ <template>
2
+ <div
3
+ class="flex"
4
+ :class="underline ? 'border-b border-gray-default gap-4' : ['bg-gray-lower rounded p-1 gap-1', { 'w-fit': !grow }]"
5
+ role="group"
6
+ @keydown="onContainerKeydown"
7
+ >
8
+ <button
9
+ v-for="option in options"
10
+ :key="option.value"
11
+ type="button"
12
+ :aria-pressed="option.value === modelValue"
13
+ :tabindex="option.value === modelValue ? 0 : -1"
14
+ class="flex items-center justify-center gap-2 text-sm"
15
+ :class="[
16
+ underline ? 'py-2 px-1' : 'rounded py-1.5 px-3',
17
+ grow ? 'flex-1' : '',
18
+ option.value === modelValue
19
+ ? underline
20
+ ? 'text-new-primary font-bold border-b-2 border-b-new-primary -mb-px'
21
+ : 'bg-white font-bold shadow-sm'
22
+ : 'text-gray-medium hover:text-gray-title',
23
+ ]"
24
+ @click="$emit('update:modelValue', option.value)"
25
+ >
26
+ <component
27
+ :is="option.icon"
28
+ v-if="option.icon"
29
+ class="size-4"
30
+ />
31
+ {{ option.label }}
32
+ </button>
33
+ </div>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import type { Component } from 'vue'
38
+
39
+ export type SegmentedControlOption = {
40
+ value: string
41
+ label: string
42
+ icon?: Component
43
+ }
44
+
45
+ const props = withDefaults(defineProps<{
46
+ modelValue: string
47
+ options: Array<SegmentedControlOption>
48
+ // Fill the available width and stretch the segments (default: fit content)
49
+ grow?: boolean
50
+ // Underline variant: left-aligned tabs over a bottom border, active segment
51
+ // in blue with a thicker blue underline (default: pill track)
52
+ underline?: boolean
53
+ }>(), {
54
+ grow: false,
55
+ underline: false,
56
+ })
57
+
58
+ const emit = defineEmits<{
59
+ 'update:modelValue': [value: string]
60
+ }>()
61
+
62
+ // Roving tabindex: only the active segment is in the tab order; arrow keys
63
+ // move between segments (standard segmented-control keyboard model).
64
+ function onContainerKeydown(event: KeyboardEvent) {
65
+ const direction = event.key === 'ArrowRight' ? 1 : event.key === 'ArrowLeft' ? -1 : 0
66
+ if (!direction && event.key !== 'Home' && event.key !== 'End') return
67
+ event.preventDefault()
68
+ const index = props.options.findIndex(o => o.value === props.modelValue)
69
+ const last = props.options.length - 1
70
+ const next = event.key === 'Home'
71
+ ? 0
72
+ : event.key === 'End'
73
+ ? last
74
+ : Math.min(last, Math.max(0, index + direction))
75
+ const option = props.options[next]
76
+ if (option) emit('update:modelValue', option.value)
77
+ }
78
+ </script>
@@ -180,7 +180,9 @@ export function getResourceExternalUrl(dataset: Dataset | DatasetV2 | Omit<Datas
180
180
  }
181
181
 
182
182
  export function isGeopfSynced(resource: Resource | CommunityResource): boolean {
183
- return resource.geopf?.push_status != null || typeof resource.geopf?.offering_id === 'string'
183
+ // 'error'/'timeout' produce no stored_data on cartes.gouv.fr so a failed push leaves nothing to protect.
184
+ const pushStatus = resource.geopf?.push_status
185
+ return pushStatus === 'pending' || pushStatus === 'done' || typeof resource.geopf?.offering_id === 'string'
184
186
  }
185
187
 
186
188
  export function resolveResourceExternalUrl<R extends Resource | CommunityResource>(
package/src/main.ts CHANGED
@@ -104,6 +104,7 @@ import Tooltip from './components/Tooltip.vue'
104
104
  import Toggletip from './components/Toggletip.vue'
105
105
  import TopicCard from './components/TopicCard.vue'
106
106
  import TranslationT from './components/TranslationT.vue'
107
+ import SegmentedControl from './components/SegmentedControl.vue'
107
108
  import GlobalSearch from './components/Search/GlobalSearch.vue'
108
109
  import SearchInput from './components/Search/SearchInput.vue'
109
110
  import SearchableSelect from './components/Form/SearchableSelect.vue'
@@ -120,6 +121,8 @@ import { configKey, useComponentsConfig, type PluginConfig } from './config.js'
120
121
  import { ofetch } from 'ofetch'
121
122
  import { useTranslation } from './composables/useTranslation'
122
123
 
124
+ export type { SegmentedControlOption } from './components/SegmentedControl.vue'
125
+
123
126
  export { Toaster, toast } from 'vue-sonner'
124
127
 
125
128
  export * from './composables/useActiveDescendant'
@@ -421,6 +424,7 @@ export {
421
424
  TranslationT,
422
425
  UserActivityList,
423
426
  GlobalSearch,
427
+ SegmentedControl,
424
428
  SearchInput,
425
429
  SearchableSelect,
426
430
  SelectGroup,
@@ -78,4 +78,7 @@ export type Organization = {
78
78
  // means published. Like `presentation_blocs`, only present when requested with
79
79
  // X-Fields.
80
80
  presentation_blocs_published_at?: string | null
81
+ banner_color?: string | null
82
+ banner_image?: string | null
83
+ banner_image_position?: number
81
84
  }