feat(ui): implement autofocus prop on input and link
This commit is contained in:
parent
dcddaaa561
commit
e9a79dfaf8
|
@ -48,12 +48,6 @@ const labels = computed(() => ({
|
||||||
usernamePlaceholder: t('components.auth.LoginForm.placeholder.username')
|
usernamePlaceholder: t('components.auth.LoginForm.placeholder.username')
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const username = ref()
|
|
||||||
onMounted(async () => {
|
|
||||||
await nextTick()
|
|
||||||
username.value?.focus()
|
|
||||||
})
|
|
||||||
|
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const errors = ref([] as string[])
|
const errors = ref([] as string[])
|
||||||
const submit = async () => {
|
const submit = async () => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
|
import onKeyboardShortcut from '~/composables/onKeyboardShortcut';
|
||||||
|
|
||||||
|
@ -13,6 +13,8 @@ const { icon, placeholder, ...restProps } = defineProps<{
|
||||||
search?: true,
|
search?: true,
|
||||||
numeric?: true,
|
numeric?: true,
|
||||||
label?: string,
|
label?: string,
|
||||||
|
|
||||||
|
autofocus? : boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
// TODO(A11y): Add `inputmode="numeric" pattern="[0-9]*"` to input if model type is number:
|
// TODO(A11y): Add `inputmode="numeric" pattern="[0-9]*"` to input if model type is number:
|
||||||
|
@ -34,6 +36,12 @@ const attributes = computed(() => ({
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const input = ref()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (restProps.autofocus) input.value.focus();
|
||||||
|
})
|
||||||
|
|
||||||
const model = defineModel<string|number>()
|
const model = defineModel<string|number>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
import { type RouterLinkProps } from 'vue-router'
|
import { type RouterLinkProps } from 'vue-router'
|
||||||
import { type ColorProps, type DefaultProps, type VariantProps, color, isNoColors } from '~/composables/color';
|
import { type ColorProps, type DefaultProps, type VariantProps, color, isNoColors } from '~/composables/color';
|
||||||
|
@ -13,6 +13,8 @@ const props = defineProps<{
|
||||||
|
|
||||||
icon?: string;
|
icon?: string;
|
||||||
round?: true;
|
round?: true;
|
||||||
|
|
||||||
|
autofocus? : boolean
|
||||||
} & RouterLinkProps
|
} & RouterLinkProps
|
||||||
& (ColorProps | DefaultProps)
|
& (ColorProps | DefaultProps)
|
||||||
& VariantProps
|
& VariantProps
|
||||||
|
@ -26,6 +28,12 @@ const isExternalLink = computed(() =>
|
||||||
const [fontWeight, activeFontWeight] = props.thickWhenActive ? [600, 900] : [400, 400]
|
const [fontWeight, activeFontWeight] = props.thickWhenActive ? [600, 900] : [400, 400]
|
||||||
|
|
||||||
const isIconOnly = computed(() => !!props.icon)
|
const isIconOnly = computed(() => !!props.icon)
|
||||||
|
|
||||||
|
const button = ref()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.autofocus) button.value.focus();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -34,6 +42,7 @@ const isIconOnly = computed(() => !!props.icon)
|
||||||
width(props)(
|
width(props)(
|
||||||
align(props)(
|
align(props)(
|
||||||
)))"
|
)))"
|
||||||
|
ref="button"
|
||||||
:class="[
|
:class="[
|
||||||
$style.link,
|
$style.link,
|
||||||
round && $style['is-round'],
|
round && $style['is-round'],
|
||||||
|
|
|
@ -31,13 +31,13 @@ const styles = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const getStyle = (props : Partial<AlignmentProps>) => ([key, value]: Entry<AlignmentProps>) =>
|
const getStyle = (props : Partial<AlignmentProps>) => ([key, value]: Entry<AlignmentProps>) =>
|
||||||
(trace(`getStyle: key=${key}, value=${value}`),
|
(
|
||||||
typeof styles[key] === 'function' ?
|
typeof styles[key] === 'function' ?
|
||||||
(trace(`getStyle: key=${key}, value=${value}`), styles[key](
|
(styles[key](
|
||||||
// We know that props[key] is a value accepted by styles[key]. The ts compiler is not so smart.
|
// We know that props[key] is a value accepted by styles[key]. The ts compiler is not so smart.
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(key in props && props[key]) ?
|
(key in props && props[key]) ?
|
||||||
props[(trace(props[key]), trace(key))]
|
props[((props[key]), (key))]
|
||||||
: value
|
: value
|
||||||
))
|
))
|
||||||
: styles[key]
|
: styles[key]
|
||||||
|
@ -73,14 +73,14 @@ export const align = <TProps extends Partial<AlignmentProps>>(
|
||||||
props: TProps,
|
props: TProps,
|
||||||
defaults: Partial<AlignmentProps> = {}
|
defaults: Partial<AlignmentProps> = {}
|
||||||
) => merge (
|
) => merge (
|
||||||
trace((Object.entries(props) as Entries<TProps>).reduce(
|
((Object.entries(props) as Entries<TProps>).reduce(
|
||||||
((acc, [key, value]) =>
|
((acc, [key, value]) =>
|
||||||
value && key in styles ?
|
value && key in styles ?
|
||||||
acc.filter(([accKey, _]) => !conflicts.find(set => set.has(accKey) && set.has(key)))
|
acc.filter(([accKey, _]) => !conflicts.find(set => set.has(accKey) && set.has(key)))
|
||||||
.concat([[key, value]])
|
.concat([[key, value]])
|
||||||
: acc
|
: acc
|
||||||
),
|
),
|
||||||
trace(Object.entries(defaults)) as Entries<Partial<AlignmentProps>>
|
(Object.entries(defaults)) as Entries<Partial<AlignmentProps>>
|
||||||
)).map(getStyle(props))
|
)).map(getStyle(props))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,7 @@ const logoUrl = computed(() => store.state.auth.authenticated ? 'library.index'
|
||||||
|
|
||||||
<!-- Sign up, Log in -->
|
<!-- Sign up, Log in -->
|
||||||
<div style="display:contents;" v-if="!store.state.auth.authenticated">
|
<div style="display:contents;" v-if="!store.state.auth.authenticated">
|
||||||
<Layout flex grow no-gap>
|
<Layout flex grow style="--gap:16px;">
|
||||||
<Link :to="{ name: 'login' }"
|
<Link :to="{ name: 'login' }"
|
||||||
solid
|
solid
|
||||||
icon="bi-box-arrow-in-right"
|
icon="bi-box-arrow-in-right"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BackendError } from '~/types'
|
import type { BackendError } from '~/types'
|
||||||
|
|
||||||
import { computed, ref, onMounted } from 'vue'
|
import { computed, ref, onMounted, nextTick } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
@ -44,9 +44,6 @@ const submit = async () => {
|
||||||
|
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailInput = ref()
|
|
||||||
onMounted(() => emailInput.value.focus())
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
Loading…
Reference in New Issue