Files
modrinth/packages/ui/src/layouts/shared/content-tab/components/managed-content-card/managed-content-card-summary.vue
T
Calum H. 06b7c44dcc refactor: content tab styling (#7091)
* refactor: managed content card

* fix: update filter pill styling to be chips-like

* fix: modpack exporting

* fixed: vanilla server projs + header

* refactor: filters

* refactor: detect external file icons + other metadata inside

* refactor: support json5 metadata

* fix: qa

* fix: qa

* fix: fmt + prepr

* fix: fmt

* fix: qa

* fix: headers

* feat: locking

* fix: clippy

* fix: qa

* fix: frozen loc

* fix: shared instances diff calc break

* fix: env filter + remove categories
2026-08-13 14:55:35 +00:00

102 lines
3.1 KiB
Vue

<script setup lang="ts">
import { BoxIcon, BracesIcon, GlassesIcon, PaintbrushIcon, PlugIcon } from '@modrinth/assets'
import { type Component, computed, onBeforeUnmount, ref, watch } from 'vue'
import BulletDivider from '#ui/components/base/BulletDivider.vue'
import { useFormatNumber } from '#ui/composables/format-number'
import { defineMessages, useVIntl } from '#ui/composables/i18n'
import { formatProjectTypeSentence } from '#ui/utils/common-messages'
import type { ManagedContentSummaryItem, ManagedContentSummaryType } from '../../types'
const props = defineProps<{
summary?: ManagedContentSummaryItem[]
installing?: boolean
}>()
const { formatMessage } = useVIntl()
const formatNumber = useFormatNumber()
const messages = defineMessages({
loading: {
id: 'content.managed-card.summary.loading',
defaultMessage: 'Loading managed content summary',
},
empty: {
id: 'content.managed-card.summary.empty',
defaultMessage: 'No managed content',
},
installing: {
id: 'content.managed-card.summary.installing',
defaultMessage: 'Installing content...',
},
})
const icons: Record<ManagedContentSummaryType, Component> = {
mod: BoxIcon,
plugin: PlugIcon,
datapack: BracesIcon,
resourcepack: PaintbrushIcon,
shader: GlassesIcon,
}
const MINIMUM_LOADING_TIME = 500
const minimumLoadingVisible = ref(false)
let minimumLoadingTimer: ReturnType<typeof setTimeout> | undefined
watch(
() => props.summary === undefined,
(isLoading) => {
if (!isLoading) return
minimumLoadingVisible.value = true
clearTimeout(minimumLoadingTimer)
minimumLoadingTimer = setTimeout(() => {
minimumLoadingVisible.value = false
}, MINIMUM_LOADING_TIME)
},
{ immediate: true },
)
onBeforeUnmount(() => clearTimeout(minimumLoadingTimer))
const showLoading = computed(() => props.summary === undefined || minimumLoadingVisible.value)
const visibleSummary = computed(() => props.summary?.filter((item) => item.count > 0))
</script>
<template>
<div
v-if="!installing && showLoading"
class="flex h-6 items-center gap-2"
role="status"
:aria-label="formatMessage(messages.loading)"
>
<div class="h-5 w-20 animate-pulse rounded bg-surface-4" />
<BulletDivider class="!mx-0" />
<div class="h-5 w-32 animate-pulse rounded bg-surface-4" />
<BulletDivider class="!mx-0" />
<div class="h-5 w-20 animate-pulse rounded bg-surface-4" />
</div>
<span v-else-if="installing" class="font-medium leading-6 text-secondary" role="status">
{{ formatMessage(messages.installing) }}
</span>
<div v-else-if="visibleSummary?.length" class="flex flex-wrap items-center gap-2 text-secondary">
<template v-for="(item, index) in visibleSummary" :key="item.type">
<BulletDivider v-if="index > 0" class="!mx-0" />
<div class="flex items-center gap-1.5 whitespace-nowrap">
<component :is="icons[item.type]" aria-hidden="true" class="size-5 shrink-0" />
<span class="font-medium leading-6">
{{ formatNumber(item.count) }}
{{ formatProjectTypeSentence(formatMessage, item.type, item.count) }}
</span>
</div>
</template>
</div>
<span v-else class="font-medium leading-6 text-secondary">
{{ formatMessage(messages.empty) }}
</span>
</template>