FlexDeck
A list with the behaviour of Table (loading, empty and error states, search, pagination) and none of its markup: you draw each row.
Source in the kit: composables/useFlexDeck.ts · components/FlexDeck/
Cards with pagination
examples/flexdeck/Cards.vue Slots #loading-state , #empty-state and #error-state replace the defaults.
0 รายการ จากทั้งหมด 0 รายการ
<template>
<FlexDeck
:options="deck"
container-class="grid gap-3 sm:grid-cols-2 lg:grid-cols-3"
@pageChange="users.fetchPageChange"
@search="users.fetchSearch"
>
<!-- The default slot draws one row; layout is entirely yours through container-class -->
<template #default="{ row }">
<Card>
<div class="flex items-center gap-3">
<Avatar :src="(row as IUser).avatar" />
<div class="min-w-0">
<p class="truncate font-medium">
{{ (row as IUser).name }}
</p>
<p class="truncate text-sm text-gray-500">
{{ (row as IUser).email }}
</p>
</div>
</div>
</Card>
</template>
</FlexDeck>
</template>
<script lang="ts" setup>
import { type IUser, createUsersLoader } from '~/loaders/users'
const users = createUsersLoader()
// useFlexDeck is useTable without columns: same loader, same states, same pagination
const deck = useFlexDeck<IUser>({
repo: users,
options: {
isEnabledSearch: true,
},
})
onMounted(() =>
users.fetchPage(1, '', {
params: {
limit: 6,
},
})
)
</script>loaders/users.ts
import { defineStore } from 'pinia'
// One file per API resource: its types, then one store per loader.
// `defineStore` makes the loader a singleton, so a list page and its edit form share the same state.
export interface IUser {
id: string
name: string
email: string
role: 'admin' | 'editor' | 'viewer'
salary: number
isActive: boolean
avatar: string
createdAt: string
}
export interface IRole {
id: string
name: string
}
// Paginated CRUD: GET /users, GET /users/:id, POST /users, PUT /users/:id, DELETE /users/:id
export const createUsersLoader = () => {
const options = useRequestOptions()
return usePageLoader<IUser>({
baseURL: '/users',
getBaseRequestOptions: options.getMock,
})
}
// The store is the shared instance: a list page and the form that edits it see the same state.
// Call createUsersLoader() instead when a component wants a private one (each docs example does).
export const useUsersLoader = defineStore('users', createUsersLoader)
// A plain array: GET /roles
export const useRolesLoader = defineStore('roles', () => {
const options = useRequestOptions()
return useListLoader<IRole>({
url: '/roles',
getRequestOptions: options.getMock,
})
})
// A single object, or any one-off request: GET /profile
export const useProfileLoader = defineStore('profile', () => {
const options = useRequestOptions()
return useObjectLoader<IUser & { loadedAt: string }>({
method: 'get',
url: '/profile',
getRequestOptions: options.getMock,
})
})Load more
examples/flexdeck/LoadMore.vue The pattern the apps use for feeds and timelines: fetchNextPage() with its own status.
<template>
<div class="space-y-3">
<FlexDeck
:options="deck"
container-class="space-y-2"
>
<template #default="{ row }">
<div class="rounded-md bg-gray-50 px-3 py-2 text-sm">
{{ (row as IUser).name }}
</div>
</template>
</FlexDeck>
<Button
v-if="hasMore"
label="Load more"
color="neutral"
variant="outline"
block
:loading="users.fetch.statusNextPage.isLoading"
@click="users.fetchNextPage()"
/>
</div>
</template>
<script lang="ts" setup>
import { type IUser, createUsersLoader } from '~/loaders/users'
const users = createUsersLoader()
const deck = useFlexDeck<IUser>({
repo: users,
options: {
// The button below replaces the page numbers
isHidePagination: true,
},
})
// fetchNextPage() appends the next page to fetch.items and reports progress in fetch.statusNextPage,
// so the list that is already on screen does not flash back to a loading state
const hasMore = computed(() => users.fetch.options.currentPage < users.fetch.options.totalPage)
onMounted(() =>
users.fetchPage(1, '', {
params: {
limit: 5,
},
})
)
defineExpose({
debug: computed(() => ({
'items.length': users.fetch.items.length,
'currentPage': users.fetch.options.currentPage,
'totalPage': users.fetch.options.totalPage,
'statusNextPage.isLoading': users.fetch.statusNextPage.isLoading,
})),
})
</script>