Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 133 additions & 0 deletions composables/useDesignData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import type { Dataservice, DatasetV2, Organization, PaginatedArray, Post, Reuse, Thread, TopicV2 } from '@datagouv/components-next'

export function useDesignData() {
const { settings } = useDesignSettings()
const { $api } = useNuxtApp()

const datasetUrl = computed(() => `/api/2/datasets/${settings.value.datasetSlug}/`)
const dataserviceUrl = computed(() => `/api/1/dataservices/${settings.value.dataserviceSlug}/`)
const organizationUrl = computed(() => `/api/1/organizations/${settings.value.organizationSlug}/`)
const reuseUrl = computed(() => `/api/1/reuses/${settings.value.reuseSlug}/`)
const tabularDatasetUrl = computed(() => `/api/2/datasets/${settings.value.tabularDatasetSlug}/`)

const { data: dataset, status: datasetStatus, refresh: refreshDataset } = useLazyFetch<DatasetV2>(datasetUrl, {
$fetch: $api,
server: false,
watch: [datasetUrl],
})

const { data: dataservice, status: dataserviceStatus, refresh: refreshDataservice } = useLazyFetch<Dataservice>(dataserviceUrl, {
$fetch: $api,
server: false,
watch: [dataserviceUrl],
})

const { data: organization, status: organizationStatus, refresh: refreshOrganization } = useLazyFetch<Organization>(organizationUrl, {
$fetch: $api,
server: false,
watch: [organizationUrl],
})

const { data: reuse, status: reuseStatus, refresh: refreshReuse } = useLazyFetch<Reuse>(reuseUrl, {
$fetch: $api,
server: false,
watch: [reuseUrl],
})

const { data: tabularDataset, status: tabularDatasetStatus, refresh: refreshTabularDataset } = useLazyFetch<DatasetV2>(tabularDatasetUrl, {
$fetch: $api,
server: false,
watch: [tabularDatasetUrl],
})

const { data: discussionsData, status: discussionsStatus, refresh: refreshDiscussions } = useLazyFetch<PaginatedArray<Thread>>('/api/1/discussions/', {
$fetch: $api,
server: false,
query: { page_size: 4 },
})

const { data: postsData, status: postsStatus, refresh: refreshPosts } = useLazyFetch<PaginatedArray<Post>>('/api/1/posts/', {
$fetch: $api,
server: false,
query: { page_size: 4 },
})

const { data: topicsData, status: topicsStatus, refresh: refreshTopics } = useLazyFetch<PaginatedArray<TopicV2>>('/api/2/topics/', {
$fetch: $api,
server: false,
query: { page_size: 4 },
})

const { data: reusesData, status: reusesStatus, refresh: refreshReuses } = useLazyFetch<PaginatedArray<Reuse>>('/api/1/reuses/', {
$fetch: $api,
server: false,
query: { page_size: 4 },
})

const discussions = computed(() => discussionsData.value?.data ?? [])
const posts = computed(() => postsData.value?.data ?? [])
const topics = computed(() => topicsData.value?.data ?? [])
const reuses = computed(() => reusesData.value?.data ?? [])

const isLoading = computed(() =>
datasetStatus.value === 'pending'
|| dataserviceStatus.value === 'pending'
|| organizationStatus.value === 'pending'
|| reuseStatus.value === 'pending',
)

const refreshAll = async () => {
await Promise.all([
refreshDataset(),
refreshDataservice(),
refreshOrganization(),
refreshReuse(),
refreshTabularDataset(),
refreshDiscussions(),
refreshPosts(),
refreshTopics(),
refreshReuses(),
])
}

return {
dataset,
datasetStatus,
refreshDataset,

dataservice,
dataserviceStatus,
refreshDataservice,

organization,
organizationStatus,
refreshOrganization,

reuse,
reuseStatus,
refreshReuse,

tabularDataset,
tabularDatasetStatus,
refreshTabularDataset,

discussions,
discussionsStatus,
refreshDiscussions,

posts,
postsStatus,
refreshPosts,

topics,
topicsStatus,
refreshTopics,

reuses,
reusesStatus,
refreshReuses,

isLoading,
refreshAll,
}
}
62 changes: 62 additions & 0 deletions composables/useDesignSettings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
export interface DesignSettings {
datasetSlug: string
dataserviceSlug: string
organizationSlug: string
reuseSlug: string
tabularDatasetSlug: string
mapDatasetSlug: string
}

const DEFAULT_SETTINGS: DesignSettings = {
datasetSlug: 'repertoire-national-des-elus-1',
dataserviceSlug: 'api-sirene-open-data',
organizationSlug: 'sante-publique-france',
reuseSlug: 'datafrance-plateforme-de-visualisation-open-data',
tabularDatasetSlug: 'fichier-consolide-des-bornes-de-recharge-pour-vehicules-electriques',
mapDatasetSlug: 'decoupage-administratif-communal-francais-issu-d-openstreetmap',
}

const STORAGE_KEY = 'design-system-settings'

export function useDesignSettings() {
const settings = useState<DesignSettings>('design-settings', () => {
if (import.meta.client) {
const stored = localStorage.getItem(STORAGE_KEY)
if (stored) {
try {
return { ...DEFAULT_SETTINGS, ...JSON.parse(stored) }
}
catch {
return { ...DEFAULT_SETTINGS }
}
}
}
return { ...DEFAULT_SETTINGS }
})

const updateSetting = <K extends keyof DesignSettings>(key: K, value: DesignSettings[K]) => {
settings.value = { ...settings.value, [key]: value }
if (import.meta.client) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(settings.value))
}
}

const resetSettings = () => {
settings.value = { ...DEFAULT_SETTINGS }
if (import.meta.client) {
localStorage.removeItem(STORAGE_KEY)
}
}

const resetSetting = <K extends keyof DesignSettings>(key: K) => {
updateSetting(key, DEFAULT_SETTINGS[key])
}

return {
settings: readonly(settings),
updateSetting,
resetSettings,
resetSetting,
defaults: DEFAULT_SETTINGS,
}
}
141 changes: 141 additions & 0 deletions design-system/CodeExample.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<template>
<div class="overflow-hidden rounded-lg border border-gray-200 bg-gray-50">
<div class="flex items-center justify-between border-b border-gray-200 bg-gray-100 px-4 py-2">
<span
v-if="title"
class="text-sm font-medium text-gray-600"
>{{ title }}</span>
<span v-else />
<button
type="button"
class="flex items-center gap-1 rounded px-2 py-1 text-xs text-gray-500 hover:bg-gray-200 hover:text-gray-700"
@click="copyCode"
>
<RiFileCopyLine
v-if="!copied"
class="size-4"
/>
<RiCheckLine
v-else
class="size-4 text-green-600"
/>
{{ copied ? 'Copié!' : 'Copier' }}
</button>
</div>
<div class="overflow-x-auto p-4">
<pre class="text-sm leading-relaxed"><code
class="hljs"
v-html="highlightedCode"
/></pre>
</div>
</div>
</template>

<script setup lang="ts">
import { RiCheckLine, RiFileCopyLine } from '@remixicon/vue'

const props = defineProps<{
code: string
title?: string
}>()

const copied = ref(false)
const hljs = ref<typeof import('highlight.js/lib/core').default | null>(null)

const normalizedCode = computed(() => {
const lines = props.code.split('\n')
if (lines[0] === '') lines.shift()
if (lines[lines.length - 1] === '') lines.pop()

const minIndent = lines
.filter(line => line.trim())
.reduce((min, line) => {
const indent = line.match(/^\s*/)?.[0].length ?? 0
return Math.min(min, indent)
}, Infinity)

return lines
.map(line => line.slice(minIndent))
.join('\n')
})

const highlightedCode = computed(() => {
if (!hljs.value) return escapeHtml(normalizedCode.value)
return hljs.value.highlight(normalizedCode.value, { language: 'xml' }).value
})

function escapeHtml(text: string) {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
}

onMounted(async () => {
const hljsModule = (await import('highlight.js/lib/core')).default
const xml = (await import('highlight.js/lib/languages/xml')).default
const javascript = (await import('highlight.js/lib/languages/javascript')).default
const typescript = (await import('highlight.js/lib/languages/typescript')).default

hljsModule.registerLanguage('xml', xml)
hljsModule.registerLanguage('javascript', javascript)
hljsModule.registerLanguage('typescript', typescript)

hljs.value = hljsModule
})

const copyCode = async () => {
await navigator.clipboard.writeText(normalizedCode.value)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
}
</script>

<style>
.hljs {
background: transparent !important;
padding: 0 !important;
}

.hljs-keyword,
.hljs-selector-tag,
.hljs-built_in,
.hljs-name,
.hljs-tag {
color: #d73a49;
}

.hljs-string,
.hljs-title,
.hljs-attr,
.hljs-variable,
.hljs-template-variable {
color: #22863a;
}

.hljs-comment,
.hljs-quote,
.hljs-meta {
color: #6a737d;
font-style: italic;
}

.hljs-number,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #005cc5;
}

.hljs-attribute {
color: #e36209;
}

.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #6f42c1;
}
</style>
5 changes: 5 additions & 0 deletions design-system/ComponentPreview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div class="overflow-hidden rounded-lg border border-gray-200 bg-white p-6">
<slot />
</div>
</template>
Loading
Loading