Update .env.example and README.md to clarify JWT_SECRET requirements; enhance telemt API route handling to preserve query parameters; adjust TypeScript configuration to exclude specific directories; improve data grid components with new features and optimizations.
This commit is contained in:
1 parent
9bebc9e92d
commit
d35fa5a285
65 files changed
+6735
-1027
No files matched your search
@@ -1,12 +1,13 @@
|
||||
"use client"
|
||||
"use no memo"
|
||||
|
||||
import { createContext, type ReactNode, useContext, useMemo } from "react"
|
||||
import {
|
||||
type Column,
|
||||
type ColumnFiltersState,
|
||||
type RowData,
|
||||
type SortingState,
|
||||
type Table,
|
||||
import { createContext, useContext, useMemo, useRef } from "react"
|
||||
import type { ReactNode } from "react"
|
||||
import type {
|
||||
Column,
|
||||
ColumnFiltersState,
|
||||
RowData,
|
||||
SortingState,
|
||||
Table,
|
||||
} from "@tanstack/react-table"
|
||||
|
||||
import { cn } from "@telemt/ui/lib/utils"
|
||||
@@ -19,6 +20,7 @@ declare module "@tanstack/react-table" {
|
||||
cellClassName?: string
|
||||
skeleton?: ReactNode
|
||||
expandedContent?: (row: TData) => ReactNode
|
||||
autoSize?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +57,73 @@ export interface DataGridContextProps<TData extends object> {
|
||||
table: Table<TData>
|
||||
recordCount: number
|
||||
isLoading: boolean
|
||||
/**
|
||||
* Internal coordinator for `meta.autoSize` columns. Lives at the core level
|
||||
* so every table variant and viewport instance shares one application state.
|
||||
*/
|
||||
autoSize?: DataGridAutoSizeController
|
||||
}
|
||||
|
||||
export type DataGridAutoSizeController = {
|
||||
/**
|
||||
* Grows the first visible `meta.autoSize` column by the given free space.
|
||||
* Applies at most once per column id; safe to call from every viewport
|
||||
* measurement. Returns true when a sizing update was dispatched.
|
||||
*/
|
||||
apply: (fillWidth: number) => boolean
|
||||
}
|
||||
|
||||
function createDataGridAutoSizeController<TData extends object>(
|
||||
table: Table<TData>
|
||||
): DataGridAutoSizeController {
|
||||
let applied: { columnId: string; base: number; grown: number } | null = null
|
||||
|
||||
return {
|
||||
apply(fillWidth: number) {
|
||||
const columnSizing = table.getState().columnSizing
|
||||
|
||||
// Re-arm after reset flows (double-click resetSize, resetColumnSizing,
|
||||
// controlled state replacement) so the column re-fills instead of
|
||||
// leaving a dead blank strip.
|
||||
if (applied && columnSizing[applied.columnId] === undefined) {
|
||||
applied = null
|
||||
}
|
||||
|
||||
if (fillWidth <= 0) return false
|
||||
|
||||
const autoSizeColumn = table
|
||||
.getVisibleLeafColumns()
|
||||
.find(
|
||||
(column) => column.columnDef.meta?.autoSize && column.getCanResize()
|
||||
)
|
||||
|
||||
if (!autoSizeColumn || applied?.columnId === autoSizeColumn.id) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Candidate switched (e.g. the grown column was hidden and another
|
||||
// meta.autoSize column took over): revert the previous growth if the
|
||||
// user hasn't manually resized that column since, so visibility
|
||||
// toggles cannot ratchet the table wider than its container forever.
|
||||
const revert =
|
||||
applied && columnSizing[applied.columnId] === applied.grown
|
||||
? applied
|
||||
: null
|
||||
const base = columnSizing[autoSizeColumn.id] ?? autoSizeColumn.getSize()
|
||||
const grown = base + fillWidth
|
||||
|
||||
applied = { columnId: autoSizeColumn.id, base, grown }
|
||||
table.setColumnSizing((old) => {
|
||||
const next = { ...old, [autoSizeColumn.id]: grown }
|
||||
if (revert && next[revert.columnId] === revert.grown) {
|
||||
next[revert.columnId] = revert.base
|
||||
}
|
||||
return next
|
||||
})
|
||||
|
||||
return true
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export type DataGridRequestParams = {
|
||||
@@ -83,6 +152,7 @@ export interface DataGridProps<TData extends object> {
|
||||
rowRounded?: boolean
|
||||
stripped?: boolean
|
||||
headerBackground?: boolean
|
||||
footerBackground?: boolean
|
||||
headerBorder?: boolean
|
||||
headerSticky?: boolean
|
||||
width?: "auto" | "fixed"
|
||||
@@ -126,36 +196,56 @@ function DataGridProvider<TData extends object>({
|
||||
...props
|
||||
}: DataGridProps<TData> & { table: Table<TData> }) {
|
||||
const tableState = table.getState()
|
||||
const resolvedColumnsResizeMode =
|
||||
props.tableLayout?.columnsResizeMode ?? "onEnd"
|
||||
|
||||
// Keep resize mode aligned with the DataGrid contract every render so
|
||||
// Latest-props ref: context reads always resolve fresh props through the
|
||||
// getter below without the memoized context value depending on unstable
|
||||
// ReactNode/function prop identities (inline emptyMessage/onRowClick would
|
||||
// otherwise publish a new context value on every consumer render - at
|
||||
// mousemove rate during a resize drag, piercing the body-rows memo).
|
||||
const propsRef = useRef(props)
|
||||
propsRef.current = props
|
||||
|
||||
// Re-assert an explicit tableLayout resize mode every render so
|
||||
// consumer-level useReactTable options cannot flip it back between drags.
|
||||
if (props.tableLayout?.columnsResizable) {
|
||||
table.options.columnResizeMode = resolvedColumnsResizeMode
|
||||
// Without one, the consumer's own tanstack columnResizeMode (default
|
||||
// "onEnd") is honored.
|
||||
if (
|
||||
props.tableLayout?.columnsResizable &&
|
||||
props.tableLayout.columnsResizeMode
|
||||
) {
|
||||
table.options.columnResizeMode = props.tableLayout.columnsResizeMode
|
||||
}
|
||||
|
||||
// One autoSize coordinator per table instance so split header/body viewports
|
||||
// cannot apply the growth twice.
|
||||
const autoSize = useMemo(
|
||||
() => createDataGridAutoSizeController(table),
|
||||
[table]
|
||||
)
|
||||
|
||||
// Memoize context value so consumers don't re-render during column resize.
|
||||
// Column sizing state is intentionally excluded from deps -- CSS variables
|
||||
// on the <table> element handle width updates without React re-renders.
|
||||
// ReactNode/function props (messages, onRowClick) are also excluded: they
|
||||
// are served fresh through the props getter, so unstable inline identities
|
||||
// cannot invalidate the context value.
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
props,
|
||||
get props() {
|
||||
return propsRef.current
|
||||
},
|
||||
table,
|
||||
recordCount: props.recordCount,
|
||||
isLoading: props.isLoading || false,
|
||||
autoSize,
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[
|
||||
table,
|
||||
autoSize,
|
||||
props.recordCount,
|
||||
props.isLoading,
|
||||
props.loadingMode,
|
||||
props.loadingMessage,
|
||||
props.fetchingMoreMessage,
|
||||
props.allRowsLoadedMessage,
|
||||
props.emptyMessage,
|
||||
props.onRowClick,
|
||||
props.className,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
JSON.stringify(props.tableLayout),
|
||||
@@ -165,6 +255,7 @@ function DataGridProvider<TData extends object>({
|
||||
tableState.pagination,
|
||||
tableState.columnFilters,
|
||||
tableState.rowSelection,
|
||||
tableState.rowPinning,
|
||||
tableState.expanded,
|
||||
tableState.columnVisibility,
|
||||
tableState.columnOrder,
|
||||
@@ -194,12 +285,14 @@ function DataGrid<TData extends object>({
|
||||
rowRounded: false,
|
||||
stripped: false,
|
||||
headerSticky: false,
|
||||
headerBackground: true,
|
||||
headerBackground: false,
|
||||
footerBackground: false,
|
||||
headerBorder: true,
|
||||
width: "fixed",
|
||||
columnsVisibility: false,
|
||||
columnsResizable: false,
|
||||
columnsResizeMode: "onEnd",
|
||||
// columnsResizeMode has no default on purpose: when unset, the
|
||||
// consumer's tanstack columnResizeMode (default "onEnd") is honored.
|
||||
columnsPinnable: false,
|
||||
columnsMovable: false,
|
||||
columnsDraggable: false,
|
||||
@@ -210,7 +303,10 @@ function DataGrid<TData extends object>({
|
||||
base: "",
|
||||
header: "",
|
||||
headerRow: "",
|
||||
headerSticky: "sticky top-0 z-15 bg-background/90 backdrop-blur-xs",
|
||||
// z-40 keeps the sticky header above pinned body cells (zIndex 30 in
|
||||
// getPinningStyles), which would otherwise paint over it while
|
||||
// scrolling vertically with columnsPinnable enabled.
|
||||
headerSticky: "sticky top-0 z-40 bg-background/90 backdrop-blur-xs",
|
||||
body: "",
|
||||
bodyRow: "",
|
||||
footer: "",
|
||||
@@ -246,21 +342,16 @@ function DataGrid<TData extends object>({
|
||||
function DataGridContainer({
|
||||
children,
|
||||
className,
|
||||
border = true,
|
||||
}: {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
/** Accepted for backwards compatibility; currently has no effect. */
|
||||
border?: boolean
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
data-slot="data-grid"
|
||||
className={cn(
|
||||
"w-full overflow-hidden",
|
||||
border &&
|
||||
"border-border rounded-lg border",
|
||||
className
|
||||
)}
|
||||
className={cn("w-full overflow-hidden", className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user