| Numéro du ticket | Titre du ticket | |------------------|-----------------| | | | ## Description de la PR ## Modification du .env ## Check list - [ ] Pas de régression - [ ] TU/TI/TF rédigée - [ ] TU/TI/TF OK - [ ] CHANGELOG modifié Reviewed-on: #18 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
182 lines
4.7 KiB
Vue
182 lines
4.7 KiB
Vue
<template>
|
|
<div class="flex min-h-screen">
|
|
<aside class="w-72 bg-m-bg p-6 text-white">
|
|
<button
|
|
type="button"
|
|
class="text-xl text-black font-semibold"
|
|
@click="clearSelection"
|
|
>
|
|
Liste des composants
|
|
</button>
|
|
|
|
<nav class="mt-6 flex flex-col gap-1">
|
|
<div
|
|
v-for="group in groups"
|
|
:key="group.category"
|
|
>
|
|
<button
|
|
type="button"
|
|
class="flex w-full items-center justify-between rounded px-3 py-2 text-left text-black font-bold hover:bg-m-primary/10"
|
|
@click="toggleCategory(group.category)"
|
|
>
|
|
{{ group.category }}
|
|
<span
|
|
class="text-xs transition-transform duration-200"
|
|
:class="openCategories.has(group.category) ? 'rotate-90' : ''"
|
|
>
|
|
▶
|
|
</span>
|
|
</button>
|
|
|
|
<div
|
|
v-if="openCategories.has(group.category)"
|
|
class="ml-3 flex flex-col gap-1 border-l border-gray-300 pl-2"
|
|
>
|
|
<button
|
|
v-for="item in group.items"
|
|
:key="item.name"
|
|
type="button"
|
|
class="rounded px-3 py-1.5 text-left text-sm text-black hover:bg-m-primary hover:text-white"
|
|
:class="selectedName === item.name ? 'bg-m-primary/50 text-white' : ''"
|
|
@click="selectItem(item.name)"
|
|
>
|
|
{{ item.label }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
</aside>
|
|
|
|
<main class="flex-1 p-6">
|
|
<component
|
|
:is="selectedDemoComponent"
|
|
v-if="selectedDemoComponent"
|
|
/>
|
|
<p
|
|
v-else-if="selectedName"
|
|
class="text-gray-700"
|
|
>
|
|
Page de demo introuvable:
|
|
<code>.playground/pages/composant/{{ selectedDemoFileName }}.vue</code>
|
|
</p>
|
|
<div v-else>
|
|
<h1 class="text-2xl font-semibold text-gray-900">
|
|
Playground composants
|
|
</h1>
|
|
<p class="mt-2 text-gray-600">
|
|
Selectionne un composant dans la liste pour afficher sa page de demo.
|
|
</p>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {computed, reactive, ref, watch, shallowRef} from 'vue'
|
|
|
|
type LoadedModule = {
|
|
default: unknown
|
|
}
|
|
|
|
type Item = {
|
|
name: string
|
|
label: string
|
|
}
|
|
|
|
type Group = {
|
|
category: string
|
|
items: Item[]
|
|
}
|
|
|
|
const componentModules = import.meta.glob('../../app/components/malio/**/*.vue')
|
|
const demoModules = import.meta.glob('./composant/**/*.vue')
|
|
|
|
const demoByName: Record<string, () => Promise<LoadedModule>> =
|
|
Object.fromEntries(
|
|
Object.entries(demoModules).map(([file, loader]) => {
|
|
const name = file.split('/').pop()?.replace('.vue', '') ?? ''
|
|
return [name.toLowerCase(), loader as () => Promise<LoadedModule>]
|
|
}),
|
|
)
|
|
|
|
const groups = computed<Group[]>(() => {
|
|
const categoryMap = new Map<string, Item[]>()
|
|
|
|
Object.keys(componentModules).forEach((file) => {
|
|
const parts = file.split('/')
|
|
const name = parts.pop()?.replace('.vue', '') ?? ''
|
|
const category = parts.pop() ?? ''
|
|
|
|
if (!categoryMap.has(category)) {
|
|
categoryMap.set(category, [])
|
|
}
|
|
categoryMap.get(category)!.push({name, label: name})
|
|
})
|
|
|
|
return Array.from(categoryMap.entries())
|
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
.map(([category, items]) => ({
|
|
category: category.charAt(0).toUpperCase() + category.slice(1),
|
|
items: items.sort((a, b) => a.label.localeCompare(b.label)),
|
|
}))
|
|
})
|
|
|
|
const openCategories = reactive(new Set<string>())
|
|
const selectedName = ref('')
|
|
const hasInitializedSelection = ref(false)
|
|
|
|
watch(
|
|
groups,
|
|
(val) => {
|
|
if (!hasInitializedSelection.value && val.length > 0) {
|
|
openCategories.add(val[0].category)
|
|
if (val[0].items.length > 0) {
|
|
selectedName.value = val[0].items[0].name
|
|
}
|
|
hasInitializedSelection.value = true
|
|
}
|
|
},
|
|
{immediate: true},
|
|
)
|
|
|
|
function toggleCategory(category: string) {
|
|
if (openCategories.has(category)) {
|
|
openCategories.delete(category)
|
|
} else {
|
|
openCategories.add(category)
|
|
}
|
|
}
|
|
|
|
function selectItem(name: string) {
|
|
selectedName.value = selectedName.value === name ? '' : name
|
|
}
|
|
|
|
function clearSelection() {
|
|
selectedName.value = ''
|
|
}
|
|
|
|
const selectedDemoComponent = shallowRef<unknown>(null)
|
|
|
|
watch(selectedName, async (name) => {
|
|
if (!name) {
|
|
selectedDemoComponent.value = null
|
|
return
|
|
}
|
|
|
|
const loader = demoByName[name.toLowerCase()]
|
|
if (!loader) {
|
|
selectedDemoComponent.value = null
|
|
return
|
|
}
|
|
|
|
const mod = await loader()
|
|
selectedDemoComponent.value = mod.default
|
|
})
|
|
|
|
const selectedDemoFileName = computed(() => {
|
|
const name = selectedName.value
|
|
if (!name) return ''
|
|
return name.charAt(0).toLowerCase() + name.slice(1)
|
|
})
|
|
</script>
|