113 lines
2.8 KiB
Vue
113 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import { useI18n } from 'vue-i18n';
|
|
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
|
|
|
|
import Button from "~/components/ui/Button.vue"
|
|
import Layout from "~/components/ui/Layout.vue"
|
|
|
|
const { icon, placeholder, ...restProps } = defineProps<{
|
|
icon?: string,
|
|
placeholder?: string,
|
|
password?: true,
|
|
search?: true,
|
|
numeric?: true,
|
|
label?: string,
|
|
}>()
|
|
|
|
// TODO(A11y): Add `inputmode="numeric" pattern="[0-9]*"` to input if model type is number:
|
|
// https://technology.blog.gov.uk/2020/02/24/why-the-gov-uk-design-system-team-changed-the-input-type-for-numbers/
|
|
// const isNumeric = restProps.numeric
|
|
|
|
const showPassword = ref(false)
|
|
onKeyboardShortcut('escape', () => showPassword.value = false)
|
|
|
|
// TODO: Accept fallback $attrs: `const fallthroughAttrs = useAttrs()`
|
|
|
|
// TODO: Implement `copy password` button?
|
|
|
|
const attributes = computed(() => ({
|
|
...(restProps.password && !showPassword.value? {type:'password'} : {}),
|
|
...(restProps.search? {type:'search'} : {}),
|
|
...(restProps.numeric? {type:'numeric'} : {}),
|
|
}))
|
|
|
|
const { t } = useI18n()
|
|
|
|
const model = defineModel<string|number>()
|
|
</script>
|
|
|
|
<template>
|
|
<Layout stack no-gap label
|
|
:class="{ 'has-icon': !!icon }"
|
|
class="funkwhale input"
|
|
>
|
|
<span v-if="$slots['label']" class="label">
|
|
<slot name="label" />
|
|
</span>
|
|
|
|
<span v-if="restProps.label" class="label">
|
|
{{ restProps.label }}
|
|
</span>
|
|
|
|
<input
|
|
v-bind="{...$attrs, ...attributes}"
|
|
v-model="model"
|
|
ref="input"
|
|
:placeholder="placeholder"
|
|
@click.stop
|
|
@blur="showPassword = false"
|
|
/>
|
|
|
|
<!-- Left side icon -->
|
|
|
|
<div v-if="icon" class="prefix">
|
|
<i :class="['bi', icon]" />
|
|
</div>
|
|
|
|
<!-- Search -->
|
|
<div v-if="restProps.search" class="prefix">
|
|
<i class="bi bi-search" />
|
|
</div>
|
|
|
|
<!-- Right side -->
|
|
|
|
<div v-if="$slots['input-right']" class="input-right">
|
|
<span>
|
|
<slot name="input-right" />
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Password -->
|
|
<button
|
|
v-if="restProps.password"
|
|
style="background:transparent; border:none; appearance:none;"
|
|
role="switch"
|
|
type="button"
|
|
@click="showPassword = !showPassword"
|
|
@blur="(e) => { console.log(e.relatedTarget); if (e.relatedTarget && 'value' in e.relatedTarget && e.relatedTarget.value === model) showPassword = showPassword; else showPassword = false; }"
|
|
class="input-right show-password" title="toggle visibility"
|
|
>
|
|
<i class="bi bi-eye" />
|
|
</button>
|
|
|
|
<!-- Search -->
|
|
<Button
|
|
solid primary
|
|
type="submit"
|
|
v-if="restProps.search"
|
|
class="input-right search"
|
|
>
|
|
{{ t('components.Sidebar.link.search') }}
|
|
</Button>
|
|
</Layout>
|
|
</template>
|
|
|
|
<style lang="scss">
|
|
@import './input.scss';
|
|
|
|
input[type=number]::-webkit-inner-spin-button {
|
|
opacity: 1;
|
|
}
|
|
</style>
|