[#MUI-27] Création d'un composant sélection de site (#29)
Composant MalioSiteSelector : bande horizontale pour choisir un site (usine ou lieu) parmi une liste. Tuiles flex proportionnelles, couleur du site sélectionné partagée par toutes les tuiles (opacité 1 / 0.4). Expose update:modelValue (id) + change (objet site complet) pour faciliter les appels API côté consommateur. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> | 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: #29 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
This commit was merged in pull request #29.
This commit is contained in:
104
app/components/malio/site/SiteSelector.vue
Normal file
104
app/components/malio/site/SiteSelector.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<div
|
||||
v-bind="$attrs"
|
||||
:id="componentId"
|
||||
role="radiogroup"
|
||||
:class="mergedGroupClass"
|
||||
>
|
||||
<button
|
||||
v-for="site in sites"
|
||||
:key="site.id"
|
||||
type="button"
|
||||
role="radio"
|
||||
:aria-checked="activeId === site.id"
|
||||
:tabindex="activeId === site.id ? 0 : -1"
|
||||
:style="{
|
||||
backgroundColor: activeColor,
|
||||
opacity: activeId === site.id ? 1 : 0.4,
|
||||
}"
|
||||
:class="mergedTileClass"
|
||||
@click="select(site.id)"
|
||||
>
|
||||
<span :class="mergedLabelClass">{{ site.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed, ref, useId} from 'vue'
|
||||
import {twMerge} from 'tailwind-merge'
|
||||
|
||||
defineOptions({name: 'MalioSiteSelector', inheritAttrs: false})
|
||||
|
||||
type Site = {
|
||||
id: string
|
||||
name: string
|
||||
color: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
sites: Site[]
|
||||
modelValue?: string
|
||||
id?: string
|
||||
groupClass?: string
|
||||
tileClass?: string
|
||||
labelClass?: string
|
||||
}>(), {
|
||||
modelValue: undefined,
|
||||
id: '',
|
||||
groupClass: '',
|
||||
tileClass: '',
|
||||
labelClass: '',
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', value: string): void
|
||||
(e: 'change', site: Site): void
|
||||
}>()
|
||||
|
||||
const generatedId = useId()
|
||||
const componentId = computed(() => props.id || `malio-site-selector-${generatedId}`)
|
||||
|
||||
const isControlled = computed(() => props.modelValue !== undefined)
|
||||
const localValue = ref(props.sites.length > 0 ? props.sites[0]!.id : '')
|
||||
|
||||
const activeId = computed(() =>
|
||||
isControlled.value ? props.modelValue! : localValue.value,
|
||||
)
|
||||
|
||||
const activeColor = computed(() =>
|
||||
props.sites.find((s) => s.id === activeId.value)?.color ?? '',
|
||||
)
|
||||
|
||||
const mergedGroupClass = computed(() =>
|
||||
twMerge(
|
||||
'flex w-full',
|
||||
props.groupClass,
|
||||
),
|
||||
)
|
||||
|
||||
const mergedTileClass = computed(() =>
|
||||
twMerge(
|
||||
'flex-1 cursor-pointer px-6 py-4 text-center transition-opacity focus:outline-none',
|
||||
props.tileClass,
|
||||
),
|
||||
)
|
||||
|
||||
const mergedLabelClass = computed(() =>
|
||||
twMerge(
|
||||
'text-white font-bold uppercase tracking-wide',
|
||||
props.labelClass,
|
||||
),
|
||||
)
|
||||
|
||||
function select(id: string) {
|
||||
const site = props.sites.find((s) => s.id === id)
|
||||
if (!site) return
|
||||
|
||||
if (!isControlled.value) {
|
||||
localValue.value = id
|
||||
}
|
||||
emit('update:modelValue', id)
|
||||
emit('change', site)
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user