Files
timi-web/src/components/description-grid/index.vue
T
2026-08-27 16:08:56 +08:00

134 lines
3.0 KiB
Vue

<template>
<div
class="cw-description-grid"
:class="{
bordered,
'mobile-single': singleColumnOnMobile
}"
:style="gridStyle"
>
<template v-for="item in items" :key="item.key">
<div class="cell label" :class="item.labelClass">
<slot :name="`label-${item.key}`" :item="item">
<span v-text="item.label" />
</slot>
</div>
<div class="cell value" :class="item.valueClass">
<slot :name="`value-${item.key}`" :item="item">
<span v-text="formatValue(item.value)" />
</slot>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import { computed } from "vue";
import type { CSSProperties, PropType } from "vue";
import type { DescriptionGridAlign, DescriptionGridItem } from "./type";
defineOptions({
name: "DescriptionGrid"
});
const props = defineProps({
items: {
type: Array as PropType<DescriptionGridItem[]>,
default: () => []
},
columns: {
type: Number,
default: 2
},
emptyText: {
type: String,
default: "-"
},
bordered: {
type: Boolean,
default: true
},
singleColumnOnMobile: {
type: Boolean,
default: true
},
labelAlign: {
type: String as PropType<DescriptionGridAlign>,
default: "left"
},
valueAlign: {
type: String as PropType<DescriptionGridAlign>,
default: "left"
}
});
const normalizedColumns = computed(() => {
if (!props.columns || props.columns < 1) {
return 1;
}
return Math.floor(props.columns);
});
const gridStyle = computed<CSSProperties>(() => {
return {
"--cw-description-columns": String(normalizedColumns.value),
"--cw-description-grid-template": Array
.from({ length: normalizedColumns.value }, () => "max-content minmax(0, 1fr)")
.join(" "),
"--cw-description-label-align": props.labelAlign,
"--cw-description-value-align": props.valueAlign
} as CSSProperties;
});
function formatValue(value: DescriptionGridItem["value"]): string {
if (value === null || value === undefined || value === "") {
return props.emptyText;
}
return String(value);
}
</script>
<style lang="less" scoped>
.cw-description-grid {
display: grid;
max-width: 100%;
grid-template-columns: var(--cw-description-grid-template);
.cell {
min-width: 0;
padding: 12px 16px;
}
.label {
color: var(--t-color-text-placeholder, var(--td-text-color-placeholder));
text-align: var(--cw-description-label-align);
white-space: nowrap;
background: var(--t-color-bg-container-secondary, var(--td-bg-color-secondarycontainer));
}
.value {
text-align: var(--cw-description-value-align);
word-break: break-all;
background: var(--t-color-bg-container, var(--td-bg-color-container));
}
&.bordered {
border-top: 1px solid var(--t-color-border-1, var(--td-component-border));
border-left: 1px solid var(--t-color-border-1, var(--td-component-border));
.cell {
border-right: 1px solid var(--t-color-border-1, var(--td-component-border));
border-bottom: 1px solid var(--t-color-border-1, var(--td-component-border));
}
}
@media (max-width: 48rem) {
&.mobile-single {
grid-template-columns: max-content minmax(0, max-content);
}
}
}
</style>