Files
speckle-server/packages/ui-components/src/components/layout/Table.vue
T
Kristaps Fabians Geikins 4dae1569cd feat(fe2): invite + list workspace invites (#2629)
* list invites table

* invites list works

* update last reminded date on resend

* fix FE

* WIP invitedialog + updated debounced utility

* invite create works

* exclude users correctly

* more adjustments

* minor cleanup

* using workspace invite server role

* test fix

* fixed multiple root eslint issues

* minor adjustments
2024-08-12 11:30:01 +03:00

194 lines
5.4 KiB
Vue

<!-- eslint-disable vuejs-accessibility/no-static-element-interactions -->
<template>
<div class="text-foreground">
<div
class="w-full text-sm overflow-x-auto overflow-y-visible simple-scrollbar border border-outline-3 rounded-lg"
>
<div
class="grid z-10 grid-cols-12 items-center space-x-6 font-medium bg-foundation-page rounded-t-lg w-full border-b border-outline-3 pb-2 pt-4 px-4 min-w-[750px]"
:style="{ paddingRight: paddingRightStyle }"
>
<div
v-for="(column, colIndex) in columns"
:key="column.id"
:class="getHeaderClasses(column.id, colIndex)"
>
{{ column.header }}
</div>
</div>
<div
class="divide-y divide-outline-3 h-full overflow-visible"
:class="{ 'pb-32': overflowCells }"
>
<div
v-if="loading || !items"
tabindex="0"
:style="{ paddingRight: paddingRightStyle }"
:class="rowsWrapperClasses"
>
<div :class="getClasses(undefined, 0, { noPadding: true })" tabindex="0">
<CommonLoadingBar loading />
</div>
</div>
<template v-else-if="items?.length">
<div
v-for="item in items"
:key="item.id"
:style="{ paddingRight: paddingRightStyle }"
:class="rowsWrapperClasses"
tabindex="0"
@click="handleRowClick(item)"
@keypress="handleRowClick(item)"
>
<template v-for="(column, colIndex) in columns" :key="column.id">
<div :class="getClasses(column.id, colIndex)" tabindex="0">
<slot :name="column.id" :item="item">
<div class="text-gray-900 font-medium order-1">Placeholder</div>
</slot>
</div>
</template>
<div class="absolute right-1.5 space-x-1 flex items-center p-0 h-full">
<template v-if="buttons">
<div v-for="button in buttons" :key="button.label">
<FormButton
:icon-left="button.icon"
size="sm"
color="outline"
hide-text
:class="button.class"
:to="isString(button.action) ? button.action : undefined"
@click.stop="!isString(button.action) ? button.action(item) : noop"
/>
</div>
</template>
</div>
</div>
</template>
<div
v-else
tabindex="0"
:style="{ paddingRight: paddingRightStyle }"
:class="rowsWrapperClasses"
>
<div :class="getClasses(undefined, 0)" tabindex="0">
<slot name="empty">
<div class="w-full text-center label-light text-foreground-2 italic">
{{ emptyMessage }}
</div>
</slot>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts" generic="T extends {id: string}, C extends string">
import { noop, isString } from 'lodash'
import { computed } from 'vue'
import type { PropAnyComponent } from '~~/src/helpers/common/components'
import { CommonLoadingBar, FormButton } from '~~/src/lib'
export type TableColumn<I> = {
id: I
header: string
classes: string
}
export type RowButton<T = unknown> = {
icon: PropAnyComponent
label: string
action: (item: T) => unknown
class?: string
}
const props = withDefaults(
defineProps<{
items: T[] | undefined | null
buttons?: RowButton<T>[]
columns: TableColumn<C>[]
overflowCells?: boolean
onRowClick?: (item: T) => void
rowItemsAlign?: 'center' | 'stretch'
emptyMessage?: string
loading?: boolean
}>(),
{ rowItemsAlign: 'center', emptyMessage: 'No data found' }
)
const buttonCount = computed(() => {
return (props.buttons || []).length
})
const paddingRightStyle = computed(() => {
let padding = 16
if (buttonCount.value > 0) {
padding = 48 + (buttonCount.value - 1) * 42
}
return `${padding}px`
})
const rowsWrapperClasses = computed(() => {
const classParts = [
'relative grid grid-cols-12 items-center space-x-6 px-4 py-0.5 min-w-[750px] bg-foundation text-body-xs'
]
if (props.onRowClick && props.items?.length) {
classParts.push('cursor-pointer hover:bg-primary-muted')
}
switch (props.rowItemsAlign) {
case 'center':
classParts.push('items-center')
break
case 'stretch':
classParts.push('items-stretch')
break
}
return classParts.join(' ')
})
const getHeaderClasses = (
column: C | undefined,
colIndex: number,
options?: Partial<{
noPadding: boolean
}>
): string => {
const classParts = [
column ? props.columns.find((c) => c.id === column)?.classes : '' || ''
]
if (!options?.noPadding) {
if (colIndex === 0) {
classParts.push('px-1')
} else {
classParts.push('lg:p-0 px-1')
}
}
return classParts.join(' ')
}
const getClasses = (
column: C | undefined,
colIndex: number,
options?: Partial<{
noPadding: boolean
}>
): string => {
const classParts = [getHeaderClasses(column, colIndex, options)]
if (colIndex === 0) {
classParts.push(`bg-transparent py-2 ${column ? 'pr-5' : 'col-span-full'}`)
} else {
classParts.push(`my-2`)
}
return classParts.join(' ')
}
const handleRowClick = (item: T) => {
props.onRowClick?.(item)
}
</script>