@squadbase/vantage 0.0.1 → 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 +21 -4
- package/dist/chunk-A2UUGASH.js +12 -0
- package/dist/chunk-A2UUGASH.js.map +1 -0
- package/dist/{chunk-ZGDU5YLH.js → chunk-PRFBSQA4.js} +9 -5
- package/dist/chunk-PRFBSQA4.js.map +1 -0
- package/dist/chunk-WQZYXXQW.js +2249 -0
- package/dist/chunk-WQZYXXQW.js.map +1 -0
- package/dist/{chunk-73J5ZD4C.js → chunk-YLAB6UQS.js} +17 -3
- package/dist/chunk-YLAB6UQS.js.map +1 -0
- package/dist/cli.js +607 -36
- package/dist/cli.js.map +1 -1
- package/dist/components/index.d.ts +554 -0
- package/dist/components/index.js +1983 -0
- package/dist/components/index.js.map +1 -0
- package/dist/markdown/index.d.ts +9 -0
- package/dist/markdown/index.js +26 -0
- package/dist/markdown/index.js.map +1 -0
- package/dist/query/index.d.ts +27 -1
- package/dist/query/index.js +18 -0
- package/dist/query/index.js.map +1 -1
- package/dist/server/node.js +6 -1
- package/dist/server/node.js.map +1 -1
- package/dist/ui/index.d.ts +316 -200
- package/dist/ui/index.js +566 -511
- package/dist/ui/index.js.map +1 -1
- package/dist/vite/index.js +2 -2
- package/docs/en/agent-skills.md +89 -0
- package/docs/en/api-and-server.md +84 -0
- package/docs/en/build-and-deploy.md +280 -0
- package/docs/en/cli-reference.md +311 -0
- package/docs/en/components.md +233 -0
- package/docs/en/data-fetching.md +78 -0
- package/docs/en/environment-variables.md +69 -0
- package/docs/en/getting-started.md +95 -0
- package/docs/en/index.md +65 -0
- package/docs/en/markdown/markdown-renderer.md +44 -0
- package/docs/en/pages-and-metadata.md +56 -0
- package/docs/en/parts/app-shell.md +54 -0
- package/docs/en/parts/dashboard-card.md +64 -0
- package/docs/en/parts/data-table.md +86 -0
- package/docs/en/parts/date-range-picker.md +41 -0
- package/docs/en/parts/echart.md +53 -0
- package/docs/en/parts/filter-bar.md +66 -0
- package/docs/en/parts/funnel-steps.md +33 -0
- package/docs/en/parts/metric-value.md +23 -0
- package/docs/en/parts/multi-select.md +27 -0
- package/docs/en/parts/page-shell.md +45 -0
- package/docs/en/parts/searchable-select.md +47 -0
- package/docs/en/parts/section-header.md +20 -0
- package/docs/en/parts/segmented-control.md +25 -0
- package/docs/en/parts/status-badge.md +44 -0
- package/docs/en/parts/trend-indicator.md +26 -0
- package/docs/en/routing.md +95 -0
- package/docs/en/ui/accordion.md +36 -0
- package/docs/en/ui/alert.md +31 -0
- package/docs/en/ui/badge.md +23 -0
- package/docs/en/ui/breadcrumb.md +38 -0
- package/docs/en/ui/button.md +39 -0
- package/docs/en/ui/calendar.md +28 -0
- package/docs/en/ui/card.md +35 -0
- package/docs/en/ui/checkbox.md +34 -0
- package/docs/en/ui/cn.md +34 -0
- package/docs/en/ui/collapsible.md +21 -0
- package/docs/en/ui/command.md +37 -0
- package/docs/en/ui/dialog.md +52 -0
- package/docs/en/ui/dropdown-menu.md +50 -0
- package/docs/en/ui/empty.md +22 -0
- package/docs/en/ui/error-state.md +50 -0
- package/docs/en/ui/input-group.md +30 -0
- package/docs/en/ui/input.md +28 -0
- package/docs/en/ui/label.md +19 -0
- package/docs/en/ui/loading.md +19 -0
- package/docs/en/ui/popover.md +34 -0
- package/docs/en/ui/progress.md +28 -0
- package/docs/en/ui/scroll-area.md +23 -0
- package/docs/en/ui/select.md +63 -0
- package/docs/en/ui/separator.md +15 -0
- package/docs/en/ui/sheet.md +28 -0
- package/docs/en/ui/sidebar.md +92 -0
- package/docs/en/ui/skeleton.md +16 -0
- package/docs/en/ui/slider.md +21 -0
- package/docs/en/ui/spinner.md +14 -0
- package/docs/en/ui/switch.md +19 -0
- package/docs/en/ui/table.md +24 -0
- package/docs/en/ui/tabs.md +23 -0
- package/docs/en/ui/textarea.md +15 -0
- package/docs/en/ui/toggle-group.md +26 -0
- package/docs/en/ui/toggle.md +19 -0
- package/docs/en/ui/tooltip.md +22 -0
- package/docs/en/ui/use-is-mobile.md +18 -0
- package/docs/en/ui-and-theming.md +95 -0
- package/docs/index.json +1306 -0
- package/docs/ja/agent-skills.md +87 -0
- package/docs/ja/api-and-server.md +84 -0
- package/docs/ja/build-and-deploy.md +279 -0
- package/docs/ja/cli-reference.md +305 -0
- package/docs/ja/components.md +229 -0
- package/docs/ja/data-fetching.md +78 -0
- package/docs/ja/environment-variables.md +68 -0
- package/docs/ja/getting-started.md +95 -0
- package/docs/ja/index.md +65 -0
- package/docs/ja/markdown/markdown-renderer.md +43 -0
- package/docs/ja/pages-and-metadata.md +56 -0
- package/docs/ja/parts/app-shell.md +54 -0
- package/docs/ja/parts/dashboard-card.md +64 -0
- package/docs/ja/parts/data-table.md +85 -0
- package/docs/ja/parts/date-range-picker.md +41 -0
- package/docs/ja/parts/echart.md +52 -0
- package/docs/ja/parts/filter-bar.md +64 -0
- package/docs/ja/parts/funnel-steps.md +33 -0
- package/docs/ja/parts/metric-value.md +25 -0
- package/docs/ja/parts/multi-select.md +27 -0
- package/docs/ja/parts/page-shell.md +45 -0
- package/docs/ja/parts/searchable-select.md +47 -0
- package/docs/ja/parts/section-header.md +20 -0
- package/docs/ja/parts/segmented-control.md +26 -0
- package/docs/ja/parts/status-badge.md +44 -0
- package/docs/ja/parts/trend-indicator.md +26 -0
- package/docs/ja/routing.md +95 -0
- package/docs/ja/ui/accordion.md +37 -0
- package/docs/ja/ui/alert.md +31 -0
- package/docs/ja/ui/badge.md +23 -0
- package/docs/ja/ui/breadcrumb.md +38 -0
- package/docs/ja/ui/button.md +39 -0
- package/docs/ja/ui/calendar.md +28 -0
- package/docs/ja/ui/card.md +35 -0
- package/docs/ja/ui/checkbox.md +34 -0
- package/docs/ja/ui/cn.md +34 -0
- package/docs/ja/ui/collapsible.md +21 -0
- package/docs/ja/ui/command.md +36 -0
- package/docs/ja/ui/dialog.md +51 -0
- package/docs/ja/ui/dropdown-menu.md +50 -0
- package/docs/ja/ui/empty.md +22 -0
- package/docs/ja/ui/error-state.md +50 -0
- package/docs/ja/ui/input-group.md +30 -0
- package/docs/ja/ui/input.md +28 -0
- package/docs/ja/ui/label.md +19 -0
- package/docs/ja/ui/loading.md +19 -0
- package/docs/ja/ui/popover.md +34 -0
- package/docs/ja/ui/progress.md +28 -0
- package/docs/ja/ui/scroll-area.md +24 -0
- package/docs/ja/ui/select.md +64 -0
- package/docs/ja/ui/separator.md +15 -0
- package/docs/ja/ui/sheet.md +28 -0
- package/docs/ja/ui/sidebar.md +92 -0
- package/docs/ja/ui/skeleton.md +16 -0
- package/docs/ja/ui/slider.md +21 -0
- package/docs/ja/ui/spinner.md +14 -0
- package/docs/ja/ui/switch.md +19 -0
- package/docs/ja/ui/table.md +24 -0
- package/docs/ja/ui/tabs.md +24 -0
- package/docs/ja/ui/textarea.md +15 -0
- package/docs/ja/ui/toggle-group.md +27 -0
- package/docs/ja/ui/toggle.md +19 -0
- package/docs/ja/ui/tooltip.md +22 -0
- package/docs/ja/ui/use-is-mobile.md +18 -0
- package/docs/ja/ui-and-theming.md +95 -0
- package/package.json +29 -4
- package/registry/blocks/sales-overview.tsx +43 -19
- package/registry/ui/data-table.tsx +702 -102
- package/skills/vantage-add-feature/SKILL.md +150 -0
- package/skills/vantage-app/SKILL.md +258 -0
- package/skills/vantage-pitfalls/SKILL.md +115 -0
- package/templates/AGENTS.md +268 -0
- package/theme.css +178 -40
- package/dist/chunk-73J5ZD4C.js.map +0 -1
- package/dist/chunk-ZGDU5YLH.js.map +0 -1
|
@@ -1,115 +1,715 @@
|
|
|
1
1
|
// Copied by `vantage add ui data-table`. This file is yours to edit.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
/**
|
|
3
|
+
* DataTable — a compound table over TanStack Table. `<DataTable>` owns the
|
|
4
|
+
* table instance and publishes it on context; the toolbar, body, pagination and
|
|
5
|
+
* column-visibility parts read from that context, so a page composes only the
|
|
6
|
+
* pieces it wants. `DataTablePreset` assembles the usual arrangement.
|
|
7
|
+
*
|
|
8
|
+
* Every piece of state (sorting, filters, pagination, selection, visibility) is
|
|
9
|
+
* optionally controlled: pass the value plus its `on…Change` to drive it from
|
|
10
|
+
* outside, or omit both and the table keeps it internally.
|
|
11
|
+
*/
|
|
12
|
+
import {
|
|
13
|
+
createContext,
|
|
14
|
+
forwardRef,
|
|
15
|
+
useCallback,
|
|
16
|
+
useContext,
|
|
17
|
+
useMemo,
|
|
18
|
+
useState,
|
|
19
|
+
type HTMLAttributes,
|
|
20
|
+
type ReactNode,
|
|
21
|
+
type SetStateAction,
|
|
22
|
+
} from "react"
|
|
23
|
+
import {
|
|
24
|
+
flexRender,
|
|
25
|
+
getCoreRowModel,
|
|
26
|
+
getFilteredRowModel,
|
|
27
|
+
getPaginationRowModel,
|
|
28
|
+
getSortedRowModel,
|
|
29
|
+
useReactTable,
|
|
30
|
+
type ColumnDef,
|
|
31
|
+
type ColumnFiltersState,
|
|
32
|
+
type OnChangeFn,
|
|
33
|
+
type PaginationState,
|
|
34
|
+
type RowSelectionState,
|
|
35
|
+
type SortingState,
|
|
36
|
+
type Table as TanstackTable,
|
|
37
|
+
type TableMeta,
|
|
38
|
+
type VisibilityState,
|
|
39
|
+
} from "@tanstack/react-table"
|
|
40
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, Columns3, Search } from "lucide-react"
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
42
|
+
import {
|
|
43
|
+
Button,
|
|
44
|
+
Checkbox,
|
|
45
|
+
DropdownMenu,
|
|
46
|
+
DropdownMenuCheckboxItem,
|
|
47
|
+
DropdownMenuContent,
|
|
48
|
+
DropdownMenuTrigger,
|
|
49
|
+
Input,
|
|
50
|
+
Select,
|
|
51
|
+
SelectContent,
|
|
52
|
+
SelectItem,
|
|
53
|
+
SelectTrigger,
|
|
54
|
+
SelectValue,
|
|
55
|
+
Table,
|
|
56
|
+
TableBody,
|
|
57
|
+
TableCell,
|
|
58
|
+
TableHead,
|
|
59
|
+
TableHeader,
|
|
60
|
+
TableRow,
|
|
61
|
+
cn,
|
|
62
|
+
} from "@squadbase/vantage/ui"
|
|
17
63
|
|
|
18
|
-
|
|
64
|
+
|
|
65
|
+
/* ------------------------------------------------------------------- types */
|
|
66
|
+
|
|
67
|
+
interface DataTableContextValue<TData> {
|
|
68
|
+
table: TanstackTable<TData>
|
|
69
|
+
selectedRows: TData[]
|
|
70
|
+
hasSelection: boolean
|
|
71
|
+
globalFilter: string
|
|
72
|
+
setGlobalFilter: OnChangeFn<string>
|
|
73
|
+
enableFiltering: boolean
|
|
74
|
+
enablePagination: boolean
|
|
75
|
+
enableRowSelection: boolean
|
|
76
|
+
visibleColumnCount: number
|
|
77
|
+
}
|
|
19
78
|
|
|
20
79
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* markup, styling and behavior freely.
|
|
80
|
+
* @template TData 行データ型
|
|
81
|
+
* @template TValue セル値の型 (省略時は任意)
|
|
24
82
|
*/
|
|
25
|
-
export
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
83
|
+
export interface DataTableProps<TData, TValue> {
|
|
84
|
+
/**
|
|
85
|
+
* TanStack Table の列定義配列。
|
|
86
|
+
*
|
|
87
|
+
* `ColumnDef<TData, TValue>[]` を annotation するか配列リテラルに
|
|
88
|
+
* `satisfies ColumnDef<TData>[]` を付けないと、`accessorKey` などの
|
|
89
|
+
* literal が `string` に拡張され型エラーになる。
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* import type { ColumnDef } from "@squadbase/vantage/components"
|
|
93
|
+
*
|
|
94
|
+
* const columns: ColumnDef<User>[] = [
|
|
95
|
+
* { accessorKey: "id", header: "ID" },
|
|
96
|
+
* { accessorKey: "name", header: "名前" },
|
|
97
|
+
* ]
|
|
98
|
+
* <DataTable columns={columns} data={users}>...</DataTable>
|
|
99
|
+
*/
|
|
100
|
+
columns: ColumnDef<TData, TValue>[]
|
|
101
|
+
data: TData[]
|
|
102
|
+
|
|
103
|
+
// Sorting
|
|
104
|
+
enableSorting?: boolean
|
|
105
|
+
sorting?: SortingState
|
|
106
|
+
onSortingChange?: OnChangeFn<SortingState>
|
|
107
|
+
|
|
108
|
+
// Filtering
|
|
109
|
+
enableFiltering?: boolean
|
|
110
|
+
columnFilters?: ColumnFiltersState
|
|
111
|
+
onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>
|
|
112
|
+
globalFilter?: string
|
|
113
|
+
onGlobalFilterChange?: OnChangeFn<string>
|
|
114
|
+
|
|
115
|
+
// Pagination
|
|
116
|
+
enablePagination?: boolean
|
|
117
|
+
pagination?: PaginationState
|
|
118
|
+
onPaginationChange?: OnChangeFn<PaginationState>
|
|
119
|
+
pageCount?: number
|
|
120
|
+
|
|
121
|
+
// Row selection
|
|
122
|
+
enableRowSelection?: boolean
|
|
123
|
+
rowSelection?: RowSelectionState
|
|
124
|
+
onRowSelectionChange?: OnChangeFn<RowSelectionState>
|
|
125
|
+
|
|
126
|
+
// Column visibility
|
|
127
|
+
columnVisibility?: VisibilityState
|
|
128
|
+
onColumnVisibilityChange?: OnChangeFn<VisibilityState>
|
|
129
|
+
|
|
130
|
+
meta?: TableMeta<TData>
|
|
131
|
+
className?: string
|
|
132
|
+
|
|
133
|
+
/** Required — the parts to render (toolbar, content, pagination). */
|
|
134
|
+
children: ReactNode
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export interface DataTableToolbarProps extends HTMLAttributes<HTMLDivElement> {
|
|
138
|
+
showSearch?: boolean
|
|
139
|
+
toolbar?: ReactNode
|
|
140
|
+
bulkActions?: (selectedRows: unknown[]) => ReactNode
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface DataTableContentProps extends HTMLAttributes<HTMLDivElement> {}
|
|
144
|
+
|
|
145
|
+
export interface DataTablePaginationProps extends HTMLAttributes<HTMLDivElement> {
|
|
146
|
+
pageSizeOptions?: number[]
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface DataTableColumnVisibilityProps {
|
|
150
|
+
/** 列 ID をキーとしたラベルマップ。ヘッダーが文字列でない列のラベルを指定する */
|
|
151
|
+
columnLabels?: Record<string, string>
|
|
152
|
+
className?: string
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface DataTablePresetProps<TData, TValue> {
|
|
156
|
+
columns: ColumnDef<TData, TValue>[]
|
|
157
|
+
data: TData[]
|
|
158
|
+
|
|
159
|
+
enableSorting?: boolean
|
|
160
|
+
sorting?: SortingState
|
|
161
|
+
onSortingChange?: OnChangeFn<SortingState>
|
|
162
|
+
|
|
163
|
+
enableFiltering?: boolean
|
|
164
|
+
columnFilters?: ColumnFiltersState
|
|
165
|
+
onColumnFiltersChange?: OnChangeFn<ColumnFiltersState>
|
|
166
|
+
globalFilter?: string
|
|
167
|
+
onGlobalFilterChange?: OnChangeFn<string>
|
|
168
|
+
|
|
169
|
+
enablePagination?: boolean
|
|
170
|
+
pagination?: PaginationState
|
|
171
|
+
onPaginationChange?: OnChangeFn<PaginationState>
|
|
172
|
+
pageCount?: number
|
|
173
|
+
|
|
174
|
+
enableRowSelection?: boolean
|
|
175
|
+
rowSelection?: RowSelectionState
|
|
176
|
+
onRowSelectionChange?: OnChangeFn<RowSelectionState>
|
|
177
|
+
|
|
178
|
+
enableColumnVisibility?: boolean
|
|
179
|
+
columnVisibility?: VisibilityState
|
|
180
|
+
onColumnVisibilityChange?: OnChangeFn<VisibilityState>
|
|
181
|
+
columnLabels?: Record<string, string>
|
|
182
|
+
|
|
183
|
+
/** Extension slots */
|
|
184
|
+
toolbar?: ReactNode
|
|
185
|
+
bulkActions?: (selectedRows: TData[]) => ReactNode
|
|
186
|
+
|
|
187
|
+
meta?: TableMeta<TData>
|
|
188
|
+
className?: string
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* ------------------------------------------------------------------ helper */
|
|
192
|
+
|
|
193
|
+
/** Use the controlled value when one is given, else keep it in local state. */
|
|
194
|
+
function useControlledState<T>(
|
|
195
|
+
controlledValue: T | undefined,
|
|
196
|
+
defaultValue: T,
|
|
197
|
+
onChange?: OnChangeFn<T>,
|
|
198
|
+
): [T, OnChangeFn<T>] {
|
|
199
|
+
const [internalValue, setInternalValue] = useState<T>(defaultValue)
|
|
200
|
+
|
|
201
|
+
const isControlled = controlledValue !== undefined
|
|
202
|
+
const value = isControlled ? controlledValue : internalValue
|
|
203
|
+
|
|
204
|
+
const setValue: OnChangeFn<T> = useCallback(
|
|
205
|
+
(updaterOrValue) => {
|
|
206
|
+
onChange?.(updaterOrValue)
|
|
207
|
+
if (!isControlled) {
|
|
208
|
+
setInternalValue(updaterOrValue as SetStateAction<T>)
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
[onChange, isControlled],
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return [value, setValue]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/* ----------------------------------------------------------------- context */
|
|
218
|
+
|
|
219
|
+
const DataTableContext = createContext<DataTableContextValue<unknown> | null>(null)
|
|
220
|
+
|
|
221
|
+
export function useDataTable<TData>(): DataTableContextValue<TData> {
|
|
222
|
+
const ctx = useContext(DataTableContext)
|
|
223
|
+
if (!ctx) throw new Error("useDataTable must be used inside <DataTable>")
|
|
224
|
+
return ctx as DataTableContextValue<TData>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* -------------------------------------------------------------------- root */
|
|
228
|
+
|
|
229
|
+
export function DataTable<TData, TValue>({
|
|
230
|
+
columns,
|
|
231
|
+
data,
|
|
232
|
+
enableSorting = true,
|
|
233
|
+
sorting: sortingProp,
|
|
234
|
+
onSortingChange,
|
|
235
|
+
enableFiltering = false,
|
|
236
|
+
columnFilters: columnFiltersProp,
|
|
237
|
+
onColumnFiltersChange,
|
|
238
|
+
globalFilter: globalFilterProp,
|
|
239
|
+
onGlobalFilterChange,
|
|
240
|
+
enablePagination = false,
|
|
241
|
+
pagination: paginationProp,
|
|
242
|
+
onPaginationChange,
|
|
243
|
+
pageCount,
|
|
244
|
+
enableRowSelection = false,
|
|
245
|
+
rowSelection: rowSelectionProp,
|
|
246
|
+
onRowSelectionChange,
|
|
247
|
+
columnVisibility: columnVisibilityProp,
|
|
248
|
+
onColumnVisibilityChange,
|
|
249
|
+
meta,
|
|
250
|
+
className,
|
|
251
|
+
children,
|
|
252
|
+
}: DataTableProps<TData, TValue>) {
|
|
253
|
+
const [sorting, setSorting] = useControlledState<SortingState>(sortingProp, [], onSortingChange)
|
|
254
|
+
|
|
255
|
+
const [columnFilters, setColumnFilters] = useControlledState<ColumnFiltersState>(
|
|
256
|
+
columnFiltersProp,
|
|
257
|
+
[],
|
|
258
|
+
onColumnFiltersChange,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
const [globalFilter, setGlobalFilter] = useControlledState<string>(
|
|
262
|
+
globalFilterProp,
|
|
263
|
+
"",
|
|
264
|
+
onGlobalFilterChange,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
const [rowSelection, setRowSelection] = useControlledState<RowSelectionState>(
|
|
268
|
+
rowSelectionProp,
|
|
269
|
+
{},
|
|
270
|
+
onRowSelectionChange,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
const [columnVisibility, setColumnVisibility] = useControlledState<VisibilityState>(
|
|
274
|
+
columnVisibilityProp,
|
|
275
|
+
{},
|
|
276
|
+
onColumnVisibilityChange,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
const [pagination, setPagination] = useControlledState<PaginationState>(
|
|
280
|
+
paginationProp,
|
|
281
|
+
{ pageIndex: 0, pageSize: 10 },
|
|
282
|
+
onPaginationChange,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
// Row selection prepends a checkbox column that the caller never declares.
|
|
286
|
+
const allColumns = useMemo<ColumnDef<TData, TValue>[]>(() => {
|
|
287
|
+
if (!enableRowSelection) return columns
|
|
288
|
+
|
|
289
|
+
const selectionColumn: ColumnDef<TData, TValue> = {
|
|
290
|
+
id: "_select",
|
|
291
|
+
header: ({ table }) => (
|
|
292
|
+
<Checkbox
|
|
293
|
+
checked={table.getIsAllPageRowsSelected()}
|
|
294
|
+
indeterminate={table.getIsSomePageRowsSelected()}
|
|
295
|
+
onCheckedChange={(checked) => table.toggleAllPageRowsSelected(checked)}
|
|
296
|
+
aria-label="全て選択"
|
|
297
|
+
/>
|
|
298
|
+
),
|
|
299
|
+
cell: ({ row }) => (
|
|
300
|
+
<Checkbox
|
|
301
|
+
checked={row.getIsSelected()}
|
|
302
|
+
onCheckedChange={(checked) => row.toggleSelected(checked)}
|
|
303
|
+
aria-label="行を選択"
|
|
304
|
+
/>
|
|
305
|
+
),
|
|
306
|
+
enableSorting: false,
|
|
307
|
+
enableHiding: false,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return [selectionColumn, ...columns]
|
|
311
|
+
}, [columns, enableRowSelection])
|
|
312
|
+
|
|
313
|
+
const table = useReactTable({
|
|
314
|
+
data,
|
|
315
|
+
columns: allColumns,
|
|
316
|
+
state: {
|
|
317
|
+
sorting,
|
|
318
|
+
columnFilters,
|
|
319
|
+
globalFilter,
|
|
320
|
+
rowSelection,
|
|
321
|
+
columnVisibility,
|
|
322
|
+
...(enablePagination ? { pagination } : {}),
|
|
323
|
+
},
|
|
324
|
+
enableSorting,
|
|
325
|
+
enableRowSelection,
|
|
326
|
+
onSortingChange: setSorting,
|
|
327
|
+
onColumnFiltersChange: setColumnFilters,
|
|
328
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
329
|
+
onRowSelectionChange: setRowSelection,
|
|
330
|
+
onColumnVisibilityChange: setColumnVisibility,
|
|
331
|
+
...(enablePagination ? { onPaginationChange: setPagination } : {}),
|
|
332
|
+
...(pageCount !== undefined ? { pageCount, manualPagination: true } : {}),
|
|
333
|
+
getCoreRowModel: getCoreRowModel(),
|
|
334
|
+
getSortedRowModel: getSortedRowModel(),
|
|
335
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
336
|
+
...(enablePagination ? { getPaginationRowModel: getPaginationRowModel() } : {}),
|
|
337
|
+
meta,
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const selectedRows = useMemo(
|
|
341
|
+
() => table.getFilteredSelectedRowModel().rows.map((row) => row.original),
|
|
342
|
+
// `table` is a stable reference, so the selection state is the real input.
|
|
343
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
344
|
+
[rowSelection, data],
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
const hasSelection = selectedRows.length > 0
|
|
348
|
+
|
|
349
|
+
const contextValue = useMemo<DataTableContextValue<TData>>(
|
|
350
|
+
() => ({
|
|
351
|
+
table,
|
|
352
|
+
selectedRows,
|
|
353
|
+
hasSelection,
|
|
354
|
+
globalFilter,
|
|
355
|
+
setGlobalFilter,
|
|
356
|
+
enableFiltering,
|
|
357
|
+
enablePagination,
|
|
358
|
+
enableRowSelection,
|
|
359
|
+
visibleColumnCount: allColumns.length,
|
|
360
|
+
}),
|
|
361
|
+
// `useReactTable` returns the same object every render, so each piece of
|
|
362
|
+
// table state has to be tracked explicitly to re-render the consumers.
|
|
363
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
364
|
+
[
|
|
365
|
+
table,
|
|
366
|
+
selectedRows,
|
|
367
|
+
hasSelection,
|
|
368
|
+
globalFilter,
|
|
369
|
+
setGlobalFilter,
|
|
370
|
+
enableFiltering,
|
|
371
|
+
enablePagination,
|
|
372
|
+
enableRowSelection,
|
|
373
|
+
allColumns.length,
|
|
374
|
+
sorting,
|
|
375
|
+
columnFilters,
|
|
376
|
+
columnVisibility,
|
|
377
|
+
pagination,
|
|
378
|
+
rowSelection,
|
|
379
|
+
],
|
|
380
|
+
)
|
|
46
381
|
|
|
47
382
|
return (
|
|
48
|
-
<
|
|
49
|
-
<table className="
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
383
|
+
<DataTableContext.Provider value={contextValue as DataTableContextValue<unknown>}>
|
|
384
|
+
<div data-slot="data-table" className={cn("space-y-4", className)}>
|
|
385
|
+
{children}
|
|
386
|
+
</div>
|
|
387
|
+
</DataTableContext.Provider>
|
|
388
|
+
)
|
|
389
|
+
}
|
|
390
|
+
DataTable.displayName = "DataTable"
|
|
391
|
+
|
|
392
|
+
/* ----------------------------------------------------------------- toolbar */
|
|
393
|
+
|
|
394
|
+
export const DataTableToolbar = forwardRef<HTMLDivElement, DataTableToolbarProps>(
|
|
395
|
+
({ showSearch, toolbar, bulkActions, className, ...props }, ref) => {
|
|
396
|
+
const { globalFilter, setGlobalFilter, hasSelection, selectedRows, enableFiltering } =
|
|
397
|
+
useDataTable()
|
|
398
|
+
|
|
399
|
+
const showSearchEffective = showSearch ?? enableFiltering
|
|
400
|
+
|
|
401
|
+
const hasContent = showSearchEffective || toolbar != null || (hasSelection && bulkActions != null)
|
|
402
|
+
|
|
403
|
+
if (!hasContent) return null
|
|
404
|
+
|
|
405
|
+
return (
|
|
406
|
+
<div
|
|
407
|
+
ref={ref}
|
|
408
|
+
data-slot="data-table-toolbar"
|
|
409
|
+
className={cn("flex flex-wrap items-center gap-2", className)}
|
|
410
|
+
{...props}
|
|
411
|
+
>
|
|
412
|
+
{showSearchEffective && (
|
|
413
|
+
<div className="relative">
|
|
414
|
+
<Search className="absolute top-2.5 left-2.5 size-4 text-muted-foreground" />
|
|
415
|
+
<Input
|
|
416
|
+
value={globalFilter}
|
|
417
|
+
onChange={(e) => setGlobalFilter(e.target.value)}
|
|
418
|
+
className="max-w-sm pl-8"
|
|
419
|
+
/>
|
|
420
|
+
</div>
|
|
421
|
+
)}
|
|
422
|
+
{toolbar}
|
|
423
|
+
{hasSelection && bulkActions && (
|
|
424
|
+
<div className="ml-auto flex items-center gap-2">{bulkActions(selectedRows)}</div>
|
|
425
|
+
)}
|
|
426
|
+
</div>
|
|
427
|
+
)
|
|
428
|
+
},
|
|
429
|
+
)
|
|
430
|
+
DataTableToolbar.displayName = "DataTableToolbar"
|
|
431
|
+
|
|
432
|
+
/* ----------------------------------------------------------------- content */
|
|
433
|
+
|
|
434
|
+
export const DataTableContent = forwardRef<HTMLDivElement, DataTableContentProps>(
|
|
435
|
+
({ className, ...props }, ref) => {
|
|
436
|
+
const { table, visibleColumnCount } = useDataTable()
|
|
437
|
+
|
|
438
|
+
return (
|
|
439
|
+
<div
|
|
440
|
+
ref={ref}
|
|
441
|
+
data-slot="data-table-content"
|
|
442
|
+
className={cn("overflow-x-auto", className)}
|
|
443
|
+
{...props}
|
|
444
|
+
>
|
|
445
|
+
<Table>
|
|
446
|
+
<TableHeader>
|
|
447
|
+
{table.getHeaderGroups().map((headerGroup) => (
|
|
448
|
+
<TableRow key={headerGroup.id}>
|
|
449
|
+
{headerGroup.headers.map((header) => (
|
|
450
|
+
<TableHead key={header.id}>
|
|
451
|
+
{header.isPlaceholder ? null : header.column.getCanSort() ? (
|
|
452
|
+
<button
|
|
453
|
+
type="button"
|
|
454
|
+
className="inline-flex items-center gap-1 rounded hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none"
|
|
455
|
+
onClick={header.column.getToggleSortingHandler()}
|
|
456
|
+
aria-sort={
|
|
457
|
+
header.column.getIsSorted() === "asc"
|
|
458
|
+
? "ascending"
|
|
459
|
+
: header.column.getIsSorted() === "desc"
|
|
460
|
+
? "descending"
|
|
461
|
+
: "none"
|
|
462
|
+
}
|
|
463
|
+
>
|
|
464
|
+
{flexRender(header.column.columnDef.header, header.getContext())}
|
|
465
|
+
{header.column.getIsSorted() === "asc" ? (
|
|
466
|
+
<ArrowUp className="size-4" />
|
|
467
|
+
) : header.column.getIsSorted() === "desc" ? (
|
|
468
|
+
<ArrowDown className="size-4" />
|
|
469
|
+
) : (
|
|
470
|
+
<ArrowUpDown className="size-4 text-muted-foreground" />
|
|
471
|
+
)}
|
|
472
|
+
</button>
|
|
473
|
+
) : (
|
|
474
|
+
flexRender(header.column.columnDef.header, header.getContext())
|
|
475
|
+
)}
|
|
476
|
+
</TableHead>
|
|
477
|
+
))}
|
|
478
|
+
</TableRow>
|
|
73
479
|
))}
|
|
74
|
-
</
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
480
|
+
</TableHeader>
|
|
481
|
+
|
|
482
|
+
<TableBody>
|
|
483
|
+
{table.getRowModel().rows.length ? (
|
|
484
|
+
table.getRowModel().rows.map((row) => (
|
|
485
|
+
<TableRow key={row.id} data-state={row.getIsSelected() ? "selected" : undefined}>
|
|
486
|
+
{row.getVisibleCells().map((cell) => (
|
|
487
|
+
<TableCell key={cell.id}>
|
|
488
|
+
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
|
489
|
+
</TableCell>
|
|
490
|
+
))}
|
|
491
|
+
</TableRow>
|
|
492
|
+
))
|
|
493
|
+
) : (
|
|
494
|
+
<TableRow>
|
|
495
|
+
<TableCell
|
|
496
|
+
colSpan={visibleColumnCount}
|
|
497
|
+
className="h-24 text-center text-muted-foreground"
|
|
498
|
+
>
|
|
499
|
+
データがありません
|
|
500
|
+
</TableCell>
|
|
501
|
+
</TableRow>
|
|
502
|
+
)}
|
|
503
|
+
</TableBody>
|
|
504
|
+
</Table>
|
|
505
|
+
</div>
|
|
506
|
+
)
|
|
507
|
+
},
|
|
508
|
+
)
|
|
509
|
+
DataTableContent.displayName = "DataTableContent"
|
|
510
|
+
|
|
511
|
+
/* -------------------------------------------------------------- pagination */
|
|
512
|
+
|
|
513
|
+
export const DataTablePagination = forwardRef<HTMLDivElement, DataTablePaginationProps>(
|
|
514
|
+
({ pageSizeOptions = [10, 20, 50], className, ...props }, ref) => {
|
|
515
|
+
const { table } = useDataTable()
|
|
516
|
+
|
|
517
|
+
const totalRows = table.getFilteredRowModel().rows.length
|
|
518
|
+
const currentPageSize = table.getState().pagination.pageSize
|
|
519
|
+
const currentPageIndex = table.getState().pagination.pageIndex
|
|
520
|
+
const rangeStart = currentPageIndex * currentPageSize + 1
|
|
521
|
+
const rangeEnd = Math.min((currentPageIndex + 1) * currentPageSize, totalRows)
|
|
522
|
+
|
|
523
|
+
return (
|
|
524
|
+
<div
|
|
525
|
+
ref={ref}
|
|
526
|
+
data-slot="data-table-pagination"
|
|
527
|
+
className={cn("flex flex-wrap items-center justify-between gap-4", className)}
|
|
528
|
+
{...props}
|
|
529
|
+
>
|
|
530
|
+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
|
531
|
+
<span>
|
|
532
|
+
{totalRows}件中 {rangeStart} - {rangeEnd} を表示
|
|
92
533
|
</span>
|
|
93
|
-
<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
>
|
|
108
|
-
|
|
109
|
-
</button>
|
|
110
|
-
</div>
|
|
534
|
+
<Select
|
|
535
|
+
items={Object.fromEntries(pageSizeOptions.map((size) => [String(size), `${size}件`]))}
|
|
536
|
+
value={String(currentPageSize)}
|
|
537
|
+
onValueChange={(value) => table.setPageSize(Number(value))}
|
|
538
|
+
>
|
|
539
|
+
<SelectTrigger size="sm" className="w-[80px]">
|
|
540
|
+
<SelectValue />
|
|
541
|
+
</SelectTrigger>
|
|
542
|
+
<SelectContent>
|
|
543
|
+
{pageSizeOptions.map((size) => (
|
|
544
|
+
<SelectItem key={size} value={String(size)}>
|
|
545
|
+
{size}件
|
|
546
|
+
</SelectItem>
|
|
547
|
+
))}
|
|
548
|
+
</SelectContent>
|
|
549
|
+
</Select>
|
|
111
550
|
</div>
|
|
112
|
-
|
|
113
|
-
|
|
551
|
+
|
|
552
|
+
<div className="flex items-center gap-1">
|
|
553
|
+
<Button
|
|
554
|
+
variant="outline"
|
|
555
|
+
size="sm"
|
|
556
|
+
onClick={() => table.firstPage()}
|
|
557
|
+
disabled={!table.getCanPreviousPage()}
|
|
558
|
+
>
|
|
559
|
+
最初
|
|
560
|
+
</Button>
|
|
561
|
+
<Button
|
|
562
|
+
variant="outline"
|
|
563
|
+
size="sm"
|
|
564
|
+
onClick={() => table.previousPage()}
|
|
565
|
+
disabled={!table.getCanPreviousPage()}
|
|
566
|
+
>
|
|
567
|
+
前へ
|
|
568
|
+
</Button>
|
|
569
|
+
<Button
|
|
570
|
+
variant="outline"
|
|
571
|
+
size="sm"
|
|
572
|
+
onClick={() => table.nextPage()}
|
|
573
|
+
disabled={!table.getCanNextPage()}
|
|
574
|
+
>
|
|
575
|
+
次へ
|
|
576
|
+
</Button>
|
|
577
|
+
<Button
|
|
578
|
+
variant="outline"
|
|
579
|
+
size="sm"
|
|
580
|
+
onClick={() => table.lastPage()}
|
|
581
|
+
disabled={!table.getCanNextPage()}
|
|
582
|
+
>
|
|
583
|
+
最後
|
|
584
|
+
</Button>
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
)
|
|
588
|
+
},
|
|
589
|
+
)
|
|
590
|
+
DataTablePagination.displayName = "DataTablePagination"
|
|
591
|
+
|
|
592
|
+
/* ------------------------------------------------------- column visibility */
|
|
593
|
+
|
|
594
|
+
export const DataTableColumnVisibility = forwardRef<
|
|
595
|
+
HTMLButtonElement,
|
|
596
|
+
DataTableColumnVisibilityProps
|
|
597
|
+
>(({ columnLabels, className }, ref) => {
|
|
598
|
+
const { table } = useDataTable()
|
|
599
|
+
|
|
600
|
+
const hideableColumns = table.getAllColumns().filter((column) => column.getCanHide())
|
|
601
|
+
|
|
602
|
+
if (hideableColumns.length === 0) return null
|
|
603
|
+
|
|
604
|
+
return (
|
|
605
|
+
<DropdownMenu>
|
|
606
|
+
<DropdownMenuTrigger
|
|
607
|
+
ref={ref}
|
|
608
|
+
render={<Button variant="outline" size="sm" className={cn(className)} />}
|
|
609
|
+
data-slot="data-table-column-visibility"
|
|
610
|
+
>
|
|
611
|
+
<Columns3 className="size-4" />
|
|
612
|
+
</DropdownMenuTrigger>
|
|
613
|
+
<DropdownMenuContent align="end" className="min-w-[10rem]">
|
|
614
|
+
{hideableColumns.map((column) => {
|
|
615
|
+
const label =
|
|
616
|
+
columnLabels?.[column.id] ??
|
|
617
|
+
(typeof column.columnDef.header === "string" ? column.columnDef.header : column.id)
|
|
618
|
+
return (
|
|
619
|
+
<DropdownMenuCheckboxItem
|
|
620
|
+
key={column.id}
|
|
621
|
+
checked={column.getIsVisible()}
|
|
622
|
+
onCheckedChange={(checked) => column.toggleVisibility(checked)}
|
|
623
|
+
>
|
|
624
|
+
{label}
|
|
625
|
+
</DropdownMenuCheckboxItem>
|
|
626
|
+
)
|
|
627
|
+
})}
|
|
628
|
+
</DropdownMenuContent>
|
|
629
|
+
</DropdownMenu>
|
|
630
|
+
)
|
|
631
|
+
})
|
|
632
|
+
DataTableColumnVisibility.displayName = "DataTableColumnVisibility"
|
|
633
|
+
|
|
634
|
+
/* ------------------------------------------------------------------ preset */
|
|
635
|
+
|
|
636
|
+
/** The usual arrangement — toolbar, body and pagination — in one component. */
|
|
637
|
+
export function DataTablePreset<TData, TValue>({
|
|
638
|
+
columns,
|
|
639
|
+
data,
|
|
640
|
+
enableSorting,
|
|
641
|
+
sorting,
|
|
642
|
+
onSortingChange,
|
|
643
|
+
enableFiltering,
|
|
644
|
+
columnFilters,
|
|
645
|
+
onColumnFiltersChange,
|
|
646
|
+
globalFilter,
|
|
647
|
+
onGlobalFilterChange,
|
|
648
|
+
enablePagination,
|
|
649
|
+
pagination,
|
|
650
|
+
onPaginationChange,
|
|
651
|
+
pageCount,
|
|
652
|
+
enableRowSelection,
|
|
653
|
+
rowSelection,
|
|
654
|
+
onRowSelectionChange,
|
|
655
|
+
enableColumnVisibility = false,
|
|
656
|
+
columnVisibility,
|
|
657
|
+
onColumnVisibilityChange,
|
|
658
|
+
columnLabels,
|
|
659
|
+
toolbar,
|
|
660
|
+
bulkActions,
|
|
661
|
+
meta,
|
|
662
|
+
className,
|
|
663
|
+
}: DataTablePresetProps<TData, TValue>) {
|
|
664
|
+
const toolbarSlot =
|
|
665
|
+
toolbar || enableColumnVisibility ? (
|
|
666
|
+
<>
|
|
667
|
+
{toolbar}
|
|
668
|
+
{enableColumnVisibility && <DataTableColumnVisibility columnLabels={columnLabels} />}
|
|
669
|
+
</>
|
|
670
|
+
) : undefined
|
|
671
|
+
|
|
672
|
+
return (
|
|
673
|
+
<DataTable<TData, TValue>
|
|
674
|
+
columns={columns}
|
|
675
|
+
data={data}
|
|
676
|
+
enableSorting={enableSorting}
|
|
677
|
+
sorting={sorting}
|
|
678
|
+
onSortingChange={onSortingChange}
|
|
679
|
+
enableFiltering={enableFiltering}
|
|
680
|
+
columnFilters={columnFilters}
|
|
681
|
+
onColumnFiltersChange={onColumnFiltersChange}
|
|
682
|
+
globalFilter={globalFilter}
|
|
683
|
+
onGlobalFilterChange={onGlobalFilterChange}
|
|
684
|
+
enablePagination={enablePagination}
|
|
685
|
+
pagination={pagination}
|
|
686
|
+
onPaginationChange={onPaginationChange}
|
|
687
|
+
pageCount={pageCount}
|
|
688
|
+
enableRowSelection={enableRowSelection}
|
|
689
|
+
rowSelection={rowSelection}
|
|
690
|
+
onRowSelectionChange={onRowSelectionChange}
|
|
691
|
+
columnVisibility={columnVisibility}
|
|
692
|
+
onColumnVisibilityChange={onColumnVisibilityChange}
|
|
693
|
+
meta={meta}
|
|
694
|
+
className={className}
|
|
695
|
+
>
|
|
696
|
+
<DataTableToolbar
|
|
697
|
+
showSearch={enableFiltering}
|
|
698
|
+
toolbar={toolbarSlot}
|
|
699
|
+
bulkActions={bulkActions as ((selectedRows: unknown[]) => ReactNode) | undefined}
|
|
700
|
+
/>
|
|
701
|
+
<DataTableContent />
|
|
702
|
+
{enablePagination && <DataTablePagination />}
|
|
703
|
+
</DataTable>
|
|
114
704
|
)
|
|
115
705
|
}
|
|
706
|
+
DataTablePreset.displayName = "DataTablePreset"
|
|
707
|
+
|
|
708
|
+
export type {
|
|
709
|
+
ColumnDef,
|
|
710
|
+
ColumnFiltersState,
|
|
711
|
+
PaginationState,
|
|
712
|
+
RowSelectionState,
|
|
713
|
+
SortingState,
|
|
714
|
+
VisibilityState,
|
|
715
|
+
} from "@tanstack/react-table"
|