[#MUI-34] Revoir le système de playground (#48)
| 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: #48 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #48.
This commit is contained in:
24
.playground/layouts/default.vue
Normal file
24
.playground/layouts/default.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<template>
|
||||
<div class="flex h-screen">
|
||||
<MalioSidebar :sections="navSections">
|
||||
<template #logo>
|
||||
<NuxtLink to="/">
|
||||
<img src="/LOGO_MALIO.png" alt="Malio">
|
||||
</NuxtLink>
|
||||
</template>
|
||||
<template #logo-collapsed>
|
||||
<NuxtLink to="/">
|
||||
<img src="/LOGO_MALIO_COLLAPSED.png" alt="Malio">
|
||||
</NuxtLink>
|
||||
</template>
|
||||
</MalioSidebar>
|
||||
|
||||
<main class="flex-1 overflow-y-auto p-6">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {navSections} from '../playground.nav'
|
||||
</script>
|
||||
@@ -1,189 +1,10 @@
|
||||
<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 class="mx-auto max-w-2xl py-16 text-center">
|
||||
<h1 class="text-3xl font-bold text-m-text">
|
||||
Playground @malio/layer-ui
|
||||
</h1>
|
||||
<p class="mt-4 text-m-muted">
|
||||
Sélectionne un composant dans la barre latérale pour afficher sa page de démonstration.
|
||||
</p>
|
||||
</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})
|
||||
})
|
||||
|
||||
const componentGroups = 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)),
|
||||
}))
|
||||
|
||||
return [
|
||||
...componentGroups,
|
||||
{
|
||||
category: 'Form',
|
||||
items: [{name: 'client', label: 'Client'}],
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
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>
|
||||
|
||||
63
.playground/playground.nav.ts
Normal file
63
.playground/playground.nav.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import type {SidebarSection} from '../app/components/malio/sidebar/Sidebar.vue'
|
||||
|
||||
export const navSections: SidebarSection[] = [
|
||||
{
|
||||
label: 'BOUTONS',
|
||||
icon: 'mdi:gesture-tap-button',
|
||||
items: [
|
||||
{label: 'Button', to: '/composant/button/button'},
|
||||
{label: 'Button Icon', to: '/composant/button/buttonIcon'},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'CHAMPS',
|
||||
icon: 'mdi:form-textbox',
|
||||
items: [
|
||||
{label: 'Texte', to: '/composant/input/inputText'},
|
||||
{label: 'Nombre', to: '/composant/input/inputNumber'},
|
||||
{label: 'Montant', to: '/composant/input/inputAmount'},
|
||||
{label: 'Email', to: '/composant/input/inputEmail'},
|
||||
{label: 'Mot de passe', to: '/composant/input/inputPassword'},
|
||||
{label: 'Téléphone', to: '/composant/input/inputPhone'},
|
||||
{label: 'Zone de texte', to: '/composant/input/inputTextArea'},
|
||||
{label: 'Saisie assistée', to: '/composant/input/inputAutocomplete'},
|
||||
{label: 'Upload', to: '/composant/input/inputUpload'},
|
||||
{label: 'Éditeur riche', to: '/composant/input/inputRichText'},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'SÉLECTIONS',
|
||||
icon: 'mdi:form-dropdown',
|
||||
items: [
|
||||
{label: 'Select', to: '/composant/select/select'},
|
||||
{label: 'Select Checkbox', to: '/composant/select/selectCheckbox'},
|
||||
{label: 'Checkbox', to: '/composant/checkbox/checkbox'},
|
||||
{label: 'Radio', to: '/composant/radio/radioButton'},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'NAVIGATION',
|
||||
icon: 'mdi:navigation-variant',
|
||||
items: [
|
||||
{label: 'Sidebar', to: '/composant/sidebar/sidebar'},
|
||||
{label: 'Drawer', to: '/composant/drawer/drawer'},
|
||||
{label: 'Onglets', to: '/composant/tab/tabList'},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'DONNÉES',
|
||||
icon: 'mdi:table',
|
||||
items: [
|
||||
{label: 'DataTable', to: '/composant/datatable/datatable'},
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'DIVERS',
|
||||
icon: 'mdi:dots-horizontal',
|
||||
items: [
|
||||
{label: 'Heure', to: '/composant/time/time'},
|
||||
{label: 'Sélecteur de site', to: '/composant/site/siteSelector'},
|
||||
{label: 'Formulaire client', to: '/composant/form/client'},
|
||||
],
|
||||
},
|
||||
]
|
||||
Reference in New Issue
Block a user