@datagouv/components-next 0.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.
- package/README.md +150 -0
- package/assets/main.css +136 -0
- package/assets/placeholders/author.png +0 -0
- package/assets/placeholders/dataset.png +0 -0
- package/assets/placeholders/news.png +0 -0
- package/assets/placeholders/organization.png +0 -0
- package/assets/placeholders/reuse.png +0 -0
- package/assets/tailwind.config.js +24 -0
- package/dist/components.css +2 -0
- package/dist/locales/de.js +155 -0
- package/dist/locales/en.js +155 -0
- package/dist/locales/es.js +155 -0
- package/dist/locales/fr.js +155 -0
- package/dist/locales/it.js +155 -0
- package/dist/locales/pt.js +155 -0
- package/dist/locales/sr.js +155 -0
- package/package.json +72 -0
- package/src/components/AppLink.vue +51 -0
- package/src/components/Avatar.vue +27 -0
- package/src/components/AvatarWithName.vue +26 -0
- package/src/components/BannerAction.vue +39 -0
- package/src/components/BrandedButton.vue +170 -0
- package/src/components/CopyButton.vue +84 -0
- package/src/components/DataserviceCard.vue +184 -0
- package/src/components/DatasetCard.vue +198 -0
- package/src/components/DatasetInformationPanel.vue +210 -0
- package/src/components/DatasetQuality.vue +68 -0
- package/src/components/DatasetQualityInline.vue +32 -0
- package/src/components/DatasetQualityItem.vue +32 -0
- package/src/components/DatasetQualityItemWarning.vue +21 -0
- package/src/components/DatasetQualityScore.vue +35 -0
- package/src/components/DatasetQualityTooltipContent.vue +79 -0
- package/src/components/DescriptionDetails.vue +23 -0
- package/src/components/DescriptionList/DescriptionDetails.stories.ts +43 -0
- package/src/components/DescriptionList/DescriptionList.stories.ts +47 -0
- package/src/components/DescriptionList/DescriptionTerm.stories.ts +28 -0
- package/src/components/DescriptionList.vue +8 -0
- package/src/components/DescriptionTerm.vue +8 -0
- package/src/components/ExtraAccordion.vue +78 -0
- package/src/components/Icons/Archive.vue +21 -0
- package/src/components/Icons/Code.vue +21 -0
- package/src/components/Icons/Documentation.vue +21 -0
- package/src/components/Icons/File.vue +21 -0
- package/src/components/Icons/Image.vue +7 -0
- package/src/components/Icons/Link.vue +21 -0
- package/src/components/Icons/Table.vue +21 -0
- package/src/components/OrganizationCard.vue +68 -0
- package/src/components/OrganizationNameWithCertificate.vue +45 -0
- package/src/components/OwnerType.vue +43 -0
- package/src/components/OwnerTypeIcon.vue +18 -0
- package/src/components/Pagination.vue +205 -0
- package/src/components/Placeholder.vue +29 -0
- package/src/components/ReadMore.vue +107 -0
- package/src/components/ResourceAccordion/DataStructure.vue +87 -0
- package/src/components/ResourceAccordion/EditButton.vue +34 -0
- package/src/components/ResourceAccordion/Metadata.vue +171 -0
- package/src/components/ResourceAccordion/Preview.vue +229 -0
- package/src/components/ResourceAccordion/PreviewLoader.vue +148 -0
- package/src/components/ResourceAccordion/ResourceAccordion.vue +484 -0
- package/src/components/ResourceAccordion/ResourceIcon.vue +16 -0
- package/src/components/ResourceAccordion/SchemaBadge.vue +148 -0
- package/src/components/ResourceAccordion/SchemaLoader.vue +30 -0
- package/src/components/ResourceAccordion/Swagger.vue +46 -0
- package/src/components/ResourceAccordion/france.svg +1 -0
- package/src/components/ReuseCard.vue +106 -0
- package/src/components/ReuseDetails.vue +45 -0
- package/src/components/SimpleBanner.vue +24 -0
- package/src/components/SmallChart.vue +149 -0
- package/src/components/StatBox.vue +100 -0
- package/src/components/Tabs/Tab.vue +62 -0
- package/src/components/Tabs/TabGroup.vue +20 -0
- package/src/components/Tabs/TabList.vue +15 -0
- package/src/components/Tabs/TabPanel.vue +7 -0
- package/src/components/Tabs/TabPanels.vue +7 -0
- package/src/components/Toggletip.vue +62 -0
- package/src/components/ToggletipButton.vue +14 -0
- package/src/composables/useActiveDescendant.ts +103 -0
- package/src/composables/useReuseType.ts +14 -0
- package/src/config.ts +33 -0
- package/src/functions/api.ts +96 -0
- package/src/functions/api.types.ts +41 -0
- package/src/functions/config.ts +12 -0
- package/src/functions/datasets.ts +24 -0
- package/src/functions/dates.ts +85 -0
- package/src/functions/helpers.ts +38 -0
- package/src/functions/markdown.ts +47 -0
- package/src/functions/matomo.ts +3 -0
- package/src/functions/organizations.ts +85 -0
- package/src/functions/owned.ts +11 -0
- package/src/functions/resources.ts +99 -0
- package/src/functions/reuses.ts +28 -0
- package/src/functions/schemas.ts +96 -0
- package/src/functions/tabularApi.ts +27 -0
- package/src/functions/users.ts +7 -0
- package/src/locales/de.json +154 -0
- package/src/locales/en.json +154 -0
- package/src/locales/es.json +154 -0
- package/src/locales/fr.json +154 -0
- package/src/locales/it.json +154 -0
- package/src/locales/pt.json +154 -0
- package/src/locales/sr.json +154 -0
- package/src/main.ts +147 -0
- package/src/types/badges.ts +5 -0
- package/src/types/contact_point.ts +7 -0
- package/src/types/dataservices.ts +68 -0
- package/src/types/datasets.ts +80 -0
- package/src/types/frequency.ts +6 -0
- package/src/types/granularity.ts +6 -0
- package/src/types/harvest.ts +3 -0
- package/src/types/keyboard.ts +1 -0
- package/src/types/licenses.ts +9 -0
- package/src/types/organizations.ts +41 -0
- package/src/types/owned.ts +9 -0
- package/src/types/resources.ts +37 -0
- package/src/types/reuses.ts +49 -0
- package/src/types/site.ts +23 -0
- package/src/types/topics.ts +20 -0
- package/src/types/ui.ts +3 -0
- package/src/types/users.ts +10 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="config?.appLink"
|
|
4
|
+
v-if="config.appLink"
|
|
5
|
+
:to
|
|
6
|
+
>
|
|
7
|
+
<slot />
|
|
8
|
+
</component>
|
|
9
|
+
<a
|
|
10
|
+
v-else-if="isExternal"
|
|
11
|
+
:href="to"
|
|
12
|
+
>
|
|
13
|
+
<slot />
|
|
14
|
+
</a>
|
|
15
|
+
<RouterLink
|
|
16
|
+
v-else
|
|
17
|
+
:to
|
|
18
|
+
><slot /></RouterLink>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed } from 'vue'
|
|
23
|
+
import { RouterLink } from 'vue-router'
|
|
24
|
+
import { useI18n } from 'vue-i18n'
|
|
25
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
26
|
+
import { useComponentsConfig } from '../config'
|
|
27
|
+
|
|
28
|
+
const config = useComponentsConfig()
|
|
29
|
+
const { locale } = useI18n()
|
|
30
|
+
|
|
31
|
+
const props = defineProps<{
|
|
32
|
+
to: string | RouteLocationRaw
|
|
33
|
+
}>()
|
|
34
|
+
|
|
35
|
+
const isExternal = computed(() => {
|
|
36
|
+
if (typeof props.to !== 'string') return false
|
|
37
|
+
return props.to && props.to.startsWith('http')
|
|
38
|
+
})
|
|
39
|
+
const to = computed(() => {
|
|
40
|
+
// If the `appLink` component is override, the override is responsible of the locale management
|
|
41
|
+
if (config.appLink) return props.to
|
|
42
|
+
|
|
43
|
+
// If it's an external link, no locale management needed.
|
|
44
|
+
if (isExternal.value) return props.to
|
|
45
|
+
|
|
46
|
+
// TODO harden this for path not starting with "/"x
|
|
47
|
+
if (typeof props.to === 'string') return `/${locale.value}${props.to}`
|
|
48
|
+
|
|
49
|
+
return props.to
|
|
50
|
+
})
|
|
51
|
+
</script>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<img
|
|
3
|
+
:class="{ 'rounded-full border border-gray-default': rounded }"
|
|
4
|
+
:src="avatarUrl"
|
|
5
|
+
:width="size"
|
|
6
|
+
:height="size"
|
|
7
|
+
loading="lazy"
|
|
8
|
+
alt=""
|
|
9
|
+
>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { computed } from 'vue'
|
|
14
|
+
import { getUserAvatar } from '../functions/users'
|
|
15
|
+
import type { User } from '../types/users'
|
|
16
|
+
|
|
17
|
+
type Props = {
|
|
18
|
+
rounded?: boolean
|
|
19
|
+
size?: number
|
|
20
|
+
user: User
|
|
21
|
+
}
|
|
22
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
23
|
+
rounded: false,
|
|
24
|
+
size: 40,
|
|
25
|
+
})
|
|
26
|
+
const avatarUrl = computed(() => getUserAvatar(props.user, props.size))
|
|
27
|
+
</script>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span class="inline-flex items-center space-x-1">
|
|
3
|
+
<Avatar
|
|
4
|
+
v-bind="$attrs"
|
|
5
|
+
:user="user"
|
|
6
|
+
:size
|
|
7
|
+
:rounded="true"
|
|
8
|
+
/>
|
|
9
|
+
<span class="fr-text--bold">
|
|
10
|
+
{{ user.first_name }}
|
|
11
|
+
{{ user.last_name }}
|
|
12
|
+
</span>
|
|
13
|
+
</span>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import type { User } from '../types/users'
|
|
18
|
+
import Avatar from './Avatar.vue'
|
|
19
|
+
|
|
20
|
+
withDefaults(defineProps<{
|
|
21
|
+
user: User
|
|
22
|
+
size?: number
|
|
23
|
+
}>(), {
|
|
24
|
+
size: 12,
|
|
25
|
+
})
|
|
26
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
class="group flex items-center justify-between rounded border p-2.5"
|
|
4
|
+
:data-banner-action-type="type"
|
|
5
|
+
:class="{
|
|
6
|
+
'!border-datagouv bg-datagouv-lightest text-datagouv-dark': type === 'primary',
|
|
7
|
+
'!border-danger-dark bg-danger-lightest text-danger-dark': type === 'danger',
|
|
8
|
+
'!border-warning-dark bg-warning-lightest text-warning-dark': type === 'warning',
|
|
9
|
+
}"
|
|
10
|
+
>
|
|
11
|
+
<div>
|
|
12
|
+
<div class="text-gray-900">
|
|
13
|
+
{{ title }}
|
|
14
|
+
</div>
|
|
15
|
+
<div
|
|
16
|
+
v-if="$slots.default"
|
|
17
|
+
class="text-xs mt-1"
|
|
18
|
+
>
|
|
19
|
+
<slot />
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div v-if="$slots.button">
|
|
23
|
+
<slot name="button" />
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script lang="ts">
|
|
29
|
+
export const bannerActionTypeKey = Symbol() as InjectionKey<'primary' | 'danger' | 'warning'>
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
const props = defineProps<{
|
|
34
|
+
type: 'primary' | 'danger' | 'warning'
|
|
35
|
+
title: string
|
|
36
|
+
}>()
|
|
37
|
+
|
|
38
|
+
provide(bannerActionTypeKey, props.type)
|
|
39
|
+
</script>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="href ? AppLink: 'button'"
|
|
4
|
+
class="inline-flex items-center rounded-full font-medium border !bg-none !no-underline after:content-none"
|
|
5
|
+
:class="[colors, sizes, removePaddingsIfNoBorders, isDisabled ? '!opacity-50' : '', iconRight && !newTab ? 'flex-row-reverse space-x-reverse' : '']"
|
|
6
|
+
:disabled="isDisabled"
|
|
7
|
+
:aria-disabled="isDisabled"
|
|
8
|
+
:role="href ? 'link' : ''"
|
|
9
|
+
:to="isDisabled ? undefined : href"
|
|
10
|
+
:target="newTab ? '_blank' : undefined"
|
|
11
|
+
:type
|
|
12
|
+
>
|
|
13
|
+
<AdminLoader
|
|
14
|
+
v-if="loading"
|
|
15
|
+
size="16"
|
|
16
|
+
:color="color === 'primary' ? 'white' : 'primary'"
|
|
17
|
+
/>
|
|
18
|
+
<component
|
|
19
|
+
:is="icon"
|
|
20
|
+
v-else-if="icon"
|
|
21
|
+
:class="iconSize"
|
|
22
|
+
v-bind="iconAttrs"
|
|
23
|
+
/>
|
|
24
|
+
<span
|
|
25
|
+
v-if="hasText"
|
|
26
|
+
class="whitespace-nowrap"
|
|
27
|
+
:class="iconOnly ? 'sr-only' : ''"
|
|
28
|
+
><slot /></span>
|
|
29
|
+
<RiExternalLinkLine
|
|
30
|
+
v-if="newTab"
|
|
31
|
+
:class="iconSize"
|
|
32
|
+
/>
|
|
33
|
+
</component>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import type {
|
|
38
|
+
Component,
|
|
39
|
+
Slot,
|
|
40
|
+
VNode,
|
|
41
|
+
} from 'vue'
|
|
42
|
+
import {
|
|
43
|
+
Comment,
|
|
44
|
+
computed,
|
|
45
|
+
inject,
|
|
46
|
+
Text,
|
|
47
|
+
useSlots,
|
|
48
|
+
} from 'vue'
|
|
49
|
+
import { RiExternalLinkLine } from '@remixicon/vue'
|
|
50
|
+
import AppLink from './AppLink.vue'
|
|
51
|
+
import { bannerActionTypeKey } from './BannerAction.vue'
|
|
52
|
+
|
|
53
|
+
type ColorType = 'primary' | 'primary-soft' | 'primary-softer' | 'secondary' | 'secondary-softer' | 'warning' | 'danger' | 'tertiary'
|
|
54
|
+
|
|
55
|
+
const props = withDefaults(defineProps<{
|
|
56
|
+
size?: '2xs' | 'xs' | 'sm' | 'lg'
|
|
57
|
+
color?: ColorType
|
|
58
|
+
disabled?: boolean
|
|
59
|
+
loading?: boolean
|
|
60
|
+
icon?: Component
|
|
61
|
+
iconAttrs?: Record<string, string>
|
|
62
|
+
href?: string
|
|
63
|
+
newTab?: boolean
|
|
64
|
+
iconOnly?: boolean
|
|
65
|
+
iconRight?: boolean
|
|
66
|
+
keepMarginsEvenWithoutBorders?: boolean
|
|
67
|
+
type?: 'submit' | 'button'
|
|
68
|
+
}>(), {
|
|
69
|
+
newTab: false,
|
|
70
|
+
iconOnly: false,
|
|
71
|
+
iconRight: false,
|
|
72
|
+
keepMarginsEvenWithoutBorders: false,
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const slots = useSlots()
|
|
76
|
+
|
|
77
|
+
const type = computed(() => {
|
|
78
|
+
if (props.type) return props.type
|
|
79
|
+
if (props.href) return undefined
|
|
80
|
+
|
|
81
|
+
return 'button'
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
const size = computed(() => {
|
|
85
|
+
if (props.size) return props.size
|
|
86
|
+
if (bannerActionType) return 'xs'
|
|
87
|
+
return 'sm'
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const color = computed<ColorType>(() => {
|
|
91
|
+
if (props.color) return props.color
|
|
92
|
+
if (bannerActionType) {
|
|
93
|
+
return {
|
|
94
|
+
primary: 'primary-soft' as ColorType,
|
|
95
|
+
warning: 'warning' as ColorType,
|
|
96
|
+
danger: 'danger' as ColorType,
|
|
97
|
+
}[bannerActionType]
|
|
98
|
+
}
|
|
99
|
+
return 'primary'
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const hasText = computed(() => {
|
|
103
|
+
return hasSlotContent(slots.default) && !props.iconOnly
|
|
104
|
+
})
|
|
105
|
+
const bannerActionType = inject(bannerActionTypeKey, null)
|
|
106
|
+
|
|
107
|
+
const isDisabled = computed(() => props.disabled || props.loading)
|
|
108
|
+
|
|
109
|
+
const colors = computed(() => {
|
|
110
|
+
return {
|
|
111
|
+
'primary': `text-white bg-datagouv-dark !border-datagouv-dark ${!isDisabled.value ? 'hover:!bg-datagouv-hover hover:!border-datagouv-hover' : ''}`,
|
|
112
|
+
'primary-soft': `text-datagouv-dark bg-transparent !border-datagouv-dark ${!isDisabled.value ? '[&&]:hover:!bg-gray-some' : ''}`,
|
|
113
|
+
'primary-softer': `text-datagouv-dark bg-transparent !border-transparent ${!isDisabled.value ? '[&&]:hover:!bg-gray-some' : ''}`,
|
|
114
|
+
'secondary': `text-gray-plain bg-white !border-gray-plain ${!isDisabled.value ? '[&&]:hover:!bg-gray-some' : ''}`,
|
|
115
|
+
'secondary-softer': `text-gray-plain !border-transparent ${!isDisabled.value ? '[&&]:hover:!bg-gray-some' : ''}`,
|
|
116
|
+
'warning': `text-warning-dark bg-white !border-warning-dark ${!isDisabled.value ? '[&&]:hover:!bg-gray-some' : ''}`,
|
|
117
|
+
'danger': `!text-danger-dark bg-white !border-danger-dark ${!isDisabled.value ? '[&&]:hover:!bg-gray-some' : ''}`,
|
|
118
|
+
'tertiary': `!border-none bg-transparent text-datagouv-dark ${!isDisabled.value ? '[&&]:hover:!bg-gray-some' : ''}`,
|
|
119
|
+
}[color.value]
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const sizes = computed(() => {
|
|
123
|
+
return {
|
|
124
|
+
'lg': `text-lg ${hasText.value ? 'px-4 py-2 space-x-2' : 'p-3'}`,
|
|
125
|
+
'sm': `text-sm leading-none ${hasText.value ? 'px-4 py-3 space-x-1' : 'p-2.5'}`,
|
|
126
|
+
'xs': `text-xs leading-[0.875rem] ${hasText.value ? 'px-4 py-2 space-x-1' : 'p-2'}`,
|
|
127
|
+
'2xs': `text-xs leading-[0.875rem] p-1 space-x-1`,
|
|
128
|
+
}[size.value]
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
const hasBorders = computed(() => {
|
|
132
|
+
return props.color !== 'primary-softer' && props.color !== 'secondary-softer'
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
const removePaddingsIfNoBorders = computed(() => {
|
|
136
|
+
if (hasBorders.value) return ''
|
|
137
|
+
if (props.keepMarginsEvenWithoutBorders) return ''
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
'lg': hasText.value ? '-mx-6' : '-mx-3',
|
|
141
|
+
'sm': hasText.value ? '-mx-4' : '-mx-2.5',
|
|
142
|
+
'xs': hasText.value ? '-mx-4' : '-mx-2',
|
|
143
|
+
'2xs': '-m-1',
|
|
144
|
+
}[size.value]
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
const iconSize = computed(() => {
|
|
148
|
+
return {
|
|
149
|
+
'lg': (hasBorders.value || hasText.value) ? 'size-6' : 'size-8',
|
|
150
|
+
'sm': (hasBorders.value || hasText.value) ? 'size-4' : 'size-6',
|
|
151
|
+
'xs': (hasBorders.value || hasText.value) ? 'size-3' : 'size-5',
|
|
152
|
+
'2xs': (hasBorders.value || hasText.value) ? 'size-3' : 'size-4',
|
|
153
|
+
}[size.value]
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
function hasSlotContent(slot: Slot | undefined, slotProps = {}): boolean {
|
|
157
|
+
if (!slot) return false
|
|
158
|
+
|
|
159
|
+
return slot(slotProps).some((vnode: VNode) => {
|
|
160
|
+
if (vnode.type === Comment) return false
|
|
161
|
+
|
|
162
|
+
if (Array.isArray(vnode.children) && !vnode.children.length) return false
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
vnode.type !== Text
|
|
166
|
+
|| (typeof vnode.children === 'string' && vnode.children.trim() !== '')
|
|
167
|
+
)
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
</script>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
class="text-sm mb-0 whitespace-nowrap relative p-1 text-gray-medium leading-none"
|
|
5
|
+
:class="{ 'border bg-white rounded-sm text-gray-title': hideLabel }"
|
|
6
|
+
@click="copy"
|
|
7
|
+
>
|
|
8
|
+
<span
|
|
9
|
+
v-if="copied"
|
|
10
|
+
class="flex items-center"
|
|
11
|
+
:class="{ 'flex-row-reverse': reverse }"
|
|
12
|
+
style="color: #3558a2;"
|
|
13
|
+
>
|
|
14
|
+
<RiCheckLine class="size-4 inline" />
|
|
15
|
+
<span
|
|
16
|
+
class="fr-ml-1v copy-label"
|
|
17
|
+
:class="{ 'fr-sr-only': hideLabel }"
|
|
18
|
+
>{{ copiedLabel }}</span>
|
|
19
|
+
</span>
|
|
20
|
+
<span
|
|
21
|
+
v-if="!copied"
|
|
22
|
+
class="flex items-center"
|
|
23
|
+
:class="{ 'flex-row-reverse': reverse }"
|
|
24
|
+
>
|
|
25
|
+
<component
|
|
26
|
+
:is="hideLabel ? RiClipboardLine : RiFileCopyLine"
|
|
27
|
+
class="size-4 inline"
|
|
28
|
+
/>
|
|
29
|
+
<span
|
|
30
|
+
class="fr-ml-1v copy-link copy-label"
|
|
31
|
+
:class="{ 'fr-sr-only': hideLabel }"
|
|
32
|
+
>{{ label }}</span>
|
|
33
|
+
</span>
|
|
34
|
+
</button>
|
|
35
|
+
</template>
|
|
36
|
+
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { RiCheckLine, RiClipboardLine, RiFileCopyLine } from '@remixicon/vue'
|
|
39
|
+
import { ref } from 'vue'
|
|
40
|
+
|
|
41
|
+
const props = withDefaults(defineProps<{
|
|
42
|
+
text: string
|
|
43
|
+
label: string
|
|
44
|
+
copiedLabel: string
|
|
45
|
+
hideLabel?: boolean
|
|
46
|
+
reverse?: boolean
|
|
47
|
+
}>(), {
|
|
48
|
+
hideLabel: false,
|
|
49
|
+
reverse: false,
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const copied = ref(false)
|
|
53
|
+
|
|
54
|
+
const copy = () => {
|
|
55
|
+
navigator.clipboard.writeText(props.text)
|
|
56
|
+
copied.value = true
|
|
57
|
+
setTimeout(() => copied.value = false, 2000)
|
|
58
|
+
}
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<style scoped>
|
|
62
|
+
button:hover .copy-icon {
|
|
63
|
+
color: #3558a2;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.copy-link {
|
|
67
|
+
/* Using opacity here to prevent moving object with display:none (for example when clamping a text before the button) */
|
|
68
|
+
opacity: 0%;
|
|
69
|
+
}
|
|
70
|
+
button:hover .copy-link {
|
|
71
|
+
opacity: 100%;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
We may want to enable this to leave more space for title in small screens… But it also affects
|
|
76
|
+
buttons in the body of the resources panels and we don't want it…
|
|
77
|
+
@container (max-width: 600px) {
|
|
78
|
+
.copy-label {
|
|
79
|
+
position: absolute;
|
|
80
|
+
bottom: -1rem;
|
|
81
|
+
left: 0;
|
|
82
|
+
}
|
|
83
|
+
} */
|
|
84
|
+
</style>
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<article
|
|
3
|
+
class="my-4 p-4 border border-gray-default fr-enlarge-link"
|
|
4
|
+
:class="{
|
|
5
|
+
'border-tabular-api': isTabularApi,
|
|
6
|
+
'mt-6': showBadge,
|
|
7
|
+
}"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
v-if="showBadge"
|
|
11
|
+
class="absolute top-0 fr-grid-row fr-grid-row--middle fr-mt-n3v fr-ml-n1v"
|
|
12
|
+
>
|
|
13
|
+
<p
|
|
14
|
+
v-if="dataservice.access_type === 'restricted'"
|
|
15
|
+
class="fr-badge fr-badge--sm fr-badge--mention-grey text-gray-medium mr-2"
|
|
16
|
+
>
|
|
17
|
+
<span
|
|
18
|
+
class="fr-icon-lock-line fr-icon--sm"
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
/>
|
|
21
|
+
{{ t('Restricted access') }}
|
|
22
|
+
</p>
|
|
23
|
+
<p
|
|
24
|
+
v-if="dataservice.private"
|
|
25
|
+
class="fr-badge fr-badge--sm fr-badge--mention-grey text-gray-medium mr-2"
|
|
26
|
+
>
|
|
27
|
+
<span
|
|
28
|
+
class="fr-icon-lock-line fr-icon--sm"
|
|
29
|
+
aria-hidden="true"
|
|
30
|
+
/>
|
|
31
|
+
{{ t('Draft') }}
|
|
32
|
+
</p>
|
|
33
|
+
<p
|
|
34
|
+
v-if="dataservice.archived_at"
|
|
35
|
+
class="fr-badge fr-badge--sm fr-badge--mention-grey text-gray-medium mr-2"
|
|
36
|
+
>
|
|
37
|
+
<span
|
|
38
|
+
class="fr-icon-lock-line fr-icon--sm"
|
|
39
|
+
aria-hidden="true"
|
|
40
|
+
/>
|
|
41
|
+
{{ t('Archived') }}
|
|
42
|
+
</p>
|
|
43
|
+
</div>
|
|
44
|
+
<h4 class="fr-text--md fr-mb-0">
|
|
45
|
+
<slot
|
|
46
|
+
name="dataserviceUrl"
|
|
47
|
+
:dataservice="dataservice"
|
|
48
|
+
:dataservice-url="dataserviceUrl"
|
|
49
|
+
>
|
|
50
|
+
<svg
|
|
51
|
+
v-if="isTabularApi"
|
|
52
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
53
|
+
viewBox="0 0 24 24"
|
|
54
|
+
fill="currentColor"
|
|
55
|
+
aria-hidden="true"
|
|
56
|
+
class="fr-mr-1v"
|
|
57
|
+
style="width:1rem"
|
|
58
|
+
><path d="M17.0007 1.20825 18.3195 3.68108 20.7923 4.99992 18.3195 6.31876 17.0007 8.79159 15.6818 6.31876 13.209 4.99992 15.6818 3.68108 17.0007 1.20825ZM10.6673 9.33325 15.6673 11.9999 10.6673 14.6666 8.00065 19.6666 5.33398 14.6666.333984 11.9999 5.33398 9.33325 8.00065 4.33325 10.6673 9.33325ZM11.4173 11.9999 9.18905 10.8115 8.00065 8.58325 6.81224 10.8115 4.58398 11.9999 6.81224 13.1883 8.00065 15.4166 9.18905 13.1883 11.4173 11.9999ZM19.6673 16.3333 18.0007 13.2083 16.334 16.3333 13.209 17.9999 16.334 19.6666 18.0007 22.7916 19.6673 19.6666 22.7923 17.9999 19.6673 16.3333Z" /></svg>
|
|
59
|
+
<span
|
|
60
|
+
v-else
|
|
61
|
+
class="fr-icon-terminal-line fr-icon--sm fr-mr-1v"
|
|
62
|
+
aria-hidden="true"
|
|
63
|
+
/>
|
|
64
|
+
<AppLink
|
|
65
|
+
class="inline-flex"
|
|
66
|
+
:to="dataserviceUrl"
|
|
67
|
+
>
|
|
68
|
+
<component
|
|
69
|
+
:is="config.textClamp"
|
|
70
|
+
v-if="config.textClamp"
|
|
71
|
+
:auto-resize="true"
|
|
72
|
+
:text="dataservice.title"
|
|
73
|
+
:max-lines="1"
|
|
74
|
+
/>
|
|
75
|
+
</AppLink>
|
|
76
|
+
</slot>
|
|
77
|
+
</h4>
|
|
78
|
+
<p
|
|
79
|
+
v-if="dataservice.organization || dataservice.owner"
|
|
80
|
+
class="text-sm text-gray-medium inline-flex mt-1 mb-0"
|
|
81
|
+
>
|
|
82
|
+
<span
|
|
83
|
+
v-if="dataservice.organization"
|
|
84
|
+
class="not-enlarged"
|
|
85
|
+
>
|
|
86
|
+
<AppLink
|
|
87
|
+
v-if="organizationUrl"
|
|
88
|
+
class="fr-link fr-text--sm"
|
|
89
|
+
:to="organizationUrl"
|
|
90
|
+
>
|
|
91
|
+
<OrganizationNameWithCertificate :organization="dataservice.organization" />
|
|
92
|
+
</AppLink>
|
|
93
|
+
<OrganizationNameWithCertificate
|
|
94
|
+
v-else
|
|
95
|
+
:organization="dataservice.organization"
|
|
96
|
+
/>
|
|
97
|
+
</span>
|
|
98
|
+
<component
|
|
99
|
+
:is="config.textClamp"
|
|
100
|
+
v-else-if="config.textClamp"
|
|
101
|
+
class="not-enlarged fr-mr-1v"
|
|
102
|
+
:auto-resize="true"
|
|
103
|
+
:text="ownerName"
|
|
104
|
+
:max-lines="1"
|
|
105
|
+
/>
|
|
106
|
+
<span class="dash-before whitespace-nowrap">{{ t('Updated {date}', { date: formatRelativeIfRecentDate(dataservice.metadata_modified_at, { dateStyle: 'medium' }) }) }}</span>
|
|
107
|
+
</p>
|
|
108
|
+
<p class="text-sm text-gray-medium mb-0 mt-1">
|
|
109
|
+
<span class="fr-icon-information-line fr-icon--sm text-gray-medium" />
|
|
110
|
+
{{ t('Availability :') }}
|
|
111
|
+
<span class="text-gray-plain">
|
|
112
|
+
<template v-if="dataservice.availability">
|
|
113
|
+
{{ t('{n}%', dataservice.availability) }}
|
|
114
|
+
</template>
|
|
115
|
+
<template v-else>
|
|
116
|
+
{{ t('unknown') }}
|
|
117
|
+
</template>
|
|
118
|
+
</span>
|
|
119
|
+
</p>
|
|
120
|
+
<component
|
|
121
|
+
:is="config.textClamp"
|
|
122
|
+
v-if="config.textClamp && description && showDescription"
|
|
123
|
+
class="fr-text--sm fr-mt-1w fr-mb-0 overflow-wrap-anywhere"
|
|
124
|
+
:auto-resize="true"
|
|
125
|
+
:text="description"
|
|
126
|
+
:max-lines="2"
|
|
127
|
+
/>
|
|
128
|
+
</article>
|
|
129
|
+
</template>
|
|
130
|
+
|
|
131
|
+
<script setup lang="ts">
|
|
132
|
+
import { computed, ref, watchEffect } from 'vue'
|
|
133
|
+
import { useI18n } from 'vue-i18n'
|
|
134
|
+
import type { RouteLocationRaw } from 'vue-router'
|
|
135
|
+
import { useComponentsConfig } from '../config'
|
|
136
|
+
import { formatRelativeIfRecentDate } from '../functions/dates'
|
|
137
|
+
import { removeMarkdown } from '../functions/markdown'
|
|
138
|
+
import { getOwnerName } from '../functions/owned'
|
|
139
|
+
import type { Dataservice } from '../types/dataservices'
|
|
140
|
+
import OrganizationNameWithCertificate from './OrganizationNameWithCertificate.vue'
|
|
141
|
+
import AppLink from './AppLink.vue'
|
|
142
|
+
|
|
143
|
+
type Props = {
|
|
144
|
+
dataservice: Dataservice
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The dataserviceUrl is a route location object to allow Vue Router to navigate to the details of a dataservice.
|
|
148
|
+
* It is used as a separate prop to allow other sites using the package to define their own dataservice pages.
|
|
149
|
+
*/
|
|
150
|
+
dataserviceUrl: RouteLocationRaw
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The organizationUrl is an optional route location object to allow Vue Router to navigate to the details of the organization linked to tha dataservice.
|
|
154
|
+
* It is used as a separate prop to allow other sites using the package to define their own organization pages.
|
|
155
|
+
*/
|
|
156
|
+
organizationUrl?: RouteLocationRaw
|
|
157
|
+
showDescription?: boolean
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
161
|
+
style: () => ({}),
|
|
162
|
+
showDescription: true,
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
const { t } = useI18n()
|
|
166
|
+
const ownerName = computed(() => getOwnerName(props.dataservice))
|
|
167
|
+
const showBadge = computed(() => props.dataservice.access_type === 'restricted' || props.dataservice.private || props.dataservice.archived_at)
|
|
168
|
+
|
|
169
|
+
const config = useComponentsConfig()
|
|
170
|
+
const isTabularApi = computed(() => {
|
|
171
|
+
return config.tabularApiDataserviceId === props.dataservice.id
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const description = ref('')
|
|
175
|
+
watchEffect(async () => {
|
|
176
|
+
description.value = await removeMarkdown(props.dataservice.description)
|
|
177
|
+
})
|
|
178
|
+
</script>
|
|
179
|
+
|
|
180
|
+
<style scoped>
|
|
181
|
+
.border-tabular-api {
|
|
182
|
+
border-color: #373C42 !important;
|
|
183
|
+
}
|
|
184
|
+
</style>
|