Skip to content

Commit 24b32b8

Browse files
Merge pull request #340 from appwrite/feat-virtual-tables
Add virtual table support and improve table API
2 parents d521606 + 3228c15 commit 24b32b8

21 files changed

+373
-99
lines changed

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Loading

v2/pink-sb/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"@floating-ui/dom": "^1.6.13",
3333
"@melt-ui/pp": "^0.3.2",
3434
"@melt-ui/svelte": "^0.86.6",
35+
"@tanstack/svelte-virtual": "^3.13.10",
3536
"ansicolor": "^2.0.3",
3637
"d3": "^7.9.0",
3738
"fuse.js": "^7.1.0",

v2/pink-sb/src/lib/InteractiveText.svelte

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
let timeout: ReturnType<typeof setTimeout>;
1212
let showCopySuccess = false;
1313
14-
function toggleVisibility() {
14+
function toggleVisibility(event: MouseEvent) {
15+
event.preventDefault();
1516
clearTimeout(timeout);
1617
if (isVisible) {
1718
isVisible = false;
@@ -23,7 +24,8 @@
2324
}
2425
}
2526
26-
async function copyToClipboard() {
27+
async function copyToClipboard(event: MouseEvent) {
28+
event.preventDefault();
2729
await clipboardCopy(value ?? text);
2830
showCopySuccess = true;
2931
setTimeout(() => {
@@ -61,9 +63,9 @@
6163
{/if}
6264
{#if copy}
6365
<div class="copy-container">
64-
<button type="button" title="Copy to clipboard" on:click={copyToClipboard}
65-
><IconDuplicate /></button
66-
>
66+
<button type="button" title="Copy to clipboard" on:click={copyToClipboard}>
67+
<IconDuplicate />
68+
</button>
6769
<div role="tooltip" aria-hidden={!showCopySuccess}>Copied</div>
6870
</div>
6971
{/if}

v2/pink-sb/src/lib/card/Selector.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
export let imageWidth: $$Props['imageWidth'] = undefined;
4141
export let disabled: $$Props['disabled'] = undefined;
4242
43-
const constrainedImageHeight = Math.min(imageHeight, 148);
43+
$: constrainedImageHeight = Math.min(imageHeight || 148, 148);
4444
</script>
4545

4646
<Card.Label {variant} {radius} {padding} selected={value === group} {disabled}>

v2/pink-sb/src/lib/input/Textarea.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import type { HTMLTextareaAttributes } from 'svelte/elements';
55
import type { States } from './types.js';
66
import { autofocusInput } from './autofocus.js';
7-
import { Layout } from '$lib';
7+
import { Layout } from '$lib/index.js';
88
99
type $$Props = HTMLTextareaAttributes &
1010
Partial<{

v2/pink-sb/src/lib/selector/Checkbox.svelte

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@
9696
border-radius: var(--border-radius-xxs);
9797
}
9898
99-
&:hover:not(.active):not([aria-disabled='true']):not([disabled]) {
99+
&:hover:not(.active):not([disabled]) {
100100
background-color: var(--overlay-button-neutral-hover);
101101
}
102102
@@ -105,8 +105,7 @@
105105
background-color: var(--bgcolor-neutral-invert);
106106
}
107107
108-
&:disabled,
109-
&[aria-disabled='true'] {
108+
&:disabled {
110109
opacity: 0.4;
111110
cursor: default;
112111
background-color: var(--bgcolor-neutral-tertiary);

v2/pink-sb/src/lib/table/Cell.svelte renamed to v2/pink-sb/src/lib/table/cell/Base.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<script lang="ts">
2-
import type { Alignment, RootProp } from './index.js';
2+
import type { Alignment, RootProp } from '../index.js';
33
44
export let column: string | undefined = undefined;
55
export let root: RootProp;
66
export let alignment: Alignment = 'middle-middle';
77
8-
$: options = column !== undefined && root.columns?.[column];
8+
$: options = column !== undefined && root.columnsMap?.[column];
99
$: isVerticalStart = alignment.startsWith('start');
1010
$: isVerticalEnd = alignment.startsWith('end');
1111
$: isHorizontalStart = alignment.endsWith('start');
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<script lang="ts">
2+
import type { VirtualItem } from '@tanstack/svelte-virtual';
3+
import type { Alignment, RootProp } from '../index.js';
4+
5+
export let column: string | undefined = undefined;
6+
export let root: RootProp;
7+
export let virtualItem: VirtualItem;
8+
export let alignment: Alignment = 'middle-middle';
9+
10+
$: options = column !== undefined && root.columnsMap?.[column];
11+
$: isVerticalStart = alignment.startsWith('start');
12+
$: isVerticalEnd = alignment.startsWith('end');
13+
$: isHorizontalStart = alignment.endsWith('start');
14+
$: isHorizontalEnd = alignment.endsWith('end');
15+
</script>
16+
17+
{#if !options || options?.hide !== true}
18+
<div
19+
role="cell"
20+
class:vertical-start={isVerticalStart}
21+
class:vertical-end={isVerticalEnd}
22+
class:horizontal-start={isHorizontalStart}
23+
class:horizontal-end={isHorizontalEnd}
24+
style:width="{virtualItem.size}px"
25+
style:transform="translateX({virtualItem.start}px)"
26+
>
27+
<slot />
28+
</div>
29+
{/if}
30+
31+
<style lang="scss">
32+
[role='cell'] {
33+
--p-cell-width: var(--cell-width);
34+
--p-cell-max-width: var(--cell-max-width);
35+
--p-cell-alignment: var(--cell-alignment);
36+
position: absolute;
37+
top: 0;
38+
left: 0;
39+
display: flex;
40+
align-items: center;
41+
padding-inline: var(--space-6);
42+
height: 40px;
43+
border-bottom: var(--border-width-s) solid var(--border-neutral);
44+
overflow: hidden;
45+
white-space: nowrap;
46+
47+
&.horizontal-start {
48+
justify-content: flex-start;
49+
}
50+
51+
&.horizontal-end {
52+
justify-content: flex-end;
53+
}
54+
55+
&.vertical-start {
56+
align-items: flex-start;
57+
}
58+
59+
&.vertical-end {
60+
align-items: flex-end;
61+
}
62+
}
63+
</style>

v2/pink-sb/src/lib/table/header/Action.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import { Button } from '$lib/button/index.js';
33
import type { ComponentProps, ComponentType } from 'svelte';
4-
import Cell from '../Cell.svelte';
4+
import Cell from '../cell/Base.svelte';
55
import Icon from '$lib/Icon.svelte';
66
77
type $$Props = ComponentProps<Cell>;

v2/pink-sb/src/lib/table/header/Cell.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import type { ComponentProps } from 'svelte';
3-
import Cell from '../Cell.svelte';
3+
import Cell from '../cell/Base.svelte';
44
55
type $$Props = ComponentProps<Cell>;
66
export let column: $$Props['column'];

v2/pink-sb/src/lib/table/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
import Root from './Root.svelte';
2-
import Cell from './Cell.svelte';
1+
import Root from './root/Default.svelte';
2+
import VirtualRoot from './root/Virtual.svelte';
3+
import Cell from './cell/Base.svelte';
4+
import VirtualCell from './cell/Virtual.svelte';
35
import Row from './row/index.js';
46
import Header from './header/index.js';
7+
58
export type Column = {
69
id: string;
710
width?:
@@ -22,7 +25,8 @@ export type RootProp = {
2225
selectedAll: boolean;
2326
selectedNone: boolean;
2427
selectedSome: boolean;
25-
columns: Record<Column['id'], Column>;
28+
columns: Array<Column> | number;
29+
columnsMap: Record<Column['id'], Column>;
2630
toggle: (id: string) => void;
2731
toggleAll: () => void;
2832
addAvailableId: (id: string) => void;
@@ -42,7 +46,9 @@ export type Alignment =
4246

4347
export default {
4448
Root,
49+
VirtualRoot,
4550
Cell,
51+
VirtualCell,
4652
Row,
4753
Header
4854
};
Lines changed: 17 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,13 @@
11
<script lang="ts">
2-
import type { Column, RootProp } from './index.js';
3-
import Row from './row/Base.svelte';
2+
import type { Column, RootProp } from '../index.js';
43
54
export let columns: Array<Column> | number;
65
export let allowSelection: boolean = false;
76
export let selectedRows: Array<string> = [];
7+
export let element: HTMLElement | undefined = undefined;
88
99
let availableIds: Set<string> = new Set();
1010
11-
function widthWithPadding(width: number): string {
12-
return `calc(${width}px + var(--p-table-cell-padding-inline))`;
13-
}
14-
15-
function createGridTemplateColumns(cols: typeof columns) {
16-
if (typeof cols === 'number') {
17-
return `repeat(${cols}, 1fr)`;
18-
}
19-
const columns = cols.filter((column) => column.hide !== true);
20-
const hasOnlyMaxWidth = columns.every(
21-
(column) => typeof column.width === 'number' || (column.width && 'max' in column.width)
22-
);
23-
24-
return columns.reduce(
25-
(acc, column) => {
26-
if (column.width === undefined) return `${acc} 1fr`;
27-
if (typeof column.width === 'number') {
28-
return `${acc} ${widthWithPadding(column.width)}`;
29-
}
30-
if ('min' in column.width && 'max' in column.width) {
31-
if (hasOnlyMaxWidth) {
32-
return `${acc} minmax(${widthWithPadding(column.width.min)}, 1fr)`;
33-
}
34-
return `${acc} minmax(${widthWithPadding(column.width.min)}, ${widthWithPadding(column.width.max)})`;
35-
}
36-
if ('min' in column.width) {
37-
return `${acc} minmax(${widthWithPadding(column.width.min)}, 1fr)`;
38-
}
39-
return acc;
40-
},
41-
allowSelection ? ' 40px' : '' // Default width for selection column
42-
);
43-
}
44-
45-
function groupById(cols: typeof columns): RootProp['columns'] {
46-
if (typeof cols === 'number') {
47-
return {};
48-
}
49-
return cols.reduce<Record<Column['id'], Column>>((acc, column) => {
50-
acc[column.id] = column;
51-
return acc;
52-
}, {});
53-
}
54-
5511
$: someRowsSelected =
5612
availableIds.size > 0 &&
5713
selectedRows.length > 0 &&
@@ -87,10 +43,21 @@
8743
availableIds = availableIds;
8844
}
8945
46+
function groupById(cols: typeof columns): RootProp['columnsMap'] {
47+
if (typeof cols === 'number') {
48+
return {};
49+
}
50+
return cols.reduce<Record<Column['id'], Column>>((acc, column) => {
51+
acc[column.id] = column;
52+
return acc;
53+
}, {});
54+
}
55+
9056
$: root = {
9157
allowSelection,
9258
selectedRows,
93-
columns: groupById(columns),
59+
columns: columns,
60+
columnsMap: groupById(columns),
9461
toggleAll,
9562
toggle,
9663
selectedSome: someRowsSelected,
@@ -101,33 +68,20 @@
10168
} as RootProp;
10269
</script>
10370

104-
<div class="root">
105-
<div role="table" style:--grid-template-columns={createGridTemplateColumns(columns)}>
106-
{#if $$slots.header}
107-
<Row type="header" {root}>
108-
<slot name="header" {root} />
109-
</Row>
110-
{/if}
111-
<slot {root} />
112-
</div>
71+
<div class="root" bind:this={element}>
72+
<slot {root} />
11373
</div>
11474

11575
<style lang="scss">
11676
.root {
11777
--p-table-cell-padding-inline: var(--space-6);
118-
overflow-x: auto;
11978
border: 1px solid var(--border-neutral);
12079
border-radius: var(--border-radius-s);
12180
background: var(--bgcolor-neutral-primary);
81+
overflow-x: auto;
12282
12383
::-webkit-scrollbar {
12484
display: none;
12585
}
126-
127-
[role='table'] {
128-
display: grid;
129-
grid-template-columns: var(--grid-template-columns);
130-
width: 100%;
131-
}
13286
}
13387
</style>

0 commit comments

Comments
 (0)