Loading src/Playground.vue +12 −2 Original line number Diff line number Diff line Loading @@ -91,8 +91,9 @@ const tableColumns: ITableColumn[] = [ }, { name: 'Age', type: 'text', type: 'number', filterable: true, sortable: true, }, { name: 'Hobbies', Loading Loading @@ -126,7 +127,7 @@ const tableData = [ value: 'John', }, { value: '25', value: '7', }, { value: 'Football', Loading Loading @@ -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" Loading src/common/interfaces/componentsProp.ts +0 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ export type TTableColumnType = | 'number' | 'text' | 'date' | 'tag' | 'select' | 'rating' | 'progressBar' Loading src/stories/components/Popup/Popup.vue +2 −3 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) => { Loading Loading @@ -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> Loading src/stories/components/Table/Table.stories.ts +4 −3 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ export const Simple: Story = { }, { name: 'Age', type: 'text', type: 'number', }, { name: 'Hobbies', Loading Loading @@ -128,7 +128,8 @@ export const Full: Story = { }, { name: 'Age', type: 'text', type: 'number', sortable: true, filterable: true, }, { Loading Loading @@ -163,7 +164,7 @@ export const Full: Story = { value: 'John', }, { value: '25', value: '7', }, { value: 'Football', Loading src/stories/components/Table/Table.vue +33 −118 Original line number Diff line number Diff line Loading @@ -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', Loading @@ -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 Loading @@ -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 = []; Loading @@ -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) => { Loading @@ -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; Loading @@ -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"> Loading Loading @@ -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); Loading @@ -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
src/Playground.vue +12 −2 Original line number Diff line number Diff line Loading @@ -91,8 +91,9 @@ const tableColumns: ITableColumn[] = [ }, { name: 'Age', type: 'text', type: 'number', filterable: true, sortable: true, }, { name: 'Hobbies', Loading Loading @@ -126,7 +127,7 @@ const tableData = [ value: 'John', }, { value: '25', value: '7', }, { value: 'Football', Loading Loading @@ -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" Loading
src/common/interfaces/componentsProp.ts +0 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,6 @@ export type TTableColumnType = | 'number' | 'text' | 'date' | 'tag' | 'select' | 'rating' | 'progressBar' Loading
src/stories/components/Popup/Popup.vue +2 −3 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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) => { Loading Loading @@ -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> Loading
src/stories/components/Table/Table.stories.ts +4 −3 Original line number Diff line number Diff line Loading @@ -84,7 +84,7 @@ export const Simple: Story = { }, { name: 'Age', type: 'text', type: 'number', }, { name: 'Hobbies', Loading Loading @@ -128,7 +128,8 @@ export const Full: Story = { }, { name: 'Age', type: 'text', type: 'number', sortable: true, filterable: true, }, { Loading Loading @@ -163,7 +164,7 @@ export const Full: Story = { value: 'John', }, { value: '25', value: '7', }, { value: 'Football', Loading
src/stories/components/Table/Table.vue +33 −118 Original line number Diff line number Diff line Loading @@ -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', Loading @@ -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 Loading @@ -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 = []; Loading @@ -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) => { Loading @@ -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; Loading @@ -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"> Loading Loading @@ -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); Loading @@ -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>