mirror of
https://github.com/chenasraf/nextcloud-forum.git
synced 2026-05-17 17:28:02 +00:00
206 lines
4.6 KiB
Vue
206 lines
4.6 KiB
Vue
<template>
|
|
<div class="admin-table">
|
|
<div class="table-scroll-container">
|
|
<div class="table-grid" :style="gridStyle">
|
|
<!-- Header Row -->
|
|
<div class="table-row header-row">
|
|
<div v-for="column in columns" :key="column.key" :class="`col-${column.key}`">
|
|
{{ column.label }}
|
|
</div>
|
|
<div v-if="hasActions" class="col-actions">{{ actionsLabel }}</div>
|
|
</div>
|
|
|
|
<!-- Data Rows -->
|
|
<div
|
|
v-for="row in rows"
|
|
:key="getRowKey(row)"
|
|
class="table-row data-row"
|
|
:class="getRowClass(row)"
|
|
>
|
|
<div v-for="column in columns" :key="column.key" :class="`col-${column.key}`">
|
|
<slot :name="`cell-${column.key}`" :row="row" :value="row[column.key]">
|
|
{{ row[column.key] }}
|
|
</slot>
|
|
</div>
|
|
<div v-if="hasActions" class="col-actions">
|
|
<slot name="actions" :row="row" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, type PropType } from 'vue'
|
|
|
|
export interface TableColumn {
|
|
key: string
|
|
label: string
|
|
minWidth?: string
|
|
maxWidth?: string
|
|
width?: string
|
|
}
|
|
|
|
export default defineComponent({
|
|
name: 'AdminTable',
|
|
props: {
|
|
columns: {
|
|
type: Array as PropType<TableColumn[]>,
|
|
required: true,
|
|
},
|
|
rows: {
|
|
type: Array as PropType<any[]>,
|
|
required: true,
|
|
},
|
|
rowKey: {
|
|
type: String,
|
|
default: 'id',
|
|
},
|
|
hasActions: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
actionsLabel: {
|
|
type: String,
|
|
default: 'Actions',
|
|
},
|
|
actionsWidth: {
|
|
type: String,
|
|
default: '98px',
|
|
},
|
|
rowClass: {
|
|
type: [String, Function] as PropType<
|
|
string | ((row: any) => string | Record<string, boolean>)
|
|
>,
|
|
default: '',
|
|
},
|
|
},
|
|
computed: {
|
|
gridStyle(): { gridTemplateColumns: string } {
|
|
const columnWidths = this.columns.map((col) => {
|
|
if (col.width) {
|
|
return col.width
|
|
}
|
|
const minWidth = col.minWidth || '120px'
|
|
const maxWidth = col.maxWidth || 'auto'
|
|
return `minmax(${minWidth}, ${maxWidth})`
|
|
})
|
|
|
|
if (this.hasActions) {
|
|
columnWidths.push(this.actionsWidth)
|
|
}
|
|
|
|
return {
|
|
gridTemplateColumns: columnWidths.join(' '),
|
|
}
|
|
},
|
|
totalColumns(): number {
|
|
return this.columns.length + (this.hasActions ? 1 : 0)
|
|
},
|
|
},
|
|
methods: {
|
|
getRowKey(row: any): string | number {
|
|
return row[this.rowKey]
|
|
},
|
|
getRowClass(row: any): string | Record<string, boolean> {
|
|
if (typeof this.rowClass === 'function') {
|
|
return this.rowClass(row)
|
|
}
|
|
return this.rowClass
|
|
},
|
|
},
|
|
})
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.admin-table {
|
|
.table-scroll-container {
|
|
overflow-x: auto;
|
|
background: var(--color-border);
|
|
|
|
&::-webkit-scrollbar {
|
|
height: 8px;
|
|
}
|
|
|
|
&::-webkit-scrollbar-track {
|
|
background: var(--color-background-dark);
|
|
}
|
|
|
|
&::-webkit-scrollbar-thumb {
|
|
background: var(--color-text-maxcontrast);
|
|
border-radius: 4px;
|
|
|
|
&:hover {
|
|
background: var(--color-main-text);
|
|
}
|
|
}
|
|
}
|
|
|
|
.table-grid {
|
|
display: grid;
|
|
width: fit-content;
|
|
min-width: 100%;
|
|
|
|
.table-row {
|
|
display: contents;
|
|
|
|
>div {
|
|
padding: 16px;
|
|
background: var(--color-main-background);
|
|
display: flex;
|
|
align-items: center;
|
|
transition: background 0.15s ease;
|
|
border-right: 1px solid var(--color-border);
|
|
border-bottom: 1px solid var(--color-border);
|
|
|
|
&:last-child {
|
|
border-right: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.header-row>div {
|
|
font-weight: 600;
|
|
font-size: 0.85rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--color-text-maxcontrast);
|
|
background: var(--color-background-hover);
|
|
transition: none;
|
|
}
|
|
|
|
.data-row {
|
|
&:hover>div {
|
|
background: var(--color-background-hover);
|
|
}
|
|
|
|
&:last-child>div {
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
|
|
.col-actions {
|
|
justify-content: center;
|
|
position: sticky;
|
|
right: 0;
|
|
z-index: 1;
|
|
box-shadow: -8px 0 12px rgba(0, 0, 0, 0.08);
|
|
|
|
@media (min-width: 1025px) {
|
|
box-shadow: -4px 0 8px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
// Ensure background covers scrolled content
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: inherit;
|
|
z-index: -1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|