55 lines
1.8 KiB
Vue
55 lines
1.8 KiB
Vue
<script setup lang="ts">
|
|
import { ref } from 'vue';
|
|
import { useTranslations } from '@/composables/useVanguard';
|
|
|
|
withDefaults(
|
|
defineProps<{
|
|
invalid?: boolean;
|
|
/** Renders the reveal toggle. */
|
|
toggleable?: boolean;
|
|
}>(),
|
|
{ toggleable: true },
|
|
);
|
|
|
|
/**
|
|
* The root is a positioning wrapper, so attributes must be forwarded to the
|
|
* input itself. Without this, id, placeholder and autocomplete would land on
|
|
* the wrapping div and the field's <label for> would associate with nothing.
|
|
*/
|
|
defineOptions({ inheritAttrs: false });
|
|
|
|
const model = defineModel<string>();
|
|
|
|
const { __ } = useTranslations();
|
|
const revealed = ref(false);
|
|
</script>
|
|
|
|
<template>
|
|
<div class="relative">
|
|
<input
|
|
v-bind="$attrs"
|
|
v-model="model"
|
|
:type="revealed ? 'text' : 'password'"
|
|
:aria-invalid="invalid || undefined"
|
|
class="bg-field block w-full rounded-md border py-2 pl-3 text-slate-700 shadow-xs transition-colors placeholder:text-slate-400 focus:outline-none"
|
|
:class="[
|
|
toggleable ? 'pr-10' : 'pr-3',
|
|
invalid
|
|
? 'border-red-500 focus:border-red-500 focus:ring-2 focus:ring-red-100'
|
|
: 'border-slate-300 focus:border-primary-500 focus:ring-2 focus:ring-primary-100',
|
|
]"
|
|
/>
|
|
|
|
<button
|
|
v-if="toggleable"
|
|
type="button"
|
|
class="absolute inset-y-0 right-0 flex w-10 cursor-pointer items-center justify-center text-slate-400 hover:text-slate-600"
|
|
:aria-label="revealed ? __('Hide password') : __('Show password')"
|
|
:aria-pressed="revealed"
|
|
@click="revealed = !revealed"
|
|
>
|
|
<i :class="revealed ? 'fas fa-eye-slash' : 'fas fa-eye'" aria-hidden="true" />
|
|
</button>
|
|
</div>
|
|
</template>
|