schon/storefront/components/ui/input.vue
Alexandr SaVBaD Waltz 761fecf67f Features: 1) Add useWishlistOverwrite composable for wishlist mutations, including adding, removing, and bulk actions; 2) Introduce new localized UI texts for cart and wishlist operations; 3) Enhance filtering logic with parseAttributesString and route query synchronization;
Fixes: 1) Replace `ElNotification` calls with `useNotification` utility across all authentication and user-related composables; 2) Add missing semicolons in multiple index exports and styled components; 3) Resolve issues with reactivity in `useStore` composable by renaming and restructuring product variables;

Extra: 1) Refactor localized strings and translations for better readability and maintenance; 2) Tweak styles including scoped styles, z-index adjustments, and SCSS mixins; 3) Remove unused components and imports to streamline storefront layout.
2025-07-06 19:49:26 +03:00

146 lines
No EOL
3.1 KiB
Vue

<template>
<div class="block">
<div class="block__inner">
<input
:placeholder="placeholder"
:type="isPasswordVisible"
:value="modelValue"
@input="onInput"
@keydown="numberOnly ? onlyNumbersKeydown($event) : null"
class="block__input"
>
<button
@click.prevent="togglePasswordVisible"
class="block__eyes"
v-if="type === 'password' && String(modelValue).length > 0"
>
<icon v-if="isPasswordVisible === 'password'" name="mdi:eye-off-outline" />
<icon v-else name="mdi:eye-outline" />
</button>
</div>
<p v-if="!isValid" class="block__error">{{ errorMessage }}</p>
</div>
</template>
<script setup lang="ts">
type Rule = (value: string) => boolean | string;
const emit = defineEmits<{
(e: 'update:modelValue', value: string | number): void;
}>();
const props = defineProps<{
type: string,
placeholder: string,
modelValue?: [string, number],
rules?: Rule[],
numberOnly?: boolean
}>();
const isPasswordVisible = ref(props.type);
const isValid = ref(true);
const errorMessage = ref('');
function togglePasswordVisible() {
isPasswordVisible.value =
isPasswordVisible.value === 'password' ? 'text' : 'password';
}
const onlyNumbersKeydown = (event: KeyboardEvent) => {
if (!/^\d$/.test(event.key) &&
!['ArrowLeft', 'ArrowRight', 'Backspace', 'Delete', 'Tab', 'Home', 'End'].includes(event.key)) {
event.preventDefault();
}
};
function onInput(e: Event) {
const target = e.target as HTMLInputElement;
let value = target.value;
if (props.numberOnly) {
const digitsOnly = value.replace(/\D/g, '');
if (digitsOnly !== value) {
target.value = digitsOnly;
value = digitsOnly;
}
}
let valid = true;
errorMessage.value = '';
props.rules?.forEach((rule) => {
const result = rule(value);
if (result !== true) {
valid = false;
errorMessage.value = String(result);
}
})
isValid.value = valid;
emit('update:modelValue', props.numberOnly ? Number(value) : value);
}
</script>
<style lang="scss" scoped>
.block {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
position: relative;
&__inner {
width: 100%;
position: relative;
}
&__input {
width: 100%;
padding: 6px 12px;
border: 1px solid #e0e0e0;
border-radius: $default_border_radius;
background-color: $white;
color: #1f1f1f;
font-size: 12px;
font-weight: 400;
line-height: 20px;
&::placeholder {
color: #2B2B2B;
}
}
&__eyes {
cursor: pointer;
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
background-color: transparent;
display: grid;
place-items: center;
font-size: 18px;
color: #838383;
}
&__error {
color: $error;
font-size: 12px;
font-weight: 500;
animation: fadeInUp 0.3s ease;
@keyframes fadeInUp {
0% {
opacity: 0;
transform: translateY(-50%);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
}
}
</style>