refactor(stage-ui): <Alert> <ErrorContainer> (#295)
--------- Co-authored-by: Neko <neko@ayaka.moe>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { ccv3 } from '@proj-airi/ccc'
|
||||
|
||||
import { Alert } from '@proj-airi/stage-ui/components'
|
||||
import { useAiriCardStore } from '@proj-airi/stage-ui/stores'
|
||||
import { InputFile } from '@proj-airi/ui'
|
||||
import { Select } from '@proj-airi/ui/components/form'
|
||||
@@ -255,18 +256,18 @@ function getModuleShortName(id: string, module: 'consciousness' | 'voice') {
|
||||
</div>
|
||||
|
||||
<!-- No search results -->
|
||||
<div
|
||||
<Alert
|
||||
v-if="searchQuery && sortedFilteredCards.length === 0"
|
||||
class="col-span-full flex items-center gap-3 border-2 border-amber-200 rounded-xl bg-amber-50/80 p-4 dark:border-amber-800 dark:bg-amber-900/30"
|
||||
type="warning"
|
||||
class="col-span-full"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.card.no_results') }}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">
|
||||
{{ t('settings.pages.card.try_different_search') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #title>
|
||||
{{ t('settings.pages.card.no_results') }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ t('settings.pages.card.try_different_search') }}
|
||||
</template>
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
|
||||
import { Alert, ErrorContainer, RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
|
||||
import { useConsciousnessStore, useProvidersStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted } from 'vue'
|
||||
@@ -109,30 +109,24 @@ function updateCustomModelName(value: string) {
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div
|
||||
<ErrorContainer
|
||||
v-else-if="activeProviderModelError"
|
||||
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
|
||||
>
|
||||
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
|
||||
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
|
||||
:error="activeProviderModelError"
|
||||
/>
|
||||
|
||||
<!-- No models available -->
|
||||
<div
|
||||
<Alert
|
||||
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
|
||||
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
type="warning"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
|
||||
}}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">{{
|
||||
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #title>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Using the new RadioCardManySelect component -->
|
||||
<template v-else-if="providerModels.length > 0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
|
||||
import { ErrorContainer, LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
|
||||
import { FieldCheckbox, FieldRange, FieldSelect } from '@proj-airi/ui'
|
||||
import { useDevicesList } from '@vueuse/core'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
@@ -432,10 +432,7 @@ onUnmounted(() => {
|
||||
<span class="text-sm">Loading...</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="vadModelError" class="flex items-center gap-2 text-red-600 dark:text-red-400">
|
||||
<div class="text-sm" i-solar:close-circle-bold-duotone />
|
||||
<span class="text-sm">Inference error: {{ vadModelError }}</span>
|
||||
</div>
|
||||
<ErrorContainer v-else-if="vadModelError" title="Inference error" :error="vadModelError" />
|
||||
|
||||
<div v-else-if="isVADModelLoaded" class="flex items-center gap-2 text-green-600 dark:text-green-400">
|
||||
<div class="text-sm" i-solar:check-circle-bold-duotone />
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
Alert,
|
||||
ErrorContainer,
|
||||
RadioCardManySelect,
|
||||
RadioCardSimple,
|
||||
Skeleton,
|
||||
@@ -230,30 +232,24 @@ function updateCustomModelName(value: string) {
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div
|
||||
<ErrorContainer
|
||||
v-else-if="activeProviderModelError"
|
||||
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
|
||||
>
|
||||
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
|
||||
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
|
||||
:error="activeProviderModelError"
|
||||
/>
|
||||
|
||||
<!-- No models available -->
|
||||
<div
|
||||
<Alert
|
||||
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
|
||||
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
type="warning"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
|
||||
}}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">{{
|
||||
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #title>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Using the new RadioCardManySelect component -->
|
||||
<template v-else-if="providerModels.length > 0">
|
||||
@@ -342,29 +338,21 @@ function updateCustomModelName(value: string) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
<ErrorContainer
|
||||
v-else-if="speechProviderError"
|
||||
class="flex items-center gap-3 border border-2 border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
|
||||
>
|
||||
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">Error loading voices</span>
|
||||
<span class="text-sm text-red-600 dark:text-red-400">{{ speechProviderError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
title="Error loading voices"
|
||||
:error="speechProviderError"
|
||||
/>
|
||||
|
||||
<!-- No voices available -->
|
||||
<div
|
||||
v-else
|
||||
class="flex items-center gap-3 border border-2 border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">No voices available</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">
|
||||
No voices were found for this provider. You can enter a custom voice name below.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-else type="warning">
|
||||
<template #title>
|
||||
No voices available
|
||||
</template>
|
||||
<template #content>
|
||||
No voices were found for this provider. You can enter a custom voice name below.
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Voice parameters -->
|
||||
<div flex="~ col gap-4">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { ccv3 } from '@proj-airi/ccc'
|
||||
|
||||
import { Alert } from '@proj-airi/stage-ui/components'
|
||||
import { useAiriCardStore } from '@proj-airi/stage-ui/stores'
|
||||
import { InputFile } from '@proj-airi/ui'
|
||||
import { Select } from '@proj-airi/ui/components/form'
|
||||
@@ -255,18 +256,14 @@ function getModuleShortName(id: string, module: 'consciousness' | 'voice') {
|
||||
</div>
|
||||
|
||||
<!-- No search results -->
|
||||
<div
|
||||
v-if="searchQuery && sortedFilteredCards.length === 0"
|
||||
class="col-span-full flex items-center gap-3 border-2 border-amber-200 rounded-xl bg-amber-50/80 p-4 dark:border-amber-800 dark:bg-amber-900/30"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.card.no_results') }}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">
|
||||
{{ t('settings.pages.card.try_different_search') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-if="searchQuery && sortedFilteredCards.length === 0" type="warning">
|
||||
<template #title>
|
||||
{{ t('settings.pages.card.no_results') }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ t('settings.pages.card.try_different_search') }}
|
||||
</template>
|
||||
</Alert>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
|
||||
import { Alert, ErrorContainer, RadioCardManySelect, RadioCardSimple } from '@proj-airi/stage-ui/components'
|
||||
import { useConsciousnessStore, useProvidersStore } from '@proj-airi/stage-ui/stores'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { onMounted } from 'vue'
|
||||
@@ -109,30 +109,24 @@ function updateCustomModelName(value: string) {
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div
|
||||
<ErrorContainer
|
||||
v-else-if="activeProviderModelError"
|
||||
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
|
||||
>
|
||||
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
|
||||
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
|
||||
:error="activeProviderModelError"
|
||||
/>
|
||||
|
||||
<!-- No models available -->
|
||||
<div
|
||||
<Alert
|
||||
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
|
||||
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
type="warning"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
|
||||
}}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">{{
|
||||
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #title>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Using the new RadioCardManySelect component -->
|
||||
<template v-else-if="providerModels.length > 0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
|
||||
import { ErrorContainer, LevelMeter, ThresholdMeter, TimeSeriesChart } from '@proj-airi/stage-ui/components'
|
||||
import { FieldCheckbox, FieldRange, FieldSelect } from '@proj-airi/ui'
|
||||
import { useDevicesList } from '@vueuse/core'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
@@ -430,10 +430,11 @@ onUnmounted(() => {
|
||||
<span class="text-sm">Loading...</span>
|
||||
</div>
|
||||
|
||||
<div v-else-if="vadModelError" class="flex items-center gap-2 text-red-600 dark:text-red-400">
|
||||
<div class="text-sm" i-solar:close-circle-bold-duotone />
|
||||
<span class="text-sm">Inference error: {{ vadModelError }}</span>
|
||||
</div>
|
||||
<ErrorContainer
|
||||
v-else-if="vadModelError"
|
||||
title="Inference error"
|
||||
:error="vadModelError"
|
||||
/>
|
||||
|
||||
<div v-else-if="isVADModelLoaded" class="flex items-center gap-2 text-green-600 dark:text-green-400">
|
||||
<div class="text-sm" i-solar:check-circle-bold-duotone />
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
import type { SpeechProviderWithExtraOptions } from '@xsai-ext/shared-providers'
|
||||
|
||||
import {
|
||||
Alert,
|
||||
ErrorContainer,
|
||||
RadioCardManySelect,
|
||||
RadioCardSimple,
|
||||
Skeleton,
|
||||
@@ -230,30 +232,21 @@ function updateCustomModelName(value: string) {
|
||||
</div>
|
||||
|
||||
<!-- Error state -->
|
||||
<div
|
||||
<ErrorContainer
|
||||
v-else-if="activeProviderModelError"
|
||||
class="flex items-center gap-3 border border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
|
||||
>
|
||||
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error') }}</span>
|
||||
<span class="text-sm text-red-600 dark:text-red-400">{{ activeProviderModelError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
:title="t('settings.pages.modules.consciousness.sections.section.provider-model-selection.error')"
|
||||
:error="activeProviderModelError"
|
||||
/>
|
||||
|
||||
<!-- No models available -->
|
||||
<div
|
||||
v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels"
|
||||
class="flex items-center gap-3 border border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models')
|
||||
}}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">{{
|
||||
t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-else-if="providerModels.length === 0 && !isLoadingActiveProviderModels" type="warning">
|
||||
<template #title>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models') }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ t('settings.pages.modules.consciousness.sections.section.provider-model-selection.no_models_description') }}
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Using the new RadioCardManySelect component -->
|
||||
<template v-else-if="providerModels.length > 0">
|
||||
@@ -342,29 +335,27 @@ function updateCustomModelName(value: string) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
<ErrorContainer
|
||||
v-else-if="speechProviderError"
|
||||
class="flex items-center gap-3 border border-2 border-red-200 rounded-lg bg-red-50 p-4 dark:border-red-800 dark:bg-red-900/20"
|
||||
>
|
||||
<div i-solar:close-circle-line-duotone class="text-2xl text-red-500 dark:text-red-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">Error loading voices</span>
|
||||
<span class="text-sm text-red-600 dark:text-red-400">{{ speechProviderError }}</span>
|
||||
</div>
|
||||
</div>
|
||||
class="mb-2"
|
||||
title="Error loading voices"
|
||||
:error="speechProviderError"
|
||||
/>
|
||||
|
||||
<!-- No voices available -->
|
||||
<div
|
||||
<Alert
|
||||
v-else
|
||||
class="flex items-center gap-3 border border-2 border-amber-200 rounded-lg bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
type="warning"
|
||||
icon="i-solar:info-circle-line-duotone"
|
||||
class="mb-2"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">No voices available</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">
|
||||
No voices were found for this provider. You can enter a custom voice name below.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<template #title>
|
||||
No voices available
|
||||
</template>
|
||||
<template #content>
|
||||
No voices were found for this provider. You can enter a custom voice name below.
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Voice parameters -->
|
||||
<div flex="~ col gap-4">
|
||||
|
||||
@@ -320,7 +320,7 @@ pages:
|
||||
label: Account ID
|
||||
placeholder: Your Cloudflare Account ID
|
||||
api-key:
|
||||
placeholder: Input Cloudflare Account ID
|
||||
placeholder: Input Cloudflare API Key
|
||||
title: Cloudflare Workers AI
|
||||
common:
|
||||
fields:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import Alert from '../Misc/Alert.vue'
|
||||
import RadioCardDetail from './RadioCardDetail.vue'
|
||||
|
||||
interface Item {
|
||||
@@ -91,18 +92,14 @@ function updateCustomValue(value: string) {
|
||||
</div>
|
||||
|
||||
<!-- No search results -->
|
||||
<div
|
||||
v-if="searchQuery && filteredItems.length === 0"
|
||||
class="flex items-center gap-3 border border-2 border-amber-200 rounded-xl bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ searchNoResultsTitle }}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">
|
||||
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-if="searchQuery && filteredItems.length === 0" type="warning">
|
||||
<template #title>
|
||||
{{ searchNoResultsTitle }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Items grid -->
|
||||
<div class="relative">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, ref, watch } from 'vue'
|
||||
|
||||
import Alert from '../Misc/Alert.vue'
|
||||
import VoiceCard from './VoiceCard.vue'
|
||||
|
||||
interface VoiceLanguage {
|
||||
@@ -322,18 +323,14 @@ const customVoiceName = ref('')
|
||||
</div>
|
||||
|
||||
<!-- No search results -->
|
||||
<div
|
||||
v-if="searchQuery && filteredVoices.length === 0"
|
||||
class="flex items-center gap-3 border border-2 border-amber-200 rounded-xl bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20"
|
||||
>
|
||||
<div i-solar:info-circle-line-duotone class="text-2xl text-amber-500 dark:text-amber-400" />
|
||||
<div class="flex flex-col">
|
||||
<span class="font-medium">{{ searchNoResultsTitle }}</span>
|
||||
<span class="text-sm text-amber-600 dark:text-amber-400">
|
||||
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Alert v-if="searchQuery && filteredVoices.length === 0" type="warning">
|
||||
<template #title>
|
||||
{{ searchNoResultsTitle }}
|
||||
</template>
|
||||
<template #content>
|
||||
{{ searchNoResultsDescription.replace('{query}', searchQuery) }}
|
||||
</template>
|
||||
</Alert>
|
||||
|
||||
<!-- Voices grid -->
|
||||
<div class="relative">
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
type?: 'error' | 'warning'
|
||||
}>()
|
||||
|
||||
const containerClass = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'error':
|
||||
return 'border-red-200 bg-red-50 dark:border-red-800 dark:bg-red-900/20'
|
||||
case 'warning':
|
||||
return 'border-amber-200 bg-amber-50 dark:border-amber-800 dark:bg-amber-900/20'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
|
||||
const iconClass = computed(() => {
|
||||
switch (props.type) {
|
||||
case 'error':
|
||||
return 'i-solar:close-circle-line-duotone text-red-500 dark:text-red-400'
|
||||
case 'warning':
|
||||
return 'i-solar:danger-circle-line-duotone text-amber-500 dark:text-amber-400'
|
||||
}
|
||||
return ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col gap-3 border rounded-lg p-4" :class="containerClass">
|
||||
<div class="flex items-center gap-1 font-medium">
|
||||
<div class="text-2xl" :class="iconClass" />
|
||||
<slot name="title" />
|
||||
</div>
|
||||
<div class="text-sm">
|
||||
<slot name="content" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script setup lang="ts">
|
||||
import Alert from './Alert.vue'
|
||||
|
||||
defineProps<{
|
||||
title?: string
|
||||
error?: any
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Alert type="error">
|
||||
<template #title>
|
||||
{{ title }}
|
||||
</template>
|
||||
<template #content>
|
||||
<pre class="whitespace-pre-wrap break-all">{{ String(error) }}</pre>
|
||||
</template>
|
||||
</Alert>
|
||||
</template>
|
||||
@@ -1,6 +1,8 @@
|
||||
export { default as Alert } from './Alert.vue'
|
||||
export { default as BidirectionalTransition } from './BidirectionalTransition.vue'
|
||||
export { default as Button } from './Button.vue'
|
||||
export { default as Collapsable } from './Collapsable.vue'
|
||||
export { default as ErrorContainer } from './ErrorContainer.vue'
|
||||
export { default as Progress } from './Progress.vue'
|
||||
export { default as Screen } from './Screen.vue'
|
||||
export { default as Skeleton } from './Skeleton.vue'
|
||||
|
||||
Reference in New Issue
Block a user