@voila.dev/ui-spreadsheet 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +58 -0
- package/src/components/nested-table-input.tsx +85 -0
- package/src/components/spreadsheet.tsx +1288 -0
- package/src/hooks/use-spreadsheet-column-sizing.ts +120 -0
- package/src/hooks/use-spreadsheet-drag.ts +617 -0
- package/src/hooks/use-spreadsheet-grid.ts +743 -0
- package/src/hooks/use-spreadsheet-image-drop.ts +114 -0
- package/src/styles.css +10 -0
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voila.dev/ui-spreadsheet",
|
|
3
|
+
"version": "1.1.9",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "An editable, virtualized grid your users will mistake for a native app.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://ui.voila.dev/components/editable-table",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/voila-voila-dev/ui.git",
|
|
11
|
+
"directory": "packages/ui-spreadsheet"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"react",
|
|
15
|
+
"spreadsheet",
|
|
16
|
+
"data-grid",
|
|
17
|
+
"editable",
|
|
18
|
+
"tailwindcss"
|
|
19
|
+
],
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"src",
|
|
25
|
+
"!src/**/*.test.ts",
|
|
26
|
+
"!src/**/*.test.tsx",
|
|
27
|
+
"!src/**/*.stories.ts",
|
|
28
|
+
"!src/**/*.stories.tsx"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": [
|
|
31
|
+
"**/*.css"
|
|
32
|
+
],
|
|
33
|
+
"exports": {
|
|
34
|
+
"./styles.css": "./src/styles.css",
|
|
35
|
+
"./components/*": "./src/components/*.tsx",
|
|
36
|
+
"./hooks/*": "./src/hooks/*.ts"
|
|
37
|
+
},
|
|
38
|
+
"imports": {
|
|
39
|
+
"#/*": "./src/*"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"check": "biome check",
|
|
43
|
+
"check-types": "tsc --noEmit",
|
|
44
|
+
"format": "biome format",
|
|
45
|
+
"lint": "biome lint",
|
|
46
|
+
"test": "vitest run"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@phosphor-icons/react": "2.1.10",
|
|
50
|
+
"@tanstack/react-virtual": "^3.14.6",
|
|
51
|
+
"@voila.dev/ui": "0.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"react": "^19.0.0",
|
|
55
|
+
"react-dom": "^19.0.0",
|
|
56
|
+
"tailwindcss": "^4.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CaretDownIcon } from "@phosphor-icons/react";
|
|
2
|
+
import {
|
|
3
|
+
Popover,
|
|
4
|
+
PopoverContent,
|
|
5
|
+
PopoverDescription,
|
|
6
|
+
PopoverHeader,
|
|
7
|
+
PopoverTitle,
|
|
8
|
+
PopoverTrigger,
|
|
9
|
+
} from "@voila.dev/ui/components/popover";
|
|
10
|
+
import { cn } from "@voila.dev/ui/lib/utils";
|
|
11
|
+
import type * as React from "react";
|
|
12
|
+
|
|
13
|
+
type NestedTableInputProps = {
|
|
14
|
+
/**
|
|
15
|
+
* What the closed cell reads, e.g. "2 paliers" or an em-dash when empty.
|
|
16
|
+
* The cell is a summary; the detail lives in the popover.
|
|
17
|
+
*/
|
|
18
|
+
summary: string;
|
|
19
|
+
/** Accessible name for the trigger (the summary alone rarely identifies it). */
|
|
20
|
+
ariaLabel: string;
|
|
21
|
+
/** Heading of the popover. */
|
|
22
|
+
title: string;
|
|
23
|
+
/** Optional sentence under the title explaining what the rows mean. */
|
|
24
|
+
description?: string;
|
|
25
|
+
/** The nested table (or any editor) shown once open. */
|
|
26
|
+
children: React.ReactNode;
|
|
27
|
+
/** Tints the cell and raises its destructive ring, like any invalid control. */
|
|
28
|
+
invalid?: boolean;
|
|
29
|
+
/** Width of the popover; the nested table sets its own column widths. */
|
|
30
|
+
contentClassName?: string;
|
|
31
|
+
className?: string;
|
|
32
|
+
} & Omit<React.ComponentProps<"button">, "children" | "title">;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A cell whose value is a whole sub-table rather than a scalar: it renders a
|
|
36
|
+
* flush summary button and opens a popover holding the nested editor. Use it
|
|
37
|
+
* for row-owned collections (price tiers, discounts) that deserve a grid column
|
|
38
|
+
* but cannot fit one — the summary keeps the row scannable, and the popover
|
|
39
|
+
* gives the collection its own table without leaving the row.
|
|
40
|
+
*
|
|
41
|
+
* The nested content is passed as children, so this stays agnostic about what
|
|
42
|
+
* it hosts and is reusable for any per-row collection.
|
|
43
|
+
*/
|
|
44
|
+
function NestedTableInput({
|
|
45
|
+
summary,
|
|
46
|
+
ariaLabel,
|
|
47
|
+
title,
|
|
48
|
+
description,
|
|
49
|
+
children,
|
|
50
|
+
invalid,
|
|
51
|
+
contentClassName,
|
|
52
|
+
className,
|
|
53
|
+
...props
|
|
54
|
+
}: NestedTableInputProps) {
|
|
55
|
+
return (
|
|
56
|
+
<Popover>
|
|
57
|
+
<PopoverTrigger
|
|
58
|
+
data-slot="nested-table-input"
|
|
59
|
+
aria-label={ariaLabel}
|
|
60
|
+
aria-invalid={invalid}
|
|
61
|
+
className={cn(
|
|
62
|
+
"flex h-9 w-full items-center justify-between gap-1 bg-transparent px-2.5 text-left text-sm outline-none",
|
|
63
|
+
"text-foreground data-[empty=true]:text-muted-foreground",
|
|
64
|
+
className,
|
|
65
|
+
)}
|
|
66
|
+
data-empty={summary.trim().length === 0}
|
|
67
|
+
{...props}
|
|
68
|
+
>
|
|
69
|
+
<span className="truncate">{summary}</span>
|
|
70
|
+
<CaretDownIcon className="size-3.5 shrink-0 text-muted-foreground" />
|
|
71
|
+
</PopoverTrigger>
|
|
72
|
+
<PopoverContent className={cn("w-96", contentClassName)} align="start">
|
|
73
|
+
<PopoverHeader>
|
|
74
|
+
<PopoverTitle>{title}</PopoverTitle>
|
|
75
|
+
{description === undefined ? null : (
|
|
76
|
+
<PopoverDescription>{description}</PopoverDescription>
|
|
77
|
+
)}
|
|
78
|
+
</PopoverHeader>
|
|
79
|
+
{children}
|
|
80
|
+
</PopoverContent>
|
|
81
|
+
</Popover>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { NestedTableInput, type NestedTableInputProps };
|