Commit ce8f2f96 authored by Дмитрий Малюгин's avatar Дмитрий Малюгин 🕓
Browse files

feat: filter for Table and component 'TableHeader'

parent 209112d0
Loading
Loading
Loading
Loading
+12 −2
Original line number Diff line number Diff line
@@ -91,8 +91,9 @@ const tableColumns: ITableColumn[] = [
  },
  {
    name: 'Age',
    type: 'text',
    type: 'number',
    filterable: true,
    sortable: true,
  },
  {
    name: 'Hobbies',
@@ -126,7 +127,7 @@ const tableData = [
      value: 'John',
    },
    {
      value: '25',
      value: '7',
    },
    {
      value: 'Football',
@@ -182,6 +183,15 @@ const tableData = [

<template>
  <h2 class="title gradient-text">Playground</h2>
  <Table
    show-all-lines
    :columns="tableColumns"
    darknessTextColor="500"
    :data="tableData"
    fontSize="36px"
    theme="black"
    stripedRows
  ></Table>
  <Table
    show-all-lines
    :columns="tableColumns"
+0 −1
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ export type TTableColumnType =
  | 'number'
  | 'text'
  | 'date'
  | 'tag'
  | 'select'
  | 'rating'
  | 'progressBar'
+2 −3
Original line number Diff line number Diff line
@@ -22,7 +22,7 @@ const left = ref();
const isOnContainerClick = ref();

const parent = computed(() => props.parentSelector);
const container = ref(document.querySelector(props.parentSelector));
const container: Ref = ref(document.querySelector(props.parentSelector));
if (!container.value) {
  setTimeout(() => {
    container.value = document.querySelector(props.parentSelector);
@@ -37,7 +37,6 @@ watch(
        const clientRect = container.value?.getBoundingClientRect();
        top.value = props.top ?? clientRect.top;
        left.value = props.left ?? clientRect.left;
        console.log('left: ', left.value);
      }

      container.value.addEventListener('pointerdown', (event: Event) => {
@@ -72,7 +71,7 @@ watch(
    @pointerdown.stop=""
    :style="`top: ${top}px; left: ${left}px; opacity: ${active ? 1 : 0}; pointer-events: ${active ? 'auto' : 'none'}; padding: ${padding}`"
  >
    <div :style="`max-width: ${maxWidth}; max-height: ${maxHeight}; overflow: auto; padding-right: 5px`">
    <div :style="`max-width: ${maxWidth}; max-height: ${maxHeight}; overflow: auto`">
      <slot />
      <p v-if="!$slots.default" style="background-color: black; color: white; padding: 10px">Popup</p>
    </div>
+4 −3
Original line number Diff line number Diff line
@@ -84,7 +84,7 @@ export const Simple: Story = {
      },
      {
        name: 'Age',
        type: 'text',
        type: 'number',
      },
      {
        name: 'Hobbies',
@@ -128,7 +128,8 @@ export const Full: Story = {
      },
      {
        name: 'Age',
        type: 'text',
        type: 'number',
        sortable: true,
        filterable: true,
      },
      {
@@ -163,7 +164,7 @@ export const Full: Story = {
          value: 'John',
        },
        {
          value: '25',
          value: '7',
        },
        {
          value: 'Football',
+33 −118
Original line number Diff line number Diff line
@@ -3,15 +3,8 @@ import type { ITableProps } from '@interfaces/componentsProps';
import { computed, ref, watch } from 'vue';
import { convertThemeToColor, convertThemeToSecondaryColor, convertThemeToTextColor } from '@helpers/common';
import type { ITableItem } from '@interfaces/componentsProp';
import FilterIcon from '@stories/icons/Mono/FilterIcon.vue';
import SortDownIcon from '@stories/icons/Mono/SortDownIcon.vue';
import SortUpIcon from '@stories/icons/Mono/SortUpIcon.vue';
import SortVerticalIcon from '@stories/icons/Mono/SortVerticalIcon.vue';
import { calcColumnPadding, calcRows } from '@stories/components/Table/helpers';
import Popup from '@stories/components/Popup/Popup.vue';
import Button from '@stories/components/Button/Button.vue';
import CheckMarkIcon from '@stories/icons/Mono/CheckMarkIcon.vue';
import CrossIcon from '@stories/icons/Mono/CrossIcon.vue';
import { calcGap, calcRows } from '@stories/components/Table/helpers';
import TableHeader from '@stories/components/Table/TableHeader.vue';

const props = withDefaults(defineProps<ITableProps>(), {
  theme: 'white',
@@ -22,28 +15,14 @@ const data = defineModel<ITableItem[][]>('data');

const columns = ref(props.columns);
const sortStateActive = ref<[number, string] | []>([]);
const isFilterPopup = ref<boolean>(false);
const columnToFilter = ref<number>(0);
const isFilterPopup = ref<boolean>(false);
const filterValue = ref<string>('');
const isRegisterSensitive = ref<boolean>(false);

watch(props.columns, () => (columns.value = props.columns));

const initGap = computed(
  () =>
    props.gap ??
    (!props.fontSize || isNaN(+props.fontSize.slice(0, -3))
      ? '5px'
      : parseInt(props.fontSize) < 20
        ? '5px'
        : parseInt(props.fontSize) < 36
          ? '10px'
          : '15px'),
);
const iconSize = computed(() => {
  const twoLetters = props.fontSize.slice(0, -2);
  const threeLetters = props.fontSize.slice(0, -3);
  return !twoLetters || isNaN(+twoLetters) ? (!threeLetters || isNaN(+threeLetters) ? '16' : threeLetters) : twoLetters;
});
const initGap = computed(() => calcGap(props.gap, props.fontSize));
const themeColor = computed(() => convertThemeToColor(props.theme, props.darknessTheme));
const color = computed(() =>
  props.textColor
@@ -52,7 +31,6 @@ const color = computed(() =>
);
const secondaryColor = computed(() => convertThemeToSecondaryColor(props.theme, props.darknessTheme));
const darkCellColor = computed(() => convertThemeToSecondaryColor(props.theme, String(+props.darknessTheme + 300)));

// ['', 'up', 'none', '', 'none', ...]
const sortState = computed<string[]>(() => {
  const result = [];
@@ -61,9 +39,15 @@ const sortState = computed<string[]>(() => {
  }
  return result;
});

const rows = computed<ITableItem[][]>(() =>
  calcRows(data.value!, sortStateActive.value, props.multipleSort, columnToFilter.value, filterValue.value),
  calcRows(
    data.value!,
    sortStateActive.value,
    props.multipleSort,
    props.columns[sortStateActive.value[0] ?? 0].type,
    filterValue.value,
    isRegisterSensitive.value,
  ),
);

const changeColumnSortMode = (index: number) => {
@@ -83,15 +67,10 @@ const setFilter = (column: number) => {
    isFilterPopup.value = !isFilterPopup.value;
  }
  if (columnToFilter.value !== column) {
    filterValue.value = '';
    columnToFilter.value = column;
  }
};
const calcLeft = (selector: string) => {
  const el = document.querySelector(selector);
  const table = document.querySelector('#table')!;
  if (!el) return 0;
  return el.getBoundingClientRect().left - table.getBoundingClientRect().left + +iconSize.value;
};
const cancelFilter = () => {
  filterValue.value = '';
  isFilterPopup.value = false;
@@ -108,69 +87,25 @@ const cancelFilter = () => {
      id="table"
    >
      <thead>
        <tr>
          <th
            :class="{
              leftBorder: showAllLines,
            }"
            v-for="(column, index) of columns"
            :key="column.name"
            class="columnHeader"
            :style="`padding: calc(${initGap} / 2) ${initGap}`"
          >
            <div
              :style="`justify-content: ${center ? 'center' : 'start'}; gap: ${center ? '0' : initGap}; padding: ${calcColumnPadding(column, props.center, initGap)}`"
              class="columnFlex"
            >
              <div class="columnHeader-container">
                <h3>
                  {{ column.name }}
                </h3>
                <button
                  v-if="column.sortable"
                  @click.prevent="changeColumnSortMode(index)"
                  style="min-width: 20px; min-height: 20px"
                >
                  <SortVerticalIcon v-show="sortState[index] === 'none'" :color="color" :size="iconSize" />
                  <SortDownIcon v-show="sortState[index] === 'down'" :color="color" :size="iconSize" />
                  <SortUpIcon v-show="sortState[index] === 'up'" :color="color" :size="iconSize" />
                </button>
                <button
                  v-if="column.filterable"
                  @pointerdown="setFilter(index)"
                  :id="`filter${column.name}`"
                  style="position: relative"
                >
                  <FilterIcon :color="color" :size="iconSize" />
                </button>
              </div>
              <div v-if="!center"></div>
            </div>
          </th>
          <Popup
            v-model:active="isFilterPopup"
            :parentSelector="`#filter${columnToFilter}`"
            buttonMenu
        <TableHeader
          v-model:filterValue="filterValue"
          v-model:isFilterPopup="isFilterPopup"
          v-model:isRegisterSensitive="isRegisterSensitive"
          :columns="columns"
          :sortState="sortState"
          :columnToFilter="columnToFilter"
          :initGap="initGap"
          :theme="theme"
            :top="+iconSize + 10"
            :left="calcLeft(`#filter${columnToFilter}`)"
          >
            <input
              v-model="filterValue"
              type="text"
              class="filterInput"
              :style="`background-color: ${themeColor}; color: ${color}`"
          :themeColor="themeColor"
          :secondaryColor="secondaryColor"
          :color="color"
          :showAllLines="!!showAllLines"
          :center="!!center"
          :fontSize="fontSize"
          @changeColumnSortMode="changeColumnSortMode"
          @setFilter="setFilter"
          @cancelFilter="cancelFilter"
        />
            <section class="filterButtons">
              <Button iconOnly size="small" theme="green" @click.prevent="isFilterPopup = false">
                <CheckMarkIcon color="white" size="20" />
              </Button>
              <Button iconOnly size="small" theme="red" @click.prevent="cancelFilter">
                <CrossIcon color="white" size="20" />
              </Button>
            </section>
          </Popup>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(row, index) of rows" :key="index">
@@ -210,15 +145,6 @@ tr::after {
  height: 1px;
  background-color: v-bind(secondaryColor);
}
.columnFlex {
  display: flex;
  font-weight: bold;
}
.columnHeader-container {
  display: flex;
  align-items: center;
  gap: 10px;
}
.tableLines {
  border-top: 1px solid v-bind(secondaryColor);
  border-right: 1px solid v-bind(secondaryColor);
@@ -229,15 +155,4 @@ tr::after {
.darkRow {
  background-color: v-bind(darkCellColor);
}
.filterInput {
  width: 150px;
  padding: 5px;
  margin-bottom: 5px;
  border: 2px solid #64748b;
  border-radius: 5px;
}
.filterButtons {
  display: flex;
  justify-content: space-between;
}
</style>
Loading