@topgrid/grid-pro-header 0.1.0
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/README.md +88 -0
- package/dist/index.cjs +229 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +173 -0
- package/dist/index.d.ts +173 -0
- package/dist/index.mjs +225 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @topgrid/grid-pro-header
|
|
2
|
+
|
|
3
|
+
Pro: Multi-row Header (Column Groups)
|
|
4
|
+
|
|
5
|
+
Provides multi-row header (column group) support for TOMIS Grid — merge columns under shared group headers, with full TanStack Table column group API integration.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @topgrid/grid-pro-header
|
|
11
|
+
# or
|
|
12
|
+
npm install @topgrid/grid-pro-header
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## License Activation
|
|
16
|
+
|
|
17
|
+
> **This is a Pro package requiring a valid license key.**
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { setLicenseKey } from '@topgrid/grid-license';
|
|
21
|
+
|
|
22
|
+
// Call once at your app entry point (e.g., main.tsx)
|
|
23
|
+
setLicenseKey('YOUR-LICENSE-KEY');
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Without a valid license, the component will render a watermark.
|
|
27
|
+
Contact [sales@topvel.com](mailto:sales@topvel.com) to obtain a license key.
|
|
28
|
+
|
|
29
|
+
## Peer Dependencies
|
|
30
|
+
|
|
31
|
+
| Package | Version |
|
|
32
|
+
|---------|---------|
|
|
33
|
+
| `@tanstack/react-table` | `^8.0.0` |
|
|
34
|
+
| `react` | `^18.0.0 \|\| ^19.0.0` |
|
|
35
|
+
| `react-dom` | `^18.0.0 \|\| ^19.0.0` |
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { setLicenseKey } from '@topgrid/grid-license';
|
|
41
|
+
import { MultiRowHeader, createColumnGroup } from '@topgrid/grid-pro-header';
|
|
42
|
+
|
|
43
|
+
setLicenseKey('YOUR-LICENSE-KEY');
|
|
44
|
+
|
|
45
|
+
// Define column groups
|
|
46
|
+
const columnGroups = createColumnGroup([
|
|
47
|
+
{
|
|
48
|
+
header: 'Personal Info',
|
|
49
|
+
columns: [
|
|
50
|
+
{ accessorKey: 'firstName', header: 'First Name' },
|
|
51
|
+
{ accessorKey: 'lastName', header: 'Last Name' },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
header: 'Contact',
|
|
56
|
+
columns: [
|
|
57
|
+
{ accessorKey: 'email', header: 'Email' },
|
|
58
|
+
{ accessorKey: 'phone', header: 'Phone' },
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
export function GroupedGrid({ data }) {
|
|
64
|
+
return (
|
|
65
|
+
<MultiRowHeader columns={columnGroups} data={data} />
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Main API
|
|
71
|
+
|
|
72
|
+
| Export | Description |
|
|
73
|
+
|--------|-------------|
|
|
74
|
+
| `MultiRowHeader` | Grid component with multi-row grouped header |
|
|
75
|
+
| `createColumnGroup` | Helper to define column group configurations |
|
|
76
|
+
| `GroupedHeaderGrid` | Legacy alias for `MultiRowHeader` (deprecated) |
|
|
77
|
+
| `ColumnGroupConfig` | Column group configuration type |
|
|
78
|
+
| `MultiRowHeaderProps` | Props type for `<MultiRowHeader>` |
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
SEE LICENSE IN [EULA.md](./EULA.md)
|
|
83
|
+
|
|
84
|
+
License terms subject to change. Contact [sales@topvel.com](mailto:sales@topvel.com) for current EULA.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
[Documentation](https://grid.tomis.dev) | [Pricing](https://topvel.com/grid/pricing)
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var gridLicense = require('@topgrid/grid-license');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var reactTable = require('@tanstack/react-table');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
|
|
10
|
+
// src/createColumnGroup.ts
|
|
11
|
+
function createColumnGroup(config) {
|
|
12
|
+
return {
|
|
13
|
+
header: config.header,
|
|
14
|
+
columns: config.columns
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function getHeaderLeftOffset(header) {
|
|
18
|
+
const direct = header.column.getStart("left");
|
|
19
|
+
if (direct !== void 0) return direct;
|
|
20
|
+
const firstLeaf = header.subHeaders[0]?.column;
|
|
21
|
+
return firstLeaf?.getStart("left") ?? 0;
|
|
22
|
+
}
|
|
23
|
+
function MultiRowHeader({
|
|
24
|
+
table,
|
|
25
|
+
enableStickyHeader,
|
|
26
|
+
frozenColumns,
|
|
27
|
+
enableGroupToggle
|
|
28
|
+
}) {
|
|
29
|
+
const _lic = gridLicense.useLicenseStatus();
|
|
30
|
+
const headerGroups = table.getHeaderGroups();
|
|
31
|
+
const visibleLeafCount = table.getVisibleLeafColumns().length;
|
|
32
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("thead", { className: "bg-gray-50", children: [
|
|
33
|
+
_lic.watermarkRequired && visibleLeafCount > 0 ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
34
|
+
"tr",
|
|
35
|
+
{
|
|
36
|
+
...enableStickyHeader === true ? { className: "sticky top-0 z-20" } : {},
|
|
37
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38
|
+
"th",
|
|
39
|
+
{
|
|
40
|
+
colSpan: visibleLeafCount,
|
|
41
|
+
className: "relative bg-yellow-50 px-4 py-1 text-center text-xs text-gray-500",
|
|
42
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(gridLicense.Watermark, { required: true })
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
) : null,
|
|
47
|
+
headerGroups.map((headerGroup, rowIndex) => {
|
|
48
|
+
const trProps = {
|
|
49
|
+
...enableStickyHeader === true && rowIndex === 0 && {
|
|
50
|
+
className: "sticky top-0 z-10"
|
|
51
|
+
},
|
|
52
|
+
...enableStickyHeader === true && rowIndex > 0 && {
|
|
53
|
+
className: "sticky z-10",
|
|
54
|
+
style: {
|
|
55
|
+
top: `calc(var(--grid-header-row-height, 40px) * ${rowIndex})`
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
return /* @__PURE__ */ jsxRuntime.jsx("tr", { ...trProps, children: headerGroup.headers.map((header) => {
|
|
60
|
+
const isFrozen = (frozenColumns ?? 0) > 0 && header.column.getIsPinned() === "left";
|
|
61
|
+
const frozenZClass = isFrozen && enableStickyHeader === true ? "sticky z-30" : isFrozen ? "sticky z-20" : "";
|
|
62
|
+
const thStyle = isFrozen ? { left: `${getHeaderLeftOffset(header)}px` } : {};
|
|
63
|
+
const isLeaf = header.subHeaders.length === 0;
|
|
64
|
+
const allLeavesHidden = !isLeaf && header.column.getLeafColumns().every((c) => !c.getIsVisible());
|
|
65
|
+
const groupClickHandler = enableGroupToggle === true && !isLeaf ? () => {
|
|
66
|
+
const leafCols = header.column.getLeafColumns();
|
|
67
|
+
leafCols.forEach((c) => c.toggleVisibility(allLeavesHidden));
|
|
68
|
+
} : void 0;
|
|
69
|
+
const effectiveColSpan = enableGroupToggle === true && allLeavesHidden ? 1 : header.colSpan;
|
|
70
|
+
if (header.isPlaceholder) {
|
|
71
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
72
|
+
"th",
|
|
73
|
+
{
|
|
74
|
+
colSpan: effectiveColSpan,
|
|
75
|
+
className: `px-4 py-3 border border-gray-200${frozenZClass ? ` ${frozenZClass}` : ""}`,
|
|
76
|
+
...isFrozen ? { style: thStyle } : {}
|
|
77
|
+
},
|
|
78
|
+
header.id
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
82
|
+
"th",
|
|
83
|
+
{
|
|
84
|
+
colSpan: effectiveColSpan,
|
|
85
|
+
className: `px-4 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider border border-gray-200 whitespace-nowrap select-none${header.column.getCanSort() && isLeaf ? " cursor-pointer hover:bg-gray-100" : ""}${enableGroupToggle === true && !isLeaf ? " cursor-pointer hover:bg-gray-100" : ""}${frozenZClass ? ` ${frozenZClass}` : ""}`,
|
|
86
|
+
...isFrozen ? { style: thStyle } : {},
|
|
87
|
+
onClick: isLeaf ? header.column.getToggleSortingHandler() : groupClickHandler,
|
|
88
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-1", children: [
|
|
89
|
+
reactTable.flexRender(header.column.columnDef.header, header.getContext()),
|
|
90
|
+
header.column.getCanSort() && isLeaf && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400", children: { asc: "\u25B2", desc: "\u25BC" }[header.column.getIsSorted()] ?? "\u21C5" }),
|
|
91
|
+
enableGroupToggle === true && !isLeaf && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-400 ml-1", children: allLeavesHidden ? "\u25B6" : "\u25BC" })
|
|
92
|
+
] })
|
|
93
|
+
},
|
|
94
|
+
header.id
|
|
95
|
+
);
|
|
96
|
+
}) }, headerGroup.id);
|
|
97
|
+
})
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
function GroupedHeaderGrid({
|
|
101
|
+
data,
|
|
102
|
+
columns,
|
|
103
|
+
pagination,
|
|
104
|
+
onRowClick,
|
|
105
|
+
loading = false,
|
|
106
|
+
emptyText = "\uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
107
|
+
className = "",
|
|
108
|
+
enableGroupToggle
|
|
109
|
+
}) {
|
|
110
|
+
const [sorting, setSorting] = react.useState([]);
|
|
111
|
+
const [pageIndex, setPageIndex] = react.useState(0);
|
|
112
|
+
const [pageSize] = react.useState(pagination?.pageSize ?? 20);
|
|
113
|
+
const table = reactTable.useReactTable({
|
|
114
|
+
data,
|
|
115
|
+
columns,
|
|
116
|
+
state: {
|
|
117
|
+
sorting,
|
|
118
|
+
pagination: { pageIndex, pageSize }
|
|
119
|
+
},
|
|
120
|
+
onSortingChange: setSorting,
|
|
121
|
+
onPaginationChange: (updater) => {
|
|
122
|
+
const next = typeof updater === "function" ? updater({ pageIndex, pageSize }) : updater;
|
|
123
|
+
setPageIndex(next.pageIndex);
|
|
124
|
+
},
|
|
125
|
+
getCoreRowModel: reactTable.getCoreRowModel(),
|
|
126
|
+
getSortedRowModel: reactTable.getSortedRowModel(),
|
|
127
|
+
// C-29: exactOptionalPropertyTypes — conditional spread avoids assigning undefined
|
|
128
|
+
// to a non-optional property when pagination is absent.
|
|
129
|
+
...pagination !== void 0 ? { getPaginationRowModel: reactTable.getPaginationRowModel() } : {},
|
|
130
|
+
manualPagination: false
|
|
131
|
+
});
|
|
132
|
+
const pageCount = table.getPageCount();
|
|
133
|
+
if (loading) {
|
|
134
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `flex flex-col ${className}`, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-40 flex items-center justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500" }) }) });
|
|
135
|
+
}
|
|
136
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `flex flex-col ${className}`, children: [
|
|
137
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto rounded-lg border border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "min-w-full text-sm border-collapse", children: [
|
|
138
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
139
|
+
MultiRowHeader,
|
|
140
|
+
{
|
|
141
|
+
table,
|
|
142
|
+
...enableGroupToggle === true ? { enableGroupToggle: true } : {}
|
|
143
|
+
}
|
|
144
|
+
),
|
|
145
|
+
/* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "bg-white divide-y divide-gray-100", children: table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
146
|
+
"td",
|
|
147
|
+
{
|
|
148
|
+
colSpan: table.getAllLeafColumns().length,
|
|
149
|
+
className: "px-4 py-10 text-center text-gray-400",
|
|
150
|
+
children: emptyText
|
|
151
|
+
}
|
|
152
|
+
) }) : table.getRowModel().rows.map((row) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
153
|
+
"tr",
|
|
154
|
+
{
|
|
155
|
+
className: `hover:bg-gray-50 ${onRowClick ? "cursor-pointer" : ""}`,
|
|
156
|
+
onClick: (e) => onRowClick?.(row.original, e),
|
|
157
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
158
|
+
"td",
|
|
159
|
+
{
|
|
160
|
+
className: "px-4 py-3 whitespace-nowrap text-gray-700 border-r border-gray-100 last:border-r-0",
|
|
161
|
+
children: reactTable.flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
162
|
+
},
|
|
163
|
+
cell.id
|
|
164
|
+
))
|
|
165
|
+
},
|
|
166
|
+
row.id
|
|
167
|
+
)) })
|
|
168
|
+
] }) }),
|
|
169
|
+
pagination !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-2 py-3 text-sm text-gray-600", children: [
|
|
170
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
171
|
+
"\uC804\uCCB4 ",
|
|
172
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: table.getFilteredRowModel().rows.length }),
|
|
173
|
+
"\uAC74"
|
|
174
|
+
] }),
|
|
175
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [
|
|
176
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
177
|
+
"button",
|
|
178
|
+
{
|
|
179
|
+
onClick: () => table.setPageIndex(0),
|
|
180
|
+
disabled: !table.getCanPreviousPage(),
|
|
181
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
182
|
+
children: "\xAB"
|
|
183
|
+
}
|
|
184
|
+
),
|
|
185
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
186
|
+
"button",
|
|
187
|
+
{
|
|
188
|
+
onClick: () => table.previousPage(),
|
|
189
|
+
disabled: !table.getCanPreviousPage(),
|
|
190
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
191
|
+
children: "\u2039"
|
|
192
|
+
}
|
|
193
|
+
),
|
|
194
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "px-3", children: [
|
|
195
|
+
pageIndex + 1,
|
|
196
|
+
" / ",
|
|
197
|
+
pageCount || 1
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
200
|
+
"button",
|
|
201
|
+
{
|
|
202
|
+
onClick: () => table.nextPage(),
|
|
203
|
+
disabled: !table.getCanNextPage(),
|
|
204
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
205
|
+
children: "\u203A"
|
|
206
|
+
}
|
|
207
|
+
),
|
|
208
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
209
|
+
"button",
|
|
210
|
+
{
|
|
211
|
+
onClick: () => table.setPageIndex(pageCount - 1),
|
|
212
|
+
disabled: !table.getCanNextPage(),
|
|
213
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
214
|
+
children: "\xBB"
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
] })
|
|
218
|
+
] })
|
|
219
|
+
] });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/index.ts
|
|
223
|
+
gridLicense.checkLicense();
|
|
224
|
+
|
|
225
|
+
exports.GroupedHeaderGrid = GroupedHeaderGrid;
|
|
226
|
+
exports.MultiRowHeader = MultiRowHeader;
|
|
227
|
+
exports.createColumnGroup = createColumnGroup;
|
|
228
|
+
//# sourceMappingURL=index.cjs.map
|
|
229
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createColumnGroup.ts","../src/MultiRowHeader.tsx","../src/legacy/GroupedHeaderGrid.tsx","../src/index.ts"],"names":["useLicenseStatus","jsxs","jsx","Watermark","flexRender","useState","useReactTable","getCoreRowModel","getSortedRowModel","getPaginationRowModel","checkLicense"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\r\n * createColumnGroup — thin wrapper to create a TanStack `GroupColumnDef<TData>`.\r\n *\r\n * Provides TypeScript generic type-safety for the standard TanStack column grouping pattern:\r\n * `{ header: string; columns: ColumnDef<TData>[] }`.\r\n *\r\n * @example\r\n * ```typescript\r\n * const infoGroup = createColumnGroup({\r\n * header: '기본 정보',\r\n * columns: [nameCol, deptCol],\r\n * });\r\n * ```\r\n *\r\n * @see AC-001 in G-001-spec.md — createColumnGroup signature\r\n * @see Section 5.1 in G-001-spec.md — API design\r\n */\r\n\r\nimport type { ColumnDef, GroupColumnDef } from '@tanstack/react-table';\r\n\r\n/**\r\n * Config object for `createColumnGroup`.\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n */\r\nexport interface ColumnGroupConfig<TData> {\r\n /** The display label for the column group header. */\r\n header: string;\r\n /** Leaf (or nested group) column definitions belonging to this group. */\r\n columns: ColumnDef<TData>[];\r\n}\r\n\r\n/**\r\n * Creates a TanStack `GroupColumnDef<TData>` from a typed config object.\r\n *\r\n * This is a thin wrapper — no logic beyond type narrowing. The returned\r\n * object is identical to writing `{ header, columns }` inline, but provides\r\n * generic type-checking at the call site.\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n * @param config - `ColumnGroupConfig<TData>` with `header` and `columns`.\r\n * @returns A `GroupColumnDef<TData>` suitable for passing to `useReactTable`.\r\n */\r\nexport function createColumnGroup<TData>(\r\n config: ColumnGroupConfig<TData>,\r\n): GroupColumnDef<TData> {\r\n return {\r\n header: config.header,\r\n columns: config.columns,\r\n };\r\n}\r\n","/**\r\n * MultiRowHeader — renders a multi-row `<thead>` section from TanStack\r\n * `table.getHeaderGroups()`.\r\n *\r\n * Extracts the header rendering logic from GroupedHeaderGrid.tsx (L75-117)\r\n * into a reusable Pro-package component. Uses only TanStack v8 standard APIs\r\n * (C-2): `getHeaderGroups`, `header.isPlaceholder`, `header.colSpan`,\r\n * `header.subHeaders`, `flexRender`.\r\n *\r\n * G-002 additions: `enableStickyHeader` prop for multi-row sticky (AC-001),\r\n * `frozenColumns` prop as on/off switch for column pinning left-offset (AC-002),\r\n * CSS variable `--grid-header-row-height` based top offset (AC-003).\r\n *\r\n * G-003 additions: `enableGroupToggle` prop for group header click to toggle\r\n * child column visibility (AC-001). Group cells get collapse icon ▼/▶ and\r\n * click handler; leaf cells retain sort handler (AC-002, D2 decision).\r\n *\r\n * Flat columns mixed with group columns are handled automatically by TanStack's\r\n * placeholder mechanism — do NOT add custom rowSpan calculation (spec §11.2).\r\n *\r\n * @see AC-001, AC-002, AC-003 in G-002-spec.md\r\n * @see Section 5.1/5.2 in G-002-spec.md — API design\r\n * @see Section 11.2/11.4 in G-002-spec.md — implementation guidelines\r\n * @see Section 6.1, 11.1 in G-003-spec.md — enableGroupToggle implementation\r\n */\r\n\r\nimport { type CSSProperties } from 'react';\r\nimport { flexRender, type Header, type Table } from '@tanstack/react-table';\r\nimport { useLicenseStatus, Watermark } from '@topgrid/grid-license';\r\n\r\n/**\r\n * Props for `MultiRowHeader`.\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n */\r\nexport interface MultiRowHeaderProps<TData = unknown> {\r\n /**\r\n * The TanStack table instance. Provides `getHeaderGroups()` used for\r\n * multi-row header rendering.\r\n */\r\n table: Table<TData>;\r\n /**\r\n * When true, applies sticky positioning to each header row so the multi-row\r\n * header remains fixed at the viewport top during vertical scroll (AC-001).\r\n * Default: false (G-001 behaviour preserved — breaking: false).\r\n */\r\n enableStickyHeader?: boolean;\r\n /**\r\n * Number of columns pinned on the left that should receive `sticky left`\r\n * positioning. Acts as an on/off switch; the actual frozen column identities\r\n * are determined from TanStack's `columnPinning.left` state via\r\n * `column.getIsPinned() === 'left'` (AC-002, D2 decision).\r\n * 0 or omitted: frozen positioning inactive.\r\n */\r\n frozenColumns?: number;\r\n /**\r\n * When true, group header cells (non-leaf) become clickable toggles that\r\n * show/hide all child (leaf) columns at once (G-003, AC-001).\r\n * Clicking a group header that has all leaves hidden will show them all;\r\n * clicking one with any visible leaf will hide them all.\r\n * Leaf columns retain their sort click handler regardless.\r\n * Default: false (G-001/G-002 behaviour preserved — breaking: false).\r\n */\r\n enableGroupToggle?: boolean;\r\n}\r\n\r\n/**\r\n * Returns the left pixel offset for a frozen header cell (AC-002, EC-05).\r\n *\r\n * TanStack's `column.getStart('left')` returns the correct offset for leaf\r\n * columns. For group header cells it may return `undefined`; in that case the\r\n * first leaf child's offset is used as a fallback (spec §11.4 EC-05).\r\n *\r\n * @typeParam TData - Row data type (matches the parent table's TData).\r\n * @see G-002-spec.md Section 11.4\r\n */\r\nfunction getHeaderLeftOffset<TData>(header: Header<TData, unknown>): number {\r\n const direct = header.column.getStart('left');\r\n if (direct !== undefined) return direct;\r\n // EC-05 fallback: group header cell — use first leaf child's offset.\r\n const firstLeaf = header.subHeaders[0]?.column;\r\n return firstLeaf?.getStart('left') ?? 0;\r\n}\r\n\r\n/**\r\n * Renders a multi-row `<thead>` element from a TanStack table instance.\r\n *\r\n * Iterates `table.getHeaderGroups()` to produce one `<tr>` per header row.\r\n * Group header cells use `header.colSpan` (computed by TanStack automatically).\r\n * Placeholder cells (`header.isPlaceholder`) are rendered as empty `<th>` elements.\r\n * Sorting is enabled only on leaf columns (`!header.subHeaders.length`).\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n * @param props - `MultiRowHeaderProps<TData>`.\r\n * @returns A `<thead>` JSX element with all header rows.\r\n */\r\nexport function MultiRowHeader<TData>({\r\n table,\r\n enableStickyHeader,\r\n frozenColumns,\r\n enableGroupToggle,\r\n}: MultiRowHeaderProps<TData>): JSX.Element {\r\n // ADR-MOD-GRID-REFACTOR-2026-05-17-001 — license watermark wiring (H-D pattern)\r\n // sub-spec §8 Step 4: extra <tr><th colSpan> watermark row inside <thead>.\r\n // §9.3 D-3 = (a): sticky top-0 when enableStickyHeader is true.\r\n const _lic = useLicenseStatus();\r\n const headerGroups = table.getHeaderGroups();\r\n const visibleLeafCount = table.getVisibleLeafColumns().length;\r\n\r\n return (\r\n <thead className=\"bg-gray-50\">\r\n {_lic.watermarkRequired && visibleLeafCount > 0 ? (\r\n <tr\r\n {...(enableStickyHeader === true\r\n ? { className: 'sticky top-0 z-20' }\r\n : {})}\r\n >\r\n <th\r\n colSpan={visibleLeafCount}\r\n className=\"relative bg-yellow-50 px-4 py-1 text-center text-xs text-gray-500\"\r\n >\r\n <Watermark required />\r\n </th>\r\n </tr>\r\n ) : null}\r\n {headerGroups.map((headerGroup, rowIndex) => {\r\n // AC-001: sticky row props — C-29 conditional spread pattern.\r\n // Row 0: sticky top-0 z-10 (Tailwind only, no inline style needed).\r\n // Row N≥1: sticky z-10 + inline style top: calc(var(--grid-header-row-height, 40px) * N).\r\n const trProps = {\r\n ...(enableStickyHeader === true && rowIndex === 0 && {\r\n className: 'sticky top-0 z-10',\r\n }),\r\n ...(enableStickyHeader === true && rowIndex > 0 && {\r\n className: 'sticky z-10',\r\n style: {\r\n top: `calc(var(--grid-header-row-height, 40px) * ${rowIndex})`,\r\n } as CSSProperties,\r\n }),\r\n };\r\n\r\n return (\r\n <tr key={headerGroup.id} {...trProps}>\r\n {headerGroup.headers.map((header) => {\r\n // AC-002: frozen column detection via TanStack native API (D2 decision).\r\n // frozenColumns > 0 acts as the feature on/off switch;\r\n // actual pinned identity comes from TanStack columnPinning state.\r\n const isFrozen =\r\n (frozenColumns ?? 0) > 0 &&\r\n header.column.getIsPinned() === 'left';\r\n\r\n // D4: z-index layers.\r\n // frozen + sticky intersection → z-30.\r\n // frozen only (sticky off) → z-20.\r\n // Neither → no extra z-class.\r\n const frozenZClass =\r\n isFrozen && enableStickyHeader === true\r\n ? 'sticky z-30'\r\n : isFrozen\r\n ? 'sticky z-20'\r\n : '';\r\n\r\n // AC-002: left offset in px (inline style — dynamic runtime value, C-5 exception).\r\n // EC-05 fallback: group cell getStart('left') may be undefined → subHeaders[0] fallback.\r\n const thStyle: CSSProperties = isFrozen\r\n ? { left: `${getHeaderLeftOffset(header as Header<unknown, unknown>)}px` }\r\n : {};\r\n\r\n // Leaf columns: sorting enabled. Group columns: group toggle (if enabled).\r\n const isLeaf = header.subHeaders.length === 0;\r\n\r\n // G-003 AC-001: pre-compute allLeavesHidden once per non-leaf header.\r\n // Used for (a) click handler, (b) effectiveColSpan, (c) collapse icon.\r\n const allLeavesHidden =\r\n !isLeaf &&\r\n header.column.getLeafColumns().every((c) => !c.getIsVisible());\r\n\r\n // G-003 AC-001: group click handler — toggle all leaf column visibility.\r\n // allLeavesHidden → show all (true); any visible leaf → hide all (false).\r\n const groupClickHandler =\r\n enableGroupToggle === true && !isLeaf\r\n ? () => {\r\n const leafCols = header.column.getLeafColumns();\r\n leafCols.forEach((c) => c.toggleVisibility(allLeavesHidden));\r\n }\r\n : undefined;\r\n\r\n // G-003: effectiveColSpan — collapsed group occupies 1 column (spec §6.1).\r\n const effectiveColSpan =\r\n enableGroupToggle === true && allLeavesHidden\r\n ? 1\r\n : header.colSpan;\r\n\r\n // isPlaceholder: filler cell for multi-row headers (TanStack placeholder mechanism).\r\n // Flat columns in the group row appear as placeholder cells — rendered empty.\r\n if (header.isPlaceholder) {\r\n return (\r\n <th\r\n key={header.id}\r\n colSpan={effectiveColSpan}\r\n className={`px-4 py-3 border border-gray-200${frozenZClass ? ` ${frozenZClass}` : ''}`}\r\n {...(isFrozen ? { style: thStyle } : {})}\r\n />\r\n );\r\n }\r\n\r\n return (\r\n <th\r\n key={header.id}\r\n colSpan={effectiveColSpan}\r\n className={`px-4 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider border border-gray-200 whitespace-nowrap select-none${\r\n header.column.getCanSort() && isLeaf\r\n ? ' cursor-pointer hover:bg-gray-100'\r\n : ''\r\n }${\r\n enableGroupToggle === true && !isLeaf\r\n ? ' cursor-pointer hover:bg-gray-100'\r\n : ''\r\n }${frozenZClass ? ` ${frozenZClass}` : ''}`}\r\n {...(isFrozen ? { style: thStyle } : {})}\r\n onClick={\r\n isLeaf ? header.column.getToggleSortingHandler() : groupClickHandler\r\n }\r\n >\r\n <div className=\"flex items-center justify-center gap-1\">\r\n {flexRender(header.column.columnDef.header, header.getContext())}\r\n {header.column.getCanSort() && isLeaf && (\r\n <span className=\"text-gray-400\">\r\n {({ asc: '▲', desc: '▼' } as Record<string, string>)[\r\n header.column.getIsSorted() as string\r\n ] ?? '⇅'}\r\n </span>\r\n )}\r\n {enableGroupToggle === true && !isLeaf && (\r\n <span className=\"text-gray-400 ml-1\">\r\n {allLeavesHidden ? '▶' : '▼'}\r\n </span>\r\n )}\r\n </div>\r\n </th>\r\n );\r\n })}\r\n </tr>\r\n );\r\n })}\r\n </thead>\r\n );\r\n}\r\n","/**\r\n * GroupedHeaderGrid — legacy alias component (G-003, C-6 deprecation alias).\r\n *\r\n * Wraps `useReactTable` + `MultiRowHeader` + tbody + pagination into a single\r\n * self-contained component, preserving the AS-IS GroupedHeaderGrid API surface\r\n * from tw-framework-front/src/components/tomis/Grid/GroupedHeaderGrid.tsx (L0).\r\n *\r\n * Behaviour and classNames are ported verbatim from L0 (C-17 visual preservation).\r\n * `verifyOrWarn` is intentionally absent here — it runs via index.ts side-effect.\r\n *\r\n * D3 decision: inline type aliases for GridPaginationOptions / GridRowSelectionOptions\r\n * to avoid reverse dependency on tw-framework-front.\r\n *\r\n * @see G-003-spec.md Section 6.2 — legacy/GroupedHeaderGrid structure\r\n * @see C-6 — 1 minor version deprecation alias (breaking: false)\r\n * @see C-17 — AS-IS L0 className is authoritative\r\n */\r\n\r\nimport { useState } from 'react';\r\nimport type { MouseEvent } from 'react';\r\nimport {\r\n useReactTable,\r\n getCoreRowModel,\r\n getSortedRowModel,\r\n getPaginationRowModel,\r\n flexRender,\r\n type SortingState,\r\n type ColumnDef,\r\n} from '@tanstack/react-table';\r\nimport type { GridPaginationOptions, GridRowSelectionOptions } from '@topgrid/grid-core';\r\nimport { MultiRowHeader } from '../MultiRowHeader';\r\n\r\n/**\r\n * Props for the legacy `GroupedHeaderGrid` wrapper component.\r\n *\r\n * @typeParam TData - The row data type.\r\n * @deprecated Migrate to composing `useReactTable` + `MultiRowHeader` directly.\r\n */\r\nexport interface GroupedHeaderGridProps<TData = unknown> {\r\n data: TData[];\r\n /**\r\n * Pass grouped column definitions using TanStack Table's native column grouping.\r\n * Use `{ header: 'Group', columns: [...leafColumns] }` structure for grouping.\r\n */\r\n columns: ColumnDef<TData>[];\r\n pagination?: GridPaginationOptions;\r\n rowSelection?: GridRowSelectionOptions<TData>;\r\n onRowClick?: (row: TData, event: MouseEvent<HTMLTableRowElement>) => void;\r\n loading?: boolean;\r\n emptyText?: string;\r\n className?: string;\r\n /** G-003 AC-001: enable group header click to toggle child column visibility. */\r\n enableGroupToggle?: boolean;\r\n}\r\n\r\n/**\r\n * Legacy self-contained grid component with grouped multi-row headers.\r\n *\r\n * Delegates header rendering to `MultiRowHeader` from `@topgrid/grid-pro-header`.\r\n * tbody and pagination are ported verbatim from AS-IS L0 (C-17).\r\n *\r\n * @typeParam TData - The row data type (must extend `object`).\r\n * @deprecated Prefer composing `useReactTable` + `MultiRowHeader` directly.\r\n */\r\nexport function GroupedHeaderGrid<TData extends object>({\r\n data,\r\n columns,\r\n pagination,\r\n onRowClick,\r\n loading = false,\r\n emptyText = '데이터가 없습니다.',\r\n className = '',\r\n enableGroupToggle,\r\n}: GroupedHeaderGridProps<TData>): JSX.Element {\r\n const [sorting, setSorting] = useState<SortingState>([]);\r\n const [pageIndex, setPageIndex] = useState(0);\r\n const [pageSize] = useState(pagination?.pageSize ?? 20);\r\n\r\n const table = useReactTable({\r\n data,\r\n columns,\r\n state: {\r\n sorting,\r\n pagination: { pageIndex, pageSize },\r\n },\r\n onSortingChange: setSorting,\r\n onPaginationChange: (updater) => {\r\n const next =\r\n typeof updater === 'function' ? updater({ pageIndex, pageSize }) : updater;\r\n setPageIndex(next.pageIndex);\r\n },\r\n getCoreRowModel: getCoreRowModel(),\r\n getSortedRowModel: getSortedRowModel(),\r\n // C-29: exactOptionalPropertyTypes — conditional spread avoids assigning undefined\r\n // to a non-optional property when pagination is absent.\r\n ...(pagination !== undefined\r\n ? { getPaginationRowModel: getPaginationRowModel() }\r\n : {}),\r\n manualPagination: false,\r\n });\r\n\r\n const pageCount = table.getPageCount();\r\n\r\n if (loading) {\r\n return (\r\n <div className={`flex flex-col ${className}`}>\r\n <div className=\"h-40 flex items-center justify-center\">\r\n <div className=\"animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500\" />\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className={`flex flex-col ${className}`}>\r\n <div className=\"overflow-x-auto rounded-lg border border-gray-200\">\r\n <table className=\"min-w-full text-sm border-collapse\">\r\n <MultiRowHeader\r\n table={table}\r\n {...(enableGroupToggle === true ? { enableGroupToggle: true } : {})}\r\n />\r\n <tbody className=\"bg-white divide-y divide-gray-100\">\r\n {table.getRowModel().rows.length === 0 ? (\r\n <tr>\r\n <td\r\n colSpan={table.getAllLeafColumns().length}\r\n className=\"px-4 py-10 text-center text-gray-400\"\r\n >\r\n {emptyText}\r\n </td>\r\n </tr>\r\n ) : (\r\n table.getRowModel().rows.map((row) => (\r\n <tr\r\n key={row.id}\r\n className={`hover:bg-gray-50 ${onRowClick ? 'cursor-pointer' : ''}`}\r\n onClick={(e) => onRowClick?.(row.original, e)}\r\n >\r\n {row.getVisibleCells().map((cell) => (\r\n <td\r\n key={cell.id}\r\n className=\"px-4 py-3 whitespace-nowrap text-gray-700 border-r border-gray-100 last:border-r-0\"\r\n >\r\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\r\n </td>\r\n ))}\r\n </tr>\r\n ))\r\n )}\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n {pagination !== undefined && (\r\n <div className=\"flex items-center justify-between px-2 py-3 text-sm text-gray-600\">\r\n <span>\r\n 전체 <strong>{table.getFilteredRowModel().rows.length}</strong>건\r\n </span>\r\n <div className=\"flex items-center gap-1\">\r\n <button\r\n onClick={() => table.setPageIndex(0)}\r\n disabled={!table.getCanPreviousPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'«'}\r\n </button>\r\n <button\r\n onClick={() => table.previousPage()}\r\n disabled={!table.getCanPreviousPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'‹'}\r\n </button>\r\n <span className=\"px-3\">\r\n {pageIndex + 1} / {pageCount || 1}\r\n </span>\r\n <button\r\n onClick={() => table.nextPage()}\r\n disabled={!table.getCanNextPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'›'}\r\n </button>\r\n <button\r\n onClick={() => table.setPageIndex(pageCount - 1)}\r\n disabled={!table.getCanNextPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'»'}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { checkLicense } from '@topgrid/grid-license';\r\n\r\ncheckLicense();\r\n\r\n// createColumnGroup helper (AC-001)\r\nexport { createColumnGroup } from './createColumnGroup';\r\nexport type { ColumnGroupConfig } from './types';\r\n\r\n// MultiRowHeader component (AC-002, AC-003, AC-004)\r\nexport { MultiRowHeader } from './MultiRowHeader';\r\nexport type { MultiRowHeaderProps } from './types';\r\n\r\n// GroupedHeaderGrid legacy alias (G-003, C-6 deprecation alias)\r\nexport { GroupedHeaderGrid } from './legacy/GroupedHeaderGrid';\r\nexport type { GroupedHeaderGridProps } from './legacy/GroupedHeaderGrid';\r\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ColumnDef, GroupColumnDef, Table } from '@tanstack/react-table';
|
|
2
|
+
import { MouseEvent } from 'react';
|
|
3
|
+
import { GridPaginationOptions, GridRowSelectionOptions } from '@topgrid/grid-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* createColumnGroup — thin wrapper to create a TanStack `GroupColumnDef<TData>`.
|
|
7
|
+
*
|
|
8
|
+
* Provides TypeScript generic type-safety for the standard TanStack column grouping pattern:
|
|
9
|
+
* `{ header: string; columns: ColumnDef<TData>[] }`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const infoGroup = createColumnGroup({
|
|
14
|
+
* header: '기본 정보',
|
|
15
|
+
* columns: [nameCol, deptCol],
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @see AC-001 in G-001-spec.md — createColumnGroup signature
|
|
20
|
+
* @see Section 5.1 in G-001-spec.md — API design
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Config object for `createColumnGroup`.
|
|
25
|
+
*
|
|
26
|
+
* @typeParam TData - The row data type of the table.
|
|
27
|
+
*/
|
|
28
|
+
interface ColumnGroupConfig<TData> {
|
|
29
|
+
/** The display label for the column group header. */
|
|
30
|
+
header: string;
|
|
31
|
+
/** Leaf (or nested group) column definitions belonging to this group. */
|
|
32
|
+
columns: ColumnDef<TData>[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a TanStack `GroupColumnDef<TData>` from a typed config object.
|
|
36
|
+
*
|
|
37
|
+
* This is a thin wrapper — no logic beyond type narrowing. The returned
|
|
38
|
+
* object is identical to writing `{ header, columns }` inline, but provides
|
|
39
|
+
* generic type-checking at the call site.
|
|
40
|
+
*
|
|
41
|
+
* @typeParam TData - The row data type of the table.
|
|
42
|
+
* @param config - `ColumnGroupConfig<TData>` with `header` and `columns`.
|
|
43
|
+
* @returns A `GroupColumnDef<TData>` suitable for passing to `useReactTable`.
|
|
44
|
+
*/
|
|
45
|
+
declare function createColumnGroup<TData>(config: ColumnGroupConfig<TData>): GroupColumnDef<TData>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* MultiRowHeader — renders a multi-row `<thead>` section from TanStack
|
|
49
|
+
* `table.getHeaderGroups()`.
|
|
50
|
+
*
|
|
51
|
+
* Extracts the header rendering logic from GroupedHeaderGrid.tsx (L75-117)
|
|
52
|
+
* into a reusable Pro-package component. Uses only TanStack v8 standard APIs
|
|
53
|
+
* (C-2): `getHeaderGroups`, `header.isPlaceholder`, `header.colSpan`,
|
|
54
|
+
* `header.subHeaders`, `flexRender`.
|
|
55
|
+
*
|
|
56
|
+
* G-002 additions: `enableStickyHeader` prop for multi-row sticky (AC-001),
|
|
57
|
+
* `frozenColumns` prop as on/off switch for column pinning left-offset (AC-002),
|
|
58
|
+
* CSS variable `--grid-header-row-height` based top offset (AC-003).
|
|
59
|
+
*
|
|
60
|
+
* G-003 additions: `enableGroupToggle` prop for group header click to toggle
|
|
61
|
+
* child column visibility (AC-001). Group cells get collapse icon ▼/▶ and
|
|
62
|
+
* click handler; leaf cells retain sort handler (AC-002, D2 decision).
|
|
63
|
+
*
|
|
64
|
+
* Flat columns mixed with group columns are handled automatically by TanStack's
|
|
65
|
+
* placeholder mechanism — do NOT add custom rowSpan calculation (spec §11.2).
|
|
66
|
+
*
|
|
67
|
+
* @see AC-001, AC-002, AC-003 in G-002-spec.md
|
|
68
|
+
* @see Section 5.1/5.2 in G-002-spec.md — API design
|
|
69
|
+
* @see Section 11.2/11.4 in G-002-spec.md — implementation guidelines
|
|
70
|
+
* @see Section 6.1, 11.1 in G-003-spec.md — enableGroupToggle implementation
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Props for `MultiRowHeader`.
|
|
75
|
+
*
|
|
76
|
+
* @typeParam TData - The row data type of the table.
|
|
77
|
+
*/
|
|
78
|
+
interface MultiRowHeaderProps<TData = unknown> {
|
|
79
|
+
/**
|
|
80
|
+
* The TanStack table instance. Provides `getHeaderGroups()` used for
|
|
81
|
+
* multi-row header rendering.
|
|
82
|
+
*/
|
|
83
|
+
table: Table<TData>;
|
|
84
|
+
/**
|
|
85
|
+
* When true, applies sticky positioning to each header row so the multi-row
|
|
86
|
+
* header remains fixed at the viewport top during vertical scroll (AC-001).
|
|
87
|
+
* Default: false (G-001 behaviour preserved — breaking: false).
|
|
88
|
+
*/
|
|
89
|
+
enableStickyHeader?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Number of columns pinned on the left that should receive `sticky left`
|
|
92
|
+
* positioning. Acts as an on/off switch; the actual frozen column identities
|
|
93
|
+
* are determined from TanStack's `columnPinning.left` state via
|
|
94
|
+
* `column.getIsPinned() === 'left'` (AC-002, D2 decision).
|
|
95
|
+
* 0 or omitted: frozen positioning inactive.
|
|
96
|
+
*/
|
|
97
|
+
frozenColumns?: number;
|
|
98
|
+
/**
|
|
99
|
+
* When true, group header cells (non-leaf) become clickable toggles that
|
|
100
|
+
* show/hide all child (leaf) columns at once (G-003, AC-001).
|
|
101
|
+
* Clicking a group header that has all leaves hidden will show them all;
|
|
102
|
+
* clicking one with any visible leaf will hide them all.
|
|
103
|
+
* Leaf columns retain their sort click handler regardless.
|
|
104
|
+
* Default: false (G-001/G-002 behaviour preserved — breaking: false).
|
|
105
|
+
*/
|
|
106
|
+
enableGroupToggle?: boolean;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Renders a multi-row `<thead>` element from a TanStack table instance.
|
|
110
|
+
*
|
|
111
|
+
* Iterates `table.getHeaderGroups()` to produce one `<tr>` per header row.
|
|
112
|
+
* Group header cells use `header.colSpan` (computed by TanStack automatically).
|
|
113
|
+
* Placeholder cells (`header.isPlaceholder`) are rendered as empty `<th>` elements.
|
|
114
|
+
* Sorting is enabled only on leaf columns (`!header.subHeaders.length`).
|
|
115
|
+
*
|
|
116
|
+
* @typeParam TData - The row data type of the table.
|
|
117
|
+
* @param props - `MultiRowHeaderProps<TData>`.
|
|
118
|
+
* @returns A `<thead>` JSX element with all header rows.
|
|
119
|
+
*/
|
|
120
|
+
declare function MultiRowHeader<TData>({ table, enableStickyHeader, frozenColumns, enableGroupToggle, }: MultiRowHeaderProps<TData>): JSX.Element;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* GroupedHeaderGrid — legacy alias component (G-003, C-6 deprecation alias).
|
|
124
|
+
*
|
|
125
|
+
* Wraps `useReactTable` + `MultiRowHeader` + tbody + pagination into a single
|
|
126
|
+
* self-contained component, preserving the AS-IS GroupedHeaderGrid API surface
|
|
127
|
+
* from tw-framework-front/src/components/tomis/Grid/GroupedHeaderGrid.tsx (L0).
|
|
128
|
+
*
|
|
129
|
+
* Behaviour and classNames are ported verbatim from L0 (C-17 visual preservation).
|
|
130
|
+
* `verifyOrWarn` is intentionally absent here — it runs via index.ts side-effect.
|
|
131
|
+
*
|
|
132
|
+
* D3 decision: inline type aliases for GridPaginationOptions / GridRowSelectionOptions
|
|
133
|
+
* to avoid reverse dependency on tw-framework-front.
|
|
134
|
+
*
|
|
135
|
+
* @see G-003-spec.md Section 6.2 — legacy/GroupedHeaderGrid structure
|
|
136
|
+
* @see C-6 — 1 minor version deprecation alias (breaking: false)
|
|
137
|
+
* @see C-17 — AS-IS L0 className is authoritative
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Props for the legacy `GroupedHeaderGrid` wrapper component.
|
|
142
|
+
*
|
|
143
|
+
* @typeParam TData - The row data type.
|
|
144
|
+
* @deprecated Migrate to composing `useReactTable` + `MultiRowHeader` directly.
|
|
145
|
+
*/
|
|
146
|
+
interface GroupedHeaderGridProps<TData = unknown> {
|
|
147
|
+
data: TData[];
|
|
148
|
+
/**
|
|
149
|
+
* Pass grouped column definitions using TanStack Table's native column grouping.
|
|
150
|
+
* Use `{ header: 'Group', columns: [...leafColumns] }` structure for grouping.
|
|
151
|
+
*/
|
|
152
|
+
columns: ColumnDef<TData>[];
|
|
153
|
+
pagination?: GridPaginationOptions;
|
|
154
|
+
rowSelection?: GridRowSelectionOptions<TData>;
|
|
155
|
+
onRowClick?: (row: TData, event: MouseEvent<HTMLTableRowElement>) => void;
|
|
156
|
+
loading?: boolean;
|
|
157
|
+
emptyText?: string;
|
|
158
|
+
className?: string;
|
|
159
|
+
/** G-003 AC-001: enable group header click to toggle child column visibility. */
|
|
160
|
+
enableGroupToggle?: boolean;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Legacy self-contained grid component with grouped multi-row headers.
|
|
164
|
+
*
|
|
165
|
+
* Delegates header rendering to `MultiRowHeader` from `@topgrid/grid-pro-header`.
|
|
166
|
+
* tbody and pagination are ported verbatim from AS-IS L0 (C-17).
|
|
167
|
+
*
|
|
168
|
+
* @typeParam TData - The row data type (must extend `object`).
|
|
169
|
+
* @deprecated Prefer composing `useReactTable` + `MultiRowHeader` directly.
|
|
170
|
+
*/
|
|
171
|
+
declare function GroupedHeaderGrid<TData extends object>({ data, columns, pagination, onRowClick, loading, emptyText, className, enableGroupToggle, }: GroupedHeaderGridProps<TData>): JSX.Element;
|
|
172
|
+
|
|
173
|
+
export { type ColumnGroupConfig, GroupedHeaderGrid, type GroupedHeaderGridProps, MultiRowHeader, type MultiRowHeaderProps, createColumnGroup };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { ColumnDef, GroupColumnDef, Table } from '@tanstack/react-table';
|
|
2
|
+
import { MouseEvent } from 'react';
|
|
3
|
+
import { GridPaginationOptions, GridRowSelectionOptions } from '@topgrid/grid-core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* createColumnGroup — thin wrapper to create a TanStack `GroupColumnDef<TData>`.
|
|
7
|
+
*
|
|
8
|
+
* Provides TypeScript generic type-safety for the standard TanStack column grouping pattern:
|
|
9
|
+
* `{ header: string; columns: ColumnDef<TData>[] }`.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const infoGroup = createColumnGroup({
|
|
14
|
+
* header: '기본 정보',
|
|
15
|
+
* columns: [nameCol, deptCol],
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @see AC-001 in G-001-spec.md — createColumnGroup signature
|
|
20
|
+
* @see Section 5.1 in G-001-spec.md — API design
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Config object for `createColumnGroup`.
|
|
25
|
+
*
|
|
26
|
+
* @typeParam TData - The row data type of the table.
|
|
27
|
+
*/
|
|
28
|
+
interface ColumnGroupConfig<TData> {
|
|
29
|
+
/** The display label for the column group header. */
|
|
30
|
+
header: string;
|
|
31
|
+
/** Leaf (or nested group) column definitions belonging to this group. */
|
|
32
|
+
columns: ColumnDef<TData>[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a TanStack `GroupColumnDef<TData>` from a typed config object.
|
|
36
|
+
*
|
|
37
|
+
* This is a thin wrapper — no logic beyond type narrowing. The returned
|
|
38
|
+
* object is identical to writing `{ header, columns }` inline, but provides
|
|
39
|
+
* generic type-checking at the call site.
|
|
40
|
+
*
|
|
41
|
+
* @typeParam TData - The row data type of the table.
|
|
42
|
+
* @param config - `ColumnGroupConfig<TData>` with `header` and `columns`.
|
|
43
|
+
* @returns A `GroupColumnDef<TData>` suitable for passing to `useReactTable`.
|
|
44
|
+
*/
|
|
45
|
+
declare function createColumnGroup<TData>(config: ColumnGroupConfig<TData>): GroupColumnDef<TData>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* MultiRowHeader — renders a multi-row `<thead>` section from TanStack
|
|
49
|
+
* `table.getHeaderGroups()`.
|
|
50
|
+
*
|
|
51
|
+
* Extracts the header rendering logic from GroupedHeaderGrid.tsx (L75-117)
|
|
52
|
+
* into a reusable Pro-package component. Uses only TanStack v8 standard APIs
|
|
53
|
+
* (C-2): `getHeaderGroups`, `header.isPlaceholder`, `header.colSpan`,
|
|
54
|
+
* `header.subHeaders`, `flexRender`.
|
|
55
|
+
*
|
|
56
|
+
* G-002 additions: `enableStickyHeader` prop for multi-row sticky (AC-001),
|
|
57
|
+
* `frozenColumns` prop as on/off switch for column pinning left-offset (AC-002),
|
|
58
|
+
* CSS variable `--grid-header-row-height` based top offset (AC-003).
|
|
59
|
+
*
|
|
60
|
+
* G-003 additions: `enableGroupToggle` prop for group header click to toggle
|
|
61
|
+
* child column visibility (AC-001). Group cells get collapse icon ▼/▶ and
|
|
62
|
+
* click handler; leaf cells retain sort handler (AC-002, D2 decision).
|
|
63
|
+
*
|
|
64
|
+
* Flat columns mixed with group columns are handled automatically by TanStack's
|
|
65
|
+
* placeholder mechanism — do NOT add custom rowSpan calculation (spec §11.2).
|
|
66
|
+
*
|
|
67
|
+
* @see AC-001, AC-002, AC-003 in G-002-spec.md
|
|
68
|
+
* @see Section 5.1/5.2 in G-002-spec.md — API design
|
|
69
|
+
* @see Section 11.2/11.4 in G-002-spec.md — implementation guidelines
|
|
70
|
+
* @see Section 6.1, 11.1 in G-003-spec.md — enableGroupToggle implementation
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Props for `MultiRowHeader`.
|
|
75
|
+
*
|
|
76
|
+
* @typeParam TData - The row data type of the table.
|
|
77
|
+
*/
|
|
78
|
+
interface MultiRowHeaderProps<TData = unknown> {
|
|
79
|
+
/**
|
|
80
|
+
* The TanStack table instance. Provides `getHeaderGroups()` used for
|
|
81
|
+
* multi-row header rendering.
|
|
82
|
+
*/
|
|
83
|
+
table: Table<TData>;
|
|
84
|
+
/**
|
|
85
|
+
* When true, applies sticky positioning to each header row so the multi-row
|
|
86
|
+
* header remains fixed at the viewport top during vertical scroll (AC-001).
|
|
87
|
+
* Default: false (G-001 behaviour preserved — breaking: false).
|
|
88
|
+
*/
|
|
89
|
+
enableStickyHeader?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Number of columns pinned on the left that should receive `sticky left`
|
|
92
|
+
* positioning. Acts as an on/off switch; the actual frozen column identities
|
|
93
|
+
* are determined from TanStack's `columnPinning.left` state via
|
|
94
|
+
* `column.getIsPinned() === 'left'` (AC-002, D2 decision).
|
|
95
|
+
* 0 or omitted: frozen positioning inactive.
|
|
96
|
+
*/
|
|
97
|
+
frozenColumns?: number;
|
|
98
|
+
/**
|
|
99
|
+
* When true, group header cells (non-leaf) become clickable toggles that
|
|
100
|
+
* show/hide all child (leaf) columns at once (G-003, AC-001).
|
|
101
|
+
* Clicking a group header that has all leaves hidden will show them all;
|
|
102
|
+
* clicking one with any visible leaf will hide them all.
|
|
103
|
+
* Leaf columns retain their sort click handler regardless.
|
|
104
|
+
* Default: false (G-001/G-002 behaviour preserved — breaking: false).
|
|
105
|
+
*/
|
|
106
|
+
enableGroupToggle?: boolean;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Renders a multi-row `<thead>` element from a TanStack table instance.
|
|
110
|
+
*
|
|
111
|
+
* Iterates `table.getHeaderGroups()` to produce one `<tr>` per header row.
|
|
112
|
+
* Group header cells use `header.colSpan` (computed by TanStack automatically).
|
|
113
|
+
* Placeholder cells (`header.isPlaceholder`) are rendered as empty `<th>` elements.
|
|
114
|
+
* Sorting is enabled only on leaf columns (`!header.subHeaders.length`).
|
|
115
|
+
*
|
|
116
|
+
* @typeParam TData - The row data type of the table.
|
|
117
|
+
* @param props - `MultiRowHeaderProps<TData>`.
|
|
118
|
+
* @returns A `<thead>` JSX element with all header rows.
|
|
119
|
+
*/
|
|
120
|
+
declare function MultiRowHeader<TData>({ table, enableStickyHeader, frozenColumns, enableGroupToggle, }: MultiRowHeaderProps<TData>): JSX.Element;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* GroupedHeaderGrid — legacy alias component (G-003, C-6 deprecation alias).
|
|
124
|
+
*
|
|
125
|
+
* Wraps `useReactTable` + `MultiRowHeader` + tbody + pagination into a single
|
|
126
|
+
* self-contained component, preserving the AS-IS GroupedHeaderGrid API surface
|
|
127
|
+
* from tw-framework-front/src/components/tomis/Grid/GroupedHeaderGrid.tsx (L0).
|
|
128
|
+
*
|
|
129
|
+
* Behaviour and classNames are ported verbatim from L0 (C-17 visual preservation).
|
|
130
|
+
* `verifyOrWarn` is intentionally absent here — it runs via index.ts side-effect.
|
|
131
|
+
*
|
|
132
|
+
* D3 decision: inline type aliases for GridPaginationOptions / GridRowSelectionOptions
|
|
133
|
+
* to avoid reverse dependency on tw-framework-front.
|
|
134
|
+
*
|
|
135
|
+
* @see G-003-spec.md Section 6.2 — legacy/GroupedHeaderGrid structure
|
|
136
|
+
* @see C-6 — 1 minor version deprecation alias (breaking: false)
|
|
137
|
+
* @see C-17 — AS-IS L0 className is authoritative
|
|
138
|
+
*/
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Props for the legacy `GroupedHeaderGrid` wrapper component.
|
|
142
|
+
*
|
|
143
|
+
* @typeParam TData - The row data type.
|
|
144
|
+
* @deprecated Migrate to composing `useReactTable` + `MultiRowHeader` directly.
|
|
145
|
+
*/
|
|
146
|
+
interface GroupedHeaderGridProps<TData = unknown> {
|
|
147
|
+
data: TData[];
|
|
148
|
+
/**
|
|
149
|
+
* Pass grouped column definitions using TanStack Table's native column grouping.
|
|
150
|
+
* Use `{ header: 'Group', columns: [...leafColumns] }` structure for grouping.
|
|
151
|
+
*/
|
|
152
|
+
columns: ColumnDef<TData>[];
|
|
153
|
+
pagination?: GridPaginationOptions;
|
|
154
|
+
rowSelection?: GridRowSelectionOptions<TData>;
|
|
155
|
+
onRowClick?: (row: TData, event: MouseEvent<HTMLTableRowElement>) => void;
|
|
156
|
+
loading?: boolean;
|
|
157
|
+
emptyText?: string;
|
|
158
|
+
className?: string;
|
|
159
|
+
/** G-003 AC-001: enable group header click to toggle child column visibility. */
|
|
160
|
+
enableGroupToggle?: boolean;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Legacy self-contained grid component with grouped multi-row headers.
|
|
164
|
+
*
|
|
165
|
+
* Delegates header rendering to `MultiRowHeader` from `@topgrid/grid-pro-header`.
|
|
166
|
+
* tbody and pagination are ported verbatim from AS-IS L0 (C-17).
|
|
167
|
+
*
|
|
168
|
+
* @typeParam TData - The row data type (must extend `object`).
|
|
169
|
+
* @deprecated Prefer composing `useReactTable` + `MultiRowHeader` directly.
|
|
170
|
+
*/
|
|
171
|
+
declare function GroupedHeaderGrid<TData extends object>({ data, columns, pagination, onRowClick, loading, emptyText, className, enableGroupToggle, }: GroupedHeaderGridProps<TData>): JSX.Element;
|
|
172
|
+
|
|
173
|
+
export { type ColumnGroupConfig, GroupedHeaderGrid, type GroupedHeaderGridProps, MultiRowHeader, type MultiRowHeaderProps, createColumnGroup };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { checkLicense, useLicenseStatus, Watermark } from '@topgrid/grid-license';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { flexRender, useReactTable, getPaginationRowModel, getSortedRowModel, getCoreRowModel } from '@tanstack/react-table';
|
|
4
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/index.ts
|
|
7
|
+
|
|
8
|
+
// src/createColumnGroup.ts
|
|
9
|
+
function createColumnGroup(config) {
|
|
10
|
+
return {
|
|
11
|
+
header: config.header,
|
|
12
|
+
columns: config.columns
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function getHeaderLeftOffset(header) {
|
|
16
|
+
const direct = header.column.getStart("left");
|
|
17
|
+
if (direct !== void 0) return direct;
|
|
18
|
+
const firstLeaf = header.subHeaders[0]?.column;
|
|
19
|
+
return firstLeaf?.getStart("left") ?? 0;
|
|
20
|
+
}
|
|
21
|
+
function MultiRowHeader({
|
|
22
|
+
table,
|
|
23
|
+
enableStickyHeader,
|
|
24
|
+
frozenColumns,
|
|
25
|
+
enableGroupToggle
|
|
26
|
+
}) {
|
|
27
|
+
const _lic = useLicenseStatus();
|
|
28
|
+
const headerGroups = table.getHeaderGroups();
|
|
29
|
+
const visibleLeafCount = table.getVisibleLeafColumns().length;
|
|
30
|
+
return /* @__PURE__ */ jsxs("thead", { className: "bg-gray-50", children: [
|
|
31
|
+
_lic.watermarkRequired && visibleLeafCount > 0 ? /* @__PURE__ */ jsx(
|
|
32
|
+
"tr",
|
|
33
|
+
{
|
|
34
|
+
...enableStickyHeader === true ? { className: "sticky top-0 z-20" } : {},
|
|
35
|
+
children: /* @__PURE__ */ jsx(
|
|
36
|
+
"th",
|
|
37
|
+
{
|
|
38
|
+
colSpan: visibleLeafCount,
|
|
39
|
+
className: "relative bg-yellow-50 px-4 py-1 text-center text-xs text-gray-500",
|
|
40
|
+
children: /* @__PURE__ */ jsx(Watermark, { required: true })
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
) : null,
|
|
45
|
+
headerGroups.map((headerGroup, rowIndex) => {
|
|
46
|
+
const trProps = {
|
|
47
|
+
...enableStickyHeader === true && rowIndex === 0 && {
|
|
48
|
+
className: "sticky top-0 z-10"
|
|
49
|
+
},
|
|
50
|
+
...enableStickyHeader === true && rowIndex > 0 && {
|
|
51
|
+
className: "sticky z-10",
|
|
52
|
+
style: {
|
|
53
|
+
top: `calc(var(--grid-header-row-height, 40px) * ${rowIndex})`
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return /* @__PURE__ */ jsx("tr", { ...trProps, children: headerGroup.headers.map((header) => {
|
|
58
|
+
const isFrozen = (frozenColumns ?? 0) > 0 && header.column.getIsPinned() === "left";
|
|
59
|
+
const frozenZClass = isFrozen && enableStickyHeader === true ? "sticky z-30" : isFrozen ? "sticky z-20" : "";
|
|
60
|
+
const thStyle = isFrozen ? { left: `${getHeaderLeftOffset(header)}px` } : {};
|
|
61
|
+
const isLeaf = header.subHeaders.length === 0;
|
|
62
|
+
const allLeavesHidden = !isLeaf && header.column.getLeafColumns().every((c) => !c.getIsVisible());
|
|
63
|
+
const groupClickHandler = enableGroupToggle === true && !isLeaf ? () => {
|
|
64
|
+
const leafCols = header.column.getLeafColumns();
|
|
65
|
+
leafCols.forEach((c) => c.toggleVisibility(allLeavesHidden));
|
|
66
|
+
} : void 0;
|
|
67
|
+
const effectiveColSpan = enableGroupToggle === true && allLeavesHidden ? 1 : header.colSpan;
|
|
68
|
+
if (header.isPlaceholder) {
|
|
69
|
+
return /* @__PURE__ */ jsx(
|
|
70
|
+
"th",
|
|
71
|
+
{
|
|
72
|
+
colSpan: effectiveColSpan,
|
|
73
|
+
className: `px-4 py-3 border border-gray-200${frozenZClass ? ` ${frozenZClass}` : ""}`,
|
|
74
|
+
...isFrozen ? { style: thStyle } : {}
|
|
75
|
+
},
|
|
76
|
+
header.id
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
"th",
|
|
81
|
+
{
|
|
82
|
+
colSpan: effectiveColSpan,
|
|
83
|
+
className: `px-4 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider border border-gray-200 whitespace-nowrap select-none${header.column.getCanSort() && isLeaf ? " cursor-pointer hover:bg-gray-100" : ""}${enableGroupToggle === true && !isLeaf ? " cursor-pointer hover:bg-gray-100" : ""}${frozenZClass ? ` ${frozenZClass}` : ""}`,
|
|
84
|
+
...isFrozen ? { style: thStyle } : {},
|
|
85
|
+
onClick: isLeaf ? header.column.getToggleSortingHandler() : groupClickHandler,
|
|
86
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-1", children: [
|
|
87
|
+
flexRender(header.column.columnDef.header, header.getContext()),
|
|
88
|
+
header.column.getCanSort() && isLeaf && /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: { asc: "\u25B2", desc: "\u25BC" }[header.column.getIsSorted()] ?? "\u21C5" }),
|
|
89
|
+
enableGroupToggle === true && !isLeaf && /* @__PURE__ */ jsx("span", { className: "text-gray-400 ml-1", children: allLeavesHidden ? "\u25B6" : "\u25BC" })
|
|
90
|
+
] })
|
|
91
|
+
},
|
|
92
|
+
header.id
|
|
93
|
+
);
|
|
94
|
+
}) }, headerGroup.id);
|
|
95
|
+
})
|
|
96
|
+
] });
|
|
97
|
+
}
|
|
98
|
+
function GroupedHeaderGrid({
|
|
99
|
+
data,
|
|
100
|
+
columns,
|
|
101
|
+
pagination,
|
|
102
|
+
onRowClick,
|
|
103
|
+
loading = false,
|
|
104
|
+
emptyText = "\uB370\uC774\uD130\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
|
|
105
|
+
className = "",
|
|
106
|
+
enableGroupToggle
|
|
107
|
+
}) {
|
|
108
|
+
const [sorting, setSorting] = useState([]);
|
|
109
|
+
const [pageIndex, setPageIndex] = useState(0);
|
|
110
|
+
const [pageSize] = useState(pagination?.pageSize ?? 20);
|
|
111
|
+
const table = useReactTable({
|
|
112
|
+
data,
|
|
113
|
+
columns,
|
|
114
|
+
state: {
|
|
115
|
+
sorting,
|
|
116
|
+
pagination: { pageIndex, pageSize }
|
|
117
|
+
},
|
|
118
|
+
onSortingChange: setSorting,
|
|
119
|
+
onPaginationChange: (updater) => {
|
|
120
|
+
const next = typeof updater === "function" ? updater({ pageIndex, pageSize }) : updater;
|
|
121
|
+
setPageIndex(next.pageIndex);
|
|
122
|
+
},
|
|
123
|
+
getCoreRowModel: getCoreRowModel(),
|
|
124
|
+
getSortedRowModel: getSortedRowModel(),
|
|
125
|
+
// C-29: exactOptionalPropertyTypes — conditional spread avoids assigning undefined
|
|
126
|
+
// to a non-optional property when pagination is absent.
|
|
127
|
+
...pagination !== void 0 ? { getPaginationRowModel: getPaginationRowModel() } : {},
|
|
128
|
+
manualPagination: false
|
|
129
|
+
});
|
|
130
|
+
const pageCount = table.getPageCount();
|
|
131
|
+
if (loading) {
|
|
132
|
+
return /* @__PURE__ */ jsx("div", { className: `flex flex-col ${className}`, children: /* @__PURE__ */ jsx("div", { className: "h-40 flex items-center justify-center", children: /* @__PURE__ */ jsx("div", { className: "animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500" }) }) });
|
|
133
|
+
}
|
|
134
|
+
return /* @__PURE__ */ jsxs("div", { className: `flex flex-col ${className}`, children: [
|
|
135
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border border-gray-200", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full text-sm border-collapse", children: [
|
|
136
|
+
/* @__PURE__ */ jsx(
|
|
137
|
+
MultiRowHeader,
|
|
138
|
+
{
|
|
139
|
+
table,
|
|
140
|
+
...enableGroupToggle === true ? { enableGroupToggle: true } : {}
|
|
141
|
+
}
|
|
142
|
+
),
|
|
143
|
+
/* @__PURE__ */ jsx("tbody", { className: "bg-white divide-y divide-gray-100", children: table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx(
|
|
144
|
+
"td",
|
|
145
|
+
{
|
|
146
|
+
colSpan: table.getAllLeafColumns().length,
|
|
147
|
+
className: "px-4 py-10 text-center text-gray-400",
|
|
148
|
+
children: emptyText
|
|
149
|
+
}
|
|
150
|
+
) }) : table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx(
|
|
151
|
+
"tr",
|
|
152
|
+
{
|
|
153
|
+
className: `hover:bg-gray-50 ${onRowClick ? "cursor-pointer" : ""}`,
|
|
154
|
+
onClick: (e) => onRowClick?.(row.original, e),
|
|
155
|
+
children: row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx(
|
|
156
|
+
"td",
|
|
157
|
+
{
|
|
158
|
+
className: "px-4 py-3 whitespace-nowrap text-gray-700 border-r border-gray-100 last:border-r-0",
|
|
159
|
+
children: flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
160
|
+
},
|
|
161
|
+
cell.id
|
|
162
|
+
))
|
|
163
|
+
},
|
|
164
|
+
row.id
|
|
165
|
+
)) })
|
|
166
|
+
] }) }),
|
|
167
|
+
pagination !== void 0 && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-2 py-3 text-sm text-gray-600", children: [
|
|
168
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
169
|
+
"\uC804\uCCB4 ",
|
|
170
|
+
/* @__PURE__ */ jsx("strong", { children: table.getFilteredRowModel().rows.length }),
|
|
171
|
+
"\uAC74"
|
|
172
|
+
] }),
|
|
173
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
174
|
+
/* @__PURE__ */ jsx(
|
|
175
|
+
"button",
|
|
176
|
+
{
|
|
177
|
+
onClick: () => table.setPageIndex(0),
|
|
178
|
+
disabled: !table.getCanPreviousPage(),
|
|
179
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
180
|
+
children: "\xAB"
|
|
181
|
+
}
|
|
182
|
+
),
|
|
183
|
+
/* @__PURE__ */ jsx(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
onClick: () => table.previousPage(),
|
|
187
|
+
disabled: !table.getCanPreviousPage(),
|
|
188
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
189
|
+
children: "\u2039"
|
|
190
|
+
}
|
|
191
|
+
),
|
|
192
|
+
/* @__PURE__ */ jsxs("span", { className: "px-3", children: [
|
|
193
|
+
pageIndex + 1,
|
|
194
|
+
" / ",
|
|
195
|
+
pageCount || 1
|
|
196
|
+
] }),
|
|
197
|
+
/* @__PURE__ */ jsx(
|
|
198
|
+
"button",
|
|
199
|
+
{
|
|
200
|
+
onClick: () => table.nextPage(),
|
|
201
|
+
disabled: !table.getCanNextPage(),
|
|
202
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
203
|
+
children: "\u203A"
|
|
204
|
+
}
|
|
205
|
+
),
|
|
206
|
+
/* @__PURE__ */ jsx(
|
|
207
|
+
"button",
|
|
208
|
+
{
|
|
209
|
+
onClick: () => table.setPageIndex(pageCount - 1),
|
|
210
|
+
disabled: !table.getCanNextPage(),
|
|
211
|
+
className: "px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100",
|
|
212
|
+
children: "\xBB"
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
] })
|
|
216
|
+
] })
|
|
217
|
+
] });
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/index.ts
|
|
221
|
+
checkLicense();
|
|
222
|
+
|
|
223
|
+
export { GroupedHeaderGrid, MultiRowHeader, createColumnGroup };
|
|
224
|
+
//# sourceMappingURL=index.mjs.map
|
|
225
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/createColumnGroup.ts","../src/MultiRowHeader.tsx","../src/legacy/GroupedHeaderGrid.tsx","../src/index.ts"],"names":["jsx","jsxs","flexRender"],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * createColumnGroup — thin wrapper to create a TanStack `GroupColumnDef<TData>`.\r\n *\r\n * Provides TypeScript generic type-safety for the standard TanStack column grouping pattern:\r\n * `{ header: string; columns: ColumnDef<TData>[] }`.\r\n *\r\n * @example\r\n * ```typescript\r\n * const infoGroup = createColumnGroup({\r\n * header: '기본 정보',\r\n * columns: [nameCol, deptCol],\r\n * });\r\n * ```\r\n *\r\n * @see AC-001 in G-001-spec.md — createColumnGroup signature\r\n * @see Section 5.1 in G-001-spec.md — API design\r\n */\r\n\r\nimport type { ColumnDef, GroupColumnDef } from '@tanstack/react-table';\r\n\r\n/**\r\n * Config object for `createColumnGroup`.\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n */\r\nexport interface ColumnGroupConfig<TData> {\r\n /** The display label for the column group header. */\r\n header: string;\r\n /** Leaf (or nested group) column definitions belonging to this group. */\r\n columns: ColumnDef<TData>[];\r\n}\r\n\r\n/**\r\n * Creates a TanStack `GroupColumnDef<TData>` from a typed config object.\r\n *\r\n * This is a thin wrapper — no logic beyond type narrowing. The returned\r\n * object is identical to writing `{ header, columns }` inline, but provides\r\n * generic type-checking at the call site.\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n * @param config - `ColumnGroupConfig<TData>` with `header` and `columns`.\r\n * @returns A `GroupColumnDef<TData>` suitable for passing to `useReactTable`.\r\n */\r\nexport function createColumnGroup<TData>(\r\n config: ColumnGroupConfig<TData>,\r\n): GroupColumnDef<TData> {\r\n return {\r\n header: config.header,\r\n columns: config.columns,\r\n };\r\n}\r\n","/**\r\n * MultiRowHeader — renders a multi-row `<thead>` section from TanStack\r\n * `table.getHeaderGroups()`.\r\n *\r\n * Extracts the header rendering logic from GroupedHeaderGrid.tsx (L75-117)\r\n * into a reusable Pro-package component. Uses only TanStack v8 standard APIs\r\n * (C-2): `getHeaderGroups`, `header.isPlaceholder`, `header.colSpan`,\r\n * `header.subHeaders`, `flexRender`.\r\n *\r\n * G-002 additions: `enableStickyHeader` prop for multi-row sticky (AC-001),\r\n * `frozenColumns` prop as on/off switch for column pinning left-offset (AC-002),\r\n * CSS variable `--grid-header-row-height` based top offset (AC-003).\r\n *\r\n * G-003 additions: `enableGroupToggle` prop for group header click to toggle\r\n * child column visibility (AC-001). Group cells get collapse icon ▼/▶ and\r\n * click handler; leaf cells retain sort handler (AC-002, D2 decision).\r\n *\r\n * Flat columns mixed with group columns are handled automatically by TanStack's\r\n * placeholder mechanism — do NOT add custom rowSpan calculation (spec §11.2).\r\n *\r\n * @see AC-001, AC-002, AC-003 in G-002-spec.md\r\n * @see Section 5.1/5.2 in G-002-spec.md — API design\r\n * @see Section 11.2/11.4 in G-002-spec.md — implementation guidelines\r\n * @see Section 6.1, 11.1 in G-003-spec.md — enableGroupToggle implementation\r\n */\r\n\r\nimport { type CSSProperties } from 'react';\r\nimport { flexRender, type Header, type Table } from '@tanstack/react-table';\r\nimport { useLicenseStatus, Watermark } from '@topgrid/grid-license';\r\n\r\n/**\r\n * Props for `MultiRowHeader`.\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n */\r\nexport interface MultiRowHeaderProps<TData = unknown> {\r\n /**\r\n * The TanStack table instance. Provides `getHeaderGroups()` used for\r\n * multi-row header rendering.\r\n */\r\n table: Table<TData>;\r\n /**\r\n * When true, applies sticky positioning to each header row so the multi-row\r\n * header remains fixed at the viewport top during vertical scroll (AC-001).\r\n * Default: false (G-001 behaviour preserved — breaking: false).\r\n */\r\n enableStickyHeader?: boolean;\r\n /**\r\n * Number of columns pinned on the left that should receive `sticky left`\r\n * positioning. Acts as an on/off switch; the actual frozen column identities\r\n * are determined from TanStack's `columnPinning.left` state via\r\n * `column.getIsPinned() === 'left'` (AC-002, D2 decision).\r\n * 0 or omitted: frozen positioning inactive.\r\n */\r\n frozenColumns?: number;\r\n /**\r\n * When true, group header cells (non-leaf) become clickable toggles that\r\n * show/hide all child (leaf) columns at once (G-003, AC-001).\r\n * Clicking a group header that has all leaves hidden will show them all;\r\n * clicking one with any visible leaf will hide them all.\r\n * Leaf columns retain their sort click handler regardless.\r\n * Default: false (G-001/G-002 behaviour preserved — breaking: false).\r\n */\r\n enableGroupToggle?: boolean;\r\n}\r\n\r\n/**\r\n * Returns the left pixel offset for a frozen header cell (AC-002, EC-05).\r\n *\r\n * TanStack's `column.getStart('left')` returns the correct offset for leaf\r\n * columns. For group header cells it may return `undefined`; in that case the\r\n * first leaf child's offset is used as a fallback (spec §11.4 EC-05).\r\n *\r\n * @typeParam TData - Row data type (matches the parent table's TData).\r\n * @see G-002-spec.md Section 11.4\r\n */\r\nfunction getHeaderLeftOffset<TData>(header: Header<TData, unknown>): number {\r\n const direct = header.column.getStart('left');\r\n if (direct !== undefined) return direct;\r\n // EC-05 fallback: group header cell — use first leaf child's offset.\r\n const firstLeaf = header.subHeaders[0]?.column;\r\n return firstLeaf?.getStart('left') ?? 0;\r\n}\r\n\r\n/**\r\n * Renders a multi-row `<thead>` element from a TanStack table instance.\r\n *\r\n * Iterates `table.getHeaderGroups()` to produce one `<tr>` per header row.\r\n * Group header cells use `header.colSpan` (computed by TanStack automatically).\r\n * Placeholder cells (`header.isPlaceholder`) are rendered as empty `<th>` elements.\r\n * Sorting is enabled only on leaf columns (`!header.subHeaders.length`).\r\n *\r\n * @typeParam TData - The row data type of the table.\r\n * @param props - `MultiRowHeaderProps<TData>`.\r\n * @returns A `<thead>` JSX element with all header rows.\r\n */\r\nexport function MultiRowHeader<TData>({\r\n table,\r\n enableStickyHeader,\r\n frozenColumns,\r\n enableGroupToggle,\r\n}: MultiRowHeaderProps<TData>): JSX.Element {\r\n // ADR-MOD-GRID-REFACTOR-2026-05-17-001 — license watermark wiring (H-D pattern)\r\n // sub-spec §8 Step 4: extra <tr><th colSpan> watermark row inside <thead>.\r\n // §9.3 D-3 = (a): sticky top-0 when enableStickyHeader is true.\r\n const _lic = useLicenseStatus();\r\n const headerGroups = table.getHeaderGroups();\r\n const visibleLeafCount = table.getVisibleLeafColumns().length;\r\n\r\n return (\r\n <thead className=\"bg-gray-50\">\r\n {_lic.watermarkRequired && visibleLeafCount > 0 ? (\r\n <tr\r\n {...(enableStickyHeader === true\r\n ? { className: 'sticky top-0 z-20' }\r\n : {})}\r\n >\r\n <th\r\n colSpan={visibleLeafCount}\r\n className=\"relative bg-yellow-50 px-4 py-1 text-center text-xs text-gray-500\"\r\n >\r\n <Watermark required />\r\n </th>\r\n </tr>\r\n ) : null}\r\n {headerGroups.map((headerGroup, rowIndex) => {\r\n // AC-001: sticky row props — C-29 conditional spread pattern.\r\n // Row 0: sticky top-0 z-10 (Tailwind only, no inline style needed).\r\n // Row N≥1: sticky z-10 + inline style top: calc(var(--grid-header-row-height, 40px) * N).\r\n const trProps = {\r\n ...(enableStickyHeader === true && rowIndex === 0 && {\r\n className: 'sticky top-0 z-10',\r\n }),\r\n ...(enableStickyHeader === true && rowIndex > 0 && {\r\n className: 'sticky z-10',\r\n style: {\r\n top: `calc(var(--grid-header-row-height, 40px) * ${rowIndex})`,\r\n } as CSSProperties,\r\n }),\r\n };\r\n\r\n return (\r\n <tr key={headerGroup.id} {...trProps}>\r\n {headerGroup.headers.map((header) => {\r\n // AC-002: frozen column detection via TanStack native API (D2 decision).\r\n // frozenColumns > 0 acts as the feature on/off switch;\r\n // actual pinned identity comes from TanStack columnPinning state.\r\n const isFrozen =\r\n (frozenColumns ?? 0) > 0 &&\r\n header.column.getIsPinned() === 'left';\r\n\r\n // D4: z-index layers.\r\n // frozen + sticky intersection → z-30.\r\n // frozen only (sticky off) → z-20.\r\n // Neither → no extra z-class.\r\n const frozenZClass =\r\n isFrozen && enableStickyHeader === true\r\n ? 'sticky z-30'\r\n : isFrozen\r\n ? 'sticky z-20'\r\n : '';\r\n\r\n // AC-002: left offset in px (inline style — dynamic runtime value, C-5 exception).\r\n // EC-05 fallback: group cell getStart('left') may be undefined → subHeaders[0] fallback.\r\n const thStyle: CSSProperties = isFrozen\r\n ? { left: `${getHeaderLeftOffset(header as Header<unknown, unknown>)}px` }\r\n : {};\r\n\r\n // Leaf columns: sorting enabled. Group columns: group toggle (if enabled).\r\n const isLeaf = header.subHeaders.length === 0;\r\n\r\n // G-003 AC-001: pre-compute allLeavesHidden once per non-leaf header.\r\n // Used for (a) click handler, (b) effectiveColSpan, (c) collapse icon.\r\n const allLeavesHidden =\r\n !isLeaf &&\r\n header.column.getLeafColumns().every((c) => !c.getIsVisible());\r\n\r\n // G-003 AC-001: group click handler — toggle all leaf column visibility.\r\n // allLeavesHidden → show all (true); any visible leaf → hide all (false).\r\n const groupClickHandler =\r\n enableGroupToggle === true && !isLeaf\r\n ? () => {\r\n const leafCols = header.column.getLeafColumns();\r\n leafCols.forEach((c) => c.toggleVisibility(allLeavesHidden));\r\n }\r\n : undefined;\r\n\r\n // G-003: effectiveColSpan — collapsed group occupies 1 column (spec §6.1).\r\n const effectiveColSpan =\r\n enableGroupToggle === true && allLeavesHidden\r\n ? 1\r\n : header.colSpan;\r\n\r\n // isPlaceholder: filler cell for multi-row headers (TanStack placeholder mechanism).\r\n // Flat columns in the group row appear as placeholder cells — rendered empty.\r\n if (header.isPlaceholder) {\r\n return (\r\n <th\r\n key={header.id}\r\n colSpan={effectiveColSpan}\r\n className={`px-4 py-3 border border-gray-200${frozenZClass ? ` ${frozenZClass}` : ''}`}\r\n {...(isFrozen ? { style: thStyle } : {})}\r\n />\r\n );\r\n }\r\n\r\n return (\r\n <th\r\n key={header.id}\r\n colSpan={effectiveColSpan}\r\n className={`px-4 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider border border-gray-200 whitespace-nowrap select-none${\r\n header.column.getCanSort() && isLeaf\r\n ? ' cursor-pointer hover:bg-gray-100'\r\n : ''\r\n }${\r\n enableGroupToggle === true && !isLeaf\r\n ? ' cursor-pointer hover:bg-gray-100'\r\n : ''\r\n }${frozenZClass ? ` ${frozenZClass}` : ''}`}\r\n {...(isFrozen ? { style: thStyle } : {})}\r\n onClick={\r\n isLeaf ? header.column.getToggleSortingHandler() : groupClickHandler\r\n }\r\n >\r\n <div className=\"flex items-center justify-center gap-1\">\r\n {flexRender(header.column.columnDef.header, header.getContext())}\r\n {header.column.getCanSort() && isLeaf && (\r\n <span className=\"text-gray-400\">\r\n {({ asc: '▲', desc: '▼' } as Record<string, string>)[\r\n header.column.getIsSorted() as string\r\n ] ?? '⇅'}\r\n </span>\r\n )}\r\n {enableGroupToggle === true && !isLeaf && (\r\n <span className=\"text-gray-400 ml-1\">\r\n {allLeavesHidden ? '▶' : '▼'}\r\n </span>\r\n )}\r\n </div>\r\n </th>\r\n );\r\n })}\r\n </tr>\r\n );\r\n })}\r\n </thead>\r\n );\r\n}\r\n","/**\r\n * GroupedHeaderGrid — legacy alias component (G-003, C-6 deprecation alias).\r\n *\r\n * Wraps `useReactTable` + `MultiRowHeader` + tbody + pagination into a single\r\n * self-contained component, preserving the AS-IS GroupedHeaderGrid API surface\r\n * from tw-framework-front/src/components/tomis/Grid/GroupedHeaderGrid.tsx (L0).\r\n *\r\n * Behaviour and classNames are ported verbatim from L0 (C-17 visual preservation).\r\n * `verifyOrWarn` is intentionally absent here — it runs via index.ts side-effect.\r\n *\r\n * D3 decision: inline type aliases for GridPaginationOptions / GridRowSelectionOptions\r\n * to avoid reverse dependency on tw-framework-front.\r\n *\r\n * @see G-003-spec.md Section 6.2 — legacy/GroupedHeaderGrid structure\r\n * @see C-6 — 1 minor version deprecation alias (breaking: false)\r\n * @see C-17 — AS-IS L0 className is authoritative\r\n */\r\n\r\nimport { useState } from 'react';\r\nimport type { MouseEvent } from 'react';\r\nimport {\r\n useReactTable,\r\n getCoreRowModel,\r\n getSortedRowModel,\r\n getPaginationRowModel,\r\n flexRender,\r\n type SortingState,\r\n type ColumnDef,\r\n} from '@tanstack/react-table';\r\nimport type { GridPaginationOptions, GridRowSelectionOptions } from '@topgrid/grid-core';\r\nimport { MultiRowHeader } from '../MultiRowHeader';\r\n\r\n/**\r\n * Props for the legacy `GroupedHeaderGrid` wrapper component.\r\n *\r\n * @typeParam TData - The row data type.\r\n * @deprecated Migrate to composing `useReactTable` + `MultiRowHeader` directly.\r\n */\r\nexport interface GroupedHeaderGridProps<TData = unknown> {\r\n data: TData[];\r\n /**\r\n * Pass grouped column definitions using TanStack Table's native column grouping.\r\n * Use `{ header: 'Group', columns: [...leafColumns] }` structure for grouping.\r\n */\r\n columns: ColumnDef<TData>[];\r\n pagination?: GridPaginationOptions;\r\n rowSelection?: GridRowSelectionOptions<TData>;\r\n onRowClick?: (row: TData, event: MouseEvent<HTMLTableRowElement>) => void;\r\n loading?: boolean;\r\n emptyText?: string;\r\n className?: string;\r\n /** G-003 AC-001: enable group header click to toggle child column visibility. */\r\n enableGroupToggle?: boolean;\r\n}\r\n\r\n/**\r\n * Legacy self-contained grid component with grouped multi-row headers.\r\n *\r\n * Delegates header rendering to `MultiRowHeader` from `@topgrid/grid-pro-header`.\r\n * tbody and pagination are ported verbatim from AS-IS L0 (C-17).\r\n *\r\n * @typeParam TData - The row data type (must extend `object`).\r\n * @deprecated Prefer composing `useReactTable` + `MultiRowHeader` directly.\r\n */\r\nexport function GroupedHeaderGrid<TData extends object>({\r\n data,\r\n columns,\r\n pagination,\r\n onRowClick,\r\n loading = false,\r\n emptyText = '데이터가 없습니다.',\r\n className = '',\r\n enableGroupToggle,\r\n}: GroupedHeaderGridProps<TData>): JSX.Element {\r\n const [sorting, setSorting] = useState<SortingState>([]);\r\n const [pageIndex, setPageIndex] = useState(0);\r\n const [pageSize] = useState(pagination?.pageSize ?? 20);\r\n\r\n const table = useReactTable({\r\n data,\r\n columns,\r\n state: {\r\n sorting,\r\n pagination: { pageIndex, pageSize },\r\n },\r\n onSortingChange: setSorting,\r\n onPaginationChange: (updater) => {\r\n const next =\r\n typeof updater === 'function' ? updater({ pageIndex, pageSize }) : updater;\r\n setPageIndex(next.pageIndex);\r\n },\r\n getCoreRowModel: getCoreRowModel(),\r\n getSortedRowModel: getSortedRowModel(),\r\n // C-29: exactOptionalPropertyTypes — conditional spread avoids assigning undefined\r\n // to a non-optional property when pagination is absent.\r\n ...(pagination !== undefined\r\n ? { getPaginationRowModel: getPaginationRowModel() }\r\n : {}),\r\n manualPagination: false,\r\n });\r\n\r\n const pageCount = table.getPageCount();\r\n\r\n if (loading) {\r\n return (\r\n <div className={`flex flex-col ${className}`}>\r\n <div className=\"h-40 flex items-center justify-center\">\r\n <div className=\"animate-spin rounded-full h-10 w-10 border-t-2 border-b-2 border-blue-500\" />\r\n </div>\r\n </div>\r\n );\r\n }\r\n\r\n return (\r\n <div className={`flex flex-col ${className}`}>\r\n <div className=\"overflow-x-auto rounded-lg border border-gray-200\">\r\n <table className=\"min-w-full text-sm border-collapse\">\r\n <MultiRowHeader\r\n table={table}\r\n {...(enableGroupToggle === true ? { enableGroupToggle: true } : {})}\r\n />\r\n <tbody className=\"bg-white divide-y divide-gray-100\">\r\n {table.getRowModel().rows.length === 0 ? (\r\n <tr>\r\n <td\r\n colSpan={table.getAllLeafColumns().length}\r\n className=\"px-4 py-10 text-center text-gray-400\"\r\n >\r\n {emptyText}\r\n </td>\r\n </tr>\r\n ) : (\r\n table.getRowModel().rows.map((row) => (\r\n <tr\r\n key={row.id}\r\n className={`hover:bg-gray-50 ${onRowClick ? 'cursor-pointer' : ''}`}\r\n onClick={(e) => onRowClick?.(row.original, e)}\r\n >\r\n {row.getVisibleCells().map((cell) => (\r\n <td\r\n key={cell.id}\r\n className=\"px-4 py-3 whitespace-nowrap text-gray-700 border-r border-gray-100 last:border-r-0\"\r\n >\r\n {flexRender(cell.column.columnDef.cell, cell.getContext())}\r\n </td>\r\n ))}\r\n </tr>\r\n ))\r\n )}\r\n </tbody>\r\n </table>\r\n </div>\r\n\r\n {pagination !== undefined && (\r\n <div className=\"flex items-center justify-between px-2 py-3 text-sm text-gray-600\">\r\n <span>\r\n 전체 <strong>{table.getFilteredRowModel().rows.length}</strong>건\r\n </span>\r\n <div className=\"flex items-center gap-1\">\r\n <button\r\n onClick={() => table.setPageIndex(0)}\r\n disabled={!table.getCanPreviousPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'«'}\r\n </button>\r\n <button\r\n onClick={() => table.previousPage()}\r\n disabled={!table.getCanPreviousPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'‹'}\r\n </button>\r\n <span className=\"px-3\">\r\n {pageIndex + 1} / {pageCount || 1}\r\n </span>\r\n <button\r\n onClick={() => table.nextPage()}\r\n disabled={!table.getCanNextPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'›'}\r\n </button>\r\n <button\r\n onClick={() => table.setPageIndex(pageCount - 1)}\r\n disabled={!table.getCanNextPage()}\r\n className=\"px-2 py-1 rounded border border-gray-300 disabled:opacity-40 hover:bg-gray-100\"\r\n >\r\n {'»'}\r\n </button>\r\n </div>\r\n </div>\r\n )}\r\n </div>\r\n );\r\n}\r\n","import { checkLicense } from '@topgrid/grid-license';\r\n\r\ncheckLicense();\r\n\r\n// createColumnGroup helper (AC-001)\r\nexport { createColumnGroup } from './createColumnGroup';\r\nexport type { ColumnGroupConfig } from './types';\r\n\r\n// MultiRowHeader component (AC-002, AC-003, AC-004)\r\nexport { MultiRowHeader } from './MultiRowHeader';\r\nexport type { MultiRowHeaderProps } from './types';\r\n\r\n// GroupedHeaderGrid legacy alias (G-003, C-6 deprecation alias)\r\nexport { GroupedHeaderGrid } from './legacy/GroupedHeaderGrid';\r\nexport type { GroupedHeaderGridProps } from './legacy/GroupedHeaderGrid';\r\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@topgrid/grid-pro-header",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "SEE LICENSE IN EULA",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"description": "Pro: Multi-row Header (Column Groups)",
|
|
10
|
+
"main": "./dist/index.cjs",
|
|
11
|
+
"module": "./dist/index.mjs",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.mjs",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"./src/index.ts"
|
|
26
|
+
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@topgrid/grid-core": "0.1.0",
|
|
29
|
+
"@topgrid/grid-license": "0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"@tanstack/react-table": "^8.0.0",
|
|
33
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
34
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "echo TODO"
|
|
40
|
+
}
|
|
41
|
+
}
|