Skip to content

Commit

Permalink
refactor: removed pagination feature and fixed toggling issue when ce…
Browse files Browse the repository at this point in the history
…ll data changes in smart table (#9)
  • Loading branch information
JoelJacobStephen committed Mar 14, 2024
1 parent fd3fd11 commit af2b1ae
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 172 deletions.
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

0 comments on commit af2b1ae

Please sign in to comment.