Skip to content

Commit

Permalink
Merge pull request #5 from matthewgallo/lit-virtual
Browse files Browse the repository at this point in the history
feat: add lit virutalize table
  • Loading branch information
matthewgallo authored Sep 20, 2024
2 parents ed920dc + eb80bce commit 7a99538
Show file tree
Hide file tree
Showing 11 changed files with 1,074 additions and 0 deletions.
24 changes: 24 additions & 0 deletions web-components/virtual/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
6 changes: 6 additions & 0 deletions web-components/virtual/.sassrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"includePaths": [
"node_modules",
"../../node_modules"
]
}
15 changes: 15 additions & 0 deletions web-components/virtual/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Lit + TS</title>
<link rel="stylesheet" href="https://1.www.s81c.com/common/carbon-for-ibm-dotcom/tag/v1/latest/plex.css" />
<link rel="stylesheet" href="./src/index.scss" />
<script type="module" src="/src/virtual.ts"></script>
</head>
<body>
<virtual-tanstack-table></virtual-tanstack-table>
</body>
</html>
23 changes: 23 additions & 0 deletions web-components/virtual/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "virtual",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"dependencies": {
"@carbon/styles": "^1.34.0",
"@carbon/web-components": "latest",
"@tanstack/lit-table": "^8.20.5",
"@tanstack/lit-virtual": "^3.10.8",
"lit": "^3.2.0",
"sass": "^1.64.1"
},
"devDependencies": {
"typescript": "^5.5.3",
"vite": "^5.4.1"
}
}
1 change: 1 addition & 0 deletions web-components/virtual/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions web-components/virtual/src/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@use '@carbon/styles/scss/reset';
@use '@carbon/styles/scss/theme';
@use '@carbon/styles/scss/themes';

:root {
@include theme.theme(themes.$white);
background-color: var(--cds-background);
color: var(--cds-text-primary);
}
76 changes: 76 additions & 0 deletions web-components/virtual/src/makeData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { faker } from '@faker-js/faker'

export type Resource = {
id: string
name: string
rule: string
status: string
other: string
example: string
subRows?: Resource[]
}

const range = (len: number) => {
const arr: number[] = []
for (let i = 0; i < len; i++) {
arr.push(i)
}
return arr
}

const newResource = (id: string, index: number): Resource => {
return {
id,
name: `Load balancer ${index}`,
rule: faker.helpers.shuffle<Resource['rule']>([
'DNS delegation',
'Round Robin'
])[0],
status: faker.helpers.shuffle<Resource['status']>([
'starting',
'active',
'disabled',
])[0]!,
other: 'Test',
example: faker.number.int(1000).toString(),
}
}

export function makeData(...lens: number[]) {
const makeDataLevel = (depth = 0): Resource[] => {
const len = lens[depth]!
return range(len).map((index): Resource => {
return {
...newResource(`load-balancer-${index}`, index),
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
}
})
}

return makeDataLevel()
}

//simulates a backend api
const data = makeData(1000)
export const fetchData = async (
start: number,
size: number,
) => {

//simulate a backend api
await new Promise(resolve => setTimeout(resolve, 2000))

return {
data: data.slice(start, start + size),
meta: {
totalRowCount: data.length,
},
}
}

export type ResourceApiResponse = {
data: Resource[]
meta: {
totalRowCount: number
}
}
200 changes: 200 additions & 0 deletions web-components/virtual/src/virtual.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { LitElement, css, html } from 'lit'
import { customElement } from 'lit/decorators.js'
import { repeat } from 'lit/directives/repeat.js'
import {
createColumnHelper,
flexRender,
getCoreRowModel,
getSortedRowModel,
Row,
TableController,
} from '@tanstack/lit-table'
import '@carbon/web-components/es/components/data-table/index.js';
import { VirtualizerController } from '@tanstack/lit-virtual'
import { createRef, ref, Ref } from 'lit/directives/ref.js'
import { styleMap } from 'lit/directives/style-map.js'

import { makeData } from './makeData';


type Resource = {
id: string
name: string
rule: string
status: string
other: string
example: string
}

const columnHelper = createColumnHelper<Resource>()

const columns = [
columnHelper.accessor(row => row.name, {
id: 'lastName',
cell: info => html`<i>${info.getValue()}</i>`,
header: () => html`<span>Name</span>`,
}),
columnHelper.accessor('rule', {
header: () => 'Rule',
cell: info => info.renderValue(),
}),
columnHelper.accessor('status', {
header: () => html`<span>Status</span>`,
}),
columnHelper.accessor('other', {
header: 'Other',
}),
columnHelper.accessor('example', {
header: 'Example',
}),
]

const data: Resource[] = makeData(1000);

/**
* An example table using `@tanstack/lit-table` and `@carbon/web-components` DataTable.
*
*/

@customElement('virtual-tanstack-table')
export class VirtualTable extends LitElement {
private tableController = new TableController<Resource>(this);

private tableContainerRef: Ref = createRef()

private rowVirtualizerController: VirtualizerController<Element, Element>

connectedCallback() {
this.rowVirtualizerController = new VirtualizerController(this, {
count: data.length,
getScrollElement: () => this.tableContainerRef.value!,
estimateSize: () => 48,
overscan: 5,
})
super.connectedCallback()
}

render() {
const table = this.tableController.table({
columns,
data,
getSortedRowModel: getSortedRowModel(),
getCoreRowModel: getCoreRowModel(),
})
const { rows } = table.getRowModel()

const virtualizer = this.rowVirtualizerController.getVirtualizer()

return html`
<div
class="container"
${ref(this.tableContainerRef)}
style="${styleMap({
overflow: 'auto', //our scrollable table container
position: 'relative', //needed for sticky header
height: '540px', //should be a fixed height
})}"
>
<cds-table style="display: grid">
<cds-table-head
style="${styleMap({
display: 'grid',
position: 'sticky',
top: 0,
zIndex: 1,
})}"
>
${repeat(
table.getHeaderGroups(),
headerGroup => headerGroup.id,
headerGroup =>
html`<cds-table-header-row style="${styleMap({ display: 'flex', width: '100%' })}">
${repeat(
headerGroup.headers,
header => header.id,
header =>
html` <cds-table-header-cell style="${styleMap({
display: 'flex',
width: `${header.getSize()}px`,
alignItems: 'center',
})}">
${header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</cds-table-header-cell>`
)}</cds-table-header-row>`
)}
</cds-table-head>
<cds-table-body style=${styleMap({
display: 'grid',
height: `${virtualizer.getTotalSize()}px`, //tells scrollbar how big the table is
position: 'relative', //needed for absolute positioning of rows
})}>
${repeat(
this.rowVirtualizerController
.getVirtualizer()
.getVirtualItems(),
item => item.key,
item => {
const row = rows[item.index] as Row<Resource>
return html`
<cds-table-row
style=${styleMap({
display: 'flex',
position: 'absolute',
transform: `translateY(${item.start}px)`,
width: '100%',
})}
${ref(node =>
this.rowVirtualizerController
.getVirtualizer()
.measureElement(node)
)}
>
${repeat(
row.getVisibleCells(),
cell => cell.id,
cell => html`
<cds-table-cell
style=${styleMap({
display: 'flex',
width: `${cell.column.getSize()}px`,
alignItems: 'center',
})}
>
${flexRender(
cell.column.columnDef.cell,
cell.getContext()
)}
</cds-table-cell>
`
)}
</cds-table-row>
`
}
)}
</cds-table-body>
</cds-table>
</div>
`
}

static styles = css`
:host {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
display: flex;
place-items: center;
}
`
}

declare global {
interface HTMLElementTagNameMap {
'virtual-tanstack-table': VirtualTable
}
}
1 change: 1 addition & 0 deletions web-components/virtual/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
24 changes: 24 additions & 0 deletions web-components/virtual/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}
Loading

0 comments on commit 7a99538

Please sign in to comment.