Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: removed pagination feature and fixed toggling issue when cell data changes in smart table #9

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
227 changes: 90 additions & 137 deletions src/components/smart/Table.vue
@@ -1,130 +1,101 @@
<template>
<div class="flex flex-1 flex-col">
<div v-if="pagination" class="mb-3 flex items-center justify-end">
<HoppButtonSecondary
outline
filled
:icon="IconLeft"
:disabled="page === 1"
@click="changePage(PageDirection.Previous)"
/>

<span class="flex h-full w-10 items-center justify-center">{{
page
}}</span>

<HoppButtonSecondary
outline
filled
:icon="IconRight"
:disabled="page === pagination.totalPages"
@click="changePage(PageDirection.Next)"
/>
</div>

<div class="overflow-auto rounded-md border border-dividerDark shadow-md">
<!-- An Extension Slot to extend the table functionality such as search -->
<slot name="extension"></slot>

<table class="w-full table-fixed">
<thead>
<div class="overflow-auto rounded-md border border-dividerDark shadow-md">
<!-- An Extension Slot to extend the table functionality such as search -->
<slot name="extension"></slot>

<table class="w-full table-fixed">
<thead>
<tr
class="border-b border-dividerDark bg-primaryLight text-left text-sm text-secondary"
>
<th v-if="selectedRows" class="w-24">
<input
ref="selectAllCheckbox"
type="checkbox"
:checked="areAllRowsSelected"
:disabled="loading"
class="flex h-full w-full items-center justify-center"
@click.stop="toggleAllRows"
/>
</th>
<slot name="head">
<th
v-for="th in headings"
:key="th.key"
scope="col"
class="px-6 py-3"
>
{{ th.label ?? th.key }}
</th>
</slot>
</tr>
</thead>

<tbody class="divide-y divide-divider">
<tr v-if="loading">
<slot name="loading-state">
<td :colspan="columnSpan">
<div class="mx-auto my-3 h-5 w-5 text-center">
<HoppSmartSpinner />
</div>
</td>
</slot>
</tr>

<tr v-else-if="!list.length">
<slot name="empty-state">
<td :colspan="columnSpan" class="py-3 text-center">
<p>No data available</p>
</td>
</slot>
</tr>

<template v-else>
<tr
class="border-b border-dividerDark bg-primaryLight text-left text-sm text-secondary"
v-for="(rowData, rowIndex) in workingList"
:key="rowIndex"
class="rounded-xl text-secondaryDark hover:cursor-pointer hover:bg-divider"
:class="{ 'divide-x divide-divider': showYBorder }"
@click="onRowClicked(rowData)"
>
<th v-if="selectedRows" class="w-24">
<td v-if="selectedRows">
<input
ref="selectAllCheckbox"
type="checkbox"
:checked="areAllRowsSelected"
:disabled="loading"
:checked="isRowSelected(rowData)"
class="flex h-full w-full items-center justify-center"
@click.stop="toggleAllRows"
@click.stop="toggleRow(rowData)"
/>
</th>
<slot name="head">
<th
v-for="th in headings"
:key="th.key"
scope="col"
class="px-6 py-3"
</td>
<slot name="body" :row="rowData">
<td
v-for="cellHeading in headings"
:key="cellHeading.key"
class="px-4 py-2"
@click="!cellHeading.preventClick && onRowClicked(rowData)"
>
{{ th.label ?? th.key }}
</th>
</slot>
</tr>
</thead>

<tbody class="divide-y divide-divider">
<tr v-if="loading">
<slot name="loading-state">
<td :colspan="columnSpan">
<div class="mx-auto my-3 h-5 w-5 text-center">
<HoppSmartSpinner />
</div>
</td>
</slot>
</tr>

<tr v-else-if="!list.length">
<slot name="empty-state">
<td :colspan="columnSpan" class="py-3 text-center">
<p>No data available</p>
<!-- Dynamic column slot -->
<slot :name="cellHeading.key" :item="rowData">
<!-- Generic implementation of the column -->
<div class="flex flex-col truncate">
<span class="truncate">
{{ rowData[cellHeading.key] ?? "-" }}
</span>
</div>
</slot>
</td>
</slot>
</tr>

<template v-else>
<tr
v-for="(rowData, rowIndex) in workingList"
:key="rowIndex"
class="rounded-xl text-secondaryDark hover:cursor-pointer hover:bg-divider"
:class="{ 'divide-x divide-divider': showYBorder }"
@click="onRowClicked(rowData)"
>
<td v-if="selectedRows">
<input
type="checkbox"
:checked="isRowSelected(rowData)"
class="flex h-full w-full items-center justify-center"
@click.stop="toggleRow(rowData)"
/>
</td>
<slot name="body" :row="rowData">
<td
v-for="cellHeading in headings"
:key="cellHeading.key"
class="px-4 py-2"
@click="!cellHeading.preventClick && onRowClicked(rowData)"
>
<!-- Dynamic column slot -->
<slot :name="cellHeading.key" :item="rowData">
<!-- Generic implementation of the column -->
<div class="flex flex-col truncate">
<span class="truncate">
{{ rowData[cellHeading.key] ?? "-" }}
</span>
</div>
</slot>
</td>
</slot>
</tr>
</template>
</tbody>
</table>
</div>
</template>
</tbody>
</table>
</div>
</template>

<script lang="ts" setup>
import { useVModel } from "@vueuse/core"
import { isEqual } from "lodash-es"
import { computed, ref, watch } from "vue"

import IconLeft from "~icons/lucide/arrow-left"
import IconRight from "~icons/lucide/arrow-right"

import { computed, ref, watch, watchEffect } from "vue"
import { HoppSmartSpinner } from ".."
import { HoppButtonSecondary } from "../button"

export type CellHeading = {
key: string
Expand All @@ -143,19 +114,16 @@ const props = withDefaults(
/** The headings of the table */
headings?: CellHeading[]

/** Contains the rows selected */
selectedRows?: Item[]

/** Whether to enable sorting */
sort?: {
/** The key to sort the list by */
key: string
direction: Direction
}

/** Whether to enable pagination */
pagination?: {
totalPages: number
}

/** Whether to show a loading spinner */
loading?: boolean
}>(),
Expand All @@ -171,31 +139,8 @@ const emit = defineEmits<{
(event: "onRowClicked", item: Item): void
(event: "update:list", list: Item[]): void
(event: "update:selectedRows", selectedRows: Item[]): void
(event: "pageNumber", page: number): void
}>()

// Pagination functionality
const page = ref(1)

enum PageDirection {
Previous,
Next,
}

const changePage = (direction: PageDirection) => {
const isPrevious = direction === PageDirection.Previous

const isValidPreviousAction = isPrevious && page.value > 1
const isValidNextAction =
!isPrevious && page.value < props.pagination!.totalPages

if (isValidNextAction || isValidPreviousAction) {
page.value += isPrevious ? -1 : 1

emit("pageNumber", page.value)
}
}

// The working version of the list that is used to perform operations upon
const workingList = useVModel(props, "list", emit)

Expand Down Expand Up @@ -259,6 +204,14 @@ const areAllRowsSelected = computed(() => {
})
})

watchEffect(() => {
if (selectedRows.value?.length === 0) {
workingList.value.forEach((item) => {
item.selected = false
})
}
})

// Sort List by key and direction which can set to ascending or descending
export type Direction = "ascending" | "descending"

Expand Down
40 changes: 5 additions & 35 deletions src/stories/Table.story.vue
Expand Up @@ -4,17 +4,14 @@
<HoppSmartTable
:headings="headings"
:loading="loading"
:list="finalList"
:list="list"
:selected-rows="selectedRows"
:pagination="{ totalPages: 2 }"
@page-number="handlePageChange"
>
</HoppSmartTable>
/>
</Variant>

<!-- Custom implementation of the Table -->
<Variant title="Custom">
<HoppSmartTable :loading="loading" :list="finalList">
<HoppSmartTable :loading="loading" :list="list">
<template #head>
<th
v-for="heading in headings"
Expand Down Expand Up @@ -60,7 +57,6 @@
:list="extensionList"
:selected-rows="selectedRows"
:sort="{ key: 'name', direction: sortDirection }"
@page-number="handlePageChange"
>
<template #extension>
<div class="flex">
Expand Down Expand Up @@ -88,7 +84,6 @@

<script setup lang="ts">
import { computed, onMounted, ref, Ref } from "vue"

import { CellHeading, Direction } from "~/components/smart/Table.vue"
import IconArrowUpDown from "~icons/lucide/arrow-up-down"
import { HoppButtonPrimary, HoppSmartInput } from ".."
Expand All @@ -111,10 +106,9 @@ const headings: CellHeading[] = [

const loading = ref(false)

const finalList = ref<List[]>([])
const selectedRows = ref<List[]>([])

const primaryList: List[] = [
const list: List[] = [
{
id: "123456",
name: "Walter",
Expand All @@ -129,39 +123,15 @@ const primaryList: List[] = [
},
]

const secondaryList: List[] = [
{
id: "123457",
name: "Gus",
members: 20,
role: "CEO",
},
{
id: "123458",
name: "Mike",
members: 15,
role: "Security",
},
]

onMounted(async () => {
loading.value = true

// Simulate network call
await new Promise((resolve) => setTimeout(resolve, 1000))

finalList.value = primaryList
loading.value = false
})

const handlePageChange = (pageNumber: number) => {
if (pageNumber === 1) {
finalList.value = primaryList
} else {
finalList.value = secondaryList
}
}

const sortDirection: Ref<Direction> = ref("ascending")

const toggleSortDirection = () => {
Expand All @@ -172,7 +142,7 @@ const toggleSortDirection = () => {
const searchQuery = ref("")

const extensionList = computed(() => {
return primaryList.filter((item) => {
return list.filter((item) => {
return Object.values(item).some((value) =>
value
.toString()
Expand Down