Files
malio-layer-ui/app/components/malio/time/internal/TimeWheel.vue
T
tristan acd531f69e
Release / release (push) Successful in 2m38s
feat: Ajout des composants modal, accordeon, datetime avec selecteur d'heure à la molette (#56)
| 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é

---------

Co-authored-by: THOLOT DECHENE Matthieu <matthieu@yuno.malio.fr>
Co-authored-by: matthieu <matthieu@yuno.malio.fr>
Reviewed-on: #56
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-05-27 12:11:51 +00:00

96 lines
2.8 KiB
Vue

<template>
<div
ref="container"
class="malio-wheel relative h-[160px] w-14 snap-y snap-mandatory overflow-y-scroll"
role="spinbutton"
:tabindex="0"
:aria-label="ariaLabel"
:aria-valuenow="modelValue"
:aria-valuemin="values[0]"
:aria-valuemax="values[values.length - 1]"
:aria-valuetext="pad(modelValue)"
@keydown="onKeydown"
>
<button
v-for="item in buffer"
:key="item.key"
type="button"
data-test="wheel-item"
class="flex h-8 w-full snap-center items-center justify-center leading-none outline-none transition-all"
:class="itemClass(item.flat)"
tabindex="-1"
@click="onItemClick(item.value)"
>
{{ pad(item.value) }}
</button>
</div>
</template>
<script setup lang="ts">
import {computed, ref, watch} from 'vue'
import {useInfiniteWheel} from '../composables/useInfiniteWheel'
import {padSegment} from '../composables/timeFormat'
defineOptions({name: 'MalioTimeWheel', inheritAttrs: false})
const props = defineProps<{
modelValue: number
values: number[]
ariaLabel: string
}>()
const emit = defineEmits<{(e: 'update:modelValue', value: number): void}>()
const ITEM_HEIGHT = 32
const container = ref<HTMLElement | null>(null)
const pad = (value: number) => padSegment(value)
const indexOfValue = (value: number) => Math.max(0, props.values.indexOf(value))
const {centeredIndex, scrollToIndex, onKeydown} = useInfiniteWheel(container, {
length: props.values.length,
itemHeight: ITEM_HEIGHT,
initialIndex: () => indexOfValue(props.modelValue),
onChange: (index) => emit('update:modelValue', props.values[index]),
})
const buffer = computed(() =>
[0, 1, 2].flatMap((copy) =>
props.values.map((value, i) => {
const flat = copy * props.values.length + i
return {value, flat, key: flat}
}),
),
)
// Taille décroissante avec la distance au centre (effet molette iOS).
const itemClass = (flat: number) => {
const distance = Math.abs(flat - (props.values.length + centeredIndex.value))
if (distance === 0) return 'text-[16px] font-medium text-black'
if (distance === 1) return 'text-[14px] text-m-muted'
return 'text-[12px] text-m-muted'
}
const onItemClick = (value: number) => scrollToIndex(indexOfValue(value))
watch(
() => props.modelValue,
(value) => {
if (props.values[centeredIndex.value] !== value) scrollToIndex(indexOfValue(value))
},
)
</script>
<style scoped>
.malio-wheel {
scrollbar-width: none;
/* Estompe les valeurs en haut et en bas (effet molette iOS) pour qu'elles ne
débordent pas visuellement du cadre. */
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 70%, transparent 100%);
mask-image: linear-gradient(to bottom, transparent 0%, #000 30%, #000 70%, transparent 100%);
}
.malio-wheel::-webkit-scrollbar {
display: none;
}
</style>