e6a46a9d60
| 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: #55 Co-authored-by: tristan <tristan@yuno.malio.fr> Co-committed-by: tristan <tristan@yuno.malio.fr>
96 lines
2.8 KiB
Vue
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>
|