Files
directus/src/components/v-table/table-header/table-header.vue
Ben Haynes 7c0976441d margin, padding, size, and color tweaks (#374)
* 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>
2020-04-09 19:27:07 -04:00

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>