Skip to content

Commit bf51f13

Browse files
Feat/data type highlighting (#17)
* ref: move cell renderers to own components * feat: add data type highlighting
1 parent 1269fea commit bf51f13

13 files changed

+251
-37
lines changed

src/components/VueScreener.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const props = withDefaults(
5959
defaultSortDirection?: 'asc' | 'desc'
6060
columns?: Record<PropertyKey, Partial<Column>>
6161
disableSearchHighlight?: boolean
62+
disableDataTypeHighlight?: boolean
6263
loading?: boolean
6364
title?: string
6465
includeHeader?: boolean
@@ -81,6 +82,7 @@ const internalScreener = computed(
8182
defaultSortDirection: props.defaultSortDirection,
8283
columns: props.columns,
8384
disableSearchHighlight: props.disableSearchHighlight,
85+
disableDataTypeHighlight: props.disableDataTypeHighlight,
8486
loading: props.loading,
8587
}),
8688
)
@@ -95,6 +97,7 @@ watch(
9597
defaultSortDirection: props.defaultSortDirection,
9698
columns: props.columns,
9799
disableSearchHighlight: props.disableSearchHighlight,
100+
disableDataTypeHighlight: props.disableDataTypeHighlight,
98101
loading: props.loading,
99102
}),
100103
(options) => internalScreener.value.actions.setOptions(options),
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<div
3+
:class="[
4+
twMerge(
5+
'vsc-relative vsc-inset-0 vsc-break-words vsc-py-2 vsc-px-2 vsc-text-[#2196f3]',
6+
truncate && 'vsc-whitespace-nowrap vsc-text-ellipsis vsc-overflow-hidden',
7+
props.class,
8+
),
9+
]"
10+
:title="text"
11+
>
12+
<slot>
13+
<span v-html="text" />
14+
<div v-if="isSearchMatch" class="vsc-absolute vsc-inset-0 vsc-bg-yellow-400/5" />
15+
</slot>
16+
</div>
17+
</template>
18+
19+
<script lang="ts" setup>
20+
import { defineProps, HTMLAttributes } from 'vue'
21+
import { twMerge } from '../../utils/tailwind-merge.utils'
22+
23+
const props = defineProps<{
24+
truncate?: boolean
25+
text?: string
26+
isSearchMatch?: boolean
27+
class?: HTMLAttributes['class']
28+
}>()
29+
</script>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<div
3+
:class="[
4+
twMerge(
5+
'vsc-relative vsc-inset-0 vsc-break-words vsc-py-2 vsc-px-2',
6+
truncate && 'vsc-whitespace-nowrap vsc-text-ellipsis vsc-overflow-hidden',
7+
props.class,
8+
),
9+
]"
10+
:title="text"
11+
>
12+
<slot>
13+
<span v-html="text" />
14+
<div v-if="isSearchMatch" class="vsc-absolute vsc-inset-0 vsc-bg-yellow-400/5" />
15+
</slot>
16+
</div>
17+
</template>
18+
19+
<script lang="ts" setup>
20+
import { defineProps, HTMLAttributes } from 'vue'
21+
import { twMerge } from '../../utils/tailwind-merge.utils'
22+
23+
const props = defineProps<{
24+
truncate?: boolean
25+
text?: string
26+
isSearchMatch?: boolean
27+
class?: HTMLAttributes['class']
28+
}>()
29+
</script>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<template>
2+
<div
3+
:class="[
4+
twMerge(
5+
'vsc-inline-flex vsc-items-center vsc-font-bold vsc-whitespace-nowrap vsc-h-8 vsc-gap-2 vsc-text-xs vsc-py-2 vsc-px-2',
6+
props.class,
7+
),
8+
isSortable && twMerge('vsc-inline-flex vsc-items-center vsc-gap-2 vsc-cursor-pointer', props.sortableClass),
9+
Boolean(column.isSortable && getSortDirection(column.field)) && props.sortingClass,
10+
]"
11+
@click="handleClickColumnHeader(column)"
12+
>
13+
<SortIcon v-if="column.isSortable && getSortDirection(column.field)" :direction="getSortDirection(column.field)" />
14+
<slot>{{ text }}</slot>
15+
<Icon v-if="column.info" icon="codicon:info" class="vsc-ml-auto vsc-min-w-3 vsc-min-h-3" v-tooltip="column.info" />
16+
</div>
17+
</template>
18+
19+
<script lang="ts" setup>
20+
import { computed, HTMLAttributes } from 'vue'
21+
import type { IVueScreener, Column } from '../../interfaces/vue-screener'
22+
import SortIcon from '../icons/SortIcon.vue'
23+
import { twMerge } from '../../utils/tailwind-merge.utils'
24+
import { Icon } from '@iconify/vue'
25+
import { vTooltip } from 'floating-vue'
26+
import 'floating-vue/dist/style.css'
27+
28+
const props = defineProps<{
29+
screener: IVueScreener
30+
column: Column
31+
sortableClass?: string
32+
sortingClass?: string
33+
text?: string | number
34+
class?: HTMLAttributes['class']
35+
}>()
36+
37+
const isSortable = computed(() => props.column.isSortable)
38+
39+
const getSortDirection = (field: string | number): 'asc' | 'desc' | null => {
40+
if (props.screener.searchQuery.value.sortField === field) {
41+
return props.screener.searchQuery.value.sortDirection
42+
}
43+
return null
44+
}
45+
46+
const handleClickColumnHeader = (column: Column) => {
47+
if (column.isSortable) {
48+
props.screener.actions.sort(column.field)
49+
}
50+
}
51+
</script>
52+
53+
<style>
54+
.v-popper__popper {
55+
@apply vsc-text-xs;
56+
}
57+
</style>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<div
3+
:class="[
4+
twMerge(
5+
'vsc-relative vsc-inset-0 vsc-break-words vsc-py-2 vsc-px-2 vsc-text-[#d81b60]',
6+
truncate && 'vsc-whitespace-nowrap vsc-text-ellipsis vsc-overflow-hidden',
7+
props.class,
8+
),
9+
]"
10+
:title="text"
11+
>
12+
<slot>
13+
<span v-html="text" />
14+
<div v-if="isSearchMatch" class="vsc-absolute vsc-inset-0 vsc-bg-yellow-400/5" />
15+
</slot>
16+
</div>
17+
</template>
18+
19+
<script lang="ts" setup>
20+
import { defineProps, HTMLAttributes } from 'vue'
21+
import { twMerge } from '../../utils/tailwind-merge.utils'
22+
23+
const props = defineProps<{
24+
truncate?: boolean
25+
text?: string
26+
isSearchMatch?: boolean
27+
class?: HTMLAttributes['class']
28+
}>()
29+
</script>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<template>
2+
<div
3+
:class="[
4+
twMerge(
5+
'vsc-relative vsc-inset-0 vsc-break-words vsc-py-2 vsc-px-2 vsc-text-[#4caf50]',
6+
truncate && 'vsc-whitespace-nowrap vsc-text-ellipsis vsc-overflow-hidden',
7+
props.class,
8+
),
9+
]"
10+
:title="text"
11+
>
12+
<slot>
13+
<span v-html="parsedText" />
14+
<div v-if="isSearchMatch" class="vsc-absolute vsc-inset-0 vsc-bg-yellow-400/5" />
15+
</slot>
16+
</div>
17+
</template>
18+
19+
<script lang="ts" setup>
20+
import { computed, defineProps, HTMLAttributes } from 'vue'
21+
import { twMerge } from '../../utils/tailwind-merge.utils'
22+
23+
const props = defineProps<{
24+
truncate?: boolean
25+
text?: string
26+
isSearchMatch?: boolean
27+
class?: HTMLAttributes['class']
28+
}>()
29+
30+
const parsedText = computed(() => `"${props.text}"`)
31+
</script>

src/components/table/VueScreenerTableCell.vue

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<div
33
:class="[
44
twMerge(
5-
'vsc-border-r vsc-border-zinc-700 vsc-py-2 vsc-px-2 vsc-whitespace-inherit last:vsc-border-r-0 vsc-bg-zinc-800 vsc-break-words vsc-relative',
5+
'vsc-border-r vsc-border-zinc-700 vsc-whitespace-inherit last:vsc-border-r-0 vsc-bg-zinc-800 vsc-relative',
66
column.truncate && 'vsc-whitespace-nowrap vsc-text-ellipsis vsc-overflow-hidden',
77
props.class,
88
),
@@ -12,23 +12,46 @@
1212
:title="column.truncate ? text : ''"
1313
>
1414
<slot>
15-
<span v-html="text" />
16-
<div v-if="isSearchMatch" class="vsc-absolute vsc-inset-0 vsc-bg-yellow-400/5" />
15+
<VueScreenerStringRenderer
16+
v-if="!disableDataTypeHighlight && type === 'string'"
17+
:truncate="column.truncate"
18+
:text="text"
19+
:is-search-match="isSearchMatch"
20+
/>
21+
<VueScreenerNumberRenderer
22+
v-else-if="!disableDataTypeHighlight && type === 'number'"
23+
:truncate="column.truncate"
24+
:text="text"
25+
:is-search-match="isSearchMatch"
26+
/>
27+
<VueScreenerBooleanRenderer
28+
v-else-if="!disableDataTypeHighlight && type === 'boolean'"
29+
:truncate="column.truncate"
30+
:text="text"
31+
:is-search-match="isSearchMatch"
32+
/>
33+
<VueScreenerDefaultRenderer v-else :truncate="column.truncate" :text="text" :is-search-match="isSearchMatch" />
1734
</slot>
1835
</div>
1936
</template>
2037

2138
<script lang="ts" setup>
2239
import { defineProps, HTMLAttributes } from 'vue'
2340
import { twMerge } from '../../utils/tailwind-merge.utils'
24-
import { Column } from '@/interfaces/vue-screener'
41+
import { Column, DataType } from '@/interfaces/vue-screener'
42+
import VueScreenerDefaultRenderer from '../renderers/VueScreenerDefaultRenderer.vue'
43+
import VueScreenerStringRenderer from '../renderers/VueScreenerStringRenderer.vue'
44+
import VueScreenerNumberRenderer from '../renderers/VueScreenerNumberRenderer.vue'
45+
import VueScreenerBooleanRenderer from '../renderers/VueScreenerBooleanRenderer.vue'
2546
2647
const props = defineProps<{
2748
column: Column
2849
pinnedClass?: string
2950
pinnedOverlappingClass?: string
3051
text?: string
52+
type?: DataType
3153
isSearchMatch?: boolean
3254
class?: HTMLAttributes['class']
55+
disableDataTypeHighlight?: boolean
3356
}>()
3457
</script>

src/components/table/VueScreenerTableHeadCell.vue

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,24 @@
99
'vsc-inline-flex vsc-bg-[#1f1f22] vsc-items-center vsc-font-bold vsc-whitespace-nowrap vsc-h-8 vsc-gap-2 vsc-text-xs last:vsc-border-r-0',
1010
props.class,
1111
),
12-
isSortable && twMerge('vsc-inline-flex vsc-items-center vsc-gap-2 vsc-cursor-pointer', props.sortableClass),
13-
Boolean(column.isSortable && getSortDirection(column.field)) && props.sortingClass,
1412
]"
15-
@click="handleClickColumnHeader(column)"
1613
>
17-
<SortIcon v-if="column.isSortable && getSortDirection(column.field)" :direction="getSortDirection(column.field)" />
18-
<slot>{{ text }}</slot>
19-
<Icon v-if="column.info" icon="codicon:info" class="vsc-ml-auto vsc-min-w-3 vsc-min-h-3" v-tooltip="column.info" />
14+
<VueScreenerTableHeadRenderer
15+
:screener="screener"
16+
:column="column"
17+
:sortable-class="sortableClass"
18+
:sorting-class="sortingClass"
19+
:text="text"
20+
/>
2021
</VueScreenerTableCell>
2122
</template>
2223

2324
<script lang="ts" setup>
24-
import { computed, HTMLAttributes } from 'vue'
25+
import { HTMLAttributes } from 'vue'
2526
import type { IVueScreener, Column } from '../../interfaces/vue-screener'
2627
import VueScreenerTableCell from '../table/VueScreenerTableCell.vue'
27-
import SortIcon from '../icons/SortIcon.vue'
28+
import VueScreenerTableHeadRenderer from '../renderers/VueScreenerHeadRenderer.vue'
2829
import { twMerge } from '../../utils/tailwind-merge.utils'
29-
import { Icon } from '@iconify/vue'
30-
import { vTooltip } from 'floating-vue'
31-
import 'floating-vue/dist/style.css'
3230
3331
const props = defineProps<{
3432
screener: IVueScreener
@@ -38,25 +36,4 @@ const props = defineProps<{
3836
text?: string | number
3937
class?: HTMLAttributes['class']
4038
}>()
41-
42-
const isSortable = computed(() => props.column.isSortable)
43-
44-
const getSortDirection = (field: string | number): 'asc' | 'desc' | null => {
45-
if (props.screener.searchQuery.value.sortField === field) {
46-
return props.screener.searchQuery.value.sortDirection
47-
}
48-
return null
49-
}
50-
51-
const handleClickColumnHeader = (column: Column) => {
52-
if (column.isSortable) {
53-
props.screener.actions.sort(column.field)
54-
}
55-
}
5639
</script>
57-
58-
<style>
59-
.v-popper__popper {
60-
@apply vsc-text-xs;
61-
}
62-
</style>

src/components/viewport/states/VueScreenerTableState.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@
3434
:screener="screener"
3535
:column="column"
3636
:row="row"
37+
:type="row.cells[column.field]?.type"
3738
:text="row.cells[column.field]?.htmlValue"
3839
:is-search-match="row.cells[column.field]?.isSearchMatch"
40+
:disable-data-type-highlight="screener.options.value.disableDataTypeHighlight"
3941
/>
4042
</slot>
4143
</VueScreenerTableRow>

src/hooks/use-vue-screener.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const useVueScreener = (inputData?: unknown[], defaultOptions: VueScreene
77
const options = ref<VueScreenerOptions>({
88
contentHeight: defaultOptions.contentHeight,
99
disableSearchHighlight: defaultOptions.disableSearchHighlight ?? false,
10+
disableDataTypeHighlight: defaultOptions.disableDataTypeHighlight ?? false,
1011
loading: defaultOptions.loading ?? false,
1112
defaultCurrentPage: defaultOptions.defaultCurrentPage ?? 1,
1213
defaultRowsPerPage: defaultOptions.defaultRowsPerPage ?? 10,

0 commit comments

Comments
 (0)