Skip to content

Commit be1700c

Browse files
committed
Add repro for issue #863
1 parent f3a5aa7 commit be1700c

File tree

1 file changed

+118
-122
lines changed

1 file changed

+118
-122
lines changed

src/routes/ScratchpadRoute.tsx

Lines changed: 118 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,140 +1,136 @@
1-
import { useState } from "react";
1+
import { useCallback, useState, type ButtonHTMLAttributes } from "react";
22
import {
3-
Grid,
4-
useGridCallbackRef,
5-
type Align,
6-
type CellComponentProps
3+
List,
4+
useDynamicRowHeight,
5+
useListCallbackRef,
6+
type RowComponentProps
77
} from "react-window";
8-
import { Block } from "../components/Block";
9-
import { Box } from "../components/Box";
10-
import { Checkbox } from "../components/Checkbox";
11-
import { Input } from "../components/Input";
12-
import { Select, type Option } from "../components/Select";
13-
import { cn } from "../utils/cn";
148

15-
const ALIGNMENTS: Option<Align>[] = (
16-
["auto", "center", "end", "smart", "start"] satisfies Align[]
17-
).map((value) => ({
18-
label: `align: ${value}`,
19-
value
20-
}));
9+
type Item = {
10+
children: Array<{ index: number }>;
11+
id: number;
12+
name: string;
13+
};
2114

2215
export default function ScratchpadRoute() {
23-
const [rtl, setRtl] = useState(false);
24-
const [columnIndex, setColumnIndex] = useState<number | undefined>();
25-
const [rowIndex, setRowIndex] = useState<number | undefined>();
26-
const [gridRef, setGridRef] = useGridCallbackRef(null);
27-
const [align, setAlign] = useState(ALIGNMENTS[0]);
16+
const [key, setKey] = useState(0);
17+
const [items, setItems] = useState(createItems);
18+
const [expandedSet, setExpandedSet] = useState<Set<number>>(new Set());
19+
20+
const [list, setList] = useListCallbackRef();
21+
const rowHeight = useDynamicRowHeight({
22+
defaultRowHeight: 24
23+
});
24+
25+
const toggleExpand = useCallback(
26+
(id: number) => {
27+
const newSet = new Set(expandedSet);
28+
if (newSet.has(id)) {
29+
newSet.delete(id);
30+
} else {
31+
newSet.add(id);
32+
}
33+
setExpandedSet(newSet);
34+
},
35+
[expandedSet]
36+
);
2837

2938
return (
30-
<Box direction="column" gap={4}>
31-
<Box
32-
align="center"
33-
direction="row"
34-
gap={4}
35-
onKeyDown={(event) => {
36-
switch (event.key) {
37-
case "Enter": {
38-
if (columnIndex !== undefined && rowIndex !== undefined) {
39-
gridRef?.scrollToCell({
40-
columnAlign: align.value,
41-
columnIndex,
42-
rowAlign: align.value,
43-
rowIndex
44-
});
45-
} else if (columnIndex !== undefined) {
46-
gridRef?.scrollToColumn({
47-
align: align.value,
48-
index: columnIndex
49-
});
50-
} else if (rowIndex !== undefined) {
51-
gridRef?.scrollToRow({
52-
align: align.value,
53-
index: rowIndex
54-
});
55-
}
56-
break;
57-
}
58-
}
59-
}}
60-
>
61-
<Select
62-
className="flex-1"
63-
onChange={setAlign}
64-
options={ALIGNMENTS}
65-
placeholder="Align"
66-
value={align}
67-
/>
68-
<Checkbox checked={rtl} onChange={setRtl} />
69-
<Input
70-
autoFocus
71-
className="grow"
72-
min={0}
73-
max={99}
74-
onChange={(value) => {
75-
const parsed = parseInt(value);
76-
setColumnIndex(isNaN(parsed) ? undefined : parsed);
39+
<div className="flex flex-col gap-2">
40+
<div className="flex items-center gap-2">
41+
<Button
42+
onClick={() => {
43+
setItems(createItems());
44+
setExpandedSet(new Set());
45+
setKey(key + 1);
7746
}}
78-
placeholder="Column"
79-
step={1}
80-
type="number"
81-
value={columnIndex === undefined ? "" : "" + columnIndex}
82-
/>
83-
<Input
84-
autoFocus
85-
className="grow"
86-
min={0}
87-
max={99}
88-
onChange={(value) => {
89-
const parsed = parseInt(value);
90-
setRowIndex(isNaN(parsed) ? undefined : parsed);
47+
>
48+
Reset
49+
</Button>
50+
<Button
51+
onClick={() => {
52+
list?.scrollToRow({
53+
align: "end",
54+
index: items.length - 1
55+
});
9156
}}
92-
placeholder="Row"
93-
step={1}
94-
type="number"
95-
value={rowIndex === undefined ? "" : "" + rowIndex}
96-
/>
97-
</Box>
98-
<Block className="w-full h-100" data-focus-within="bold">
99-
<Grid
100-
cellComponent={CellComponent}
101-
cellProps={{
102-
focusedColumnIndex: columnIndex,
103-
focusedRowIndex: rowIndex
104-
}}
105-
columnCount={100}
106-
columnWidth={75}
107-
dir={rtl ? "rtl" : undefined}
108-
key={rtl ? "rtl" : "ltr"}
109-
gridRef={setGridRef}
110-
rowCount={100}
111-
rowHeight={35}
112-
/>
113-
</Block>
114-
</Box>
57+
>
58+
Scroll to Bottom
59+
</Button>
60+
</div>
61+
<List
62+
className="w-full h-100 border"
63+
key={key}
64+
listRef={setList}
65+
rowComponent={Row}
66+
rowCount={items.length}
67+
rowHeight={rowHeight}
68+
rowProps={{
69+
expandedSet,
70+
items,
71+
toggleExpand
72+
}}
73+
/>
74+
</div>
11575
);
11676
}
11777

118-
function CellComponent({
119-
columnIndex,
120-
focusedColumnIndex,
121-
focusedRowIndex,
122-
rowIndex,
78+
function Row({
79+
expandedSet,
80+
items,
81+
toggleExpand,
82+
index,
12383
style
124-
}: CellComponentProps<{
125-
focusedColumnIndex: number | undefined;
126-
focusedRowIndex: number | undefined;
84+
}: RowComponentProps<{
85+
expandedSet: Set<number>;
86+
items: Item[];
87+
toggleExpand: (id: number) => void;
12788
}>) {
89+
const item = items[index];
90+
const isExpanded = expandedSet.has(index);
91+
12892
return (
129-
<div
130-
className={cn("flex items-center justify-center text-xs", {
131-
"bg-slate-900": columnIndex % 2 === rowIndex % 2,
132-
"bg-slate-800":
133-
columnIndex === focusedColumnIndex || rowIndex === focusedRowIndex
134-
})}
135-
style={style}
136-
>
137-
row {rowIndex}, col {columnIndex}
93+
<div className="py-1 px-2 flex items-center gap-2" style={style}>
94+
<Button
95+
disabled={!item.children.length}
96+
onClick={() => toggleExpand(index)}
97+
>
98+
{!item.children.length || isExpanded ? "–" : "+"}
99+
</Button>
100+
{item.name}
101+
{isExpanded && (
102+
<pre className="text-xs">{JSON.stringify(item.children, null, 2)}</pre>
103+
)}
138104
</div>
139105
);
140106
}
107+
108+
function createItems() {
109+
const items: Item[] = [];
110+
111+
for (let index = 0; index < 500; ++index) {
112+
items.push({
113+
children: new Array(index % 5).fill(true).map((_, index) => ({
114+
index
115+
})),
116+
id: index,
117+
name: `item ${index}`
118+
});
119+
}
120+
121+
return items;
122+
}
123+
124+
function Button({
125+
className,
126+
disabled,
127+
...rest
128+
}: ButtonHTMLAttributes<HTMLButtonElement>) {
129+
return (
130+
<button
131+
className={`rounded bg-gray-700 px-2 ${disabled ? "opacity-35" : "cursor-pointer"} ${className}`}
132+
disabled={disabled}
133+
{...rest}
134+
/>
135+
);
136+
}

0 commit comments

Comments
 (0)