Add paged orders API and infinite scroll, short order numbers, and align the guest invite card with the logged-in hero shell. Co-authored-by: Cursor <cursoragent@cursor.com>
120 lines
2.9 KiB
Vue
120 lines
2.9 KiB
Vue
<!-- Ported from legacy henza_pwa/src/components/input/SingleOtpInput.vue -->
|
|
<template>
|
|
<div class="otp-single-input">
|
|
<input
|
|
:type="inputType"
|
|
:placeholder="placeholder"
|
|
:disabled="disabled"
|
|
ref="input"
|
|
min="0"
|
|
max="9"
|
|
maxlength="1"
|
|
pattern="[0-9]"
|
|
v-model="model"
|
|
:class="inputClasses"
|
|
@input="handleOnChange"
|
|
@keydown="handleOnKeyDown"
|
|
@paste="handleOnPaste"
|
|
@focus="handleOnFocus"
|
|
@blur="handleOnBlur"
|
|
/>
|
|
<span v-if="!isLastChild && separator">
|
|
<span v-html="separator"></span>
|
|
</span>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { onMounted, ref, watch, type Ref } from 'vue'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
inputType?: string
|
|
value?: string | number
|
|
separator?: string
|
|
focus?: boolean
|
|
inputClasses?: string
|
|
shouldAutoFocus?: boolean
|
|
isLastChild?: boolean
|
|
placeholder?: string
|
|
disabled?: boolean
|
|
}>(),
|
|
{ inputType: 'tel' },
|
|
)
|
|
const emit = defineEmits<{
|
|
(e: 'on-change', value: string | number): void
|
|
(e: 'on-keydown', value: KeyboardEvent): void
|
|
(e: 'on-paste', value: Event): void
|
|
(e: 'on-focus'): void
|
|
(e: 'on-blur'): void
|
|
}>()
|
|
const model = ref(props.value || '')
|
|
const input = ref<HTMLInputElement | null>(null) as Ref<HTMLInputElement>
|
|
|
|
const handleOnChange = () => {
|
|
model.value = model.value.toString()
|
|
if (model.value.length > 1) {
|
|
model.value = model.value.slice(0, 1)
|
|
}
|
|
return emit('on-change', model.value)
|
|
}
|
|
|
|
const isCodeNumeric = (charCode: number) => (charCode >= 48 && charCode <= 57) || (charCode >= 96 && charCode <= 105)
|
|
|
|
const handleOnKeyDown = (event: KeyboardEvent) => {
|
|
if (props.disabled) {
|
|
event.preventDefault()
|
|
}
|
|
const keyEvent = event || window.event
|
|
const charCode = keyEvent.which ? keyEvent.which : keyEvent.keyCode
|
|
if (isCodeNumeric(charCode) || [8, 9, 13, 37, 39, 46, 86].includes(charCode)) {
|
|
emit('on-keydown', event)
|
|
} else {
|
|
keyEvent.preventDefault()
|
|
}
|
|
}
|
|
|
|
const handleOnPaste = (event: ClipboardEvent) => emit('on-paste', event)
|
|
|
|
const handleOnFocus = () => {
|
|
input.value.select()
|
|
return emit('on-focus')
|
|
}
|
|
|
|
const handleOnBlur = () => emit('on-blur')
|
|
|
|
watch(
|
|
() => props.value,
|
|
(newValue, oldValue) => {
|
|
if (newValue !== oldValue) {
|
|
model.value = newValue ?? ''
|
|
}
|
|
},
|
|
)
|
|
watch(
|
|
() => props.focus,
|
|
(newFocusValue, oldFocusValue) => {
|
|
if (oldFocusValue !== newFocusValue && input.value && props.focus) {
|
|
input.value.focus()
|
|
input.value.select()
|
|
}
|
|
},
|
|
)
|
|
|
|
// Must be onMounted — onBeforeMount runs before the <input> is in the DOM,
|
|
// so autofocus was a silent no-op and the OTP boxes never grabbed focus.
|
|
onMounted(() => {
|
|
if (input.value && props.focus && props.shouldAutoFocus) {
|
|
input.value.focus()
|
|
input.value.select()
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<style lang="less">
|
|
.otp-single-input {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
</style>
|