Files
calendar/apps/web/modules/data-table/hooks/useColumnResizing.ts
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
ab21c7f805 refactor: Cal.diy (#28903)
* feat: Cal.diy — community-driven MIT-licensed fork of Cal.com

This squashed commit contains all Cal.diy changes applied on top of calcom/cal.com main:

- Rebrand Cal.com to Cal.diy across the entire codebase
- Remove Enterprise Edition (EE) features, license checks, and AGPL restrictions
- Switch license from AGPL-3.0 to MIT
- Remove docs/ directory (migrated to Nextra at cal.diy)
- Remove dead code: org tests, EE tips, platform nav, premium username, SAML/SSO, etc.
- Clean up .env.example for self-hosted Cal.diy
- Update Docker image references to calcom/cal.diy
- Update README, CONTRIBUTING.md, and issue templates for Cal.diy community fork
- Add PR welcome bot for Cal.diy contributors
- Fix API v2 breaking changes oasdiff ignore entries
- Replace Blacksmith CI runners with default GitHub Actions

3893 files changed, 20789 insertions(+), 411020 deletions(-)

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* refactor: remove org-specific /organizations/:orgId endpoints from API v2 atoms controllers (#1701)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix: revert Cal.diy Inc to Cal.com, Inc. in license files, copyright notices, and package metadata (#1702)

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* rip out org related comments in api v2

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-04-15 09:52:36 -03:00

178 lines
6.0 KiB
TypeScript

"use client";
import type { ColumnSizingState, Header, Table } from "@tanstack/react-table";
// eslint-disable-next-line no-restricted-imports
import debounce from "lodash/debounce";
import { useCallback, useEffect, useMemo, useRef } from "react";
import { useDataTable, useDebouncedWidth } from ".";
type UseColumnResizingProps<TData> = {
enabled: boolean;
table: Table<TData>;
tableContainerRef: React.RefObject<HTMLDivElement>;
};
function getAdjustedColumnSizing<TData>({
headers,
containerWidth,
initialColumnSizing,
currentColumnSizing,
resizedColumns,
}: {
headers: Header<TData, unknown>[];
containerWidth: number;
initialColumnSizing: ColumnSizingState;
currentColumnSizing: ColumnSizingState;
resizedColumns: Set<string>;
}) {
// Return early if any column has auto width
const hasAutoWidthColumn = headers.some((header) => header.column.columnDef.meta?.autoWidth);
if (hasAutoWidthColumn) {
return currentColumnSizing;
}
const getColumnSize = (header: Header<TData, unknown>) => {
const id = header.id;
if (!initialColumnSizing[id]) {
initialColumnSizing[id] = header.getSize();
}
if (resizedColumns.has(id)) {
return currentColumnSizing[id] ?? initialColumnSizing[id];
}
return initialColumnSizing[id];
};
const isAdjustable = (header: Header<TData, unknown>) =>
header.column.columnDef.enableResizing !== false && !resizedColumns.has(header.id);
// Calculate how many columns can be adjusted
const numberOfAdjustableColumns = headers.filter(isAdjustable).length;
// Calculate widths and required adjustments
const totalColumnsWidth = headers.reduce((total, header) => total + getColumnSize(header), 0);
const widthToFill = Math.max(0, containerWidth - totalColumnsWidth);
const widthPerAdjustableColumn =
numberOfAdjustableColumns === 0 ? 0 : Math.floor(widthToFill / numberOfAdjustableColumns);
// Handle any leftover width (due to rounding)
const leftoverWidth = widthToFill - widthPerAdjustableColumn * numberOfAdjustableColumns;
// Build the new column sizing object
const newColumnSizing = headers.reduce((acc, header, index) => {
const baseWidth = getColumnSize(header);
const adjustmentWidth = isAdjustable(header) ? widthPerAdjustableColumn : 0;
const isLastColumn = index === headers.length - 1;
const extraWidth = isLastColumn ? leftoverWidth : 0;
acc[header.id] = baseWidth + adjustmentWidth + extraWidth;
return acc;
}, {} as ColumnSizingState);
return newColumnSizing;
}
function getPartialColumnSizing(columnSizing: ColumnSizingState, columnsToExtract: Set<string>) {
return Object.keys(columnSizing).reduce((acc, key) => {
if (columnsToExtract.has(key)) {
acc[key] = columnSizing[key];
}
return acc;
}, {} as ColumnSizingState);
}
export function useColumnResizing<TData>({
enabled,
table,
tableContainerRef,
}: UseColumnResizingProps<TData>) {
const initialized = useRef(false);
const columnSizing = useRef<ColumnSizingState>({});
const initialColumnSizing = useRef<ColumnSizingState>({});
const resizedColumns = useRef<Set<string>>(new Set());
const { columnSizing: loadedColumnSizing, setColumnSizing: setColumnSizingToContext } = useDataTable();
const debouncedSaveColumnSizing = useMemo(
() => debounce(setColumnSizingToContext, 1000),
[setColumnSizingToContext]
);
const adjustColumnSizing = useCallback(
(updater: ColumnSizingState | ((old: ColumnSizingState) => ColumnSizingState)) => {
table.setState((oldTableState) => {
let newColumnSizing = typeof updater === "function" ? updater(oldTableState.columnSizing) : updater;
newColumnSizing = getAdjustedColumnSizing({
headers: table.getFlatHeaders(),
containerWidth: tableContainerRef.current?.clientWidth || 0,
initialColumnSizing: initialColumnSizing.current,
currentColumnSizing: newColumnSizing,
resizedColumns: resizedColumns.current,
});
debouncedSaveColumnSizing(getPartialColumnSizing(newColumnSizing, resizedColumns.current));
columnSizing.current = newColumnSizing;
return {
...oldTableState,
columnSizing: newColumnSizing,
};
});
},
[table, tableContainerRef, debouncedSaveColumnSizing]
);
const onColumnSizingChange = useCallback(
(updater: ColumnSizingState | ((old: ColumnSizingState) => ColumnSizingState)) => {
const isResizingColumn = table.getState().columnSizingInfo.isResizingColumn;
if (isResizingColumn) {
resizedColumns.current.add(isResizingColumn);
}
adjustColumnSizing(updater);
},
[table, adjustColumnSizing]
);
const debouncedContainerWidth = useDebouncedWidth(tableContainerRef);
useEffect(() => {
if (!enabled || !initialized.current) return;
const newColumnSizing = getAdjustedColumnSizing({
headers: table.getFlatHeaders(),
containerWidth: debouncedContainerWidth,
initialColumnSizing: initialColumnSizing.current,
currentColumnSizing: columnSizing.current,
resizedColumns: resizedColumns.current,
});
columnSizing.current = newColumnSizing;
table.setState((old) => ({
...old,
columnSizing: newColumnSizing,
}));
}, [debouncedContainerWidth, table.getFlatHeaders().length]);
useEffect(() => {
if (!enabled) return;
// loadedColumnSizing is a partial object of explicitly resized columns.
// combine loaded sizes and the default sizes from TanStack Table
initialColumnSizing.current = table.getFlatHeaders().reduce((acc, header) => {
acc[header.id] = loadedColumnSizing[header.id] || header.getSize();
return acc;
}, {} as ColumnSizingState);
columnSizing.current = loadedColumnSizing;
resizedColumns.current = new Set(Object.keys(loadedColumnSizing));
table.setState((old) => ({
...old,
columnSizing: loadedColumnSizing,
}));
table.setOptions((prev) => ({
...prev,
columnResizeMode: "onChange",
onColumnSizingChange,
}));
initialized.current = true;
}, [enabled, table, onColumnSizingChange]);
}