mirror of
https://github.com/directus/directus.git
synced 2026-02-15 23:05:02 -05:00
* margin, padding, size, and color tweaks * Remove commented out code * Use pointer-events instead of double click event * Use v-deep for v-sheet in v-dialog * Add newline * Fix stylelint complaint * Fix tests Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
322 lines
6.2 KiB
Vue
322 lines
6.2 KiB
Vue
<template>
|
|
<thead class="table-header" :class="{ dragging }">
|
|
<tr :class="{ fixed }">
|
|
<th v-if="showManualSort" class="manual cell" @click="toggleManualSort" scope="col">
|
|
<v-icon name="sort" class="drag-handle" small />
|
|
</th>
|
|
|
|
<th v-if="showSelect" class="select cell" scope="col">
|
|
<v-checkbox
|
|
:inputValue="allItemsSelected"
|
|
:indeterminate="someItemsSelected"
|
|
@change="toggleSelectAll"
|
|
/>
|
|
</th>
|
|
|
|
<th
|
|
v-for="(header, index) in headers"
|
|
:key="header.value"
|
|
:class="getClassesForHeader(header)"
|
|
class="cell"
|
|
scope="col"
|
|
>
|
|
<div class="content" @click="changeSort(header)">
|
|
<span v-show="header.width > 90 || header.width === null">
|
|
<slot :name="`header.${header.value}`" :header="header">
|
|
{{ header.text }}
|
|
</slot>
|
|
</span>
|
|
<v-icon v-if="header.sortable" name="sort" class="sort-icon" small />
|
|
</div>
|
|
<span
|
|
class="resize-handle"
|
|
v-if="showResize && index !== headers.length - 1"
|
|
@click.stop
|
|
@mousedown="onResizeHandleMouseDown(header, $event)"
|
|
/>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, ref, PropType } from '@vue/composition-api';
|
|
import useEventListener from '@/compositions/use-event-listener';
|
|
import { Header, Sort } from '../types';
|
|
import { throttle, clone } from 'lodash';
|
|
|
|
export default defineComponent({
|
|
props: {
|
|
headers: {
|
|
type: Array as PropType<Header[]>,
|
|
required: true,
|
|
},
|
|
sort: {
|
|
type: Object as PropType<Sort>,
|
|
required: true,
|
|
},
|
|
showSelect: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
showResize: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
showManualSort: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
someItemsSelected: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
allItemsSelected: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
fixed: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
mustSort: {
|
|
type: Boolean,
|
|
default: false,
|
|
},
|
|
},
|
|
setup(props, { emit }) {
|
|
const dragging = ref<boolean>(false);
|
|
const dragStartX = ref<number>(0);
|
|
const dragStartWidth = ref<number>(0);
|
|
const dragHeader = ref<Header>(null);
|
|
|
|
useEventListener(window, 'mousemove', throttle(onMouseMove, 40));
|
|
useEventListener(window, 'mouseup', onMouseUp);
|
|
|
|
return {
|
|
changeSort,
|
|
dragging,
|
|
dragHeader,
|
|
dragStartWidth,
|
|
dragStartX,
|
|
getClassesForHeader,
|
|
onMouseMove,
|
|
onResizeHandleMouseDown,
|
|
toggleManualSort,
|
|
toggleSelectAll,
|
|
};
|
|
|
|
function getClassesForHeader(header: Header) {
|
|
const classes: string[] = [];
|
|
|
|
if (header.align) {
|
|
classes.push(`align-${header.align}`);
|
|
}
|
|
|
|
if (header.sortable) {
|
|
classes.push('sortable');
|
|
}
|
|
|
|
if (props.sort.by === header.value) {
|
|
if (props.sort.desc === false) {
|
|
classes.push('sort-asc');
|
|
} else {
|
|
classes.push('sort-desc');
|
|
}
|
|
}
|
|
|
|
return classes;
|
|
}
|
|
|
|
function changeSort(header: Header) {
|
|
if (header.sortable === false) return;
|
|
if (dragging.value === true) return;
|
|
|
|
if (header.value === props.sort.by) {
|
|
if (props.mustSort) {
|
|
return emit('update:sort', {
|
|
by: props.sort.by,
|
|
desc: !props.sort.desc,
|
|
});
|
|
}
|
|
|
|
if (props.sort.desc === false) {
|
|
return emit('update:sort', {
|
|
by: props.sort.by,
|
|
desc: true,
|
|
});
|
|
}
|
|
|
|
return emit('update:sort', {
|
|
by: null,
|
|
desc: false,
|
|
});
|
|
}
|
|
|
|
return emit('update:sort', {
|
|
by: header.value,
|
|
desc: false,
|
|
});
|
|
}
|
|
|
|
function toggleSelectAll() {
|
|
emit('toggle-select-all', !props.allItemsSelected);
|
|
}
|
|
|
|
function onResizeHandleMouseDown(header: Header, event: MouseEvent) {
|
|
const target = event.target as HTMLDivElement;
|
|
const parent = target.parentElement as HTMLTableHeaderCellElement;
|
|
|
|
dragging.value = true;
|
|
dragStartX.value = event.pageX;
|
|
dragStartWidth.value = parent.offsetWidth;
|
|
dragHeader.value = header;
|
|
}
|
|
|
|
function onMouseMove(event: MouseEvent) {
|
|
if (dragging.value === true) {
|
|
const newWidth = dragStartWidth.value + (event.pageX - dragStartX.value);
|
|
const currentHeaders = clone(props.headers);
|
|
const newHeaders = currentHeaders.map((existing: Header) => {
|
|
if (existing.value === dragHeader.value?.value) {
|
|
return {
|
|
...existing,
|
|
width: Math.max(50, newWidth),
|
|
};
|
|
}
|
|
|
|
return existing;
|
|
});
|
|
emit('update:headers', newHeaders);
|
|
}
|
|
}
|
|
|
|
function onMouseUp() {
|
|
if (dragging.value === true) {
|
|
dragging.value = false;
|
|
}
|
|
}
|
|
|
|
function toggleManualSort() {
|
|
if (props.sort.by === '$manual') {
|
|
emit('update:sort', {
|
|
by: null,
|
|
desc: false,
|
|
});
|
|
} else {
|
|
emit('update:sort', {
|
|
by: '$manual',
|
|
desc: false,
|
|
});
|
|
}
|
|
}
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.table-header {
|
|
.cell {
|
|
position: relative;
|
|
height: 50px; // +2px for bottom border
|
|
padding: 0 12px;
|
|
font-weight: 500;
|
|
font-size: 14px;
|
|
background-color: var(--background-page);
|
|
border-bottom: 2px solid var(--border-subdued);
|
|
|
|
&.select,
|
|
&.sort {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.content {
|
|
display: flex;
|
|
align-items: center;
|
|
height: 100%;
|
|
font-weight: 600;
|
|
|
|
> span {
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
}
|
|
|
|
.sortable {
|
|
cursor: pointer;
|
|
.sort-icon {
|
|
margin-left: 4px;
|
|
color: var(--foreground-subdued);
|
|
transform: scaleY(-1);
|
|
opacity: 0;
|
|
transition: opacity var(--fast) var(--transition);
|
|
}
|
|
|
|
&:hover .sort-icon {
|
|
opacity: 1;
|
|
}
|
|
|
|
&.sort-asc,
|
|
&.sort-desc {
|
|
.sort-icon {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
&.sort-desc {
|
|
.sort-icon {
|
|
transform: scaleY(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
:not(&.dragging) .sortable {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.select,
|
|
.manual {
|
|
padding-right: 0;
|
|
}
|
|
|
|
.fixed th {
|
|
position: sticky;
|
|
top: var(--v-table-sticky-offset-top);
|
|
z-index: 2;
|
|
}
|
|
|
|
.resize-handle {
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
width: 5px;
|
|
height: 100%;
|
|
cursor: ew-resize;
|
|
opacity: 0;
|
|
transition: opacity var(--fast) var(--transition);
|
|
|
|
&::after {
|
|
position: relative;
|
|
top: 20%;
|
|
left: 2px;
|
|
display: block;
|
|
width: 2px;
|
|
height: 60%;
|
|
background-color: var(--border-subdued);
|
|
content: '';
|
|
}
|
|
|
|
&:hover::after {
|
|
background-color: var(--foreground-subdued);
|
|
}
|
|
}
|
|
|
|
th:hover .resize-handle {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
</style>
|