data-grid
Data grid with column icons
Data grid with column icons
Загрузка превью…
src/data-grid/c-data-grid-10.vue
<script setup lang="ts">
/**
* Порт c-data-grid-10.tsx (data-grid с иконками в заголовках колонок).
*
* `IconPlaceholder` в заголовках (User/Mail/MapPinCheckInside) заменён
* инлайновым `<svg>` — пути lucide-react v0.545.0 ("user", "mail",
* "map-pin-check-inside"), переданные в именованный слот `#icon` у
* `DataGridColumnHeader` (docs/PORTING.md §2/§5).
*
* Сетевые аватары (`images.unsplash.com`) и флаги (`flagcdn.com`) заменены
* общей 1x1 `data:` заглушкой.
*/
import { h, ref, type VNode } from "vue"
import { DataGrid, DataGridColumnHeader, DataGridContainer, DataGridPagination, DataGridScrollArea, DataGridTable } from "@/components/reui/data-grid"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import {
getCoreRowModel,
getPaginationRowModel,
getSortedRowModel,
useVueTable,
} from "@tanstack/vue-table"
import type { ColumnDef, PaginationState, SortingState } from "@tanstack/vue-table"
const PIXEL = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="
interface IData {
id: string
name: string
availability: "online" | "away" | "busy" | "offline"
avatar: string
status: "active" | "inactive"
flag: string
email: string
company: string
role: string
joined: string
location: string
balance: number
}
const demoData: IData[] = [
{ id: "1", name: "Alex Johnson", availability: "online", avatar: PIXEL, status: "active", flag: "us", email: "alex@apple.com", company: "Apple", role: "CEO", joined: "Jan, 2024", location: "United States", balance: 5143.03 },
{ id: "2", name: "Sarah Chen", availability: "away", avatar: PIXEL, status: "inactive", flag: "gb", email: "sarah@openai.com", company: "OpenAI", role: "CTO", joined: "Mar, 2023", location: "United Kingdom", balance: 4321.87 },
{ id: "3", name: "Michael Rodriguez", availability: "busy", avatar: PIXEL, status: "active", flag: "ca", email: "michael@meta.com", company: "Meta", role: "Designer", joined: "Jun, 2022", location: "Canada", balance: 7654.98 },
{ id: "4", name: "Emma Wilson", availability: "offline", avatar: PIXEL, status: "inactive", flag: "au", email: "emma@tesla.com", company: "Tesla", role: "Developer", joined: "Sep, 2024", location: "Australia", balance: 3456.45 },
{ id: "5", name: "David Kim", availability: "online", avatar: PIXEL, status: "active", flag: "de", email: "david@sap.com", company: "SAP", role: "Lawyer", joined: "Nov, 2023", location: "Germany", balance: 9876.54 },
{ id: "6", name: "Aron Thompson", availability: "away", avatar: PIXEL, status: "active", flag: "my", email: "aron@keenthemes.com", company: "Keenthemes", role: "Director", joined: "Feb, 2022", location: "Malaysia", balance: 6214.22 },
{ id: "7", name: "James Brown", availability: "busy", avatar: PIXEL, status: "inactive", flag: "es", email: "james@bbva.es", company: "BBVA", role: "Product Manager", joined: "Aug, 2024", location: "Spain", balance: 5321.77 },
{ id: "8", name: "Maria Garcia", availability: "offline", avatar: PIXEL, status: "active", flag: "jp", email: "maria@sony.jp", company: "Sony", role: "Marketing Lead", joined: "Dec, 2023", location: "Japan", balance: 8452.39 },
{ id: "9", name: "Nick Johnson", availability: "online", avatar: PIXEL, status: "active", flag: "fr", email: "nick@lvmh.fr", company: "LVMH", role: "Data Scientist", joined: "Apr, 2022", location: "France", balance: 7345.1 },
{ id: "10", name: "Liam Thompson", availability: "away", avatar: PIXEL, status: "inactive", flag: "it", email: "liam@eni.it", company: "ENI", role: "Engineer", joined: "Jul, 2024", location: "Italy", balance: 5214.88 },
{ id: "11", name: "Alex Johnson", availability: "busy", avatar: PIXEL, status: "active", flag: "br", email: "alex@vale.br", company: "Vale", role: "Software Engineer", joined: "May, 2023", location: "Brazil", balance: 9421.5 },
{ id: "12", name: "Sarah Chen", availability: "offline", avatar: PIXEL, status: "active", flag: "in", email: "sarah@tata.in", company: "Tata", role: "Sales Manager", joined: "Oct, 2024", location: "India", balance: 4521.67 },
]
const pagination = ref<PaginationState>({ pageIndex: 0, pageSize: 5 })
const sorting = ref<SortingState>([{ id: "name", desc: true }])
function initials(name: string) {
return name.split(" ").map((n) => n[0]).join("")
}
function icon(path: VNode[]) {
return h(
"svg",
{ xmlns: "http://www.w3.org/2000/svg", width: 24, height: 24, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", "stroke-width": 2, "stroke-linecap": "round", "stroke-linejoin": "round" },
path
)
}
const userIcon = () =>
icon([h("path", { d: "M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2" }), h("circle", { cx: 12, cy: 7, r: 4 })])
const mailIcon = () =>
icon([h("path", { d: "m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7" }), h("rect", { x: 2, y: 4, width: 20, height: 16, rx: 2 })])
const mapPinCheckInsideIcon = () =>
icon([
h("path", { d: "M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0" }),
h("path", { d: "m9 10 2 2 4-4" }),
])
const columns: ColumnDef<IData>[] = [
{
accessorKey: "name",
id: "name",
header: (ctx) =>
h(DataGridColumnHeader, { column: ctx.column as never, title: "User" }, { icon: userIcon }),
cell: (info) => {
const row = info.row.original
return h("div", { class: "flex items-center gap-3" }, [
h(Avatar, { class: "size-8" }, () => [
h(AvatarImage, { src: row.avatar, alt: row.name }),
h(AvatarFallback, () => initials(row.name)),
]),
h("div", {}, [
h("div", { class: "text-foreground font-medium" }, row.name),
h("div", { class: "text-muted-foreground" }, row.email),
]),
])
},
size: 250,
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "email",
header: (ctx) =>
h(DataGridColumnHeader, { column: ctx.column as never, title: "Email" }, { icon: mailIcon }),
cell: (info) =>
h("a", { href: `mailto:${info.getValue()}`, class: "hover:text-primary hover:underline" }, info.getValue() as string),
size: 150,
enableSorting: false,
},
{
accessorKey: "location",
header: (ctx) =>
h(DataGridColumnHeader, { column: ctx.column as never, title: "Location" }, { icon: mapPinCheckInsideIcon }),
cell: (info) => {
const row = info.row.original
return h("div", { class: "flex items-center gap-1.5" }, [
h("img", { src: PIXEL, alt: row.flag, class: "size-4 rounded-full object-cover" }),
h("div", { class: "text-foreground font-medium" }, row.location),
])
},
size: 150,
enableSorting: false,
},
]
const table = useVueTable({
get data() {
return demoData
},
columns,
get pageCount() {
return Math.ceil(demoData.length / pagination.value.pageSize)
},
getRowId: (row: IData) => row.id,
state: {
get pagination() {
return pagination.value
},
get sorting() {
return sorting.value
},
},
onPaginationChange: (updater) => {
pagination.value = typeof updater === "function" ? updater(pagination.value) : updater
},
onSortingChange: (updater) => {
sorting.value = typeof updater === "function" ? updater(sorting.value) : updater
},
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
})
</script>
<template>
<DataGrid :table="table" :record-count="demoData.length">
<div class="w-full space-y-2.5">
<DataGridContainer>
<DataGridScrollArea>
<DataGridTable />
</DataGridScrollArea>
</DataGridContainer>
<DataGridPagination />
</div>
</DataGrid>
</template>