Skip to content

Commit

Permalink
feat(table): allow selecting rows (#343) (#344)
Browse files Browse the repository at this point in the history
close #343 

---------

Co-authored-by: Kia King Ishii <kia.king.08@gmail.com>
  • Loading branch information
brc-dd and kiaking committed Sep 20, 2023
1 parent d3d4893 commit 78b1af0
Show file tree
Hide file tree
Showing 16 changed files with 289 additions and 99 deletions.
3 changes: 1 addition & 2 deletions lib/components/SButton.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { type MaybeRef } from '@vueuse/core'
import { computed, unref, useSlots } from 'vue'
import { type MaybeRef, computed, unref, useSlots } from 'vue'
import { type Position } from '../composables/Tooltip'
import SFragment from './SFragment.vue'
import SIcon from './SIcon.vue'
Expand Down
2 changes: 1 addition & 1 deletion lib/components/SButtonGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</template>

<style scoped lang="postcss">
.SButtonGroup :slotted(.SButton) {
.SButtonGroup :deep(.SButton) {
border-left-width: 0;
border-radius: 0;
Expand Down
3 changes: 1 addition & 2 deletions lib/components/SDropdownSectionFilter.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script setup lang="ts">
import IconCheck from '@iconify-icons/ph/check'
import { type MaybeRef } from '@vueuse/core'
import Fuse from 'fuse.js'
import { computed, onMounted, ref, unref } from 'vue'
import { type MaybeRef, computed, onMounted, ref, unref } from 'vue'
import { type DropdownSectionFilterOption, type DropdownSectionFilterSelectedValue } from '../composables/Dropdown'
import { isArray } from '../support/Utils'
import SDropdownSectionFilterItem from './SDropdownSectionFilterItem.vue'
Expand Down
135 changes: 120 additions & 15 deletions lib/components/STable.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { useVirtualizer } from '@tanstack/vue-virtual'
import { useResizeObserver } from '@vueuse/core'
import xor from 'lodash-es/xor'
import {
computed,
nextTick,
Expand All @@ -11,6 +12,7 @@ import {
watch
} from 'vue'
import { type Table } from '../composables/Table'
import SInputCheckbox from './SInputCheckbox.vue'
import SSpinner from './SSpinner.vue'
import STableCell from './STableCell.vue'
import STableColumn from './STableColumn.vue'
Expand All @@ -20,6 +22,11 @@ import STableItem from './STableItem.vue'
const props = defineProps<{
options: Table
selected?: unknown[]
}>()
const emit = defineEmits<{
(e: 'update:selected', value: unknown[]): void
}>()
const head = shallowRef<HTMLElement | null>(null)
Expand All @@ -28,9 +35,13 @@ const block = shallowRef<HTMLElement | null>(null)
const row = shallowRef<HTMLElement | null>(null)
const ordersToShow = computed(() => {
return unref(props.options.orders).filter((key) => {
const orders = unref(props.options.orders).filter((key) => {
return unref(props.options.columns)[key]?.show !== false
})
if (!props.selected) {
return orders
}
return ['__select', ...orders]
})
watch(() => ordersToShow.value, handleResize)
Expand Down Expand Up @@ -114,10 +125,62 @@ const recordsWithSummary = computed(() => {
return summary ? [...records, summary] : records
})
const indexes = computed(() => {
if (!props.selected) {
return []
}
const records = unref(props.options.records) ?? []
const indexField = unref(props.options.indexField)
return records.map((record, i) => indexField ? record[indexField] : i)
})
const selectedIndexes = reactive(new Set())
const control = computed({
get() {
if (!props.selected) {
return false
}
const selected = indexes.value.filter((index) => {
return selectedIndexes.has(index)
})
updateSelected(selected)
return selected.length === indexes.value.length
? true
: selected.length ? 'indeterminate' : false
},
set(newValue) {
if (newValue === false) {
selectedIndexes.clear()
} else if (newValue === true) {
indexes.value.forEach((index) => {
selectedIndexes.add(index)
})
}
}
})
watch(indexes, (newValue, oldValue) => {
if (!props.selected) {
return
}
xor(newValue, oldValue).forEach((index) => {
selectedIndexes.delete(index)
})
})
const virtualizerOptions = computed(() => ({
count: recordsWithSummary.value.length,
getScrollElement: () => body.value,
estimateSize: () => unref(props.options.rowSize) ?? 41,
estimateSize: (index: number) => {
const rowSize = unref(props.options.rowSize) ?? 40
const borderSize = unref(props.options.borderSize) ?? 1
return lastRow(index) ? rowSize : rowSize + borderSize
},
overscan: 10
}))
Expand Down Expand Up @@ -220,9 +283,18 @@ function lastRow(index: number) {
}
function getCell(key: string, index: number) {
if (key === '__select') {
return { type: 'custom' }
}
const col = unref(props.options.columns)[key]
return (isSummary(index) && col?.summaryCell) ? col?.summaryCell : col?.cell
}
function updateSelected(selected: unknown[]) {
if (xor(selected, props.selected ?? []).length) {
emit('update:selected', selected)
}
}
</script>

<template>
Expand All @@ -233,8 +305,10 @@ function getCell(key: string, index: number) {
:total="unref(options.total)"
:reset="unref(options.reset)"
:menu="unref(options.menu)"
:actions="unref(options.actions)"
:borderless="unref(options.borderless)"
:on-reset="options.onReset"
:selected="selected"
/>

<div class="table" role="grid">
Expand All @@ -251,18 +325,23 @@ function getCell(key: string, index: number) {
v-for="key in ordersToShow"
:key="key"
:name="key"
:class-name="unref(options.columns)[key].className"
:class-name="unref(options.columns)[key]?.className"
:width="colWidths[key]"
>
<STableColumn
:name="key"
:label="unref(options.columns)[key].label"
:class-name="unref(options.columns)[key].className"
:dropdown="unref(options.columns)[key].dropdown"
:label="unref(options.columns)[key]?.label"
:class-name="unref(options.columns)[key]?.className"
:dropdown="unref(options.columns)[key]?.dropdown"
:has-header="showHeader"
:resizable="unref(options.columns)[key].resizable"
:resizable="unref(options.columns)[key]?.resizable"
@resize="(value) => updateColWidth(key, value, true)"
/>
>
<SInputCheckbox
v-if="key === '__select' && unref(options.records)?.length"
v-model="control"
/>
</STableColumn>
</STableItem>
</div>
</div>
Expand Down Expand Up @@ -304,18 +383,24 @@ function getCell(key: string, index: number) {
v-for="key in ordersToShow"
:key="key"
:name="key"
:class-name="unref(options.columns)[key].className"
:class-name="unref(options.columns)[key]?.className"
:width="colWidths[key]"
>
<STableCell
:name="key"
:class="isSummary(index) && 'summary'"
:class-name="unref(options.columns)[key].className"
:class-name="unref(options.columns)[key]?.className"
:cell="getCell(key, index)"
:value="recordsWithSummary[index][key]"
:record="recordsWithSummary[index]"
:records="unref(options.records)!"
/>
>
<SInputCheckbox
v-if="key === '__select' && !isSummary(index)"
:value="selectedIndexes.has(indexes[index])"
@change="c => selectedIndexes[c ? 'add' : 'delete'](indexes[index])"
/>
</STableCell>
</STableItem>
</div>
</div>
Expand Down Expand Up @@ -380,9 +465,10 @@ function getCell(key: string, index: number) {
position: var(--table-head-position, static);
top: var(--table-head-top, auto);
z-index: 100;
border-radius: var(--table-border-radius) var(--table-border-radius) 0 0;
border-radius: calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px) 0 0;
background-color: var(--bg-elv-2);
scrollbar-width: none;
line-height: 0;
&::-webkit-scrollbar {
display: none;
Expand All @@ -394,7 +480,7 @@ function getCell(key: string, index: number) {
}
.container.body {
border-radius: 6px 6px var(--table-border-radius) var(--table-border-radius);
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
line-height: 0;
max-height: var(--table-max-height, 100%);
Expand All @@ -419,18 +505,22 @@ function getCell(key: string, index: number) {
}
.missing {
border-radius: 0 0 6px 6px;
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
padding: 48px 32px;
text-align: center;
background-color: var(--c-bg-elv-3);
line-height: 24px;
font-size: 14px;
font-weight: 500;
color: var(--c-text-3);
.has-footer & {
border-radius: 0;
}
}
.loading {
border-radius: 0 0 6px 6px;
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
padding: 64px 32px;
background-color: var(--c-bg-elv-3);
}
Expand All @@ -446,4 +536,19 @@ function getCell(key: string, index: number) {
height: 48px;
color: var(--c-text-1);
}
.STable .col-__select {
--table-padding-left: 0;
--table-col-width: 48px;
:deep(.input) {
align-items: center;
padding: 0 16px;
min-height: 40px;
}
:deep(.container) {
padding: 0;
}
}
</style>
6 changes: 6 additions & 0 deletions lib/components/STableCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { computed } from 'vue'
import { type TableCell } from '../composables/Table'
import STableCellAvatar from './STableCellAvatar.vue'
import STableCellAvatars from './STableCellAvatars.vue'
import STableCellCustom from './STableCellCustom.vue'
import STableCellDay from './STableCellDay.vue'
import STableCellEmpty from './STableCellEmpty.vue'
import STableCellNumber from './STableCellNumber.vue'
Expand Down Expand Up @@ -98,6 +99,11 @@ const computedCell = computed<TableCell | undefined>(() =>
:avatars="computedCell.avatars"
:color="computedCell.color"
/>
<STableCellCustom
v-else-if="computedCell.type === 'custom'"
>
<slot />
</STableCellCustom>
<STableCellEmpty
v-else-if="computedCell.type === 'empty'"
/>
Expand Down
11 changes: 11 additions & 0 deletions lib/components/STableCellCustom.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<template>
<div class="STableCellCustom">
<slot />
</div>
</template>

<style scoped lang="postcss">
.STableCellCustom {
min-height: 40px;
}
</style>
35 changes: 18 additions & 17 deletions lib/components/STableColumn.vue
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,23 @@ function stopDialogPositionListener() {
<template>
<div class="STableColumn STableCell" :class="classes" ref="column">
<div class="container">
<p class="label">{{ label }}</p>

<div v-if="dropdown" class="action" ref="container">
<button class="button" :class="{ active: buttonActive }" @click="toggle">
<SIcon :icon="IconDotsThree" class="icon" />
</button>

<transition name="fade">
<div v-if="isOpen" class="dialog" :style="{ top, left }" ref="dialog">
<SDropdown :sections="dropdown" />
</div>
</transition>
</div>

<div v-if="resizable" class="grip" @mousedown="grip" />
<slot>
<p class="label">{{ label }}</p>

<div v-if="dropdown" class="action" ref="container">
<button class="button" :class="{ active: buttonActive }" @click="toggle">
<SIcon :icon="IconDotsThree" class="icon" />
</button>

<transition name="fade">
<div v-if="isOpen" class="dialog" :style="{ top, left }" ref="dialog">
<SDropdown :sections="dropdown" />
</div>
</transition>
</div>

<div v-if="resizable" class="grip" @mousedown="grip" />
</slot>
</div>
</div>
</template>
Expand Down Expand Up @@ -239,13 +241,12 @@ function stopDialogPositionListener() {
}
.grip {
position: relative;
position: absolute;
right: -8px;
top: 0px;
bottom: 0px;
width: 16px;
z-index: 1;
position: absolute;
cursor: col-resize;
&::before {
Expand Down
2 changes: 1 addition & 1 deletion lib/components/STableFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ const hasNext = computed(() => {
<style scoped lang="postcss">
.STableFooter {
border-top: 1px solid var(--c-divider-2);
border-radius: 0 0 6px 6px;
border-radius: 0 0 calc(var(--table-border-radius) - 1px) calc(var(--table-border-radius) - 1px);
padding-right: var(--table-padding-right);
padding-left: var(--table-padding-left);
background-color: var(--c-bg-elv-3);
Expand Down

0 comments on commit 78b1af0

Please sign in to comment.