feat: add show validation message with debounce

This commit is contained in:
tdgao
2026-08-28 12:54:52 -06:00
parent c8499db174
commit 725b851dcc
4 changed files with 41 additions and 19 deletions
@@ -28,7 +28,7 @@
<script setup lang="ts">
import { LightBulbIcon, TriangleAlertIcon, XCircleIcon } from '@modrinth/assets'
import { type MessageDescriptor, useVIntl } from '@modrinth/ui'
import { computed } from 'vue'
import { computed, onScopeDispose, shallowRef, watch } from 'vue'
interface ValidationCheck {
code?: string
@@ -37,15 +37,46 @@ interface ValidationCheck {
values?: Record<string, unknown>
}
const props = withDefaults(defineProps<{ check?: ValidationCheck | ValidationCheck[] | null }>(), {
check: null,
})
type ValidationCheckInput = ValidationCheck | ValidationCheck[] | null
const props = withDefaults(
defineProps<{
check?: ValidationCheckInput
debounce?: number
}>(),
{
check: null,
debounce: 300,
},
)
const { formatMessage } = useVIntl()
const displayedCheck = shallowRef<ValidationCheckInput>(props.check)
let debounceTimer: ReturnType<typeof setTimeout> | undefined
watch(
() => props.check,
(check) => {
clearTimeout(debounceTimer)
if (props.debounce <= 0) {
displayedCheck.value = check
return
}
debounceTimer = setTimeout(() => {
displayedCheck.value = check
}, props.debounce)
},
)
onScopeDispose(() => clearTimeout(debounceTimer))
const validations = computed(() =>
(Array.isArray(props.check) ? props.check : props.check ? [props.check] : []).filter(
(validation) => validation.severity !== 'valid',
),
(Array.isArray(displayedCheck.value)
? displayedCheck.value
: displayedCheck.value
? [displayedCheck.value]
: []
).filter((validation) => validation.severity !== 'valid'),
)
</script>
@@ -40,7 +40,6 @@
autocomplete="off"
:disabled="hasHitLimit"
@update:model-value="updatedName()"
@blur="nameForValidation = name"
/>
<ValidationMessage :check="nameValidation" />
</div>
@@ -335,8 +334,7 @@ const visibilities = ref<VisibilityOption[]>([
])
const visibility = ref<VisibilityOption>(visibilities.value[0])
const nameForValidation = ref(name.value)
const nameValidation = useProjectTitleValidation(nameForValidation)
const nameValidation = useProjectTitleValidation(name)
const summaryValidation = useProjectSummaryValidation(description, name)
const disableCreate = computed(() => {
@@ -516,7 +514,6 @@ async function createProject() {
async function show(event?: MouseEvent, options?: ShowOptions) {
name.value = ''
nameForValidation.value = name.value
slug.value = ''
description.value = ''
manualSlug.value = false
@@ -57,8 +57,7 @@ const {
const { confirmLeaveModal } = usePageLeaveSafety(hasChanges)
const titleForValidation = ref(current.value.title)
const titleValidation = useProjectTitleValidation(titleForValidation)
const titleValidation = useProjectTitleValidation(() => current.value.title)
const taglineValidation = useProjectSummaryValidation(
() => current.value.tagline,
() => current.value.title,
@@ -88,7 +87,6 @@ async function save() {
function reset() {
resetForm()
titleForValidation.value = current.value.title
}
const messages = defineMessages({
@@ -204,7 +202,6 @@ const placeholder = computed(() => placeholders[placeholderIndex.value] ?? place
autocomplete="off"
:maxlength="50"
wrapper-class="flex-grow"
@blur="titleForValidation = current.title"
/>
</div>
<ValidationMessage :check="titleValidation" class="mt-2" />
@@ -28,7 +28,6 @@
:maxlength="2048"
wrapper-class="w-full max-w-72"
:disabled="!hasPermission"
@blur="nameForValidation = name"
/>
<ValidationMessage :check="nameValidation" class="mt-2" />
</div>
@@ -437,8 +436,7 @@ const hasPermission = computed(() => {
)
})
const nameForValidation = ref(name.value)
const nameValidation = useProjectTitleValidation(nameForValidation)
const nameValidation = useProjectTitleValidation(name)
const summaryValidation = useProjectSummaryValidation(summary, name)
const hasValidationIssues = computed(
() =>
@@ -545,7 +543,6 @@ const { confirmLeaveModal } = usePageLeaveSafety(hasChanges)
function resetChanges() {
name.value = project.value.name
nameForValidation.value = name.value
slug.value = project.value.slug ?? ''
summary.value = project.value.summary
visibility.value = tags.value.approvedStatuses.includes(project.value.status)