123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- <!-- Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/ -->
- <script setup lang="ts">
- import { computed, toRef } from 'vue'
- import type { Props as CommonLinkProps } from '#shared/components/CommonLink/CommonLink.vue'
- import CommonActionMenu from '#desktop/components/CommonActionMenu/CommonActionMenu.vue'
- import { useTableCheckboxes } from '#desktop/components/CommonTable/composables/useTableCheckboxes.ts'
- import TableCaption from '#desktop/components/CommonTable/TableCaption.vue'
- import TableRow from '#desktop/components/CommonTable/TableRow.vue'
- import { useCellContent } from './composables/useCellContent.ts'
- import type {
- SimpleTableProps,
- TableSimpleHeader,
- TableItem,
- TableItemLinkValue,
- } from './types.ts'
- const props = withDefaults(defineProps<SimpleTableProps>(), {
- showCaption: false,
- })
- defineEmits<{
- 'click-row': [TableItem]
- }>()
- // Styling
- const cellAlignmentClasses = {
- right: 'text-right',
- center: 'text-center',
- left: 'text-left',
- }
- const tableHeaders = computed(() => props.headers)
- const columnSeparatorClasses =
- 'border-r border-neutral-100 dark:border-gray-900'
- const getTooltipText = (item: TableItem, header: TableSimpleHeader) => {
- return header.truncate ? item[header.key] : undefined
- }
- const rowHandlers = computed(() =>
- props.onClickRow
- ? {
- 'click-row': (event: TableItem) => {
- if (props.onClickRow) props.onClickRow(event)
- },
- }
- : {},
- )
- const { getCellContentComponent } = useCellContent()
- const checkedRows = defineModel<Array<TableItem>>('checkedRows', {
- required: false,
- default: (props: SimpleTableProps) =>
- props.items.filter((item) => item.checked), // is not reactive by default and making it reactive causes other issues.
- })
- const {
- hasCheckboxId,
- allCheckboxRowsSelected,
- selectAllRowCheckboxes,
- handleCheckboxUpdate,
- } = useTableCheckboxes(checkedRows, toRef(props, 'items'))
- </script>
- <template>
- <table class="pb-3">
- <TableCaption :show="showCaption">{{ caption }}</TableCaption>
- <thead>
- <tr>
- <th
- v-for="header in tableHeaders"
- :key="header.key"
- class="h-10 p-2.5 text-xs ltr:text-left rtl:text-right"
- :class="[
- header.headerClass,
- header.columnSeparator && columnSeparatorClasses,
- ]"
- >
- <FormKit
- v-if="hasCheckboxColumn && header.key === 'checkbox'"
- name="checkbox-all-rows"
- :aria-label="
- allCheckboxRowsSelected
- ? $t('Deselect all entries')
- : $t('Select all entries')
- "
- type="checkbox"
- :model-value="allCheckboxRowsSelected"
- @update:model-value="selectAllRowCheckboxes"
- />
- <slot v-else :name="`column-header-${header.key}`" :header="header">
- <CommonLabel
- class="-:font-normal -:text-stone-200 -:dark:text-neutral-500"
- :class="[
- cellAlignmentClasses[header.alignContent || 'left'],
- header.labelClass || '',
- ]"
- size="small"
- >
- {{ $t(header.label, ...(header.labelPlaceholder || [])) }}
- </CommonLabel>
- </slot>
- <slot :name="`header-suffix-${header.key}`" :item="header" />
- </th>
- <th v-if="actions" class="h-10 w-0 p-2.5 text-center">
- <CommonLabel
- class="font-normal text-stone-200 dark:text-neutral-500"
- size="small"
- >{{ $t('Actions') }}
- </CommonLabel>
- </th>
- </tr>
- </thead>
- <tbody>
- <TableRow
- v-for="item in items"
- :key="item.id"
- :item="item"
- :is-row-selected="!hasCheckboxColumn && item.id === props.selectedRowId"
- :has-checkbox="hasCheckboxColumn"
- v-on="rowHandlers"
- >
- <template #default="{ isRowSelected }">
- <td
- v-for="header in tableHeaders"
- :key="`${item.id}-${header.key}`"
- class="h-10 p-2.5 text-sm"
- :class="[
- header.columnSeparator && columnSeparatorClasses,
- cellAlignmentClasses[header.alignContent || 'left'],
- {
- 'max-w-32 truncate text-black dark:text-white': header.truncate,
- },
- ]"
- >
- <FormKit
- v-if="hasCheckboxColumn && header.key === 'checkbox'"
- :key="`checkbox-${item.id}-${header.key}`"
- :name="`checkbox-${item.id}`"
- :aria-label="
- hasCheckboxId(item.id)
- ? $t('Deselect this entry')
- : $t('Select this entry')
- "
- type="checkbox"
- alternative-backrgound
- :classes="{
- decorator:
- 'group-active:formkit-checked:border-white group-hover:dark:border-white group-hover:group-active:border-white group-hover:group-active:peer-hover:border-white group-hover:formkit-checked:border-black group-hover:dark:formkit-checked:border-white group-hover:dark:peer-hover:border-white ltr:group-hover:dark:group-hover:peer-hover:formkit-checked:border-white ltr:group-hover:peer-hover:dark:border-white rtl:group-hover:peer-hover:dark:border-white ltr:group-hover:peer-hover:border-black rtl:group-hover:peer-hover:border-black group-hover:border-black',
- decoratorIcon:
- 'group-active:formkit-checked:text-white group-hover:formkit-checked:text-black group-hover:formkit-checked:dark:text-white',
- }"
- :disabled="!!item.disabled"
- :model-value="hasCheckboxId(item.id)"
- @click="handleCheckboxUpdate(item)"
- @keydown.enter="handleCheckboxUpdate(item)"
- @keydown.space="handleCheckboxUpdate(item)"
- />
- <slot
- v-else
- :name="`column-cell-${header.key}`"
- :item="item"
- :is-row-selected="isRowSelected"
- :header="header"
- >
- <CommonLink
- v-if="header.type === 'link'"
- v-tooltip.truncate="getTooltipText(item, header)"
- v-bind="item[header.key] as CommonLinkProps"
- :class="{
- 'ltr:text-black rtl:text-black dark:text-white':
- isRowSelected,
- }"
- class="truncate text-sm hover:no-underline group-hover:text-black group-focus-visible:text-white group-active:text-white group-hover:dark:text-white"
- @click.stop
- @keydown.stop
- >{{ (item[header.key] as TableItemLinkValue).label }}
- </CommonLink>
- <CommonLabel
- v-else
- v-tooltip.truncate="getTooltipText(item, header)"
- class="-:text-gray-100 -:dark:text-neutral-400 inline group-hover:text-black group-focus-visible:text-white group-active:text-white group-hover:dark:text-white"
- :class="[
- {
- 'text-black dark:text-white': isRowSelected,
- },
- ]"
- >
- <template v-if="!item[header.key]">-</template>
- <component
- :is="getCellContentComponent(header.type)"
- v-else
- :value="item[header.key] as string"
- :is-row-selected="isRowSelected"
- />
- </CommonLabel>
- </slot>
- <slot :name="`item-suffix-${header.key}`" :item="item" />
- </td>
- <td v-if="actions" class="h-10 p-2.5 text-center">
- <slot name="actions" v-bind="{ actions, item }">
- <CommonActionMenu
- class="flex items-center justify-center"
- :actions="actions"
- :entity="item"
- button-size="medium"
- />
- </slot>
- </td>
- </template>
- </TableRow>
- </tbody>
- </table>
- </template>
|