@kreativa/ui 0.1.1 → 0.1.2

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/dist/index.d.mts CHANGED
@@ -142,16 +142,18 @@ declare function FormButtonGroup({ cancelText, submitText, loadingText, isLoadin
142
142
  interface TableColumn<T> {
143
143
  /** Unique key for the column */
144
144
  key: string;
145
- /** Column header text */
146
- header: string;
145
+ /** Column header text or React element */
146
+ header: ReactNode;
147
147
  /** Function to render cell content */
148
148
  render: (item: T) => ReactNode;
149
149
  /** Whether this column is sortable */
150
150
  sortable?: boolean;
151
151
  /** Custom sort function */
152
152
  sortFn?: (a: T, b: T) => number;
153
- /** Column width (e.g., '200px', '20%') */
153
+ /** Column width (e.g., '200px', '20%') - used as initial/min width */
154
154
  width?: string;
155
+ /** Minimum column width when resizing */
156
+ minWidth?: number;
155
157
  /** Text alignment */
156
158
  align?: 'left' | 'center' | 'right';
157
159
  }
@@ -170,18 +172,30 @@ interface TableProps<T> {
170
172
  emptyMessage?: string;
171
173
  /** Additional class names for the table container */
172
174
  className?: string;
175
+ /** Enable manual column resizing (default: false) */
176
+ resizable?: boolean;
177
+ /** Use fixed table layout for consistent column widths */
178
+ fixedLayout?: boolean;
173
179
  }
174
180
  /**
175
- * Table component for displaying tabular data with sorting support.
181
+ * Table component for displaying tabular data with sorting and optional resizing.
182
+ *
183
+ * Features:
184
+ * - Responsive by default - shrinks with container
185
+ * - Optional column resizing with drag handles
186
+ * - Sortable columns
187
+ * - Loading and empty states
176
188
  */
177
- declare function Table<T>({ data, columns, getRowKey, onRowClick, loading, emptyMessage, className, }: TableProps<T>): react_jsx_runtime.JSX.Element;
189
+ declare function Table<T>({ data, columns, getRowKey, onRowClick, loading, emptyMessage, className, resizable, fixedLayout, }: TableProps<T>): react_jsx_runtime.JSX.Element;
178
190
 
179
191
  type FilterType = 'text' | 'select' | 'multiselect' | 'boolean' | 'date' | 'none';
180
192
  interface FilterOption {
181
193
  value: string;
182
194
  label: string;
183
195
  }
184
- interface DataTableColumn<T> extends Omit<TableColumn<T>, 'sortable'> {
196
+ interface DataTableColumn<T> extends Omit<TableColumn<T>, 'sortable' | 'header'> {
197
+ /** Column header text (must be string for filter placeholder) */
198
+ header: string;
185
199
  /** Whether this column is sortable (default: true) */
186
200
  sortable?: boolean;
187
201
  /** Filter type for this column (default: 'text') */
package/dist/index.d.ts CHANGED
@@ -142,16 +142,18 @@ declare function FormButtonGroup({ cancelText, submitText, loadingText, isLoadin
142
142
  interface TableColumn<T> {
143
143
  /** Unique key for the column */
144
144
  key: string;
145
- /** Column header text */
146
- header: string;
145
+ /** Column header text or React element */
146
+ header: ReactNode;
147
147
  /** Function to render cell content */
148
148
  render: (item: T) => ReactNode;
149
149
  /** Whether this column is sortable */
150
150
  sortable?: boolean;
151
151
  /** Custom sort function */
152
152
  sortFn?: (a: T, b: T) => number;
153
- /** Column width (e.g., '200px', '20%') */
153
+ /** Column width (e.g., '200px', '20%') - used as initial/min width */
154
154
  width?: string;
155
+ /** Minimum column width when resizing */
156
+ minWidth?: number;
155
157
  /** Text alignment */
156
158
  align?: 'left' | 'center' | 'right';
157
159
  }
@@ -170,18 +172,30 @@ interface TableProps<T> {
170
172
  emptyMessage?: string;
171
173
  /** Additional class names for the table container */
172
174
  className?: string;
175
+ /** Enable manual column resizing (default: false) */
176
+ resizable?: boolean;
177
+ /** Use fixed table layout for consistent column widths */
178
+ fixedLayout?: boolean;
173
179
  }
174
180
  /**
175
- * Table component for displaying tabular data with sorting support.
181
+ * Table component for displaying tabular data with sorting and optional resizing.
182
+ *
183
+ * Features:
184
+ * - Responsive by default - shrinks with container
185
+ * - Optional column resizing with drag handles
186
+ * - Sortable columns
187
+ * - Loading and empty states
176
188
  */
177
- declare function Table<T>({ data, columns, getRowKey, onRowClick, loading, emptyMessage, className, }: TableProps<T>): react_jsx_runtime.JSX.Element;
189
+ declare function Table<T>({ data, columns, getRowKey, onRowClick, loading, emptyMessage, className, resizable, fixedLayout, }: TableProps<T>): react_jsx_runtime.JSX.Element;
178
190
 
179
191
  type FilterType = 'text' | 'select' | 'multiselect' | 'boolean' | 'date' | 'none';
180
192
  interface FilterOption {
181
193
  value: string;
182
194
  label: string;
183
195
  }
184
- interface DataTableColumn<T> extends Omit<TableColumn<T>, 'sortable'> {
196
+ interface DataTableColumn<T> extends Omit<TableColumn<T>, 'sortable' | 'header'> {
197
+ /** Column header text (must be string for filter placeholder) */
198
+ header: string;
185
199
  /** Whether this column is sortable (default: true) */
186
200
  sortable?: boolean;
187
201
  /** Filter type for this column (default: 'text') */
package/dist/index.js CHANGED
@@ -340,10 +340,15 @@ function Table({
340
340
  onRowClick,
341
341
  loading = false,
342
342
  emptyMessage = "No data available",
343
- className = ""
343
+ className = "",
344
+ resizable = false,
345
+ fixedLayout = false
344
346
  }) {
345
347
  const [sortKey, setSortKey] = (0, import_react5.useState)(null);
346
348
  const [sortDirection, setSortDirection] = (0, import_react5.useState)(null);
349
+ const [columnWidths, setColumnWidths] = (0, import_react5.useState)({});
350
+ const tableRef = (0, import_react5.useRef)(null);
351
+ const resizingRef = (0, import_react5.useRef)(null);
347
352
  const handleHeaderClick = (column) => {
348
353
  if (!column.sortable) return;
349
354
  if (sortKey === column.key) {
@@ -372,63 +377,133 @@ function Table({
372
377
  });
373
378
  return sortDirection === "desc" ? sorted.reverse() : sorted;
374
379
  }, [data, columns, sortKey, sortDirection]);
380
+ const handleResizeStart = (0, import_react5.useCallback)(
381
+ (e, columnKey) => {
382
+ e.preventDefault();
383
+ e.stopPropagation();
384
+ const headerCell = e.currentTarget.parentElement;
385
+ if (!headerCell) return;
386
+ const startWidth = headerCell.getBoundingClientRect().width;
387
+ resizingRef.current = {
388
+ columnKey,
389
+ startX: e.clientX,
390
+ startWidth
391
+ };
392
+ document.addEventListener("mousemove", handleResizeMove);
393
+ document.addEventListener("mouseup", handleResizeEnd);
394
+ document.body.style.cursor = "col-resize";
395
+ document.body.style.userSelect = "none";
396
+ },
397
+ []
398
+ );
399
+ const handleResizeMove = (0, import_react5.useCallback)((e) => {
400
+ if (!resizingRef.current) return;
401
+ const { columnKey, startX, startWidth } = resizingRef.current;
402
+ const column = columns.find((c) => c.key === columnKey);
403
+ const minWidth = column?.minWidth ?? 50;
404
+ const newWidth = Math.max(minWidth, startWidth + (e.clientX - startX));
405
+ setColumnWidths((prev) => ({
406
+ ...prev,
407
+ [columnKey]: newWidth
408
+ }));
409
+ }, [columns]);
410
+ const handleResizeEnd = (0, import_react5.useCallback)(() => {
411
+ resizingRef.current = null;
412
+ document.removeEventListener("mousemove", handleResizeMove);
413
+ document.removeEventListener("mouseup", handleResizeEnd);
414
+ document.body.style.cursor = "";
415
+ document.body.style.userSelect = "";
416
+ }, [handleResizeMove]);
375
417
  const getSortIcon = (column) => {
376
418
  if (!column.sortable) return null;
377
419
  if (sortKey !== column.key) {
378
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-gray-400", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) });
420
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-gray-400 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4" }) });
379
421
  }
380
422
  if (sortDirection === "asc") {
381
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-primary-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 15l7-7 7 7" }) });
423
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-primary-600 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 15l7-7 7 7" }) });
382
424
  }
383
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-primary-600", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) });
425
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("svg", { className: "w-4 h-4 text-primary-600 shrink-0", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" }) });
384
426
  };
385
427
  const alignmentClasses = {
386
428
  left: "text-left",
387
429
  center: "text-center",
388
430
  right: "text-right"
389
431
  };
390
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: `overflow-x-auto ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { className: "w-full border-collapse", children: [
391
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { className: "bg-gray-50 border-b border-gray-200", children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
392
- "th",
393
- {
394
- className: `
395
- px-4 py-3 text-sm font-semibold text-gray-700
432
+ const getColumnStyle = (column) => {
433
+ if (columnWidths[column.key]) {
434
+ return { width: columnWidths[column.key], minWidth: columnWidths[column.key] };
435
+ }
436
+ if (column.width) {
437
+ return { width: column.width, minWidth: column.minWidth ?? 50 };
438
+ }
439
+ return { minWidth: column.minWidth ?? 50 };
440
+ };
441
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: `overflow-x-auto ${className}`, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
442
+ "table",
443
+ {
444
+ ref: tableRef,
445
+ className: "w-full border-collapse",
446
+ style: { tableLayout: fixedLayout || resizable ? "fixed" : "auto" },
447
+ children: [
448
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { className: "bg-gray-50 border-b border-gray-200", children: columns.map((column, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
449
+ "th",
450
+ {
451
+ className: `
452
+ px-4 py-3 text-sm font-semibold text-gray-700 relative
396
453
  ${alignmentClasses[column.align ?? "left"]}
397
454
  ${column.sortable ? "cursor-pointer select-none hover:bg-gray-100" : ""}
398
455
  `,
399
- style: { width: column.width },
400
- onClick: () => handleHeaderClick(column),
401
- children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: `flex items-center gap-1 ${column.align === "right" ? "justify-end" : column.align === "center" ? "justify-center" : ""}`, children: [
402
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: column.header }),
403
- getSortIcon(column)
404
- ] })
405
- },
406
- column.key
407
- )) }) }),
408
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [
409
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-5 h-5 border-2 border-primary-200 border-t-primary-600 rounded-full animate-spin" }),
410
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Loading..." })
411
- ] }) }) }) : sortedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: emptyMessage }) }) : sortedData.map((item) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
412
- "tr",
413
- {
414
- className: `
456
+ style: getColumnStyle(column),
457
+ onClick: () => handleHeaderClick(column),
458
+ children: [
459
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
460
+ "div",
461
+ {
462
+ className: `flex items-center gap-1 overflow-hidden ${column.align === "right" ? "justify-end" : column.align === "center" ? "justify-center" : ""}`,
463
+ children: [
464
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { className: "truncate", children: column.header }),
465
+ getSortIcon(column)
466
+ ]
467
+ }
468
+ ),
469
+ resizable && index < columns.length - 1 && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
470
+ "div",
471
+ {
472
+ className: "absolute top-0 right-0 w-1 h-full cursor-col-resize bg-transparent hover:bg-primary-300 transition-colors",
473
+ onMouseDown: (e) => handleResizeStart(e, column.key),
474
+ onClick: (e) => e.stopPropagation()
475
+ }
476
+ )
477
+ ]
478
+ },
479
+ column.key
480
+ )) }) }),
481
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: loading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "flex items-center justify-center gap-2", children: [
482
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "w-5 h-5 border-2 border-primary-200 border-t-primary-600 rounded-full animate-spin" }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { children: "Loading..." })
484
+ ] }) }) }) : sortedData.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tr", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { colSpan: columns.length, className: "px-4 py-8 text-center text-gray-500", children: emptyMessage }) }) : sortedData.map((item) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
485
+ "tr",
486
+ {
487
+ className: `
415
488
  border-b border-gray-100 hover:bg-gray-50 transition-colors
416
489
  ${onRowClick ? "cursor-pointer" : ""}
417
490
  `,
418
- onClick: () => onRowClick?.(item),
419
- children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
420
- "td",
421
- {
422
- className: `px-4 py-3 text-sm text-gray-800 ${alignmentClasses[column.align ?? "left"]}`,
423
- style: { width: column.width },
424
- children: column.render(item)
491
+ onClick: () => onRowClick?.(item),
492
+ children: columns.map((column) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
493
+ "td",
494
+ {
495
+ className: `px-4 py-3 text-sm text-gray-800 overflow-hidden ${alignmentClasses[column.align ?? "left"]}`,
496
+ style: getColumnStyle(column),
497
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "truncate", children: column.render(item) })
498
+ },
499
+ column.key
500
+ ))
425
501
  },
426
- column.key
427
- ))
428
- },
429
- getRowKey(item)
430
- )) })
431
- ] }) });
502
+ getRowKey(item)
503
+ )) })
504
+ ]
505
+ }
506
+ ) });
432
507
  }
433
508
 
434
509
  // src/components/DataTable.tsx